[
  {
    "path": ".gitattributes",
    "content": "# Disable autocrlf on generated files, they always generate with LF\n# Add any extra files or paths here to make git stop saying they\n# are changed when only line endings change.\nsrc/generated/**/.cache/cache text eol=lf\nsrc/generated/**/*.json text eol=lf\n"
  },
  {
    "path": ".github/workflows/gradle.yml",
    "content": "name: Java CI with Gradle\n\non: [push]\njobs:\n\n  jdk17:\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Checkout repository\n      uses: actions/checkout@v3\n\n    - name: Setup JDK 17\n      uses: actions/setup-java@v3\n      with:\n        java-version: '17'\n        distribution: 'temurin'\n\n    - name: Build with Gradle\n      uses: gradle/gradle-build-action@v2\n      with:\n        arguments: build\n\n    - name: Locate built JARfile\n      id: jar\n      run: jarfile=$(find build/libs/ -name \"*-all.jar\" -not -name \"*slim*\" -not -name \"*source*\" | tr '\\n' ' '); echo \"jarfile=$jarfile\" >> $GITHUB_OUTPUT\n\n    - name: Set Artifact name\n      id: jarname\n      run: jarname=$(find build/libs/ -name \"*-all.jar\" -not -name \"*slim*\" -not -name \"*source*\" | sed 's:.*/::' | tr '\\n' ' '); echo \"jarname=$jarname\" >> $GITHUB_OUTPUT\n\n    - name: Upload artifact\n      uses: actions/upload-artifact@v2.2.3\n      with:\n          name: ${{ steps.jarname.outputs.jarname }}\n          path: ${{ steps.jar.outputs.jarfile }}\n\n"
  },
  {
    "path": ".gitignore",
    "content": "# eclipse\nbin\n*.launch\n.settings\n.metadata\n.classpath\n.project\n\n# idea\nout\n*.ipr\n*.iws\n*.iml\n.idea\n\n# gradle\nbuild\n.gradle\n\n# other\neclipse\nrun\nrun-data\n\n# Files from Forge MDK\nforge*changelog.txt\n\nlibs_off"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "## Pull Requests\n\n#### New Pull Requests are not accepted right now, and will be closed.\n"
  },
  {
    "path": "CREDITS.txt",
    "content": "Minecraft Forge: Credits/Thank You\n\nForge is a set of tools and modifications to the Minecraft base game code to assist \nmod developers in creating new and exciting content. It has been in development for \nseveral years now, but I would like to take this time thank a few people who have \nhelped it along it's way.\n\nFirst, the people who originally created the Forge projects way back in Minecraft \nalpha. Eloraam of RedPower, and SpaceToad of Buildcraft, without their acceptiance \nof me taking over the project, who knows what Minecraft modding would be today.\n\nSecondly, someone who has worked with me, and developed some of the core features\nthat allow modding to be as functional, and as simple as it is, cpw. For developing\nFML, which stabelized the client and server modding ecosystem. As well as the base\nloading system that allows us to modify Minecraft's code as elegently as possible.\n\nMezz, who has stepped up as the issue and pull request manager. Helping to keep me\nsane as well as guiding the community into creating better additions to Forge.\n\nSearge, Bspks, Fesh0r, ProfMobious, and all the rest over on the MCP team {of which \nI am a part}. For creating some of the core tools needed to make Minecraft modding \nboth possible, and as stable as can be.\n  On that note, here is some specific information of the MCP data we use:\n    * Minecraft Coder Pack (MCP) *\n      Forge Mod Loader and Minecraft Forge have permission to distribute and automatically \n      download components of MCP and distribute MCP data files. This permission is not \n      transitive and others wishing to redistribute the Minecraft Forge source independently\n      should seek permission of MCP or remove the MCP data files and request their users \n      to download MCP separately.\n      \nAnd lastly, the countless community members who have spent time submitting bug reports, \npull requests, and just helping out the community in general. Thank you.\n\n--LexManos\n\n=========================================================================\n\nThis is Forge Mod Loader.\n\nYou can find the source code at all times at https://github.com/MinecraftForge/MinecraftForge/tree/1.12.x/src/main/java/net/minecraftforge/fml\n\nThis minecraft mod is a clean open source implementation of a mod loader for minecraft servers\nand minecraft clients.\n\nThe code is authored by cpw.\n\nIt began by partially implementing an API defined by the client side ModLoader, authored by Risugami.\nhttp://www.minecraftforum.net/topic/75440-\nThis support has been dropped as of Minecraft release 1.7, as Risugami no longer maintains ModLoader.\n\nIt also contains suggestions and hints and generous helpings of code from LexManos, author of MinecraftForge.\nhttp://www.minecraftforge.net/\n\nAdditionally, it contains an implementation of topological sort based on that \npublished at http://keithschwarz.com/interesting/code/?dir=topological-sort\n\nIt also contains code from the Maven project for performing versioned dependency\nresolution. http://maven.apache.org/\n\nIt also contains a partial repackaging of the javaxdelta library from http://sourceforge.net/projects/javaxdelta/\nwith credit to it's authors.\n\nForge Mod Loader downloads components from the Minecraft Coder Pack\n(http://mcp.ocean-labs.de/index.php/Main_Page) with kind permission from the MCP team.\n\n"
  },
  {
    "path": "LICENSE.txt",
    "content": "Unless noted below, Minecraft Forge, Forge Mod Loader, and all \nparts herein are licensed under the terms of the LGPL 2.1 found\nhere http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt and \ncopied below.\n\nHomepage: http://minecraftforge.net/\n          https://github.com/MinecraftForge/MinecraftForge\n          \n\nA note on authorship:\nAll source artifacts are property of their original author, with\nthe exclusion of the contents of the patches directory and others\ncopied from it from time to time. Authorship of the contents of\nthe patches directory is retained by the Minecraft Forge project.\nThis is because the patches are partially machine generated\nartifacts, and are changed heavily due to the way forge works.\nIndividual attribution within them is impossible.\n\nConsent:\nAll contributions to Forge must consent to the release of any\npatch content to the Forge project.\n\nA note on infectivity:\nThe LGPL is chosen specifically so that projects may depend on Forge\nfeatures without being infected with its license. That is the \npurpose of the LGPL. Mods and others using this code via ordinary\nJava mechanics for referencing libraries are specifically not bound\nby Forge's license for the Mod code.\n\n\n=== MCP Data ===\nThis software includes data from the Minecraft Coder Pack (MCP), with kind permission\nfrom them. The license to MCP data is not transitive - distribution of this data by\nthird parties requires independent licensing from the MCP team. This data is not\nredistributable without permission from the MCP team.\n\n=== Sharing ===\nI grant permission for some parts of FML to be redistributed outside the terms of the LGPL, for the benefit of\nthe minecraft modding community. All contributions to these parts should be licensed under the same additional grant.\n\n-- Runtime patcher --\nLicense is granted to redistribute the runtime patcher code (src/main/java/net/minecraftforge/fml/common/patcher\nand subdirectories) under any alternative open source license as classified by the OSI (http://opensource.org/licenses)\n\n-- ASM transformers --\nLicense is granted to redistribute the ASM transformer code (src/main/java/net/minecraftforge/common/asm/ and subdirectories)\nunder any alternative open source license as classified by the OSI (http://opensource.org/licenses)\n\n=========================================================================\nThis software includes portions from the Apache Maven project at\nhttp://maven.apache.org/ specifically the ComparableVersion.java code. It is\nincluded based on guidelines at\nhttp://www.softwarefreedom.org/resources/2007/gpl-non-gpl-collaboration.html\nwith notices intact. The only change is a non-functional change of package name.\n\nThis software contains a partial repackaging of javaxdelta, a BSD licensed program for generating\nbinary differences and applying them, sourced from the subversion at http://sourceforge.net/projects/javaxdelta/\nauthored by genman, heikok, pivot.\nThe only changes are to replace some Trove collection types with standard Java collections, and repackaged.\n=========================================================================\n\n\n                  GNU LESSER GENERAL PUBLIC LICENSE\n                       Version 2.1, February 1999\n\n Copyright (C) 1991, 1999 Free Software Foundation, Inc.\n 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n[This is the first released version of the Lesser GPL.  It also counts\n as the successor of the GNU Library Public License, version 2, hence\n the version number 2.1.]\n\n                            Preamble\n\n  The licenses for most software are designed to take away your\nfreedom to share and change it.  By contrast, the GNU General Public\nLicenses are intended to guarantee your freedom to share and change\nfree software--to make sure the software is free for all its users.\n\n  This license, the Lesser General Public License, applies to some\nspecially designated software packages--typically libraries--of the\nFree Software Foundation and other authors who decide to use it.  You\ncan use it too, but we suggest you first think carefully about whether\nthis license or the ordinary General Public License is the better\nstrategy to use in any particular case, based on the explanations below.\n\n  When we speak of free software, we are referring to freedom of use,\nnot price.  Our General Public Licenses are designed to make sure that\nyou have the freedom to distribute copies of free software (and charge\nfor this service if you wish); that you receive source code or can get\nit if you want it; that you can change the software and use pieces of\nit in new free programs; and that you are informed that you can do\nthese things.\n\n  To protect your rights, we need to make restrictions that forbid\ndistributors to deny you these rights or to ask you to surrender these\nrights.  These restrictions translate to certain responsibilities for\nyou if you distribute copies of the library or if you modify it.\n\n  For example, if you distribute copies of the library, whether gratis\nor for a fee, you must give the recipients all the rights that we gave\nyou.  You must make sure that they, too, receive or can get the source\ncode.  If you link other code with the library, you must provide\ncomplete object files to the recipients, so that they can relink them\nwith the library after making changes to the library and recompiling\nit.  And you must show them these terms so they know their rights.\n\n  We protect your rights with a two-step method: (1) we copyright the\nlibrary, and (2) we offer you this license, which gives you legal\npermission to copy, distribute and/or modify the library.\n\n  To protect each distributor, we want to make it very clear that\nthere is no warranty for the free library.  Also, if the library is\nmodified by someone else and passed on, the recipients should know\nthat what they have is not the original version, so that the original\nauthor's reputation will not be affected by problems that might be\nintroduced by others.\n\f\n  Finally, software patents pose a constant threat to the existence of\nany free program.  We wish to make sure that a company cannot\neffectively restrict the users of a free program by obtaining a\nrestrictive license from a patent holder.  Therefore, we insist that\nany patent license obtained for a version of the library must be\nconsistent with the full freedom of use specified in this license.\n\n  Most GNU software, including some libraries, is covered by the\nordinary GNU General Public License.  This license, the GNU Lesser\nGeneral Public License, applies to certain designated libraries, and\nis quite different from the ordinary General Public License.  We use\nthis license for certain libraries in order to permit linking those\nlibraries into non-free programs.\n\n  When a program is linked with a library, whether statically or using\na shared library, the combination of the two is legally speaking a\ncombined work, a derivative of the original library.  The ordinary\nGeneral Public License therefore permits such linking only if the\nentire combination fits its criteria of freedom.  The Lesser General\nPublic License permits more lax criteria for linking other code with\nthe library.\n\n  We call this license the \"Lesser\" General Public License because it\ndoes Less to protect the user's freedom than the ordinary General\nPublic License.  It also provides other free software developers Less\nof an advantage over competing non-free programs.  These disadvantages\nare the reason we use the ordinary General Public License for many\nlibraries.  However, the Lesser license provides advantages in certain\nspecial circumstances.\n\n  For example, on rare occasions, there may be a special need to\nencourage the widest possible use of a certain library, so that it becomes\na de-facto standard.  To achieve this, non-free programs must be\nallowed to use the library.  A more frequent case is that a free\nlibrary does the same job as widely used non-free libraries.  In this\ncase, there is little to gain by limiting the free library to free\nsoftware only, so we use the Lesser General Public License.\n\n  In other cases, permission to use a particular library in non-free\nprograms enables a greater number of people to use a large body of\nfree software.  For example, permission to use the GNU C Library in\nnon-free programs enables many more people to use the whole GNU\noperating system, as well as its variant, the GNU/Linux operating\nsystem.\n\n  Although the Lesser General Public License is Less protective of the\nusers' freedom, it does ensure that the user of a program that is\nlinked with the Library has the freedom and the wherewithal to run\nthat program using a modified version of the Library.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.  Pay close attention to the difference between a\n\"work based on the library\" and a \"work that uses the library\".  The\nformer contains code derived from the library, whereas the latter must\nbe combined with the library in order to run.\n\f\n                  GNU LESSER GENERAL PUBLIC LICENSE\n   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION\n\n  0. This License Agreement applies to any software library or other\nprogram which contains a notice placed by the copyright holder or\nother authorized party saying it may be distributed under the terms of\nthis Lesser General Public License (also called \"this License\").\nEach licensee is addressed as \"you\".\n\n  A \"library\" means a collection of software functions and/or data\nprepared so as to be conveniently linked with application programs\n(which use some of those functions and data) to form executables.\n\n  The \"Library\", below, refers to any such software library or work\nwhich has been distributed under these terms.  A \"work based on the\nLibrary\" means either the Library or any derivative work under\ncopyright law: that is to say, a work containing the Library or a\nportion of it, either verbatim or with modifications and/or translated\nstraightforwardly into another language.  (Hereinafter, translation is\nincluded without limitation in the term \"modification\".)\n\n  \"Source code\" for a work means the preferred form of the work for\nmaking modifications to it.  For a library, complete source code means\nall the source code for all modules it contains, plus any associated\ninterface definition files, plus the scripts used to control compilation\nand installation of the library.\n\n  Activities other than copying, distribution and modification are not\ncovered by this License; they are outside its scope.  The act of\nrunning a program using the Library is not restricted, and output from\nsuch a program is covered only if its contents constitute a work based\non the Library (independent of the use of the Library in a tool for\nwriting it).  Whether that is true depends on what the Library does\nand what the program that uses the Library does.\n\n  1. You may copy and distribute verbatim copies of the Library's\ncomplete source code as you receive it, in any medium, provided that\nyou conspicuously and appropriately publish on each copy an\nappropriate copyright notice and disclaimer of warranty; keep intact\nall the notices that refer to this License and to the absence of any\nwarranty; and distribute a copy of this License along with the\nLibrary.\n\n  You may charge a fee for the physical act of transferring a copy,\nand you may at your option offer warranty protection in exchange for a\nfee.\n\f\n  2. You may modify your copy or copies of the Library or any portion\nof it, thus forming a work based on the Library, and copy and\ndistribute such modifications or work under the terms of Section 1\nabove, provided that you also meet all of these conditions:\n\n    a) The modified work must itself be a software library.\n\n    b) You must cause the files modified to carry prominent notices\n    stating that you changed the files and the date of any change.\n\n    c) You must cause the whole of the work to be licensed at no\n    charge to all third parties under the terms of this License.\n\n    d) If a facility in the modified Library refers to a function or a\n    table of data to be supplied by an application program that uses\n    the facility, other than as an argument passed when the facility\n    is invoked, then you must make a good faith effort to ensure that,\n    in the event an application does not supply such function or\n    table, the facility still operates, and performs whatever part of\n    its purpose remains meaningful.\n\n    (For example, a function in a library to compute square roots has\n    a purpose that is entirely well-defined independent of the\n    application.  Therefore, Subsection 2d requires that any\n    application-supplied function or table used by this function must\n    be optional: if the application does not supply it, the square\n    root function must still compute square roots.)\n\nThese requirements apply to the modified work as a whole.  If\nidentifiable sections of that work are not derived from the Library,\nand can be reasonably considered independent and separate works in\nthemselves, then this License, and its terms, do not apply to those\nsections when you distribute them as separate works.  But when you\ndistribute the same sections as part of a whole which is a work based\non the Library, the distribution of the whole must be on the terms of\nthis License, whose permissions for other licensees extend to the\nentire whole, and thus to each and every part regardless of who wrote\nit.\n\nThus, it is not the intent of this section to claim rights or contest\nyour rights to work written entirely by you; rather, the intent is to\nexercise the right to control the distribution of derivative or\ncollective works based on the Library.\n\nIn addition, mere aggregation of another work not based on the Library\nwith the Library (or with a work based on the Library) on a volume of\na storage or distribution medium does not bring the other work under\nthe scope of this License.\n\n  3. You may opt to apply the terms of the ordinary GNU General Public\nLicense instead of this License to a given copy of the Library.  To do\nthis, you must alter all the notices that refer to this License, so\nthat they refer to the ordinary GNU General Public License, version 2,\ninstead of to this License.  (If a newer version than version 2 of the\nordinary GNU General Public License has appeared, then you can specify\nthat version instead if you wish.)  Do not make any other change in\nthese notices.\n\f\n  Once this change is made in a given copy, it is irreversible for\nthat copy, so the ordinary GNU General Public License applies to all\nsubsequent copies and derivative works made from that copy.\n\n  This option is useful when you wish to copy part of the code of\nthe Library into a program that is not a library.\n\n  4. You may copy and distribute the Library (or a portion or\nderivative of it, under Section 2) in object code or executable form\nunder the terms of Sections 1 and 2 above provided that you accompany\nit with the complete corresponding machine-readable source code, which\nmust be distributed under the terms of Sections 1 and 2 above on a\nmedium customarily used for software interchange.\n\n  If distribution of object code is made by offering access to copy\nfrom a designated place, then offering equivalent access to copy the\nsource code from the same place satisfies the requirement to\ndistribute the source code, even though third parties are not\ncompelled to copy the source along with the object code.\n\n  5. A program that contains no derivative of any portion of the\nLibrary, but is designed to work with the Library by being compiled or\nlinked with it, is called a \"work that uses the Library\".  Such a\nwork, in isolation, is not a derivative work of the Library, and\ntherefore falls outside the scope of this License.\n\n  However, linking a \"work that uses the Library\" with the Library\ncreates an executable that is a derivative of the Library (because it\ncontains portions of the Library), rather than a \"work that uses the\nlibrary\".  The executable is therefore covered by this License.\nSection 6 states terms for distribution of such executables.\n\n  When a \"work that uses the Library\" uses material from a header file\nthat is part of the Library, the object code for the work may be a\nderivative work of the Library even though the source code is not.\nWhether this is true is especially significant if the work can be\nlinked without the Library, or if the work is itself a library.  The\nthreshold for this to be true is not precisely defined by law.\n\n  If such an object file uses only numerical parameters, data\nstructure layouts and accessors, and small macros and small inline\nfunctions (ten lines or less in length), then the use of the object\nfile is unrestricted, regardless of whether it is legally a derivative\nwork.  (Executables containing this object code plus portions of the\nLibrary will still fall under Section 6.)\n\n  Otherwise, if the work is a derivative of the Library, you may\ndistribute the object code for the work under the terms of Section 6.\nAny executables containing that work also fall under Section 6,\nwhether or not they are linked directly with the Library itself.\n\f\n  6. As an exception to the Sections above, you may also combine or\nlink a \"work that uses the Library\" with the Library to produce a\nwork containing portions of the Library, and distribute that work\nunder terms of your choice, provided that the terms permit\nmodification of the work for the customer's own use and reverse\nengineering for debugging such modifications.\n\n  You must give prominent notice with each copy of the work that the\nLibrary is used in it and that the Library and its use are covered by\nthis License.  You must supply a copy of this License.  If the work\nduring execution displays copyright notices, you must include the\ncopyright notice for the Library among them, as well as a reference\ndirecting the user to the copy of this License.  Also, you must do one\nof these things:\n\n    a) Accompany the work with the complete corresponding\n    machine-readable source code for the Library including whatever\n    changes were used in the work (which must be distributed under\n    Sections 1 and 2 above); and, if the work is an executable linked\n    with the Library, with the complete machine-readable \"work that\n    uses the Library\", as object code and/or source code, so that the\n    user can modify the Library and then relink to produce a modified\n    executable containing the modified Library.  (It is understood\n    that the user who changes the contents of definitions files in the\n    Library will not necessarily be able to recompile the application\n    to use the modified definitions.)\n\n    b) Use a suitable shared library mechanism for linking with the\n    Library.  A suitable mechanism is one that (1) uses at run time a\n    copy of the library already present on the user's computer system,\n    rather than copying library functions into the executable, and (2)\n    will operate properly with a modified version of the library, if\n    the user installs one, as long as the modified version is\n    interface-compatible with the version that the work was made with.\n\n    c) Accompany the work with a written offer, valid for at\n    least three years, to give the same user the materials\n    specified in Subsection 6a, above, for a charge no more\n    than the cost of performing this distribution.\n\n    d) If distribution of the work is made by offering access to copy\n    from a designated place, offer equivalent access to copy the above\n    specified materials from the same place.\n\n    e) Verify that the user has already received a copy of these\n    materials or that you have already sent this user a copy.\n\n  For an executable, the required form of the \"work that uses the\nLibrary\" must include any data and utility programs needed for\nreproducing the executable from it.  However, as a special exception,\nthe materials to be distributed need not include anything that is\nnormally distributed (in either source or binary form) with the major\ncomponents (compiler, kernel, and so on) of the operating system on\nwhich the executable runs, unless that component itself accompanies\nthe executable.\n\n  It may happen that this requirement contradicts the license\nrestrictions of other proprietary libraries that do not normally\naccompany the operating system.  Such a contradiction means you cannot\nuse both them and the Library together in an executable that you\ndistribute.\n\f\n  7. You may place library facilities that are a work based on the\nLibrary side-by-side in a single library together with other library\nfacilities not covered by this License, and distribute such a combined\nlibrary, provided that the separate distribution of the work based on\nthe Library and of the other library facilities is otherwise\npermitted, and provided that you do these two things:\n\n    a) Accompany the combined library with a copy of the same work\n    based on the Library, uncombined with any other library\n    facilities.  This must be distributed under the terms of the\n    Sections above.\n\n    b) Give prominent notice with the combined library of the fact\n    that part of it is a work based on the Library, and explaining\n    where to find the accompanying uncombined form of the same work.\n\n  8. You may not copy, modify, sublicense, link with, or distribute\nthe Library except as expressly provided under this License.  Any\nattempt otherwise to copy, modify, sublicense, link with, or\ndistribute the Library is void, and will automatically terminate your\nrights under this License.  However, parties who have received copies,\nor rights, from you under this License will not have their licenses\nterminated so long as such parties remain in full compliance.\n\n  9. You are not required to accept this License, since you have not\nsigned it.  However, nothing else grants you permission to modify or\ndistribute the Library or its derivative works.  These actions are\nprohibited by law if you do not accept this License.  Therefore, by\nmodifying or distributing the Library (or any work based on the\nLibrary), you indicate your acceptance of this License to do so, and\nall its terms and conditions for copying, distributing or modifying\nthe Library or works based on it.\n\n  10. Each time you redistribute the Library (or any work based on the\nLibrary), the recipient automatically receives a license from the\noriginal licensor to copy, distribute, link with or modify the Library\nsubject to these terms and conditions.  You may not impose any further\nrestrictions on the recipients' exercise of the rights granted herein.\nYou are not responsible for enforcing compliance by third parties with\nthis License.\n\f\n  11. If, as a consequence of a court judgment or allegation of patent\ninfringement or for any other reason (not limited to patent issues),\nconditions 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\ndistribute so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you\nmay not distribute the Library at all.  For example, if a patent\nlicense would not permit royalty-free redistribution of the Library by\nall those who receive copies directly or indirectly through you, then\nthe only way you could satisfy both it and this License would be to\nrefrain entirely from distribution of the Library.\n\nIf any portion of this section is held invalid or unenforceable under any\nparticular circumstance, the balance of the section is intended to apply,\nand the section as a whole is intended to apply in other circumstances.\n\nIt is not the purpose of this section to induce you to infringe any\npatents or other property right claims or to contest validity of any\nsuch claims; this section has the sole purpose of protecting the\nintegrity of the free software distribution system which is\nimplemented by public license practices.  Many people have made\ngenerous contributions to the wide range of software distributed\nthrough that system in reliance on consistent application of that\nsystem; it is up to the author/donor to decide if he or she is willing\nto distribute software through any other system and a licensee cannot\nimpose that choice.\n\nThis section is intended to make thoroughly clear what is believed to\nbe a consequence of the rest of this License.\n\n  12. If the distribution and/or use of the Library is restricted in\ncertain countries either by patents or by copyrighted interfaces, the\noriginal copyright holder who places the Library under this License may add\nan explicit geographical distribution limitation excluding those countries,\nso that distribution is permitted only in or among countries not thus\nexcluded.  In such case, this License incorporates the limitation as if\nwritten in the body of this License.\n\n  13. The Free Software Foundation may publish revised and/or new\nversions of the Lesser General Public License from time to time.\nSuch new versions will be similar in spirit to the present version,\nbut may differ in detail to address new problems or concerns.\n\nEach version is given a distinguishing version number.  If the Library\nspecifies a version number of this License which applies to it and\n\"any later version\", you have the option of following the terms and\nconditions either of that version or of any later version published by\nthe Free Software Foundation.  If the Library does not specify a\nlicense version number, you may choose any version ever published by\nthe Free Software Foundation.\n\f\n  14. If you wish to incorporate parts of the Library into other free\nprograms whose distribution conditions are incompatible with these,\nwrite to the author to ask for permission.  For software which is\ncopyrighted by the Free Software Foundation, write to the Free\nSoftware Foundation; we sometimes make exceptions for this.  Our\ndecision will be guided by the two goals of preserving the free status\nof all derivatives of our free software and of promoting the sharing\nand reuse of software generally.\n\n                            NO WARRANTY\n\n  15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO\nWARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.\nEXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR\nOTHER PARTIES PROVIDE THE LIBRARY \"AS IS\" WITHOUT WARRANTY OF ANY\nKIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE\nIMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE\nLIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME\nTHE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN\nWRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY\nAND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU\nFOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR\nCONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE\nLIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING\nRENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A\nFAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF\nSUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH\nDAMAGES.\n\n                     END OF TERMS AND CONDITIONS\n"
  },
  {
    "path": "README.txt",
    "content": "\nSource installation information for modders\n-------------------------------------------\nThis code follows the Minecraft Forge installation methodology. It will apply\nsome small patches to the vanilla MCP source code, giving you and it access \nto some of the data and functions you need to build a successful mod.\n\nNote also that the patches are built against \"un-renamed\" MCP source code (aka\nSRG Names) - this means that you will not be able to read them directly against\nnormal code.\n\nSetup Process:\n==============================\n\nStep 1: Open your command-line and browse to the folder where you extracted the zip file.\n\nStep 2: You're left with a choice.\nIf you prefer to use Eclipse:\n1. Run the following command: `gradlew genEclipseRuns` (`./gradlew genEclipseRuns` if you are on Mac/Linux)\n2. Open Eclipse, Import > Existing Gradle Project > Select Folder \n   or run `gradlew eclipse` to generate the project.\n\nIf you prefer to use IntelliJ:\n1. Open IDEA, and import project.\n2. Select your build.gradle file and have it import.\n3. Run the following command: `gradlew genIntellijRuns` (`./gradlew genIntellijRuns` if you are on Mac/Linux)\n4. Refresh the Gradle Project in IDEA if required.\n\nIf at any point you are missing libraries in your IDE, or you've run into problems you can \nrun `gradlew --refresh-dependencies` to refresh the local cache. `gradlew clean` to reset everything \n{this does not affect your code} and then start the process again.\n\nMapping Names:\n=============================\nBy default, the MDK is configured to use the official mapping names from Mojang for methods and fields \nin the Minecraft codebase. These names are covered by a specific license. All modders should be aware of this\nlicense, if you do not agree with it you can change your mapping names to other crowdsourced names in your \nbuild.gradle. For the latest license text, refer to the mapping file itself, or the reference copy here:\nhttps://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md\n\nAdditional Resources: \n=========================\nCommunity Documentation: http://mcforge.readthedocs.io/en/latest/gettingstarted/  \nLexManos' Install Video: https://www.youtube.com/watch?v=8VEdtQLuLO0  \nForge Forum: https://forums.minecraftforge.net/  \nForge Discord: https://discord.gg/UvedJ9m  "
  },
  {
    "path": "build.gradle",
    "content": "buildscript {\n    repositories {\n        maven {\n            name = 'sponge'\n            url = 'https://repo.spongepowered.org/repository/maven-public/'\n        }\n    }\n    dependencies {\n        classpath group: 'net.minecraftforge.gradle', name: 'ForgeGradle', version: '5.1.+', changing: true\n        classpath \"org.spongepowered:mixingradle:0.7-SNAPSHOT\"\n    }\n}\n\nplugins {\n    id 'eclipse'\n    id 'idea'\n    id 'maven-publish'\n    id 'net.minecraftforge.gradle' version '[6.0,6.2)'\n}\n\nversion = mod_version\ngroup = mod_group_id\n\nbase {\n    archivesName = mod_id\n}\n\next.buildnumber = 0\next.projversion = 0\n\nproject.projversion = mod_version\narchivesBaseName = mod_id\n\nif (System.getenv('GITHUB_RUN_NUMBER')) {\n    project.buildnumber = System.getenv('GITHUB_RUN_NUMBER')\n    version = \"${projversion}+${buildnumber}-gha\"\n} else {\n    version = \"${projversion}\"\n}\n\n\n\n// Mojang ships Java 17 to end users in 1.18+, so your mod should target Java 17.\njava.toolchain.languageVersion = JavaLanguageVersion.of(17)\n\nprintln \"Java: ${System.getProperty 'java.version'}, JVM: ${System.getProperty 'java.vm.version'} (${System.getProperty 'java.vendor'}), Arch: ${System.getProperty 'os.arch'}\"\nminecraft {\n    // The mappings can be changed at any time and must be in the following format.\n    // Channel:   Version:\n    // official   MCVersion             Official field/method names from Mojang mapping files\n    // parchment  YYYY.MM.DD-MCVersion  Open community-sourced parameter names and javadocs layered on top of official\n    //\n    // You must be aware of the Mojang license when using the 'official' or 'parchment' mappings.\n    // See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md\n    //\n    // Parchment is an unofficial project maintained by ParchmentMC, separate from MinecraftForge\n    // Additional setup is needed to use their mappings: https://parchmentmc.org/docs/getting-started\n    //\n    // Use non-default mappings at your own risk. They may not always work.\n    // Simply re-run your setup task after changing the mappings to update your workspace.\n    mappings channel: mapping_channel, version: mapping_version\n\n    // When true, this property will have all Eclipse/IntelliJ IDEA run configurations run the \"prepareX\" task for the given run configuration before launching the game.\n    // In most cases, it is not necessary to enable.\n    // enableEclipsePrepareRuns = true\n    // enableIdeaPrepareRuns = true\n\n    // This property allows configuring Gradle's ProcessResources task(s) to run on IDE output locations before launching the game.\n    // It is REQUIRED to be set to true for this template to function.\n    // See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html\n    copyIdeResources = true\n\n    // When true, this property will add the folder name of all declared run configurations to generated IDE run configurations.\n    // The folder name can be set on a run configuration using the \"folderName\" property.\n    // By default, the folder name of a run configuration is the name of the Gradle project containing it.\n    // generateRunFolders = true\n\n    // This property enables access transformers for use in development.\n    // They will be applied to the Minecraft artifact.\n    // The access transformer file can be anywhere in the project.\n    // However, it must be at \"META-INF/accesstransformer.cfg\" in the final mod jar to be loaded by Forge.\n    // This default location is a best practice to automatically put the file in the right place in the final jar.\n    // See https://docs.minecraftforge.net/en/latest/advanced/accesstransformers/ for more information.\n    accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg')\n\n    // Default run configurations.\n    // These can be tweaked, removed, or duplicated as needed.\n    runs {\n        // applies to all the run configs below\n        configureEach {\n            workingDirectory project.file('run')\n            arg \"-mixin.config=weather2.mixins.json\"\n\n            // Recommended logging data for a userdev environment\n            // The markers can be added/remove as needed separated by commas.\n            // \"SCAN\": For mods scan.\n            // \"REGISTRIES\": For firing of registry events.\n            // \"REGISTRYDUMP\": For getting the contents of all registries.\n            property 'forge.logging.markers', 'REGISTRIES'\n\n            // Recommended logging level for the console\n            // You can set various levels here.\n            // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels\n            property 'forge.logging.console.level', 'debug'\n            property 'mixin.env.remapRefMap', 'true'\n            property 'mixin.env.refMapRemappingFile', \"${projectDir}/build/createSrgToMcp/output.srg\"\n\n            mods {\n                \"${mod_id}\" {\n                    source sourceSets.main\n                }\n            }\n        }\n\n        client {\n            // Comma-separated list of namespaces to load gametests from. Empty = all namespaces.\n            property 'forge.enabledGameTestNamespaces', mod_id\n        }\n\n        server {\n            property 'forge.enabledGameTestNamespaces', mod_id\n            args '--nogui'\n        }\n\n        // This run config launches GameTestServer and runs all registered gametests, then exits.\n        // By default, the server will crash when no gametests are provided.\n        // The gametest system is also enabled by default for other run configs under the /test command.\n        gameTestServer {\n            property 'forge.enabledGameTestNamespaces', mod_id\n        }\n\n        data {\n            // example of overriding the workingDirectory set in configureEach above\n            workingDirectory project.file('run-data')\n\n            // Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources.\n            args '--mod', mod_id, '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/')\n        }\n    }\n}\n\n// Include resources generated by data generators.\nsourceSets.main.resources { srcDir 'src/generated/resources' }\n\napply plugin: 'org.spongepowered.mixin'\n\nmixin {\n    add sourceSets.main, 'weather2.refmap.json'\n    config 'weather2.mixins.json'\n}\n\nconfigurations {\n    modImpl\n    implementation.extendsFrom(modImpl)\n    testImplementation.extendsFrom(modImpl)\n\n    modRuntime\n    runtimeOnly.extendsFrom(modRuntime)\n    testRuntimeOnly.extendsFrom(modRuntime)\n\n    modCompile\n    compileOnly.extendsFrom(modCompile)\n    testCompileOnly.extendsFrom(modCompile)\n}\n\nrepositories {\n    // Put repositories for dependencies here\n    // ForgeGradle automatically adds the Forge maven and Maven Central for you\n    maven { url = \"https://maven.tterrag.com/\" }\n    maven { url = \"https://api.modrinth.com/maven\" }\n    maven {\n        name = 'sponge'\n        url = 'https://repo.spongepowered.org/repository/maven-public/'\n    }\n    // If you have mod jar dependencies in ./libs, you can declare them as a repository like so.\n    // See https://docs.gradle.org/current/userguide/declaring_repositories.html#sub:flat_dir_resolver\n    flatDir {\n        dir 'libs'\n    }\n}\n\ndependencies {\n    // Specify the version of Minecraft to use.\n    // Any artifact can be supplied so long as it has a \"userdev\" classifier artifact and is a compatible patcher artifact.\n    // The \"userdev\" classifier will be requested and setup by ForgeGradle.\n    // If the group id is \"net.minecraft\" and the artifact id is one of [\"client\", \"server\", \"joined\"],\n    // then special handling is done to allow a setup of a vanilla dependency without the use of an external repository.\n    minecraft \"net.minecraftforge:forge:${minecraft_version}-${forge_version}\"\n\n    //implementation fg.deobf('com.corosus.coroutil:coroutil:1.20.1-1.3.3')\n    implementation fg.deobf('com.corosus.coroutil:coroutil-forge:1.20.1-1.3.7')\n    //implementation fg.deobf('com.corosus.enderio:EnderIO:1.20.1-6.0.21-alpha')\n    //implementation fg.deobf('com.corosus.pipez:pipez:1.20.1-1.1.5')\n    //implementation fg.deobf('oculus:oculus:mc1.20.1-1.6.9')\n    //implementation fg.deobf('rubidium:rubidium:mc1.20.1-0.7.1')\n    //implementation fg.deobf('spark:spark:1.10.42-forge')\n    //implementation fg.deobf('tropicraft:tropicraft:9.6.3-beta+551-gha')\n    annotationProcessor 'org.spongepowered:mixin:0.8.5-SNAPSHOT:processor'\n    //implementation fg.deobf('com.lovetropics.ltweather:ltweather:1.20.1-1.0')\n    //implementation fg.deobf('com.lovetropics.minigames:LTMinigames:0.1.0-alpha+custom')\n    //modImpl fg.deobf('maven.modrinth:tropicraft:9.6.1-1.20.1')\n    //implementation fg.deobf(\"com.tterrag.registrate:Registrate:MC1.20-${registrate_version}\")\n    //jarJar(modCompile(fg.deobf(\"com.tterrag.registrate:Registrate:$registrate_version\"))) {\n        //jarJar.ranged(it, \"[$registrate_version,MC1.21)\")\n    //}\n    // Example mod dependency with JEI - using fg.deobf() ensures the dependency is remapped to your development mappings\n    // The JEI API is declared for compile time use, while the full JEI artifact is used at runtime\n    // compileOnly fg.deobf(\"mezz.jei:jei-${mc_version}-common-api:${jei_version}\")\n    // compileOnly fg.deobf(\"mezz.jei:jei-${mc_version}-forge-api:${jei_version}\")\n    // runtimeOnly fg.deobf(\"mezz.jei:jei-${mc_version}-forge:${jei_version}\")\n\n    // Example mod dependency using a mod jar from ./libs with a flat dir repository\n    // This maps to ./libs/coolmod-${mc_version}-${coolmod_version}.jar\n    // The group id is ignored when searching -- in this case, it is \"blank\"\n    // implementation fg.deobf(\"blank:coolmod-${mc_version}:${coolmod_version}\")\n\n    // For more info:\n    // http://www.gradle.org/docs/current/userguide/artifact_dependencies_tutorial.html\n    // http://www.gradle.org/docs/current/userguide/dependency_management.html\n}\n\n// This block of code expands all declared replace properties in the specified resource targets.\n// A missing property will result in an error. Properties are expanded using ${} Groovy notation.\n// When \"copyIdeResources\" is enabled, this will also run before the game launches in IDE environments.\n// See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html\ntasks.named('processResources', ProcessResources).configure {\n    var replaceProperties = [\n            minecraft_version: minecraft_version, minecraft_version_range: minecraft_version_range,\n            forge_version: forge_version, forge_version_range: forge_version_range,\n            loader_version_range: loader_version_range,\n            mod_id: mod_id, mod_name: mod_name, mod_license: mod_license, mod_version: mod_version,\n            mod_authors: mod_authors, mod_description: mod_description,\n    ]\n    inputs.properties replaceProperties\n\n    filesMatching(['META-INF/mods.toml', 'pack.mcmeta']) {\n        expand replaceProperties + [project: project]\n    }\n}\n\n// Example for how to get properties into the manifest for reading at runtime.\ntasks.named('jar', Jar).configure {\n    manifest {\n        attributes([\n                'Specification-Title'     : mod_id,\n                'Specification-Vendor'    : mod_authors,\n                'Specification-Version'   : '1', // We are version 1 of ourselves\n                'Implementation-Title'    : project.name,\n                'Implementation-Version'  : project.jar.archiveVersion,\n                'Implementation-Vendor'   : mod_authors,\n                'Implementation-Timestamp': new Date().format(\"yyyy-MM-dd'T'HH:mm:ssZ\"),\n                'MixinConfigs': 'weather2.mixins.json'\n        ])\n    }\n\n    // This is the preferred method to reobfuscate your jar file\n    finalizedBy 'reobfJar'\n}\n\n// However if you are in a multi-project build, dev time needs unobfed jar files, so you can delay the obfuscation until publishing by doing:\n// tasks.named('publish').configure {\n//     dependsOn 'reobfJar'\n// }\n\n// Example configuration to allow publishing using the maven-publish plugin\npublishing {\n    publications {\n        register('mavenJava', MavenPublication) {\n            artifact jar\n        }\n    }\n    repositories {\n        maven {\n            url \"file://${project.projectDir}/mcmodsrepo\"\n        }\n    }\n}\n\ntasks.named('jarJar').configure {\n    archiveClassifier = ''\n    finalizedBy 'reobfJarJar'\n}\n\nreobf {\n    jarJar { }\n}\n\ntask sourceJar(type: Jar, dependsOn: classes) {\n    archiveClassifier = 'sources'\n    from sourceSets.main.java\n}\n\ntasks.withType(JavaCompile).configureEach {\n    options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation\n}\n"
  },
  {
    "path": "changelog.txt",
    "content": "Build: 1.18.1-39.0.5 - Mon Dec 13 21:58:30 GMT 2021\n\tpupnewfster:\n\t\tAdd RenderArmEvent to make overriding just the arm rendering not require copying nearly as much vanilla code (#8254)\n\n=========\nBuild: 1.18.1-39.0.4 - Mon Dec 13 21:32:20 GMT 2021\n\tbageldotjpg:\n\t\tAdd MobEffect tags (#8231)\n\n=========\nBuild: 1.18.1-39.0.3 - Mon Dec 13 19:49:00 GMT 2021\n\txfacthd:\n\t\tLog missing or unsupported dependencies (#8218)\n\n=========\nBuild: 1.18.1-39.0.2 - Mon Dec 13 19:33:05 GMT 2021\n\tsciwhiz12:\n\t\tFix datagen test for sounds definitions provider  (#8249)\n\n=========\nBuild: 1.18.1-39.0.1 - Mon Dec 13 19:14:15 GMT 2021\n\twilliewillus:\n\t\tFix wrong stage being declared in transition to common (#8267)\n\n=========\nBuild: 1.18.1-39.0.0 - Fri Dec 10 19:32:24 GMT 2021\n\tcurle:\n\t\tUpdate to 1.18.1\n\t\t\n\t\tCo-Authored by:\n\t\t- Curle\n\t\t_ Orion\n\n=========\nBuild: 1.18-38.0.17 - Fri Dec 10 09:23:45 GMT 2021\n\toriondevelopment:\n\t\t[CVE-2021-44228]: Update Log4J to fix the security issue inside it. (#8268)\n\n=========\nBuild: 1.18-38.0.16 - Wed Dec 08 00:09:40 GMT 2021\n\tjaredlll08:\n\t\tFix KeyMappings only checking if they conflict with themselves. (#8256)\n\n=========\nBuild: 1.18-38.0.15 - Sun Dec 05 19:40:15 GMT 2021\n\txfacthd:\n\t\tFix ChunkWatchEvent not being fired (#8253)\n\n=========\nBuild: 1.18-38.0.14 - Sat Dec 04 01:30:30 GMT 2021\n\tgit:\n\t\tCall handleUpdateTag for BlockEntities again (#8237)\n\n=========\nBuild: 1.18-38.0.13 - Fri Dec 03 22:10:25 GMT 2021\n\tcommoble:\n\t\tFix test worldgen data (#8248)\n\n=========\nBuild: 1.18-38.0.12 - Thu Dec 02 20:16:47 GMT 2021\n\tlexmanos:\n\t\tAllow Forge Registries to return key information for overridden objects. Fixes #8230\n\n=========\nBuild: 1.18-38.0.11 - Thu Dec 02 19:17:12 GMT 2021\n\tcurle:\n\t\tSave Chunk capabilities to the chunk, rather than recursively to the capabilities.\n\n=========\nBuild: 1.18-38.0.10 - Thu Dec 02 15:24:47 GMT 2021\n\tgigaherz:\n\t\tMake HandshakeConsumer public again.\n\t\tFixes #8241\n\n\tgigaherz:\n\t\tFix LevelChunk capability attach crash.\n\t\tFix client chunks not having capability providers attached.\n\t\tAdd capability attach tests.\n\n=========\nBuild: 1.18-38.0.8 - Thu Dec 02 00:44:15 GMT 2021\n\tcurle:\n\t\tAdd missing biomes back to the BiomeDictionary\n\n\tcurle:\n\t\tComplete TODO in ShapedRecipe patch causing logspam related to minecraft:air\n\n=========\nBuild: 1.18-38.0.6 - Wed Dec 01 22:12:05 GMT 2021\n\tcurle:\n\t\tReadd Mixin 0.8.5 to fix modules issues.\n\n=========\nBuild: 1.18-38.0.5 - Wed Dec 01 16:56:24 GMT 2021\n\tcurle:\n\t\tReadd PoseStack field to RenderTooltipEvent.\n\n=========\nBuild: 1.18-38.0.4 - Wed Dec 01 01:29:57 GMT 2021\n\tcurle:\n\t\tFix custom loot serializers using wrong registry names\n\n=========\nBuild: 1.18-38.0.3 - Wed Dec 01 01:15:13 GMT 2021\n\tlexmanos:\n\t\tFix DungeonHooks not returning correct values. Fixes dungeons in world spawning pigs.\n\n=========\nBuild: 1.18-38.0.2 - Wed Dec 01 00:23:23 GMT 2021\n\tlexmanos:\n\t\tFix dedicated server install. Closes #8226\n\t\tFix example mod\n\t\tFix obf issue with records. Closes #8228\n\t\tFix dependencies beingg out of sync from vanilla. Closes #8227\n\t\tDisable mixin due to module incompatibility.\n\n=========\nBuild: 1.18-38.0.1 - Tue Nov 30 20:56:52 GMT 2021\n\tgigaherz:\n\t\tFix mod resources not loading.\n\t\tAdd BreakingItemParticle.java.patch which I forgot to commit during the porting.\n"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.1.1-bin.zip\nnetworkTimeout=10000\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Sets default memory used for gradle commands. Can be overridden by user or command line properties.\n# This is required to provide enough memory for the Minecraft decompilation process.\norg.gradle.jvmargs=-Xmx3G\norg.gradle.daemon=false\n\n\n## Environment Properties\n\n#for gh action\n#registrate_version=1.3.3\n\n#for dev testing\nregistrate_version=MC1.20-1.3.3\n\n# The Minecraft version must agree with the Forge version to get a valid artifact\nminecraft_version=1.20.1\n# The Minecraft version range can use any release version of Minecraft as bounds.\n# Snapshots, pre-releases, and release candidates are not guaranteed to sort properly\n# as they do not follow standard versioning conventions.\nminecraft_version_range=[1.20.1,1.21)\n# The Forge version must agree with the Minecraft version to get a valid artifact\nforge_version=47.1.0\n# The Forge version range can use any version of Forge as bounds or match the loader version range\nforge_version_range=[47,)\n# The loader version range can only use the major version of Forge/FML as bounds\nloader_version_range=[47,)\n# The mapping channel to use for mappings.\n# The default set of supported mapping channels are [\"official\", \"snapshot\", \"snapshot_nodoc\", \"stable\", \"stable_nodoc\"].\n# Additional mapping channels can be registered through the \"channelProviders\" extension in a Gradle plugin.\n#\n# | Channel   | Version              |                                                                                |\n# |-----------|----------------------|--------------------------------------------------------------------------------|\n# | official  | MCVersion            | Official field/method names from Mojang mapping files                          |\n# | parchment | YYYY.MM.DD-MCVersion | Open community-sourced parameter names and javadocs layered on top of official |\n#\n# You must be aware of the Mojang license when using the 'official' or 'parchment' mappings.\n# See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md\n#\n# Parchment is an unofficial project maintained by ParchmentMC, separate from Minecraft Forge.\n# Additional setup is needed to use their mappings, see https://parchmentmc.org/docs/getting-started\nmapping_channel=official\n# The mapping version to query from the mapping channel.\n# This must match the format required by the mapping channel.\nmapping_version=1.20.1\n\n\n## Mod Properties\n\n# The unique mod identifier for the mod. Must be lowercase in English locale. Must fit the regex [a-z][a-z0-9_]{1,63}\n# Must match the String constant located in the main mod class annotated with @Mod.\nmod_id=weather2\n# The human-readable display name for the mod.\nmod_name=Weather2\n# The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default.\nmod_license=All Rights Reserved\n# The mod version. See https://semver.org/\nmod_version=1.20.1-2.8.3\n# The group ID for the mod. It is only important when publishing as an artifact to a Maven repository.\n# This should match the base package used for the mod sources.\n# See https://maven.apache.org/guides/mini/guide-naming-conventions.html\nmod_group_id=com.corosus.weather2\n# The authors of the mod. This is a simple text string that is used for display purposes in the mod list.\nmod_authors=Corosus\n# The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list.\nmod_description=Speeeeeeen"
  },
  {
    "path": "gradlew",
    "content": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#      https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n#\n\n##############################################################################\n#\n#   Gradle start up script for POSIX generated by Gradle.\n#\n#   Important for running:\n#\n#   (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is\n#       noncompliant, but you have some other compliant shell such as ksh or\n#       bash, then to run this script, type that shell name before the whole\n#       command line, like:\n#\n#           ksh Gradle\n#\n#       Busybox and similar reduced shells will NOT work, because this script\n#       requires all of these POSIX shell features:\n#         * functions;\n#         * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,\n#           «${var#prefix}», «${var%suffix}», and «$( cmd )»;\n#         * compound commands having a testable exit status, especially «case»;\n#         * various built-in commands including «command», «set», and «ulimit».\n#\n#   Important for patching:\n#\n#   (2) This script targets any POSIX shell, so it avoids extensions provided\n#       by Bash, Ksh, etc; in particular arrays are avoided.\n#\n#       The \"traditional\" practice of packing multiple parameters into a\n#       space-separated string is a well documented source of bugs and security\n#       problems, so this is (mostly) avoided, by progressively accumulating\n#       options in \"$@\", and eventually passing that to Java.\n#\n#       Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,\n#       and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;\n#       see the in-line comments for details.\n#\n#       There are tweaks for specific operating systems such as AIX, CygWin,\n#       Darwin, MinGW, and NonStop.\n#\n#   (3) This script is generated from the Groovy template\n#       https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt\n#       within the Gradle project.\n#\n#       You can find Gradle at https://github.com/gradle/gradle/.\n#\n##############################################################################\n\n# Attempt to set APP_HOME\n\n# Resolve links: $0 may be a link\napp_path=$0\n\n# Need this for daisy-chained symlinks.\nwhile\n    APP_HOME=${app_path%\"${app_path##*/}\"}  # leaves a trailing /; empty if no leading path\n    [ -h \"$app_path\" ]\ndo\n    ls=$( ls -ld \"$app_path\" )\n    link=${ls#*' -> '}\n    case $link in             #(\n      /*)   app_path=$link ;; #(\n      *)    app_path=$APP_HOME$link ;;\n    esac\ndone\n\n# This is normally unused\n# shellcheck disable=SC2034\nAPP_BASE_NAME=${0##*/}\nAPP_HOME=$( cd \"${APP_HOME:-./}\" && pwd -P ) || exit\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS='\"-Xmx64m\" \"-Xms64m\"'\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=maximum\n\nwarn () {\n    echo \"$*\"\n} >&2\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n} >&2\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"$( uname )\" in                #(\n  CYGWIN* )         cygwin=true  ;; #(\n  Darwin* )         darwin=true  ;; #(\n  MSYS* | MINGW* )  msys=true    ;; #(\n  NONSTOP* )        nonstop=true ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=$JAVA_HOME/jre/sh/java\n    else\n        JAVACMD=$JAVA_HOME/bin/java\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=java\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif ! \"$cygwin\" && ! \"$darwin\" && ! \"$nonstop\" ; then\n    case $MAX_FD in #(\n      max*)\n        # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045 \n        MAX_FD=$( ulimit -H -n ) ||\n            warn \"Could not query maximum file descriptor limit\"\n    esac\n    case $MAX_FD in  #(\n      '' | soft) :;; #(\n      *)\n        # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.\n        # shellcheck disable=SC3045 \n        ulimit -n \"$MAX_FD\" ||\n            warn \"Could not set maximum file descriptor limit to $MAX_FD\"\n    esac\nfi\n\n# Collect all arguments for the java command, stacking in reverse order:\n#   * args from the command line\n#   * the main class name\n#   * -classpath\n#   * -D...appname settings\n#   * --module-path (only if needed)\n#   * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.\n\n# For Cygwin or MSYS, switch paths to Windows format before running java\nif \"$cygwin\" || \"$msys\" ; then\n    APP_HOME=$( cygpath --path --mixed \"$APP_HOME\" )\n    CLASSPATH=$( cygpath --path --mixed \"$CLASSPATH\" )\n\n    JAVACMD=$( cygpath --unix \"$JAVACMD\" )\n\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    for arg do\n        if\n            case $arg in                                #(\n              -*)   false ;;                            # don't mess with options #(\n              /?*)  t=${arg#/} t=/${t%%/*}              # looks like a POSIX filepath\n                    [ -e \"$t\" ] ;;                      #(\n              *)    false ;;\n            esac\n        then\n            arg=$( cygpath --path --ignore --mixed \"$arg\" )\n        fi\n        # Roll the args list around exactly as many times as the number of\n        # args, so each arg winds up back in the position where it started, but\n        # possibly modified.\n        #\n        # NB: a `for` loop captures its iteration list before it begins, so\n        # changing the positional parameters here affects neither the number of\n        # iterations, nor the values presented in `arg`.\n        shift                   # remove old arg\n        set -- \"$@\" \"$arg\"      # push replacement arg\n    done\nfi\n\n# Collect all arguments for the java command;\n#   * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of\n#     shell script including quotes and variable substitutions, so put them in\n#     double quotes to make sure that they get re-expanded; and\n#   * put everything else in single quotes, so that it's not re-expanded.\n\nset -- \\\n        \"-Dorg.gradle.appname=$APP_BASE_NAME\" \\\n        -classpath \"$CLASSPATH\" \\\n        org.gradle.wrapper.GradleWrapperMain \\\n        \"$@\"\n\n# Stop when \"xargs\" is not available.\nif ! command -v xargs >/dev/null 2>&1\nthen\n    die \"xargs is not available\"\nfi\n\n# Use \"xargs\" to parse quoted args.\n#\n# With -n1 it outputs one arg per line, with the quotes and backslashes removed.\n#\n# In Bash we could simply go:\n#\n#   readarray ARGS < <( xargs -n1 <<<\"$var\" ) &&\n#   set -- \"${ARGS[@]}\" \"$@\"\n#\n# but POSIX shell has neither arrays nor command substitution, so instead we\n# post-process each arg (as a line of input to sed) to backslash-escape any\n# character that might be a shell metacharacter, then use eval to reverse\n# that process (while maintaining the separation between arguments), and wrap\n# the whole thing up as a single \"set\" statement.\n#\n# This will of course break if any of these variables contains a newline or\n# an unmatched quote.\n#\n\neval \"set -- $(\n        printf '%s\\n' \"$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS\" |\n        xargs -n1 |\n        sed ' s~[^-[:alnum:]+,./:=@_]~\\\\&~g; ' |\n        tr '\\n' ' '\n    )\" '\"$@\"'\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \"License\");\n@rem you may not use this file except in compliance with the License.\n@rem You may obtain a copy of the License at\n@rem\n@rem      https://www.apache.org/licenses/LICENSE-2.0\n@rem\n@rem Unless required by applicable law or agreed to in writing, software\n@rem distributed under the License is distributed on an \"AS IS\" BASIS,\n@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n@rem See the License for the specific language governing permissions and\n@rem limitations under the License.\n@rem\n\n@if \"%DEBUG%\"==\"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\"==\"\" set DIRNAME=.\n@rem This is normally unused\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Resolve any \".\" and \"..\" in APP_HOME to make it shorter.\nfor %%i in (\"%APP_HOME%\") do set APP_HOME=%%~fi\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\"-Xmx64m\" \"-Xms64m\"\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif %ERRORLEVEL% equ 0 goto execute\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto execute\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %*\n\n:end\n@rem End local scope for the variables with windows NT shell\nif %ERRORLEVEL% equ 0 goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nset EXIT_CODE=%ERRORLEVEL%\nif %EXIT_CODE% equ 0 set EXIT_CODE=1\nif not \"\"==\"%GRADLE_EXIT_CONSOLE%\" exit %EXIT_CODE%\nexit /b %EXIT_CODE%\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "settings.gradle",
    "content": "pluginManagement {\n    repositories {\n        gradlePluginPortal()\n        maven {\n            name = 'MinecraftForge'\n            url = 'https://maven.minecraftforge.net/'\n        }\n    }\n}\n\nplugins {\n    id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0'\n}"
  },
  {
    "path": "src/generated/resources/.cache/0d0f48ca72ebc11ea3eaf67230fd125a3c581fa7",
    "content": "// 1.20.1\t2023-11-22T02:08:00.197677\tatlases generator for coroutil\n74f8b853ba67f301962a10de1f0c163cbd8a8879 assets/minecraft/atlases/particles.json\n"
  },
  {
    "path": "src/generated/resources/.cache/59eb3dbb5f86130e09b3c62d89b9525ee01cf52d",
    "content": "// 1.20.1\t2023-11-23T22:56:39.0222298\tLoot Tables\nc8c5ce560e926e54e54807bfa8292631975278c8 data/weather2/loot_tables/blocks/anemometer.json\nc8c3920b1604bd0d96bb0f2fe36af5bec0d17a68 data/weather2/loot_tables/blocks/sand_layer.json\n110f066250df5b3b40a1ab65f5db0e42b149bfc0 data/weather2/loot_tables/blocks/tornado_sensor.json\ndab63dc3440645fa945cceb5bf0e90e65617307c data/weather2/loot_tables/blocks/tornado_siren.json\n52d48e562562111bcbc28c4d88000e7f53be2c23 data/weather2/loot_tables/blocks/weather_deflector.json\nc0a4e2a903db1b6c8a509f541f413283c73e4905 data/weather2/loot_tables/blocks/weather_forecast.json\n5144ca5845a17038ade0ad75085ec40b1fe313d8 data/weather2/loot_tables/blocks/wind_turbine.json\n9844b43ed3b5bdcf2cb5caa511707ef6bf53a3f9 data/weather2/loot_tables/blocks/wind_vane.json\n"
  },
  {
    "path": "src/generated/resources/.cache/9fb1092f32d4fcbf9e061ffd718d4ec689c6c95e",
    "content": "// 1.20.1\t2023-11-23T15:40:45.726425\tRecipes\na6fcf55b00f782cb61ea7ff8c36947cc71d1b6eb data/weather2/advancements/recipes/misc/anemometer.json\nf05e205a927078351fb102a2efb42abb0b621702 data/weather2/advancements/recipes/misc/sand_layer.json\n1628c853ad5eb3f28a2183446344bc04891eb91c data/weather2/advancements/recipes/misc/tornado_sensor.json\nf13d83986054c3bc720075107179268a8d729079 data/weather2/advancements/recipes/misc/tornado_siren.json\n1a192c2be326846c2fd212595e7c1c8336494294 data/weather2/advancements/recipes/misc/weather_deflector.json\n877fab4cc1e153017cbbc49bbecdb403dd48c4b8 data/weather2/advancements/recipes/misc/weather_forecast.json\n7def7a8363c66c36f79299afc71c888668c6dc82 data/weather2/advancements/recipes/misc/weather_item.json\n27039723785ab5a173e8f855f777dd90ca4f1712 data/weather2/advancements/recipes/misc/wind_turbine.json\n31301eabe51f450b5c713189ed8800a6e56c85af data/weather2/advancements/recipes/misc/wind_vane.json\n7191a7c546fa76fda67b9eff30be60d24c66c0f9 data/weather2/recipes/anemometer.json\nbedb68269184b52e111d66ab1a430f50f74b2c1f data/weather2/recipes/sand_layer.json\n386f9af237dac1c3ded5cb82859c8f2f409c994f data/weather2/recipes/tornado_sensor.json\n8fd7a464306fd74e6d6fc771d0c918003125ade2 data/weather2/recipes/tornado_siren.json\nfa3fbf7a57f04cd6bafe3d294d99e0359c3c1aae data/weather2/recipes/weather_deflector.json\n5465cb6ea0be946f98da47fedcd7d4328a61a1ee data/weather2/recipes/weather_forecast.json\nfc16ba8ec6cae16f7a3c29bba99215b13a13ef97 data/weather2/recipes/weather_item.json\ne79f45df5a4ad54d1d56b36885e6be58135475d3 data/weather2/recipes/wind_turbine.json\nfc56b6dad0bb7d6b22ae4b95d8c69cbcd964b4fa data/weather2/recipes/wind_vane.json\n"
  },
  {
    "path": "src/generated/resources/.cache/f991d9ad226694c30732bf45648e031ffa7d50dc",
    "content": "// 1.20.1\t2023-11-17T01:47:33.0547988\tatlases generator for weather2\n8b58f99c389a93e757d0e4e05efaaa16ce22dd8c assets/minecraft/atlases/blocks.json\n"
  },
  {
    "path": "src/generated/resources/assets/minecraft/atlases/blocks.json",
    "content": "{\n  \"sources\": [\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"weather2:blocks/tornado_siren\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"weather2:blocks/tornado_siren_manual\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"weather2:blocks/tornado_siren_manual_on\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"weather2:blocks/tornado_sensor\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"weather2:blocks/weather_deflector\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"weather2:blocks/weather_forecast\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"weather2:blocks/weather_machine\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"weather2:blocks/anemometer\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"weather2:blocks/wind_vane\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"weather2:blocks/wind_turbine\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"weather2:items/weather_item\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"weather2:items/sand_layer\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"weather2:items/sand_layer_placeable\"\n    }\n  ]\n}"
  },
  {
    "path": "src/generated/resources/assets/minecraft/atlases/particles.json",
    "content": "{\n  \"sources\": [\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"coroutil:particles/white\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"coroutil:particles/cloud256\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"coroutil:particles/cloud256_fire\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"coroutil:particles/cloud256_6\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"coroutil:particles/downfall3\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"coroutil:particles/chicken\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"coroutil:particles/potato\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"coroutil:particles/leaf\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"coroutil:particles/rain_white\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"coroutil:particles/snow\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"coroutil:particles/snow2\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"coroutil:particles/tumbleweed\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"coroutil:particles/debris_1\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"coroutil:particles/debris_2\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"coroutil:particles/debris_3\"\n    },\n    {\n      \"type\": \"minecraft:single\",\n      \"resource\": \"coroutil:particles/hail\"\n    }\n  ]\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/advancements/recipes/misc/anemometer.json",
    "content": "{\n  \"parent\": \"minecraft:recipes/root\",\n  \"criteria\": {\n    \"has_the_recipe\": {\n      \"conditions\": {\n        \"recipe\": \"weather2:anemometer\"\n      },\n      \"trigger\": \"minecraft:recipe_unlocked\"\n    },\n    \"has_weather_item\": {\n      \"conditions\": {\n        \"items\": [\n          {\n            \"items\": [\n              \"weather2:weather_item\"\n            ]\n          }\n        ]\n      },\n      \"trigger\": \"minecraft:inventory_changed\"\n    }\n  },\n  \"requirements\": [\n    [\n      \"has_weather_item\",\n      \"has_the_recipe\"\n    ]\n  ],\n  \"rewards\": {\n    \"recipes\": [\n      \"weather2:anemometer\"\n    ]\n  },\n  \"sends_telemetry_event\": false\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/advancements/recipes/misc/sand_layer.json",
    "content": "{\n  \"parent\": \"minecraft:recipes/root\",\n  \"criteria\": {\n    \"has_sand\": {\n      \"conditions\": {\n        \"items\": [\n          {\n            \"items\": [\n              \"minecraft:sand\"\n            ]\n          }\n        ]\n      },\n      \"trigger\": \"minecraft:inventory_changed\"\n    },\n    \"has_the_recipe\": {\n      \"conditions\": {\n        \"recipe\": \"weather2:sand_layer\"\n      },\n      \"trigger\": \"minecraft:recipe_unlocked\"\n    }\n  },\n  \"requirements\": [\n    [\n      \"has_sand\",\n      \"has_the_recipe\"\n    ]\n  ],\n  \"rewards\": {\n    \"recipes\": [\n      \"weather2:sand_layer\"\n    ]\n  },\n  \"sends_telemetry_event\": false\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/advancements/recipes/misc/tornado_sensor.json",
    "content": "{\n  \"parent\": \"minecraft:recipes/root\",\n  \"criteria\": {\n    \"has_the_recipe\": {\n      \"conditions\": {\n        \"recipe\": \"weather2:tornado_sensor\"\n      },\n      \"trigger\": \"minecraft:recipe_unlocked\"\n    },\n    \"has_weather_item\": {\n      \"conditions\": {\n        \"items\": [\n          {\n            \"items\": [\n              \"weather2:weather_item\"\n            ]\n          }\n        ]\n      },\n      \"trigger\": \"minecraft:inventory_changed\"\n    }\n  },\n  \"requirements\": [\n    [\n      \"has_weather_item\",\n      \"has_the_recipe\"\n    ]\n  ],\n  \"rewards\": {\n    \"recipes\": [\n      \"weather2:tornado_sensor\"\n    ]\n  },\n  \"sends_telemetry_event\": false\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/advancements/recipes/misc/tornado_siren.json",
    "content": "{\n  \"parent\": \"minecraft:recipes/root\",\n  \"criteria\": {\n    \"has_sensor_item\": {\n      \"conditions\": {\n        \"items\": [\n          {\n            \"items\": [\n              \"weather2:tornado_sensor\"\n            ]\n          }\n        ]\n      },\n      \"trigger\": \"minecraft:inventory_changed\"\n    },\n    \"has_the_recipe\": {\n      \"conditions\": {\n        \"recipe\": \"weather2:tornado_siren\"\n      },\n      \"trigger\": \"minecraft:recipe_unlocked\"\n    }\n  },\n  \"requirements\": [\n    [\n      \"has_sensor_item\",\n      \"has_the_recipe\"\n    ]\n  ],\n  \"rewards\": {\n    \"recipes\": [\n      \"weather2:tornado_siren\"\n    ]\n  },\n  \"sends_telemetry_event\": false\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/advancements/recipes/misc/weather_deflector.json",
    "content": "{\n  \"parent\": \"minecraft:recipes/root\",\n  \"criteria\": {\n    \"has_the_recipe\": {\n      \"conditions\": {\n        \"recipe\": \"weather2:weather_deflector\"\n      },\n      \"trigger\": \"minecraft:recipe_unlocked\"\n    },\n    \"has_weather_item\": {\n      \"conditions\": {\n        \"items\": [\n          {\n            \"items\": [\n              \"weather2:weather_item\"\n            ]\n          }\n        ]\n      },\n      \"trigger\": \"minecraft:inventory_changed\"\n    }\n  },\n  \"requirements\": [\n    [\n      \"has_weather_item\",\n      \"has_the_recipe\"\n    ]\n  ],\n  \"rewards\": {\n    \"recipes\": [\n      \"weather2:weather_deflector\"\n    ]\n  },\n  \"sends_telemetry_event\": false\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/advancements/recipes/misc/weather_forecast.json",
    "content": "{\n  \"parent\": \"minecraft:recipes/root\",\n  \"criteria\": {\n    \"has_the_recipe\": {\n      \"conditions\": {\n        \"recipe\": \"weather2:weather_forecast\"\n      },\n      \"trigger\": \"minecraft:recipe_unlocked\"\n    },\n    \"has_weather_item\": {\n      \"conditions\": {\n        \"items\": [\n          {\n            \"items\": [\n              \"weather2:weather_item\"\n            ]\n          }\n        ]\n      },\n      \"trigger\": \"minecraft:inventory_changed\"\n    }\n  },\n  \"requirements\": [\n    [\n      \"has_weather_item\",\n      \"has_the_recipe\"\n    ]\n  ],\n  \"rewards\": {\n    \"recipes\": [\n      \"weather2:weather_forecast\"\n    ]\n  },\n  \"sends_telemetry_event\": false\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/advancements/recipes/misc/weather_item.json",
    "content": "{\n  \"parent\": \"minecraft:recipes/root\",\n  \"criteria\": {\n    \"has_redstone\": {\n      \"conditions\": {\n        \"items\": [\n          {\n            \"items\": [\n              \"minecraft:redstone\"\n            ]\n          }\n        ]\n      },\n      \"trigger\": \"minecraft:inventory_changed\"\n    },\n    \"has_the_recipe\": {\n      \"conditions\": {\n        \"recipe\": \"weather2:weather_item\"\n      },\n      \"trigger\": \"minecraft:recipe_unlocked\"\n    }\n  },\n  \"requirements\": [\n    [\n      \"has_redstone\",\n      \"has_the_recipe\"\n    ]\n  ],\n  \"rewards\": {\n    \"recipes\": [\n      \"weather2:weather_item\"\n    ]\n  },\n  \"sends_telemetry_event\": false\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/advancements/recipes/misc/wind_turbine.json",
    "content": "{\n  \"parent\": \"minecraft:recipes/root\",\n  \"criteria\": {\n    \"has_the_recipe\": {\n      \"conditions\": {\n        \"recipe\": \"weather2:wind_turbine\"\n      },\n      \"trigger\": \"minecraft:recipe_unlocked\"\n    },\n    \"has_wind_vane\": {\n      \"conditions\": {\n        \"items\": [\n          {\n            \"items\": [\n              \"weather2:wind_turbine\"\n            ]\n          }\n        ]\n      },\n      \"trigger\": \"minecraft:inventory_changed\"\n    }\n  },\n  \"requirements\": [\n    [\n      \"has_wind_vane\",\n      \"has_the_recipe\"\n    ]\n  ],\n  \"rewards\": {\n    \"recipes\": [\n      \"weather2:wind_turbine\"\n    ]\n  },\n  \"sends_telemetry_event\": false\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/advancements/recipes/misc/wind_vane.json",
    "content": "{\n  \"parent\": \"minecraft:recipes/root\",\n  \"criteria\": {\n    \"has_the_recipe\": {\n      \"conditions\": {\n        \"recipe\": \"weather2:wind_vane\"\n      },\n      \"trigger\": \"minecraft:recipe_unlocked\"\n    },\n    \"has_weather_item\": {\n      \"conditions\": {\n        \"items\": [\n          {\n            \"items\": [\n              \"weather2:weather_item\"\n            ]\n          }\n        ]\n      },\n      \"trigger\": \"minecraft:inventory_changed\"\n    }\n  },\n  \"requirements\": [\n    [\n      \"has_weather_item\",\n      \"has_the_recipe\"\n    ]\n  ],\n  \"rewards\": {\n    \"recipes\": [\n      \"weather2:wind_vane\"\n    ]\n  },\n  \"sends_telemetry_event\": false\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/loot_tables/blocks/anemometer.json",
    "content": "{\n  \"type\": \"minecraft:block\",\n  \"pools\": [\n    {\n      \"bonus_rolls\": 0.0,\n      \"conditions\": [\n        {\n          \"condition\": \"minecraft:survives_explosion\"\n        }\n      ],\n      \"entries\": [\n        {\n          \"type\": \"minecraft:item\",\n          \"name\": \"weather2:anemometer\"\n        }\n      ],\n      \"rolls\": 1.0\n    }\n  ],\n  \"random_sequence\": \"weather2:blocks/anemometer\"\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/loot_tables/blocks/sand_layer.json",
    "content": "{\n  \"type\": \"minecraft:block\",\n  \"pools\": [\n    {\n      \"bonus_rolls\": 0.0,\n      \"conditions\": [\n        {\n          \"condition\": \"minecraft:survives_explosion\"\n        }\n      ],\n      \"entries\": [\n        {\n          \"type\": \"minecraft:item\",\n          \"name\": \"weather2:sand_layer\"\n        }\n      ],\n      \"rolls\": 1.0\n    }\n  ],\n  \"random_sequence\": \"weather2:blocks/sand_layer\"\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/loot_tables/blocks/tornado_sensor.json",
    "content": "{\n  \"type\": \"minecraft:block\",\n  \"pools\": [\n    {\n      \"bonus_rolls\": 0.0,\n      \"conditions\": [\n        {\n          \"condition\": \"minecraft:survives_explosion\"\n        }\n      ],\n      \"entries\": [\n        {\n          \"type\": \"minecraft:item\",\n          \"name\": \"weather2:tornado_sensor\"\n        }\n      ],\n      \"rolls\": 1.0\n    }\n  ],\n  \"random_sequence\": \"weather2:blocks/tornado_sensor\"\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/loot_tables/blocks/tornado_siren.json",
    "content": "{\n  \"type\": \"minecraft:block\",\n  \"pools\": [\n    {\n      \"bonus_rolls\": 0.0,\n      \"conditions\": [\n        {\n          \"condition\": \"minecraft:survives_explosion\"\n        }\n      ],\n      \"entries\": [\n        {\n          \"type\": \"minecraft:item\",\n          \"name\": \"weather2:tornado_siren\"\n        }\n      ],\n      \"rolls\": 1.0\n    }\n  ],\n  \"random_sequence\": \"weather2:blocks/tornado_siren\"\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/loot_tables/blocks/weather_deflector.json",
    "content": "{\n  \"type\": \"minecraft:block\",\n  \"pools\": [\n    {\n      \"bonus_rolls\": 0.0,\n      \"conditions\": [\n        {\n          \"condition\": \"minecraft:survives_explosion\"\n        }\n      ],\n      \"entries\": [\n        {\n          \"type\": \"minecraft:item\",\n          \"name\": \"weather2:weather_deflector\"\n        }\n      ],\n      \"rolls\": 1.0\n    }\n  ],\n  \"random_sequence\": \"weather2:blocks/weather_deflector\"\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/loot_tables/blocks/weather_forecast.json",
    "content": "{\n  \"type\": \"minecraft:block\",\n  \"pools\": [\n    {\n      \"bonus_rolls\": 0.0,\n      \"conditions\": [\n        {\n          \"condition\": \"minecraft:survives_explosion\"\n        }\n      ],\n      \"entries\": [\n        {\n          \"type\": \"minecraft:item\",\n          \"name\": \"weather2:weather_forecast\"\n        }\n      ],\n      \"rolls\": 1.0\n    }\n  ],\n  \"random_sequence\": \"weather2:blocks/weather_forecast\"\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/loot_tables/blocks/wind_turbine.json",
    "content": "{\n  \"type\": \"minecraft:block\",\n  \"pools\": [\n    {\n      \"bonus_rolls\": 0.0,\n      \"conditions\": [\n        {\n          \"condition\": \"minecraft:survives_explosion\"\n        }\n      ],\n      \"entries\": [\n        {\n          \"type\": \"minecraft:item\",\n          \"name\": \"weather2:wind_turbine\"\n        }\n      ],\n      \"rolls\": 1.0\n    }\n  ],\n  \"random_sequence\": \"weather2:blocks/wind_turbine\"\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/loot_tables/blocks/wind_vane.json",
    "content": "{\n  \"type\": \"minecraft:block\",\n  \"pools\": [\n    {\n      \"bonus_rolls\": 0.0,\n      \"conditions\": [\n        {\n          \"condition\": \"minecraft:survives_explosion\"\n        }\n      ],\n      \"entries\": [\n        {\n          \"type\": \"minecraft:item\",\n          \"name\": \"weather2:wind_vane\"\n        }\n      ],\n      \"rolls\": 1.0\n    }\n  ],\n  \"random_sequence\": \"weather2:blocks/wind_vane\"\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/recipes/anemometer.json",
    "content": "{\n  \"type\": \"minecraft:crafting_shaped\",\n  \"category\": \"misc\",\n  \"key\": {\n    \"D\": {\n      \"item\": \"minecraft:redstone\"\n    },\n    \"X\": {\n      \"item\": \"weather2:weather_item\"\n    }\n  },\n  \"pattern\": [\n    \"X X\",\n    \"XDX\",\n    \"X X\"\n  ],\n  \"result\": {\n    \"item\": \"weather2:anemometer\"\n  },\n  \"show_notification\": true\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/recipes/sand_layer.json",
    "content": "{\n  \"type\": \"minecraft:crafting_shaped\",\n  \"category\": \"misc\",\n  \"key\": {\n    \"D\": {\n      \"item\": \"minecraft:sand\"\n    }\n  },\n  \"pattern\": [\n    \"DDD\",\n    \"D D\",\n    \"DDD\"\n  ],\n  \"result\": {\n    \"count\": 8,\n    \"item\": \"weather2:sand_layer\"\n  },\n  \"show_notification\": true\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/recipes/tornado_sensor.json",
    "content": "{\n  \"type\": \"minecraft:crafting_shaped\",\n  \"category\": \"misc\",\n  \"key\": {\n    \"D\": {\n      \"item\": \"minecraft:redstone\"\n    },\n    \"I\": {\n      \"item\": \"weather2:weather_item\"\n    },\n    \"X\": {\n      \"item\": \"minecraft:iron_ingot\"\n    }\n  },\n  \"pattern\": [\n    \"X X\",\n    \"DID\",\n    \"X X\"\n  ],\n  \"result\": {\n    \"item\": \"weather2:tornado_sensor\"\n  },\n  \"show_notification\": true\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/recipes/tornado_siren.json",
    "content": "{\n  \"type\": \"minecraft:crafting_shaped\",\n  \"category\": \"misc\",\n  \"key\": {\n    \"D\": {\n      \"item\": \"minecraft:redstone\"\n    },\n    \"I\": {\n      \"item\": \"weather2:tornado_sensor\"\n    },\n    \"X\": {\n      \"item\": \"minecraft:iron_ingot\"\n    }\n  },\n  \"pattern\": [\n    \"XDX\",\n    \"DID\",\n    \"XDX\"\n  ],\n  \"result\": {\n    \"item\": \"weather2:tornado_siren\"\n  },\n  \"show_notification\": true\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/recipes/weather_deflector.json",
    "content": "{\n  \"type\": \"minecraft:crafting_shaped\",\n  \"category\": \"misc\",\n  \"key\": {\n    \"D\": {\n      \"item\": \"minecraft:redstone\"\n    },\n    \"I\": {\n      \"item\": \"weather2:weather_item\"\n    },\n    \"X\": {\n      \"item\": \"minecraft:iron_ingot\"\n    }\n  },\n  \"pattern\": [\n    \"XDX\",\n    \"DID\",\n    \"XDX\"\n  ],\n  \"result\": {\n    \"item\": \"weather2:weather_deflector\"\n  },\n  \"show_notification\": true\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/recipes/weather_forecast.json",
    "content": "{\n  \"type\": \"minecraft:crafting_shaped\",\n  \"category\": \"misc\",\n  \"key\": {\n    \"D\": {\n      \"item\": \"minecraft:redstone\"\n    },\n    \"I\": {\n      \"item\": \"minecraft:compass\"\n    },\n    \"X\": {\n      \"item\": \"weather2:weather_item\"\n    }\n  },\n  \"pattern\": [\n    \"XDX\",\n    \"DID\",\n    \"XDX\"\n  ],\n  \"result\": {\n    \"item\": \"weather2:weather_forecast\"\n  },\n  \"show_notification\": true\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/recipes/weather_item.json",
    "content": "{\n  \"type\": \"minecraft:crafting_shaped\",\n  \"category\": \"misc\",\n  \"key\": {\n    \"D\": {\n      \"item\": \"minecraft:redstone\"\n    },\n    \"I\": {\n      \"item\": \"minecraft:gold_ingot\"\n    },\n    \"X\": {\n      \"item\": \"minecraft:iron_ingot\"\n    }\n  },\n  \"pattern\": [\n    \"X X\",\n    \"DID\",\n    \"X X\"\n  ],\n  \"result\": {\n    \"item\": \"weather2:weather_item\"\n  },\n  \"show_notification\": true\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/recipes/wind_turbine.json",
    "content": "{\n  \"type\": \"minecraft:crafting_shaped\",\n  \"category\": \"misc\",\n  \"key\": {\n    \"D\": {\n      \"item\": \"minecraft:diamond\"\n    },\n    \"G\": {\n      \"item\": \"minecraft:gold_ingot\"\n    },\n    \"I\": {\n      \"item\": \"minecraft:iron_block\"\n    },\n    \"O\": {\n      \"item\": \"minecraft:iron_ingot\"\n    },\n    \"R\": {\n      \"item\": \"minecraft:redstone_block\"\n    },\n    \"V\": {\n      \"item\": \"weather2:wind_vane\"\n    }\n  },\n  \"pattern\": [\n    \"ODO\",\n    \"IVI\",\n    \"RGR\"\n  ],\n  \"result\": {\n    \"item\": \"weather2:wind_turbine\"\n  },\n  \"show_notification\": true\n}"
  },
  {
    "path": "src/generated/resources/data/weather2/recipes/wind_vane.json",
    "content": "{\n  \"type\": \"minecraft:crafting_shaped\",\n  \"category\": \"misc\",\n  \"key\": {\n    \"D\": {\n      \"item\": \"minecraft:redstone\"\n    },\n    \"X\": {\n      \"item\": \"weather2:weather_item\"\n    }\n  },\n  \"pattern\": [\n    \"X X\",\n    \"DXD\",\n    \"X X\"\n  ],\n  \"result\": {\n    \"item\": \"weather2:wind_vane\"\n  },\n  \"show_notification\": true\n}"
  },
  {
    "path": "src/main/java/extendedrenderer/ExtendedRenderer.java",
    "content": "package extendedrenderer;\n\npublic class ExtendedRenderer {\n\n    public static String modid = \"coroutil\";\n\n}\n"
  },
  {
    "path": "src/main/java/extendedrenderer/ParticleManagerExtended.java",
    "content": "package extendedrenderer;\n\nimport com.google.common.collect.*;\nimport com.mojang.blaze3d.systems.RenderSystem;\nimport com.mojang.blaze3d.vertex.BufferBuilder;\nimport com.mojang.blaze3d.vertex.PoseStack;\nimport com.mojang.blaze3d.vertex.Tesselator;\nimport com.mojang.logging.LogUtils;\nimport extendedrenderer.particle.entity.EntityRotFX;\nimport it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;\nimport net.minecraft.CrashReport;\nimport net.minecraft.CrashReportCategory;\nimport net.minecraft.ReportedException;\nimport net.minecraft.Util;\nimport net.minecraft.client.Camera;\nimport net.minecraft.client.multiplayer.ClientLevel;\nimport net.minecraft.client.particle.*;\nimport net.minecraft.client.renderer.GameRenderer;\nimport net.minecraft.client.renderer.LightTexture;\nimport net.minecraft.client.renderer.MultiBufferSource;\nimport net.minecraft.client.renderer.texture.SpriteLoader;\nimport net.minecraft.client.renderer.texture.TextureAtlas;\nimport net.minecraft.client.renderer.texture.TextureAtlasSprite;\nimport net.minecraft.client.renderer.texture.TextureManager;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.core.Direction;\nimport net.minecraft.core.particles.ParticleGroup;\nimport net.minecraft.core.particles.ParticleOptions;\nimport net.minecraft.core.particles.ParticleType;\nimport net.minecraft.core.particles.ParticleTypes;\nimport net.minecraft.core.registries.BuiltInRegistries;\nimport net.minecraft.resources.FileToIdConverter;\nimport net.minecraft.resources.ResourceLocation;\nimport net.minecraft.server.packs.resources.PreparableReloadListener;\nimport net.minecraft.server.packs.resources.Resource;\nimport net.minecraft.server.packs.resources.ResourceManager;\nimport net.minecraft.util.GsonHelper;\nimport net.minecraft.util.Mth;\nimport net.minecraft.util.RandomSource;\nimport net.minecraft.util.profiling.ProfilerFiller;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.level.block.RenderShape;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.world.phys.AABB;\nimport net.minecraft.world.phys.shapes.VoxelShape;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport org.slf4j.Logger;\n\nimport javax.annotation.Nullable;\nimport java.io.IOException;\nimport java.io.Reader;\nimport java.util.*;\nimport java.util.concurrent.CompletableFuture;\nimport java.util.concurrent.Executor;\nimport java.util.stream.Collectors;\n\n@OnlyIn(Dist.CLIENT)\npublic class ParticleManagerExtended implements PreparableReloadListener {\n   private static final Logger LOGGER = LogUtils.getLogger();\n   private static final FileToIdConverter PARTICLE_LISTER = FileToIdConverter.json(\"particles\");\n   private static final ResourceLocation PARTICLES_ATLAS_INFO = new ResourceLocation(\"particles\");\n   private static final int MAX_PARTICLES_PER_LAYER = 16384;\n   private static final List<ParticleRenderType> RENDER_ORDER = ImmutableList.of(ParticleRenderType.TERRAIN_SHEET, ParticleRenderType.PARTICLE_SHEET_OPAQUE, ParticleRenderType.PARTICLE_SHEET_LIT, ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT, ParticleRenderType.CUSTOM, ParticleRenderType.CUSTOM, EntityRotFX.SORTED_OPAQUE_BLOCK, EntityRotFX.SORTED_TRANSLUCENT);\n   protected ClientLevel level;\n   private final Map<ParticleRenderType, Queue<Particle>> particles = Maps.newTreeMap(net.minecraftforge.client.ForgeHooksClient.makeParticleRenderTypeComparator(RENDER_ORDER));\n   private final Queue<TrackingEmitter> trackingEmitters = Queues.newArrayDeque();\n   private final TextureManager textureManager;\n   private final RandomSource random = RandomSource.create();\n   private final Map<ResourceLocation, ParticleProvider<?>> providers = new java.util.HashMap<>();\n   private final Queue<Particle> particlesToAdd = Queues.newArrayDeque();\n   private final Map<ResourceLocation, ParticleManagerExtended.MutableSpriteSet> spriteSets = Maps.newHashMap();\n   private final TextureAtlas textureAtlas;\n   private final Object2IntOpenHashMap<ParticleGroup> trackedParticleCounts = new Object2IntOpenHashMap<>();\n\n   public ParticleManagerExtended(ClientLevel p_107299_, TextureManager p_107300_) {\n      this.textureAtlas = new TextureAtlas(TextureAtlas.LOCATION_PARTICLES);\n      //p_107300_.register(this.textureAtlas.location(), this.textureAtlas);\n      this.level = p_107299_;\n      this.textureManager = p_107300_;\n   }\n\n   public CompletableFuture<Void> reload(PreparableReloadListener.PreparationBarrier p_107305_, ResourceManager p_107306_, ProfilerFiller p_107307_, ProfilerFiller p_107308_, Executor p_107309_, Executor p_107310_) {\n      @OnlyIn(Dist.CLIENT)\n      record ParticleDefinition(ResourceLocation id, Optional<List<ResourceLocation>> sprites) {\n      }\n      CompletableFuture<List<ParticleDefinition>> completablefuture = CompletableFuture.supplyAsync(() -> {\n         return PARTICLE_LISTER.listMatchingResources(p_107306_);\n      }, p_107309_).thenCompose((p_247914_) -> {\n         List<CompletableFuture<ParticleDefinition>> list = new ArrayList<>(p_247914_.size());\n         p_247914_.forEach((p_247903_, p_247904_) -> {\n            ResourceLocation resourcelocation = PARTICLE_LISTER.fileToId(p_247903_);\n            list.add(CompletableFuture.supplyAsync(() -> {\n               return new ParticleDefinition(resourcelocation, this.loadParticleDescription(resourcelocation, p_247904_));\n            }, p_107309_));\n         });\n         return Util.sequence(list);\n      });\n      CompletableFuture<SpriteLoader.Preparations> completablefuture1 = SpriteLoader.create(this.textureAtlas).loadAndStitch(p_107306_, PARTICLES_ATLAS_INFO, 0, p_107309_).thenCompose(SpriteLoader.Preparations::waitForUpload);\n      return CompletableFuture.allOf(completablefuture1, completablefuture).thenCompose(p_107305_::wait).thenAcceptAsync((p_247900_) -> {\n         this.clearParticles();\n         p_107308_.startTick();\n         p_107308_.push(\"upload\");\n         SpriteLoader.Preparations spriteloader$preparations = completablefuture1.join();\n         this.textureAtlas.upload(spriteloader$preparations);\n         p_107308_.popPush(\"bindSpriteSets\");\n         Set<ResourceLocation> set = new HashSet<>();\n         TextureAtlasSprite textureatlassprite = spriteloader$preparations.missing();\n         completablefuture.join().forEach((p_247911_) -> {\n            Optional<List<ResourceLocation>> optional = p_247911_.sprites();\n            if (!optional.isEmpty()) {\n               List<TextureAtlasSprite> list = new ArrayList<>();\n\n               for(ResourceLocation resourcelocation : optional.get()) {\n                  TextureAtlasSprite textureatlassprite1 = spriteloader$preparations.regions().get(resourcelocation);\n                  if (textureatlassprite1 == null) {\n                     set.add(resourcelocation);\n                     list.add(textureatlassprite);\n                  } else {\n                     list.add(textureatlassprite1);\n                  }\n               }\n\n               if (list.isEmpty()) {\n                  list.add(textureatlassprite);\n               }\n\n               this.spriteSets.get(p_247911_.id()).rebind(list);\n            }\n         });\n         if (!set.isEmpty()) {\n            LOGGER.warn(\"Missing particle sprites: {}\", set.stream().sorted().map(ResourceLocation::toString).collect(Collectors.joining(\",\")));\n         }\n\n         p_107308_.pop();\n         p_107308_.endTick();\n      }, p_107310_);\n   }\n\n   public void close() {\n      this.textureAtlas.clearTextureData();\n   }\n\n   private Optional<List<ResourceLocation>> loadParticleDescription(ResourceLocation p_250648_, Resource p_248793_) {\n      if (!this.spriteSets.containsKey(p_250648_)) {\n         LOGGER.debug(\"Redundant texture list for particle: {}\", (Object)p_250648_);\n         return Optional.empty();\n      } else {\n         try (Reader reader = p_248793_.openAsReader()) {\n            ParticleDescription particledescription = ParticleDescription.fromJson(GsonHelper.parse(reader));\n            return Optional.of(particledescription.getTextures());\n         } catch (IOException ioexception) {\n            throw new IllegalStateException(\"Failed to load description for particle \" + p_250648_, ioexception);\n         }\n      }\n   }\n\n   @Nullable\n   private <T extends ParticleOptions> Particle makeParticle(T p_107396_, double p_107397_, double p_107398_, double p_107399_, double p_107400_, double p_107401_, double p_107402_) {\n      ParticleProvider<T> particleprovider = (ParticleProvider<T>)this.providers.get(BuiltInRegistries.PARTICLE_TYPE.getKey(p_107396_.getType()));\n      return particleprovider == null ? null : particleprovider.createParticle(p_107396_, this.level, p_107397_, p_107398_, p_107399_, p_107400_, p_107401_, p_107402_);\n   }\n\n   public void add(Particle p_107345_) {\n      Optional<ParticleGroup> optional = p_107345_.getParticleGroup();\n      if (optional.isPresent()) {\n         if (this.hasSpaceInParticleLimit(optional.get())) {\n            this.particlesToAdd.add(p_107345_);\n            this.updateCount(optional.get(), 1);\n         }\n      } else {\n         this.particlesToAdd.add(p_107345_);\n      }\n\n   }\n\n   public void tick() {\n      this.level.getProfiler().push(\"weather2_particle_tick\");\n      this.particles.forEach((p_288249_, p_288250_) -> {\n         this.level.getProfiler().push(\"weather2_particle_tick_\" + p_288249_.toString());\n         this.tickParticleList(p_288250_);\n         this.level.getProfiler().pop();\n      });\n      if (!this.trackingEmitters.isEmpty()) {\n         List<TrackingEmitter> list = Lists.newArrayList();\n\n         for(TrackingEmitter trackingemitter : this.trackingEmitters) {\n            trackingemitter.tick();\n            if (!trackingemitter.isAlive()) {\n               list.add(trackingemitter);\n            }\n         }\n\n         this.trackingEmitters.removeAll(list);\n      }\n\n      Particle particle;\n      if (!this.particlesToAdd.isEmpty()) {\n         while((particle = this.particlesToAdd.poll()) != null) {\n            this.particles.computeIfAbsent(particle.getRenderType(), (p_107347_) -> {\n               return EvictingQueue.create(16384 * 2);\n            }).add(particle);\n         }\n      }\n      this.level.getProfiler().pop();\n   }\n\n   private void tickParticleList(Collection<Particle> p_107385_) {\n      if (!p_107385_.isEmpty()) {\n         Iterator<Particle> iterator = p_107385_.iterator();\n\n         while(iterator.hasNext()) {\n            Particle particle = iterator.next();\n            this.tickParticle(particle);\n            if (!particle.isAlive()) {\n               particle.getParticleGroup().ifPresent((p_172289_) -> {\n                  this.updateCount(p_172289_, -1);\n               });\n               iterator.remove();\n            }\n         }\n      }\n\n   }\n\n   private void updateCount(ParticleGroup p_172282_, int p_172283_) {\n      this.trackedParticleCounts.addTo(p_172282_, p_172283_);\n   }\n\n   private void tickParticle(Particle p_107394_) {\n      try {\n         p_107394_.tick();\n      } catch (Throwable throwable) {\n         CrashReport crashreport = CrashReport.forThrowable(throwable, \"Ticking Particle\");\n         CrashReportCategory crashreportcategory = crashreport.addCategory(\"Particle being ticked\");\n         crashreportcategory.setDetail(\"Particle\", p_107394_::toString);\n         crashreportcategory.setDetail(\"Particle Type\", p_107394_.getRenderType()::toString);\n         throw new ReportedException(crashreport);\n      }\n   }\n\n   /**@deprecated Forge: use {@link #render(PoseStack, MultiBufferSource.BufferSource, LightTexture, Camera, float, net.minecraft.client.renderer.culling.Frustum)} with Frustum as additional parameter*/\n   @Deprecated\n   public void render(PoseStack p_107337_, MultiBufferSource.BufferSource p_107338_, LightTexture p_107339_, Camera p_107340_, float p_107341_) {\n       render(p_107337_, p_107338_, p_107339_, p_107340_, p_107341_, null);\n   }\n\n   public void render(PoseStack p_107337_, MultiBufferSource.BufferSource p_107338_, LightTexture p_107339_, Camera p_107340_, float p_107341_, @Nullable net.minecraft.client.renderer.culling.Frustum clippingHelper) {\n      this.level.getProfiler().push(\"weather2_particle_render\");\n      //if (true) return;\n      float fogStart = RenderSystem.getShaderFogStart();\n      float fogEnd = RenderSystem.getShaderFogEnd();\n      RenderSystem.setShaderFogStart(fogStart * 4);\n      RenderSystem.setShaderFogEnd(fogEnd * 4);\n\n      p_107339_.turnOnLightLayer();\n      RenderSystem.enableDepthTest();\n\n      //these didnt exist in our 1.18 modification, why?\n      RenderSystem.activeTexture(org.lwjgl.opengl.GL13.GL_TEXTURE2);\n      RenderSystem.activeTexture(org.lwjgl.opengl.GL13.GL_TEXTURE0);\n\n      PoseStack posestack = RenderSystem.getModelViewStack();\n      posestack.pushPose();\n      posestack.mulPoseMatrix(p_107337_.last().pose());\n      RenderSystem.applyModelViewMatrix();\n\n      RenderSystem.disableCull();\n      int particleCount = 0;\n\n      for(ParticleRenderType particlerendertype : this.particles.keySet()) { // Forge: allow custom IParticleRenderType's\n         this.level.getProfiler().push(particlerendertype.toString());\n         if (particlerendertype == ParticleRenderType.NO_RENDER) continue;\n         Iterable<Particle> iterable = this.particles.get(particlerendertype);\n         if (iterable != null) {\n            RenderSystem.setShader(GameRenderer::getParticleShader);\n            Tesselator tesselator = Tesselator.getInstance();\n            BufferBuilder bufferbuilder = tesselator.getBuilder();\n            particlerendertype.begin(bufferbuilder, this.textureManager);\n\n            for(Particle particle : iterable) {\n\n               if (particle instanceof EntityRotFX) {\n                  if (clippingHelper != null && particle.shouldCull() && !clippingHelper.isVisible(((EntityRotFX)particle).getBoundingBoxForRender(p_107341_)))\n                     continue;\n               } else {\n                  if (clippingHelper != null && particle.shouldCull() && !clippingHelper.isVisible(particle.getBoundingBox()))\n                     continue;\n               }\n\n               try {\n                  particle.render(bufferbuilder, p_107340_, p_107341_);\n               } catch (Throwable throwable) {\n                  CrashReport crashreport = CrashReport.forThrowable(throwable, \"Rendering Particle\");\n                  CrashReportCategory crashreportcategory = crashreport.addCategory(\"Particle being rendered\");\n                  crashreportcategory.setDetail(\"Particle\", particle::toString);\n                  crashreportcategory.setDetail(\"Particle Type\", particlerendertype::toString);\n                  throw new ReportedException(crashreport);\n               }\n            }\n\n            particlerendertype.end(tesselator);\n         }\n         this.level.getProfiler().pop();\n      }\n\n      posestack.popPose();\n      RenderSystem.applyModelViewMatrix();\n      RenderSystem.depthMask(true);\n      RenderSystem.disableBlend();\n      p_107339_.turnOffLightLayer();\n\n      RenderSystem.setShaderFogStart(fogStart);\n      RenderSystem.setShaderFogEnd(fogEnd);\n      this.level.getProfiler().pop();\n   }\n\n   public void setLevel(@Nullable ClientLevel p_107343_) {\n      this.level = p_107343_;\n      this.clearParticles();\n      this.trackingEmitters.clear();\n   }\n\n   public String countParticles() {\n      return String.valueOf(this.particles.values().stream().mapToInt(Collection::size).sum());\n   }\n\n   private boolean hasSpaceInParticleLimit(ParticleGroup p_172280_) {\n      return this.trackedParticleCounts.getInt(p_172280_) < p_172280_.getLimit();\n   }\n\n   public void clearParticles() {\n      this.particles.clear();\n      this.particlesToAdd.clear();\n      this.trackingEmitters.clear();\n      this.trackedParticleCounts.clear();\n   }\n\n   @OnlyIn(Dist.CLIENT)\n   static class MutableSpriteSet implements SpriteSet {\n      private List<TextureAtlasSprite> sprites;\n\n      public TextureAtlasSprite get(int p_107413_, int p_107414_) {\n         return this.sprites.get(p_107413_ * (this.sprites.size() - 1) / p_107414_);\n      }\n\n      public TextureAtlasSprite get(RandomSource p_233889_) {\n         return this.sprites.get(p_233889_.nextInt(this.sprites.size()));\n      }\n\n      public void rebind(List<TextureAtlasSprite> p_107416_) {\n         this.sprites = ImmutableList.copyOf(p_107416_);\n      }\n   }\n\n   public Map<ParticleRenderType, Queue<Particle>> getParticles() {\n      return particles;\n   }\n}\n"
  },
  {
    "path": "src/main/java/extendedrenderer/ParticleRegistry2ElectricBubbleoo.java",
    "content": "package extendedrenderer;\n\nimport weather2.DeferredHelper;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.client.particle.WaterDropParticle;\nimport net.minecraft.core.particles.SimpleParticleType;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport net.minecraftforge.client.event.RegisterParticleProvidersEvent;\nimport net.minecraftforge.eventbus.api.SubscribeEvent;\nimport net.minecraftforge.fml.common.Mod;\nimport net.minecraftforge.registries.RegistryObject;\nimport weather2.Weather;\n\n//@ObjectHolder(Weather.MODID)\n@Mod.EventBusSubscriber(modid = Weather.MODID, bus = Mod.EventBusSubscriber.Bus.MOD)\npublic class ParticleRegistry2ElectricBubbleoo {\n\n    //@ObjectHolder(\"acidrain_splash\")\n    //public static SimpleParticleType ACIDRAIN_SPLASH;\n    public static final RegistryObject<SimpleParticleType> ACIDRAIN_SPLASH = Weather.R.particle(\"acidrain_splash\", () -> new SimpleParticleType(false));\n\n    /*@SubscribeEvent\n    public static void registerParticles(RegistryEvent.Register<ParticleType<?>> evt){\n        SimpleParticleType acidrain_splash = new SimpleParticleType(false);\n        acidrain_splash.setRegistryName(Weather.MODID, \"acidrain_splash\");\n        evt.getRegistry().register(acidrain_splash);\n    }*/\n\n    /*@OnlyIn(Dist.CLIENT)\n    @SubscribeEvent\n    public static void registerParticleFactory(ParticleFactoryRegisterEvent evt){\n        Minecraft.getInstance().particleEngine.register(ParticleRegistry2ElectricBubbleoo.ACIDRAIN_SPLASH.get(),\n                WaterDropParticle.Provider::new);\n    }*/\n\n    @SubscribeEvent\n    @OnlyIn(Dist.CLIENT)\n    public static void factories(RegisterParticleProvidersEvent event) {\n        //event.registerSprite(ACIDRAIN_SPLASH.get(), WaterDropParticleImpl::new);\n        Minecraft.getInstance().particleEngine.register(new SimpleParticleType(false), WaterDropParticle.Provider::new);\n        //event.registerSpecial(new SimpleParticleType(false), WaterDropParticle.Provider::new);\n    }\n\n    public static void bootstrap() {}\n}\n"
  },
  {
    "path": "src/main/java/extendedrenderer/WeatherSpriteSourceProvider.java",
    "content": "/*\n * Copyright (c) Forge Development LLC and contributors\n * SPDX-License-Identifier: LGPL-2.1-only\n */\n\npackage extendedrenderer;\n\nimport net.minecraft.client.renderer.texture.atlas.sources.SingleFile;\nimport net.minecraft.data.PackOutput;\nimport net.minecraft.resources.ResourceLocation;\nimport net.minecraftforge.common.data.ExistingFileHelper;\nimport net.minecraftforge.common.data.SpriteSourceProvider;\nimport weather2.Weather;\n\nimport java.util.Optional;\n\npublic class WeatherSpriteSourceProvider extends SpriteSourceProvider\n{\n    public WeatherSpriteSourceProvider(PackOutput output, ExistingFileHelper fileHelper)\n    {\n        super(output, fileHelper, Weather.MODID);\n    }\n\n    @Override\n    protected void addSources()\n    {\n        atlas(SpriteSourceProvider.PARTICLES_ATLAS).addSource(new SingleFile(new ResourceLocation(Weather.MODID + \"white\"), Optional.empty()));\n    }\n}\n"
  },
  {
    "path": "src/main/java/extendedrenderer/particle/ParticleRegistry.java",
    "content": "package extendedrenderer.particle;\n\nimport extendedrenderer.ExtendedRenderer;\nimport net.minecraft.client.renderer.texture.TextureAtlas;\nimport net.minecraft.client.renderer.texture.TextureAtlasSprite;\nimport net.minecraft.client.renderer.texture.atlas.sources.SingleFile;\nimport net.minecraft.data.PackOutput;\nimport net.minecraft.resources.ResourceLocation;\nimport net.minecraftforge.client.event.TextureStitchEvent;\nimport net.minecraftforge.common.data.ExistingFileHelper;\nimport net.minecraftforge.common.data.SpriteSourceProvider;\nimport net.minecraftforge.eventbus.api.SubscribeEvent;\nimport weather2.Weather;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Optional;\n\npublic class ParticleRegistry extends SpriteSourceProvider {\n\n\tpublic static TextureAtlasSprite squareGrey;\n\tpublic static TextureAtlasSprite smoke;\n\t//public static TextureAtlasSprite smokeTest;\n\tpublic static TextureAtlasSprite cloud;\n\tpublic static TextureAtlasSprite cloud256;\n\tpublic static TextureAtlasSprite cloud256_fire;\n\tpublic static TextureAtlasSprite cloud256_test;\n\t//public static TextureAtlasSprite cloud256_2;\n\tpublic static TextureAtlasSprite groundSplash;\n\t//public static TextureAtlasSprite downfall2;\n\tpublic static TextureAtlasSprite downfall3;\n\t//public static TextureAtlasSprite downfall4;\n\t//public static TextureAtlasSprite cloud256_7;\n\tpublic static TextureAtlasSprite chicken;\n\tpublic static TextureAtlasSprite potato;\n\tpublic static TextureAtlasSprite leaf;\n\tpublic static TextureAtlasSprite rain;\n\tpublic static TextureAtlasSprite rain_white;\n\t//public static TextureAtlasSprite rain_white_trans;\n\t//public static TextureAtlasSprite rain_white_2;\n\t//public static TextureAtlasSprite rain_10;\n\t//public static TextureAtlasSprite rain_vanilla;\n\t//public static TextureAtlasSprite snow_vanilla;\n\tpublic static TextureAtlasSprite snow;\n\tpublic static TextureAtlasSprite snow2;\n\t//public static TextureAtlasSprite test;\n\t//public static TextureAtlasSprite cloud256dark;\n\t//public static TextureAtlasSprite cloudDownfall;\n\tpublic static TextureAtlasSprite tumbleweed;\n\tpublic static TextureAtlasSprite debris_1;\n\tpublic static TextureAtlasSprite debris_2;\n\tpublic static TextureAtlasSprite debris_3;\n\tpublic static TextureAtlasSprite test_texture;\n\tpublic static TextureAtlasSprite white_square;\n\tpublic static List<TextureAtlasSprite> listFish = new ArrayList<>();\n\t//public static List<TextureAtlasSprite> listSeaweed = new ArrayList<>();\n\tpublic static TextureAtlasSprite grass;\n\tpublic static TextureAtlasSprite hail;\n\tpublic static TextureAtlasSprite cloudNew;\n\tpublic static TextureAtlasSprite cloud_square;\n\tpublic static TextureAtlasSprite square16;\n\tpublic static TextureAtlasSprite square64;\n\n\tpublic ParticleRegistry(PackOutput output, ExistingFileHelper fileHelper)\n\t{\n\t\tsuper(output, fileHelper, ExtendedRenderer.modid);\n\t}\n\n\t@Override\n\tprotected void addSources()\n\t{\n\t\t//atlas(SpriteSourceProvider.PARTICLES_ATLAS).addSource(new SingleFile(new ResourceLocation(Weather.MODID + \":white\"), Optional.empty()));\n\n\t\taddSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/white\"));\n\t\t//addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/smoke_00\"));\n\t\t//smokeTest = event.addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/smoke_2\"));\n\t\t//cloud = event.addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/cloud64\"));\n\t\taddSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/cloud256\"));\n\t\taddSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/cloud256_fire\"));\n\t\t//addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/cloud256_test\"));\n\t\t//cloud256_2 = addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/cloud256_5\"));\n\t\t//ground splash\n\t\taddSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/cloud256_6\"));\n\t\t//cloud256_7 = addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/cloud256_7\"));\n\t\t//downfall2 = addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/downfall2\"));\n\t\taddSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/downfall3\"));\n\t\t//downfall4 = addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/downfall4\"));\n\t\tif (!Weather.isLoveTropicsInstalled()) {\n\t\t\taddSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/chicken\"));\n\t\t\taddSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/potato\"));\n\t\t}\n\t\taddSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/leaf\"));\n\t\t//rain = addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/rain\"));\n\t\t//addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/test_texture\"));\n\t\t//addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/white_square\"));\n\t\taddSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/rain_white\"));\n\t\t//rain_white_trans = addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/rain_white_trans\"));\n\t\t//rain_white_2 = addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/rain_white_2\"));\n\t\t//rain_10 = addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/rain_10\"));\n\t\t//rain_vanilla = addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/vanilla/rain\"));\n\t\t//snow_vanilla = addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/vanilla/snow\"));\n\t\taddSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/snow\"));\n\t\taddSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/snow2\"));\n\t\t//cloud256dark = addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/cloud256dark\"));\n\t\t//cloudDownfall = addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/downfall\"));\n\t\taddSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/tumbleweed\"));\n\t\taddSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/debris_1\"));\n\t\taddSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/debris_2\"));\n\t\taddSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/debris_3\"));\n\t\t/*for (int i = 1; i <= 9; i++) {\n\t\t\tlistFish.add(addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/fish_\" + i)));\n\t\t}\n\t\tfor (int i = 1; i <= 7; i++) {\n\t\t\tlistSeaweed.add(addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/seaweed_section_\" + i)));\n\t\t}*/\n\t\t//used indirectly not via reference\n\t\t//addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/grass\"));\n\t\taddSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/hail\"));\n\t\t//addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/cloud\"));\n\t\t//addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/cloud_square\"));\n\n\t\t//addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/white16\"));\n\t\t//addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/white64\"));\n\n\t\t//TODO: 1.14 uncomment\n\t\t/*MeshBufferManagerParticle.cleanup();\n\t\tMeshBufferManagerFoliage.cleanup();*/\n\t}\n\n\tpublic void addSprite(ResourceLocation res) {\n\t\tatlas(SpriteSourceProvider.PARTICLES_ATLAS).addSource(new SingleFile(res, Optional.empty()));\n\t}\n\n\t@SubscribeEvent\n\tpublic static void getRegisteredParticles(TextureStitchEvent.Post event) {\n\n\t\tif (!event.getAtlas().location().equals(TextureAtlas.LOCATION_PARTICLES)) {\n\t\t\treturn;\n\t\t}\n\n\t\tsquareGrey = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/white\"));\n\t\t//smoke = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/smoke_00\"));\n\t\t//smokeTest = event.addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/smoke_2\"));\n\t\t//cloud = event.addSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/cloud64\"));\n\t\tcloud256 = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/cloud256\"));\n\t\tcloud256_fire = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/cloud256_fire\"));\n\t\t//cloud256_test = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/cloud256_test\"));\n\t\t//cloud256_2 = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/cloud256_5\"));\n\t\t//ground splash\n\t\tgroundSplash = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/cloud256_6\"));\n\t\t//cloud256_7 = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/cloud256_7\"));\n\t\t//downfall2 = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/downfall2\"));\n\t\tdownfall3 = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/downfall3\"));\n\t\t//downfall4 = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/downfall4\"));\n\t\tif (!Weather.isLoveTropicsInstalled()) {\n\t\t\tchicken = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/chicken\"));\n\t\t\tpotato = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/potato\"));\n\t\t}\n\t\tleaf = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/leaf\"));\n\t\t//rain = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/rain\"));\n\t\t//test_texture = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/test_texture\"));\n\t\t//white_square = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/white_square\"));\n\t\train_white = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/rain_white\"));\n\t\t//rain_white_trans = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/rain_white_trans\"));\n\t\t//rain_white_2 = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/rain_white_2\"));\n\t\t//rain_10 = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/rain_10\"));\n\t\t//rain_vanilla = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/vanilla/rain\"));\n\t\t//snow_vanilla = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/vanilla/snow\"));\n\t\tsnow = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/snow\"));\n\t\tsnow2 = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/snow2\"));\n\t\t//cloud256dark = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/cloud256dark\"));\n\t\t//cloudDownfall = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/downfall\"));\n\t\ttumbleweed = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/tumbleweed\"));\n\t\tdebris_1 = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/debris_1\"));\n\t\tdebris_2 = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/debris_2\"));\n\t\tdebris_3 = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/debris_3\"));\n\t\t/*for (int i = 1; i <= 9; i++) {\n\t\t\tlistFish.add(event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/fish_\" + i)));\n\t\t}\n\t\tfor (int i = 1; i <= 7; i++) {\n\t\t\tlistSeaweed.add(event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/seaweed_section_\" + i)));\n\t\t}*/\n\t\t//used indirectly not via reference\n\t\t//grass = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/grass\"));\n\t\thail = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/hail\"));\n\t\t//cloudNew = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/cloud\"));\n\t\t//cloud_square = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/cloud_square\"));\n\n\t\t//square16 = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/white16\"));\n\t\t//square64 = event.getAtlas().getSprite(new ResourceLocation(ExtendedRenderer.modid + \":particles/white64\"));\n\n\t\t//TODO: 1.14 uncomment\n\t\t/*if (RotatingParticleManager.useShaders) {\n\t\t\tRotatingParticleManager.forceShaderReset = true;\n\t\t}*/\n\n\t}\n\n\t/*public static TextureAtlasSprite addSprite(TextureStitchEvent.Pre event, ResourceLocation resourceLocation) {\n\t\tevent.addSprite(resourceLocation);\n\t\treturn event.getAtlas().getSprite(resourceLocation);\n\t}*/\n}\n"
  },
  {
    "path": "src/main/java/extendedrenderer/particle/behavior/ParticleBehaviorFog.java",
    "content": "package extendedrenderer.particle.behavior;\n\nimport extendedrenderer.particle.entity.EntityRotFX;\nimport net.minecraft.world.phys.Vec3;\n\npublic class ParticleBehaviorFog extends ParticleBehaviors {\n\n\t//Externally updated variables, adjusting how templated behavior works\n\tpublic int curTick = 0;\n\tpublic int ticksMax = 1;\n\n\n\n\t//TODO: temp, for comparing until done\n\t//public static boolean newCloudWay = false;\n\t\n\tpublic ParticleBehaviorFog(Vec3 source) {\n\t\tsuper(source);\n\t}\n\t\n\tpublic EntityRotFX initParticle(EntityRotFX particle) {\n\t\tsuper.initParticle(particle);\n\t\t\n\t\t//particle.particleGravity = 0.5F;\n\t\t//fog\n\t\tparticle.rotationYaw = rand.nextInt(360);\n\t\tparticle.rotationPitch = rand.nextInt(50)-rand.nextInt(50);\n\t\t\n\t\t//cloud\n\t\tparticle.rotationYaw = rand.nextInt(360);\n\t\tparticle.rotationPitch = -90+rand.nextInt(50)-rand.nextInt(50);\n\n\n\t\t\n\t\tparticle.setMaxAge(650+rand.nextInt(10));\n\t\tparticle.setGravity(0.01F);\n\t\tfloat randFloat = (rand.nextFloat() * 0.6F);\n\t\tfloat baseBright = 0.7F;\n\t\tfloat finalBright = Math.min(1F, baseBright+randFloat);\n\t\tparticle.setColor(finalBright, finalBright, finalBright);\n\t\t//particle.setColor(72F/255F, 239F/255F, 8F/255F);\n\t\t\n\t\t//sand\n\t\t//particle.setColor(204F/255F, 198F/255F, 120F/255F);\n\t\t//red\n\t\t//particle.setColor(0.6F + (rand.nextFloat() * 0.4F), 0.2F + (rand.nextFloat() * 0.7F), 0);\n\t\t//green\n\t\t//particle.setColor(0, 0.4F + (rand.nextFloat() * 0.4F), 0);\n\t\t//tealy blue\n\t\t//particle.setColor(0, 0.4F + (rand.nextFloat() * 0.4F), 0.4F + (rand.nextFloat() * 0.4F));\n\t\t//particle.setColor(0.4F + (rand.nextFloat() * 0.4F), 0.4F + (rand.nextFloat() * 0.4F), 0.4F + (rand.nextFloat() * 0.4F));\n\t\t\n\t\t//location based color shift\n\t\t//particle.setColor((float) (0.4F + (Math.abs(particle.posX / 300D) * 0.6D)), 0.4F, (float) (0.4F + (Math.abs(particle.posZ / 300D) * 0.6D)));\n\t\tparticle.setUseCustomBBForRenderCulling(true);\n\t\tparticle.setScale(0.25F + 0.2F * rand.nextFloat());\n\t\tparticle.brightness = 1F;\n\t\tparticle.setAlphaF(0);\n\t\t\n\t\tfloat sizeBase = (float) (500+(rand.nextDouble()*40));\n\t\tsizeBase *= 0.15F;\n\n\t\tparticle.setScale(sizeBase);\n\t\t//particle.spawnY = (float) particle.posY;\n\t\t//particle.noClip = false;\n\t\tparticle.setCanCollide(true);\n\t\t//entityfx.spawnAsWeatherEffect();\n\t\t\n\t\tparticle.renderRange = 2048;\n\t\t\n\t\treturn particle;\n\t}\n\n\t@Override\n\tpublic void tickUpdateAct(EntityRotFX particle) {\n\t\t//particle.particleScale = 900;\n\t\t//particle.rotationPitch = 30;\n\t\t//for (int i = 0; i < particles.size(); i++) {\n\t\t\t//EntityRotFX particle = particles.get(i);\n\t\t\t\n\t\t\tif (!particle.isAlive()) {\n\t\t\t\tparticles.remove(particle);\n\t\t\t} else {\n\t\t\t\tif (particle.getEntityId() % 2 == 0) {\n\t\t\t\t\tparticle.rotationYaw -= 0.02;\n\t\t\t\t} else {\n\t\t\t\t\tparticle.rotationYaw += 0.02;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tfloat ticksFadeInMax = 50;\n\t\t\t\tfloat ticksFadeOutMax = 50;\n\t\t\t\t\n\t\t\t\tif (particle.getAge() < ticksFadeInMax) {\n\t\t\t\t\t//System.out.println(\"particle.getAge(): \" + particle.getAge());\n\t\t\t\t\tparticle.setAlphaF(particle.getAge() / ticksFadeInMax);\n\t\t\t\t\t//particle.setAlphaF(1);\n\t\t\t\t} else if (particle.getAge() > particle.getMaxAge() - ticksFadeOutMax) {\n\t\t\t\t\tfloat count = particle.getAge() - (particle.getMaxAge() - ticksFadeOutMax);\n\t\t\t\t\tfloat val = (ticksFadeOutMax - (count)) / ticksFadeOutMax;\n\t\t\t\t\t//System.out.println(val);\n\t\t\t\t\tparticle.setAlphaF(val);\n\t\t\t\t} else {\n\t\t\t\t\t/*if (particle.getAlphaF() > 0) {\n\t\t\t\t\t\tparticle.setAlphaF(particle.getAlphaF() - rateAlpha*1.3F);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tparticle.remove();\n\t\t\t\t\t}*/\n\t\t\t\t}\n\t\t\t\tdouble moveSpeed = 0.001D;\n\t\t\t\t//1.10.2 no\n\t\t\t\t/*if (particle.onGround) {\n\t\t\t\t\tmoveSpeed = 0.012D;\n\t\t\t\t\tparticle.setMotionY(particle.getMotionY() + 0.01D);\n\t\t\t\t}*/\n\t\t\t\t\n\t\t\t\t//if (particle.isCollidedHorizontally) {\n\t\t\t\tif (particle.isCollided()) {\n\t\t\t\t\tparticle.rotationYaw += 0.1;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tparticle.setMotionX(particle.getMotionX() - Math.sin(Math.toRadians((particle.rotationYaw + particle.getEntityId()) % 360)) * moveSpeed);\n\t\t\t\tparticle.setMotionZ(particle.getMotionZ() + Math.cos(Math.toRadians((particle.rotationYaw + particle.getEntityId()) % 360)) * moveSpeed);\n\t\t\t\t\n\t\t\t\tdouble moveSpeedRand = 0.005D;\n\t\t\t\t\n\t\t\t\tparticle.setMotionX(particle.getMotionX() + (rand.nextDouble() * moveSpeedRand - rand.nextDouble() * moveSpeedRand));\n\t\t\t\tparticle.setMotionZ(particle.getMotionZ() + (rand.nextDouble() * moveSpeedRand - rand.nextDouble() * moveSpeedRand));\n\t\t\t\t\n\t\t\t\tparticle.setScale(particle.getScale() - 0.1F);\n\t\t\t\t\n\t\t\t\tif (particle.spawnY != -1) {\n\t\t\t\t\tparticle.setPosition(particle.getPosX(), particle.spawnY, particle.getPosZ());\n\t\t\t\t\t//particle.posY = particle.spawnY;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t//particle.remove();\n\t\t\t}\n\t\t//}\n\t}\n}\n"
  },
  {
    "path": "src/main/java/extendedrenderer/particle/behavior/ParticleBehaviorSandstorm.java",
    "content": "package extendedrenderer.particle.behavior;\n\nimport com.corosus.coroutil.util.CoroUtilBlock;\nimport extendedrenderer.particle.entity.EntityRotFX;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.entity.player.Player;\nimport net.minecraft.world.level.biome.Biome;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.world.phys.Vec3;\nimport weather2.ClientTickHandler;\nimport weather2.ClientWeatherProxy;\nimport weather2.datatypes.PrecipitationType;\n\npublic class ParticleBehaviorSandstorm extends ParticleBehaviors {\n\n\t//Externally updated variables, adjusting how templated behavior works\n\tpublic int curTick = 0;\n\tpublic int ticksMax = 1;\n\t\n\tpublic ParticleBehaviorSandstorm(Vec3 source) {\n\t\tsuper(source);\n\t}\n\t\n\tpublic EntityRotFX initParticle(EntityRotFX particle) {\n\t\tsuper.initParticle(particle);\n\t\t\n\t\t//particle.particleGravity = 0.5F;\n\t\t//fog\n\t\tparticle.rotationYaw = rand.nextInt(360);\n\t\tparticle.rotationPitch = rand.nextInt(50)-rand.nextInt(50);\n\t\t\n\t\t//cloud\n\t\t//particle.rotationYaw = rand.nextInt(360);\n\t\t//particle.rotationPitch = -90+rand.nextInt(50)-rand.nextInt(50);\n\t\t\n\t\tparticle.setLifetime(450+rand.nextInt(10));\n\t\tfloat randFloat = (rand.nextFloat() * 0.6F);\n\t\tfloat baseBright = 0.7F;\n\t\tfloat finalBright = Math.min(1F, baseBright+randFloat);\n\t\tparticle.setColor(finalBright, finalBright, finalBright);\n\t\t//particle.setColor(72F/255F, 239F/255F, 8F/255F);\n\t\t\n\t\t//sand\n\t\t//particle.setColor(204F/255F, 198F/255F, 120F/255F);\n\t\t//red\n\t\t//particle.setColor(0.6F + (rand.nextFloat() * 0.4F), 0.2F + (rand.nextFloat() * 0.7F), 0);\n\t\t//green\n\t\t//particle.setColor(0, 0.4F + (rand.nextFloat() * 0.4F), 0);\n\t\t//tealy blue\n\t\t//particle.setColor(0, 0.4F + (rand.nextFloat() * 0.4F), 0.4F + (rand.nextFloat() * 0.4F));\n\t\t//particle.setColor(0.4F + (rand.nextFloat() * 0.4F), 0.4F + (rand.nextFloat() * 0.4F), 0.4F + (rand.nextFloat() * 0.4F));\n\t\t\n\t\t//location based color shift\n\t\t//particle.setColor((float) (0.4F + (Math.abs(particle.posX / 300D) * 0.6D)), 0.4F, (float) (0.4F + (Math.abs(particle.posZ / 300D) * 0.6D)));\n\t\t//particle.setScale(0.25F + 0.2F * rand.nextFloat());\n\t\tparticle.brightness = 1F;\n\t\tparticle.setAlpha(1F);\n\t\t\n\t\tfloat sizeBase = (float) (30+(rand.nextDouble()*4));\n\t\t\n\t\tparticle.setScale(sizeBase);\n\t\t//particle.spawnY = (float) particle.posY;\n\t\t//particle.noClip = false;\n\t\tparticle.setCanCollide(true);\n\t\t//entityfx.spawnAsWeatherEffect();\n\t\t\n\t\tparticle.renderRange = 2048;\n\t\t\n\t\tparticle.setFacePlayer(true);\n\t\tparticle.setGravity(0.03F);\n\t\t\n\t\treturn particle;\n\t}\n\n\t@Override\n\tpublic void tickUpdateAct(EntityRotFX particle) {\n\t\t//particle.particleScale = 900;\n\t\t//particle.rotationPitch = 30;\n\t\t//for (int i = 0; i < particles.size(); i++) {\n\t\t\t//EntityRotFX particle = particles.get(i);\n\t\t\t\n\t\t\tif (!particle.isAlive()) {\n\t\t\t\tparticles.remove(particle);\n\t\t\t} else {\n\t\t\t\t//random rotation yaw adjustment\n\t\t\t\tif (particle.getEntityId() % 2 == 0) {\n\t\t\t\t\tparticle.rotationYaw -= 0.1;\n\t\t\t\t} else {\n\t\t\t\t\tparticle.rotationYaw += 0.1;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tfloat ticksFadeInMax = 10;\n\t\t\t\tfloat ticksFadeOutMax = 10;\n\t\t\t\t\n\t\t\t\t//fade in and fade out near age edges\n\t\t\t\tif (particle.getAge() < ticksFadeInMax) {\n\t\t\t\t\t//System.out.println(\"particle.getAge(): \" + particle.getAge());\n\t\t\t\t\tparticle.setAlpha(Math.min(1F, particle.getAge() / ticksFadeInMax));\n\t\t\t\t\t//System.out.println(particle.getAge() / ticksFadeInMax);\n\t\t\t\t\t//particle.setAlphaF(1);\n\t\t\t\t} else if (particle.getAge() > particle.getLifetime() - ticksFadeOutMax) {\n\t\t\t\t\tfloat count = particle.getAge() - (particle.getLifetime() - ticksFadeOutMax);\n\t\t\t\t\tfloat val = (ticksFadeOutMax - (count)) / ticksFadeOutMax;\n\t\t\t\t\t//System.out.println(val);\n\t\t\t\t\tparticle.setAlpha(val);\n\t\t\t\t} else {\n\t\t\t\t\t/*if (particle.getAlphaF() > 0) {\n\t\t\t\t\t\tparticle.setAlphaF(particle.getAlphaF() - rateAlpha*1.3F);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tparticle.remove();\n\t\t\t\t\t}*/\n\t\t\t\t\t//particle.setAlphaF(1F);\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t//TEMP\n\t\t\t\t//particle.setAlphaF(1F);\n\t\t\t\t\n\t\t\t\t\n\t\t\t\tdouble moveSpeed = 0.001D;\n\t\t\t\t//1.10.2 no\n\t\t\t\t/*if (particle.onGround) {\n\t\t\t\t\tmoveSpeed = 0.012D;\n\t\t\t\t\tparticle.setMotionY(particle.getMotionY() + 0.01D);\n\t\t\t\t}*/\n\t\t\t\t\n\t\t\t\t//get pos a bit under particle\n\t\t\t\tBlockPos pos = CoroUtilBlock.blockPos(particle.getPosX(), particle.getPosY() - particle.aboveGroundHeight, particle.getPosZ());\n\t\t\t\tBlockState state = particle.getWorld().getBlockState(pos);\n\t\t\t\t//if particle is near ground, push it up to keep from landing\n\t\t\t\tif (!state.isAir()) {\n\t\t\t\t\tif (particle.getMotionY() < particle.bounceSpeedMax) {\n\t\t\t\t\t\tparticle.setMotionY(particle.getMotionY() + particle.bounceSpeed);\n\t\t\t\t\t}\n\t\t\t\t//check ahead for better flowing over large cliffs\n\t\t\t\t} else {\n\t\t\t\t\tdouble aheadMultiplier = 20D;\n\t\t\t\t\tBlockPos posAhead = CoroUtilBlock.blockPos((particle.getPosX() + (particle.getMotionX() * aheadMultiplier)), particle.getPosY() - particle.aboveGroundHeight, particle.getPosZ() + (particle.getMotionZ() * aheadMultiplier));\n\t\t\t\t\tBlockState stateAhead = particle.getWorld().getBlockState(posAhead);\n\t\t\t\t\tif (!stateAhead.isAir()) {\n\t\t\t\t\t\tif (particle.getMotionY() < particle.bounceSpeedMaxAhead) {\n\t\t\t\t\t\t\tparticle.setMotionY(particle.getMotionY() +  particle.bounceSpeedAhead);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t//if (particle.isCollidedHorizontally) {\n\t\t\t\t/*if (particle.isCollided()) {\n\t\t\t\t\tparticle.rotationYaw += 0.1;\n\t\t\t\t}*/\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t/*particle.setMotionX(particle.getMotionX() - Math.sin(Math.toRadians((particle.rotationYaw + particle.getEntityId()) % 360)) * moveSpeed);\n\t\t\t\tparticle.setMotionZ(particle.getMotionZ() + Math.cos(Math.toRadians((particle.rotationYaw + particle.getEntityId()) % 360)) * moveSpeed);*/\n\t\t\t\t\n\t\t\t\tdouble moveSpeedRand = 0.005D;\n\t\t\t\t\n\t\t\t\tparticle.setMotionX(particle.getMotionX() + (rand.nextDouble() * moveSpeedRand - rand.nextDouble() * moveSpeedRand));\n\t\t\t\tparticle.setMotionZ(particle.getMotionZ() + (rand.nextDouble() * moveSpeedRand - rand.nextDouble() * moveSpeedRand));\n\t\t\t\t\n\t\t\t\t//TEMPOFF?\n\t\t\t\t//particle.setScale(particle.getScale() - 0.1F);\n\t\t\t\t\n\t\t\t\tif (particle.spawnY != -1) {\n\t\t\t\t\tparticle.setPos(particle.getPosX(), particle.spawnY, particle.getPosZ());\n\t\t\t\t\t//particle.posY = particle.spawnY;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t//particle.remove();\n\t\t\t}\n\t\t//}\n\t}\n}\n"
  },
  {
    "path": "src/main/java/extendedrenderer/particle/behavior/ParticleBehaviors.java",
    "content": "package extendedrenderer.particle.behavior;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Random;\n\nimport com.corosus.coroutil.util.CoroUtilBlock;\nimport com.corosus.coroutil.util.CoroUtilMisc;\nimport extendedrenderer.particle.entity.EntityRotFX;\nimport extendedrenderer.particle.entity.ParticleTexExtraRender;\nimport extendedrenderer.particle.entity.ParticleTexFX;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.client.multiplayer.ClientLevel;\nimport net.minecraft.client.renderer.texture.TextureAtlasSprite;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.entity.player.Player;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.biome.Biome;\nimport net.minecraft.world.phys.Vec3;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport weather2.ClientTickHandler;\nimport weather2.ClientWeatherProxy;\nimport weather2.client.SceneEnhancer;\nimport weather2.datatypes.PrecipitationType;\nimport weather2.util.WeatherUtilParticle;\n\n@OnlyIn(Dist.CLIENT)\npublic class ParticleBehaviors {\n\n\tpublic List<EntityRotFX> particles = new ArrayList<EntityRotFX>();\n\tpublic Vec3 coordSource;\n\tpublic Entity sourceEntity = null;\n\tpublic Random rand = new Random();\n\t\n\t//Visual tweaks\n\tpublic float rateDarken = 0.025F;\n\tpublic float rateBrighten = 0.010F;\n\tpublic float rateBrightenSlower = 0.003F;\n\tpublic float rateAlpha = 0.002F;\n\tpublic float rateScale = 0.1F;\n\tpublic int tickSmokifyTrigger = 40;\n\n\tfloat acidRainRed = 0.5F;\n\tfloat acidRainGreen = 1F;\n\tfloat acidRainBlue = 0.5F;\n\n\tfloat vanillaRainRed = 0.7F;\n\tfloat vanillaRainGreen = 0.7F;\n\tfloat vanillaRainBlue = 1F;\n\t\n\tpublic ParticleBehaviors(Vec3 source) {\n\t\tcoordSource = source;\n\t}\n\t\n\tpublic void tickUpdateList() { //shouldnt be used, particles tick their own method, who removes it though?\n\t\tfor (int i = 0; i < particles.size(); i++) {\n\t\t\tEntityRotFX particle = particles.get(i);\n\t\t\t\n\t\t\tif (!particle.isAlive()) {\n\t\t\t\tparticles.remove(particle);\n\t\t\t} else {\n\t\t\t\ttickUpdate(particle);\n\t\t\t}\n\t\t}\n\t}\n\t\n\tpublic void tickUpdate(EntityRotFX particle) {\n\t\t\n\t\tif (sourceEntity != null) {\n\t\t\tcoordSource = sourceEntity.position();\n\t\t}\n\t\t\n\t\ttickUpdateAct(particle);\n\t}\n\t\n\t//default is smoke effect, override for custom\n\tpublic void tickUpdateAct(EntityRotFX particle) {\n\t\t\n\t\t\t\n\t\tdouble centerX = particle.getPosX();\n\t\t//double centerY = particle.posY;\n\t\tdouble centerZ = particle.getPosZ();\n\t\t\n\t\tif (coordSource != null) {\n\t\t\tcenterX = coordSource.x/* + 0.5D*/;\n\t\t\t//centerY = coordSource.yCoord/* + 0.5D*/;\n\t\t\tcenterZ = coordSource.z/* + 0.5D*/;\n\t\t}\n\t\t\n\t\tdouble vecX = centerX - particle.getPosX();\n\t\tdouble vecZ = centerZ - particle.getPosZ();\n\t\tdouble distToCenter = Math.sqrt(vecX * vecX + vecZ * vecZ);\n\t\tdouble rotYaw = (float)(Math.atan2(vecZ, vecX) * 180.0D / Math.PI);\n\t\tdouble adjYaw = Math.min(360, 45+particle.getAge());\n\t\t\n\t\trotYaw -= adjYaw;\n\t\t//rotYaw -= 90D;\n\t\t//rotYaw += 20D;\n\t\tdouble speed = 0.1D;\n\t\tif (particle.getAge() < 25 && distToCenter > 0.05D) {\n\t\t\tparticle.setMotionX(Math.cos(rotYaw * 0.017453D) * speed);\n\t\t\tparticle.setMotionZ(Math.sin(rotYaw * 0.017453D) * speed);\n\t\t} else {\n\t\t\tdouble speed2 = 0.008D;\n\t\t\t\n\t\t\tdouble pSpeed = Math.sqrt(particle.getMotionX() * particle.getMotionX() + particle.getMotionZ() * particle.getMotionZ());\n\t\t\t\n\t\t\t//cheap air search code\n\t\t\tif (pSpeed < 0.2 && particle.getMotionY() < 0.01) {\n\t\t\t\tspeed2 = 0.08D;\n\t\t\t}\n\t\t\t\n\t\t\tif (pSpeed < 0.002 && Math.abs(particle.getMotionY()) < 0.02) {\n\t\t\t\tparticle.setMotionY(particle.getMotionY() - 0.15D);\n\t\t\t}\n\t\t\t\n\t\t\tparticle.setMotionX(particle.getMotionX() + (rand.nextDouble() - rand.nextDouble()) * speed2);\n\t\t\tparticle.setMotionZ(particle.getMotionZ() + (rand.nextDouble() - rand.nextDouble()) * speed2);\n\t\t\t\n\t\t}\n\t\t\n\t\tfloat brightnessShiftRate = rateDarken;\n\t\t\n\t\tint stateChangeTick = tickSmokifyTrigger;\n\t\t\n\t\tif (particle.getAge() < stateChangeTick) {\n\t\t\tparticle.setGravity(-0.2F);\n\t\t\tparticle.setColor(particle.rCol - brightnessShiftRate, particle.gCol - brightnessShiftRate, particle.bCol - brightnessShiftRate);\n\t\t} else if (particle.getAge() == stateChangeTick) {\n\t\t\tparticle.setColor(0,0,0);\n\t\t} else {\n\t\t\tbrightnessShiftRate = rateBrighten;\n\t\t\tparticle.setGravity(-0.05F);\n\t\t\t//particle.motionY *= 0.99F;\n\t\t\tif (particle.rCol < 0.3F) {\n\t\t\t\t\n\t\t\t} else {\n\t\t\t\tbrightnessShiftRate = rateBrightenSlower;\n\t\t\t}\n\t\t\t\n\t\t\tparticle.setColor(particle.rCol + brightnessShiftRate, particle.gCol + brightnessShiftRate, particle.bCol + brightnessShiftRate);\n\t\t\t\n\t\t\tif (particle.getAlphaF() > 0) {\n\t\t\t\tparticle.setAlpha(particle.getAlphaF() - rateAlpha);\n\t\t\t} else {\n\t\t\t\tparticle.remove();\n\t\t\t}\n\t\t}\n\t\t\n\t\tif (particle.getScale() < 8F) particle.setScale(particle.getScale() + rateScale);\n\t\t\n\t\t/*if (particle.getAge() % cycle < cycle/2) {\n\t\t\tparticle.setGravity(-0.02F);\n\t\t} else {*/\n\t\t\t\n\t\t//}\n\t\t\t\n\t\t\n\t}\n\t\n\tpublic void tickUpdateCloud(EntityRotFX particle) {\n\t\tparticle.rotationYaw -= 0.1;\n\t\t\n\t\tint ticksFadeInMax = 100;\n\t\t\n\t\tif (particle.getAge() < ticksFadeInMax) {\n\t\t\t//System.out.println(\"particle.getAge(): \" + particle.getAge());\n\t\t\tparticle.setAlpha(particle.getAge() * 0.01F);\n\t\t} else {\n\t\t\tif (particle.getAlphaF() > 0) {\n\t\t\t\tparticle.setAlpha(particle.getAlphaF() - rateAlpha*1.3F);\n\t\t\t} else {\n\t\t\t\tparticle.remove();\n\t\t\t}\n\t\t}\n\t}\n\t\n\tpublic EntityRotFX spawnNewParticleIconFX(Level world, TextureAtlasSprite icon, double x, double y, double z, double vecX, double vecY, double vecZ) {\n\t\treturn spawnNewParticleIconFX(world, icon, x, y, z, vecX, vecY, vecZ, 0);\n\t}\n\t\n\tpublic EntityRotFX spawnNewParticleIconFX(Level world, TextureAtlasSprite icon, double x, double y, double z, double vecX, double vecY, double vecZ, int renderOrder) {\n\t\tEntityRotFX entityfx = new ParticleTexFX((ClientLevel) world, x, y, z, vecX, vecY, vecZ, icon);\n\t\tentityfx.pb = this;\n\t\tentityfx.renderOrder = renderOrder;\n\t\treturn entityfx;\n\t}\n\t\n\tpublic EntityRotFX initParticle(EntityRotFX particle) {\n\t\t\n\t\tparticle.setPrevPosX(particle.getPosX());\n\t\tparticle.setPrevPosY(particle.getPosY());\n\t\tparticle.setPrevPosZ(particle.getPosZ());\n\t\t/*particle.prevPosX = particle.getPosX();\n\t\tparticle.prevPosY = particle.getPosY();\n\t\tparticle.prevPosZ = particle.getPosZ();*/\n\t\t\n\t\t//keep AABB small, very important to performance\n\t\tparticle.setSize(0.01F, 0.01F);\n\t\t\n\t\treturn particle;\n\t}\n\n\tpublic void initParticleRain(EntityRotFX particle, int extraRenderCount) {\n\t\tparticle.setKillWhenUnderTopmostBlock(true);\n\t\tparticle.setCanCollide(false);\n\t\tparticle.killWhenUnderCameraAtLeast = 5;\n\t\tparticle.setDontRenderUnderTopmostBlock(true);\n\t\tif (particle instanceof ParticleTexExtraRender) {\n\t\t\t((ParticleTexExtraRender)particle).setExtraParticlesBaseAmount(extraRenderCount);\n\t\t}\n\t\tparticle.fastLight = true;\n\t\tparticle.setSlantParticleToWind(true);\n\t\tparticle.windWeight = 5F;\n\t\tparticle.setFacePlayer(false);\n\t\tparticle.setScale(2F * 0.15F);\n\t\tparticle.isTransparent = true;\n\t\tparticle.setGravity(1.8F);\n\t\tparticle.setLifetime(50);\n\t\tparticle.setTicksFadeInMax(5);\n\t\tparticle.setTicksFadeOutMax(5);\n\t\tparticle.setTicksFadeOutMaxOnDeath(3);\n\t\tparticle.setFullAlphaTarget(0.6F);\n\t\tparticle.setAlpha(0);\n\t\tparticle.rotationYaw = CoroUtilMisc.random.nextInt(360) - 180F;\n\t\tparticle.setMotionY(-0.5D);\n\t\tClientTickHandler.getClientWeather().getWindManager().applyWindForceNew(particle, 10F, 0.5F);\n\t\tPlayer entP = Minecraft.getInstance().player;\n\t\tBiome biome = entP.level().getBiome(new BlockPos(Mth.floor(entP.getX()), (int)Math.floor(entP.getY()), Mth.floor(entP.getZ()))).value();\n\t\tif (ClientWeatherProxy.get().getPrecipitationType(biome) == PrecipitationType.ACID) {\n\t\t\tparticle.rCol = acidRainRed;\n\t\t\tparticle.gCol = acidRainGreen;\n\t\t\tparticle.bCol = acidRainBlue;\n\t\t} else {\n\t\t\tparticle.setFullAlphaTarget(0.8F);\n\t\t\tparticle.rCol = vanillaRainRed;\n\t\t\tparticle.gCol = vanillaRainGreen;\n\t\t\tparticle.bCol = vanillaRainBlue;\n\t\t}\n\t\tparticle.spawnAsWeatherEffect();\n\t}\n\n\tpublic void initParticleGroundSplash(EntityRotFX particle) {\n\t\tparticle.setKillWhenUnderTopmostBlock(true);\n\t\tparticle.setCanCollide(false);\n\t\tparticle.killWhenUnderCameraAtLeast = 5;\n\t\tboolean upward = rand.nextBoolean();\n\t\tparticle.windWeight = 20F;\n\t\tparticle.setFacePlayer(upward);\n\t\tparticle.setScale(0.2F + (rand.nextFloat() * 0.05F));\n\t\tparticle.setLifetime(15);\n\t\tparticle.setGravity(-0.0F);\n\t\tparticle.setTicksFadeInMax(3);\n\t\tparticle.setFullAlphaTarget(0.6F);\n\t\tparticle.setAlpha(0);\n\t\tparticle.setTicksFadeOutMax(4);\n\t\tparticle.renderOrder = 2;\n\t\tparticle.rotationYaw = CoroUtilMisc.random.nextInt(360) - 180F;\n\t\tparticle.rotationPitch = 90;\n\t\tparticle.setMotionY(0D);\n\t\tparticle.setMotionX((rand.nextFloat() - 0.5F) * 0.01F);\n\t\tparticle.setMotionZ((rand.nextFloat() - 0.5F) * 0.01F);\n\t\t//ClientTickHandler.getClientWeather().getWindManager().applyWindForceNew(particle, 1F / 5F, 0.5F);\n\t\tPlayer entP = Minecraft.getInstance().player;\n\t\tBiome biome = entP.level().getBiome(new BlockPos(Mth.floor(entP.getX()), Mth.floor(entP.getY()), Mth.floor(entP.getZ()))).value();\n\t\tif (ClientWeatherProxy.get().getPrecipitationType(biome) == PrecipitationType.ACID) {\n\t\t\tparticle.rCol = acidRainRed;\n\t\t\tparticle.gCol = acidRainGreen;\n\t\t\tparticle.bCol = acidRainBlue;\n\t\t} else {\n\t\t\tparticle.rCol = vanillaRainRed;\n\t\t\tparticle.gCol = vanillaRainGreen;\n\t\t\tparticle.bCol = vanillaRainBlue;\n\t\t}\n\t}\n\n\tpublic void initParticleRainDownfall(EntityRotFX particle) {\n\t\tparticle.setCanCollide(false);\n\t\tparticle.killWhenUnderCameraAtLeast = 15;\n\t\tparticle.setKillWhenUnderTopmostBlock(true);\n\t\tparticle.setKillWhenUnderTopmostBlock_ScanAheadRange(3);\n\t\tparticle.setTicksFadeOutMaxOnDeath(10);\n\t\tparticle.setDontRenderUnderTopmostBlock(false);\n\t\tparticle.windWeight = 5F;\n\t\tparticle.setFacePlayer(false);\n\t\tparticle.facePlayerYaw = true;\n\t\tparticle.setScale(12F + (rand.nextFloat() * 0.3F));\n\t\tparticle.setSize(10, 50);\n\t\tparticle.setLifetime(120);\n\t\tparticle.setGravity(0.35F);\n\t\tparticle.setTicksFadeInMax(20);\n\t\tparticle.setFullAlphaTarget(1F);\n\t\tparticle.setAlpha(0);\n\t\tparticle.setTicksFadeOutMax(10);\n\t\tparticle.rotationYaw = CoroUtilMisc.random.nextInt(360) - 180F;\n\t\tparticle.rotationPitch = 0;\n\t\tparticle.setMotionY(-0.3D);\n\t\tparticle.setMotionX((rand.nextFloat() - 0.5F) * 0.01F);\n\t\tparticle.setMotionZ((rand.nextFloat() - 0.5F) * 0.01F);\n\t\tPlayer entP = Minecraft.getInstance().player;\n\t\tBiome biome = entP.level().getBiome(CoroUtilBlock.blockPos(entP.getX(), entP.getY(), entP.getZ())).get();\n\t\tif (ClientWeatherProxy.get().getPrecipitationType(biome) == PrecipitationType.ACID) {\n\t\t\tparticle.rCol = acidRainRed;\n\t\t\tparticle.gCol = acidRainGreen;\n\t\t\tparticle.bCol = acidRainBlue;\n\t\t} else {\n\t\t\tparticle.rCol = vanillaRainRed;\n\t\t\tparticle.gCol = vanillaRainGreen;\n\t\t\tparticle.bCol = vanillaRainBlue;\n\t\t}\n\t}\n\n\tpublic void initParticleSnow(EntityRotFX particle, int extraRenderCount, float windSpeed) {\n\t\tfloat windScale = Math.max(0.1F, 1F - windSpeed);\n\t\tparticle.setCanCollide(false);\n\t\t//particle.setKillWhenUnderTopmostBlock(true);\n\t\tparticle.setTicksFadeOutMaxOnDeath(5);\n\t\tparticle.setDontRenderUnderTopmostBlock(true);\n\t\tparticle.setKillWhenUnderTopmostBlock(true);\n\t\tif (particle instanceof ParticleTexExtraRender) {\n\t\t\t((ParticleTexExtraRender)particle).setExtraParticlesBaseAmount(extraRenderCount);\n\t\t}\n\t\tparticle.killWhenFarFromCameraAtLeast = 25;\n\t\tparticle.setMotionX(0);\n\t\tparticle.setMotionZ(0);\n\t\tparticle.setMotionY(0);\n\t\tparticle.setScale(1.3F * 0.15F);\n\t\tparticle.setGravity(0.05F);\n\t\tparticle.windWeight = 5F;\n\t\tparticle.setMaxAge((int) (120F * 12F * windScale));\n\t\tparticle.setFacePlayer(true);\n\t\tparticle.setTicksFadeInMax(40 * windScale);\n\t\tparticle.setAlphaF(0);\n\t\tparticle.setTicksFadeOutMax(40 * windScale);\n\t\tparticle.setTicksFadeOutMaxOnDeath(10);\n\t\t//particle.setTicksFadeOutMax(5);\n\t\tparticle.rotationYaw = CoroUtilMisc.random.nextInt(360) - 180F;\n\t\tClientTickHandler.getClientWeather().getWindManager().applyWindForceNew(particle, 1F, 0.5F);\n\t}\n\n\tpublic void initParticleSnowstorm(EntityRotFX particle, int extraRenderCount) {\n\t\tparticle.setCanCollide(false);\n\t\t//particle.setKillWhenUnderTopmostBlock(true);\n\t\tparticle.setTicksFadeOutMaxOnDeath(5);\n\t\tparticle.setDontRenderUnderTopmostBlock(true);\n\t\tif (particle instanceof ParticleTexExtraRender) {\n\t\t\t((ParticleTexExtraRender)particle).setExtraParticlesBaseAmount(extraRenderCount);\n\t\t}\n\t\tparticle.killWhenFarFromCameraAtLeast = 15;\n\t\tparticle.setMotionX(0);\n\t\tparticle.setMotionZ(0);\n\t\tparticle.setMotionY(0D);\n\t\tparticle.setScale(1.3F * 0.15F);\n\t\tparticle.setGravity(0.05F);\n\t\tparticle.windWeight = 5F;\n\t\tparticle.setMaxAge(120);\n\t\tparticle.setFacePlayer(false);\n\t\tparticle.setTicksFadeInMax(5);\n\t\tparticle.setAlphaF(0);\n\t\tparticle.setTicksFadeOutMax(20);\n\t\tparticle.rotationYaw = CoroUtilMisc.random.nextInt(360) - 180F;\n\t\tClientTickHandler.getClientWeather().getWindManager().applyWindForceNew(particle, 1F, 0.5F);\n\t}\n\n\tpublic void initParticleHail(EntityRotFX particle) {\n\t\tparticle.setKillWhenUnderTopmostBlock(false);\n\t\tparticle.setCanCollide(true);\n\t\tparticle.setKillOnCollide(true);\n\t\tparticle.killWhenUnderCameraAtLeast = 5;\n\t\tparticle.setDontRenderUnderTopmostBlock(true);\n\t\tparticle.rotationYaw = rand.nextInt(360);\n\t\tparticle.rotationPitch = rand.nextInt(360);\n\t\tparticle.fastLight = true;\n\t\tparticle.setSlantParticleToWind(true);\n\t\tparticle.windWeight = 5F;\n\t\tparticle.spinFast = true;\n\t\tparticle.spinFastRate = 10F;\n\t\tparticle.setFacePlayer(false);\n\t\tparticle.setScale(0.7F * 0.15F);\n\t\t//particle.setScale(2F * 0.15F);\n\t\tparticle.isTransparent = true;\n\t\tparticle.setGravity(3.5F);\n\t\tparticle.setLifetime(70);\n\t\tparticle.setTicksFadeInMax(5);\n\t\tparticle.setTicksFadeOutMax(5);\n\t\tparticle.setTicksFadeOutMaxOnDeath(50);\n\t\tparticle.setFullAlphaTarget(1F);\n\t\tparticle.setAlpha(0);\n\t\tparticle.rotationYaw = CoroUtilMisc.random.nextInt(360) - 180F;\n\t\tparticle.setMotionY(-0.5D);\n\t\tClientTickHandler.getClientWeather().getWindManager().applyWindForceNew(particle, 1F, 0.5F);\n\t\tparticle.rCol = 0.9F;\n\t\tparticle.gCol = 0.9F;\n\t\tparticle.bCol = 0.9F;\n\t\tparticle.bounceOnVerticalImpact = true;\n\t\tparticle.bounceOnVerticalImpactEnergy = 0.2F;\n\t}\n\n\tpublic void initParticleCube(EntityRotFX particle) {\n\t\tparticle.setKillWhenUnderTopmostBlock(false);\n\t\tparticle.setCanCollide(true);\n\t\tparticle.setKillOnCollide(true);\n\t\tparticle.setKillOnCollideActivateAtAge(30);\n\t\tparticle.killWhenUnderCameraAtLeast = 0;\n\t\tparticle.setDontRenderUnderTopmostBlock(true);\n\t\tparticle.rotationYaw = rand.nextInt(360);\n\t\tparticle.rotationPitch = rand.nextInt(360);\n\t\tparticle.fastLight = true;\n\t\tparticle.windWeight = 5 + ((float)((Math.random() * 0.3) - (Math.random() * 0.3)));\n\t\tparticle.spinFast = true;\n\t\tparticle.spinFastRate = 1F;\n\t\tparticle.setFacePlayer(false);\n\t\tparticle.setScale(3F * 0.15F);\n\t\tparticle.isTransparent = false;\n\t\tparticle.setGravity(4F);\n\t\tparticle.setLifetime(20*20);\n\t\tparticle.setTicksFadeInMax(5);\n\t\tparticle.setTicksFadeOutMax(5);\n\t\tparticle.setTicksFadeOutMaxOnDeath(20);\n\t\tparticle.setFullAlphaTarget(1F);\n\t\tparticle.setAlpha(0);\n\t\tparticle.rotationYaw = CoroUtilMisc.random.nextInt(360) - 180F;\n\t\t//particle.setMotionY(-0.5D);\n\t\t//ClientTickHandler.getClientWeather().getWindManager().applyWindForceNew(particle, 1F, 0.5F);\n\t\t/*float tempBrightness = 0.5F;\n\t\tparticle.rCol = 0.5F * tempBrightness;\n\t\tparticle.gCol = 0.9F * tempBrightness;\n\t\tparticle.bCol = 0.5F * tempBrightness;*/\n\t\tparticle.setVanillaMotionDampen(true);\n\t\tparticle.bounceOnVerticalImpact = true;\n\t\tparticle.bounceOnVerticalImpactEnergy = 0.2F;\n\t}\n\n\tpublic void initParticleDustAir(EntityRotFX particle) {\n\t\tparticle.setKillWhenUnderTopmostBlock(false);\n\t\tparticle.setCanCollide(false);\n\t\tparticle.killWhenUnderCameraAtLeast = 5;\n\t\tparticle.setTicksFadeOutMaxOnDeath(5);\n\t\tparticle.setDontRenderUnderTopmostBlock(true);\n\t\tif (particle instanceof ParticleTexExtraRender) {\n\t\t\t((ParticleTexExtraRender)particle).setExtraParticlesBaseAmount(0);\n\t\t}\n\t\tparticle.setMotionX(0);\n\t\tparticle.setMotionZ(0);\n\t\tparticle.setMotionY(0);\n\t\tparticle.fastLight = true;\n\t\tparticle.windWeight = 10F;\n\t\tparticle.setFacePlayer(true);\n\t\tparticle.setScale(0.1F * 0.15F);\n\t\tparticle.isTransparent = true;\n\t\tparticle.setGravity(0F);\n\t\tparticle.setLifetime(80);\n\t\tparticle.setTicksFadeInMax(20);\n\t\tparticle.setTicksFadeOutMax(20);\n\t\tparticle.setTicksFadeOutMaxOnDeath(20);\n\t\tparticle.setFullAlphaTarget(0.6F);\n\t\tparticle.setAlpha(0);\n\t\tfloat brightness = 0.5F + (rand.nextFloat() * 0.5F);\n\t\tparticle.setColor(particle.rCol * brightness, particle.gCol * brightness, particle.bCol * brightness);\n\t\tparticle.rotationYaw = CoroUtilMisc.random.nextInt(360) - 180F;\n\t\t//ClientTickHandler.getClientWeather().getWindManager().applyWindForceNew(particle, 10F, 0.5F);\n\t}\n\n\tpublic void initParticleDustGround(EntityRotFX particle, boolean spawnInside, boolean spawnAboveSnow) {\n\t\tparticle.setKillOnCollide(false);\n\t\tparticle.setKillWhenUnderTopmostBlock(false);\n\t\tparticle.killWhenUnderCameraAtLeast = 5;\n\t\tparticle.setDontRenderUnderTopmostBlock(false);\n\t\tparticle.setMotionX(0);\n\t\tparticle.setMotionZ(0);\n\t\tparticle.setMotionY(0);\n\t\tparticle.fastLight = true;\n\t\tparticle.windWeight = 1F;\n\t\tparticle.setFacePlayer(true);\n\t\tparticle.setScale(0.15F * 0.15F);\n\t\tparticle.isTransparent = true;\n\t\tparticle.setGravity(0.06F);\n\t\tparticle.setCanCollide(false);\n\t\tparticle.setCanCollide(true);\n\t\tparticle.collisionSpeedDampen = false;\n\t\t/*if (spawnInside) {\n\t\t\tparticle.setGravity(0.05F);\n\t\t\tparticle.setCanCollide(true);\n\t\t}*/\n\t\tparticle.setLifetime(30);\n\t\tparticle.setTicksFadeInMax(5);\n\t\tparticle.setTicksFadeOutMax(5);\n\t\tparticle.setTicksFadeOutMaxOnDeath(5);\n\t\tparticle.setFullAlphaTarget(0.6F);\n\t\tparticle.setAlpha(0);\n\t\tif (spawnAboveSnow || !spawnInside) {\n\t\t\tfloat brightness = 0.5F;\n\t\t\tparticle.setColor(particle.rCol * brightness, particle.gCol * brightness, particle.bCol * brightness);\n\t\t}\n\t\tparticle.rotationYaw = CoroUtilMisc.random.nextInt(360) - 180F;\n\t}\n\n\tpublic void initParticleLeaf(EntityRotFX particle, float particleAABB) {\n\t\tVec3 windForce = ClientTickHandler.getClientWeather().getWindManager().getWindForce(WeatherUtilParticle.getPos(particle));\n\t\tparticle.setMotionX(windForce.x / 2);\n\t\tparticle.setMotionZ(windForce.z / 2);\n\t\tparticle.setMotionY(windForce.y / 2);\n\t\tparticle.setSize(particleAABB, particleAABB);\n\t\tparticle.setGravity(0.05F);\n\t\tparticle.setCanCollide(true);\n\t\tparticle.setKillOnCollide(false);\n\t\tparticle.collisionSpeedDampen = false;\n\t\tparticle.killWhenUnderCameraAtLeast = 20;\n\t\tparticle.killWhenFarFromCameraAtLeast = 20;\n\t\tparticle.isTransparent = false;\n\t\tparticle.rotationYaw = rand.nextInt(360);\n\t\tparticle.rotationPitch = rand.nextInt(360);\n\t}\n\n\tpublic void initParticleSnowstormCloudDust(EntityRotFX particle) {\n\t\tboolean farSpawn = Minecraft.getInstance().player.isSpectator() || !SceneEnhancer.isPlayerOutside;\n\t\tVec3 windForce = ClientTickHandler.getClientWeather().getWindManager().getWindForce(null);\n\t\tparticle.setMotionX(windForce.x * 0.3);\n\t\tparticle.setMotionZ(windForce.z * 0.3);\n\t\tparticle.setFacePlayer(false);\n\t\tparticle.isTransparent = true;\n\t\tparticle.rotationYaw = (float)rand.nextInt(360);\n\t\tparticle.rotationPitch = (float)rand.nextInt(360);\n\t\tparticle.setLifetime(farSpawn ? 30 : 10);\n\t\tparticle.setLifetime(20);\n\t\tparticle.setGravity(0.09F);\n\t\tparticle.setAlpha(0F);\n\t\tfloat brightnessMulti = 1F - (rand.nextFloat() * 0.4F);\n\t\tparticle.setColor(1F * brightnessMulti, 1F * brightnessMulti, 1F * brightnessMulti);\n\t\tparticle.setScale(30 * 0.15F);\n\t\tparticle.aboveGroundHeight = 0.2D;\n\t\tparticle.setKillOnCollide(true);\n\t\tparticle.killWhenFarFromCameraAtLeast = 15;\n\t\tparticle.windWeight = 1F;\n\t\tparticle.setTicksFadeInMax(5);\n\t\tparticle.setTicksFadeOutMax(3);\n\t\tparticle.setTicksFadeOutMaxOnDeath(3);\n\t\tClientTickHandler.getClientWeather().getWindManager().applyWindForceNew(particle, 1F / 5F, 0.5F);\n\t}\n\n\tpublic void initParticleSandstormDust(EntityRotFX particle) {\n\t\tVec3 windForce = ClientTickHandler.getClientWeather().getWindManager().getWindForce(null);\n\t\tparticle.setMotionX(windForce.x);\n\t\tparticle.setMotionZ(windForce.z);\n\t\tparticle.setFacePlayer(false);\n\t\tparticle.isTransparent = true;\n\t\tparticle.rotationYaw = (float)rand.nextInt(360);\n\t\tparticle.rotationPitch = (float)rand.nextInt(360);\n\t\tparticle.setLifetime(40);\n\t\tparticle.setGravity(0.09F);\n\t\tparticle.setAlpha(0F);\n\t\tfloat brightnessMulti = 1F - (rand.nextFloat() * 0.5F);\n\t\tparticle.setColor(0.65F * brightnessMulti, 0.6F * brightnessMulti, 0.3F * brightnessMulti);\n\t\tparticle.setScale(40 * 0.15F);\n\t\tparticle.aboveGroundHeight = 0.2D;\n\t\tparticle.setKillOnCollide(true);\n\t\tparticle.killWhenFarFromCameraAtLeast = 15;\n\t\tparticle.setTicksFadeInMax(5);\n\t\tparticle.setTicksFadeOutMax(5);\n\t\tparticle.setTicksFadeOutMaxOnDeath(5);\n\t\tparticle.windWeight = 1F;\n\t}\n\n\tpublic void initParticleSandstormTumbleweed(EntityRotFX particle) {\n\t\tVec3 windForce = ClientTickHandler.getClientWeather().getWindManager().getWindForce(null);\n\t\tparticle.setMotionX(windForce.x);\n\t\tparticle.setMotionZ(windForce.z);\n\t\tparticle.setFacePlayer(false);\n\t\tparticle.facePlayerYaw = false;\n\t\tparticle.spinTowardsMotionDirection = true;\n\t\tparticle.isTransparent = true;\n\t\tparticle.rotationYaw = (float)rand.nextInt(360);\n\t\tparticle.rotationPitch = (float)rand.nextInt(360);\n\t\tparticle.setLifetime(80);\n\t\tparticle.setGravity(0.3F);\n\t\tparticle.setAlpha(0F);\n\t\tfloat brightnessMulti = 1F - (rand.nextFloat() * 0.2F);\n\t\tparticle.setColor(1F * brightnessMulti, 1F * brightnessMulti, 1F * brightnessMulti);\n\t\tparticle.setScale(8 * 0.15F);\n\t\tparticle.aboveGroundHeight = 0.5D;\n\t\tparticle.collisionSpeedDampen = false;\n\t\tparticle.bounceSpeed = 0.03D;\n\t\tparticle.bounceSpeedAhead = 0.03D;\n\t\tparticle.setKillOnCollide(false);\n\t\tparticle.killWhenFarFromCameraAtLeast = 30;\n\t\tparticle.setTicksFadeInMax(5);\n\t\tparticle.setTicksFadeOutMax(5);\n\t\tparticle.setTicksFadeOutMaxOnDeath(5);\n\t\tparticle.windWeight = 1F;\n\t}\n\n\tpublic void initParticleSandstormDebris(EntityRotFX particle) {\n\t\tVec3 windForce = ClientTickHandler.getClientWeather().getWindManager().getWindForce(null);\n\t\tparticle.setMotionX(windForce.x);\n\t\tparticle.setMotionZ(windForce.z);\n\t\tparticle.setFacePlayer(false);\n\t\tparticle.spinFast = true;\n\t\tparticle.spinFastRate = 2F;\n\t\tparticle.isTransparent = true;\n\t\tparticle.rotationYaw = (float)rand.nextInt(360);\n\t\tparticle.rotationPitch = (float)rand.nextInt(360);\n\t\tparticle.setLifetime(80);\n\t\tparticle.setGravity(0.3F);\n\t\tparticle.setAlpha(0F);\n\t\tfloat brightnessMulti = 1F - (rand.nextFloat() * 0.5F);\n\t\tparticle.setColor(1F * brightnessMulti, 1F * brightnessMulti, 1F * brightnessMulti);\n\t\tparticle.setScale(8 * 0.15F);\n\t\tparticle.aboveGroundHeight = 0.5D;\n\t\tparticle.collisionSpeedDampen = false;\n\t\tparticle.bounceSpeed = 0.03D;\n\t\tparticle.bounceSpeedAhead = 0.03D;\n\t\tparticle.setKillOnCollide(false);\n\t\tparticle.killWhenFarFromCameraAtLeast = 30;\n\t\tparticle.setTicksFadeInMax(5);\n\t\tparticle.setTicksFadeOutMax(5);\n\t\tparticle.setTicksFadeOutMaxOnDeath(5);\n\t\tparticle.windWeight = 1F;\n\t}\n\t\n\tpublic static EntityRotFX setParticleRandoms(EntityRotFX particle, boolean yaw, boolean pitch) {\n\t\tRandom rand = new Random();\n\t\tif (yaw) particle.rotationYaw = rand.nextInt(360);\n\t\tif (pitch) particle.rotationPitch = rand.nextInt(360);\n\t\treturn particle;\n\t}\n\t\n\tpublic static EntityRotFX setParticleFire(EntityRotFX particle) {\n\t\tRandom rand = new Random();\n\t\tparticle.setColor(0.6F + (rand.nextFloat() * 0.4F), 0.2F + (rand.nextFloat() * 0.2F), 0);\n\t\tparticle.setScale(0.25F + 0.2F * rand.nextFloat());\n\t\tparticle.brightness = 1F;\n\t\tparticle.setSize(0.1F, 0.1F);\n\t\tparticle.setAlpha(0.6F);\n\t\treturn particle;\n\t}\n\t\n\tpublic static EntityRotFX setParticleCloud(EntityRotFX particle, float freezeY) {\n\t\tparticle.spawnY = freezeY;\n\t\tparticle.rotationPitch = 90F;\n\t\t//particle.renderDistanceWeight = 999D;\n\t\t//1.10.2 no known replacement for above\n        //particle.noClip = true;\n\t\tparticle.setCanCollide(false);\n        particle.setSize(0.25F, 0.25F);\n        particle.setScale(500F);\n        //particle.particleScale = 200F;\n        particle.callUpdateSuper = false;\n        particle.callUpdatePB = false;\n        particle.setLifetime(500);\n        particle.setColor(1F, 1F, 1F);\n        particle.brightness = 0.3F;//- ((200F - particle.spawnY) * 0.05F);\n        particle.renderRange = 999F;\n        particle.setAlpha(0F);\n\t\treturn particle;\n\t}\n\t\n\tpublic void cleanup() {\n\t\t\n\t}\n\t\n}\n"
  },
  {
    "path": "src/main/java/extendedrenderer/particle/entity/DustEmitter.java",
    "content": "package extendedrenderer.particle.entity;\n\nimport extendedrenderer.particle.ParticleRegistry;\nimport net.minecraft.client.multiplayer.ClientLevel;\nimport weather2.client.SceneEnhancer;\n\npublic class DustEmitter extends ParticleEmitter {\n    public DustEmitter(ClientLevel par1World, double par2, double par4, double par6, double par8, double par10, double par12) {\n        super(par1World, par2, par4, par6, par8, par10, par12);\n    }\n\n    @Override\n    public void tick() {\n        super.tick();\n\n        ParticleTexExtraRender dust = new ParticleTexExtraRender(level,\n                x,\n                y,\n                z,\n                0D, 0D, 0D, ParticleRegistry.squareGrey);\n        SceneEnhancer.particleBehavior.initParticleDustAir(dust);\n\n        dust.spawnAsWeatherEffect();\n    }\n}\n"
  },
  {
    "path": "src/main/java/extendedrenderer/particle/entity/EntityRotFX.java",
    "content": "package extendedrenderer.particle.entity;\n\nimport com.corosus.coroutil.util.CoroUtilBlock;\nimport com.corosus.coroutil.util.CoroUtilMisc;\nimport com.mojang.blaze3d.systems.RenderSystem;\nimport com.mojang.blaze3d.vertex.*;\nimport com.mojang.math.Axis;\nimport extendedrenderer.particle.behavior.ParticleBehaviors;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.client.particle.ParticleRenderType;\nimport net.minecraft.client.particle.TextureSheetParticle;\nimport net.minecraft.client.Camera;\nimport net.minecraft.client.renderer.GameRenderer;\nimport net.minecraft.client.renderer.texture.TextureAtlas;\nimport net.minecraft.client.renderer.texture.TextureAtlasSprite;\nimport net.minecraft.client.renderer.texture.TextureManager;\nimport net.minecraft.client.multiplayer.ClientLevel;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.phys.AABB;\nimport net.minecraft.world.phys.Vec3;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.levelgen.Heightmap;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport org.joml.Quaternionf;\nimport org.joml.Vector3f;\nimport weather2.ClientTickHandler;\nimport weather2.IWindHandler;\nimport weather2.config.ConfigParticle;\nimport weather2.weathersystem.WeatherManagerClient;\nimport weather2.weathersystem.wind.WindManager;\n\nimport java.util.List;\nimport java.util.stream.Stream;\n\n@OnlyIn(Dist.CLIENT)\npublic class EntityRotFX extends TextureSheetParticle implements IWindHandler\n{\n    public static final ParticleRenderType SORTED_TRANSLUCENT = new ParticleRenderType() {\n\n        @Override\n        public void begin(BufferBuilder p_217600_1_, TextureManager p_217600_2_) {\n            RenderSystem.disableCull();\n            ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT.begin(p_217600_1_, p_217600_2_);\n        }\n\n        @Override\n        public void end(Tesselator p_217599_1_) {\n            //TODO: not possible in 1.20 now i guess, cant remember why this line was important\n            //p_217599_1_.getBuilder().setQuadSortOrigin(0, 0, 0);\n            ParticleRenderType.PARTICLE_SHEET_TRANSLUCENT.end(p_217599_1_);\n        }\n\n        @Override\n        public String toString() {\n            return \"PARTICLE_SHEET_SORTED_TRANSLUCENT\";\n        }\n    };\n    public static final ParticleRenderType SORTED_OPAQUE_BLOCK = new ParticleRenderType() {\n\n        @Override\n        public void begin(BufferBuilder p_217600_1_, TextureManager p_217600_2_) {\n            RenderSystem.disableBlend();\n            RenderSystem.depthMask(true);\n            RenderSystem.setShader(GameRenderer::getParticleShader);\n            RenderSystem.setShaderTexture(0, TextureAtlas.LOCATION_BLOCKS);\n            p_217600_1_.begin(VertexFormat.Mode.QUADS, DefaultVertexFormat.PARTICLE);\n        }\n\n        @Override\n        public void end(Tesselator p_217599_1_) {\n            //TODO: not possible in 1.20 now i guess, cant remember why this line was important\n            //p_217599_1_.getBuilder().setQuadSortOrigin(0, 0, 0);\n            ParticleRenderType.PARTICLE_SHEET_OPAQUE.end(p_217599_1_);\n        }\n\n        @Override\n        public String toString() {\n            return \"PARTICLE_BLOCK_SHEET_SORTED_OPAQUE\";\n        }\n    };\n    public boolean weatherEffect = false;\n\n    public float spawnY = -1;\n\n    //this field and 2 methods below are for backwards compatibility with old particle system from the new icon based system\n    public int particleTextureIndexInt = 0;\n\n    public float brightness = 0.7F;\n\n    public boolean callUpdateSuper = true;\n    public boolean callUpdatePB = true;\n\n    public float renderRange = 128F;\n\n    //used in RotatingEffectRenderer to assist in solving some transparency ordering issues, eg, tornado funnel before clouds\n    public int renderOrder = 0;\n\n    //not a real entity ID now, just used for making rendering of entities slightly unique\n    private int entityID = 0;\n\n    public int debugID = 0;\n\n    public float prevRotationYaw;\n    public float rotationYaw;\n    public float prevRotationPitch;\n    public float rotationPitch;\n\n    public float windWeight = 5;\n    public boolean isTransparent = true;\n\n    public boolean killOnCollide = false;\n    public int killOnCollideActivateAtAge = 0;\n\n    public boolean facePlayer = false;\n\n    //facePlayer will override this\n    public boolean facePlayerYaw = false;\n\n    public boolean vanillaMotionDampen = true;\n\n    //for particle behaviors\n    public double aboveGroundHeight = 4.5D;\n    public boolean checkAheadToBounce = true;\n    public boolean collisionSpeedDampen = true;\n\n    public double bounceSpeed = 0.05D;\n    public double bounceSpeedMax = 0.15D;\n    public double bounceSpeedAhead = 0.35D;\n    public double bounceSpeedMaxAhead = 0.25D;\n\n    public boolean bounceOnVerticalImpact = false;\n    public double bounceOnVerticalImpactEnergy = 0.3F;\n\n    public boolean spinFast = false;\n    public float spinFastRate = 10F;\n    public boolean spinTowardsMotionDirection = false;\n\n    private float ticksFadeInMax = 0;\n    private float ticksFadeOutMax = 0;\n\n    private float fullAlphaTarget = 1F;\n\n    private boolean dontRenderUnderTopmostBlock = false;\n\n    private boolean killWhenUnderTopmostBlock = false;\n    private int killWhenUnderTopmostBlock_ScanAheadRange = 0;\n\n    public int killWhenUnderCameraAtLeast = 0;\n\n    public int killWhenFarFromCameraAtLeast = 0;\n\n    private float ticksFadeOutMaxOnDeath = -1;\n    private float ticksFadeOutCurOnDeath = 0;\n    protected boolean fadingOut = false;\n\n    public float avoidTerrainAngle = 0;\n\n    //this is for yaw only\n    public boolean useRotationAroundCenter = false;\n    public float rotationAroundCenter = 0;\n    public float rotationAroundCenterPrev = 0;\n    public float rotationSpeedAroundCenter = 0;\n    public float rotationDistAroundCenter = 0;\n\n    private boolean slantParticleToWind = false;\n\n    /*public Quaternion rotation;\n    public Quaternion rotationPrev;*/\n\n    //set to true for direct quaternion control, not EULER conversion helper\n    public boolean quatControl = false;\n\n    public boolean fastLight = false;\n\n    public float brightnessCache = 0.5F;\n\n    public boolean rotateOrderXY = false;\n\n    public float extraYRotation = 0;\n\n    public boolean markCollided = false;\n\n    public boolean isCollidedHorizontally = false;\n    public boolean isCollidedVerticallyDownwards = false;\n    public boolean isCollidedVerticallyUpwards = false;\n\n    //used for translational rotation around a point\n    public Vector3f rotationAround = new Vector3f();\n\n    //workaround for particles that are fading out while partially in the ground, keeps them rendering at previous brightness instead of 0\n    protected int lastNonZeroBrightness = 15728640;\n\n    public ParticleBehaviors pb = null; //designed to be a reference to the central objects particle behavior\n\n    //workaround for avoiding using vanilla bb which causes huge performance issues for large sizes\n    private boolean useCustomBBForRenderCulling = false;\n    private static final AABB INITIAL_AABB = new AABB(0.0D, 0.0D, 0.0D, 0.0D, 0.0D, 0.0D);\n    private AABB bbRender = INITIAL_AABB;\n    private float renderDistanceCull = -1;\n    private boolean useDynamicWindSpeed = true;\n\n    public EntityRotFX(ClientLevel par1World, double par2, double par4, double par6, double par8, double par10, double par12)\n    {\n        super(par1World, par2, par4, par6, par8, par10, par12);\n        setSize(0.3F, 0.3F);\n        //this.isImmuneToFire = true;\n        //this.setMaxAge(100);\n\n        this.entityID = CoroUtilMisc.random.nextInt(100000);\n\n        //rotation = new Quaternion();\n\n        //TODO: 1.14 uncomment for shaders\n        //brightnessCache = CoroUtilBlockLightCache.getBrightnessCached(world, (float)posX, (float)posY, (float)posZ);\n    }\n\n    public boolean isSlantParticleToWind() {\n        return slantParticleToWind;\n    }\n\n    public void setSlantParticleToWind(boolean slantParticleToWind) {\n        this.slantParticleToWind = slantParticleToWind;\n    }\n\n    public float getTicksFadeOutMaxOnDeath() {\n        return ticksFadeOutMaxOnDeath;\n    }\n\n    public void setTicksFadeOutMaxOnDeath(float ticksFadeOutMaxOnDeath) {\n        this.ticksFadeOutMaxOnDeath = ticksFadeOutMaxOnDeath;\n    }\n\n    public boolean isKillWhenUnderTopmostBlock() {\n        return killWhenUnderTopmostBlock;\n    }\n\n    public void setKillWhenUnderTopmostBlock(boolean killWhenUnderTopmostBlock) {\n        this.killWhenUnderTopmostBlock = killWhenUnderTopmostBlock;\n    }\n\n    public boolean isDontRenderUnderTopmostBlock() {\n        return dontRenderUnderTopmostBlock;\n    }\n\n    public void setDontRenderUnderTopmostBlock(boolean dontRenderUnderTopmostBlock) {\n        this.dontRenderUnderTopmostBlock = dontRenderUnderTopmostBlock;\n    }\n\n    public float getTicksFadeInMax() {\n        return ticksFadeInMax;\n    }\n\n    public void setTicksFadeInMax(float ticksFadeInMax) {\n        this.ticksFadeInMax = ticksFadeInMax;\n    }\n\n    public float getTicksFadeOutMax() {\n        return ticksFadeOutMax;\n    }\n\n    public void setTicksFadeOutMax(float ticksFadeOutMax) {\n        this.ticksFadeOutMax = ticksFadeOutMax;\n    }\n\n    public int getParticleTextureIndex()\n    {\n        return this.particleTextureIndexInt;\n    }\n\n    public void setLifetime(int par) {\n        lifetime = par;\n    }\n\n    public float getAlphaF()\n    {\n        return this.alpha;\n    }\n\n    @Override\n    public void remove() {\n        if (pb != null) pb.particles.remove(this);\n        super.remove();\n    }\n\n    @Override\n    public void tick() {\n        super.tick();\n        this.prevRotationPitch = this.rotationPitch;\n        if (!(this instanceof PivotingParticle)) {\n            this.prevRotationYaw = this.rotationYaw;\n        }\n\n        Entity ent = Minecraft.getInstance().getCameraEntity();\n\n        //if (this.entityID % 400 == 0) System.out.println(\"tick time: \" + this.worldObj.getGameTime());\n\n        if (!isVanillaMotionDampen()) {\n            //cancel motion dampening (which is basically air resistance)\n            //keep this up to date with the inverse of whatever Particle.tick uses\n            this.xd /= 0.9800000190734863D;\n            this.yd /= 0.9800000190734863D;\n            this.zd /= 0.9800000190734863D;\n        }\n\n        if (!this.removed && !fadingOut) {\n            if (killOnCollide && (killOnCollideActivateAtAge == 0 || age >= killOnCollideActivateAtAge)) {\n                if (this.isCollided()) {\n                    startDeath();\n                }\n\n            }\n\n            BlockPos pos = CoroUtilBlock.blockPos(this.x, this.y, this.z);\n\n            if (killWhenUnderTopmostBlock) {\n\n\n                //int height = this.world.getPrecipitationHeight(new BlockPos(this.posX, this.posY, this.posZ)).getY();\n                int height = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos).getY();\n                if (this.y - killWhenUnderTopmostBlock_ScanAheadRange <= height) {\n                    startDeath();\n                }\n            }\n\n            //case: when on high pillar and rain is falling far below you, start killing it / fading it out\n            if (killWhenUnderCameraAtLeast != 0) {\n                if (this.y < ent.getY() - killWhenUnderCameraAtLeast) {\n                    startDeath();\n                }\n            }\n\n            if (killWhenFarFromCameraAtLeast != 0) {\n                if (getAge() > 20 && getAge() % 5 == 0) {\n\n                    if (ent.distanceToSqr(this.x, this.y, this.z) > killWhenFarFromCameraAtLeast * killWhenFarFromCameraAtLeast) {\n                        //System.out.println(\"far kill\");\n                        startDeath();\n                    }\n                }\n            }\n        }\n\n        if (!collisionSpeedDampen) {\n            //if (this.isCollided()) {\n            if (this.onGround) {\n                this.xd /= 0.699999988079071D;\n                this.zd /= 0.699999988079071D;\n            }\n        }\n\n        double speedXZ = Math.sqrt(getMotionX() * getMotionX() + /*getMotionY() * getMotionY() + */getMotionZ() * getMotionZ());\n        double spinFastRateAdj = spinFastRate * speedXZ * 10F;\n        //spinFastRateAdj = 0;\n\n        if (spinFast) {\n            this.rotationPitch += this.entityID % 2 == 0 ? spinFastRateAdj : -spinFastRateAdj;\n            this.rotationYaw += this.entityID % 2 == 0 ? -spinFastRateAdj : spinFastRateAdj;\n        }\n\n        float angleToMovement = (float) (Math.toDegrees(Math.atan2(xd, zd)));\n\n        if (spinTowardsMotionDirection) {\n            this.rotationYaw = angleToMovement;\n            this.rotationPitch += spinFastRate;\n        }\n\n        if (!fadingOut) {\n            if (ticksFadeInMax > 0 && this.getAge() < ticksFadeInMax) {\n                //System.out.println(\"this.getAge() / ticksFadeInMax: \" + this.getAge() / ticksFadeInMax);\n                this.setAlpha((float)this.getAge() / ticksFadeInMax * getFullAlphaTarget());\n                //this.setAlphaF(0.15F);\n            } else if (ticksFadeOutMax > 0 && this.getAge() > this.getLifetime() - ticksFadeOutMax) {\n                float count = this.getAge() - (this.getLifetime() - ticksFadeOutMax);\n                float val = (ticksFadeOutMax - (count)) / ticksFadeOutMax;\n                //System.out.println(val);\n                this.setAlpha(val * getFullAlphaTarget());\n                //make sure fully visible otherwise\n            } else if (ticksFadeInMax > 0 || ticksFadeOutMax > 0) {\n                this.setAlpha(getFullAlphaTarget());\n            }\n        } else {\n            if (ticksFadeOutCurOnDeath < ticksFadeOutMaxOnDeath) {\n                ticksFadeOutCurOnDeath++;\n            } else {\n                this.remove();\n            }\n            float val = 1F - (ticksFadeOutCurOnDeath / ticksFadeOutMaxOnDeath);\n            //System.out.println(val);\n            this.setAlpha(val * getFullAlphaTarget());\n        }\n\n        if (level.getGameTime() % 5 == 0) {\n            //TODO: 1.14 uncomment\n            //brightnessCache = CoroUtilBlockLightCache.getBrightnessCached(world, (float)posX, (float)posY, (float)posZ);\n        }\n\n        rotationAroundCenter += rotationSpeedAroundCenter;\n        rotationAroundCenter %= 360;\n        /*while (rotationAroundCenter >= 360) {\n            System.out.println(rotationAroundCenter);\n            rotationAroundCenter -= 360;\n        }*/\n\n        tickExtraRotations();\n    }\n\n    public void tickExtraRotations() {\n        if (slantParticleToWind) {\n            double motionXZ = Math.sqrt(xd * xd + zd * zd);\n            rotationPitch = (float)Math.atan2(yd, motionXZ);\n        }\n\n        WeatherManagerClient weatherMan = ClientTickHandler.weatherManager;\n        if (weatherMan == null) return;\n        WindManager windMan = weatherMan.getWindManager();\n        if (windMan == null) return;\n        if (this instanceof PivotingParticle) return;\n        //particles on ground shouldnt get blown as hard (idea for hail)\n        if (onGround) {\n            windMan.applyWindForceNew(this, (1F / 20F) * 0.3F, 0.5F, useDynamicWindSpeed);\n        } else {\n            windMan.applyWindForceNew(this, 1F / 20F, 0.5F, useDynamicWindSpeed);\n        }\n\n        /*if (!quatControl) {\n            rotationPrev = new Quaternion(rotation);\n            Entity ent = Minecraft.getInstance().getRenderViewEntity();\n            updateQuaternion(ent);\n        }*/\n    }\n\n    public void startDeath() {\n        if (ticksFadeOutMaxOnDeath > 0) {\n            ticksFadeOutCurOnDeath = 0;//ticksFadeOutMaxOnDeath;\n            fadingOut = true;\n        } else {\n            this.remove();\n        }\n    }\n    \n    /*public void setParticleTextureIndex(int par1)\n    {\n        this.particleTextureIndexInt = par1;\n        if (this.getFXLayer() == 0) super.setParticleTextureIndex(par1);\n    }*/\n\n    /*@Override\n    public int getFXLayer()\n    {\n        return 5;\n    }*/\n\n    public void spawnAsWeatherEffect()\n    {\n        weatherEffect = true;\n        if (ConfigParticle.Particle_engine_weather2) {\n            ClientTickHandler.particleManagerExtended().add(this);\n        } else {\n            Minecraft.getInstance().particleEngine.add(this);\n        }\n    }\n\n    public int getAge()\n    {\n        return age;\n    }\n\n    public void setAge(int age)\n    {\n        this.age = age;\n    }\n\n    public int getLifetime()\n    {\n        return lifetime;\n    }\n\n    public void setSize(float par1, float par2)\n    {\n        super.setSize(par1, par2);\n        // MC-12269 - fix particle being offset to the NW\n        this.setPos(x, y, z);\n    }\n\n    public void setGravity(float par) {\n        gravity = par;\n    }\n\n    public float maxRenderRange() {\n        return renderRange;\n    }\n\n    public void setScale(float parScale) {\n        //dont set the AABB as big as the render scale, otherwise huge performance losses, we'll just use 0.3 in constructor for now\n        //super.setSize(parScale, parScale);\n        this.setSizeForRenderCulling(parScale, parScale);\n        quadSize = parScale;\n    }\n\n    public Vector3f getPosition() {\n        return new Vector3f((float)x, (float)y, (float)z);\n    }\n\n    /*@Override\n    public Quaternion getQuaternion() {\n        return this.rotation;\n    }\n\n    @Override\n    public Quaternion getQuaternionPrev() {\n        return this.rotationPrev;\n    }*/\n\n    public float getScale() {\n        return quadSize;\n    }\n\n    public Vec3 getPos() {\n        return new Vec3(x, y, z);\n    }\n\n    public double getPosX() {\n        return x;\n    }\n\n    public void setPosX(double posX) {\n        this.x = posX;\n    }\n\n    public double getPosY() {\n        return y;\n    }\n\n    public void setPosY(double posY) {\n        this.y = posY;\n    }\n\n    public double getPosZ() {\n        return z;\n    }\n\n    public void setPosZ(double posZ) {\n        this.z = posZ;\n    }\n\n    public double getMotionX() {\n        return xd;\n    }\n\n    public void setMotionX(double motionX) {\n        this.xd = motionX;\n    }\n\n    public double getMotionY() {\n        return yd;\n    }\n\n    public void setMotionY(double motionY) {\n        this.yd = motionY;\n    }\n\n    public double getMotionZ() {\n        return zd;\n    }\n\n    public void setMotionZ(double motionZ) {\n        this.zd = motionZ;\n    }\n\n    public double getPrevPosX() {\n        return xo;\n    }\n\n    public void setPrevPosX(double prevPosX) {\n        this.xo = prevPosX;\n    }\n\n    public double getPrevPosY() {\n        return yo;\n    }\n\n    public void setPrevPosY(double prevPosY) {\n        this.yo = prevPosY;\n    }\n\n    public double getPrevPosZ() {\n        return zo;\n    }\n\n    public void setPrevPosZ(double prevPosZ) {\n        this.zo = prevPosZ;\n    }\n\n    public int getEntityId() {\n        return entityID;\n    }\n\n    public Level getWorld() {\n        return this.level;\n    }\n\n    public void setCanCollide(boolean val) {\n        this.hasPhysics = val;\n    }\n\n    public boolean getCanCollide() {\n        return this.hasPhysics;\n    }\n\n    public boolean isCollided() {\n        return this.onGround || isCollidedHorizontally;\n    }\n\n    public double getDistance(double x, double y, double z)\n    {\n        double d0 = this.x - x;\n        double d1 = this.y - y;\n        double d2 = this.z - z;\n        return Mth.sqrt((float) (d0 * d0 + d1 * d1 + d2 * d2));\n    }\n\n    @Override\n    public float getQuadSize(float scaleFactor) {\n        return this.quadSize;\n    }\n\n    @Override\n    public void render(VertexConsumer buffer, Camera renderInfo, float partialTicks) {\n\n        Vec3 Vector3d = renderInfo.getPosition();\n        Vec3 pivotedPosition = getPivotedPosition(partialTicks);\n        float f = (float)(Mth.lerp(partialTicks, this.xo, this.x) + pivotedPosition.x - Vector3d.x());\n        float f1 = (float)(Mth.lerp(partialTicks, this.yo, this.y) + pivotedPosition.y - Vector3d.y());\n        float f2 = (float)(Mth.lerp(partialTicks, this.zo, this.z) + pivotedPosition.z - Vector3d.z());\n        Quaternionf quaternion;\n        if (this.facePlayer || (this.rotationPitch == 0 && this.rotationYaw == 0)) {\n            quaternion = renderInfo.rotation();\n        } else {\n            // override rotations\n            quaternion = new Quaternionf(0, 0, 0, 1);\n            if (facePlayerYaw) {\n                quaternion.mul(Axis.YP.rotationDegrees(-renderInfo.getYRot()));\n            } else {\n                quaternion.mul(Axis.YP.rotationDegrees(Mth.lerp(partialTicks, this.prevRotationYaw, rotationYaw)));\n            }\n            quaternion.mul(Axis.XP.rotationDegrees(Mth.lerp(partialTicks, this.prevRotationPitch, rotationPitch)));\n        }\n\n        Quaternionf quaternionf;\n        if (this.roll == 0.0F) {\n            quaternionf = renderInfo.rotation();\n        } else {\n            quaternionf = new Quaternionf(renderInfo.rotation());\n            quaternionf.rotateZ(Mth.lerp(partialTicks, this.oRoll, this.roll));\n        }\n\n        Vector3f[] avector3f = new Vector3f[]{new Vector3f(-1.0F, -1.0F, 0.0F), new Vector3f(-1.0F, 1.0F, 0.0F), new Vector3f(1.0F, 1.0F, 0.0F), new Vector3f(1.0F, -1.0F, 0.0F)};\n        float f4 = this.getQuadSize(partialTicks);\n\n        for(int i = 0; i < 4; ++i) {\n            Vector3f vector3f = avector3f[i];\n            vector3f.rotate(quaternion);\n            vector3f.mul(f4);\n            vector3f.add(f, f1, f2);\n        }\n\n        float f7 = this.getU0();\n        float f8 = this.getU1();\n        float f5 = this.getV0();\n        float f6 = this.getV1();\n        int j = this.getLightColor(partialTicks);\n        //int j = 15728800;\n        if (j > 0) {\n            lastNonZeroBrightness = j;\n        } else {\n            j = lastNonZeroBrightness;\n        }\n        buffer.vertex(avector3f[0].x(), avector3f[0].y(), avector3f[0].z()).uv(f8, f6).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(j).endVertex();\n        buffer.vertex(avector3f[1].x(), avector3f[1].y(), avector3f[1].z()).uv(f8, f5).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(j).endVertex();\n        buffer.vertex(avector3f[2].x(), avector3f[2].y(), avector3f[2].z()).uv(f7, f5).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(j).endVertex();\n        buffer.vertex(avector3f[3].x(), avector3f[3].y(), avector3f[3].z()).uv(f7, f6).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(j).endVertex();\n\n    }\n\n    //TODO: 1.14 uncomment\n\t/*public void renderParticleForShader(InstancedMeshParticle mesh, Transformation transformation, Matrix4fe viewMatrix, Entity entityIn,\n                                        float partialTicks, float rotationX, float rotationZ,\n                                        float rotationYZ, float rotationXY, float rotationXZ) {\n\n        if (mesh.curBufferPos >= mesh.numInstances) return;\n\n        //camera relative positions, for world position, remove the interpPos values\n        float posX = (float) (this.prevPosX + (this.posX - this.prevPosX) * (double) partialTicks - this.interpPosX);\n        float posY = (float) (this.prevPosY + (this.posY - this.prevPosY) * (double) partialTicks - this.interpPosY);\n        float posZ = (float) (this.prevPosZ + (this.posZ - this.prevPosZ) * (double) partialTicks - this.interpPosZ);\n        //Vector3f pos = new Vector3f((float) (entityIn.posX - particle.posX), (float) (entityIn.posY - particle.posY), (float) (entityIn.posZ - particle.posZ));\n        Vector3f pos = new Vector3f(posX, posY, posZ);\n\n        Matrix4fe modelMatrix = transformation.buildModelMatrix(this, pos, partialTicks);\n\n        //adjust to perspective and camera\n        //Matrix4fe modelViewMatrix = transformation.buildModelViewMatrix(modelMatrix, viewMatrix);\n        //upload to buffer\n        modelMatrix.get(mesh.INSTANCE_SIZE_FLOATS * (mesh.curBufferPos), mesh.instanceDataBuffer);\n\n        //brightness\n        float brightness;\n        //brightness = CoroUtilBlockLightCache.getBrightnessCached(world, (float)this.posX, (float)this.posY, (float)this.posZ);\n        brightness = brightnessCache;\n        //brightness = -1F;\n        //brightness = CoroUtilBlockLightCache.brightnessPlayer;\n        mesh.instanceDataBuffer.put(mesh.INSTANCE_SIZE_FLOATS * (mesh.curBufferPos) + mesh.MATRIX_SIZE_FLOATS, brightness);\n\n        int rgbaIndex = 0;\n        mesh.instanceDataBuffer.put(mesh.INSTANCE_SIZE_FLOATS * (mesh.curBufferPos)\n                + mesh.MATRIX_SIZE_FLOATS + 1 + (rgbaIndex++), this.particleRed);\n        mesh.instanceDataBuffer.put(mesh.INSTANCE_SIZE_FLOATS * (mesh.curBufferPos)\n                + mesh.MATRIX_SIZE_FLOATS + 1 + (rgbaIndex++), this.particleGreen);\n        mesh.instanceDataBuffer.put(mesh.INSTANCE_SIZE_FLOATS * (mesh.curBufferPos)\n                + mesh.MATRIX_SIZE_FLOATS + 1 + (rgbaIndex++), this.particleBlue);\n        mesh.instanceDataBuffer.put(mesh.INSTANCE_SIZE_FLOATS * (mesh.curBufferPos)\n                + mesh.MATRIX_SIZE_FLOATS + 1 + (rgbaIndex++), this.getAlphaF());\n\n        mesh.curBufferPos++;\n        \n    }*/\n\n    /*public void renderParticleForShaderTest(InstancedMeshParticle mesh, Transformation transformation, Matrix4fe viewMatrix, Entity entityIn,\n                                            float partialTicks, float rotationX, float rotationZ,\n                                            float rotationYZ, float rotationXY, float rotationXZ) {\n\n        if (mesh.curBufferPos >= mesh.numInstances) return;\n\n        int rgbaIndex = 0;\n        mesh.instanceDataBufferTest.put(mesh.INSTANCE_SIZE_FLOATS_TEST * (mesh.curBufferPos)\n                + (rgbaIndex++), this.getRedColorF());\n        mesh.instanceDataBufferTest.put(mesh.INSTANCE_SIZE_FLOATS_TEST * (mesh.curBufferPos)\n                + (rgbaIndex++), this.getGreenColorF());\n        mesh.instanceDataBufferTest.put(mesh.INSTANCE_SIZE_FLOATS_TEST * (mesh.curBufferPos)\n                + (rgbaIndex++), this.getBlueColorF());\n        mesh.instanceDataBufferTest.put(mesh.INSTANCE_SIZE_FLOATS_TEST * (mesh.curBufferPos)\n                + (rgbaIndex++), this.getAlphaF());\n\n        mesh.curBufferPos++;\n    }*/\n\n    public void setKillOnCollide(boolean val) {\n        this.killOnCollide = val;\n    }\n\n    //override for extra isCollided types\n    @Override\n    public void move(double x, double y, double z) {\n        double xx = x;\n        double yy = y;\n        double zz = z;\n        if (this.hasPhysics && (x != 0.0D || y != 0.0D || z != 0.0D)) {\n            Vec3 Vector3d = Entity.collideBoundingBox(null, new Vec3(x, y, z), this.getBoundingBox(), this.level, List.of());\n            x = Vector3d.x;\n            y = Vector3d.y;\n            z = Vector3d.z;\n        }\n\n        if (x != 0.0D || y != 0.0D || z != 0.0D) {\n            this.setBoundingBox(this.getBoundingBox().move(x, y, z));\n            if (isUseCustomBBForRenderCulling()) {\n                this.setBoundingBoxForRender(this.getBoundingBoxForRender(1F).move(x, y, z));\n            }\n            /*Vec3 pivotedPosition = getPivotedPosition(0);\n            if (pivotedPosition != Vec3.ZERO) {\n                this.setBoundingBox(this.getBoundingBox().move(x + pivotedPosition.x, y + pivotedPosition.y, z + pivotedPosition.z));\n            } else {\n                this.setBoundingBox(this.getBoundingBox().move(x, y, z));\n            }*/\n\n            this.setLocationFromBoundingbox();\n        }\n\n        this.onGround = yy != y && yy < 0.0D;\n        this.isCollidedHorizontally = xx != x || zz != z;\n        this.isCollidedVerticallyDownwards = yy < y;\n        this.isCollidedVerticallyUpwards = yy > y;\n        if (xx != x) {\n            this.xd = 0.0D;\n        }\n\n        if (zz != z) {\n            this.zd = 0.0D;\n        }\n\n        if (!markCollided) {\n            if (onGround || isCollidedVerticallyDownwards || isCollidedHorizontally || isCollidedVerticallyUpwards) {\n                onHit();\n                markCollided = true;\n            }\n\n            if (bounceOnVerticalImpact && (onGround || isCollidedVerticallyDownwards)) {\n                setMotionY(-getMotionY() * bounceOnVerticalImpactEnergy);\n            }\n        }\n\n    }\n\n    public void setFacePlayer(boolean val) {\n        this.facePlayer = val;\n    }\n\n    public TextureAtlasSprite getParticleTexture() {\n        return this.sprite;\n    }\n\n    public boolean isVanillaMotionDampen() {\n        return vanillaMotionDampen;\n    }\n\n    public void setVanillaMotionDampen(boolean motionDampen) {\n        this.vanillaMotionDampen = motionDampen;\n    }\n\n    @Override\n    public int getLightColor(float p_189214_1_) {\n        return super.getLightColor(p_189214_1_);//(int)((float)super.getBrightnessForRender(p_189214_1_))/* * this.world.getSunBrightness(1F))*/;\n    }\n\n    /*public void updateQuaternion(Entity camera) {\n\n        if (camera != null) {\n            if (this.facePlayer) {\n                this.rotationYaw = camera.rotationYaw;\n                this.rotationPitch = camera.rotationPitch;\n            } else if (facePlayerYaw) {\n                this.rotationYaw = camera.rotationYaw;\n            }\n        }\n\n        Quaternion qY = new Quaternion();\n        Quaternion qX = new Quaternion();\n        qY.setFromAxisAngle(new Vector4f(0, 1, 0, (float)Math.toRadians(-this.rotationYaw - 180F)));\n        qX.setFromAxisAngle(new Vector4f(1, 0, 0, (float)Math.toRadians(-this.rotationPitch)));\n        if (this.rotateOrderXY) {\n            Quaternion.mul(qX, qY, this.rotation);\n        } else {\n            Quaternion.mul(qY, qX, this.rotation);\n        }\n    }*/\n\n    @Override\n    public void setColor(float particleRedIn, float particleGreenIn, float particleBlueIn) {\n        super.setColor(particleRedIn, particleGreenIn, particleBlueIn);\n        //TODO: 1.14 uncomment\n        /*RotatingParticleManager.markDirtyVBO2();*/\n    }\n\n    @Override\n    public void setAlpha(float alpha) {\n        super.setAlpha(alpha);\n        //TODO: 1.14 uncomment\n        /*RotatingParticleManager.markDirtyVBO2();*/\n    }\n\n    public int getKillWhenUnderTopmostBlock_ScanAheadRange() {\n        return killWhenUnderTopmostBlock_ScanAheadRange;\n    }\n\n    public void setKillWhenUnderTopmostBlock_ScanAheadRange(int killWhenUnderTopmostBlock_ScanAheadRange) {\n        this.killWhenUnderTopmostBlock_ScanAheadRange = killWhenUnderTopmostBlock_ScanAheadRange;\n    }\n\n    public boolean isCollidedVertically() {\n        return isCollidedVerticallyDownwards || isCollidedVerticallyUpwards;\n    }\n\n    @Override\n    public ParticleRenderType getRenderType() {\n        return SORTED_TRANSLUCENT;\n    }\n\n    @Override\n    public void setSprite(TextureAtlasSprite sprite) {\n        super.setSprite(sprite);\n    }\n\n    public TextureAtlasSprite getSprite() {\n        return sprite;\n    }\n\n    public float getFullAlphaTarget() {\n        return fullAlphaTarget;\n    }\n\n    public void setFullAlphaTarget(float fullAlphaTarget) {\n        this.fullAlphaTarget = fullAlphaTarget;\n    }\n\n    public int getLastNonZeroBrightness() {\n        return lastNonZeroBrightness;\n    }\n\n    public void setLastNonZeroBrightness(int lastNonZeroBrightness) {\n        this.lastNonZeroBrightness = lastNonZeroBrightness;\n    }\n\n    public void onHit() {\n\n    }\n\n    public void setMaxAge(int par) {\n        setLifetime(par);\n    }\n\n    public int getMaxAge() {\n        return getLifetime();\n    }\n\n    public void setAlphaF(float val) {\n        setAlpha(val);\n    }\n\n    public void setPosition(double posX, double posY, double posZ) {\n        this.setPos(posX, posY, posZ);\n    }\n\n    public Vec3 getPivotedPosition(float partialTicks) {\n        return Vec3.ZERO;\n    }\n\n    public void setBoundingBoxForRender(AABB p_107260_) {\n        this.bbRender = p_107260_;\n    }\n\n    public AABB getBoundingBoxForRender(float partialTicks) {\n        if (isUseCustomBBForRenderCulling()) {\n            return bbRender;\n        } else {\n            return this.getBoundingBox();\n        }\n    }\n\n    public void setSizeForRenderCulling(float p_107251_, float p_107252_) {\n        if (p_107251_ != this.bbWidth || p_107252_ != this.bbHeight) {\n            this.bbWidth = p_107251_;\n            this.bbHeight = p_107252_;\n            AABB aabb = this.getBoundingBox();\n            double d0 = (aabb.minX + aabb.maxX - (double)p_107251_) / 2.0D;\n            double d1 = (aabb.minZ + aabb.maxZ - (double)p_107251_) / 2.0D;\n            this.setBoundingBoxForRender(new AABB(d0, aabb.minY, d1, d0 + (double)this.bbWidth, aabb.minY + (double)this.bbHeight, d1 + (double)this.bbWidth));\n        }\n\n    }\n\n    public boolean isUseCustomBBForRenderCulling() {\n        return useCustomBBForRenderCulling;\n    }\n\n    public void setUseCustomBBForRenderCulling(boolean useCustomBBForRenderCulling) {\n        this.useCustomBBForRenderCulling = useCustomBBForRenderCulling;\n    }\n\n    @Override\n    public float getWindWeight() {\n        return windWeight;\n    }\n\n    @Override\n    public int getParticleDecayExtra() {\n        return 0;\n    }\n\n    public int getKillOnCollideActivateAtAge() {\n        return killOnCollideActivateAtAge;\n    }\n\n    public void setKillOnCollideActivateAtAge(int killOnCollideActivateAtAge) {\n        this.killOnCollideActivateAtAge = killOnCollideActivateAtAge;\n    }\n\n    public float getRenderDistanceCull() {\n        return renderDistanceCull;\n    }\n\n    public void setRenderDistanceCull(float renderDistanceCull) {\n        this.renderDistanceCull = renderDistanceCull;\n    }\n\n    public boolean isUseDynamicWindSpeed() {\n        return useDynamicWindSpeed;\n    }\n\n    public void setUseDynamicWindSpeed(boolean useDynamicWindSpeed) {\n        this.useDynamicWindSpeed = useDynamicWindSpeed;\n    }\n}\n"
  },
  {
    "path": "src/main/java/extendedrenderer/particle/entity/ParticleCrossSection.java",
    "content": "package extendedrenderer.particle.entity;\n\nimport com.mojang.blaze3d.vertex.VertexConsumer;\nimport com.mojang.math.Axis;\nimport net.minecraft.client.Camera;\nimport net.minecraft.client.renderer.texture.TextureAtlasSprite;\nimport net.minecraft.client.multiplayer.ClientLevel;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.phys.Vec3;\nimport net.minecraft.world.level.Level;\nimport org.joml.Quaternionf;\nimport org.joml.Vector3f;\n\npublic class ParticleCrossSection extends ParticleTexFX {\n\n\tpublic ParticleCrossSection(Level worldIn, double posXIn, double posYIn,\n                                double posZIn, double mX, double mY, double mZ,\n                                TextureAtlasSprite par8Item) {\n\t\tsuper((ClientLevel) worldIn, posXIn, posYIn, posZIn, mX, mY, mZ, par8Item);\n\t}\n\n\t@Override\n\tpublic void render(VertexConsumer buffer, Camera renderInfo, float partialTicks) {\n\n\t\tVec3 Vector3d = renderInfo.getPosition();\n\t\tfloat f = (float)(Mth.lerp(partialTicks, this.xo, this.x) - Vector3d.x());\n\t\tfloat f1 = (float)(Mth.lerp(partialTicks, this.yo, this.y) - Vector3d.y());\n\t\tfloat f2 = (float)(Mth.lerp(partialTicks, this.zo, this.z) - Vector3d.z());\n\t\tQuaternionf quaternion;\n\t\tif (this.facePlayer || (this.rotationPitch == 0 && this.rotationYaw == 0)) {\n\t\t\tquaternion = renderInfo.rotation();\n\t\t} else {\n\t\t\t// override rotations\n\t\t\tquaternion = new Quaternionf(0, 0, 0, 1);\n\t\t\tif (facePlayerYaw) {\n\t\t\t\tquaternion.mul(Axis.YP.rotationDegrees(-renderInfo.getYRot()));\n\t\t\t} else {\n\t\t\t\tquaternion.mul(Axis.YP.rotationDegrees(Mth.lerp(partialTicks, this.prevRotationYaw, rotationYaw)));\n\t\t\t}\n\t\t\tquaternion.mul(Axis.XP.rotationDegrees(Mth.lerp(partialTicks, this.prevRotationPitch, rotationPitch)));\n\t\t}\n\n\t\tVector3f[] avector3f = new Vector3f[]{\n\t\t\t\tnew Vector3f(-1.0F, -1.0F, 0.0F),\n\t\t\t\tnew Vector3f(-1.0F, 1.0F, 0.0F),\n\t\t\t\tnew Vector3f(1.0F, 1.0F, 0.0F),\n\t\t\t\tnew Vector3f(1.0F, -1.0F, 0.0F)};\n\n\t\tVector3f[] avector3f2 = new Vector3f[]{\n\t\t\t\tnew Vector3f(0.0F, -1.0F, -1.0F),\n\t\t\t\tnew Vector3f(0.0F, 1.0F, -1.0F),\n\t\t\t\tnew Vector3f(0.0F, 1.0F, 1.0F),\n\t\t\t\tnew Vector3f(0.0F, -1.0F, 1.0F)};\n\n\t\tVector3f[] avector3f3 = new Vector3f[]{\n\t\t\t\tnew Vector3f(-1.0F, 0.0F, -1.0F),\n\t\t\t\tnew Vector3f(-1.0F, 0.0F, 1.0F),\n\t\t\t\tnew Vector3f(1.0F, 0.0F, 1.0F),\n\t\t\t\tnew Vector3f(1.0F, 0.0F, -1.0F)};\n\n\t\tfloat f4 = this.getQuadSize(partialTicks);\n\n\t\tfor(int i = 0; i < 4; ++i) {\n\t\t\tVector3f vector3f = avector3f[i];\n\t\t\tvector3f.rotate(quaternion);\n\t\t\tvector3f.mul(f4);\n\t\t\tvector3f.add(f, f1, f2);\n\t\t}\n\n\t\tfor(int i = 0; i < 4; ++i) {\n\t\t\tVector3f vector3f = avector3f2[i];\n\t\t\tvector3f.rotate(quaternion);\n\t\t\tvector3f.mul(f4);\n\t\t\tvector3f.add(f, f1, f2);\n\t\t}\n\n\t\tfor(int i = 0; i < 4; ++i) {\n\t\t\tVector3f vector3f = avector3f3[i];\n\t\t\tvector3f.rotate(quaternion);\n\t\t\tvector3f.mul(f4);\n\t\t\tvector3f.add(f, f1, f2);\n\t\t}\n\n\t\tfloat f7 = this.getU0();\n\t\tfloat f8 = this.getU1();\n\t\tfloat f5 = this.getV0();\n\t\tfloat f6 = this.getV1();\n\t\tint j = this.getLightColor(partialTicks);\n\t\tif (j > 0) {\n\t\t\tlastNonZeroBrightness = j;\n\t\t} else {\n\t\t\tj = lastNonZeroBrightness;\n\t\t}\n\t\tbuffer.vertex(avector3f[0].x(), avector3f[0].y(), avector3f[0].z()).uv(f8, f6).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(j).endVertex();\n\t\tbuffer.vertex(avector3f[1].x(), avector3f[1].y(), avector3f[1].z()).uv(f8, f5).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(j).endVertex();\n\t\tbuffer.vertex(avector3f[2].x(), avector3f[2].y(), avector3f[2].z()).uv(f7, f5).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(j).endVertex();\n\t\tbuffer.vertex(avector3f[3].x(), avector3f[3].y(), avector3f[3].z()).uv(f7, f6).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(j).endVertex();\n\n\t\tbuffer.vertex(avector3f2[0].x(), avector3f2[0].y(), avector3f2[0].z()).uv(f8, f6).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(j).endVertex();\n\t\tbuffer.vertex(avector3f2[1].x(), avector3f2[1].y(), avector3f2[1].z()).uv(f8, f5).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(j).endVertex();\n\t\tbuffer.vertex(avector3f2[2].x(), avector3f2[2].y(), avector3f2[2].z()).uv(f7, f5).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(j).endVertex();\n\t\tbuffer.vertex(avector3f2[3].x(), avector3f2[3].y(), avector3f2[3].z()).uv(f7, f6).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(j).endVertex();\n\n\t\tbuffer.vertex(avector3f3[0].x(), avector3f3[0].y(), avector3f3[0].z()).uv(f8, f6).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(j).endVertex();\n\t\tbuffer.vertex(avector3f3[1].x(), avector3f3[1].y(), avector3f3[1].z()).uv(f8, f5).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(j).endVertex();\n\t\tbuffer.vertex(avector3f3[2].x(), avector3f3[2].y(), avector3f3[2].z()).uv(f7, f5).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(j).endVertex();\n\t\tbuffer.vertex(avector3f3[3].x(), avector3f3[3].y(), avector3f3[3].z()).uv(f7, f6).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(j).endVertex();\n\n\t}\n}\n"
  },
  {
    "path": "src/main/java/extendedrenderer/particle/entity/ParticleCube.java",
    "content": "package extendedrenderer.particle.entity;\n\nimport com.corosus.coroutil.util.CULog;\nimport com.corosus.coroutil.util.CoroUtilBlock;\nimport com.corosus.coroutil.util.CoroUtilMisc;\nimport com.mojang.blaze3d.vertex.VertexConsumer;\nimport com.mojang.math.Axis;\nimport extendedrenderer.particle.ParticleRegistry;\nimport net.minecraft.client.Camera;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.client.multiplayer.ClientLevel;\nimport net.minecraft.client.particle.ParticleRenderType;\nimport net.minecraft.client.renderer.RenderType;\nimport net.minecraft.client.renderer.block.BlockRenderDispatcher;\nimport net.minecraft.client.renderer.block.model.BakedQuad;\nimport net.minecraft.client.renderer.texture.TextureAtlasSprite;\nimport net.minecraft.client.resources.model.BakedModel;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.core.Direction;\nimport net.minecraft.util.Mth;\nimport net.minecraft.util.RandomSource;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.block.Blocks;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.world.phys.Vec3;\nimport org.joml.Quaternionf;\nimport org.joml.Vector3f;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Random;\n\npublic class ParticleCube extends ParticleTexFX {\n\n\tpublic ParticleCube(Level worldIn, double posXIn, double posYIn,\n                        double posZIn, double mX, double mY, double mZ,\n                        BlockState state) {\n\t\tsuper((ClientLevel) worldIn, posXIn, posYIn, posZIn, mX, mY, mZ, ParticleRegistry.potato);\n\n\t\t/**\n\t\t * really basic way to get a sprite from a blockstate, could easily get the wrong one if multiple quads are used per direction\n\t\t * should do fine for most blocks that have the same texture on every side\n\t\t */\n\t\tTextureAtlasSprite sprite = getSpriteFromState(state);\n\t\tif (sprite != null) {\n\t\t\tsetSprite(sprite);\n\t\t} else {\n\t\t\tCULog.dbg(\"unable to find sprite to use from block: \" + state);\n\t\t\tsprite = getSpriteFromState(Blocks.DIRT.defaultBlockState());\n\t\t\t//if (CoroUtilMisc.random().nextBoolean()) sprite = getSpriteFromState(Blocks.GRASS.defaultBlockState());\n\t\t\tif (sprite != null) {\n\t\t\t\tsetSprite(sprite);\n\t\t\t}\n\t\t}\n\t\tint multiplier = Minecraft.getInstance().getBlockColors().getColor(state, this.level, CoroUtilBlock.blockPos(posXIn, posYIn, posZIn), 0);\n\t\tfloat mr = ((multiplier >>> 16) & 0xFF) / 255f;\n\t\tfloat mg = ((multiplier >>> 8) & 0xFF) / 255f;\n\t\tfloat mb = (multiplier & 0xFF) / 255f;\n\t\tsetColor(mr, mg, mb);\n\t}\n\n\tpublic TextureAtlasSprite getSpriteFromState(BlockState state) {\n\t\tBlockRenderDispatcher blockrenderdispatcher = Minecraft.getInstance().getBlockRenderer();\n\t\tBakedModel model = blockrenderdispatcher.getBlockModel(state);\n\t\tfor(Direction direction : Direction.values()) {\n\t\t\tList<BakedQuad> list = model.getQuads(state, direction, RandomSource.create());\n\t\t\tif (list.size() > 0) {\n\t\t\t\treturn list.get(0).getSprite();\n\t\t\t}\n\t\t\t//plan b\n\t\t\tif (model.getParticleIcon() != null) {\n\t\t\t\treturn model.getParticleIcon();\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t}\n\n\t@Override\n\tpublic void render(VertexConsumer buffer, Camera renderInfo, float partialTicks) {\n\t\t//if (true) return;\n\t\tVec3 Vector3d = renderInfo.getPosition();\n\t\tfloat f = (float)(Mth.lerp(partialTicks, this.xo, this.x) - Vector3d.x());\n\t\tfloat f1 = (float)(Mth.lerp(partialTicks, this.yo, this.y) - Vector3d.y());\n\t\tfloat f2 = (float)(Mth.lerp(partialTicks, this.zo, this.z) - Vector3d.z());\n\t\tQuaternionf quaternion;\n\t\tif (this.facePlayer || (this.rotationPitch == 0 && this.rotationYaw == 0)) {\n\t\t\tquaternion = renderInfo.rotation();\n\t\t} else {\n\t\t\t// override rotations\n\t\t\tquaternion = new Quaternionf(0, 0, 0, 1);\n\t\t\tif (facePlayerYaw) {\n\t\t\t\tquaternion.mul(Axis.YP.rotationDegrees(-renderInfo.getYRot()));\n\t\t\t} else {\n\t\t\t\tquaternion.mul(Axis.YP.rotationDegrees(Mth.lerp(partialTicks, this.prevRotationYaw, rotationYaw)));\n\t\t\t}\n\t\t\tquaternion.mul(Axis.XP.rotationDegrees(Mth.lerp(partialTicks, this.prevRotationPitch, rotationPitch)));\n\t\t}\n\n\t\tTextureAtlasSprite sprite = null;\n\n\t\tList<Vector3f[]> faces = new ArrayList<>();\n\n\t\tVector3f[] face;\n\n\t\t//xy -z\n\t\tface = new Vector3f[]{\n\t\t\t\tnew Vector3f(-1.0F, -1.0F, -1.0F),\n\t\t\t\tnew Vector3f(-1.0F, 1.0F, -1.0F),\n\t\t\t\tnew Vector3f(1.0F, 1.0F, -1.0F),\n\t\t\t\tnew Vector3f(1.0F, -1.0F, -1.0F)};\n\t\tfaces.add(face);\n\n\t\t//xy +z\n\t\tface = new Vector3f[]{\n\t\t\t\tnew Vector3f(-1.0F, -1.0F, 1.0F),\n\t\t\t\tnew Vector3f(-1.0F, 1.0F, 1.0F),\n\t\t\t\tnew Vector3f(1.0F, 1.0F, 1.0F),\n\t\t\t\tnew Vector3f(1.0F, -1.0F, 1.0F)};\n\t\tfaces.add(face);\n\n\t\t//yz -x\n\t\tface = new Vector3f[]{\n\t\t\t\tnew Vector3f(-1.0F, -1.0F, -1.0F),\n\t\t\t\tnew Vector3f(-1.0F, 1.0F, -1.0F),\n\t\t\t\tnew Vector3f(-1.0F, 1.0F, 1.0F),\n\t\t\t\tnew Vector3f(-1.0F, -1.0F, 1.0F)};\n\t\tfaces.add(face);\n\n\t\t//yz +x\n\t\tface = new Vector3f[]{\n\t\t\t\tnew Vector3f(1.0F, -1.0F, -1.0F),\n\t\t\t\tnew Vector3f(1.0F, 1.0F, -1.0F),\n\t\t\t\tnew Vector3f(1.0F, 1.0F, 1.0F),\n\t\t\t\tnew Vector3f(1.0F, -1.0F, 1.0F)};\n\t\tfaces.add(face);\n\n\t\t//xz -y\n\t\tface = new Vector3f[]{\n\t\t\t\tnew Vector3f(-1.0F, -1.0F, -1.0F),\n\t\t\t\tnew Vector3f(-1.0F, -1.0F, 1.0F),\n\t\t\t\tnew Vector3f(1.0F, -1.0F, 1.0F),\n\t\t\t\tnew Vector3f(1.0F, -1.0F, -1.0F)};\n\t\tfaces.add(face);\n\n\t\t//xz +y\n\t\tface = new Vector3f[]{\n\t\t\t\tnew Vector3f(-1.0F, 1.0F, -1.0F),\n\t\t\t\tnew Vector3f(-1.0F, 1.0F, 1.0F),\n\t\t\t\tnew Vector3f(1.0F, 1.0F, 1.0F),\n\t\t\t\tnew Vector3f(1.0F, 1.0F, -1.0F)};\n\t\tfaces.add(face);\n\n\t\tfloat f4 = this.getQuadSize(partialTicks);\n\n\t\tfor (Vector3f[] entryFace : faces) {\n\t\t\tfor(int i = 0; i < 4; ++i) {\n\t\t\t\tentryFace[i].rotate(quaternion);\n\t\t\t\tentryFace[i].mul(f4);\n\t\t\t\tentryFace[i].add(f, f1, f2);\n\t\t\t}\n\t\t}\n\n\t\tfloat f7 = this.getU0();\n\t\tfloat f8 = this.getU1();\n\t\tfloat f5 = this.getV0();\n\t\tfloat f6 = this.getV1();\n\t\tif (sprite != null) {\n\t\t\tf7 = sprite.getU0();\n\t\t\tf8 = sprite.getU1();\n\t\t\tf5 = sprite.getV0();\n\t\t\tf6 = sprite.getV1();\n\t\t}\n\t\tint j = this.getLightColor(partialTicks);\n\t\tif (j > 0) {\n\t\t\tlastNonZeroBrightness = j;\n\t\t} else {\n\t\t\tj = lastNonZeroBrightness;\n\t\t}\n\t\tfor (Vector3f[] entryFace : faces) {\n\t\t\tbuffer.vertex(entryFace[0].x(), entryFace[0].y(), entryFace[0].z()).uv(f8, f6).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(j).endVertex();\n\t\t\tbuffer.vertex(entryFace[1].x(), entryFace[1].y(), entryFace[1].z()).uv(f8, f5).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(j).endVertex();\n\t\t\tbuffer.vertex(entryFace[2].x(), entryFace[2].y(), entryFace[2].z()).uv(f7, f5).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(j).endVertex();\n\t\t\tbuffer.vertex(entryFace[3].x(), entryFace[3].y(), entryFace[3].z()).uv(f7, f6).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(j).endVertex();\n\t\t}\n\n\t}\n\n\t@Override\n\tpublic ParticleRenderType getRenderType() {\n\t\treturn SORTED_OPAQUE_BLOCK;\n\t\t//return super.getRenderType();\n\t}\n}\n"
  },
  {
    "path": "src/main/java/extendedrenderer/particle/entity/ParticleEmitter.java",
    "content": "package extendedrenderer.particle.entity;\n\nimport com.mojang.blaze3d.vertex.VertexConsumer;\nimport net.minecraft.client.Camera;\nimport net.minecraft.client.multiplayer.ClientLevel;\n\npublic class ParticleEmitter extends EntityRotFX {\n\n    public ParticleEmitter(ClientLevel par1World, double par2, double par4, double par6, double par8, double par10, double par12) {\n        super(par1World, par2, par4, par6, par8, par10, par12);\n    }\n\n    @Override\n    public void tick() {\n        //super.tick();\n        if (this.age++ >= this.lifetime) {\n            this.remove();\n        }\n    }\n\n    @Override\n    public void render(VertexConsumer buffer, Camera renderInfo, float partialTicks) {\n        //super.render(buffer, renderInfo, partialTicks);\n    }\n}\n"
  },
  {
    "path": "src/main/java/extendedrenderer/particle/entity/ParticleTexExtraRender.java",
    "content": "package extendedrenderer.particle.entity;\n\nimport com.corosus.coroutil.util.CoroUtilBlock;\nimport com.corosus.coroutil.util.CoroUtilParticle;\nimport com.mojang.blaze3d.vertex.VertexConsumer;\nimport com.mojang.math.Axis;\nimport net.minecraft.client.Camera;\nimport net.minecraft.client.renderer.texture.TextureAtlasSprite;\nimport net.minecraft.client.multiplayer.ClientLevel;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.phys.Vec3;\nimport net.minecraft.world.level.levelgen.Heightmap;\nimport org.joml.Quaternionf;\nimport org.joml.Vector3f;\nimport weather2.ClientTickHandler;\nimport weather2.weathersystem.WeatherManagerClient;\nimport weather2.weathersystem.wind.WindManager;\n\npublic class ParticleTexExtraRender extends ParticleTexFX {\n\n\tprivate int severityOfRainRate = 2;\n\n\tprivate int extraParticlesBaseAmount = 5;\n\n\tpublic boolean noExtraParticles = false;\n\n\tprivate float extraRandomSecondaryYawRotation = 360;\n\n\t//public float[] cachedLight;\n\t\n\tpublic ParticleTexExtraRender(ClientLevel worldIn, double posXIn, double posYIn,\n\t\t\t\t\t\t\t\t  double posZIn, double mX, double mY, double mZ,\n\t\t\t\t\t\t\t\t  TextureAtlasSprite par8Item) {\n\t\tsuper(worldIn, posXIn, posYIn, posZIn, mX, mY, mZ, par8Item);\n\n\t\t/*cachedLight = new float[CoroUtilParticle.rainPositions.length];\n\t\tif (worldObj.getGameTime() % 5 == 0) {\n\t\t\tfor (int i = 0; i < cachedLight.length; i++) {\n\t\t\t\tVector3 vec = CoroUtilParticle.rainPositions[i];\n\t\t\t\tcachedLight[i] = getBrightnessNonLightmap(new BlockPos(posX+vec.xCoord, posY+vec.yCoord, posZ+vec.zCoord), 1F);\n\t\t\t}\n\t\t}*/\n\t}\n\n\tpublic int getSeverityOfRainRate() {\n\t\treturn severityOfRainRate;\n\t}\n\n\tpublic void setSeverityOfRainRate(int severityOfRainRate) {\n\t\tthis.severityOfRainRate = severityOfRainRate;\n\t}\n\n\tpublic int getExtraParticlesBaseAmount() {\n\t\treturn extraParticlesBaseAmount;\n\t}\n\n\tpublic void setExtraParticlesBaseAmount(int extraParticlesBaseAmount) {\n\t\tthis.extraParticlesBaseAmount = extraParticlesBaseAmount;\n\t}\n\n\t@Override\n\tpublic void tickExtraRotations() {\n\t\t//super.tickExtraRotations();\n\n\t\tWeatherManagerClient weatherMan = ClientTickHandler.weatherManager;\n\t\tif (weatherMan == null) return;\n\t\tWindManager windMan = weatherMan.getWindManager();\n\t\tif (windMan == null) return;\n\n\t\tif (isSlantParticleToWind()) {\n\t\t\tdouble speed = xd * xd + zd * zd;\n\t\t\trotationYaw = -(float)Math.toDegrees(Math.atan2(zd, xd)) - 90;\n\t\t\trotationPitch = Math.min(45, (float)(speed * 120));\n\t\t\trotationPitch += (this.getEntityId() % 10) - 5;\n\t\t}\n\n\t\twindMan.applyWindForceNew(this, 1F / 2F, 0.5F);\n\t}\n\n\t/**\n\t * make sure extra renderings arent culled out\n\t *\n\t * @return\n\t */\n\t/*@Override\n\tpublic boolean shouldCull() {\n\t\treturn false;\n\t}*/\n\n\t@Override\n\tpublic void render(VertexConsumer buffer, Camera renderInfo, float partialTicks) {\n\t\t//override rotations\n        Vec3 Vector3d = renderInfo.getPosition();\n        Quaternionf quaternion;\n        if (this.facePlayer || (this.rotationPitch == 0 && this.rotationYaw == 0)) {\n           quaternion = renderInfo.rotation();\n        } else {\n           // override rotations\n           quaternion = new Quaternionf(0, 0, 0, 1);\n           quaternion.mul(Axis.YP.rotationDegrees(this.rotationYaw));\n           quaternion.mul(Axis.XP.rotationDegrees(this.rotationPitch));\n           if (extraRandomSecondaryYawRotation > 0) {\n\t\t\t   quaternion.mul(Axis.YP.rotationDegrees(getEntityId() % extraRandomSecondaryYawRotation));\n\t\t   }\n        }\n        \n        float posX = (float)(Mth.lerp((double)partialTicks, this.xo, this.x) - Vector3d.x());\n        float posY = (float)(Mth.lerp((double)partialTicks, this.yo, this.y) - Vector3d.y());\n        float posZ = (float)(Mth.lerp((double)partialTicks, this.zo, this.z) - Vector3d.z());\n\n\n\t\tfloat f = this.getU0();\n\t\tfloat f1 = this.getU1();\n\t\tfloat f2 = this.getV0();\n\t\tfloat f3 = this.getV1();\n\n\t\tfloat fixY = 0;\n\n\t\tfloat part = 16F / 3F;\n\t\tfloat offset = 0;\n\t\tfloat posBottom = (float)(this.y - 10D);\n\n\t\tfloat height = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, CoroUtilBlock.blockPos(this.x, this.y, this.z)).getY();\n\n\t\tif (posBottom < height) {\n\t\t\tfloat diff = height - posBottom;\n\t\t\toffset = diff;\n\t\t\tfixY = 0;//diff * 1.0F;\n\t\t\tif (offset > part) offset = part;\n\t\t}\n\n\t\tint renderAmount = 0;\n\t\tif (noExtraParticles) {\n\t\t\trenderAmount = 1;\n\t\t} else {\n\t\t\t//renderAmount = Math.min(extraParticlesBaseAmount + ((Math.max(0, severityOfRainRate-1)) * 5), CoroUtilParticle.maxRainDrops);\n\t\t\trenderAmount = Math.min(1 + extraParticlesBaseAmount, CoroUtilParticle.maxRainDrops);\n\t\t}\n\n\t\t//catch code hotload crash, doesnt help much anyways\n\t\ttry {\n\t\t\tfor (int ii = 0; ii < renderAmount; ii++) {\n\t\t\t\tdouble xx = 0;\n\t\t\t\tdouble zz = 0;\n\t\t\t\tdouble yy = 0;\n\t\t\t\tif (ii != 0) {\n\t\t\t\t\txx = CoroUtilParticle.rainPositions[ii].x;\n\t\t\t\t\tzz = CoroUtilParticle.rainPositions[ii].z;\n\t\t\t\t\t//yy = CoroUtilParticle.rainPositions[ii].y;\n\t\t\t\t\tyy = CoroUtilParticle.rainPositions[ii].y;\n\t\t\t\t}\n\n\t\t\t\t//prevent precip under overhangs/inside for extra render\n\t\t\t\tif (this.isDontRenderUnderTopmostBlock()) {\n\t\t\t\t\tint height2 = level.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, CoroUtilBlock.blockPos(this.x + xx, this.y, this.z + zz)).getY();\n\t\t\t\t\tif (this.y + yy < height2) continue;\n\t\t\t\t}\n\n\t\t\t\t//TODO: 1.14 uncomment\n\t\t\t\t/*if (ii != 0) {\n\t\t\t\t\tRotatingParticleManager.debugParticleRenderCount++;\n\t\t\t\t}*/\n\n\t\t\t\t/*int height = entityIn.world.getPrecipitationHeight(new BlockPos(ActiveRenderInfo.getPosition().xCoord + f5, this.posY + f6, ActiveRenderInfo.getPosition().zCoord + f7)).getY();\n\t\t\t\tif (ActiveRenderInfo.getPosition().yCoord + f6 <= height) continue;*/\n\n\t\t\t\tint i = this.getLightColor(partialTicks);\n\t\t\t\tif (i > 0) {\n\t\t\t\t\tsetLastNonZeroBrightness(i);\n\t\t\t\t} else {\n\t\t\t\t\ti = getLastNonZeroBrightness();\n\t\t\t\t}\n\n\t\t        Vector3f[] avector3f = new Vector3f[]{new Vector3f(-1.0F, -1.0F, 0.0F), new Vector3f(-1.0F, 1.0F, 0.0F), new Vector3f(1.0F, 1.0F, 0.0F), new Vector3f(1.0F, -1.0F, 0.0F)};\n\t\t        float scale = this.getQuadSize(partialTicks);\n\n\t\t        \n\t\t        for(int v = 0; v < 4; ++v) {\n\t\t           Vector3f vector3f = avector3f[v];\n\t\t           vector3f.rotate(quaternion);\n\t\t           vector3f.mul(scale);\n\t\t           vector3f.add(posX, posY, posZ);\n\t\t        }\n\n\t\t\t\tbuffer.vertex(xx + avector3f[0].x(), yy + avector3f[0].y(), zz + avector3f[0].z()).uv(f1, f3).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(i).endVertex();\n\t\t\t\tbuffer.vertex(xx + avector3f[1].x(), yy + avector3f[1].y(), zz + avector3f[1].z()).uv(f1, f2).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(i).endVertex();\n\t\t\t\tbuffer.vertex(xx + avector3f[2].x(), yy + avector3f[2].y(), zz + avector3f[2].z()).uv(f, f2).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(i).endVertex();\n\t\t\t\tbuffer.vertex(xx + avector3f[3].x(), yy + avector3f[3].y(), zz + avector3f[3].z()).uv(f, f3).color(this.rCol, this.gCol, this.bCol, this.alpha).uv2(i).endVertex();\n\t\t\t}\n\t\t} catch (Throwable ex) {\n\t\t\tex.printStackTrace();\n\t\t}\n\t\t\n\n\n        \n\t}\n\n\t//TODO: 1.14 uncomment\n\t/*public void renderParticleForShader(InstancedMeshParticle mesh, Transformation transformation, Matrix4fe viewMatrix, Entity entityIn,\n\t\t\t\t\t\t\t\t\t\tfloat partialTicks, float rotationX, float rotationZ,\n\t\t\t\t\t\t\t\t\t\tfloat rotationYZ, float rotationXY, float rotationXZ) {\n\n\t\tfloat posX = (float) (this.prevPosX + (this.posX - this.prevPosX) * (double) partialTicks);\n\t\tfloat posY = (float) (this.prevPosY + (this.posY - this.prevPosY) * (double) partialTicks);\n\t\tfloat posZ = (float) (this.prevPosZ + (this.posZ - this.prevPosZ) * (double) partialTicks);\n\t\t//Vector3f pos = new Vector3f((float) (entityIn.posX - particle.posX), (float) (entityIn.posY - particle.posY), (float) (entityIn.posZ - particle.posZ));\n\n\t\tint renderAmount = 0;\n\t\tif (noExtraParticles) {\n\t\t\trenderAmount = 1;\n\t\t} else {\n\t\t\trenderAmount = Math.min(extraParticlesBaseAmount + ((Math.max(0, severityOfRainRate-1)) * 5), CoroUtilParticle.maxRainDrops);\n\t\t}\n\n\t\tfor (int iii = 0; iii < renderAmount; iii++) {\n\n\t\t\tif (mesh.curBufferPos >= mesh.numInstances) return;\n\n\t\t\tVector3f pos;\n\n\t\t\tif (iii != 0) {\n\t\t\t\tpos = new Vector3f(posX + (float) CoroUtilParticle.rainPositions[iii].xCoord,\n\t\t\t\t\t\tposY + (float) CoroUtilParticle.rainPositions[iii].yCoord,\n\t\t\t\t\t\tposZ + (float) CoroUtilParticle.rainPositions[iii].zCoord);\n\t\t\t} else {\n\t\t\t\tpos = new Vector3f(posX, posY, posZ);\n\t\t\t}\n\n\t\t\tif (false && useRotationAroundCenter) {\n\t\t\t\tfloat deltaRot = rotationAroundCenterPrev + (rotationAroundCenter - rotationAroundCenterPrev) * partialTicks;\n\t\t\t\tfloat rotX = (float) Math.sin(Math.toRadians(deltaRot));\n\t\t\t\tfloat rotZ = (float) Math.cos(Math.toRadians(deltaRot));\n\t\t\t\tpos.x += rotX * rotationDistAroundCenter;\n\t\t\t\tpos.z += rotZ * rotationDistAroundCenter;\n\t\t\t}\n\n\t\t\tif (this.isDontRenderUnderTopmostBlock()) {\n\t\t\t\tint height = world.getHeight(Heightmap.Type.MOTION_BLOCKING, new BlockPos(pos.x, this.posY, pos.z)).getY();\n\t\t\t\tif (pos.y <= height) continue;\n\t\t\t}\n\n\t\t\t//adjust to relative to camera positions finally\n\t\t\tpos.x -= interpPosX;\n\t\t\tpos.y -= interpPosY;\n\t\t\tpos.z -= interpPosZ;\n\n\t\t\tMatrix4fe modelMatrix = transformation.buildModelMatrix(this, pos, partialTicks);\n\n\t\t\t//adjust to perspective and camera\n\t\t\t//Matrix4fe modelViewMatrix = transformation.buildModelViewMatrix(modelMatrix, viewMatrix);\n\t\t\t//upload to buffer\n\t\t\tmodelMatrix.get(mesh.INSTANCE_SIZE_FLOATS * (mesh.curBufferPos), mesh.instanceDataBuffer);\n\n\t\t\t//brightness\n\t\t\tfloat brightness;\n\t\t\t//brightness = CoroUtilBlockLightCache.getBrightnessCached(worldObj, pos.x, pos.y, pos.z);\n\t\t\t//brightness = this.brightnessCache;\n\t\t\tif (fastLight) {\n\t\t\t\tbrightness = CoroUtilBlockLightCache.brightnessPlayer;\n\t\t\t} else {\n\t\t\t\tbrightness = CoroUtilBlockLightCache.getBrightnessCached(world, (float)this.posX, (float)this.posY, (float)this.posZ);\n\t\t\t}\n\n\t\t\t//brightness to buffer\n\t\t\tmesh.instanceDataBuffer.put(mesh.INSTANCE_SIZE_FLOATS * (mesh.curBufferPos) + mesh.MATRIX_SIZE_FLOATS, brightness);\n\n\t\t\t//rgba to buffer\n\t\t\tint rgbaIndex = 0;\n\t\t\tmesh.instanceDataBuffer.put(mesh.INSTANCE_SIZE_FLOATS * (mesh.curBufferPos)\n\t\t\t\t\t+ mesh.MATRIX_SIZE_FLOATS + 1 + (rgbaIndex++), this.particleRed);\n\t\t\tmesh.instanceDataBuffer.put(mesh.INSTANCE_SIZE_FLOATS * (mesh.curBufferPos)\n\t\t\t\t\t+ mesh.MATRIX_SIZE_FLOATS + 1 + (rgbaIndex++), this.particleGreen);\n\t\t\tmesh.instanceDataBuffer.put(mesh.INSTANCE_SIZE_FLOATS * (mesh.curBufferPos)\n\t\t\t\t\t+ mesh.MATRIX_SIZE_FLOATS + 1 + (rgbaIndex++), this.particleBlue);\n\t\t\tmesh.instanceDataBuffer.put(mesh.INSTANCE_SIZE_FLOATS * (mesh.curBufferPos)\n\t\t\t\t\t+ mesh.MATRIX_SIZE_FLOATS + 1 + (rgbaIndex++), this.getAlphaF());\n\n\t\t\tmesh.curBufferPos++;\n\t\t}\n\n\t}*/\n\n\t/*@Override\n\tpublic void renderParticleForShaderTest(InstancedMeshParticle mesh, Transformation transformation, Matrix4fe viewMatrix, Entity entityIn, float partialTicks, float rotationX, float rotationZ, float rotationYZ, float rotationXY, float rotationXZ) {\n\n\n\t\tfloat posX = (float) (this.prevPosX + (this.posX - this.prevPosX) * (double) partialTicks);\n\t\tfloat posY = (float) (this.prevPosY + (this.posY - this.prevPosY) * (double) partialTicks);\n\t\tfloat posZ = (float) (this.prevPosZ + (this.posZ - this.prevPosZ) * (double) partialTicks);\n\n\t\tint renderAmount = 0;\n\t\tif (noExtraParticles) {\n\t\t\trenderAmount = 1;\n\t\t} else {\n\t\t\trenderAmount = Math.min(extraParticlesBaseAmount + ((Math.max(0, severityOfRainRate-1)) * 5), CoroUtilParticle.maxRainDrops);\n\t\t}\n\n\t\tfor (int iii = 0; iii < renderAmount; iii++) {\n\n\t\t\tif (mesh.curBufferPos >= mesh.numInstances) return;\n\n\t\t\tVector3f pos;\n\n\t\t\tif (iii != 0) {\n\t\t\t\tpos = new Vector3f(posX + (float) CoroUtilParticle.rainPositions[iii].xCoord,\n\t\t\t\t\t\tposY + (float) CoroUtilParticle.rainPositions[iii].yCoord,\n\t\t\t\t\t\tposZ + (float) CoroUtilParticle.rainPositions[iii].zCoord);\n\t\t\t} else {\n\t\t\t\tpos = new Vector3f(posX, posY, posZ);\n\t\t\t}\n\n\t\t\tif (this.isDontRenderUnderTopmostBlock()) {\n\t\t\t\tint height = this.world.getPrecipitationHeight(new BlockPos(pos.x, this.posY, pos.z)).getY();\n\t\t\t\tif (pos.y <= height) continue;\n\t\t\t}\n\n\t\t\t//adjust to relative to camera positions finally\n\t\t\tpos.x -= interpPosX;\n\t\t\tpos.y -= interpPosY;\n\t\t\tpos.z -= interpPosZ;\n\n\t\t\tint rgbaIndex = 0;\n\t\t\tmesh.instanceDataBufferTest.put(mesh.INSTANCE_SIZE_FLOATS_TEST * (mesh.curBufferPos)\n\t\t\t\t\t+ (rgbaIndex++), this.getRedColorF());\n\t\t\tmesh.instanceDataBufferTest.put(mesh.INSTANCE_SIZE_FLOATS_TEST * (mesh.curBufferPos)\n\t\t\t\t\t+ (rgbaIndex++), this.getGreenColorF());\n\t\t\tmesh.instanceDataBufferTest.put(mesh.INSTANCE_SIZE_FLOATS_TEST * (mesh.curBufferPos)\n\t\t\t\t\t+ (rgbaIndex++), this.getBlueColorF());\n\t\t\tmesh.instanceDataBufferTest.put(mesh.INSTANCE_SIZE_FLOATS_TEST * (mesh.curBufferPos)\n\t\t\t\t\t+ (rgbaIndex++), this.getAlphaF());\n\n\t\t\tmesh.curBufferPos++;\n\n\t\t}\n\n\t}*/\n\n\t/*@Override\n\tpublic void updateQuaternion(Entity camera) {\n\n\t\tif (camera != null) {\n\t\t\tif (this.facePlayer) {\n\t\t\t\tthis.rotationYaw = camera.rotationYaw;\n\t\t\t\tthis.rotationPitch = camera.rotationPitch;\n\t\t\t} else if (facePlayerYaw) {\n\t\t\t\tthis.rotationYaw = camera.rotationYaw;\n\t\t\t}\n\t\t}\n\n\t\tQuaternion qY = new Quaternion();\n\t\tQuaternion qX = new Quaternion();\n\t\tqY.setFromAxisAngle(new Vector4f(0, 1, 0, (float)Math.toRadians(-this.rotationYaw - 180F)));\n\t\tqX.setFromAxisAngle(new Vector4f(1, 0, 0, (float)Math.toRadians(-this.rotationPitch)));\n\t\tif (this.rotateOrderXY) {\n\t\t\tQuaternion.mul(qX, qY, this.rotation);\n\t\t} else {\n\t\t\tQuaternion.mul(qY, qX, this.rotation);\n\n\t\t\tif (extraYRotation != 0) {\n\t\t\t\t//float rot = (new Random()).nextFloat() * 360F;\n\t\t\t\tqY = new Quaternion();\n\t\t\t\tqY.setFromAxisAngle(new Vector4f(0, 1, 0, extraYRotation));\n\t\t\t\tQuaternion.mul(this.rotation, qY, this.rotation);\n\t\t\t}\n\t\t}\n\t}*/\n\n}\n"
  },
  {
    "path": "src/main/java/extendedrenderer/particle/entity/ParticleTexFX.java",
    "content": "package extendedrenderer.particle.entity;\n\nimport net.minecraft.client.renderer.texture.TextureAtlasSprite;\nimport net.minecraft.client.multiplayer.ClientLevel;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\n\n@OnlyIn(Dist.CLIENT)\npublic class ParticleTexFX extends EntityRotFX {\n\n\tpublic ParticleTexFX(ClientLevel worldIn, double posXIn, double posYIn, double posZIn, double mX, double mY, double mZ, TextureAtlasSprite par8Item)\n    {\n        super(worldIn, posXIn, posYIn, posZIn, mX, mY-0.5, mZ);\n        this.setSprite(par8Item);\n        //this.setParticleTexture(Minecraft.getInstance().getItemRenderer().getItemModelMesher().getParticleIcon(Items.IRON_AXE, 0));\n        this.rCol = 1.0F;\n        this.gCol = 1.0F;\n        this.bCol = 1.0F;\n        this.gravity = 1F;\n        this.quadSize = 0.15F;\n        this.setLifetime(100);\n        this.setCanCollide(false);\n    }\n\t\n\tpublic float getParticleGravity() {\n\t\treturn this.gravity;\n\t}\n\n    /*@Override\n    public int getFXLayer()\n    {\n        return 1;\n    }*/\n}\n"
  },
  {
    "path": "src/main/java/extendedrenderer/particle/entity/ParticleTexLeafColor.java",
    "content": "package extendedrenderer.particle.entity;\n\nimport com.corosus.coroutil.util.CoroUtilColor;\nimport com.corosus.coroutil.util.CoroUtilMisc;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.client.color.block.BlockColors;\nimport net.minecraft.client.renderer.texture.TextureAtlasSprite;\nimport net.minecraft.client.multiplayer.ClientLevel;\nimport net.minecraft.core.BlockPos;\nimport org.apache.commons.lang3.ArrayUtils;\n\nimport java.util.concurrent.ConcurrentHashMap;\n\npublic class ParticleTexLeafColor extends ParticleTexFX {\n    \n\t// Save a few stack depth by caching this\n\tprivate static BlockColors colors;\n\n\t//TODO: see forge note on field to use registry delegate\n\t//private static final Field _blockColorMap = null;//BlockColors.blockColors;\n\t//private static final Field _blockColorMap = ObfuscationReflectionHelper.findField(BlockColors.class, \"blockColors\");\n\t//private static Map<IRegistryDelegate<Block>, BlockColor> blockColorMap;\n\n\tprivate static ConcurrentHashMap<BlockState, int[]> colorCache = new ConcurrentHashMap<>();\n\t/*static {\n\t\t((SimpleReloadableResourceManager) Minecraft.getInstance().getResourceManager()).registerReloadListener(rm -> colorCache.clear());\n\t}*/\n\n\t//only use positives for now\n\tpublic float rotationYawMomentum;\n\tpublic float rotationPitchMomentum;\n\n\tpublic ParticleTexLeafColor(ClientLevel worldIn, double posXIn, double posYIn,\n\t\t\t\t\t\t\t\tdouble posZIn, double mX, double mY, double mZ,\n\t\t\t\t\t\t\t\tTextureAtlasSprite par8Item) {\n\t\tsuper(worldIn, posXIn, posYIn, posZIn, mX, mY, mZ, par8Item);\n\t\t\n\t\tif (colors == null) {\n\t\t    colors = Minecraft.getInstance().getBlockColors();\n\t\t\t/*try {\n\t\t\t\tblockColorMap = (Map<IRegistryDelegate<Block>, BlockColor>) _blockColorMap.get(colors);\n\t\t\t} catch (IllegalArgumentException | IllegalAccessException e) {\n\t\t\t\tthrow new RuntimeException(e);\n\t\t\t}*/\n\t\t}\n\t\t\n\t\tBlockPos pos = new BlockPos((int)Math.floor(posXIn), (int)Math.floor(posYIn), (int)Math.floor(posZIn));\n\t\tBlockState state = worldIn.getBlockState(pos);\n\n\t    // top of double plants doesn't have variant property\n\t\t//TODO: 1.14 uncomment\n\t\t/*if (state.getBlock() instanceof DoublePlantBlock && state.get(DoublePlantBlock.HALF) == EnumBlockHalf.UP) {\n\t\t    state = state.with(DoublePlantBlock.name, worldIn.getBlockState(pos.down()).get(DoublePlantBlock.name));\n\t\t}*/\n\n\t\tint multiplier = this.colors.getColor(state, this.level, pos, 0);\n\n\t\t//was this supposed to be temp?!\n\t\t//colorCache.clear();\n\t\tint[] colors = colorCache.get(state);\n\t\tif (colors == null) {\n\n//\t\t\tcolors = IntArrays.EMPTY_ARRAY;\n\t\t    colors = CoroUtilColor.getColors(state);\n\n\t\t    if (colors.length == 0) {\n\n\t\t    \t//if there is no color to use AND theres no multiplier, fallback to good ol green\n\t\t\t\tif (!hasColor(state) || (multiplier & 0xFFFFFF) == 0xFFFFFF) {\n\t\t\t\t\tmultiplier = 5811761; //color for vanilla leaf in forest biome\n\t\t\t\t}\n\n\t\t\t\t//add just white that will get colormultiplied\n\t\t\t\t//TODO: adjusted for LT19\n\t\t\t\t//colors = new int[] { 0xFFFFFF };\n\t\t\t\tcolors = new int[] { 0x00FF00 };\n\t\t    }\n\t\t    // Remove duplicate colors from end of array, this will skew the random choice later\n\t\t\tif (colors.length > 1) {\n\t\t\t\twhile (colors[colors.length - 1] == colors[colors.length - 2]) {\n\t\t\t\t\tcolors = ArrayUtils.remove(colors, colors.length - 1);\n\t\t\t\t}\n\t\t\t}\n\t\t    colorCache.put(state, colors);\n\t\t}\n\t\t\n\t\t// Randomize the color with exponential decrease in likelihood. That is, the first color has a 50% chance, then 25%, etc.\n\t\tint randMax = 1 << (colors.length - 1);\n\t\tint choice = 32 - Integer.numberOfLeadingZeros(CoroUtilMisc.random.nextInt(randMax));\n\t\tint color = colors[choice];\n\n\t\tfloat mr = ((multiplier >>> 16) & 0xFF) / 255f;\n\t\tfloat mg = ((multiplier >>> 8) & 0xFF) / 255f;\n\t\tfloat mb = (multiplier & 0xFF) / 255f;\n\n\t\tthis.rCol *= (float) (color >> 16 & 255) / 255.0F * mr;\n\t\tthis.gCol *= (float) (color >> 8 & 255) / 255.0F * mg;\n\t\tthis.bCol *= (float) (color & 255) / 255.0F * mb;\n\t}\n\n\t@Override\n\tpublic void tick() {\n\t\tsuper.tick();\n\n\t\t//make leafs catch on the ground and cause them to bounce up and slow a bit for effect\n\t\tif (isCollidedVerticallyDownwards && random.nextInt(10) == 0) {\n\t\t\tdouble speed = Math.sqrt(this.xd * this.xd + this.zd * this.zd);\n\t\t\tif (speed > 0.07) {\n\t\t\t\tthis.yd = 0.02D + random.nextDouble() * 0.03D;\n\t\t\t\tthis.xd *= 0.6D;\n\t\t\t\tthis.zd *= 0.6D;\n\n\t\t\t\trotationYawMomentum = 30;\n\t\t\t\trotationPitchMomentum = 30;\n\t\t\t}\n\t\t}\n\n\t\tif (rotationYawMomentum > 0) {\n\n\t\t\tthis.rotationYaw += rotationYawMomentum;\n\n\t\t\trotationYawMomentum -= 1.5F;\n\n\t\t\tif (rotationYawMomentum < 0) {\n\t\t\t\trotationYawMomentum = 0;\n\t\t\t}\n\t\t} else {\n\t\t\trotationYawMomentum += random.nextDouble() * 30;\n\t\t}\n\n\t\tif (rotationPitchMomentum > 0) {\n\n\t\t\tthis.rotationPitch += rotationPitchMomentum;\n\n\t\t\trotationPitchMomentum -= 1.5F;\n\n\t\t\tif (rotationPitchMomentum < 0) {\n\t\t\t\trotationPitchMomentum = 0;\n\t\t\t}\n\t\t} else {\n\t\t\trotationPitchMomentum += random.nextDouble() * 30;\n\t\t}\n\t}\n\n\tprivate final boolean hasColor(BlockState state) {\n\t\treturn false;//blockColorMap.containsKey(state.getBlock().delegate);\n\t}\n\n}\n"
  },
  {
    "path": "src/main/java/extendedrenderer/particle/entity/PivotingParticle.java",
    "content": "package extendedrenderer.particle.entity;\n\nimport com.mojang.blaze3d.vertex.VertexConsumer;\nimport net.minecraft.client.Camera;\nimport net.minecraft.client.multiplayer.ClientLevel;\nimport net.minecraft.client.renderer.texture.TextureAtlasSprite;\nimport net.minecraft.world.phys.AABB;\nimport net.minecraft.world.phys.Vec3;\n\n\n/**\n * Particle that has a secondary rotation with a 1 dimensional pivot point.\n * For rotating particles around a point with nice rotational interpolation.\n */\n\npublic class PivotingParticle extends ParticleTexFX {\n\n    private Vec3 pivot = new Vec3(0, 0, 0);\n    private Vec3 pivotPrev = new Vec3(0, 0, 0);\n    //in degrees\n    private Vec3 pivotRot = new Vec3(0, 0, 0);\n    private Vec3 pivotRotPrev = new Vec3(0, 0, 0);\n\n    public PivotingParticle(ClientLevel worldIn, double posXIn, double posYIn, double posZIn, double mX, double mY, double mZ, TextureAtlasSprite par8Item) {\n        super(worldIn, posXIn, posYIn, posZIn, mX, mY, mZ, par8Item);\n    }\n\n    @Override\n    public void tick() {\n        super.tick();\n    }\n\n    /**\n     * Get coordinates for pivoted rotation\n     * For now we'll just do a rotation around the y axis\n     * if we need to do full 3d pivoting, use a rotation matrix with quaternion and make use of the full vectors\n     *\n     * @param partialTicks\n     * @return\n     */\n    @Override\n    public Vec3 getPivotedPosition(float partialTicks) {\n        Vec3 pivotLerped = pivotPrev.lerp(pivot, partialTicks);\n        Vec3 pivotRotLerped = pivotRotPrev.lerp(pivotRot, partialTicks);\n        float x = (float) (-Math.sin(Math.toRadians(pivotRotLerped.y)) * pivotLerped.y);\n        float z = (float) (Math.cos(Math.toRadians(pivotRotLerped.y)) * pivotLerped.y);\n        return new Vec3(x, 0, z);\n    }\n\n    public Vec3 getPivot() {\n        return pivot;\n    }\n\n    public void setPivot(Vec3 pivot) {\n        this.pivot = pivot;\n    }\n\n    public Vec3 getPivotPrev() {\n        return pivotPrev;\n    }\n\n    public void setPivotPrev(Vec3 pivotPrev) {\n        this.pivotPrev = pivotPrev;\n    }\n\n    public Vec3 getPivotRot() {\n        return pivotRot;\n    }\n\n    public void setPivotRot(Vec3 pivotRot) {\n        this.pivotRot = pivotRot;\n    }\n\n    public Vec3 getPivotRotPrev() {\n        return pivotRotPrev;\n    }\n\n    public void setPivotRotPrev(Vec3 pivotRotPrev) {\n        this.pivotRotPrev = pivotRotPrev;\n    }\n\n    @Override\n    public AABB getBoundingBoxForRender(float partialTicks) {\n        return getBoundingBox().move(getPivotedPosition(partialTicks));\n    }\n\n    @Override\n    public void render(VertexConsumer buffer, Camera renderInfo, float partialTicks) {\n        super.render(buffer, renderInfo, partialTicks);\n    }\n}\n"
  },
  {
    "path": "src/main/java/extendedrenderer/particle/entity/WaterDropParticleImpl.java",
    "content": "package extendedrenderer.particle.entity;\n\nimport net.minecraft.client.multiplayer.ClientLevel;\nimport net.minecraft.client.particle.WaterDropParticle;\nimport net.minecraft.core.particles.SimpleParticleType;\nimport net.minecraft.world.level.Level;\n\npublic class WaterDropParticleImpl extends WaterDropParticle {\n    public WaterDropParticleImpl(SimpleParticleType simpleParticleType, ClientLevel p_108484_, double p_108485_, double p_108486_, double p_108487_, double v3, double v4, double v5) {\n        super(p_108484_, p_108485_, p_108486_, p_108487_);\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/ClientRegistry.java",
    "content": "package weather2;\n\nimport net.minecraft.client.renderer.entity.EntityRenderers;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport net.minecraftforge.client.event.EntityRenderersEvent;\nimport net.minecraftforge.eventbus.api.SubscribeEvent;\nimport net.minecraftforge.fml.common.Mod;\nimport net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;\nimport weather2.client.tile.AnemometerEntityRenderer;\nimport weather2.client.tile.WindTurbineEntityRenderer;\nimport weather2.client.tile.WindVaneEntityRenderer;\nimport weather2.client.entity.model.AnemometerModel;\nimport weather2.client.entity.model.WindTurbineModel;\nimport weather2.client.entity.model.WindVaneModel;\nimport weather2.client.entity.render.LightningBoltWeatherNewRenderer;\n\n@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)\npublic class ClientRegistry {\n\n    @OnlyIn(Dist.CLIENT)\n    @SubscribeEvent\n    public static void clientSetup(FMLClientSetupEvent event) {\n        EntityRenderers.register(EntityRegistry.LIGHTNING_BOLT.get(), render -> new LightningBoltWeatherNewRenderer(render));\n    }\n\n    @OnlyIn(Dist.CLIENT)\n    @SubscribeEvent\n    public static void registerRenderers(EntityRenderersEvent.RegisterRenderers e) {\n        e.registerBlockEntityRenderer(WeatherBlocks.BLOCK_ENTITY_ANEMOMETER.get(), AnemometerEntityRenderer::new);\n        e.registerBlockEntityRenderer(WeatherBlocks.BLOCK_ENTITY_WIND_VANE.get(), WindVaneEntityRenderer::new);\n        e.registerBlockEntityRenderer(WeatherBlocks.BLOCK_ENTITY_WIND_TURBINE.get(), WindTurbineEntityRenderer::new);\n    }\n\n    @OnlyIn(Dist.CLIENT)\n    @SubscribeEvent\n    public static void registerLayerDefinitions(EntityRenderersEvent.RegisterLayerDefinitions event) {\n        event.registerLayerDefinition(WindVaneModel.LAYER_LOCATION, WindVaneModel::createBodyLayer);\n        event.registerLayerDefinition(AnemometerModel.LAYER_LOCATION, AnemometerModel::createBodyLayer);\n        event.registerLayerDefinition(WindTurbineModel.LAYER_LOCATION, WindTurbineModel::createBodyLayer);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/weather2/ClientTickHandler.java",
    "content": "package weather2;\n\nimport com.corosus.coroutil.util.CULog;\nimport extendedrenderer.ParticleManagerExtended;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.client.gui.screens.BackupConfirmScreen;\nimport net.minecraft.client.multiplayer.ClientLevel;\nimport net.minecraft.nbt.CompoundTag;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.level.Level;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.event.TickEvent;\nimport net.minecraftforge.eventbus.api.SubscribeEvent;\nimport net.minecraftforge.fml.common.Mod;\nimport net.minecraftforge.network.NetworkDirection;\nimport weather2.client.SceneEnhancer;\nimport weather2.config.ClientConfigData;\nimport weather2.config.ConfigDebug;\nimport weather2.util.WeatherUtil;\nimport weather2.util.WindReader;\nimport weather2.weathersystem.WeatherManagerClient;\n\n@Mod.EventBusSubscriber(modid = Weather.MODID, value = Dist.CLIENT)\npublic class ClientTickHandler\n{\n\tpublic static final ClientTickHandler INSTANCE = new ClientTickHandler();\n\n\tpublic static Level lastWorld;\n\t\n\tpublic static WeatherManagerClient weatherManager;\n\tpublic static SceneEnhancer sceneEnhancer;\n\n\tpublic static ClientConfigData clientConfigData;\n\n\tpublic float smoothAngle = 0;\n\n\tpublic float smoothAngleRotationalVelAccel = 0;\n\n\tpublic float smoothAngleAdj = 0.1F;\n\n\tpublic int prevDir = 0;\n\n\tpublic long lastParticleResetTime = 0;\n\n\tprivate static ParticleManagerExtended particleManagerExtended;\n\n\tprivate ClientTickHandler() {\n\t\t//this constructor gets called multiple times when created from proxy, this prevents multiple inits\n\t\tif (sceneEnhancer == null) {\n\t\t\tsceneEnhancer = new SceneEnhancer();\n\t\t\t(new Thread(sceneEnhancer, \"Weather2 Scene Enhancer\")).start();\n\t\t}\n\n\t\tclientConfigData = new ClientConfigData();\n\t}\n\n\t@SubscribeEvent\n\tpublic static void tick(TickEvent.ClientTickEvent event) {\n\t\tif (event.phase == TickEvent.Phase.START) {\n\t\t\tINSTANCE.onTickInGame();\n\t\t}\n\t}\n\n    public void onTickInGame()\n    {\n        Minecraft mc = Minecraft.getInstance();\n        Level world = mc.level;\n\n\t\tif (world != null) {\n\t\t\tgetClientWeather();\n\n\t\t\tweatherManager.tick();\n\t\t\tsceneEnhancer.tickClient();\n\n\t\t\tif (!WeatherUtil.isPausedForClient()) {\n\t\t\t\tif (ConfigDebug.Particle_engine_tick) {\n\t\t\t\t\tparticleManagerExtended().tick();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (ConfigDebug.Particle_Reset_Frequency > 0 && lastParticleResetTime + ConfigDebug.Particle_Reset_Frequency < world.getGameTime()) {\n\t\t\t\tCULog.log(\"clearing vanilla particles, set Weather2 Debug Particle_Reset_Frequency to 0 to disable\");\n\t\t\t\tlastParticleResetTime = world.getGameTime();\n\t\t\t\tmc.particleEngine.clearParticles();\n\t\t\t}\n\n\t\t\t//TODO: evaluate if best here\n\t\t\tfloat windDir = WindReader.getWindAngle(world);\n\t\t\tfloat windSpeed = WindReader.getWindSpeed(world, mc.player != null ? mc.player.blockPosition() : null);\n\n\t\t\t//windDir = 0;\n\t\t\t//TODO: ???????????? what is all this even affecting now\n\t\t\tfloat diff = Math.abs(windDir - smoothAngle)/* - 180*/;\n\n\t\t\tif (true && diff > 10/* && (smoothAngle > windDir - give || smoothAngle < windDir + give)*/) {\n\n\t\t\t\tif (smoothAngle > 180) smoothAngle -= 360;\n\t\t\t\tif (smoothAngle < -180) smoothAngle += 360;\n\n\t\t\t\tfloat bestMove = Mth.wrapDegrees(windDir - smoothAngle);\n\n\t\t\t\tsmoothAngleAdj = windSpeed;//0.2F;\n\n\t\t\t\tif (Math.abs(bestMove) < 180/* - (angleAdjust * 2)*/) {\n\t\t\t\t\tfloat realAdj = smoothAngleAdj;//Math.max(smoothAngleAdj, Math.abs(bestMove));\n\n\t\t\t\t\tif (realAdj * 2 > windSpeed) {\n\t\t\t\t\t\tif (bestMove > 0) {\n\t\t\t\t\t\t\tsmoothAngleRotationalVelAccel -= realAdj;\n\t\t\t\t\t\t\tif (prevDir < 0) {\n\t\t\t\t\t\t\t\tsmoothAngleRotationalVelAccel = 0;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tprevDir = 1;\n\t\t\t\t\t\t} else if (bestMove < 0) {\n\t\t\t\t\t\t\tsmoothAngleRotationalVelAccel += realAdj;\n\t\t\t\t\t\t\tif (prevDir > 0) {\n\t\t\t\t\t\t\t\tsmoothAngleRotationalVelAccel = 0;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tprevDir = -1;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (smoothAngleRotationalVelAccel > 0.3 || smoothAngleRotationalVelAccel < -0.3) {\n\t\t\t\t\t\tsmoothAngle += smoothAngleRotationalVelAccel * 0.3F;\n\t\t\t\t\t} else {\n\t\t\t\t\t\t//smoothAngleRotationalVelAccel *= 0.9F;\n\t\t\t\t\t}\n\n\t\t\t\t\tsmoothAngleRotationalVelAccel *= 0.80F;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tresetClientWeather();\n\t\t}\n\n    }\n\n    public static void resetClientWeather() {\n\t\tweatherManager = null;\n\t\tClientWeatherProxy.get().reset();\n\t\tClientWeatherHelper.get().reset();\n\t}\n\n    public static WeatherManagerClient getClientWeather() {\n\n    \ttry {\n\t\t\tLevel world = Minecraft.getInstance().level;\n    \t\tif (weatherManager == null || world != lastWorld) {\n    \t\t\tinit(world);\n        \t}\n    \t} catch (Exception ex) {\n    \t\tWeather.dbg(\"Weather2: Warning, client received packet before it was ready to use, and failed to init client weather due to null world\");\n    \t}\n\t\treturn weatherManager;\n    }\n\n    public static void init(Level world) {\n\t\tWeather.dbg(\"Weather2: Initializing WeatherManagerClient for client world and requesting full sync\");\n\n    \tlastWorld = world;\n    \tweatherManager = new WeatherManagerClient(world.dimension());\n\n    \tMinecraft mc = Minecraft.getInstance();\n\n    \tif (particleManagerExtended == null) {\n\t\t\tparticleManagerExtended = new ParticleManagerExtended(mc.level, mc.textureManager);\n\t\t} else {\n\t\t\tparticleManagerExtended.setLevel((ClientLevel) world);\n\t\t}\n\n\t\t//((IReloadableResourceManager)mc.getResourceManager()).addReloadListener(particleManagerExtended);\n\t\tCompoundTag data = new CompoundTag();\n\t\tdata.putString(\"command\", \"syncFull\");\n\t\tdata.putString(\"packetCommand\", \"WeatherData\");\n\t\t//Weather.eventChannel.sendToServer(PacketHelper.getNBTPacket(data, Weather.eventChannelName));\n\t\tWeatherNetworking.HANDLER.sendTo(new PacketNBTFromClient(data), mc.player.connection.getConnection(), NetworkDirection.PLAY_TO_SERVER);\n    }\n\n\tpublic static ParticleManagerExtended particleManagerExtended() {\n\t\treturn particleManagerExtended;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/ClientWeatherHelper.java",
    "content": "package weather2;\n\nimport com.corosus.coroutil.util.CULog;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.world.entity.player.Player;\nimport net.minecraft.world.phys.Vec3;\nimport weather2.config.ConfigMisc;\nimport weather2.config.ConfigStorm;\nimport weather2.weathersystem.storm.StormObject;\n\n/**\n * Moving client weather logic to here from scene enhancer.\n * Also when LT isnt in control of the weather, logic redirects to here for weather2s more special rules\n */\npublic final class ClientWeatherHelper {\n\tprivate static ClientWeatherHelper instance;\n\n\tprivate float curPrecipStr = 0F;\n\tprivate float curPrecipStrTarget = 0F;\n\n\tprivate float curOvercastStr = 0F;\n\tprivate float curOvercastStrTarget = 0F;\n\n\tprivate ClientWeatherHelper() {\n\t}\n\n\tpublic static ClientWeatherHelper get() {\n\t\tif (instance == null) {\n\t\t\tinstance = new ClientWeatherHelper();\n\t\t}\n\t\treturn instance;\n\t}\n\n\tpublic void reset() {\n\t\tinstance.curPrecipStr = 0F;\n\t\tinstance.curPrecipStrTarget = 0F;\n\n\t\tinstance.curOvercastStr = 0F;\n\t\tinstance.curOvercastStrTarget = 0F;\n\t}\n\n\tpublic void tick() {\n\t\ttickRainRates();\n\t}\n\n\tpublic float getPrecipitationStrength(Player entP) {\n\t\treturn getPrecipitationStrength(entP, false);\n\t}\n\n\t/**\n\t * returns 0 to 1 of storm strength\n\t *\n\t * @param entP\n\t * @param forOvercast\n\t * @return\n\t */\n\tpublic float getPrecipitationStrength(Player entP, boolean forOvercast) {\n\n\t\tif (entP == null) return 0;\n\t\tdouble maxStormDist = 512 / 4 * 3;\n\t\tVec3 plPos = new Vec3(entP.getX(), StormObject.static_YPos_layer0, entP.getZ());\n\t\tStormObject storm;\n\n\t\tClientTickHandler.getClientWeather();\n\n\t\tstorm = ClientTickHandler.weatherManager.getClosestStorm(plPos, maxStormDist, StormObject.STATE_FORMING, -1, true);\n\n\t\tboolean closeEnough = false;\n\t\tdouble stormDist = 9999;\n\t\tfloat tempAdj = 1F;\n\n\t\tfloat sizeToUse = 0;\n\n\t\tfloat overcastModeMinPrecip = 0.23F;\n\t\t//overcastModeMinPrecip = 0.16F;\n\t\t//overcastModeMinPrecip = (float) ConfigStorm.Storm_Rain_Overcast_Amount;\n\t\tovercastModeMinPrecip = ClientTickHandler.weatherManager.vanillaRainAmountOnServer;\n\n\t\t//evaluate if storms size is big enough to be over player\n\t\tif (storm != null) {\n\n\t\t\tsizeToUse = storm.size;\n\t\t\t//extend overcast effect, using x2 for now since we cant cancel sound and ground particles, originally was 4x, then 3x, change to that for 1.7 if lex made change\n\t\t\tif (forOvercast) {\n\t\t\t\tsizeToUse *= 1F;\n\t\t\t}\n\n\t\t\tstormDist = storm.pos.distanceTo(plPos);\n\t\t\t//System.out.println(\"storm dist: \" + stormDist);\n\t\t\tif (sizeToUse > stormDist) {\n\t\t\t\tcloseEnough = true;\n\t\t\t}\n\t\t}\n\n\t\tif (closeEnough) {\n\t\t\t//max of 1 if at center of storm, subtract player xz distance out of the size to act like its a weaker storm\n\t\t\tdouble stormIntensity = (sizeToUse - stormDist) / sizeToUse;\n\n\t\t\t//why is this not a -1 or 1 anymore?!\n\t\t\t//tempAdj = storm.levelTemperature/* > 0 ? 1F : -1F*/;\n\n\t\t\ttempAdj = 1F;//storm.levelTemperature/* > 0 ? 1F : -1F*/;\n\n\t\t\t//limit plain rain clouds to light intensity\n\t\t\tif (storm.levelCurIntensityStage == StormObject.STATE_NORMAL) {\n\t\t\t\tif (stormIntensity > 0.3) stormIntensity = 0.3;\n\t\t\t}\n\n\t\t\tif (ConfigStorm.Storm_NoRainVisual) {\n\t\t\t\tstormIntensity = 0;\n\t\t\t}\n\n\t\t\t//TODO: verify this if statement was added correctly\n\t\t\tif (forOvercast) {\n\t\t\t\tif (stormIntensity < overcastModeMinPrecip) {\n\t\t\t\t\tstormIntensity = overcastModeMinPrecip;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (forOvercast) {\n\t\t\t\tcurOvercastStrTarget = (float) stormIntensity;\n\t\t\t} else {\n\t\t\t\tcurPrecipStrTarget = (float) stormIntensity;\n\t\t\t}\n\t\t} else {\n\t\t\tif (!ClientTickHandler.clientConfigData.overcastMode) {\n\t\t\t\tif (forOvercast) {\n\t\t\t\t\tcurOvercastStrTarget = 0;\n\t\t\t\t} else {\n\t\t\t\t\tcurPrecipStrTarget = 0;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (ClientTickHandler.weatherManager.isVanillaRainActiveOnServer) {\n\t\t\t\t\tif (forOvercast) {\n\t\t\t\t\t\tcurOvercastStrTarget = overcastModeMinPrecip;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcurPrecipStrTarget = overcastModeMinPrecip;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif (forOvercast) {\n\t\t\t\t\t\tcurOvercastStrTarget = 0;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcurPrecipStrTarget = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (forOvercast) {\n\t\t\tif (curOvercastStr < 0.002 && curOvercastStr > -0.002F) {\n\t\t\t\treturn 0;\n\t\t\t} else {\n\t\t\t\treturn curOvercastStr * tempAdj;\n\t\t\t}\n\t\t} else {\n\t\t\tif (curPrecipStr < 0.002 && curPrecipStr > -0.002F) {\n\t\t\t\treturn 0;\n\t\t\t} else {\n\t\t\t\treturn curPrecipStr * tempAdj;\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic void controlVisuals(boolean precipitating) {\n\t\tMinecraft mc = Minecraft.getInstance();\n\t\tClientTickHandler.getClientWeather();\n\t\tClientWeatherProxy weather = ClientWeatherProxy.get();\n\t\tfloat rainAmount = weather.getVanillaRainAmount();\n\t\tfloat visualDarknessAmplifier = 0.5F;\n\t\t//using 1F to make shaders happy\n\t\tvisualDarknessAmplifier = 1F;\n\t\t//CULog.dbg(\"rainAmount: \" + rainAmount);\n\t\tif (!ConfigMisc.Aesthetic_Only_Mode) {\n\t\t\tif (precipitating) {\n\t\t\t\tmc.level.getLevelData().setRaining(rainAmount > 0);\n\t\t\t\tmc.level.setRainLevel(rainAmount * visualDarknessAmplifier);\n\t\t\t\tmc.level.setThunderLevel(rainAmount * visualDarknessAmplifier);\n\n\t\t\t} else {\n\t\t\t\t//TODO: i think these glitch out and trigger on world load if it was already raining, will think its false for a sec and lock sky visual to off\n\t\t\t\tif (!ClientTickHandler.clientConfigData.overcastMode) {\n\t\t\t\t\tmc.level.getLevelData().setRaining(false);\n\t\t\t\t\tmc.level.setRainLevel(0);\n\t\t\t\t\tmc.level.setThunderLevel(0);\n\t\t\t\t} else {\n\t\t\t\t\tif (ClientTickHandler.weatherManager.isVanillaRainActiveOnServer) {\n\t\t\t\t\t\tmc.level.getLevelData().setRaining(true);\n\t\t\t\t\t\tmc.level.setRainLevel(rainAmount * visualDarknessAmplifier);\n\t\t\t\t\t\tmc.level.setThunderLevel(rainAmount * visualDarknessAmplifier);\n\t\t\t\t\t} else {\n\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t//TESTING\n\t\t/*mc.level.getLevelData().setRaining(true);\n\t\tmc.level.setRainLevel(1);\n\t\tmc.level.setThunderLevel(1);*/\n\t}\n\n\tpublic void tickRainRates() {\n\n\t\tfloat rateChange = 0.0015F;\n\n\t\tif (curOvercastStr > curOvercastStrTarget) {\n\t\t\tcurOvercastStr -= rateChange;\n\t\t} else if (curOvercastStr < curOvercastStrTarget) {\n\t\t\tcurOvercastStr += rateChange;\n\t\t}\n\n\t\tif (curPrecipStr > curPrecipStrTarget) {\n\t\t\tcurPrecipStr -= rateChange;\n\t\t} else if (curPrecipStr < curPrecipStrTarget) {\n\t\t\tcurPrecipStr += rateChange;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/ClientWeatherProxy.java",
    "content": "package weather2;\n\nimport net.minecraft.world.level.biome.Biome;\nimport weather2.datatypes.PrecipitationType;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.world.entity.player.Player;\nimport net.minecraft.world.phys.Vec3;\nimport weather2.client.SceneEnhancer;\nimport weather2.ltcompat.ClientWeatherIntegration;\nimport weather2.weathersystem.storm.StormObject;\nimport weather2.weathersystem.storm.WeatherObjectParticleStorm;\n\nimport javax.annotation.Nullable;\n\npublic final class ClientWeatherProxy {\n\tprivate static ClientWeatherProxy instance;\n\n\tprivate static boolean cacheIsSnowstorm = false;\n\tprivate static boolean cacheIsSandstorm = false;\n\tprivate static boolean cacheIsHail = false;\n\tprivate static int cacheRate = 40;\n\n\tprivate ClientWeatherProxy() {\n\t}\n\n\tpublic static ClientWeatherProxy get() {\n\t\tif (instance == null) {\n\t\t\tinstance = new ClientWeatherProxy();\n\t\t}\n\t\treturn instance;\n\t}\n\n\tpublic void reset() {\n\t\tcacheIsSnowstorm = false;\n\t\tcacheIsSandstorm = false;\n\t\tcacheIsHail = false;\n\t}\n\n\tpublic float getRainAmount() {\n\t\tif (isWeatherEffectsServerSideControlled()) {\n\t\t\treturn ClientWeatherIntegration.get().getRainAmount();\n\t\t/*} else if (ClientTickHandler.clientConfigData.overcastMode) {\n\t\t\tif (Minecraft.getInstance().level == null) return 0;\n\t\t\treturn Math.max(0, Minecraft.getInstance().level.rainLevel * SceneEnhancer.downfallSheetThreshold - 0.02F); //enough rain without the downfall\n\t\t*/} else {\n\t\t\treturn ClientWeatherHelper.get().getPrecipitationStrength(Minecraft.getInstance().player);\n\t\t}\n\t}\n\n\tpublic float getVanillaRainAmount() {\n\t\tif (Weather.isLoveTropicsInstalled()) {\n\t\t\treturn ClientWeatherIntegration.get().getVanillaRainAmount();\n\t\t/*} else if (ClientTickHandler.clientConfigData.overcastMode) {\n\t\t\tif (Minecraft.getInstance().level == null) return 0;\n\t\t\treturn Minecraft.getInstance().level.rainLevel * 1F;\n\t\t*/} else {\n\t\t\treturn ClientWeatherHelper.get().getPrecipitationStrength(Minecraft.getInstance().player);\n\t\t}\n\t}\n\n\t@Nullable\n\tpublic PrecipitationType getPrecipitationType(Biome biome) {\n\t\tif (Weather.isLoveTropicsInstalled()) {\n\t\t\treturn ClientWeatherIntegration.get().getPrecipitationType();\n\t\t} else {\n\t\t\tif (biome == null) return null;\n\t\t\tif (biome.hasPrecipitation() && biome.getModifiedClimateSettings().temperatureModifier() == Biome.TemperatureModifier.NONE) return PrecipitationType.NORMAL;\n\t\t\tif (biome.hasPrecipitation() && biome.getModifiedClimateSettings().temperatureModifier() == Biome.TemperatureModifier.FROZEN) return PrecipitationType.SNOW;\n\t\t\tif (!biome.hasPrecipitation()) return null;\n\t\t}\n\t\treturn null;\n\t}\n\n\tpublic float getWindSpeed() {\n\t\treturn ClientWeatherIntegration.get().getWindSpeed();\n\t}\n\n\tpublic boolean isHeatwave() {\n\t\treturn ClientWeatherIntegration.get().isHeatwave();\n\t}\n\n\tpublic boolean isSandstorm() {\n\t\tif (isWeatherEffectsServerSideControlled()) {\n\t\t\treturn ClientWeatherIntegration.get().isSandstorm();\n\t\t} else {\n\t\t\tMinecraft client = Minecraft.getInstance();\n\t\t\tPlayer player = client.player;\n\t\t\tif (player == null) return false;\n\t\t\tif (player.level().getGameTime() % cacheRate == 0) {\n\t\t\t\tVec3 posPlayer = new Vec3(client.player.getX(), 0, client.player.getZ());\n\t\t\t\tWeatherObjectParticleStorm storm = ClientTickHandler.weatherManager.getClosestParticleStormByIntensity(posPlayer, WeatherObjectParticleStorm.StormType.SANDSTORM);\n\t\t\t\tcacheIsSandstorm = storm != null && posPlayer.distanceTo(storm.pos) < storm.getSize();\n\t\t\t}\n\t\t\treturn cacheIsSandstorm;\n\t\t}\n\t}\n\n\tpublic boolean isSnowstorm() {\n\t\t//return ClientWeatherIntegration.get().isSnowstorm();\n\t\tif (isWeatherEffectsServerSideControlled()) {\n\t\t\treturn ClientWeatherIntegration.get().isSnowstorm();\n\t\t} else {\n\t\t\tMinecraft client = Minecraft.getInstance();\n\t\t\tPlayer player = client.player;\n\t\t\tif (player == null) return false;\n\t\t\tif (player.level().getGameTime() % cacheRate == 0) {\n\t\t\t\tVec3 posPlayer = new Vec3(client.player.getX(), 0, client.player.getZ());\n\t\t\t\tWeatherObjectParticleStorm storm = ClientTickHandler.weatherManager.getClosestParticleStormByIntensity(posPlayer, WeatherObjectParticleStorm.StormType.SNOWSTORM);\n\t\t\t\tcacheIsSnowstorm = storm != null && posPlayer.distanceTo(storm.pos) < storm.getSize();\n\t\t\t}\n\t\t\treturn cacheIsSnowstorm;\n\t\t}\n\t}\n\n\tpublic boolean isHail() {\n\t\tMinecraft client = Minecraft.getInstance();\n\t\tPlayer player = client.player;\n\t\tif (player == null) return false;\n\t\tif (player.level().getGameTime() % cacheRate == 0) {\n\t\t\tVec3 posPlayer = new Vec3(client.player.getX(), 0, client.player.getZ());\n\t\t\tdouble maxStormDist = 512 / 4 * 3;\n\t\t\tStormObject storm = ClientTickHandler.weatherManager.getClosestStorm(posPlayer, maxStormDist, StormObject.STATE_HAIL, StormObject.STATE_HAIL, false);\n\t\t\tcacheIsHail = storm != null && posPlayer.distanceTo(storm.posGround) < storm.getSize();\n\t\t}\n\t\treturn cacheIsHail;\n\t}\n\n\tpublic boolean hasWeather() {\n\t\tif (SceneEnhancer.FORCE_ON_DEBUG_TESTING) return true;\n\t\treturn ClientWeatherIntegration.get().hasWeather();\n\t}\n\n\tpublic boolean isWeatherEffectsServerSideControlled() {\n\t\treturn Weather.isLoveTropicsInstalled();\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/DeferredHelper.java",
    "content": "package weather2;\n\nimport java.lang.invoke.MethodHandle;\nimport java.lang.invoke.MethodHandles;\nimport java.lang.reflect.Method;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.IdentityHashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Supplier;\n\nimport net.minecraft.core.Registry;\nimport net.minecraft.core.particles.ParticleOptions;\nimport net.minecraft.core.particles.ParticleType;\nimport net.minecraft.core.registries.Registries;\nimport net.minecraft.resources.ResourceKey;\nimport net.minecraft.resources.ResourceLocation;\nimport net.minecraft.sounds.SoundEvent;\nimport net.minecraft.stats.StatFormatter;\nimport net.minecraft.stats.StatType;\nimport net.minecraft.stats.Stats;\nimport net.minecraft.world.Container;\nimport net.minecraft.world.effect.MobEffect;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.entity.EntityType;\nimport net.minecraft.world.entity.ai.attributes.Attribute;\nimport net.minecraft.world.entity.decoration.PaintingVariant;\nimport net.minecraft.world.inventory.AbstractContainerMenu;\nimport net.minecraft.world.inventory.MenuType;\nimport net.minecraft.world.item.CreativeModeTab;\nimport net.minecraft.world.item.Item;\nimport net.minecraft.world.item.alchemy.Potion;\nimport net.minecraft.world.item.crafting.Recipe;\nimport net.minecraft.world.item.crafting.RecipeSerializer;\nimport net.minecraft.world.item.crafting.RecipeType;\nimport net.minecraft.world.item.enchantment.Enchantment;\nimport net.minecraft.world.level.block.Block;\nimport net.minecraft.world.level.block.entity.BlockEntity;\nimport net.minecraft.world.level.block.entity.BlockEntityType;\nimport net.minecraft.world.level.levelgen.feature.Feature;\nimport net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration;\nimport net.minecraft.world.level.material.Fluid;\nimport net.minecraftforge.eventbus.api.SubscribeEvent;\nimport net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;\nimport net.minecraftforge.registries.RegisterEvent;\nimport net.minecraftforge.registries.RegistryObject;\n\npublic class DeferredHelper {\n\n    protected final String modid;\n    protected final Map<ResourceKey<? extends Registry<?>>, List<Registrar<?>>> objects;\n\n    /**\n     * Creates a new DeferredHelper and registers it to the mod event bus.\n     *\n     * @param modid The modid of the owning mod.\n     * @return A new DeferredHelper.\n     */\n    public static DeferredHelper create(String modid) {\n        DeferredHelper helper = new DeferredHelper(modid);\n        FMLJavaModLoadingContext.get().getModEventBus().register(helper);\n        return helper;\n    }\n\n    protected DeferredHelper(String modid) {\n        this.modid = modid;\n        this.objects = new IdentityHashMap<>();\n    }\n\n    public <T extends Block> RegistryObject<T> block(String path, Supplier<T> factory) {\n        return this.create(path, Registries.BLOCK, factory);\n    }\n\n    public <T extends Fluid> RegistryObject<T> fluid(String path, Supplier<T> factory) {\n        return this.create(path, Registries.FLUID, factory);\n    }\n\n    public <T extends Item> RegistryObject<T> item(String path, Supplier<T> factory) {\n        return this.create(path, Registries.ITEM, factory);\n    }\n\n    public <T extends MobEffect> RegistryObject<T> effect(String path, Supplier<T> factory) {\n        return this.create(path, Registries.MOB_EFFECT, factory);\n    }\n\n    public <T extends SoundEvent> RegistryObject<T> sound(String path, Supplier<T> factory) {\n        return this.create(path, Registries.SOUND_EVENT, factory);\n    }\n\n    public RegistryObject<SoundEvent> sound(String path) {\n        return sound(path, () -> SoundEvent.createVariableRangeEvent(new ResourceLocation(modid, path)));\n    }\n\n    public <T extends Potion> RegistryObject<T> potion(String path, Supplier<T> factory) {\n        return this.create(path, Registries.POTION, factory);\n    }\n\n    public <T extends Enchantment> RegistryObject<T> enchant(String path, Supplier<T> factory) {\n        return this.create(path, Registries.ENCHANTMENT, factory);\n    }\n\n    public <U extends Entity, T extends EntityType<U>> RegistryObject<T> entity(String path, Supplier<T> factory) {\n        return this.create(path, Registries.ENTITY_TYPE, factory);\n    }\n\n    public <U extends BlockEntity, T extends BlockEntityType<U>> RegistryObject<T> blockEntity(String path, Supplier<T> factory) {\n        return this.create(path, Registries.BLOCK_ENTITY_TYPE, factory);\n    }\n\n    public <U extends ParticleOptions, T extends ParticleType<U>> RegistryObject<T> particle(String path, Supplier<T> factory) {\n        return this.create(path, Registries.PARTICLE_TYPE, factory);\n    }\n\n    public <U extends AbstractContainerMenu, T extends MenuType<U>> RegistryObject<T> menu(String path, Supplier<T> factory) {\n        return this.create(path, Registries.MENU, factory);\n    }\n\n    public <T extends PaintingVariant> RegistryObject<T> painting(String path, Supplier<T> factory) {\n        return this.create(path, Registries.PAINTING_VARIANT, factory);\n    }\n\n    public <C extends Container, U extends Recipe<C>, T extends RecipeType<U>> RegistryObject<T> recipe(String path, Supplier<T> factory) {\n        return this.create(path, Registries.RECIPE_TYPE, factory);\n    }\n\n    public <C extends Container, U extends Recipe<C>, T extends RecipeSerializer<U>> RegistryObject<T> recipeSerializer(String path, Supplier<T> factory) {\n        return this.create(path, Registries.RECIPE_SERIALIZER, factory);\n    }\n\n    public <T extends Attribute> RegistryObject<T> attribute(String path, Supplier<T> factory) {\n        return this.create(path, Registries.ATTRIBUTE, factory);\n    }\n\n    public <S, U extends StatType<S>, T extends StatType<U>> RegistryObject<T> stat(String path, Supplier<T> factory) {\n        return this.create(path, Registries.STAT_TYPE, factory);\n    }\n\n    /**\n     * Creates a custom stat with the given path and formatter.<br>\n     * Calling {@link StatType#get} on {@link Stats#CUSTOM} is required for full registration, for some reason.\n     *\n     * @see Stats#makeCustomStat\n     */\n    public RegistryObject<ResourceLocation> customStat(String path, StatFormatter formatter) {\n        return this.create(path, Registries.CUSTOM_STAT, () -> {\n            ResourceLocation id = new ResourceLocation(this.modid, path);\n            Stats.CUSTOM.get(id, formatter);\n            return id;\n        });\n    }\n\n    public <U extends FeatureConfiguration, T extends Feature<U>> RegistryObject<T> feature(String path, Supplier<T> factory) {\n        return this.create(path, Registries.FEATURE, factory);\n    }\n\n    public <T extends CreativeModeTab> RegistryObject<T> tab(String path, Supplier<T> factory) {\n        return this.create(path, Registries.CREATIVE_MODE_TAB, factory);\n    }\n\n    public <P, T extends P> RegistryObject<T> custom(String path, ResourceKey<Registry<P>> registry, Supplier<T> factory) {\n        return this.create(path, registry, factory);\n    }\n\n    protected <P, T extends P> RegistryObject<T> create(String path, ResourceKey<Registry<P>> regKey, Supplier<T> factory) {\n        List<Registrar<?>> registrars = this.objects.computeIfAbsent(regKey, k -> new ArrayList<>());\n        ResourceLocation id = new ResourceLocation(this.modid, path);\n        RegistryObject<T> obj = RegistryObject.create(id, regKey, this.modid);\n        registrars.add(new Registrar<>(id, obj, factory));\n        return obj;\n    }\n\n    private static MethodHandle RO_updateReference;\n\n    static {\n        try {\n            Method m = RegistryObject.class.getDeclaredMethod(\"updateReference\", RegisterEvent.class);\n            m.setAccessible(true);\n            RO_updateReference = MethodHandles.lookup().unreflect(m);\n        }\n        catch (Exception ex) {\n            // Failing means we're using Neo, and RO has been replaced with DH, so this is unnecessary anyway.\n        }\n    }\n\n    @SubscribeEvent\n    @SuppressWarnings({ \"rawtypes\", \"unchecked\" })\n    public void register(RegisterEvent e) {\n        this.objects.getOrDefault(e.getRegistryKey(), Collections.emptyList()).forEach(registrar -> {\n            e.register((ResourceKey) e.getRegistryKey(), registrar.id, (Supplier) registrar.factory);\n            if (RO_updateReference != null) {\n                try {\n                    RO_updateReference.invoke(registrar.obj, e);\n                }\n                catch (Throwable t) {\n                    throw new RuntimeException(t);\n                }\n            }\n        });\n    }\n\n    protected static record Registrar<T>(ResourceLocation id, RegistryObject<T> obj, Supplier<T> factory) {\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/weather2/EntityRegistry.java",
    "content": "package weather2;\n\nimport net.minecraft.world.entity.EntityType;\nimport net.minecraft.world.entity.MobCategory;\nimport net.minecraftforge.fml.common.Mod;\nimport net.minecraftforge.registries.RegistryObject;\nimport weather2.weathersystem.storm.LightningBoltWeatherNew;\n\n@Mod.EventBusSubscriber(bus = Mod.EventBusSubscriber.Bus.MOD)\npublic class EntityRegistry {\n\n    /*@ObjectHolder(Weather.MODID + \":lightning_bolt\")\n    public static EntityType<LightningBoltWeatherNew> lightning_bolt;*/\n\n    public static final RegistryObject<EntityType<LightningBoltWeatherNew>> LIGHTNING_BOLT = Weather.R.entity(\"lightning_bolt\", () -> EntityType.Builder\n            .<LightningBoltWeatherNew>of(LightningBoltWeatherNew::new, MobCategory.MISC)\n            .noSave()\n            .sized(0.0F, 0.0F)\n            .clientTrackingRange(16)\n            .updateInterval(Integer.MAX_VALUE)\n            .build(\"gateway\"));\n    /*\n    @SubscribeEvent\n    public static void registerEntity(RegistryEvent.Register<EntityType<?>> e) {\n        IForgeRegistry<EntityType<?>> r = e.getRegistry();\n        r.register(\n                EntityType.Builder.of(LightningBoltWeatherNew::new, MobCategory.MISC)\n                        .noSave()\n                        .sized(0.0F, 0.0F)\n                        .clientTrackingRange(16)\n                        .updateInterval(Integer.MAX_VALUE)\n                        .build(\"lightning_bolt\")\n                        .setRegistryName(\"lightning_bolt\"));\n    }*/\n\n}\n"
  },
  {
    "path": "src/main/java/weather2/EventHandlerForge.java",
    "content": "package weather2;\n\nimport com.corosus.coroutil.util.CULog;\nimport com.corosus.coroutil.util.CoroUtilCompatibility;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.nbt.CompoundTag;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.entity.player.Player;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.biome.Biome;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport net.minecraftforge.client.event.RegisterClientCommandsEvent;\nimport net.minecraftforge.client.event.RenderLevelStageEvent;\nimport net.minecraftforge.client.event.ViewportEvent;\nimport net.minecraftforge.event.TickEvent;\nimport net.minecraftforge.event.entity.living.LivingEvent;\nimport net.minecraftforge.event.entity.player.PlayerEvent;\nimport net.minecraftforge.eventbus.api.EventPriority;\nimport net.minecraftforge.eventbus.api.SubscribeEvent;\nimport net.minecraftforge.fml.common.Mod;\nimport weather2.client.SceneEnhancer;\nimport weather2.command.CommandWeather2Client;\nimport weather2.config.ConfigDebug;\nimport weather2.util.WeatherUtilBlock;\nimport weather2.util.WeatherUtilEntity;\nimport weather2.weathersystem.WeatherManagerClient;\nimport weather2.weathersystem.wind.WindManager;\n\n@Mod.EventBusSubscriber(modid = Weather.MODID, bus = Mod.EventBusSubscriber.Bus.MOD)\npublic class EventHandlerForge {\n\n\t@SubscribeEvent\n\t@OnlyIn(Dist.CLIENT)\n    public void worldRender(RenderLevelStageEvent event)\n    {\n\t\tif (event.getStage() == RenderLevelStageEvent.Stage.AFTER_WEATHER) {\n\t\t\tClientTickHandler.getClientWeather();\n\t\t} else if (event.getStage() == RenderLevelStageEvent.Stage.AFTER_PARTICLES) {\n\t\t\tif (ConfigDebug.Particle_engine_render) {\n\t\t\t\tClientTickHandler.particleManagerExtended().render(event.getPoseStack(), null, Minecraft.getInstance().gameRenderer.lightTexture(), event.getCamera(), event.getPartialTick(), event.getFrustum());\n\t\t\t}\n\t\t}\n    }\n\n\t@SubscribeEvent(priority = EventPriority.LOWEST)\n\t@OnlyIn(Dist.CLIENT)\n    public void onFogColors(ViewportEvent.ComputeFogColor event) {\n        SceneEnhancer.getFogAdjuster().onFogColors(event);\n\t\t\n\t}\n\t\n\t@SubscribeEvent(priority = EventPriority.LOWEST)\n\t@OnlyIn(Dist.CLIENT)\n\tpublic void onFogRender(ViewportEvent.RenderFog event) {\n\t\tSceneEnhancer.getFogAdjuster().onFogRender(event);\n\t}\n\t\n\t@SubscribeEvent\n\t@OnlyIn(Dist.CLIENT)\n\tpublic void onRenderTick(TickEvent.RenderTickEvent event) {\n\t\tSceneEnhancer.renderTick(event);\n\t}\n\n\t@SubscribeEvent\n\tpublic void onEntityLivingUpdate(LivingEvent.LivingTickEvent event) {\n\t\tEntity ent = event.getEntity();\n\t\tif (ent.level().isClientSide && (ent instanceof Player && ((Player) ent).isLocalPlayer())) {\n\t\t\tonClientPlayerUpdate(event);\n\t\t}\n\t\t/*if (!ent.level.isClientSide && ent instanceof Player) {\n\t\t\tonServerPlayerUpdate(event);\n\t\t}*/\n\t}\n\n\t@SubscribeEvent\n\tpublic void onPlayerClone(PlayerEvent.Clone event) {\n\t\tCompoundTag tag = event.getOriginal().getPersistentData();\n\t\tCompoundTag tag2 = event.getEntity().getPersistentData();\n\t\ttag2.putLong(\"lastSandstormTime\", tag.getLong(\"lastSandstormTime\"));\n\t\ttag2.putLong(\"lastStormDeadlyTime\", tag.getLong(\"lastStormDeadlyTime\"));\n\t}\n\n\tpublic void onServerPlayerUpdate(LivingEvent.LivingTickEvent event) {\n\t\tLevel level = event.getEntity().level();\n\t\tif (level.getGameTime() % 40 == 0) {\n\t\t\tEntity ent = event.getEntity();\n\t\t\tBiome bgb = level.getBiome(WeatherUtilBlock.getPrecipitationHeightSafe(level, new BlockPos(Mth.floor(ent.position().x), 0, Mth.floor(ent.position().z)))).get();\n\t\t\tfloat biomeTemp = CoroUtilCompatibility.getAdjustedTemperature(ent.level(), bgb, new BlockPos(Mth.floor(ent.position().x), Mth.floor(ent.position().y), Mth.floor(ent.position().z)));\n\t\t\tCULog.dbg(\"biomeTemp: \" + biomeTemp);\n\t\t}\n\n\t}\n\n\t@SubscribeEvent\n\t@OnlyIn(Dist.CLIENT)\n\tpublic void onClientPlayerUpdate(LivingEvent.LivingTickEvent event) {\n\n\t\tEntity ent = event.getEntity();\n\t\tWeatherManagerClient weatherMan = ClientTickHandler.weatherManager;\n\t\tif (weatherMan == null) return;\n\t\tWindManager windMan = weatherMan.getWindManager();\n\t\tif (windMan == null) return;\n\n\t\tClientWeatherProxy weather = ClientWeatherProxy.get();\n\t\tif (weather.isSnowstorm() || weather.isSandstorm()) {\n\t\t\tif (ent.onGround() && !ent.isSpectator() && !WeatherUtilEntity.isPlayerSheltered(ent)/* && ent.world.getGameTime() % 20 == 0*/) {\n\n\t\t\t\tfloat playerSpeed = (float) Math.sqrt(ent.getDeltaMovement().x * ent.getDeltaMovement().x + ent.getDeltaMovement().z * ent.getDeltaMovement().z);\n\n\t\t\t\tif (playerSpeed > 0.02F && playerSpeed < 0.3F) {\n\n\t\t\t\t\t//System.out.println(\"playerSpeed: \" + playerSpeed);\n\n\t\t\t\t\t/**\n\t\t\t\t\t * Calculate the players angle from motion, compare it against wind\n\t\t\t\t\t * under 90 means theyre moving with the wind, above 90 means against the wind, 90 means perpendicular to it\n\t\t\t\t\t * scale wind assistance / resistance to wind based on dist from 0 to 90 or 90 to 180\n\t\t\t\t\t */\n\n\t\t\t\t\tfloat playerAngle = -(float) (Math.toDegrees(Math.atan2(ent.getDeltaMovement().x, ent.getDeltaMovement().z)));\n\t\t\t\t\tint phi = (int) (Math.abs(windMan.getWindAngle(ent.position()) - playerAngle) % 360);\n\t\t\t\t\tfloat diffAngle = phi > 180 ? 360 - phi : phi;\n\t\t\t\t\t//System.out.println(\"diffAngle: \" + diffAngle);\n\t\t\t\t\tif (diffAngle < 90) {\n\t\t\t\t\t\tfloat assistRate = 1F - (diffAngle / 90F);\n\t\t\t\t\t\tfloat assist = 1F + (0.12F * assistRate);\n\t\t\t\t\t\t//System.out.println(\"assist: \" + assist);\n\t\t\t\t\t\tent.setDeltaMovement(ent.getDeltaMovement().x * assist, ent.getDeltaMovement().y, ent.getDeltaMovement().z * assist);\n\t\t\t\t\t} else if (diffAngle >= 90) {\n\t\t\t\t\t\tfloat dampenRate = ((diffAngle - 90F) / 90F);\n\t\t\t\t\t\tfloat dampen = 1F - (0.12F * dampenRate);\n\t\t\t\t\t\t//System.out.println(\"dampen: \" + dampen);\n\t\t\t\t\t\tif (dampen != 0) {\n\t\t\t\t\t\t\tent.setDeltaMovement(ent.getDeltaMovement().x * dampen, ent.getDeltaMovement().y, ent.getDeltaMovement().z * dampen);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t}\n\n\t@SubscribeEvent\n\tpublic void registerCommandsClient(RegisterClientCommandsEvent event) {\n\t\tCommandWeather2Client.register(event.getDispatcher());\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/IWindHandler.java",
    "content": "package weather2;\n\npublic interface IWindHandler {\n\t\n\tfloat getWindWeight();\n\t\n\tint getParticleDecayExtra();\n\n}\n"
  },
  {
    "path": "src/main/java/weather2/IWorldData.java",
    "content": "package weather2;\n\nimport net.minecraft.nbt.CompoundTag;\n\npublic interface IWorldData {\n\n    CompoundTag save(CompoundTag data);\n\n}\n"
  },
  {
    "path": "src/main/java/weather2/PacketNBTFromClient.java",
    "content": "package weather2;\n\nimport net.minecraft.nbt.CompoundTag;\nimport net.minecraft.network.FriendlyByteBuf;\nimport net.minecraft.server.level.ServerPlayer;\nimport net.minecraftforge.network.NetworkEvent;\n\nimport java.util.function.Supplier;\n\npublic class PacketNBTFromClient {\n    private final CompoundTag nbt;\n\n    public PacketNBTFromClient(CompoundTag nbt) {\n        this.nbt = nbt;\n    }\n\n    public static void encode(PacketNBTFromClient msg, FriendlyByteBuf buffer) {\n        buffer.writeNbt(msg.nbt);\n    }\n\n    public static PacketNBTFromClient decode(FriendlyByteBuf buffer) {\n        return new PacketNBTFromClient(buffer.readNbt());\n    }\n\n    public static class Handler {\n        public static void handle(final PacketNBTFromClient msg, Supplier<NetworkEvent.Context> ctx) {\n            ServerPlayer playerEntity = ctx.get().getSender();\n            if( playerEntity == null ) {\n                ctx.get().setPacketHandled(true);\n                return;\n            }\n\n            ctx.get().enqueueWork(() -> {\n                try {\n                    CompoundTag nbt = msg.nbt;\n\n                    String packetCommand = nbt.getString(\"packetCommand\");\n                    String command = nbt.getString(\"command\");\n\n                    Weather.dbg(\"Weather2 packet command from client: \" + packetCommand + \" - \" + command);\n\n                    if (packetCommand.equals(\"WeatherData\")) {\n\n                        if (command.equals(\"syncFull\")) {\n                            ServerTickHandler.playerClientRequestsFullSync(playerEntity);\n                        }\n\n                    }\n\n                } catch (Exception ex) {\n                    ex.printStackTrace();\n                }\n                /*ItemStack heldItem = GadgetCopyPaste.getGadget(playerEntity);\n                if (heldItem.isEmpty()) return;\n\n                BlockPos startPos = msg.start;\n                BlockPos endPos = msg.end;\n                if (startPos.equals(BlockPos.ZERO) && endPos.equals(BlockPos.ZERO)) {\n                    GadgetCopyPaste.setSelectedRegion(heldItem, null);\n                    playerEntity.sendStatusMessage(MessageTranslation.AREA_RESET.componentTranslation().setStyle(Styles.AQUA), true);\n                } else {\n                    GadgetCopyPaste.setSelectedRegion(heldItem, new Region(startPos, endPos));\n                }*/\n            });\n\n            ctx.get().setPacketHandled(true);\n        }\n    }\n}"
  },
  {
    "path": "src/main/java/weather2/PacketNBTFromServer.java",
    "content": "package weather2;\n\nimport net.minecraft.nbt.CompoundTag;\nimport net.minecraft.network.FriendlyByteBuf;\nimport net.minecraftforge.network.NetworkEvent;\n\nimport java.util.function.Supplier;\n\npublic class PacketNBTFromServer {\n    private final CompoundTag nbt;\n\n    public PacketNBTFromServer(CompoundTag nbt) {\n        this.nbt = nbt;\n    }\n\n    public static void encode(PacketNBTFromServer msg, FriendlyByteBuf buffer) {\n        buffer.writeNbt(msg.nbt);\n    }\n\n    public static PacketNBTFromServer decode(FriendlyByteBuf buffer) {\n        return new PacketNBTFromServer(buffer.readNbt());\n    }\n\n    public static class Handler {\n        public static void handle(final PacketNBTFromServer msg, Supplier<NetworkEvent.Context> ctx) {\n            /*ServerPlayerEntity playerEntity = ctx.get().getSender();\n            if( playerEntity == null ) {\n                ctx.get().setPacketHandled(true);\n                return;\n            }*/\n\n            ctx.get().enqueueWork(() -> {\n                try {\n                    CompoundTag nbt = msg.nbt;\n\n                    String packetCommand = nbt.getString(\"packetCommand\");\n                    String command = nbt.getString(\"command\");\n\n                    //System.out.println(\"Weather2 packet command from server: \" + packetCommand);\n                    if (packetCommand.equals(\"WeatherData\")) {\n                        ClientTickHandler.getClientWeather();\n\n                        //this line still gets NPE's despite it checking if its null right before it, wtf\n                        ClientTickHandler.weatherManager.nbtSyncFromServer(nbt);\n                    } else if (packetCommand.equals(\"ClientConfigData\")) {\n                        if (command.equals(\"syncUpdate\")) {\n                            ClientTickHandler.clientConfigData.readNBT(nbt);\n                        }\n                    }\n                } catch (Exception ex) {\n                    ex.printStackTrace();\n                }\n            });\n\n            ctx.get().setPacketHandled(true);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/PerlinNoiseHelper.java",
    "content": "package weather2;\n\nimport com.mojang.serialization.Codec;\nimport com.mojang.serialization.codecs.RecordCodecBuilder;\nimport it.unimi.dsi.fastutil.doubles.DoubleArrayList;\nimport it.unimi.dsi.fastutil.doubles.DoubleList;\nimport net.minecraft.world.level.levelgen.LegacyRandomSource;\nimport net.minecraft.world.level.levelgen.synth.NormalNoise;\nimport net.minecraft.world.level.levelgen.synth.PerlinNoise;\nimport net.minecraft.world.level.levelgen.synth.SimplexNoise;\n\nimport java.util.List;\nimport java.util.Random;\n\npublic class PerlinNoiseHelper {\n\n    public static class NoiseParameters {\n        private final int firstOctave;\n        private final DoubleList amplitudes;\n        public static final Codec<NoiseParameters> CODEC = RecordCodecBuilder.create((p_48510_) -> {\n            return p_48510_.group(Codec.INT.fieldOf(\"firstOctave\").forGetter(NoiseParameters::firstOctave), Codec.DOUBLE.listOf().fieldOf(\"amplitudes\").forGetter(NoiseParameters::amplitudes)).apply(p_48510_, NoiseParameters::new);\n        });\n\n        public NoiseParameters(int p_48506_, List<Double> p_48507_) {\n            this.firstOctave = p_48506_;\n            this.amplitudes = new DoubleArrayList(p_48507_);\n        }\n\n        public NoiseParameters(int p_151854_, double... p_151855_) {\n            this.firstOctave = p_151854_;\n            this.amplitudes = new DoubleArrayList(p_151855_);\n        }\n\n        public int firstOctave() {\n            return this.firstOctave;\n        }\n\n        public DoubleList amplitudes() {\n            return this.amplitudes;\n        }\n    }\n\n    private SimplexNoise simplexNoise;\n    private PerlinNoise perlinNoise;\n    private NormalNoise normalNoise;\n\n    public SimplexNoise getSimplexNoise() {\n        return simplexNoise;\n    }\n\n    public PerlinNoise getPerlinNoise() {\n        return perlinNoise;\n    }\n\n    public NormalNoise getNormalNoise() {\n        return normalNoise;\n    }\n\n    private static PerlinNoiseHelper instance = null;\n\n    public static PerlinNoiseHelper get() {\n        if (instance == null) instance = new PerlinNoiseHelper();\n        return instance;\n    }\n\n    public PerlinNoiseHelper() {\n        Random random = new Random(5);\n        simplexNoise = new SimplexNoise(new LegacyRandomSource(random.nextLong()));\n        NoiseParameters noiseParameters = new NoiseParameters(-9, 1.0D, 0.0D, 3.0D, 3.0D, 3.0D, 3.0D);\n        this.perlinNoise = PerlinNoise.create(new LegacyRandomSource(random.nextLong()), noiseParameters.firstOctave(), noiseParameters.amplitudes());\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/weather2/ServerTickHandler.java",
    "content": "package weather2;\n\nimport com.corosus.coroutil.util.CULog;\nimport com.corosus.modconfig.ConfigMod;\nimport it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;\nimport net.minecraft.nbt.CompoundTag;\nimport net.minecraft.server.level.ServerPlayer;\nimport net.minecraft.resources.ResourceKey;\nimport net.minecraft.world.entity.player.Player;\nimport net.minecraft.world.level.LevelAccessor;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.server.level.ServerLevel;\nimport net.minecraftforge.event.TickEvent;\nimport net.minecraftforge.event.entity.player.PlayerEvent;\nimport net.minecraftforge.event.level.LevelEvent;\nimport net.minecraftforge.eventbus.api.SubscribeEvent;\nimport net.minecraftforge.fml.InterModComms;\nimport net.minecraftforge.fml.common.Mod;\nimport net.minecraftforge.network.NetworkDirection;\nimport net.minecraftforge.network.PacketDistributor;\nimport weather2.config.ClientConfigData;\nimport weather2.config.ConfigMisc;\nimport weather2.config.WeatherUtilConfig;\nimport weather2.weathersystem.WeatherManagerServer;\nimport weather2.weathersystem.storm.StormObject;\nimport weather2.weathersystem.wind.WindManager;\n\nimport java.util.HashMap;\nimport java.util.Map;\nimport java.util.UUID;\n\n@Mod.EventBusSubscriber(modid = Weather.MODID)\npublic class ServerTickHandler {\n\tprivate static final Map<ResourceKey<Level>, WeatherManagerServer> MANAGERS = new Reference2ObjectOpenHashMap<>();\n\tprivate static final HashMap<String, WeatherManagerServer> MANAGERSLOOKUP = new HashMap<>();\n\n\t@SubscribeEvent\n\tpublic static void onWorldLoad(LevelEvent.Load event) {\n\t\tLevelAccessor world = event.getLevel();\n\t\tif (!world.isClientSide() && world instanceof ServerLevel) {\n\t\t\tServerLevel serverWorld = (ServerLevel) world;\n\t\t\tResourceKey<Level> dimension = serverWorld.dimension();\n\t\t\tWeatherManagerServer weatherManagerServer = new WeatherManagerServer(serverWorld);\n\t\t\tif (WeatherUtilConfig.listDimensionsWeather.contains(weatherManagerServer.getWorld().dimension().location().toString())) {\n\t\t\t\tweatherManagerServer.read();\n\t\t\t}\n\t\t\tMANAGERS.put(dimension, weatherManagerServer);\n\t\t\tMANAGERSLOOKUP.put(dimension.location().toString(), weatherManagerServer);\n\t\t}\n\t}\n\n\t@SubscribeEvent\n\tpublic static void onWorldUnload(LevelEvent.Unload event) {\n\t\tLevelAccessor world = event.getLevel();\n\t\tif (!world.isClientSide() && world instanceof ServerLevel) {\n\t\t\tServerLevel serverWorld = (ServerLevel) world;\n\t\t\tMANAGERS.remove(serverWorld.dimension());\n\t\t\tMANAGERSLOOKUP.remove(serverWorld.dimension().toString());\n\t\t}\n\t}\n\n\t@SubscribeEvent\n\tpublic static void tickServer(TickEvent.ServerTickEvent event) {\n\t\tif (event.phase == TickEvent.Phase.START) {\n\t\t\tfor (WeatherManagerServer manager : MANAGERS.values()) {\n\t\t\t\t//for non whitelisted dimensions i chose to still tick the manager, and also register it, so it can get cleaned up if people spawn stuff or change config\n\t\t\t\t//if (WeatherUtilConfig.listDimensionsWeather.contains(manager.getWorld().dimension().location().toString())) {\n\t\t\t\t\tmanager.tick();\n\t\t\t\t//}\n\t\t\t}\n\n\t\t\tprocessIMCMessages();\n\t\t}\n\t}\n\n\t@SubscribeEvent\n\tpublic static void tickServer(TickEvent.LevelTickEvent event) {\n\n\t\t//TODO: TEMPPPPPPPPPPPP\n\t\t//ConfigMisc.Aesthetic_Only_Mode = true;\n\t\t//ConfigMisc.overcastMode = true;\n\n\t\tif (event.level.dimension() == Level.OVERWORLD && event.phase == TickEvent.Phase.END && !event.level.isClientSide()) {\n\t\t\tif (ConfigMisc.Aesthetic_Only_Mode) {\n\t\t\t\tif (!ConfigMisc.overcastMode) {\n\t\t\t\t\tConfigMisc.overcastMode = true;\n\t\t\t\t\tCULog.dbg(\"detected Aesthetic_Only_Mode on, setting overcast mode on\");\n\t\t\t\t\t//WeatherUtilConfig.setOvercastModeServerSide(ConfigMisc.overcastMode);\n\t\t\t\t\tConfigMod.forceSaveAllFilesFromRuntimeSettings();\n\t\t\t\t\tsyncServerConfigToClient(null);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t//TODO: only sync when things change? is now sent via PlayerLoggedInEvent at least\n\t\t\t/*if (event.level.getGameTime() % 200 == 0) {\n\t\t\t\tsyncServerConfigToClient(null);\n\t\t\t}*/\n\t\t}\n\n\t}\n\n\tpublic static void processIMCMessages() {\n\t\tInterModComms.getMessages(\"weather2\").forEach((msg) -> {\n\t\t\tCompoundTag tag = (CompoundTag) msg.messageSupplier().get();\n\t\t\tString dimResource = tag.getString(\"dimension\");\n\t\t\tWeatherManagerServer wm = MANAGERSLOOKUP.get(dimResource);\n\t\t\tif (wm != null) {\n\t\t\t\tif (msg.method().equals(\"player_tornado\")) {\n\t\t\t\t\tint timeTicks = tag.getInt(\"time_ticks\");\n\t\t\t\t\tboolean baby = tag.getBoolean(\"baby\");\n\t\t\t\t\t//boolean sharknado = tag.getBoolean(\"sharknado\");\n\t\t\t\t\tString uuid = tag.getString(\"uuid\");\n\t\t\t\t\tPlayer player = wm.getWorld().getPlayerByUUID(UUID.fromString(uuid));\n\t\t\t\t\tif (player != null) {\n\t\t\t\t\t\tStormObject stormObject = new StormObject(wm);\n\n\t\t\t\t\t\tstormObject.setupStorm(player);\n\t\t\t\t\t\tstormObject.levelCurIntensityStage = StormObject.STATE_STAGE1;\n\t\t\t\t\t\tstormObject.levelStormIntensityMax = StormObject.STATE_STAGE1;\n\t\t\t\t\t\tstormObject.setupPlayerControlledTornado(player);\n\t\t\t\t\t\tstormObject.setPlayerControlledTimeLeft(timeTicks);\n\t\t\t\t\t\tstormObject.setBaby(baby);\n\t\t\t\t\t\t//stormObject.setSharknado(sharknado);\n\n\t\t\t\t\t\twm.addStormObject(stormObject);\n\t\t\t\t\t\twm.syncStormNew(stormObject);\n\n\t\t\t\t\t\tCULog.dbg(\"processed imc message: \" + tag);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tCULog.err(\"error cant find player in dimension \" + dimResource + \" for uuid \" + uuid + \" via IMC\");\n\t\t\t\t\t}\n\t\t\t\t} else if (msg.method().equals(\"sharknado\")) {\n\t\t\t\t\tStormObject stormObject = new StormObject(wm);\n\n\t\t\t\t\tstormObject.setupStorm(null);\n\t\t\t\t\tstormObject.levelCurIntensityStage = StormObject.STATE_STAGE1;\n\t\t\t\t\tstormObject.levelStormIntensityMax = StormObject.STATE_STAGE4;\n\t\t\t\t\tstormObject.setSharknado(true);\n\t\t\t\t\tstormObject.setupTornadoAwayFromPlayersAimAtPlayers();\n\n\t\t\t\t\twm.addStormObject(stormObject);\n\t\t\t\t\twm.syncStormNew(stormObject);\n\n\t\t\t\t\tCULog.dbg(\"processed imc message: \" + tag);\n\t\t\t\t} else if (msg.method().equals(\"firenado\")) {\n\t\t\t\t\tStormObject stormObject = new StormObject(wm);\n\n\t\t\t\t\tstormObject.setupStorm(null);\n\t\t\t\t\tstormObject.levelCurIntensityStage = StormObject.STATE_STAGE1;\n\t\t\t\t\tstormObject.levelStormIntensityMax = StormObject.STATE_STAGE4;\n\t\t\t\t\tstormObject.isFirenado = true;\n\t\t\t\t\tstormObject.setupTornadoAwayFromPlayersAimAtPlayers();\n\n\t\t\t\t\twm.addStormObject(stormObject);\n\t\t\t\t\twm.syncStormNew(stormObject);\n\n\t\t\t\t\tCULog.dbg(\"processed imc message: \" + tag);\n\t\t\t\t} else if (msg.method().equals(\"tornado\")) {\n\t\t\t\t\tStormObject stormObject = new StormObject(wm);\n\n\t\t\t\t\tstormObject.setupStorm(null);\n\t\t\t\t\tstormObject.levelCurIntensityStage = StormObject.STATE_STAGE1;\n\t\t\t\t\tstormObject.levelStormIntensityMax = StormObject.STATE_STAGE4;\n\t\t\t\t\tstormObject.setSharknado(false);\n\t\t\t\t\tstormObject.setupTornadoAwayFromPlayersAimAtPlayers();\n\n\t\t\t\t\twm.addStormObject(stormObject);\n\t\t\t\t\twm.syncStormNew(stormObject);\n\n\t\t\t\t\tCULog.dbg(\"processed imc message: \" + tag);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tCULog.err(\"error cant find WeatherManagerServer for dimension \" + dimResource + \" via IMC\");\n\t\t\t}\n\t\t});\n\t}\n\n\t@SubscribeEvent\n\tpublic static void tickPlayer(TickEvent.PlayerTickEvent event) {\n\t\tif (!event.player.level().isClientSide()) {\n\t\t\tsyncServerConfigToClient(event.player);\n\t\t}\n\t}\n\n\tpublic static void joinPlayer(PlayerEvent.PlayerLoggedInEvent event) {\n\n\t}\n\n\tpublic static WeatherManagerServer getWeatherManagerFor(ResourceKey<Level> dimension) {\n\t\treturn MANAGERS.get(dimension);\n\t}\n\n\tpublic static WeatherManagerServer getWeatherManagerFor(Level level) {\n\t\treturn MANAGERS.get(level.dimension());\n\t}\n\n\tpublic static void playerClientRequestsFullSync(ServerPlayer entP) {\n\t\tWeatherManagerServer wm = MANAGERS.get(entP.level().dimension());\n\t\tif (wm != null) {\n\t\t\twm.playerJoinedWorldSyncFull(entP);\n\t\t}\n\t}\n\n\tpublic static void syncServerConfigToClient(Player player) {\n\t\tCompoundTag data = new CompoundTag();\n\t\tdata.putString(\"packetCommand\", \"ClientConfigData\");\n\t\tdata.putString(\"command\", \"syncUpdate\");\n\t\tClientConfigData.writeNBT(data);\n\t\tif (player != null) {\n\t\t\tWeatherNetworking.HANDLER.send(PacketDistributor.PLAYER.with(() -> (ServerPlayer) player), new PacketNBTFromServer(data));\n\t\t} else {\n\t\t\tWeatherNetworking.HANDLER.send(PacketDistributor.ALL.noArg(), new PacketNBTFromServer(data));\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/ServerWeatherProxy.java",
    "content": "package weather2;\n\nimport net.minecraft.server.level.ServerLevel;\nimport weather2.datatypes.StormState;\nimport weather2.ltcompat.ServerWeatherIntegration;\n\npublic class ServerWeatherProxy {\n\n    public static float getWindSpeed(ServerLevel level) {\n        if (isWeatherEffectsServerSideControlled()) {\n            return ServerWeatherIntegration.getWindSpeed(level);\n        } else {\n            return -1;\n        }\n    }\n\n    public static StormState getSandstormForEverywhere(ServerLevel level) {\n        if (isWeatherEffectsServerSideControlled()) {\n            return ServerWeatherIntegration.getSandstormForEverywhere(level);\n        } else { return null; }\n    }\n\n    public static StormState getSnowstormForEverywhere(ServerLevel level) {\n        if (isWeatherEffectsServerSideControlled()) {\n            return ServerWeatherIntegration.getSnowstormForEverywhere(level);\n        } else { return null; }\n    }\n\n    public static boolean isWeatherEffectsServerSideControlled() {\n        return Weather.isLoveTropicsInstalled();\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/weather2/SoundRegistry.java",
    "content": "package weather2;\n\nimport java.util.HashMap;\n\nimport net.minecraft.resources.ResourceLocation;\nimport net.minecraft.sounds.SoundEvent;\nimport net.minecraftforge.registries.ForgeRegistries;\n\npublic class SoundRegistry {\n\n\tprivate static HashMap<String, SoundEvent> lookupStringToEvent = new HashMap<String, SoundEvent>();\n\n\n\tpublic static void init() {\n\t\tregister(\"env.waterfall\");\n\t\tregister(\"env.wind_calm\");\n\t\tregister(\"env.wind_calmfade\");\n\t\tregister(\"streaming.destruction\");\n\t\tregister(\"streaming.destruction_0_\");\n\t\tregister(\"streaming.destruction_1_\");\n\t\tregister(\"streaming.destruction_2_\");\n\t\tregister(\"streaming.destruction_s\");\n\t\tregister(\"streaming.destructionb\");\n\t\tregister(\"streaming.siren\");\n\t\tregister(\"streaming.wind_close\");\n\t\tregister(\"streaming.wind_close_0_\");\n\t\tregister(\"streaming.wind_close_1_\");\n\t\tregister(\"streaming.wind_close_2_\");\n\t\tregister(\"streaming.wind_far\");\n\t\tregister(\"streaming.wind_far_0_\");\n\t\tregister(\"streaming.wind_far_1_\");\n\t\tregister(\"streaming.wind_far_2_\");\n\n\t\tregister(\"streaming.sandstorm_high1\");\n\t\tregister(\"streaming.sandstorm_med1\");\n\t\tregister(\"streaming.sandstorm_med2\");\n\t\tregister(\"streaming.sandstorm_low1\");\n\t\tregister(\"streaming.sandstorm_low2\");\n\n\t\tregister(\"streaming.siren_sandstorm_1\");\n\t\tregister(\"streaming.siren_sandstorm_2\");\n\t\tregister(\"streaming.siren_sandstorm_3\");\n\t\tregister(\"streaming.siren_sandstorm_4\");\n\t\tregister(\"streaming.siren_sandstorm_5_extra\");\n\t\tregister(\"streaming.siren_sandstorm_6_extra\");\n\t\t\n\t}\n\n\tpublic static void register(String soundPath) {\n\t\tResourceLocation resLoc = new ResourceLocation(Weather.MODID, soundPath);\n\t\t//SoundEvent event = new SoundEvent(resLoc).setRegistryName(resLoc);\n\t\tSoundEvent event = SoundEvent.createVariableRangeEvent(resLoc);\n\t\t//TODO: WIP SoundEvent event = SoundEvent.createVariableRangeEvent(resLoc).setRegistryName(resLoc);\n\t\tForgeRegistries.SOUND_EVENTS.register(resLoc, event);\n\t\tif (lookupStringToEvent.containsKey(soundPath)) {\n\t\t\tSystem.out.println(\"WEATHER SOUNDS WARNING: duplicate sound registration for \" + soundPath);\n\t\t}\n\t\tlookupStringToEvent.put(soundPath, event);\n\t}\n\n\tpublic static SoundEvent get(String soundPath) {\n\t\treturn lookupStringToEvent.get(soundPath);\n\t}\n\n}\n"
  },
  {
    "path": "src/main/java/weather2/Weather.java",
    "content": "package weather2;\n\nimport com.corosus.coroutil.util.CULog;\nimport com.corosus.modconfig.ConfigMod;\nimport com.corosus.modconfig.IConfigCategory;\nimport com.mojang.brigadier.CommandDispatcher;\nimport extendedrenderer.ParticleRegistry2ElectricBubbleoo;\nimport extendedrenderer.particle.ParticleRegistry;\nimport net.minecraft.commands.CommandSourceStack;\nimport net.minecraft.core.registries.Registries;\nimport net.minecraft.data.DataGenerator;\nimport net.minecraft.data.PackOutput;\nimport net.minecraft.data.loot.LootTableProvider;\nimport net.minecraft.network.chat.Component;\nimport net.minecraft.world.item.CreativeModeTab;\nimport net.minecraft.world.item.CreativeModeTabs;\nimport net.minecraft.world.level.storage.loot.parameters.LootContextParamSets;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport net.minecraftforge.common.MinecraftForge;\nimport net.minecraftforge.common.data.ExistingFileHelper;\nimport net.minecraftforge.data.event.GatherDataEvent;\nimport net.minecraftforge.event.BuildCreativeModeTabContentsEvent;\nimport net.minecraftforge.event.RegisterCommandsEvent;\nimport net.minecraftforge.event.server.ServerStartedEvent;\nimport net.minecraftforge.event.server.ServerStoppedEvent;\nimport net.minecraftforge.eventbus.api.IEventBus;\nimport net.minecraftforge.eventbus.api.SubscribeEvent;\nimport net.minecraftforge.fml.ModList;\nimport net.minecraftforge.fml.common.Mod;\nimport net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;\nimport net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;\nimport net.minecraftforge.fml.event.lifecycle.InterModProcessEvent;\nimport net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;\nimport net.minecraftforge.fml.loading.FMLEnvironment;\nimport net.minecraftforge.registries.DeferredRegister;\nimport net.minecraftforge.registries.RegistryObject;\nimport org.apache.logging.log4j.LogManager;\nimport org.apache.logging.log4j.Logger;\nimport weather2.command.WeatherCommand;\nimport weather2.config.*;\nimport weather2.data.BlockAndItemProvider;\nimport weather2.data.BlockLootTables;\nimport weather2.data.WeatherRecipeProvider;\nimport weather2.util.WeatherUtilSound;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n// The value here should match an entry in the META-INF/mods.toml file\n@Mod(Weather.MODID)\npublic class Weather\n{\n    // Directly reference a log4j logger.\n    public static final Logger LOGGER = LogManager.getLogger();\n\n    public static final DeferredHelper R = DeferredHelper.create(Weather.MODID);\n\n    public static final String MODID = \"weather2\";\n\n    public static boolean initProperNeededForWorld = true;\n\n    public static List<IConfigCategory> listConfigs = new ArrayList<>();\n    public static ConfigMisc configMisc = null;\n\n    //public static final CreativeModeTab CREATIVE_TAB = new WeatherTab();\n\n    public static final DeferredRegister<CreativeModeTab> CREATIVE_MODE_TABS = DeferredRegister.create(Registries.CREATIVE_MODE_TAB, MODID);\n    public static final RegistryObject<CreativeModeTab> WEATHER_TAB = CREATIVE_MODE_TABS.register(\"weather_tab\", () -> CreativeModeTab.builder()\n            .withTabsBefore(CreativeModeTabs.COMBAT)\n            .title(Component.translatable(\"itemGroup.weather2\"))\n            .icon(() -> WeatherItems.WEATHER_ITEM.get().getDefaultInstance())\n            .displayItems((parameters, output) -> {\n                output.accept(WeatherItems.WEATHER_ITEM.get());\n                output.accept(WeatherItems.BLOCK_TORNADO_SIREN_ITEM.get());\n                output.accept(WeatherItems.BLOCK_TORNADO_SENSOR_ITEM.get());\n                output.accept(WeatherItems.BLOCK_DEFLECTOR_ITEM.get());\n                output.accept(WeatherItems.BLOCK_FORECAST_ITEM.get());\n                output.accept(WeatherItems.BLOCK_SAND_LAYER_ITEM.get());\n                output.accept(WeatherItems.BLOCK_ANEMOMETER_ITEM.get());\n                output.accept(WeatherItems.BLOCK_WIND_VANE_ITEM.get());\n                output.accept(WeatherItems.BLOCK_WIND_TURBINE_ITEM.get());\n            }).build());\n\n    public Weather() {\n\n        ParticleRegistry2ElectricBubbleoo.bootstrap();\n\n        IEventBus modBus = FMLJavaModLoadingContext.get().getModEventBus();\n        modBus.addListener(this::setup);\n        MinecraftForge.EVENT_BUS.addListener(this::serverStop);\n        MinecraftForge.EVENT_BUS.addListener(this::serverStart);\n        CREATIVE_MODE_TABS.register(modBus);\n        modBus.addListener(this::clientSetup);\n        modBus.addListener(this::gatherData);\n        modBus.addListener(this::processIMC);\n        modBus.addListener(this::addCreative);\n\n        MinecraftForge.EVENT_BUS.register(this);\n        WeatherBlocks.registerHandlers(modBus);\n        WeatherItems.registerHandlers(modBus);\n\n        MinecraftForge.EVENT_BUS.register(new EventHandlerForge());\n        MinecraftForge.EVENT_BUS.addListener(this::registerCommands);\n        MinecraftForge.EVENT_BUS.register(new WeatherBlocks());\n\n        new File(\"./config/Weather2\").mkdirs();\n        configMisc = new ConfigMisc();\n        ConfigMod.addConfigFile(MODID, addConfig(configMisc));\n        ConfigMod.addConfigFile(MODID, addConfig(new ConfigWind()));\n        ConfigMod.addConfigFile(MODID, addConfig(new ConfigSand()));\n        ConfigMod.addConfigFile(MODID, addConfig(new ConfigSnow()));\n        ConfigMod.addConfigFile(MODID, addConfig(new ConfigStorm()));\n        ConfigMod.addConfigFile(MODID, addConfig(new ConfigTornado()));\n        ConfigMod.addConfigFile(MODID, addConfig(new ConfigParticle()));\n        ConfigMod.addConfigFile(MODID, addConfig(new ConfigDebug()));\n        ConfigMod.addConfigFile(MODID, addConfig(new ConfigSound()));\n        //ConfigMod.addConfigFile(MODID, addConfig(new ConfigFoliage()));\n        //WeatherUtilConfig.nbtLoadDataAll();\n\n        SoundRegistry.init();\n\n        if (FMLEnvironment.dist.isClient()) {\n            modBus.addListener(ParticleRegistry::getRegisteredParticles);\n            modBus.addListener(ClientRegistry::registerLayerDefinitions);\n        }\n    }\n\n    private void addCreative(BuildCreativeModeTabContentsEvent event)\n    {\n        if (event.getTabKey() == CreativeModeTabs.BUILDING_BLOCKS)\n            event.accept(WeatherItems.WEATHER_ITEM);\n    }\n\n    public static IConfigCategory addConfig(IConfigCategory config) {\n        listConfigs.add(config);\n        return config;\n    }\n\n    private void setup(final FMLCommonSetupEvent event) {\n        WeatherNetworking.register();\n    }\n\n    private void clientSetup(FMLClientSetupEvent event) {\n        WeatherUtilSound.init();\n\n    }\n\n    private void processIMC(final InterModProcessEvent event)\n    {\n        LOGGER.info(\"Got IMC {}\", event.getIMCStream().\n                map(m->m.getMessageSupplier().get()).\n                collect(Collectors.toList()));\n    }\n\n    @SubscribeEvent\n    public void serverStart(ServerStartedEvent event) {\n        //initProperNeededForWorld = true;\n        //WeatherUtil.testAllBlocks();\n    }\n\n    @SubscribeEvent\n    public void serverStop(ServerStoppedEvent event) {\n        initProperNeededForWorld = true;\n    }\n\n    public static void dbg(Object obj) {\n        CULog.dbg(\"\" + obj);\n    }\n\n    public static boolean isLoveTropicsInstalled() {\n        return ModList.get().isLoaded(\"ltminigames\");\n    }\n\n    private void registerCommands(RegisterCommandsEvent event) {\n        CommandDispatcher<CommandSourceStack> dispatcher = event.getDispatcher();\n\n        WeatherCommand.register(dispatcher);\n    }\n\n\n    /**\n     *\n     * run runData for me\n     *\n     * @param event\n     */\n    private void gatherData(GatherDataEvent event) {\n        DataGenerator gen = event.getGenerator();\n        if (event.includeServer()) {\n            gen.addProvider(event.includeServer(), new WeatherRecipeProvider(gen.getPackOutput()));\n            gen.addProvider(event.includeServer(), new LootTableProvider(gen.getPackOutput(), Collections.emptySet(),\n                    List.of(new LootTableProvider.SubProviderEntry(BlockLootTables::new, LootContextParamSets.BLOCK))));\n        }\n        if (event.includeClient()) {\n            gatherClientData(event);\n        }\n    }\n\n    /**\n     *\n     * run runData for me\n     *\n     * @param event\n     */\n    @OnlyIn(Dist.CLIENT)\n    private void gatherClientData(GatherDataEvent event) {\n        DataGenerator gen = event.getGenerator();\n        PackOutput packOutput = gen.getPackOutput();\n        ExistingFileHelper existingFileHelper = event.getExistingFileHelper();\n        gen.addProvider(event.includeClient(), new ParticleRegistry(packOutput, existingFileHelper));\n        gen.addProvider(event.includeClient(), new BlockAndItemProvider(packOutput, existingFileHelper));\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/WeatherBlocks.java",
    "content": "package weather2;\n\nimport net.minecraft.world.entity.ai.village.poi.PoiType;\nimport net.minecraft.world.item.BlockItem;\nimport net.minecraft.world.item.Item;\nimport net.minecraft.world.level.block.Blocks;\nimport net.minecraft.world.level.block.entity.BlockEntityType;\nimport net.minecraft.world.level.block.state.BlockBehaviour;\nimport net.minecraft.world.level.block.Block;\nimport net.minecraft.world.level.block.SoundType;\nimport net.minecraft.world.level.material.MapColor;\nimport net.minecraftforge.eventbus.api.IEventBus;\nimport net.minecraftforge.eventbus.api.SubscribeEvent;\nimport net.minecraftforge.fml.common.Mod;\nimport net.minecraftforge.registries.DeferredRegister;\nimport net.minecraftforge.registries.ForgeRegistries;\nimport net.minecraftforge.registries.RegistryObject;\nimport weather2.block.*;\nimport weather2.blockentity.*;\nimport weather2.item.WeatherItem;\n\n@Mod.EventBusSubscriber(modid = Weather.MODID, bus = Mod.EventBusSubscriber.Bus.MOD)\npublic class WeatherBlocks {\n\n    public static final String SAND_LAYER = \"sand_layer\";\n    public static final String DEFLECTOR = \"weather_deflector\";\n    public static final String TORNADO_SENSOR = \"tornado_sensor\";\n    public static final String TORNADO_SIREN = \"tornado_siren\";\n    public static final String WEATHER_MACHINE = \"weather_machine\";\n\n    public static final String WEATHER_FORECAST = \"weather_forecast\";\n    public static final String WIND_VANE = \"wind_vane\";\n    public static final String ANEMOMETER = \"anemometer\";\n    public static final String TORNADO_SIREN_MANUAL = \"tornado_siren_manual\";\n\n    public static final String SAND_LAYER_PLACEABLE = \"sand_layer_placeable\";\n    public static final String WEATHER_ITEM = \"weather_item\";\n    public static final String POCKET_SAND = \"pocket_sand\";\n    public static final String WIND_TURBINE = \"wind_turbine\";\n\n    private static final DeferredRegister<Block> BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, Weather.MODID);\n    private static final DeferredRegister<BlockEntityType<?>> BLOCK_ENTITIES = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITY_TYPES, Weather.MODID);\n\n    public static final RegistryObject<SandLayerBlock> BLOCK_SAND_LAYER = BLOCKS.register(SAND_LAYER, () -> new SandLayerBlock(BlockBehaviour.Properties.copy(Blocks.DIRT).mapColor(MapColor.SAND).strength(0.1F).sound(SoundType.SAND)));\n    public static final RegistryObject<DeflectorBlock> BLOCK_DEFLECTOR = BLOCKS.register(DEFLECTOR, () -> new DeflectorBlock(BlockBehaviour.Properties.copy(Blocks.DIRT).mapColor(MapColor.STONE).strength(0.5F, 6F).sound(SoundType.STONE)));\n    public static final RegistryObject<ForecastBlock> BLOCK_FORECAST = BLOCKS.register(WEATHER_FORECAST, () -> new ForecastBlock(BlockBehaviour.Properties.of().mapColor(MapColor.STONE).strength(0.5F, 6F).sound(SoundType.STONE)));\n    public static final RegistryObject<SensorBlock> BLOCK_TORNADO_SENSOR = BLOCKS.register(TORNADO_SENSOR, () -> new SensorBlock(BlockBehaviour.Properties.of().mapColor(MapColor.STONE).strength(0.5F, 6F).sound(SoundType.STONE)));\n    public static final RegistryObject<AnemometerBlock> BLOCK_ANEMOMETER = BLOCKS.register(ANEMOMETER, () -> new AnemometerBlock(BlockBehaviour.Properties.of().mapColor(MapColor.STONE).strength(0.5F, 6F).sound(SoundType.STONE)));\n    public static final RegistryObject<WindVaneBlock> BLOCK_WIND_VANE = BLOCKS.register(WIND_VANE, () -> new WindVaneBlock(BlockBehaviour.Properties.of().mapColor(MapColor.STONE).strength(0.5F, 6F).sound(SoundType.STONE)));\n    public static final RegistryObject<SirenBlock> BLOCK_TORNADO_SIREN = BLOCKS.register(TORNADO_SIREN, () -> new SirenBlock(BlockBehaviour.Properties.of().mapColor(MapColor.STONE).strength(0.5F, 6F).sound(SoundType.STONE)));\n    public static final RegistryObject<WindTurbineBlock> BLOCK_WIND_TURBINE = BLOCKS.register(WIND_TURBINE, () -> new WindTurbineBlock(BlockBehaviour.Properties.of().mapColor(MapColor.STONE).strength(0.5F, 6F).sound(SoundType.STONE)));\n    //public static final RegistryObject<WeatherMachineBlock> BLOCK_WEATHER_MACHINE = BLOCKS.register(WEATHER_MACHINE, () -> new WeatherMachineBlock(BlockBehaviour.Properties.of(Material.STONE).strength(0.5F, 6F).sound(SoundType.STONE)));\n\n    @SuppressWarnings(\"ConstantConditions\")\n    public static final RegistryObject<BlockEntityType<DeflectorBlockEntity>> BLOCK_ENTITY_DEFLECTOR = BLOCK_ENTITIES.register(DEFLECTOR, () ->\n            BlockEntityType.Builder.of(DeflectorBlockEntity::new, BLOCK_DEFLECTOR.get()).build(null));\n\n    @SuppressWarnings(\"ConstantConditions\")\n    public static final RegistryObject<BlockEntityType<SirenBlockEntity>> BLOCK_ENTITY_TORNADO_SIREN = BLOCK_ENTITIES.register(TORNADO_SIREN, () ->\n            BlockEntityType.Builder.of(SirenBlockEntity::new, BLOCK_TORNADO_SIREN.get()).build(null));\n\n    public static final RegistryObject<BlockEntityType<SensorBlockEntity>> BLOCK_ENTITY_TORNADO_SENSOR = BLOCK_ENTITIES.register(TORNADO_SENSOR, () ->\n            BlockEntityType.Builder.of(SensorBlockEntity::new, BLOCK_TORNADO_SENSOR.get()).build(null));\n\n    public static final RegistryObject<BlockEntityType<AnemometerBlockEntity>> BLOCK_ENTITY_ANEMOMETER = BLOCK_ENTITIES.register(ANEMOMETER, () ->\n            BlockEntityType.Builder.of(AnemometerBlockEntity::new, BLOCK_ANEMOMETER.get()).build(null));\n\n    public static final RegistryObject<BlockEntityType<WindVaneBlockEntity>> BLOCK_ENTITY_WIND_VANE = BLOCK_ENTITIES.register(WIND_VANE, () ->\n            BlockEntityType.Builder.of(WindVaneBlockEntity::new, BLOCK_WIND_VANE.get()).build(null));\n\n    public static final RegistryObject<BlockEntityType<WindTurbineBlockEntity>> BLOCK_ENTITY_WIND_TURBINE = BLOCK_ENTITIES.register(WIND_TURBINE, () ->\n            BlockEntityType.Builder.of(WindTurbineBlockEntity::new, BLOCK_WIND_TURBINE.get()).build(null));\n\n    /*public static final RegistryObject<BlockEntityType<WeatherMachineBlockEntity>> BLOCK_ENTITY_WEATHER_MACHINE = BLOCK_ENTITIES.register(WEATHER_MACHINE, () ->\n            BlockEntityType.Builder.of(WeatherMachineBlockEntity::new, BLOCK_WEATHER_MACHINE.get()).build(null));*/\n\n    public static void registerHandlers(IEventBus modBus) {\n        BLOCKS.register(modBus);\n        BLOCK_ENTITIES.register(modBus);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/weather2/WeatherItems.java",
    "content": "package weather2;\n\nimport net.minecraft.world.item.BlockItem;\nimport net.minecraft.world.item.Item;\nimport net.minecraftforge.eventbus.api.IEventBus;\nimport net.minecraftforge.registries.DeferredRegister;\nimport net.minecraftforge.registries.ForgeRegistries;\nimport net.minecraftforge.registries.RegistryObject;\nimport weather2.item.WeatherItem;\n\npublic class WeatherItems {\n\n    private static final DeferredRegister<Item> ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, Weather.MODID);\n\n    public static final RegistryObject<Item> WEATHER_ITEM = ITEMS.register(WeatherBlocks.WEATHER_ITEM, () -> new WeatherItem(new Item.Properties().stacksTo(64)));\n    public static final RegistryObject<BlockItem> BLOCK_DEFLECTOR_ITEM = WeatherItems.ITEMS.register(WeatherBlocks.DEFLECTOR, () -> new BlockItem(WeatherBlocks.BLOCK_DEFLECTOR.get(), new Item.Properties()));\n    public static final RegistryObject<BlockItem> BLOCK_TORNADO_SIREN_ITEM = ITEMS.register(WeatherBlocks.TORNADO_SIREN, () -> new BlockItem(WeatherBlocks.BLOCK_TORNADO_SIREN.get(), new Item.Properties()));\n    public static final RegistryObject<BlockItem> BLOCK_TORNADO_SENSOR_ITEM = ITEMS.register(WeatherBlocks.TORNADO_SENSOR, () -> new BlockItem(WeatherBlocks.BLOCK_TORNADO_SENSOR.get(), new Item.Properties()));\n    public static final RegistryObject<BlockItem> BLOCK_SAND_LAYER_ITEM = ITEMS.register(WeatherBlocks.SAND_LAYER, () -> new BlockItem(WeatherBlocks.BLOCK_SAND_LAYER.get(), new Item.Properties()));\n    public static final RegistryObject<BlockItem> BLOCK_FORECAST_ITEM = ITEMS.register(WeatherBlocks.WEATHER_FORECAST, () -> new BlockItem(WeatherBlocks.BLOCK_FORECAST.get(), new Item.Properties()));\n    public static final RegistryObject<BlockItem> BLOCK_ANEMOMETER_ITEM = ITEMS.register(WeatherBlocks.ANEMOMETER, () -> new BlockItem(WeatherBlocks.BLOCK_ANEMOMETER.get(), new Item.Properties()));\n    public static final RegistryObject<BlockItem> BLOCK_WIND_VANE_ITEM = ITEMS.register(WeatherBlocks.WIND_VANE, () -> new BlockItem(WeatherBlocks.BLOCK_WIND_VANE.get(), new Item.Properties()));\n    public static final RegistryObject<BlockItem> BLOCK_WIND_TURBINE_ITEM = ITEMS.register(WeatherBlocks.WIND_TURBINE, () -> new BlockItem(WeatherBlocks.BLOCK_WIND_TURBINE.get(), new Item.Properties()));\n\n    public static void registerHandlers(IEventBus modBus) {\n        ITEMS.register(modBus);\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/WeatherNetworking.java",
    "content": "package weather2;\n\nimport net.minecraft.network.FriendlyByteBuf;\nimport net.minecraft.resources.ResourceLocation;\nimport net.minecraftforge.network.NetworkDirection;\nimport net.minecraftforge.network.NetworkEvent;\nimport net.minecraftforge.network.NetworkRegistry;\nimport net.minecraftforge.network.simple.SimpleChannel;\n\nimport java.util.Optional;\nimport java.util.function.BiConsumer;\nimport java.util.function.Function;\nimport java.util.function.Supplier;\n\npublic class WeatherNetworking {\n\n    private static final String PROTOCOL_VERSION = Integer.toString(4);\n    private static short lastID = 0;\n    public static final ResourceLocation NETWORK_CHANNEL_ID_MAIN = new ResourceLocation(Weather.MODID, \"main\");\n\n    public static final SimpleChannel HANDLER = NetworkRegistry.ChannelBuilder\n            .named(NETWORK_CHANNEL_ID_MAIN)\n            .clientAcceptedVersions(PROTOCOL_VERSION::equals)\n            .serverAcceptedVersions(PROTOCOL_VERSION::equals)\n            .networkProtocolVersion(() -> PROTOCOL_VERSION)\n            .simpleChannel();\n\n    public static void register() {\n        registerMessage(PacketNBTFromServer.class, PacketNBTFromServer::encode, PacketNBTFromServer::decode, PacketNBTFromServer.Handler::handle, NetworkDirection.PLAY_TO_CLIENT);\n        registerMessage(PacketNBTFromClient.class, PacketNBTFromClient::encode, PacketNBTFromClient::decode, PacketNBTFromClient.Handler::handle, NetworkDirection.PLAY_TO_SERVER);\n    }\n\n    private static <MSG> void registerMessage(Class<MSG> messageType, BiConsumer<MSG, FriendlyByteBuf> encoder, Function<FriendlyByteBuf, MSG> decoder, BiConsumer<MSG, Supplier<NetworkEvent.Context>> messageConsumer, NetworkDirection networkDirection) {\n        HANDLER.registerMessage(lastID, messageType, encoder, decoder, messageConsumer, Optional.ofNullable(networkDirection));\n        lastID++;\n        if (lastID > 0xFF)\n            throw new RuntimeException(\"Too many messages!\");\n    }\n\n}\n\n"
  },
  {
    "path": "src/main/java/weather2/WeatherTab.java",
    "content": "package weather2;\n\nimport net.minecraft.world.item.CreativeModeTab;\nimport net.minecraft.world.item.ItemStack;\nimport net.minecraft.world.level.block.state.properties.WoodType;\n\npublic class WeatherTab extends CreativeModeTab {\n\tprivate ItemStack tabIcon;\n\n\tpublic WeatherTab(Builder builder, ItemStack tabIcon) {\n\t\tsuper(builder);\n\t\tthis.tabIcon = tabIcon;\n\t}\n\n\t/*WeatherTab() {\n\t\tsuper(Weather.MODID);\n\t}\n\n\t@Override\n\tpublic ItemStack makeIcon() {\n\t\tif (tabIcon == null) {\n\t\t\ttabIcon = new ItemStack(WeatherItems.WEATHER_ITEM.get());\n\t\t}\n\t\treturn tabIcon;\n\t}*/\n}\n"
  },
  {
    "path": "src/main/java/weather2/WorldNBTData.java",
    "content": "package weather2;\n\nimport net.minecraft.nbt.CompoundTag;\nimport net.minecraft.world.level.saveddata.SavedData;\n\npublic class WorldNBTData extends SavedData {\n\n    private CompoundTag data;\n    private IWorldData dataHandler;\n\n    public WorldNBTData() {\n        this.data = new CompoundTag();\n    }\n\n    public WorldNBTData(CompoundTag data) {\n        this.data = data;\n    }\n\n    public void setDataHandler(IWorldData dataHandler) {\n        this.dataHandler = dataHandler;\n    }\n\n    public static WorldNBTData load(CompoundTag p_151484_) {\n        return new WorldNBTData(p_151484_);\n    }\n\n    @Override\n    public CompoundTag save(CompoundTag p_77763_) {\n        dataHandler.save(p_77763_);\n        return p_77763_;\n    }\n\n    public CompoundTag getData() {\n        return data;\n    }\n\n    @Override\n    //backwards compat, the data is always changing\n    public boolean isDirty() {\n        return true;\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/block/AnemometerBlock.java",
    "content": "package weather2.block;\n\nimport net.minecraft.ChatFormatting;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.core.Direction;\nimport net.minecraft.network.chat.Component;\nimport net.minecraft.world.item.ItemStack;\nimport net.minecraft.world.item.TooltipFlag;\nimport net.minecraft.world.level.BlockGetter;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.block.BaseEntityBlock;\nimport net.minecraft.world.level.block.Block;\nimport net.minecraft.world.level.block.RenderShape;\nimport net.minecraft.world.level.block.entity.BlockEntity;\nimport net.minecraft.world.level.block.entity.BlockEntityTicker;\nimport net.minecraft.world.level.block.entity.BlockEntityType;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.world.level.block.state.StateDefinition;\nimport net.minecraft.world.level.block.state.properties.BlockStateProperties;\nimport net.minecraft.world.level.block.state.properties.BooleanProperty;\nimport net.minecraft.world.phys.shapes.CollisionContext;\nimport net.minecraft.world.phys.shapes.VoxelShape;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport org.jetbrains.annotations.Nullable;\nimport weather2.WeatherBlocks;\nimport weather2.blockentity.AnemometerBlockEntity;\nimport weather2.blockentity.SensorBlockEntity;\n\nimport java.util.List;\n\npublic class AnemometerBlock extends BaseEntityBlock {\n\n\tpublic static final VoxelShape SHAPE = box(6.0, 0.0, 6.0, 10.0, 16.0, 10.0);\n\n    public static final void register() {}\n\n\tpublic AnemometerBlock(Properties properties) {\n\t\tsuper(properties);\n\t}\n\n\t@Override\n\tprotected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> pBuilder) {\n\n\t}\n\n\t@Override\n\tpublic VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) {\n\t\treturn SHAPE;\n\t}\n\n\t@Override\n\tpublic RenderShape getRenderShape(BlockState p_49232_) {\n\t\treturn RenderShape.INVISIBLE;\n\t}\n\n\t@Override\n\t@OnlyIn(Dist.CLIENT)\n\tpublic void appendHoverText(ItemStack stack, BlockGetter worldIn, List<Component> tooltip, TooltipFlag flagIn) {\n\t\tsuper.appendHoverText(stack, worldIn, tooltip, flagIn);\n\t\t//tooltip.add(Component.translatable(this.getDescriptionId() + \".desc\").withStyle(ChatFormatting.GRAY));\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic BlockEntity newBlockEntity(BlockPos p_153215_, BlockState p_153216_) {\n\t\treturn new AnemometerBlockEntity(p_153215_, p_153216_);\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level p_153212_, BlockState p_153213_, BlockEntityType<T> p_153214_) {\n\t\treturn createTickerHelper(p_153214_, WeatherBlocks.BLOCK_ENTITY_ANEMOMETER.get(), AnemometerBlockEntity::tick);\n\t}\n\n\t@Nullable\n\t@SuppressWarnings(\"unchecked\")\n\tprivate static <E extends BlockEntity, A extends BlockEntity> BlockEntityTicker<A> createTicker(final BlockEntityType<A> type, final BlockEntityType<E> tickerType, final BlockEntityTicker<? super E> ticker) {\n\t\treturn tickerType == type ? (BlockEntityTicker<A>) ticker : null;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/block/DeflectorBlock.java",
    "content": "package weather2.block;\n\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.block.BaseEntityBlock;\nimport net.minecraft.world.level.block.RenderShape;\nimport net.minecraft.world.level.block.entity.BellBlockEntity;\nimport net.minecraft.world.level.block.entity.BlockEntity;\nimport net.minecraft.world.level.block.entity.BlockEntityTicker;\nimport net.minecraft.world.level.block.entity.BlockEntityType;\nimport net.minecraft.world.level.block.state.BlockState;\nimport org.jetbrains.annotations.Nullable;\nimport weather2.WeatherBlocks;\nimport weather2.blockentity.DeflectorBlockEntity;\n\npublic class DeflectorBlock extends BaseEntityBlock {\n\n    public DeflectorBlock(Properties p_49224_) {\n        super(p_49224_);\n    }\n\n    @Nullable\n    @Override\n    public BlockEntity newBlockEntity(BlockPos p_153215_, BlockState p_153216_) {\n        return new DeflectorBlockEntity(p_153215_, p_153216_);\n    }\n\n    @Override\n    public RenderShape getRenderShape(BlockState p_49232_) {\n        return RenderShape.MODEL;\n    }\n\n    @Nullable\n    @Override\n    public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level p_153212_, BlockState p_153213_, BlockEntityType<T> p_153214_) {\n        return createTickerHelper(p_153214_, WeatherBlocks.BLOCK_ENTITY_DEFLECTOR.get(), DeflectorBlockEntity::tickHelper);\n    }\n\n    @Override\n    public void onRemove(BlockState p_60515_, Level level, BlockPos pos, BlockState p_60518_, boolean p_60519_) {\n        //System.out.println(\"removed deflector block\");\n        if (level.getBlockEntity(pos) instanceof DeflectorBlockEntity deflectorBlockEntity) {\n            deflectorBlockEntity.blockBroken();\n        }\n        super.onRemove(p_60515_, level, pos, p_60518_, p_60519_);\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/block/ForecastBlock.java",
    "content": "package weather2.block;\n\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.network.chat.Component;\nimport net.minecraft.world.InteractionHand;\nimport net.minecraft.world.InteractionResult;\nimport net.minecraft.world.entity.player.Player;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.block.Block;\nimport net.minecraft.world.level.block.RenderShape;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.world.phys.BlockHitResult;\nimport weather2.ServerTickHandler;\nimport weather2.config.ConfigStorm;\nimport weather2.weathersystem.WeatherManagerServer;\n\npublic class ForecastBlock extends Block {\n\n    public ForecastBlock(Properties p_49224_) {\n        super(p_49224_);\n    }\n\n    @Override\n    public RenderShape getRenderShape(BlockState p_49232_) {\n        return RenderShape.MODEL;\n    }\n\n    @Override\n    public InteractionResult use(BlockState p_60503_, Level p_60504_, BlockPos p_60505_, Player p_60506_, InteractionHand p_60507_, BlockHitResult p_60508_) {\n\n        if (!p_60504_.isClientSide) {\n            WeatherManagerServer wm = ServerTickHandler.getWeatherManagerFor(p_60506_.level().dimension());\n            float chance = wm.getBiomeBasedStormSpawnChanceInArea(new BlockPos(p_60506_.blockPosition()));\n            float chanceEvery10Days = 0;\n\n            int rateOften;\n            int rateLessOften;\n            int day = 20*60*20;\n            int diceRollRate = ConfigStorm.Storm_AllTypes_TickRateDelay;\n            if (ConfigStorm.Server_Storm_Deadly_UseGlobalRate) {\n                rateOften = ConfigStorm.Server_Storm_Deadly_TimeBetweenInTicks / day;\n                rateLessOften = ConfigStorm.Server_Storm_Deadly_TimeBetweenInTicks_Land_Based / day;\n                chanceEvery10Days = ConfigStorm.Server_Storm_Deadly_OddsTo1_Land_Based;\n            } else {\n                rateOften = ConfigStorm.Player_Storm_Deadly_TimeBetweenInTicks / day;\n                rateLessOften = ConfigStorm.Player_Storm_Deadly_TimeBetweenInTicks_Land_Based / day;\n                chanceEvery10Days = ConfigStorm.Player_Storm_Deadly_OddsTo1_Land_Based;\n            }\n            if (chanceEvery10Days > 0 && diceRollRate > 0) {\n                chanceEvery10Days = ((float)day / (float)diceRollRate) / chanceEvery10Days;\n            }\n            p_60506_.sendSystemMessage(Component.literal(String.format(\"Chance of a deadly storm here every:\")));\n            p_60506_.sendSystemMessage(Component.literal(String.format(\"%d days: %.2f\", rateOften, (chance * 100F)) + \"% from nearby biome temperature differences\"));\n            p_60506_.sendSystemMessage(Component.literal(String.format(\"%d days: %.2f\", rateLessOften, (chanceEvery10Days * 100F)) + \"% from randomly trying once a day\"));\n\n            int count = 0;\n            for (int i = 0; i < 100000; i++) {\n                if (p_60504_.random.nextInt(1000) == 0) {\n                    count++;\n                }\n            }\n            //System.out.println(count);\n        }\n\n        return InteractionResult.CONSUME;\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/block/SandLayerBlock.java",
    "content": "package weather2.block;\n\nimport net.minecraft.world.level.block.Block;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.world.level.block.Blocks;\nimport net.minecraft.world.item.context.BlockPlaceContext;\nimport net.minecraft.world.level.pathfinder.PathComputationType;\nimport net.minecraft.world.level.block.state.properties.IntegerProperty;\nimport net.minecraft.world.level.block.state.StateDefinition;\nimport net.minecraft.world.level.block.state.properties.BlockStateProperties;\nimport net.minecraft.core.Direction;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.world.phys.shapes.CollisionContext;\nimport net.minecraft.world.phys.shapes.VoxelShape;\nimport net.minecraft.world.phys.shapes.Shapes;\nimport net.minecraft.world.level.BlockGetter;\nimport net.minecraft.world.level.LevelAccessor;\nimport net.minecraft.world.level.LevelReader;\nimport net.minecraft.world.level.LightLayer;\nimport net.minecraft.server.level.ServerLevel;\n\nimport javax.annotation.Nullable;\nimport java.util.Random;\n\nimport net.minecraft.world.level.block.state.BlockBehaviour.Properties;\n\npublic class SandLayerBlock extends Block {\n   public static final IntegerProperty LAYERS = BlockStateProperties.LAYERS;\n   protected static final VoxelShape[] SHAPES = new VoxelShape[]{Shapes.empty(), Block.box(0.0D, 0.0D, 0.0D, 16.0D, 2.0D, 16.0D), Block.box(0.0D, 0.0D, 0.0D, 16.0D, 4.0D, 16.0D), Block.box(0.0D, 0.0D, 0.0D, 16.0D, 6.0D, 16.0D), Block.box(0.0D, 0.0D, 0.0D, 16.0D, 8.0D, 16.0D), Block.box(0.0D, 0.0D, 0.0D, 16.0D, 10.0D, 16.0D), Block.box(0.0D, 0.0D, 0.0D, 16.0D, 12.0D, 16.0D), Block.box(0.0D, 0.0D, 0.0D, 16.0D, 14.0D, 16.0D), Block.box(0.0D, 0.0D, 0.0D, 16.0D, 16.0D, 16.0D)};\n\n   public SandLayerBlock(Properties properties) {\n      super(properties);\n      this.registerDefaultState(this.stateDefinition.any().setValue(LAYERS, Integer.valueOf(1)));\n   }\n\n   public boolean isPathfindable(BlockState state, BlockGetter worldIn, BlockPos pos, PathComputationType type) {\n      switch(type) {\n      case LAND:\n         return state.getValue(LAYERS) < 5;\n      case WATER:\n         return false;\n      case AIR:\n         return false;\n      default:\n         return false;\n      }\n   }\n\n   public VoxelShape getShape(BlockState state, BlockGetter worldIn, BlockPos pos, CollisionContext context) {\n      return SHAPES[state.getValue(LAYERS)];\n   }\n\n   public VoxelShape getCollisionShape(BlockState state, BlockGetter worldIn, BlockPos pos, CollisionContext context) {\n      return SHAPES[state.getValue(LAYERS) - 1];\n   }\n\n   public VoxelShape getBlockSupportShape(BlockState state, BlockGetter reader, BlockPos pos) {\n      return SHAPES[state.getValue(LAYERS)];\n   }\n\n   public VoxelShape getVisualShape(BlockState state, BlockGetter reader, BlockPos pos, CollisionContext context) {\n      return SHAPES[state.getValue(LAYERS)];\n   }\n\n   public boolean useShapeForLightOcclusion(BlockState state) {\n      return true;\n   }\n\n   public boolean canSurvive(BlockState state, LevelReader worldIn, BlockPos pos) {\n      BlockState blockstate = worldIn.getBlockState(pos.below());\n      if (!blockstate.is(Blocks.ICE) && !blockstate.is(Blocks.PACKED_ICE) && !blockstate.is(Blocks.BARRIER)) {\n         if (!blockstate.is(Blocks.HONEY_BLOCK) && !blockstate.is(Blocks.SOUL_SAND)) {\n            return Block.isFaceFull(blockstate.getCollisionShape(worldIn, pos.below()), Direction.UP) || blockstate.getBlock() == this && blockstate.getValue(LAYERS) == 8;\n         } else {\n            return true;\n         }\n      } else {\n         return false;\n      }\n   }\n\n   /**\n    * Update the provided state given the provided neighbor facing and neighbor state, returning a new state.\n    * For example, fences make their connections to the passed in state if possible, and wet concrete powder immediately\n    * returns its solidified counterpart.\n    * Note that this method should ideally consider only the specific face passed in.\n    */\n   public BlockState updateShape(BlockState stateIn, Direction facing, BlockState facingState, LevelAccessor worldIn, BlockPos currentPos, BlockPos facingPos) {\n      return !stateIn.canSurvive(worldIn, currentPos) ? Blocks.AIR.defaultBlockState() : super.updateShape(stateIn, facing, facingState, worldIn, currentPos, facingPos);\n   }\n\n   /**\n    * Performs a random tick on a block.\n    */\n   public void randomTick(BlockState state, ServerLevel worldIn, BlockPos pos, Random random) {\n      if (worldIn.getBrightness(LightLayer.BLOCK, pos) > 11) {\n         dropResources(state, worldIn, pos);\n         worldIn.removeBlock(pos, false);\n      }\n\n   }\n\n   public boolean canBeReplaced(BlockState state, BlockPlaceContext useContext) {\n      int i = state.getValue(LAYERS);\n      return i == 1;\n   }\n\n   @Nullable\n   public BlockState getStateForPlacement(BlockPlaceContext context) {\n      BlockState blockstate = context.getLevel().getBlockState(context.getClickedPos());\n      if (blockstate.is(this)) {\n         int i = blockstate.getValue(LAYERS);\n         return blockstate.setValue(LAYERS, Integer.valueOf(Math.min(8, i + 1)));\n      } else {\n         return super.getStateForPlacement(context);\n      }\n   }\n\n   protected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> builder) {\n      builder.add(LAYERS);\n   }\n}"
  },
  {
    "path": "src/main/java/weather2/block/SensorBlock.java",
    "content": "package weather2.block;\n\nimport net.minecraft.ChatFormatting;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.core.Direction;\nimport net.minecraft.network.chat.Component;\nimport net.minecraft.world.item.ItemStack;\nimport net.minecraft.world.item.TooltipFlag;\nimport net.minecraft.world.level.BlockGetter;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.block.BaseEntityBlock;\nimport net.minecraft.world.level.block.Block;\nimport net.minecraft.world.level.block.RenderShape;\nimport net.minecraft.world.level.block.entity.BlockEntity;\nimport net.minecraft.world.level.block.entity.BlockEntityTicker;\nimport net.minecraft.world.level.block.entity.BlockEntityType;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.world.level.block.state.StateDefinition;\nimport net.minecraft.world.level.block.state.properties.BlockStateProperties;\nimport net.minecraft.world.level.block.state.properties.BooleanProperty;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport org.jetbrains.annotations.Nullable;\nimport weather2.WeatherBlocks;\nimport weather2.blockentity.SensorBlockEntity;\n\nimport java.util.List;\n\npublic class SensorBlock extends BaseEntityBlock {\n\n\tpublic static final BooleanProperty POWERED = BlockStateProperties.POWERED;\n\n    public static final void register() {}\n\n\tpublic SensorBlock(Properties properties) {\n\t\tsuper(properties);\n\t\tthis.registerDefaultState(this.stateDefinition.any().setValue(POWERED, Boolean.valueOf(false)));\n\t}\n\n\t@Override\n\tprotected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> pBuilder) {\n\t\tpBuilder.add(POWERED);\n\t}\n\n\t@Override\n\tpublic RenderShape getRenderShape(BlockState p_49232_) {\n\t\treturn RenderShape.MODEL;\n\t}\n\n\t@Override\n\t@OnlyIn(Dist.CLIENT)\n\tpublic void appendHoverText(ItemStack stack, BlockGetter worldIn, List<Component> tooltip, TooltipFlag flagIn) {\n\t\tsuper.appendHoverText(stack, worldIn, tooltip, flagIn);\n\t\t//tooltip.add(Component.translatable(this.getDescriptionId() + \".desc\").withStyle(ChatFormatting.GRAY));\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic BlockEntity newBlockEntity(BlockPos p_153215_, BlockState p_153216_) {\n\t\treturn new SensorBlockEntity(p_153215_, p_153216_);\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level p_153212_, BlockState p_153213_, BlockEntityType<T> p_153214_) {\n\t\treturn createTickerHelper(p_153214_, WeatherBlocks.BLOCK_ENTITY_TORNADO_SENSOR.get(), SensorBlockEntity::tick);\n\t}\n\n\t@Nullable\n\t@SuppressWarnings(\"unchecked\")\n\tprivate static <E extends BlockEntity, A extends BlockEntity> BlockEntityTicker<A> createTicker(final BlockEntityType<A> type, final BlockEntityType<E> tickerType, final BlockEntityTicker<? super E> ticker) {\n\t\treturn tickerType == type ? (BlockEntityTicker<A>) ticker : null;\n\t}\n\n\t@Override\n\tpublic int getSignal(BlockState pState, BlockGetter pLevel, BlockPos pPos, Direction pDirection) {\n\t\treturn pState.getValue(POWERED) ? 15 : 0;\n\t}\n\n\t@Override\n\tpublic int getDirectSignal(BlockState pBlockState, BlockGetter pBlockAccess, BlockPos pPos, Direction pSide) {\n\t\treturn pBlockState.getValue(POWERED) ? 15 : 0;\n\t}\n\n\t@Override\n\tpublic boolean isSignalSource(BlockState pState) {\n\t\treturn true;\n\t}\n\n\tpublic BlockState setPoweredState(BlockState pState, Level pLevel, BlockPos pPos, boolean state) {\n\t\tpLevel.setBlock(pPos, pState.setValue(POWERED, Boolean.valueOf(state)), 3);\n\t\tpLevel.updateNeighborsAt(pPos, this);\n\t\treturn pState;\n\t}\n\n\tpublic BlockState toggle(BlockState pState, Level pLevel, BlockPos pPos) {\n\t\tpState = pState.cycle(POWERED);\n\t\tpLevel.setBlock(pPos, pState, 3);\n\t\tpLevel.updateNeighborsAt(pPos, this);\n\t\treturn pState;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/block/SirenBlock.java",
    "content": "package weather2.block;\n\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.block.BaseEntityBlock;\nimport net.minecraft.world.level.block.RenderShape;\nimport net.minecraft.world.level.block.entity.BlockEntity;\nimport net.minecraft.world.level.block.entity.BlockEntityTicker;\nimport net.minecraft.world.level.block.entity.BlockEntityType;\nimport net.minecraft.world.level.block.state.BlockState;\nimport org.jetbrains.annotations.Nullable;\nimport weather2.WeatherBlocks;\nimport weather2.blockentity.SirenBlockEntity;\n\npublic class SirenBlock extends BaseEntityBlock {\n\n    public SirenBlock(Properties p_49224_) {\n        super(p_49224_);\n    }\n\n    @Nullable\n    @Override\n    public BlockEntity newBlockEntity(BlockPos p_153215_, BlockState p_153216_) {\n        return new SirenBlockEntity(p_153215_, p_153216_);\n    }\n\n    @Override\n    public RenderShape getRenderShape(BlockState p_49232_) {\n        return RenderShape.MODEL;\n    }\n\n    @Nullable\n    @Override\n    public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level p_153212_, BlockState p_153213_, BlockEntityType<T> p_153214_) {\n        return createTickerHelper(p_153214_, WeatherBlocks.BLOCK_ENTITY_TORNADO_SIREN.get(), SirenBlockEntity::tickHelper);\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/block/WeatherMachineBlock.java",
    "content": "package weather2.block;\n\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.block.BaseEntityBlock;\nimport net.minecraft.world.level.block.RenderShape;\nimport net.minecraft.world.level.block.entity.BlockEntity;\nimport net.minecraft.world.level.block.entity.BlockEntityTicker;\nimport net.minecraft.world.level.block.entity.BlockEntityType;\nimport net.minecraft.world.level.block.state.BlockState;\nimport org.jetbrains.annotations.Nullable;\nimport weather2.WeatherBlocks;\nimport weather2.blockentity.DeflectorBlockEntity;\nimport weather2.blockentity.WeatherMachineBlockEntity;\n\npublic class WeatherMachineBlock extends BaseEntityBlock {\n\n    public WeatherMachineBlock(Properties p_49224_) {\n        super(p_49224_);\n    }\n\n    @Nullable\n    @Override\n    public BlockEntity newBlockEntity(BlockPos p_153215_, BlockState p_153216_) {\n        return new WeatherMachineBlockEntity(p_153215_, p_153216_);\n    }\n\n    @Override\n    public RenderShape getRenderShape(BlockState p_49232_) {\n        return RenderShape.MODEL;\n    }\n\n    /*@Nullable\n    @Override\n    public <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level p_153212_, BlockState p_153213_, BlockEntityType<T> p_153214_) {\n        return createTickerHelper(p_153214_, WeatherBlocks.BLOCK_ENTITY_WEATHER_MACHINE.get(), WeatherMachineBlockEntity::tickHelper);\n    }*/\n}\n"
  },
  {
    "path": "src/main/java/weather2/block/WindTurbineBlock.java",
    "content": "package weather2.block;\n\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.network.chat.Component;\nimport net.minecraft.world.entity.player.Player;\nimport net.minecraft.world.item.ItemStack;\nimport net.minecraft.world.item.TooltipFlag;\nimport net.minecraft.world.level.BlockGetter;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.block.BaseEntityBlock;\nimport net.minecraft.world.level.block.Block;\nimport net.minecraft.world.level.block.Blocks;\nimport net.minecraft.world.level.block.RenderShape;\nimport net.minecraft.world.level.block.entity.BlockEntity;\nimport net.minecraft.world.level.block.entity.BlockEntityTicker;\nimport net.minecraft.world.level.block.entity.BlockEntityType;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.world.level.block.state.StateDefinition;\nimport net.minecraft.world.phys.shapes.CollisionContext;\nimport net.minecraft.world.phys.shapes.VoxelShape;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport org.jetbrains.annotations.Nullable;\nimport weather2.WeatherBlocks;\nimport weather2.blockentity.WindTurbineBlockEntity;\nimport weather2.blockentity.WindVaneBlockEntity;\n\nimport java.util.List;\n\npublic class WindTurbineBlock extends BaseEntityBlock {\n\n\tpublic static final VoxelShape SHAPE = box(2.0, 0.0, 2.0, 14.0, 32.0, 14.0);\n\n    public static final void register() {}\n\n\tpublic WindTurbineBlock(Properties properties) {\n\t\tsuper(properties);\n\t}\n\n\t@Override\n\tprotected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> pBuilder) {\n\n\t}\n\n\t@Override\n\tpublic VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) {\n\t\treturn SHAPE;\n\t}\n\n\t@Override\n\tpublic RenderShape getRenderShape(BlockState p_49232_) {\n\t\treturn RenderShape.INVISIBLE;\n\t}\n\n\t@Override\n\t@OnlyIn(Dist.CLIENT)\n\tpublic void appendHoverText(ItemStack stack, BlockGetter worldIn, List<Component> tooltip, TooltipFlag flagIn) {\n\t\tsuper.appendHoverText(stack, worldIn, tooltip, flagIn);\n\t\t//tooltip.add(Component.translatable(this.getDescriptionId() + \".desc\").withStyle(ChatFormatting.GRAY));\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic BlockEntity newBlockEntity(BlockPos p_153215_, BlockState p_153216_) {\n\t\treturn new WindTurbineBlockEntity(p_153215_, p_153216_);\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level p_153212_, BlockState p_153213_, BlockEntityType<T> p_153214_) {\n\t\treturn createTickerHelper(p_153214_, WeatherBlocks.BLOCK_ENTITY_WIND_TURBINE.get(), WindTurbineBlockEntity::tick);\n\t}\n\n\t@Nullable\n\t@SuppressWarnings(\"unchecked\")\n\tprivate static <E extends BlockEntity, A extends BlockEntity> BlockEntityTicker<A> createTicker(final BlockEntityType<A> type, final BlockEntityType<E> tickerType, final BlockEntityTicker<? super E> ticker) {\n\t\treturn tickerType == type ? (BlockEntityTicker<A>) ticker : null;\n\t}\n\n}\n"
  },
  {
    "path": "src/main/java/weather2/block/WindVaneBlock.java",
    "content": "package weather2.block;\n\nimport net.minecraft.ChatFormatting;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.core.Direction;\nimport net.minecraft.network.chat.Component;\nimport net.minecraft.world.item.ItemStack;\nimport net.minecraft.world.item.TooltipFlag;\nimport net.minecraft.world.level.BlockGetter;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.block.BaseEntityBlock;\nimport net.minecraft.world.level.block.Block;\nimport net.minecraft.world.level.block.RenderShape;\nimport net.minecraft.world.level.block.entity.BlockEntity;\nimport net.minecraft.world.level.block.entity.BlockEntityTicker;\nimport net.minecraft.world.level.block.entity.BlockEntityType;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.world.level.block.state.StateDefinition;\nimport net.minecraft.world.level.block.state.properties.BlockStateProperties;\nimport net.minecraft.world.level.block.state.properties.BooleanProperty;\nimport net.minecraft.world.phys.shapes.CollisionContext;\nimport net.minecraft.world.phys.shapes.VoxelShape;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport org.jetbrains.annotations.Nullable;\nimport weather2.WeatherBlocks;\nimport weather2.blockentity.WindVaneBlockEntity;\n\nimport java.util.List;\n\npublic class WindVaneBlock extends BaseEntityBlock {\n\n\tpublic static final VoxelShape SHAPE = box(6.0, 0.0, 6.0, 10.0, 16.0, 10.0);\n\n    public static final void register() {}\n\n\tpublic WindVaneBlock(Properties properties) {\n\t\tsuper(properties);\n\t}\n\n\t@Override\n\tprotected void createBlockStateDefinition(StateDefinition.Builder<Block, BlockState> pBuilder) {\n\n\t}\n\n\t@Override\n\tpublic VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) {\n\t\treturn SHAPE;\n\t}\n\n\t@Override\n\tpublic RenderShape getRenderShape(BlockState p_49232_) {\n\t\treturn RenderShape.INVISIBLE;\n\t}\n\n\t@Override\n\t@OnlyIn(Dist.CLIENT)\n\tpublic void appendHoverText(ItemStack stack, BlockGetter worldIn, List<Component> tooltip, TooltipFlag flagIn) {\n\t\tsuper.appendHoverText(stack, worldIn, tooltip, flagIn);\n\t\t//tooltip.add(Component.translatable(this.getDescriptionId() + \".desc\").withStyle(ChatFormatting.GRAY));\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic BlockEntity newBlockEntity(BlockPos p_153215_, BlockState p_153216_) {\n\t\treturn new WindVaneBlockEntity(p_153215_, p_153216_);\n\t}\n\n\t@Nullable\n\t@Override\n\tpublic <T extends BlockEntity> BlockEntityTicker<T> getTicker(Level p_153212_, BlockState p_153213_, BlockEntityType<T> p_153214_) {\n\t\treturn createTickerHelper(p_153214_, WeatherBlocks.BLOCK_ENTITY_WIND_VANE.get(), WindVaneBlockEntity::tick);\n\t}\n\n\t@Nullable\n\t@SuppressWarnings(\"unchecked\")\n\tprivate static <E extends BlockEntity, A extends BlockEntity> BlockEntityTicker<A> createTicker(final BlockEntityType<A> type, final BlockEntityType<E> tickerType, final BlockEntityTicker<? super E> ticker) {\n\t\treturn tickerType == type ? (BlockEntityTicker<A>) ticker : null;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/blockentity/AnemometerBlockEntity.java",
    "content": "package weather2.blockentity;\n\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.nbt.CompoundTag;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.block.entity.BlockEntity;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.world.phys.Vec3;\nimport weather2.WeatherBlocks;\nimport weather2.block.AnemometerBlock;\nimport weather2.util.WeatherUtilEntity;\nimport weather2.util.WindReader;\n\npublic class AnemometerBlockEntity extends BlockEntity {\n\n\tpublic float smoothAngle = 0;\n\tpublic float smoothAnglePrev = 0;\n\n\tpublic float smoothAngleRotationalVel = 0;\n\n\tpublic boolean isOutsideCached = false;\n\n\tpublic AnemometerBlockEntity(BlockPos p_155229_, BlockState p_155230_) {\n\t\tsuper(WeatherBlocks.BLOCK_ENTITY_ANEMOMETER.get(), p_155229_, p_155230_);\n\t}\n\n\t@Override\n\tpublic void setLevel(final Level level) {\n\t\tsuper.setLevel(level);\n\t}\n\n\tpublic static void tick(Level level, BlockPos pos, BlockState state, AnemometerBlockEntity entity) {\n\t\tentity.tick(level, pos, state);\n\t}\n\n\tpublic void tick(Level level, BlockPos pos, BlockState state) {\n\t\tif (!level.isClientSide) {\n\n\t\t} else {\n\t\t\tif (level.getGameTime() % 40 == 0) {\n\t\t\t\tisOutsideCached = WeatherUtilEntity.isPosOutside(level, new Vec3(getBlockPos().getX()+0.5F, getBlockPos().getY()+0.5F, getBlockPos().getZ()+0.5F), false, true);\n\t\t\t}\n\n\t\t\tif (isOutsideCached) {\n\t\t\t\t//do not cache for this, the amp value used will mess with the turbines amp, design oversight\n\t\t\t\tfloat windSpeed = WindReader.getWindSpeed(level, pos, 1);\n\t\t\t\tfloat rotMax = 50F;\n\t\t\t\tfloat maxSpeed = (windSpeed / 1.2F) * rotMax;\n\t\t\t\tif (smoothAngleRotationalVel < maxSpeed) {\n\t\t\t\t\tsmoothAngleRotationalVel += windSpeed * 0.3F;\n\t\t\t\t}\n\t\t\t\tif (smoothAngleRotationalVel > rotMax) smoothAngleRotationalVel = rotMax;\n\t\t\t\tif (smoothAngle >= 180) smoothAngle -= 360;\n\t\t\t}\n\n\t\t\tsmoothAnglePrev = smoothAngle;\n\t\t\tsmoothAngle += smoothAngleRotationalVel;\n\t\t\tsmoothAngleRotationalVel -= 0.01F;\n\n\t\t\tsmoothAngleRotationalVel *= 0.99F;\n\n\t\t\tif (smoothAngleRotationalVel <= 0) smoothAngleRotationalVel = 0;\n\t\t}\n\t}\n\n\t@Override\n\tpublic void load(final CompoundTag tag) {\n\t\tsuper.load(tag);\n\t}\n\n\t@Override\n\tprotected void saveAdditional(final CompoundTag tag) {\n\t\tsuper.saveAdditional(tag);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/blockentity/DeflectorBlockEntity.java",
    "content": "package weather2.blockentity;\n\nimport com.corosus.coroutil.util.CULog;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.server.level.ServerLevel;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.block.entity.BlockEntity;\nimport net.minecraft.world.level.block.state.BlockState;\nimport weather2.ServerTickHandler;\nimport weather2.WeatherBlocks;\nimport weather2.weathersystem.WeatherManagerServer;\n\npublic class DeflectorBlockEntity extends BlockEntity {\n\n    private boolean needsInit = true;\n\n    public DeflectorBlockEntity(BlockPos p_155229_, BlockState p_155230_) {\n        super(WeatherBlocks.BLOCK_ENTITY_DEFLECTOR.get(), p_155229_, p_155230_);\n    }\n\n    public static void tickHelper(Level level, BlockPos pos, BlockState state, BlockEntity blockEntity) {\n        ((DeflectorBlockEntity)blockEntity).tick();\n    }\n\n    public void tick() {\n        if (needsInit) {\n            needsInit = false;\n            init();\n        }\n    }\n\n    public void init() {\n        if (!level.isClientSide()) {\n            ServerTickHandler.getWeatherManagerFor(level).registerDeflector(getBlockPos());\n        }\n    }\n\n    public void blockBroken() {\n        WeatherManagerServer weatherManagerServer = ServerTickHandler.getWeatherManagerFor(level);\n        if (weatherManagerServer != null) {\n            //CULog.dbg(\"removing weather deflector poi at \" + getBlockPos());\n            weatherManagerServer.removeDeflector(getBlockPos());\n        }\n    }\n\n    @Override\n    public void setRemoved() {\n        super.setRemoved();\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/blockentity/SensorBlockEntity.java",
    "content": "package weather2.blockentity;\n\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.nbt.CompoundTag;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.block.entity.BlockEntity;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.world.phys.Vec3;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport weather2.ClientTickHandler;\nimport weather2.ServerTickHandler;\nimport weather2.WeatherBlocks;\nimport weather2.block.SensorBlock;\nimport weather2.config.ConfigMisc;\nimport weather2.weathersystem.WeatherManagerServer;\nimport weather2.weathersystem.storm.StormObject;\n\npublic class SensorBlockEntity extends BlockEntity {\n\n\tpublic SensorBlockEntity(BlockPos p_155229_, BlockState p_155230_) {\n\t\tsuper(WeatherBlocks.BLOCK_ENTITY_TORNADO_SENSOR.get(), p_155229_, p_155230_);\n\t}\n\n\t@Override\n\tpublic void setLevel(final Level level) {\n\t\tsuper.setLevel(level);\n\t}\n\n\tpublic static void tick(Level level, BlockPos pos2, BlockState state, SensorBlockEntity entity) {\n\t\tif (!level.isClientSide && level.getGameTime() % 100 == 0) {\n\t\t\tWeatherManagerServer wm = ServerTickHandler.getWeatherManagerFor(level);\n\t\t\tif (wm != null) {\n\t\t\t\tVec3 pos = new Vec3(pos2.getX(), pos2.getY(), pos2.getZ());\n\t\t\t\tStormObject so = wm.getClosestStorm(pos, ConfigMisc.sirenActivateDistance, StormObject.STATE_FORMING);\n\t\t\t\tif (so != null) {\n\t\t\t\t\tentity.setPoweredState(true);\n\t\t\t\t} else {\n\t\t\t\t\tentity.setPoweredState(false);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic void setPoweredState(boolean state) {\n\t\tBlockState blockState = level.getBlockState(this.getBlockPos());\n\t\tif (blockState.getBlock() instanceof SensorBlock) {\n\t\t\t((SensorBlock) blockState.getBlock()).setPoweredState(this.getBlockState(), level, this.getBlockPos(), state);\n\t\t}\n\t}\n\n\t@Override\n\tpublic void load(final CompoundTag tag) {\n\t\tsuper.load(tag);\n\t}\n\n\t@Override\n\tprotected void saveAdditional(final CompoundTag tag) {\n\t\tsuper.saveAdditional(tag);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/blockentity/SirenBlockEntity.java",
    "content": "package weather2.blockentity;\n\nimport com.corosus.coroutil.util.CoroUtilMisc;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.block.entity.BlockEntity;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.world.phys.Vec3;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport weather2.ClientTickHandler;\nimport weather2.WeatherBlocks;\nimport weather2.config.ConfigMisc;\nimport weather2.config.ConfigSand;\nimport weather2.config.ConfigSound;\nimport weather2.util.WeatherUtilSound;\nimport weather2.weathersystem.storm.StormObject;\nimport weather2.weathersystem.storm.WeatherObjectParticleStorm;\n\nimport java.util.List;\n\npublic class SirenBlockEntity extends BlockEntity {\n\n    public long lastPlayTime = 0L;\n\n    public SirenBlockEntity(BlockPos p_155229_, BlockState p_155230_) {\n        super(WeatherBlocks.BLOCK_ENTITY_TORNADO_SIREN.get(), p_155229_, p_155230_);\n    }\n\n    public static void tickHelper(Level level, BlockPos pos, BlockState state, BlockEntity blockEntity) {\n        ((SirenBlockEntity)blockEntity).tick();\n    }\n\n    public void tick() {\n        if (level.isClientSide()) {\n            tickClient();\n        }\n    }\n\n    @OnlyIn(Dist.CLIENT)\n    public void tickClient() {\n        if (this.lastPlayTime < System.currentTimeMillis())\n        {\n            Vec3 pos = new Vec3(getBlockPos().getX(), getBlockPos().getY(), getBlockPos().getZ());\n\n            StormObject so = ClientTickHandler.weatherManager.getClosestStorm(pos, ConfigMisc.sirenActivateDistance, StormObject.STATE_FORMING);\n\n            if (so != null)\n            {\n                this.lastPlayTime = System.currentTimeMillis() + 13000L;\n                WeatherUtilSound.playNonMovingSound(pos, \"streaming.siren\", (float) ConfigSound.sirenVolume, 1.0F, 120);\n            } else {\n                if (!ConfigSand.Sandstorm_Siren_PleaseNoDarude) {\n                    WeatherObjectParticleStorm storm = ClientTickHandler.weatherManager.getClosestParticleStormByIntensity(pos, WeatherObjectParticleStorm.StormType.SANDSTORM);\n                    if (storm == null) {\n                        storm = ClientTickHandler.weatherManager.getClosestParticleStormByIntensity(pos, WeatherObjectParticleStorm.StormType.SNOWSTORM);\n                    }\n\n                    if (storm != null) {\n\n                        if (pos.distanceTo(storm.pos) < storm.getSize()) {\n                            String soundToPlay = \"siren_sandstorm_5_extra\";\n                            if (CoroUtilMisc.random.nextBoolean()) {\n                                soundToPlay = \"siren_sandstorm_6_extra\";\n                            }\n\n                            float distScaleFunnyPitchChangeHaha = Math.max(0.1F, 1F - (float) ((pos.distanceTo(storm.pos)) / storm.getSize()));\n\n                            this.lastPlayTime = System.currentTimeMillis() + 15000L;//WeatherUtilSound.soundToLength.get(soundToPlay) - 500L;\n                            WeatherUtilSound.playNonMovingSound(pos, \"streaming.\" + soundToPlay, (float) ConfigSound.sirenVolume, distScaleFunnyPitchChangeHaha, storm.getSize());\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/blockentity/WeatherMachineBlockEntity.java",
    "content": "package weather2.blockentity;\n\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.block.entity.BlockEntity;\nimport net.minecraft.world.level.block.state.BlockState;\nimport weather2.WeatherBlocks;\n\npublic class WeatherMachineBlockEntity extends BlockEntity {\n    public WeatherMachineBlockEntity(BlockPos p_155229_, BlockState p_155230_) {\n        super(WeatherBlocks.BLOCK_ENTITY_DEFLECTOR.get(), p_155229_, p_155230_);\n    }\n\n    public static void tickHelper(Level level, BlockPos pos, BlockState state, BlockEntity blockEntity) {\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/blockentity/WindTurbineBlockEntity.java",
    "content": "package weather2.blockentity;\n\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.core.Direction;\nimport net.minecraft.nbt.CompoundTag;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.block.entity.BlockEntity;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.world.phys.Vec3;\nimport net.minecraftforge.common.capabilities.Capability;\nimport net.minecraftforge.common.capabilities.ForgeCapabilities;\nimport net.minecraftforge.common.util.LazyOptional;\nimport org.jetbrains.annotations.NotNull;\nimport org.jetbrains.annotations.Nullable;\nimport weather2.WeatherBlocks;\nimport weather2.config.ConfigWind;\nimport weather2.energy.EnergyManager;\nimport weather2.util.WeatherUtilEntity;\nimport weather2.util.WindReader;\nimport weather2.weathersystem.WeatherManager;\n\npublic class WindTurbineBlockEntity extends BlockEntity {\n\n\tpublic float smoothAngle = 0;\n\tpublic float smoothAnglePrev = 0;\n\n\tpublic float smoothAngleRotationalVel = 0;\n\n\tpublic boolean isOutsideCached = false;\n\n\tprivate boolean needsInit = true;\n\n\t//private final EnergyManager energyManager;\n\tprivate LazyOptional<EnergyManager> energy;\n\tprivate EnergyManager energyManager;\n\n\t//amount generated at windspeed of 1, theoretical max windspeed is 2 when tornado right on top of it\n\tprivate int maxNormalGenerated = ConfigWind.Wind_Turbine_FE_Generated_Per_Tick;\n\tprivate int capacity = maxNormalGenerated * 2;\n\tprivate int maxTransfer = capacity;\n\n\tprivate float lastWindSpeed = 0;\n\n\tpublic WindTurbineBlockEntity(BlockPos p_155229_, BlockState p_155230_) {\n\t\tsuper(WeatherBlocks.BLOCK_ENTITY_WIND_TURBINE.get(), p_155229_, p_155230_);\n\n\t\tthis.energyManager = new EnergyManager(maxTransfer, capacity);\n\t\tthis.energy = LazyOptional.of(() -> this.energyManager);\n\t}\n\n\t@Override\n\tpublic void setLevel(final Level level) {\n\t\tsuper.setLevel(level);\n\t}\n\n\tpublic static void tick(Level level, BlockPos pos, BlockState state, WindTurbineBlockEntity entity) {\n\t\tentity.tick(level, pos, state);\n\t}\n\n\tpublic void tick(Level level, BlockPos pos, BlockState state) {\n\t\tif (needsInit) {\n\t\t\tneedsInit = false;\n\t\t\tupdateIsOutside();\n\t\t}\n\t\tif (level.getGameTime() % 100 == 0) {\n\t\t\tupdateIsOutside();\n\t\t}\n\t\tif (isOutsideCached) {\n\t\t\tif (level.getGameTime() % 20 == 0) {\n\t\t\t\tWeatherManager weatherManager = WindReader.getWeatherManagerFor(level);\n\t\t\t\tif (weatherManager != null) {\n\t\t\t\t\tlastWindSpeed = weatherManager.getWindManager().getWindSpeedPositional(getBlockPos(), 2, false);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (!level.isClientSide) {\n\t\t\tif (isOutsideCached) {\n\t\t\t\tthis.energyManager.addEnergy((int) (maxNormalGenerated * lastWindSpeed));\n\t\t\t\toutputEnergy();\n\t\t\t}\n\t\t} else {\n\t\t\tif (isOutsideCached) {\n\t\t\t\tfloat windSpeed = lastWindSpeed;//WindReader.getWindSpeed(level);\n\t\t\t\tfloat rotMax = 100F;\n\t\t\t\tfloat maxSpeed = (windSpeed / 2F) * rotMax;\n\t\t\t\tif (smoothAngleRotationalVel < maxSpeed) {\n\t\t\t\t\tsmoothAngleRotationalVel += windSpeed * 0.3F;\n\t\t\t\t}\n\t\t\t\tif (smoothAngleRotationalVel > rotMax) smoothAngleRotationalVel = rotMax;\n\t\t\t\tif (smoothAngle >= 180) smoothAngle -= 360;\n\t\t\t}\n\n\t\t\tsmoothAnglePrev = smoothAngle;\n\t\t\tsmoothAngle += smoothAngleRotationalVel;\n\t\t\tsmoothAngleRotationalVel -= 0.01F;\n\n\t\t\tsmoothAngleRotationalVel *= 0.99F;\n\n\t\t\tif (smoothAngleRotationalVel <= 0) smoothAngleRotationalVel = 0;\n\t\t}\n\t}\n\n\tpublic void updateIsOutside() {\n\t\tisOutsideCached = WeatherUtilEntity.isPosOutside(level, new Vec3(getBlockPos().getX()+0.5F, getBlockPos().getY()+0.5F, getBlockPos().getZ()+0.5F), false, true);\n\t}\n\n\tpublic void outputEnergy() {\n\t\t//System.out.println(this.energyManager.getEnergyStored());\n\t\tif (this.energyManager.getEnergyStored() >= this.energyManager.getMaxExtract() && this.energyManager.canExtract()) {\n\t\t\tfor (final var direction : Direction.values()) {\n\t\t\t\tfinal BlockEntity be = this.level.getBlockEntity(this.worldPosition.relative(direction));\n\t\t\t\tif (be == null) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tbe.getCapability(ForgeCapabilities.ENERGY, direction.getOpposite()).ifPresent(storage -> {\n\t\t\t\t\tif (be != this && storage.getEnergyStored() < storage.getMaxEnergyStored()) {\n\t\t\t\t\t\tthis.energyManager.drainEnergy(this.energyManager.getMaxExtract());\n\t\t\t\t\t\t//Weather.LOGGER.info(\"Send: {}\", this.energyManager.getMaxExtract());\n\t\t\t\t\t\tfinal int received = storage.receiveEnergy(this.energyManager.getMaxExtract(), false);\n\t\t\t\t\t\t//Weather.LOGGER.info(\"Final Received: {}\", received);\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic void load(final CompoundTag tag) {\n\t\tsuper.load(tag);\n\t}\n\n\t@Override\n\tprotected void saveAdditional(final CompoundTag tag) {\n\t\tsuper.saveAdditional(tag);\n\t}\n\n\t@Override\n\tpublic @NotNull <T> LazyOptional<T> getCapability(@NotNull Capability<T> cap, @Nullable Direction side) {\n\t\tLazyOptional<T> energyCapability = energyManager.getCapability(cap);\n\t\tif (energyCapability.isPresent()) {\n\t\t\treturn energyCapability;\n\t\t}\n\t\treturn super.getCapability(cap, side);\n\t}\n\n\t@Override\n\tpublic void invalidateCaps() {\n\t\tsuper.invalidateCaps();\n\t\tthis.energy.invalidate();\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/blockentity/WindVaneBlockEntity.java",
    "content": "package weather2.blockentity;\n\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.nbt.CompoundTag;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.block.entity.BlockEntity;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.world.phys.Vec3;\nimport weather2.WeatherBlocks;\nimport weather2.block.AnemometerBlock;\nimport weather2.util.WeatherUtilEntity;\nimport weather2.util.WindReader;\n\npublic class WindVaneBlockEntity extends BlockEntity {\n\n\tpublic float smoothAngle = 0;\n\tpublic float smoothAnglePrev = 0;\n\tpublic float smoothAngleRotationalVelAccel = 0;\n\n\tpublic boolean isOutsideCached = false;\n\n\tpublic WindVaneBlockEntity(BlockPos p_155229_, BlockState p_155230_) {\n\t\tsuper(WeatherBlocks.BLOCK_ENTITY_WIND_VANE.get(), p_155229_, p_155230_);\n\t}\n\n\t@Override\n\tpublic void setLevel(final Level level) {\n\t\tsuper.setLevel(level);\n\t}\n\n\tpublic static void tick(Level level, BlockPos pos, BlockState state, WindVaneBlockEntity entity) {\n\t\tentity.tick(level, pos, state);\n\t}\n\n\tpublic void tick(Level level, BlockPos pos, BlockState state) {\n\t\tif (!level.isClientSide) {\n\n\t\t} else {\n\t\t\tif (level.getGameTime() % 40 == 0) {\n\t\t\t\tisOutsideCached = WeatherUtilEntity.isPosOutside(level, new Vec3(getBlockPos().getX()+0.5F, getBlockPos().getY()+0.5F, getBlockPos().getZ()+0.5F), false, true);\n\t\t\t}\n\n\t\t\tif (isOutsideCached) {\n\t\t\t\tfloat targetAngle = WindReader.getWindAngle(level, new Vec3(getBlockPos().getX(), getBlockPos().getY(), getBlockPos().getZ()));\n\t\t\t\t//do not cache for this, the amp value used will mess with the turbines amp, design oversight\n\t\t\t\tfloat windSpeed = WindReader.getWindSpeed(level, pos, 1);\n\t\t\t\tif (smoothAngle > 180) smoothAngle -= 360;\n\t\t\t\tif (smoothAngle < -180) smoothAngle += 360;\n\n\t\t\t\tif (smoothAnglePrev > 180) smoothAnglePrev -= 360;\n\t\t\t\tif (smoothAnglePrev < -180) smoothAnglePrev += 360;\n\n\t\t\t\tsmoothAnglePrev = smoothAngle;\n\n\t\t\t\tfloat bestMove = Mth.wrapDegrees(targetAngle - smoothAngle);\n\t\t\t\tif (Math.abs(bestMove) < 180) {\n\t\t\t\t\tif (bestMove > 0) smoothAngleRotationalVelAccel -= windSpeed * 0.4;\n\t\t\t\t\tif (bestMove < 0) smoothAngleRotationalVelAccel += windSpeed * 0.4;\n\t\t\t\t\tif (smoothAngleRotationalVelAccel > 0.3 || smoothAngleRotationalVelAccel < -0.3) {\n\t\t\t\t\t\tsmoothAngle += smoothAngleRotationalVelAccel;\n\t\t\t\t\t}\n\t\t\t\t\tsmoothAngleRotationalVelAccel *= 0.96F;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t@Override\n\tpublic void load(final CompoundTag tag) {\n\t\tsuper.load(tag);\n\t}\n\n\t@Override\n\tprotected void saveAdditional(final CompoundTag tag) {\n\t\tsuper.saveAdditional(tag);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/client/MovingSoundStreamingSource.java",
    "content": "package weather2.client;\n\nimport com.corosus.coroutil.util.CULog;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.client.resources.sounds.AbstractTickableSoundInstance;\nimport net.minecraft.client.resources.sounds.SoundInstance;\nimport net.minecraft.world.entity.player.Player;\nimport net.minecraft.sounds.SoundSource;\nimport net.minecraft.sounds.SoundEvent;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.phys.Vec3;\nimport weather2.weathersystem.storm.StormObject;\n\npublic class MovingSoundStreamingSource extends AbstractTickableSoundInstance {\n\n\tprivate StormObject storm = null;\n\tpublic float cutOffRange = 128;\n\tpublic Vec3 realSource = null;\n\tpublic boolean lockToPlayer = false;\n\tprivate float extraVolumeAdjForDistScale = 1F;\n\n\tpublic MovingSoundStreamingSource(Vec3 parPos, SoundEvent event, SoundSource category, float parVolume, float parPitch, boolean lockToPlayer) {\n\t\tsuper(event, category, SoundInstance.createUnseededRandom());\n\t\tthis.looping = false;\n\t\tthis.volume = parVolume;\n\t\tthis.extraVolumeAdjForDistScale = parVolume;\n\t\tthis.pitch = parPitch;\n\t\tthis.realSource = parPos;\n\n\t\tthis.lockToPlayer = lockToPlayer;\n\n\t\ttick();\n\t}\n\n\t//constructor for non moving sounds\n\tpublic MovingSoundStreamingSource(Vec3 parPos, SoundEvent event, SoundSource category, float parVolume, float parPitch, float parCutOffRange)\n\t{\n\t\tsuper(event, category, SoundInstance.createUnseededRandom());\n\t\tthis.looping = false;\n\t\tthis.volume = parVolume;\n\t\tthis.extraVolumeAdjForDistScale = parVolume;\n\t\tthis.pitch = parPitch;\n\t\tcutOffRange = parCutOffRange;\n\t\trealSource = parPos;\n\n\t\t//sync position\n\t\ttick();\n\t}\n\n\t//constructor for moving sounds\n\tpublic MovingSoundStreamingSource(StormObject parStorm, SoundEvent event, SoundSource category, float parVolume, float parPitch, float parCutOffRange)\n\t{\n\t\tsuper(event, category, SoundInstance.createUnseededRandom());\n\t\tthis.storm = parStorm;\n\t\tthis.looping = false;\n\t\tthis.volume = parVolume;\n\t\tthis.extraVolumeAdjForDistScale = parVolume;\n\t\tthis.pitch = parPitch;\n\t\tcutOffRange = parCutOffRange;\n\n\t\t//sync position\n\t\ttick();\n\t}\n\n\tpublic void tick()\n\t{\n\t\tPlayer entP = Minecraft.getInstance().player;\n\n\t\tif (entP != null) {\n\t\t\tthis.x = (float) entP.getX();\n\t\t\tthis.y = (float) entP.getY();\n\t\t\tthis.z = (float) entP.getZ();\n\t\t}\n\n\t\tif (storm != null) {\n\t\t\trealSource = this.storm.posGround;\n\t\t}\n\n\t\t//if locked to player, don't dynamically adjust volume\n\t\tif (!lockToPlayer) {\n\t\t\tdouble dist = getDistanceFrom(realSource, entP.position());\n\t\t\tif (dist > cutOffRange) {\n\t\t\t\tvolume = 0;\n\t\t\t} else {\n\t\t\t\tvolume = (float) (1F - (dist / cutOffRange)) * extraVolumeAdjForDistScale;\n\t\t\t}\n\t\t\t//CULog.dbg(\"sound: \" + this.location + \" vol: \" + volume + \" cutOffRange: \" + cutOffRange + \" dist: \" + dist);\n\t\t}\n\n\t}\n\n\tpublic double getDistanceFrom(Vec3 source, Vec3 targ)\n\t{\n\t\treturn source.distanceTo(targ);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/client/SceneEnhancer.java",
    "content": "package weather2.client;\n\nimport com.corosus.coroutil.config.ConfigCoroUtil;\nimport com.corosus.coroutil.util.*;\nimport net.minecraft.core.Direction;\nimport net.minecraft.core.particles.ParticleOptions;\nimport net.minecraft.sounds.SoundEvents;\nimport net.minecraft.tags.FluidTags;\nimport net.minecraft.world.level.LevelReader;\nimport net.minecraft.world.level.block.*;\nimport net.minecraft.world.level.material.FluidState;\nimport net.minecraft.world.level.material.MapColor;\nimport weather2.config.ConfigMisc;\nimport weather2.config.ConfigSound;\nimport weather2.datatypes.PrecipitationType;\nimport weather2.datatypes.WeatherEventType;\nimport extendedrenderer.particle.ParticleRegistry;\nimport extendedrenderer.particle.behavior.ParticleBehaviorSandstorm;\nimport extendedrenderer.particle.entity.*;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.client.particle.FlameParticle;\nimport net.minecraft.client.particle.Particle;\nimport net.minecraft.client.particle.SuspendedParticle;\nimport net.minecraft.client.renderer.texture.TextureAtlasSprite;\nimport net.minecraft.client.ParticleStatus;\nimport net.minecraft.client.multiplayer.ClientLevel;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.entity.player.Player;\nimport net.minecraft.core.particles.ParticleTypes;\nimport net.minecraft.resources.ResourceLocation;\nimport net.minecraft.sounds.SoundSource;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.phys.shapes.VoxelShape;\nimport net.minecraft.world.phys.Vec3;\nimport net.minecraft.core.Vec3i;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.biome.Biome;\nimport net.minecraft.world.level.levelgen.Heightmap;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport net.minecraftforge.event.TickEvent;\nimport weather2.*;\nimport weather2.client.entity.particle.ParticleHail;\nimport weather2.client.entity.particle.ParticleSandstorm;\nimport weather2.config.ConfigParticle;\nimport weather2.config.ConfigSand;\nimport weather2.util.*;\nimport weather2.weathersystem.WeatherManagerClient;\nimport weather2.weathersystem.fog.FogAdjuster;\nimport weather2.weathersystem.storm.StormObject;\nimport weather2.weathersystem.storm.WeatherObjectParticleStorm;\nimport weather2.weathersystem.tornado.TornadoManagerTodoRenameMe;\nimport weather2.weathersystem.wind.WindManager;\n\nimport java.util.*;\n\n@OnlyIn(Dist.CLIENT)\npublic class SceneEnhancer implements Runnable {\n\n\tprivate static final double PRECIPITATION_PARTICLE_EFFECT_RATE = 0.7;\n\n\t//this is for the thread we make\n\tpublic ClientLevel lastWorldDetected = null;\n\n\tpublic static List<Particle> spawnQueueNormal = new ArrayList<>();\n    public static List<Particle> spawnQueue = new ArrayList<>();\n\n    public static long threadLastWorldTickTime;\n    public static int lastTickFoundBlocks;\n    public static long lastTickAmbient;\n    public static long lastTickAmbientThreaded;\n\n    public static ArrayList<ChunkCoordinatesBlock> soundLocations = new ArrayList<>();\n    public static HashMap<ChunkCoordinatesBlock, Long> soundTimeLocations = new HashMap<>();\n\n    public static List<Block> LEAVES_BLOCKS = new ArrayList<>();\n\n\tprivate static final List<BlockPos> listPosRandom = new ArrayList<>();\n\n\tpublic static final ResourceLocation RAIN_TEXTURES_GREEN = new ResourceLocation(Weather.MODID, \"textures/environment/rain_green.png\");\n\tpublic static final ResourceLocation RAIN_TEXTURES = new ResourceLocation(\"textures/environment/rain.png\");\n\n\tpublic static boolean FORCE_ON_DEBUG_TESTING = false;\n\n\t/*public static int fadeInTimer = 0;\n\tpublic static int fadeInTimerMax = 400;*/\n\n\tpublic static ParticleBehaviorSandstorm particleBehavior;\n\n\tprivate static FogAdjuster fogAdjuster;\n\n\tpublic static boolean isPlayerOutside = true;\n\tpublic static boolean isPlayerNearTornadoCached = false;\n\n\tpublic static WeatherEventType lastWeatherType = null;\n\n\tpublic static int particleRateLerp = 0;\n\tpublic static int particleRateLerpMax = 100;\n\n\tpublic static TornadoManagerTodoRenameMe playerManagerClient;\n\n\tprivate static Biome lastBiomeIn = null;\n\n\tpublic static float downfallSheetThreshold = 0.32F;\n\n\tpublic SceneEnhancer() {\n\t\tlistPosRandom.clear();\n\t\tlistPosRandom.add(new BlockPos(0, -1, 0));\n\t\tlistPosRandom.add(new BlockPos(1, 0, 0));\n\t\tlistPosRandom.add(new BlockPos(-1, 0, 0));\n\t\tlistPosRandom.add(new BlockPos(0, 0, 1));\n\t\tlistPosRandom.add(new BlockPos(0, 0, -1));\n\n\t\t//TODO: tags bruh\n\t\tCollections.addAll(LEAVES_BLOCKS, Blocks.OAK_LEAVES, Blocks.SPRUCE_LEAVES, Blocks.BIRCH_LEAVES, Blocks.JUNGLE_LEAVES, Blocks.ACACIA_LEAVES, Blocks.CHERRY_LEAVES, Blocks.DARK_OAK_LEAVES, Blocks.MANGROVE_LEAVES, Blocks.AZALEA_LEAVES, Blocks.FLOWERING_AZALEA_LEAVES);\n\t}\n\n\t@Override\n\tpublic void run() {\n\t\twhile (true) {\n\t\t\ttry {\n\t\t\t\ttickClientThreaded();\n\t\t\t\tThread.sleep(400);\n\t\t\t} catch (Throwable throwable) {\n                throwable.printStackTrace();\n            }\n\t\t}\n\t}\n\n\t//run from client side _client_ thread\n\tpublic void tickClient() {\n\t\tif (!Minecraft.getInstance().isPaused()) {\n\t\t\tMinecraft client = Minecraft.getInstance();\n\n\t\t\tif (client.level != null && lastWorldDetected != client.level) {\n\t\t\t\tlastWorldDetected = client.level;\n\t\t\t\treset();\n\t\t\t}\n\n\t\t\tboolean testTornadoTech = false;\n\n\t\t\tif (testTornadoTech) {\n\t\t\t\tif (playerManagerClient == null) {\n\t\t\t\t\tplayerManagerClient = new TornadoManagerTodoRenameMe();\n\t\t\t\t}\n\n\t\t\t\tplayerManagerClient.tick(client.level);\n\t\t\t}\n\n\t\t\tWeatherManagerClient weatherMan = ClientTickHandler.weatherManager;\n\t\t\tif (weatherMan == null) return;\n\t\t\tWindManager windMan = weatherMan.getWindManager();\n\t\t\tif (windMan == null) return;\n\n\t\t\tClientTickHandler.getClientWeather();\n\t\t\tClientWeatherProxy weather = ClientWeatherProxy.get();\n\n\t\t\tWeatherEventType curWeather = getWeatherState();\n\t\t\tif (curWeather != lastWeatherType) {\n\t\t\t\t//System.out.println(\"new weather changed to: \" + curWeather);\n\t\t\t\tparticleRateLerp = 0;\n\t\t\t}\n\t\t\tlastWeatherType = getWeatherState();\n\t\t\tif (particleRateLerp < particleRateLerpMax) {\n\t\t\t\tparticleRateLerp++;\n\t\t\t}\n\n\t\t\tif (weather.hasWeather() || windMan.cachedWindSpeedClient > 0) {\n\t\t\t\ttryParticleSpawning();\n\t\t\t}\n\n\t\t\tFORCE_ON_DEBUG_TESTING = false;\n\n\t\t\tif (weather.hasWeather() || !Weather.isLoveTropicsInstalled()) {\n\t\t\t\tClientWeatherHelper.get().tick();\n\t\t\t\ttickEnvironmentalParticleSpawning();\n\t\t\t\ttrySoundPlaying();\n\t\t\t\ttryWind(client.level);\n\t\t\t}\n\n\t\t\ttickMisc();\n\n\t\t\tgetFogAdjuster().tickGame(weather);\n\n\t\t\tcheckParticleBehavior();\n\t\t\tparticleBehavior.tickUpdateList();\n\n\t\t\tif (client.player != null && client.level != null && client.level.getGameTime() % 10 == 0) {\n\t\t\t\tisPlayerOutside = WeatherUtilEntity.isEntityOutside(client.player);\n\t\t\t}\n\t\t}\n\t}\n\n\t//run from our newly created thread\n\tpublic void tickClientThreaded() {\n\t\tMinecraft client = Minecraft.getInstance();\n\n\t\tif (client != null && client.level != null && client.player != null) {\n\t\t\tprofileSurroundings();\n\t\t\tif (!Weather.isLoveTropicsInstalled() || ClientWeatherProxy.get().hasWeather()) {\n\t\t\t\ttryAmbientSounds();\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic synchronized void trySoundPlaying()\n    {\n\t\ttry {\n\t\t\tMinecraft client = Minecraft.getInstance();\n\n\t\t\ttickRainSound();\n\n\t\t\tif (lastTickAmbient < System.currentTimeMillis()) {\n\t    \t\tlastTickAmbient = System.currentTimeMillis() + 500;\n\n\t        \tLevel worldRef = client.level;\n\t        \tPlayer player = client.player;\n\n\t        \tint size = 32;\n\t            int hsize = size / 2;\n\t            BlockPos cur = player.blockPosition();\n\n\t            Random rand = new Random();\n\n\t            //trim out distant sound locations, also tick last time played\n\t            for (int i = 0; i < soundLocations.size(); i++) {\n\n\t            \tChunkCoordinatesBlock cCor = soundLocations.get(i);\n\n\t            \tif (Math.sqrt(cCor.distSqr(cur)) > size) {\n\t            \t\tsoundLocations.remove(i--);\n\t            \t\tsoundTimeLocations.remove(cCor);\n\t            \t\t//System.out.println(\"trim out soundlocation\");\n\t            \t} else {\n\n\t                    Block block = getBlock(worldRef, cCor.getX(), cCor.getY(), cCor.getZ());//Block.blocksList[id];\n\n\t\t\t\t\t\t//if (block == null || (block.defaultBlockState().getMaterial() != Material.WATER && block.defaultBlockState().getMaterial() != Material.LEAVES)) {\n\t\t\t\t\t\tif (block == null || (block.defaultMapColor() != MapColor.WATER && block.defaultMapColor() != MapColor.PLANT)) {\n\t                    \tsoundLocations.remove(i);\n\t                \t\tsoundTimeLocations.remove(cCor);\n\t                    } else {\n\n\t\t            \t\tlong lastPlayTime = 0;\n\n\t\t\t\t\t\t\tfloat soundMuffle = 0.6F;\n\t\t\t\t\t\t\tif (getWeatherState() == WeatherEventType.SANDSTORM || getWeatherState() == WeatherEventType.SNOWSTORM) {\n\t\t\t\t\t\t\t\tsoundMuffle = 0.15F;\n\t\t\t\t\t\t\t}\n\n\t\t            \t\tif (soundTimeLocations.containsKey(cCor)) {\n\t\t            \t\t\tlastPlayTime = soundTimeLocations.get(cCor);\n\t\t            \t\t}\n\n\t\t\t\t\t\t\tfloat maxLeavesVolume = 1;\n\n\t\t\t\t\t\t\tsoundMuffle *= ConfigSound.leavesVolume;\n\n\t\t            \t\t//System.out.println(Math.sqrt(cCor.getDistanceSquared(curX, curY, curZ)));\n\t\t\t\t\t\t\tif (lastPlayTime < System.currentTimeMillis()) {\n\t\t\t\t\t\t\t\tif (LEAVES_BLOCKS.contains(cCor.block)) {\n\t\t\t\t\t\t\t\t\tfloat windSpeed = WindReader.getWindSpeed(client.level, cur);\n\t\t\t\t\t\t\t\t\tif (windSpeed > 0.2F) {\n\t\t\t\t\t\t\t\t\t\tsoundTimeLocations.put(cCor, System.currentTimeMillis() + 12000 + rand.nextInt(50));\n\t\t\t\t\t\t\t\t\t\t//client.getSoundHandler().playSound(Weather.modID + \":wind_calmfade\", cCor.getPosX(), cCor.getPosY(), cCor.getPosZ(), (float)(windSpeed * 4F * ConfigMisc.volWindTreesScale), 0.70F + (rand.nextFloat() * 0.1F));\n\t\t\t\t\t\t\t\t\t\t//client.world.playSound(cCor.getPosX(), cCor.getPosY(), cCor.getPosZ(), Weather.modID + \":env.wind_calmfade\", (float)(windSpeed * 4F * ConfigMisc.volWindTreesScale), 0.70F + (rand.nextFloat() * 0.1F), false);\n\t\t\t\t\t\t\t\t\t\tclient.level.playLocalSound(cCor, SoundRegistry.get(\"env.wind_calmfade\"), SoundSource.AMBIENT, (float)Math.min(maxLeavesVolume, (windSpeed * 2F) * soundMuffle), 0.70F + (rand.nextFloat() * 0.1F), false);\n\t\t\t\t\t\t\t\t\t\t//System.out.println(\"play leaves sound at: \" + cCor.getPosX() + \" - \" + cCor.getPosY() + \" - \" + cCor.getPosZ() + \" - windSpeed: \" + windSpeed);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\twindSpeed = WindReader.getWindSpeed(client.level, cur);\n\t\t\t\t\t\t\t\t\t\t//if (windSpeed > 0.3F) {\n\t\t\t\t\t\t\t\t\t\tif (CoroUtilMisc.random.nextInt(15) == 0) {\n\t\t\t\t\t\t\t\t\t\t\tsoundTimeLocations.put(cCor, System.currentTimeMillis() + 12000 + rand.nextInt(50));\n\t\t\t\t\t\t\t\t\t\t\t//client.getSoundHandler().playSound(Weather.modID + \":wind_calmfade\", cCor.getPosX(), cCor.getPosY(), cCor.getPosZ(), (float)(windSpeed * 2F * ConfigMisc.volWindTreesScale), 0.70F + (rand.nextFloat() * 0.1F));\n\t\t\t\t\t\t\t\t\t\t\t//client.world.playSound(cCor.getPosX(), cCor.getPosY(), cCor.getPosZ(), Weather.modID + \":env.wind_calmfade\", (float)(windSpeed * 2F * ConfigMisc.volWindTreesScale), 0.70F + (rand.nextFloat() * 0.1F), false);\n\t\t\t\t\t\t\t\t\t\t\tclient.level.playLocalSound(cCor, SoundRegistry.get(\"env.wind_calmfade\"), SoundSource.AMBIENT, Math.min(maxLeavesVolume, windSpeed * soundMuffle), 0.70F + (rand.nextFloat() * 0.1F), false);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t//System.out.println(\"play leaves sound at: \" + cCor.getPosX() + \" - \" + cCor.getPosY() + \" - \" + cCor.getPosZ() + \" - windSpeed: \" + windSpeed);\n\t\t\t\t\t\t\t\t\t\t//}\n\t\t\t\t\t\t\t\t\t}\n\n\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t            \t}\n\t            }\n\t\t\t}\n\t\t} catch (Exception ex) {\n    \t\tSystem.out.println(\"Weather2: Error handling sound play queue: \");\n    \t\tex.printStackTrace();\n    \t}\n    }\n\n\t/**\n\t * This method is meant to keep playing rain sound past the point where vanilla cuts off its own rain sounds\n\t * edit: used to, now we just fully override\n\t * edit2: it also now prevents rain sounds when its actually snowing\n\t * Modified copy of LevelRenderer.tickRain\n\t * @param p_109694_\n\t */\n\tprivate int rainSoundTime;\n\tpublic void tickRainSound() {\n\t\tMinecraft minecraft = Minecraft.getInstance();\n\t\tPlayer player = Minecraft.getInstance().player;\n\n\t\tfloat precipitationStrength = ClientWeatherHelper.get().getPrecipitationStrength(player);\n\t\tif (!(precipitationStrength <= 0.0F) && !shouldSnowHere(player.level(), player.level().getBiome(player.blockPosition()).get(), player.blockPosition())) {\n\t\t\tRandom random = new Random(player.level().getGameTime() * 312987231L);\n\t\t\tLevelReader levelreader = minecraft.level;\n\t\t\tBlockPos blockpos = player.blockPosition();\n\t\t\tBlockPos blockpos1 = null;\n\t\t\tint i = (int)(100.0F * precipitationStrength * precipitationStrength) / (minecraft.options.particles.get() == ParticleStatus.DECREASED ? 2 : 1);\n\n\t\t\tfor(int j = 0; j < i; ++j) {\n\t\t\t\tint k = random.nextInt(21) - 10;\n\t\t\t\tint l = random.nextInt(21) - 10;\n\t\t\t\tBlockPos blockpos2 = levelreader.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, blockpos.offset(k, 0, l));\n\t\t\t\tBiome biome = levelreader.getBiome(blockpos2).get();\n\t\t\t\tif (blockpos2.getY() > levelreader.getMinBuildHeight() && blockpos2.getY() <= blockpos.getY() + 10 && blockpos2.getY() >= blockpos.getY() - 10 && biome.getPrecipitationAt(blockpos2) == Biome.Precipitation.RAIN && biome.warmEnoughToRain(blockpos2)) {\n\t\t\t\t\tblockpos1 = blockpos2.below();\n\t\t\t\t\tif (minecraft.options.particles.get() == ParticleStatus.MINIMAL) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\tdouble d0 = random.nextDouble();\n\t\t\t\t\tdouble d1 = random.nextDouble();\n\t\t\t\t\tBlockState blockstate = levelreader.getBlockState(blockpos1);\n\t\t\t\t\tFluidState fluidstate = levelreader.getFluidState(blockpos1);\n\t\t\t\t\tVoxelShape voxelshape = blockstate.getCollisionShape(levelreader, blockpos1);\n\t\t\t\t\tdouble d2 = voxelshape.max(Direction.Axis.Y, d0, d1);\n\t\t\t\t\tdouble d3 = (double)fluidstate.getHeight(levelreader, blockpos1);\n\t\t\t\t\tdouble d4 = Math.max(d2, d3);\n\t\t\t\t\tParticleOptions particleoptions = !fluidstate.is(FluidTags.LAVA) && !blockstate.is(Blocks.MAGMA_BLOCK) && !CampfireBlock.isLitCampfire(blockstate) ? ParticleTypes.RAIN : ParticleTypes.SMOKE;\n\t\t\t\t\tminecraft.level.addParticle(particleoptions, (double)blockpos1.getX() + d0, (double)blockpos1.getY() + d4, (double)blockpos1.getZ() + d1, 0.0D, 0.0D, 0.0D);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (blockpos1 != null && random.nextInt(3) < this.rainSoundTime++) {\n\t\t\t\tthis.rainSoundTime = 0;\n\t\t\t\tif (blockpos1.getY() > blockpos.getY() + 1 && levelreader.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, blockpos).getY() > Mth.floor((float)blockpos.getY())) {\n\t\t\t\t\tminecraft.level.playLocalSound(blockpos1, SoundEvents.WEATHER_RAIN_ABOVE, SoundSource.WEATHER, 0.1F + (0.4F * precipitationStrength), 0.5F, false);\n\t\t\t\t} else {\n\t\t\t\t\tminecraft.level.playLocalSound(blockpos1, SoundEvents.WEATHER_RAIN, SoundSource.WEATHER, 0.2F + (0.6F * precipitationStrength), 1.0F, false);\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\t}\n\n\t//Threaded function\n    @OnlyIn(Dist.CLIENT)\n    public static void tryAmbientSounds()\n    {\n    \tMinecraft client = Minecraft.getInstance();\n\n    \tLevel worldRef = client.level;\n    \tPlayer player = client.player;\n\n\t\t//this is currently only used for leaves and its default off now\n    \tif (lastTickAmbientThreaded < System.currentTimeMillis() && ConfigSound.leavesVolume > 0) {\n    \t\tlastTickAmbientThreaded = System.currentTimeMillis() + 500;\n\n    \t\tint size = 32;\n            int hsize = size / 2;\n            int curX = (int)player.getX();\n            int curY = (int)player.getY();\n            int curZ = (int)player.getZ();\n\n            //soundLocations.clear();\n\n\n\n    \t\tfor (int xx = curX - hsize; xx < curX + hsize; xx++)\n            {\n                for (int yy = curY - (hsize / 2); yy < curY + hsize; yy++)\n                {\n                    for (int zz = curZ - hsize; zz < curZ + hsize; zz++)\n                    {\n                        Block block = getBlock(worldRef, xx, yy, zz);\n\n                        if (block != null) {\n                        \tif (((block.defaultMapColor() == MapColor.PLANT))) {\n                            \tboolean proxFail = false;\n\t\t\t\t\t\t\t\tfor (ChunkCoordinatesBlock soundLocation : soundLocations) {\n\t\t\t\t\t\t\t\t\tif (Math.sqrt(soundLocation.distSqr(new Vec3i(xx, yy, zz))) < 15) {\n\t\t\t\t\t\t\t\t\t\tproxFail = true;\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n                \t\t\t\tif (!proxFail) {\n                \t\t\t\t\tsoundLocations.add(new ChunkCoordinatesBlock(xx, yy, zz, block));\n                \t\t\t\t}\n                            }\n                        }\n                    }\n                }\n            }\n    \t}\n    }\n\n\tpublic void reset() {\n\t\tif (WeatherUtilParticle.fxLayers == null) {\n\t\t\tWeatherUtilParticle.getFXLayers();\n\t\t}\n\n\t}\n\n\tprivate static void tickHeatwave(ClientWeatherProxy weather) {\n\t\tMinecraft client = Minecraft.getInstance();\n\n\t\t/*if (weather.isHeatwave() || true) {\n\t\t\theatwaveIntensityTarget = 0.7F;\n\t\t} else {\n\t\t\theatwaveIntensityTarget = 0.0F;\n\t\t}\n\n\t\theatwaveIntensity = CoroUtilMisc.adjVal(heatwaveIntensity, heatwaveIntensityTarget, 0.01F);*/\n\n\t\t/*if (fogAdjuster.getActiveIntensity() > 0) {\n\t\t\tif (fogAdjuster.getActiveIntensity() < 0.33F) {\n\t\t\t\ttryPlayPlayerLockedSound(WeatherUtilSound.snd_sandstorm_low, 5, client.player, 1F);\n\t\t\t} else if (fogAdjuster.getActiveIntensity() < 0.66F) {\n\t\t\t\ttryPlayPlayerLockedSound(WeatherUtilSound.snd_sandstorm_med, 4, client.player, 1F);\n\t\t\t} else {\n\t\t\t\ttryPlayPlayerLockedSound(WeatherUtilSound.snd_sandstorm_high, 3, client.player, 1F);\n\t\t\t}\n\t\t}*/\n\t}\n\n\tpublic static boolean tryPlayPlayerLockedSound(String[] sound, int arrIndex, Entity source, float vol)\n\t{\n\t\tRandom rand = new Random();\n\n\t\tif (WeatherUtilSound.soundTimer[arrIndex] <= System.currentTimeMillis())\n\t\t{\n\t\t\tString soundStr = sound[WeatherUtilSound.snd_rand[arrIndex]];\n\n\t\t\tWeatherUtilSound.playPlayerLockedSound(source.position(), new StringBuilder().append(\"streaming.\" + soundStr).toString(), vol, 1.0F);\n\n\t\t\tint length = WeatherUtilSound.soundToLength.get(soundStr);\n\t\t\t//-500L, for blending\n\t\t\tWeatherUtilSound.soundTimer[arrIndex] = System.currentTimeMillis() + length - 500L;\n\t\t\tWeatherUtilSound.snd_rand[arrIndex] = rand.nextInt(sound.length);\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tpublic void tickMisc() {\n\n\t\t/*ClientWeatherProxy weather = ClientWeatherProxy.get();\n\t\tif (weather.getPrecipitationType() == RainType.ACID) {\n\t\t\tif (LevelRenderer.RAIN_LOCATION != RAIN_TEXTURES_GREEN) {\n\t\t\t\tLevelRenderer.RAIN_LOCATION = RAIN_TEXTURES_GREEN;\n\t\t\t}\n\t\t} else {\n\t\t\tif (LevelRenderer.RAIN_LOCATION != RAIN_TEXTURES) {\n\t\t\t\tLevelRenderer.RAIN_LOCATION = RAIN_TEXTURES;\n\t\t\t}\n\t\t}*/\n\n\t}\n\n\tpublic void tickEnvironmentalParticleSpawning() {\n\n\t\tFORCE_ON_DEBUG_TESTING = false;\n\n\t\tPlayer entP = Minecraft.getInstance().player;\n\t\tWeatherManagerClient weatherMan = ClientTickHandler.weatherManager;\n\t\tif (weatherMan == null) return;\n\t\tWindManager windMan = weatherMan.getWindManager();\n\t\tif (windMan == null) return;\n\t\tif (particleBehavior == null) return;\n\n\t\tClientWeatherProxy weather = ClientWeatherProxy.get();\n\n\t\t//returns 0 to 1 now\n\t\tfloat curPrecipVal = weather.getRainAmount();\n\t\t//System.out.println(\"curPrecipVal: \" + curPrecipVal);\n\n\t\tif (FORCE_ON_DEBUG_TESTING) {\n\t\t\tcurPrecipVal = 0.3F;\n\t\t\t//curPrecipVal = (float)((entP.getLevel().getGameTime() / 10) % 100) / 100F;\n\t\t\t//curPrecipVal = 0F;\n\t\t}\n\n\t\t//workaround until i clean up logic that is flickering the state between heavy rain and null\n\t\tif (Weather.isLoveTropicsInstalled()) {\n\t\t\tif (curPrecipVal < 0.0001F) {\n\t\t\t\tcurPrecipVal = 0;\n\t\t\t}\n\t\t}\n\t\tfloat maxPrecip = 1F;\n\n\n\t\tBlockPos posPlayer = CoroUtilBlock.blockPos(entP.getX(), entP.getY(), entP.getZ());\n\t\tBiome biome = entP.level().getBiome(posPlayer).get();\n\t\tlastBiomeIn = biome;\n\n\t\tLevel world = entP.level();\n\t\tRandom rand = CoroUtilMisc.random();\n\n\t\tfloat windSpeed = windMan.getWindSpeed(posPlayer);\n\n\t\t//funnel.tickGame();\n\n\n\t\t//now absolute it for ez math\n\t\t//off cause ltminigames can give 1 tick under 0 worth of rain value change\n\t\t//curPrecipVal = Math.min(maxPrecip, Math.abs(curPrecipVal));\n\t\tif (Weather.isLoveTropicsInstalled()) {\n\t\t\tcurPrecipVal = Math.min(maxPrecip, Math.max(0, curPrecipVal));\n\t\t} else {\n\t\t\tcurPrecipVal = Math.min(maxPrecip, Math.abs(curPrecipVal));\n\t\t}\n\n\t\tfloat particleSettingsAmplifier = 1F;\n\t\tif (Minecraft.getInstance().options.particles.get() == ParticleStatus.DECREASED) {\n\t\t\tparticleSettingsAmplifier = 0.5F;\n\t\t} else if (Minecraft.getInstance().options.particles.get() == ParticleStatus.MINIMAL) {\n\t\t\tparticleSettingsAmplifier = 0.2F;\n\t\t}\n\n\t\tparticleSettingsAmplifier *= ConfigParticle.Particle_effect_rate;\n\n\t\t/**\n\t\t * we set the spawn amount for actual particles up to 0.5 intensity\n\t\t * then after 0.5, we up the extra render amount\n\t\t * ensures super low precip isnt patchy, and stops increasing rate of real particles at higher precip\n\t\t */\n\t\tfloat curPrecipValMaxNoExtraRender = 0.3F;\n\t\tint extraRenderCountMax = 10;\n\t\tint extraRenderCount = 0;\n\n\t\tif (curPrecipVal > curPrecipValMaxNoExtraRender) {\n\t\t\tfloat precipValForExtraRenders = curPrecipVal - curPrecipValMaxNoExtraRender;\n\t\t\tfloat precipValExtraRenderRange = 1F - curPrecipValMaxNoExtraRender;\n\t\t\textraRenderCount = Math.min((int) (extraRenderCountMax * (precipValForExtraRenders / precipValExtraRenderRange)), extraRenderCountMax);\n\t\t}\n\n\t\t//cap at amount before extra rendering starts\n\t\tfloat curPrecipCappedForSpawnNeed = Math.min(curPrecipVal, curPrecipValMaxNoExtraRender);\n\n\t\tint spawnCount;\n\t\tdouble spawnNeedBase = curPrecipCappedForSpawnNeed * ConfigParticle.Precipitation_Particle_effect_rate * particleSettingsAmplifier;\n\t\tint safetyCutout = 100;\n\n\t\tif (world.getGameTime() % 20 == 0) {\n\t\t\tStormObject stormObject = ClientTickHandler.weatherManager.getClosestStorm(entP.position(), ConfigMisc.sirenActivateDistance, StormObject.STATE_FORMING);\n\t\t\tif (stormObject != null && entP.position().distanceTo(stormObject.pos) < stormObject.getSize()) {\n\t\t\t\tisPlayerNearTornadoCached = true;\n\t\t\t} else {\n\t\t\t\tisPlayerNearTornadoCached = false;\n\t\t\t}\n\t\t}\n\n\t\t//adjusted to this way to make it work with serene seasons\n\t\tboolean canPrecip = weather.getPrecipitationType(biome) == PrecipitationType.NORMAL || weather.getPrecipitationType(biome) == PrecipitationType.SNOW;\n\n\t\tboolean isRain = canPrecip && shouldRainHere(world, biome, posPlayer);\n\t\tif (Weather.isLoveTropicsInstalled() && ClientWeatherProxy.get().getPrecipitationType(biome) == PrecipitationType.ACID) isRain = true;\n\t\tboolean isHail = weather.isHail();\n\t\tif (Weather.isLoveTropicsInstalled() && ClientWeatherProxy.get().getPrecipitationType(biome) == PrecipitationType.HAIL) isHail = true;\n\t\tboolean isSnowstorm = weather.isSnowstorm();\n\t\tboolean isSandstorm = weather.isSandstorm();\n\t\tboolean isRain_WaterParticle = true;\n\t\tboolean isRain_GroundSplash = true;\n\t\tboolean isRain_DownfallSheet = true;\n\t\tif (isHail) {\n\t\t\tisRain_WaterParticle = false;\n\t\t\tisRain_GroundSplash = false;\n\t\t\tisRain_DownfallSheet = false;\n\t\t}\n\n\t\t//wind should be so rediculous that sheets of rain isnt gonna be happening in your face\n\t\tif (isPlayerNearTornadoCached) {\n\t\t\tisRain_DownfallSheet = false;\n\t\t}\n\n\t\tboolean farSpawn = Minecraft.getInstance().player.isSpectator() || !isPlayerOutside;\n\n\t\tfloat particleStormIntensity = 0;\n\t\tif (isSandstorm) {\n\t\t\tif (Weather.isLoveTropicsInstalled()) {\n\t\t\t\tif (getWeatherState() == WeatherEventType.SANDSTORM) {\n\t\t\t\t\tparticleStormIntensity = 1;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tWeatherObjectParticleStorm storm = ClientTickHandler.weatherManager.getClosestParticleStormByIntensity(entP.position(), WeatherObjectParticleStorm.StormType.SANDSTORM);\n\t\t\t\tif (storm != null) {\n\t\t\t\t\tparticleStormIntensity = storm.getIntensity();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (isSnowstorm) {\n\t\t\tif (Weather.isLoveTropicsInstalled()) {\n\t\t\t\tif (getWeatherState() == WeatherEventType.SNOWSTORM) {\n\t\t\t\t\tparticleStormIntensity = 1;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tWeatherObjectParticleStorm storm = ClientTickHandler.weatherManager.getClosestParticleStormByIntensity(entP.position(), WeatherObjectParticleStorm.StormType.SNOWSTORM);\n\t\t\t\tif (storm != null) {\n\t\t\t\t\tparticleStormIntensity = storm.getIntensity();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (Weather.isLoveTropicsInstalled() && farSpawn) {\n\t\t\tparticleStormIntensity *= 0.5F;\n\t\t}\n\n\t\t//let snowstorm buildup a bit before turning off regular snow\n\t\tboolean isSnow = canPrecip && (!weather.isSnowstorm() || particleStormIntensity < 0.1) && !isHail && shouldSnowHere(world, biome, posPlayer);\n\n\t\t//System.out.println(\"particleStormIntensity: \" + particleStormIntensity);\n\n\t\t//dev testing\n\t\tboolean devTest = false;\n\t\tif (devTest) {\n\t\t\t/*isRain = false;\n\t\t\tisSnow = false;\n\t\t\tisSnowstorm = false;\n\t\t\tisSandstorm = true;\n\t\t\tparticleStormIntensity = 1F;\n\t\t\t*/\n\n\t\t\t//isRain = false;\n\t\t\t//isRain_DownfallSheet = false;\n\n\n\t\t\tif (entP.level().getGameTime() % 40 == 0) {\n\t\t\t\tif (ConfigCoroUtil.useLoggingDebug) {\n\t\t\t\t\tSystem.out.printf(\"curPrecipVal: %.2f\", curPrecipVal);\n\t\t\t\t\tSystem.out.println(\"\");\n\t\t\t\t}\n\t\t\t\tCULog.dbg(\"spawnNeedBase: \" + spawnNeedBase);\n\t\t\t\tCULog.dbg(\"extraRenderCount: \" + extraRenderCount);\n\t\t\t\tCULog.dbg(\"particleStormIntensity: \" + particleStormIntensity);\n\t\t\t}\n\t\t}\n\n\t\t//check rules same way vanilla texture precip does\n\t\tif (biome != null && (biome.getPrecipitationAt(posPlayer) != Biome.Precipitation.NONE)) {\n\t\t\tif (curPrecipVal > 0) {\n\t\t\t\tif (isRain) {\n\t\t\t\t\tspawnCount = 0;\n\t\t\t\t\tint spawnAreaSize = 30;\n\n\t\t\t\t\tint spawnNeed = (int) (spawnNeedBase * 300);\n\t\t\t\t\tif (entP.level().getGameTime() % 40 == 0) {\n\t\t\t\t\t\t//CULog.dbg(\"rain spawnNeed: \" + spawnNeed);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (isRain_WaterParticle && spawnNeed > 0) {\n\n\t\t\t\t\t\tfor (int i = 0; i < safetyCutout; i++) {\n\t\t\t\t\t\t\tBlockPos pos = CoroUtilBlock.blockPos(\n\t\t\t\t\t\t\t\t\tentP.getX() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2),\n\t\t\t\t\t\t\t\t\tentP.getY() - 5 + rand.nextInt(25),\n\t\t\t\t\t\t\t\t\tentP.getZ() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2));\n\n\t\t\t\t\t\t\tif (canPrecipitateAt(world, pos)) {\n\t\t\t\t\t\t\t\tParticleTexExtraRender rain = new ParticleTexExtraRender((ClientLevel) entP.level(),\n\t\t\t\t\t\t\t\t\t\tpos.getX(),\n\t\t\t\t\t\t\t\t\t\tpos.getY(),\n\t\t\t\t\t\t\t\t\t\tpos.getZ(),\n\t\t\t\t\t\t\t\t\t\t0D, 0D, 0D, ParticleRegistry.rain_white);\n\t\t\t\t\t\t\t\tparticleBehavior.initParticleRain(rain, extraRenderCount);\n\n\t\t\t\t\t\t\t\tspawnCount++;\n\t\t\t\t\t\t\t\tif (spawnCount >= spawnNeed) {\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t//TODO: make ground splash and downfall use spawnNeed var style design\n\n\t\t\t\t\tspawnAreaSize = 40;\n\t\t\t\t\tif (isRain_GroundSplash && curPrecipVal > 0.15) {\n\t\t\t\t\t\tfor (int i = 0; i < 30F * curPrecipVal * PRECIPITATION_PARTICLE_EFFECT_RATE * 8F * particleSettingsAmplifier; i++) {\n\t\t\t\t\t\t\tBlockPos pos = CoroUtilBlock.blockPos(\n\t\t\t\t\t\t\t\t\tentP.getX() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2),\n\t\t\t\t\t\t\t\t\tentP.getY() - 5 + rand.nextInt(15),\n\t\t\t\t\t\t\t\t\tentP.getZ() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2));\n\n\n\t\t\t\t\t\t\t//get the block on the topmost ground\n\t\t\t\t\t\t\tpos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos).below();\n\n\t\t\t\t\t\t\tBlockState state = world.getBlockState(pos);\n\t\t\t\t\t\t\tdouble maxY = 0;\n\t\t\t\t\t\t\tdouble minY = 0;\n\t\t\t\t\t\t\tVoxelShape shape = state.getShape(world, pos);\n\t\t\t\t\t\t\tif (!shape.isEmpty()) {\n\t\t\t\t\t\t\t\tminY = shape.bounds().minY;\n\t\t\t\t\t\t\t\tmaxY = shape.bounds().maxY;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (pos.distSqr(entP.blockPosition()) > (spawnAreaSize / 2) * (spawnAreaSize / 2))\n\t\t\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t\t\t//block above topmost ground\n\t\t\t\t\t\t\tif (canPrecipitateAt(world, pos.above())) {\n\n\t\t\t\t\t\t\t\t//fix for splash spawning invisibly 1 block underwater\n\t\t\t\t\t\t\t\tif (world.getBlockState(pos).getBlock().defaultMapColor() == MapColor.WATER) {\n\t\t\t\t\t\t\t\t\tpos = pos.offset(0,1,0);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tParticleTexFX rain = new ParticleTexFX((ClientLevel) entP.level(),\n\t\t\t\t\t\t\t\t\t\tpos.getX() + rand.nextFloat(),\n\t\t\t\t\t\t\t\t\t\tpos.getY() + 0.01D + maxY,\n\t\t\t\t\t\t\t\t\t\tpos.getZ() + rand.nextFloat(),\n\t\t\t\t\t\t\t\t\t\t0D, 0D, 0D, ParticleRegistry.groundSplash);\n\t\t\t\t\t\t\t\tparticleBehavior.initParticleGroundSplash(rain);\n\n\t\t\t\t\t\t\t\train.spawnAsWeatherEffect();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tspawnAreaSize = 30;\n\t\t\t\t\t//downfall - at just above 0.3 cause rainstorms lock at 0.3 but flicker a bit above and below\n\t\t\t\t\tif (isRain_DownfallSheet && curPrecipVal > downfallSheetThreshold) {\n\n\t\t\t\t\t\tint scanAheadRange = 0;\n\t\t\t\t\t\t//quick is outside check, prevent them spawning right near ground\n\t\t\t\t\t\t//and especially right above the roof so they have enough space to fade out\n\t\t\t\t\t\t//results in not seeing them through roofs\n\t\t\t\t\t\tif (WeatherUtilDim.canBlockSeeSky(world, entP.blockPosition())) {\n\t\t\t\t\t\t\tscanAheadRange = 3;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tscanAheadRange = 10;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tdouble closeDistCutoff = 10D;\n\n\t\t\t\t\t\tfor (int i = 0; i < 2F * curPrecipVal * PRECIPITATION_PARTICLE_EFFECT_RATE * particleSettingsAmplifier * 0.5F; i++) {\n\t\t\t\t\t\t\tBlockPos pos = CoroUtilBlock.blockPos(\n\t\t\t\t\t\t\t\t\tentP.getX() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2),\n\t\t\t\t\t\t\t\t\tentP.getY() + 5 + rand.nextInt(15),\n\t\t\t\t\t\t\t\t\tentP.getZ() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2));\n\n\t\t\t\t\t\t\tif (WeatherUtilEntity.getDistanceSqEntToPos(entP, pos) < closeDistCutoff * closeDistCutoff) continue;\n\n\t\t\t\t\t\t\tif (canPrecipitateAt(world, pos.above(-scanAheadRange))/*world.isRainingAt(pos)*/) {\n\t\t\t\t\t\t\t\tParticleTexFX rain = new ParticleTexFX((ClientLevel) entP.level(),\n\t\t\t\t\t\t\t\t\t\tpos.getX() + rand.nextFloat(),\n\t\t\t\t\t\t\t\t\t\tpos.getY() - 1 + 0.01D,\n\t\t\t\t\t\t\t\t\t\tpos.getZ() + rand.nextFloat(),\n\t\t\t\t\t\t\t\t\t\t0D, 0D, 0D, ParticleRegistry.downfall3);\n\t\t\t\t\t\t\t\tparticleBehavior.initParticleRainDownfall(rain);\n\n\t\t\t\t\t\t\t\train.spawnAsWeatherEffect();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t//snow\n\t\t\t\t} else if (isSnow) {\n\t\t\t\t\tspawnCount = 0;\n\t\t\t\t\tint spawnAreaSize = 50;\n\t\t\t\t\tint spawnNeed = (int) (spawnNeedBase * 80);\n\t\t\t\t\tif (entP.level().getGameTime() % 40 == 0) {\n\t\t\t\t\t\t//CULog.dbg(\"snow spawnNeed: \" + spawnNeed);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (spawnNeed > 0) {\n\t\t\t\t\t\tfor (int i = 0; i < safetyCutout; i++) {\n\t\t\t\t\t\t\tBlockPos pos = CoroUtilBlock.blockPos(\n\t\t\t\t\t\t\t\t\tentP.getX() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2),\n\t\t\t\t\t\t\t\t\tentP.getY() - 5 + rand.nextInt(25),\n\t\t\t\t\t\t\t\t\tentP.getZ() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2));\n\n\t\t\t\t\t\t\tif (canPrecipitateAt(world, pos)) {\n\t\t\t\t\t\t\t\tParticleTexExtraRender snow = new ParticleTexExtraRender((ClientLevel) entP.level(), pos.getX(), pos.getY(), pos.getZ(),\n\t\t\t\t\t\t\t\t\t\t0D, 0D, 0D, ParticleRegistry.snow2);\n\n\t\t\t\t\t\t\t\tparticleBehavior.initParticleSnow(snow, extraRenderCount, windSpeed);\n\t\t\t\t\t\t\t\tsnow.spawnAsWeatherEffect();\n\n\t\t\t\t\t\t\t\tspawnCount++;\n\t\t\t\t\t\t\t\tif (spawnCount >= spawnNeed) {\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t}\n\n\t\t\t\tint spawnAreaSize = 30;\n\t\t\t\tspawnCount = 0;\n\t\t\t\tint spawnNeed = (int) (spawnNeedBase * 80);\n\n\t\t\t\tif ((getWeatherState() == WeatherEventType.HAIL || isHail) && spawnNeed > 0) {\n\t\t\t\t\tfor (int i = 0; i < safetyCutout / 4; i++) {\n\t\t\t\t\t\tBlockPos pos = CoroUtilBlock.blockPos(\n\t\t\t\t\t\t\t\tentP.getX() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2),\n\t\t\t\t\t\t\t\tentP.getY() - 5 + rand.nextInt(25),\n\t\t\t\t\t\t\t\tentP.getZ() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2));\n\n\t\t\t\t\t\tif (canPrecipitateAt(world, pos)) {\n\t\t\t\t\t\t\tParticleHail hail = new ParticleHail((ClientLevel) entP.level(),\n\t\t\t\t\t\t\t\t\tpos.getX(),\n\t\t\t\t\t\t\t\t\tpos.getY(),\n\t\t\t\t\t\t\t\t\tpos.getZ(),\n\t\t\t\t\t\t\t\t\t0D, 0D, 0D, ParticleRegistry.hail);\n\n\t\t\t\t\t\t\t/*ParticleCube hail = new ParticleCube((ClientLevel) entP.level,\n\t\t\t\t\t\t\t\t\tpos.getX(),\n\t\t\t\t\t\t\t\t\tpos.getY(),\n\t\t\t\t\t\t\t\t\tpos.getZ(),\n\t\t\t\t\t\t\t\t\t0D, 0D, 0D, ParticleRegistry.hail);*/\n\t\t\t\t\t\t\tparticleBehavior.initParticleHail(hail);\n\n\t\t\t\t\t\t\thail.spawnAsWeatherEffect();\n\n\t\t\t\t\t\t\tspawnCount++;\n\t\t\t\t\t\t\tif (spawnCount >= spawnNeed) {\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\n\n\t\t\tboolean groundFire = ClientWeatherProxy.get().isHeatwave();\n\t\t\tint spawnAreaSize = 40;\n\n\t\t\tif (groundFire) {\n\t\t\t\tfor (int i = 0; i < 10F * PRECIPITATION_PARTICLE_EFFECT_RATE * 1F * particleSettingsAmplifier; i++) {\n\t\t\t\t\tBlockPos pos = CoroUtilBlock.blockPos(\n\t\t\t\t\t\t\tentP.getX() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2),\n\t\t\t\t\t\t\tentP.getY() - 5 + rand.nextInt(15),\n\t\t\t\t\t\t\tentP.getZ() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2));\n\n\n\t\t\t\t\t//get the block on the topmost ground\n\t\t\t\t\tpos = world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, pos).below();\n\n\t\t\t\t\tBlockState state = world.getBlockState(pos);\n\t\t\t\t\tdouble maxY = 0;\n\t\t\t\t\tdouble minY = 0;\n\t\t\t\t\tVoxelShape shape = state.getShape(world, pos);\n\t\t\t\t\tif (!shape.isEmpty()) {\n\t\t\t\t\t\tminY = shape.bounds().minY;\n\t\t\t\t\t\tmaxY = shape.bounds().maxY;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (pos.distSqr(entP.blockPosition()) > (spawnAreaSize / 2) * (spawnAreaSize / 2))\n\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t//block above topmost ground\n\t\t\t\t\tif (canPrecipitateAt(world, pos.above()) && world.getBlockState(pos).getBlock().defaultMapColor() != MapColor.WATER) {\n\n\t\t\t\t\t\tworld.addParticle(ParticleTypes.SMOKE, pos.getX() + rand.nextFloat(), pos.getY() + 0.01D + maxY, pos.getZ() + rand.nextFloat(), 0.0D, 0.0D, 0.0D);\n\t\t\t\t\t\tworld.addParticle(ParticleTypes.FLAME, pos.getX() + rand.nextFloat(), pos.getY() + 0.01D + maxY, pos.getZ() + rand.nextFloat(), 0.0D, 0.0D, 0.0D);\n\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t//extra dust in the air\n\t\t{\n\t\t\tint spawnAreaSize = 25;\n\t\t\tint spawnNeed = (int) (particleSettingsAmplifier * 5 * windSpeed);\n\t\t\tspawnCount = 0;\n\n\t\t\tfor (int i = 0; i < safetyCutout; i++) {\n\t\t\t\tif (spawnCount >= spawnNeed) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif (windSpeed >= 0.1F/* && rand.nextInt(1) == 0*/) {\n\t\t\t\t\tBlockPos pos = CoroUtilBlock.blockPos(\n\t\t\t\t\t\t\tentP.getX() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2),\n\t\t\t\t\t\t\tentP.getY() - 5 + rand.nextInt(25),\n\t\t\t\t\t\t\tentP.getZ() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2));\n\n\t\t\t\t\tif (canPrecipitateAt(world, pos)) {\n\t\t\t\t\t\tParticleTexExtraRender dust = new ParticleTexExtraRender((ClientLevel) entP.level(),\n\t\t\t\t\t\t\t\tpos.getX(),\n\t\t\t\t\t\t\t\tpos.getY(),\n\t\t\t\t\t\t\t\tpos.getZ(),\n\t\t\t\t\t\t\t\t0D, 0D, 0D, ParticleRegistry.squareGrey);\n\t\t\t\t\t\tparticleBehavior.initParticleDustAir(dust);\n\n\t\t\t\t\t\tdust.spawnAsWeatherEffect();\n\n\t\t\t\t\t\tspawnCount++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (isSnowstorm) {\n\t\t\tspawnCount = 0;\n\t\t\t//less for snow, since it falls slower so more is on screen longer\n\t\t\tfloat particleSettingsAmplifierExtra = particleSettingsAmplifier;\n\t\t\t//spawnNeedBase = Math.max(1, ConfigParticle.Precipitation_Particle_effect_rate * particleSettingsAmplifierExtra);\n\t\t\tspawnNeedBase = ConfigParticle.Precipitation_Particle_effect_rate * particleSettingsAmplifierExtra;\n\t\t\tint spawnNeed = (int) Math.max(0, spawnNeedBase * 5);\n\t\t\tsafetyCutout = 60;\n\t\t\tint spawnAreaSize = 20;\n\t\t\tdouble closeDistCutoff = 7D;\n\t\t\tfloat yetAnotherRateNumber = 120 * getParticleFadeInLerpForNewWeatherState();\n\t\t\tif (farSpawn) {\n\t\t\t\tsafetyCutout = 20;\n\t\t\t\tspawnAreaSize = 100;\n\t\t\t\tyetAnotherRateNumber = 40 * getParticleFadeInLerpForNewWeatherState();\n\t\t\t}\n\n\t\t\tif (particleStormIntensity >= 0.01F) {\n\n\t\t\t\tif (spawnNeed > 0) {\n\n\t\t\t\t\tif (getParticleFadeInLerpForNewWeatherState() > 0.5F) {\n\t\t\t\t\t\tparticleSettingsAmplifierExtra *= (getParticleFadeInLerpForNewWeatherState() - 0.5F) * 2F;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tparticleSettingsAmplifierExtra = 0;\n\t\t\t\t\t}\n\n\t\t\t\t\t//System.out.println(\"particleSettingsAmplifierExtra: \" + particleSettingsAmplifierExtra);\n\n\t\t\t\t\t//snow\n\t\t\t\t\tfor (int i = 0; i < Math.max(1, safetyCutout * particleSettingsAmplifierExtra)/*curPrecipVal * 20F * PRECIPITATION_PARTICLE_EFFECT_RATE*/; i++) {\n\t\t\t\t\t\tBlockPos pos = CoroUtilBlock.blockPos(\n\t\t\t\t\t\t\t\tentP.getX() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2),\n\t\t\t\t\t\t\t\tentP.getY() - 5 + rand.nextInt(20),\n\t\t\t\t\t\t\t\tentP.getZ() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2));\n\n\t\t\t\t\t\tVec3 windForce = ClientTickHandler.getClientWeather().getWindManager().getWindForce(null);\n\t\t\t\t\t\tdouble upwindDistAdjust = -10D;\n\t\t\t\t\t\twindForce = windForce.multiply(upwindDistAdjust, upwindDistAdjust, upwindDistAdjust);\n\t\t\t\t\t\tpos = pos.offset(Mth.floor(windForce.x), Mth.floor(windForce.y), Mth.floor(windForce.z));\n\n\t\t\t\t\t\tif (WeatherUtilEntity.getDistanceSqEntToPos(entP, pos) < closeDistCutoff * closeDistCutoff)\n\t\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t\tif (canPrecipitateAt(world, pos)) {\n\t\t\t\t\t\t\tParticleTexExtraRender snow = new ParticleTexExtraRender((ClientLevel) entP.level(), pos.getX(), pos.getY(), pos.getZ(),\n\t\t\t\t\t\t\t\t\t0D, 0D, 0D, ParticleRegistry.snow);\n\n\t\t\t\t\t\t\tparticleBehavior.initParticleSnowstorm(snow, (int)(10 * particleStormIntensity));\n\t\t\t\t\t\t\tsnow.spawnAsWeatherEffect();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tdouble sandstormParticleRateDust = ConfigSand.Sandstorm_Particle_Dust_effect_rate;\n\n\t\t\t\t\tMinecraft client = Minecraft.getInstance();\n\t\t\t\t\tPlayer player = client.player;\n\t\t\t\t\t//float adjustAmountSmooth75 = (particleStormIntensity * 8F) - 7F;\n\t\t\t\t\tfloat adjustAmountSmooth75 = particleStormIntensity;\n\n\t\t\t\t\t//extra snow cloud dust\n\t\t\t\t\tfor (int i = 0; i < (particleSettingsAmplifier * yetAnotherRateNumber * adjustAmountSmooth75 * sandstormParticleRateDust)/*adjustAmountSmooth * 20F * ConfigMisc.Particle_Precipitation_effect_rate*/; i++) {\n\n\t\t\t\t\t\tBlockPos pos = CoroUtilBlock.blockPos(\n\t\t\t\t\t\t\t\tplayer.getX() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2),\n\t\t\t\t\t\t\t\tplayer.getY() - 2 + rand.nextInt(10),\n\t\t\t\t\t\t\t\tplayer.getZ() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2));\n\n\t\t\t\t\t\tif (WeatherUtilEntity.getDistanceSqEntToPos(entP, pos) < closeDistCutoff * closeDistCutoff)\n\t\t\t\t\t\t\tcontinue;\n\n\t\t\t\t\t\tif (canPrecipitateAt(world, pos)) {\n\t\t\t\t\t\t\tTextureAtlasSprite sprite = ParticleRegistry.cloud256;\n\n\t\t\t\t\t\t\tParticleSandstorm part = new ParticleSandstorm(world, pos.getX(),\n\t\t\t\t\t\t\t\t\tpos.getY(),\n\t\t\t\t\t\t\t\t\tpos.getZ(),\n\t\t\t\t\t\t\t\t\t0, 0, 0, sprite);\n\t\t\t\t\t\t\tparticleBehavior.initParticle(part);\n\t\t\t\t\t\t\tparticleBehavior.initParticleSnowstormCloudDust(part);\n\t\t\t\t\t\t\tparticleBehavior.particles.add(part);\n\t\t\t\t\t\t\tpart.spawnAsWeatherEffect();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t//works for snowstorms too\n\t\t\ttickSandstormSound();\n\t\t}\n\n\t\tif (isSandstorm) {\n\t\t\tMinecraft client = Minecraft.getInstance();\n\t\t\tPlayer player = client.player;\n\t\t\tClientTickHandler.getClientWeather();\n\n\t\t\t//enhance the scene further with particles around player, check for sandstorm to account for pocket sand modifying adjustAmountTarget\n\t\t\tif (particleStormIntensity >= 0.01F) {\n\n\t\t\t\trand = CoroUtilMisc.random();\n\t\t\t\tint spawnAreaSize = 60;\n\n\t\t\t\tdouble sandstormParticleRateDebris = ConfigSand.Sandstorm_Particle_Debris_effect_rate;\n\t\t\t\tdouble sandstormParticleRateDust = ConfigSand.Sandstorm_Particle_Dust_effect_rate;\n\n\t\t\t\t//float adjustAmountSmooth75 = (particleStormIntensity * 8F) - 7F;\n\t\t\t\tfloat adjustAmountSmooth75 = particleStormIntensity;\n\n\t\t\t\tif (farSpawn) {\n\t\t\t\t\tadjustAmountSmooth75 *= 0.3F;\n\t\t\t\t}\n\n\t\t\t\t/*if (Minecraft.getInstance().options.particles == ParticleStatus.DECREASED) {\n\t\t\t\t\tadjustAmountSmooth75 *= 0.5F;\n\t\t\t\t} else if (Minecraft.getInstance().options.particles == ParticleStatus.MINIMAL) {\n\t\t\t\t\tadjustAmountSmooth75 *= 0.25F;\n\t\t\t\t}*/\n\n\t\t\t\tadjustAmountSmooth75 *= particleSettingsAmplifier;\n\n\t\t\t\tadjustAmountSmooth75 *= getParticleFadeInLerpForNewWeatherState();\n\n\t\t\t\t//extra dust\n\t\t\t\tfor (int i = 0; i < ((float) 60 * adjustAmountSmooth75 * sandstormParticleRateDust)/*adjustAmountSmooth * 20F * ConfigMisc.Particle_Precipitation_effect_rate*/; i++) {\n\n\t\t\t\t\tBlockPos pos = CoroUtilBlock.blockPos(\n\t\t\t\t\t\t\tplayer.getX() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2),\n\t\t\t\t\t\t\tplayer.getY() - 2 + rand.nextInt(10),\n\t\t\t\t\t\t\tplayer.getZ() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2));\n\n\t\t\t\t\tif (canPrecipitateAt(world, pos)) {\n\t\t\t\t\t\tTextureAtlasSprite sprite = ParticleRegistry.cloud256;\n\n\t\t\t\t\t\tParticleSandstorm part = new ParticleSandstorm(world, pos.getX(),\n\t\t\t\t\t\t\t\tpos.getY(),\n\t\t\t\t\t\t\t\tpos.getZ(),\n\t\t\t\t\t\t\t\t0, 0, 0, sprite);\n\t\t\t\t\t\tparticleBehavior.initParticle(part);\n\t\t\t\t\t\tparticleBehavior.initParticleSandstormDust(part);\n\t\t\t\t\t\tparticleBehavior.particles.add(part);\n\t\t\t\t\t\tpart.spawnAsWeatherEffect();\n\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t//tumbleweed\n\t\t\t\tfor (int i = 0; i < ((float) 1 * adjustAmountSmooth75 * sandstormParticleRateDebris)/*adjustAmountSmooth * 20F * ConfigMisc.Particle_Precipitation_effect_rate*/; i++) {\n\t\t\t\t\tBlockPos pos = CoroUtilBlock.blockPos(\n\t\t\t\t\t\t\tplayer.getX() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2),\n\t\t\t\t\t\t\tplayer.getY() - 2 + rand.nextInt(10),\n\t\t\t\t\t\t\tplayer.getZ() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2));\n\n\t\t\t\t\tif (canPrecipitateAt(world, pos)) {\n\t\t\t\t\t\tTextureAtlasSprite sprite = ParticleRegistry.tumbleweed;\n\n\t\t\t\t\t\tParticleCrossSection part = new ParticleCrossSection(world, pos.getX(),\n\t\t\t\t\t\t\t\tpos.getY(),\n\t\t\t\t\t\t\t\tpos.getZ(),\n\t\t\t\t\t\t\t\t0, 0, 0, sprite);\n\t\t\t\t\t\tparticleBehavior.initParticle(part);\n\t\t\t\t\t\tparticleBehavior.initParticleSandstormTumbleweed(part);\n\t\t\t\t\t\tparticleBehavior.particles.add(part);\n\t\t\t\t\t\tpart.spawnAsWeatherEffect();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t//debris\n\t\t\t\tfor (int i = 0; i < ((float) 8 * adjustAmountSmooth75 * sandstormParticleRateDebris)/*adjustAmountSmooth * 20F * ConfigMisc.Particle_Precipitation_effect_rate*/; i++) {\n\t\t\t\t\tBlockPos pos = CoroUtilBlock.blockPos(\n\t\t\t\t\t\t\tplayer.getX() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2),\n\t\t\t\t\t\t\tplayer.getY() - 2 + rand.nextInt(10),\n\t\t\t\t\t\t\tplayer.getZ() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2));\n\n\t\t\t\t\tif (canPrecipitateAt(world, pos)) {\n\t\t\t\t\t\tTextureAtlasSprite sprite = null;\n\t\t\t\t\t\tint tex = rand.nextInt(3);\n\t\t\t\t\t\tif (tex == 0) {\n\t\t\t\t\t\t\tsprite = ParticleRegistry.debris_1;\n\t\t\t\t\t\t} else if (tex == 1) {\n\t\t\t\t\t\t\tsprite = ParticleRegistry.debris_2;\n\t\t\t\t\t\t} else if (tex == 2) {\n\t\t\t\t\t\t\tsprite = ParticleRegistry.debris_3;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tParticleSandstorm part = new ParticleSandstorm(world, pos.getX(),\n\t\t\t\t\t\t\t\tpos.getY(),\n\t\t\t\t\t\t\t\tpos.getZ(),\n\t\t\t\t\t\t\t\t0, 0, 0, sprite);\n\t\t\t\t\t\tparticleBehavior.initParticle(part);\n\t\t\t\t\t\tparticleBehavior.initParticleSandstormDebris(part);\n\t\t\t\t\t\tparticleBehavior.particles.add(part);\n\t\t\t\t\t\tpart.spawnAsWeatherEffect();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\ttickSandstormSound();\n\t\t}\n\t}\n\n\tpublic static boolean canPrecipitateAt(Level world, BlockPos strikePosition)\n\t{\n\t\treturn world.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, strikePosition).getY() <= strikePosition.getY();\n\t}\n\n\tpublic synchronized void tryParticleSpawning()\n    {\n    \ttry {\n\t\t\tfor (Particle ent : spawnQueue) {\n\t\t\t\tif (ent != null/* && ent.world != null*/) {\n\t\t\t\t\tif (ent instanceof EntityRotFX) {\n\t\t\t\t\t\t((EntityRotFX) ent).spawnAsWeatherEffect();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (Particle ent : spawnQueueNormal) {\n\t\t\t\tif (ent != null/* && ent.world != null*/) {\n\t\t\t\t\tMinecraft.getInstance().particleEngine.add(ent);\n\t\t\t\t}\n\t\t\t}\n    \t} catch (Exception ex) {\n    \t\t//CMEs occur, its fine\n    \t\t//ex.printStackTrace();\n    \t}\n\n        spawnQueue.clear();\n        spawnQueueNormal.clear();\n    }\n\n\tpublic void profileSurroundings()\n    {\n        //tryClouds();\n\n    \tMinecraft client = Minecraft.getInstance();\n    \tClientLevel worldRef = lastWorldDetected;\n    \tPlayer player = Minecraft.getInstance().player;\n        WeatherManagerClient manager = ClientTickHandler.weatherManager;\n\n        if (worldRef == null || player == null || manager == null || manager.getWindManager() == null || (manager.getWindManager() != null && manager.getWindManager().cachedWindSpeedClient == 0))\n        {\n        \ttry {\n        \t\tThread.sleep(1000L);\n        \t} catch (Exception ex) {\n        \t\tex.printStackTrace();\n        \t}\n            return;\n        }\n\n        if (threadLastWorldTickTime == worldRef.getGameTime())\n        {\n            return;\n        }\n\n        threadLastWorldTickTime = worldRef.getGameTime();\n\n        Random rand = new Random();\n\n        //mining a tree causes leaves to fall\n        int size = 40;\n        int hsize = size / 2;\n        int curX = (int)player.getX();\n        int curY = (int)player.getY();\n        int curZ = (int)player.getZ();\n\n        float windStr = manager.getWindManager().cachedWindSpeedClient;\n\n\t\t//System.out.println(\"windStr \" + windStr);\n\n        //Wind requiring code goes below\n        int spawnRateRandChanceOdds = (int)(30 / (windStr + 0.001));\n\n        float lastBlockCount = lastTickFoundBlocks;\n\n        float particleCreationRate = 0.7F;\n\n        float maxScaleSample = 15000;\n        if (lastBlockCount > maxScaleSample) lastBlockCount = maxScaleSample-1;\n        float scaleRate = (maxScaleSample - lastBlockCount) / maxScaleSample;\n\n        spawnRateRandChanceOdds = (int) ((spawnRateRandChanceOdds / (scaleRate + 0.001F)) / (particleCreationRate + 0.001F));\n\n\t\tfloat particleSettingsAmplifier = 1F;\n\t\tif (Minecraft.getInstance().options.particles.get() == ParticleStatus.DECREASED) {\n\t\t\tparticleSettingsAmplifier = 0.5F;\n\t\t} else if (Minecraft.getInstance().options.particles.get() == ParticleStatus.MINIMAL) {\n\t\t\tparticleSettingsAmplifier = 0.2F;\n\t\t}\n\n\t\tparticleSettingsAmplifier *= ConfigParticle.Particle_effect_rate;\n\n        //spawnRate *= (client.options.particles.getId()+1);\n        spawnRateRandChanceOdds /= particleSettingsAmplifier;\n        //since reducing threaded ticking to 200ms sleep, 1/4 rate, must decrease rand size\n        spawnRateRandChanceOdds /= 2;\n\n        //performance fix\n        if (spawnRateRandChanceOdds < 40)\n        {\n            spawnRateRandChanceOdds = 40;\n        }\n\n        lastTickFoundBlocks = 0;\n\n\t\tdouble particleAmp = 1F;\n\n\t\tspawnRateRandChanceOdds = (int)((double)spawnRateRandChanceOdds / particleAmp);\n\n\t\t//Weather.dbg(\"spawnRate: \" + spawnRate);\n\n        for (int xx = curX - hsize; xx < curX + hsize; xx++)\n        {\n            for (int yy = curY - (hsize / 2); yy < curY + hsize; yy++)\n            {\n                for (int zz = curZ - hsize; zz < curZ + hsize; zz++)\n                {\n\t\t\t\t\tBlock block = getBlock(worldRef, xx, yy, zz);\n\n\t\t\t\t\tif (block != null) {\n\t\t\t\t\t\t//leaf particle spawning\n\t\t\t\t\t\tif ((block.defaultMapColor() == MapColor.PLANT)) {\n\n\t\t\t\t\t\t\tlastTickFoundBlocks++;\n\n\t\t\t\t\t\t\tif (CoroUtilMisc.random.nextInt(spawnRateRandChanceOdds) == 0) {\n\t\t\t\t\t\t\t\t//bottom of tree check || air beside vine check\n\n\t\t\t\t\t\t\t\t//far out enough to avoid having the AABB already inside the block letting it phase through more\n\t\t\t\t\t\t\t\t//close in as much as we can to make it look like it came from the block\n\t\t\t\t\t\t\t\tdouble relAdj = 0.70D;\n\n\t\t\t\t\t\t\t\tBlockPos pos = getRandomWorkingPos(worldRef, new BlockPos(xx, yy, zz));\n\t\t\t\t\t\t\t\tdouble xRand = 0;\n\t\t\t\t\t\t\t\tdouble yRand = 0;\n\t\t\t\t\t\t\t\tdouble zRand = 0;\n\n\t\t\t\t\t\t\t\tif (pos != null) {\n\n\t\t\t\t\t\t\t\t\t//further limit the spawn position along the face side to prevent it clipping into perpendicular blocks\n\t\t\t\t\t\t\t\t\tfloat particleAABB = 0.1F;\n\t\t\t\t\t\t\t\t\tfloat particleAABBAndBuffer = particleAABB + 0.05F;\n\t\t\t\t\t\t\t\t\tfloat invert = 1F - (particleAABBAndBuffer * 2F);\n\n\t\t\t\t\t\t\t\t\tif (pos.getY() != 0) {\n\t\t\t\t\t\t\t\t\t\txRand = particleAABBAndBuffer + (rand.nextDouble() - 0.5D) * invert;\n\t\t\t\t\t\t\t\t\t\tzRand = particleAABBAndBuffer + (rand.nextDouble() - 0.5D) * invert;\n\t\t\t\t\t\t\t\t\t} else if (pos.getX() != 0) {\n\t\t\t\t\t\t\t\t\t\tyRand = particleAABBAndBuffer + (rand.nextDouble() - 0.5D) * invert;\n\t\t\t\t\t\t\t\t\t\tzRand = particleAABBAndBuffer + (rand.nextDouble() - 0.5D) * invert;\n\t\t\t\t\t\t\t\t\t} else if (pos.getZ() != 0) {\n\t\t\t\t\t\t\t\t\t\tyRand = particleAABBAndBuffer + (rand.nextDouble() - 0.5D) * invert;\n\t\t\t\t\t\t\t\t\t\txRand = particleAABBAndBuffer + (rand.nextDouble() - 0.5D) * invert;\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tEntityRotFX particle = new ParticleTexLeafColor(worldRef, xx, yy, zz, 0D, 0D, 0D, ParticleRegistry.leaf);\n\t\t\t\t\t\t\t\t\tparticle.setPos(xx + 0.5D + (pos.getX() * relAdj) + xRand,\n\t\t\t\t\t\t\t\t\t\t\tyy + 0.5D + (pos.getY() * relAdj) + yRand,\n\t\t\t\t\t\t\t\t\t\t\tzz + 0.5D + (pos.getZ() * relAdj) + zRand);\n\t\t\t\t\t\t\t\t\tparticle.setPrevPosX(particle.getPosX());\n\t\t\t\t\t\t\t\t\tparticle.setPrevPosY(particle.getPosY());\n\t\t\t\t\t\t\t\t\tparticle.setPrevPosZ(particle.getPosZ());\n\t\t\t\t\t\t\t\t\tparticleBehavior.initParticleLeaf(particle, particleAABB);\n\n\t\t\t\t\t\t\t\t\tspawnQueue.add(particle);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (windStr >= 0.1F) {\n\t\t\t\t\t\t\tif (block instanceof GrassBlock\n\t\t\t\t\t\t\t\t\t|| block.defaultMapColor() == MapColor.DIRT\n\t\t\t\t\t\t\t\t\t|| block.defaultMapColor() == MapColor.SAND\n\t\t\t\t\t\t\t\t\t|| block.defaultMapColor() == MapColor.PLANT) {\n\n\t\t\t\t\t\t\t\tlastTickFoundBlocks++;\n\n\t\t\t\t\t\t\t\tboolean spawnInside = false;\n\t\t\t\t\t\t\t\tboolean spawnAbove = false;\n\t\t\t\t\t\t\t\tboolean spawnAboveSnow = false;\n\n\t\t\t\t\t\t\t\t//boolean placeAbove = false;\n\t\t\t\t\t\t\t\tif (block instanceof GrassBlock\n\t\t\t\t\t\t\t\t\t\t|| block.defaultMapColor() == MapColor.DIRT\n\t\t\t\t\t\t\t\t\t\t|| block.defaultMapColor() == MapColor.SAND) {\n\t\t\t\t\t\t\t\t\tspawnAbove = true;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tint oddsTo1 = spawnRateRandChanceOdds;\n\t\t\t\t\t\t\t\tif (block.defaultMapColor() == MapColor.PLANT) {\n\t\t\t\t\t\t\t\t\toddsTo1 = spawnRateRandChanceOdds / 3;\n\t\t\t\t\t\t\t\t\tspawnInside = true;\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t//oddsTo1 = (int) (oddsTo1 * (5F * windStr));\n\n\t\t\t\t\t\t\t\tif (CoroUtilMisc.random.nextInt(oddsTo1) == 0) {\n\t\t\t\t\t\t\t\t\tBlockPos pos = new BlockPos(xx, yy, zz);\n\t\t\t\t\t\t\t\t\tBlockPos posAbove = new BlockPos(xx, yy + 1, zz);\n\t\t\t\t\t\t\t\t\tBlockState blockStateAbove = getBlockState(worldRef, posAbove);\n\n\t\t\t\t\t\t\t\t\tif (blockStateAbove != null && (blockStateAbove.isAir() || blockStateAbove.getBlock() instanceof SnowLayerBlock)) {\n\n\t\t\t\t\t\t\t\t\t\tspawnAboveSnow = blockStateAbove.getBlock() instanceof SnowLayerBlock;\n\n\t\t\t\t\t\t\t\t\t\tboolean test = false;\n\t\t\t\t\t\t\t\t\t\tif (!test) {\n\t\t\t\t\t\t\t\t\t\t\tParticleTexLeafColor dust = new ParticleTexLeafColor(worldRef,\n\t\t\t\t\t\t\t\t\t\t\t\t\tpos.getX(),\n\t\t\t\t\t\t\t\t\t\t\t\t\tspawnAboveSnow ? posAbove.getY() : pos.getY(),\n\t\t\t\t\t\t\t\t\t\t\t\t\tpos.getZ(),\n\t\t\t\t\t\t\t\t\t\t\t\t\t0D, 0D, 0D, ParticleRegistry.squareGrey);\n\t\t\t\t\t\t\t\t\t\t\tif (spawnAbove) {\n\t\t\t\t\t\t\t\t\t\t\t\tif (spawnAboveSnow) {\n\t\t\t\t\t\t\t\t\t\t\t\t\tdust.setPosition(posAbove.getX(), posAbove.getY() + 0.4F, posAbove.getZ());\n\t\t\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\t\t\tdust.setPosition(posAbove.getX(), posAbove.getY() + 0.1F, posAbove.getZ());\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t} else if (spawnInside) {\n\t\t\t\t\t\t\t\t\t\t\t\tdust.setPosition(pos.getX() + rand.nextFloat(), pos.getY() + rand.nextFloat(), pos.getZ() + rand.nextFloat());\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\tdust.setPrevPosX(dust.getPosX());\n\t\t\t\t\t\t\t\t\t\t\tdust.setPrevPosY(dust.getPosY());\n\t\t\t\t\t\t\t\t\t\t\tdust.setPrevPosZ(dust.getPosZ());\n\t\t\t\t\t\t\t\t\t\t\tparticleBehavior.initParticleDustGround(dust, spawnInside, spawnAboveSnow);\n\t\t\t\t\t\t\t\t\t\t\tspawnQueue.add(dust);\n\t\t\t\t\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\t\t\t\t\tDustEmitter dustEmitter = new DustEmitter(worldRef,\n\t\t\t\t\t\t\t\t\t\t\t\t\tpos.getX(),\n\t\t\t\t\t\t\t\t\t\t\t\t\tspawnAboveSnow ? posAbove.getY() : pos.getY(),\n\t\t\t\t\t\t\t\t\t\t\t\t\tpos.getZ(),\n\t\t\t\t\t\t\t\t\t\t\t\t\t0D, 0D, 0D);\n\t\t\t\t\t\t\t\t\t\t\tdustEmitter.setLifetime(20);\n\t\t\t\t\t\t\t\t\t\t\tspawnQueue.add(dustEmitter);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n                }\n            }\n        }\n    }\n\n\t/**\n\t * Returns the successful relative position\n\t *\n\t * @param world\n\t * @param posOrigin\n\t * @return\n\t */\n    public static BlockPos getRandomWorkingPos(Level world, BlockPos posOrigin) {\n\t\tCollections.shuffle(listPosRandom);\n\t\tfor (BlockPos posRel : listPosRandom) {\n\t\t\tBlock blockCheck = getBlock(world, posOrigin.offset(posRel));\n\n\t\t\tif (blockCheck != null && CoroUtilBlock.isAir(blockCheck)) {\n\t\t\t\treturn posRel;\n\t\t\t}\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t@OnlyIn(Dist.CLIENT)\n    public static void tryWind(Level world)\n    {\n\t\tMinecraft client = Minecraft.getInstance();\n\t\tPlayer player = client.player;\n\n        if (player == null)\n        {\n            return;\n        }\n\n        WeatherManagerClient weatherMan = ClientTickHandler.weatherManager;\n        if (weatherMan == null) return;\n        WindManager windMan = weatherMan.getWindManager();\n        if (windMan == null) return;\n\n        //Weather Effects\n\n\t\t//Built in particles\n        if (WeatherUtilParticle.fxLayers != null && windMan.getWindSpeed(player.blockPosition()) >= 0.10) {\n\t\t\tfor (Queue<Particle> type : WeatherUtilParticle.fxLayers.values()) {\n\t\t\t\tfor (Particle particle : type) {\n\t                    if (particle instanceof SuspendedParticle) {\n\t                    \tcontinue;\n\t\t\t\t\t\t}\n\n\t                    if ((WeatherUtilBlock.getPrecipitationHeightSafe(world, WeatherUtilParticle.getPos(particle)).getY() - 1 < (int)Mth.floor(particle.y) + 1) || (particle instanceof ParticleTexFX))\n\t                    {\n\t                        if ((particle instanceof FlameParticle))\n\t                        {\n\t                        \tif (windMan.getWindSpeed(player.blockPosition()) >= 0.20) {\n\t\t\t\t\t\t\t\t\tparticle.age += 1;\n\t\t\t\t\t\t\t\t}\n\t                        }\n\n\t\t\t\t\t\t\twindMan.applyWindForceNew(particle, 1F/20F, 0.5F, true);\n\t                    }\n\n                }\n            }\n        }\n    }\n\n\t//Thread safe functions\n\n\t@OnlyIn(Dist.CLIENT)\n\tprivate static Block getBlock(Level parWorld, BlockPos pos)\n\t{\n\t\treturn getBlock(parWorld, pos.getX(), pos.getY(), pos.getZ());\n\t}\n\n    @OnlyIn(Dist.CLIENT)\n    private static Block getBlock(Level parWorld, int x, int y, int z)\n    {\n        try\n        {\n            if (!parWorld.hasChunkAt(new BlockPos(x, 0, z)))\n            {\n                return null;\n            }\n\n            return parWorld.getBlockState(new BlockPos(x, y, z)).getBlock();\n        }\n        catch (Exception ex)\n        {\n            return null;\n        }\n    }\n\n\t@OnlyIn(Dist.CLIENT)\n\tprivate static BlockState getBlockState(Level parWorld, BlockPos pos)\n\t{\n\t\treturn getBlockState(parWorld, pos.getX(), pos.getY(), pos.getZ());\n\t}\n\n\t@OnlyIn(Dist.CLIENT)\n\tprivate static BlockState getBlockState(Level parWorld, int x, int y, int z)\n\t{\n\t\ttry\n\t\t{\n\t\t\tif (!parWorld.hasChunkAt(new BlockPos(x, 0, z)))\n\t\t\t{\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\treturn parWorld.getBlockState(new BlockPos(x, y, z));\n\t\t}\n\t\tcatch (Exception ex)\n\t\t{\n\t\t\treturn null;\n\t\t}\n\t}\n\n    public static boolean isFogOverridding() {\n\t\tMinecraft client = Minecraft.getInstance();\n\t\tBlockState blockAtCamera = client.gameRenderer.getMainCamera().getBlockAtCamera();\n\t\tif (blockAtCamera.getBlock().defaultMapColor() == MapColor.WATER) return false;\n    \t//return heatwaveIntensity > 0;\n\t\t//return true;\n\t\treturn fogAdjuster.isFogOverriding();\n    }\n\n    public static void renderTick(TickEvent.RenderTickEvent event) {\n\t\tMinecraft client = Minecraft.getInstance();\n\t\tClientWeatherProxy weather = ClientWeatherProxy.get();\n\t\tif (client.level != null) {\n\t\t\tClientWeatherHelper.get().controlVisuals(weather.getVanillaRainAmount() > 0);\n\t\t}\n\t}\n\n\tpublic static void tickSandstorm() {\n\n\t\tMinecraft client = Minecraft.getInstance();\n\t\tPlayer player = client.player;\n\t\tLevel world = client.level;\n\t\tWindManager windMan = ClientTickHandler.weatherManager.getWindManager();\n\t\tClientTickHandler.getClientWeather();\n\n\t\tboolean farSpawn = Minecraft.getInstance().player.isSpectator() || !isPlayerOutside;\n\n\t\tfloat adjustAmountSmooth = 0;\n\n\t\tWeatherObjectParticleStorm sandstorm = ClientTickHandler.weatherManager.getClosestParticleStormByIntensity(player.position(), WeatherObjectParticleStorm.StormType.SANDSTORM);\n\t\tif (sandstorm != null) {\n\t\t\tadjustAmountSmooth = sandstorm.getIntensity();\n\t\t\t//CULog.dbg(\"sandstorm: \" + adjustAmountSmooth);\n\t\t}\n\n\t\t//enhance the scene further with particles around player, check for sandstorm to account for pocket sand modifying adjustAmountTarget\n\t\tif (adjustAmountSmooth >= 0.25F/* && sandstorm != null*/) {\n\n\t\t\t//porting whee\n\t\t\tadjustAmountSmooth += 0.5F;\n\n\t\t\tRandom rand = CoroUtilMisc.random();\n\t\t\tint spawnAreaSize = 80;\n\n\t\t\tdouble sandstormParticleRateDebris = ConfigSand.Sandstorm_Particle_Debris_effect_rate;\n\t\t\tdouble sandstormParticleRateDust = ConfigSand.Sandstorm_Particle_Dust_effect_rate;\n\n\t\t\tfloat adjustAmountSmooth75 = (adjustAmountSmooth * 8F) - 7F;\n\n\t\t\tif (farSpawn) {\n\t\t\t\tadjustAmountSmooth75 *= 0.3F;\n\t\t\t}\n\n\t\t\tif (Minecraft.getInstance().options.particles.get() == ParticleStatus.DECREASED) {\n\t\t\t\tadjustAmountSmooth75 *= 0.5F;\n\t\t\t} else if (Minecraft.getInstance().options.particles.get() == ParticleStatus.MINIMAL) {\n\t\t\t\tadjustAmountSmooth75 *= 0.25F;\n\t\t\t}\n\n\t\t\tadjustAmountSmooth75 *= getParticleFadeInLerpForNewWeatherState();\n\n\t\t\t//extra dust\n\t\t\tfor (int i = 0; i < ((float)60 * adjustAmountSmooth75 * sandstormParticleRateDust)/*adjustAmountSmooth * 20F * ConfigMisc.Particle_Precipitation_effect_rate*/; i++) {\n\n\t\t\t\tBlockPos pos = CoroUtilBlock.blockPos(\n\t\t\t\t\t\tplayer.getX() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2),\n\t\t\t\t\t\tplayer.getY() - 2 + rand.nextInt(10),\n\t\t\t\t\t\tplayer.getZ() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2));\n\n\n\n\t\t\t\tif (canPrecipitateAt(world, pos)) {\n\t\t\t\t\tTextureAtlasSprite sprite = ParticleRegistry.cloud256;\n\n\t\t\t\t\tParticleSandstorm part = new ParticleSandstorm(world, pos.getX(),\n\t\t\t\t\t\t\tpos.getY(),\n\t\t\t\t\t\t\tpos.getZ(),\n\t\t\t\t\t\t\t0, 0, 0, sprite);\n\t\t\t\t\tparticleBehavior.initParticle(part);\n\t\t\t\t\tparticleBehavior.initParticleSandstormDust(part);\n\t\t\t\t\tparticleBehavior.particles.add(part);\n\t\t\t\t\tpart.spawnAsWeatherEffect();\n\n\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t//tumbleweed\n\t\t\tfor (int i = 0; i < ((float)1 * adjustAmountSmooth75 * sandstormParticleRateDebris)/*adjustAmountSmooth * 20F * ConfigMisc.Particle_Precipitation_effect_rate*/; i++) {\n\n\t\t\t\tBlockPos pos = CoroUtilBlock.blockPos(\n\t\t\t\t\t\tplayer.getX() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2),\n\t\t\t\t\t\tplayer.getY() - 2 + rand.nextInt(10),\n\t\t\t\t\t\tplayer.getZ() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2));\n\n\t\t\t\tif (canPrecipitateAt(world, pos)) {\n\t\t\t\t\tTextureAtlasSprite sprite = ParticleRegistry.tumbleweed;\n\n\t\t\t\t\tParticleCrossSection part = new ParticleCrossSection(world, pos.getX(),\n\t\t\t\t\t\t\tpos.getY(),\n\t\t\t\t\t\t\tpos.getZ(),\n\t\t\t\t\t\t\t0, 0, 0, sprite);\n\t\t\t\t\tparticleBehavior.initParticle(part);\n\t\t\t\t\tparticleBehavior.initParticleSandstormTumbleweed(part);\n\t\t\t\t\tparticleBehavior.particles.add(part);\n\t\t\t\t\tpart.spawnAsWeatherEffect();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t//debris\n\t\t\tfor (int i = 0; i < ((float)8 * adjustAmountSmooth75 * sandstormParticleRateDebris)/*adjustAmountSmooth * 20F * ConfigMisc.Particle_Precipitation_effect_rate*/; i++) {\n\t\t\t\tBlockPos pos = CoroUtilBlock.blockPos(\n\t\t\t\t\t\tplayer.getX() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2),\n\t\t\t\t\t\tplayer.getY() - 2 + rand.nextInt(10),\n\t\t\t\t\t\tplayer.getZ() + rand.nextInt(spawnAreaSize) - (spawnAreaSize / 2));\n\n\t\t\t\tif (canPrecipitateAt(world, pos)) {\n\t\t\t\t\tTextureAtlasSprite sprite = null;\n\t\t\t\t\tint tex = rand.nextInt(3);\n\t\t\t\t\tif (tex == 0) {\n\t\t\t\t\t\tsprite = ParticleRegistry.debris_1;\n\t\t\t\t\t} else if (tex == 1) {\n\t\t\t\t\t\tsprite = ParticleRegistry.debris_2;\n\t\t\t\t\t} else if (tex == 2) {\n\t\t\t\t\t\tsprite = ParticleRegistry.debris_3;\n\t\t\t\t\t}\n\n\t\t\t\t\tParticleSandstorm part = new ParticleSandstorm(world, pos.getX(),\n\t\t\t\t\t\t\tpos.getY(),\n\t\t\t\t\t\t\tpos.getZ(),\n\t\t\t\t\t\t\t0, 0, 0, sprite);\n\t\t\t\t\tparticleBehavior.initParticle(part);\n\t\t\t\t\tparticleBehavior.initParticleSandstormDebris(part);\n\t\t\t\t\tparticleBehavior.particles.add(part);\n\t\t\t\t\tpart.spawnAsWeatherEffect();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ttickSandstormSound();\n\t}\n\n\tpublic static void tickSandstormSound() {\n\t\t/**\n\t\t * dist + storm intensity\n\t\t * 0F - 1F\n\t\t *\n\t\t * 0 = low\n\t\t * 0.33 = med\n\t\t * 0.66 = high\n\t\t *\n\t\t * static sound volume, keep at player\n\t\t */\n\n\t\tMinecraft mc = Minecraft.getInstance();\n\t\tif (particleRateLerp > 0) {\n\t\t\tif (particleRateLerp < 0.66F) {\n\t\t\t\ttryPlayPlayerLockedSound(WeatherUtilSound.snd_sandstorm_low, 5, mc.player, 0.6F);\n\t\t\t} else if (particleRateLerp < 0.85F) {\n\t\t\t\ttryPlayPlayerLockedSound(WeatherUtilSound.snd_sandstorm_med, 4, mc.player, 0.6F);\n\t\t\t} else {\n\t\t\t\ttryPlayPlayerLockedSound(WeatherUtilSound.snd_sandstorm_high, 3, mc.player, 0.6F);\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic static FogAdjuster getFogAdjuster() {\n\t\tif (fogAdjuster == null) {\n\t\t\tfogAdjuster = new FogAdjuster();\n\t\t}\n\t\treturn fogAdjuster;\n\t}\n\n\tpublic static WeatherEventType getWeatherState() {\n\t\tClientWeatherProxy clientWeather = ClientWeatherProxy.get();\n\t\tif (clientWeather.isSandstorm()) {\n\t\t\treturn WeatherEventType.SANDSTORM;\n\t\t} else if (clientWeather.isSnowstorm()) {\n\t\t\treturn WeatherEventType.SNOWSTORM;\n\t\t} else if (clientWeather.isHeatwave()) {\n\t\t\treturn WeatherEventType.HEATWAVE;\n\t\t} else if (clientWeather.getRainAmount() > 0 && clientWeather.getPrecipitationType(lastBiomeIn) == PrecipitationType.ACID) {\n\t\t\treturn WeatherEventType.ACID_RAIN;\n\t\t} else if (clientWeather.getRainAmount() > 0 && clientWeather.getPrecipitationType(lastBiomeIn) == PrecipitationType.NORMAL) {\n\t\t\treturn WeatherEventType.HEAVY_RAIN;\n\t\t} else if (clientWeather.getRainAmount() > 0 && clientWeather.getPrecipitationType(lastBiomeIn) == PrecipitationType.HAIL) {\n\t\t\treturn WeatherEventType.HAIL;\n\t\t} else {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tpublic static float getParticleFadeInLerpForNewWeatherState() {\n    \treturn (float)particleRateLerp / (float)particleRateLerpMax;\n\t}\n\n\t/**\n\t * Needed for serene seasons compat\n\t *\n\t * @param level\n\t * @param biome\n\t * @param pos\n\t * @return\n\t */\n\tpublic static boolean shouldRainHere(Level level, Biome biome, BlockPos pos) {\n\t\treturn CoroUtilCompatibility.warmEnoughToRain(biome, pos, level);\n\t}\n\n\t/**\n\t * Needed for serene seasons compat\n\t *\n\t * @param level\n\t * @param biome\n\t * @param pos\n\t * @return\n\t */\n\tpublic static boolean shouldSnowHere(Level level, Biome biome, BlockPos pos) {\n\t\treturn CoroUtilCompatibility.coldEnoughToSnow(biome, pos, level);\n\t}\n\n\tpublic static void checkParticleBehavior() {\n\t\tif (particleBehavior == null) {\n\t\t\tparticleBehavior = new ParticleBehaviorSandstorm(null);\n\t\t}\n\t}\n}\n\n"
  },
  {
    "path": "src/main/java/weather2/client/entity/model/AnemometerModel.java",
    "content": "package weather2.client.entity.model;// Made with Blockbench 4.8.3\n// Exported for Minecraft version 1.17 or later with Mojang mappings\n// Paste this class into your mod and generate all required imports\n\n\nimport com.mojang.blaze3d.vertex.PoseStack;\nimport com.mojang.blaze3d.vertex.VertexConsumer;\nimport net.minecraft.client.model.EntityModel;\nimport net.minecraft.client.model.HierarchicalModel;\nimport net.minecraft.client.model.geom.ModelLayerLocation;\nimport net.minecraft.client.model.geom.ModelPart;\nimport net.minecraft.client.model.geom.PartPose;\nimport net.minecraft.client.model.geom.builders.*;\nimport net.minecraft.resources.ResourceLocation;\nimport net.minecraft.world.entity.Entity;\nimport weather2.Weather;\n\npublic class AnemometerModel<T extends Entity> extends HierarchicalModel<T> {\n\t// This layer location should be baked with EntityRendererProvider.Context in the entity renderer and passed into this model's constructor\n\tpublic static final ModelLayerLocation LAYER_LOCATION = new ModelLayerLocation(new ResourceLocation(Weather.MODID, \"anemometer\"), \"main\");\n\tprivate final ModelPart root;\n\n\tpublic AnemometerModel(ModelPart root) {\n\t\tthis.root = root.getChild(\"root\");\n\t}\n\n\t@Override\n\tpublic ModelPart root() {\n\t\treturn this.root;\n\t}\n\n\tpublic static LayerDefinition createBodyLayer() {\n\t\tMeshDefinition meshdefinition = new MeshDefinition();\n\t\tPartDefinition partdefinition = meshdefinition.getRoot();\n\n\t\tPartDefinition root = partdefinition.addOrReplaceChild(\"root\", CubeListBuilder.create().texOffs(0, 0).addBox(-1.5F, -1.0F, -1.5F, 3.0F, 1.0F, 3.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 24.0F, 0.0F));\n\n\t\tPartDefinition base = root.addOrReplaceChild(\"base\", CubeListBuilder.create().texOffs(1, 1).addBox(-0.5F, -11.0F, -0.5F, 1.0F, 11.0F, 1.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 0.0F, 0.0F));\n\n\t\tPartDefinition top = base.addOrReplaceChild(\"top\", CubeListBuilder.create().texOffs(0, 0).addBox(-1.0F, -1.0F, -1.0F, 2.0F, 2.0F, 2.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, -11.5F, 0.0F));\n\n\t\tPartDefinition arm1 = top.addOrReplaceChild(\"arm1\", CubeListBuilder.create().texOffs(-5, -5).addBox(-0.5F, -0.5F, -8.0F, 1.0F, 1.0F, 7.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 0.0F, 0.0F));\n\n\t\tPartDefinition cup1 = arm1.addOrReplaceChild(\"cup1\", CubeListBuilder.create().texOffs(1, 1).addBox(0.5F, -12.0F, -8.5F, 1.0F, 1.0F, 1.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(-1, -1).addBox(0.5F, -13.0F, -8.5F, 1.0F, 1.0F, 3.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(1, 1).addBox(0.5F, -12.0F, -6.5F, 1.0F, 1.0F, 1.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(-1, -1).addBox(0.5F, -11.0F, -8.5F, 1.0F, 1.0F, 3.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 11.5F, 0.0F));\n\n\t\tPartDefinition arm2 = top.addOrReplaceChild(\"arm2\", CubeListBuilder.create().texOffs(-5, -5).addBox(-0.5F, -0.5F, -8.0F, 1.0F, 1.0F, 7.0F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(0.0F, 0.0F, 0.0F, 0.0F, -1.5708F, 0.0F));\n\n\t\tPartDefinition cup2 = arm2.addOrReplaceChild(\"cup2\", CubeListBuilder.create().texOffs(1, 1).addBox(0.5F, -12.0F, -8.5F, 1.0F, 1.0F, 1.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(-1, -1).addBox(0.5F, -13.0F, -8.5F, 1.0F, 1.0F, 3.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(1, 1).addBox(0.5F, -12.0F, -6.5F, 1.0F, 1.0F, 1.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(-1, -1).addBox(0.5F, -11.0F, -8.5F, 1.0F, 1.0F, 3.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 11.5F, 0.0F));\n\n\t\tPartDefinition arm3 = top.addOrReplaceChild(\"arm3\", CubeListBuilder.create().texOffs(-5, -5).addBox(-0.5F, -0.5F, -8.0F, 1.0F, 1.0F, 7.0F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(0.0F, 0.0F, 0.0F, 0.0F, 3.1416F, 0.0F));\n\n\t\tPartDefinition cup3 = arm3.addOrReplaceChild(\"cup3\", CubeListBuilder.create().texOffs(1, 1).addBox(0.5F, -12.0F, -8.5F, 1.0F, 1.0F, 1.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(-1, -1).addBox(0.5F, -13.0F, -8.5F, 1.0F, 1.0F, 3.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(1, 1).addBox(0.5F, -12.0F, -6.5F, 1.0F, 1.0F, 1.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(-1, -1).addBox(0.5F, -11.0F, -8.5F, 1.0F, 1.0F, 3.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 11.5F, 0.0F));\n\n\t\tPartDefinition arm4 = top.addOrReplaceChild(\"arm4\", CubeListBuilder.create().texOffs(-5, -5).addBox(-0.5F, -0.5F, -8.0F, 1.0F, 1.0F, 7.0F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(0.0F, 0.0F, 0.0F, 0.0F, 1.5708F, 0.0F));\n\n\t\tPartDefinition cup4 = arm4.addOrReplaceChild(\"cup4\", CubeListBuilder.create().texOffs(1, 1).addBox(0.5F, -12.0F, -8.5F, 1.0F, 1.0F, 1.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(-1, -1).addBox(0.5F, -13.0F, -8.5F, 1.0F, 1.0F, 3.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(1, 1).addBox(0.5F, -12.0F, -6.5F, 1.0F, 1.0F, 1.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(-1, -1).addBox(0.5F, -11.0F, -8.5F, 1.0F, 1.0F, 3.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 11.5F, 0.0F));\n\n\t\treturn LayerDefinition.create(meshdefinition, 16, 16);\n\t}\n\n\t@Override\n\tpublic void setupAnim(T entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) {\n\n\t}\n\n\t@Override\n\tpublic void renderToBuffer(PoseStack poseStack, VertexConsumer vertexConsumer, int packedLight, int packedOverlay, float red, float green, float blue, float alpha) {\n\t\troot.render(poseStack, vertexConsumer, packedLight, packedOverlay, red, green, blue, alpha);\n\t}\n}"
  },
  {
    "path": "src/main/java/weather2/client/entity/model/WindTurbineModel.java",
    "content": "package weather2.client.entity.model;// Made with Blockbench 4.8.3\n// Exported for Minecraft version 1.17 or later with Mojang mappings\n// Paste this class into your mod and generate all required imports\n\n\nimport com.mojang.blaze3d.vertex.PoseStack;\nimport com.mojang.blaze3d.vertex.VertexConsumer;\nimport net.minecraft.client.model.HierarchicalModel;\nimport net.minecraft.client.model.geom.ModelLayerLocation;\nimport net.minecraft.client.model.geom.ModelPart;\nimport net.minecraft.client.model.geom.PartPose;\nimport net.minecraft.client.model.geom.builders.*;\nimport net.minecraft.resources.ResourceLocation;\nimport net.minecraft.world.entity.Entity;\nimport weather2.Weather;\n\npublic class WindTurbineModel<T extends Entity> extends HierarchicalModel<T> {\n\t// This layer location should be baked with EntityRendererProvider.Context in the entity renderer and passed into this model's constructor\n\tpublic static final ModelLayerLocation LAYER_LOCATION = new ModelLayerLocation(new ResourceLocation(Weather.MODID, \"wind_turbine\"), \"main\");\n\tprivate final ModelPart root;\n\n\tpublic WindTurbineModel(ModelPart root) {\n\t\tthis.root = root;\n\t}\n\n\t@Override\n\tpublic ModelPart root() {\n\t\treturn this.root;\n\t}\n\n\tpublic static LayerDefinition createBodyLayer() {\n\t\tMeshDefinition meshdefinition = new MeshDefinition();\n\t\tPartDefinition partdefinition = meshdefinition.getRoot();\n\n\t\tPartDefinition root = partdefinition.addOrReplaceChild(\"root\", CubeListBuilder.create().texOffs(0, 0).addBox(-6.0F, -2.0F, -6.0F, 12.0F, 2.0F, 12.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(36, 2).addBox(-3.0F, -4.0F, -3.0F, 6.0F, 2.0F, 6.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 24.0F, 0.0F));\n\n\t\tPartDefinition shaft = root.addOrReplaceChild(\"shaft\", CubeListBuilder.create().texOffs(0, 35).addBox(-1.0F, -31.0F, -1.0F, 2.0F, 29.0F, 2.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(0, 20).addBox(-2.0F, -30.0F, -2.0F, 4.0F, 2.0F, 4.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(0, 33).addBox(-9.0F, -29.5F, -0.5F, 18.0F, 1.0F, 1.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(0, 14).addBox(-0.5F, -29.5F, -9.0F, 1.0F, 1.0F, 18.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(2, 16).addBox(-0.5F, -13.5F, -8.0F, 1.0F, 1.0F, 16.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(36, 0).addBox(-8.0F, -13.5F, -0.5F, 16.0F, 1.0F, 1.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(2, 16).addBox(-0.5F, -13.5F, -8.0F, 1.0F, 1.0F, 16.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(0, 14).addBox(-2.0F, -14.0F, -2.0F, 4.0F, 2.0F, 4.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 0.0F, 0.0F));\n\n\t\tPartDefinition fin_r1 = shaft.addOrReplaceChild(\"fin_r1\", CubeListBuilder.create().texOffs(8, 35).addBox(-2.0F, -21.0F, -1.0F, 4.0F, 22.0F, 2.0F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(0.0F, -13.0F, -8.0F, -0.4162F, 0.1863F, 0.3969F));\n\n\t\tPartDefinition fin_r2 = shaft.addOrReplaceChild(\"fin_r2\", CubeListBuilder.create().texOffs(8, 35).addBox(-2.0F, -21.0F, -1.0F, 4.0F, 22.0F, 2.0F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(0.0F, -13.0F, 8.0F, 0.4164F, 0.2075F, -0.436F));\n\n\t\tPartDefinition fin_r3 = shaft.addOrReplaceChild(\"fin_r3\", CubeListBuilder.create().texOffs(34, 31).addBox(-1.0F, -21.0F, -2.0F, 2.0F, 22.0F, 4.0F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(-8.0F, -13.0F, 0.0F, 0.4363F, 0.0F, 0.4712F));\n\n\t\tPartDefinition fin_r4 = shaft.addOrReplaceChild(\"fin_r4\", CubeListBuilder.create().texOffs(34, 31).addBox(-1.0F, -21.0F, -2.0F, 2.0F, 22.0F, 4.0F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(8.0F, -13.0F, 0.0F, -0.4363F, 0.0F, -0.4712F));\n\n\t\treturn LayerDefinition.create(meshdefinition, 128, 128);\n\t}\n\n\t@Override\n\tpublic void setupAnim(T entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) {\n\n\t}\n\n\t@Override\n\tpublic void renderToBuffer(PoseStack poseStack, VertexConsumer vertexConsumer, int packedLight, int packedOverlay, float red, float green, float blue, float alpha) {\n\t\troot.render(poseStack, vertexConsumer, packedLight, packedOverlay, red, green, blue, alpha);\n\t}\n}"
  },
  {
    "path": "src/main/java/weather2/client/entity/model/WindVaneModel.java",
    "content": "package weather2.client.entity.model;// Made with Blockbench 4.8.3\n// Exported for Minecraft version 1.17 or later with Mojang mappings\n// Paste this class into your mod and generate all required imports\n\n\nimport com.mojang.blaze3d.vertex.PoseStack;\nimport com.mojang.blaze3d.vertex.VertexConsumer;\nimport net.minecraft.client.model.HierarchicalModel;\nimport net.minecraft.client.model.geom.ModelLayerLocation;\nimport net.minecraft.client.model.geom.ModelPart;\nimport net.minecraft.client.model.geom.PartPose;\nimport net.minecraft.client.model.geom.builders.*;\nimport net.minecraft.resources.ResourceLocation;\nimport net.minecraft.world.entity.Entity;\nimport weather2.Weather;\nimport weather2.blockentity.AnemometerBlockEntity;\nimport weather2.blockentity.WindVaneBlockEntity;\n\npublic class WindVaneModel<T extends Entity> extends HierarchicalModel<T> {\n\t// This layer location should be baked with EntityRendererProvider.Context in the entity renderer and passed into this model's constructor\n\tpublic static final ModelLayerLocation LAYER_LOCATION = new ModelLayerLocation(new ResourceLocation(Weather.MODID, \"wind_vane\"), \"main\");\n\tprivate final ModelPart root;\n\n\tpublic WindVaneModel(ModelPart root) {\n\t\tthis.root = root;\n\t}\n\n\t@Override\n\tpublic ModelPart root() {\n\t\treturn this.root;\n\t}\n\n\tpublic static LayerDefinition createBodyLayer() {\n\t\tMeshDefinition meshdefinition = new MeshDefinition();\n\t\tPartDefinition partdefinition = meshdefinition.getRoot();\n\n\t\tPartDefinition root = partdefinition.addOrReplaceChild(\"root\", CubeListBuilder.create(), PartPose.offset(0.0F, 24.0F, 0.0F));\n\n\t\tPartDefinition base = root.addOrReplaceChild(\"base\", CubeListBuilder.create().texOffs(13, 15).addBox(-0.25F, -7.0F, -0.25F, 0.5F, 7.0F, 0.5F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(0, 0).addBox(-0.75F, -0.5F, -0.75F, 1.5F, 0.5F, 1.5F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 0.0F, 0.0F));\n\n\t\tPartDefinition middle = base.addOrReplaceChild(\"middle\", CubeListBuilder.create().texOffs(17, 14).addBox(-0.25F, 0.5F, -0.25F, 0.5F, 3.0F, 0.5F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(0, 0).addBox(-0.75F, 3.25F, -0.75F, 1.5F, 1.5F, 1.5F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, -11.5F, 0.0F));\n\n\t\tPartDefinition cube_r1 = middle.addOrReplaceChild(\"cube_r1\", CubeListBuilder.create().texOffs(0, 0).addBox(-0.75F, -0.75F, -0.75F, 1.5F, 1.5F, 1.5F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(0.0F, 4.0F, 0.0F, 0.0F, 0.0F, 0.7854F));\n\n\t\tPartDefinition cube_r2 = middle.addOrReplaceChild(\"cube_r2\", CubeListBuilder.create().texOffs(0, 0).addBox(-0.75F, -0.75F, -0.75F, 1.5F, 1.5F, 1.5F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(0.0F, 4.0F, 0.0F, 0.0F, -0.7854F, 0.0F));\n\n\t\tPartDefinition cube_r3 = middle.addOrReplaceChild(\"cube_r3\", CubeListBuilder.create().texOffs(0, 0).addBox(-0.75F, -0.75F, -0.75F, 1.5F, 1.5F, 1.5F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(0.0F, 4.0F, 0.0F, -0.7854F, 0.0F, 0.0F));\n\n\t\tPartDefinition arm1 = middle.addOrReplaceChild(\"arm1\", CubeListBuilder.create().texOffs(4, 13).addBox(-0.25F, -0.25F, -4.0F, 0.5F, 0.5F, 4.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(0, 28).addBox(-0.05F, -1.0F, -6.0F, 0.05F, 2.0F, 2.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 4.0F, 0.0F));\n\n\t\tPartDefinition top = middle.addOrReplaceChild(\"top\", CubeListBuilder.create().texOffs(0, 0).addBox(-0.75F, -0.75F, -0.75F, 1.5F, 1.5F, 1.5F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(23, 0).addBox(-0.05F, -5.0F, -2.5F, 0.05F, 4.0F, 4.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 0.0F, 0.0F));\n\n\t\tPartDefinition cube_r4 = top.addOrReplaceChild(\"cube_r4\", CubeListBuilder.create().texOffs(0, 0).addBox(-0.75F, -0.75F, -0.75F, 1.5F, 1.5F, 1.5F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(0.0F, 0.0F, 0.0F, 0.0F, -0.7854F, 0.0F));\n\n\t\tPartDefinition cube_r5 = top.addOrReplaceChild(\"cube_r5\", CubeListBuilder.create().texOffs(0, 0).addBox(-0.75F, -0.75F, -0.75F, 1.5F, 1.5F, 1.5F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(0.0F, 0.0F, 0.0F, -0.7854F, 0.0F, 0.0F));\n\n\t\tPartDefinition cube_r6 = top.addOrReplaceChild(\"cube_r6\", CubeListBuilder.create().texOffs(0, 0).addBox(-0.75F, -0.75F, -0.75F, 1.5F, 1.5F, 1.5F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.7854F));\n\n\t\tPartDefinition toparm1 = top.addOrReplaceChild(\"toparm1\", CubeListBuilder.create().texOffs(12, 0).addBox(-0.25F, -0.25F, -4.0F, 0.5F, 0.5F, 4.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(20, 28).addBox(-0.05F, -1.0F, -6.0F, 0.05F, 2.0F, 2.0F, new CubeDeformation(0.0F)), PartPose.offset(0.0F, 0.0F, 0.0F));\n\n\t\tPartDefinition toparm2 = top.addOrReplaceChild(\"toparm2\", CubeListBuilder.create().texOffs(4, 9).addBox(-0.25F, -0.25F, -4.0F, 0.5F, 0.5F, 4.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(25, 28).addBox(-0.05F, -1.0F, -6.0F, 0.05F, 2.0F, 2.0F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(0.0F, 0.0F, 0.0F, 0.0F, 3.1416F, 0.0F));\n\n\t\tPartDefinition arm2 = middle.addOrReplaceChild(\"arm2\", CubeListBuilder.create().texOffs(-1, 11).addBox(-0.25F, -0.25F, -4.0F, 0.5F, 0.5F, 4.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(15, 28).addBox(-0.05F, -1.0F, -6.0F, 0.05F, 2.0F, 2.0F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(0.0F, 4.0F, 0.0F, 0.0F, -1.5708F, 0.0F));\n\n\t\tPartDefinition arm3 = middle.addOrReplaceChild(\"arm3\", CubeListBuilder.create().texOffs(9, 10).addBox(-0.25F, -0.25F, -4.0F, 0.5F, 0.5F, 4.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(10, 28).addBox(-0.05F, -1.0F, -6.0F, 0.05F, 2.0F, 2.0F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(0.0F, 4.0F, 0.0F, 0.0F, 3.1416F, 0.0F));\n\n\t\tPartDefinition arm4 = middle.addOrReplaceChild(\"arm4\", CubeListBuilder.create().texOffs(10, 6).addBox(-0.25F, -0.25F, -4.0F, 0.5F, 0.5F, 4.0F, new CubeDeformation(0.0F))\n\t\t\t\t.texOffs(5, 28).addBox(-0.05F, -1.0F, -6.0F, 0.05F, 2.0F, 2.0F, new CubeDeformation(0.0F)), PartPose.offsetAndRotation(0.0F, 4.0F, 0.0F, 0.0F, 1.5708F, 0.0F));\n\n\t\treturn LayerDefinition.create(meshdefinition, 32, 32);\n\t}\n\n\t@Override\n\tpublic void setupAnim(T entity, float limbSwing, float limbSwingAmount, float ageInTicks, float netHeadYaw, float headPitch) {\n\n\t}\n\n\t@Override\n\tpublic void renderToBuffer(PoseStack poseStack, VertexConsumer vertexConsumer, int packedLight, int packedOverlay, float red, float green, float blue, float alpha) {\n\t\troot.render(poseStack, vertexConsumer, packedLight, packedOverlay, red, green, blue, alpha);\n\t}\n}"
  },
  {
    "path": "src/main/java/weather2/client/entity/particle/ParticleHail.java",
    "content": "package weather2.client.entity.particle;\n\nimport com.corosus.coroutil.util.CoroUtilBlock;\nimport com.corosus.coroutil.util.CoroUtilMisc;\nimport extendedrenderer.particle.entity.ParticleCrossSection;\nimport extendedrenderer.particle.entity.ParticleTexExtraRender;\nimport net.minecraft.client.renderer.texture.TextureAtlasSprite;\nimport net.minecraft.client.multiplayer.ClientLevel;\nimport net.minecraft.sounds.SoundSource;\nimport net.minecraft.sounds.SoundEvents;\nimport net.minecraft.core.BlockPos;\n\npublic class ParticleHail extends ParticleCrossSection {\n\n    public ParticleHail(ClientLevel worldIn, double posXIn, double posYIn, double posZIn, double mX, double mY, double mZ, TextureAtlasSprite par8Item) {\n        super(worldIn, posXIn, posYIn, posZIn, mX, mY, mZ, par8Item);\n    }\n\n    @Override\n    public void tick() {\n        super.tick();\n    }\n\n    @Override\n    public void onHit() {\n        super.onHit();\n        if (CoroUtilMisc.random.nextInt(30) == 0) {\n            level.playLocalSound(CoroUtilBlock.blockPos(x, y, z), SoundEvents.WOOD_BREAK, SoundSource.WEATHER, 0.2F, 2F, false);\n        }\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/client/entity/particle/ParticleSandstorm.java",
    "content": "package weather2.client.entity.particle;\n\nimport net.minecraft.client.Camera;\nimport net.minecraft.client.renderer.texture.TextureAtlasSprite;\nimport net.minecraft.client.multiplayer.ClientLevel;\nimport net.minecraft.world.level.Level;\n\nimport com.mojang.blaze3d.vertex.VertexConsumer;\n\nimport extendedrenderer.particle.entity.ParticleTexFX;\n\npublic class ParticleSandstorm extends ParticleTexFX {\n\n\tpublic double angleToStorm = 0;\n\tpublic int heightLayer = 0;\n\tpublic double distAdj = 0;\n\tpublic boolean lockPosition = false;\n\t\n\tpublic ParticleSandstorm(Level worldIn, double posXIn, double posYIn,\n\t\t\tdouble posZIn, double mX, double mY, double mZ,\n\t\t\tTextureAtlasSprite par8Item) {\n\t\tsuper((ClientLevel) worldIn, posXIn, posYIn, posZIn, mX, mY, mZ, par8Item);\n\t}\n\n\t@Override\n\tpublic void render(VertexConsumer buffer, Camera renderInfo, float partialTicks) {\n\t\tsuper.render(buffer, renderInfo, partialTicks);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/client/entity/render/LightningBoltWeatherNewRenderer.java",
    "content": "package weather2.client.entity.render;\n\nimport com.mojang.blaze3d.vertex.PoseStack;\nimport com.mojang.blaze3d.vertex.VertexConsumer;\nimport java.util.Random;\nimport net.minecraft.client.renderer.MultiBufferSource;\nimport net.minecraft.client.renderer.RenderType;\nimport net.minecraft.client.renderer.entity.EntityRenderer;\nimport net.minecraft.client.renderer.entity.EntityRendererProvider;\nimport net.minecraft.client.renderer.texture.TextureAtlas;\nimport net.minecraft.resources.ResourceLocation;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport org.joml.Matrix4f;\nimport weather2.weathersystem.storm.LightningBoltWeatherNew;\n\n@OnlyIn(Dist.CLIENT)\npublic class LightningBoltWeatherNewRenderer extends EntityRenderer<LightningBoltWeatherNew> {\n   public LightningBoltWeatherNewRenderer(EntityRendererProvider.Context p_174286_) {\n      super(p_174286_);\n   }\n\n   public void render(LightningBoltWeatherNew p_115266_, float p_115267_, float p_115268_, PoseStack p_115269_, MultiBufferSource p_115270_, int p_115271_) {\n      float[] afloat = new float[8];\n      float[] afloat1 = new float[8];\n      float f = 0.0F;\n      float f1 = 0.0F;\n      Random random = new Random(p_115266_.seed);\n\n      for(int i = 7; i >= 0; --i) {\n         afloat[i] = f;\n         afloat1[i] = f1;\n         f += (float)(random.nextInt(11) - 5);\n         f1 += (float)(random.nextInt(11) - 5);\n      }\n\n      VertexConsumer vertexconsumer = p_115270_.getBuffer(RenderType.lightning());\n      Matrix4f matrix4f = p_115269_.last().pose();\n\n      for(int j = 0; j < 4; ++j) {\n         Random random1 = new Random(p_115266_.seed);\n\n         for(int k = 0; k < 3; ++k) {\n            int l = 7;\n            int i1 = 0;\n            if (k > 0) {\n               l = 7 - k;\n            }\n\n            if (k > 0) {\n               i1 = l - 2;\n            }\n\n            float f2 = afloat[l] - f;\n            float f3 = afloat1[l] - f1;\n\n            for(int j1 = l; j1 >= i1; --j1) {\n               float f4 = f2;\n               float f5 = f3;\n               if (k == 0) {\n                  f2 += (float)(random1.nextInt(11) - 5);\n                  f3 += (float)(random1.nextInt(11) - 5);\n               } else {\n                  f2 += (float)(random1.nextInt(31) - 15);\n                  f3 += (float)(random1.nextInt(31) - 15);\n               }\n\n               float f6 = 0.5F;\n               float f7 = 0.45F;\n               float f8 = 0.45F;\n               float f9 = 0.5F;\n               float f10 = 0.1F + (float)j * 0.2F;\n               if (k == 0) {\n                  f10 *= (float)j1 * 0.1F + 1.0F;\n               }\n\n               float f11 = 0.1F + (float)j * 0.2F;\n               if (k == 0) {\n                  f11 *= ((float)j1 - 1.0F) * 0.1F + 1.0F;\n               }\n\n               quad(matrix4f, vertexconsumer, f2, f3, j1, f4, f5, 0.45F, 0.45F, 0.5F, f10, f11, false, false, true, false);\n               quad(matrix4f, vertexconsumer, f2, f3, j1, f4, f5, 0.45F, 0.45F, 0.5F, f10, f11, true, false, true, true);\n               quad(matrix4f, vertexconsumer, f2, f3, j1, f4, f5, 0.45F, 0.45F, 0.5F, f10, f11, true, true, false, true);\n               quad(matrix4f, vertexconsumer, f2, f3, j1, f4, f5, 0.45F, 0.45F, 0.5F, f10, f11, false, true, false, false);\n            }\n         }\n      }\n\n   }\n\n   private static void quad(Matrix4f p_115273_, VertexConsumer p_115274_, float p_115275_, float p_115276_, int p_115277_, float p_115278_, float p_115279_, float p_115280_, float p_115281_, float p_115282_, float p_115283_, float p_115284_, boolean p_115285_, boolean p_115286_, boolean p_115287_, boolean p_115288_) {\n      p_115274_.vertex(p_115273_, p_115275_ + (p_115285_ ? p_115284_ : -p_115284_), (float)(p_115277_ * 16), p_115276_ + (p_115286_ ? p_115284_ : -p_115284_)).color(p_115280_, p_115281_, p_115282_, 0.3F).endVertex();\n      p_115274_.vertex(p_115273_, p_115278_ + (p_115285_ ? p_115283_ : -p_115283_), (float)((p_115277_ + 1) * 16), p_115279_ + (p_115286_ ? p_115283_ : -p_115283_)).color(p_115280_, p_115281_, p_115282_, 0.3F).endVertex();\n      p_115274_.vertex(p_115273_, p_115278_ + (p_115287_ ? p_115283_ : -p_115283_), (float)((p_115277_ + 1) * 16), p_115279_ + (p_115288_ ? p_115283_ : -p_115283_)).color(p_115280_, p_115281_, p_115282_, 0.3F).endVertex();\n      p_115274_.vertex(p_115273_, p_115275_ + (p_115287_ ? p_115284_ : -p_115284_), (float)(p_115277_ * 16), p_115276_ + (p_115288_ ? p_115284_ : -p_115284_)).color(p_115280_, p_115281_, p_115282_, 0.3F).endVertex();\n   }\n\n   public ResourceLocation getTextureLocation(LightningBoltWeatherNew p_115264_) {\n      return TextureAtlas.LOCATION_BLOCKS;\n   }\n}"
  },
  {
    "path": "src/main/java/weather2/client/tile/AnemometerEntityRenderer.java",
    "content": "package weather2.client.tile;\n\nimport com.google.common.collect.Maps;\nimport com.mojang.blaze3d.vertex.PoseStack;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.client.model.Model;\nimport net.minecraft.client.model.geom.ModelPart;\nimport net.minecraft.client.renderer.MultiBufferSource;\nimport net.minecraft.client.renderer.blockentity.BlockEntityRenderer;\nimport net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;\nimport net.minecraft.client.renderer.texture.TextureAtlas;\nimport net.minecraft.client.resources.model.Material;\nimport net.minecraft.resources.ResourceLocation;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.level.LightLayer;\nimport net.minecraft.world.level.block.Block;\nimport net.minecraft.world.level.block.entity.BlockEntity;\nimport net.minecraft.world.phys.Vec3;\nimport org.joml.Vector3f;\nimport weather2.ClientTickHandler;\nimport weather2.Weather;\nimport weather2.WeatherBlocks;\nimport weather2.blockentity.AnemometerBlockEntity;\nimport weather2.client.entity.model.AnemometerModel;\nimport weather2.weathersystem.WeatherManagerClient;\nimport weather2.weathersystem.wind.WindManager;\n\nimport java.util.Map;\nimport java.util.Random;\n\npublic class AnemometerEntityRenderer<T extends BlockEntity> implements BlockEntityRenderer<T> {\n\n    private static Map<String, ResourceLocation> resLocMap = Maps.newHashMap();\n    private static Map<String, Material> materialMap = Maps.newHashMap();\n\n    public static Material getTEMaterial(final String path) {\n        return materialMap.computeIfAbsent(path, m -> createTEMaterial(path));\n    }\n\n    private static Material createTEMaterial(final String path) {\n        return new Material(TextureAtlas.LOCATION_BLOCKS, getTextureTE(path));\n    }\n\n    public static ResourceLocation getTextureTE(String path) {\n        return getTexture(String.format(\"textures/blocks/te/%s.png\", path));\n    }\n\n    public static ResourceLocation getTexture(String path) {\n        return resLocMap.computeIfAbsent(path, k -> getResLoc(path));\n    }\n\n    private static ResourceLocation getResLoc(String path) {\n        return new ResourceLocation(Weather.MODID, path);\n    }\n\n    public static void renderModel(final Material material, final Model model, PoseStack stack, MultiBufferSource buffer, int combinedLightIn, int combinedOverlayIn) {\n        model.renderToBuffer(stack, buffer.getBuffer(model.renderType(material.texture())), combinedLightIn, combinedOverlayIn, 1, 1, 1, 1);\n    }\n\n    private final Block block;\n    protected final AnemometerModel model;\n\n    public AnemometerEntityRenderer(final BlockEntityRendererProvider.Context context) {\n        super();\n        this.block = WeatherBlocks.BLOCK_ANEMOMETER.get();\n        this.model = new AnemometerModel<>(Minecraft.getInstance().getEntityModels().bakeLayer(AnemometerModel.LAYER_LOCATION));\n    }\n\n    @Override\n    public void render(T te, float partialTicks, PoseStack stack, MultiBufferSource buffer, int combinedLightIn, int combinedOverlayIn) {\n        this.model.root().getAllParts().forEach(ModelPart::resetPose);\n\n        //fixes for block\n        ModelPart root = this.model.root();\n        root.x += 8;\n        root.y += 8;\n        root.z += 8;\n        root.xRot += Math.toRadians(180);\n        root.yRot += Math.toRadians(180);\n        root.y -= 32;\n\n        ModelPart top = this.model.root().getChild(\"base\").getChild(\"top\");\n        if (top != null) {\n            WeatherManagerClient weatherMan = ClientTickHandler.weatherManager;\n            if (weatherMan == null) return;\n            WindManager windMan = weatherMan.getWindManager();\n            if (windMan == null) return;\n\n            float lerpAngle = (float) Mth.lerp((double)partialTicks, ((AnemometerBlockEntity) te).smoothAnglePrev, ((AnemometerBlockEntity) te).smoothAngle);\n            float renderAngle = lerpAngle;\n\n            top.yRot = (float) Math.toRadians(renderAngle);\n            boolean shaking = ((AnemometerBlockEntity) te).smoothAngleRotationalVel > 45;\n            if (shaking) {\n                Random rand = new Random(te.getLevel().getGameTime());\n                top.xRot = (float) ((rand.nextFloat() - rand.nextFloat()) * Math.toRadians(7));\n                top.zRot = (float) ((rand.nextFloat() - rand.nextFloat()) * Math.toRadians(7));\n            }\n        }\n\n        renderModel(getTEMaterial(\"anemometer\"), model, stack, buffer, combinedLightIn, combinedOverlayIn);\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/client/tile/WindTurbineEntityRenderer.java",
    "content": "package weather2.client.tile;\n\nimport com.google.common.collect.Maps;\nimport com.mojang.blaze3d.vertex.PoseStack;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.client.model.Model;\nimport net.minecraft.client.model.geom.ModelPart;\nimport net.minecraft.client.renderer.MultiBufferSource;\nimport net.minecraft.client.renderer.blockentity.BlockEntityRenderer;\nimport net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;\nimport net.minecraft.client.renderer.texture.TextureAtlas;\nimport net.minecraft.client.resources.model.Material;\nimport net.minecraft.resources.ResourceLocation;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.level.block.Block;\nimport net.minecraft.world.level.block.entity.BlockEntity;\nimport org.joml.Vector3f;\nimport weather2.ClientTickHandler;\nimport weather2.Weather;\nimport weather2.WeatherBlocks;\nimport weather2.blockentity.WindTurbineBlockEntity;\nimport weather2.blockentity.WindVaneBlockEntity;\nimport weather2.client.entity.model.WindTurbineModel;\nimport weather2.client.entity.model.WindVaneModel;\nimport weather2.weathersystem.WeatherManagerClient;\nimport weather2.weathersystem.wind.WindManager;\n\nimport java.util.Map;\nimport java.util.Random;\n\npublic class WindTurbineEntityRenderer<T extends BlockEntity> implements BlockEntityRenderer<T> {\n\n    private static Map<String, ResourceLocation> resLocMap = Maps.newHashMap();\n    private static Map<String, Material> materialMap = Maps.newHashMap();\n\n    public static Material getTEMaterial(final String path) {\n        return materialMap.computeIfAbsent(path, m -> createTEMaterial(path));\n    }\n\n    private static Material createTEMaterial(final String path) {\n        return new Material(TextureAtlas.LOCATION_BLOCKS, getTextureTE(path));\n    }\n\n    public static ResourceLocation getTextureTE(String path) {\n        return getTexture(String.format(\"textures/blocks/te/%s.png\", path));\n    }\n\n    public static ResourceLocation getTexture(String path) {\n        return resLocMap.computeIfAbsent(path, k -> getResLoc(path));\n    }\n\n    private static ResourceLocation getResLoc(String path) {\n        return new ResourceLocation(Weather.MODID, path);\n    }\n\n    public static void renderModel(final Material material, final Model model, PoseStack stack, MultiBufferSource buffer, int combinedLightIn, int combinedOverlayIn) {\n        model.renderToBuffer(stack, buffer.getBuffer(model.renderType(material.texture())), combinedLightIn, combinedOverlayIn, 1, 1, 1, 1);\n    }\n\n    private final Block block;\n    protected final WindTurbineModel model;\n\n    public WindTurbineEntityRenderer(final BlockEntityRendererProvider.Context context) {\n        super();\n        this.block = WeatherBlocks.BLOCK_WIND_TURBINE.get();\n        this.model = new WindTurbineModel<>(Minecraft.getInstance().getEntityModels().bakeLayer(WindTurbineModel.LAYER_LOCATION));\n    }\n\n    @Override\n    public void render(T te, float partialTicks, PoseStack stack, MultiBufferSource buffer, int combinedLightIn, int combinedOverlayIn) {\n        this.model.root().getAllParts().forEach(ModelPart::resetPose);\n\n        //fixes for block\n        ModelPart root = this.model.root();\n        root.x += 8;\n        root.y += 8;\n        root.z += 8;\n        root.xRot += Math.toRadians(180);\n        root.yRot += Math.toRadians(180);\n        //te.getLevel().getBrightness(LightLayer.BLOCK, te.getBlockPos().above())\n\n        root.y += 16;\n\n        ModelPart top = this.model.root().getChild(\"root\").getChild(\"shaft\");\n        if (top != null) {\n            float lerpAngle = (float) Mth.lerp((double)partialTicks, ((WindTurbineBlockEntity) te).smoothAnglePrev, ((WindTurbineBlockEntity) te).smoothAngle);\n            float renderAngle = lerpAngle;\n\n            top.yRot = (float) Math.toRadians(renderAngle);\n        }\n\n        renderModel(getTEMaterial(\"wind_turbine\"), model, stack, buffer, combinedLightIn, combinedOverlayIn);\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/client/tile/WindVaneEntityRenderer.java",
    "content": "package weather2.client.tile;\n\nimport com.google.common.collect.Maps;\nimport com.mojang.blaze3d.vertex.PoseStack;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.client.model.Model;\nimport net.minecraft.client.model.geom.ModelPart;\nimport net.minecraft.client.renderer.MultiBufferSource;\nimport net.minecraft.client.renderer.blockentity.BlockEntityRenderer;\nimport net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider;\nimport net.minecraft.client.renderer.texture.TextureAtlas;\nimport net.minecraft.client.resources.model.Material;\nimport net.minecraft.resources.ResourceLocation;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.level.block.Block;\nimport net.minecraft.world.level.block.entity.BlockEntity;\nimport org.joml.Vector3f;\nimport weather2.ClientTickHandler;\nimport weather2.Weather;\nimport weather2.WeatherBlocks;\nimport weather2.blockentity.AnemometerBlockEntity;\nimport weather2.blockentity.WindVaneBlockEntity;\nimport weather2.client.entity.model.WindVaneModel;\nimport weather2.weathersystem.WeatherManagerClient;\nimport weather2.weathersystem.wind.WindManager;\n\nimport java.util.Map;\nimport java.util.Random;\n\npublic class WindVaneEntityRenderer<T extends BlockEntity> implements BlockEntityRenderer<T> {\n\n    private static Map<String, ResourceLocation> resLocMap = Maps.newHashMap();\n    private static Map<String, Material> materialMap = Maps.newHashMap();\n\n    public static Material getTEMaterial(final String path) {\n        return materialMap.computeIfAbsent(path, m -> createTEMaterial(path));\n    }\n\n    private static Material createTEMaterial(final String path) {\n        return new Material(TextureAtlas.LOCATION_BLOCKS, getTextureTE(path));\n    }\n\n    public static ResourceLocation getTextureTE(String path) {\n        return getTexture(String.format(\"textures/blocks/te/%s.png\", path));\n    }\n\n    public static ResourceLocation getTexture(String path) {\n        return resLocMap.computeIfAbsent(path, k -> getResLoc(path));\n    }\n\n    private static ResourceLocation getResLoc(String path) {\n        return new ResourceLocation(Weather.MODID, path);\n    }\n\n    public static void renderModel(final Material material, final Model model, PoseStack stack, MultiBufferSource buffer, int combinedLightIn, int combinedOverlayIn) {\n        model.renderToBuffer(stack, buffer.getBuffer(model.renderType(material.texture())), combinedLightIn, combinedOverlayIn, 1, 1, 1, 1);\n    }\n\n    private final Block block;\n    protected final WindVaneModel model;\n\n    public WindVaneEntityRenderer(final BlockEntityRendererProvider.Context context) {\n        super();\n        this.block = WeatherBlocks.BLOCK_WIND_VANE.get();\n        this.model = new WindVaneModel<>(Minecraft.getInstance().getEntityModels().bakeLayer(WindVaneModel.LAYER_LOCATION));\n    }\n\n    @Override\n    public void render(T te, float partialTicks, PoseStack stack, MultiBufferSource buffer, int combinedLightIn, int combinedOverlayIn) {\n        this.model.root().getAllParts().forEach(ModelPart::resetPose);\n\n        //fixes for block\n        ModelPart root = this.model.root();\n        root.x += 8;\n        root.y += 8;\n        root.z += 8;\n        root.xRot += Math.toRadians(180);\n        root.yRot += Math.toRadians(180);\n\n        root.y += 28;\n        float scale = 0.5F;\n        root.offsetScale(new Vector3f(scale, scale, scale));\n\n        ModelPart top = this.model.root().getChild(\"root\").getChild(\"base\").getChild(\"middle\").getChild(\"top\");\n        if (top != null) {\n            WeatherManagerClient weatherMan = ClientTickHandler.weatherManager;\n            if (weatherMan == null) return;\n            WindManager windMan = weatherMan.getWindManager();\n            if (windMan == null) return;\n\n            float lerpAngle = (float) Mth.lerp((double)partialTicks, ((WindVaneBlockEntity) te).smoothAnglePrev, ((WindVaneBlockEntity) te).smoothAngle);\n            float renderAngle = lerpAngle;\n\n            top.yRot = (float) Math.toRadians(renderAngle);\n\n            boolean shaking = windMan.getWindSpeed(te.getBlockPos()) >= 1.5;\n            if (shaking) {\n                Random rand = new Random(te.getLevel().getGameTime());\n                top.yRot += (float) ((rand.nextFloat() - rand.nextFloat()) * Math.toRadians(2));\n                top.zRot = (float) ((rand.nextFloat() - rand.nextFloat()) * Math.toRadians(1));\n            }\n        }\n\n        renderModel(getTEMaterial(\"wind_vane\"), model, stack, buffer, combinedLightIn, combinedOverlayIn);\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/command/CommandWeather2Client.java",
    "content": "package weather2.command;\n\nimport com.corosus.coroutil.util.CULog;\nimport com.corosus.modconfig.ConfigMod;\nimport com.mojang.brigadier.Command;\nimport com.mojang.brigadier.CommandDispatcher;\nimport com.mojang.brigadier.arguments.BoolArgumentType;\nimport com.mojang.brigadier.arguments.FloatArgumentType;\nimport com.mojang.brigadier.arguments.IntegerArgumentType;\nimport com.mojang.brigadier.context.CommandContext;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.client.particle.Particle;\nimport net.minecraft.client.particle.ParticleEngine;\nimport net.minecraft.client.particle.ParticleRenderType;\nimport net.minecraft.commands.CommandSourceStack;\nimport net.minecraft.commands.Commands;\nimport net.minecraft.network.chat.Component;\nimport net.minecraftforge.fml.util.ObfuscationReflectionHelper;\nimport weather2.ClientTickHandler;\nimport weather2.config.ConfigDebug;\nimport weather2.config.ConfigParticle;\n\nimport java.lang.reflect.Field;\nimport java.util.Map;\nimport java.util.Queue;\n\nimport static net.minecraft.commands.Commands.argument;\nimport static net.minecraft.commands.Commands.literal;\n\npublic class CommandWeather2Client {\n\tpublic static void register(final CommandDispatcher<CommandSourceStack> dispatcher) {\n\t\tdispatcher.register(\n\t\t\t\tCommands.literal(getCommandName())\n\t\t\t\t\t\t.then(literal(\"client\")\n\t\t\t\t\t\t\t\t.then(literal(\"particle_rate\")\n\t\t\t\t\t\t\t\t\t\t.then(argument(\"value\", FloatArgumentType.floatArg(0, 1F)).executes(c -> {\n\t\t\t\t\t\t\t\t\t\t\tfloat value = FloatArgumentType.getFloat(c, \"value\");\n\t\t\t\t\t\t\t\t\t\t\tConfigParticle.Particle_effect_rate = value;\n\t\t\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Set weather2 particle effect rate to \" + value), true);\n\t\t\t\t\t\t\t\t\t\t\tConfigMod.forceSaveAllFilesFromRuntimeSettings();\n\t\t\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t/*.then(literal(\"particle_reset_frequency\")\n\t\t\t\t\t\t\t\t\t\t.then(argument(\"seconds\", IntegerArgumentType.integer(0, 20*60*24)).executes(c -> {\n\t\t\t\t\t\t\t\t\t\t\tint value = IntegerArgumentType.getInteger(c, \"seconds\");\n\t\t\t\t\t\t\t\t\t\t\tConfigDebug.Particle_Reset_Frequency = value * 20;\n\t\t\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Set weather2 particle reset frequency \" + value), true);\n\t\t\t\t\t\t\t\t\t\t\tConfigMod.forceSaveAllFilesFromRuntimeSettings();\n\t\t\t\t\t\t\t\t\t\t\tConfigMod.forceSaveAllFilesFromRuntimeSettings();\n\t\t\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t)*/\n\t\t\t\t\t\t\t\t.then(literal(\"particle_vanilla_precipitation\")\n\t\t\t\t\t\t\t\t\t\t.then(argument(\"value\", BoolArgumentType.bool()).executes(c -> {\n\t\t\t\t\t\t\t\t\t\t\tboolean value = BoolArgumentType.getBool(c, \"value\");\n\t\t\t\t\t\t\t\t\t\t\tConfigParticle.Particle_vanilla_precipitation = value;\n\t\t\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Set weather2 to use vanilla particles?: \" + value), true);\n\t\t\t\t\t\t\t\t\t\t\tConfigMod.forceSaveAllFilesFromRuntimeSettings();\n\t\t\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t.then(literal(\"particle_engine\")\n\t\t\t\t\t\t\t\t\t\t.then(literal(\"weather2\").executes(c -> {\n\t\t\t\t\t\t\t\t\t\t\tConfigParticle.Particle_engine_weather2 = true;\n\t\t\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Set particle engine to weather2\"), true);\n\t\t\t\t\t\t\t\t\t\t\tConfigMod.forceSaveAllFilesFromRuntimeSettings();\n\t\t\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t\t\t.then(literal(\"vanilla\").executes(c -> {\n\t\t\t\t\t\t\t\t\t\t\tConfigParticle.Particle_engine_weather2 = false;\n\t\t\t\t\t\t\t\t\t\t\tClientTickHandler.particleManagerExtended().clearParticles();\n\t\t\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Set particle engine to vanilla\"), true);\n\t\t\t\t\t\t\t\t\t\t\tConfigMod.forceSaveAllFilesFromRuntimeSettings();\n\t\t\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t.then(literal(\"debug\")\n\t\t\t\t\t\t\t\t\t.then(literal(\"particles_weather2\").executes(c -> {\n\t\t\t\t\t\t\t\t\t\tmsg(c, \"total particle count: \" + ClientTickHandler.particleManagerExtended().countParticles());\n\t\t\t\t\t\t\t\t\t\tMap<ParticleRenderType, Queue<Particle>> particles = ClientTickHandler.particleManagerExtended().getParticles();\n\t\t\t\t\t\t\t\t\t\tif (particles != null) {\n\t\t\t\t\t\t\t\t\t\t\tmsg(c, \"particle type count: \" + particles.size());\n\t\t\t\t\t\t\t\t\t\t\tmsg(c, \"detailed particle info output to log file\");\n\t\t\t\t\t\t\t\t\t\t\t//Map<ParticleRenderType, Queue<Particle>> particles = particleEngine.particles;\n\t\t\t\t\t\t\t\t\t\t\tint maxCount = 200;\n\t\t\t\t\t\t\t\t\t\t\tint count = 0;\n\t\t\t\t\t\t\t\t\t\t\tCULog.log(\"outputting particle data:\");\n\t\t\t\t\t\t\t\t\t\t\tfor (Map.Entry<ParticleRenderType, Queue<Particle>> type : particles.entrySet()) {\n\t\t\t\t\t\t\t\t\t\t\t\tCULog.log(\"type: \" + type.getKey() + \" -> \" + type.getValue().size() + \" - classpath: \" + type.getKey().getClass().getName());\n\t\t\t\t\t\t\t\t\t\t\t\tif (count > maxCount) {\n\t\t\t\t\t\t\t\t\t\t\t\t\tCULog.log(\"aborted due to large particle type list\");\n\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tcount++;\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tmsg(c, \"failed to get particles list\");\n\t\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\n\t\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t\t.then(literal(\"particles_vanilla\").executes(c -> {\n\t\t\t\t\t\t\t\t\t\tParticleEngine particleEngine = Minecraft.getInstance().particleEngine;\n\t\t\t\t\t\t\t\t\t\t//Map<ParticleRenderType, Queue<Particle>> particles = ObfuscationReflectionHelper.getPrivateValue(ParticleEngine.class, particleEngine, \"particles\");\n\t\t\t\t\t\t\t\t\t\tmsg(c, \"total particle count: \" + particleEngine.countParticles());\n\t\t\t\t\t\t\t\t\t\tmsg(c, \"emitter count: \" + particleEngine.trackingEmitters.size());\n\t\t\t\t\t\t\t\t\t\tMap<ParticleRenderType, Queue<Particle>> particles = getParticles();\n\t\t\t\t\t\t\t\t\t\tif (particles != null) {\n\t\t\t\t\t\t\t\t\t\t\tmsg(c, \"particle type count: \" + particles.size());\n\t\t\t\t\t\t\t\t\t\t\tmsg(c, \"detailed particle info output to log file\");\n\t\t\t\t\t\t\t\t\t\t\t//Map<ParticleRenderType, Queue<Particle>> particles = particleEngine.particles;\n\t\t\t\t\t\t\t\t\t\t\tint maxCount = 200;\n\t\t\t\t\t\t\t\t\t\t\tint count = 0;\n\t\t\t\t\t\t\t\t\t\t\tCULog.log(\"outputting particle data:\");\n\t\t\t\t\t\t\t\t\t\t\tfor (Map.Entry<ParticleRenderType, Queue<Particle>> type : particles.entrySet()) {\n\t\t\t\t\t\t\t\t\t\t\t\tCULog.log(\"type: \" + type.getKey() + \" -> \" + type.getValue().size() + \" - classpath: \" + type.getKey().getClass().getName());\n\t\t\t\t\t\t\t\t\t\t\t\tif (count > maxCount) {\n\t\t\t\t\t\t\t\t\t\t\t\t\tCULog.log(\"aborted due to large particle type list\");\n\t\t\t\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\t\tcount++;\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tmsg(c, \"failed to get particles list\");\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\n\t\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t\t.then(literal(\"particle_engine_render\")\n\t\t\t\t\t\t\t\t\t\t\t.then(argument(\"value\", BoolArgumentType.bool()).executes(c -> {\n\t\t\t\t\t\t\t\t\t\t\t\tboolean value = BoolArgumentType.getBool(c, \"value\");\n\t\t\t\t\t\t\t\t\t\t\t\tConfigDebug.Particle_engine_render = value;\n\t\t\t\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"ConfigParticle.Particle_engine_render: \" + value), true);\n\t\t\t\t\t\t\t\t\t\t\t\tConfigMod.forceSaveAllFilesFromRuntimeSettings();\n\t\t\t\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t.then(literal(\"particle_engine_tick\")\n\t\t\t\t\t\t\t\t\t\t\t.then(argument(\"value\", BoolArgumentType.bool()).executes(c -> {\n\t\t\t\t\t\t\t\t\t\t\t\tboolean value = BoolArgumentType.getBool(c, \"value\");\n\t\t\t\t\t\t\t\t\t\t\t\tConfigDebug.Particle_engine_tick = value;\n\t\t\t\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"ConfigParticle.Particle_engine_tick: \" + value), true);\n\t\t\t\t\t\t\t\t\t\t\t\tConfigMod.forceSaveAllFilesFromRuntimeSettings();\n\t\t\t\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t\t.then(literal(\"reset_vanilla_particles\")\n\t\t\t\t\t\t\t\t\t\t.executes(c -> {\n\t\t\t\t\t\t\t\t\t\t\tMinecraft.getInstance().particleEngine.clearParticles();\n\t\t\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"cleared particles\"), true);\n\t\t\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t)\n\t\t);\n\t}\n\n\tpublic static Map<ParticleRenderType, Queue<Particle>> getParticles() {\n\t\ttry {\n\t\t\tField[] fields = ParticleEngine.class.getDeclaredFields();\n\t\t\tfields[6].setAccessible(true);\n\t\t\treturn (Map<ParticleRenderType, Queue<Particle>>) fields[6].get(Minecraft.getInstance().particleEngine);\n\t\t} catch (IllegalAccessException ex) {\n\t\t\tex.printStackTrace();\n\t\t}\n\t\treturn null;\n\t}\n\n\tpublic static void msg(CommandContext<CommandSourceStack> c, String msg) {\n\t\tc.getSource().sendSuccess(() -> Component.literal(msg), true);\n\t}\n\n\tpublic static String getCommandName() {\n\t\treturn \"weather2\";\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/command/WeatherCommand.java",
    "content": "package weather2.command;\n\nimport com.corosus.coroutil.util.CoroUtilBlock;\nimport com.mojang.brigadier.Command;\nimport com.mojang.brigadier.CommandDispatcher;\nimport com.mojang.brigadier.arguments.FloatArgumentType;\nimport com.mojang.brigadier.arguments.IntegerArgumentType;\nimport com.mojang.brigadier.context.CommandContext;\nimport net.minecraft.commands.CommandSourceStack;\nimport net.minecraft.commands.arguments.RangeArgument;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.nbt.CompoundTag;\nimport net.minecraft.network.chat.Component;\nimport net.minecraft.world.phys.Vec3;\nimport net.minecraftforge.fml.InterModComms;\nimport weather2.ServerTickHandler;\nimport weather2.config.ConfigMisc;\nimport weather2.config.ConfigWind;\nimport weather2.config.WeatherUtilConfig;\nimport weather2.util.WeatherUtil;\nimport weather2.weathersystem.WeatherManagerServer;\nimport weather2.weathersystem.storm.StormObject;\nimport weather2.weathersystem.storm.WeatherObjectParticleStorm;\n\nimport static net.minecraft.commands.Commands.argument;\nimport static net.minecraft.commands.Commands.literal;\n\npublic class WeatherCommand {\n\tpublic static void register(final CommandDispatcher<CommandSourceStack> dispatcher) {\n\t\tdispatcher.register(\n\t\t\t\tliteral(\"weather2\")\n\t\t\t\t\t\t.then(literal(\"kill_all_storms\").requires(s -> s.hasPermission(2)).executes(c -> {\n\t\t\t\t\t\t\tWeatherManagerServer wm = ServerTickHandler.getWeatherManagerFor(c.getSource().getLevel().dimension());\n\t\t\t\t\t\t\twm.clearAllStorms();\n\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Killed all storms\"), true);\n\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t}))\n\t\t\t\t\t\t.then(literal(\"debug\").requires(s -> s.hasPermission(2))\n\t\t\t\t\t\t\t\t.then(literal(\"print_grab_list\").executes(c -> {\n\t\t\t\t\t\t\t\t\tWeatherUtil.testAllBlocks();\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Tornado grab list printed to debug.log\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t.then(literal(\"storm_chance\").executes(c -> {\n\t\t\t\t\t\t\t\t\tWeatherManagerServer wm = ServerTickHandler.getWeatherManagerFor(c.getSource().getLevel().dimension());\n\t\t\t\t\t\t\t\t\tfloat chance = wm.getBiomeBasedStormSpawnChanceInArea(CoroUtilBlock.blockPos(c.getSource().getPosition().x, c.getSource().getPosition().y, c.getSource().getPosition().z));\n\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Likelyhood of storms to spawn here within 1024 blocks: \" + (chance * 100)), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.then(literal(\"wind_event\").requires(s -> s.hasPermission(2))\n\t\t\t\t\t\t\t\t.then(literal(\"clear\").executes(c -> {\n\t\t\t\t\t\t\t\t\tWeatherManagerServer wm = ServerTickHandler.getWeatherManagerFor(c.getSource().getLevel().dimension());\n\t\t\t\t\t\t\t\t\twm.getWindManager().stopLowWindEvent();\n\t\t\t\t\t\t\t\t\twm.getWindManager().stopHighWindEvent();\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Stopped any active high or low wind events\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t.then(literal(\"high\").executes(c -> {\n\t\t\t\t\t\t\t\t\tWeatherManagerServer wm = ServerTickHandler.getWeatherManagerFor(c.getSource().getLevel().dimension());\n\t\t\t\t\t\t\t\t\twm.getWindManager().stopLowWindEvent();\n\t\t\t\t\t\t\t\t\twm.getWindManager().startHighWindEvent();\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Started high wind event\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t.then(literal(\"low\").executes(c -> {\n\t\t\t\t\t\t\t\t\tWeatherManagerServer wm = ServerTickHandler.getWeatherManagerFor(c.getSource().getLevel().dimension());\n\t\t\t\t\t\t\t\t\twm.getWindManager().stopHighWindEvent();\n\t\t\t\t\t\t\t\t\twm.getWindManager().startLowWindEvent();\n\t\t\t\t\t\t\t\t\twm.getWindManager().windSpeedGlobal = (float) (ConfigWind.windSpeedMin + 0.2F);\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Started low wind event\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.then(literal(\"wind_angle\").requires(s -> s.hasPermission(2))\n\t\t\t\t\t\t\t\t.then(argument(\"angle\", IntegerArgumentType.integer(0, 359)).executes(c -> {\n\t\t\t\t\t\t\t\t\tint angle = IntegerArgumentType.getInteger(c, \"angle\");\n\t\t\t\t\t\t\t\t\tWeatherManagerServer wm = ServerTickHandler.getWeatherManagerFor(c.getSource().getLevel().dimension());\n\t\t\t\t\t\t\t\t\twm.getWindManager().windAngleGlobal = angle;\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Set wind angle for clouds to \" + angle), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.then(literal(\"wind_speed\").requires(s -> s.hasPermission(2))\n\t\t\t\t\t\t\t\t.then(argument(\"speed\", FloatArgumentType.floatArg(0, 1.5F)).executes(c -> {\n\t\t\t\t\t\t\t\t\tfloat speed = FloatArgumentType.getFloat(c, \"speed\");\n\t\t\t\t\t\t\t\t\tWeatherManagerServer wm = ServerTickHandler.getWeatherManagerFor(c.getSource().getLevel().dimension());\n\t\t\t\t\t\t\t\t\twm.getWindManager().windSpeedGlobal = speed;\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Set wind speed for clouds to \" + speed), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.then(literal(\"server_precipitation\").requires(s -> s.hasPermission(2))\n\t\t\t\t\t\t\t\t.then(argument(\"amount\", FloatArgumentType.floatArg(0, 1.0F)).executes(c -> {\n\t\t\t\t\t\t\t\t\tfloat amount = FloatArgumentType.getFloat(c, \"amount\");\n\t\t\t\t\t\t\t\t\tWeatherManagerServer wm = ServerTickHandler.getWeatherManagerFor(c.getSource().getLevel().dimension());\n\t\t\t\t\t\t\t\t\twm.vanillaRainAmountOnServer = amount;\n\t\t\t\t\t\t\t\t\tif (ConfigMisc.overcastMode) {\n\t\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Server precipitation amount set to \" + amount), true);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"overcastMode not on, this will change nothing\"), true);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.then(literal(\"summon\").requires(s -> s.hasPermission(2)).requires(s -> WeatherUtilConfig.listDimensionsWeather.contains(s.getLevel().dimension().location().toString()))\n\t\t\t\t\t\t\t\t.then(literal(\"storm_rain\").executes(c -> {\n\t\t\t\t\t\t\t\t\tStormObject stormObject = summonStorm(c, StormObject.STATE_NORMAL);\n\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned rain storm\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t.then(literal(\"storm_lightning\").executes(c -> {\n\t\t\t\t\t\t\t\t\tStormObject stormObject = summonStorm(c, StormObject.STATE_THUNDER);\n\n\t\t\t\t\t\t\t\t\tstormObject.initRealStorm(null, null);\n\t\t\t\t\t\t\t\t\tstormObject.levelCurIntensityStage = StormObject.STATE_THUNDER;\n\t\t\t\t\t\t\t\t\tstormObject.levelStormIntensityMax = StormObject.STATE_THUNDER;\n\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned lightning storm\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t.then(literal(\"storm_highwind\").executes(c -> {\n\t\t\t\t\t\t\t\t\tStormObject stormObject = summonStorm(c, StormObject.STATE_HIGHWIND);\n\n\t\t\t\t\t\t\t\t\tstormObject.initRealStorm(null, null);\n\t\t\t\t\t\t\t\t\tstormObject.levelCurIntensityStage = StormObject.STATE_HIGHWIND;\n\t\t\t\t\t\t\t\t\tstormObject.levelStormIntensityMax = StormObject.STATE_HIGHWIND;\n\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned highwind storm\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t.then(literal(\"storm_hail\").executes(c -> {\n\t\t\t\t\t\t\t\t\tStormObject stormObject = summonStorm(c, StormObject.STATE_HAIL);\n\n\t\t\t\t\t\t\t\t\tstormObject.initRealStorm(null, null);\n\t\t\t\t\t\t\t\t\tstormObject.levelCurIntensityStage = StormObject.STATE_HAIL;\n\t\t\t\t\t\t\t\t\tstormObject.levelStormIntensityMax = StormObject.STATE_HAIL;\n\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned hail storm\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t.then(literal(\"tornado_f0\").executes(c -> {\n\t\t\t\t\t\t\t\t\tStormObject stormObject = summonStorm(c, StormObject.STATE_FORMING);\n\t\t\t\t\t\t\t\t\tstormObject.levelStormIntensityMax = StormObject.STATE_STAGE1;\n\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned forming tornado\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t.then(literal(\"tornado_f1\").executes(c -> {\n\t\t\t\t\t\t\t\t\tStormObject stormObject = summonStorm(c, StormObject.STATE_STAGE1);\n\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned f1 tornado\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t.then(literal(\"tornado_f2\").executes(c -> {\n\t\t\t\t\t\t\t\t\tStormObject stormObject = summonStorm(c, StormObject.STATE_STAGE2);\n\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned f2 tornado\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t.then(literal(\"tornado_f3\").executes(c -> {\n\t\t\t\t\t\t\t\t\tStormObject stormObject = summonStorm(c, StormObject.STATE_STAGE3);\n\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned f3 tornado\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t.then(literal(\"tornado_f4\").executes(c -> {\n\t\t\t\t\t\t\t\t\tStormObject stormObject = summonStorm(c, StormObject.STATE_STAGE4);\n\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned f4 tornado\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t.then(literal(\"sharknado\").executes(c -> {\n\t\t\t\t\t\t\t\t\tStormObject stormObject = summonStorm(c, StormObject.STATE_STAGE1);\n\t\t\t\t\t\t\t\t\tstormObject.levelStormIntensityMax = StormObject.STATE_STAGE4;\n\n\t\t\t\t\t\t\t\t\tstormObject.setSharknado(true);\n\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned sharknado\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t.then(literal(\"firenado_f0\").executes(c -> {\n\t\t\t\t\t\t\t\t\tStormObject stormObject = summonStorm(c, StormObject.STATE_FORMING);\n\t\t\t\t\t\t\t\t\tstormObject.levelStormIntensityMax = StormObject.STATE_STAGE4;\n\t\t\t\t\t\t\t\t\tstormObject.isFirenado = true;\n\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned firenado\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t.then(literal(\"firenado_f1\").executes(c -> {\n\t\t\t\t\t\t\t\t\tStormObject stormObject = summonStorm(c, StormObject.STATE_STAGE1);\n\t\t\t\t\t\t\t\t\tstormObject.levelStormIntensityMax = StormObject.STATE_STAGE4;\n\t\t\t\t\t\t\t\t\tstormObject.isFirenado = true;\n\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned firenado\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t/*.then(literal(\"tornado_player_baby\").executes(c -> {\n\t\t\t\t\t\t\t\t\tStormObject stormObject = summonStorm(c, StormObject.STATE_STAGE1);\n\n\t\t\t\t\t\t\t\t\tstormObject.setupPlayerControlledTornado(c.getSource().getEntity());\n\t\t\t\t\t\t\t\t\tstormObject.setPlayerControlledTimeLeft(800);\n\t\t\t\t\t\t\t\t\tstormObject.setBaby(true);\n\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned baby player tornado\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t.then(literal(\"tornado_baby\").executes(c -> {\n\t\t\t\t\t\t\t\t\tStormObject stormObject = summonStorm(c, StormObject.STATE_STAGE1);\n\n\t\t\t\t\t\t\t\t\tstormObject.setBaby(true);\n\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned baby tornado\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))*/\n\t\t\t\t\t\t\t\t.then(literal(\"tornado_player\").executes(c -> {\n\t\t\t\t\t\t\t\t\tStormObject stormObject = summonStorm(c, StormObject.STATE_STAGE1);\n\n\t\t\t\t\t\t\t\t\tstormObject.setupPlayerControlledTornado(c.getSource().getEntity());\n\t\t\t\t\t\t\t\t\tstormObject.setPlayerControlledTimeLeft(600);\n\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned player tornado\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))/*\n\t\t\t\t\t\t\t\t.then(literal(\"tornado_pet\").executes(c -> {\n\t\t\t\t\t\t\t\t\tStormObject stormObject = summonStorm(c, StormObject.STATE_STAGE1);\n\n\t\t\t\t\t\t\t\t\tstormObject.setupPlayerControlledTornado(c.getSource().getEntity());\n\t\t\t\t\t\t\t\t\tstormObject.setPlayerControlledTimeLeft(-1);\n\t\t\t\t\t\t\t\t\tstormObject.setPet(true);\n\t\t\t\t\t\t\t\t\tstormObject.setPetGrabsItems(true);\n\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned pet tornado\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t.then(literal(\"tornado_pet_no_item_grab\").executes(c -> {\n\t\t\t\t\t\t\t\t\tStormObject stormObject = summonStorm(c, StormObject.STATE_STAGE1);\n\n\t\t\t\t\t\t\t\t\tstormObject.setupPlayerControlledTornado(c.getSource().getEntity());\n\t\t\t\t\t\t\t\t\tstormObject.setPlayerControlledTimeLeft(-1);\n\t\t\t\t\t\t\t\t\tstormObject.setPet(true);\n\t\t\t\t\t\t\t\t\tstormObject.setPetGrabsItems(false);\n\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned pet tornado with no item grabbing\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t.then(literal(\"tornadotestimc\").executes(c -> {\n\n\t\t\t\t\t\t\t\t\tInterModComms.sendTo(\"weather2\", \"sharknado\", () -> {\n\t\t\t\t\t\t\t\t\t\tCompoundTag tag = new CompoundTag();\n\t\t\t\t\t\t\t\t\t\ttag.putString(\"uuid\", c.getSource().getEntity().getUUID().toString());\n\t\t\t\t\t\t\t\t\t\ttag.putInt(\"time_ticks\", 1200);\n\t\t\t\t\t\t\t\t\t\ttag.putBoolean(\"baby\", false);\n\t\t\t\t\t\t\t\t\t\ttag.putBoolean(\"sharknado\", true);\n\t\t\t\t\t\t\t\t\t\ttag.putString(\"dimension\", c.getSource().getEntity().getLevel().dimension().location().toString());\n\t\t\t\t\t\t\t\t\t\treturn tag;\n\t\t\t\t\t\t\t\t\t});\n\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned tornado test\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))*/\n\t\t\t\t\t\t\t\t.then(literal(\"tornado_f0_max\").executes(c -> {\n\t\t\t\t\t\t\t\t\tStormObject stormObject = summonStorm(c, StormObject.STATE_FORMING);\n\t\t\t\t\t\t\t\t\tstormObject.levelStormIntensityMax = StormObject.STATE_FORMING;\n\t\t\t\t\t\t\t\t\tstormObject.alwaysProgresses = false;\n\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned tornado\"), true);\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t.then(literal(\"sandstorm_try\").executes(c -> {\n\n\t\t\t\t\t\t\t\t\tWeatherManagerServer wm = ServerTickHandler.getWeatherManagerFor(c.getSource().getLevel().dimension());\n\t\t\t\t\t\t\t\t\tboolean sandstormMade = wm.trySpawnParticleStormNearPos(c.getSource().getLevel(), c.getSource().getPosition(), WeatherObjectParticleStorm.StormType.SANDSTORM);\n\t\t\t\t\t\t\t\t\tif (sandstormMade) {\n\t\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned sandstorm\"), true);\n\t\t\t\t\t\t\t\t\t\twm.getWindManager().stopLowWindEvent();\n\t\t\t\t\t\t\t\t\t\twm.getWindManager().startHighWindEvent();\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Couldn't spawn, try being in a large desert\"), true);\n\t\t\t\t\t\t\t\t\t}\n\n\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t.then(literal(\"snowstorm_try\").executes(c -> {\n\n\t\t\t\t\t\t\t\t\tWeatherManagerServer wm = ServerTickHandler.getWeatherManagerFor(c.getSource().getLevel().dimension());\n\t\t\t\t\t\t\t\t\tboolean sandstormMade = wm.trySpawnParticleStormNearPos(c.getSource().getLevel(), c.getSource().getPosition(), WeatherObjectParticleStorm.StormType.SNOWSTORM);\n\t\t\t\t\t\t\t\t\tif (sandstormMade) {\n\t\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned snowstorm\"), true);\n\t\t\t\t\t\t\t\t\t\twm.getWindManager().stopLowWindEvent();\n\t\t\t\t\t\t\t\t\t\twm.getWindManager().startHighWindEvent();\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Couldn't spawn, try being in a large snowy area\"), true);\n\t\t\t\t\t\t\t\t\t}\n\n\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t.then(literal(\"sandstorm_force\").executes(c -> {\n\n\t\t\t\t\t\t\t\t\tWeatherManagerServer wm = ServerTickHandler.getWeatherManagerFor(c.getSource().getLevel().dimension());\n\n\t\t\t\t\t\t\t\t\twm.spawnParticleStorm(CoroUtilBlock.blockPos(c.getSource().getPosition()), WeatherObjectParticleStorm.StormType.SANDSTORM);\n\t\t\t\t\t\t\t\t\twm.getWindManager().stopLowWindEvent();\n\t\t\t\t\t\t\t\t\twm.getWindManager().startHighWindEvent();\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned sandstorm\"), true);\n\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\t\t\t\t\t\t\t\t.then(literal(\"snowstorm_force\").executes(c -> {\n\n\t\t\t\t\t\t\t\t\tWeatherManagerServer wm = ServerTickHandler.getWeatherManagerFor(c.getSource().getLevel().dimension());\n\n\t\t\t\t\t\t\t\t\twm.spawnParticleStorm(CoroUtilBlock.blockPos(c.getSource().getPosition()), WeatherObjectParticleStorm.StormType.SNOWSTORM);\n\t\t\t\t\t\t\t\t\twm.getWindManager().stopLowWindEvent();\n\t\t\t\t\t\t\t\t\twm.getWindManager().startHighWindEvent();\n\t\t\t\t\t\t\t\t\tc.getSource().sendSuccess(() -> Component.literal(\"Summoned snowstorm\"), true);\n\n\t\t\t\t\t\t\t\t\treturn Command.SINGLE_SUCCESS;\n\t\t\t\t\t\t\t\t}))\n\n\t\t\t\t\t\t)\n\t\t);\n\n\t}\n\n\tprivate static StormObject summonStorm(CommandContext<CommandSourceStack> c, int intensity) {\n\t\tWeatherManagerServer wm = ServerTickHandler.getWeatherManagerFor(c.getSource().getLevel().dimension());\n\t\tStormObject stormObject = new StormObject(wm);\n\t\t\n\t\tstormObject.setupStorm(c.getSource().getEntity());\n\t\tstormObject.levelCurIntensityStage = intensity;\n\t\tstormObject.levelStormIntensityMax = intensity;\n\t\tstormObject.initPositions(new Vec3(c.getSource().getPosition().x, StormObject.layers.get(stormObject.layer), c.getSource().getPosition().z));\n\n\t\twm.addStormObject(stormObject);\n\t\twm.syncStormNew(stormObject);\n\t\treturn stormObject;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/config/ClientConfigData.java",
    "content": "package weather2.config;\n\nimport net.minecraft.nbt.CompoundTag;\n\n/**\n * Used for anything that needs to be used on both client and server side, to avoid config mismatch between dedicated server and clients\n */\npublic class ClientConfigData {\n\n    public boolean overcastMode = false;\n    public boolean Storm_Tornado_grabPlayer = true;\n    public boolean Storm_Tornado_grabPlayersOnly = false;\n    public boolean Storm_Tornado_grabMobs = true;\n    public boolean Storm_Tornado_grabAnimals = true;\n    public boolean Storm_Tornado_grabItems = false;\n    public boolean Storm_Tornado_grabVillagers = true;\n    public boolean Aesthetic_Only_Mode = false;\n\n    /**\n     * For client side\n     *\n     * @param nbt\n     */\n    public void readNBT(CompoundTag nbt) {\n        overcastMode = nbt.getBoolean(\"overcastMode\");\n        Storm_Tornado_grabPlayer = nbt.getBoolean(\"Storm_Tornado_grabPlayer\");\n        Storm_Tornado_grabPlayersOnly = nbt.getBoolean(\"Storm_Tornado_grabPlayersOnly\");\n        Storm_Tornado_grabMobs = nbt.getBoolean(\"Storm_Tornado_grabMobs\");\n        Storm_Tornado_grabAnimals = nbt.getBoolean(\"Storm_Tornado_grabAnimals\");\n        Storm_Tornado_grabVillagers = nbt.getBoolean(\"Storm_Tornado_grabVillagers\");\n        Storm_Tornado_grabItems = nbt.getBoolean(\"Storm_Tornado_grabItems\");\n        Aesthetic_Only_Mode = nbt.getBoolean(\"Aesthetic_Only_Mode\");\n    }\n\n    /**\n     * For server side\n     *\n     * @param data\n     */\n    public static void writeNBT(CompoundTag data) {\n\n        data.putBoolean(\"overcastMode\", ConfigMisc.overcastMode);\n        data.putBoolean(\"Storm_Tornado_grabPlayer\", ConfigTornado.Storm_Tornado_grabPlayer);\n        data.putBoolean(\"Storm_Tornado_grabPlayersOnly\", ConfigTornado.Storm_Tornado_grabPlayersOnly);\n        data.putBoolean(\"Storm_Tornado_grabMobs\", ConfigTornado.Storm_Tornado_grabMobs);\n        data.putBoolean(\"Storm_Tornado_grabAnimals\", ConfigTornado.Storm_Tornado_grabAnimals);\n        data.putBoolean(\"Storm_Tornado_grabVillagers\", ConfigTornado.Storm_Tornado_grabVillagers);\n        data.putBoolean(\"Storm_Tornado_grabItems\", ConfigTornado.Storm_Tornado_grabItems);\n        data.putBoolean(\"Aesthetic_Only_Mode\", ConfigMisc.Aesthetic_Only_Mode);\n\n\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/weather2/config/ConfigDebug.java",
    "content": "package weather2.config;\n\nimport com.corosus.modconfig.IConfigCategory;\nimport weather2.Weather;\n\nimport java.io.File;\n\n\npublic class ConfigDebug implements IConfigCategory {\n\n    //public static int Particle_Reset_Frequency = 20*60*20;\n    public static int Particle_Reset_Frequency = 0;\n    public static boolean Particle_engine_render = true;\n    public static boolean Particle_engine_tick = true;\n\n    @Override\n    public String getName() {\n        return \"Debug\";\n    }\n\n    @Override\n    public String getRegistryName() {\n        return Weather.MODID + getName();\n    }\n\n    @Override\n    public String getConfigFileName() {\n        return \"Weather2\" + File.separator + getName();\n    }\n\n    @Override\n    public String getCategory() {\n        return \"Weather2: \" + getName();\n    }\n\n    @Override\n    public void hookUpdatedValues() {\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/config/ConfigFoliage.java",
    "content": "package weather2.config;\n\nimport com.corosus.modconfig.IConfigCategory;\nimport weather2.Weather;\n\nimport java.io.File;\n\npublic class ConfigFoliage implements IConfigCategory {\n\n    /*public static int foliageShaderRange = 40;\n    public static int Thread_Foliage_Process_Delay = 1000;\n    public static boolean extraGrass = false;*/\n\n    @Override\n    public String getName() {\n        return \"Foliage\";\n    }\n\n    @Override\n    public String getRegistryName() {\n        return Weather.MODID + getName();\n    }\n\n    @Override\n    public String getConfigFileName() {\n        return \"Weather2\" + File.separator + getName();\n    }\n\n    @Override\n    public String getCategory() {\n        return \"Weather2: \" + getName();\n    }\n\n    @Override\n    public void hookUpdatedValues() {\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/config/ConfigMisc.java",
    "content": "package weather2.config;\n\nimport java.io.File;\nimport java.util.ArrayList;\nimport java.util.Arrays;\n\nimport com.corosus.modconfig.ConfigComment;\nimport com.corosus.modconfig.IConfigCategory;\nimport weather2.Weather;\nimport weather2.weathersystem.storm.StormObject;\n\n\npublic class ConfigMisc implements IConfigCategory {\n\t\n\t//misc\n\t//public static boolean Misc_proxyRenderOverrideEnabled = true;\n\t//public static boolean Misc_takeControlOfGlobalRain = true;\n\n\t//cutoff a bit extra, noticed lots of storms being insta killed on creation\n\tpublic static int Misc_simBoxRadiusCutoff = 1024+100;\n\tpublic static int Misc_simBoxRadiusSpawn = 1024;\n\t/*public static boolean Misc_ForceVanillaCloudsOff = false;\n\tpublic static int Misc_AutoDataSaveIntervalInTicks = 20*60*30;\n\tpublic static boolean consoleDebug = false;\n\n\tpublic static boolean radarCloudDebug = false;*/\n\t\n\t//Weather\n\t@ConfigComment(\"If true, lets server side do vanilla weather rules, weather2 will only make storms when server side says 'rain' is on\")\n\tpublic static boolean overcastMode = false;\n\t@ConfigComment(\"Used if overcastMode is off, 1 = lock weather on, 0 = lock weather off, -1 = dont lock anything, let server do whatever\")\n\tpublic static int lockServerWeatherMode = 0; //is only used if overcastMode is off\n\t//cloudOption\n\t@ConfigComment(\"How many ticks between cloud particle spawning\")\n\tpublic static int Cloud_ParticleSpawnDelay = 2;\n\t@ConfigComment(\"Distance between cloud formations, not particles, this includes invisible cloudless formations used during partial cloud coverage\")\n\tpublic static int Cloud_Formation_MinDistBetweenSpawned = 300;\n\t@ConfigComment(\"For a second layer of passive non storm progressing cloudOption\")\n\tpublic static boolean Cloud_Layer1_Enable = false;\n\tpublic static int Cloud_Layer0_Height = 200 + 64;\n\tpublic static int Cloud_Layer1_Height = 350 + 64;\n\t@ConfigComment(\"Not used at the moment\")\n\tpublic static int Cloud_Layer2_Height = 500 + 64;\n\n\t@ConfigComment(\"How much to randomly change cloud coverage % amount, performed every 10 seconds\")\n\tpublic static double Cloud_Coverage_Random_Change_Amount = 0.05D;\n\n\t@ConfigComment(\"Minimum percent of cloud coverage, supports negative for extended cloudless sky coverage\")\n\tpublic static double Cloud_Coverage_Min_Percent = 0D;\n\n\t@ConfigComment(\"Maximum percent of cloud coverage, supports over 100% for extended full cloud sky coverage\")\n\tpublic static double Cloud_Coverage_Max_Percent = 100D;\n\t\n\t/*public static int Thread_Particle_Process_Delay = 400;\n\t//sound\n\tpublic static double volWindScale = 0.05D;\n\tpublic static double volWaterfallScale = 0.5D;\n\tpublic static double volWindTreesScale = 0.5D;\n\tpublic static double volWindLightningScale = 1D;*/\n\t\n\t//blocks\n\tpublic static double sirenActivateDistance = 256D;\n\t/*public static double sensorActivateDistance = 256D;\n\tpublic static boolean Block_WeatherMachineNoTornadosOrCyclones = false;\n\n\tpublic static boolean Block_WeatherMachineNoRecipe = false;\n\tpublic static boolean Block_SensorNoRecipe = false;\n\tpublic static boolean Block_SirenNoRecipe = false;\n\tpublic static boolean Block_SirenManualNoRecipe = false;\n\tpublic static boolean Block_WindVaneNoRecipe = false;\n\tpublic static boolean Block_AnemometerNoRecipe = false;\n\tpublic static boolean Block_WeatherForecastNoRecipe = false;\n\tpublic static boolean Block_WeatherDeflectorNoRecipe = false;\n\tpublic static boolean Block_SandLayerNoRecipe = false;\n\tpublic static boolean Block_SandNoRecipe = false;\n\tpublic static boolean Item_PocketSandNoRecipe = false;\n\t@ConfigComment(\"Disabling this recipe will keep them from using other recipes since it depends on this item\")\n\tpublic static boolean Item_WeatherItemNoRecipe = false;*/\n\n\t\n\t//dimension settings\n\tpublic static String Dimension_List_Weather = \"minecraft:overworld, tropicraft:tropicraft\";\n\tpublic static String Dimension_List_Clouds = \"minecraft:overworld, tropicraft:tropicraft\";\n\tpublic static String Dimension_List_Storms = \"minecraft:overworld, tropicraft:tropicraft\";\n\tpublic static String Dimension_List_WindEffects = \"minecraft:overworld, tropicraft:tropicraft\";\n\n\t/*public static boolean Villager_MoveInsideForStorms = true;\n\tpublic static int Villager_MoveInsideForStorms_Dist = 256;\n\n\tpublic static double shaderParticleRateAmplifier = 3D;*/\n\n\tpublic static boolean blockBreakingInvokesCancellableEvent = false;\n\n\t/*@ConfigComment(\"If true, will cancel vanilla behavior of setting clear weather when the player sleeps, for global overcast mode\")\n\tpublic static boolean Global_Overcast_Prevent_Rain_Reset_On_Sleep = false;*/\n\n\t/*@ConfigComment(\"Use if you are on a server with weather but want it ALL off client side for performance reasons, overrides basically every client based setting\")\n\tpublic static boolean Client_PotatoPC_Mode = false;*/\n\n\t@ConfigComment(\"Server and client side, Locks down the mod to only do wind, leaves, foliage shader if on, etc. No weather systems, turns overcast mode on\")\n\tpublic static boolean Aesthetic_Only_Mode = false;\n\n\t@ConfigComment(\"Runs regardless of Aesthetic_Only_Mode, makes snowstorms possible everywhere\")\n\tpublic static boolean Winter_Wonderland = false;\n\n\tpublic ConfigMisc() {\n\t\t\n\t}\n\n\t@Override\n\tpublic String getName() {\n\t\treturn \"Misc\";\n\t}\n\n\t@Override\n\tpublic String getRegistryName() {\n\t\treturn Weather.MODID + getName();\n\t}\n\n\t@Override\n\tpublic String getConfigFileName() {\n\t\treturn \"Weather2\" + File.separator + getName();\n\t}\n\n\t@Override\n\tpublic String getCategory() {\n\t\treturn \"Weather2: \" + getName();\n\t}\n\n\t@Override\n\tpublic void hookUpdatedValues() {\n\t\t//Weather.dbg(\"block list processing disabled\");\n\t\t//TODO: 1.14 uncomment\n\t\t//WeatherUtil.doBlockList();\n\t\tWeatherUtilConfig.processLists();\n\t\t\n\t\tStormObject.static_YPos_layer0 = Cloud_Layer0_Height;\n\t\tStormObject.static_YPos_layer1 = Cloud_Layer1_Height;\n\t\tStormObject.static_YPos_layer2 = Cloud_Layer2_Height;\n\t\tStormObject.layers = new ArrayList<>(Arrays.asList(StormObject.static_YPos_layer0, StormObject.static_YPos_layer1, StormObject.static_YPos_layer2));\n\t}\n\n}\n"
  },
  {
    "path": "src/main/java/weather2/config/ConfigParticle.java",
    "content": "package weather2.config;\n\nimport com.corosus.modconfig.ConfigComment;\nimport com.corosus.modconfig.IConfigCategory;\nimport weather2.Weather;\n\nimport java.io.File;\n\n\npublic class ConfigParticle implements IConfigCategory {\n\n\n\n    //particles\n\t/*public static boolean Wind_Particle_leafs = true;\n    @ConfigComment(\"Currently used for rates of leaf, waterfall, and fire particles\")\n\tpublic static double Wind_Particle_effect_rate = 0.7D;\n\tpublic static boolean Wind_Particle_waterfall = true;\n\t//public static boolean Wind_Particle_snow = false;\n\tpublic static boolean Wind_Particle_fire = false;\n\t@ConfigComment(\"Enables or disables all precipitation particle types\")\n\tpublic static boolean Particle_RainSnow = true;\n    public static boolean Particle_Rain = false;\n    public static boolean Particle_Rain_GroundSplash = true;\n    public static boolean Particle_Rain_DownfallSheet = false;\n\tpublic static boolean Particle_VanillaAndWeatherOnly = false;*/\n\n    @ConfigComment(\"Adjust amount of precipitation based particles, works as a multiplier\")\n\tpublic static double Precipitation_Particle_effect_rate = 0.7D;\n\t//public static double Sandstorm_Particle_Debris_effect_rate = 0.6D;\n\t//public static double Sandstorm_Particle_Dust_effect_rate = 0.6D;\n\n    @ConfigComment(\"Adjust amount of all weather2 based particles, works as a multiplier\")\n    public static double Particle_effect_rate = 1D;\n\n    @ConfigComment(\"If true, uses vanilla rain/snow non particle precipitation\")\n    public static boolean Particle_vanilla_precipitation = false;\n\n    @ConfigComment(\"If set to false, particles are spawned in using the vanilla particle renderer, may cause issues, performance seems worse\")\n    public static boolean Particle_engine_weather2 = true;\n\n    @ConfigComment(\"Extra flying block particles to spawn when the tornado rips up a block\")\n    public static int Particle_Tornado_extraParticleCubes = 2;\n\n    @Override\n    public String getName() {\n        return \"Particle\";\n    }\n\n    @Override\n    public String getRegistryName() {\n        return Weather.MODID + getName();\n    }\n\n    @Override\n    public String getConfigFileName() {\n        return \"Weather2\" + File.separator + getName();\n    }\n\n    @Override\n    public String getCategory() {\n        return \"Weather2: \" + getName();\n    }\n\n    @Override\n    public void hookUpdatedValues() {\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/config/ConfigSand.java",
    "content": "package weather2.config;\n\nimport com.corosus.modconfig.ConfigComment;\nimport com.corosus.modconfig.IConfigCategory;\nimport weather2.Weather;\n\nimport java.io.File;\n\n\npublic class ConfigSand implements IConfigCategory {\n\n\n    @ConfigComment(\"Takes the sand out of sandwiches\")\n    public static boolean Storm_NoSandstorms = false;\n\n\t//sandstorm settings\n\tpublic static boolean Sandstorm_UseGlobalServerRate = false;\n\tpublic static int Sandstorm_OddsTo1 = 30;\n\t@ConfigComment(\"Time between sandstorms for either each player or entire server depending on if global rate is on, default: 3 client days\")\n\tpublic static int Sandstorm_TimeBetweenInTicks = 20*60*20*3;\n\n    @ConfigComment(\"Amount of game ticks between sand buildup iterations, keep it high to prevent client side chunk tick spam that destroys FPS\")\n    public static int Sandstorm_Sand_Buildup_TickRate = 40;\n\n    @ConfigComment(\"Base amount of loops done per iteration, scaled by the sandstorms intensity (value given here is the max possible)\")\n    public static int Sandstorm_Sand_Buildup_LoopAmountBase = 800;\n\n    @ConfigComment(\"Max height of sand allowed to buildup against something, higher = things get more buried over time\")\n    public static int Sandstorm_Sand_Block_Max_Height = 3;\n\n    @ConfigComment(\"Allow layered sand blocks to buildup outside deserty biomes where sandstorm is\")\n    public static boolean Sandstorm_Sand_Buildup_AllowOutsideDesert = true;\n\n    public static double Sandstorm_Particle_Dust_effect_rate = 0.6D;\n    //public static double Precipitation_Particle_effect_rate = 0.7D;\n    public static double Sandstorm_Particle_Debris_effect_rate = 0.6D;\n\n    public static boolean Sandstorm_Siren_PleaseNoDarude = false;\n\n    @Override\n    public String getName() {\n        return \"Sand\";\n    }\n\n    @Override\n    public String getRegistryName() {\n        return Weather.MODID + getName();\n    }\n\n    @Override\n    public String getConfigFileName() {\n        return \"Weather2\" + File.separator + getName();\n    }\n\n    @Override\n    public String getCategory() {\n        return \"Weather2: \" + getName();\n    }\n\n    @Override\n    public void hookUpdatedValues() {\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/config/ConfigSnow.java",
    "content": "package weather2.config;\n\nimport com.corosus.modconfig.ConfigComment;\nimport com.corosus.modconfig.IConfigCategory;\nimport weather2.Weather;\n\nimport java.io.File;\n\n\npublic class ConfigSnow implements IConfigCategory {\n\n    public static boolean Storm_NoSnowstorms = false;\n\n    public static boolean Snowstorm_UseGlobalServerRate = false;\n    public static int Snowstorm_OddsTo1 = 30;\n    @ConfigComment(\"Time between snowstorms for either each player or entire server depending on if global rate is on, default: 3 client days\")\n    public static int Snowstorm_TimeBetweenInTicks = 20*60*20*3;\n\n    @ConfigComment(\"Amount of game ticks between snow buildup iterations, keep it high to prevent client side chunk tick spam that destroys FPS\")\n    public static int Snowstorm_Snow_Buildup_TickRate = 40;\n\n    @ConfigComment(\"Base amount of loops done per iteration, scaled by the snowstorms intensity (value given here is the max possible), eg: at max storm intensity, every 40th tick, itll try to build up snow in 800 places around the storm\")\n    public static int Snowstorm_Snow_Buildup_LoopAmountBase = 800;\n\n    @ConfigComment(\"Max height of snow allowed to buildup against something, higher = things get more buried over time\")\n    public static int Snowstorm_Snow_Block_Max_Height = 5;\n\n    @ConfigComment(\"Allow layered snow blocks to buildup outside cold biomes where snowstorm is\")\n    public static boolean Snowstorm_Snow_Buildup_AllowOutsideColdBiomes = true;\n\n    @Override\n    public String getName() {\n        return \"Snow\";\n    }\n\n    @Override\n    public String getRegistryName() {\n        return Weather.MODID + getName();\n    }\n\n    @Override\n    public String getConfigFileName() {\n        return \"Weather2\" + File.separator + getName();\n    }\n\n    @Override\n    public String getCategory() {\n        return \"Weather2: \" + getName();\n    }\n\n    @Override\n    public void hookUpdatedValues() {\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/config/ConfigSound.java",
    "content": "package weather2.config;\n\nimport com.corosus.modconfig.ConfigParams;\nimport com.corosus.modconfig.IConfigCategory;\nimport weather2.Weather;\n\nimport java.io.File;\n\npublic class ConfigSound implements IConfigCategory {\n\n    @ConfigParams(min = 0, max = 5)\n    public static double leavesVolume = 0F;\n    @ConfigParams(min = 0, max = 5)\n    public static double tornadoWindVolume = 1F;\n    @ConfigParams(min = 0, max = 5)\n    public static double tornadoDamageVolume = 1F;\n    @ConfigParams(min = 0, max = 5)\n    public static double windyStormVolume = 1F;\n    @ConfigParams(min = 0, max = 5)\n    public static double sirenVolume = 1F;\n\n    @Override\n    public String getName() {\n        return \"Sound\";\n    }\n\n    @Override\n    public String getRegistryName() {\n        return Weather.MODID + getName();\n    }\n\n    @Override\n    public String getConfigFileName() {\n        return \"Weather2\" + File.separator + getName();\n    }\n\n    @Override\n    public String getCategory() {\n        return \"Weather2: \" + getName();\n    }\n\n    @Override\n    public void hookUpdatedValues() {\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/config/ConfigStorm.java",
    "content": "package weather2.config;\n\nimport com.corosus.modconfig.ConfigComment;\nimport com.corosus.modconfig.IConfigCategory;\nimport weather2.Weather;\n\nimport java.io.File;\n\n\npublic class ConfigStorm implements IConfigCategory {\n\n\n\n    public static int Storm_OddsTo1OfHighWindWaterSpout = 150;\n\tpublic static boolean Storm_FlyingBlocksHurt = true;\n\tpublic static int Storm_MaxPerPlayerPerLayer = 20;\n\tpublic static int Storm_Deadly_CollideDistance = 128;\n\tpublic static int Storm_LightningStrikeBaseValueOddsTo1 = 200;\n\tpublic static boolean Storm_NoRainVisual = false;\n\tpublic static int Storm_MaxRadius = 300;\n\tpublic static int Storm_AllTypes_TickRateDelay = 60;\n\tpublic static int Storm_Rain_WaterBuildUpRate = 10;\n\tpublic static int Storm_Rain_WaterSpendRate = 3;\n\tpublic static int Storm_Rain_WaterBuildUpOddsTo1FromSource = 15;\n\tpublic static int Storm_Rain_WaterBuildUpOddsTo1FromNothing = 100;\n\tpublic static int Storm_Rain_WaterBuildUpOddsTo1FromOvercastRaining = 30;\n\t//public static int Storm_Rain_WaterBuildUp = 150;\n\tpublic static double Storm_TemperatureAdjustRate = 0.1D;\n\t//public static double Storm_Deadly_MinIntensity = 5.3D;\n\tpublic static int Storm_HailPerTick = 10;\n\tpublic static int Storm_OddsTo1OfOceanBasedStorm = 300;\n\t//public static int Storm_OddsTo1OfLandBasedStorm = -1;\n\t//public static int Storm_OddsTo1OfProgressionBase = 15;\n\t//public static int Storm_OddsTo1OfProgressionStageMultiplier = 3;\n\tpublic static int Storm_PercentChanceOf_HighWind = 90;\n\tpublic static int Storm_PercentChanceOf_Hail = 80;\n\tpublic static int Storm_PercentChanceOf_F0_Tornado = 70;\n\tpublic static int Storm_PercentChanceOf_C0_Cyclone = 70;\n\tpublic static int Storm_PercentChanceOf_F1_Tornado = 50;\n\tpublic static int Storm_PercentChanceOf_C1_Cyclone = 50;\n\tpublic static int Storm_PercentChanceOf_F2_Tornado = 40;\n\tpublic static int Storm_PercentChanceOf_C2_Cyclone = 40;\n\tpublic static int Storm_PercentChanceOf_F3_Tornado = 30;\n\tpublic static int Storm_PercentChanceOf_C3_Cyclone = 30;\n\tpublic static int Storm_PercentChanceOf_F4_Tornado = 20;\n\tpublic static int Storm_PercentChanceOf_C4_Cyclone = 20;\n\tpublic static int Storm_PercentChanceOf_F5_Tornado = 10;\n\t@ConfigComment(\"Also known as full blown hurricane\")\n\tpublic static int Storm_PercentChanceOf_C5_Cyclone = 10;\n\tpublic static int Storm_ParticleSpawnDelay = 3;\n\t\n\t//per player storm settings\n\tpublic static int Player_Storm_Deadly_OddsTo1 = 30;\n\tpublic static int Player_Storm_Deadly_TimeBetweenInTicks = 20*60*20*3; //3 mc days\n\t\n\t//per server storm settings\n\tpublic static boolean Server_Storm_Deadly_UseGlobalRate = true;\n\t@ConfigComment(\"Used if Server_Storm_Deadly_UseGlobalRate is on, replaces use of Player_Storm_Deadly_OddsTo1\")\n\tpublic static int Server_Storm_Deadly_OddsTo1 = 30;\n\t@ConfigComment(\"Used if Server_Storm_Deadly_UseGlobalRate is on, replaces use of Player_Storm_Deadly_TimeBetweenInTicks\")\n\tpublic static int Server_Storm_Deadly_TimeBetweenInTicks = 20*60*20*3;\n\n\t@ConfigComment(\"For areas without the right mix of hot and cold biomes\")\n\tpublic static int Player_Storm_Deadly_OddsTo1_Land_Based = 1200;\n\t@ConfigComment(\"For areas without the right mix of hot and cold biomes\")\n\tpublic static int Player_Storm_Deadly_TimeBetweenInTicks_Land_Based = 20*60*20*10; //10 mc days\n\t@ConfigComment(\"Used if Server_Storm_Deadly_UseGlobalRate is on, for areas without the right mix of hot and cold biomes\")\n\tpublic static int Server_Storm_Deadly_OddsTo1_Land_Based = 1200;\n\t@ConfigComment(\"Used if Server_Storm_Deadly_UseGlobalRate is on, for areas without the right mix of hot and cold biomes\")\n\tpublic static int Server_Storm_Deadly_TimeBetweenInTicks_Land_Based = 20*60*20*10; //10 mc days\n\n\tpublic static boolean preventServerThunderstorms = true;\n\t//lightning\n\tpublic static int Lightning_OddsTo1OfFire = 20;\n\tpublic static int Lightning_lifetimeOfFire = 3;\n\tpublic static int Lightning_DistanceToPlayerForEffects = 256;\n\n\tpublic static boolean Lightning_StartsFires = false;\n\n\tpublic static int Storm_Deflector_RadiusOfStormRemoval = 150;\n\n    @ConfigComment(\"The minimum stage a storm has to be at to be removed, stages are: 0 = anything, 1 = thunder, 2 = high wind, 3 = hail, 4 = F0/C0, 5 = F1/C1, 6 = F2/C2, 7 = F3/C3, 8 = F4/C4, 9 = F5/C5\")\n    public static int Storm_Deflector_MinStageRemove = 1;\n    public static boolean Storm_Deflector_RemoveRainstorms = false;\n    public static boolean Storm_Deflector_RemoveSandstorms = true;\n\n\t/*@ConfigComment(\"Minimum amount of visual rain shown when its raining globally during overcast mode\")\n    public static double Storm_Rain_Overcast_Amount = 0.01D;*/\n\tpublic static int Storm_Rain_Overcast_OddsTo1 = 50;\n\n\tpublic static int Storm_Rain_OddsTo1 = 150;\n\n\t@ConfigComment(\"How often in ticks, a rainstorm updates its list of entities under the rainstorm to extinguish. Extinguishes entities under rainclouds when globalOvercast is off. Set to 0 or less to disable\")\n\tpublic static int Storm_Rain_TrackAndExtinguishEntitiesRate = 200;\n\n    @Override\n    public String getName() {\n        return \"Storm\";\n    }\n\n    @Override\n    public String getRegistryName() {\n        return Weather.MODID + getName();\n    }\n\n    @Override\n    public String getConfigFileName() {\n        return \"Weather2\" + File.separator + getName();\n    }\n\n    @Override\n    public String getCategory() {\n        return \"Weather2: \" + getName();\n    }\n\n    @Override\n    public void hookUpdatedValues() {\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/config/ConfigTornado.java",
    "content": "package weather2.config;\n\nimport com.corosus.coroutil.config.ConfigCoroUtil;\nimport com.corosus.modconfig.ConfigComment;\nimport com.corosus.modconfig.IConfigCategory;\nimport weather2.Weather;\nimport weather2.util.WeatherUtil;\n\nimport java.io.File;\n\n\npublic class ConfigTornado implements IConfigCategory {\n\n\n\n    @ConfigComment(\"Prevents tearing up of dirt, grass, sand and logs, overrides strength based grabbing\")\n\tpublic static boolean Storm_Tornado_RefinedGrabRules = true;\n\t@ConfigComment(\"Makes weather boring! or peacefull?\")\n\tpublic static boolean Storm_NoTornadosOrCyclones = false;\n\t//tornado\n\t@ConfigComment(\"Grab player or not\")\n\tpublic static boolean Storm_Tornado_grabPlayer = true;\n\t@ConfigComment(\"Prevent grabbing of non players\")\n\tpublic static boolean Storm_Tornado_grabPlayersOnly = false;\n\t@ConfigComment(\"Grab hostile mobs, overridden by Storm_Tornado_grabPlayersOnly\")\n\tpublic static boolean Storm_Tornado_grabMobs = true;\n\t@ConfigComment(\"Grab animals, overridden by Storm_Tornado_grabPlayersOnly\")\n\tpublic static boolean Storm_Tornado_grabAnimals = true;\n\t@ConfigComment(\"Grab villagers, overridden by Storm_Tornado_grabPlayersOnly\")\n\tpublic static boolean Storm_Tornado_grabVillagers = true;\n\t@ConfigComment(\"Tear up blocks from the ground based on conditions defined\")\n\tpublic static boolean Storm_Tornado_grabBlocks = true;\n\t@ConfigComment(\"Grab entity items, overridden by Storm_Tornado_grabPlayersOnly\")\n\tpublic static boolean Storm_Tornado_grabItems = false;\n\t@ConfigComment(\"Grab blocks based on how well a diamond axe can mine the block, so mostly wooden blocks\")\n\tpublic static boolean Storm_Tornado_GrabCond_StrengthGrabbing = true;\n\t@ConfigComment(\"Use a list of blocks or block tags instead of grabbing based on calculated strength of block, if true this overrides StrengthGrabbing and RefinedGrabRules\")\n\tpublic static boolean Storm_Tornado_GrabCond_List = false;\n\t//removed, because tags should suffice, i hope\n\t//public static boolean Storm_Tornado_GrabCond_List_PartialMatches = false;\n\t//public static boolean Storm_Tornado_GrabCond_List_TrimSpaces = true;\n\t@ConfigComment(\"Treat block grab list as a blacklist instead of whitelist\")\n\tpublic static boolean Storm_Tornado_GrabListBlacklistMode = false;\n\t@ConfigComment(\"Enable Storm_Tornado_GrabCond_List to use, add registered block names or block tags to list, for tags, indicate with #, use commas to separate values, if namespace missing, 'minecraft:' is automatically used\")\n\tpublic static String Storm_Tornado_GrabList = \"#fences, #minecraft:fence_gates, #wooden_doors, #wooden_stairs, #wooden_slabs, #flowers, #planks, #wool, #wooden_trapdoors, #wooden_pressure_plates, #cave_vines, #saplings, #banners, #leaves, #small_flowers, #beds, #tall_flowers, #flowers, #candles, #wall_signs, #signs, #fire, #campfires, #replaceable_plants, #wall_post_override\";\n\t@ConfigComment(\"Max amount of flying entity blocks allowed active, if it goes over this, it stops turning destroyed blocks into entities\")\n\tpublic static int Storm_Tornado_maxFlyingEntityBlocks = 200;\n\tpublic static int Storm_Tornado_maxBlocksGrabbedPerTick = 5;\n\t//@ConfigComment(\"How rarely a block will be removed while spinning around a tornado\")\n\t//public static int Storm_Tornado_rarityOfDisintegrate = 15;\n\t//public static int Storm_Tornado_rarityOfBreakOnFall = 5;\n\t//@ConfigComment(\":D\")\n\t//public static int Storm_Tornado_rarityOfFirenado = -1;\n\t@ConfigComment(\"Make tornados initial heading aimed towards closest player\")\n\tpublic static boolean Storm_Tornado_aimAtPlayerOnSpawn = true;\n\t@ConfigComment(\"Accuracy of tornado aimed at player\")\n\tpublic static int Storm_Tornado_aimAtPlayerAngleVariance = 5;\n\n\t@ConfigComment(\"Extra bit of grab angle for the tornado, tweak for different grab shapes, higher = tigher grab, lower = wider grab, might toss them away\")\n\tpublic static int Storm_Tornado_extraGrabAngle = 20;\n\n\tpublic static boolean Storm_Tornado_fallDamage = true;\n\n\t//@ConfigComment(\"Experimental idea, places the WIP repairing block where a tornado does damage instead of removing the block, causes tornado damage to self repair, recommend setting Storm_Tornado_rarityOfBreakOnFall to 0 to avoid duplicated blocks\")\n\t//public static boolean Storm_Tornado_grabbedBlocksRepairOverTime = false;\n\n\t//@ConfigComment(\"Used if Storm_Tornado_grabbedBlocksRepairOverTime is true, minimum of 600 ticks (30 seconds) required\")\n\t//public static int Storm_Tornado_TicksToRepairBlock = 20*60*5;\n\n    @Override\n    public String getName() {\n        return \"Tornado\";\n    }\n\n    @Override\n    public String getRegistryName() {\n        return Weather.MODID + getName();\n    }\n\n    @Override\n    public String getConfigFileName() {\n        return \"Weather2\" + File.separator + getName();\n    }\n\n    @Override\n    public String getCategory() {\n        return \"Weather2: \" + getName();\n    }\n\n    @Override\n    public void hookUpdatedValues() {\n\t\tWeatherUtil.updateGrabBlockList(Storm_Tornado_GrabList);\n\n\t\t//if (ConfigCoroUtil.useLoggingDebug) {\n\t\t\t//WeatherUtil.testAllBlocks();\n\t\t//}\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/config/ConfigWind.java",
    "content": "package weather2.config;\n\nimport com.corosus.modconfig.ConfigComment;\nimport com.corosus.modconfig.IConfigCategory;\nimport weather2.Weather;\n\nimport java.io.File;\n\npublic class ConfigWind implements IConfigCategory {\n\n    public static boolean Misc_windOn = true;\n    public static boolean Wind_LowWindEvents = true;\n    public static boolean Wind_HighWindEvents = true;\n    public static boolean Wind_UsePerlinNoise = false;\n\n    public static int lowWindTimerEnableAmountBase = 20*60*2;\n    public static int lowWindTimerEnableAmountRnd = 20*60*10;\n    public static int lowWindOddsTo1 = 20*200;\n\n    public static int highWindTimerEnableAmountBase = 20*60*2;\n    public static int highWindTimerEnableAmountRnd = 20*60*10;\n    public static int highWindOddsTo1 = 20*400;\n\n    public static double globalWindAngleChangeAmountRate = 1F;\n\n    public static double windSpeedMin = 0.00001D;\n    public static double windSpeedMax = 1D;\n\n    @ConfigComment(\"Min wind speed to maintain if its raining with global overcast mode on, overrides low wind events and windSpeedMin\")\n    public static double windSpeedMinGlobalOvercastRaining = 0.01D;\n\n\n    public static int Wind_Turbine_FE_Generated_Per_Tick = 10;\n\n    @Override\n    public String getName() {\n        return \"Wind\";\n    }\n\n    @Override\n    public String getRegistryName() {\n        return Weather.MODID + getName();\n    }\n\n    @Override\n    public String getConfigFileName() {\n        return \"Weather2\" + File.separator + getName();\n    }\n\n    @Override\n    public String getCategory() {\n        return \"Weather2: \" + getName();\n    }\n\n    @Override\n    public void hookUpdatedValues() {\n\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/config/WeatherUtilConfig.java",
    "content": "package weather2.config;\n\nimport net.minecraft.resources.ResourceKey;\nimport net.minecraft.world.level.Level;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\npublic class WeatherUtilConfig {\n\n\tpublic static List<String> listDimensionsWeather = new ArrayList<>();\n\tpublic static List<String> listDimensionsClouds = new ArrayList<>();\n\t//used for deadly storms and sandstorms\n\tpublic static List<String> listDimensionsStorms = new ArrayList<>();\n\tpublic static List<String> listDimensionsWindEffects = new ArrayList<>();\n\n\tpublic static boolean shouldTickClouds(String levelResourceKey) {\n\t\treturn listDimensionsClouds.contains(levelResourceKey);\n\t}\n\n\tpublic static void processLists() {\n\t\tlistDimensionsWeather = parseList(ConfigMisc.Dimension_List_Weather);\n\t\tlistDimensionsClouds = parseList(ConfigMisc.Dimension_List_Clouds);\n\t\tlistDimensionsStorms = parseList(ConfigMisc.Dimension_List_Storms);\n\t\tlistDimensionsWindEffects = parseList(ConfigMisc.Dimension_List_WindEffects);\n\t}\n\n\tpublic static List<String> parseList(String parData) {\n\t\tString listStr = parData;\n\t\tlistStr = listStr.replace(\",\", \" \");\n\t\tString[] arrStr = listStr.split(\" \");\n\t\tfor (int i = 0; i < arrStr.length; i++) {\n\t\t\ttry {\n\t\t\t\tarrStr[i] = arrStr[i];\n\t\t\t} catch (Exception ex) {\n\t\t\t\tarrStr[i] = \"minecraft:none\"; //set to -999999, hope no dimension id of this exists\n\t\t\t}\n\t\t}\n\t\treturn new ArrayList(Arrays.asList(arrStr));\n\t}\n\t\n}\n"
  },
  {
    "path": "src/main/java/weather2/data/BlockAndItemProvider.java",
    "content": "package weather2.data;\n\nimport net.minecraft.client.renderer.texture.atlas.sources.SingleFile;\nimport net.minecraft.data.PackOutput;\nimport net.minecraft.resources.ResourceLocation;\nimport net.minecraftforge.common.data.ExistingFileHelper;\nimport net.minecraftforge.common.data.SpriteSourceProvider;\nimport weather2.Weather;\n\nimport java.util.Optional;\n\npublic class BlockAndItemProvider extends SpriteSourceProvider {\n\n\tpublic BlockAndItemProvider(PackOutput output, ExistingFileHelper fileHelper)\n\t{\n\t\tsuper(output, fileHelper, Weather.MODID);\n\t}\n\n\t@Override\n\tprotected void addSources()\n\t{\n\t\taddSpriteBlock(\"tornado_siren\");\n\t\taddSpriteBlock(\"tornado_siren_manual\");\n\t\taddSpriteBlock(\"tornado_siren_manual_on\");\n\t\taddSpriteBlock(\"tornado_sensor\");\n\t\taddSpriteBlock(\"weather_deflector\");\n\t\taddSpriteBlock(\"weather_forecast\");\n\t\taddSpriteBlock(\"weather_machine\");\n\t\taddSpriteBlock(\"anemometer\");\n\t\taddSpriteBlock(\"wind_vane\");\n\t\taddSpriteBlock(\"wind_turbine\");\n\t\taddSpriteItem(\"weather_item\");\n\t\taddSpriteItem(\"sand_layer\");\n\t\taddSpriteItem(\"sand_layer_placeable\");\n\t}\n\n\tpublic void addSpriteBlock(String textureName) {\n\t\tatlas(SpriteSourceProvider.BLOCKS_ATLAS).addSource(new SingleFile(new ResourceLocation(Weather.MODID + \":blocks/\" + textureName), Optional.empty()));\n\t}\n\n\tpublic void addSpriteItem(String textureName) {\n\t\tatlas(SpriteSourceProvider.BLOCKS_ATLAS).addSource(new SingleFile(new ResourceLocation(Weather.MODID + \":items/\" + textureName), Optional.empty()));\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/data/BlockLootTables.java",
    "content": "package weather2.data;\n\nimport net.minecraft.data.loot.BlockLootSubProvider;\nimport net.minecraft.world.flag.FeatureFlags;\nimport net.minecraft.world.level.block.Block;\nimport net.minecraftforge.registries.ForgeRegistries;\nimport weather2.Weather;\nimport weather2.WeatherBlocks;\n\nimport java.util.Set;\nimport java.util.stream.Collectors;\n\npublic class BlockLootTables extends BlockLootSubProvider {\n\n\tpublic BlockLootTables() {\n\t\tsuper(Set.of(), FeatureFlags.REGISTRY.allFlags());\n\t}\n\n\t@Override\n\tprotected void generate() {\n\t\tdropSelf(WeatherBlocks.BLOCK_WIND_TURBINE.get());\n\t\tdropSelf(WeatherBlocks.BLOCK_WIND_VANE.get());\n\t\tdropSelf(WeatherBlocks.BLOCK_ANEMOMETER.get());\n\t\tdropSelf(WeatherBlocks.BLOCK_DEFLECTOR.get());\n\t\tdropSelf(WeatherBlocks.BLOCK_FORECAST.get());\n\t\tdropSelf(WeatherBlocks.BLOCK_TORNADO_SENSOR.get());\n\t\tdropSelf(WeatherBlocks.BLOCK_SAND_LAYER.get());\n\t\tdropSelf(WeatherBlocks.BLOCK_TORNADO_SIREN.get());\n\t}\n\n\t@Override\n\tprotected Iterable<Block> getKnownBlocks() {\n\t\treturn ForgeRegistries.BLOCKS.getValues().stream().filter(block -> ForgeRegistries.BLOCKS.getKey(block).getNamespace().equals(Weather.MODID)).collect(Collectors.toList());\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/data/WeatherRecipeProvider.java",
    "content": "package weather2.data;\n\nimport net.minecraft.data.DataGenerator;\nimport net.minecraft.data.PackOutput;\nimport net.minecraft.data.recipes.FinishedRecipe;\nimport net.minecraft.data.recipes.RecipeCategory;\nimport net.minecraft.data.recipes.RecipeProvider;\nimport net.minecraft.data.recipes.ShapedRecipeBuilder;\nimport net.minecraft.world.item.Items;\nimport weather2.Weather;\nimport weather2.WeatherBlocks;\nimport weather2.WeatherItems;\n\nimport java.util.function.Consumer;\n\npublic class WeatherRecipeProvider extends RecipeProvider {\n\n    public WeatherRecipeProvider(PackOutput p_125973_) {\n        super(p_125973_);\n    }\n\n    @Override\n    protected void buildRecipes(Consumer<FinishedRecipe> consumer) {\n\n        ShapedRecipeBuilder.shaped(RecipeCategory.MISC, WeatherItems.WEATHER_ITEM.get(), 1)\n                .pattern(\"X X\").pattern(\"DID\").pattern(\"X X\")\n                .define('D', Items.REDSTONE)\n                .define('I', Items.GOLD_INGOT)\n                .define('X', Items.IRON_INGOT)\n                .unlockedBy(\"has_redstone\", has(Items.REDSTONE))\n                .save(consumer);\n\n        ShapedRecipeBuilder.shaped(RecipeCategory.MISC, WeatherItems.BLOCK_DEFLECTOR_ITEM.get(), 1)\n                .pattern(\"XDX\").pattern(\"DID\").pattern(\"XDX\")\n                .define('D', Items.REDSTONE)\n                .define('I', WeatherItems.WEATHER_ITEM.get())\n                .define('X', Items.IRON_INGOT)\n                .unlockedBy(\"has_weather_item\", has(WeatherItems.WEATHER_ITEM.get()))\n                .save(consumer);\n\n        ShapedRecipeBuilder.shaped(RecipeCategory.MISC, WeatherItems.BLOCK_FORECAST_ITEM.get(), 1)\n                .pattern(\"XDX\").pattern(\"DID\").pattern(\"XDX\")\n                .define('D', Items.REDSTONE)\n                .define('I', Items.COMPASS)\n                .define('X', WeatherItems.WEATHER_ITEM.get())\n                .unlockedBy(\"has_weather_item\", has(WeatherItems.WEATHER_ITEM.get()))\n                .save(consumer);\n\n        ShapedRecipeBuilder.shaped(RecipeCategory.MISC, WeatherItems.BLOCK_TORNADO_SENSOR_ITEM.get(), 1)\n                .pattern(\"X X\").pattern(\"DID\").pattern(\"X X\")\n                .define('D', Items.REDSTONE)\n                .define('I', WeatherItems.WEATHER_ITEM.get())\n                .define('X', Items.IRON_INGOT)\n                .unlockedBy(\"has_weather_item\", has(WeatherItems.WEATHER_ITEM.get()))\n                .save(consumer);\n\n        ShapedRecipeBuilder.shaped(RecipeCategory.MISC, WeatherItems.BLOCK_TORNADO_SIREN_ITEM.get(), 1)\n                .pattern(\"XDX\").pattern(\"DID\").pattern(\"XDX\")\n                .define('D', Items.REDSTONE)\n                .define('I', WeatherItems.BLOCK_TORNADO_SENSOR_ITEM.get())\n                .define('X', Items.IRON_INGOT)\n                .unlockedBy(\"has_sensor_item\", has(WeatherItems.BLOCK_TORNADO_SENSOR_ITEM.get()))\n                .save(consumer);\n\n        ShapedRecipeBuilder.shaped(RecipeCategory.MISC, WeatherItems.BLOCK_WIND_VANE_ITEM.get(), 1)\n                .pattern(\"X X\").pattern(\"DXD\").pattern(\"X X\")\n                .define('D', Items.REDSTONE)\n                .define('X', WeatherItems.WEATHER_ITEM.get())\n                .unlockedBy(\"has_weather_item\", has(WeatherItems.WEATHER_ITEM.get()))\n                .save(consumer);\n\n        ShapedRecipeBuilder.shaped(RecipeCategory.MISC, WeatherItems.BLOCK_WIND_TURBINE_ITEM.get(), 1)\n                .pattern(\"ODO\").pattern(\"IVI\").pattern(\"RGR\")\n                .define('I', Items.IRON_BLOCK)\n                .define('O', Items.IRON_INGOT)\n                .define('D', Items.DIAMOND)\n                .define('V', WeatherItems.BLOCK_WIND_VANE_ITEM.get())\n                .define('R', Items.REDSTONE_BLOCK)\n                .define('G', Items.GOLD_INGOT)\n                .unlockedBy(\"has_wind_vane\", has(WeatherItems.BLOCK_WIND_TURBINE_ITEM.get()))\n                .save(consumer);\n\n        ShapedRecipeBuilder.shaped(RecipeCategory.MISC, WeatherItems.BLOCK_ANEMOMETER_ITEM.get(), 1)\n                .pattern(\"X X\").pattern(\"XDX\").pattern(\"X X\")\n                .define('D', Items.REDSTONE)\n                .define('X', WeatherItems.WEATHER_ITEM.get())\n                .unlockedBy(\"has_weather_item\", has(WeatherItems.WEATHER_ITEM.get()))\n                .save(consumer);\n\n        ShapedRecipeBuilder.shaped(RecipeCategory.MISC, WeatherItems.BLOCK_SAND_LAYER_ITEM.get(), 8)\n                .pattern(\"DDD\").pattern(\"D D\").pattern(\"DDD\")\n                .define('D', Items.SAND)\n                .unlockedBy(\"has_sand\", has(Items.SAND))\n                .save(consumer);\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/datatypes/PrecipitationType.java",
    "content": "package weather2.datatypes;\n\npublic enum PrecipitationType {\n\tNORMAL,\n\tACID,\n\tHAIL,\n\tSNOW;\n\n\tpublic static final PrecipitationType[] VALUES = values();\n}\n"
  },
  {
    "path": "src/main/java/weather2/datatypes/StormState.java",
    "content": "package weather2.datatypes;\n\nimport net.minecraft.network.FriendlyByteBuf;\n\npublic final class StormState {\n\tprivate final int buildupTickRate;\n\tprivate final int maxStackable;\n\n\tpublic StormState(int buildupTickRate, int maxStackable) {\n\t\tthis.buildupTickRate = buildupTickRate;\n\t\tthis.maxStackable = maxStackable;\n\t}\n\n\tpublic int getBuildupTickRate() {\n\t\treturn buildupTickRate;\n\t}\n\n\tpublic int getMaxStackable() {\n\t\treturn maxStackable;\n\t}\n\n\tpublic void encode(FriendlyByteBuf buffer) {\n\t\tbuffer.writeVarInt(this.buildupTickRate);\n\t\tbuffer.writeVarInt(this.maxStackable);\n\t}\n\n\tpublic static StormState decode(FriendlyByteBuf buffer) {\n\t\tint buildupTickRate = buffer.readVarInt();\n\t\tint maxStackable = buffer.readVarInt();\n\t\treturn new StormState(buildupTickRate, maxStackable);\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/datatypes/WeatherEventType.java",
    "content": "package weather2.datatypes;\n\npublic enum WeatherEventType {\n\tHEAVY_RAIN(\"heavy_rain\"),\n\tACID_RAIN(\"acid_rain\"),\n\tHAIL(\"hail\"),\n\tHEATWAVE(\"heatwave\"),\n\tSANDSTORM(\"sandstorm\"),\n\tSNOWSTORM(\"snowstorm\");\n\n\tprivate final String key;\n\n\tpublic static final WeatherEventType[] VALUES = values();\n\n\tWeatherEventType(String key) {\n\t\tthis.key = key;\n\t}\n\n\tpublic String getKey() {\n\t\treturn key;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/energy/EnergyManager.java",
    "content": "package weather2.energy;\n\nimport net.minecraft.nbt.CompoundTag;\nimport net.minecraftforge.common.capabilities.Capability;\nimport net.minecraftforge.common.capabilities.ForgeCapabilities;\nimport net.minecraftforge.common.util.LazyOptional;\nimport net.minecraftforge.energy.EnergyStorage;\nimport net.minecraftforge.energy.IEnergyStorage;\n\npublic class EnergyManager extends EnergyStorage {\n\tprivate boolean canExtract = true;\n\n\tpublic EnergyManager(int maxTransfer, int capacity) {\n\t\tsuper(capacity, maxTransfer, maxTransfer);\n\t}\n\n\tpublic int getMaxExtract() {\n\t\treturn maxExtract;\n\t}\n\n\tpublic void setReceiveOnly() {\n\t\tcanExtract = false;\n\t}/*\n\n\t@Override\n\tpublic void read(CompoundTag nbt) {\n\t\tsetEnergyStored(nbt.getInt(\"Energy\"));\n\t}\n\n\t@Override\n\tpublic CompoundTag write(CompoundTag nbt) {\n\t\tnbt.putInt(\"Energy\", energy);\n\t\treturn nbt;\n\t}*/\n\n\tpublic int getMaxEnergyReceived() {\n\t\treturn this.maxReceive;\n\t}\n\n\t/**\n\t * Drains an amount of energy, due to decay from lack of work or other factors\n\t */\n\tpublic void drainEnergy(int amount) {\n\t\tsetEnergyStored(energy - amount);\n\t}\n\n\tpublic void addEnergy(int amount) {\n\t\tsetEnergyStored(energy + amount);\n\t}\n\n\tpublic int getEnergy() {\n\t\treturn energy;\n\t}\n\n\tpublic void setEnergyStored(int energyStored) {\n\t\tthis.energy = energyStored;\n\t\tif (this.energy > capacity) {\n\t\t\tthis.energy = capacity;\n\t\t} else if (this.energy < 0) {\n\t\t\tthis.energy = 0;\n\t\t}\n\t}\n\n\tpublic <T> LazyOptional<T> getCapability(Capability<T> capability) {\n\t\tif (capability == ForgeCapabilities.ENERGY) {\n            //IEnergyStorage energyStorage = new EnergyStorageWrapper(this, canExtract);\n            return LazyOptional.of(() -> this).cast();\n        }\n\n\t\treturn LazyOptional.empty();\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/item/WeatherItem.java",
    "content": "package weather2.item;\n\nimport net.minecraft.core.NonNullList;\nimport net.minecraft.world.item.CreativeModeTab;\nimport net.minecraft.world.item.Item;\nimport net.minecraft.world.item.ItemStack;\n\npublic class WeatherItem extends Item {\n    public WeatherItem(Item.Properties properties) {\n        super(properties);\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/ltcompat/ClientWeatherIntegration.java",
    "content": "package weather2.ltcompat;\n\nimport weather2.datatypes.PrecipitationType;\n\npublic final class ClientWeatherIntegration {\n\tprivate static ClientWeatherIntegration instance = new ClientWeatherIntegration();\n\n\tprivate ClientWeatherIntegration() {\n\t}\n\n\tpublic static ClientWeatherIntegration get() {\n\t\treturn instance;\n\t}\n\n\tpublic static void reset() {\n\t\tinstance = new ClientWeatherIntegration();\n\t}\n\n\tpublic float getRainAmount() {\n\t\treturn 0;\n\t}\n\n\tpublic float getVanillaRainAmount() {\n\t\treturn 0;\n\t}\n\n\tpublic PrecipitationType getPrecipitationType() {\n\t\treturn PrecipitationType.VALUES[0];\n\t}\n\n\tpublic float getWindSpeed() {\n\t\treturn 0;\n\t}\n\n\tpublic boolean isHeatwave() {\n\t\treturn false;\n\t}\n\n\tpublic boolean isSandstorm() {\n\t\treturn false;\n\t}\n\n\tpublic boolean isSnowstorm() {\n\t\treturn false;\n\t}\n\n\tpublic boolean hasWeather() {\n\t\treturn false;\n\t}\n\n\t/**\n\t * TODO: for LT, turn back on when LT is needed, activates dependency on LTWeather\n\t */\n\t/*public float getRainAmount() {\n\t\treturn ClientWeather.get().getRainAmount();\n\t}\n\n\tpublic float getVanillaRainAmount() {\n\t\treturn ClientWeather.get().getVanillaRainAmount();\n\t}\n\n\tpublic PrecipitationType getPrecipitationType() {\n\t\treturn PrecipitationType.VALUES[TypeBridge.getPrecipitationTypeOrdinal(ClientWeather.get())];\n\t}\n\n\tpublic float getWindSpeed() {\n\t\treturn ClientWeather.get().getWindSpeed();\n\t}\n\n\tpublic boolean isHeatwave() {\n\t\treturn ClientWeather.get().isHeatwave();\n\t}\n\n\tpublic boolean isSandstorm() {\n\t\treturn ClientWeather.get().isSandstorm();\n\t}\n\n\tpublic boolean isSnowstorm() {\n\t\treturn ClientWeather.get().isSnowstorm();\n\t}\n\n\tpublic boolean hasWeather() {\n\t\treturn ClientWeather.get().hasWeather();\n\t}*/\n}\n"
  },
  {
    "path": "src/main/java/weather2/ltcompat/ServerWeatherIntegration.java",
    "content": "package weather2.ltcompat;\n\nimport net.minecraft.server.level.ServerLevel;\nimport net.minecraft.util.Tuple;\nimport weather2.datatypes.StormState;\n\npublic class ServerWeatherIntegration {\n\n    public static float getWindSpeed(ServerLevel level) {\n        return 0;\n    }\n\n    public static StormState getSandstormForEverywhere(ServerLevel level) {\n        return null;\n    }\n\n    public static StormState getSnowstormForEverywhere(ServerLevel level) {\n        return null;\n    }\n\n    /**\n     * TODO: for LT, turn back on when LT is needed, activates dependency on LTWeather\n     */\n    /*public static float getWindSpeed(ServerLevel level) {\n        return TypeBridge.getWindSpeed(level);\n    }\n\n    public static StormState getSandstormForEverywhere(ServerLevel level) {\n        Tuple<Integer, Integer> data = TypeBridge.getSandstormData(level);\n        return data != null ? new StormState(data.getA(), data.getB()) : null;\n    }\n\n    public static StormState getSnowstormForEverywhere(ServerLevel level) {\n        Tuple<Integer, Integer> data = TypeBridge.getSnowstormData(level);\n        return data != null ? new StormState(data.getA(), data.getB()) : null;\n    }*/\n\n}\n"
  },
  {
    "path": "src/main/java/weather2/mixin/client/GameRendererOverride.java",
    "content": "package weather2.mixin.client;\n\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.client.renderer.GameRenderer;\nimport org.spongepowered.asm.mixin.Mixin;\nimport org.spongepowered.asm.mixin.Overwrite;\n\n@Mixin(GameRenderer.class)\npublic abstract class GameRendererOverride {\n\n    /**\n     * @author Corosus\n     * @reason render particle clouds further\n     *\n     * UNUSED ATM\n     */\n    @Overwrite\n    public float getDepthFar() {\n        //CULog.dbg(\"getDepthFar override\");\n        return Minecraft.getInstance().gameRenderer.getRenderDistance() * 4F;\n    }\n}"
  },
  {
    "path": "src/main/java/weather2/mixin/client/RenderParticlesOverride.java",
    "content": "package weather2.mixin.client;\n\nimport com.mojang.blaze3d.vertex.PoseStack;\nimport net.minecraft.client.Camera;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.client.particle.ParticleEngine;\nimport net.minecraft.client.renderer.LevelRenderer;\nimport net.minecraft.client.renderer.LightTexture;\nimport net.minecraft.client.renderer.MultiBufferSource;\nimport org.spongepowered.asm.mixin.Mixin;\nimport org.spongepowered.asm.mixin.injection.At;\nimport org.spongepowered.asm.mixin.injection.Redirect;\nimport weather2.ClientTickHandler;\nimport weather2.config.ConfigParticle;\n\nimport javax.annotation.Nullable;\n\n@Mixin(LevelRenderer.class)\npublic abstract class RenderParticlesOverride {\n\n    //replaced by RenderLevelStageEvent.Stage.AFTER_PARTICLES\n    /*@Redirect(method = \"renderLevel\",\n            at = @At(value = \"INVOKE\",\n                    target = \"Lnet/minecraft/client/particle/ParticleEngine;render(Lcom/mojang/blaze3d/vertex/PoseStack;Lnet/minecraft/client/renderer/MultiBufferSource$BufferSource;Lnet/minecraft/client/renderer/LightTexture;Lnet/minecraft/client/Camera;FLnet/minecraft/client/renderer/culling/Frustum;)V\"))\n    public void render(ParticleEngine particleManager, PoseStack matrixStackIn, MultiBufferSource.BufferSource bufferIn, LightTexture lightTextureIn, Camera activeRenderInfoIn, float partialTicks, @Nullable net.minecraft.client.renderer.culling.Frustum clippingHelper) {\n        ClientTickHandler.particleManagerExtended().render(matrixStackIn, bufferIn, lightTextureIn, activeRenderInfoIn, partialTicks, clippingHelper);\n        particleManager.render(matrixStackIn, bufferIn, lightTextureIn, activeRenderInfoIn, partialTicks, clippingHelper);\n\n    }*/\n\n    @Redirect(method = \"renderLevel\",\n            at = @At(value = \"INVOKE\",\n                    target = \"Lnet/minecraft/client/renderer/LevelRenderer;renderSnowAndRain(Lnet/minecraft/client/renderer/LightTexture;FDDD)V\"))\n    public void renderSnowAndRain(LevelRenderer worldRenderer, LightTexture lightmapIn, float partialTicks, double xIn, double yIn, double zIn) {\n        //CULog.dbg(\"renderSnowAndRain hook\");\n        //stopping vanilla from running renderRainSnow\n        if (ConfigParticle.Particle_vanilla_precipitation) {\n            worldRenderer.renderSnowAndRain(lightmapIn, partialTicks, xIn, yIn, zIn);\n        }\n    }\n\n    /*@Redirect(method = \"renderLevel\",\n            at = @At(value = \"INVOKE\",\n                    target = \"Lnet/minecraft/client/renderer/LevelRenderer;renderClouds(Lcom/mojang/blaze3d/vertex/PoseStack;Lcom/mojang/math/Matrix4f;FDDD)V\"))\n    public void renderClouds(LevelRenderer instance, PoseStack poseStack, Matrix4f l, float i1, double f1, double f2, double d0) {\n        //CULog.dbg(\"renderClouds hook\");\n        //workaround for missing projection matrix info\n        ICloudRenderHandler cloudRenderHandler = Minecraft.getInstance().level().effects().getCloudRenderHandler();\n        if (cloudRenderHandler instanceof CloudRenderHandler) {\n            ((CloudRenderHandler)cloudRenderHandler).render(poseStack, l, i1, f1, f2, d0);\n        } else {\n            instance.renderClouds(poseStack, l, i1, f1, f2, d0);\n        }\n    }*/\n}"
  },
  {
    "path": "src/main/java/weather2/player/PlayerData.java",
    "content": "package weather2.player;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.Map;\n\nimport net.minecraft.nbt.CompoundTag;\nimport weather2.Weather;\n\npublic class PlayerData {\n\n\tpublic static HashMap<String, CompoundTag> playerNBT = new HashMap<>();\n\t\n\tpublic static CompoundTag getPlayerNBT(String username) {\n\t\tif (!playerNBT.containsKey(username)) {\n\t\t\t//TODO: 1.18\n\t\t\t//tryLoadPlayerNBT(username);\n\t\t\t//TODO: 1.18 remove this\n\t\t\tplayerNBT.put(username, new CompoundTag());\n\t\t}\n\t\treturn playerNBT.get(username);\n\t}\n\t\n\t/*public static void tryLoadPlayerNBT(String username) {\n\t\t//try read from hw/playerdata/player.dat\n\t\t//init with data, if fail, init default blank\n\t\t\n\t\tCompoundTag playerData = new CompoundTag();\n\t\t\n\t\ttry {\n\t\t\tString fileURL = CoroUtilFile.getWorldSaveFolderPath() + CoroUtilFile.getWorldFolderName() + File.separator + \"weather2\" + File.separator + \"PlayerData\" + File.separator + username + \".dat\";\n\t\t\t\n\t\t\tif ((new File(fileURL)).exists()) {\n\t\t\t\tplayerData = CompressedStreamTools.readCompressed(new FileInputStream(fileURL));\n\t\t\t}\n\t\t} catch (Exception ex) {\n\t\t\t//Weather.dbg(\"no saved data found for \" + username);\n\t\t}\n\t\t\n\t\tplayerNBT.put(username, playerData);\n\t}\n\t\n\tpublic static void writeAllPlayerNBT(boolean resetData) {\n\t\t//Weather.dbg(\"writing out all player nbt\");\n\t\t\n\t\tString fileURL = CoroUtilFile.getWorldSaveFolderPath() + CoroUtilFile.getWorldFolderName() + File.separator + \"weather2\" + File.separator + \"PlayerData\";\n\t\tif (!new File(fileURL).exists()) new File(fileURL).mkdir();\n\t\t\n\t\tIterator it = playerNBT.entrySet().iterator();\n\t    while (it.hasNext()) {\n\t        Map.Entry pairs = (Map.Entry)it.next();\n\t        //Weather.dbg(pairs.getKey() + \" = \" + pairs.getValue());\n\t        writePlayerNBT((String)pairs.getKey(), (CompoundTag)pairs.getValue());\n\t    }\n\t    \n\t    if (resetData) {\n\t    \tplayerNBT.clear();\n\t    }\n\t}\n\t\n\tpublic static void writePlayerNBT(String username, CompoundTag parData) {\n\t\t//Weather.dbg(\"writing \" + username);\n\t\t\n\t\tString fileURL = CoroUtilFile.getWorldSaveFolderPath() + CoroUtilFile.getWorldFolderName() + File.separator + \"weather2\" + File.separator + \"PlayerData\" + File.separator + username + \".dat\";\n\t\t\n\t\ttry {\n\t\t\tFileOutputStream fos = new FileOutputStream(fileURL);\n\t    \tCompressedStreamTools.writeCompressed(parData, fos);\n\t    \tfos.close();\n\t\t} catch (Exception ex) {\n\t\t\tex.printStackTrace();\n\t\t\tWeather.dbg(\"Error writing Weather2 player data for \" + username);\n\t\t}\n\t}*/\n\t\n}\n"
  },
  {
    "path": "src/main/java/weather2/util/CachedNBTTagCompound.java",
    "content": "package weather2.util;\n\nimport net.minecraft.nbt.CompoundTag;\n\n/**\n * Caches nbt data to remove redundant data sending over network\n *\n * @author cosmicdan\n *\n * revisions made to further integrate it into the newer design of WeatherObjects\n */\npublic class CachedNBTTagCompound {\n\tprivate CompoundTag newData;\n\tprivate CompoundTag cachedData;\n\tprivate boolean forced = false;\n\n\tpublic CachedNBTTagCompound() {\n\t\tthis.newData = new CompoundTag();\n\t\tthis.cachedData = new CompoundTag();\n\t}\n\n\tpublic void setCachedNBT(CompoundTag cachedData) {\n\t\tif (cachedData == null)\n\t\t\tcachedData = new CompoundTag();\n\t\tthis.cachedData = cachedData;\n\t}\n\n\tpublic CompoundTag getCachedNBT() {\n\t\treturn cachedData;\n\t}\n\n\tpublic CompoundTag getNewNBT() {\n\t\treturn newData;\n\t}\n\n\tpublic void setNewNBT(CompoundTag newData) {\n\t\tthis.newData = newData;\n\t}\n\n\tpublic void setUpdateForced(boolean forced) {\n\t\tthis.forced = forced;\n\t}\n\n\tpublic long getLong(String key) {\n\t\tif (!newData.contains(key))\n\t\t\tnewData.putLong(key, cachedData.getLong(key));\n\t\treturn newData.getLong(key);\n\t}\n\n\tpublic void putLong(String key, long newVal) {\n\t\tif (!cachedData.contains(key) || cachedData.getLong(key) != newVal || forced) {\n\t\t\tnewData.putLong(key, newVal);\n\t\t}\n\t\tcachedData.putLong(key, newVal);\n\t}\n\n\tpublic int getInt(String key) {\n\t\tif (!newData.contains(key))\n\t\t\tnewData.putInt(key, cachedData.getInt(key));\n\t\treturn newData.getInt(key);\n\t}\n\n\tpublic void putInt(String key, int newVal) {\n\t\tif (!cachedData.contains(key) || cachedData.getInt(key) != newVal || forced) {\n\t\t\tnewData.putInt(key, newVal);\n\t\t}\n\t\tcachedData.putInt(key, newVal);\n\t}\n\n\tpublic short getShort(String key) {\n\t\tif (!newData.contains(key))\n\t\t\tnewData.putShort(key, cachedData.getShort(key));\n\t\treturn newData.getShort(key);\n\t}\n\n\tpublic void putShort(String key, short newVal) {\n\t\tif (!cachedData.contains(key) || cachedData.getShort(key) != newVal || forced) {\n\t\t\tnewData.putShort(key, newVal);\n\t\t}\n\t\tcachedData.putShort(key, newVal);\n\t}\n\n\tpublic String getString(String key) {\n\t\tif (!newData.contains(key))\n\t\t\tnewData.putString(key, cachedData.getString(key));\n\t\treturn newData.getString(key);\n\t}\n\n\tpublic void putString(String key, String newVal) {\n\t\tif (!cachedData.contains(key) || !cachedData.getString(key).equals(newVal) || forced) {\n\t\t\tnewData.putString(key, newVal);\n\t\t}\n\t\tcachedData.putString(key, newVal);\n\t}\n\n\tpublic boolean getBoolean(String key) {\n\t\tif (!newData.contains(key))\n\t\t\tnewData.putBoolean(key, cachedData.getBoolean(key));\n\t\treturn newData.getBoolean(key);\n\t}\n\n\tpublic void putBoolean(String key, boolean newVal) {\n\t\tif (!cachedData.contains(key) || cachedData.getBoolean(key) != newVal || forced) {\n\t\t\tnewData.putBoolean(key, newVal);\n\t\t}\n\t\tcachedData.putBoolean(key, newVal);\n\t}\n\n\tpublic float getFloat(String key) {\n\t\tif (!newData.contains(key))\n\t\t\tnewData.putFloat(key, cachedData.getFloat(key));\n\t\treturn newData.getFloat(key);\n\t}\n\n\tpublic void putFloat(String key, float newVal) {\n\t\tif (!cachedData.contains(key) || cachedData.getFloat(key) != newVal || forced) {\n\t\t\tnewData.putFloat(key, newVal);\n\t\t}\n\t\tcachedData.putFloat(key, newVal);\n\t}\n\n\tpublic double getDouble(String key) {\n\t\tif (!newData.contains(key))\n\t\t\tnewData.putDouble(key, cachedData.getDouble(key));\n\t\treturn newData.getDouble(key);\n\t}\n\n\tpublic void putDouble(String key, double newVal) {\n\t\tif (!cachedData.contains(key) || cachedData.getDouble(key) != newVal || forced) {\n\t\t\tnewData.putDouble(key, newVal);\n\t\t}\n\t\tcachedData.putDouble(key, newVal);\n\t}\n\n\tpublic CompoundTag get(String key) {\n\t\treturn newData.getCompound(key);\n\t}\n\n\t/** warning, not cached **/\n\tpublic void put(String key, CompoundTag tag) {\n\t\tnewData.put(key, tag);\n\t\tcachedData.put(key, tag);\n\t}\n\n\tpublic boolean contains(String key) {\n\t\treturn newData.contains(key);\n\t}\n\n\tpublic void updateCacheFromNew() {\n\t\tthis.cachedData = this.newData;\n\t}\n\n}"
  },
  {
    "path": "src/main/java/weather2/util/WeatherUtil.java",
    "content": "package weather2.util;\n\nimport com.corosus.coroutil.util.CULog;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.core.Registry;\nimport net.minecraft.resources.ResourceKey;\nimport net.minecraft.resources.ResourceLocation;\nimport net.minecraft.server.level.ServerLevel;\nimport net.minecraft.tags.BlockTags;\nimport net.minecraft.tags.TagKey;\nimport net.minecraft.world.item.ItemStack;\nimport net.minecraft.world.item.Items;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.block.*;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.world.level.material.MapColor;\nimport net.minecraftforge.registries.ForgeRegistries;\nimport net.minecraftforge.server.ServerLifecycleHooks;\nimport org.joml.Vector3d;\nimport org.joml.Vector3f;\nimport weather2.config.ConfigTornado;\n\nimport java.util.*;\n\npublic class WeatherUtil {\n\n    public static HashMap<ResourceLocation, Boolean> listGrabBlockCache = new HashMap<>();\n    public static List<String> listGrabBlocks = new ArrayList<>();\n    public static List<String> listGrabBlockTags = new ArrayList<>();\n\n    public static String lastConfigChecked = \"\";\n\n    public static void updateGrabBlockList(String grabListStr) {\n        CULog.dbg(\"Updating weather2 tornado grab list\");\n\n        listGrabBlocks.clear();\n        listGrabBlockTags.clear();\n        listGrabBlockCache.clear();\n\n        try {\n            String[] splEnts = grabListStr.split(\",\");\n\n            for (String str : splEnts) {\n                str = str.trim();\n                if (str.contains(\"#\")) {\n                    listGrabBlockTags.add(addNamespaceIfMissing(str.substring(1)));\n                } else {\n                    listGrabBlocks.add(addNamespaceIfMissing(str));\n                }\n            }\n        } catch (ArrayIndexOutOfBoundsException ex) {\n            ex.printStackTrace();\n        }\n    }\n\n    public static void testAllBlocks() {\n        //Blocks.GLASS\n        //if (!ConfigTornado.Storm_Tornado_GrabList.equals(lastConfigChecked)) {\n            lastConfigChecked = ConfigTornado.Storm_Tornado_GrabList;\n            CULog.log(\"PRINTING OUT ALL WEATHER2 TORNADO GRABBABLE BLOCKS WITH CURRENT CONFIG: \");\n            ForgeRegistries.BLOCKS.forEach(block -> {\n                List<BlockState> list = block.getStateDefinition().getPossibleStates();\n                for (BlockState state : list) {\n                    boolean result = canGrabViaLists(state);\n                    if (result) {\n                        CULog.log(state + \" -> \" + result);\n                    }\n                }\n            });\n\n            //boolean wat = canGrabViaLists(Blocks.TORCH.defaultBlockState());\n            //System.out.println(\"wat: \" + wat);\n        //}\n    }\n\n    public static String addNamespaceIfMissing(String str) {\n        if (!str.contains(\":\")) {\n            str = \"minecraft:\" + str;\n        }\n        return str;\n    }\n\n    public static boolean isStateInListOfTags(BlockState state) {\n        for (String str : listGrabBlockTags) {\n            TagKey<Block> key = getTagKeyFor(str);\n            if (key != null) {\n                if (state.is(key)) {\n                    return true;\n                }\n            }\n        }\n        return false;\n    }\n\n    public static TagKey<Block> getTagKeyFor(String str) {\n        return TagKey.create(ForgeRegistries.BLOCKS.getRegistryKey(), new ResourceLocation(str));\n    }\n\n    public static boolean canGrabViaLists(BlockState state) {\n        boolean returnVal = !ConfigTornado.Storm_Tornado_GrabListBlacklistMode;\n        ResourceLocation registeredName = ForgeRegistries.BLOCKS.getKey(state.getBlock());\n        if (listGrabBlockCache.containsKey(registeredName)) {\n            return listGrabBlockCache.get(registeredName);\n        }\n\n        if (listGrabBlocks.contains(registeredName.toString())) {\n            listGrabBlockCache.put(registeredName, returnVal);\n            return returnVal;\n        }\n\n        if (isStateInListOfTags(state)) {\n            listGrabBlockCache.put(registeredName, returnVal);\n            return returnVal;\n        }\n\n        listGrabBlockCache.put(registeredName, !returnVal);\n        return !returnVal;\n    }\n\t\n    public static boolean isPaused() {\n    \tif (Minecraft.getInstance().isPaused()) return true;\n    \treturn false;\n    }\n    \n    public static boolean isPausedSideSafe(Level world) {\n    \t//return false if server side because it cant be paused legit\n    \tif (!world.isClientSide) return false;\n    \treturn isPausedForClient();\n    }\n    \n    public static boolean isPausedForClient() {\n    \tif (Minecraft.getInstance().isPaused()) return true;\n    \treturn false;\n    }\n\n    public static boolean isAprilFoolsDay() {\n        Calendar calendar = Calendar.getInstance();\n        calendar.setTimeInMillis(System.currentTimeMillis());\n\n        //test\n        //return calendar.get(Calendar.MONTH) == Calendar.MARCH && calendar.get(Calendar.DAY_OF_MONTH) == 25;\n\n        return calendar.get(Calendar.MONTH) == Calendar.APRIL && calendar.get(Calendar.DAY_OF_MONTH) == 1;\n    }\n\n    public static boolean shouldRemoveBlock(BlockState blockID)\n    {\n        //water no\n        if (blockID.getBlock().defaultMapColor() == MapColor.WATER)\n        {\n            return false;\n        }\n\n        return true;\n    }\n\n    public static boolean isOceanBlock(Block blockID)\n    {\n        return false;\n    }\n\n    public static boolean isSolidBlock(Block id)\n    {\n        return (id == Blocks.STONE ||\n                id == Blocks.COBBLESTONE ||\n                id == Blocks.SANDSTONE);\n    }\n\n    public static boolean shouldGrabBlock(Level parWorld, BlockState state)\n    {\n        try\n        {\n            ItemStack itemStr = new ItemStack(Items.DIAMOND_AXE);\n\n            Block block = state.getBlock();\n\n            boolean result = true;\n\n            if (ConfigTornado.Storm_Tornado_GrabCond_List)\n            {\n                try {\n                    result = canGrabViaLists(state);\n                } catch (Exception e) {\n                    //sometimes NPEs (pre 1.18), just assume false if so\n                    e.printStackTrace();\n                    result = false;\n                }\n            } else {\n\n                if (ConfigTornado.Storm_Tornado_GrabCond_StrengthGrabbing)\n                {\n                    float strMin = 0.0F;\n                    float strMax = 0.74F;\n\n                    if (block == null) {\n                        result = false;\n                        return result; //force return false to prevent unchecked future code outside scope\n                    } else {\n\n                        //float strVsBlock = block.getBlockHardness(block.defaultBlockState(), parWorld, new BlockPos(0, 0, 0)) - (((itemStr.getStrVsBlock(block.defaultBlockState()) - 1) / 4F));\n                        float strVsBlock = state.getDestroySpeed(parWorld, new BlockPos(0, 0, 0)) - (((itemStr.getDestroySpeed(block.defaultBlockState()) - 1) / 4F));\n\n                        //System.out.println(strVsBlock);\n                        if (/*block.getHardness() <= 10000.6*/ (strVsBlock <= strMax && strVsBlock >= strMin) ||\n                                (state.getBlock().defaultMapColor() == MapColor.WOOD) ||\n                                state.getBlock().defaultMapColor() == MapColor.WOOL ||\n                                state.getBlock().defaultMapColor() == MapColor.PLANT ||/*\n                                state.getMaterial() == Material.VINE ||*/\n                                block instanceof TallGrassBlock) {\n                            if (!safetyCheck(state)) {\n                                result = false;\n                            }\n                        } else {\n                            result = false;\n                        }\n\n                    }\n                }\n\n                if (ConfigTornado.Storm_Tornado_RefinedGrabRules) {\n                    if (block == Blocks.DIRT || block == Blocks.COARSE_DIRT || block == Blocks.ROOTED_DIRT || block == Blocks.GRASS_BLOCK || block == Blocks.DIRT_PATH || block == Blocks.SAND || block == Blocks.RED_SAND || (block instanceof RotatedPillarBlock && state.getBlock().defaultMapColor() == MapColor.WOOD)) {\n                        result = false;\n                    }\n                    if (!canTornadoGrabBlockRefinedRules(state)) {\n                        result = false;\n                    }\n                }\n            }\n\n            //TODO: 1.18\n            /*if (block == CommonProxy.blockWeatherMachine) {\n                result = false;\n            }*/\n\n            return result;\n        }\n        catch (Exception ex)\n        {\n            ex.printStackTrace();\n            return false;\n        }\n    }\n\n    public static boolean safetyCheck(BlockState state)\n    {\n        Block id = state.getBlock();\n        if (id != Blocks.BEDROCK && id != Blocks.ACACIA_LOG && id != Blocks.CHEST && id != Blocks.JUKEBOX/* && id != Block.waterMoving.blockID && id != Block.waterStill.blockID */)\n        {\n            return true;\n        }\n        else\n        {\n            return false;\n        }\n    }\n\n    public static ServerLevel getWorld(ResourceKey<Level> levelResourceKey) {\n        return ServerLifecycleHooks.getCurrentServer().getLevel(levelResourceKey);\n    }\n\n    public static boolean canTornadoGrabBlockRefinedRules(BlockState state) {\n        ResourceLocation registeredName = ForgeRegistries.BLOCKS.getKey(state.getBlock());\n        if (registeredName.getNamespace().equals(\"dynamictrees\")) {\n            if (registeredName.getPath().contains(\"rooty\") || registeredName.getPath().contains(\"branch\")) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    public static float dist(Vector3f vec1, Vector3f vec2) {\n        double d0 = vec2.x() - vec1.x();\n        double d1 = vec2.y() - vec1.y();\n        double d2 = vec2.z() - vec1.z();\n        return (float) Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2);\n    }\n\n    public static double dist(Vector3d vec1, Vector3d vec2) {\n        double d0 = vec2.x - vec1.x;\n        double d1 = vec2.y - vec1.y;\n        double d2 = vec2.z - vec1.z;\n        return Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2);\n    }\n    \n}\n"
  },
  {
    "path": "src/main/java/weather2/util/WeatherUtilBlock.java",
    "content": "package weather2.util;\n\nimport com.corosus.coroutil.util.CoroUtilBlock;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.core.Direction;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.block.Block;\nimport net.minecraft.world.level.block.Blocks;\nimport net.minecraft.world.level.block.SlabBlock;\nimport net.minecraft.world.level.block.SnowLayerBlock;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.world.level.levelgen.Heightmap;\nimport net.minecraft.world.level.material.MapColor;\nimport net.minecraft.world.phys.AABB;\nimport net.minecraft.world.phys.Vec3;\nimport net.minecraft.world.phys.shapes.BooleanOp;\nimport net.minecraft.world.phys.shapes.Shapes;\nimport net.minecraft.world.phys.shapes.VoxelShape;\nimport weather2.WeatherBlocks;\nimport weather2.block.SandLayerBlock;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * All stackable block code in this class considers \"height\" as a meta val height, not actual pixel height of AABB, basically 1 meta height = 2 pixel height, this is also used for all amount values\n * \n * @author Corosus\n *\n */\npublic class WeatherUtilBlock {\n\n\t//TODO: 1.14 restore removed methods from previous git commits\n\n\n\tpublic static int layerableHeightPropMax = 8;\n\n\tpublic static void fillAgainstWallSmoothly(Level world, Vec3 posSource, float directionYaw, float scanDistance, float fillRadius, Block blockLayerable, int maxBlockStackingAllowed) {\n\t\tfillAgainstWallSmoothly(world, posSource, directionYaw, scanDistance, fillRadius, blockLayerable, 4, maxBlockStackingAllowed);\n\t}\n\n\tpublic static void fillAgainstWallSmoothly(Level world, Vec3 posSource, float directionYaw, float scanDistance, float fillRadius, Block blockLayerable, int heightDiff, int maxBlockStackingAllowed) {\n\n\t\t//layerableHeightPropMax = 8;\n\n\t\t/**\n\t\t * for now, work in halves\n\t\t * if \"wall\" is 4 height (aka 8 pixels high) or less, we can \"go over it\" aka continue onto next block past it\n\t\t *\n\t\t * starting point needs to be air above solid\n\t\t *\n\t\t * scan forward till not air or not placeable\n\t\t *\n\t\t * get block height, if height < 4\n\t\t * - place infront of wall\n\t\t * if height >= 4\n\t\t * - progress onto it and continue past it\n\t\t *\n\t\t *\n\t\t *\n\t\t * - factor in height of current block we are on if its not air, aka half filled sand block vs next block\n\t\t */\n\n\t\t//fix for starting on a layerable block\n\t\tBlockState stateTest = world.getBlockState(CoroUtilBlock.blockPos(posSource));\n\t\tif (stateTest.getBlock() == blockLayerable) {\n\t\t\tint heightTest = getHeightForAnyBlock(stateTest);\n\t\t\tif (heightTest < 8) {\n\t\t\t\t//posSource = new Vector3d(posSource.addVector(0, -1, 0));\n\t\t\t}\n\t\t}\n\n\t\tBlockPos posSourcei = CoroUtilBlock.blockPos(posSource);\n\t\t//int ySource = world.getHeight(posSourcei).getY();\n\t\tint y = posSourcei.getY();\n\t\tfloat tickStep = 0.75F;\n\n\t\t//float startScan = scanDistance;\n\n\t\tVec3 posLastNonWall = new Vec3(posSource.x, posSource.y, posSource.z);\n\t\tVec3 posWall = null;\n\n\t\tBlockPos lastScannedPosXZ = null;//new BlockPos(posSourcei);\n\n\t\t//System.out.println(\"Start block (should be air): \" + world.getBlockState(posSourcei));\n\n\t\tint previousBlockHeight = 0;\n\n\t\t/*System.out.println(\"directionYaw: \" + directionYaw);\n\t\tSystem.out.println(\"x: \" + -Math.sin(Math.toRadians(directionYaw)));\n\t\tSystem.out.println(\"z: \" + Math.cos(Math.toRadians(directionYaw)));*/\n\n\t\t//looking for a proper wall we cant fly over as sand\n\t\tfor (float i = 0; i < scanDistance; i += tickStep) {\n\t\t\tdouble vecX = (-Math.sin(Math.toRadians(directionYaw)) * (i));\n\t\t\tdouble vecZ = (Math.cos(Math.toRadians(directionYaw)) * (i));\n\n\t\t\tint x = Mth.floor(posSource.x + vecX);\n\t\t\tint z = Mth.floor(posSource.z + vecZ);\n\n\t\t\tBlockPos pos = new BlockPos(x, y, z);\n\t\t\tBlockPos posXZ = new BlockPos(x, 0, z);\n\t\t\tBlockState state = world.getBlockState(pos);\n\n\t\t\tif (lastScannedPosXZ == null || !posXZ.equals(lastScannedPosXZ)) {\n\n\t\t\t\tlastScannedPosXZ = new BlockPos(posXZ);\n\n\t\t\t\tAABB aabbCompare = new AABB(pos);\n\t\t\t\tList<AABB> listAABBCollision = new ArrayList<>();\n\t\t\t\tVoxelShape voxelshape = Shapes.create(aabbCompare);\n\t\t\t\t//boolean collided = VoxelShapes.compare(state.getCollisionShapeUncached(world, pos).withOffset((double)l1, (double)k2, (double)i2), voxelshape, IBooleanFunction.AND);\n\t\t\t\tboolean collided = Shapes.joinIsNotEmpty(state.getCollisionShape(world, pos).move(pos.getX(), pos.getY(), pos.getZ()), voxelshape, BooleanOp.AND);\n\t\t\t\t//state.addCollisionBoxToList(world, pos, aabbCompare, listAABBCollision, null, false);\n\n\t\t\t\t//TODO: isReplaceable would require a fake player, see if we can avoid using isReplaceable, it let us place into things like grass? maybe?\n\n\t\t\t\t/*System.out.println(\"try: \" + pos + \" - collided: \" + collided);\n\t\t\t\tif (pos.getX() == 80 && pos.getY() == 87 && pos.getZ() == 153) {\n\t\t\t\t\tSystem.out.println(\"!!!!: \" + pos + \" - collided: \" + collided);\n\t\t\t\t}*/\n\n\t\t\t\t//if solid ground we can place on\n\t\t\t\tif (!state.isAir() && state.getBlock().defaultMapColor() != MapColor.PLANT &&\n\t\t\t\t\t\t/*(!state.getBlock().isReplaceable(world, pos) && */collided) {\n\t\t\t\t\tBlockPos posUp = new BlockPos(x, y + 1, z);\n\t\t\t\t\tBlockState stateUp = world.getBlockState(posUp);\n\t\t\t\t\t//if above it is air\n\t\t\t\t\tif (stateUp.isAir()) {\n\t\t\t\t\t\tint height = getHeightForAnyBlock(state);\n\n\t\t\t\t\t\t//if height of block minus block we are on/comparing against is short enough, we can continue onto it\n\t\t\t\t\t\tif (height - previousBlockHeight <= heightDiff) {\n\t\t\t\t\t\t\t//if block we are progressing to is a full block, reset height val\n\t\t\t\t\t\t\tif (height == 8) {\n\t\t\t\t\t\t\t\tpreviousBlockHeight = 0;\n\t\t\t\t\t\t\t\ty++;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tpreviousBlockHeight = height;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tposLastNonWall = new Vec3(posSource.x + vecX, y, posSource.z + vecZ);\n\n\t\t\t\t\t\t\t//System.out.println(posLastNonWall);\n\n\t\t\t\t\t\t\tcontinue;\n\t\t\t\t\t\t\t//too high, count as a wall\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tposWall = new Vec3(posSource.x + vecX, y, posSource.z + vecZ);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t//hit a wall\n\t\t\t\t\t} else {\n\t\t\t\t\t\tposWall = new Vec3(posSource.x + vecX, y, posSource.z + vecZ);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\n\t\t\t\t\t//startScan = i;\n\t\t\t\t\t//posWall = new Vector3d(posSource.x + vecX, y, posSource.z + vecZ);\n\t\t\t\t\t//break;\n\t\t\t\t} else {\n\t\t\t\t\tposLastNonWall = new Vec3(posSource.x + vecX, y, posSource.z + vecZ);\n\t\t\t\t}\n\n\t\t\t} else {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t}\n\n\t\tif (posWall != null) {\n\t\t\tint amountWeHave = 1;\n\t\t\tint amountToAddPerXZ = 1;\n\n\t\t\tBlockState state = world.getBlockState(CoroUtilBlock.blockPos(posWall));\n\t\t\tBlockState state1 = world.getBlockState(CoroUtilBlock.blockPos(posLastNonWall).offset(1, 0, 0));\n\t\t\tBlockState state22 = world.getBlockState(CoroUtilBlock.blockPos(posLastNonWall).offset(-1, 0, 0));\n\t\t\tBlockState state3 = world.getBlockState(CoroUtilBlock.blockPos(posLastNonWall).offset(0, 0, 1));\n\t\t\tBlockState state4 = world.getBlockState(CoroUtilBlock.blockPos(posLastNonWall).offset(0, 0, -1));\n\n\t\t\t//check all around place spot for cactus and cancel if true, to prevent cactus pop off when we place next to it\n\t\t\tif (state.getBlock() == Blocks.CACTUS || state1.getBlock() == Blocks.CACTUS ||\n\t\t\t\t\tstate22.getBlock() == Blocks.CACTUS || state3.getBlock() == Blocks.CACTUS || state4.getBlock() == Blocks.CACTUS) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tBlockPos pos2 = CoroUtilBlock.blockPos(posLastNonWall.x, posLastNonWall.y, posLastNonWall.z);\n\t\t\tBlockState state2 = world.getBlockState(pos2);\n\t\t\tif (state2.getBlock().defaultMapColor() == MapColor.WATER || state2.getBlock().defaultMapColor() == MapColor.FIRE) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tamountWeHave = trySpreadOnPos2(world, CoroUtilBlock.blockPos(posLastNonWall.x, posLastNonWall.y, posLastNonWall.z), amountWeHave, amountToAddPerXZ, 10, blockLayerable, maxBlockStackingAllowed);\n\t\t} else {\n\t\t\t//System.out.println(\"no wall found\");\n\t\t}\n\t}\n\n\tpublic static int trySpreadOnPos2(Level world, BlockPos posSpreadTo, int amount, int amountAllowedToAdd, int maxDropAllowed, Block blockLayerable, int maxBlockStackingAllowed) {\n\n\t\tif (amount <= 0) return amount;\n\n\t\t/**\n\t\t * - check pos for solid\n\t\t * - if air, tick down till not air or drop limit\n\t\t * - at first non air, find first block with up face solid or snow block\n\t\t * - set air to everything between not air and up face solid or snow block (2 high tall grass removal)\n\t\t * - \n\t\t * - run code that sets snow, deals with solid up face or existing snow, fully or partially layered\n\t\t *\n\t\t *\n\t\t */\n\n\t\t//must have clear air above first spots\n\t\t//TODO: might need special case so we can fill up a partially layered snow block\n\t\tif (!world.getBlockState(posSpreadTo.offset(0, 1, 0)).isAir()) {\n\t\t\treturn amount;\n\t\t}\n\n\t\tBlockState statePos = world.getBlockState(posSpreadTo);\n\n\t\tBlockPos posCheckNonAir = new BlockPos(posSpreadTo);\n\t\tBlockState stateCheckNonAir = world.getBlockState(posCheckNonAir);\n\n\t\tint depth = 0;\n\n\t\t//find first non air\n\t\twhile (stateCheckNonAir.isAir()) {\n\t\t\tposCheckNonAir = posCheckNonAir.offset(0, -1, 0);\n\t\t\tstateCheckNonAir = world.getBlockState(posCheckNonAir);\n\t\t\tdepth++;\n\t\t\t//bail if drop too far, aka sand/snow fully particleizes\n\t\t\tif (depth > maxDropAllowed) {\n\t\t\t\treturn amount;\n\t\t\t}\n\t\t}\n\n\t\tBlockPos posCheckPlaceable = new BlockPos(posCheckNonAir);\n\t\tBlockState stateCheckPlaceable = world.getBlockState(posCheckPlaceable);\n\n\t\t//new check to limit how high snow/sand can stack\n\t\tif (maxBlockStackingAllowed > 0) {\n\t\t\tboolean sandMode = false;\n\t\t\tif (blockLayerable == Blocks.SNOW) {\n\t\t\t\tsandMode = false;\n\t\t\t} else if (blockLayerable == WeatherBlocks.BLOCK_SAND_LAYER.get()) {\n\t\t\t\tsandMode = true;\n\t\t\t}\n\t\t\tint foundBlocks = 0;\n\t\t\tBlockPos posCheckDownForStacks = new BlockPos(posCheckPlaceable);\n\t\t\tBlockState stateCheckDownForStacks = world.getBlockState(posCheckPlaceable);\n\t\t\tif ((!sandMode && stateCheckPlaceable.getBlock() == Blocks.SNOW_BLOCK) || (sandMode && stateCheckPlaceable.getBlock() == Blocks.SAND)) {\n\t\t\t\twhile ((!sandMode && stateCheckDownForStacks.getBlock() == Blocks.SNOW_BLOCK) || (sandMode && stateCheckDownForStacks.getBlock() == Blocks.SAND)) {\n\t\t\t\t\tfoundBlocks++;\n\t\t\t\t\tif (foundBlocks >= maxBlockStackingAllowed) {\n\t\t\t\t\t\t//System.out.println(\"max snow stack allowed, bail\");\n\t\t\t\t\t\treturn amount;\n\t\t\t\t\t}\n\t\t\t\t\tposCheckDownForStacks = posCheckDownForStacks.offset(0, -1, 0);\n\t\t\t\t\tstateCheckDownForStacks = world.getBlockState(posCheckDownForStacks);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\n\t\tint distForPlaceableBlocks = 0;\n\n\t\twhile (true && distForPlaceableBlocks < 10) {\n\t\t\t//if can be placed into, continue, as long as its not our block as it is replacable at layer height 1\n\t\t\tAABB aabbCompare = new AABB(posCheckPlaceable);\n\t\t\t//List<AxisAlignedBB> listAABBCollision = new ArrayList<>();\n\t\t\tVoxelShape voxelshape = Shapes.create(aabbCompare);\n\t\t\tboolean collided = Shapes.joinIsNotEmpty(stateCheckPlaceable.getCollisionShape(world, posCheckPlaceable).move(posCheckPlaceable.getX(), posCheckPlaceable.getY(), posCheckPlaceable.getZ()), voxelshape, BooleanOp.AND);\n\t\t\t//stateCheckPlaceable.addCollisionBoxToList(world, posCheckPlaceable, aabbCompare, listAABBCollision, null, false);\n\n\t\t\t//TODO: isReplaceable would require a fake player, see if we can avoid using isReplaceable, it let us place into things like grass? maybe?\n\n\t\t\tif (stateCheckPlaceable.getBlock() != blockLayerable && /*stateCheckPlaceable.getBlock().isReplaceable(world, posCheckPlaceable) && */!collided && !stateCheckPlaceable.liquid()) {\n\t\t\t\tposCheckPlaceable = posCheckPlaceable.offset(0, -1, 0);\n\t\t\t\tstateCheckPlaceable = world.getBlockState(posCheckPlaceable);\n\t\t\t\tdistForPlaceableBlocks++;\n\t\t\t\tcontinue;\n\t\t\t\t//if its the kind of solid we want, break loop\n\t\t\t} else if (stateCheckPlaceable.isFaceSturdy(world, posCheckPlaceable, Direction.UP) ||\n\t\t\t\t\tstateCheckPlaceable.getBlock() == blockLayerable) {\n\t\t\t\tbreak;\n\t\t\t\t//its something we cant stack onto\n\t\t\t} else {\n\t\t\t\t//System.out.println(\"found unstackable block: \" + stateCheckPlaceable);\n\t\t\t\treturn amount;\n\t\t\t}\n\t\t}\n\n\t\t//for some reason theres 10+ blocks of half solid blocks, lets just abort\n\t\tif (distForPlaceableBlocks >= 10) {\n\t\t\treturn amount;\n\t\t}\n\n\t\t//at this point the block we are about to work with is solid facing up, or snow\n\t\tif (!stateCheckPlaceable.isFaceSturdy(world, posCheckPlaceable, Direction.UP) &&\n\t\t\t\tstateCheckPlaceable.getBlock() != blockLayerable) {\n\t\t\tSystem.out.println(\"shouldnt be, failed a check somewhere!\");\n\t\t\treturn amount;\n\t\t}\n\n\t\t//lets clear out the blocks we found between air and solid or snow block\n\t\tfor (int i = 0; i < distForPlaceableBlocks; i++) {\n\t\t\t//System.out.println(\"clear out pos: \" + posCheckNonAir);\n\t\t\t//System.out.println(\"clear out pos: \" + world.getBlockState(posCheckNonAir));\n\t\t\tworld.setBlockAndUpdate(posCheckNonAir.offset(0, -i, 0), Blocks.AIR.defaultBlockState());\n\t\t}\n\n\t\tBlockPos posPlaceLayerable = new BlockPos(posCheckPlaceable);\n\t\tBlockState statePlaceLayerable = world.getBlockState(posPlaceLayerable);\n\n\t\tint amountToAdd = amountAllowedToAdd;\n\n\t\t//add in the amount of air blocks we found\n\t\t//distForPlaceableBlocks += depth;\n\n\t\t//just place while stuff to add and air above\n\n\t\twhile (amountAllowedToAdd > 0 && world.getBlockState(posPlaceLayerable.offset(0, 1, 0)).isAir()) {\n\t\t\t//if no more layers to add\n\t\t\tif (amountAllowedToAdd <= 0) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\t//if its snow we can add snow to\n\t\t\tif (statePlaceLayerable.getBlock() == blockLayerable && getHeightForLayeredBlock(statePlaceLayerable) < layerableHeightPropMax) {\n\t\t\t\tint height = getHeightForLayeredBlock(statePlaceLayerable);\n\t\t\t\t//System.out.println(\"old height: \" + height);\n\t\t\t\t//if (height < snowMetaMax) {\n\t\t\t\theight += amountAllowedToAdd;\n\t\t\t\tif (height > layerableHeightPropMax) {\n\t\t\t\t\tamountAllowedToAdd = height - layerableHeightPropMax;\n\t\t\t\t\theight = layerableHeightPropMax;\n\n\t\t\t\t} else {\n\t\t\t\t\tamountAllowedToAdd = 0;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\t//System.out.println(\"new height: \" + height);\n\t\t\t\t\tworld.setBlockAndUpdate(posPlaceLayerable, setBlockWithLayerState(blockLayerable, height));\n\t\t\t\t} catch (Exception e) {\n\t\t\t\t\te.printStackTrace();\n\t\t\t\t}\n\n\t\t\t\t//if we maxed it, up the val\n\t\t\t\tif (height == layerableHeightPropMax) {\n\t\t\t\t\tposPlaceLayerable = posPlaceLayerable.offset(0, 1, 0);\n\t\t\t\t\tstatePlaceLayerable = world.getBlockState(posPlaceLayerable);\n\t\t\t\t}\n\t\t\t\t//}\n\t\t\t\t//solid block------------------- or air because we moved up 1 due to the previous being fully filled snow\n\t\t\t} else if (statePlaceLayerable.isFaceSturdy(world, posPlaceLayerable, Direction.UP)) {\n\t\t\t\tposPlaceLayerable = posPlaceLayerable.offset(0, 1, 0);\n\t\t\t\tstatePlaceLayerable = world.getBlockState(posPlaceLayerable);\n\t\t\t\t//air\n\t\t\t} else if (statePlaceLayerable.isAir()) {\n\t\t\t\t//copypasta, refactor/reduce once things work\n\t\t\t\tint height = amountAllowedToAdd;\n\t\t\t\tif (height > layerableHeightPropMax) {\n\t\t\t\t\tamountAllowedToAdd = height - layerableHeightPropMax;\n\t\t\t\t\theight = layerableHeightPropMax;\n\n\t\t\t\t} else {\n\t\t\t\t\tamountAllowedToAdd = 0;\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\t//System.out.println(\"air logic\");\n\t\t\t\t\t//TODO: if other idea fails, put the stacks of snow count check here\n\t\t\t\t\tworld.setBlockAndUpdate(posPlaceLayerable, setBlockWithLayerState(blockLayerable, height));\n\t\t\t\t} catch (Exception e) {\n\t\t\t\t\te.printStackTrace();\n\t\t\t\t}\n\n\t\t\t\t//if we maxed it, up the val\n\t\t\t\tif (height == layerableHeightPropMax) {\n\t\t\t\t\tposPlaceLayerable = posPlaceLayerable.offset(0, 1, 0);\n\t\t\t\t\tstatePlaceLayerable = world.getBlockState(posPlaceLayerable);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t//System.out.println(\"wat! - \" + statePlaceLayerable);\n\t\t\t}\n\t\t}\n\n\t\tif (amountAllowedToAdd < 0) {\n\t\t\t//System.out.println(\"wat\");\n\t\t}\n\t\tint amountAdded = amountToAdd - amountAllowedToAdd;\n\t\tamount -= amountAdded;\n\t\treturn amount;\n\n\t}\n\n\tpublic static int getHeightForAnyBlock(BlockState state) {\n\t\tBlock block = state.getBlock();\n\t\tif (block == Blocks.SNOW) {\n\t\t\treturn state.getValue(SnowLayerBlock.LAYERS).intValue();\n\t\t} else if (block == WeatherBlocks.BLOCK_SAND_LAYER.get()) {\n\t\t\treturn state.getValue(SandLayerBlock.LAYERS).intValue();\n\t\t} else if (block == Blocks.SAND || block == Blocks.SNOW_BLOCK) {\n\t\t\treturn 8;\n\t\t} else if (block instanceof SlabBlock) {\n\t\t\treturn 4;\n\t\t} else if (block == Blocks.AIR) {\n\t\t\treturn 0;\n\t\t} else {\n\t\t\treturn 8;\n\t\t}\n\t}\n\n\tpublic static int getHeightForLayeredBlock(BlockState state) {\n\t\tif (state.getBlock() == Blocks.SNOW) {\n\t\t\treturn (state.getValue(SnowLayerBlock.LAYERS)).intValue();\n\t\t} else if (state.getBlock() == WeatherBlocks.BLOCK_SAND_LAYER.get()) {\n\t\t\treturn state.getValue(SandLayerBlock.LAYERS).intValue();\n\t\t} else if (state.getBlock() == Blocks.SAND || state.getBlock() == Blocks.SNOW_BLOCK) {\n\t\t\treturn 8;\n\t\t} else {\n\t\t\t//missing implementation\n\t\t\treturn 0;\n\t\t}\n\t}\n\n\tpublic static BlockState setBlockWithLayerState(Block block, int height) {\n\t\tboolean solidBlockUnderMode = true;\n\t\tif (block == Blocks.SNOW) {\n\t\t\tif (height == layerableHeightPropMax && solidBlockUnderMode) {\n\t\t\t\treturn Blocks.SNOW_BLOCK.defaultBlockState();\n\t\t\t} else {\n\t\t\t\treturn block.defaultBlockState().setValue(SnowLayerBlock.LAYERS, height);\n\t\t\t}\n\t\t} else if (block == WeatherBlocks.BLOCK_SAND_LAYER.get()) {\n\t\t\tif (height == layerableHeightPropMax && solidBlockUnderMode) {\n\t\t\t\treturn Blocks.SAND.defaultBlockState();\n\t\t\t} else {\n\t\t\t\treturn block.defaultBlockState().setValue(SandLayerBlock.LAYERS, height);\n\t\t\t}\n\t\t} else {\n\t\t\t//means missing implementation\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tpublic static BlockPos getPrecipitationHeightSafe(Level world, BlockPos pos) {\n\t\treturn getPrecipitationHeightSafe(world, pos, Heightmap.Types.MOTION_BLOCKING);\n\t}\n\n\t/**\n\t * Safe version of World.getPrecipitationHeight that wont invoke chunkgen/chunkload if its requesting height in unloaded chunk\n\t *\n\t * @param world\n\t * @param pos\n\t * @return\n\t */\n\tpublic static BlockPos getPrecipitationHeightSafe(Level world, BlockPos pos, Heightmap.Types heightmapType) {\n\t\tif (world.hasChunkAt(pos)) {\n\t\t\treturn world.getHeightmapPos(heightmapType, pos);\n\t\t} else {\n\t\t\treturn new BlockPos(pos.getX(), -255, pos.getZ());\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/util/WeatherUtilDim.java",
    "content": "package weather2.util;\n\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.world.level.Level;\n\npublic class WeatherUtilDim {\n\n    public static boolean canBlockSeeSky(Level world, BlockPos pos) {\n        if (pos.getY() >= getSeaLevel(world)) {\n            return world.canSeeSkyFromBelowWater(pos);\n        } else {\n            BlockPos blockpos = new BlockPos(pos.getX(), getSeaLevel(world), pos.getZ());\n            if (!world.canSeeSkyFromBelowWater(blockpos)) {\n                return false;\n            } else {\n                for(BlockPos blockpos1 = blockpos.below(); blockpos1.getY() > pos.getY(); blockpos1 = blockpos1.below()) {\n                    BlockState blockstate = world.getBlockState(blockpos1);\n                    if (blockstate.getLightBlock(world, blockpos1) > 0 && !blockstate.liquid()) {\n                        return false;\n                    }\n                }\n\n                return true;\n            }\n        }\n    }\n\n    public static int getSeaLevel(Level world) {\n        //TODO: sync customizable sea level to client, also use World.getSeaLevel if logical server\n        return 63;\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/weather2/util/WeatherUtilEntity.java",
    "content": "package weather2.util;\n\nimport com.corosus.coroutil.util.CoroUtilEntOrParticle;\nimport extendedrenderer.particle.entity.EntityRotFX;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.core.Direction;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.entity.LivingEntity;\nimport net.minecraft.world.entity.animal.Squid;\nimport net.minecraft.world.entity.item.ItemEntity;\nimport net.minecraft.world.entity.player.Player;\nimport net.minecraft.world.entity.projectile.FishingHook;\nimport net.minecraft.world.entity.vehicle.AbstractMinecart;\nimport net.minecraft.world.entity.vehicle.Boat;\nimport net.minecraft.world.item.ArmorItem;\nimport net.minecraft.world.item.Item;\nimport net.minecraft.world.item.ItemStack;\nimport net.minecraft.world.item.Items;\nimport net.minecraft.world.level.ClipContext;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.levelgen.Heightmap;\nimport net.minecraft.world.phys.BlockHitResult;\nimport net.minecraft.world.phys.HitResult;\nimport net.minecraft.world.phys.Vec3;\nimport net.minecraftforge.fml.LogicalSide;\nimport net.minecraftforge.fml.util.thread.EffectiveSide;\n\nimport java.util.HashMap;\n\npublic class WeatherUtilEntity {\n\t\n    public static float getWeight(Object entity1, boolean forTornado)\n    {\n    \tLevel world = CoroUtilEntOrParticle.getWorld(entity1);\n\n        if (world == null) {\n            return 1F;\n        }\n\n\t\tif (isParticleRotServerSafe(world, entity1))\n\t\t{\n\t\t\tfloat var = WeatherUtilParticle.getParticleWeight((EntityRotFX)entity1);\n\n\t\t\tif (var != -1)\n\t\t\t{\n\t\t\t\treturn var;\n\t\t\t}\n\t\t}\n\n\t\tif (entity1 instanceof Squid)\n\t\t{\n\t\t\treturn 400F;\n\t\t}\n\n        if (entity1 instanceof LivingEntity)\n        {\n        \tLivingEntity livingEnt = (LivingEntity) entity1;\n        \tint airTime = livingEnt.getPersistentData().getInt(\"timeInAir\");\n        \tif (livingEnt.onGround() || livingEnt.isInWater())\n            {\n                airTime = 0;\n            }\n            else {\n            \tairTime++;\n            }\n        \t\n        \tlivingEnt.getPersistentData().putInt(\"timeInAir\", airTime);\n\n\t\t\tif (entity1 instanceof Player) {\n\t\t\t\tif (((Player) entity1).abilities.instabuild) return 99999999F;\n\t\t\t\treturn 5.0F + airTime / 400.0F;\n\t\t\t} else {\n\t\t\t\treturn 500.0F + (livingEnt.onGround() ? 2.0F : 0.0F) + (airTime / 400.0F);\n\t\t\t}\n        }\n\n        if (entity1 instanceof Boat || entity1 instanceof ItemEntity || entity1 instanceof FishingHook)\n        {\n            return 4000F;\n        }\n\n        if (entity1 instanceof AbstractMinecart)\n        {\n            return 80F;\n        }\n\n        return 1F;\n    }\n\n\tpublic static HashMap<Item, Float> armorToWeight = new HashMap<>();\n\n\tstatic {\n\t\tarmorToWeight.put(Items.IRON_HELMET, 0.1F);\n\t\tarmorToWeight.put(Items.IRON_CHESTPLATE, 0.2F);\n\t\tarmorToWeight.put(Items.IRON_LEGGINGS, 0.15F);\n\t\tarmorToWeight.put(Items.IRON_BOOTS, 0.1F);\n\n\t\tarmorToWeight.put(Items.GOLDEN_HELMET, 0.1F);\n\t\tarmorToWeight.put(Items.GOLDEN_CHESTPLATE, 0.2F);\n\t\tarmorToWeight.put(Items.GOLDEN_LEGGINGS, 0.15F);\n\t\tarmorToWeight.put(Items.GOLDEN_BOOTS, 0.1F);\n\n\t\tarmorToWeight.put(Items.DIAMOND_HELMET, 0.1F);\n\t\tarmorToWeight.put(Items.DIAMOND_CHESTPLATE, 0.2F);\n\t\tarmorToWeight.put(Items.DIAMOND_LEGGINGS, 0.15F);\n\t\tarmorToWeight.put(Items.DIAMOND_BOOTS, 0.1F);\n\t}\n\n\tpublic static float getWeightAdjFromEquipment(float weightIn, Player player) {\n\t\tfloat influence = 1.2F;\n\t\tfor (ItemStack stack : player.getArmorSlots()) {\n\t\t\tif (armorToWeight.containsKey(stack.getItem())) {\n\t\t\t\tweightIn += armorToWeight.get(stack.getItem()) * influence;\n\t\t\t}\n\t\t}\n\t\treturn weightIn;\n\t}\n\n\tpublic static float getWeight(Object entity1)\n\t{\n\t\tLevel world = CoroUtilEntOrParticle.getWorld(entity1);\n\n\t\tif (world == null) {\n\t\t\treturn 1F;\n\t\t}\n\n\t\tif (isParticleRotServerSafe(world, entity1))\n\t\t{\n\t\t\tfloat var = WeatherUtilParticle.getParticleWeight((EntityRotFX)entity1);\n\n\t\t\tif (var != -1)\n\t\t\t{\n\t\t\t\treturn var;\n\t\t\t}\n\t\t}\n\n\t\tif (entity1 instanceof Squid)\n\t\t{\n\t\t\treturn 400F;\n\t\t}\n\n\t\tif (entity1 instanceof LivingEntity)\n\t\t{\n\t\t\tLivingEntity livingEnt = (LivingEntity) entity1;\n\t\t\tint airTime = livingEnt.getPersistentData().getInt(\"timeInAir\");\n\t\t\tif (livingEnt.onGround() || livingEnt.isInWater())\n\t\t\t{\n\t\t\t\tairTime = 0;\n\t\t\t}\n\t\t\telse {\n\t\t\t\tairTime++;\n\t\t\t}\n\n\t\t\tlivingEnt.getPersistentData().putInt(\"timeInAir\", airTime);\n\n\t\t\tif (entity1 instanceof Player) {\n\t\t\t\tif (((Player) entity1).abilities.instabuild) return 99999999F;\n\t\t\t\treturn 5.0F + airTime / 400.0F;\n\t\t\t} else {\n\t\t\t\treturn 500.0F + (livingEnt.onGround() ? 2.0F : 0.0F) + (airTime / 400.0F);\n\t\t\t}\n\t\t}\n\n\t\tif (entity1 instanceof Boat || entity1 instanceof ItemEntity || entity1 instanceof FishingHook)\n\t\t{\n\t\t\treturn 4000F;\n\t\t}\n\n\t\tif (entity1 instanceof AbstractMinecart)\n\t\t{\n\t\t\treturn 80F;\n\t\t}\n\n\t\treturn 1F;\n\t}\n    \n    public static boolean isParticleRotServerSafe(Level world, Object obj) {\n    \tif (EffectiveSide.get().equals(LogicalSide.SERVER)) {\n    \t\treturn false;\n    \t}\n    \tif (!world.isClientSide) return false;\n    \treturn isParticleRotClientCheck(obj);\n    }\n    \n    public static boolean isParticleRotClientCheck(Object obj) {\n    \treturn obj instanceof EntityRotFX;\n    }\n    \n    public static double getDistanceSqEntToPos(Entity ent, BlockPos pos) {\n    \treturn ent.position().distanceToSqr(Vec3.atCenterOf(pos));\n    }\n\n\tpublic static boolean isEntityOutside(Entity parEnt) {\n\t\treturn isEntityOutside(parEnt, false);\n\t}\n\n\tpublic static boolean isEntityOutside(Entity parEnt, boolean cheapCheck) {\n\t\treturn isPosOutside(parEnt.level(), parEnt.position(), cheapCheck, false);\n\t}\n\n\tpublic static boolean isPosOutside(Level parWorld, Vec3 parPos) {\n\t\treturn isPosOutside(parWorld, parPos, false, false);\n\t}\n\n\tpublic static boolean isPosOutside(Level parWorld, Vec3 parPos, boolean cheapCheck, boolean eachSideClearCheck) {\n\n\t\tint height = WeatherUtilBlock.getPrecipitationHeightSafe(parWorld, new BlockPos(Mth.floor(parPos.x), 0, Mth.floor(parPos.z))).getY();\n\t\tif (height < parPos.y+1) {\n\t\t\treturn true;\n\t\t}\n\t\tif (cheapCheck) return false;\n\n\t\tint rangeCheck = 5;\n\t\tint yOffset = 0;\n\t\t//start 1 block away from start position to avoid colliding with self when used for a block\n\t\tint xzInitialOffset = 1;\n\n\t\tboolean nsCheck = false;\n\n\t\tVec3 vecTry = new Vec3(parPos.x + Direction.NORTH.getStepX()*rangeCheck, parPos.y+yOffset, parPos.z + Direction.NORTH.getStepZ()*rangeCheck);\n\t\tVec3 parPosTry = new Vec3(parPos.x + Direction.NORTH.getStepX()*xzInitialOffset, parPos.y+yOffset, parPos.z + Direction.NORTH.getStepZ()*xzInitialOffset);\n\t\tif (checkVecOutside(parWorld, parPosTry, vecTry)) {\n\t\t\tif (eachSideClearCheck) {\n\t\t\t\tnsCheck = true;\n\t\t\t} else {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\tvecTry = new Vec3(parPos.x + Direction.SOUTH.getStepX()*rangeCheck, parPos.y+yOffset, parPos.z + Direction.SOUTH.getStepZ()*rangeCheck);\n\t\tparPosTry = new Vec3(parPos.x + Direction.SOUTH.getStepX()*xzInitialOffset, parPos.y+yOffset, parPos.z + Direction.SOUTH.getStepZ()*xzInitialOffset);\n\t\tif (checkVecOutside(parWorld, parPosTry, vecTry)) {\n\t\t\tif (eachSideClearCheck) {\n\t\t\t\treturn nsCheck;\n\t\t\t} else {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\tnsCheck = false;\n\n\t\tvecTry = new Vec3(parPos.x + Direction.EAST.getStepX()*rangeCheck, parPos.y+yOffset, parPos.z + Direction.EAST.getStepZ()*rangeCheck);\n\t\tparPosTry = new Vec3(parPos.x + Direction.EAST.getStepX()*xzInitialOffset, parPos.y+yOffset, parPos.z + Direction.EAST.getStepZ()*xzInitialOffset);\n\t\tif (checkVecOutside(parWorld, parPosTry, vecTry)) {\n\t\t\tif (eachSideClearCheck) {\n\t\t\t\tnsCheck = true;\n\t\t\t} else {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\tvecTry = new Vec3(parPos.x + Direction.WEST.getStepX()*rangeCheck, parPos.y+yOffset, parPos.z + Direction.WEST.getStepZ()*rangeCheck);\n\t\tparPosTry = new Vec3(parPos.x + Direction.WEST.getStepX()*xzInitialOffset, parPos.y+yOffset, parPos.z + Direction.WEST.getStepZ()*xzInitialOffset);\n\t\tif (checkVecOutside(parWorld, parPosTry, vecTry)) {\n\t\t\tif (eachSideClearCheck) {\n\t\t\t\treturn nsCheck;\n\t\t\t} else {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tpublic static boolean checkVecOutside(Level parWorld, Vec3 parPos, Vec3 parCheckPos) {\n\t\tBlockHitResult blockhitresult = parWorld.clip(new ClipContext(parPos, parCheckPos, ClipContext.Block.VISUAL, ClipContext.Fluid.NONE, null));\n\t\tif (blockhitresult.getType() == HitResult.Type.MISS) {\n\t\t\tint height = WeatherUtilBlock.getPrecipitationHeightSafe(parWorld, new BlockPos(Mth.floor(parCheckPos.x), 0, Mth.floor(parCheckPos.z))).getY();\n\t\t\t//System.out.println(\"height: \" + height + \" vs \" + parCheckPos.y);\n\t\t\tif (height < parCheckPos.y) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\tpublic static boolean isPlayerSheltered(Entity player) {\n\t\tint x = Mth.floor(player.getX());\n\t\tint y = Mth.floor(player.getY() + player.getEyeHeight());\n\t\tint z = Mth.floor(player.getZ());\n\t\treturn player.level().getHeight(Heightmap.Types.MOTION_BLOCKING, x, z) > y;\n\t}\n\n\tpublic static boolean canPosSeePos(Level level, Vec3 pos1, Vec3 pos2) {\n\t\tVec3 vec3 = new Vec3(pos1.x(), pos1.y(), pos1.z());\n\t\tVec3 vec31 = new Vec3(pos2.x(), pos2.y(), pos2.z());\n\t\tif (vec31.distanceTo(vec3) > 128.0D) {\n\t\t\treturn false;\n\t\t} else {\n\t\t\treturn level.clip(new ClipContext(vec3, vec31, ClipContext.Block.COLLIDER, ClipContext.Fluid.NONE, null)).getType() == HitResult.Type.MISS;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/util/WeatherUtilParticle.java",
    "content": "package weather2.util;\n\nimport java.lang.reflect.Field;\nimport java.util.ArrayDeque;\nimport java.util.Map;\nimport java.util.Queue;\nimport java.util.Random;\n\nimport com.corosus.coroutil.util.CoroUtilEntOrParticle;\nimport com.google.common.collect.Maps;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.client.particle.ParticleRenderType;\nimport net.minecraft.client.particle.Particle;\nimport net.minecraft.client.particle.ParticleEngine;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.util.Mth;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport extendedrenderer.particle.entity.EntityRotFX;\nimport extendedrenderer.particle.entity.ParticleTexFX;\nimport weather2.IWindHandler;\n\npublic class WeatherUtilParticle {\n    //public static ArrayDeque<Particle>[][] fxLayers;\n    public static Map<ParticleRenderType, Queue<Particle>> fxLayers;\n    \n    public static int effLeafID = 0;\n    public static int effRainID = 1;\n    public static int effWindID = 2;\n    public static int effSnowID = 3;\n    /*public static int effSandID = 4;\n    public static int effWind2ID = 2;*/\n    \n    public static Random rand = new Random();\n    //public static int rainDrops = 20;\n    \n    \n    \n    //weather2: not sure what will happen to this in 1.7, copied over for convenience\n    public static int getParticleAge(Particle ent)\n    {\n        return ent.age;\n        //return (Integer) OldUtil.getPrivateValueBoth(Particle.class, ent, \"age\", \"age\");\n    }\n\n    //weather2: not sure what will happen to this in 1.7, copied over for convenience\n    public static void setParticleAge(Particle ent, int val)\n    {\n        ent.age = val;\n        //OldUtil.setPrivateValueBoth(Particle.class, ent, \"age\", \"age\", val);\n    }\n\n    @OnlyIn(Dist.CLIENT)\n    public static void getFXLayers()\n    {\n        //fxLayers\n        Field field = null;\n\n        try\n        {\n            field = (ParticleEngine.class).getDeclaredField(\"particles\");//ObfuscationReflectionHelper.remapFieldNames(\"net.minecraft.client.particle.EffectRenderer\", new String[] { \"fxLayers\" })[0]);\n            field.setAccessible(true);\n            fxLayers = (Map<ParticleRenderType, Queue<Particle>>)field.get(Minecraft.getInstance().particleEngine);\n        }\n        catch (Exception ex)\n        {\n        \t//System.out.println(\"temp message: obf reflection fail!\");\n        \t//ex.printStackTrace();\n            try\n            {\n                field = (ParticleEngine.class).getDeclaredField(\"f_107289_\");\n                field.setAccessible(true);\n                fxLayers = (Map<ParticleRenderType, Queue<Particle>>)field.get(Minecraft.getInstance().particleEngine);\n            }\n            catch (Exception ex2)\n            {\n                ex2.printStackTrace();\n            }\n        }\n    }\n\n    @OnlyIn(Dist.CLIENT)\n    public static float getParticleWeight(EntityRotFX entity1)\n    {\n    \t//commented out for weather2 copy\n        /*if (entity1 instanceof EntityFallingRainFX)\n        {\n            return 1.1F;\n        }*/\n\n        if (entity1 instanceof IWindHandler) {\n            return ((IWindHandler) entity1).getWindWeight();\n        }\n\n        if (entity1 instanceof ParticleTexFX)\n        {\n            return 5.0F + ((float)entity1.getAge() / 200);\n        }\n\n        //commented out for weather2 copy\n        /*if (entity1 instanceof EntityWindFX)\n        {\n            return 1.4F + ((float)entity1.getAge() / 200);\n        }*/\n\n        if (entity1 instanceof Particle)\n        {\n            return 5.0F + ((float)entity1.getAge() / 200);\n        }\n\n        return -1;\n    }\n\n    public static BlockPos getPos(Particle particle) {\n        return new BlockPos(Mth.floor(particle.x), Mth.floor(particle.y), Mth.floor(Mth.floor(particle.z)));\n    }\n    \n    /*@SideOnly(Side.CLIENT)\n    public static void shakeTrees(int range)\n    {\n        int size = range;\n        int hsize = size / 2;\n        int curX = (int)player.posX;\n        int curY = (int)player.posY - 1;\n        int curZ = (int)player.posZ;\n        //if (true) return;\n        float windStr = 1F;\n\n        for (int xx = curX - hsize; xx < curX + hsize; xx++)\n        {\n            for (int yy = curY - hsize; yy < curY + hsize + 10; yy++)\n            {\n                for (int zz = curZ - hsize; zz < curZ + hsize; zz++)\n                {\n                    //REMOVE VINES!!!!!\n                    int uh = (int)(40 / (windStr + 0.001));\n\n                    //System.out.println(uh);\n                    if (uh < 1)\n                    {\n                        uh = 1;\n                    }\n\n                    if (worldRef.rand.nextInt(uh) == 0)\n                    {\n                        for (int i = 0; i < p_blocks_leaf.size(); i++)\n                        {\n                            int id = getBlockId(xx, yy, zz);\n\n                            if (id == ((Block)p_blocks_leaf.get(i)).blockID)\n                            {\n                                if (id == Block.leaves.blockID)\n                                {\n                                    if (getBlockId(xx, yy - 1, zz) == 0)\n                                    {\n                                        EntityRotFX var31 = new EntityTexBiomeColorFX(worldRef, (double)xx, (double)yy - 0.5, (double)zz, 0D, 0D, 0D, 10D, 0, effLeafID, id, getBlockMetadata(xx, yy, zz), xx, yy, zz);\n                                        WeatherUtil.setParticleGravity((EntityFX)var31, 0.3F);\n\n                                        for (int ii = 0; ii < 10; ii++)\n                                        {\n                                            applyWindForce(var31);\n                                        }\n\n                                        var31.rotationYaw = rand.nextInt(360);\n                                        //var31.spawnAsWeatherEffect();\n                                        spawnQueue.add(var31);\n                                    }\n                                }\n                                else\n                                {\n                                    //This is non leaves, as in wildgrass or wahtever is in the p_blocks_leaf list (no special rules)\n                                    EntityRotFX var31 = new EntityTexBiomeColorFX(worldRef, (double)xx, (double)yy + 0.5, (double)zz, 0D, 0D, 0D, 10D, 0, effLeafID, id, getBlockMetadata(xx, yy, zz), xx, yy, zz);\n                                    WeatherUtil.setParticleGravity((EntityFX)var31, 0.3F);\n                                    //var31.spawnAsWeatherEffect();\n                                    spawnQueue.add(var31);\n                                }\n                            }\n                            else if (id == 0)\n                            {\n                                if (weatherMan.wind.strength > 0.02)\n                                {\n                                    if (worldRef.rand.nextInt(400 - (int)(weatherMan.wind.strength * 100)) == 0)\n                                    {\n                                        //EntityFX var31 = new EntitySmokeFX(worldRef, (double)xx, (double)yy+0.5, (double)zz, 0D, 0D, 0D);\n                                        EntityRotFX var31 = new EntityTexFX(worldRef, (double)xx, (double)yy + 0.5, (double)zz, 0D, 0D, 0D, 10D, 0, effWind2ID);\n                                        //var31.particleGravity = 0.3F;\n                                        //mod_ExtendedRenderer.rotEffRenderer.addEffect(var31);\n\n                                        for (int ii = 0; ii < 20; ii++)\n                                        {\n                                            applyWindForce(var31);\n                                        }\n\n                                        WeatherUtil.setParticleGravity((EntityFX)var31, 0.0F);\n                                        var31.noClip = true;\n                                        WeatherUtil.setParticleScale((EntityFX)var31, 0.3F);\n                                        var31.rotationYaw = rand.nextInt(360);\n                                        //var31.spawnAsWeatherEffect();\n                                        spawnQueue.add(var31);\n                                    }\n                                }\n                            }\n                        }\n\n                        int id = getBlockId(xx, yy, zz);\n\n                        if (id == ((Block)p_blocks_sand.get(0)).blockID)\n                        {\n                            if (id == Block.sand.blockID)\n                            {\n                                if (getBlockId(xx, yy + 1, zz) == 0)\n                                {\n                                    EntityTexFX var31 = new EntityTexFX(worldRef, (double)xx, (double)yy + 0.5, (double)zz, 0D, 0D, 0D, 10D, 0, effSandID);\n                                    //var31 = new EntityWindFX(worldRef, (double)xx, (double)yy+1.2, (double)zz, 0D, 0.0D, 0D, 9.5D, 1);\n                                    var31.rotationYaw = rand.nextInt(360) - 180F;\n                                    var31.type = 1;\n                                    WeatherUtil.setParticleGravity((EntityFX)var31, 0.6F);\n                                    WeatherUtil.setParticleScale((EntityFX)var31, 0.3F);\n                                    //var31.spawnAsWeatherEffect();\n                                    spawnQueue.add(var31);\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }*/\n}\n"
  },
  {
    "path": "src/main/java/weather2/util/WeatherUtilPhysics.java",
    "content": "package weather2.util;\n\nimport net.minecraft.world.phys.Vec3;\n\nimport java.util.List;\n\n/**\n * Full of repurposed stack overflow examples\n * \n * @author Corosus\n *\n */\npublic class WeatherUtilPhysics {\n\n    /**\n     * Return true if the given point is contained inside the boundary.\n     * See: http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html\n     * @param test The point to check\n     * @return true if the point is inside the boundary, false otherwise\n     *\n     */\n    public static boolean isInConvexShape(Vec3 test, List<Vec3> points) {\n    \tint i;\n    \tint j;\n    \tboolean result = false;\n    \tfor (i = 0, j = points.size() - 1; i < points.size(); j = i++) {\n    \t\tVec3 vecI = points.get(i);\n    \t\tVec3 vecJ = points.get(j);\n    \t\tif ((vecI.z > test.z) != (vecJ.z > test.z) &&\n    \t\t\t\t(test.x < (vecJ.x - vecI.x) * (test.z - vecI.z) / (vecJ.z-vecI.z) + vecI.x)) {\n    \t\t\tresult = !result;\n    \t\t}\n    \t}\n    \treturn result;\n    }\n    \n    /**\n     * Gets minimum distance to shape, 2D only, y not used\n     * Doesnt check if you are inside shape, use isInConvexShape for that\n     * \n     * @param point\n     * @param points\n     * @return\n     */\n    public static double getDistanceToShape(Vec3 point, List<Vec3> points) {\n    \tfloat closestDist1 = 9999;\n    \tfloat closestDist2 = 9999;\n    \t\n    \tVec3 closestPoint1 = null;\n    \tVec3 closestPoint2 = null;\n    \t\n    \t//loop twice to account for edge case where points are ordered in increasing order of closeness, causing second closest clause to never trigger\n    \tfor (int i = 0; i < 2; i++) {\n\t    \tfor (Vec3 pointTest : points) {\n\t    \t\tdouble dist = pointTest.distanceTo(point);\n\t    \t\t\n\t    \t\tif (dist < closestDist1) {\n\t    \t\t\tclosestDist1 = (float) dist;\n\t    \t\t\tclosestPoint1 = pointTest;\n\t    \t\t} else if (dist < closestDist2 && pointTest != closestPoint1) {\n\t    \t\t\tclosestDist2 = (float) dist;\n\t    \t\t\tclosestPoint2 = pointTest;\n\t    \t\t}\n\t    \t}\n    \t}\n    \t\n    \tif (closestPoint1 == null || closestPoint2 == null) {\n    \t\t//should never happen unless too few points\n    \t\treturn -1;\n    \t}\n    \t\n    \treturn distBetweenPointAndLine(point.x, point.z, closestPoint1.x, closestPoint1.z, closestPoint2.x, closestPoint2.z);\n    }\n    \n    /**\n     * x and y are point, rest is line, 2D only\n     * \n     * @param x\n     * @param y\n     * @param x1\n     * @param y1\n     * @param x2\n     * @param y2\n     * @return\n     */\n    public static double distBetweenPointAndLine(double x, double y, double x1, double y1, double x2, double y2) {\n    \t// A - the standalone point (x, y)\n    \t// B - start point of the line segment (x1, y1)\n    \t// C - end point of the line segment (x2, y2)\n    \t// D - the crossing point between line from A to BC\n\n    \tdouble AB = distBetween(x, y, x1, y1);\n    \tdouble BC = distBetween(x1, y1, x2, y2);\n    \tdouble AC = distBetween(x, y, x2, y2);\n\n    \t// Heron's formula\n    \tdouble s = (AB + BC + AC) / 2;\n    \tdouble area = Math.sqrt(s * (s - AB) * (s - BC) * (s - AC));\n\n    \t// but also area == (BC * AD) / 2\n    \t// BC * AD == 2 * area\n    \t// AD == (2 * area) / BC\n    \t// TODO: check if BC == 0\n    \tdouble AD = (2 * area) / BC;\n    \treturn AD;\n    }\n\n    public static double distBetween(double x, double y, double x1, double y1) {\n    \tdouble xx = x1 - x;\n    \tdouble yy = y1 - y;\n\n    \treturn Math.sqrt(xx * xx + yy * yy);\n    }\n\t\n}\n"
  },
  {
    "path": "src/main/java/weather2/util/WeatherUtilSound.java",
    "content": "package weather2.util;\n\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.sounds.SoundSource;\nimport net.minecraft.sounds.SoundEvent;\nimport net.minecraft.world.phys.Vec3;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport weather2.SoundRegistry;\nimport weather2.client.MovingSoundStreamingSource;\nimport weather2.weathersystem.storm.StormObject;\n\nimport java.util.HashMap;\nimport java.util.Random;\n\n/**\n * TODO: rewrite this to use a class that contains array of sounds, amount of them, length of them, and the last played time and next random index\n * would help cleanup the weird array use this class does\n */\npublic class WeatherUtilSound {\n\n    public static String snd_tornado_dmg_close[] = new String[3];\n    public static String snd_wind_close[] = new String[3];\n    public static String snd_wind_far[] = new String[3];\n    public static String snd_sandstorm_low[] = new String[2];\n    public static String snd_sandstorm_med[] = new String[2];\n    public static String snd_sandstorm_high[] = new String[1];\n    public static HashMap<String, Integer> soundToLength = new HashMap<>();\n\n    /**\n     * These need to match the amount of array'd strings we use for sounds, was 3, now 6 for sandstorm addition\n     */\n    public static int snd_rand[] = new int[6];\n    public static long soundTimer[] = new long[6];\n    \n    public static void init() {\n    \tRandom rand = new Random();\n    \tsnd_tornado_dmg_close[0] = \"destruction_0_\";\n        snd_tornado_dmg_close[1] = \"destruction_1_\";\n        snd_tornado_dmg_close[2] = \"destruction_2_\";\n        snd_wind_close[0] = \"wind_close_0_\";\n        snd_wind_close[1] = \"wind_close_1_\";\n        snd_wind_close[2] = \"wind_close_2_\";\n        snd_wind_far[0] = \"wind_far_0_\";\n        snd_wind_far[1] = \"wind_far_1_\";\n        snd_wind_far[2] = \"wind_far_2_\";\n        snd_sandstorm_low[0] = \"sandstorm_low1\";\n        snd_sandstorm_low[1] = \"sandstorm_low2\";\n        snd_sandstorm_med[0] = \"sandstorm_med1\";\n        snd_sandstorm_med[1] = \"sandstorm_med2\";\n        snd_sandstorm_high[0] = \"sandstorm_high1\";\n        snd_rand[0] = rand.nextInt(snd_tornado_dmg_close.length);\n        snd_rand[1] = rand.nextInt(snd_wind_close.length);\n        snd_rand[2] = rand.nextInt(snd_wind_far.length);\n        snd_rand[3] = rand.nextInt(snd_sandstorm_high.length);\n        snd_rand[4] = rand.nextInt(snd_sandstorm_med.length);\n        snd_rand[5] = rand.nextInt(snd_sandstorm_low.length);\n        /*soundID[0] = -1;\n        soundID[1] = -1;\n        soundID[2] = -1;*/\n        soundToLength.put(snd_tornado_dmg_close[0], 2515);\n        soundToLength.put(snd_tornado_dmg_close[1], 2580);\n        soundToLength.put(snd_tornado_dmg_close[2], 2741);\n        soundToLength.put(snd_wind_close[0], 4698);\n        soundToLength.put(snd_wind_close[1], 7324);\n        soundToLength.put(snd_wind_close[2], 6426);\n        soundToLength.put(snd_wind_far[0], 12892);\n        soundToLength.put(snd_wind_far[1], 9653);\n        soundToLength.put(snd_wind_far[2], 12003);\n        soundToLength.put(snd_sandstorm_low[0], 8004);\n        soundToLength.put(snd_sandstorm_low[1], 7119);\n        soundToLength.put(snd_sandstorm_med[0], 16325);\n        soundToLength.put(snd_sandstorm_med[1], 12776);\n        soundToLength.put(snd_sandstorm_high[0], 23974);\n\n        soundToLength.put(\"siren_sandstorm_1\", 11923);\n        soundToLength.put(\"siren_sandstorm_2\", 20122);\n        soundToLength.put(\"siren_sandstorm_3\", 10366);\n        soundToLength.put(\"siren_sandstorm_4\", 44274);\n        soundToLength.put(\"siren_sandstorm_5_extra\", 1282);\n    }\n\n    @OnlyIn(Dist.CLIENT)\n    public static void playNonMovingSound(Vec3 parPos, String var1, float parVolume, float parPitch, float parCutOffRange)\n    {\n        //String prefix = \"streaming.\";\n        String affix = \".ogg\";\n        //ResourceLocation res = new ResourceLocation(var1);\n        SoundEvent event = SoundRegistry.get(var1);\n        MovingSoundStreamingSource sound = new MovingSoundStreamingSource(parPos, event, SoundSource.WEATHER, parVolume, parPitch, parCutOffRange);\n        Minecraft.getInstance().getSoundManager().play(sound);\n    }\n\n    @OnlyIn(Dist.CLIENT)\n    public static void playMovingSound(StormObject parStorm, String var1, float parVolume, float parPitch, float parCutOffRange)\n    {\n        //String prefix = \"streaming.\";\n        String affix = \".ogg\";\n\n        //ResourceLocation res = new ResourceLocation(var1);\n        SoundEvent event = SoundRegistry.get(var1);\n\n        try {\n            MovingSoundStreamingSource sound = new MovingSoundStreamingSource(parStorm, event, SoundSource.WEATHER, parVolume, parPitch, parCutOffRange);\n\n            Minecraft.getInstance().getSoundManager().play(sound);\n        } catch (Exception ex) {\n            //catching annoying 'java.lang.NoClassDefFoundError: weather2/client/MovingSoundStreamingSource' crash when hot reloading in dev\n            ex.printStackTrace();\n        }\n\n\n    }\n\n    @OnlyIn(Dist.CLIENT)\n    public static void playPlayerLockedSound(Vec3 parPos, String var1, float var5, float var6)\n    {\n        SoundEvent event = SoundRegistry.get(var1);\n        MovingSoundStreamingSource sound = new MovingSoundStreamingSource(parPos, event, SoundSource.WEATHER, var5, var6, true);\n        Minecraft.getInstance().getSoundManager().play(sound);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/weather2/util/WindReader.java",
    "content": "package weather2.util;\n\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.phys.Vec3;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport weather2.ClientTickHandler;\nimport weather2.ServerTickHandler;\nimport weather2.weathersystem.WeatherManager;\n\nimport javax.annotation.Nullable;\n\npublic class WindReader {\n\tpublic static float getWindAngle(Level world) {\n\t\treturn getWindAngle(world,null);\n\t}\n\n\tpublic static float getWindAngle(Level world, Vec3 pos) {\n\t\tWeatherManager weather = getWeatherManagerFor(world);\n\t\treturn weather != null ? weather.getWindManager().getWindAngle(pos) : 0;\n\t}\n\n\tpublic static float getWindSpeed(Level world) {\n\t\treturn getWindSpeed(world, null);\n\t}\n\n\tpublic static float getWindSpeed(Level world, @Nullable BlockPos pos) {\n\t\treturn getWindSpeed(world, pos, 1);\n\t}\n\n\tpublic static float getWindSpeed(Level world, @Nullable BlockPos pos, float extraHeightAmpMax) {\n\t\tWeatherManager weather = getWeatherManagerFor(world);\n\t\treturn weather != null ? weather.getWindManager().getWindSpeed(pos, extraHeightAmpMax) : 0;\n\t}\n\n\tpublic static float getWindSpeedCached(Level world, @Nullable BlockPos pos, float extraHeightAmpMax) {\n\t\tWeatherManager weather = getWeatherManagerFor(world);\n\t\treturn weather != null ? weather.getWindManager().getCachedWindSpeedForHeight(pos, extraHeightAmpMax) : 0;\n\t}\n\n\tpublic static WeatherManager getWeatherManagerFor(Level world) {\n\t\tif (world.isClientSide) {\n\t\t\treturn getWeatherManagerClient();\n\t\t} else {\n\t\t\treturn ServerTickHandler.getWeatherManagerFor((world.dimension()));\n\t\t}\n\t}\n\n\t@OnlyIn(Dist.CLIENT)\n\tprivate static WeatherManager getWeatherManagerClient() {\n\t\treturn ClientTickHandler.weatherManager;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/WeatherManager.java",
    "content": "package weather2.weathersystem;\n\nimport com.corosus.coroutil.util.CULog;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.nbt.CompoundTag;\nimport net.minecraft.resources.ResourceKey;\nimport net.minecraft.server.level.ServerLevel;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.phys.Vec3;\nimport weather2.IWorldData;\nimport weather2.Weather;\nimport weather2.WorldNBTData;\nimport weather2.config.ConfigStorm;\nimport weather2.config.WeatherUtilConfig;\nimport weather2.weathersystem.storm.*;\nimport weather2.weathersystem.wind.WindManager;\n\nimport java.util.*;\n\npublic abstract class WeatherManager implements IWorldData {\n\tpublic final ResourceKey<Level> dimension;\n\tprivate final WindManager wind = new WindManager(this);\n\tprivate List<WeatherObject> listStormObjects = new ArrayList<>();\n\tpublic HashMap<Long, WeatherObject> lookupStormObjectsByID = new HashMap<>();\n\n\t//non particle storm\n\tpublic long lastStormFormed = 0;\n\n\tpublic long lastSandstormFormed = 0;\n\tpublic long lastSnowstormFormed = 0;\n\n\t//0 = none, 1 = usual max overcast\n\tpublic float cloudIntensity = 1F;\n\n\t//for client only\n\tpublic boolean isVanillaRainActiveOnServer = false;\n\tpublic boolean isVanillaThunderActiveOnServer = false;\n\tpublic int vanillaRainTimeOnServer = 0;\n\n\t//going to vary the amount randomly over time like wind, for aesthetic only mode\n\tpublic float vanillaRainAmountOnServer = 0;\n\n\tprivate HashMap<Long, BlockPos> lookupWeatherBlockDamageDeflector = new HashMap<>();\n\n\tpublic WeatherManager(ResourceKey<Level> dimension) {\n\t\tthis.dimension = dimension;\n\t}\n\n\tpublic abstract Level getWorld();\n\n\tpublic void tick() {\n\t\tLevel world = getWorld();\n\t\tif (world != null) {\n\t\t\t//tick storms\n\t\t\tList<WeatherObject> list = getStormObjects();\n\t\t\tfor (int i = 0; i < list.size(); i++) {\n\t\t\t\tWeatherObject so = list.get(i);\n\t\t\t\tif (this instanceof WeatherManagerServer && so.isDead) {\n\t\t\t\t\tremoveStormObject(so.ID);\n\t\t\t\t\t((WeatherManagerServer)this).syncStormRemove(so);\n\t\t\t\t} else {\n\n\t\t\t\t\tif (!so.isDead) {\n\t\t\t\t\t\tso.tick();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (getWorld().isClientSide) {\n\t\t\t\t\t\t\tWeather.dbg(\"WARNING!!! - detected isDead storm object still in client side list, had to remove storm object with ID \" + so.ID + \" from client side, wasnt properly isDead via main channels\");\n\t\t\t\t\t\t\tremoveStormObject(so.ID);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t//tick wind\n\t\t\tif (WeatherUtilConfig.listDimensionsWindEffects.contains(getWorld().dimension().location().toString())) {\n\t\t\t\twind.tick();\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic WeatherObjectParticleStorm getClosestParticleStormByIntensity(Vec3 parPos, WeatherObjectParticleStorm.StormType type) {\n\t\treturn getClosestParticleStormByIntensity(parPos, type, false);\n\t}\n\n\t/**\n\t * Gets the most intense sandstorm, used for effects and sounds\n\t *\n\t * @param parPos\n\t * @return\n\t */\n\tpublic WeatherObjectParticleStorm getClosestParticleStormByIntensity(Vec3 parPos, WeatherObjectParticleStorm.StormType type, boolean forced) {\n\n\t\tWeatherObjectParticleStorm bestStorm = null;\n\t\tdouble closestDist = 9999999;\n\n\t\tList<WeatherObject> listStorms = getStormObjects();\n\n\t\tfor (int i = 0; i < listStorms.size(); i++) {\n\t\t\tWeatherObject wo = listStorms.get(i);\n\t\t\tif (wo instanceof WeatherObjectParticleStorm) {\n\t\t\t\tWeatherObjectParticleStorm sandstorm = (WeatherObjectParticleStorm) wo;\n\t\t\t\tif (sandstorm == null || sandstorm.isDead || sandstorm.getType() != type) continue;\n\n\t\t\t\tdouble dist = parPos.distanceTo(sandstorm.pos);\n\n\t\t\t\tif (closestDist > 0/* && dist < maxDist*/) {\n\t\t\t\t\tif (dist < closestDist) {\n\t\t\t\t\t\tclosestDist = dist;\n\t\t\t\t\t\tbestStorm = sandstorm;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\treturn bestStorm;\n\t}\n\n\tpublic void reset() {\n\t\tfor (int i = 0; i < getStormObjects().size(); i++) {\n\t\t\tWeatherObject so = getStormObjects().get(i);\n\n\t\t\tso.reset();\n\t\t}\n\n\t\tgetStormObjects().clear();\n\t\tlookupStormObjectsByID.clear();\n\n\t\twind.reset();\n\n\t\t//do not reset this, its static (shared between client and server) and client side calls reset()\n\t\t//WeatherObject.lastUsedStormID = 0;\n\t}\n\n\tpublic void tickRender(float partialTick) {\n\t\tLevel world = getWorld();\n\t\tif (world != null) {\n\t\t\t//tick storms\n\t\t\t//There are scenarios where getStormObjects().get(i) returns a null storm, uncertain why, for now try to catch it and move on\n\t\t\ttry {\n\t\t\t\tfor (int i = 0; i < getStormObjects().size(); i++) {\n\t\t\t\t\tWeatherObject obj = getStormObjects().get(i);\n\t\t\t\t\tif (obj != null) {\n\t\t\t\t\t\tobj.tickRender(partialTick);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (Exception e) {\n\t\t\t\te.printStackTrace();\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic List<WeatherObject> getStormObjects() {\n\t\treturn listStormObjects;\n\t}\n\n\tpublic StormObject getStormObjectByID(long ID) {\n\t\tWeatherObject obj = lookupStormObjectsByID.get(ID);\n\t\tif (obj instanceof StormObject) {\n\t\t\treturn (StormObject) obj;\n\t\t} else {\n\t\t\treturn null;\n\t\t}\n\t}\n\n\tpublic void addStormObject(WeatherObject so) {\n\t\tif (!lookupStormObjectsByID.containsKey(so.ID)) {\n\t\t\tlistStormObjects.add(so);\n\t\t\tlookupStormObjectsByID.put(so.ID, so);\n\t\t\tif (so instanceof StormObject) {\n\t\t\t\tStormObject so2 = (StormObject) so;\n\t\t\t}\n\t\t} else {\n\t\t\tWeather.dbg(\"Weather2 WARNING!!! Received new storm create for an ID that is already active! design bug or edgecase with PlayerEvent.Clone, ID: \" + so.ID);\n\t\t\t//Weather.dbgStackTrace();\n\n\t\t}\n\t}\n\n\tpublic void removeStormObject(long ID) {\n\t\tWeatherObject so = lookupStormObjectsByID.get(ID);\n\n\t\tif (so != null) {\n\t\t\tso.remove();\n\t\t\tlistStormObjects.remove(so);\n\t\t\tlookupStormObjectsByID.remove(ID);\n\t\t\tif (so instanceof StormObject) {\n\t\t\t\tStormObject so2 = (StormObject) so;\n\t\t\t}\n\t\t} else {\n\t\t\tWeather.dbg(\"error looking up storm ID on server for removal: \" + ID + \" - lookup count: \" + lookupStormObjectsByID.size() + \" - last used ID: \" + WeatherObject.lastUsedStormID);\n\t\t}\n\t}\n\n\tpublic StormObject getClosestStormAny(Vec3 parPos, double maxDist) {\n\t\treturn getClosestStorm(parPos, maxDist, -1, -1, true);\n\t}\n\n\tpublic StormObject getClosestStorm(Vec3 parPos, double maxDist, int severityFlagMin) {\n\t\treturn getClosestStorm(parPos, maxDist, severityFlagMin, -1, false);\n\t}\n\n\tpublic StormObject getClosestStorm(Vec3 parPos, double maxDist, int severityFlagMin, int severityFlagMax, boolean orRain) {\n\n\t\tStormObject closestStorm = null;\n\t\tdouble closestDist = Double.MAX_VALUE;\n\n\t\tList<WeatherObject> listStorms = getStormObjects();\n\n\t\tfor (int i = 0; i < listStorms.size(); i++) {\n\t\t\tWeatherObject wo = listStorms.get(i);\n\t\t\tif (wo instanceof StormObject) {\n\t\t\t\tStormObject storm = (StormObject) wo;\n\t\t\t\tif (storm == null || storm.isDead) continue;\n\t\t\t\tdouble dist = storm.pos.distanceTo(parPos);\n\t\t\t\tif (dist < closestDist && dist <= maxDist && !storm.isFirenado) {\n\t\t\t\t\tif ((storm.attrib_precipitation && orRain) ||\n\t\t\t\t\t\t\t((severityFlagMin == -1 || storm.levelCurIntensityStage >= severityFlagMin) &&\n\t\t\t\t\t\t\t\t\t(severityFlagMax == -1 || storm.levelCurIntensityStage <= severityFlagMax))) {\n\t\t\t\t\t\tclosestStorm = storm;\n\t\t\t\t\t\tclosestDist = dist;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn closestStorm;\n\n\t\t//not sure i can avoid a double use of distance calculation adding to iteration cost, this method might not be stream worthy\n\t\t/*return getStormObjects().stream()\n\t\t\t\t.map(wo -> (StormObject)wo)\n\t\t\t\t.filter(so -> !so.isDead)\n\t\t\t\t.filter(so -> (so.attrib_precipitation && orRain) || (severityFlagMin == -1 || so.levelCurIntensityStage >= severityFlagMin))\n\t\t\t\t.filter(so -> so.pos.distanceTo(parPos) < maxDist)\n\t\t\t\t.min(Comparator.comparing(so -> so.pos.distanceTo(parPos))).orElse(null);*/\n\t}\n\n\tpublic boolean isPrecipitatingAt(BlockPos pos) {\n\t\treturn isPrecipitatingAt(new Vec3(pos.getX(), pos.getY(), pos.getZ()));\n\t}\n\n\t/**\n\t * TODO: Heavy on the processing, consider caching the result by location for 20 ticks\n\t *\n\t * @param parPos\n\t * @return\n\t */\n\tpublic boolean isPrecipitatingAt(Vec3 parPos) {\n\t\t/*List<WeatherObject> listStorms = getStormObjects();\n\n\t\tfor (int i = 0; i < listStorms.size(); i++) {\n\t\t\tWeatherObject wo = listStorms.get(i);\n\t\t\tif (wo instanceof StormObject) {\n\t\t\t\tStormObject storm = (StormObject) wo;\n\t\t\t\tif (storm == null || storm.isDead) continue;\n\t\t\t\tif (storm.attrib_precipitation) {\n\t\t\t\t\tdouble dist = storm.pos.distanceTo(parPos);\n\t\t\t\t\tif (dist < storm.size) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn false;*/\n\n\t\treturn getStormObjects().stream()\n\t\t\t\t.map(wo -> (StormObject)wo)\n\t\t\t\t.anyMatch(so -> !so.isDead && so.attrib_precipitation && so.pos.distanceTo(parPos) < so.size);\n\t}\n\n\t/**\n\t * Simply compares stormfront distances, doesnt factor in tail\n\t *\n\t * @param parPos\n\t * @param maxDist\n\t * @return\n\t */\n\tpublic WeatherObjectSandstormOld getClosestSandstorm(Vec3 parPos, double maxDist) {\n\n\t\tWeatherObjectSandstormOld closestStorm = null;\n\t\tdouble closestDist = 9999999;\n\n\t\tList<WeatherObject> listStorms = getStormObjects();\n\n\t\tfor (int i = 0; i < listStorms.size(); i++) {\n\t\t\tWeatherObject wo = listStorms.get(i);\n\t\t\tif (wo instanceof WeatherObjectSandstormOld) {\n\t\t\t\tWeatherObjectSandstormOld storm = (WeatherObjectSandstormOld) wo;\n\t\t\t\tif (storm == null || storm.isDead) continue;\n\t\t\t\tdouble dist = storm.pos.distanceTo(parPos);\n\t\t\t\t/*if (getWorld().isRemote) {\n\t\t\t\t\tSystem.out.println(\"close storm candidate: \" + dist + \" - \" + storm.state + \" - \" + storm.attrib_rain);\n\t\t\t\t}*/\n\t\t\t\tif (dist < closestDist && dist <= maxDist) {\n\t\t\t\t\t//if ((storm.attrib_precipitation && orRain) || (severityFlagMin == -1 || storm.levelCurIntensityStage >= severityFlagMin)) {\n\t\t\t\t\tclosestStorm = storm;\n\t\t\t\t\tclosestDist = dist;\n\t\t\t\t\t//}\n\t\t\t\t}\n\t\t\t}\n\n\t\t}\n\n\t\treturn closestStorm;\n\t}\n\n\tpublic List<WeatherObject> getSandstormsAround(Vec3 parPos, double maxDist) {\n\t\tList<WeatherObject> storms = new ArrayList<>();\n\n\t\tfor (int i = 0; i < getStormObjects().size(); i++) {\n\t\t\tWeatherObject wo = getStormObjects().get(i);\n\t\t\tif (wo instanceof WeatherObjectSandstormOld) {\n\t\t\t\tWeatherObjectSandstormOld storm = (WeatherObjectSandstormOld) wo;\n\t\t\t\tif (storm.isDead) continue;\n\n\t\t\t\tif (storm.pos.distanceTo(parPos) < maxDist) {\n\t\t\t\t\tstorms.add(storm);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn storms;\n\t}\n\n\tpublic List<WeatherObject> getStormsAroundForDeflector(Vec3 parPos, double maxDist) {\n\t\tList<WeatherObject> storms = new ArrayList<>();\n\n\t\tfor (int i = 0; i < getStormObjects().size(); i++) {\n\t\t\tWeatherObject wo = getStormObjects().get(i);\n\t\t\tif (wo.isDead) continue;\n\t\t\tif (wo instanceof StormObject) {\n\t\t\t\tStormObject storm = (StormObject) wo;\n\t\t\t\tif (storm.pos.distanceTo(parPos) < maxDist && ((storm.attrib_precipitation && ConfigStorm.Storm_Deflector_RemoveRainstorms) || storm.levelCurIntensityStage >= ConfigStorm.Storm_Deflector_MinStageRemove)) {\n\t\t\t\t\tstorms.add(storm);\n\t\t\t\t}\n\t\t\t} else if (wo instanceof WeatherObjectSandstormOld && ConfigStorm.Storm_Deflector_RemoveSandstorms) {\n\t\t\t\tWeatherObjectSandstormOld sandstorm = (WeatherObjectSandstormOld)wo;\n\t\t\t\tdouble distToStorm = parPos.distanceTo(sandstorm.pos);\n\t\t\t\tif (distToStorm < maxDist) {\n\t\t\t\t\tstorms.add(wo);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn storms;\n\t}\n\n\tpublic List<WeatherObject> getStormsAround(Vec3 parPos, double maxDist) {\n\t\tList<WeatherObject> storms = new ArrayList<>();\n\n\t\tfor (int i = 0; i < getStormObjects().size(); i++) {\n\t\t\tWeatherObject wo = getStormObjects().get(i);\n\t\t\tif (wo.isDead) continue;\n\t\t\tif (wo instanceof StormObject) {\n\t\t\t\tStormObject storm = (StormObject) wo;\n\t\t\t\tif (storm.pos.distanceTo(parPos) < maxDist && (storm.attrib_precipitation || storm.levelCurIntensityStage > StormObject.STATE_NORMAL)) {\n\t\t\t\t\tstorms.add(storm);\n\t\t\t\t}\n\t\t\t} else if (wo instanceof WeatherObjectSandstormOld) {\n\t\t\t\tWeatherObjectSandstormOld sandstorm = (WeatherObjectSandstormOld)wo;\n\t\t\t\tdouble distToStorm = parPos.distanceTo(sandstorm.pos);\n\t\t\t\tif (distToStorm < maxDist) {\n\t\t\t\t\tstorms.add(wo);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn storms;\n\t}\n\n\t@Override\n\tpublic CompoundTag save(CompoundTag data) {\n\n\t\tCULog.dbg(\"WeatherManager save\");\n\n\t\tCompoundTag listStormsNBT = new CompoundTag();\n\t\tfor (int i = 0; i < listStormObjects.size(); i++) {\n\t\t\tWeatherObject obj = listStormObjects.get(i);\n\t\t\tobj.getNbtCache().setUpdateForced(true);\n\t\t\tobj.write();\n\t\t\tobj.getNbtCache().setUpdateForced(false);\n\t\t\tlistStormsNBT.put(\"storm_\" + obj.ID, obj.getNbtCache().getNewNBT());\n\t\t}\n\t\tdata.put(\"stormData\", listStormsNBT);\n\t\tCompoundTag listDeflectorsNBT = new CompoundTag();\n\t\tint i = 0;\n\t\tfor (Map.Entry<Long, BlockPos> entry : lookupWeatherBlockDamageDeflector.entrySet()) {\n\t\t\tCULog.dbg(\"writing out deflector to disk: \" + entry.getKey());\n\t\t\tlistDeflectorsNBT.putLong(\"deflector_\" + i, entry.getKey());\n\t\t\ti++;\n\t\t}\n\t\tdata.put(\"deflectorData\", listDeflectorsNBT);\n\t\tdata.putLong(\"lastUsedIDStorm\", WeatherObject.lastUsedStormID);\n\n\t\tdata.putLong(\"lastStormFormed\", lastStormFormed);\n\n\t\tdata.putLong(\"lastSandstormFormed\", lastSandstormFormed);\n\t\tdata.putLong(\"lastSnowstormFormed\", lastSnowstormFormed);\n\n\t\tdata.putFloat(\"cloudIntensity\", this.cloudIntensity);\n\n\t\tdata.put(\"windMan\", wind.write(new CompoundTag()));\n\t\treturn data;\n\t}\n\n\tpublic void read() {\n\n\t\tWorldNBTData worldNBTData = ((ServerLevel)getWorld()).getDataStorage().computeIfAbsent(WorldNBTData::load, WorldNBTData::new, Weather.MODID + \"-\" + \"weather_data\");\n\t\tworldNBTData.setDataHandler(this);\n\n\t\tCULog.dbg(\"weather data: \" + worldNBTData.getData());\n\n\t\tCompoundTag data = worldNBTData.getData();\n\n\t\tlastStormFormed = data.getLong(\"lastStormFormed\");\n\t\tlastSandstormFormed = data.getLong(\"lastSandstormFormed\");\n\t\tlastSnowstormFormed = data.getLong(\"lastSnowstormFormed\");\n\n\t\t//prevent setting to 0 for worlds updating to new weather version\n\t\tif (data.contains(\"cloudIntensity\")) {\n\t\t\tcloudIntensity = data.getFloat(\"cloudIntensity\");\n\t\t}\n\n\t\tWeatherObject.lastUsedStormID = data.getLong(\"lastUsedIDStorm\");\n\n\t\twind.read(data.getCompound(\"windMan\"));\n\n\t\tCompoundTag nbtStorms = data.getCompound(\"stormData\");\n\n\t\tIterator it = nbtStorms.getAllKeys().iterator();\n\n\t\twhile (it.hasNext()) {\n\t\t\tString tagName = (String) it.next();\n\t\t\tCompoundTag stormData = nbtStorms.getCompound(tagName);\n\n\t\t\t//if (ServerTickHandler.getWeatherManagerFor(dimension) != null) {\n\t\t\t\tWeatherObject wo = null;\n\t\t\t\tif (stormData.getInt(\"weatherObjectType\") == EnumWeatherObjectType.CLOUD.ordinal()) {\n\t\t\t\t\two = new StormObject(this);\n\t\t\t\t} else if (stormData.getInt(\"weatherObjectType\") == EnumWeatherObjectType.SAND.ordinal()) {\n\t\t\t\t\two = new WeatherObjectParticleStorm(this);\n\t\t\t\t\t((WeatherObjectParticleStorm)wo).setType(WeatherObjectParticleStorm.StormType.SANDSTORM);\n\t\t\t\t\t//initStormNew???\n\t\t\t\t} else if (stormData.getInt(\"weatherObjectType\") == EnumWeatherObjectType.SNOW.ordinal()) {\n\t\t\t\t\two = new WeatherObjectParticleStorm(this);\n\t\t\t\t\t((WeatherObjectParticleStorm)wo).setType(WeatherObjectParticleStorm.StormType.SNOWSTORM);\n\t\t\t\t\t//initStormNew???\n\t\t\t\t}\n\t\t\t\ttry {\n\t\t\t\t\two.getNbtCache().setNewNBT(stormData);\n\t\t\t\t\two.read();\n\t\t\t\t\two.getNbtCache().updateCacheFromNew();\n\t\t\t\t} catch (Exception ex) {\n\t\t\t\t\tex.printStackTrace();\n\t\t\t\t}\n\t\t\t\taddStormObject(wo);\n\n\t\t\t\t//TODO: possibly unneeded/redundant/bug inducing, packets will be sent upon request from client\n\t\t\t\t((WeatherManagerServer)(this)).syncStormNew(wo);\n\t\t\t/*} else {\n\t\t\t\tSystem.out.println(\"WARNING: trying to load storm objects for missing dimension: \" + dimension);\n\t\t\t}*/\n\t\t}\n\n\t\tCompoundTag nbtDeflectors = data.getCompound(\"deflectorData\");\n\n\t\tIterator it2 = nbtDeflectors.getAllKeys().iterator();\n\n\t\twhile (it2.hasNext()) {\n\t\t\tString tagName = (String) it2.next();\n\t\t\tlong hash = nbtDeflectors.getLong(tagName);\n\t\t\tif (!BlockPos.of(hash).equals(new BlockPos(0, 0, 0))) {\n\t\t\t\tCULog.dbg(\"adding deflector from disk: \" + BlockPos.of(hash));\n\t\t\t\tregisterDeflector(BlockPos.of(hash));\n\t\t\t} else {\n\t\t\t\tCULog.dbg(\"????????\");\n\t\t\t}\n\t\t}\n\n\t\tCULog.dbg(\"reloaded weather objects: \" + listStormObjects.size());\n\t}\n\n\tpublic WindManager getWindManager() {\n\t\treturn this.wind;\n\t}\n\n\tpublic HashMap<Long, BlockPos> getLookupWeatherBlockDamageDeflector() {\n\t\treturn lookupWeatherBlockDamageDeflector;\n\t}\n\n\tpublic void registerDeflector(BlockPos pos) {\n\t\tlong hash = BlockPos.asLong(pos.getX(), pos.getY(), pos.getZ());\n\t\tif (!this.lookupWeatherBlockDamageDeflector.containsKey(hash)) {\n\t\t\tCULog.dbg(\"adding weather deflector poi at \" + pos);\n\t\t\tthis.lookupWeatherBlockDamageDeflector.put(hash, pos);\n\t\t}\n\t}\n\n\tpublic void removeDeflector(BlockPos pos) {\n\t\tlong hash = BlockPos.asLong(pos.getX(), pos.getY(), pos.getZ());\n\t\tCULog.dbg(\"removing weather deflector poi at \" + pos);\n\t\tthis.lookupWeatherBlockDamageDeflector.remove(hash);\n\t}\n\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/WeatherManagerClient.java",
    "content": "package weather2.weathersystem;\n\nimport com.corosus.coroutil.util.CoroUtilMisc;\nimport extendedrenderer.particle.entity.ParticleCube;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.core.registries.Registries;\nimport net.minecraft.nbt.CompoundTag;\nimport net.minecraft.nbt.NbtUtils;\nimport net.minecraft.resources.ResourceKey;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport weather2.ClientTickHandler;\nimport weather2.Weather;\nimport weather2.client.SceneEnhancer;\nimport weather2.config.ConfigParticle;\nimport weather2.weathersystem.storm.EnumWeatherObjectType;\nimport weather2.weathersystem.storm.StormObject;\nimport weather2.weathersystem.storm.WeatherObject;\nimport weather2.weathersystem.storm.WeatherObjectParticleStorm;\n\nimport java.util.Random;\n\n@OnlyIn(Dist.CLIENT)\npublic class WeatherManagerClient extends WeatherManager {\n\n\t//public CloudManager cloudManager = new CloudManager();\n\n\tpublic WeatherManagerClient(ResourceKey<Level> dimension) {\n\t\tsuper(dimension);\n\t}\n\n\t@Override\n\tpublic void tick() {\n\t\tsuper.tick();\n\t\tif (!Weather.isLoveTropicsInstalled()) {\n\t\t\t//TODO: disabled for 1.20, might need to go mixin from here\n\t\t\t/*ICloudRenderHandler cloudRenderHandler = ((ClientLevel) getWorld()).effects().getCloudRenderHandler();\n\t\t\tif (cloudRenderHandler == null) {\n\t\t\t\t((ClientLevel) getWorld()).effects().setCloudRenderHandler(new CloudRenderHandler());\n\t\t\t}\n\t\t\tIWeatherParticleRenderHandler handler = ((ClientLevel) getWorld()).effects().getWeatherParticleRenderHandler();\n\t\t\tif (handler == null) {\n\t\t\t\t((ClientLevel) getWorld()).effects().setWeatherParticleRenderHandler(new WeatherParticleRenderHandler());\n\t\t\t}*/\n\n\t\t\tboolean cloudTest = false;\n\t\t\tif (cloudTest) {\n\t\t\t\t//cloudManager.tick();\n\t\t\t}\n\t\t}\n\t\t//((ClientLevel)getWorld()).effects().setCloudRenderHandler(null);\n\n\t}\n\n\t@Override\n\tpublic Level getWorld() {\n\t\treturn Minecraft.getInstance().level;\n\t}\n\n\tpublic void nbtSyncFromServer(CompoundTag parNBT) {\n\t\t//check command\n\t\t//commands:\n\t\t//new storm\n\t\t//tick storm\n\t\t//remove storm\n\n\t\t//new volcano\n\t\t//tick volcano\n\t\t//remove volcano???\n\n\t\tString command = parNBT.getString(\"command\");\n\n\t\tif (command.equals(\"syncStormNew\")) {\n\t\t\t//Weather.dbg(\"creating client side storm\");\n\t\t\tCompoundTag stormNBT = parNBT.getCompound(\"data\");\n\t\t\tlong ID = stormNBT.getLong(\"ID\");\n\t\t\tWeather.dbg(\"syncStormNew, ID: \" + ID);\n\n\t\t\tEnumWeatherObjectType weatherObjectType = EnumWeatherObjectType.get(stormNBT.getInt(\"weatherObjectType\"));\n\n\t\t\tWeatherObject wo = null;\n\t\t\tif (weatherObjectType == EnumWeatherObjectType.CLOUD) {\n\t\t\t\two = new StormObject(ClientTickHandler.weatherManager);\n\t\t\t} else if (weatherObjectType == EnumWeatherObjectType.SAND) {\n\t\t\t\two = new WeatherObjectParticleStorm(ClientTickHandler.weatherManager);\n\t\t\t\t((WeatherObjectParticleStorm)wo).setType(WeatherObjectParticleStorm.StormType.SANDSTORM);\n\t\t\t} else if (weatherObjectType == EnumWeatherObjectType.SNOW) {\n\t\t\t\two = new WeatherObjectParticleStorm(ClientTickHandler.weatherManager);\n\t\t\t\t((WeatherObjectParticleStorm)wo).setType(WeatherObjectParticleStorm.StormType.SNOWSTORM);\n\t\t\t}\n\n\t\t\t//StormObject so\n\t\t\two.getNbtCache().setNewNBT(stormNBT);\n\t\t\two.nbtSyncFromServer();\n\t\t\two.getNbtCache().updateCacheFromNew();\n\n\t\t\taddStormObject(wo);\n\n\t\t} else if (command.equals(\"syncStormRemove\")) {\n\t\t\t//Weather.dbg(\"removing client side storm\");\n\t\t\tCompoundTag stormNBT = parNBT.getCompound(\"data\");\n\t\t\tlong ID = stormNBT.getLong(\"ID\");\n\n\t\t\tWeatherObject so = lookupStormObjectsByID.get(ID);\n\t\t\tif (so != null) {\n\t\t\t\tWeather.dbg(\"syncStormRemove, ID: \" + ID);\n\t\t\t\tremoveStormObject(ID);\n\t\t\t} else {\n\t\t\t\tWeather.dbg(\"error removing storm, cant find by ID: \" + ID);\n\t\t\t}\n\t\t} else if (command.equals(\"syncStormUpdate\")) {\n\t\t\t//Weather.dbg(\"updating client side storm\");\n\t\t\tCompoundTag stormNBT = parNBT.getCompound(\"data\");\n\t\t\tlong ID = stormNBT.getLong(\"ID\");\n\n\t\t\tWeatherObject so = lookupStormObjectsByID.get(ID);\n\t\t\tif (so != null) {\n\t\t\t\tso.getNbtCache().setNewNBT(stormNBT);\n\t\t\t\tso.nbtSyncFromServer();\n\t\t\t\tso.getNbtCache().updateCacheFromNew();\n\t\t\t} else {\n\t\t\t\tWeather.dbg(\"error syncing storm, cant find by ID: \" + ID + \", probably due to client resetting and waiting on full resync (this is ok)\");\n\t\t\t\t//Weather.dbgStackTrace();\n\t\t\t}\n\t\t} else if (command.equals(\"syncWindUpdate\")) {\n\t\t\t//Weather.dbg(\"updating client side wind\");\n\n\t\t\tCompoundTag nbt = parNBT.getCompound(\"data\");\n\n\t\t\tgetWindManager().nbtSyncFromServer(nbt);\n\t\t} else if (command.equals(\"syncWeatherUpdate\")) {\n\t\t\t//Weather.dbg(\"updating client side wind\");\n\n\t\t\t//NBTTagCompound nbt = parNBT.getCompound(\"data\");\n\n\t\t\tisVanillaRainActiveOnServer = parNBT.getBoolean(\"isVanillaRainActiveOnServer\");\n\t\t\tisVanillaThunderActiveOnServer = parNBT.getBoolean(\"isVanillaThunderActiveOnServer\");\n\t\t\tvanillaRainTimeOnServer = parNBT.getInt(\"vanillaRainTimeOnServer\");\n\t\t\tvanillaRainAmountOnServer = parNBT.getFloat(\"vanillaRainAmountOnServer\");\n\n\t\t\t//windMan.nbtSyncFromServer(nbt);\n\t\t} else if (command.equals(\"syncBlockParticleNew\")) {\n\t\t\t//Weather.dbg(\"updating client side wind\");\n\n\t\t\tCompoundTag nbt = parNBT.getCompound(\"data\");\n\n\t\t\tint posX = nbt.getInt(\"posX\");\n\t\t\tint posY = nbt.getInt(\"posY\") + 1;\n\t\t\tint posZ = nbt.getInt(\"posZ\");\n\n\t\t\tBlockState state = NbtUtils.readBlockState(getWorld().holderLookup(Registries.BLOCK), nbt.getCompound(\"blockstate\"));\n\n\t\t\tlong ownerID = nbt.getLong(\"ownerID\");\n\n\t\t\t//CULog.dbg(\"add cube at \" + posX + \" \" + posY + \" \" + posZ);\n\n\t\t\tStormObject storm = getStormObjectByID(ownerID);\n\t\t\tif (storm != null) {\n\n\t\t\t\tif (ConfigParticle.Particle_effect_rate > 0) {\n\t\t\t\t\tint extraCubes = (int) (1 + ConfigParticle.Particle_Tornado_extraParticleCubes * ConfigParticle.Particle_effect_rate);\n\t\t\t\t\tRandom rand = CoroUtilMisc.random();\n\t\t\t\t\tfloat randRange = 3;\n\t\t\t\t\tfor (int i = 0; i < extraCubes; i++) {\n\t\t\t\t\t\tParticleCube hail = new ParticleCube(getWorld(),\n\t\t\t\t\t\t\t\tposX + (rand.nextFloat() - rand.nextFloat()) * randRange,\n\t\t\t\t\t\t\t\tposY + (rand.nextFloat() - rand.nextFloat()) * randRange,\n\t\t\t\t\t\t\t\tposZ + (rand.nextFloat() - rand.nextFloat()) * randRange,\n\t\t\t\t\t\t\t\t0D, 0D, 0D, state);\n\t\t\t\t\t\tSceneEnhancer.checkParticleBehavior();\n\t\t\t\t\t\tSceneEnhancer.particleBehavior.initParticleCube(hail);\n\t\t\t\t\t\tstorm.listParticlesDebris.add(hail);\n\n\t\t\t\t\t\thail.spawnAsWeatherEffect();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/WeatherManagerServer.java",
    "content": "package weather2.weathersystem;\n\nimport com.corosus.coroutil.util.*;\nimport com.google.common.collect.Lists;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.nbt.CompoundTag;\nimport net.minecraft.nbt.NbtUtils;\nimport net.minecraft.server.level.ChunkHolder;\nimport net.minecraft.server.level.ServerLevel;\nimport net.minecraft.server.level.ServerPlayer;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.entity.EntitySelector;\nimport net.minecraft.world.entity.LivingEntity;\nimport net.minecraft.world.entity.player.Player;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.biome.Biome;\nimport net.minecraft.world.level.block.Block;\nimport net.minecraft.world.level.block.Blocks;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.world.level.chunk.LevelChunk;\nimport net.minecraft.world.phys.Vec3;\nimport net.minecraftforge.network.NetworkDirection;\nimport net.minecraftforge.network.PacketDistributor;\nimport weather2.*;\nimport weather2.config.*;\nimport weather2.datatypes.StormState;\nimport weather2.util.CachedNBTTagCompound;\nimport weather2.util.WeatherUtilBlock;\nimport weather2.weathersystem.storm.StormObject;\nimport weather2.weathersystem.storm.WeatherObject;\nimport weather2.weathersystem.storm.WeatherObjectParticleStorm;\nimport weather2.weathersystem.wind.WindManager;\n\nimport javax.annotation.Nullable;\nimport java.util.*;\n\npublic class WeatherManagerServer extends WeatherManager {\n\tprivate final ServerLevel world;\n\n\tpublic WeatherManagerServer(ServerLevel world) {\n\t\tsuper(world.dimension());\n\t\tthis.world = world;\n\t}\n\n\t@Override\n\tpublic Level getWorld() {\n\t\treturn world;\n\t}\n\n\t@Override\n\tpublic void tick() {\n\t\tsuper.tick();\n\n\t\tStormState snowstorm = ServerWeatherProxy.getSnowstormForEverywhere(world);\n\t\tif (snowstorm != null) {\n\t\t\ttickStormBlockBuildup(snowstorm, Blocks.SNOW);\n\t\t}\n\t\tStormState sandstorm = ServerWeatherProxy.getSandstormForEverywhere(world);\n\t\tif (sandstorm != null) {\n\t\t\ttickStormBlockBuildup(sandstorm, WeatherBlocks.BLOCK_SAND_LAYER.get());\n\t\t}\n\n\t\t//tickStormBlockBuildup(new StormState(1, 2), WeatherBlocks.blockSandLayer);\n\n\t\ttickWeatherCoverage();\n\n\t\tif (world != null) {\n\t\t\tWindManager windMan = getWindManager();\n\n\t\t\tgetStormObjects().stream()\n\t\t\t\t\t.filter(wo -> world.getGameTime() % wo.getUpdateRateForNetwork() == 0)\n\t\t\t\t\t.forEach(this::syncStormUpdate);\n\n\t\t\t/*getStormObjects().stream()\n\t\t\t\t\t.filter(wo -> world.getGameTime() % 2 == 0)\n\t\t\t\t\t.forEach(this::syncStormUpdate);*/\n\n\t\t\t//sync wind\n\t\t\tif (world.getGameTime() % 60 == 0) {\n\t\t\t\tsyncWindUpdate(windMan);\n\t\t\t}\n\n\t\t\t/*for (Entity ent : world.getEntities().getAll()) {\n\t\t\t\tif (ent instanceof LivingEntity) {\n\t\t\t\t\t((LivingEntity) ent).addEffect(new MobEffectInstance(MobEffects.SLOW_FALLING, 600, 0, false, false, true));\n\t\t\t\t}\n\t\t\t}*/\n\n\t\t\t//sim box work\n\t\t\tint rate = 20;\n\t\t\tif (world.getGameTime() % rate == 0) {\n\n\t\t\t\t//removal pass\n\t\t\t\tfor (int i = 0; i < getStormObjects().size(); i++) {\n\t\t\t\t\tWeatherObject so = getStormObjects().get(i);\n\t\t\t\t\tPlayer closestPlayer = world.getNearestPlayer(so.posGround.x, so.posGround.y, so.posGround.z, ConfigMisc.Misc_simBoxRadiusCutoff, EntitySelector.ENTITY_STILL_ALIVE);\n\n\t\t\t\t\tif (so instanceof StormObject && ((StormObject) so).isPet()) continue;\n\n\t\t\t\t\tif (ConfigMisc.Winter_Wonderland && so instanceof WeatherObjectParticleStorm && ((WeatherObjectParticleStorm) so).getType() == WeatherObjectParticleStorm.StormType.SNOWSTORM) continue;\n\n\t\t\t\t\t//removed check is done in WeatherManagerBase\n\t\t\t\t\tif (closestPlayer == null || ConfigMisc.Aesthetic_Only_Mode) {\n\t\t\t\t\t\tso.ticksSinceNoNearPlayer += rate;\n\t\t\t\t\t\t//finally remove if nothing near for 30 seconds, gives multiplayer server a chance to get players in\n\t\t\t\t\t\tif (so.ticksSinceNoNearPlayer > 20 * 30 || ConfigMisc.Aesthetic_Only_Mode) {\n\t\t\t\t\t\t\tif (world.getPlayers(LivingEntity::isAlive).size() == 0) {\n\t\t\t\t\t\t\t\tWeather.dbg(\"removing distant storm: \" + so.ID + \", running without players\");\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tWeather.dbg(\"removing distant storm: \" + so.ID);\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tremoveStormObject(so.ID);\n\t\t\t\t\t\t\tsyncStormRemove(so);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tso.ticksSinceNoNearPlayer = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t/*if (world.getGameTime() % rate == 0) {\n\t\t\t\t\tif (ConfigMisc.Aesthetic_Only_Mode) {\n\t\t\t\t\t\tgetStormObjects().stream().forEach(this::removeWeatherObjectAndSync);\n\t\t\t\t\t} else {\n\n\t\t\t\t\t\t//streams are probably not actually ideal for this situation since we need to run code on both states of player presence\n\t\t\t\t\t\t//and streams work best for just filtering out instead of splitting, and running .stream() 3 times is costly probably\n\n\t\t\t\t\t\t//split weather objects into list of ones near player and ones not\n\t\t\t\t\t\tMap<Boolean, List<WeatherObject>> playersNearWeatherObjects = getStormObjects()\n\t\t\t\t\t\t\t\t.stream()\n\t\t\t\t\t\t\t\t.collect(Collectors.partitioningBy(wo -> world.getNearestPlayer(wo.posGround.x, wo.posGround.y, wo.posGround.z, ConfigMisc.Misc_simBoxRadiusCutoff, EntitySelector.ENTITY_STILL_ALIVE) != null));\n\n\t\t\t\t\t\t//ones near\n\t\t\t\t\t\tplayersNearWeatherObjects.get(true).stream()\n\t\t\t\t\t\t\t\t.forEach(wo -> wo.ticksSinceNoNearPlayer = 0);\n\n\t\t\t\t\t\t//ones not near\n\t\t\t\t\t\tplayersNearWeatherObjects.get(false).stream()\n\t\t\t\t\t\t\t\t.peek(wo -> wo.ticksSinceNoNearPlayer += rate)\n\t\t\t\t\t\t\t\t.filter(wo -> wo.ticksSinceNoNearPlayer > 20 * 30)\n\t\t\t\t\t\t\t\t.forEach(this::removeWeatherObjectAndSync);\n\t\t\t\t\t}\n\t\t\t\t}*/\n\n\n\n\t\t\t\tRandom rand = new Random();\n\n\t\t\t\t//test with high wind to maximize movement/recycling\n\t\t\t\t//cloud data:\n\t\t\t\t//0.6: 19-20/20\n\t\t\t\t//0.5: 17-18/20\n\t\t\t\t//0.4: 16-17/20\n\t\t\t\t//0.3: 16-18/20?\n\t\t\t\t//0.2: 12/20?\n\n\t\t\t\t/**\n\t\t\t\t * max size of cloud sets = 300 radius\n\t\t\t\t * sim box size = 1024 radius\n\t\t\t\t *\n\t\t\t\t * ~9 cloud sets in a player simbox\n\t\t\t\t */\n\n\t\t\t\t//TEMP!!!\n\t\t\t\t/*windMan.startHighWindEvent();\n\t\t\t\tcloudIntensity = 0.3F;\n\t\t\t\tint countDbg = 0;\n\t\t\t\tint countDbg2 = 0;\n\t\t\t\tfor (StormObject so : getStormObjectsByLayer(0)) {\n\t\t\t\t\tif (!so.isCloudlessStorm()) {\n\t\t\t\t\t\tcountDbg++;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcountDbg2++;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tSystem.out.println(\"cloud/cloudless/max count: \" + countDbg + \"/\" + countDbg2 + \"/\" + (ConfigStorm.Storm_MaxPerPlayerPerLayer * world.playerEntities.size()));*/\n\n\t\t\t\t//cloud formation spawning - REFINE ME!\n\t\t\t\tboolean spawnClouds = true;\n\t\t\t\tif (spawnClouds && !Weather.isLoveTropicsInstalled() && !ConfigMisc.Aesthetic_Only_Mode && WeatherUtilConfig.shouldTickClouds(world.dimension().location().toString())) {\n\t\t\t\t\tfor (int i = 0; i < world.players().size(); i++) {\n\t\t\t\t\t\tPlayer entP = world.players().get(i);\n\n\t\t\t\t\t\t//Weather.dbg(\"getStormObjects().size(): \" + getStormObjects().size());\n\n\t\t\t\t\t\t//layer 0\n\t\t\t\t\t\tif (getStormObjects().size() < ConfigStorm.Storm_MaxPerPlayerPerLayer * world.players().size()) {\n\t\t\t\t\t\t\tif (rand.nextInt(5) == 0) {\n\t\t\t\t\t\t\t\t//if (rand.nextFloat() <= cloudIntensity) {\n\t\t\t\t\t\t\t\ttrySpawnStormCloudNearPlayerForLayer(entP, 0);\n\t\t\t\t\t\t\t\t//}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t//layer 1\n\t\t\t\t\t\t/*if (getStormObjectsByLayer(1).size() < ConfigStorm.Storm_MaxPerPlayerPerLayer * world.players().size()) {\n\t\t\t\t\t\t\tif (ConfigMisc.Cloud_Layer1_Enable) {\n\t\t\t\t\t\t\t\tif (rand.nextInt(5) == 0) {\n\t\t\t\t\t\t\t\t\t//if (rand.nextFloat() <= cloudIntensity) {\n\t\t\t\t\t\t\t\t\ttrySpawnStormCloudNearPlayerForLayer(entP, 1);\n\t\t\t\t\t\t\t\t\t//}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}*/\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t//if dimension can have storms, tick sandstorm spawning every 10 seconds\n\t\t\tif (!Weather.isLoveTropicsInstalled() && WeatherUtilConfig.listDimensionsStorms.contains(world.dimension().location().toString()) && world.getGameTime() % 200 == 0 && windMan.isHighWindEventActive()) {\n\t\t\t\tif (!ConfigSand.Storm_NoSandstorms) {\n\t\t\t\t\ttryParticleStorm(world, WeatherObjectParticleStorm.StormType.SANDSTORM);\n\t\t\t\t}\n\t\t\t\tif (!ConfigSnow.Storm_NoSnowstorms && (!ConfigMisc.Aesthetic_Only_Mode || ConfigMisc.Winter_Wonderland)) {\n\t\t\t\t\ttryParticleStorm(world, WeatherObjectParticleStorm.StormType.SNOWSTORM);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic Optional<BlockPos> findWeatherDeflector(BlockPos pos, int range) {\n\t\tdouble closestDist = Float.MAX_VALUE;\n\t\tBlockPos closestPos = null;\n\t\tfor (Map.Entry<Long, BlockPos> entrySet : getLookupWeatherBlockDamageDeflector().entrySet()) {\n\t\t\tdouble dist = pos.distSqr(entrySet.getValue());\n\t\t\tif (dist < range * range) {\n\t\t\t\tif (dist < closestDist) {\n\t\t\t\t\tclosestDist = dist;\n\t\t\t\t\tclosestPos = entrySet.getValue();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (closestPos != null) {\n\t\t\treturn Optional.of(closestPos);\n\t\t} else {\n\t\t\treturn Optional.empty();\n\t\t}\n\n\t}\n\n\tpublic void tickStormBlockBuildup(StormState stormState, Block block) {\n\t\tLevel world = getWorld();\n\t\tWindManager windMan = getWindManager();\n\t\tRandom rand = CoroUtilMisc.random();\n\n\t\tfloat angle = windMan.getWindAngle(null);\n\n\t\tint rate = 1;\n\t\tint maxStack;\n\t\tif (stormState != null) {\n\t\t\trate = stormState.getBuildupTickRate();\n\t\t\tmaxStack = stormState.getMaxStackable();\n\t\t} else {\n\t\t\tmaxStack = 4;\n\t\t}\n\t\tif (world.getGameTime() % rate == 0) {\n\t\t\tList<ChunkHolder> list = Lists.newArrayList(((ServerLevel)world).getChunkSource().chunkMap.getChunks());\n\t\t\tCollections.shuffle(list);\n\t\t\tlist.forEach((p_241099_7_) -> {\n\t\t\t\tOptional<LevelChunk> optional = p_241099_7_.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK).left();\n\t\t\t\tif (optional.isPresent()) {\n\t\t\t\t\tfor (int i = 0; i < 10; i++) {\n\t\t\t\t\t\tBlockPos blockPos = new BlockPos((optional.get().getPos().x * 16) + rand.nextInt(16), 0, (optional.get().getPos().z * 16) + rand.nextInt(16));\n\t\t\t\t\t\tint y = WeatherUtilBlock.getPrecipitationHeightSafe(world, blockPos).getY();\n\t\t\t\t\t\tVec3 pos = new Vec3(blockPos.getX(), y, blockPos.getZ());\n\t\t\t\t\t\tWeatherUtilBlock.fillAgainstWallSmoothly(world, pos, angle, 15, 2, block, maxStack);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n\n\tpublic void syncStormRemove(WeatherObject parStorm) {\n\t\t//packets\n\t\tCompoundTag data = new CompoundTag();\n\t\tdata.putString(\"packetCommand\", \"WeatherData\");\n\t\tdata.putString(\"command\", \"syncStormRemove\");\n\t\tparStorm.nbtSyncForClient();\n\t\tdata.put(\"data\", parStorm.getNbtCache().getNewNBT());\n\t\t//data.put(\"data\", parStorm.nbtSyncForClient(new NBTTagCompound()));\n\t\t//fix for client having broken states\n\t\tdata.getCompound(\"data\").putBoolean(\"removed\", true);\n\t\t//Weather.eventChannel.sendToDimension(PacketHelper.getNBTPacket(data, Weather.eventChannelName), getWorld().getDimension().getType().getId());\n\t\tWeatherNetworking.HANDLER.send(PacketDistributor.DIMENSION.with(() -> getWorld().dimension()), new PacketNBTFromServer(data));\n\t}\n\n\tpublic void syncWindUpdate(WindManager parManager) {\n\t\t//packets\n\t\tCompoundTag data = new CompoundTag();\n\t\tdata.putString(\"packetCommand\", \"WeatherData\");\n\t\tdata.putString(\"command\", \"syncWindUpdate\");\n\t\tdata.put(\"data\", parManager.nbtSyncForClient());\n\t\tWeatherNetworking.HANDLER.send(PacketDistributor.DIMENSION.with(() -> getWorld().dimension()), new PacketNBTFromServer(data));\n\t}\n\n\tpublic void tickWeatherCoverage() {\n\t\tServerLevel world = (ServerLevel) this.getWorld();\n\t\tif (world != null) {\n\t\t\tif (!ConfigMisc.overcastMode) {\n\t\t\t\tif (ConfigMisc.lockServerWeatherMode != -1) {\n\t\t\t\t\tworld.serverLevelData.setRaining(ConfigMisc.lockServerWeatherMode == 1);\n\t\t\t\t\tworld.serverLevelData.setThundering(ConfigMisc.lockServerWeatherMode == 1);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (ConfigStorm.preventServerThunderstorms && !ConfigMisc.Aesthetic_Only_Mode) {\n\t\t\t\tworld.serverLevelData.setThundering(false);\n\t\t\t}\n\n\t\t\t//if (ConfigMisc.overcastMode) {\n\t\t\tif (world.getGameTime() % 40 == 0) {\n\t\t\t\tisVanillaRainActiveOnServer = world.isRaining();\n\t\t\t\tisVanillaThunderActiveOnServer = world.isThundering();\n\t\t\t\tvanillaRainTimeOnServer = world.serverLevelData.getRainTime();\n\t\t\t\tfloat minRain = 0;\n\t\t\t\tfloat maxRain = 0;\n\t\t\t\tif (world.isThundering()) {\n\t\t\t\t\tminRain = 0.3F;\n\t\t\t\t\tmaxRain = 1F;\n\t\t\t\t} else if (world.isRaining()) {\n\t\t\t\t\tminRain = 0.1F;\n\t\t\t\t\tmaxRain = 0.7F;\n\t\t\t\t}\n\t\t\t\tvanillaRainAmountOnServer = Math.max(minRain, Math.min(maxRain, vanillaRainAmountOnServer + (CoroUtilMisc.random().nextFloat() - CoroUtilMisc.random().nextFloat()) * 0.02F));\n\t\t\t\t//vanillaRainAmountOnServer = 1F;\n\t\t\t\t//System.out.println(\"server precip: \" + vanillaRainAmountOnServer);\n\t\t\t\tsyncWeatherVanilla();\n\t\t\t}\n\t\t\t//}\n\n\t\t\tif (world.getGameTime() % 400 == 0) {\n\t\t\t\t//Weather.dbg(\"for dim: \" + world.provider.dimensionId + \" - is server dimension raining?: \" + world.isRaining() + \" time: \" + world.serverLevelData.getRainTime());\n\t\t\t}\n\n\t\t\t//tick partial cloud cover variation\n\t\t\t//windMan.startHighWindEvent();\n\t\t\t//windMan.stopLowWindEvent();\n\t\t\t//cloudIntensity = 0.3F;\n\n\t\t\tif (world.getGameTime() % 200 == 0) {\n\t\t\t\tRandom rand = new Random();\n\t\t\t\tcloudIntensity += (float)((rand.nextDouble() * ConfigMisc.Cloud_Coverage_Random_Change_Amount) - (rand.nextDouble() * ConfigMisc.Cloud_Coverage_Random_Change_Amount));\n\t\t\t\tif (ConfigMisc.overcastMode && world.isRaining()) {\n\t\t\t\t\tcloudIntensity = 1;\n\t\t\t\t} else {\n\t\t\t\t\tif (cloudIntensity < ConfigMisc.Cloud_Coverage_Min_Percent / 100F) {\n\t\t\t\t\t\tcloudIntensity = (float) ConfigMisc.Cloud_Coverage_Min_Percent / 100F;\n\t\t\t\t\t} else if (cloudIntensity > ConfigMisc.Cloud_Coverage_Max_Percent / 100F) {\n\t\t\t\t\t\tcloudIntensity = (float) ConfigMisc.Cloud_Coverage_Max_Percent / 100F;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (world.getGameTime() % 2000 == 0) {\n\t\t\t\t\t//Weather.dbg(\"cloudIntensity FORCED MAX: \" + cloudIntensity);\n\t\t\t\t}\n\n\t\t\t\t//force full cloudIntensity if server side raining\n\t\t\t\t//note: storms also revert to clouded storms for same condition\n\n\t\t\t}\n\n\t\t\t//temp lock to max for fps comparisons\n\t\t\t//cloudIntensity = 1F;\n\t\t}\n\t}\n\n\tpublic void tryParticleStorm(Level level, WeatherObjectParticleStorm.StormType type) {\n\t\t//boolean stormMade = false;\n\t\tint stormOdds = ConfigSand.Sandstorm_OddsTo1;\n\t\tint timeBetweenTicks = ConfigSand.Sandstorm_TimeBetweenInTicks;\n\t\tlong lastStormTime = this.lastSandstormFormed;\n\t\tboolean useGlobalServerRate = ConfigSand.Sandstorm_UseGlobalServerRate;\n\t\tString stormString;\n\n\t\tif (type == WeatherObjectParticleStorm.StormType.SNOWSTORM) {\n\t\t\tstormOdds = ConfigSnow.Snowstorm_OddsTo1;\n\t\t\tstormString = \"lastSnowstormTime\";\n\t\t\tuseGlobalServerRate = ConfigSnow.Snowstorm_UseGlobalServerRate;\n\t\t\tlastStormTime = this.lastSnowstormFormed;\n\t\t} else {\n\t\t\tstormString = \"lastSandstormTime\";\n\t\t}\n\n\t\tif (stormOdds <= 0 || CoroUtilMisc.random().nextInt(stormOdds) == 0) {\n\t\t\tif (useGlobalServerRate) {\n\t\t\t\tif (lastStormTime == 0 || lastStormTime + timeBetweenTicks < level.getGameTime()) {\n\t\t\t\t\tif (world.players().size() > 0) {\n\t\t\t\t\t\tPlayer entP = world.players().get(CoroUtilMisc.random().nextInt(world.players().size()));\n\t\t\t\t\t\tboolean stormMade = trySpawnParticleStormNearPos(level, entP.position(), type);\n\t\t\t\t\t\tif (stormMade) {\n\t\t\t\t\t\t\tif (type == WeatherObjectParticleStorm.StormType.SANDSTORM) {\n\t\t\t\t\t\t\t\tlastSandstormFormed = world.getGameTime();\n\t\t\t\t\t\t\t} else if (type == WeatherObjectParticleStorm.StormType.SNOWSTORM) {\n\t\t\t\t\t\t\t\tlastSnowstormFormed = world.getGameTime();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tworld.players().stream().forEach(player -> {\n\t\t\t\t\tCompoundTag playerNBT = player.getPersistentData();\n\t\t\t\t\tlong lastStormTimePlayer = playerNBT.getLong(stormString);\n\t\t\t\t\tif (lastStormTimePlayer == 0 || lastStormTimePlayer + timeBetweenTicks < level.getGameTime()) {\n\t\t\t\t\t\tboolean stormMade = trySpawnParticleStormNearPos(player.level(), player.position(), type);\n\t\t\t\t\t\tif (stormMade) {\n\t\t\t\t\t\t\tplayerNBT.putLong(stormString, world.getGameTime());\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic boolean trySpawnParticleStormNearPos(Level world, Vec3 posIn, WeatherObjectParticleStorm.StormType type) {\n\t\treturn trySpawnParticleStormNearPos(world, posIn, type, false);\n\t}\n\n\tpublic boolean trySpawnParticleStormNearPos(Level world, Vec3 posIn, WeatherObjectParticleStorm.StormType type, boolean force) {\n\t\t/**\n\t\t * 1. Start upwind\n\t\t * 2. Find random spot near there loaded and in desert\n\t\t * 3. scan upwind and downwind, require a good stretch of sand/snow for a storm\n\t\t */\n\n\t\tint searchRadius = 64;\n\n\t\tdouble angle = getWindManager().getWindAngleForClouds();\n\t\t//-1 for upwind\n\t\tdouble dirX = -Math.sin(Math.toRadians(angle));\n\t\tdouble dirZ = Math.cos(Math.toRadians(angle));\n\t\tdouble vecX = dirX * searchRadius/2 * -1;\n\t\tdouble vecZ = dirZ * searchRadius/2 * -1;\n\n\t\tRandom rand = new Random();\n\n\t\tBlockPos foundPos = null;\n\n\t\tint findTriesMax = 30;\n\t\tfor (int i = 0; i < findTriesMax; i++) {\n\n\t\t\tint x = Mth.floor(posIn.x + vecX + rand.nextInt(searchRadius * 2) - searchRadius);\n\t\t\tint z = Mth.floor(posIn.z + vecZ + rand.nextInt(searchRadius * 2) - searchRadius);\n\n\t\t\tBlockPos pos = WeatherUtilBlock.getPrecipitationHeightSafe(world, new BlockPos(x, 0, z));\n\n\t\t\tif (!world.isLoaded(pos)) continue;\n\t\t\t//Biome biomeIn = world.m_204166_ForCoordsBody(pos);\n\t\t\tBiome biomeIn = world.getBiome(pos).get();\n\n\t\t\tif (force || WeatherObjectParticleStorm.canSpawnHere(world, pos, type, true)) {\n\t\t\t\t//found\n\t\t\t\tfoundPos = pos;\n\t\t\t\t//break;\n\n\t\t\t\t//check left and right about 20 blocks, if its not still desert, force retry\n\t\t\t\tdouble dirXLeft = -Math.sin(Math.toRadians(angle-90));\n\t\t\t\tdouble dirZLeft = Math.cos(Math.toRadians(angle-90));\n\t\t\t\tdouble dirXRight = -Math.sin(Math.toRadians(angle+90));\n\t\t\t\tdouble dirZRight = Math.cos(Math.toRadians(angle+90));\n\n\t\t\t\tdouble distLeftRight = 20;\n\t\t\t\tBlockPos posLeft = WeatherUtilBlock.getPrecipitationHeightSafe(world, CoroUtilBlock.blockPos(foundPos.getX() + (dirXLeft * distLeftRight), 0, foundPos.getZ() + (dirZLeft * distLeftRight)));\n\t\t\t\tif (!world.isLoaded(posLeft)) continue;\n\t\t\t\t//if (!WeatherObjectSandstorm.isDesert(world.m_204166_ForCoordsBody(posLeft))) continue;\n\t\t\t\tif (!WeatherObjectParticleStorm.canSpawnHere(world, posLeft, type, false)) continue;\n\t\t\t\t//if (!WeatherObjectSandstorm.isDesert(world.m_204166_(posLeft).m_203334_())) continue;\n\n\t\t\t\tBlockPos posRight = WeatherUtilBlock.getPrecipitationHeightSafe(world, CoroUtilBlock.blockPos(foundPos.getX() + (dirXRight * distLeftRight), 0, foundPos.getZ() + (dirZRight * distLeftRight)));\n\t\t\t\tif (!world.isLoaded(posRight)) continue;\n\t\t\t\t//if (!WeatherObjectSandstorm.isDesert(world.m_204166_ForCoordsBody(posRight))) continue;\n\t\t\t\tif (!WeatherObjectParticleStorm.canSpawnHere(world, posRight, type, false)) continue;\n\t\t\t\t//if (!WeatherObjectSandstorm.isDesert(world.m_204166_(posRight).m_203334_())) continue;\n\n\t\t\t\t//go as far upwind as possible until no desert / unloaded area\n\n\t\t\t\tBlockPos posFind = new BlockPos(foundPos);\n\t\t\t\tBlockPos posFindLastGoodUpwind = new BlockPos(foundPos);\n\t\t\t\tBlockPos posFindLastGoodDownwind = new BlockPos(foundPos);\n\t\t\t\tdouble tickDist = 10;\n\n\t\t\t\t//while (world.isLoaded(posFind) && WeatherObjectSandstorm.isDesert(world.m_204166_ForCoordsBody(posFind))) {\n\t\t\t\twhile (world.isLoaded(posFind) && WeatherObjectParticleStorm.canSpawnHere(world, posFind, type, true)) {\n\t\t\t\t\t//tick last good\n\t\t\t\t\tposFindLastGoodUpwind = new BlockPos(posFind);\n\n\t\t\t\t\t//scan against wind (upwind)\n\t\t\t\t\tint xx = Mth.floor(posFind.getX() + (dirX * -1D * tickDist));\n\t\t\t\t\tint zz = Mth.floor(posFind.getZ() + (dirZ * -1D * tickDist));\n\n\t\t\t\t\tposFind = WeatherUtilBlock.getPrecipitationHeightSafe(world, new BlockPos(xx, 0, zz));\n\t\t\t\t}\n\n\t\t\t\t//reset for downwind scan\n\t\t\t\tposFind = new BlockPos(foundPos);\n\n\t\t\t\t//while (world.isLoaded(posFind) && WeatherObjectSandstorm.isDesert(world.m_204166_ForCoordsBody(posFind))) {\n\t\t\t\twhile (world.isLoaded(posFind) && WeatherObjectParticleStorm.canSpawnHere(world, posFind, type, true)) {\n\t\t\t\t\t//tick last good\n\t\t\t\t\tposFindLastGoodDownwind = new BlockPos(posFind);\n\n\t\t\t\t\t//scan with wind (downwind)\n\t\t\t\t\tint xx = Mth.floor(posFind.getX() + (dirX * 1D * tickDist));\n\t\t\t\t\tint zz = Mth.floor(posFind.getZ() + (dirZ * 1D * tickDist));\n\n\t\t\t\t\tposFind = WeatherUtilBlock.getPrecipitationHeightSafe(world, new BlockPos(xx, 0, zz));\n\t\t\t\t}\n\n\t\t\t\tint minDistanceOfDesertStretchNeeded = 20;\n\t\t\t\tdouble dist = posFindLastGoodUpwind.distSqr(posFindLastGoodDownwind);\n\n\t\t\t\tif (force || dist >= minDistanceOfDesertStretchNeeded * minDistanceOfDesertStretchNeeded) {\n\n\t\t\t\t\tif (ConfigMisc.Winter_Wonderland && type == WeatherObjectParticleStorm.StormType.SNOWSTORM) {\n\t\t\t\t\t\t//spawn it right on the player so they are guaranteed to see it, might want to do this anyways in future since sandstorm changes\n\t\t\t\t\t\tspawnParticleStorm(CoroUtilBlock.blockPos(posIn), type);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tspawnParticleStorm(posFindLastGoodUpwind, type);\n\t\t\t\t\t}\n\n\n\t\t\t\t\tWeather.dbg(\"found decent spot and stretch for particle storm, stretch: \" + dist + \", type: \" + type);\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tWeather.dbg(\"couldnt spawn particle storm\");\n\t\treturn false;\n\t}\n\n\tpublic void spawnParticleStorm(BlockPos pos, WeatherObjectParticleStorm.StormType type) {\n\t\tWeatherObjectParticleStorm storm = new WeatherObjectParticleStorm(this);\n\n\t\tstorm.setType(type);\n\t\tstorm.initFirstTime();\n\t\tBlockPos posSpawn = new BlockPos(WeatherUtilBlock.getPrecipitationHeightSafe(world, pos)).above();\n\t\tstorm.initStormSpawn(new Vec3(posSpawn.getX(), posSpawn.getY(), posSpawn.getZ()));\n\t\taddStormObject(storm);\n\t\tsyncStormNew(storm);\n\t}\n\n\tpublic void trySpawnStormCloudNearPlayerForLayer(Player entP, int layer) {\n\n\t\t//if (true) return;\n\n\t\tRandom rand = new Random();\n\n\t\tint tryCountMax = 10;\n\t\tint tryCountCur = 0;\n\t\tint spawnX = -1;\n\t\tint spawnZ = -1;\n\t\tVec3 tryPos = null;\n\t\tStormObject soClose = null;\n\t\tPlayer playerClose = null;\n\n\t\tint closestToPlayer = 128;\n\n\t\t//use 256 or the cutoff val if its configured small\n\t\tfloat windOffsetDist = Math.min(256, ConfigMisc.Misc_simBoxRadiusCutoff / 4 * 3);\n\t\tdouble angle = getWindManager().getWindAngleForClouds();\n\t\tdouble vecX = -Math.sin(Math.toRadians(angle)) * windOffsetDist;\n\t\tdouble vecZ = Math.cos(Math.toRadians(angle)) * windOffsetDist;\n\n\t\twhile (tryCountCur++ == 0 || (tryCountCur < tryCountMax && (soClose != null || playerClose != null))) {\n\t\t\tspawnX = (int) (entP.getX() - vecX + rand.nextInt(ConfigMisc.Misc_simBoxRadiusSpawn) - rand.nextInt(ConfigMisc.Misc_simBoxRadiusSpawn));\n\t\t\tspawnZ = (int) (entP.getZ() - vecZ + rand.nextInt(ConfigMisc.Misc_simBoxRadiusSpawn) - rand.nextInt(ConfigMisc.Misc_simBoxRadiusSpawn));\n\t\t\ttryPos = new Vec3(spawnX, StormObject.layers.get(layer), spawnZ);\n\t\t\tsoClose = getClosestStormAny(tryPos, ConfigMisc.Cloud_Formation_MinDistBetweenSpawned);\n\t\t\tplayerClose = entP.level().getNearestPlayer(spawnX, 50, spawnZ, closestToPlayer, false);\n\t\t}\n\n\t\tif (soClose == null) {\n\t\t\t//Weather.dbg(\"spawning storm at: \" + spawnX + \" - \" + spawnZ);\n\n\t\t\tStormObject so = new StormObject(this);\n\t\t\tso.pos = tryPos;\n\t\t\tso.layer = layer;\n\t\t\tso.initFirstTime();\n\t\t\t//make only layer 0 produce deadly storms\n\t\t\tif (layer != 0) {\n\t\t\t\tso.canBeDeadly = false;\n\t\t\t}\n\t\t\tso.spawnerUUID = entP.getStringUUID();\n\t\t\tif (rand.nextFloat() >= cloudIntensity) {\n\t\t\t\tso.setCloudlessStorm(true);\n\t\t\t}\n\t\t\taddStormObject(so);\n\t\t\tsyncStormNew(so);\n\t\t} else {\n\t\t\t//Weather.dbg(\"couldnt find space to spawn cloud formation\");\n\t\t}\n\t}\n\n\tpublic void playerJoinedWorldSyncFull(ServerPlayer entP) {\n\t\tWeather.dbg(\"Weather2: playerJoinedWorldSyncFull for dim: \" + dimension);\n\t\tLevel world = getWorld();\n\t\tif (world != null) {\n\t\t\tWeather.dbg(\"Weather2: playerJoinedWorldSyncFull, sending \" + getStormObjects().size() + \" weather objects to: \" + entP.getName() + \", dim: \" + dimension);\n\t\t\t//sync storms\n\t\t\tfor (int i = 0; i < getStormObjects().size(); i++) {\n\t\t\t\tsyncStormNew(getStormObjects().get(i), entP);\n\t\t\t}\n\t\t}\n\t}\n\n\t//populate data with rain storms and deadly storms\n\t/*public void nbtStormsForIMC() {\n\t\tCompoundTag data = new CompoundTag();\n\n\t\tfor (int i = 0; i < getStormObjects().size(); i++) {\n\t\t\tWeatherObject wo = getStormObjects().get(i);\n\n\t\t\tif (wo instanceof StormObject) {\n\t\t\t\tStormObject so = (StormObject) wo;\n\t\t\t\tif (so.levelCurIntensityStage > 0 || so.attrib_precipitation) {\n\t\t\t\t\tCompoundTag nbtStorm = so.nbtForIMC();\n\n\t\t\t\t\tdata.put(\"storm_\" + so.ID, nbtStorm);\n\t\t\t\t}\n\t\t\t}\n\n\n\t\t}\n\n\t\tif (!data.hasNoTags()) {\n\t\t\tFMLInterModComms.sendRuntimeMessage(Weather.instance, Weather.MODID, \"weather.storms\", data);\n\t\t}\n\t}*/\n\n\tpublic void syncLightningNew(Entity parEnt, boolean custom) {\n\t\tCompoundTag data = new CompoundTag();\n\t\tdata.putString(\"packetCommand\", \"WeatherData\");\n\t\tdata.putString(\"command\", \"syncLightningNew\");\n\t\tCompoundTag nbt = new CompoundTag();\n\t\tnbt.putInt(\"posX\", Mth.floor(parEnt.getX()));\n\t\tnbt.putInt(\"posY\", Mth.floor(parEnt.getY()));\n\t\tnbt.putInt(\"posZ\", Mth.floor(parEnt.getZ()));\n\t\tnbt.putInt(\"entityID\", parEnt.getId());\n\t\tnbt.putBoolean(\"custom\", custom);\n\t\tdata.put(\"data\", nbt);\n\n\t\tWeatherNetworking.HANDLER.send(PacketDistributor.DIMENSION.with(() -> getWorld().dimension()), new PacketNBTFromServer(data));\n\t}\n\n\tpublic void syncBlockParticleNew(BlockPos pos, BlockState state, WeatherObject owner) {\n\t\tCompoundTag data = new CompoundTag();\n\t\tdata.putString(\"packetCommand\", \"WeatherData\");\n\t\tdata.putString(\"command\", \"syncBlockParticleNew\");\n\t\tCompoundTag nbt = new CompoundTag();\n\t\tnbt.putInt(\"posX\", pos.getX());\n\t\tnbt.putInt(\"posY\", pos.getY());\n\t\tnbt.putInt(\"posZ\", pos.getZ());\n\t\tnbt.put(\"blockstate\", NbtUtils.writeBlockState(state));\n\t\tnbt.putLong(\"ownerID\", owner.ID);\n\t\tdata.put(\"data\", nbt);\n\n\t\tWeatherNetworking.HANDLER.send(PacketDistributor.DIMENSION.with(() -> getWorld().dimension()), new PacketNBTFromServer(data));\n\t}\n\n\tpublic void syncStormNew(WeatherObject parStorm) {\n\t\tsyncStormNew(parStorm, null);\n\t}\n\n\tpublic void syncStormNew(WeatherObject parStorm, @Nullable ServerPlayer entP) {\n\t\tCompoundTag data = new CompoundTag();\n\t\tdata.putString(\"packetCommand\", \"WeatherData\");\n\t\tdata.putString(\"command\", \"syncStormNew\");\n\n\t\tCachedNBTTagCompound cache = parStorm.getNbtCache();\n\t\tcache.setUpdateForced(true);\n\t\tparStorm.nbtSyncForClient();\n\t\tcache.setUpdateForced(false);\n\t\tdata.put(\"data\", cache.getNewNBT());\n\n\t\tif (entP == null) {\n\t\t\tWeatherNetworking.HANDLER.send(PacketDistributor.DIMENSION.with(() -> getWorld().dimension()), new PacketNBTFromServer(data));\n\t\t} else {\n\t\t\tWeatherNetworking.HANDLER.sendTo(new PacketNBTFromServer(data), entP.connection.connection, NetworkDirection.PLAY_TO_CLIENT);\n\t\t}\n\t}\n\n\tpublic void syncStormUpdate(WeatherObject parStorm) {\n\t\t//packets\n\t\tCompoundTag data = new CompoundTag();\n\t\tdata.putString(\"packetCommand\", \"WeatherData\");\n\t\tdata.putString(\"command\", \"syncStormUpdate\");\n\t\tparStorm.getNbtCache().setNewNBT(new CompoundTag());\n\t\tparStorm.nbtSyncForClient();\n\t\tdata.put(\"data\", parStorm.getNbtCache().getNewNBT());\n\t\tboolean testNetworkData = false;\n\t\tif (testNetworkData) {\n\t\t\tSystem.out.println(\"sending to client: \" + parStorm.getNbtCache().getNewNBT().getAllKeys().size());\n\t\t\tif (parStorm instanceof StormObject) {\n\t\t\t\tSystem.out.println(\"Real: \" + ((StormObject) parStorm).levelCurIntensityStage);\n\t\t\t\tif (parStorm.getNbtCache().getNewNBT().contains(\"levelCurIntensityStage\")) {\n\t\t\t\t\tSystem.out.println(\" vs \" + parStorm.getNbtCache().getNewNBT().getInt(\"levelCurIntensityStage\"));\n\t\t\t\t} else {\n\t\t\t\t\tSystem.out.println(\"no key!\");\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tIterator iterator = parStorm.getNbtCache().getNewNBT().getAllKeys().iterator();\n\t\t\tString keys = \"\";\n\t\t\twhile (iterator.hasNext()) {\n\t\t\t\tkeys = keys.concat((String) iterator.next() + \"; \");\n\t\t\t}\n\t\t\tSystem.out.println(\"sending    \" + keys);\n\t\t}\n\t\tWeatherNetworking.HANDLER.send(PacketDistributor.DIMENSION.with(() -> getWorld().dimension()), new PacketNBTFromServer(data));\n\t}\n\n\tpublic void syncWeatherVanilla() {\n\n\t\tCompoundTag data = new CompoundTag();\n\t\tdata.putString(\"packetCommand\", \"WeatherData\");\n\t\tdata.putString(\"command\", \"syncWeatherUpdate\");\n\t\tdata.putBoolean(\"isVanillaRainActiveOnServer\", isVanillaRainActiveOnServer);\n\t\tdata.putBoolean(\"isVanillaThunderActiveOnServer\", isVanillaThunderActiveOnServer);\n\t\tdata.putInt(\"vanillaRainTimeOnServer\", vanillaRainTimeOnServer);\n\t\tdata.putFloat(\"vanillaRainAmountOnServer\", vanillaRainAmountOnServer);\n\t\tWeatherNetworking.HANDLER.send(PacketDistributor.DIMENSION.with(() -> getWorld().dimension()), new PacketNBTFromServer(data));\n\t}\n\n\tpublic void removeWeatherObjectAndSync(WeatherObject parStorm) {\n\t\t//because stream()s\n\t\tif (parStorm == null) {\n\t\t\treturn;\n\t\t}\n\t\tif (getWorld().players().size() == 0) {\n\t\t\tWeather.dbg(\"removing distant storm: \" + parStorm.ID + \", running without players\");\n\t\t} else {\n\t\t\tWeather.dbg(\"removing distant storm: \" + parStorm.ID);\n\t\t}\n\t\tremoveStormObject(parStorm.ID);\n\t\tsyncStormRemove(parStorm);\n\t}\n\n\tpublic void clearAllStorms() {\n\t\tIterator<WeatherObject> it = getStormObjects().iterator();\n\t\twhile (it.hasNext()) {\n\t\t\tWeatherObject so = it.next();\n\t\t\t//removeStormObject(so.ID);\n\t\t\tso.remove();\n\t\t\tsyncStormRemove(so);\n\n\t\t}\n\t\tgetStormObjects().clear();\n\t\tlookupStormObjectsByID.clear();\n\t}\n\n\t/**\n\t * @param posCenter\n\t * @return value between 0 and 1, 0 = no chance, 1 = high chance\n\t */\n\tpublic float getBiomeBasedStormSpawnChanceInArea(BlockPos posCenter) {\n\n\t\tint scanResolution = 64;\n\t\tfloat samples = 0;\n\t\tfloat allTemperaturesAdded = 0;\n\t\t/**\n\t\t * The closer to 0 allTemperaturesAdded is the more likely storms can spawn, the closer it is to samples or -samples the less likely storms can spawn\n\t\t * 0 = found equal amount of warm and cold biomes, great env for spawning\n\t\t * negative or positive sample count = found either only warm or only cold\n\t\t */\n\t\tfor (int x = -ConfigMisc.Misc_simBoxRadiusSpawn; x <= ConfigMisc.Misc_simBoxRadiusSpawn; x += scanResolution) {\n\t\t\tfor (int z = -ConfigMisc.Misc_simBoxRadiusSpawn; z <= ConfigMisc.Misc_simBoxRadiusSpawn; z += scanResolution) {\n\t\t\t\tBlockPos pos = new BlockPos(posCenter.getX() + x, posCenter.getY(), posCenter.getZ() + z);\n\t\t\t\tif (getWorld().isLoaded(pos)) {\n\t\t\t\t\tpos = WeatherUtilBlock.getPrecipitationHeightSafe(getWorld(), pos);\n\t\t\t\t\tBiome bgb = getWorld().getBiome(pos).get();\n\t\t\t\t\tallTemperaturesAdded += StormObject.getTemperatureMCToWeatherSys(CoroUtilCompatibility.getAdjustedTemperature(getWorld(), bgb, pos));\n\t\t\t\t\tsamples++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tCULog.dbg(\"samples: \" + samples);\n\t\tCULog.dbg(\"allTemperaturesAdded: \" + allTemperaturesAdded);\n\n\t\tfloat chance = 1 - (Math.abs(allTemperaturesAdded) / samples);\n\t\treturn chance;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/fog/FogAdjuster.java",
    "content": "package weather2.weathersystem.fog;\n\nimport com.corosus.coroutil.util.CULog;\nimport net.minecraft.util.Mth;\nimport net.minecraftforge.client.event.ViewportEvent;\nimport org.joml.Vector3f;\nimport weather2.datatypes.WeatherEventType;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.client.renderer.FogRenderer;\nimport net.minecraft.world.entity.player.Player;\nimport weather2.ClientTickHandler;\nimport weather2.ClientWeatherProxy;\nimport weather2.client.SceneEnhancer;\nimport weather2.util.WeatherUtilEntity;\n\nimport java.util.Random;\n\npublic class FogAdjuster {\n\n    private FogProfile fogHeatwave;\n    private FogProfile fogSandstorm;\n    private FogProfile fogSnowstorm;\n\n    //initial values arent really used for this one, just used to store dynamically updated values for smooth transitions\n    private FogProfile fogVanilla;\n\n    private FogProfile targetProfile;\n    private FogProfile activeProfile;\n    private FogProfile activeProfileLerps;\n\n    private int lerpTicksCur = 20 * 15;\n    private int lerpTicksMax = 20 * 15;\n\n    //reinit fog values when changes\n    private boolean useFarFog = false;\n\n    public static WeatherEventType lastWeatherType = null;\n\n    public int randDelay = 0;\n\n    private boolean firstUseInit = true;\n\n    /**\n     *\n     * new fog adjust way:\n     * when theres a new request to change the state\n     * - set old state to prev state\n     * - for each thing we have to fade (each color, density)\n     * -- calculate a lerp rate so they all take the same amount of time?\n     * - actually, how do we want to decide on how long itll take? we might want that dynamic\n     * - maybe average it based on the distance between each color, so like, white to black and far dist fog change = long\n     * - but white to grey with not much fog dist change = short\n     *\n     * - we could let color and dist change at diff rates\n     * - important the rgbs transition the same, for obvious reasons\n     *\n     *  important note: activeProfile will now be updated with current actual vals\n     *  - so when we get interrupted, we actually have last state we were at\n     *\n     *  - never change active profile, just set a new target and rates\n     *\n     *  - monitor vanilla color/fog changes and i guess push a new target? should be fine, will be slow jank with my static 100 tick update for now\n     */\n\n\n    public FogAdjuster() {\n        initProfiles(false);\n        activeProfile = new FogProfile(fogVanilla);\n        targetProfile = fogVanilla;\n        activeProfileLerps = new FogProfile(new Vector3f(0F, 0F, 0F), 0, 0);\n    }\n\n    public void initProfiles(boolean spectator) {\n\n        float distAmp = 1F;\n        if (spectator) {\n            distAmp = 4F;\n        }\n        fogHeatwave = new FogProfile(new Vector3f(0.5F, 0.2F, 0.1F), 0, 75);\n        fogSandstorm = new FogProfile(new Vector3f(0.7F, 0.5F, 0.2F), 0, 18 * distAmp);\n        fogSnowstorm = new FogProfile(new Vector3f(0.7F, 0.7F, 0.7F), 0, 20 * distAmp);\n        fogVanilla = new FogProfile(new Vector3f(-1F, -1F, -1F), -1, -1);\n    }\n\n    public void tickGame(ClientWeatherProxy weather) {\n        updateWeatherState();\n\n        boolean fogDisco = false;\n        if (fogDisco) {\n            //if (lastWeatherType != null) {\n                if (randDelay <= 0) {\n                    Random rand = new Random();\n                    randDelay = 20 + rand.nextInt(5);\n                    startRandom();\n                }\n            //}\n\n            randDelay--;\n        }\n\n        if ((SceneEnhancer.getWeatherState() == WeatherEventType.SANDSTORM || SceneEnhancer.getWeatherState() == WeatherEventType.SNOWSTORM)) {\n            Player player = Minecraft.getInstance().player;\n            //use non cached version of isPlayerOutside to fix data mismatch that is timing crucial here\n            boolean isPlayerOutside = WeatherUtilEntity.isEntityOutside(player);\n            boolean playerOutside = isPlayerOutside || player.isInWater();\n            boolean setFogFar = !playerOutside || player.isSpectator();\n            /*CULog.dbg(\"set to far mode?: \" + setFogFar);\n            CULog.dbg(\"playerOutside: \" + SceneEnhancer.isPlayerOutside);\n            CULog.dbg(\"isInWater: \" + player.isInWater());\n            CULog.dbg(\"setFogFar: \" + setFogFar);*/\n            if (player != null) {\n                if ((setFogFar && !useFarFog) || !setFogFar && useFarFog) {\n                    initProfiles(setFogFar);\n                    if (SceneEnhancer.getWeatherState() == WeatherEventType.SANDSTORM) {\n                        startSandstorm();\n                    } else if (SceneEnhancer.getWeatherState() == WeatherEventType.SNOWSTORM) {\n                        startSnowstorm();\n                    }\n                }\n                useFarFog = setFogFar;\n            }\n        }\n\n        if (lerpTicksCur < lerpTicksMax) {\n            float newLerpX = activeProfile.getRgb().x() + activeProfileLerps.getRgb().x();\n            float newLerpY = activeProfile.getRgb().y() + activeProfileLerps.getRgb().y();\n            float newLerpZ = activeProfile.getRgb().z() + activeProfileLerps.getRgb().z();\n            activeProfile.getRgb().set(newLerpX, newLerpY, newLerpZ);\n\n            activeProfile.setFogStart(activeProfile.getFogStart() + activeProfileLerps.getFogStart());\n            activeProfile.setFogEnd(activeProfile.getFogEnd() + activeProfileLerps.getFogEnd());\n\n            activeProfile.setFogStartSky(activeProfile.getFogStartSky() + activeProfileLerps.getFogStartSky());\n            activeProfile.setFogEndSky(activeProfile.getFogEndSky() + activeProfileLerps.getFogEndSky());\n\n            lerpTicksCur++;\n\n            //System.out.println(lerpTicksCur + \" - \" + activeProfile.getFogStart() + \" - \" + activeProfile.getFogEnd());\n        }\n    }\n\n    public void onFogColors(ViewportEvent.ComputeFogColor event) {\n        updateWeatherState();\n\n        //get vanilla settings\n        fogVanilla.getRgb().set(event.getRed(), event.getGreen(), event.getBlue());\n\n        if (SceneEnhancer.isFogOverridding()) {\n            float brightness = Mth.clamp(Mth.cos(Minecraft.getInstance().level.getTimeOfDay(1F) * ((float)Math.PI * 2F)) * 2.0F + 0.5F, 0.0F, 1.0F);\n            event.setRed(activeProfile.getRgb().x() * brightness);\n            event.setGreen(activeProfile.getRgb().y() * brightness);\n            event.setBlue(activeProfile.getRgb().z() * brightness);\n        }\n    }\n\n    public void onFogRender(ViewportEvent.RenderFog event) {\n        updateWeatherState();\n\n        //get vanilla settings\n        if (event.getMode() == FogRenderer.FogMode.FOG_SKY) {\n            fogVanilla.setFogStartSky(event.getNearPlaneDistance());\n            fogVanilla.setFogEndSky(event.getFarPlaneDistance());\n        } else {\n            fogVanilla.setFogStart(event.getNearPlaneDistance());\n            fogVanilla.setFogEnd(event.getFarPlaneDistance());\n        }\n\n        if (SceneEnhancer.isFogOverridding()) {\n            if (event.getMode() == FogRenderer.FogMode.FOG_SKY) {\n                event.setNearPlaneDistance(activeProfile.getFogStartSky());\n                event.setFarPlaneDistance(activeProfile.getFogEndSky());\n                event.setCanceled(true);\n            } else {\n                event.setNearPlaneDistance(activeProfile.getFogStart());\n                event.setFarPlaneDistance(activeProfile.getFogEnd());\n                event.setCanceled(true);\n            }\n        }\n    }\n\n    public void startRandom() {\n        Random rand = new Random();\n        int randFog = 0;\n        if (activeProfile.getFogEnd() < 50) {\n            randFog = 50 + rand.nextInt(50);\n        } else {\n            randFog = rand.nextInt(50);\n        }\n        randFog = rand.nextInt(100);\n        targetProfile = new FogProfile(new Vector3f(rand.nextFloat(), rand.nextFloat(), rand.nextFloat()), 0, randFog);\n        lerpTicksMax = 20 + rand.nextInt(50);\n        setupNewLerpRates();\n        CULog.dbg(\"startRandom: \" + randFog);\n    }\n\n    public void startHeatwave() {\n        CULog.dbg(\"startHeatwave\");\n        //activeProfile = targetProfile;\n        targetProfile = new FogProfile(fogHeatwave);\n        setupNewLerpRates();\n    }\n\n    public void startSandstorm() {\n        CULog.dbg(\"startSandstorm\");\n        //activeProfile = targetProfile;\n        targetProfile = new FogProfile(fogSandstorm);\n        setupNewLerpRates();\n    }\n\n    public void startSnowstorm() {\n        CULog.dbg(\"startSnowstorm\");\n        //activeProfile = targetProfile;\n        targetProfile = new FogProfile(fogSnowstorm);\n        setupNewLerpRates();\n    }\n\n    public void restoreVanilla() {\n        CULog.dbg(\"restoreVanilla\");\n        //activeProfile = targetProfile;\n        targetProfile = new FogProfile(fogVanilla);\n        setupNewLerpRates();\n    }\n\n    public void setupNewLerpRates() {\n        if (firstUseInit) {\n            //if we've correctly set the starting vanilla fog values for both event states\n            if (fogVanilla.getFogEnd() != -1 && fogVanilla.getFogEndSky() != -1) {\n                activeProfile = new FogProfile(fogVanilla);\n                firstUseInit = false;\n            }\n        }\n\n        lerpTicksCur = 0;\n        float partialLerpX = getLerpRate(activeProfile.getRgb().x(), targetProfile.getRgb().x(), lerpTicksMax);\n        float partialLerpY = getLerpRate(activeProfile.getRgb().y(), targetProfile.getRgb().y(), lerpTicksMax);\n        float partialLerpZ = getLerpRate(activeProfile.getRgb().z(), targetProfile.getRgb().z(), lerpTicksMax);\n        activeProfileLerps.getRgb().set(partialLerpX, partialLerpY, partialLerpZ);\n\n        activeProfileLerps.setFogStart(getLerpRate(activeProfile.getFogStart(), targetProfile.getFogStart(), lerpTicksMax));\n        activeProfileLerps.setFogEnd(getLerpRate(activeProfile.getFogEnd(), targetProfile.getFogEnd(), lerpTicksMax));\n        activeProfileLerps.setFogStartSky(getLerpRate(activeProfile.getFogStartSky(), targetProfile.getFogStartSky(), lerpTicksMax));\n        activeProfileLerps.setFogEndSky(getLerpRate(activeProfile.getFogEndSky(), targetProfile.getFogEndSky(), lerpTicksMax));\n    }\n\n    public float getLerpRate(float curVal, float endVal, float fullLerpTicks) {\n        return (endVal - curVal) / fullLerpTicks;\n    }\n\n    public boolean isFogOverriding() {\n        ClientTickHandler.getClientWeather();\n        ClientWeatherProxy weather = ClientWeatherProxy.get();\n        return (weather.isHeatwave() || weather.isSandstorm() || weather.isSnowstorm()) || lerpTicksCur < lerpTicksMax;\n    }\n\n    /**\n     * In its own method so quick render update calls can force an update check to prevent old data use which causes flickers\n     */\n    public void updateWeatherState() {\n        WeatherEventType curWeather = SceneEnhancer.getWeatherState();\n\n        //System.out.println(\"curWeather: \" + curWeather);\n        //System.out.println(\"lastWeatherType: \" + lastWeatherType);\n\n        /*if (curWeather != WeatherEventType.SANDSTORM &&\n                curWeather != WeatherEventType.SNOWSTORM &&\n                curWeather != WeatherEventType.HEATWAVE &&\n                curWeather != null) {\n            return;\n        }*/\n\n        //count ones we dont want fog for as null, to keep the transitions clean and less glitchy\n        if (curWeather == WeatherEventType.ACID_RAIN || curWeather == WeatherEventType.HEAVY_RAIN || curWeather == WeatherEventType.HAIL) {\n            curWeather = null;\n        }\n\n        boolean match = false;\n        if (curWeather != lastWeatherType) {\n            if (curWeather == WeatherEventType.SANDSTORM) {\n                startSandstorm();\n                match = true;\n            } else if (curWeather == WeatherEventType.SNOWSTORM) {\n                startSnowstorm();\n                match = true;\n            } else if (curWeather == WeatherEventType.HEATWAVE) {\n                startHeatwave();\n                match = true;\n            } else if (curWeather == null) {\n                restoreVanilla();\n                match = true;\n            }\n        }\n        if (match) {\n            lastWeatherType = curWeather;\n        }\n    }\n\n    /**\n     * 0 = off\n     * 1 = max on\n     * @return\n     */\n    public float getLerpFraction() {\n        if (lerpTicksMax == 0) return 0;\n        return lerpTicksCur / lerpTicksMax;\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/fog/FogProfile.java",
    "content": "package weather2.weathersystem.fog;\n\nimport org.joml.Vector3f;\n\npublic class FogProfile {\n\n    private Vector3f rgb;\n    private float fogStart;\n    private float fogEnd;\n    private float fogStartSky;\n    private float fogEndSky;\n\n    public FogProfile(FogProfile profile) {\n        this.rgb = new Vector3f(profile.rgb.x(), profile.rgb.y(), profile.rgb.z());\n        this.fogStart = profile.fogStart;\n        this.fogStartSky = profile.fogStartSky;\n        this.fogEnd = profile.fogEnd;\n        this.fogEndSky = profile.fogEndSky;\n    }\n\n    public FogProfile(Vector3f rgb, float fogStart, float fogEnd) {\n        this.rgb = new Vector3f(rgb.x(), rgb.y(), rgb.z());;\n        this.fogStart = fogStart;\n        this.fogStartSky = fogStart;\n        this.fogEnd = fogEnd;\n        this.fogEndSky = fogEnd;\n    }\n\n    public Vector3f getRgb() {\n        return rgb;\n    }\n\n    public void setRgb(Vector3f rgb) {\n        this.rgb = rgb;\n    }\n\n    public float getFogStart() {\n        return fogStart;\n    }\n\n    public void setFogStart(float fogStart) {\n        this.fogStart = fogStart;\n    }\n\n    public float getFogEnd() {\n        return fogEnd;\n    }\n\n    public void setFogEnd(float fogEnd) {\n        this.fogEnd = fogEnd;\n    }\n\n    public float getFogStartSky() {\n        return fogStartSky;\n    }\n\n    public void setFogStartSky(float fogStartSky) {\n        this.fogStartSky = fogStartSky;\n    }\n\n    public float getFogEndSky() {\n        return fogEndSky;\n    }\n\n    public void setFogEndSky(float fogEndSky) {\n        this.fogEndSky = fogEndSky;\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/storm/EnumWeatherObjectType.java",
    "content": "package weather2.weathersystem.storm;\n\nimport java.util.EnumSet;\nimport java.util.HashMap;\nimport java.util.Map;\n\npublic enum EnumWeatherObjectType {\n\t\n\tCLOUD, SAND, SNOW;\n\t\n\tprivate static final Map<Integer, EnumWeatherObjectType> lookup = new HashMap<Integer, EnumWeatherObjectType>();\n    static { for(EnumWeatherObjectType e : EnumSet.allOf(EnumWeatherObjectType.class)) { lookup.put(e.ordinal(), e); } }\n    public static EnumWeatherObjectType get(int intValue) { return lookup.get(intValue); }\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/storm/LightningBoltWeather.java",
    "content": "package weather2.weathersystem.storm;\n\nimport com.corosus.coroutil.util.CoroUtilBlock;\nimport com.google.common.collect.Sets;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.stream.Stream;\nimport javax.annotation.Nullable;\nimport net.minecraft.advancements.CriteriaTriggers;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.nbt.CompoundTag;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.ClientboundAddEntityPacket;\nimport net.minecraft.server.level.ServerLevel;\nimport net.minecraft.server.level.ServerPlayer;\nimport net.minecraft.sounds.SoundEvents;\nimport net.minecraft.sounds.SoundSource;\nimport net.minecraft.world.Difficulty;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.entity.EntityType;\nimport net.minecraft.world.level.GameRules;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.block.BaseFireBlock;\nimport net.minecraft.world.level.block.Blocks;\nimport net.minecraft.world.level.block.LightningRodBlock;\nimport net.minecraft.world.level.block.WeatheringCopper;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.world.level.gameevent.GameEvent;\nimport net.minecraft.world.phys.AABB;\nimport net.minecraft.world.phys.Vec3;\n\npublic class LightningBoltWeather extends Entity {\n   private static final int START_LIFE = 2;\n   private static final double DAMAGE_RADIUS = 3.0D;\n   private static final double DETECTION_RADIUS = 15.0D;\n   private int life;\n   public long seed;\n   private int flashes;\n   private boolean visualOnly;\n   @Nullable\n   private ServerPlayer cause;\n   private final Set<Entity> hitEntities = Sets.newHashSet();\n   private int blocksSetOnFire;\n   private float damage = 5.0F;\n\n   public LightningBoltWeather(EntityType<? extends LightningBoltWeather> p_20865_, Level p_20866_) {\n      super(p_20865_, p_20866_);\n      this.noCulling = true;\n      this.life = 2;\n      this.seed = this.random.nextLong();\n      this.flashes = this.random.nextInt(3) + 1;\n   }\n\n   public LightningBoltWeather(EntityType<? extends LightningBoltWeather> p_20865_, Level p_20866_, double x, double y, double z) {\n      this(p_20865_, p_20866_);\n      this.setPos(x, y, z);\n   }\n\n   public void setVisualOnly(boolean p_20875_) {\n      this.visualOnly = p_20875_;\n   }\n\n   public SoundSource getSoundSource() {\n      return SoundSource.WEATHER;\n   }\n\n   @Nullable\n   public ServerPlayer getCause() {\n      return this.cause;\n   }\n\n   public void setCause(@Nullable ServerPlayer p_20880_) {\n      this.cause = p_20880_;\n   }\n\n   private void powerLightningRod() {\n      BlockPos blockpos = this.getStrikePosition();\n      BlockState blockstate = this.level().getBlockState(blockpos);\n      if (blockstate.is(Blocks.LIGHTNING_ROD)) {\n         ((LightningRodBlock)blockstate.getBlock()).onLightningStrike(blockstate, this.level(), blockpos);\n      }\n\n   }\n\n   public void setDamage(float damage) {\n      this.damage = damage;\n   }\n\n   public float getDamage() {\n      return this.damage;\n   }\n\n   public void tick() {\n      super.tick();\n      if (this.life == 2) {\n         if (this.level().isClientSide()) {\n            this.level().playLocalSound(this.getX(), this.getY(), this.getZ(), SoundEvents.LIGHTNING_BOLT_THUNDER, SoundSource.WEATHER, 10000.0F, 0.8F + this.random.nextFloat() * 0.2F, false);\n            this.level().playLocalSound(this.getX(), this.getY(), this.getZ(), SoundEvents.LIGHTNING_BOLT_IMPACT, SoundSource.WEATHER, 2.0F, 0.5F + this.random.nextFloat() * 0.2F, false);\n         } else {\n            Difficulty difficulty = this.level().getDifficulty();\n            if (difficulty == Difficulty.NORMAL || difficulty == Difficulty.HARD) {\n               this.spawnFire(4);\n            }\n\n            this.powerLightningRod();\n            clearCopperOnLightningStrike(this.level(), this.getStrikePosition());\n            this.gameEvent(GameEvent.LIGHTNING_STRIKE);\n         }\n      }\n\n      --this.life;\n      if (this.life < 0) {\n         if (this.flashes == 0) {\n\n            this.discard();\n         } else if (this.life < -this.random.nextInt(10)) {\n            --this.flashes;\n            this.life = 1;\n            this.seed = this.random.nextLong();\n            this.spawnFire(0);\n         }\n      }\n\n      if (this.life >= 0) {\n         if (!(this.level() instanceof ServerLevel)) {\n            this.level().setSkyFlashTime(2);\n         } else if (!this.visualOnly) {\n            List<Entity> list1 = this.level().getEntities(this, new AABB(this.getX() - 3.0D, this.getY() - 3.0D, this.getZ() - 3.0D, this.getX() + 3.0D, this.getY() + 6.0D + 3.0D, this.getZ() + 3.0D), Entity::isAlive);\n\n            /*for(Entity entity : list1) {\n               if (!net.minecraftforge.event.ForgeEventFactory.onEntityStruckByLightning(entity, this))\n               entity.thunderHit((ServerLevel)this.level, this);\n            }*/\n\n            this.hitEntities.addAll(list1);\n            if (this.cause != null) {\n               CriteriaTriggers.CHANNELED_LIGHTNING.trigger(this.cause, list1);\n            }\n         }\n      }\n\n   }\n\n   private BlockPos getStrikePosition() {\n      Vec3 vec3 = this.position();\n      return CoroUtilBlock.blockPos(vec3.x, vec3.y - 1.0E-6D, vec3.z);\n   }\n\n   private void spawnFire(int p_20871_) {\n      if (!this.visualOnly && !this.level().isClientSide && this.level().getGameRules().getBoolean(GameRules.RULE_DOFIRETICK)) {\n         BlockPos blockpos = this.blockPosition();\n         BlockState blockstate = BaseFireBlock.getState(this.level(), blockpos);\n         if (this.level().getBlockState(blockpos).isAir() && blockstate.canSurvive(this.level(), blockpos)) {\n            this.level().setBlockAndUpdate(blockpos, blockstate);\n            ++this.blocksSetOnFire;\n         }\n\n         for(int i = 0; i < p_20871_; ++i) {\n            BlockPos blockpos1 = blockpos.offset(this.random.nextInt(3) - 1, this.random.nextInt(3) - 1, this.random.nextInt(3) - 1);\n            blockstate = BaseFireBlock.getState(this.level(), blockpos1);\n            if (this.level().getBlockState(blockpos1).isAir() && blockstate.canSurvive(this.level(), blockpos1)) {\n               this.level().setBlockAndUpdate(blockpos1, blockstate);\n               ++this.blocksSetOnFire;\n            }\n         }\n\n      }\n   }\n\n   private static void clearCopperOnLightningStrike(Level p_147151_, BlockPos p_147152_) {\n      BlockState blockstate = p_147151_.getBlockState(p_147152_);\n      BlockPos blockpos;\n      BlockState blockstate1;\n      if (blockstate.is(Blocks.LIGHTNING_ROD)) {\n         blockpos = p_147152_.relative(blockstate.getValue(LightningRodBlock.FACING).getOpposite());\n         blockstate1 = p_147151_.getBlockState(blockpos);\n      } else {\n         blockpos = p_147152_;\n         blockstate1 = blockstate;\n      }\n\n      if (blockstate1.getBlock() instanceof WeatheringCopper) {\n         p_147151_.setBlockAndUpdate(blockpos, WeatheringCopper.getFirst(p_147151_.getBlockState(blockpos)));\n         BlockPos.MutableBlockPos blockpos$mutableblockpos = p_147152_.mutable();\n         int i = p_147151_.random.nextInt(3) + 3;\n\n         for(int j = 0; j < i; ++j) {\n            int k = p_147151_.random.nextInt(8) + 1;\n            randomWalkCleaningCopper(p_147151_, blockpos, blockpos$mutableblockpos, k);\n         }\n\n      }\n   }\n\n   private static void randomWalkCleaningCopper(Level p_147146_, BlockPos p_147147_, BlockPos.MutableBlockPos p_147148_, int p_147149_) {\n      p_147148_.set(p_147147_);\n\n      for(int i = 0; i < p_147149_; ++i) {\n         Optional<BlockPos> optional = randomStepCleaningCopper(p_147146_, p_147148_);\n         if (!optional.isPresent()) {\n            break;\n         }\n\n         p_147148_.set(optional.get());\n      }\n\n   }\n\n   private static Optional<BlockPos> randomStepCleaningCopper(Level p_147154_, BlockPos p_147155_) {\n      for(BlockPos blockpos : BlockPos.randomInCube(p_147154_.random, 10, p_147155_, 1)) {\n         BlockState blockstate = p_147154_.getBlockState(blockpos);\n         if (blockstate.getBlock() instanceof WeatheringCopper) {\n            WeatheringCopper.getPrevious(blockstate).ifPresent((p_147144_) -> {\n               p_147154_.setBlockAndUpdate(blockpos, p_147144_);\n            });\n            p_147154_.levelEvent(3002, blockpos, -1);\n            return Optional.of(blockpos);\n         }\n      }\n\n      return Optional.empty();\n   }\n\n   public boolean shouldRenderAtSqrDistance(double p_20869_) {\n      double d0 = 64.0D * getViewScale();\n      return p_20869_ < d0 * d0;\n   }\n\n   protected void defineSynchedData() {\n   }\n\n   protected void readAdditionalSaveData(CompoundTag p_20873_) {\n   }\n\n   protected void addAdditionalSaveData(CompoundTag p_20877_) {\n   }\n\n   public int getBlocksSetOnFire() {\n      return this.blocksSetOnFire;\n   }\n\n   public Stream<Entity> getHitEntities() {\n      return this.hitEntities.stream().filter(Entity::isAlive);\n   }\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/storm/LightningBoltWeatherNew.java",
    "content": "package weather2.weathersystem.storm;\n\nimport com.corosus.coroutil.util.CoroUtilBlock;\nimport com.google.common.collect.Sets;\nimport java.util.List;\nimport java.util.Optional;\nimport java.util.Set;\nimport java.util.stream.Stream;\nimport javax.annotation.Nullable;\nimport net.minecraft.advancements.CriteriaTriggers;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.nbt.CompoundTag;\nimport net.minecraft.network.protocol.Packet;\nimport net.minecraft.network.protocol.game.ClientboundAddEntityPacket;\nimport net.minecraft.server.level.ServerLevel;\nimport net.minecraft.server.level.ServerPlayer;\nimport net.minecraft.sounds.SoundEvents;\nimport net.minecraft.sounds.SoundSource;\nimport net.minecraft.world.Difficulty;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.entity.EntityType;\nimport net.minecraft.world.level.GameRules;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.block.BaseFireBlock;\nimport net.minecraft.world.level.block.Blocks;\nimport net.minecraft.world.level.block.LightningRodBlock;\nimport net.minecraft.world.level.block.WeatheringCopper;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.world.level.gameevent.GameEvent;\nimport net.minecraft.world.phys.AABB;\nimport net.minecraft.world.phys.Vec3;\n\npublic class LightningBoltWeatherNew extends Entity {\n   private static final int START_LIFE = 2;\n   private static final double DAMAGE_RADIUS = 3.0D;\n   private static final double DETECTION_RADIUS = 15.0D;\n   private int life;\n   public long seed;\n   private int flashes;\n   private boolean visualOnly;\n   @Nullable\n   private ServerPlayer cause;\n   private final Set<Entity> hitEntities = Sets.newHashSet();\n   private int blocksSetOnFire;\n   private float damage = 5.0F;\n\n   public LightningBoltWeatherNew(EntityType<? extends LightningBoltWeatherNew> p_20865_, Level p_20866_) {\n      super(p_20865_, p_20866_);\n      this.noCulling = true;\n      this.life = 2;\n      this.seed = this.random.nextLong();\n      this.flashes = this.random.nextInt(3) + 1;\n   }\n\n   public void setVisualOnly(boolean p_20875_) {\n      this.visualOnly = p_20875_;\n   }\n\n   public SoundSource getSoundSource() {\n      return SoundSource.WEATHER;\n   }\n\n   @Nullable\n   public ServerPlayer getCause() {\n      return this.cause;\n   }\n\n   public void setCause(@Nullable ServerPlayer p_20880_) {\n      this.cause = p_20880_;\n   }\n\n   private void powerLightningRod() {\n      BlockPos blockpos = this.getStrikePosition();\n      BlockState blockstate = this.level().getBlockState(blockpos);\n      if (blockstate.is(Blocks.LIGHTNING_ROD)) {\n         ((LightningRodBlock)blockstate.getBlock()).onLightningStrike(blockstate, this.level(), blockpos);\n      }\n\n   }\n\n   public void setDamage(float damage) {\n      this.damage = damage;\n   }\n\n   public float getDamage() {\n      return this.damage;\n   }\n\n   public void tick() {\n      super.tick();\n      if (this.life == 2) {\n         if (this.level().isClientSide()) {\n            this.level().playLocalSound(this.getX(), this.getY(), this.getZ(), SoundEvents.LIGHTNING_BOLT_THUNDER, SoundSource.WEATHER, 10000.0F, 0.8F + this.random.nextFloat() * 0.2F, false);\n            this.level().playLocalSound(this.getX(), this.getY(), this.getZ(), SoundEvents.LIGHTNING_BOLT_IMPACT, SoundSource.WEATHER, 2.0F, 0.5F + this.random.nextFloat() * 0.2F, false);\n         } else {\n            /*Difficulty difficulty = this.level().getDifficulty();\n            if (difficulty == Difficulty.NORMAL || difficulty == Difficulty.HARD) {\n               this.spawnFire(4);\n            }*/\n\n            this.powerLightningRod();\n            clearCopperOnLightningStrike(this.level(), this.getStrikePosition());\n            this.gameEvent(GameEvent.LIGHTNING_STRIKE);\n         }\n      }\n\n      --this.life;\n      if (this.life < 0) {\n         if (this.flashes == 0) {\n            if (this.level() instanceof ServerLevel) {\n               /*List<Entity> list = this.level().getEntities(this, new AABB(this.getX() - 15.0D, this.getY() - 15.0D, this.getZ() - 15.0D, this.getX() + 15.0D, this.getY() + 6.0D + 15.0D, this.getZ() + 15.0D), (p_147140_) -> {\n                  return p_147140_.isAlive() && !this.hitEntities.contains(p_147140_);\n               });\n\n               for(ServerPlayer serverplayer : ((ServerLevel)this.level()).getPlayers((p_147157_) -> {\n                  return p_147157_.distanceTo(this) < 256.0F;\n               })) {\n                  CriteriaTriggers.LIGHTNING_STRIKE.trigger(serverplayer, this, list);\n               }*/\n            }\n\n            this.discard();\n         } else if (this.life < -this.random.nextInt(10)) {\n            --this.flashes;\n            this.life = 1;\n            this.seed = this.random.nextLong();\n            /*this.spawnFire(0);*/\n         }\n      }\n\n      if (this.life >= 0) {\n         if (!(this.level() instanceof ServerLevel)) {\n            this.level().setSkyFlashTime(2);\n         } else if (!this.visualOnly) {\n            List<Entity> list1 = this.level().getEntities(this, new AABB(this.getX() - 3.0D, this.getY() - 3.0D, this.getZ() - 3.0D, this.getX() + 3.0D, this.getY() + 6.0D + 3.0D, this.getZ() + 3.0D), Entity::isAlive);\n\n            /*for(Entity entity : list1) {\n               if (!net.minecraftforge.event.ForgeEventFactory.onEntityStruckByLightning(entity, this))\n               entity.thunderHit((ServerLevel)this.level(), this);\n            }*/\n\n            this.hitEntities.addAll(list1);\n            if (this.cause != null) {\n               CriteriaTriggers.CHANNELED_LIGHTNING.trigger(this.cause, list1);\n            }\n         }\n      }\n\n   }\n\n   private BlockPos getStrikePosition() {\n      Vec3 vec3 = this.position();\n      return CoroUtilBlock.blockPos(vec3.x, vec3.y - 1.0E-6D, vec3.z);\n   }\n\n   private void spawnFire(int p_20871_) {\n      if (!this.visualOnly && !this.level().isClientSide && this.level().getGameRules().getBoolean(GameRules.RULE_DOFIRETICK)) {\n         BlockPos blockpos = this.blockPosition();\n         BlockState blockstate = BaseFireBlock.getState(this.level(), blockpos);\n         if (this.level().getBlockState(blockpos).isAir() && blockstate.canSurvive(this.level(), blockpos)) {\n            this.level().setBlockAndUpdate(blockpos, blockstate);\n            ++this.blocksSetOnFire;\n         }\n\n         for(int i = 0; i < p_20871_; ++i) {\n            BlockPos blockpos1 = blockpos.offset(this.random.nextInt(3) - 1, this.random.nextInt(3) - 1, this.random.nextInt(3) - 1);\n            blockstate = BaseFireBlock.getState(this.level(), blockpos1);\n            if (this.level().getBlockState(blockpos1).isAir() && blockstate.canSurvive(this.level(), blockpos1)) {\n               this.level().setBlockAndUpdate(blockpos1, blockstate);\n               ++this.blocksSetOnFire;\n            }\n         }\n\n      }\n   }\n\n   private static void clearCopperOnLightningStrike(Level p_147151_, BlockPos p_147152_) {\n      BlockState blockstate = p_147151_.getBlockState(p_147152_);\n      BlockPos blockpos;\n      BlockState blockstate1;\n      if (blockstate.is(Blocks.LIGHTNING_ROD)) {\n         blockpos = p_147152_.relative(blockstate.getValue(LightningRodBlock.FACING).getOpposite());\n         blockstate1 = p_147151_.getBlockState(blockpos);\n      } else {\n         blockpos = p_147152_;\n         blockstate1 = blockstate;\n      }\n\n      if (blockstate1.getBlock() instanceof WeatheringCopper) {\n         p_147151_.setBlockAndUpdate(blockpos, WeatheringCopper.getFirst(p_147151_.getBlockState(blockpos)));\n         BlockPos.MutableBlockPos blockpos$mutableblockpos = p_147152_.mutable();\n         int i = p_147151_.random.nextInt(3) + 3;\n\n         for(int j = 0; j < i; ++j) {\n            int k = p_147151_.random.nextInt(8) + 1;\n            randomWalkCleaningCopper(p_147151_, blockpos, blockpos$mutableblockpos, k);\n         }\n\n      }\n   }\n\n   private static void randomWalkCleaningCopper(Level p_147146_, BlockPos p_147147_, BlockPos.MutableBlockPos p_147148_, int p_147149_) {\n      p_147148_.set(p_147147_);\n\n      for(int i = 0; i < p_147149_; ++i) {\n         Optional<BlockPos> optional = randomStepCleaningCopper(p_147146_, p_147148_);\n         if (!optional.isPresent()) {\n            break;\n         }\n\n         p_147148_.set(optional.get());\n      }\n\n   }\n\n   private static Optional<BlockPos> randomStepCleaningCopper(Level p_147154_, BlockPos p_147155_) {\n      for(BlockPos blockpos : BlockPos.randomInCube(p_147154_.random, 10, p_147155_, 1)) {\n         BlockState blockstate = p_147154_.getBlockState(blockpos);\n         if (blockstate.getBlock() instanceof WeatheringCopper) {\n            WeatheringCopper.getPrevious(blockstate).ifPresent((p_147144_) -> {\n               p_147154_.setBlockAndUpdate(blockpos, p_147144_);\n            });\n            p_147154_.levelEvent(3002, blockpos, -1);\n            return Optional.of(blockpos);\n         }\n      }\n\n      return Optional.empty();\n   }\n\n   public boolean shouldRenderAtSqrDistance(double p_20869_) {\n      double d0 = 64.0D * getViewScale();\n      return p_20869_ < d0 * d0;\n   }\n\n   protected void defineSynchedData() {\n   }\n\n   protected void readAdditionalSaveData(CompoundTag p_20873_) {\n   }\n\n   protected void addAdditionalSaveData(CompoundTag p_20877_) {\n   }\n\n   public int getBlocksSetOnFire() {\n      return this.blocksSetOnFire;\n   }\n\n   public Stream<Entity> getHitEntities() {\n      return this.hitEntities.stream().filter(Entity::isAlive);\n   }\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/storm/StormObject.java",
    "content": "package weather2.weathersystem.storm;\n\nimport com.corosus.coroutil.config.ConfigCoroUtil;\nimport com.corosus.coroutil.util.*;\nimport extendedrenderer.particle.ParticleRegistry;\nimport extendedrenderer.particle.behavior.ParticleBehaviorFog;\nimport extendedrenderer.particle.entity.EntityRotFX;\nimport extendedrenderer.particle.entity.ParticleCrossSection;\nimport extendedrenderer.particle.entity.ParticleCube;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.client.renderer.texture.TextureAtlasSprite;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.core.Holder;\nimport net.minecraft.core.particles.DustParticleOptions;\nimport net.minecraft.core.particles.ParticleTypes;\nimport net.minecraft.nbt.CompoundTag;\nimport net.minecraft.server.level.ServerLevel;\nimport net.minecraft.tags.BlockTags;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.damagesource.DamageSource;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.entity.LivingEntity;\nimport net.minecraft.world.entity.animal.Dolphin;\nimport net.minecraft.world.entity.player.Player;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.biome.Biome;\nimport net.minecraft.world.level.block.Blocks;\nimport net.minecraft.world.level.block.LiquidBlock;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.world.level.levelgen.Heightmap;\nimport net.minecraft.world.phys.AABB;\nimport net.minecraft.world.phys.Vec3;\nimport net.minecraft.world.phys.shapes.Shapes;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport net.minecraftforge.fml.LogicalSide;\nimport net.minecraftforge.fml.util.thread.EffectiveSide;\nimport net.minecraftforge.registries.ForgeRegistries;\nimport weather2.EntityRegistry;\nimport weather2.ServerTickHandler;\nimport weather2.Weather;\nimport weather2.client.SceneEnhancer;\nimport weather2.config.*;\nimport weather2.player.PlayerData;\nimport weather2.util.*;\nimport weather2.weathersystem.WeatherManager;\nimport weather2.weathersystem.WeatherManagerServer;\nimport weather2.weathersystem.tornado.ActiveTornadoConfig;\nimport weather2.weathersystem.tornado.simple.Layer;\nimport weather2.weathersystem.tornado.simple.TornadoFunnelSimple;\n\nimport java.util.*;\n\npublic class StormObject extends WeatherObject {\n\n\t//used on both server and client side, mark things SideOnly where needed\n\t\n\t//size, state\n\t\n\t//should they extend entity?\n\t\n\t//management stuff\n\t\n\tpublic String spawnerUUID = \"\";\n\n\t//newer cloud managing list for more strict render optimized positioning\n\t@OnlyIn(Dist.CLIENT)\n\tpublic HashMap<Integer, EntityRotFX> lookupParticlesCloud;\n\n\t@OnlyIn(Dist.CLIENT)\n\tpublic HashMap<Integer, EntityRotFX> lookupParticlesCloudLower;\n\n\t@OnlyIn(Dist.CLIENT)\n\tpublic HashMap<Integer, EntityRotFX> lookupParticlesFunnel;\n\n\t@OnlyIn(Dist.CLIENT)\n\tpublic List<EntityRotFX> listParticlesCloud;\n\t@OnlyIn(Dist.CLIENT)\n\tpublic List<EntityRotFX> listParticlesGround;\n\t@OnlyIn(Dist.CLIENT)\n\tpublic List<EntityRotFX> listParticlesFunnel;\n\t@OnlyIn(Dist.CLIENT)\n\tpublic List<EntityRotFX> listParticlesDebris;\n\t@OnlyIn(Dist.CLIENT)\n\tpublic ParticleBehaviorFog particleBehaviorFog;\n\t\n\tpublic int sizeMaxFunnelParticles = 600;\n\t\n\t//public WeatherEntityConfig conf = WeatherTypes.weatherEntTypes.get(1);\n\t//this was pulled over from weather1 i believe\n\t//public int curWeatherType = 1; //NEEDS SYNCING\n\t\n\t//basic info\n\tpublic static int static_YPos_layer0 = ConfigMisc.Cloud_Layer0_Height;\n\tpublic static int static_YPos_layer1 = ConfigMisc.Cloud_Layer1_Height;\n\tpublic static int static_YPos_layer2 = ConfigMisc.Cloud_Layer2_Height;\n\tpublic static List<Integer> layers = new ArrayList<Integer>(Arrays.asList(static_YPos_layer0, static_YPos_layer1, static_YPos_layer2));\n\tpublic int layer = 0;\n\t\n\tpublic boolean angleIsOverridden = false;\n\tpublic float angleMovementTornadoOverride = 0;\n\n\tpublic float tempAngleFormingTornado = 0;\n\n\t//growth / progression info\n\t\n\tpublic boolean isGrowing = true;\n\t\n\t//cloud formation data, helps storms\n\tpublic int levelWater = 0; //builds over water and humid biomes, causes rainfall (not technically a storm)\n\tpublic float levelWindMomentum = 0; //high elevation builds this, plains areas lowers it, 0 = no additional speed ontop of global speed\n\tpublic float levelTemperature = 0; //negative for cold, positive for warm, we subtract 0.7 from vanilla values to make forest = 0, plains 0.1, ocean -0.5, etc\n\t//public float levelWindDirectionAdjust = 0; //for persistant direction change i- wait just calculate on the fly based on temperature\n\t\n\tpublic int levelWaterStartRaining = 100;\n\t\n\t//storm data, used when its determined a storm will happen from cloud front collisions\n\tpublic int levelStormIntensityMax = 0; //calculated from colliding warm and cold fronts, used to determine how crazy a storm _will_ get\n\t\n\t//revision, ints for each stage of intensity, and a float for the intensity of THAT current stage\n\tpublic int levelCurIntensityStage = 0; //since we want storms to build up to a climax still, this will start from 0 and peak to levelStormIntensityMax\n\tpublic float levelCurStagesIntensity = 0;\n\t//public boolean isRealStorm = false;\n\tpublic boolean hasStormPeaked = false;\n\t\n\tpublic int maxIntensityStage = STATE_STAGE5;\n\t\n\t//used to mark difference between land and water based storms\n\tpublic int stormType = TYPE_LAND;\n\tpublic static int TYPE_LAND = 0; //for tornados\n\tpublic static int TYPE_WATER = 1; //for tropical cyclones / hurricanes\n\t\n\t//used to mark intensity stages\n\tpublic static int STATE_NORMAL = 0;\n\tpublic static int STATE_THUNDER = 1;\n\tpublic static int STATE_HIGHWIND = 2;\n\tpublic static int STATE_HAIL = 3;\n\tpublic static int STATE_FORMING = 4; //forming tornado for land, for water... stage 0 or something?\n\tpublic static int STATE_STAGE1 = 5; //these are for both tornados (land) and tropical cyclones (water)\n\tpublic static int STATE_STAGE2 = 6;\n\tpublic static int STATE_STAGE3 = 7;\n\tpublic static int STATE_STAGE4 = 8;\n\tpublic static int STATE_STAGE5 = 9; //counts as hurricane for water\n\t\n\t//helper val, adjust with flags method\n\tpublic static float levelStormIntensityFormingStartVal = STATE_FORMING;\n\t\n\t\n\t//spin speed for potential tornado formations, should go up with intensity increase;\n\tpublic double spinSpeed = 0.02D;\n\t\n\t//PENDING REVISION \\\\ - use based on levelStormIntensityCur ???\n\t\n\t//states that combine all lesser states\n\t//public int state = STATE_NORMAL;\n\t\n\t\n\t//used for sure, rain is dependant on water level values\n\tpublic boolean attrib_precipitation = false;\n\tpublic boolean attrib_waterSpout = false;\n\t\n\t//copied from EntTornado\n\t//buildup var - unused in new system currently, but might be needed for touchdown effect\n\t\n\t//unused tornado scale, always 1F\n\tpublic float scale = 1F;\n\tpublic float strength = 100;\n\tpublic int maxHeight = 60;\n\t\n\tpublic int currentTopYBlock = -1;\n\t\n\tpublic TornadoHelper tornadoHelper = new TornadoHelper(this);\n\tprivate TornadoFunnelSimple tornadoFunnelSimple;\n\t\n\t//public Set<ChunkCoordIntPair> doneChunks = new HashSet<ChunkCoordIntPair>();\n\tpublic int updateLCG = (new Random()).nextInt();\n    \n    public float formingStrength = 0; //for transition from 0 (in clouds) to 1 (touch down)\n    \n    public Vec3 posBaseFormationPos = new Vec3(pos.x, pos.y, pos.z); //for formation / touchdown progress, where all the ripping methods scan from\n\n    public boolean naturallySpawned = true;\n\t//to prevent things like it progressing to next stage before weather machine undoes it\n\tpublic boolean weatherMachineControlled = false;\n    public boolean canSnowFromCloudTemperature = false;\n    public boolean alwaysProgresses = false;\n    \n    \n    //to let client know server is raining (since we override client side raining state for render changes)\n    //public boolean overCastModeAndRaining = false;\n    \n    /*@SideOnly(Side.CLIENT)\n    public RenderCubeCloud renderBlock;*/\n    \n    //there is an issue with rainstorms sometimes never going away, this is a patch to mend the underlying issue i cant find yet\n    public long ticksSinceLastPacketReceived = 0;\n\t\n    //public static long lastStormFormed = 0;\n    \n    public boolean canBeDeadly = true;\n\n\t/**\n\t * Populate sky with stormless/cloudless storm objects in order to allow clear skies with current design\n\t */\n\tpublic boolean cloudlessStorm = false;\n\n\t//used to cache a scan for blocks ahead of storm, to move around\n\tpublic float cachedAngleAvoidance = 0;\n\n\tpublic boolean isFirenado = false;\n\n\tpublic List<LivingEntity> listEntitiesUnderClouds = new ArrayList<>();\n\n\tprivate boolean playerControlled = false;\n\tprivate int playerControlledTimeLeft = 20;\n\tprivate boolean baby = false;\n\tprivate boolean pet = false;\n\tprivate boolean petGrabsItems = false;\n\tprivate boolean sharknado = false;\n\n\tprivate boolean configNeedsSync = true;\n\n\tprivate int age;\n\tprivate int ageSinceTornadoTouchdown;\n\n\tprivate boolean isBeingDeflectedCached = true;\n\n\tprivate boolean debugCloudTemperature = false;\n    \n\tpublic StormObject(WeatherManager parManager) {\n\t\tsuper(parManager);\n\t\t\n\t\tpos = new Vec3(0, static_YPos_layer0, 0);\n\t\tmaxSize = ConfigStorm.Storm_MaxRadius;\n\t\t\n\t\tif (parManager.getWorld().isClientSide()) {\n\t\t\tlistParticlesCloud = new ArrayList<>();\n\t\t\tlistParticlesFunnel = new ArrayList<>();\n\t\t\tlistParticlesDebris = new ArrayList<>();\n\t\t\tlistParticlesGround = new ArrayList<>();\n\t\t\tlookupParticlesCloud = new HashMap<>();\n\t\t\tlookupParticlesCloudLower = new HashMap<>();\n\t\t\tlookupParticlesFunnel = new HashMap<>();\n\t\t\t//renderBlock = new RenderCubeCloud();\n\t\t}\n\t}\n\t\n\tpublic void initFirstTime() {\n\t\tsuper.initFirstTime();\n\n\t\tBiome bgb = manager.getWorld().getBiome(WeatherUtilBlock.getPrecipitationHeightSafe(manager.getWorld(), new BlockPos(Mth.floor(pos.x), 0, Mth.floor(pos.z)))).get();\n\n\t\tfloat temp = 1;\n\t\t\n\t\tif (bgb != null) {\n\t\t\t//temp = bgb.getFloatTemperature(new BlockPos(Mth.floor(pos.x), Mth.floor(pos.y), Mth.floor(pos.z)));\n\t\t\ttemp = CoroUtilCompatibility.getAdjustedTemperature(manager.getWorld(), bgb, new BlockPos(Mth.floor(pos.x), Mth.floor(pos.y), Mth.floor(pos.z)));\n\t\t}\n\t\t\n\t\t//initial setting, more apparent than gradual adjustments\n\t\tif (naturallySpawned) {\n\t\t\tlevelTemperature = getTemperatureMCToWeatherSys(temp);\n\t\t\tCULog.dbg(\"init levelTemperature: \" + levelTemperature);\n\t\t}\n\t\t//levelWater = 0;\n\t\tlevelWindMomentum = 0;\n\t\t\n\t\t//Weather.dbg(\"initialize temp to: \" + levelTemperature + \" - biome: \" + bgb.biomeName);\n\t\t\n\t\t\n\t\t \n\t}\n\n\tpublic boolean isCloudlessStorm() {\n\t\treturn cloudlessStorm;\n\t}\n\n\tpublic void setCloudlessStorm(boolean cloudlessStorm) {\n\t\tthis.cloudlessStorm = cloudlessStorm;\n\t}\n\n\tpublic boolean isPrecipitating() {\n\t\treturn attrib_precipitation;\n\t}\n\t\n\tpublic void setPrecipitating(boolean parVal) {\n\t\tattrib_precipitation = parVal;\n\t}\n\t\n\tpublic boolean isRealStorm() {\n\t\treturn levelCurIntensityStage > STATE_NORMAL;\n\t}\n\t\n\tpublic boolean isTornadoFormingOrGreater() {\n\t\treturn (stormType == TYPE_LAND && levelCurIntensityStage >= STATE_FORMING) || isPet();\n\t}\n\t\n\tpublic boolean isCycloneFormingOrGreater() {\n\t\treturn stormType == TYPE_WATER && levelCurIntensityStage >= STATE_FORMING;\n\t}\n\t\n\tpublic boolean isSpinning() {\n\t\treturn levelCurIntensityStage >= STATE_HIGHWIND;\n\t}\n\t\n\tpublic boolean isTropicalCyclone() {\n\t\treturn levelCurIntensityStage >= STATE_STAGE1;\n\t}\n\t\n\tpublic boolean isHurricane() {\n\t\treturn levelCurIntensityStage >= STATE_STAGE5;\n\t}\n\n\t@Override\n\tpublic void read()\n    {\n\t\tsuper.read();\n\t\tnbtSyncFromServer();\n\n\t\tCachedNBTTagCompound var1 = this.getNbtCache();\n\t\t\n\n\t\tangleIsOverridden = var1.getBoolean(\"angleIsOverridden\");\n\t\tangleMovementTornadoOverride = var1.getFloat(\"angleMovementTornadoOverride\");\n\n\t\tspawnerUUID = var1.getString(\"spawnerUUID\");\n    }\n\n    @Override\n\tpublic void write()\n    {\n\t\tsuper.write();\n\t\tnbtSyncForClient();\n\n\t\tCachedNBTTagCompound nbt = this.getNbtCache();\n\t\t\n\n\t\tnbt.putBoolean(\"angleIsOverridden\", angleIsOverridden);\n\t\tnbt.putFloat(\"angleMovementTornadoOverride\", angleMovementTornadoOverride);\n\n\t\tnbt.putString(\"spawnerUUID\", spawnerUUID);\n\n    }\n\t\n\t//receiver method\n\t@Override\n\tpublic void nbtSyncFromServer() {\n\n\t\tCachedNBTTagCompound parNBT = this.getNbtCache();\n\n\t\tboolean testNetworkData = false;\n\t\tif (testNetworkData) {\n\t\t\tSystem.out.println(\"Received payload from server; length=\" + parNBT.getNewNBT().getAllKeys().size());\n\t\t\tIterator iterator = parNBT.getNewNBT().getAllKeys().iterator();\n\t\t\tString keys = \"\";\n\t\t\twhile (iterator.hasNext()) {\n\t\t\t\tkeys = keys.concat((String) iterator.next() + \"; \");\n\t\t\t}\n\t\t\tSystem.out.println(\"Received    \" + keys);\n\t\t}\n\n\t\tsuper.nbtSyncFromServer();\n\n\t\t//state = parNBT.getInt(\"state\");\n\t\t\n\t\t//attrib_tornado_severity = parNBT.getInt(\"attrib_tornado_severity\");\n\t\t\n\t\t//attrib_highwind = parNBT.getBoolean(\"attrib_highwind\");\n\t\t//attrib_tornado = parNBT.getBoolean(\"attrib_tornado\");\n\t\t//attrib_hurricane = parNBT.getBoolean(\"attrib_hurricane\");\n\t\tattrib_precipitation = parNBT.getBoolean(\"attrib_rain\");\n\t\tattrib_waterSpout = parNBT.getBoolean(\"attrib_waterSpout\");\n\t\t\n\t\tcurrentTopYBlock = parNBT.getInt(\"currentTopYBlock\");\n\t\t\n\t\tlevelTemperature = parNBT.getFloat(\"levelTemperature\");\n\t\tlevelWater = parNBT.getInt(\"levelWater\");\n\t\t\n\t\tlayer = parNBT.getInt(\"layer\");\n\t\t\n\t\t//curWeatherType = parNBT.getInt(\"curWeatherType\");\n\t\t\n\t\t//formingStrength = parNBT.getFloat(\"formingStrength\");\n\n\t\tlevelCurIntensityStage = parNBT.getInt(\"levelCurIntensityStage\");\n\t\tlevelStormIntensityMax = parNBT.getInt(\"levelStormIntensityMax\");\n\t\tlevelCurStagesIntensity = parNBT.getFloat(\"levelCurStagesIntensity\");\n\t\tstormType = parNBT.getInt(\"stormType\");\n\t\t\n\t\thasStormPeaked = parNBT.getBoolean(\"hasStormPeaked\");\n\t\t\n\t\t//overCastModeAndRaining = parNBT.getBoolean(\"overCastModeAndRaining\");\n\t\t\n\t\tisDead = parNBT.getBoolean(\"isDead\");\n\n\t\tcloudlessStorm = parNBT.getBoolean(\"cloudlessStorm\");\n\n\t\tisFirenado = parNBT.getBoolean(\"isFirenado\");\n\t\t\n\t\tticksSinceLastPacketReceived = 0;//manager.getWorld().getGameTime();\n\n\t\tweatherMachineControlled = parNBT.getBoolean(\"weatherMachineControlled\");\n\n\t\t//TODO: sync tornado config for size etc\n\t\tString prefix = \"tornadoFunnelData_layer_\";\n\t\tif (parNBT.contains(prefix + \"count\") && tornadoFunnelSimple != null) {\n\t\t\tint count = parNBT.getInt(prefix + \"count\");\n\t\t\tfor (int i = 0; i < tornadoFunnelSimple.listLayers.size(); i++) {\n\t\t\t\tLayer layer = tornadoFunnelSimple.listLayers.get(i);\n\t\t\t\tVec3 newPos = new Vec3(parNBT.getDouble(prefix + i + \"_posX\"), parNBT.getDouble(prefix + i + \"_posY\"), parNBT.getDouble(prefix + i + \"_posZ\"));\n\t\t\t\tlayer.setPos(newPos);\n\t\t\t}\n\t\t}\n\n\t\tplayerControlled = parNBT.getBoolean(\"playerControlled\");\n\t\tspawnerUUID = parNBT.getString(\"spawnerUUID\");\n\n\t\tif (tornadoFunnelSimple != null && parNBT.contains(\"config\")) {\n\t\t\ttornadoFunnelSimple.setConfig(ActiveTornadoConfig.deserialize(parNBT.get(\"config\")));\n\t\t}\n\n\t\tbaby = parNBT.getBoolean(\"baby\");\n\t\tpet = parNBT.getBoolean(\"pet\");\n\t\tpetGrabsItems = parNBT.getBoolean(\"petGrabsItems\");\n\t\tsharknado = parNBT.getBoolean(\"sharknado\");\n\n\t\tif (posBaseFormationPos == Vec3.ZERO) {\n\t\t\tposBaseFormationPos = new Vec3(parNBT.getDouble(\"posBaseFormationPosX\"), parNBT.getDouble(\"posBaseFormationPosX\"), parNBT.getDouble(\"posBaseFormationPosX\"));\n\t\t}\n\n\t\tisBeingDeflectedCached = parNBT.getBoolean(\"isBeingDeflectedCached\");\n\t}\n\t\n\t//compose nbt data for packet (and serialization in future)\n\t@Override\n\tpublic void nbtSyncForClient() {\n\t\tsuper.nbtSyncForClient();\n\n\t\tCachedNBTTagCompound data = this.getNbtCache();\n\t\t\n\t\t//data.putInt(\"state\", state);\n\t\t\n\t\t//data.putInt(\"attrib_tornado_severity\", attrib_tornado_severity);\n\t\t\n\t\t//data.putBoolean(\"attrib_highwind\", attrib_highwind);\n\t\t//data.putBoolean(\"attrib_tornado\", attrib_tornado);\n\t\t//data.putBoolean(\"attrib_hurricane\", attrib_hurricane);\n\t\tif (attrib_precipitation) {\n\t\t\t//CULog.dbg(\"syncing rain state true: \" + pos);\n\t\t}\n\t\tdata.putBoolean(\"attrib_rain\", attrib_precipitation);\n\t\tdata.putBoolean(\"attrib_waterSpout\", attrib_waterSpout);\n\t\t\n\t\tdata.putInt(\"currentTopYBlock\", currentTopYBlock);\n\t\t\n\t\tdata.putFloat(\"levelTemperature\", levelTemperature);\n\t\tdata.putInt(\"levelWater\", levelWater);\n\t\t\n\t\tdata.putInt(\"layer\", layer);\n\t\t\n\t\t//data.putInt(\"curWeatherType\", curWeatherType);\n\t\t\n\t\t//data.putFloat(\"formingStrength\", formingStrength);\n\t\t\n\t\tdata.putInt(\"levelCurIntensityStage\", levelCurIntensityStage);\n\t\tdata.putFloat(\"levelCurStagesIntensity\", levelCurStagesIntensity);\n\t\tdata.putFloat(\"levelStormIntensityMax\", levelStormIntensityMax);\n\t\tdata.putInt(\"stormType\", stormType);\n\t\t\n\t\tdata.putBoolean(\"hasStormPeaked\", hasStormPeaked);\n\t\t\n\t\t//data.putBoolean(\"overCastModeAndRaining\", overCastModeAndRaining);\n\t\t\n\t\tdata.putBoolean(\"isDead\", isDead);\n\n\t\tdata.putBoolean(\"cloudlessStorm\", cloudlessStorm);\n\n\n\t\tdata.putBoolean(\"isFirenado\", isFirenado);\n\n\t\tdata.putBoolean(\"weatherMachineControlled\", weatherMachineControlled);\n\n\t\t//sync the data heavy tornado funnel every 5 seconds\n\t\tif (manager != null && tornadoFunnelSimple != null && (manager.getWorld().getGameTime() % 100 == 0 || configNeedsSync)) {\n\t\t\tString prefix = \"tornadoFunnelData_layer_\";\n\t\t\tdata.putInt(prefix + \"count\", tornadoFunnelSimple.listLayers.size());\n\t\t\tfor (int i = 0; i < tornadoFunnelSimple.listLayers.size(); i++) {\n\t\t\t\tVec3 layerPos = tornadoFunnelSimple.listLayers.get(i).getPos();\n\t\t\t\tdata.putDouble(prefix + i + \"_posX\", layerPos.x);\n\t\t\t\tdata.putDouble(prefix + i + \"_posY\", layerPos.y);\n\t\t\t\tdata.putDouble(prefix + i + \"_posZ\", layerPos.z);\n\t\t\t}\n\t\t}\n\n\t\tdata.putBoolean(\"playerControlled\", playerControlled);\n\t\tdata.putString(\"spawnerUUID\", spawnerUUID);\n\n\t\tif (configNeedsSync && tornadoFunnelSimple != null) {\n\t\t\tdata.put(\"config\", tornadoFunnelSimple.getConfig().serialize());\n\n\t\t\tconfigNeedsSync = false;\n\t\t}\n\n\t\tdata.putBoolean(\"baby\", baby);\n\t\tdata.putBoolean(\"pet\", pet);\n\t\tdata.putBoolean(\"petGrabsItems\", petGrabsItems);\n\t\tdata.putBoolean(\"sharknado\", sharknado);\n\n\t\t//do a force sync every 30 seconds, solves issues like first data sometimes not coming in right: eg top y block if height never changes in flat world\n\t\tif (manager != null && (manager.getWorld().getGameTime()) % (20*30) == 0) {\n\t\t\tdata.setUpdateForced(true);\n\t\t} else {\n\t\t\tdata.setUpdateForced(false);\n\t\t}\n\n\t\tdata.putDouble(\"posBaseFormationPosX\", posBaseFormationPos.x);\n\t\tdata.putDouble(\"posBaseFormationPosY\", posBaseFormationPos.y);\n\t\tdata.putDouble(\"posBaseFormationPosZ\", posBaseFormationPos.z);\n\n\t\tdata.putBoolean(\"isBeingDeflectedCached\", isBeingDeflectedCached);\n\n\t}\n\t\n\tpublic CompoundTag nbtForIMC() {\n\t\t//we basically need all the same data minus a few soooo whatever\n\t\tnbtSyncForClient();\n\t\treturn getNbtCache().getNewNBT();\n\t}\n\t\n\t@OnlyIn(Dist.CLIENT)\n\tpublic void tickRender(float partialTick) {\n\t\tsuper.tickRender(partialTick);\n\n\n\n\t\t//renderBlock.doRenderClouds(this, 0, 0, 0, 0, partialTick);\n\t\t/*if (layer == 1) {\n\t\t\trenderBlock.doRenderClouds(this, pos.x, pos.y, pos.z, 0, partialTick);\n\t\t}*/\n\n\t\t//TODO: consider only putting funnel in this method since its the fast part, the rest might be slow enough to only need to do per gametick\n\n\t\tif (!WeatherUtil.isPaused()) {\n\n\t\t\tint count = 8+1;\n\n\n\t\t\t//ParticleBehaviorFog.newCloudWay = true;\n\n\t\t\tIterator<Map.Entry<Integer, EntityRotFX>> it = lookupParticlesCloud.entrySet().iterator();\n\t\t\twhile (it.hasNext()) {\n\t\t\t\tMap.Entry<Integer, EntityRotFX> entry = it.next();\n\t\t\t\tEntityRotFX ent = entry.getValue();\n\t\t\t\tif (!ent.isAlive()) {\n\t\t\t\t\tit.remove();\n\t\t\t\t} else {\n\t\t\t\t\tint i = entry.getKey();\n\t\t\t\t\tVec3 tryPos = null;\n\t\t\t\t\tdouble spawnRad = 120;//(ticksExisted % 100) + 10;\n\t\t\t\t\tdouble speed = 2D / (spawnRad);\n\t\t\t\t\tif (isSpinning()) {\n\t\t\t\t\t\tspeed = 50D / (spawnRad);\n\t\t\t\t\t}\n\t\t\t\t\tent.rotationSpeedAroundCenter = (float)speed;\n\t\t\t\t\tif (i == 0) {\n\t\t\t\t\t\ttryPos = new Vec3(pos.x, layers.get(layer), pos.z);\n\t\t\t\t\t\tent.rotationYaw = ent.rotationAroundCenter;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdouble rad = Math.toRadians(ent.rotationAroundCenter - ent.rotationSpeedAroundCenter + (ent.rotationSpeedAroundCenter * partialTick));\n\t\t\t\t\t\tdouble x = -Math.sin(rad) * spawnRad;\n\t\t\t\t\t\tdouble z = Math.cos(rad) * spawnRad;\n\t\t\t\t\t\ttryPos = new Vec3(pos.x + x, layers.get(layer), pos.z + z);\n\n\t\t\t\t\t\tdouble var16 = this.pos.x - ent.getPosX();\n\t\t\t\t\t\tdouble var18 = this.pos.z - ent.getPosZ();\n\t\t\t\t\t\tent.rotationYaw = (float)(Math.atan2(var18, var16) * 180.0D / Math.PI) - 90.0F;\n\t\t\t\t\t\t//ent.rotationPitch = -20F;// - (ent.getEntityId() % 10);\n\n\t\t\t\t\t\t//ent.setAge(100);\n\n\n\t\t\t\t\t}\n\t\t\t\t\tent.setPosition(tryPos.x, tryPos.y, tryPos.z);\n\t\t\t\t}\n\t\t\t}\n\n\n\n\t\t\tcount = 16*2;\n\n\t\t\tit = lookupParticlesCloudLower.entrySet().iterator();\n\t\t\twhile (it.hasNext()) {\n\t\t\t\tMap.Entry<Integer, EntityRotFX> entry = it.next();\n\t\t\t\tEntityRotFX ent = entry.getValue();\n\t\t\t\tif (!ent.isAlive()) {\n\t\t\t\t\tit.remove();\n\t\t\t\t} else {\n\t\t\t\t\tint i = entry.getKey();\n\t\t\t\t\tVec3 tryPos = null;\n\n\t\t\t\t\tent.setScale(800 * 0.15F);\n\n\t\t\t\t\tdouble countPerLayer = 16;\n\t\t\t\t\tdouble rotPos = i % 16;\n\t\t\t\t\tint layerRot = i / 16;\n\t\t\t\t\tdouble spawnRad = 80;\n\t\t\t\t\tif (layerRot == 1) {\n\t\t\t\t\t\tspawnRad = 60;\n\t\t\t\t\t\tent.setScale(600 * 0.15F);\n\t\t\t\t\t}\n\t\t\t\t\tdouble speed = 50D / (spawnRad * 2D);\n\n\t\t\t\t\tent.rotationSpeedAroundCenter = (float)speed;\n\t\t\t\t\tdouble rad = Math.toRadians(ent.rotationAroundCenter - ent.rotationSpeedAroundCenter + (ent.rotationSpeedAroundCenter * partialTick));\n\t\t\t\t\tdouble x = -Math.sin(rad) * spawnRad;\n\t\t\t\t\tdouble z = Math.cos(rad) * spawnRad;\n\t\t\t\t\ttryPos = new Vec3(pos.x + x, layers.get(layer) - 20, pos.z + z);\n\n\t\t\t\t\tent.setPosition(tryPos.x, tryPos.y, tryPos.z);\n\n\t\t\t\t\tdouble var16 = this.pos.x - ent.getPosX();\n\t\t\t\t\tdouble var18 = this.pos.z - ent.getPosZ();\n\t\t\t\t\tent.rotationYaw = (float)(Math.atan2(var18, var16) * 180.0D / Math.PI) - 90.0F;\n\t\t\t\t\tent.rotationPitch = -20F;// - (ent.getEntityId() % 10);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic void setupTornado() {\n\t\tActiveTornadoConfig activeTornadoConfig;\n\t\tif (isPet()) {\n\t\t\tactiveTornadoConfig = new ActiveTornadoConfig()\n\t\t\t\t\t.setHeight(1.7F)\n\t\t\t\t\t//this is overwritten in TornadoFunnelSimple\n\t\t\t\t\t.setRadiusOfBase(0.5F)\n\t\t\t\t\t.setSpinSpeed(360F / 20F)\n\t\t\t\t\t.setRadiusIncreasePerLayer(0.02F)\n\t\t\t\t\t/*.setRadiusIncreasePerLayer(0.08F)*/\n\t\t\t\t\t.setEntityPullDistXZ(2)\n\t\t\t\t\t.setEntityPullDistXZForY(2);\n\t\t} else if (isBaby()) {\n\t\t\tactiveTornadoConfig = new ActiveTornadoConfig()\n\t\t\t\t\t.setHeight(20)\n\t\t\t\t\t//this is overwritten in TornadoFunnelSimple\n\t\t\t\t\t.setRadiusOfBase(5F + 0F)\n\t\t\t\t\t.setSpinSpeed(360F / 20F)\n\t\t\t\t\t.setRadiusIncreasePerLayer(0.2F)\n\t\t\t\t\t.setEntityPullDistXZ(20)\n\t\t\t\t\t.setEntityPullDistXZForY(5);\n\t\t} else {\n\t\t\tactiveTornadoConfig = new ActiveTornadoConfig()\n\t\t\t\t\t.setHeight(150)\n\t\t\t\t\t//this is overwritten in TornadoFunnelSimple\n\t\t\t\t\t.setRadiusOfBase(5F + 5F)\n\t\t\t\t\t.setSpinSpeed(360F / 20F)\n\t\t\t\t\t.setRadiusIncreasePerLayer(0.2F)\n\t\t\t\t\t.setEntityPullDistXZ(120)\n\t\t\t\t\t.setEntityPullDistXZForY(90);\n\t\t}\n\t\ttornadoFunnelSimple = new TornadoFunnelSimple(activeTornadoConfig, this);\n\t}\n\t\n\tpublic void tick() {\n\t\tsuper.tick();\n\t\tage++;\n\t\tif (levelCurIntensityStage >= STATE_STAGE1) ageSinceTornadoTouchdown++;\n\t\t//Weather.dbg(\"ticking storm \" + ID + \" - manager: \" + manager);\n\n\t\t//CULog.dbg(\"gametime: \" + manager.getWorld().getGameTime());\n\t\t//System.out.println(\"1gametime: \" + manager.getWorld().getGameTime());\n\t\t\n\t\t//adjust posGround to be pos with the ground Y pos for convinient usage\n\t\tif (!playerControlled) {\n\t\t\tposGround = new Vec3(pos.x, currentTopYBlock, pos.z);\n\t\t} else {\n\t\t\tposGround = new Vec3(pos.x, pos.y, pos.z);\n\t\t}\n\t\t\n\t\tLogicalSide side = EffectiveSide.get();\n\t\tif (side == LogicalSide.CLIENT) {\n\t\t\tif (!WeatherUtil.isPaused()) {\n\n\t\t\t\t//CULog.dbg(\"levelTemperature: \" + levelTemperature);\n\n\t\t\t\tif (isTornadoFormingOrGreater() || isCycloneFormingOrGreater()) {\n\t\t\t\t\tsetAndUpdateTornado();\n\n\t\t\t\t\ttornadoFunnelSimple.tick();\n\t\t\t\t} else {\n\t\t\t\t\tif (tornadoFunnelSimple != null && tornadoFunnelSimple.listLayers.size() > 0) {\n\t\t\t\t\t\ttornadoFunnelSimple.fadeOut();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tticksSinceLastPacketReceived++;\n\t\t\t\t\n\t\t\t\t//if (layer == 0) {\n\t\t\t\t\ttickClient();\n\t\t\t\t//}\n\n\t\t\t\tif (isTornadoFormingOrGreater() || isCycloneFormingOrGreater()) {\n\t\t\t\t\tsetAndUpdateTornado();\n\t\t\t\t\tif (!isBeingDeflectedCached()) {\n\t\t\t\t\t\ttornadoHelper.tick(manager.getWorld());\n\t\t\t\t\t}\n\n\t\t\t\t\ttornadoFunnelSimple.tickClient();\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tif (levelCurIntensityStage >= STATE_HIGHWIND) {\n\t\t\t\t\tif (manager.getWorld().isClientSide()) {\n\t\t\t\t\t\ttornadoHelper.soundUpdates(true, isTornadoFormingOrGreater() || isCycloneFormingOrGreater());\n\t\t\t        }\n\t\t\t\t}\n\n\t\t\t\ttickMovementClient();\n\n\t\t\t\tif (layer == 0) {\n\t\t\t\t\t//sync X Y Z, Y gets changed below\n\t\t\t\t\tposBaseFormationPos = calculateBaseFormationPos();\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\n\t\t\t/*if (ConfigCoroUtil.useLoggingDebug) {\n\t\t\t\t((ServerLevel) this.manager.getWorld()).sendParticles(ParticleTypes.HEART, this.pos.x, 200, this.pos.z, 10, 0.3D, 0D, 0.3D, 1D);\n\t\t\t}*/\n\n\t\t\tif (isTornadoFormingOrGreater() || isCycloneFormingOrGreater()) {\n\t\t\t\tsetAndUpdateTornado();\n\n\t\t\t\ttornadoFunnelSimple.tick();\n\t\t\t}\n\n\t\t\tif (isCloudlessStorm()) {\n\t\t\t\tif (ConfigMisc.overcastMode && manager.getWorld().isRaining()) {\n\t\t\t\t\tthis.setCloudlessStorm(false);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (isTornadoFormingOrGreater() || isCycloneFormingOrGreater()) {\n\t\t\t\tif (!isBeingDeflectedCached()) {\n\t\t\t\t\ttornadoHelper.tick(manager.getWorld());\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (levelCurIntensityStage >= STATE_HIGHWIND) {\n\t\t\t\tif (manager.getWorld().isClientSide()) {\n\t\t\t\t\ttornadoHelper.soundUpdates(true, isTornadoFormingOrGreater() || isCycloneFormingOrGreater());\n\t\t        }\n\t\t\t}\n\n\t\t\t//debug \\\\\n\n\t\t\t//maxSize = 200;\n\t\t\t//isGrowing = true;\n\t\t\t\n\t\t\t/*maxSize = 200;\n\t\t\t//size = maxSize;\n\t\t\tisGrowing = true;\n\t\t\t//state = STATE_HAIL;\n\t\t\tstate = STATE_NORMAL;\n\t\t\tattrib_hurricane = false;\n\t\t\tattrib_tornado = true;\n\t\t\tattrib_tornado = false;\n\t\t\tattrib_highwind = false;\n\t\t\tattrib_tornado_severity = 0;*/\n\t\t\t//attrib_tornado_severity = ATTRIB_F1;\n\t\t\t//debug //\n\n\n\n\t\t\ttickMovement();\n\n\t\t\t//System.out.println(\"cloud motion: \" + motion + \" wind angle: \" + angle);\n\n\t\t\tif (layer == 0) {\n\t\t\t\tif (!isCloudlessStorm()) {\n\t\t\t\t\ttickWeatherEvents();\n\t\t\t\t\ttickProgression();\n\t\t\t\t\t//tickSnowFall();\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t//make layer 1 max size for visuals\n\t\t\t\tsize = maxSize;\n\t\t\t}\n\n\t\t\tif (layer == 0) {\n\t\t\t\t//sync X Y Z, Y gets changed below\n\t\t\t\tposBaseFormationPos = calculateBaseFormationPos();\n\t\t\t}\n\t\t\t\n\t\t\t//overCastModeAndRaining = ConfigMisc.overcastMode && manager.getWorld().isRaining();\n\t        \n\t\t}\n\t\t\n\t}\n\n\tpublic Vec3 calculateBaseFormationPos() {\n\t\tVec3 calculatedPos = new Vec3(pos.x, pos.y, pos.z);\n\n\t\tif (levelCurIntensityStage >= StormObject.levelStormIntensityFormingStartVal) {\n\t\t\tif (levelCurIntensityStage >= StormObject.levelStormIntensityFormingStartVal + 1) {\n\t\t\t\tformingStrength = 1;\n\t\t\t\tcalculatedPos = new Vec3(calculatedPos.x, posGround.y, calculatedPos.z);\n\t\t\t} else {\n\n\t\t\t\t//make it so storms touchdown at 0.5F intensity instead of 1 then instantly start going back up, keeps them down for a full 1F worth of intensity val\n\t\t\t\tfloat intensityAdj = Math.min(1F, levelCurStagesIntensity * 2F);\n\n\t\t\t\t//shouldnt this just be intensityAdj?\n\t\t\t\tfloat val = (levelCurIntensityStage + intensityAdj) - StormObject.levelStormIntensityFormingStartVal;\n\t\t\t\tformingStrength = val;\n\t\t\t\tdouble yDiff = pos.y - posGround.y;\n\t\t\t\tcalculatedPos = new Vec3(calculatedPos.x, pos.y - (yDiff * formingStrength), calculatedPos.z);\n\t\t\t}\n\t\t} else {\n\t\t\tif (levelCurIntensityStage == STATE_HIGHWIND) {\n\t\t\t\tformingStrength = 1;\n\t\t\t\tcalculatedPos = new Vec3(calculatedPos.x, posGround.y, calculatedPos.z);\n\t\t\t} else {\n\t\t\t\tformingStrength = 0;\n\t\t\t\tcalculatedPos = new Vec3(calculatedPos.x, pos.y, calculatedPos.z);\n\t\t\t}\n\t\t}\n\t\treturn calculatedPos;\n\t}\n\t\n\tpublic void tickMovement() {\n\n\t\tdouble distToTarget = 1F;\n\t\tif (playerControlled) {\n\t\t\tPlayer entP = getPlayer();\n\t\t\tif (entP != null) {\n\t\t\t\tif (!isPet()) {\n\t\t\t\t\t//aimStormAtClosestOrProvidedPlayer(entP);\n\n\t\t\t\t\tthis.pos = new Vec3(entP.position().x, entP.position().y, entP.position().z);\n\t\t\t\t\tthis.posGround = new Vec3(entP.position().x, entP.position().y, entP.position().z);\n\t\t\t\t} else {\n\t\t\t\t\tdistToTarget = entP.position().distanceTo(posGround);\n\t\t\t\t\tif (distToTarget > 5F) {\n\t\t\t\t\t\taimStormAtClosestOrProvidedPlayer(entP);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t//storm movement via wind\n\t\tfloat angle = getAdjustedAngle();\n\t\tRandom rand = new Random();\n\n\t\tif (angleIsOverridden) {\n\t\t\tangle = angleMovementTornadoOverride;\n\t\t\t//debug\n\t\t\t/*if (manager.getWorld().getGameTime() % 20 == 0) {\n\t\t\t\tEntityPlayer entP = manager.getWorld().getClosestPlayer(pos.x, pos.y, pos.z, -1);\n\t\t\t\tif (entP != null) {\n\n\t\t\t\t\t//even more debug, heat seak test\n\t\t\t\t\t//Random rand = new Random();\n\t\t\t\t\tdouble var11 = entP.posX - pos.x;\n\t\t            double var15 = entP.posZ - pos.z;\n\t\t            float yaw = -((float)Math.atan2(var11, var15)) * 180.0F / (float)Math.PI;\n\t\t            //weather override!\n\t\t            //yaw = weatherMan.wind.direction;\n\t\t            //int size = ConfigMisc.Storm_Tornado_aimAtPlayerAngleVariance;\n\t\t            //yaw += rand.nextInt(size) - (size / 2);\n\n\t\t\t\t\tangleMovementTornadoOverride = yaw;\n\n\t\t\t\t\tWeather.dbg(\"angle override: \" + angle + \" - dist from player: \" + entP.getDistance(pos.x, pos.y, pos.z));\n\t\t\t\t}\n\n\t\t\t}*/\n\t\t}\n\n\t\tif (levelCurIntensityStage == STATE_FORMING) {\n\t\t\tangle = tempAngleFormingTornado;\n\n\t\t\t//if its an F0 forming, make it all over the place, adding on top of cloud direction each tick\n\t\t\tangle += (rand.nextFloat() - rand.nextFloat()) * 30F;\n\t\t}\n\n\t\t//despite overridden angle, still avoid obstacles\n\n\t\t//slight randomness to angle\n\t\tangle += (rand.nextFloat() - rand.nextFloat()) * 0.15F;\n\n\t\t//avoid large obstacles\n\t\tdouble scanDist = 50;\n\t\tdouble scanX = this.pos.x + (-Math.sin(Math.toRadians(angle)) * scanDist);\n\t\tdouble scanZ = this.pos.z + (Math.cos(Math.toRadians(angle)) * scanDist);\n\n\t\tint height = WeatherUtilBlock.getPrecipitationHeightSafe(this.manager.getWorld(), CoroUtilBlock.blockPos(scanX, 0, scanZ)).getY();\n\n\t\tif (this.pos.y < height) {\n\t\t\tfloat angleAdj = 45;\n\t\t\tif (this.ID % 2 == 0) {\n\t\t\t\tangleAdj = -45;\n\t\t\t}\n\t\t\tangle += angleAdj;\n\t\t}\n\n\t\t//update new angle back to temp forming tornado angle\n\t\ttempAngleFormingTornado = angle;\n\t\t\n\t\t//Weather.dbg(\"cur angle: \" + angle);\n\t\t\n\t\tdouble vecX = -Math.sin(Math.toRadians(angle));\n\t\tdouble vecZ = Math.cos(Math.toRadians(angle));\n\t\t\n\t\tfloat cloudSpeedAmp = 0.2F;\n\n\t\tboolean love_tropics_tweaks = Weather.isLoveTropicsInstalled();\n\t\t//tweaking for lt\n\t\tif (love_tropics_tweaks) cloudSpeedAmp = 3F;\n\t\t\n\t\t\n\t\t\n\t\tfloat finalSpeed = getAdjustedSpeed() * cloudSpeedAmp;\n\n\t\tif (levelCurIntensityStage == STATE_FORMING) {\n\t\t\tfinalSpeed = 0.2F;\n\t\t} else if (levelCurIntensityStage >= STATE_THUNDER) {\n\t\t\tfinalSpeed = 0.15F;\n\t\t}\n\t\t\n\t\tif (!love_tropics_tweaks && levelCurIntensityStage >= levelStormIntensityFormingStartVal) {\n\t\t\tfinalSpeed /= ((float)(levelCurIntensityStage-levelStormIntensityFormingStartVal+1F));\n\t\t}\n\t\t\n\t\tif (finalSpeed < 0.03F) {\n\t\t\tfinalSpeed = 0.03F;\n\t\t}\n\t\t\n\t\tif (!love_tropics_tweaks && finalSpeed > 0.3F) {\n\t\t\tfinalSpeed = 0.3F;\n\t\t}\n\n\t\tif (levelCurIntensityStage == STATE_FORMING) {\n\t\t\tfinalSpeed = 0.3F;\n\t\t}\n\n\t\tif (love_tropics_tweaks) {\n\t\t\tfinalSpeed = 0.1F;\n\t\t\t//finalSpeed = 0F;\n\t\t}\n\n\t\tif (playerControlled) {\n\t\t\tfinalSpeed = 0.5F;\n\t\t\tPlayer player = getPlayer();\n\t\t\tif (player != null) {\n\t\t\t\tif (posGround.distanceTo(player.position()) > 30) {\n\t\t\t\t\tpos = new Vec3(player.position().x, player.position().y, player.position().z);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (isPet()) {\n\t\t\tfinalSpeed = 0.05F;\n\t\t\tif (distToTarget > 5.5F) {\n\t\t\t\tfinalSpeed = 0.5F;\n\t\t\t}\n\t\t}\n\n\t\tif (isSharknado()) {\n\t\t\tfinalSpeed = 0.1F;\n\t\t}\n\t\t\n\t\tif (manager.getWorld().getGameTime() % 100 == 0 && levelCurIntensityStage >= STATE_FORMING) {\n\t\t\t\n\t\t\t//finalSpeed = 0.5F;\n\t\t\t\n\t\t\t//Weather.dbg(\"storm ID: \" + this.ID + \", stage: \" + levelCurIntensityStage + \", storm speed: \" + finalSpeed);\n\t\t}\n\n\t\t//push tornado away faster if being deflected\n\t\tif (isBeingDeflectedCached()) {\n\t\t\tfinalSpeed *= 5;\n\t\t}\n\n\t\tboolean testing = false;\n\t\tif (testing) {\n\t\t\tfinalSpeed = 0.5F;\n\t\t}\n\n\t\tif (!weatherMachineControlled) {\n\t\t\tmotion = new Vec3(vecX * finalSpeed, 0, vecZ * finalSpeed);\n\n\t\t\tdouble max = 0.2D;\n\t\t\t//max speed\n\n\t\t\t/*if (motion.x < -max) motion.x = -max;\n\t\t\tif (motion.x > max) motion.x = max;\n\t\t\tif (motion.z < -max) motion.z = -max;\n\t\t\tif (motion.z > max) motion.z = max;*/\n\n\t\t\t//actually move storm\n\t\t\tpos = pos.add(motion);\n\t\t}\n\n\t\tif (levelCurIntensityStage >= STATE_FORMING) {\n\t\t\tOptional<BlockPos> optional = ((WeatherManagerServer) manager).findWeatherDeflector(CoroUtilBlock.blockPos(posGround), 128);\n\t\t\tif (optional.isPresent()) {\n\t\t\t\tisBeingDeflectedCached = true;\n\t\t\t\t//CULog.dbg(\"optional.get(): \" + optional.get());\n\t\t\t\taimAwayFromCoords(new Vec3(optional.get().getX(), optional.get().getY(), optional.get().getZ()));\n\t\t\t\t((ServerLevel) this.manager.getWorld()).sendParticles(DustParticleOptions.REDSTONE, optional.get().getX() + 0.5D, optional.get().getY() + 0.5D, optional.get().getZ() + 0.5D, 1, 0.3D, 0D, 0.3D, 1D);\n\t\t\t} else {\n\t\t\t\tisBeingDeflectedCached = false;\n\t\t\t}\n\t\t} else {\n\t\t\tisBeingDeflectedCached = false;\n\t\t}\n\n\t}\n\n\tpublic void tickMovementClient() {\n\t\tif (!weatherMachineControlled) {\n\t\t\tpos = pos.add(motion);\n\t\t}\n\t}\n\t\n\tpublic void tickWeatherEvents() {\n\t\tRandom rand = new Random();\n\t\tLevel world = manager.getWorld();\n\n\t\t//if (world.getGameTime() % 20 == 0){\n\t\t\tcurrentTopYBlock = calculateTopYBlock();\n\t\t//}\n\n\t\t//Weather.dbg(\"currentTopYBlock: \" + currentTopYBlock);\n\t\tif (levelCurIntensityStage >= STATE_THUNDER && !isBaby() && !isPet()) {\n\t\t\tif (rand.nextInt((int)Math.max(1, ConfigStorm.Storm_LightningStrikeBaseValueOddsTo1 - (levelCurIntensityStage * 10))) == 0) {\n\t\t\t\tint x = (int) Math.floor(pos.x + rand.nextInt(size) - rand.nextInt(size));\n\t\t\t\tint z = (int) Math.floor(pos.z + rand.nextInt(size) - rand.nextInt(size));\n\t\t\t\tif (world.isLoaded(new BlockPos(x, 0, z))) {\n\t\t\t\t\tint y = world.getHeight(Heightmap.Types.MOTION_BLOCKING, x, z);\n\t\t\t\t\tif (world instanceof ServerLevel) {\n\t\t\t\t\t\tOptional<BlockPos> optional = ((ServerLevel)world).findLightningRod(new BlockPos(x, y, z));\n\t\t\t\t\t\tif (optional.isPresent()) {\n\t\t\t\t\t\t\tx = optional.get().getX();\n\t\t\t\t\t\t\ty = optional.get().getY();\n\t\t\t\t\t\t\tz = optional.get().getZ();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tLightningBoltWeatherNew ent = new LightningBoltWeatherNew(EntityRegistry.LIGHTNING_BOLT.get(), world);\n\t\t\t\t\tent.setPos(x + 0.5, y, z + 0.5);\n\t\t\t\t\taddWeatherEffectLightning(ent, false);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\t//dont forget, this doesnt account for storm size, so small storms have high concentration of hail, as it grows, it appears to lessen in rate\n\t\tif (isPrecipitating() && levelCurIntensityStage == STATE_HAIL && stormType == TYPE_LAND) {\n\t\t\t//if (rand.nextInt(1) == 0) {\n\t\t\tfor (int i = 0; i < Math.max(1, ConfigStorm.Storm_HailPerTick * (size/maxSize)); i++) {\n\t\t\t\tint x = (int) Math.floor(pos.x + rand.nextInt(size) - rand.nextInt(size));\n\t\t\t\tint z = (int) Math.floor(pos.z + rand.nextInt(size) - rand.nextInt(size));\n\t\t\t\tif (world.isLoaded(new BlockPos(x, static_YPos_layer0, z)) && (world.getNearestPlayer(x, 50, z, 80, false) != null)) {\n\t\t\t\t\t//int y = world.getPrecipitationHeight(x, z);\n\t\t\t\t\t//if (world.canLightningStrikeAt(x, y, z)) {\n\t\t\t\t\t//TODO: 1.14 uncomment\n\t\t\t\t\t/*EntityIceBall hail = new EntityIceBall(world);\n\t\t\t\t\thail.setPosition(x, layers.get(layer), z);\n\t\t\t\t\tworld.addEntity(hail);*/\n\t\t\t\t\t//world.addWeatherEffect(new LightningBoltWeather(world, (double)x, (double)y, (double)z));\n\t\t\t\t\t//}\n\t\t\t\t\t\n\t\t\t\t\t//System.out.println(\"spawned hail: \" );\n\t\t\t\t} else {\n\t\t\t\t\t//System.out.println(\"nope\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ttrackAndExtinguishEntities();\n\t}\n\n\tpublic void trackAndExtinguishEntities() {\n\n\t\tif (ConfigStorm.Storm_Rain_TrackAndExtinguishEntitiesRate <= 0) return;\n\n\t\tif (isPrecipitating()) {\n\n\t\t\t//efficient caching\n\t\t\tif ((manager.getWorld().getGameTime() + (ID * 20)) % ConfigStorm.Storm_Rain_TrackAndExtinguishEntitiesRate == 0) {\n\t\t\t\tlistEntitiesUnderClouds.clear();\n\t\t\t\tBlockPos posBP = CoroUtilBlock.blockPos(posGround.x, posGround.y, posGround.z);\n\t\t\t\tList<LivingEntity> listEnts = manager.getWorld().getEntitiesOfClass(LivingEntity.class, new AABB(posBP).inflate(size));\n\t\t\t\tfor (LivingEntity ent : listEnts) {\n\t\t\t\t\tif (ent.level().canSeeSky(ent.blockPosition())) {\n\t\t\t\t\t\tlistEntitiesUnderClouds.add(ent);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (LivingEntity ent : listEntitiesUnderClouds) {\n\t\t\t\tif (!isFirenado) {\n\t\t\t\t\tent.clearFire();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t\n\tpublic void tickProgression() {\n\t\tLevel world = manager.getWorld();\n\t\t\n\t\t//storm progression, heavy WIP\n\t\tif (world.getGameTime() % 3 == 0) {\n\t\t\tif (isGrowing) {\n\t\t\t\tif (size < maxSize) {\n\t\t\t\t\tsize++;\n\t\t\t\t} else {\n\t\t\t\t\t//isGrowing = false;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t/*if (size > 0) {\n\t\t\t\t\tsize--;\n\t\t\t\t} else if (size <= 0) {\n\t\t\t\t\t//kill\n\t\t\t\t\t//manager.removeStormObject(ID);\n\t\t\t\t}*/\n\t\t\t}\n\n\t\t\t//System.out.println(\"cur size: \" + size);\n\t\t}\n\t\t\n\t\tfloat tempAdjustRate = (float)ConfigStorm.Storm_TemperatureAdjustRate;//0.1F;\n\t\tint levelWaterBuildRate = ConfigStorm.Storm_Rain_WaterBuildUpRate;\n\t\tint levelWaterSpendRate = ConfigStorm.Storm_Rain_WaterSpendRate;\n\t\tint randomChanceOfWaterBuildFromWater = ConfigStorm.Storm_Rain_WaterBuildUpOddsTo1FromSource;\n\t\tint randomChanceOfWaterBuildFromNothing = ConfigStorm.Storm_Rain_WaterBuildUpOddsTo1FromNothing;\n\t\tint randomChanceOfWaterBuildFromOvercastRaining = ConfigStorm.Storm_Rain_WaterBuildUpOddsTo1FromOvercastRaining;\n\t\trandomChanceOfWaterBuildFromOvercastRaining = 10;\n\t\t//int randomChanceOfRain = ConfigMisc.Player_Storm_Rain_OddsTo1;\n\t\t\n\t\tboolean isInOcean = false;\n\t\tboolean isOverWater = false;\n\t\tboolean tryFormStorm = false;\n\t\tfloat levelStormIntensityRate = 0.02F;\n\t\tfloat minIntensityToProgress = 0.6F;\n\t\t\n\t\tif (world.getGameTime() % ConfigStorm.Storm_AllTypes_TickRateDelay == 0) {\n\n\t\t\t//boolean debug = false;\n\t\t\t//can be null if spawned via server console\n\t\t\tPlayer player = getPlayer();\n\t\t\tCompoundTag playerNBT = null;\n\t\t\tif (player != null) {\n\t\t\t\tplayerNBT = player.getPersistentData();\n\t\t\t} else {\n\t\t\t\tplayerNBT = new CompoundTag();\n\t\t\t}\n\t\t\t//CompoundTag playerNBT = PlayerData.getPlayerNBT(spawnerUUID);\n\t\t\t\n\t\t\tlong lastStormDeadlyTime = playerNBT.getLong(\"lastStormDeadlyTime\");\n\t\t\t//long lastStormRainTime = playerNBT.getLong(\"lastStormRainTime\");\n\n\t\t\t//set it up so that theres an initial delay before first deadly storm on first world load\n\t\t\tif (lastStormDeadlyTime == 0) {\n\t\t\t\tlastStormDeadlyTime = world.getGameTime();\n\t\t\t}\n\t\t\tWeatherManagerServer wm = ServerTickHandler.getWeatherManagerFor(world.dimension());\n\t\t\tif (wm.lastStormFormed == 0) {\n\t\t\t\twm.lastStormFormed = world.getGameTime();\n\t\t\t}\n\n\t\t\t//Biome bgb = null;\n\t\t\tHolder<Biome> bgb = world.getBiome(WeatherUtilBlock.getPrecipitationHeightSafe(world, new BlockPos(Mth.floor(pos.x), 0, Mth.floor(pos.z))));\n\n\t\t\t//temperature scan\n\t\t\tif (bgb.get() != null) {\n\n\t\t\t\tisInOcean = bgb.unwrap().left().toString().toLowerCase().contains(\"ocean\");\n\t\t\t\t\n\t\t\t\t//float biomeTempAdj = getTemperatureMCToWeatherSys(bgb.getFloatTemperature(new BlockPos(Mth.floor(pos.x), Mth.floor(pos.y), Mth.floor(pos.z))));\n\t\t\t\tfloat biomeTempAdj = getTemperatureMCToWeatherSys(CoroUtilCompatibility.getAdjustedTemperature(manager.getWorld(), bgb.get(), new BlockPos(Mth.floor(pos.x), Mth.floor(pos.y), Mth.floor(pos.z))));\n\t\t\t\tif (levelTemperature > biomeTempAdj) {\n\t\t\t\t\tlevelTemperature -= tempAdjustRate;\n\t\t\t\t} else {\n\t\t\t\t\tlevelTemperature += tempAdjustRate;\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tboolean performBuildup = false;\n\t\t\t\n\t\t\tRandom rand = new Random();\n\t\t\t\n\t\t\tif (!isPrecipitating() && rand.nextInt(randomChanceOfWaterBuildFromNothing) == 0) {\n\t\t\t\tperformBuildup = true;\n\t\t\t}\n\n\t\t\tif (!isPrecipitating() && ConfigMisc.overcastMode && manager.getWorld().isRaining() &&\n\t\t\t\t\trand.nextInt(randomChanceOfWaterBuildFromOvercastRaining) == 0) {\n\t\t\t\tperformBuildup = true;\n\t\t\t}\n\n\t\t\tBlockPos tryPos = new BlockPos(Mth.floor(pos.x), currentTopYBlock - 1, Mth.floor(pos.z));\n\t\t\tif (world.isLoaded(tryPos)) {\n\t\t\t\tBlockState state = world.getBlockState(tryPos);\n\t\t\t\tif (!CoroUtilBlock.isAir(state.getBlock())) {\n\t\t\t\t\t//Block block = Block.blocksList[blockID];\n\t\t\t\t\tif (state.liquid()) {\n\t\t\t\t\t\tisOverWater = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\t//water scan - dont build up if raining already\n\t\t\tif (!performBuildup && !isPrecipitating() && rand.nextInt(randomChanceOfWaterBuildFromWater) == 0) {\n\t\t\t\tif (isOverWater) {\n\t\t\t\t\tperformBuildup = true;\n\t\t\t\t}\n\n\t\t\t\tif (bgb.get() != null) {\n\t\t\t\t\tString biomecat = bgb.unwrap().left().toString().toLowerCase();\n\n\t\t\t\t\tif (!performBuildup && (isInOcean || biomecat.contains(\"swamp\") || biomecat.contains(\"jungle\") || biomecat.contains(\"river\"))) {\n\t\t\t\t\t\tperformBuildup = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tif (performBuildup) {\n\t\t\t\t//System.out.println(\"RAIN BUILD TEMP OFF\");\n\t\t\t\tlevelWater += levelWaterBuildRate;\n\t\t\t\tWeather.dbg(ID + \": building rain: \" + levelWater);\n\t\t\t}\n\t\t\t\n\t\t\t//water values adjust when raining\n\t\t\tif (isPrecipitating()) {\n\t\t\t\tlevelWater -= levelWaterSpendRate;\n\t\t\t\t\n\t\t\t\t//TEMP!!!\n\t\t\t\t/*System.out.println(\"TEMP!!!\");\n\t\t\t\tlevelWater = 0;*/\n\t\t\t\t\n\t\t\t\tif (levelWater < 0) levelWater = 0;\n\t\t\t\t\n\t\t\t\tif (levelWater <= 0) {\n\t\t\t\t\tsetPrecipitating(false);\n\t\t\t\t\tWeather.dbg(\"ending raining for: \" + ID);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (levelWater >= levelWaterStartRaining) {\n\t\t\t\t\tif (ConfigMisc.overcastMode) {\n\t\t\t\t\t\tif (manager.getWorld().isRaining()) {\n\t\t\t\t\t\t\tif (ConfigStorm.Storm_Rain_Overcast_OddsTo1 != -1 && rand.nextInt(ConfigStorm.Storm_Rain_Overcast_OddsTo1) == 0) {\n\t\t\t\t\t\t\t\tsetPrecipitating(true);\n\t\t\t\t\t\t\t\tWeather.dbg(\"starting raining for: \" + ID);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tif (ConfigStorm.Storm_Rain_OddsTo1 != -1 && rand.nextInt(ConfigStorm.Storm_Rain_OddsTo1) == 0) {\n\t\t\t\t\t\t\tsetPrecipitating(true);\n\t\t\t\t\t\t\tWeather.dbg(\"starting raining for: \" + ID);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t}\n\t\t\t\n\t\t\t//actual storm formation chance\n\n\t\t\tboolean tempAlwaysFormStorm = false;\n\t\t\t\n\t\t\tif (this.canBeDeadly && this.levelCurIntensityStage == STATE_NORMAL) {\n\t\t\t\tif (ConfigStorm.Server_Storm_Deadly_UseGlobalRate) {\n\t\t\t\t\tif (ConfigStorm.Server_Storm_Deadly_TimeBetweenInTicks != -1) {\n\t\t\t\t\t\tif (wm.lastStormFormed == 0 || wm.lastStormFormed + ConfigStorm.Server_Storm_Deadly_TimeBetweenInTicks < world.getGameTime()) {\n\t\t\t\t\t\t\ttryFormStorm = true;\n\t\t\t\t\t\t} else if (ConfigStorm.Server_Storm_Deadly_TimeBetweenInTicks_Land_Based != -1 && wm.lastStormFormed + ConfigStorm.Server_Storm_Deadly_TimeBetweenInTicks_Land_Based < world.getGameTime()) {\n\t\t\t\t\t\t\ttryFormStorm = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif (tempAlwaysFormStorm || ConfigStorm.Player_Storm_Deadly_TimeBetweenInTicks != -1) {\n\t\t\t\t\t\tif (tempAlwaysFormStorm || lastStormDeadlyTime == 0 || lastStormDeadlyTime + ConfigStorm.Player_Storm_Deadly_TimeBetweenInTicks < world.getGameTime()) {\n\t\t\t\t\t\t\ttryFormStorm = true;\n\t\t\t\t\t\t} else if (ConfigStorm.Player_Storm_Deadly_TimeBetweenInTicks_Land_Based != -1 && lastStormDeadlyTime + ConfigStorm.Player_Storm_Deadly_TimeBetweenInTicks_Land_Based < world.getGameTime()) {\n\t\t\t\t\t\t\ttryFormStorm = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (weatherMachineControlled) {\n\t\t\t    return;\n            }\n\n\t\t\tif (((ConfigMisc.overcastMode && manager.getWorld().isRaining()) || !ConfigMisc.overcastMode)\n\t\t\t\t\t&& WeatherUtilConfig.listDimensionsStorms.contains(manager.getWorld().dimension().location().toString()) && tryFormStorm) {\n\t\t\t\tint stormFrontCollideDist = ConfigStorm.Storm_Deadly_CollideDistance;\n\t\t\t\tint randomChanceOfCollide = ConfigStorm.Player_Storm_Deadly_OddsTo1;\n\t\t\t\tint randomChanceOfCollideLand = ConfigStorm.Player_Storm_Deadly_OddsTo1_Land_Based;\n\n\t\t\t\tif (ConfigStorm.Server_Storm_Deadly_UseGlobalRate) {\n\t\t\t\t\trandomChanceOfCollide = ConfigStorm.Server_Storm_Deadly_OddsTo1;\n\t\t\t\t\trandomChanceOfCollideLand = ConfigStorm.Server_Storm_Deadly_OddsTo1_Land_Based;\n\t\t\t\t}\n\n\t\t\t\tif (isInOcean && (ConfigStorm.Storm_OddsTo1OfOceanBasedStorm > 0 && rand.nextInt(ConfigStorm.Storm_OddsTo1OfOceanBasedStorm) == 0)) {\n\t\t\t\t\tPlayer entP = getPlayer();\n\n\t\t\t\t\tif (entP != null) {\n\t\t\t\t\t\tinitRealStorm(entP, null);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tinitRealStorm(null, null);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (ConfigStorm.Server_Storm_Deadly_UseGlobalRate) {\n\t\t\t\t\t\twm.lastStormFormed = world.getGameTime();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tplayerNBT.putLong(\"lastStormDeadlyTime\", world.getGameTime());\n\t\t\t\t\t}\n\t\t\t\t} else if ((!isInOcean && randomChanceOfCollideLand > 0 && rand.nextInt(randomChanceOfCollideLand) == 0)) {\n\t\t\t\t\tPlayer entP = getPlayer();\n\n\t\t\t\t\tif (entP != null) {\n\t\t\t\t\t\tinitRealStorm(entP, null);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tinitRealStorm(null, null);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (ConfigStorm.Server_Storm_Deadly_UseGlobalRate) {\n\t\t\t\t\t\twm.lastStormFormed = world.getGameTime();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tplayerNBT.putLong(\"lastStormDeadlyTime\", world.getGameTime());\n\t\t\t\t\t}\n\t\t\t\t} else if (rand.nextInt(randomChanceOfCollide) == 0) {\n\t\t\t\t\tfor (int i = 0; i < manager.getStormObjects().size(); i++) {\n\t\t\t\t\t\tWeatherObject wo = manager.getStormObjects().get(i);\n\n\t\t\t\t\t\tif (wo instanceof StormObject) {\n\t\t\t\t\t\t\tStormObject so = (StormObject) wo;\n\n\n\n\t\t\t\t\t\t\tboolean startStorm = false;\n\n\t\t\t\t\t\t\tif (so.ID != this.ID && so.levelCurIntensityStage <= 0 && !so.isCloudlessStorm() && !so.weatherMachineControlled) {\n\t\t\t\t\t\t\t\tif (so.pos.distanceTo(pos) < stormFrontCollideDist) {\n\t\t\t\t\t\t\t\t\tif (this.levelTemperature < 0) {\n\t\t\t\t\t\t\t\t\t\tif (so.levelTemperature > 0) {\n\t\t\t\t\t\t\t\t\t\t\tstartStorm = true;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t} else if (this.levelTemperature > 0) {\n\t\t\t\t\t\t\t\t\t\tif (so.levelTemperature < 0) {\n\t\t\t\t\t\t\t\t\t\t\tstartStorm = true;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (startStorm) {\n\n\t\t\t\t\t\t\t\t//Weather.dbg(\"start storm!\");\n\n\t\t\t\t\t\t\t\tplayerNBT.putLong(\"lastStormDeadlyTime\", world.getGameTime());\n\n\t\t\t\t\t\t\t\t//EntityPlayer entP = manager.getWorld().getClosestPlayer(pos.x, pos.y, pos.z, -1);\n\t\t\t\t\t\t\t\tPlayer entP = getPlayer();\n\n\t\t\t\t\t\t\t\tif (entP != null) {\n\t\t\t\t\t\t\t\t\tinitRealStorm(entP, so);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tinitRealStorm(null, so);\n\t\t\t\t\t\t\t\t\t//can happen, chunkloaded emtpy overworld, let the storm do what it must without a player\n\t\t\t\t\t\t\t\t\t//Weather.dbg(\"Weather2 WARNING!!!! Failed to get a player object for new tornado, this shouldnt happen\");\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tif (isRealStorm()) {\n\t\t\t\t\n\t\t\t\t//force storms to die if its no longer raining while overcast mode is active\n\t\t\t\tif (ConfigMisc.overcastMode) {\n\t\t\t\t\tif (!manager.getWorld().isRaining()) {\n\t\t\t\t\t\thasStormPeaked = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t//force rain on while real storm and not dying\n\t\t\t\tif (!hasStormPeaked) {\n\t\t\t\t\tlevelWater = levelWaterStartRaining;\n\t\t\t\t\tsetPrecipitating(true);\n\t\t\t\t}\n\n\t\t\t\t//temp\n\t\t\t\t//levelWater = 0;\n\t\t\t\t//setPrecipitating(false);\n\t\t\t\t\n\t\t\t\tif ((levelCurIntensityStage == STATE_HIGHWIND || levelCurIntensityStage == STATE_HAIL) && isOverWater) {\n\t\t\t\t\tif (ConfigStorm.Storm_OddsTo1OfHighWindWaterSpout != 0 && rand.nextInt(ConfigStorm.Storm_OddsTo1OfHighWindWaterSpout) == 0) {\n\t\t\t\t\t\tattrib_waterSpout = true;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tattrib_waterSpout = false;\n\t\t\t\t}\n\t\t\t\t//change since storms have a predetermined max now, nevermind, storms take too long, limited simbox area\n\t\t\t\t//minIntensityToProgress = 0.8F;\n\t\t\t\t//int oddsTo1OfIntensityProgressionBase = ConfigStorm.Storm_OddsTo1OfProgressionBase;\n\n\t\t\t\t//int oddsTo1OfIntensityProgression = oddsTo1OfIntensityProgressionBase + (levelCurIntensityStage * ConfigStorm.Storm_OddsTo1OfProgressionStageMultiplier);\n\n\t\t\t\tif (!hasStormPeaked) {\n\t\t\t\t\t\n\t\t\t\t\tif (levelCurIntensityStage < maxIntensityStage && (!ConfigTornado.Storm_NoTornadosOrCyclones || levelCurIntensityStage < STATE_FORMING-1)) {\n\t\t\t\t\t\tif (levelCurStagesIntensity >= minIntensityToProgress) {\n\t\t\t\t\t\t\t//Weather.dbg(\"storm ID: \" + this.ID + \" trying to hit next stage\");\n\t\t\t\t\t\t\tif (alwaysProgresses || levelCurIntensityStage < levelStormIntensityMax/*rand.nextInt(oddsTo1OfIntensityProgression) == 0*/) {\n\t\t\t\t\t\t\t\tstageNext();\n\t\t\t\t\t\t\t\tWeather.dbg(\"storm ID: \" + this.ID + \" - growing, stage: \" + levelCurIntensityStage + \" pos: \" + pos);\n\t\t\t\t\t\t\t\t//mark is tropical cyclone if needed! and never unmark it!\n\t\t\t\t\t\t\t\t//TODO: re-enable this to allow hurricanes again\n\t\t\t\t\t\t\t\tif (isInOcean && false) {\n\t\t\t\t\t\t\t\t\t//make it ONLY allow to change during forming stage, so it locks in\n\t\t\t\t\t\t\t\t\tif (levelCurIntensityStage == STATE_FORMING) {\n\t\t\t\t\t\t\t\t\t\tWeather.dbg(\"storm ID: \" + this.ID + \" marked as tropical cyclone!\");\n\t\t\t\t\t\t\t\t\t\tstormType = TYPE_WATER;\n\n\t\t\t\t\t\t\t\t\t\t//reroll dice on ocean storm since we only just define it here\n\t\t\t\t\t\t\t\t\t\tlevelStormIntensityMax = rollDiceOnMaxIntensity();\n\t\t\t\t\t\t\t\t\t\tWeather.dbg(\"rerolled odds for ocean storm, max stage will be: \" + levelStormIntensityMax);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t//new F0 forming touches down at 0.5, needs to switch to F1 if it can ASAP\n\t\t\t\t\t\tif (levelCurIntensityStage == STATE_FORMING) {\n\t\t\t\t\t\t\tif (levelCurStagesIntensity >= 0.5 && levelCurStagesIntensity < 0.9) {\n\t\t\t\t\t\t\t\tlevelCurStagesIntensity = 0.90F;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\tWeather.dbg(\"storm ID: \" + this.ID + \" - growing, stage \" + levelCurIntensityStage + \" of max \" + levelStormIntensityMax + \", at intensity: \" + levelCurStagesIntensity + \" pos: \" + pos);\n\t\t\t\t\t\n\t\t\t\t\tif (levelCurStagesIntensity >= 1F) {\n\t\t\t\t\t\tWeather.dbg(\"storm peaked at: \" + levelCurIntensityStage);\n\t\t\t\t\t\thasStormPeaked = true;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t\n\t\t\t\t\tif (ConfigMisc.overcastMode && manager.getWorld().isRaining()) {\n\t\t\t\t\t\tlevelCurStagesIntensity -= levelStormIntensityRate * 0.9F;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tlevelCurStagesIntensity -= levelStormIntensityRate * 0.3F;\n\t\t\t\t\t\tif (levelCurIntensityStage >= STATE_FORMING) {\n\t\t\t\t\t\t\tWeather.dbg(\"storm ID: \" + this.ID + \" - active info, stage: \" + levelCurIntensityStage + \" levelCurStagesIntensity: \" + levelCurStagesIntensity + \" pos: \" + pos);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t//insta set it to 0.5 so it visually starts taking off if its dying\n\t\t\t\t\tif (levelCurIntensityStage == STATE_FORMING) {\n\t\t\t\t\t\tif (levelCurStagesIntensity > 0.5) {\n\t\t\t\t\t\t\tlevelCurStagesIntensity = 0.5F;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t//extra decay to above code\n\t\t\t\t\t\tlevelCurStagesIntensity -= levelStormIntensityRate * 0.9F;\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\tif (levelCurStagesIntensity <= 0) {\n\t\t\t\t\t\tstagePrev();\n\t\t\t\t\t\tWeather.dbg(\"storm ID: \" + this.ID + \" - dying, stage: \" + levelCurIntensityStage + \" pos: \" + pos);\n\t\t\t\t\t\tif (levelCurIntensityStage <= 0) {\n\t\t\t\t\t\t\tsetNoStorm();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t//levelStormIntensityCur value ranges and what they influence\n\t\t\t\t//revised to remove rain and factor in tropical storm / hurricane\n\t\t\t\t//1 = thunderstorm (and more rain???)\n\t\t\t\t//2 = high wind\n\t\t\t\t//3 = hail\n\t\t\t\t//4 = tornado forming OR tropical cyclone (forming?) - logic splits off here where its marked as hurricane if its over water\n\t\t\t\t//5 = F1 OR TC 2\n\t\t\t\t//6 = F2 OR TC 3\n\t\t\t\t//7 = F3 OR TC 4\n\t\t\t\t//8 = F4 OR TC 5\n\t\t\t\t//9 = F5 OR hurricane ??? (perhaps hurricanes spawn differently, like over ocean only, and sustain when hitting land for a bit)\n\t\t\t\t\n\t\t\t\t//what about tropical storm? that is a mini hurricane, perhaps also ocean based\n\t\t\t\t\n\t\t\t\t//levelWindMomentum = rate of increase of storm??? (in addition to the pre storm system speeds)\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t//POST DEV NOTES READ!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!:\n\t\t\t\t\n\t\t\t\t//it might be a good idea to make something else determine increase from high winds to tornado and higher\n\t\t\t\t//using temperatures is a little unstable at such a large range of variation....\n\t\t\t\t\n\t\t\t\t//updateStormFlags();\n\t\t\t\t//curWeatherType = Math.min(WeatherTypes.weatherEntTypes.size()-1, Math.max(1, levelCurIntensityStage - 1));\n\t\t\t} else {\n\t\t\t\tif (ConfigMisc.overcastMode) {\n\t\t\t\t\tif (!manager.getWorld().isRaining()) {\n\t\t\t\t\t\tif (attrib_precipitation) {\n\t\t\t\t\t\t\tsetPrecipitating(false);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t//tick this more often for smoother forming on the client side (packet sync rate is 2 so 2 is good enough here)\n\t\tif (world.getGameTime() % 2 == 0) {\n\t\t\tif (((ConfigMisc.overcastMode && manager.getWorld().isRaining()) || !ConfigMisc.overcastMode) && WeatherUtilConfig.listDimensionsStorms.contains(manager.getWorld().dimension().location().toString())/* && tryFormStorm*/) {\n\t\t\t\tif (isRealStorm() && !hasStormPeaked) {\n\n\t\t\t\t\t//speed up forming and greater progression when past forming state\n\t\t\t\t\tif (levelCurIntensityStage >= levelStormIntensityFormingStartVal) {\n\t\t\t\t\t\tlevelStormIntensityRate *= 3;\n\t\t\t\t\t\t//oddsTo1OfIntensityProgressionBase /= 3;\n\t\t\t\t\t}\n\n\t\t\t\t\t//30F because this was ticking once every 60 ticks, now its every 2 ticks\n\t\t\t\t\tlevelCurStagesIntensity += levelStormIntensityRate / 30F;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (playerControlled) {\n\t\t\tif (playerControlledTimeLeft > 0) {\n\t\t\t\tplayerControlledTimeLeft--;\n\n\t\t\t\tif (playerControlledTimeLeft <= 0) {\n\t\t\t\t\tfeatherFallAllNearbyPlayers();\n\t\t\t\t\tremove();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic void featherFallAllNearbyPlayers() {\n\t\t/*double dist = 100 * 2;\n\t\tAABB aabb = new AABB(pos.x, currentTopYBlock, pos.z, pos.x, currentTopYBlock, pos.z);\n\t\taabb = aabb.inflate(dist, maxHeight * 3, dist);\n\t\tList list = manager.getWorld().getEntitiesOfClass(Entity.class, aabb);*/\n\n\t\ttornadoHelper.forceRotate(manager.getWorld(), true);\n\t}\n\t\n\tpublic WeatherEntityConfig getWeatherEntityConfigForStorm() {\n\t\t//default spout\n\t\tWeatherEntityConfig weatherConfig = WeatherTypes.weatherEntTypes.get(0);\n\t\tif (levelCurIntensityStage >= STATE_STAGE5) {\n\t\t\tweatherConfig = WeatherTypes.weatherEntTypes.get(5);\n\t\t} else if (levelCurIntensityStage >= STATE_STAGE4) {\n\t\t\tweatherConfig = WeatherTypes.weatherEntTypes.get(4);\n\t\t} else if (levelCurIntensityStage >= STATE_STAGE3) {\n\t\t\tweatherConfig = WeatherTypes.weatherEntTypes.get(3);\n\t\t} else if (levelCurIntensityStage >= STATE_STAGE2) {\n\t\t\tweatherConfig = WeatherTypes.weatherEntTypes.get(2);\n\t\t} else if (levelCurIntensityStage >= STATE_STAGE1) {\n\t\t\tweatherConfig = WeatherTypes.weatherEntTypes.get(1);\n\t\t} else if (levelCurIntensityStage >= STATE_FORMING) {\n\t\t\tweatherConfig = WeatherTypes.weatherEntTypes.get(0);\n\t\t}\n\t\treturn weatherConfig;\n\t}\n\t\n\tpublic void stageNext() {\n\t\tlevelCurIntensityStage++;\n\t\tlevelCurStagesIntensity = 0F;\n\t\tif (ConfigTornado.Storm_Tornado_aimAtPlayerOnSpawn) {\n\t\t\tif (!hasStormPeaked && levelCurIntensityStage == STATE_FORMING) {\n\t\t\t\tif (!Weather.isLoveTropicsInstalled()) {\n\t\t\t\t\taimStormAtClosestOrProvidedPlayer(null);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t\n\tpublic void stagePrev() {\n\t\tlevelCurIntensityStage--;\n\t\tlevelCurStagesIntensity = 1F;\n\t}\n\t\n\tpublic void initRealStorm(Player entP, StormObject stormToAbsorb) {\n\t\t\n\t\t//new way of storm progression\n\t\tlevelCurIntensityStage = STATE_THUNDER;\n\t\t\n\t\t\n\t\t//isRealStorm = true;\n\t\tfloat diff = 4;\n\t\tif (stormToAbsorb != null) {\n\t\t\tdiff = this.levelTemperature - stormToAbsorb.levelTemperature;\n\t\t}\n\t\tif (naturallySpawned) {\n\t\t\tthis.levelWater = this.levelWaterStartRaining * 2;\n\t\t\t/*this.levelStormIntensityMax = (float) (diff * ConfigMisc.Storm_IntensityAmplifier);\n\t\t\tif (levelStormIntensityMax < ConfigMisc.Storm_Deadly_MinIntensity) {\n\t\t\t\tlevelStormIntensityMax = (float)ConfigMisc.Storm_Deadly_MinIntensity;\n\t\t\t}*/\n\t\t}\n\n\t\tthis.levelStormIntensityMax = rollDiceOnMaxIntensity();\n\t\tWeather.dbg(\"rolled odds for storm, unless it becomes ocean storm, max stage will be: \" + levelStormIntensityMax);\n\n\t\tthis.attrib_precipitation = true;\n\n\t\tif (stormToAbsorb != null) {\n\t\t\tWeather.dbg(\"stormfront collision happened between ID \" + this.ID + \" and \" + stormToAbsorb.ID);\n\t\t\tmanager.removeStormObject(stormToAbsorb.ID);\n\t\t\t((WeatherManagerServer)manager).syncStormRemove(stormToAbsorb);\n\t\t} else {\n\t\t\tWeather.dbg(\"ocean storm happened, ID \" + this.ID);\n\t\t}\n\t\t\n\t\tif (ConfigTornado.Storm_Tornado_aimAtPlayerOnSpawn) {\n\t\t\tif (!Weather.isLoveTropicsInstalled()) {\n\t\t\t\taimStormAtClosestOrProvidedPlayer(entP);\n\t\t\t}\n\t\t\t\n\t\t}\n\t}\n\n\tpublic int rollDiceOnMaxIntensity() {\n\t\tRandom rand = new Random();\n\t\tint randVal = rand.nextInt(100);\n\t\tif (stormType == TYPE_LAND) {\n\t\t\tif (randVal <= ConfigStorm.Storm_PercentChanceOf_F5_Tornado) {\n\t\t\t\treturn STATE_STAGE5;\n\t\t\t} else if (randVal <= ConfigStorm.Storm_PercentChanceOf_F4_Tornado) {\n\t\t\t\treturn STATE_STAGE4;\n\t\t\t} else if (randVal <= ConfigStorm.Storm_PercentChanceOf_F3_Tornado) {\n\t\t\t\treturn STATE_STAGE3;\n\t\t\t} else if (randVal <= ConfigStorm.Storm_PercentChanceOf_F2_Tornado) {\n\t\t\t\treturn STATE_STAGE2;\n\t\t\t} else if (randVal <= ConfigStorm.Storm_PercentChanceOf_F1_Tornado) {\n\t\t\t\treturn STATE_STAGE1;\n\t\t\t} else if (randVal <= ConfigStorm.Storm_PercentChanceOf_F0_Tornado) {\n\t\t\t\treturn STATE_FORMING;\n\t\t\t} else if (randVal <= ConfigStorm.Storm_PercentChanceOf_Hail) {\n\t\t\t\treturn STATE_HAIL;\n\t\t\t} else if (randVal <= ConfigStorm.Storm_PercentChanceOf_HighWind) {\n\t\t\t\treturn STATE_HIGHWIND;\n\t\t\t}\n\t\t} else if (stormType == TYPE_WATER) {\n\t\t\tif (randVal <= ConfigStorm.Storm_PercentChanceOf_C5_Cyclone) {\n\t\t\t\treturn STATE_STAGE5;\n\t\t\t} else if (randVal <= ConfigStorm.Storm_PercentChanceOf_C4_Cyclone) {\n\t\t\t\treturn STATE_STAGE4;\n\t\t\t} else if (randVal <= ConfigStorm.Storm_PercentChanceOf_C3_Cyclone) {\n\t\t\t\treturn STATE_STAGE3;\n\t\t\t} else if (randVal <= ConfigStorm.Storm_PercentChanceOf_C2_Cyclone) {\n\t\t\t\treturn STATE_STAGE2;\n\t\t\t} else if (randVal <= ConfigStorm.Storm_PercentChanceOf_C1_Cyclone) {\n\t\t\t\treturn STATE_STAGE1;\n\t\t\t} else if (randVal <= ConfigStorm.Storm_PercentChanceOf_C0_Cyclone) {\n\t\t\t\treturn STATE_FORMING;\n\t\t\t} else if (randVal <= ConfigStorm.Storm_PercentChanceOf_Hail) {\n\t\t\t\treturn STATE_HAIL;\n\t\t\t} else if (randVal <= ConfigStorm.Storm_PercentChanceOf_HighWind) {\n\t\t\t\treturn STATE_HIGHWIND;\n\t\t\t}\n\t\t}\n\n\t\treturn STATE_THUNDER;\n\t}\n\t\n\tpublic void aimStormAtClosestOrProvidedPlayer(Player entP) {\n\t\t\n\t\tif (entP == null) {\n\t\t\tentP = manager.getWorld().getNearestPlayer(pos.x, pos.y, pos.z, -1, false);\n\t\t}\n\t\t\n\t\tif (entP != null) {\n\t\t\taimAtCoords(entP.position());\n\t\t}\n\t}\n\n\tpublic void aimAtCoords(Vec3 vec) {\n\t\tRandom rand = new Random();\n\t\tdouble var11 = vec.x() - pos.x;\n\t\tdouble var15 = vec.z() - pos.z;\n\t\tfloat yaw = -(float)(Math.atan2(var11, var15) * 180.0D / Math.PI);\n\t\t//weather override!\n\t\t//yaw = weatherMan.wind.direction;\n\t\tint size = ConfigTornado.Storm_Tornado_aimAtPlayerAngleVariance;\n\t\tif (size > 0) {\n\t\t\tif (!Weather.isLoveTropicsInstalled()) {\n\t\t\t\tyaw += rand.nextInt(size) - (size / 2);\n\t\t\t}\n\t\t}\n\n\t\tangleIsOverridden = true;\n\t\tangleMovementTornadoOverride = yaw;\n\n\t\t//Weather.dbg(\"stormfront aimed at player \" + CoroUtilEntity.getName(entP));\n\t}\n\n\tpublic void aimAwayFromCoords(Vec3 vec) {\n\t\tRandom rand = new Random();\n\t\tdouble var11 = vec.x() - pos.x;\n\t\tdouble var15 = vec.z() - pos.z;\n\t\tfloat yaw = -(float)(Math.atan2(var11, var15) * 180.0D / Math.PI);\n\t\t//invert\n\t\tyaw += 180;\n\n\t\tangleIsOverridden = true;\n\t\tangleMovementTornadoOverride = yaw;\n\n\t\t//Weather.dbg(\"stormfront aimed at player \" + CoroUtilEntity.getName(entP));\n\t}\n\t\n\t//FYI rain doesnt count as storm\n\tpublic void setNoStorm() {\n\t\tWeather.dbg(\"storm ID: \" + this.ID + \" - ended storm event\");\n\t\tlevelCurIntensityStage = STATE_NORMAL;\n\t\tlevelCurStagesIntensity = 0;\n\t}\n\t\n\t@OnlyIn(Dist.CLIENT)\n\tpublic void tickClient() {\n\n\t\tif (false && ConfigCoroUtil.useLoggingDebug && SceneEnhancer.particleBehavior != null) {\n\t\t\tTextureAtlasSprite sprite = ParticleRegistry.tumbleweed;\n\n\t\t\tParticleCrossSection part = new ParticleCrossSection(manager.getWorld(), pos.x, pos.y - 20, pos.z,\n\t\t\t\t\t0, 0, 0, sprite);\n\t\t\tSceneEnhancer.particleBehavior.initParticle(part);\n\t\t\tSceneEnhancer.particleBehavior.initParticleSandstormTumbleweed(part);\n\t\t\tpart.windWeight = 9999;\n\t\t\tpart.setGravity(0.5F);\n\t\t\tif (cloudlessStorm) {\n\t\t\t\tpart.setGravity(0);\n\t\t\t}\n\t\t\tSceneEnhancer.particleBehavior.particles.add(part);\n\t\t\tpart.spawnAsWeatherEffect();\n\t\t}\n\n\t\tif (isCloudlessStorm()) return;\n\n\t\tif (particleBehaviorFog == null) {\n\t\t\tparticleBehaviorFog = new ParticleBehaviorFog(new Vec3(pos.x, pos.y, pos.z));\n\t\t\t//particleBehaviorFog.sourceEntity = this;\n\t\t} else {\n\t\t\tif (!Minecraft.getInstance().isPaused()) {\n\t\t\t\tparticleBehaviorFog.tickUpdateList();\n\t\t\t}\n\t\t}\n        \n\t\tPlayer entP = Minecraft.getInstance().player;\n\t\t\n\t\tspinSpeed = 0.02D;\n\t\tdouble spinSpeedMax = 0.4D;\n\t\t/*if (isHurricane()) {\n\t\t\tspinSpeed = spinSpeedMax * 1.2D;\n\t\t\tWeather.dbg(\"spin speed: \" + spinSpeed);\n\t\t} else */if (isCycloneFormingOrGreater()) {\n\t\t\tspinSpeed = spinSpeedMax * 0.00D + ((levelCurIntensityStage-levelStormIntensityFormingStartVal+1) * spinSpeedMax * 0.2D);\n\t\t\t//Weather.dbg(\"spin speed: \" + spinSpeed);\n\t\t} else if (isTornadoFormingOrGreater()) {\n\t\t\tspinSpeed = spinSpeedMax * 0.2D;\n\t\t} else if (levelCurIntensityStage >= STATE_HIGHWIND) {\n\t\t\tspinSpeed = spinSpeedMax * 0.05D;\n\t\t} else {\n\t\t\tspinSpeed = spinSpeedMax * 0.02D;\n\t\t}\n\t\t\n\t\t//bonus!\n\t\tif (isHurricane()) {\n\t\t\tspinSpeed += 0.1D;\n\t\t}\n\t\t\n\t\tif (size == 0) size = 1;\n\t\tint delay = Math.max(1, (int)(100F / size * 1F));\n\t\tint loopSize = 1;//(int)(1 * size * 0.1F);\n\t\t\n\t\tint extraSpawning = 0;\n\t\t\n\t\tif (isSpinning()) {\n\t\t\tloopSize += 4;\n\t\t\textraSpawning = 300;\n\t\t}\n\t\t\n\t\t//adjust particle creation rate for upper tropical cyclone work\n\t\tif (stormType == TYPE_WATER) {\n\t\t\tif (levelCurIntensityStage >= STATE_STAGE5) {\n\t\t\t\tloopSize = 10;\n\t\t\t\textraSpawning = 800;\n\t\t\t} else if (levelCurIntensityStage >= STATE_STAGE4) {\n\t\t\t\tloopSize = 8;\n\t\t\t\textraSpawning = 700;\n\t\t\t} else if (levelCurIntensityStage >= STATE_STAGE3) {\n\t\t\t\tloopSize = 6;\n\t\t\t\textraSpawning = 500; \n\t\t\t} else if (levelCurIntensityStage >= STATE_STAGE2) {\n\t\t\t\tloopSize = 4;\n\t\t\t\textraSpawning = 400;\n\t\t\t} else {\n\t\t\t\textraSpawning = 300;\n\t\t\t}\n\t\t}\n\t\t\n\t\t//Weather.dbg(\"size: \" + size + \" - delay: \" + delay); \n\t\t\n\t\tRandom rand = new Random();\n\t\t\n\t\tVec3 playerAdjPos = new Vec3(entP.getX(), pos.y, entP.getZ());\n\t\tdouble maxSpawnDistFromPlayer = 512;\n\t\t\n\n\n\t\t//maintain clouds new system\n\n\n\t\t//spawn clouds\n\n\t\tif (!pet && !baby && this.manager.getWorld().getGameTime() % (delay + (isSpinning() ? ConfigStorm.Storm_ParticleSpawnDelay : ConfigMisc.Cloud_ParticleSpawnDelay)) == 0) {\n\t\t\tfor (int i = 0; i < loopSize; i++) {\n\t\t\t\t/*if (listParticlesCloud.size() == 0) {\n\t\t\t\t\tdouble spawnRad = 1;\n\t\t\t\t\tVec3 tryPos = new Vec3(pos.x + (rand.nextDouble()*spawnRad) - (rand.nextDouble()*spawnRad), layers.get(layer), pos.z + (rand.nextDouble()*spawnRad) - (rand.nextDouble()*spawnRad));\n\t\t\t\t\tEntityRotFX particle;\n\t\t\t\t\tif (WeatherUtil.isAprilFoolsDay()) {\n\t\t\t\t\t\tparticle = spawnFogParticle(tryPos.x, tryPos.y, tryPos.z, 0, ParticleRegistry.chicken);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tparticle = spawnFogParticle(tryPos.x, tryPos.y, tryPos.z, 0, ParticleRegistry.cloud256_test);\n\t\t\t\t\t}\n\n\t\t\t\t\tlistParticlesCloud.add(particle);\n\t\t\t\t}*/\n\t\t\t\tif (listParticlesCloud.size() < (size + extraSpawning) / 1F) {\n\t\t\t\t\tdouble spawnRad = size;\n\t\t\t\t\t\n\t\t\t\t\t/*if (layer != 0) {\n\t\t\t\t\t\tspawnRad = size * 5;\n\t\t\t\t\t}*/\n\t\t\t\t\t\n\t\t\t\t\t//Weather.dbg(\"listParticlesCloud.size(): \" + listParticlesCloud.size());\n\t\t\t\t\t\n\t\t\t\t\t//Vec3 tryPos = new Vec3(pos.x + (rand.nextDouble()*spawnRad) - (rand.nextDouble()*spawnRad), layers.get(layer), pos.z + (rand.nextDouble()*spawnRad) - (rand.nextDouble()*spawnRad));\n\t\t\t\t\tVec3 tryPos = new Vec3(pos.x + (rand.nextDouble()*spawnRad) - (rand.nextDouble()*spawnRad), getPosTop().y + 30, pos.z + (rand.nextDouble()*spawnRad) - (rand.nextDouble()*spawnRad));\n\t\t\t\t\tif (tryPos.distanceTo(playerAdjPos) < maxSpawnDistFromPlayer) {\n\t\t\t\t\t\tif (getAvoidAngleIfTerrainAtOrAheadOfPosition(getAdjustedAngle(), tryPos) == 0) {\n\t\t\t\t\t\t\tEntityRotFX particle;\n\t\t\t\t\t\t\tif (WeatherUtil.isAprilFoolsDay() && !Weather.isLoveTropicsInstalled()) {\n\t\t\t\t\t\t\t\tparticle = spawnFogParticle(tryPos.x, tryPos.y, tryPos.z, 0, ParticleRegistry.chicken);\n\t\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\t\tparticle = spawnFogParticle(tryPos.x, tryPos.y, tryPos.z, 0);\n\t\t\t\t\t\t\t\tif (isFirenado && isSpinning()) {\n\t\t\t\t\t\t\t\t\t//if (particle.getEntityId() % 20 < 5) {\n\t\t\t\t\t\t\t\t\t\tparticle.setSprite(ParticleRegistry.cloud256_fire);\n\t\t\t\t\t\t\t\t\t\tparticle.setColor(1F, 1F, 1F);\n\n\t\t\t\t\t\t\t\t\t//}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t/*if (layer == 0) {\n\t\t\t\t\t\t\t\tparticle.particleScale = 500;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tparticle.particleScale = 2000;\n\t\t\t\t\t\t\t}*/\n\n\t\t\t\t\t\t\tlistParticlesCloud.add(particle);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t\n\t\t\t}\n\t\t}\n\t\t\n\t\t//ground effects\n\t\tif (levelCurIntensityStage >= STATE_HIGHWIND && !baby && !pet) {\n\t\t\tfor (int i = 0; i < (stormType == TYPE_WATER ? 50 : 3)/*loopSize/2*/; i++) {\n\t\t\t\tif (listParticlesGround.size() < (stormType == TYPE_WATER ? 600 : 150)/*size + extraSpawning*/) {\n\t\t\t\t\tdouble spawnRad = size/4*3;\n\t\t\t\t\t\n\t\t\t\t\tif (stormType == TYPE_WATER) {\n\t\t\t\t\t\tspawnRad = size*3;\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t//Weather.dbg(\"listParticlesCloud.size(): \" + listParticlesCloud.size());\n\t\t\t\t\t\n\t\t\t\t\tVec3 tryPos = new Vec3(pos.x + (rand.nextDouble()*spawnRad) - (rand.nextDouble()*spawnRad), posGround.y, pos.z + (rand.nextDouble()*spawnRad) - (rand.nextDouble()*spawnRad));\n\t\t\t\t\tif (tryPos.distanceTo(playerAdjPos) < maxSpawnDistFromPlayer) {\n\t\t\t\t\t\tint groundY = WeatherUtilBlock.getPrecipitationHeightSafe(manager.getWorld(), new BlockPos((int)tryPos.x, 0, (int)tryPos.z)).getY();\n\t\t\t\t\t\tEntityRotFX particle;\n\t\t\t\t\t\tif (WeatherUtil.isAprilFoolsDay() && !Weather.isLoveTropicsInstalled()) {\n\t\t\t\t\t\t\tparticle = spawnFogParticle(tryPos.x, groundY + 3, tryPos.z, 0, ParticleRegistry.potato);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tparticle = spawnFogParticle(tryPos.x, groundY + 3, tryPos.z, 0);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t\tparticle.setScale(200 * 0.15F);\n\t\t\t\t\t\tparticle.rotationYaw = rand.nextInt(360);\n\t\t\t\t\t\tparticle.rotationPitch = rand.nextInt(360);\n\t\t\t\t\t\t\n\t\t\t\t\t\tlistParticlesGround.add(particle);\n\t\t\t\t\t\t\n\t\t\t\t\t\t//Weather.dbg(\"ground fog!\");\n\t\t\t\t\t\t\n\t\t\t\t    \t/*if (layer == 0) {\n\t\t\t\t    \t\tparticle.particleScale = 500;\n\t\t\t\t    \t} else {\n\t\t\t\t    \t\tparticle.particleScale = 2000;\n\t\t\t\t    \t}*/\n\t\t\t\t\t\t\n\t\t\t\t\t\t//listParticlesCloud.add(particle);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t\n\t\t\t}\n\t\t}\n\t\t\n\t\tdelay = 1;\n\t\tloopSize = 2;\n\t\t\n\t\tdouble spawnRad = size/48;\n\t\t\n\t\tif (levelCurIntensityStage >= STATE_STAGE5) {\n\t\t\tspawnRad = 200;\n\t\t\tloopSize = 10;\n\t\t\tsizeMaxFunnelParticles = 1200;\n\t\t} else if (levelCurIntensityStage >= STATE_STAGE4) {\n\t\t\tspawnRad = 150;\n\t\t\tloopSize = 8;\n\t\t\tsizeMaxFunnelParticles = 1000;\n\t\t} else if (levelCurIntensityStage >= STATE_STAGE3) {\n\t\t\tspawnRad = 100;\n\t\t\tloopSize = 6;\n\t\t\tsizeMaxFunnelParticles = 800; \n\t\t} else if (levelCurIntensityStage >= STATE_STAGE2) {\n\t\t\tspawnRad = 50;\n\t\t\tloopSize = 4;\n\t\t\tsizeMaxFunnelParticles = 600;\n\t\t} else {\n\t\t\tsizeMaxFunnelParticles = 600;\n\t\t}\n\n\t\tboolean tornadoV2 = true;\n\n\t\t//spawn funnel\n\t\tif (!tornadoV2) {\n\t\t\tif (isTornadoFormingOrGreater() || (attrib_waterSpout)) {\n\t\t\t\tif (this.manager.getWorld().getGameTime() % (delay + ConfigStorm.Storm_ParticleSpawnDelay) == 0) {\n\t\t\t\t\tfor (int i = 0; i < loopSize; i++) {\n\t\t\t\t\t\t//temp comment out\n\t\t\t\t\t\t//if (attrib_tornado_severity > 0) {\n\n\t\t\t\t\t\t//Weather.dbg(\"spawn\");\n\n\t\t\t\t\t\t//trim!\n\t\t\t\t\t\tif (listParticlesFunnel.size() >= sizeMaxFunnelParticles) {\n\t\t\t\t\t\t\tlistParticlesFunnel.get(0).remove();\n\t\t\t\t\t\t\tlistParticlesFunnel.remove(0);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (listParticlesFunnel.size() < sizeMaxFunnelParticles) {\n\n\n\t\t\t\t\t\t\tVec3 tryPos = new Vec3(pos.x + (rand.nextDouble() * spawnRad) - (rand.nextDouble() * spawnRad), pos.y, pos.z + (rand.nextDouble() * spawnRad) - (rand.nextDouble() * spawnRad));\n\t\t\t\t\t\t\t//int y = entP.world.getPrecipitationHeight((int)tryPos.x, (int)tryPos.z);\n\n\t\t\t\t\t\t\tif (tryPos.distanceTo(playerAdjPos) < maxSpawnDistFromPlayer) {\n\t\t\t\t\t\t\t\tEntityRotFX particle;\n\t\t\t\t\t\t\t\tif (!isFirenado/* && false*/) {\n\t\t\t\t\t\t\t\t\tif (WeatherUtil.isAprilFoolsDay() && !Weather.isLoveTropicsInstalled()) {\n\t\t\t\t\t\t\t\t\t\tparticle = spawnFogParticle(tryPos.x, posBaseFormationPos.y, tryPos.z, 1, ParticleRegistry.potato);\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tparticle = spawnFogParticle(tryPos.x, posBaseFormationPos.y, tryPos.z, 1);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tparticle = spawnFogParticle(tryPos.x, posBaseFormationPos.y, tryPos.z, 1, ParticleRegistry.cloud256_fire);\n\n\t\t\t\t\t\t\t\t}\n\n\n\t\t\t\t\t\t\t\t//move these to a damn profile damnit!\n\t\t\t\t\t\t\t\tparticle.setMaxAge(150 + ((levelCurIntensityStage - 1) * 100) + rand.nextInt(100));\n\n\t\t\t\t\t\t\t\tfloat baseBright = 0.3F;\n\t\t\t\t\t\t\t\tfloat randFloat = (rand.nextFloat() * 0.6F);\n\n\t\t\t\t\t\t\t\tparticle.rotationYaw = rand.nextInt(360);\n\n\t\t\t\t\t\t\t\tfloat finalBright = Math.min(1F, baseBright + randFloat);\n\n\t\t\t\t\t\t\t\t//highwind aka spout in this current code location\n\t\t\t\t\t\t\t\tif (levelCurIntensityStage == STATE_HIGHWIND) {\n\t\t\t\t\t\t\t\t\tparticle.setScale(150 * 0.15F);\n\t\t\t\t\t\t\t\t\tparticle.setColor(finalBright - 0.2F, finalBright - 0.2F, finalBright);\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tparticle.setScale(250 * 0.15F);\n\t\t\t\t\t\t\t\t\tparticle.setColor(finalBright, finalBright, finalBright);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (isFirenado) {\n\t\t\t\t\t\t\t\t\tparticle.setColor(1F, 1F, 1F);\n\t\t\t\t\t\t\t\t\tparticle.setScale(particle.getScale() * 0.7F);\n\t\t\t\t\t\t\t\t}\n\n\n\t\t\t\t\t\t\t\tlistParticlesFunnel.add(particle);\n\n\t\t\t\t\t\t\t\t//System.out.println(listParticlesFunnel.size());\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t//Weather.dbg(\"particles maxed\");\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (int i = 0; i < listParticlesFunnel.size(); i++) {\n\t\t\t\tEntityRotFX ent = listParticlesFunnel.get(i);\n\t\t\t\t//System.out.println(ent.getPosY());\n\t\t\t\tif (!ent.isAlive()) {\n\t\t\t\t\tlistParticlesFunnel.remove(ent);\n\t\t\t\t} else if (ent.getPosY() > pos.y) {\n\t\t\t\t\tent.remove();\n\t\t\t\t\tlistParticlesFunnel.remove(ent);\n\t\t\t\t\t//System.out.println(\"asd\");\n\t\t\t\t} else {\n\t\t\t\t\tdouble var16 = this.pos.x - ent.getPosX();\n\t\t\t\t\tdouble var18 = this.pos.z - ent.getPosZ();\n\t\t\t\t\tent.rotationYaw = -(float) (Math.atan2(var18, var16) * 180.0D / Math.PI) - 90.0F;\n\t\t\t\t\tent.rotationYaw -= ent.getEntityId() % 90;\n\t\t\t\t\tent.rotationPitch = 30F;\n\n\t\t\t\t\t//fade spout blue to grey\n\t\t\t\t\tif (levelCurIntensityStage == STATE_HIGHWIND) {\n\t\t\t\t\t\tint fadingDistStart = 30;\n\t\t\t\t\t\tif (ent.getPosY() > posGround.y + fadingDistStart) {\n\t\t\t\t\t\t\tfloat maxVal = ent.bCol;\n\t\t\t\t\t\t\tfloat fadeRate = 0.002F;\n\t\t\t\t\t\t\tent.setColor(Math.min(maxVal, ent.rCol + fadeRate), Math.min(maxVal, ent.gCol + fadeRate), maxVal);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tspinEntity(ent);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (isTornadoFormingOrGreater() || (attrib_waterSpout)) {\n\t\t\tif (manager.getWorld().getGameTime() % 5 == 0) {\n\t\t\t\t//CULog.dbg(\"listParticlesDebris.size(): \" + listParticlesDebris.size());\n\t\t\t}\n\n\t\t\tfor (int i = 0; i < listParticlesDebris.size(); i++) {\n\t\t\t\tEntityRotFX ent = listParticlesDebris.get(i);\n\t\t\t\tif (!ent.isAlive()/* && false*/) {\n\t\t\t\t\t//CULog.dbg(\"removed: \" + ent);\n\t\t\t\t\tlistParticlesDebris.remove(ent);\n\t\t\t\t} else {\n\n\t\t\t\t\t/*this.xxa *= 0.98F;\n\t\t\t\t\tthis.zza *= 0.98F;*/\n\n\t\t\t\t\tdouble speedXZ = Math.sqrt(ent.getMotionX() * ent.getMotionX() + ent.getMotionY() * ent.getMotionY() + ent.getMotionZ() * ent.getMotionZ());\n\t\t\t\t\tif (speedXZ < 30) {\n\t\t\t\t\t\tVec3 motion = spinObject(ent.getPos(), new Vec3(ent.getMotionX(), ent.getMotionY(), ent.getMotionZ()), false, 0.91F, 1F, ent instanceof ParticleCube, 0);\n\t\t\t\t\t\t//Vec3 motion = spinObject(ent.getPos(), new Vec3(ent.getMotionX(), ent.getMotionY(), ent.getMotionZ()), false, 0.85F);\n\t\t\t\t\t\tfloat damp = 1F;\n\t\t\t\t\t\tdamp = ent.getWindWeight() / 5F;\n\t\t\t\t\t\tmotion = motion.multiply(damp, 1F, damp);\n\t\t\t\t\t\t//System.out.println(\"motion: \" + motion);\n\t\t\t\t\t\tent.setMotionX(motion.x);\n\t\t\t\t\t\tent.setMotionY(motion.y);\n\t\t\t\t\t\tent.setMotionZ(motion.z);\n\n\t\t\t\t\t\t//trying to match entity drag and gravity\n\t\t\t\t\t\t/*ent.setMotionX(ent.getMotionX() * 0.91);\n\t\t\t\t\t\tent.setMotionY(ent.getMotionY() - 0.08);\n\t\t\t\t\t\tent.setMotionZ(ent.getMotionZ() * 0.91);*/\n\n\t\t\t\t\t\t//ent.setMotionX(ent.getMotionX() * 0.91);\n\t\t\t\t\t\t//ent.setMotionY(ent.getMotionY() - 0.08);\n\t\t\t\t\t\t//ent.setMotionZ(ent.getMotionZ() * 0.91);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\tfor (int i = 0; i < listParticlesCloud.size(); i++) {\n\t\t\tEntityRotFX ent = listParticlesCloud.get(i);\n\t\t\tif (!ent.isAlive()) {\n\t\t\t\tlistParticlesCloud.remove(ent);\n\t\t\t} else {\n\t\t\t\t//ent.posX = pos.x + i*10;\n\t\t\t\t/*float radius = 50 + (i/1F);\n\t\t\t\tfloat posX = (float) Math.sin(ent.getEntityId());\n\t\t\t\tfloat posZ = (float) Math.cos(ent.getEntityId());\n\t\t\t\tent.setPosition(pos.x + posX*radius, ent.posY, pos.z + posZ*radius);*/\n\t\t        \n\t\t\t\tdouble curSpeed = Math.sqrt(ent.getMotionX() * ent.getMotionX() + ent.getMotionY() * ent.getMotionY() + ent.getMotionZ() * ent.getMotionZ());\n\t\t\t\t\n\t\t\t\tdouble curDist = ent.getDistance(getPosTop().x, ent.getPosY(), getPosTop().z);\n\n\t\t\t\tfloat dropDownRange = 15F;\n\t\t        \n\t\t        float extraDropCalc = 0;\n\t\t        if (curDist < 200 && ent.getEntityId() % 20 < 5) {\n\t\t\t        //cyclone and hurricane dropdown modifications here\n\t\t        \textraDropCalc = ((ent.getEntityId() % 20) * dropDownRange);\n\t\t        \tif (isCycloneFormingOrGreater()) {\n\t\t        \t\textraDropCalc = ((ent.getEntityId() % 20) * dropDownRange * 5F);\n\t\t        \t\t//Weather.dbg(\"extraDropCalc: \" + extraDropCalc);\n\t\t        \t}\n\t\t        }\n\t\t        \n\t\t        \n\t\t\t\t\n\t\t\t\tif (isSpinning()) {\n\t\t\t\t\tdouble speed = spinSpeed + (rand.nextDouble() * 0.01D);\n\t\t\t\t\tdouble distt = size;//300D;\n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t\tdouble vecX = ent.getPosX() - getPosTop().x;\n\t\t\t        double vecZ = ent.getPosZ() - getPosTop().z;\n\t\t\t        float angle = (float)(Math.atan2(vecZ, vecX) * 180.0D / Math.PI);\n\t\t\t        //System.out.println(\"angle: \" + angle);\n\t\t\t        \n\t\t\t        //fix speed causing inner part of formation to have a gap\n\t\t\t        angle += speed * 50D;\n\t\t\t        //angle += 20;\n\t\t\t        \n\t\t\t        angle -= (ent.getEntityId() % 10) * 3D;\n\t\t\t        \n\t\t\t        //random addition\n\t\t\t        angle += rand.nextInt(10) - rand.nextInt(10);\n\t\t\t        \n\t\t\t        if (curDist > distt) {\n\t\t\t        \t//System.out.println(\"curving\");\n\t\t\t        \tangle += 40;\n\t\t\t        \t//speed = 1D;\n\t\t\t        }\n\t\t\t        \n\t\t\t        //keep some near always - this is the lower formation part\n\t\t\t        if (ent.getEntityId() % 20 < 5) {\n\t\t\t        \tif (levelCurIntensityStage >= STATE_FORMING) {\n\t\t\t        \t\tif (stormType == TYPE_WATER) {\n\t\t\t        \t\t\tangle += 40 + ((ent.getEntityId() % 5) * 4);\n\t\t\t        \t\t\tif (curDist > 150 + ((levelCurIntensityStage-levelStormIntensityFormingStartVal+1) * 30)) {\n\t\t\t        \t\t\t\tangle += 10;\n\t\t\t        \t\t\t}\n\t\t\t        \t\t} else {\n\t\t\t        \t\t\tangle += 30 + ((ent.getEntityId() % 5) * 4);\n\t\t\t        \t\t}\n\t\t\t        \t\t\n\t\t\t        \t} else {\n\t\t\t        \t\t//make a wider spinning lower area of cloud, for high wind\n\t\t\t        \t\tif (curDist > 150) {\n\t\t\t        \t\t\tangle += 50 + ((ent.getEntityId() % 5) * 4);\n\t\t\t        \t\t}\n\t\t\t        \t}\n\n\t\t\t        \tdouble diffX = this.getPosTop().x - ent.getPosX();\n\t\t                double diffZ = this.getPosTop().z - ent.getPosZ();\n\t\t                ent.rotationYaw = (float)(Math.atan2(diffZ, diffX) * 180.0D / Math.PI) - 90.0F;\n\t\t                ent.rotationYaw = -(float)(Math.atan2(diffZ, diffX) * 180.0D / Math.PI) - 90.0F;\n\t\t                //ent.rotationYaw = (float) Math.toDegrees(Math.atan2(diffZ, diffX)) + 90F;\n\t\t                ent.rotationPitch = 20F - (ent.getEntityId() % 10);\n\t\t\t        }\n\t\t\t        \n\t\t\t        \n\t\t\t        \n\t\n\t\t\t        \n\t\t\t        /*if (curDist < 30) {\n\t\t\t        \tent.motionY -= 0.2D;\n\t\t\t        \tif (ent.rotationPitch > 0) {\n\t\t\t        \t\tent.rotationPitch--;\n\t\t\t        \t} else if (ent.rotationPitch < 0) {\n\t\t\t        \t\tent.rotationPitch++;\n\t\t\t        \t} else {\n\t\t\t        \t\tent.rotationPitch = 0;\n\t\t\t        \t}\n\t\t\t        \t\n\t\t\t        \tangle = -45;\n\t\t\t        } else {\n\t\t\t        \tif (ent.rotationPitch > 90) {\n\t\t\t        \t\tent.rotationPitch--;\n\t\t\t        \t} else if (ent.rotationPitch < 90) {\n\t\t\t        \t\tent.rotationPitch++;\n\t\t\t        \t} else {\n\t\t\t        \t\tent.rotationPitch = 90;\n\t\t\t        \t}\n\t\t\t        \t//angle = 90;\n\t\t\t        \t\n\t\t\t        }*/\n\t\t\t        \n\t\t\t        \n\t\t\t        \n\t\t\t        if (curSpeed < speed * 20D) {\n\t\t\t        \tent.setMotionX(ent.getMotionX() + -Math.sin(Math.toRadians(angle)) * speed);\n\t\t\t\t        ent.setMotionZ(ent.getMotionZ() + Math.cos(Math.toRadians(angle)) * speed);\n\t\t\t        }\n\t\t\t\t} else {\n\t\t\t\t\tfloat cloudMoveAmp = 0.2F * (1 + layer);\n\t\t\t\t\t\n\t\t\t\t\tfloat speed = getAdjustedSpeed() * cloudMoveAmp;\n\t\t\t\t\tfloat angle = getAdjustedAngle();\n\n\t\t\t\t\t//TODO: prevent new particles spawning inside or near solid blocks\n\n\t\t\t\t\tif ((manager.getWorld().getGameTime()+this.ID) % 40 == 0) {\n\t\t\t\t\t\tent.avoidTerrainAngle = getAvoidAngleIfTerrainAtOrAheadOfPosition(angle, ent.getPos());\n\t\t\t\t\t}\n\n\t\t\t\t\tangle += ent.avoidTerrainAngle;\n\n\t\t\t\t\tif (ent.avoidTerrainAngle != 0) {\n\t\t\t\t\t\t/*float angleAdj = 90;\n\t\t\t\t\t\tif (this.ID % 2 == 0) {\n\t\t\t\t\t\t\tangleAdj = -90;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tangle += angleAdj;*/\n\n\t\t\t\t\t\tspeed *= 0.5D;\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tdropDownRange = 5;\n\t\t\t        if (/*curDist < 200 && */ent.getEntityId() % 20 < 5) {\n\t\t\t        \textraDropCalc = ((ent.getEntityId() % 20) * dropDownRange);\n\t\t\t        }\n\t\t\t\t\t\n\t\t\t\t\tif (curSpeed < speed * 1D) {\n\t\t\t        \tent.setMotionX(ent.getMotionX() + -Math.sin(Math.toRadians(angle)) * speed);\n\t\t\t\t        ent.setMotionZ(ent.getMotionZ() + Math.cos(Math.toRadians(angle)) * speed);\n\t\t\t        }\n\t\t\t\t}\n\t\t        \n\t\t\t\tif (Math.abs(ent.getPosY() - (getPosTop().y - extraDropCalc)) > 2F) {\n\t\t\t        if (ent.getPosY() < getPosTop().y - extraDropCalc) {\n\t\t        \t\tent.setMotionY(ent.getMotionY() + 0.1D);\n\t\t        \t} else {\n\t\t        \t\tent.setMotionY(ent.getMotionY() - 0.1D);\n\t\t        \t}\n\t\t\t\t}\n\t\t        \n\t\t\t\tfloat dropDownSpeedMax = 0.15F;\n\t\t\t\t\n\t\t\t\tif (isCycloneFormingOrGreater()) {\n\t\t\t\t\tdropDownSpeedMax = 0.9F;\n\t\t\t\t}\n\t\t\t\t\n\t\t        if (ent.getMotionY() < -dropDownSpeedMax) {\n\t\t        \tent.setMotionY(-dropDownSpeedMax);\n\t\t        }\n\t\t        \n\t\t        if (ent.getMotionY() > dropDownSpeedMax) {\n\t\t        \tent.setMotionY(dropDownSpeedMax);\n\t\t        }\n\t\t        \n\t\t        //double distToGround = ent.world.getHeightValue((int)pos.x, (int)pos.z);\n\t\t        \n\t\t        //ent.setPosition(ent.posX, pos.y, ent.posZ);\n\t\t\t}\n\t\t\t/*if (ent.getAge() > 300) {\n\t\t\t\tent.remove();\n\t\t\t\tlistParticles.remove(ent);\n\t\t\t}*/\n\t\t}\n\t\t\n\t\tfor (int i = 0; i < listParticlesGround.size(); i++) {\n\t\t\tEntityRotFX ent = listParticlesGround.get(i);\n\t\t\t\n\t\t\tdouble curDist = ent.getDistance(pos.x, ent.getPosY(), pos.z);\n\t\t\t\n\t\t\tif (!ent.isAlive()) {\n\t\t\t\tlistParticlesGround.remove(ent);\n\t\t\t} else {\n\t\t\t\tdouble curSpeed = Math.sqrt(ent.getMotionX() * ent.getMotionX() + ent.getMotionY() * ent.getMotionY() + ent.getMotionZ() * ent.getMotionZ());\n\t\t\t\n\t\t\t\tdouble speed = Math.max(0.2F, 5F * spinSpeed) + (rand.nextDouble() * 0.01D);\n\t\t\t\tdouble distt = size;//300D;\n\t\t\t\t\n\t\t\t\t\n\t\t\t\tdouble vecX = ent.getPosX() - pos.x;\n\t\t        double vecZ = ent.getPosZ() - pos.z;\n\t\t        float angle = (float)(Math.atan2(vecZ, vecX) * 180.0D / Math.PI);\n\t\t        \n\t\t        angle += 85;\n\t\t        \n\t\t        int maxParticleSize = 60;\n\t\t        \n\t\t        if (stormType == TYPE_WATER) {\n\t\t        \tmaxParticleSize = 150;\n\t\t        \tspeed /= 5D;\n\t\t\t\t}\n\t\t        \n\t\t        ent.setScale((float) Math.min(maxParticleSize * 0.15F, curDist * 2F * 0.15F));\n\t\t        \n\t\t        if (curDist < 20) {\n\t\t        \tent.remove();\n\t\t        }\n\n\t        \tdouble var16 = this.pos.x - ent.getPosX();\n                double var18 = this.pos.z - ent.getPosZ();\n\t\t        //ent.rotationYaw += 5;//(float)(Math.atan2(var18, var16) * 180.0D / Math.PI) - 90.0F;\n                //ent.rotationPitch = 0;//-20F - (ent.getEntityId() % 10);\n                \n                if (curSpeed < speed * 20D) {\n\t\t        \tent.setMotionX(ent.getMotionX() + -Math.sin(Math.toRadians(angle)) * speed);\n\t\t\t        ent.setMotionZ(ent.getMotionZ() + Math.cos(Math.toRadians(angle)) * speed);\n\t\t        }\n\t\t\t}\n\t\t}\n\t\t\n\t\t//System.out.println(\"size: \" + listParticlesCloud.size());\n\t}\n\t\n\tpublic float getAdjustedSpeed() {\n\t\treturn manager.getWindManager().getWindSpeedForClouds();\n\t}\n\t\n\tpublic float getAdjustedAngle() {\n\t\tfloat angle = manager.getWindManager().getWindAngleForClouds();\n\t\t\n\t\t//float angleAdjust = Math.max(10, Math.min(45, 45F * levelTemperature * 0.6F));\n\t\tfloat angleAdjust = 45;\n\t\tfloat targetYaw = 0;\n\t\t\n\t\t//coldfronts go south to 0, warmfronts go north to 180\n\t\tif (levelTemperature > 0) {\n\t\t\t//Weather.dbg(\"warmer!\");\n\t\t\ttargetYaw = 180;\n\t\t} else {\n\t\t\t//Weather.dbg(\"colder!\");\n\t\t\ttargetYaw = 0;\n\t\t}\n\t\t\n\t\tfloat bestMove = Mth.wrapDegrees(targetYaw - angle);\n\t\t\n\t\tif (Math.abs(bestMove) < 180/* - (angleAdjust * 2)*/) {\n\t\t\tif (bestMove > 0) angle -= angleAdjust;\n\t\t\tif (bestMove < 0) angle += angleAdjust;\n\t\t}\n\n\t\tif (manager.getWorld().getGameTime() % 40 == 0) {\n\t\t\t//Weather.dbg(\"ID: \" + ID + \" - \" + manager.getWindManager().getWindAngleForClouds() + \" - final angle: \" + angle + \" levelTemperature: \" + levelTemperature);\n\t\t}\n\t\t\n\t\treturn angle;\n\t}\n\n\tpublic float getAvoidAngleIfTerrainAtOrAheadOfPosition(float angle, Vec3 pos) {\n\t\tdouble scanDistMax = 120;\n\t\tfor (int scanAngle = -20; scanAngle < 20; scanAngle += 10) {\n\t\t\tfor (double scanDistRange = 20; scanDistRange < scanDistMax; scanDistRange += 10) {\n\t\t\t\tdouble scanX = pos.x + (-Math.sin(Math.toRadians(angle + scanAngle)) * scanDistRange);\n\t\t\t\tdouble scanZ = pos.z + (Math.cos(Math.toRadians(angle + scanAngle)) * scanDistRange);\n\n\t\t\t\tint height = WeatherUtilBlock.getPrecipitationHeightSafe(this.manager.getWorld(), CoroUtilBlock.blockPos(scanX, 0, scanZ)).getY();\n\n\t\t\t\tif (pos.y < height) {\n\t\t\t\t\tif (scanAngle <= 0) {\n\t\t\t\t\t\treturn 90;\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn -90;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn 0;\n\t}\n\n\tpublic Player getRandomNearPlayer(Entity entityLineOfSightSource) {\n\t\t//Player player = manager.getWorld().getNearbyPlayers(TargetingConditions.forCombat().range(64.0D), )\n\n\t\tList<? extends Player> players = manager.getWorld().players();\n\t\tList<Player> playersNear = new ArrayList<>();\n\t\tfor (Player player : players) {\n\t\t\tif (!player.isSpectator() && player.position().distanceTo(posGround) < 196 && player.hasLineOfSight(entityLineOfSightSource)) {\n\t\t\t\tplayersNear.add(player);\n\t\t\t}\n\t\t}\n\t\tif (!playersNear.isEmpty()) {\n\t\t\tCollections.shuffle(playersNear);\n\t\t\treturn playersNear.get(0);\n\t\t}\n\t\treturn null;\n\t}\n\n\tpublic void spinEntityv2(Entity entity) {\n\n\t\tif (entity.getPersistentData().getBoolean(\"tornado_shoot\")) {\n\t\t\tif (!entity.getPersistentData().contains(\"tornado_shoot_target\")) {\n\t\t\t\tPlayer player = getRandomNearPlayer(entity);\n\t\t\t\tif (player != null) {\n\t\t\t\t\tentity.getPersistentData().putString(\"tornado_shoot_target\", player.getUUID().toString());\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tUUID uuid = UUID.fromString(entity.getPersistentData().getString(\"tornado_shoot_target\"));\n\t\t\t\tPlayer player = manager.getWorld().getPlayerByUUID(uuid);\n\t\t\t\tif (player != null) {\n\t\t\t\t\tdouble vecx = player.position().x - entity.position().x;\n\t\t\t\t\tdouble vecy = player.position().y - entity.position().y;\n\t\t\t\t\tdouble vecz = player.position().z - entity.position().z;\n\t\t\t\t\tVec3 vec = new Vec3(vecx, vecy, vecz);\n\t\t\t\t\tdouble speed = 2F;\n\t\t\t\t\tvec = vec.normalize().multiply(speed, speed, speed);\n\t\t\t\t\tentity.setDeltaMovement(vec.x, vec.y, vec.z);\n\t\t\t\t\tfloat rotationYaw = (float)(Mth.atan2(vecz, vecx) * 180.0D / Math.PI) - 90.0F;\n\t\t\t\t\tentity.setYRot(rotationYaw);\n\n\t\t\t\t\tif (player.position().distanceTo(entity.position()) < 3 && entity.isAlive()) {\n\t\t\t\t\t\tplayer.hurt(manager.getWorld().damageSources().cactus(), 1.5F);\n\t\t\t\t\t\tentity.kill();\n\t\t\t\t\t}\n\n\t\t\t\t\tif (entity.isInWater()) {\n\t\t\t\t\t\tentity.getPersistentData().putBoolean(\"tornado_shoot\", false);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tfloat dampenY = 1F;\n\t\t\tfloat dampenXZ = 1F;\n\t\t\tfloat grabAdj = 0F;\n\n\t\t\tdouble entHeightFromBase = Math.max(0.1F, entity.getY() - posBaseFormationPos.y);\n\n\t\t\tif (entity instanceof Player) {\n\t\t\t\tif (((Player) entity).getMainHandItem().getItem().toString().contains(\"acid_repellent_umbrella\") ||\n\t\t\t\t\t\t((Player) entity).getOffhandItem().getItem().toString().contains(\"acid_repellent_umbrella\")) {\n\t\t\t\t\tif (entHeightFromBase > 80) {\n\t\t\t\t\t\tdampenY = 0.7F;\n\t\t\t\t\t}\n\t\t\t\t\tdampenXZ = 1.4F;\n\t\t\t\t\tgrabAdj -= 20;\n\t\t\t\t}\n\n\t\t\t\tfloat weightAdj = WeatherUtilEntity.getWeightAdjFromEquipment(1F, (Player) entity);\n\n\t\t\t\tdampenY /= weightAdj;\n\t\t\t\tdampenXZ /= weightAdj;\n\t\t\t}\n\n\n\n\t\t\tentity.setDeltaMovement(spinObject(entity.position(), entity.getDeltaMovement(), entity instanceof Player, dampenXZ, dampenY, false, grabAdj));\n\n\t\t\tif (!ConfigTornado.Storm_Tornado_fallDamage || CoroUtilEntOrParticle.getMotionY(entity) > -0.8)\n\t\t\t{\n\t\t\t\tentity.fallDistance = 0F;\n\t\t\t}\n\n\t\t\tif (isFirenado) {\n\t\t\t\tVec3 posEnt = entity.position();\n\t\t\t\tVec3 posFunnel = getFunnelCenter(posEnt);\n\t\t\t\tdouble distXZ = entity.position().distanceTo(new Vec3(posFunnel.x, posEnt.y, posFunnel.z));\n\t\t\t\tif (entHeightFromBase > 5 && entHeightFromBase < 70 && distXZ < (entHeightFromBase * 1.3)) {\n\t\t\t\t\tentity.setSecondsOnFire(6);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (entHeightFromBase > 90) {\n\t\t\t\tif (Weather.isLoveTropicsInstalled()) {\n\t\t\t\t\t//TODO: for LT, reenable or make it a soft dependency somehow\n\t\t\t\t\t/*if (isSharknado() && entity instanceof SharkEntity) {\n\t\t\t\t\t\tentity.getPersistentData().putBoolean(\"tornado_shoot\", true);\n\t\t\t\t\t}*/\n\t\t\t\t\tif (isSharknado() && entity instanceof Dolphin) {\n\t\t\t\t\t\tentity.getPersistentData().putBoolean(\"tornado_shoot\", true);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tif (isSharknado() && entity instanceof Dolphin) {\n\t\t\t\t\t\tentity.getPersistentData().putBoolean(\"tornado_shoot\", true);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\n\t}\n\n\tpublic Vec3 getFunnelCenter(Vec3 position) {\n\t\tVec3 posCenter = getPosTop();\n\t\tfor (Layer layer : tornadoFunnelSimple.listLayers) {\n\t\t\tif (position.y - 1.5F < layer.getPos().y) {\n\t\t\t\tposCenter = layer.getPos();\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn posCenter;\n\t}\n\n\tpublic Vec3 spinObject(Vec3 position, Vec3 motion, boolean forPlayer, float dampenXZ, float dampenY, boolean forCube, float grabAdj) {\n\t\tVec3 posCenter = getFunnelCenter(position);\n\n\t\tdouble vecx = posCenter.x - position.x;\n\t\tdouble vecz = posCenter.z - position.z;\n\n\t\tVec3 vecXZ = new Vec3(posCenter.x, position.y, posCenter.z);\n\n\t\tdouble distXZMax = tornadoFunnelSimple.getConfig().getEntityPullDistXZ();\n\t\tdouble distXZMaxForYGrab = tornadoFunnelSimple.getConfig().getEntityPullDistXZForY();\n\t\tdouble distXZ = Math.min(position.distanceTo(vecXZ), distXZMax);\n\t\tdouble distXZForYGrab = Math.min(position.distanceTo(vecXZ), distXZMaxForYGrab);\n\n\t\tdouble grabAmp = (float)(levelCurIntensityStage - STATE_FORMING) / (float)(STATE_STAGE5 - STATE_FORMING);\n\n\t\tfloat angle = (float)(Mth.atan2(vecz, vecx) * 180.0D / Math.PI + 180F);\n\n\t\t//more is tighter\n\t\tangle += ConfigTornado.Storm_Tornado_extraGrabAngle;\n\t\tif (pet) angle += 50;\n\n\t\tif (forCube) {\n\t\t\tangle += 20;\n\t\t}\n\n\t\tangle += grabAdj;\n\n\t\tdouble entHeightFromBase = Math.max(0.1F, position.y - posBaseFormationPos.y);\n\t\tdouble heightMathMax = 50 * 2.5;\n\t\theightMathMax = 50 * 3.5;\n\t\t//amp from new size here?\n\t\tif (baby) heightMathMax = 15;\n\t\tif (pet) heightMathMax = 4;\n\t\tif (playerControlled) heightMathMax = 40;\n\t\t//double heightMathMax = tornadoFunnelSimple.getConfig().getHeight();\n\t\tdouble heightAmp = (heightMathMax - entHeightFromBase) / heightMathMax;\n\t\t//CULog.dbg(\"heightAmp: \" + heightAmp);\n\n\t\tif (!pet) {\n\t\t\tangle += (40 * heightAmp);\n\t\t}\n\n\t\t//wider grab as tornado gets bigger\n\t\tangle -= 40 * grabAmp;\n\n\t\tangle = (float) Math.toRadians(angle);\n\t\tdouble pullStrength = 0.2;\n\t\tdouble pullStrengthY = 0.2;\n\t\tpullStrengthY += 0.2 * grabAmp;\n\t\tdouble pullYAmp = 1D;\n\t\t//as tornado shrinks to and from forming, adjust its pull power\n\t\tif (levelCurIntensityStage == STATE_FORMING) {\n\t\t\tpullYAmp = levelCurStagesIntensity;\n\t\t}\n\t\tpullStrengthY *= pullYAmp;\n\t\tpullStrength *= pullYAmp;\n\t\tif (pet) pullStrength = 0.05;\n\t\tif (pet) pullStrengthY = 0.05;\n\t\tif (sharknado && forPlayer) pullStrength = 0.05;\n\t\tif (sharknado && forPlayer) pullStrengthY = 0.1;\n\t\tdouble pullY = pullStrengthY * (distXZMaxForYGrab - distXZForYGrab) / distXZMaxForYGrab;\n\t\tdouble pullXZ = pullStrength * (distXZMax - distXZ) / distXZMax;\n\t\tdouble xx = -Math.sin(angle) * pullXZ;\n\t\tdouble zz = Math.cos(angle) * pullXZ;\n\t\tdouble yy = pullY;\n\n\t\tif (!baby && motion.y > 0.5F) {\n\t\t\tyy = 0;\n\t\t}\n\n\t\tif (pet && motion.y > 0.15F) {\n\t\t\tyy = 0;\n\t\t}\n\n\t\treturn new Vec3(motion.x + (xx * dampenXZ), motion.y + (yy * dampenY), motion.z + (zz * dampenXZ));\n\t}\n\t\n\tpublic void spinEntity(Object entity1) {\n\t\t\n\t\tStormObject entT = this;\n\t\tStormObject entity = this;\n\t\tWeatherEntityConfig conf = getWeatherEntityConfigForStorm();//WeatherTypes.weatherEntTypes.get(curWeatherType);\n\t\t\n\t\tRandom rand = new Random();\n\t\t\n    \t/*if (entity instanceof EntTornado) {\n    \t\tentT = (EntTornado) entity;\n    \t}*/\n    \n    \tboolean forTornado = true;//entT != null;\n    \t\n    \tLevel world = CoroUtilEntOrParticle.getWorld(entity1);\n    \tlong worldTime = world.getGameTime();\n    \t\n    \tEntity ent = null;\n    \tif (entity1 instanceof Entity) {\n    \t\tent = (Entity) entity1;\n    \t}\n    \t\n        //ConfigTornado.Storm_Tornado_height;\n        double radius = 10D;\n        double scale = conf.tornadoWidthScale;\n        double d1 = entity.pos.x - CoroUtilEntOrParticle.getPosX(entity1);\n        double d2 = entity.pos.z - CoroUtilEntOrParticle.getPosZ(entity1);\n        \n        if (conf.type == conf.TYPE_SPOUT) {\n        \tfloat range = 30F * (float) Math.sin((Math.toRadians(((worldTime * 0.5F) + (ID * 50)) % 360)));\n        \tfloat heightPercent = (float) (1F - ((CoroUtilEntOrParticle.getPosY(entity1) - posGround.y) / (pos.y - posGround.y)));\n        \tfloat posOffsetX = (float) Math.sin((Math.toRadians(heightPercent * 360F)));\n        \tfloat posOffsetZ = (float) -Math.cos((Math.toRadians(heightPercent * 360F)));\n        \t//Weather.dbg(\"posOffset: \" + posOffset);\n        \t//d1 += 50F*heightPercent*posOffset;\n        \td1 += range*posOffsetX;\n        \td2 += range*posOffsetZ;\n        }\n        \n        float f = (float)((Math.atan2(d2, d1) * 180D) / Math.PI) - 90F;\n        float f1;\n\n        for (f1 = f; f1 < -180F; f1 += 360F) { }\n\n        for (; f1 >= 180F; f1 -= 360F) { }\n\n        double distY = entity.pos.y - CoroUtilEntOrParticle.getPosY(entity1);\n        double distXZ = Math.sqrt(Math.abs(d1)) + Math.sqrt(Math.abs(d2));\n\n        if (CoroUtilEntOrParticle.getPosY(entity1) - entity.pos.y < 0.0D)\n        {\n            distY = 1.0D;\n        }\n        else\n        {\n            distY = CoroUtilEntOrParticle.getPosY(entity1) - entity.pos.y;\n        }\n\n        if (distY > maxHeight)\n        {\n            distY = maxHeight;\n        }\n\n        //float weight = WeatherUtilEntity.getWeight(entity1, forTornado);\n        float weight = WeatherUtilEntity.getWeight(entity1);\n        double grab = (10D / weight)/* / ((distY / maxHeight) * 1D)*/ * ((Math.abs((maxHeight - distY)) / maxHeight));\n        float pullY = 0.0F;\n\n        //some random y pull\n        if (rand.nextInt(5) != 0)\n        {\n            //pullY = 0.035F;\n        }\n\n        if (distXZ > 5D)\n        {\n            grab = grab * (radius / distXZ);\n        }\n        \n        //Weather.dbg(\"TEMP!!!!\");\n        //WeatherTypes.initWeatherTypes();\n\n        pullY += (float)(conf.tornadoLiftRate / (weight / 2F)/* * (Math.abs(radius - distXZ) / radius)*/);\n        \n        \n        if (entity1 instanceof Player)\n        {\n            double adjPull = 0.2D / ((weight * ((distXZ + 1D) / radius)));\n            /*if (!entity1.onGround) {\n            \tadjPull /= (((float)(((double)playerInAirTime+1D) / 200D)) * 15D);\n            }*/\n            pullY += adjPull;\n            //0.2D / ((getWeight(entity1) * ((distXZ+1D) / radius)) * (((distY) / maxHeight)) * 3D);\n            //grab = grab + (10D * ((distY / maxHeight) * 1D));\n            //double adjGrab = (10D * (((float)(((double)WeatherUtilEntity.playerInAirTime + 1D) / 400D))));\n            double adjGrab = (10D * (((float)(((double)0 + 1D) / 400D))));\n\n            if (adjGrab > 50)\n            {\n                adjGrab = 50D;\n            }\n            \n            if (adjGrab < -50)\n            {\n                adjGrab = -50D;\n            }\n\n            grab = grab - adjGrab;\n\n            if (CoroUtilEntOrParticle.getMotionY(entity1) > -0.8)\n            {\n            \t//System.out.println(entity1.motionY);\n                ent.fallDistance = 0F;\n            }\n\n            \n        }\n        else if (entity1 instanceof LivingEntity)\n        {\n            double adjPull = 0.005D / ((weight * ((distXZ + 1D) / radius)));\n            /*if (!entity1.onGround) {\n            \tadjPull /= (((float)(((double)playerInAirTime+1D) / 200D)) * 15D);\n            }*/\n            pullY += adjPull;\n            //0.2D / ((getWeight(entity1) * ((distXZ+1D) / radius)) * (((distY) / maxHeight)) * 3D);\n            //grab = grab + (10D * ((distY / maxHeight) * 1D));\n            int airTime = ent.getPersistentData().getInt(\"timeInAir\");\n            double adjGrab = (10D * (((float)(((double)(airTime) + 1D) / 400D))));\n\n            if (adjGrab > 50)\n            {\n                adjGrab = 50D;\n            }\n            \n            if (adjGrab < -50)\n            {\n                adjGrab = -50D;\n            }\n\n            grab = grab - adjGrab;\n\n            if (ent.getDeltaMovement().y > -1.5)\n            {\n                ent.fallDistance = 0F;\n            }\n            \n            if (ent.getDeltaMovement().y > 0.3F) ent.setDeltaMovement(ent.getDeltaMovement().x, 0.3F, ent.getDeltaMovement().z);\n\n            if (forTornado) ent.setOnGround(false);\n\n            //its always raining during these, might as well extinguish them\n\t\t\tif (!isFirenado) {\n\t\t\t\tent.clearFire();\n\t\t\t}\n\n            //System.out.println(adjPull);\n        }\n        \n        \n        grab += conf.relTornadoSize;\n        \n        double profileAngle = Math.max(1, (75D + grab - (10D * scale)));\n        \n        f1 = (float)((double)f1 + profileAngle);\n        \n        //debug - dont do this here, breaks server\n        /*if (entity1 instanceof EntityIconFX) {\n        \tif (entity1.getEntityId() % 20 < 5) {\n        \t\tif (((EntityIconFX) entity1).renderOrder != -1) {\n        \t\t\tif (entity1.world.getGameTime() % 40 == 0) {\n        \t\t\t\t//Weather.dbg(\"final grab angle: \" + profileAngle);\n        \t\t\t}\n        \t\t}\n        \t}\n        }*/\n        \n        if (entT != null) {\n        \t\n        \tif (entT.scale != 1F) f1 += 20 - (20 * entT.scale);\n        }\n        \n        float f3 = (float)Math.cos(-f1 * 0.01745329F - (float)Math.PI);\n        float f4 = (float)Math.sin(-f1 * 0.01745329F - (float)Math.PI);\n        float f5 = conf.tornadoPullRate * 1;\n        \n        if (entT != null) {\n        \tif (entT.scale != 1F) f5 *= entT.scale * 1.2F;\n        }\n\n        if (entity1 instanceof LivingEntity)\n        {\n            //f5 /= (WeatherUtilEntity.getWeight(entity1, forTornado) * ((distXZ + 1D) / radius));\n            f5 /= (WeatherUtilEntity.getWeight(entity1) * ((distXZ + 1D) / radius));\n        }\n        \n        //if player and not spout\n        if (entity1 instanceof Player && conf.type != 0) {\n        \t//System.out.println(\"grab: \" + f5);\n        \tif (ent.onGround()) {\n        \t\tf5 *= 10.5F;\n        \t} else {\n        \t\tf5 *= 5F;\n        \t}\n        \t//if (entity1.world.rand.nextInt(2) == 0) entity1.onGround = false;\n        } else if (entity1 instanceof LivingEntity && conf.type != 0) {\n        \tf5 *= 1.5F;\n        }\n\n        if (conf.type == conf.TYPE_SPOUT && entity1 instanceof LivingEntity) {\n        \tf5 *= 0.3F;\n        }\n        \n        float moveX = f3 * f5;\n        float moveZ = f4 * f5;\n        //tornado strength changes\n        float str = 1F;\n\n        /*if (entity instanceof EntTornado)\n        {\n            str = ((EntTornado)entity).strength;\n        }*/\n        \n        str = strength;\n        \n        if (conf.type == conf.TYPE_SPOUT && entity1 instanceof LivingEntity) {\n        \tstr *= 0.3F;\n        }\n\n        pullY *= str / 100F;\n        \n        if (entT != null) {\n        \tif (entT.scale != 1F) {\n        \t\tpullY *= entT.scale * 1.0F;\n        \t\tpullY += 0.002F;\n        \t}\n        }\n        \n        //prevent double+ pull on entities\n        if (entity1 instanceof Entity) {\n\t        long lastPullTime = ent.getPersistentData().getLong(\"lastPullTime\");\n\t        if (lastPullTime == worldTime) {\n\t        \t//System.out.println(\"preventing double pull\");\n\t        \tpullY = 0;\n\t        }\n\t        ent.getPersistentData().putLong(\"lastPullTime\", worldTime);\n        }\n        \n        setVel(entity1, -moveX, pullY, moveZ);\n\t}\n\t\n\tpublic void setVel(Object entity, float f, float f1, float f2)\n    {\n        CoroUtilEntOrParticle.setMotionX(entity, CoroUtilEntOrParticle.getMotionX(entity) + f);\n\t\tCoroUtilEntOrParticle.setMotionY(entity, CoroUtilEntOrParticle.getMotionY(entity) + f1);\n\t\tCoroUtilEntOrParticle.setMotionZ(entity, CoroUtilEntOrParticle.getMotionZ(entity) + f2);\n    }\n\n\t@OnlyIn(Dist.CLIENT)\n\tpublic EntityRotFX spawnFogParticle(double x, double y, double z, int parRenderOrder) {\n\t\treturn spawnFogParticle(x, y, z, parRenderOrder, ParticleRegistry.cloud256);\n\t}\n\t\n\t@OnlyIn(Dist.CLIENT)\n    public EntityRotFX spawnFogParticle(double x, double y, double z, int parRenderOrder, TextureAtlasSprite tex) {\n    \tdouble speed = 0D;\n\t\tRandom rand = new Random();\n    \tEntityRotFX entityfx = particleBehaviorFog.spawnNewParticleIconFX(Minecraft.getInstance().level, tex, x, y, z, (rand.nextDouble() - rand.nextDouble()) * speed, 0.0D/*(rand.nextDouble() - rand.nextDouble()) * speed*/, (rand.nextDouble() - rand.nextDouble()) * speed, parRenderOrder);\n\t\tparticleBehaviorFog.initParticle(entityfx);\n\t\t\n\t\t//potato\n\t\t//entityfx.setColor(1f, 1f, 1f);\n\t\t\n\t\t//lock y\n\t\t//entityfx.spawnY = (float) entityfx.posY;\n\t\t//entityfx.spawnY = ((int)200 - 5) + rand.nextFloat() * 5;\n\t\tentityfx.setCanCollide(false);\n    \tentityfx.callUpdatePB = false;\n\n\t\tdebugCloudTemperature = false;\n    \tboolean debug = debugCloudTemperature;\n    \t\n    \tif (debug) {\n    \t\t//entityfx.setMaxAge(50 + rand.nextInt(10));\n    \t} else {\n\t    \t\n    \t}\n    \t\n    \tif (levelCurIntensityStage == STATE_NORMAL) {\n    \t\tentityfx.setMaxAge(300 + rand.nextInt(100));\n    \t} else {\n    \t\tentityfx.setMaxAge((size/2) + rand.nextInt(100));\n    \t}\n    \t\n\t\t//pieces that move down with funnel need render order shift, also only for relevant storm formations\n\t\tif (entityfx.getEntityId() % 20 < 5 && isSpinning()) {\n\t\t\tentityfx.renderOrder = 1;\n\t\t\t\n\t\t\tentityfx.setMaxAge((size) + rand.nextInt(100));\n\t\t}\n    \t\n    \tfloat randFloat = (rand.nextFloat() * 0.6F);\n\t\tfloat baseBright = 0.7F;\n\t\tif (levelCurIntensityStage > STATE_NORMAL) {\n\t\t\tbaseBright = 0.2F;\n\t\t} else if (attrib_precipitation) {\n\t\t\tbaseBright = 0.2F;\n\t\t} else if (manager.isVanillaRainActiveOnServer) {\n\t\t\tbaseBright = 0.2F;\n\t\t} else {\n\t\t\tfloat adj = Math.min(1F, levelWater / levelWaterStartRaining) * 0.6F;\n\t\t\tbaseBright -= adj;\n\t\t}\n\t\t\n\t\t/*if (layer == 1) {\n\t\t\tbaseBright = 0.1F;\n\t\t}*/\n\t\t\n\t\tfloat finalBright = Math.min(1F, baseBright+randFloat);\n\n\t\t/*if (isFirenado) {\n\t\t\tfinalBright = 1F;\n\t\t}*/\n\n\t\tentityfx.setColor(finalBright, finalBright, finalBright);\n\t\t\n\t\t//entityfx.setColor(1, 1, 1);\n\t\t\n\t\t//DEBUG\n\t\tif (debug) {\n\t\t\tif (levelTemperature < 0) {\n\t\t\t\tentityfx.setColor(0, 0, finalBright);\n\t\t\t} else if (levelTemperature > 0) {\n\t\t\t\tentityfx.setColor(finalBright, 0, 0);\n\t\t\t}\n\t\t}\n\n\t\tentityfx.setTicksFadeInMax(20);\n\t\tentityfx.setTicksFadeOutMax(20);\n\t\tentityfx.setTicksFadeOutMaxOnDeath(20);\n\n\t\tentityfx.setUseDynamicWindSpeed(false);\n\n\t\tif (ConfigParticle.Particle_effect_rate != 0) {\n\t\t\tentityfx.spawnAsWeatherEffect();\n\t\t\t//Minecraft.getInstance().particleEngine.add(entityfx);\n\t\t\tparticleBehaviorFog.particles.add(entityfx);\n\t\t}\n\t\treturn entityfx;\n    }\n\n\t@Override\n\tpublic void cleanup() {\n\t\tif (tornadoHelper != null) {\n\t\t\t//we must save the animals\n\t\t\tif (this.levelCurIntensityStage >= STATE_FORMING) {\n\t\t\t\tfeatherFallAllNearbyPlayers();\n\t\t\t}\n\t\t\ttornadoHelper.cleanup();\n\t\t}\n\t\tsuper.cleanup();\n\t\ttornadoHelper = null;\n\t\tif (tornadoFunnelSimple != null) {\n\t\t\ttornadoFunnelSimple.cleanup();\n\t\t}\n\t}\n\t\n\t@OnlyIn(Dist.CLIENT)\n\t@Override\n\tpublic void cleanupClient() {\n\t\tsuper.cleanupClient();\n\t\tlistParticlesCloud.clear();\n\t\tlistParticlesFunnel.clear();\n\t\tif (particleBehaviorFog != null && particleBehaviorFog.particles != null) particleBehaviorFog.particles.clear();\n\t\tparticleBehaviorFog = null;\n\t\ttornadoHelper = null;\n\t\tif (tornadoFunnelSimple != null) {\n\t\t\ttornadoFunnelSimple.cleanupClient();\n\t\t}\n\t}\n\t\n\tpublic static float getTemperatureMCToWeatherSys(float parOrigVal) {\n\t\tif (parOrigVal > 0) {\n\t\t\treturn 1;\n\t\t} else {\n \t\t\treturn -1;\n\t\t}\n\t}\n\t\n\tpublic void addWeatherEffectLightning(LightningBoltWeatherNew parEnt, boolean custom) {\n\t\tmanager.getWorld().addFreshEntity(parEnt);\n\t\t((WeatherManagerServer)manager).syncLightningNew(parEnt, custom);\n\t}\n\t\n\t@Override\n\tpublic int getUpdateRateForNetwork() {\n\t\tif (levelCurIntensityStage >= StormObject.STATE_HIGHWIND) {\n\t\t\treturn 2;\n\t\t} else {\n\t\t\treturn super.getUpdateRateForNetwork();\n\t\t}\n\t}\n\n\tpublic Vec3 getPosTop() {\n\t\tif (isTornadoFormingOrGreater()) {\n\t\t\treturn tornadoFunnelSimple.getPosTop();\n\t\t}\n\t\treturn pos;\n\t}\n\n\tpublic void setupStorm(Entity entity) {\n\t\tif (entity != null) {\n\t\t\tthis.spawnerUUID = entity.getUUID().toString();\n\t\t\tinitPositions(entity.position());\n\t\t}\n\n\t\tthis.layer = 0;\n\t\tthis.naturallySpawned = false;\n\t\tthis.levelTemperature = 0.1F;\n\t\tthis.levelWater = this.levelWaterStartRaining * 2;\n\t\tthis.attrib_precipitation = true;\n\t\tthis.levelCurIntensityStage = this.STATE_STAGE1;\n\t\tthis.alwaysProgresses = false;\n\n\t\tthis.initFirstTime();\n\n\t\t//lock it to current stage or less\n\t\tthis.levelStormIntensityMax = this.levelCurIntensityStage;\n\t}\n\n\tpublic void initPositions(Vec3 pos) {\n\t\tthis.pos = pos;\n\n\t\tcurrentTopYBlock = calculateTopYBlock();\n\t\tposGround = new Vec3(pos.x, currentTopYBlock, pos.z);\n\t\tposBaseFormationPos = calculateBaseFormationPos();\n\t}\n\n\tpublic int calculateTopYBlock() {\n\t\tLevel world = manager.getWorld();\n\t\tint calculatedYPos = WeatherUtilBlock.getPrecipitationHeightSafe(world, new BlockPos(Mth.floor(pos.x), 0, Mth.floor(pos.z)), Heightmap.Types.WORLD_SURFACE_WG).getY();\n\n\t\t//TODO: only recalc if pos x z changes\n\t\tboolean filterOutLogs = true;\n\t\tif (filterOutLogs && world.hasChunkAt(CoroUtilBlock.blockPos(pos))) {\n\t\t\tint y = calculatedYPos-1;\n\t\t\tBlockState state = world.getBlockState(CoroUtilBlock.blockPos(pos.x, y, pos.z));\n\t\t\t//System.out.println(\"state: \" + state);\n\t\t\tint iter = 0;\n\t\t\twhile ((state.is(BlockTags.LOGS) || state.is(BlockTags.LEAVES) || state.is(BlockTags.CAVE_VINES) || state.is(BlockTags.REPLACEABLE_BY_TREES) || state.is(Blocks.COCOA)  || state.is(Blocks.BAMBOO) || state.isAir()) && y > world.getMinBuildHeight()) {\n\t\t\t\ty--;\n\t\t\t\tstate = world.getBlockState(CoroUtilBlock.blockPos(pos.x, y, pos.z));\n\t\t\t\t//CULog.dbg(\"filter logs, found: \" + state);\n\t\t\t\titer++;\n\t\t\t}\n\t\t\tif (y > world.getMinBuildHeight()) calculatedYPos = y;\n\t\t\t//CULog.dbg(\"filterOutLogs iter: \" + iter);\n\t\t}\n\n\t\t//for survive the tide rising mechanic that doesnt update heightmap\n\t\tif (Weather.isLoveTropicsInstalled()) {\n\t\t\twhile (world.getBlockState(CoroUtilBlock.blockPos(pos.x, calculatedYPos, pos.z)).getBlock() instanceof LiquidBlock && calculatedYPos < world.getMaxBuildHeight()) {\n\t\t\t\tcalculatedYPos++;\n\t\t\t}\n\t\t}\n\n\t\tif (isBaby()) {\n\t\t\tPlayer player = getPlayer();\n\t\t\tif (player != null) {\n\t\t\t\tcalculatedYPos = (int) player.position().y;\n\t\t\t}\n\t\t}\n\n\t\t//buggy, needs rework\n\t\tif (isPet()) {\n\t\t\tPlayer entP = getPlayer();\n\t\t\tif (entP != null) {\n\t\t\t\tif (entP.position().distanceTo(posGround) < 10) {\n\t\t\t\t\tif (calculatedYPos + 4 > entP.position().y) {\n\t\t\t\t\t\tint y = (int) (entP.position().y + 2);\n\t\t\t\t\t\twhile (y > entP.position().y - 3) {\n\t\t\t\t\t\t\tBlockPos blockPos = CoroUtilBlock.blockPos(pos.x, y, pos.z);\n\t\t\t\t\t\t\tBlockPos blockPosUp = CoroUtilBlock.blockPos(pos.x, y+1, pos.z);\n\t\t\t\t\t\t\tBlockState state = world.getBlockState(blockPos);\n\t\t\t\t\t\t\tBlockState stateUp = world.getBlockState(blockPosUp);\n\t\t\t\t\t\t\tif ((!state.isAir() && state.getCollisionShape(world, blockPosUp) != Shapes.empty()) && (stateUp.isAir() || stateUp.getCollisionShape(world, blockPosUp) == Shapes.empty())) {\n\t\t\t\t\t\t\t\tcalculatedYPos = y+1;\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\ty--;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn calculatedYPos;\n\t}\n\n\tpublic void setupTornadoAwayFromPlayersAimAtPlayers() {\n\t\tList<? extends Player> players = manager.getWorld().players();\n\t\tList<Player> playersNear = new ArrayList<>();\n\t\tdouble xAdd = 0;\n\t\tdouble yAdd = 0;\n\t\tdouble zAdd = 0;\n\t\tVec3 vecAdd = new Vec3(0, 0, 0);\n\t\tfor (Player player : players) {\n\t\t\tvecAdd = vecAdd.add(player.position());\n\t\t}\n\t\tvecAdd = new Vec3(vecAdd.x / players.size(), vecAdd.y / players.size(), vecAdd.z / players.size());\n\t\tinitPositions(vecAdd.add(150, 0, 150));\n\t\taimAtCoords(vecAdd);\n\t}\n\n\tpublic void setupPlayerControlledTornado(Entity entity) {\n\t\tthis.playerControlled = true;\n\t}\n\n\tpublic Player getPlayer() {\n\t\tif (spawnerUUID.equals(\"\")) return null;\n\t\treturn manager.getWorld().getPlayerByUUID(UUID.fromString(spawnerUUID));\n\t}\n\n\tpublic boolean isPlayerControlled() {\n\t\treturn playerControlled;\n\t}\n\n\tpublic void setPlayerControlled(boolean playerControlled) {\n\t\tthis.playerControlled = playerControlled;\n\t}\n\n\tpublic int getPlayerControlledTimeLeft() {\n\t\treturn playerControlledTimeLeft;\n\t}\n\n\tpublic void setPlayerControlledTimeLeft(int playerControlledTimeLeft) {\n\t\tthis.playerControlledTimeLeft = playerControlledTimeLeft;\n\t}\n\n\tpublic boolean isBaby() {\n\t\treturn baby;\n\t}\n\n\tpublic void setBaby(boolean baby) {\n\t\tthis.baby = baby;\n\t}\n\n\tpublic boolean isPet() {\n\t\treturn pet;\n\t}\n\n\tpublic void setPet(boolean pet) {\n\t\tthis.pet = pet;\n\t}\n\n\tpublic boolean isPetGrabsItems() {\n\t\treturn petGrabsItems;\n\t}\n\n\tpublic void setPetGrabsItems(boolean petGrabsItems) {\n\t\tthis.petGrabsItems = petGrabsItems;\n\t}\n\n\tpublic boolean isSharknado() {\n\t\treturn sharknado;\n\t}\n\n\tpublic void setSharknado(boolean sharknado) {\n\t\tthis.sharknado = sharknado;\n\t}\n\n\tpublic void setAndUpdateTornado() {\n\t\tif (tornadoFunnelSimple == null) {\n\t\t\tsetupTornado();\n\t\t}\n\n\t\t//tornadoFunnelSimple.pos = new Vec3(posGround.x, posGround.y, posGround.z);\n\t\ttornadoFunnelSimple.pos = new Vec3(posGround.x, posBaseFormationPos.y, posGround.z);\n\t}\n\n\tpublic TornadoFunnelSimple getTornadoFunnelSimple() {\n\t\treturn tornadoFunnelSimple;\n\t}\n\n\tpublic void setTornadoFunnelSimple(TornadoFunnelSimple tornadoFunnelSimple) {\n\t\tthis.tornadoFunnelSimple = tornadoFunnelSimple;\n\t}\n\n\tpublic int getAge() {\n\t\treturn age;\n\t}\n\n\tpublic void setAge(int age) {\n\t\tthis.age = age;\n\t}\n\n\tpublic int getAgeSinceTornadoTouchdown() {\n\t\treturn ageSinceTornadoTouchdown;\n\t}\n\n\tpublic void setAgeSinceTornadoTouchdown(int ageSinceTornadoTouchdown) {\n\t\tthis.ageSinceTornadoTouchdown = ageSinceTornadoTouchdown;\n\t}\n\n\tpublic boolean isBeingDeflectedCached() {\n\t\treturn isBeingDeflectedCached;\n\t}\n\n\tpublic void setBeingDeflectedCached(boolean beingDeflectedCached) {\n\t\tisBeingDeflectedCached = beingDeflectedCached;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/storm/TornadoHelper.java",
    "content": "package weather2.weathersystem.storm;\n\nimport com.corosus.coroutil.util.CoroUtilBlock;\nimport com.mojang.authlib.GameProfile;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.resources.ResourceKey;\nimport net.minecraft.server.level.ServerLevel;\nimport net.minecraft.sounds.SoundEvents;\nimport net.minecraft.sounds.SoundSource;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.effect.MobEffectInstance;\nimport net.minecraft.world.effect.MobEffects;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.entity.LivingEntity;\nimport net.minecraft.world.entity.animal.Animal;\nimport net.minecraft.world.entity.item.ItemEntity;\nimport net.minecraft.world.entity.monster.Enemy;\nimport net.minecraft.world.entity.npc.Npc;\nimport net.minecraft.world.entity.player.Player;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.biome.Biome;\nimport net.minecraft.world.level.block.Block;\nimport net.minecraft.world.level.block.Blocks;\nimport net.minecraft.world.level.block.state.BlockState;\nimport net.minecraft.world.level.levelgen.Heightmap;\nimport net.minecraft.world.level.material.MapColor;\nimport net.minecraft.world.phys.AABB;\nimport net.minecraft.world.phys.Vec3;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport net.minecraftforge.common.MinecraftForge;\nimport net.minecraftforge.common.util.FakePlayerFactory;\nimport net.minecraftforge.event.level.BlockEvent;\nimport weather2.ClientTickHandler;\nimport weather2.Weather;\nimport weather2.config.*;\nimport weather2.util.WeatherUtil;\nimport weather2.util.WeatherUtilBlock;\nimport weather2.util.WeatherUtilEntity;\nimport weather2.util.WeatherUtilSound;\nimport weather2.weathersystem.WeatherManagerServer;\nimport weather2.weathersystem.tornado.simple.Layer;\nimport weather2.weathersystem.tornado.simple.TornadoFunnelSimple;\n\nimport java.util.*;\n\npublic class TornadoHelper {\n\t\n\tpublic StormObject storm;\n\t\n\t//public int blockCount = 0;\n\t\n\tpublic int ripCount = 0;\n\n    public long lastGrabTime = 0;\n    public int tickGrabCount = 0;\n    public int removeCount = 0;\n    public int tryRipCount = 0;\n    \n    public int tornadoBaseSize = 5;\n    public int grabDist = 100;\n    \n    //potentially an issue var\n    public boolean lastTickPlayerClose;\n    \n    /**\n     * this tick queue isnt perfect, created to reduce chunk updates on client, but not removing block right away messes with block rip logic:\n     * - wont dig for blocks under this block until current is removed\n     * - initially, entries were spam added as the block still existed, changed list to hashmap to allow for blockpos hash lookup before adding another entry\n     * - entity creation relocated to queue processing to initially prevent entity spam, but with entry lookup, not needed, other issues like collision are now the reason why we still relocated entity creation to queue process\n     */\n    private HashMap<BlockPos, BlockUpdateSnapshot> listBlockUpdateQueue = new HashMap<BlockPos, BlockUpdateSnapshot>();\n    private int queueProcessRate = 40;\n\n    //for client player, for use of playing sounds\n\tpublic static boolean isOutsideCached = false;\n\n\t//for caching query on if a block damage preventer block is nearby, also assume blocked at first for safety\n\tpublic boolean isBlockGrabbingBlockedCached = true;\n\tpublic long isBlockGrabbingBlockedCached_LastCheck = 0;\n\n\t//static because its a shared list for the whole dimension\n\t//public static HashMap<Integer, Long> flyingBlock_LastQueryTime = new HashMap<>();\n\t//public static HashMap<Integer, Integer> flyingBlock_LastCount = new HashMap<>();\n\n\tpublic static GameProfile fakePlayerProfile = null;\n    \n    public static class BlockUpdateSnapshot {\n    \t//private int dimID;\n\t\tprivate ResourceKey<Level> dimension;\n    \tprivate BlockState state;\n    \tprivate BlockState statePrev;\n\t\tprivate BlockPos pos;\n    \tprivate boolean createEntityForBlockRemoval;\n\n\t\tpublic BlockUpdateSnapshot(ResourceKey<Level> dimension, BlockState state, BlockState statePrev, BlockPos pos, boolean createEntityForBlockRemoval) {\n\t\t\tthis.dimension = dimension;\n\t\t\tthis.state = state;\n\t\t\tthis.statePrev = statePrev;\n\t\t\tthis.pos = pos;\n\t\t\tthis.createEntityForBlockRemoval = createEntityForBlockRemoval;\n\t\t}\n\n\t\tpublic ResourceKey<Level> getDimension() {\n\t\t\treturn dimension;\n\t\t}\n\n\t\tpublic void setDimension(ResourceKey<Level> dimension) {\n\t\t\tthis.dimension = dimension;\n\t\t}\n\n\t\tpublic BlockState getState() {\n\t\t\treturn state;\n\t\t}\n\n\t\tpublic void setState(BlockState state) {\n\t\t\tthis.state = state;\n\t\t}\n\n\t\tpublic BlockPos getPos() {\n\t\t\treturn pos;\n\t\t}\n\n\t\tpublic void setPos(BlockPos pos) {\n\t\t\tthis.pos = pos;\n\t\t}\n    \t\n    \tpublic boolean isCreateEntityForBlockRemoval() {\n\t\t\treturn createEntityForBlockRemoval;\n\t\t}\n\n\t\tpublic void setCreateEntityForBlockRemoval(boolean createEntityForBlockRemoval) {\n\t\t\tthis.createEntityForBlockRemoval = createEntityForBlockRemoval;\n\t\t}\n\t\t\n    \tpublic BlockState getStatePrev() {\n\t\t\treturn statePrev;\n\t\t}\n\n\t\tpublic void setStatePrev(BlockState statePrev) {\n\t\t\tthis.statePrev = statePrev;\n\t\t}\n    \t\n    }\n\t\n\tpublic TornadoHelper(StormObject parStorm) {\n\t\tstorm = parStorm;\n\t}\n\t\n\tpublic int getTornadoBaseSize() {\n        int sizeChange = 10;\n\n\t\t//special love tropics overrides\n\t\tif (storm.isPlayerControlled()) {\n\t\t\tif (storm.isBaby()) {\n\t\t\t\treturn 8;\n\t\t\t}\n\t\t}\n\n\t\tif (storm.levelCurIntensityStage >= StormObject.STATE_STAGE5) {\n        \treturn sizeChange * 9;\n        } else if (storm.levelCurIntensityStage >= StormObject.STATE_STAGE4) {\n        \treturn sizeChange * 7;\n        } else if (storm.levelCurIntensityStage >= StormObject.STATE_STAGE3) {\n        \treturn sizeChange * 5;\n        } else if (storm.levelCurIntensityStage >= StormObject.STATE_STAGE2) {\n        \treturn sizeChange * 4;\n        } else if (storm.levelCurIntensityStage >= StormObject.STATE_STAGE1) {\n        \treturn sizeChange * 3;\n        } else if (storm.levelCurIntensityStage >= StormObject.STATE_FORMING) {\n        \treturn sizeChange * 1;\n        } else {\n        \treturn 5;\n        }\n\t}\n\n\n\n\tpublic void tick(Level parWorld) {\n\n\t\tif (!parWorld.isClientSide()) {\n\t\t\tif (parWorld.getGameTime() % queueProcessRate == 0) {\n\t\t\t\tIterator<BlockUpdateSnapshot> it = listBlockUpdateQueue.values().iterator();\n\t\t\t\twhile (it.hasNext()) {\n\t\t\t\t\tBlockUpdateSnapshot snapshot = it.next();\n\t\t\t\t\tLevel world = WeatherUtil.getWorld(snapshot.getDimension());\n\t\t\t\t\tif (world != null) {\n\t\t\t\t\t\tworld.setBlock(snapshot.getPos(), snapshot.getState(), 3);\n\t\t\t\t\t\tif (snapshot.isCreateEntityForBlockRemoval()) {\n\t\t\t\t\t\t\t//moved to where we add to the queue for less clunky visuals\n\t\t\t\t\t\t\t//((WeatherManagerServer)this.storm.manager).syncBlockParticleNew(snapshot.getPos(), snapshot.getStatePrev(), storm);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tlistBlockUpdateQueue.clear();\n\t\t\t}\n\t\t}\n\n\t\tif (storm == null) return;\n\n\t\tboolean seesLight = false;\n\t\ttickGrabCount = 0;\n\t\tremoveCount = 0;\n\t\ttryRipCount = 0;\n\t\tint tryRipMax = 300;\n\t\tif (storm.levelCurIntensityStage >= StormObject.STATE_STAGE5) {\n\t\t\ttryRipMax *= 2.5;\n\t\t} else if (storm.levelCurIntensityStage >= StormObject.STATE_STAGE4) {\n\t\t\ttryRipMax *= 1.8;\n\t\t} else if (storm.levelCurIntensityStage >= StormObject.STATE_STAGE3) {\n\t\t\ttryRipMax *= 1.5;\n\t\t} else if (storm.levelCurIntensityStage >= StormObject.STATE_STAGE2) {\n\t\t\ttryRipMax *= 1.2;\n\t\t}\n\t\t//tryRipMax = 1000;\n\t\tint firesPerTickMax = 1;\n\t\t//tornado profile changing from storm data\n\t\ttornadoBaseSize = getTornadoBaseSize();\n\n\t\tif (storm.stormType == storm.TYPE_WATER) {\n\t\t\ttornadoBaseSize *= 3;\n\t\t}\n\n\t\tforceRotate(parWorld);\n\n\t\tRandom rand = new Random();\n\t\tif (!parWorld.isClientSide() && !Weather.isLoveTropicsInstalled() && (ConfigTornado.Storm_Tornado_grabBlocks || storm.isFirenado))\n\t\t{\n\t\t\t//int yStart = (int) (storm.posGround.y - 10);\n\t\t\tint yStart = 0;\n\t\t\tint yEnd = (int)storm.pos.y/* + 72*/;\n\t\t\tint yInc = 1;\n\t\t\tBiome bgb = parWorld.getBiome(new BlockPos(WeatherUtilBlock.getPrecipitationHeightSafe(parWorld, new BlockPos(Mth.floor(storm.pos.x), 0, Mth.floor(storm.pos.z))))).get();\n\n\t\t\t//prevent grabbing in high areas (hills)\n\t\t\t//TODO: 1.10 make sure minHeight/maxHeight converted to baseHeight/scale is correct, guessing we can just not factor in variation\n\t\t\t//TODO: 1.18: getDepth formally base height, seems entirely gone now\n\t\t\tdouble depth = 0;\n\t\t\t//depth = bgb.getDepth();\n\t\t\tif (bgb != null && (depth/* + bgb.getScale()*/ <= 0.7 || storm.isFirenado)) {\n\n\t\t\t\tboolean newTest = false;\n\n\t\t\t\tint tryCount = 0;\n\n\t\t\t\tif (newTest) {\n\t\t\t\t\tTornadoFunnelSimple tornadoFunnelSimple = storm.getTornadoFunnelSimple();\n\t\t\t\t\tint layers = tornadoFunnelSimple.listLayers.size();\n\n\t\t\t\t\tfor (int i = 0; i < layers; i++) {\n\t\t\t\t\t\tfloat radius = tornadoFunnelSimple.getConfig().getRadiusOfBase() + (tornadoFunnelSimple.getConfig().getRadiusIncreasePerLayer() * (i));\n\n\t\t\t\t\t\tLayer layer = tornadoFunnelSimple.listLayers.get(i);\n\n\t\t\t\t\t\tfloat circumference = radius * 2 * Mth.PI;\n\t\t\t\t\t\tfloat particleSpaceOccupy = 1F;\n\t\t\t\t\t\tfloat scanResolution = (float) Math.floor(circumference / particleSpaceOccupy);\n\t\t\t\t\t\tfloat particleSpacingDegrees = 360 / scanResolution;\n\t\t\t\t\t\t//scanResolution = 1F;\n\n\t\t\t\t\t\tfor (float deg = 0; deg < 360; deg += particleSpacingDegrees) {\n\t\t\t\t\t\t\tfloat radiusScanSize = 5F;\n\t\t\t\t\t\t\t//for (float radiusToUse = radius - radiusScanSize; radiusToUse <= radius; radiusToUse+=0.5F) {\n\t\t\t\t\t\t\tfor (float radiusToUse = radius; radiusToUse <= radius; radiusToUse += 1F) {\n\t\t\t\t\t\t\t\tfloat x = (float) (layer.getPos().x + (-Math.sin(Math.toRadians(deg)) * radiusScanSize));\n\t\t\t\t\t\t\t\tfloat z = (float) (layer.getPos().z + (Math.cos(Math.toRadians(deg)) * radiusScanSize));\n\t\t\t\t\t\t\t\tfloat y = (float) layer.getPos().y;\n\n\t\t\t\t\t\t\t\tBlockPos pos = new BlockPos((int) Math.floor(x), (int) Math.floor(y) - 1, (int) Math.floor(z));\n\n\t\t\t\t\t\t\t\tboolean performed = false;\n\n\t\t\t\t\t\t\t\tBlockState state = parWorld.getBlockState(pos);\n\n\t\t\t\t\t\t\t\tif (parWorld.getGameTime() % 10 == 0 && radiusToUse == radius - radiusScanSize) {\n\t\t\t\t\t\t\t\t\t//((ServerLevel) parWorld).sendParticles(ParticleTypes.HEART, pos.getX(), pos.getY(), pos.getZ(), 1, 0.3D, 0D, 0.3D, 1D);\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\ttryCount++;\n\n\t\t\t\t\t\t\t\tif (canGrab(parWorld, state, pos)) {\n\t\t\t\t\t\t\t\t\ttryRipCount++;\n\t\t\t\t\t\t\t\t\tseesLight = tryRip(parWorld, pos.getX(), pos.getY(), pos.getZ());\n\t\t\t\t\t\t\t\t\tperformed = seesLight;\n\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\tif (!performed && ConfigTornado.Storm_Tornado_RefinedGrabRules) {\n\t\t\t\t\t\t\t\t\tif (state.getBlock() == Blocks.GRASS_BLOCK) {\n\t\t\t\t\t\t\t\t\t\tif (!listBlockUpdateQueue.containsKey(pos)) {\n\t\t\t\t\t\t\t\t\t\t\tlistBlockUpdateQueue.put(pos, new BlockUpdateSnapshot(parWorld.dimension(), Blocks.DIRT.defaultBlockState(), state, pos, false));\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t//CULog.dbg(\"tryCount: \" + tryCount);\n\t\t\t\t} else {\n\t\t\t\t\tint ii = 2;\n\n\t\t\t\t\tint stageIntensity = (int) ((storm.levelCurIntensityStage+1 - storm.levelStormIntensityFormingStartVal));\n\t\t\t\t\tint loopAmount = stageIntensity * 500;\n\n\t\t\t\t\tif (storm.stormType == StormObject.TYPE_WATER) {\n\t\t\t\t\t\tloopAmount = 1 + ii/2;\n\t\t\t\t\t}\n\n\t\t\t\t\tfor (int k = 0; k < loopAmount; k++)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (tryRipCount > tryRipMax) {\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tint bottomY = (int) Math.max(parWorld.getMinBuildHeight(), storm.posBaseFormationPos.y - 10);\n\t\t\t\t\t\tint topY = (int) Math.max(parWorld.getMaxBuildHeight(), storm.getPosTop().y);\n\t\t\t\t\t\tif (bottomY >= topY) bottomY = topY - 1;\n\t\t\t\t\t\tint tryY = rand.nextInt(bottomY, topY);\n\n\t\t\t\t\t\tif (tryY > parWorld.getMaxBuildHeight()) {\n\t\t\t\t\t\t\ttryY = parWorld.getMaxBuildHeight();\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tint tryX = (int)storm.pos.x + rand.nextInt(tornadoBaseSize + (ii)) - ((tornadoBaseSize / 2) + (ii / 2));\n\t\t\t\t\t\tint tryZ = (int)storm.pos.z + rand.nextInt(tornadoBaseSize + (ii)) - ((tornadoBaseSize / 2) + (ii / 2));\n\n\t\t\t\t\t\tdouble d0 = storm.pos.x - tryX;\n\t\t\t\t\t\tdouble d2 = storm.pos.z - tryZ;\n\t\t\t\t\t\tdouble dist = Mth.sqrt((float) (d0 * d0 + d2 * d2));\n\t\t\t\t\t\tBlockPos pos = new BlockPos(tryX, tryY, tryZ);\n\n\t\t\t\t\t\tif (dist < tornadoBaseSize/2 + ii/2 && tryRipCount < tryRipMax)\n\t\t\t\t\t\t{\n\n\t\t\t\t\t\t\tBlockState state = parWorld.getBlockState(pos);\n\t\t\t\t\t\t\tBlock blockID = state.getBlock();\n\n\t\t\t\t\t\t\tboolean performed = false;\n\n\t\t\t\t\t\t\ttryCount++;\n\n\t\t\t\t\t\t\tif (canGrab(parWorld, state, pos))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttryRipCount++;\n\t\t\t\t\t\t\t\tseesLight = tryRip(parWorld, tryX, tryY, tryZ);\n\n\t\t\t\t\t\t\t\tperformed = seesLight;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tif (!performed && ConfigTornado.Storm_Tornado_RefinedGrabRules) {\n\t\t\t\t\t\t\t\tif (blockID == Blocks.GRASS_BLOCK) {\n\t\t\t\t\t\t\t\t\tif (!listBlockUpdateQueue.containsKey(pos)) {\n\t\t\t\t\t\t\t\t\t\tlistBlockUpdateQueue.put(pos, new BlockUpdateSnapshot(parWorld.dimension(), Blocks.DIRT.defaultBlockState(), state, pos, false));\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tint spawnYOffset = (int) storm.posBaseFormationPos.y - 10;\n\n\t\t\t\t\tfor (int k = 0; k < 10; k++) {\n\t\t\t\t\t\tint randSize = 40;\n\n\t\t\t\t\t\trandSize = 10;\n\n\t\t\t\t\t\tint tryX = (int)storm.pos.x + rand.nextInt(randSize) - randSize/2;\n\t\t\t\t\t\tint tryY = (int)spawnYOffset - 2 + rand.nextInt(8);\n\t\t\t\t\t\tint tryZ = (int)storm.pos.z + rand.nextInt(randSize) - randSize/2;\n\n\t\t\t\t\t\tdouble d0 = storm.pos.x - tryX;\n\t\t\t\t\t\tdouble d2 = storm.pos.z - tryZ;\n\t\t\t\t\t\tdouble dist = Mth.sqrt((float) (d0 * d0 + d2 * d2));\n\n\t\t\t\t\t\tif (dist < tornadoBaseSize/2 + randSize/2 && tryRipCount < tryRipMax) {\n\t\t\t\t\t\t\tBlockPos pos = new BlockPos(tryX, tryY, tryZ);\n\t\t\t\t\t\t\tBlockState state = parWorld.getBlockState(pos);\n\n\t\t\t\t\t\t\ttryCount++;\n\n\t\t\t\t\t\t\tif (canGrab(parWorld, state, pos))\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\ttryRipCount++;\n\t\t\t\t\t\t\t\ttryRip(parWorld, tryX, tryY, tryZ);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t//CULog.dbg(\"tryCount: \" + tryCount);\n\t\t\t\t}\n\n\n\t\t\t}\n\t\t}\n\t\telse\n\t\t{\n\t\t\tseesLight = true;\n\t\t}\n\n\t\t/*if (Math.abs((spawnYOffset - storm.pos.y)) > 5)\n\t\t{\n\t\t\tseesLight = true;\n\t\t}*/\n\n\t\tif (!parWorld.isClientSide() && storm.isFirenado) {\n\t\t\tif (storm.levelCurIntensityStage >= storm.STATE_STAGE1)\n\t\t\t\tfor (int i = 0; i < firesPerTickMax; i++) {\n\t\t\t\t\tBlockPos posUp = CoroUtilBlock.blockPos(storm.posGround.x, storm.posGround.y + rand.nextInt(30), storm.posGround.z);\n\t\t\t\t\tBlockState state = parWorld.getBlockState(posUp);\n\t\t\t\t\tif (CoroUtilBlock.isAir(state.getBlock())) {\n\t\t\t\t\t\t//parWorld.setBlockState(posUp, Blocks.FIRE.getDefaultState());\n\n\t\t\t\t\t\t//TODO: 1.14 uncomment\n\t\t\t\t\t/*EntityMovingBlock mBlock = new EntityMovingBlock(parWorld, posUp.getX(), posUp.getY(), posUp.getZ(), Blocks.FIRE.getDefaultState(), storm);\n\t\t\t\t\tmBlock.metadata = 15;\n\t\t\t\t\tdouble speed = 2D;\n\t\t\t\t\tmBlock.motionX += (rand.nextDouble() - rand.nextDouble()) * speed;\n\t\t\t\t\tmBlock.motionZ += (rand.nextDouble() - rand.nextDouble()) * speed;\n\t\t\t\t\tmBlock.motionY = 1D;\n\t\t\t\t\tmBlock.mode = 0;\n\t\t\t\t\tparWorld.addEntity(mBlock);*/\n\t\t\t\t\t}\n\t\t\t\t}\n\n\n\t\t\tint randSize = 10;\n\n\t\t\tint tryX = (int)storm.pos.x + rand.nextInt(randSize) - randSize/2;\n\n\t\t\tint tryZ = (int)storm.pos.z + rand.nextInt(randSize) - randSize/2;\n\t\t\tint tryY = parWorld.getHeight(Heightmap.Types.MOTION_BLOCKING, tryX, tryZ) - 1;\n\n\t\t\tint funnelBottomY = (int) storm.pos.y;\n\t\t\tif (storm.getTornadoFunnelSimple().listLayers.size() > 0) {\n\t\t\t\tLayer layer = storm.getTornadoFunnelSimple().listLayers.get(0);\n\t\t\t\tfunnelBottomY = (int) layer.getPos().y();\n\t\t\t}\n\n\t\t\t//if firenado funnel reached down enough to hit this y coord\n\t\t\tif (tryY > funnelBottomY - 3) {\n\t\t\t\tdouble d0 = storm.pos.x - tryX;\n\t\t\t\tdouble d2 = storm.pos.z - tryZ;\n\t\t\t\tdouble dist = Mth.sqrt((float) (d0 * d0 + d2 * d2));\n\n\t\t\t\tif (dist < tornadoBaseSize / 2 + randSize / 2 && tryRipCount < tryRipMax) {\n\t\t\t\t\tBlockPos pos = new BlockPos(tryX, tryY, tryZ);\n\t\t\t\t\tBlock block = parWorld.getBlockState(pos).getBlock();\n\t\t\t\t\tBlockPos posUp = new BlockPos(tryX, tryY + 1, tryZ);\n\t\t\t\t\tBlock blockUp = parWorld.getBlockState(posUp).getBlock();\n\n\t\t\t\t\tif (!CoroUtilBlock.isAir(block) && CoroUtilBlock.isAir(blockUp)) {\n\t\t\t\t\t\tparWorld.setBlock(posUp, Blocks.FIRE.defaultBlockState(), 3);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t\n\tpublic boolean isNoDigCoord(int x, int y, int z) {\n\n        // MCPC start\n          /*org.bukkit.entity.Entity bukkitentity = this.getBukkitEntity();\n          if ((bukkitentity instanceof Player)) {\n            Player player = (Player)bukkitentity;\n            BlockBreakEvent breakev = new BlockBreakEvent(player.getWorld().getBlockStateAt(x, y, z), player);\n            Bukkit.getPluginManager().callEvent(breakev);\n            if (breakev.isCancelled()) {\n                return true;\n            }\n          }*/\n          // MCPC end\n          \n          return false;\n    }\n\n\tpublic boolean tryRip(Level parWorld, int tryX, int tryY, int tryZ/*, boolean notify*/)\n    {\n        boolean tryRip = true;\n\t\tBlockPos pos = new BlockPos(tryX, tryY, tryZ);\n\t\tif (listBlockUpdateQueue.containsKey(pos)) {\n\t\t\treturn true;\n\t\t}\n        \n        if (!tryRip) return true;\n        if (!ConfigTornado.Storm_Tornado_grabBlocks) return true;\n        if (isNoDigCoord(tryX, tryY, tryZ)) return true;\n\n        boolean seesLight = false;\n        BlockState state = parWorld.getBlockState(pos);\n        Block blockID = state.getBlock();\n        if ((((WeatherUtilBlock.getPrecipitationHeightSafe(parWorld, new BlockPos(tryX, 0, tryZ)).getY() - 1 == tryY) ||\n\t\tWeatherUtilBlock.getPrecipitationHeightSafe(parWorld, new BlockPos(tryX + 1, 0, tryZ)).getY() - 1 < tryY ||\n\t\tWeatherUtilBlock.getPrecipitationHeightSafe(parWorld, new BlockPos(tryX, 0, tryZ + 1)).getY() - 1 < tryY ||\n\t\tWeatherUtilBlock.getPrecipitationHeightSafe(parWorld, new BlockPos(tryX - 1, 0, tryZ)).getY() - 1 < tryY ||\n\t\tWeatherUtilBlock.getPrecipitationHeightSafe(parWorld, new BlockPos(tryX, 0, tryZ - 1)).getY() - 1 < tryY))) {\n\n        \tint blockCount = 0;\n\n\t\t\t//old per storm blockCount seems glitched... lets use a global we cache count of\n            if (parWorld.isLoaded(CoroUtilBlock.blockPos(storm.pos.x, 128, storm.pos.z)) &&\n\t\t\t\tlastGrabTime < System.currentTimeMillis() &&\n\t\t\t\ttickGrabCount < ConfigTornado.Storm_Tornado_maxBlocksGrabbedPerTick) {\n\n                lastGrabTime = System.currentTimeMillis() - 5;\n\n                if (blockID != Blocks.PACKED_ICE && blockID != Blocks.ICE && blockID != Blocks.SNOW_BLOCK && blockID != Blocks.SNOW && blockID != Blocks.POWDER_SNOW)\n                {\n                \tboolean playerClose = parWorld.getNearestPlayer(storm.posBaseFormationPos.x, storm.posBaseFormationPos.y, storm.posBaseFormationPos.z, 140, false) != null;\n                    if (playerClose) {\n\t                    tickGrabCount++;\n\t                    ripCount++;\n\t                    seesLight = true;\n                    }\n\n\t\t\t\t\tif (WeatherUtil.shouldRemoveBlock(state))\n\t\t\t\t\t{\n\t\t\t\t\t\tremoveCount++;\n\t\t\t\t\t\tboolean shouldEntityify = blockCount <= ConfigTornado.Storm_Tornado_maxFlyingEntityBlocks;\n\t\t\t\t\t\tlistBlockUpdateQueue.put(pos, new BlockUpdateSnapshot(parWorld.dimension(), Blocks.AIR.defaultBlockState(), state, pos, playerClose && shouldEntityify));\n\t\t\t\t\t\tif (playerClose && shouldEntityify && (state.canOcclude() || state.getBlock().defaultMapColor() == MapColor.PLANT)) {\n\t\t\t\t\t\t\t((WeatherManagerServer) this.storm.manager).syncBlockParticleNew(pos, state, storm);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n                }\n\t\t\t\tif (blockID == Blocks.GLASS)\n\t\t\t\t{\n\t\t\t\t\tparWorld.playSound(null, new BlockPos(tryX, tryY, tryZ), SoundEvents.GLASS_BREAK, SoundSource.AMBIENT, 5.0F, 1.0F);\n\t\t\t\t}\n            }\n        }\n\n        return seesLight;\n    }\n\n    public boolean canGrab(Level parWorld, BlockState state, BlockPos pos)\n    {\n        if (!CoroUtilBlock.isAir(state.getBlock()) &&\n\t\t\t\tstate.getBlock() != Blocks.FIRE &&\n\t\t\t\t//TODO: 1.14 uncomment\n\t\t\t\t/*state.getBlock() != CommonProxy.blockRepairingBlock &&*/\n\t\t\t\tWeatherUtil.shouldGrabBlock(parWorld, state) &&\n\t\t\t\t!isBlockGrabbingBlocked(parWorld, state, pos))\n        {\n        \treturn canGrabEventCheck(parWorld, state, pos);\n        }\n\n        return false;\n    }\n\n    public boolean canGrabEventCheck(Level world, BlockState state, BlockPos pos) {\n    \tif (!ConfigMisc.blockBreakingInvokesCancellableEvent) return true;\n    \tif (world instanceof ServerLevel) {\n\t\t\tif (fakePlayerProfile == null) {\n\t\t\t\tfakePlayerProfile = new GameProfile(UUID.fromString(\"1396b887-2570-4948-86e9-0633d1d22946\"), \"weather2FakePlayer\");\n\t\t\t}\n\t\t\tBlockEvent.BreakEvent event = new BlockEvent.BreakEvent(world, pos, state, FakePlayerFactory.get((ServerLevel) world, fakePlayerProfile));\n\t\t\tMinecraftForge.EVENT_BUS.post(event);\n\t\t\treturn !event.isCanceled();\n\t\t} else {\n    \t\treturn false;\n\t\t}\n\t}\n\n    public boolean canGrabEntity(Entity ent) {\n\t\tif (ent.level().isClientSide()) {\n\t\t\treturn canGrabEntityClient(ent);\n\t\t} else {\n\t\t\tif (ent instanceof Player) {\n\t\t\t\tif (!((Player) ent).isCreative()) {\n\t\t\t\t\tif (ConfigTornado.Storm_Tornado_grabPlayer) {\n\t\t\t\t\t\tif (storm.isPlayerControlled() && storm.spawnerUUID != null && storm.spawnerUUID.equals(ent.getUUID().toString())) {\n\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (ConfigTornado.Storm_Tornado_grabPlayersOnly) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tif (ent instanceof Npc) {\n\t\t\t\t\treturn ConfigTornado.Storm_Tornado_grabVillagers;\n\t\t\t\t}\n\t\t\t\tif (ent instanceof ItemEntity) {\n\t\t\t\t\treturn ConfigTornado.Storm_Tornado_grabItems || storm.isPet();\n\t\t\t\t}\n\t\t\t\tif (ent instanceof Enemy) {\n\t\t\t\t\treturn ConfigTornado.Storm_Tornado_grabMobs;\n\t\t\t\t}\n\t\t\t\tif (ent instanceof Animal) {\n\t\t\t\t\treturn ConfigTornado.Storm_Tornado_grabAnimals;\n\t\t\t\t}\n\t\t\t}\n\t\t\t//for moving blocks, other non livings\n\t\t\treturn true;\n\t\t}\n\n\t}\n\n\t@OnlyIn(Dist.CLIENT)\n\tpublic boolean canGrabEntityClient(Entity ent) {\n\t\tClientConfigData clientConfig = ClientTickHandler.clientConfigData;\n\t\tif (ent instanceof Player) {\n\t\t\tif (!((Player) ent).isCreative()) {\n\t\t\t\tif (ConfigTornado.Storm_Tornado_grabPlayer) {\n\t\t\t\t\tif (storm.isPlayerControlled() && storm.spawnerUUID != null && storm.spawnerUUID.equals(ent.getUUID().toString())) {\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t}\n\t\t\t\t\treturn true;\n\t\t\t\t} else {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} else {\n\t\t\tif (clientConfig.Storm_Tornado_grabPlayersOnly) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tif (ent instanceof Npc) {\n\t\t\t\treturn clientConfig.Storm_Tornado_grabVillagers;\n\t\t\t}\n\t\t\tif (ent instanceof ItemEntity) {\n\t\t\t\treturn clientConfig.Storm_Tornado_grabItems || storm.isPet();\n\t\t\t}\n\t\t\tif (ent instanceof Enemy) {\n\t\t\t\treturn clientConfig.Storm_Tornado_grabMobs;\n\t\t\t}\n\t\t\tif (ent instanceof Animal) {\n\t\t\t\treturn clientConfig.Storm_Tornado_grabAnimals;\n\t\t\t}\n\t\t}\n\t\t//for moving blocks, other non livings\n\t\treturn true;\n\t}\n\n\tpublic boolean forceRotate(Level parWorld)\n\t{\n\t\treturn forceRotate(parWorld, false);\n\t}\n\n    public boolean forceRotate(Level parWorld, boolean featherFallInstead)\n    {\n    \t\n    \t//changed for weather2:\n    \t//canEntityBeSeen commented out till replaced with coord one, might cause issues\n    \t\n        double dist = grabDist * 2;\n\t\tif (storm.isPet()) {\n\t\t\tdist = 3F;\n\t\t}\n        AABB aabb = new AABB(storm.pos.x, storm.currentTopYBlock, storm.pos.z, storm.pos.x, storm.currentTopYBlock, storm.pos.z);\n\t\tif (storm.isPet()) {\n\t\t\taabb = aabb.inflate(dist, 3, dist);\n\t\t} else {\n\t\t\taabb = aabb.inflate(dist, this.storm.maxHeight * 3.8, dist);\n\t\t}\n\n        List list = parWorld.getEntitiesOfClass(Entity.class, aabb);\n        boolean foundEnt = false;\n        int killCount = 0;\n\n        if (list != null)\n        {\n            for (int i = 0; i < list.size(); i++)\n            {\n                Entity entity1 = (Entity)list.get(i);\n\n                if (canGrabEntity(entity1)) {\n\t\t\t\t\tif (getDistanceXZ(storm.posBaseFormationPos, entity1.getX(), entity1.getY(), entity1.getZ()) < dist)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (!storm.isPet()) {\n\t\t\t\t\t\t\tif (false/* && (entity1 instanceof EntityMovingBlock && !((EntityMovingBlock)entity1).collideFalling)*/) {\n\t\t\t\t\t\t\t\tstorm.spinEntity(entity1);\n\t\t\t\t\t\t\t\t//spin(entity, conf, entity1);\n\t\t\t\t\t\t\t\tfoundEnt = true;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tif (entity1 instanceof Player) {\n\t\t\t\t\t\t\t\t\t//dont waste cpu on server side doing LOS checks, since player movement is client side only, in all situations ive seen\n\t\t\t\t\t\t\t\t\t//actually we need to still change its motion var, otherwise weird things happen\n\t\t\t\t\t\t\t\t\t//if (entity1.world.isClientSide()) {\n\t\t\t\t\t\t\t\t\tif (WeatherUtilEntity.isEntityOutside(entity1) || (storm.isPlayerControlled() && WeatherUtilEntity.canPosSeePos(parWorld, entity1.position(), storm.posGround))) {\n\t\t\t\t\t\t\t\t\t\t//Weather.dbg(\"entity1.motionY: \" + entity1.motionY);\n\t\t\t\t\t\t\t\t\t\tif (featherFallInstead) {\n\t\t\t\t\t\t\t\t\t\t\t((Player) entity1).addEffect(new MobEffectInstance(MobEffects.SLOW_FALLING, 600, 0, false, true, true));\n\t\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t\tstorm.spinEntityv2(entity1);\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\tfoundEnt = true;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else if (entity1 instanceof LivingEntity && (WeatherUtilEntity.isEntityOutside(entity1, false) || (storm.isPlayerControlled() && WeatherUtilEntity.canPosSeePos(parWorld, entity1.position(), storm.posGround)))) {//OldUtil.canVecSeeCoords(parWorld, storm.pos, entity1.posX, entity1.posY, entity1.posZ)/*OldUtil.canEntSeeCoords(entity1, entity.posX, entity.posY + 80, entity.posZ)*/) {\n\t\t\t\t\t\t\t\t\t//trying only server side to fix warp back issue (which might mean client and server are mismatching for some rules)\n\t\t\t\t\t\t\t\t\t//if (!entity1.world.isClientSide()) {\n\t\t\t\t\t\t\t\t\tif (featherFallInstead) {\n\t\t\t\t\t\t\t\t\t\t((LivingEntity) entity1).addEffect(new MobEffectInstance(MobEffects.SLOW_FALLING, 600, 0, false, true, true));\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\tstorm.spinEntityv2(entity1);\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t//spin(entity, conf, entity1);\n\t\t\t\t\t\t\t\t\tfoundEnt = true;\n\t\t\t\t\t\t\t\t\t//}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tif (entity1 instanceof ItemEntity && storm.isPetGrabsItems()) {\n\t\t\t\t\t\t\t\tstorm.spinEntityv2(entity1);\n\t\t\t\t\t\t\t\tfoundEnt = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n            }\n        }\n\n        return foundEnt;\n    }\n    \n    public double getDistanceXZ(Vec3 parVec, double var1, double var3, double var5)\n    {\n        double var7 = parVec.x - var1;\n        //double var9 = ent.posY - var3;\n        double var11 = parVec.z - var5;\n        return Mth.sqrt((float) (var7 * var7/* + var9 * var9*/ + var11 * var11));\n    }\n    \n    public double getDistanceXZ(Entity ent, double var1, double var3, double var5)\n    {\n        double var7 = ent.getX() - var1;\n        //double var9 = ent.posY - var3;\n        double var11 = ent.getZ() - var5;\n        return Mth.sqrt((float) (var7 * var7/* + var9 * var9*/ + var11 * var11));\n    }\n    \n    @OnlyIn(Dist.CLIENT)\n    public void soundUpdates(boolean playFarSound, boolean playNearSound)\n    {\n    \tif (storm.isPet()) return;\n    \tMinecraft mc = Minecraft.getInstance();\n    \t\n        if (mc.player == null)\n        {\n            return;\n        }\n\n        //close sounds\n        int far = 200;\n        int close = 120;\n        if (storm.stormType == storm.TYPE_WATER) {\n        \tclose = 200;\n        }\n        Vec3 plPos = new Vec3(mc.player.getX(), mc.player.getY(), mc.player.getZ());\n\n\t\tfloat quietTornadoTweak = 1F;\n\t\tfloat quietAmbientTweak = 1F;\n\t\tif (Weather.isLoveTropicsInstalled()) {\n\t\t\tif (storm.isPlayerControlled()) {\n\t\t\t\tquietTornadoTweak = 0.25F;\n\t\t\t\tquietAmbientTweak = 0.1F;\n\t\t\t}\n\t\t\tclose = 7;\n\t\t}\n        \n        double distToPlayer = this.storm.posGround.distanceTo(plPos);\n        \n        float volScaleFar = (float) ((far - distToPlayer) / far);\n        float volScaleClose = (float) ((close - distToPlayer) / close);\n\n        if (volScaleFar < 0F)\n        {\n            volScaleFar = 0.0F;\n        }\n\n        if (volScaleClose < 0F)\n        {\n            volScaleClose = 0.0F;\n        }\n\n        if (distToPlayer < close)\n        {\n            lastTickPlayerClose = true;\n        }\n        else\n        {\n            lastTickPlayerClose = false;\n        }\n\n        if (distToPlayer < far)\n        {\n            if (playFarSound) {\n\t\t\t\tif (mc.level.getGameTime() % 40 == 0) {\n\t\t\t\t\tisOutsideCached = WeatherUtilEntity.isPosOutside(mc.level,\n\t\t\t\t\t\t\tnew Vec3(mc.player.getPosition(1).x()+0.5F, mc.player.getPosition(1).y()+0.5F, mc.player.getPosition(1).z()+0.5F));\n\t\t\t\t}\n\t\t\t\tif (isOutsideCached) {\n\t\t\t\t\ttryPlaySound(WeatherUtilSound.snd_wind_far, 2, mc.player, (float)(volScaleFar * quietAmbientTweak * ConfigSound.windyStormVolume), far);\n\t\t\t\t}\n\t\t\t}\n\n            if (playNearSound) tryPlaySound(WeatherUtilSound.snd_wind_close, 1, mc.player, (float)(volScaleClose * quietTornadoTweak * ConfigSound.tornadoWindVolume), close);\n\n            if (storm.levelCurIntensityStage >= storm.STATE_FORMING && storm.stormType == storm.TYPE_LAND)\n            {\n                tryPlaySound(WeatherUtilSound.snd_tornado_dmg_close, 0, mc.player, (float)(volScaleClose * quietTornadoTweak * ConfigSound.tornadoDamageVolume), close);\n            }\n        }\n    }\n\n    public boolean tryPlaySound(String[] sound, int arrIndex, Entity source, float vol, float parCutOffRange)\n    {\n        Random rand = new Random();\n\n        if (WeatherUtilSound.soundTimer[arrIndex] <= System.currentTimeMillis())\n        {\n        \tWeatherUtilSound.playMovingSound(storm, new StringBuilder().append(\"streaming.\" + sound[WeatherUtilSound.snd_rand[arrIndex]]).toString(), vol, 1.0F, parCutOffRange);\n            int length = WeatherUtilSound.soundToLength.get(sound[WeatherUtilSound.snd_rand[arrIndex]]);\n            //-500L, for blending\n            WeatherUtilSound.soundTimer[arrIndex] = System.currentTimeMillis() + length - 500L;\n            WeatherUtilSound.snd_rand[arrIndex] = rand.nextInt(3);\n        }\n\n        return false;\n    }\n\n\tpublic boolean isBlockGrabbingBlocked(Level world, BlockState state, BlockPos pos) {\n\t\tint queryRate = 40;\n\t\tif (isBlockGrabbingBlockedCached_LastCheck + queryRate < world.getGameTime()) {\n\t\t\tisBlockGrabbingBlockedCached_LastCheck = world.getGameTime();\n\n\t\t\tisBlockGrabbingBlockedCached = false;\n\n\t\t\tfor (Long hash : storm.manager.getLookupWeatherBlockDamageDeflector().keySet()) {\n\t\t\t\tBlockPos posDeflect = BlockPos.of(hash);\n\n\t\t\t\tif (pos.distSqr(posDeflect) < ConfigStorm.Storm_Deflector_RadiusOfStormRemoval * ConfigStorm.Storm_Deflector_RadiusOfStormRemoval) {\n\t\t\t\t\tisBlockGrabbingBlockedCached = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn isBlockGrabbingBlockedCached;\n\t}\n\n\tpublic void cleanup() {\n\t\tlistBlockUpdateQueue.clear();\n\t\tstorm = null;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/storm/WeatherEntityConfig.java",
    "content": "package weather2.weathersystem.storm;\n\n\npublic class WeatherEntityConfig\n{\n    public static int TYPE_SPOUT = 0;\n    public static int TYPE_TORNADO = 1;\n    public static int TYPE_HURRICANE = 2;\n    public int type = 1;\n    public float tornadoInitialSpeed;\n    public float tornadoPullRate;\n    public float tornadoLiftRate;\n    public int relTornadoSize;\n    public int tornadoBaseSize;\n    public float tornadoWidthScale;\n    public double grabDist = 120.0D;\n    public int tornadoTime = 2500;\n    public boolean grabsBlocks = true;\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/storm/WeatherObject.java",
    "content": "package weather2.weathersystem.storm;\n\nimport net.minecraft.world.phys.Vec3;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport net.minecraftforge.fml.LogicalSide;\nimport net.minecraftforge.fml.util.thread.EffectiveSide;\nimport weather2.util.CachedNBTTagCompound;\nimport weather2.weathersystem.WeatherManager;\n\npublic class WeatherObject {\n\n\tpublic static long lastUsedStormID = 0; //ID starts from 0 for each game start, no storm nbt disk reload for now\n\tpublic long ID; //loosely accurate ID for tracking, but we wanted to persist between world reloads..... need proper UUID??? I guess add in UUID later and dont persist, start from 0 per game run\n\tpublic boolean isDead = false;\n\n\t/**\n\t * used to count up to a threshold to finally remove weather objects,\n\t * solves issue of simbox cutoff removing storms for first few ticks as player is joining in singleplayer\n\t * helps with multiplayer, requiring 30 seconds of no players near before removal\n\t */\n\tpublic int ticksSinceNoNearPlayer = 0;\n\t\n\tpublic WeatherManager manager;\n\t\n\tpublic Vec3 pos = Vec3.ZERO;\n\tpublic Vec3 posGround = Vec3.ZERO;\n\tpublic Vec3 motion = Vec3.ZERO;\n\n\t//used as radius\n\tpublic int size = 50;\n\tpublic int maxSize = 0;\n\n\t//unused\n\tpublic EnumWeatherObjectType weatherObjectType = EnumWeatherObjectType.CLOUD;\n\n\tprivate CachedNBTTagCompound nbtCache;\n\n\t//private NBTTagCompound cachedClientNBTState;\n\n\tpublic WeatherObject(WeatherManager parManager) {\n\t\tmanager = parManager;\n\t\tnbtCache = new CachedNBTTagCompound();\n\t}\n\t\n\tpublic void initFirstTime() {\n\t\tID = lastUsedStormID++;\n\t}\n\t\n\tpublic void tick() {\n\t\t\n\t}\n\t\n\t@OnlyIn(Dist.CLIENT)\n\tpublic void tickRender(float partialTick) {\n\t\t\n\t}\n\t\n\tpublic void reset() {\n\t\tremove();\n\t}\n\t\n\tpublic void remove() {\n\t\t//Weather.dbg(\"storm killed, ID: \" + ID);\n\t\t\n\t\tisDead = true;\n\t\t\n\t\t//cleanup memory\n\t\t//if (FMLCommonHandler.instance().getEffectiveSide() == Dist.CLIENT/*manager.getWorld().isRemote*/) {\n\t\tif (EffectiveSide.get().equals(LogicalSide.CLIENT)) {\n\t\t\tcleanupClient();\n\t\t}\n\t\t\n\t\tcleanup();\n\t}\n\t\n\tpublic void cleanup() {\n\t\tmanager = null;\n\t}\n\t\n\t@OnlyIn(Dist.CLIENT)\n\tpublic void cleanupClient() {\n\t\t\n\t}\n\t\n\tpublic int getUpdateRateForNetwork() {\n\t\treturn 40;\n\t}\n\t\n\tpublic void read() {\n\t\t\n    }\n\t\n\tpublic void write() {\n\n    }\n\t\n\tpublic void nbtSyncFromServer() {\n\t\tCachedNBTTagCompound parNBT = this.getNbtCache();\n\t\tID = parNBT.getLong(\"ID\");\n\t\t//Weather.dbg(\"StormObject \" + ID + \" receiving sync\");\n\t\t\n\t\tpos = new Vec3(parNBT.getDouble(\"posX\"), parNBT.getDouble(\"posY\"), parNBT.getDouble(\"posZ\"));\n\t\t//motion = new Vec3(parNBT.getDouble(\"motionX\"), parNBT.getDouble(\"motionY\"), parNBT.getDouble(\"motionZ\"));\n\t\tmotion = new Vec3(parNBT.getDouble(\"vecX\"), parNBT.getDouble(\"vecY\"), parNBT.getDouble(\"vecZ\"));\n\t\tsize = parNBT.getInt(\"size\");\n\t\tmaxSize = parNBT.getInt(\"maxSize\");\n\t\tthis.weatherObjectType = EnumWeatherObjectType.get(parNBT.getInt(\"weatherObjectType\"));\n\t}\n\t\n\tpublic void nbtSyncForClient() {\n\t\tCachedNBTTagCompound nbt = this.getNbtCache();\n\t\tnbt.putDouble(\"posX\", pos.x);\n\t\tnbt.putDouble(\"posY\", pos.y);\n\t\tnbt.putDouble(\"posZ\", pos.z);\n\n\t\t/*nbt.putDouble(\"motionX\", motion.xCoord);\n\t\tnbt.putDouble(\"motionY\", motion.yCoord);\n\t\tnbt.putDouble(\"motionZ\", motion.zCoord);*/\n\t\tnbt.putDouble(\"vecX\", motion.x);\n\t\tnbt.putDouble(\"vecY\", motion.y);\n\t\tnbt.putDouble(\"vecZ\", motion.z);\n\n\t\tnbt.putLong(\"ID\", ID);\n\t\t//just blind set ID into non cached data so client always has it, no need to check for forced state and restore orig state\n\t\tnbt.getNewNBT().putLong(\"ID\", ID);\n\n\t\tnbt.putInt(\"size\", size);\n\t\tnbt.putInt(\"maxSize\", maxSize);\n\t\tnbt.putInt(\"weatherObjectType\", this.weatherObjectType.ordinal());\n\t}\n\n\tpublic CachedNBTTagCompound getNbtCache() {\n\t\treturn nbtCache;\n\t}\n\n\tpublic void setNbtCache(CachedNBTTagCompound nbtCache) {\n\t\tthis.nbtCache = nbtCache;\n\t}\n\n\tpublic int getSize() {\n\t\treturn size;\n\t}\n\t\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/storm/WeatherObjectParticleStorm.java",
    "content": "package weather2.weathersystem.storm;\n\nimport com.corosus.coroutil.util.CoroUtilBlock;\nimport com.corosus.coroutil.util.CoroUtilCompatibility;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.core.Holder;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.biome.Biome;\nimport net.minecraft.world.level.biome.Biomes;\nimport net.minecraft.world.level.block.Block;\nimport net.minecraft.world.level.block.Blocks;\nimport net.minecraft.world.phys.Vec3;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport weather2.WeatherBlocks;\nimport weather2.config.ConfigSand;\nimport weather2.config.ConfigSnow;\nimport weather2.util.CachedNBTTagCompound;\nimport weather2.util.WeatherUtilBlock;\nimport weather2.weathersystem.WeatherManager;\nimport weather2.weathersystem.wind.WindManager;\n\nimport java.util.Random;\n\npublic class WeatherObjectParticleStorm extends WeatherObject {\n\n\tpublic int age = 0;\n\tpublic int maxAge = 20*20;\n\n\tpublic Random rand = new Random();\n\n\tpublic StormType type;\n\n\tpublic enum StormType {\n\t\tSANDSTORM(\"SANDSTORM\"),\n\t\tSNOWSTORM(\"SNOWSTORM\");\n\n\t\tprivate final String key;\n\n\t\tpublic static final StormType[] VALUES = values();\n\n\t\tStormType(String key) {\n\t\t\tthis.key = key;\n\t\t}\n\n\t\tpublic String getKey() {\n\t\t\treturn key;\n\t\t}\n\t}\n\n\tpublic WeatherObjectParticleStorm(WeatherManager parManager) {\n\t\tsuper(parManager);\n\t\t\n\t\tthis.weatherObjectType = EnumWeatherObjectType.SAND;\n\t}\n\t\n\tpublic void initStormSpawn(Vec3 pos) {\n\t\tthis.pos = pos;\n\t\tthis.maxAge = 20*60*5;\n\t}\n\n\tpublic static boolean canSpawnHere(Level world, BlockPos pos, StormType type, boolean forSpawn) {\n\t\tHolder<Biome> biomeIn = world.getBiome(pos);\n\t\tif (type == StormType.SANDSTORM) {\n\t\t\treturn isDesert(biomeIn, forSpawn);\n\t\t} else if (type == StormType.SNOWSTORM) {\n\t\t\treturn isColdForStorm(world, biomeIn, forSpawn, pos);\n\t\t}\n\t\treturn false;\n\t}\n\n\tpublic static boolean isColdForStorm(Level world, Holder<Biome> biome, boolean forSpawn, BlockPos pos) {\n\t\t//return biome.getPrecipitation() == Biome.Precipitation.SNOW;\n\t\t//adjusted to this way to make it work with serene seasons\n\t\tboolean canPrecip = biome.get().getPrecipitationAt(pos) == Biome.Precipitation.RAIN || biome.get().getPrecipitationAt(pos) == Biome.Precipitation.SNOW;\n\t\treturn canPrecip && CoroUtilCompatibility.coldEnoughToSnow(biome.get(), pos, world);\n\t}\n\n\tpublic static boolean isDesert(Holder<Biome> biome, boolean forSpawn) {\n\t\treturn biome.get().equals(Biomes.DESERT) || (!forSpawn && biome.get().equals(Biomes.RIVER)) || biome.unwrap().left().toString().toLowerCase().contains(\"desert\");\n\t}\n\n\t@Override\n\tpublic int getSize() {\n\t\treturn 250;\n\t}\n\t\n\t@Override\n\tpublic void tick() {\n\t\tsuper.tick();\n\n\t\tif (!manager.getWorld().isClientSide()) {\n\t\t\tthis.age++;\n\t\t\t//CULog.dbg(\"this.age: \" + this.age);\n\t\t\tif (this.age > this.maxAge) {\n\t\t\t\tthis.remove();\n\t\t\t}\n\n\t\t\tif (getIntensity() > 0.2D) {\n\t\t\t\ttickBlockSandBuildup();\n\t\t\t}\n\n\t\t\tif (manager != null && manager.getWindManager() != null && !manager.getWindManager().isHighWindEventActive()) {\n\t\t\t\tmanager.getWindManager().stopLowWindEvent();\n\t\t\t\tmanager.getWindManager().startHighWindEvent();\n\t\t\t}\n\t\t}\n\n\t\tposGround = pos;\n\t}\n\n\t/**\n\t * 0-1F for first half of age, 1-0F for second half of age\n\t * @return\n\t */\n\tpublic float getIntensity() {\n\t\tfloat age = this.age;\n\t\tfloat maxAge = this.maxAge;\n\t\tif (age / maxAge <= 0.5F) {\n\t\t\treturn age / (maxAge/2);\n\t\t} else {\n\t\t\treturn 1F - (age / (maxAge/2) - 1F);\n\t\t}\n\t}\n\n\t@OnlyIn(Dist.CLIENT)\n\tpublic void tickClient() {\n\n\t}\n\n\tpublic Block getBlockForBuildup() {\n\t\tif (this.type == StormType.SANDSTORM) {\n\t\t\treturn WeatherBlocks.BLOCK_SAND_LAYER.get();\n\t\t} else if (this.type == StormType.SNOWSTORM) {\n\t\t\treturn Blocks.SNOW;\n\t\t}\n\t\treturn null;\n\t}\n\n\tpublic void tickBlockSandBuildup() {\n\n\t\tLevel world = manager.getWorld();\n\t\tWindManager windMan = manager.getWindManager();\n\n\t\tfloat angle = windMan.getWindAngleForClouds();\n\n\t\t//keep it set to do a lot of work only occasionally, prevents chunk render tick spam for client which kills fps\n\t\tint delay = ConfigSand.Sandstorm_Sand_Buildup_TickRate;\n\t\tint loop = (int)((float)ConfigSand.Sandstorm_Sand_Buildup_LoopAmountBase * getIntensity());\n\t\tboolean buildupOutsideArea = ConfigSand.Sandstorm_Sand_Buildup_AllowOutsideDesert;\n\t\tint maxBlockStackingAllowed = ConfigSand.Sandstorm_Sand_Block_Max_Height;\n\n\t\tif (getType() == StormType.SNOWSTORM) {\n\t\t\tdelay = ConfigSnow.Snowstorm_Snow_Buildup_TickRate;\n\t\t\tloop = (int)((float)ConfigSnow.Snowstorm_Snow_Buildup_LoopAmountBase * getIntensity());\n\t\t\tbuildupOutsideArea = ConfigSnow.Snowstorm_Snow_Buildup_AllowOutsideColdBiomes;\n\t\t\tmaxBlockStackingAllowed = ConfigSnow.Snowstorm_Snow_Block_Max_Height;\n\t\t}\n\n\t\t//delay = 1;\n\n\t\t//sand block buildup\n\t\tif (!world.isClientSide) {\n\t\t\tif (getBlockForBuildup() != null) {\n\t\t\t\tif (world.getGameTime() % delay == 0) {\n\n\t\t\t\t\tfor (int i = 0; i < loop; i++) {\n\n\t\t\t\t\t\t//rate of placement based on storm intensity\n\t\t\t\t\t\tif (rand.nextDouble() >= getIntensity()) continue;\n\n\t\t\t\t\t\tVec3 vecPos = getRandomPosInStorm();\n\n\t\t\t\t\t\tBlockPos blockPos = WeatherUtilBlock.getPrecipitationHeightSafe(world, CoroUtilBlock.blockPos(vecPos.x, 0, vecPos.z));\n\n\t\t\t\t\t\t//avoid unloaded areas\n\t\t\t\t\t\tif (!world.hasChunkAt(blockPos)) continue;\n\n\t\t\t\t\t\tif (buildupOutsideArea ||\n\t\t\t\t\t\t\t\tcanSpawnHere(world, blockPos, getType(), false)) {\n\t\t\t\t\t\t\tWeatherUtilBlock.fillAgainstWallSmoothly(world, new Vec3(blockPos.getX(), blockPos.getY(), blockPos.getZ()), angle, 15, 2, getBlockForBuildup(), maxBlockStackingAllowed);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic Vec3 getRandomPosInStorm() {\n\t\tRandom rand = new Random();\n\t\tint x = (int) Math.floor(posGround.x + rand.nextInt(getSize()) - rand.nextInt(getSize()));\n\t\tint z = (int) Math.floor(posGround.z + rand.nextInt(getSize()) - rand.nextInt(getSize()));\n\t\tint y = WeatherUtilBlock.getPrecipitationHeightSafe(manager.getWorld(), new BlockPos(x, 128, z)).getY();\n\t\tVec3 vec = new Vec3(x, y, z);\n\t\treturn vec;\n\t}\n\t\n\t@Override\n\tpublic int getUpdateRateForNetwork() {\n\t\treturn 1;\n\t}\n\t\n\t@Override\n\tpublic void nbtSyncForClient() {\n\t\tsuper.nbtSyncForClient();\n\t\tCachedNBTTagCompound data = this.getNbtCache();\n\t\tdata.putInt(\"age\", age);\n\t\tdata.putInt(\"maxAge\", maxAge);\n\t\tdata.putString(\"type\", type.key);\n\t\tdata.putString(\"test\", \"WHAT\");\n\t}\n\t\n\t@Override\n\tpublic void nbtSyncFromServer() {\n\t\tsuper.nbtSyncFromServer();\n\t\tCachedNBTTagCompound parNBT = this.getNbtCache();\n\t\tthis.age = parNBT.getInt(\"age\");\n\t\tthis.maxAge = parNBT.getInt(\"maxAge\");\n\t\tif (parNBT.contains(\"type\")) {\n\t\t\tthis.type = StormType.valueOf(parNBT.getString(\"type\").toUpperCase());\n\t\t}\n\t}\n\n\t@Override\n\tpublic void read()\n\t{\n\t\tsuper.read();\n\t\tnbtSyncFromServer();\n\t\tCachedNBTTagCompound var1 = this.getNbtCache();\n\t\tmotion = new Vec3(var1.getDouble(\"vecX\"), var1.getDouble(\"vecY\"), var1.getDouble(\"vecZ\"));\n\t}\n\n\t@Override\n\tpublic void write()\n\t{\n\t\tsuper.write();\n\t\tnbtSyncForClient();\n\n\t\tCachedNBTTagCompound nbt = this.getNbtCache();\n\n\t\tnbt.putDouble(\"vecX\", motion.x);\n\t\tnbt.putDouble(\"vecY\", motion.y);\n\t\tnbt.putDouble(\"vecZ\", motion.z);\n\n\t}\n\n\t@Override\n\tpublic void cleanup() {\n\t\tsuper.cleanup();\n\t}\n\n\t@OnlyIn(Dist.CLIENT)\n\t@Override\n\tpublic void cleanupClient() {\n\t\tsuper.cleanupClient();\n\t}\n\n\tpublic StormType getType() {\n\t\treturn type;\n\t}\n\n\tpublic void setType(StormType type) {\n\t\tthis.type = type;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/storm/WeatherObjectSandstormOld.java",
    "content": "package weather2.weathersystem.storm;\n\nimport java.util.Random;\n\nimport com.corosus.coroutil.util.CoroUtilBlock;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.world.phys.Vec3;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.level.biome.Biome;\nimport net.minecraft.world.level.biome.Biomes;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport net.minecraftforge.registries.ForgeRegistries;\nimport weather2.WeatherBlocks;\nimport weather2.config.ConfigSand;\nimport weather2.util.CachedNBTTagCompound;\nimport weather2.util.WeatherUtilBlock;\nimport weather2.weathersystem.WeatherManager;\nimport weather2.weathersystem.wind.WindManager;\n\npublic class WeatherObjectSandstormOld extends WeatherObject {\n\n\tpublic int age = 0;\n\tpublic int maxAge = 20*20;\n\n\tpublic Random rand = new Random();\n\t\n\tpublic WeatherObjectSandstormOld(WeatherManager parManager) {\n\t\tsuper(parManager);\n\t\t\n\t\tthis.weatherObjectType = EnumWeatherObjectType.SAND;\n\t}\n\t\n\tpublic void initSandstormSpawn(Vec3 pos) {\n\t\tthis.pos = pos;\n\t\tthis.maxAge = 20*60*5;\n\t}\n\t\n\tpublic static boolean isDesert(Biome biome) {\n\t\treturn isDesert(biome, false);\n\t}\n\n\tpublic static boolean isDesert(Biome biome, boolean forSpawn) {\n\t\t//TODO: make sure new comparison works\n\t\tif (ForgeRegistries.BIOMES.getKey(biome) == null) return false;\n\t\treturn biome.equals(Biomes.DESERT) || (!forSpawn && biome.equals(Biomes.RIVER)) || ForgeRegistries.BIOMES.getKey(biome).toString().toLowerCase().contains(\"desert\");\n\t}\n\n\tpublic int getSize() {\n\t\treturn 250;\n\t}\n\t\n\t@Override\n\tpublic void tick() {\n\t\tsuper.tick();\n\n\t\tif (!manager.getWorld().isClientSide()) {\n\t\t\tthis.age++;\n\t\t\t//CULog.dbg(\"this.age: \" + this.age);\n\t\t\tif (this.age > this.maxAge) {\n\t\t\t\tthis.remove();\n\t\t\t}\n\n\t\t\tif (getIntensity() > 0.2D) {\n\t\t\t\ttickBlockSandBuildup();\n\t\t\t}\n\t\t}\n\n\t\tposGround = pos;\n\t}\n\n\t/**\n\t * 0-1F for first half of age, 1-0F for second half of age\n\t * @return\n\t */\n\tpublic float getIntensity() {\n\t\tfloat age = this.age;\n\t\tfloat maxAge = this.maxAge;\n\t\tif (age / maxAge <= 0.5F) {\n\t\t\treturn age / (maxAge/2);\n\t\t} else {\n\t\t\treturn 1F - (age / (maxAge/2) - 1F);\n\t\t}\n\t}\n\n\t@OnlyIn(Dist.CLIENT)\n\tpublic void tickClient() {\n\n\t}\n\n\tpublic void tickBlockSandBuildup() {\n\n\t\tLevel world = manager.getWorld();\n\t\tWindManager windMan = manager.getWindManager();\n\n\t\tfloat angle = windMan.getWindAngleForClouds();\n\n\t\t//keep it set to do a lot of work only occasionally, prevents chunk render tick spam for client which kills fps\n\t\tint delay = ConfigSand.Sandstorm_Sand_Buildup_TickRate;\n\t\tint loop = (int)((float)ConfigSand.Sandstorm_Sand_Buildup_LoopAmountBase * getIntensity());\n\n\t\t//sand block buildup\n\t\tif (!world.isClientSide) {\n\t\t\tif (world.getGameTime() % delay == 0) {\n\n\t\t\t\tfor (int i = 0; i < loop; i++) {\n\n\t\t\t\t\t//rate of placement based on storm intensity\n\t\t\t\t\tif (rand.nextDouble() >= getIntensity()) continue;\n\n\t\t\t\t\tVec3 vecPos = getRandomPosInSandstorm();\n\n\t\t\t\t\tint y = WeatherUtilBlock.getPrecipitationHeightSafe(world, CoroUtilBlock.blockPos(vecPos.x, 0, vecPos.z)).getY();\n\n\t\t\t\t\tBlockPos blockPos = CoroUtilBlock.blockPos(vecPos.x, y, vecPos.z);\n\n\t\t\t\t\t//avoid unloaded areas\n\t\t\t\t\tif (!world.hasChunkAt(blockPos)) continue;\n\n\t\t\t\t\tBiome biomeIn = world.getBiome(blockPos).get();\n\n\t\t\t\t\tif (ConfigSand.Sandstorm_Sand_Buildup_AllowOutsideDesert || isDesert(biomeIn)) {\n\t\t\t\t\t\tWeatherUtilBlock.fillAgainstWallSmoothly(world, new Vec3(blockPos.getX(), blockPos.getY(), blockPos.getZ()), angle, 15, 2, WeatherBlocks.BLOCK_SAND_LAYER.get(), 3);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic Vec3 getRandomPosInSandstorm() {\n\t\tRandom rand = new Random();\n\t\tint x = (int) Math.floor(posGround.x + rand.nextInt(getSize()) - rand.nextInt(getSize()));\n\t\tint z = (int) Math.floor(posGround.z + rand.nextInt(getSize()) - rand.nextInt(getSize()));\n\t\tint y = WeatherUtilBlock.getPrecipitationHeightSafe(manager.getWorld(), new BlockPos(x, 128, z)).getY();\n\t\tVec3 vec = new Vec3(x, y, z);\n\t\treturn vec;\n\t}\n\t\n\t@Override\n\tpublic int getUpdateRateForNetwork() {\n\t\treturn 1;\n\t}\n\t\n\t@Override\n\tpublic void nbtSyncForClient() {\n\t\tsuper.nbtSyncForClient();\n\t\tCachedNBTTagCompound data = this.getNbtCache();\n\t\tdata.putInt(\"age\", age);\n\t\tdata.putInt(\"maxAge\", maxAge);\n\t}\n\t\n\t@Override\n\tpublic void nbtSyncFromServer() {\n\t\tsuper.nbtSyncFromServer();\n\t\tCachedNBTTagCompound parNBT = this.getNbtCache();\n\t\tthis.age = parNBT.getInt(\"age\");\n\t\tthis.maxAge = parNBT.getInt(\"maxAge\");\n\t}\n\n\t@Override\n\tpublic void read()\n\t{\n\t\tsuper.read();\n\t\tnbtSyncFromServer();\n\t\tCachedNBTTagCompound var1 = this.getNbtCache();\n\t\tmotion = new Vec3(var1.getDouble(\"vecX\"), var1.getDouble(\"vecY\"), var1.getDouble(\"vecZ\"));\n\t}\n\n\t@Override\n\tpublic void write()\n\t{\n\t\tsuper.write();\n\t\tnbtSyncForClient();\n\n\t\tCachedNBTTagCompound nbt = this.getNbtCache();\n\n\t\tnbt.putDouble(\"vecX\", motion.x);\n\t\tnbt.putDouble(\"vecY\", motion.y);\n\t\tnbt.putDouble(\"vecZ\", motion.z);\n\n\t}\n\n\t@Override\n\tpublic void cleanup() {\n\t\tsuper.cleanup();\n\t}\n\n\t@OnlyIn(Dist.CLIENT)\n\t@Override\n\tpublic void cleanupClient() {\n\t\tsuper.cleanupClient();\n\t}\n\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/storm/WeatherTypes.java",
    "content": "package weather2.weathersystem.storm;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class WeatherTypes {\n\n\tpublic static List<WeatherEntityConfig> weatherEntTypes;\n\t\n\tstatic {\n\t\tinitWeatherTypes();\n\t}\n\t\n\tpublic static void initWeatherTypes() {\n    \tweatherEntTypes = new ArrayList();\n        WeatherEntityConfig sConf = new WeatherEntityConfig();\n        //0 = spout\n        //1 = F1\n        //2 = F3\n        //3 = F5\n        //4 = F6\n        //5 = Hurricane C1\n        //water spout\n        sConf.tornadoInitialSpeed = 0.2F;\n        sConf.tornadoPullRate = 0.04F;\n        sConf.tornadoLiftRate = 0.05F;\n        sConf.relTornadoSize = 0;\n        sConf.tornadoBaseSize = 3;\n        sConf.tornadoWidthScale = 1.0F;\n        sConf.grabDist = 40D;\n        sConf.tornadoTime = 4500;\n        sConf.type = 0;\n        sConf.grabsBlocks = false;\n        weatherEntTypes.add(sConf);\n        \n        //F1 tornado\n        sConf = new WeatherEntityConfig();\n        sConf.tornadoInitialSpeed = 0.2F;\n        sConf.tornadoPullRate = 0.04F;\n        sConf.tornadoLiftRate = 0.05F;\n        sConf.relTornadoSize = -20;\n        //sConf.tornadoBaseSize = 3;\n        sConf.tornadoWidthScale = 1.5F;\n        //sConf.grabDist = 100D;\n        weatherEntTypes.add(sConf);\n        \n        //F2 tornado\n        sConf = new WeatherEntityConfig();\n        sConf.tornadoInitialSpeed = 0.2F;\n        sConf.tornadoPullRate = 0.04F;\n        sConf.tornadoLiftRate = 0.06F;\n        sConf.relTornadoSize = -30;\n        //sConf.tornadoBaseSize = 6;\n        sConf.tornadoWidthScale = 1.5F;\n        //sConf.grabDist = 100D;\n        weatherEntTypes.add(sConf);\n        \n        //F3 tornado\n        sConf = new WeatherEntityConfig();\n        //sConf.tornadoInitialSpeed = 0.2F;\n        sConf.tornadoPullRate = 0.04F;\n        sConf.tornadoLiftRate = 0.07F;\n        sConf.relTornadoSize = -40;\n        //sConf.tornadoBaseSize = 10;\n        sConf.tornadoWidthScale = 1.9F;\n        weatherEntTypes.add(sConf);\n        \n        //F4 tornado\n        sConf = new WeatherEntityConfig();\n        //sConf.tornadoInitialSpeed = 0.2F;\n        sConf.tornadoPullRate = 0.04F;\n        sConf.tornadoLiftRate = 0.08F;\n        sConf.relTornadoSize = -50;\n        //sConf.tornadoBaseSize = 10;\n        sConf.tornadoWidthScale = 1.9F;\n        weatherEntTypes.add(sConf);\n        \n        //F5 tornado\n        sConf = new WeatherEntityConfig();\n        //sConf.tornadoInitialSpeed = 0.15F;\n        sConf.tornadoPullRate = 0.04F;\n        sConf.tornadoLiftRate = 0.09F;\n        sConf.relTornadoSize = -60;\n        //sConf.tornadoBaseSize = 25;\n        sConf.tornadoWidthScale = 2.5F;\n        weatherEntTypes.add(sConf);\n        \n        //F6\n        sConf = new WeatherEntityConfig();\n        //sConf.tornadoInitialSpeed = 0.15F;\n        sConf.tornadoPullRate = 0.15F;\n        sConf.tornadoLiftRate = 0.10F;\n        sConf.relTornadoSize = -95;\n        //sConf.tornadoBaseSize = 95;\n        sConf.tornadoWidthScale = 3.5F;\n        weatherEntTypes.add(sConf);\n        \n        //Hurricane\n        /*sConf = new WeatherEntityConfig();\n        //sConf.tornadoInitialSpeed = 0.15F;\n        sConf.tornadoPullRate = 0.15F;\n        sConf.tornadoLiftRate = 0.04F;\n        sConf.relTornadoSize = -105;\n        //sConf.tornadoBaseSize = 155;\n        sConf.tornadoWidthScale = 3.5F;\n        //sConf.tornadoTime = 4500;\n        sConf.type = 2;\n        weatherEntTypes.add(sConf);*/\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/tornado/ActiveTornadoConfig.java",
    "content": "package weather2.weathersystem.tornado;\n\nimport net.minecraft.nbt.CompoundTag;\n\n/**\n * Defines the shape and other characteristics of a tornado\n */\npublic class ActiveTornadoConfig {\n\n    private float radiusOfBase;\n    //incremental size of radius per layer\n    private float radiusIncreasePerLayer;\n    private float height;\n    private float spinSpeed;\n    private float entityPullDistXZ;\n    private float entityPullDistXZForY;\n\n    public CompoundTag serialize() {\n        CompoundTag tag = new CompoundTag();\n        tag.putFloat(\"radiusOfBase\", radiusOfBase);\n        tag.putFloat(\"radiusIncreasePerLayer\", radiusIncreasePerLayer);\n        tag.putFloat(\"height\", height);\n        tag.putFloat(\"spinSpeed\", spinSpeed);\n        tag.putFloat(\"entityPullDistXZ\", entityPullDistXZ);\n        tag.putFloat(\"entityPullDistXZForY\", entityPullDistXZForY);\n        return tag;\n    }\n\n    public static ActiveTornadoConfig deserialize(CompoundTag tag) {\n        ActiveTornadoConfig config = new ActiveTornadoConfig();\n        config.setRadiusOfBase(tag.getFloat(\"radiusOfBase\"));\n        config.setRadiusIncreasePerLayer(tag.getFloat(\"radiusIncreasePerLayer\"));\n        config.setHeight(tag.getFloat(\"height\"));\n        config.setSpinSpeed(tag.getFloat(\"spinSpeed\"));\n        config.setEntityPullDistXZ(tag.getFloat(\"entityPullDistXZ\"));\n        config.setEntityPullDistXZForY(tag.getFloat(\"entityPullDistXZForY\"));\n        return config;\n    }\n\n    public float getRadiusOfBase() {\n        return radiusOfBase;\n    }\n\n    public ActiveTornadoConfig setRadiusOfBase(float radiusOfBase) {\n        this.radiusOfBase = radiusOfBase;\n        return this;\n    }\n\n    public float getRadiusIncreasePerLayer() {\n        return radiusIncreasePerLayer;\n    }\n\n    public ActiveTornadoConfig setRadiusIncreasePerLayer(float radiusIncreasePerLayer) {\n        this.radiusIncreasePerLayer = radiusIncreasePerLayer;\n        return this;\n    }\n\n    public float getHeight() {\n        return height;\n    }\n\n    public ActiveTornadoConfig setHeight(float height) {\n        this.height = height;\n        return this;\n    }\n\n    public float getSpinSpeed() {\n        return spinSpeed;\n    }\n\n    public ActiveTornadoConfig setSpinSpeed(float spinSpeed) {\n        this.spinSpeed = spinSpeed;\n        return this;\n    }\n\n    public float getEntityPullDistXZ() {\n        return entityPullDistXZ;\n    }\n\n    public ActiveTornadoConfig setEntityPullDistXZ(float entityPullDistXZ) {\n        this.entityPullDistXZ = entityPullDistXZ;\n        return this;\n    }\n\n    public float getEntityPullDistXZForY() {\n        return entityPullDistXZForY;\n    }\n\n    public ActiveTornadoConfig setEntityPullDistXZForY(float entityPullDistXZForY) {\n        this.entityPullDistXZForY = entityPullDistXZForY;\n        return this;\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/tornado/CatmullRomSpline.java",
    "content": "\n/*******************************************************************************\n * Copyright 2011 See AUTHORS file.\n * \n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * \n *   http://www.apache.org/licenses/LICENSE-2.0\n * \n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n ******************************************************************************/\n\npackage weather2.weathersystem.tornado;\n\nimport net.minecraft.util.Mth;\n\n/** @author Xoppa */\npublic class CatmullRomSpline<T extends Vector<T>> implements Path<T> {\n\t/** Calculates the catmullrom value for the given position (t).\n\t * @param out The Vector to set to the result.\n\t * @param t The position (0<=t<=1) on the spline\n\t * @param points The control points\n\t * @param continuous If true the b-spline restarts at 0 when reaching 1\n\t * @param tmp A temporary vector used for the calculation\n\t * @return The value of out */\n\tpublic static <T extends Vector<T>> T calculate (final T out, final float t, final T[] points, final boolean continuous,\n\t\tfinal T tmp) {\n\t\tfinal int n = continuous ? points.length : points.length - 3;\n\t\tfloat u = t * n;\n\t\tint i = (t >= 1f) ? (n - 1) : (int)u;\n\t\tu -= i;\n\t\treturn calculate(out, i, u, points, continuous, tmp);\n\t}\n\n\t/** Calculates the catmullrom value for the given span (i) at the given position (u).\n\t * @param out The Vector to set to the result.\n\t * @param i The span (0<=i<spanCount) spanCount = continuous ? points.length : points.length - degree\n\t * @param u The position (0<=u<=1) on the span\n\t * @param points The control points\n\t * @param continuous If true the b-spline restarts at 0 when reaching 1\n\t * @param tmp A temporary vector used for the calculation\n\t * @return The value of out */\n\tpublic static <T extends Vector<T>> T calculate (final T out, final int i, final float u, final T[] points,\n\t\tfinal boolean continuous, final T tmp) {\n\t\tfinal int n = points.length;\n\t\tfinal float u2 = u * u;\n\t\tfinal float u3 = u2 * u;\n\t\tout.set(points[i]).scl(1.5f * u3 - 2.5f * u2 + 1.0f);\n\t\tif (continuous || i > 0) out.add(tmp.set(points[(n + i - 1) % n]).scl(-0.5f * u3 + u2 - 0.5f * u));\n\t\tif (continuous || i < (n - 1)) out.add(tmp.set(points[(i + 1) % n]).scl(-1.5f * u3 + 2f * u2 + 0.5f * u));\n\t\tif (continuous || i < (n - 2)) out.add(tmp.set(points[(i + 2) % n]).scl(0.5f * u3 - 0.5f * u2));\n\t\treturn out;\n\t}\n\n\t/** Calculates the derivative of the catmullrom spline for the given position (t).\n\t * @param out The Vector to set to the result.\n\t * @param t The position (0<=t<=1) on the spline\n\t * @param points The control points\n\t * @param continuous If true the b-spline restarts at 0 when reaching 1\n\t * @param tmp A temporary vector used for the calculation\n\t * @return The value of out */\n\tpublic static <T extends Vector<T>> T derivative (final T out, final float t, final T[] points, final boolean continuous,\n\t\tfinal T tmp) {\n\t\tfinal int n = continuous ? points.length : points.length - 3;\n\t\tfloat u = t * n;\n\t\tint i = (t >= 1f) ? (n - 1) : (int)u;\n\t\tu -= i;\n\t\treturn derivative(out, i, u, points, continuous, tmp);\n\t}\n\n\t/** Calculates the derivative of the catmullrom spline for the given span (i) at the given position (u).\n\t * @param out The Vector to set to the result.\n\t * @param i The span (0<=i<spanCount) spanCount = continuous ? points.length : points.length - degree\n\t * @param u The position (0<=u<=1) on the span\n\t * @param points The control points\n\t * @param continuous If true the b-spline restarts at 0 when reaching 1\n\t * @param tmp A temporary vector used for the calculation\n\t * @return The value of out */\n\tpublic static <T extends Vector<T>> T derivative (final T out, final int i, final float u, final T[] points,\n\t\tfinal boolean continuous, final T tmp) {\n\t\t/*\n\t\t * catmull'(u) = 0.5 *((-p0 + p2) + 2 * (2*p0 - 5*p1 + 4*p2 - p3) * u + 3 * (-p0 + 3*p1 - 3*p2 + p3) * u * u)\n\t\t */\n\t\tfinal int n = points.length;\n\t\tfinal float u2 = u * u;\n\t\t// final float u3 = u2 * u;\n\t\tout.set(points[i]).scl(-u * 5 + u2 * 4.5f);\n\t\tif (continuous || i > 0) out.add(tmp.set(points[(n + i - 1) % n]).scl(-0.5f + u * 2 - u2 * 1.5f));\n\t\tif (continuous || i < (n - 1)) out.add(tmp.set(points[(i + 1) % n]).scl(0.5f + u * 4 - u2 * 4.5f));\n\t\tif (continuous || i < (n - 2)) out.add(tmp.set(points[(i + 2) % n]).scl(-u + u2 * 1.5f));\n\t\treturn out;\n\t}\n\n\tpublic T[] controlPoints;\n\tpublic boolean continuous;\n\tpublic int spanCount;\n\tprivate T tmp;\n\tprivate T tmp2;\n\tprivate T tmp3;\n\n\tpublic CatmullRomSpline () {\n\t}\n\n\tpublic CatmullRomSpline (final T[] controlPoints, final boolean continuous) {\n\t\tset(controlPoints, continuous);\n\t}\n\n\tpublic CatmullRomSpline set (final T[] controlPoints, final boolean continuous) {\n\t\tif (tmp == null) tmp = controlPoints[0].cpy();\n\t\tif (tmp2 == null) tmp2 = controlPoints[0].cpy();\n\t\tif (tmp3 == null) tmp3 = controlPoints[0].cpy();\n\t\tthis.controlPoints = controlPoints;\n\t\tthis.continuous = continuous;\n\t\tthis.spanCount = continuous ? controlPoints.length : controlPoints.length - 3;\n\t\treturn this;\n\t}\n\n\t@Override\n\tpublic T valueAt (T out, float t) {\n\t\tfinal int n = spanCount;\n\t\tfloat u = t * n;\n\t\tint i = (t >= 1f) ? (n - 1) : (int)u;\n\t\tu -= i;\n\t\treturn valueAt(out, i, u);\n\t}\n\n\t/** @return The value of the spline at position u of the specified span */\n\tpublic T valueAt (final T out, final int span, final float u) {\n\t\treturn calculate(out, continuous ? span : (span + 1), u, controlPoints, continuous, tmp);\n\t}\n\n\t@Override\n\tpublic T derivativeAt (T out, float t) {\n\t\tfinal int n = spanCount;\n\t\tfloat u = t * n;\n\t\tint i = (t >= 1f) ? (n - 1) : (int)u;\n\t\tu -= i;\n\t\treturn derivativeAt(out, i, u);\n\t}\n\n\t/** @return The derivative of the spline at position u of the specified span */\n\tpublic T derivativeAt (final T out, final int span, final float u) {\n\t\treturn derivative(out, continuous ? span : (span + 1), u, controlPoints, continuous, tmp);\n\t}\n\n\t/** @return The span closest to the specified value */\n\tpublic int nearest (final T in) {\n\t\treturn nearest(in, 0, spanCount);\n\t}\n\n\t/** @return The span closest to the specified value, restricting to the specified spans. */\n\tpublic int nearest (final T in, int start, final int count) {\n\t\twhile (start < 0)\n\t\t\tstart += spanCount;\n\t\tint result = start % spanCount;\n\t\tfloat dst = in.dst2(controlPoints[result]);\n\t\tfor (int i = 1; i < count; i++) {\n\t\t\tfinal int idx = (start + i) % spanCount;\n\t\t\tfinal float d = in.dst2(controlPoints[idx]);\n\t\t\tif (d < dst) {\n\t\t\t\tdst = d;\n\t\t\t\tresult = idx;\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\t@Override\n\tpublic float approximate (T v) {\n\t\treturn approximate(v, nearest(v));\n\t}\n\n\tpublic float approximate (final T in, int start, final int count) {\n\t\treturn approximate(in, nearest(in, start, count));\n\t}\n\n\tpublic float approximate (final T in, final int near) {\n\t\tint n = near;\n\t\tfinal T nearest = controlPoints[n];\n\t\tfinal T previous = controlPoints[n > 0 ? n - 1 : spanCount - 1];\n\t\tfinal T next = controlPoints[(n + 1) % spanCount];\n\t\tfinal float dstPrev2 = in.dst2(previous);\n\t\tfinal float dstNext2 = in.dst2(next);\n\t\tT P1, P2, P3;\n\t\tif (dstNext2 < dstPrev2) {\n\t\t\tP1 = nearest;\n\t\t\tP2 = next;\n\t\t\tP3 = in;\n\t\t} else {\n\t\t\tP1 = previous;\n\t\t\tP2 = nearest;\n\t\t\tP3 = in;\n\t\t\tn = n > 0 ? n - 1 : spanCount - 1;\n\t\t}\n\t\tfloat L1Sqr = P1.dst2(P2);\n\t\tfloat L2Sqr = P3.dst2(P2);\n\t\tfloat L3Sqr = P3.dst2(P1);\n\t\tfloat L1 = (float)Math.sqrt(L1Sqr);\n\t\tfloat s = (L2Sqr + L1Sqr - L3Sqr) / (2f * L1);\n\t\tfloat u = Mth.clamp((L1 - s) / L1, 0f, 1f);\n\t\treturn (n + u) / spanCount;\n\t}\n\n\t@Override\n\tpublic float locate (T v) {\n\t\treturn approximate(v);\n\t}\n\n\t@Override\n\tpublic float approxLength (int samples) {\n\t\tfloat tempLength = 0;\n\t\tfor (int i = 0; i < samples; ++i) {\n\t\t\ttmp2.set(tmp3);\n\t\t\tvalueAt(tmp3, (i) / ((float)samples - 1));\n\t\t\tif (i > 0) tempLength += tmp2.dst(tmp3);\n\t\t}\n\t\treturn tempLength;\n\t}\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/tornado/CubicBezierCurve.java",
    "content": "package weather2.weathersystem.tornado;\n\n\nimport org.joml.Vector3f;\n\n/**\n * source: http://www.java2s.com/Code/Java/2D-Graphics-GUI/AclassthatmodelsaCubicBeziercurve.htm\n */\n\npublic class CubicBezierCurve {\n    private static final long serialVersionUID = -5219859720055898005L;\n    public Vector3f[] P;\n\n    /**\n     * a contructor\n     *\n     * @param pointsVector 4 points that are required to build the bezier curve\n     */\n    public CubicBezierCurve(Vector3f[] pointsVector) {\n        this.P = pointsVector;\n    }\n\n\n    /**\n     * returns the point in 3d space that corresponds to the given value of t\n     *\n     * @param t curve's parameter that should be in the range [0, 1.0]\n     * @return the point in 3d space that corresponds to the given value of t\n     */\n    public Vector3f getValue(float t) {\n        if (t > 1.0 || t < 0.0) {\n            throw new IllegalArgumentException(\"The value of t is out of range: \" + t + \" .\");\n        }\n        float one_minus_t = 1 - t;\n        Vector3f retValue = new Vector3f(0, 0, 0);\n        Vector3f[] terms = new Vector3f[4];\n        terms[0] = calcNewVector(one_minus_t * one_minus_t * one_minus_t, P[0]);\n        terms[1] = calcNewVector(3 * one_minus_t * one_minus_t * t, P[1]);\n        terms[2] = calcNewVector(3 * one_minus_t * t * t, P[2]);\n        terms[3] = calcNewVector(t * t * t, P[3]);\n        for (int i = 0; i < 4; i++) {\n            retValue.add(terms[i]);\n        }\n        return retValue;\n    }\n\n    public Vector3f getValueTest(float t) {\n        if (t > 1.0 || t < 0.0) {\n            throw new IllegalArgumentException(\"The value of t is out of range: \" + t + \" .\");\n        }\n        float one_minus_t = 1 - t;\n        Vector3f retValue = new Vector3f(0, 0, 0);\n        Vector3f[] terms = new Vector3f[6];\n        float magicnumber = 5;\n        terms[0] = calcNewVector(one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t, P[0]);\n        terms[1] = calcNewVector(magicnumber * one_minus_t * one_minus_t * one_minus_t * one_minus_t * t, P[1]);\n        terms[2] = calcNewVector(magicnumber * one_minus_t * one_minus_t * one_minus_t * t * t, P[2]);\n        terms[3] = calcNewVector(magicnumber * one_minus_t * one_minus_t * t * t * t, P[3]);\n        terms[4] = calcNewVector(magicnumber * one_minus_t * t * t * t * t, P[4]);\n        terms[5] = calcNewVector(t * t * t * t * t, P[5]);\n        for (int i = 0; i < 6; i++) {\n            retValue.add(terms[i]);\n        }\n        return retValue;\n    }\n\n    public Vector3f getValueTest10(float t) {\n        if (t > 1.0 || t < 0.0) {\n            throw new IllegalArgumentException(\"The value of t is out of range: \" + t + \" .\");\n        }\n        float one_minus_t = 1 - t;\n        Vector3f retValue = new Vector3f(0, 0, 0);\n        Vector3f[] terms = new Vector3f[10];\n        float mn = 9;\n        terms[0] = calcNewVector(one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t, P[0]);\n        terms[1] = calcNewVector(mn * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * t, P[1]);\n        terms[2] = calcNewVector(mn * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * t * t, P[2]);\n        terms[3] = calcNewVector(mn * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * t * t * t, P[3]);\n        terms[4] = calcNewVector(mn * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * t * t * t * t, P[4]);\n        terms[5] = calcNewVector(mn * one_minus_t * one_minus_t * one_minus_t * one_minus_t * t * t * t * t * t, P[5]);\n        terms[6] = calcNewVector(mn * one_minus_t * one_minus_t * one_minus_t * t * t * t * t * t * t, P[6]);\n        terms[7] = calcNewVector(mn * one_minus_t * one_minus_t * t * t * t * t * t * t * t, P[7]);\n        terms[8] = calcNewVector(mn * one_minus_t * t * t * t * t * t * t * t * t, P[8]);\n        terms[9] = calcNewVector(t * t * t * t * t * t * t * t * t, P[9]);\n        if (t > 0.8F) {\n            int awt = 0;\n        }\n        for (int i = 0; i < 10; i++) {\n            retValue.add(terms[i]);\n        }\n        return retValue;\n    }\n\n    /*public Vector3f getValueTest10(float t) {\n        if (t > 1.0 || t < 0.0) {\n            throw new IllegalArgumentException(\"The value of t is out of range: \" + t + \" .\");\n        }\n        float one_minus_t = 1 - t;\n        Vector3f retValue = new Vector3f(0, 0, 0);\n        Vector3f[] terms = new Vector3f[10];\n        int mn = 9;\n        terms[0] = calcNewVector(one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t, P[0]);\n        terms[1] = calcNewVector(mn * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * t, P[1]);\n        terms[2] = calcNewVector(mn * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * t * t, P[2]);\n        terms[3] = calcNewVector(mn * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * t * t * t, P[3]);\n        terms[4] = calcNewVector(mn * one_minus_t * one_minus_t * one_minus_t * one_minus_t * one_minus_t * t * t * t * t, P[4]);\n        terms[5] = calcNewVector(mn * one_minus_t * one_minus_t * one_minus_t * one_minus_t * t * t * t * t * t, P[5]);\n        terms[6] = calcNewVector(mn * one_minus_t * one_minus_t * one_minus_t * t * t * t * t * t * t, P[6]);\n        terms[7] = calcNewVector(mn * one_minus_t * one_minus_t * t * t * t * t * t * t * t, P[7]);\n        terms[8] = calcNewVector(mn * one_minus_t * t * t * t * t * t * t * t * t, P[8]);\n        terms[9] = calcNewVector(t * t * t * t * t * t * t * t * t, P[9]);\n        for (int i = 0; i < 10; i++) {\n            retValue.add(terms[i]);\n        }\n        return retValue;\n    }*/\n\n    /**\n     * calculates and returns a new vector that is base * scaler\n     *\n     * @param scaler\n     * @param base\n     * @return\n     */\n    private Vector3f calcNewVector(float scaler, Vector3f base) {\n        //Vector3f retValue = new Vector3f(base);\n        Vector3f retValue = new Vector3f(base.x(), base.y(), base.z());\n        retValue.mul(scaler);\n        return retValue;\n    }\n\n}"
  },
  {
    "path": "src/main/java/weather2/weathersystem/tornado/Path.java",
    "content": "/*******************************************************************************\n * Copyright 2011 See AUTHORS file.\n * \n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n * \n *   http://www.apache.org/licenses/LICENSE-2.0\n * \n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n ******************************************************************************/\n\npackage weather2.weathersystem.tornado;\n\n/** Interface that specifies a path of type T within the window 0.0<=t<=1.0.\n * @author Xoppa */\npublic interface Path<T> {\n\tT derivativeAt (T out, float t);\n\n\t/** @return The value of the path at t where 0<=t<=1 */\n\tT valueAt (T out, float t);\n\n\t/** @return The approximated value (between 0 and 1) on the path which is closest to the specified value. Note that the\n\t *         implementation of this method might be optimized for speed against precision, see {@link #locate(Object)} for a more\n\t *         precise (but more intensive) method. */\n\tfloat approximate (T v);\n\n\t/** @return The precise location (between 0 and 1) on the path which is closest to the specified value. Note that the\n\t *         implementation of this method might be CPU intensive, see {@link #approximate(Object)} for a faster (but less\n\t *         precise) method. */\n\tfloat locate (T v);\n\n\t/** @param samples The amount of divisions used to approximate length. Higher values will produce more precise results, but\n\t *           will be more CPU intensive.\n\t * @return An approximated length of the spline through sampling the curve and accumulating the euclidean distances between the\n\t *         sample points. */\n\tfloat approxLength (int samples);\n\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/tornado/TornadoFunnel.java",
    "content": "package weather2.weathersystem.tornado;\n\nimport com.corosus.coroutil.util.CoroUtilBlock;\nimport extendedrenderer.particle.ParticleRegistry;\nimport extendedrenderer.particle.entity.ParticleTexFX;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.client.multiplayer.ClientLevel;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.entity.player.Player;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.phys.Vec3;\nimport org.joml.Matrix3f;\nimport org.joml.Quaternionf;\nimport org.joml.Vector3d;\nimport org.joml.Vector3f;\n\nimport java.util.*;\n\n/**\n * To contain the full funnel, with each component piece\n */\npublic class TornadoFunnel {\n\n    public Vector3d pos = new Vector3d(0, 0, 0);\n\n    public LinkedList<FunnelPiece> listFunnel = new LinkedList();\n\n    //temp?\n\n    public int amountPerLayer = 30;\n    public int particleCount = amountPerLayer * 50;\n    public int funnelPieces = 2;\n\n    CubicBezierCurve bezierCurve;\n\n    static class FunnelPiece {\n\n        public List<ParticleTexFX> listParticles = new ArrayList<>();\n\n        public Vector3d posStart = new Vector3d(0, 0, 0);\n        public Vector3d posEnd = new Vector3d(0, 20, 0);\n\n        //public Vector3d vecDir = new Vector3d(0, 0, 0);\n        public float vecDirX = 0;\n        public float vecDirZ = 0;\n\n        public boolean needInit = true;\n\n        CubicBezierCurve bezierCurve;\n\n    }\n\n    public TornadoFunnel() {\n\n    }\n\n    public void tickGame() {\n\n        amountPerLayer = 30;\n        particleCount = amountPerLayer * 50;\n        funnelPieces = 10;\n\n\n\n        tickGameTestCreate();\n        tickUpdateFunnel();\n    }\n\n    private void tickGameTestCreate() {\n\n        Player entP = Minecraft.getInstance().player;\n\n        Random rand = new Random();\n\n        //listFunnel.clear();\n\n        while (listFunnel.size() < funnelPieces) {\n            addPieceToEnd(new FunnelPiece());\n        }\n\n        //for (FunnelPiece piece : listFunnel) {\n        for (int i = 0; i < listFunnel.size(); i++) {\n            FunnelPiece piece = listFunnel.get(i);\n\n            if (piece.needInit) {\n                piece.needInit = false;\n\n                int height = 10;\n                //temp\n                //TODO: LINK TO PREVIOUS OR NEXT PIECE IF THERE IS ONE\n                if (i == 0) {\n                    piece.posStart = new Vector3d(entP.getX(), entP.getY(), entP.getZ());\n                    piece.posEnd = new Vector3d(entP.getX(), entP.getY() + height, entP.getZ());\n                    //piece.posEnd = new Vector3d(entP.posX, entP.posY + entP.getEyeHeight(), entP.posZ);\n\n                } else {\n                    Vector3d prev = listFunnel.get(i-1).posEnd;\n                    piece.posStart = new Vector3d(prev.x, prev.y, prev.z);\n                    piece.posEnd = new Vector3d(piece.posStart.x, piece.posStart.y + height, piece.posStart.z);\n                }\n\n                if (i == funnelPieces - 1) {\n                    piece.posEnd = new Vector3d(piece.posStart.x, piece.posStart.y + height, piece.posStart.z);\n                }\n\n                piece.vecDirX = rand.nextBoolean() ? 1 : -1;\n                piece.vecDirZ = rand.nextBoolean() ? 1 : -1;\n            }\n\n            double dist = distanceTo(piece.posStart, piece.posEnd);\n\n            double sizeXYParticle = 1;\n            double funnelRadius = 3;\n\n            double circumference = funnelRadius * 2D * Math.PI;\n\n            amountPerLayer = (int) (circumference / sizeXYParticle);\n            int layers = (int) (dist / sizeXYParticle);\n\n            particleCount = layers * amountPerLayer;\n\n            /*while (piece.listParticles.size() > particleCount) {\n                piece.listParticles.get(piece.listParticles.size() - 1).setExpired();\n                piece.listParticles.remove(piece.listParticles.size() - 1);\n            }*/\n\n            /*while (piece.listParticles.size() > 0) {\n                piece.listParticles.get(piece.listParticles.size() - 1).setExpired();\n                piece.listParticles.remove(piece.listParticles.size() - 1);\n            }*/\n\n            if (piece.bezierCurve == null || entP.level().getGameTime() % 40 == 0) {\n                Vector3f[] vecs = new Vector3f[4];\n                for (int ii = 0; ii < vecs.length; ii++) {\n                    vecs[ii] = new Vector3f(entP.level().random.nextFloat(), entP.level().random.nextFloat(), entP.level().random.nextFloat());\n                }\n                piece.bezierCurve = new CubicBezierCurve(vecs);\n            }\n\n            if (bezierCurve == null || entP.level().getGameTime() % 40 == 0) {\n                Vector3f[] vecs = new Vector3f[4];\n                for (int ii = 0; ii < vecs.length; ii++) {\n                    vecs[ii] = new Vector3f(entP.level().random.nextFloat(), entP.level().random.nextFloat(), entP.level().random.nextFloat());\n                }\n                bezierCurve = new CubicBezierCurve(vecs);\n            }\n\n            while (piece.listParticles.size() < particleCount) {\n                BlockPos pos = CoroUtilBlock.blockPos(piece.posEnd.x, piece.posEnd.y, piece.posEnd.z);\n\n                //if (entP.getDistanceSq(pos) < 10D * 10D) continue;\n\n                //pos = world.getPrecipitationHeight(pos).add(0, 1, 0);\n\n                ClientLevel world = (ClientLevel)entP.level();\n\n                ParticleTexFX particleTest = new ParticleTexFX(world, pos.getX() + rand.nextFloat(),\n                        pos.getY(),\n                        pos.getZ() + rand.nextFloat(), 0, 0, 0, ParticleRegistry.square16);\n\n                //particleTest.setSprite();\n                particleTest.setMaxAge(250);\n                particleTest.setParticleSpeed(0, 0, 0);\n                particleTest.setScale(0.1F);\n                //particleTest.setColor(0.1F * (particles.size() % particleCountCircle), 0, 0);\n                particleTest.setColor(world.random.nextFloat(), world.random.nextFloat(), world.random.nextFloat());\n                particleTest.setGravity(0);\n                /*if (piece.listParticles.size() < particleCountCircle * 5) {\n                    particleTest.setColor(1, 1, 1);\n                }*/\n                //particleTest.move(0, -0.1, 0);\n                Minecraft.getInstance().particleEngine.add(particleTest);\n\n                piece.listParticles.add(particleTest);\n            }\n        }\n\n        //reset\n        /*for (int i = 0; i < listFunnel.size(); i++) {\n            FunnelPiece piece = listFunnel.get(i);\n\n            while (piece.listParticles.size() > particleCount) {\n                piece.listParticles.get(piece.listParticles.size() - 1).setExpired();\n                piece.listParticles.remove(piece.listParticles.size() - 1);\n            }\n        }*/\n        //listFunnel.clear();\n\n\n    }\n\n    private void tickUpdateFunnel() {\n\n        Level world = Minecraft.getInstance().level;\n        Player player = Minecraft.getInstance().player;\n\n        //for (FunnelPiece piece : listFunnel) {\n        for (int ii = 0; ii < listFunnel.size(); ii++) {\n            FunnelPiece piece = listFunnel.get(ii);\n\n            /*if (ii == listFunnel.size() - 1) {\n                piece.posEnd = new Vector3d(piece.posStart.x, piece.posStart.y + 20, piece.posStart.z);\n            }*/\n\n            double rate = 0.2F/* + (ii * 0.1F)*/;\n            double distMax = 5 + (listFunnel.size() - ii);\n\n            Random rand = new Random();\n\n            piece.posEnd.add(new Vector3d(rate * piece.vecDirX, 0, rate * piece.vecDirZ * 0.7));\n            //piece.posEnd = piece.posEnd.add(rate * random.nextFloat() * piece.vecDirX, 0, rate * random.nextFloat() * piece.vecDirZ);\n\n            int offset = 360 / listFunnel.size();\n            long timeC = (world.getGameTime() * (ii+1) + (offset * ii)) * 1;\n            float range = 35F;\n\n            //piece.posEnd = new Vector3d(piece.posStart.x + Math.sin(Math.toRadians(timeC % 360)) * range, piece.posStart.y + 3, piece.posStart.z + Math.cos(Math.toRadians(timeC % 360)) * range);\n\n            //piece.posEnd.\n\n            //piece.posEnd = piece.posEnd.addVector(-1, 0, 0);\n\n            float speedAmp = 0.3F;\n\n            double xx1 = piece.posEnd.x - piece.posStart.x;\n            double zz1 = piece.posEnd.z - piece.posStart.z;\n            double xzDist2 = (double) Mth.sqrt((float) (xx1 * xx1 + zz1 * zz1));\n\n            if (xzDist2 > distMax) {\n                if (piece.posEnd.x - piece.posStart.x > 0) {\n                    piece.vecDirX = -1;\n                    piece.vecDirX *= (0.5F + rand.nextFloat()) + (ii * speedAmp);\n                }\n\n                if (piece.posEnd.x - piece.posStart.x < 0) {\n                    piece.vecDirX = 1;\n                    piece.vecDirX *= (0.5F + rand.nextFloat()) + (ii * speedAmp);\n                }\n\n                if (piece.posEnd.z - piece.posStart.z > 0) {\n                    piece.vecDirZ = -1;\n                    piece.vecDirZ *= (0.5F + rand.nextFloat()) + (ii * speedAmp);\n                }\n\n                if (piece.posEnd.z - piece.posStart.z < 0) {\n                    piece.vecDirZ = 1;\n                    piece.vecDirZ *= (0.5F + rand.nextFloat()) + (ii * speedAmp);\n                }\n            }\n\n            /*if (Math.abs(piece.posStart.x - piece.posEnd.x) > distMax) {\n                piece.vecDirX *= -1;\n            }\n\n            if (Math.abs(piece.posStart.z - piece.posEnd.z) > distMax) {\n                piece.vecDirZ *= -1;\n            }*/\n\n            if (ii > 0) {\n                Vector3d prev = listFunnel.get(ii-1).posEnd;\n                piece.posStart = new Vector3d(prev.x, prev.y, prev.z);\n            }\n\n            double dist = distanceTo(piece.posStart, piece.posEnd);\n\n            double x1 = piece.posEnd.x - piece.posStart.x;\n            double y1 = piece.posEnd.y - piece.posStart.y;\n            double z1 = piece.posEnd.z - piece.posStart.z;\n            Vector3d vec = new Vector3d(x1 / dist, y1 / dist, z1 / dist);\n\n            double sizeXYParticle = 1;\n            double funnelRadius = 3;\n\n            double circumference = funnelRadius * 2D * Math.PI;\n\n            amountPerLayer = (int) (circumference / sizeXYParticle);\n            int layers = (int) (dist / sizeXYParticle);\n\n            particleCount = layers * amountPerLayer;\n\n            Iterator<ParticleTexFX> it = piece.listParticles.iterator();\n            int index = 0;\n            while (it.hasNext()) {\n                ParticleTexFX part = it.next();\n                if (!part.isAlive()) {\n                    it.remove();\n                } else {\n\n                    int particleCountCircle = 20;\n                    int particleCountLayers = 40;\n\n                    int yIndex = index / amountPerLayer;\n                    int rotIndex = index % amountPerLayer;\n                    int yCount = particleCount / amountPerLayer;\n\n                    float x = 0;//((world.getGameTime() * 0.5F) % 360);\n                    float y = /*((world.getGameTime() * 3) % 360) + */((index % particleCountCircle) * (360 / particleCountCircle));\n                    float y2 = ((world.getGameTime() * 3) % 360) + ((index % particleCountCircle) * (360 / particleCountCircle));\n                    float z = 0;//((world.getGameTime() * 0.3F) % 360);\n\n\n                    int testY = 100;\n\n                    //float dist2 = (float)Math.sqrt(player.getDistanceSq(0.5, testY, 0.5));\n                    float dist2 = (float)Math.sqrt(distanceTo(piece.posStart, piece.posEnd));\n\n                    Vector3f vecDiff = new Vector3f(\n                            (float)(piece.posStart.x - piece.posEnd.x) / dist2,\n                            (float)(piece.posStart.y - piece.posEnd.y) / dist2,\n                            (float)(piece.posStart.z - piece.posEnd.z) / dist2);\n                    Vector3f vecAngles = new Vector3f(\n                            (float)Math.atan2(vecDiff.y(), vecDiff.z()),\n                            (float)Math.atan2(vecDiff.z(), vecDiff.x()), //invert if needed\n                            (float)Math.atan2(vecDiff.x(), vecDiff.y())); //invert if needed\n\n                    //convert to degrees\n                    vecAngles = new Vector3f((float)Math.toDegrees(vecAngles.x()), (float)Math.toDegrees(vecAngles.y()), (float)Math.toDegrees(vecAngles.z()));\n\n                    double xx = piece.posStart.x - piece.posEnd.x;\n                    double zz = piece.posStart.z - piece.posEnd.z;\n                    double xzDist = Math.sqrt(xx * xx + zz * zz);\n                    float pitchAngle = (float)Math.toDegrees(Math.atan2(vecDiff.y(), xzDist / dist2));\n\n                    pitchAngle += 90;\n                    y = vecAngles.y() - 90;\n\n                    double curvePoint = Math.min(1F, (float)(index / particleCountCircle) / (float)particleCountLayers);\n                    double curvePoint2 = (index / particleCountCircle);\n                    double yDiff = curvePoint2 * (dist / particleCountLayers)/* - (particleCountLayers / 2)*/;\n                    float yDiffDist = 2F;\n                    float curveAmp = 1F;\n\n                    Quaternionf quaternionY = new Quaternionf(0.0F, 1.0F, 0.0F, Math.toRadians(-y));\n                    Quaternionf quaternionYCircle = new Quaternionf(0.0F, 1.0F, 0.0F, Math.toRadians(-y2));\n\n                    Quaternionf quatPitch = new Quaternionf(1.0F, 0.0F, 0.0F, Math.toRadians(-pitchAngle));\n                    Vector3f vecCurve = piece.bezierCurve.getValue((float)curvePoint);\n                    //Vector3f vecNew = new Vector3f((float)vecCurve.x * curveAmp, 1 + ((float)yDiff) * yDiffDist, (float)vecCurve.z * curveAmp);\n                    Vector3f vecNew = new Vector3f((float)vecCurve.x() * curveAmp, 1 + ((float)yDiff) * yDiffDist, (float)vecCurve.z() * curveAmp);\n                    //Vector3f vecNew = new Vector3f((float)0, 1 + ((float)yDiff) * yDiffDist, (float)0);\n\n                    float rotAroundPosX = 0;\n                    float rotAroundPosY = 0;\n                    float rotAroundPosZ = 0;\n                    Matrix3f matrix = new Matrix3f();\n                    matrix.rotation(quaternionY);\n                    matrix.rotation(quatPitch);\n                    //multiply in the radial shape of the tornado\n                    matrix.rotation(quaternionYCircle);\n                    vecNew.mulTranspose(matrix);\n\n                    rotAroundPosX = vecNew.x();\n                    rotAroundPosY = vecNew.y();\n                    rotAroundPosZ = vecNew.z();\n\n                    //part.setPosition(player.getX() + rotAroundPosX, player.getY() + rotAroundPosY, player.getZ() + rotAroundPosZ);\n                    //part.setPosition(pos.x + rotAroundPosX, pos.y + rotAroundPosY, pos.z + rotAroundPosZ);\n                    //part.setPosition(pos.x + x1, pos.y + y1, pos.z + z1);\n                    part.setPosition(piece.posStart.x + rotAroundPosX, piece.posStart.y + rotAroundPosY, piece.posStart.z + rotAroundPosZ);\n                }\n\n                index++;\n            }\n        }\n    }\n\n    public void addPieceToEnd(FunnelPiece piece) {\n        listFunnel.addLast(piece);\n    }\n\n    public double distanceTo(Vector3d vec1, Vector3d p_82555_) {\n        double d0 = p_82555_.x - vec1.x;\n        double d1 = p_82555_.y - vec1.y;\n        double d2 = p_82555_.z - vec1.z;\n        return Math.sqrt(d0 * d0 + d1 * d1 + d2 * d2);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/tornado/TornadoManagerTodoRenameMe.java",
    "content": "package weather2.weathersystem.tornado;\n\nimport extendedrenderer.particle.ParticleRegistry;\nimport extendedrenderer.particle.entity.ParticleTexFX;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.entity.player.Player;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.phys.Vec2;\nimport net.minecraft.world.phys.Vec3;\nimport org.joml.Matrix3f;\nimport org.joml.Quaternionf;\nimport org.joml.Vector3d;\nimport org.joml.Vector3f;\nimport weather2.weathersystem.tornado.simple.TornadoFunnelSimple;\n\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\n\npublic class TornadoManagerTodoRenameMe {\n\n    private Class lastScreenClass = null;\n\n    private ParticleTexFX particleTest = null;\n    private List<ParticleTexFX> particles = new ArrayList<>();\n\n    private TornadoFunnel funnel;\n\n    private TornadoFunnelSimple funnelSimple;\n\n    //public CubicBezierCurve bezierCurve;\n    public List<CubicBezierCurve> curves = new ArrayList<>();\n\n    public Vector3f[] vecSpeeds = new Vector3f[10];\n\n    public void tick(Level world) {\n        Minecraft mc = Minecraft.getInstance();\n        if (mc.level == null || mc.player == null) return;\n\n        if (mc.level.getGameTime() % 1 == 0) {\n            for (Player playerEntity : mc.level.players()) {\n                if (true || mc.player.distanceTo(playerEntity) < 20) {\n\n                    int particleCountCircle = 20;\n                    int particleCountLayers = 40;\n\n                    while (particles.size() < particleCountCircle * particleCountLayers) {\n                        particleTest = new ParticleTexFX(mc.level, playerEntity.getX(), playerEntity.getY() + 2.2, playerEntity.getZ(), 0, 0, 0, ParticleRegistry.square16);\n                        //particleTest.setSprite(ParticleRegistry.square16);\n                        particleTest.setMaxAge(250);\n                        //particleTest.setMotion(0, 0, 0);\n                        particleTest.setScale(0.2F);\n                        //particleTest.setColor(0.1F * (particles.size() % particleCountCircle), 0, 0);\n                        particleTest.setColor(world.random.nextFloat(), world.random.nextFloat(), world.random.nextFloat());\n                        if (particles.size() < particleCountCircle * 5) {\n                            particleTest.setColor(1, 1, 1);\n                        }\n                        float randGrey = 0.4F + (world.random.nextFloat() * 0.4F);\n                        particleTest.setColor(randGrey, randGrey, randGrey);\n                        //particleTest.move(0, -0.1, 0);\n                        mc.particleEngine.add(particleTest);\n                        //particleTest.setAngle(world.randomom.nextInt(360));\n                        particles.add(particleTest);\n                    }\n\n                    int testY = 100;\n\n                    Vector3f pos1 = new Vector3f(0.5F, 70, 0.5F);\n                    Vector3f pos2 = new Vector3f(0.5F, 120, 0.5F);\n\n                    /*float dist = (float)Math.sqrt(playerEntity.getDistanceSq(0.5, testY, 0.5));\n                    Vector3f vecDiff = new Vector3f(\n                            (float)(playerEntity.getPosX() - 0.5) / dist,\n                            (float)(playerEntity.getPosY() - testY) / dist,\n                            (float)(playerEntity.getPosZ() - 0.5) / dist);\n                    Vector3f vecAngles = new Vector3f(\n                            (float)Math.atan2(vecDiff.y(), vecDiff.z()),\n                            (float)Math.atan2(vecDiff.z(), vecDiff.x()), //invert if needed\n                            (float)Math.atan2(vecDiff.x(), vecDiff.y())); //invert if needed*/\n\n                    float dist = getDistance(pos1, pos2);\n                    Vector3f vecDiff = new Vector3f(\n                            (pos1.x() - pos2.x()) / dist,\n                            (pos1.y() - pos2.y()) / dist,\n                            (pos1.z() - pos2.z()) / dist);\n                    Vector3f vecAngles = new Vector3f(\n                            (float)Math.atan2(vecDiff.y(), vecDiff.z()),\n                            (float)Math.atan2(vecDiff.z(), vecDiff.x()), //invert if needed\n                            (float)Math.atan2(vecDiff.x(), vecDiff.y())); //invert if needed\n\n                    //convert to degrees\n                    vecAngles = new Vector3f((float)Math.toDegrees(vecAngles.x()), (float)Math.toDegrees(vecAngles.y()), (float)Math.toDegrees(vecAngles.z()));\n\n                    double xx = pos1.x() - pos2.x();\n                    double zz = pos1.z() - pos2.z();\n                    double xzDist = Math.sqrt(xx * xx + zz * zz);\n                    float pitchAngle = (float)Math.toDegrees(Math.atan2(vecDiff.y(), xzDist / dist));\n\n                    pitchAngle += 90;\n\n                    /*if (playerEntity.isSprinting()) {\n                        curves.clear();\n                    }*/\n\n                    while (curves.size() < 2) {\n                        //Vector3f[] vecs = new Vector3f[4];\n                        Vector3f[] vecs = new Vector3f[10];\n                        for (int i = 0; i < vecs.length; i++) {\n                            vecs[i] = new Vector3f(world.random.nextFloat(), world.random.nextFloat(), world.random.nextFloat());\n                        }\n                        curves.add(new CubicBezierCurve(vecs));\n                    }\n\n                    //trying to smooth the 2 curves together...\n\n                    //match the end and start of each curve\n                    //tempoffcurves.get(1).P[0].set(curves.get(0).P[3].x(), curves.get(0).P[3].y(), curves.get(0).P[3].z());\n\n                    //try to match the previous second last to the next second so it'd present a smooth curve\n                    //doesnt work, or im doing it wrong\n                    //curves.get(1).P[1].set(1F - curves.get(0).P[2].x(), 1F - curves.get(0).P[2].y(), 1F - curves.get(0).P[2].z());\n\n                    //curves.get(1).P[0].set(1F-curves.get(0).P[3].x(), 1F-curves.get(0).P[3].y(), 1F-curves.get(0).P[3].z());\n                    //curves.get(1).P[1].set(1F-curves.get(0).P[2].x(), 1F-curves.get(0).P[2].y(), 1F-curves.get(0).P[2].z());\n                    //curves.get(1).P[2].set(1F-curves.get(0).P[1].x(), 1F-curves.get(0).P[1].y(), 1F-curves.get(0).P[1].z());\n                    //curves.get(1).P[3].set(1F-curves.get(0).P[0].x(), 1F-curves.get(0).P[0].y(), 1F-curves.get(0).P[0].z());\n\n                    CubicBezierCurve bezierCurve = curves.get(0);\n\n                    /*if (bezierCurve == null) {\n                        Vector3f[] vecs = new Vector3f[4];\n                        for (int i = 0; i < vecs.length; i++) {\n                            vecs[i] = new Vector3f(world.random.nextFloat(), world.random.nextFloat(), world.random.nextFloat());\n                        }\n                        bezierCurve = new CubicBezierCurve(vecs);\n                    }*/\n\n                    /*if (bezierCurve != null) {\n                        float randScale = 0.1F;\n                        for (int i = 0; i < bezierCurve.P.length; i++) {\n                            bezierCurve.P[i].add((world.random.nextFloat() - world.random.nextFloat()) * randScale, (world.random.nextFloat() - world.random.nextFloat()) * randScale, (world.random.nextFloat() - world.random.nextFloat()) * randScale);\n                            bezierCurve.P[i].normalize();\n                        }\n                    }*/\n\n                    if (bezierCurve != null && true) {\n                        float randScale = 0.1F;\n                        for (int i = 0; i < bezierCurve.P.length; i++) {\n                            if (vecSpeeds[i] == null) {\n                                vecSpeeds[i] = new Vector3f(world.random.nextFloat(), world.random.nextFloat(), world.random.nextFloat());\n                            }\n\n                            bezierCurve.P[i].add(vecSpeeds[i].x() * 0.01F, vecSpeeds[i].y() * 0.01F, vecSpeeds[i].z() * 0.01F);\n\n                            float maxY = 1F;\n                            float minY = 0F;\n\n                            /*if (i == 0) {\n                                maxY = 0.25F;\n                                minY = 0.0F;\n                            } else if (i == 1) {\n                                maxY = 0.9F;\n                                minY = 0.1F;\n                            } else if (i == 2) {\n                                maxY = 0.9F;\n                                minY = 0.1F;\n                            } else if (i == 3) {\n                                maxY = 1.0F;\n                                minY = 0.75F;\n                            }*/\n\n                            //maxY += 2;\n                            float minXZ = 0;\n                            float maxXZ = 1;\n\n                            float randSpeed = 1.5F;\n\n                            if (bezierCurve.P[i].x() > maxXZ) {\n                                vecSpeeds[i].set(world.random.nextFloat() * -1 * randSpeed, vecSpeeds[i].y(), vecSpeeds[i].z());\n                            } else if (bezierCurve.P[i].x() < minXZ) {\n                                vecSpeeds[i].set(world.random.nextFloat() * randSpeed, vecSpeeds[i].y(), vecSpeeds[i].z());\n                            }\n                            if (bezierCurve.P[i].y() > maxY) {\n                                vecSpeeds[i].set(vecSpeeds[i].x(), world.random.nextFloat() * -1 * randSpeed, vecSpeeds[i].z());\n                            } else if (bezierCurve.P[i].y() < minY) {\n                                vecSpeeds[i].set(vecSpeeds[i].x(), world.random.nextFloat() * randSpeed, vecSpeeds[i].z());\n                            }\n                            if (bezierCurve.P[i].z() > maxXZ) {\n                                vecSpeeds[i].set(vecSpeeds[i].x(), vecSpeeds[i].y(), world.random.nextFloat() * -1 * randSpeed);\n                            } else if (bezierCurve.P[i].z() < minXZ) {\n                                vecSpeeds[i].set(vecSpeeds[i].x(), vecSpeeds[i].y(), world.random.nextFloat() * randSpeed);\n                            }\n                            //bezierCurve.P[i].normalize();\n                        }\n\n                        //bezierCurve.P[0] = new Vector3f(0.5F, 0, 0.5F);\n\n                        //base of tornado\n                        //bezierCurve.P[1].set(bezierCurve.P[0].x(), bezierCurve.P[0].y() + 0.3F, bezierCurve.P[0].z());\n                        //bezierCurve.P[0].set(bezierCurve.P[3].x(), 0, bezierCurve.P[3].z());\n                        //bezierCurve.P[0].set(bezierCurve.P[0].x(), 0, bezierCurve.P[0].z());\n\n                        //top of tornado\n                        //bezierCurve.P[2].set(bezierCurve.P[3].x(), bezierCurve.P[3].y() - 0.3F, bezierCurve.P[3].z());\n                        //bezierCurve.P[3].set(bezierCurve.P[3].x(), 1, bezierCurve.P[3].z());\n                        if (bezierCurve.P.length == 6) {\n                            //bezierCurve.P[5].set(bezierCurve.P[5].x(), 1, bezierCurve.P[5].z());\n                        }\n\n                        //bezierCurve.P[0].set(0.5F, 0, 0.5F);\n                        //bezierCurve.P[1].set(0.5F, 0.8F, 0.5F);\n                        //bezierCurve.P[2].set(0.5F, 0.2F, 0.5F);\n                        //bezierCurve.P[3].set(0.5F, 0.99F, 0.5F);\n\n                        //bezierCurve.P[3] = new Vector3f(0.5F, 1, 0.5F);\n                    }\n\n                    /*if (mc.level.getGameTime() % 40 == 0 && WATUT.playerManagerClient.getPlayerStatus(mc.player.getUniqueID()).getStatusType() == PlayerStatus.StatusType.CHAT) {\n                        System.out.println(\"x: \" + vecAngles.x());\n                        System.out.println(\"y: \" + vecAngles.y());\n                        System.out.println(\"z: \" + vecAngles.z());\n                        //System.out.println(\"yDiff: \" + (playerEntity.getPosY() - testY));\n                        System.out.println(\"pitchAngle: \" + pitchAngle);\n                    }*/\n\n                    Iterator<ParticleTexFX> it = particles.iterator();\n                    int index = 0;\n\n                    float adjustedCurvePos = 0;\n\n                    while (it.hasNext()) {\n                        ParticleTexFX particle = it.next();\n                        if (!particle.isAlive()) {\n                            it.remove();\n                        } else {\n                            //(index * (360 / particleCount))\n                            float x = 0;//((world.getGameTime() * 0.5F) % 360);\n                            float y2 = ((world.getGameTime() * 2) % 360) + ((index % particleCountCircle) * (360 / particleCountCircle));\n                            float y = /*((world.getGameTime() * 3) % 360) + */((index % particleCountCircle) * (360 / particleCountCircle));\n                            float z = 0;//((world.getGameTime() * 0.3F) % 360);\n\n                            y = vecAngles.y() - 90;\n\n                            int yDiff = (index / particleCountCircle) - (particleCountLayers / 2);\n                            float yDiffDist = 0.01F;\n\n                            int curLayer = (index / particleCountCircle);\n                            float curvePoint = (float)curLayer / (float)particleCountLayers * 1F;\n                            float curvePoint2 = (float)Math.min(1D, (float)(curLayer+1) / (float)particleCountLayers) * 1F;\n                            float stretchCurveY = 4F;\n                            float curveAmp = 2F;\n                            y2 = ((world.getGameTime() * (7 + (particleCountLayers - curLayer) * (particleCountLayers - curLayer) * 0.02F)) % 360) + ((index % particleCountCircle) * (360 / particleCountCircle));\n                            //y2 = ((index % particleCountCircle) * (360 / particleCountCircle));\n\n                            float distFinal = dist / 2F;\n\n                            /*Vector3f vecCurve1 = bezierCurve.getValue(curvePoint);\n                            Vector3f vecCurve2 = bezierCurve.getValue((float)Math.min(1D, (float)(curLayer+1) / (float)particleCountLayers));*/\n                            Vector3f vecCurve1 = getCurveValue(curvePoint);\n                            Vector3f vecCurve2 = getCurveValue(curvePoint2);\n\n                            Vec2 curvePointYawPitch = yawPitch(vecCurve2, vecCurve1);\n                            float curveDist = getDistance(vecCurve1, vecCurve2);\n\n                            if ((index % particleCountCircle) == 0) {\n                                adjustedCurvePos += curveDist;\n                                //System.out.println(getDistance(vecCurve1, vecCurve2));\n                                //System.out.println(curvePointYawPitch.x + \" - \" + curvePointYawPitch.y);\n                            }\n\n                            //Quaternionf quaternionY = new Quaternionf(new Vector3f(0.0F, 1.0F, 0.0F), -y, true);\n                            Quaternionf quaternionY = new Quaternionf(0.0F, 1.0F, 0.0F, Math.toRadians(-curvePointYawPitch.x - 90));\n                            //adding quaternionY here cancels out the unwanted rotations from the bezier curve adjustments\n                            Quaternionf quaternionYCircle = new Quaternionf(0.0F, 1.0F, 0.0F, Math.toRadians(-y2 + (curvePointYawPitch.x - 90)));\n\n                            Quaternionf quatPitch = new Quaternionf(1.0F, 0.0F, 0.0F, Math.toRadians(curvePointYawPitch.y));\n                            //Vector3f vecNew = new Vector3f(1F, 1 + ((float)yDiff) * yDiffDist, 0);\n                            //Vector3d vecCurve = bezierCurve.getValue(curvePoint);\n                            Vector3f vecCurve = getCurveValue(curvePoint);\n                            //System.out.println(\"curvePoint: \" + curvePoint + \", \" + vecCurve);\n                            //Vector3f vecNew = new Vector3f((float)vecCurve.x * curveAmp, (float)vecCurve.y * curveAmp * stretchCurveY * (((float)yDiff) * yDiffDist) + 10F, (float)vecCurve.z * curveAmp);\n                            //Vector3f vecNew = new Vector3f((float)vecCurve.x() * curveAmp - curveAmp/2F, (1F + ((float)yDiff) * yDiffDist * (dist*2F)) - (dist/2F), (float)vecCurve.z() * curveAmp - curveAmp/2F);\n                            //Vector3f vecNew = new Vector3f(1F * curveAmp, (1F + ((float)yDiff) * yDiffDist * (dist*2F)) - (dist/2F), 0F);\n                            //Vector3f vecNew = new Vector3f(1F * curveAmp, (1F + ((float)yDiff) * yDiffDist * (dist*2F)) - (dist/2F), 1F * curveAmp);\n                            //Vector3f vecNew = new Vector3f(1F * curveAmp, (((float)yDiff) * distFinal) - (dist/2F), 0);\n                            //Vector3f vecNew = new Vector3f(1.3F + Math.min((curLayer * curLayer * 0.005F), 40)/* + (curLayer * 0.05F)*/, 0F, 0);\n                            //Vector3f vecNew = new Vector3f(1.3F + 1/* + (curLayer * 0.05F)*/, 0F, 0);\n                            Vector3f vecNew = new Vector3f(1/* + (curLayer * 0.05F)*/, 0F, 0);\n\n                            float rotAroundPosX = 0;\n                            float rotAroundPosY = 0;\n                            float rotAroundPosZ = 0;\n                            Matrix3f matrix = new Matrix3f();\n                            matrix.rotation(quaternionY);\n                            matrix.rotation(quatPitch);\n                            matrix.rotation(quaternionYCircle);\n                            vecNew.mulTranspose(matrix);\n\n                            rotAroundPosX = vecNew.x();\n                            rotAroundPosY = vecNew.y();\n                            rotAroundPosZ = vecNew.z();\n\n                            float tiltAdj = 1F;\n                            /*tiltAdj = curvePoint;\n                            if (tiltAdj > 0.5) {\n                                tiltAdj = 1F - tiltAdj;\n                            } else if (tiltAdj < 0.2) {\n                                //tiltAdj = tiltAdj;\n                            } else {\n                                tiltAdj = 1F;\n                            }*/\n\n                            //particle.setPosition(pos1.x() + rotAroundPosX, pos1.y() + rotAroundPosY, pos1.z() + rotAroundPosZ);\n                            //particle.setPosition(pos1.x() + (vecCurve1.x()*distFinal) + rotAroundPosX, pos1.y() + (vecCurve1.y()*distFinal) + rotAroundPosY, pos1.z() + (vecCurve1.z()*distFinal) + rotAroundPosZ);\n                            //particle.setPosition(pos1.x() + (vecCurve1.x()*distFinal) + rotAroundPosX, pos1.y() + (curLayer) + rotAroundPosY, pos1.z() + (vecCurve1.z()*distFinal) + rotAroundPosZ);\n                            //particle.setPosition(pos1.x() + (vecCurve1.x()*distFinal) + rotAroundPosX, pos1.y() + (curLayer) + (rotAroundPosY * (tiltAdj)), pos1.z() + (vecCurve1.z()*distFinal) + rotAroundPosZ);\n                            particle.setPosition(pos1.x() + (vecCurve1.x()*distFinal) + rotAroundPosX, pos1.y() + (vecCurve1.y()*distFinal) + (rotAroundPosY * (tiltAdj)), pos1.z() + (vecCurve1.z()*distFinal) + rotAroundPosZ);\n                            //particle.setPosition(0, 100, 0);\n                            particle.setMotionX(0);\n                            particle.setMotionY(0);\n                            particle.setMotionZ(0);\n                            /*particle.setPrevPosX(particle.getPosX());\n                            particle.setPrevPosY(particle.getPosY());\n                            particle.setPrevPosZ(particle.getPosZ());*/\n                            //particle.setPosition(pos1.x() + (vecCurve1.x()*distFinal) + rotAroundPosX, pos1.y() + 1 + curLayer, pos1.z() + (vecCurve1.z()*distFinal) + rotAroundPosZ);\n                        }\n                        index++;\n                    }\n                }\n            }\n        }\n\n        if (funnel == null) {\n            funnel = new TornadoFunnel();\n            funnel.pos = new Vector3d(mc.player.getX(), mc.player.getY(), mc.player.getZ());\n        }\n\n        //funnel.tickGame();\n\n        /*if (funnelSimple == null) {\n            ActiveTornadoConfig activeTornadoConfig = new ActiveTornadoConfig().setHeight(10).setRadiusOfBase(3).setSpinSpeed(360F / 20F).setRadiusIncreasePerLayer(0.5F);\n            funnelSimple = new TornadoFunnelSimple(activeTornadoConfig);\n            funnelSimple.pos = new Vec3(mc.player.getX(), mc.player.getY(), mc.player.getZ());\n        }*/\n\n        //funnelSimple.tickClient();\n\n    }\n\n    public float getDistance(Vector3f vec1, Vector3f vec2) {\n        float f = (vec1.x() - vec2.x());\n        float f1 = (vec1.y() - vec2.y());\n        float f2 = (vec1.z() - vec2.z());\n        return Mth.sqrt(f * f + f1 * f1 + f2 * f2);\n    }\n\n    /**\n     *\n     * @param pos2\n     * @param pos1\n     * @return yaw and pitch in degrees\n     */\n    public Vec2 yawPitch(Vector3f pos2, Vector3f pos1) {\n        float dist = getDistance(pos1, pos2);\n        Vector3f vecDiff = new Vector3f(\n                (pos1.x() - pos2.x()) / dist,\n                (pos1.y() - pos2.y()) / dist,\n                (pos1.z() - pos2.z()) / dist);\n        Vector3f vecAngles = new Vector3f(\n                (float)Math.atan2(vecDiff.y(), vecDiff.z()),\n                (float)Math.atan2(vecDiff.z(), vecDiff.x()), //invert if needed\n                (float)Math.atan2(vecDiff.x(), vecDiff.y())); //invert if needed\n\n        double xx = pos1.x() - pos2.x();\n        double zz = pos1.z() - pos2.z();\n        double xzDist = Math.sqrt(xx * xx + zz * zz);\n        double wat = xzDist / dist;\n        float pitchAngle = (float)Math.toDegrees(Math.atan2(vecDiff.y(), xzDist / dist));\n\n        vecAngles = new Vector3f((float)Math.toDegrees(vecAngles.x()), (float)Math.toDegrees(vecAngles.y()), (float)Math.toDegrees(vecAngles.z()));\n\n        pitchAngle += 90;\n\n        return new Vec2(vecAngles.y(), pitchAngle);\n    }\n\n    public Vector3f getCurveValue(float val) {\n        int arrayEntry = (int)Math.floor(val);\n        if (arrayEntry > curves.size()-1) {\n            System.out.println(\"out of bounds on curve lookup, val: \" + val + \" curves: - \" + curves.size());\n            return new Vector3f(1F, 1F, 1F);\n        }\n        CubicBezierCurve curve = curves.get(arrayEntry);\n        return curve.getValue(val % 1F);\n    }\n\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/tornado/Vector.java",
    "content": "/*******************************************************************************\n * Copyright 2011 See AUTHORS file.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *   http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n ******************************************************************************/\n\npackage weather2.weathersystem.tornado;\n\n/** Encapsulates a general vector. Allows chaining operations by returning a reference to itself in all modification methods. See\n *\n * @author Xoppa */\npublic interface Vector<T extends Vector<T>> {\n\t/** @return a copy of this vector */\n\tT cpy ();\n\n\t/** @return The euclidean length */\n\tfloat len ();\n\n\t/** This method is faster than {@link Vector#len()} because it avoids calculating a square root. It is useful for comparisons,\n\t * but not for getting exact lengths, as the return value is the square of the actual length.\n\t * @return The squared euclidean length */\n\tfloat len2 ();\n\n\t/** Limits the length of this vector, based on the desired maximum length.\n\t * @param limit desired maximum length for this vector\n\t * @return this vector for chaining */\n\tT limit (float limit);\n\n\t/** Limits the length of this vector, based on the desired maximum length squared.\n\t * <p />\n\t * This method is slightly faster than limit().\n\t * @param limit2 squared desired maximum length for this vector\n\t * @return this vector for chaining\n\t * @see #len2() */\n\tT limit2 (float limit2);\n\n\t/** Sets the length of this vector. Does nothing if this vector is zero.\n\t * @param len desired length for this vector\n\t * @return this vector for chaining */\n\tT setLength (float len);\n\n\t/** Sets the length of this vector, based on the square of the desired length. Does nothing if this vector is zero.\n\t * <p />\n\t * This method is slightly faster than setLength().\n\t * @param len2 desired square of the length for this vector\n\t * @return this vector for chaining\n\t * @see #len2() */\n\tT setLength2 (float len2);\n\n\t/** Clamps this vector's length to given min and max values\n\t * @param min Min length\n\t * @param max Max length\n\t * @return This vector for chaining */\n\tT clamp (float min, float max);\n\n\t/** Sets this vector from the given vector\n\t * @param v The vector\n\t * @return This vector for chaining */\n\tT set (T v);\n\n\t/** Subtracts the given vector from this vector.\n\t * @param v The vector\n\t * @return This vector for chaining */\n\tT sub (T v);\n\n\t/** Normalizes this vector. Does nothing if it is zero.\n\t * @return This vector for chaining */\n\tT nor ();\n\n\t/** Adds the given vector to this vector\n\t * @param v The vector\n\t * @return This vector for chaining */\n\tT add (T v);\n\n\t/** @param v The other vector\n\t * @return The dot product between this and the other vector */\n\tfloat dot (T v);\n\n\t/** Scales this vector by a scalar\n\t * @param scalar The scalar\n\t * @return This vector for chaining */\n\tT scl (float scalar);\n\n\t/** Scales this vector by another vector\n\t * @return This vector for chaining */\n\tT scl (T v);\n\n\t/** @param v The other vector\n\t * @return the distance between this and the other vector */\n\tfloat dst (T v);\n\n\t/** This method is faster than {@link Vector#dst(Vector)} because it avoids calculating a square root. It is useful for\n\t * comparisons, but not for getting accurate distances, as the return value is the square of the actual distance.\n\t * @param v The other vector\n\t * @return the squared distance between this and the other vector */\n\tfloat dst2 (T v);\n\n\t/** Linearly interpolates between this vector and the target vector by alpha which is in the range [0,1]. The result is stored\n\t * in this vector.\n\t * @param target The target vector\n\t * @param alpha The interpolation coefficient\n\t * @return This vector for chaining. */\n\tT lerp (T target, float alpha);\n\n\t/** Sets this vector to the unit vector with a random direction\n\t * @return This vector for chaining */\n\tT setToRandomDirection ();\n\n\t/** @return Whether this vector is a unit length vector */\n\tboolean isUnit ();\n\n\t/** @return Whether this vector is a unit length vector within the given margin. */\n\tboolean isUnit (final float margin);\n\n\t/** @return Whether this vector is a zero vector */\n\tboolean isZero ();\n\n\t/** @return Whether the length of this vector is smaller than the given margin */\n\tboolean isZero (final float margin);\n\n\t/** @return true if this vector is in line with the other vector (either in the same or the opposite direction) */\n\tboolean isOnLine (T other, float epsilon);\n\n\t/** @return true if this vector is in line with the other vector (either in the same or the opposite direction) */\n\tboolean isOnLine (T other);\n\n\t/** @return true if this vector is collinear with the other vector ({@link #isOnLine(Vector, float)} &&\n\t *         {@link #hasSameDirection(Vector)}). */\n\tboolean isCollinear (T other, float epsilon);\n\n\t/** @return true if this vector is collinear with the other vector ({@link #isOnLine(Vector)} &&\n\t *         {@link #hasSameDirection(Vector)}). */\n\tboolean isCollinear (T other);\n\n\t/** @return true if this vector is opposite collinear with the other vector ({@link #isOnLine(Vector, float)} &&\n\t *         {@link #hasOppositeDirection(Vector)}). */\n\tboolean isCollinearOpposite (T other, float epsilon);\n\n\t/** @return true if this vector is opposite collinear with the other vector ({@link #isOnLine(Vector)} &&\n\t *         {@link #hasOppositeDirection(Vector)}). */\n\tboolean isCollinearOpposite (T other);\n\n\t/** @return Whether this vector is perpendicular with the other vector. True if the dot product is 0. */\n\tboolean isPerpendicular (T other);\n\n\t/** @return Whether this vector is perpendicular with the other vector. True if the dot product is 0.\n\t * @param epsilon a positive small number close to zero */\n\tboolean isPerpendicular (T other, float epsilon);\n\n\t/** @return Whether this vector has similar direction compared to the other vector. True if the normalized dot product is >\n\t *         0. */\n\tboolean hasSameDirection (T other);\n\n\t/** @return Whether this vector has opposite direction compared to the other vector. True if the normalized dot product is <\n\t *         0. */\n\tboolean hasOppositeDirection (T other);\n\n\t/** Compares this vector with the other vector, using the supplied epsilon for fuzzy equality testing.\n\t * @param other\n\t * @param epsilon\n\t * @return whether the vectors have fuzzy equality. */\n\tboolean epsilonEquals (T other, float epsilon);\n\n\t/** First scale a supplied vector, then add it to this vector.\n\t * @param v addition vector\n\t * @param scalar for scaling the addition vector */\n\tT mulAdd (T v, float scalar);\n\n\t/** First scale a supplied vector, then add it to this vector.\n\t * @param v addition vector\n\t * @param mulVec vector by whose values the addition vector will be scaled */\n\tT mulAdd (T v, T mulVec);\n\n\t/** Sets the components of this vector to 0\n\t * @return This vector for chaining */\n\tT setZero ();\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/tornado/simple/Layer.java",
    "content": "package weather2.weathersystem.tornado.simple;\n\nimport extendedrenderer.particle.entity.PivotingParticle;\nimport net.minecraft.world.phys.Vec3;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport net.minecraftforge.fml.LogicalSide;\nimport net.minecraftforge.fml.util.thread.EffectiveSide;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\npublic class Layer {\n\n    @OnlyIn(Dist.CLIENT)\n    private List<PivotingParticle> listParticles;\n    @OnlyIn(Dist.CLIENT)\n    private List<PivotingParticle> listParticlesExtra;\n    private Vec3 pos = Vec3.ZERO;\n    private float rotation;\n\n    public Layer(Vec3 pos) {\n        this.pos = new Vec3(pos.x, pos.y, pos.z);\n\n        if (EffectiveSide.get() == LogicalSide.CLIENT) {\n            initClient();\n        }\n    }\n\n    @OnlyIn(Dist.CLIENT)\n    public void initClient() {\n        listParticles = new ArrayList<>();\n        listParticlesExtra = new ArrayList<>();\n    }\n\n    @OnlyIn(Dist.CLIENT)\n    public List<PivotingParticle> getListParticles() {\n        return listParticles;\n    }\n\n    @OnlyIn(Dist.CLIENT)\n    public void setListParticles(List<PivotingParticle> listParticles) {\n        this.listParticles = listParticles;\n    }\n\n    @OnlyIn(Dist.CLIENT)\n    public List<PivotingParticle> getListParticlesExtra() {\n        return listParticlesExtra;\n    }\n\n    @OnlyIn(Dist.CLIENT)\n    public void setListParticlesExtra(List<PivotingParticle> listParticlesExtra) {\n        this.listParticlesExtra = listParticlesExtra;\n    }\n\n    public Vec3 getPos() {\n        return pos;\n    }\n\n    public void setPos(Vec3 pos) {\n        this.pos = pos;\n    }\n\n    public float getRotation() {\n        return rotation;\n    }\n\n    public void setRotation(float rotation) {\n        this.rotation = rotation;\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/tornado/simple/TornadoFunnelSimple.java",
    "content": "package weather2.weathersystem.tornado.simple;\n\nimport com.corosus.coroutil.util.CULog;\nimport extendedrenderer.particle.ParticleRegistry;\nimport extendedrenderer.particle.entity.PivotingParticle;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.client.ParticleStatus;\nimport net.minecraft.client.multiplayer.ClientLevel;\nimport net.minecraft.client.renderer.texture.TextureAtlasSprite;\nimport net.minecraft.util.Mth;\nimport net.minecraft.world.entity.Entity;\nimport net.minecraft.world.entity.EntityType;\nimport net.minecraft.world.entity.animal.Dolphin;\nimport net.minecraft.world.level.Level;\nimport net.minecraft.world.phys.Vec3;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport weather2.Weather;\nimport weather2.weathersystem.storm.StormObject;\nimport weather2.weathersystem.tornado.ActiveTornadoConfig;\n\nimport java.util.ArrayList;\nimport java.util.Iterator;\nimport java.util.List;\nimport java.util.Random;\n\npublic class TornadoFunnelSimple {\n\n    private ActiveTornadoConfig config;\n\n    public Vec3 pos = new Vec3(0, 0, 0);\n    public List<Layer> listLayers = new ArrayList<>();\n\n    private float heightPerLayer = 1F;\n\n    private StormObject stormObject;\n\n    private float targetSizeRadius = 0;\n    private float sizeRadiusRate = 0;\n    private float renderDistCutoff = 50;\n\n    //hack to fix client data coming in late\n    private boolean wasFirenado = false;\n\n    public TornadoFunnelSimple(ActiveTornadoConfig config, StormObject stormObject) {\n        this.config = config;\n        this.stormObject = stormObject;\n        config.setRadiusOfBase(stormObject.tornadoHelper.getTornadoBaseSize() / 2);\n    }\n\n    public void init() {\n        listLayers.clear();\n    }\n\n    public void tick() {\n        if (stormObject.isPet()) {\n            heightPerLayer = 0.2F;\n        }\n\n        //TESTING\n        //config.setEntityPullDistXZForY(90);\n\n        //dynamic sizing\n        targetSizeRadius = stormObject.tornadoHelper.getTornadoBaseSize() / 2;\n        sizeRadiusRate = 0.01F;\n\n        if (config.getRadiusOfBase() != targetSizeRadius) {\n            //CULog.dbg(\"tornado size transitioning: \" + config.getRadiusOfBase());\n            if (config.getRadiusOfBase() < targetSizeRadius) {\n                config.setRadiusOfBase(config.getRadiusOfBase() + sizeRadiusRate);\n                if (config.getRadiusOfBase() > targetSizeRadius) config.setRadiusOfBase(targetSizeRadius);\n            } else {\n                config.setRadiusOfBase(config.getRadiusOfBase() - sizeRadiusRate);\n                if (config.getRadiusOfBase() < targetSizeRadius) config.setRadiusOfBase(targetSizeRadius);\n            }\n        }\n\n        int layers = (int) (config.getHeight() / heightPerLayer);\n        float radiusMax = config.getRadiusOfBase() + (config.getRadiusIncreasePerLayer() * (layers+1));\n\n        for (int i = 0; i < layers; i++) {\n\n            //grow layer count as height increases\n            if (i >= listLayers.size()) {\n                listLayers.add(new Layer(stormObject.posBaseFormationPos));\n            }\n\n            /**\n             * get radius for current layer\n             * convert to circumference (c = r2 * pi)\n             * count = space per particle / circumference\n             */\n\n            float radius = config.getRadiusOfBase() + (config.getRadiusIncreasePerLayer() * (i));\n\n            Vec3 posLayer = listLayers.get(i).getPos();\n\n            float relYDown1 = (heightPerLayer * (radius / radiusMax));\n\n            Vec3 posLayerLower;\n            if (i == 0) {\n                posLayerLower = new Vec3(pos.x, pos.y, pos.z);\n            } else {\n                Vec3 temp = listLayers.get(i-1).getPos();\n                posLayerLower = new Vec3(temp.x, temp.y + relYDown1, temp.z);\n            }\n\n            double dist = posLayer.distanceTo(posLayerLower);\n            //easy way to fix the spawning at 0,0 issue\n            if (dist > 50) {\n                CULog.dbg(\"teleporting tornado layer to lower piece\");\n                listLayers.get(i).setPos(new Vec3(posLayerLower.x, posLayerLower.y, posLayerLower.z));\n            } else if (dist > 0.1F * (radius / radiusMax)) {\n                double dynamicSpeed = 15F * (Math.min(30F, dist) / 30F);\n                double speed = dynamicSpeed;//0.01F;\n                Vec3 moveVec = posLayer.vectorTo(posLayerLower).normalize().multiply(speed, speed * 1F, speed);\n                Vec3 newPos = posLayer.add(moveVec);\n                listLayers.get(i).setPos(new Vec3(newPos.x, newPos.y, newPos.z));\n            }\n        }\n\n        Level level = stormObject.manager.getWorld();\n\n        if (stormObject.isSharknado()) {\n            if (!level.isClientSide()) {\n                if (level.getGameTime() % 20 == 0) {\n                    Entity ent = null;\n                    if (Weather.isLoveTropicsInstalled()) {\n                        /**\n                         * TODO: for LT, turn back on when LT is needed, activates dependency on LTWeather / Tropicraft\n                         */\n                        //ent = new SharkEntity(TropicraftEntities.HAMMERHEAD.get(), level);\n                    } else {\n                        ent = new Dolphin(EntityType.DOLPHIN, level);\n                    }\n                    if (ent == null) {\n                        CULog.log(\"SharkEntity not spawned, enable in weather mod\");\n                        ent = new Dolphin(EntityType.DOLPHIN, level);\n                    }\n                    Vec3 posRand = new Vec3(pos.x + 0, pos.y + 3, pos.z - 5);\n                    ent.setPos(posRand);\n                    ent.setDeltaMovement(3F, 0, 0);\n                    level.addFreshEntity(ent);\n                }\n            }\n        }\n    }\n\n    @OnlyIn(Dist.CLIENT)\n    public void tickClient() {\n        long gameTime = stormObject.getAge();\n\n        Level level = stormObject.manager.getWorld();\n\n        renderDistCutoff = Minecraft.getInstance().gameRenderer.getRenderDistance() * 4;\n\n        int layers = (int) (config.getHeight() / heightPerLayer);\n        float radiusMax = config.getRadiusOfBase() + (config.getRadiusIncreasePerLayer() * (layers+1));\n\n        boolean isBaby = stormObject.isBaby();\n        boolean isPet = stormObject.isPet();\n\n        //cleanup layers beyond current size\n        //while (listLayers.size() > layers) {\n        for (int i = layers; i < listLayers.size(); i++) {\n            List<PivotingParticle> listLayer = listLayers.get(i).getListParticles();\n            Iterator<PivotingParticle> it = listLayer.iterator();\n            while (it.hasNext()) {\n                PivotingParticle particle = it.next();\n                it.remove();\n                particle.remove();\n            }\n        }\n\n        int particleCount = 0;\n\n        float adjustedRate = 1F;\n        if (!isPet) {\n            if (Minecraft.getInstance().options.particles.get() == ParticleStatus.DECREASED) {\n                adjustedRate = 0.6F;\n            } else if (Minecraft.getInstance().options.particles.get() == ParticleStatus.MINIMAL) {\n                adjustedRate = 0.3F;\n            }\n        }\n\n        int layersWithDebris = stormObject.getAgeSinceTornadoTouchdown()/5;\n        //CULog.dbg(\"layersWithDebris: \" + layersWithDebris);\n\n        for (int i = 0; i < layers; i++) {\n\n            /**\n             * get radius for current layer\n             * convert to circumference (c = r2 * pi)\n             * count = space per particle / circumference\n             */\n\n            List<PivotingParticle> listLayer = listLayers.get(i).getListParticles();\n            List<PivotingParticle> listLayerExtra = listLayers.get(i).getListParticlesExtra();\n\n            float radius = config.getRadiusOfBase() + (config.getRadiusIncreasePerLayer() * (i));\n            float radiusAdjustedForParticleSize = radius * (radius / radiusMax);\n\n            float circumference = radius * 2 * Mth.PI;\n            //float particleSpaceOccupy = 0.5F * (radius / radiusMax);\n            float particleSpaceOccupy = (15F / adjustedRate) * (radius / radiusMax);\n            if (isBaby) particleSpaceOccupy = (2F / adjustedRate) * (radius / radiusMax);\n            if (isPet) particleSpaceOccupy = (0.2F / adjustedRate) * (radius / radiusMax);\n            float particlesPerLayer = (float) /*Math.floor(*/circumference / particleSpaceOccupy/*)*/;\n\n            Iterator<PivotingParticle> itt = listLayer.iterator();\n            float indexx = 0;\n            while (itt.hasNext()) {\n                PivotingParticle particle = itt.next();\n                if (!particle.isAlive() || indexx >= particlesPerLayer) {\n                    particle.remove();\n                    itt.remove();\n                } else {\n                    indexx++;\n                }\n            }\n            //cleanupList(listLayer, (int)particlesPerLayer);\n\n            int firstLayerForParticles = 6;\n            if (stormObject.isBaby()) {\n                firstLayerForParticles = 0;\n            }\n\n            while (listLayer.size() < particlesPerLayer && i >= firstLayerForParticles) {\n                PivotingParticle particle = createParticle((ClientLevel) level, pos.x, pos.y, pos.z);\n                particle.spawnAsWeatherEffect();\n                listLayer.add(particle);\n            }\n\n            float particleSpacingDegrees = 360 / particlesPerLayer;\n            float spinSpeedLayer = 1F - ((float)(i+1) / (float)layers) + 1F;\n\n            if (isPet) {\n                listLayers.get(i).setRotation(listLayers.get(i).getRotation() + (10F * spinSpeedLayer / (radiusAdjustedForParticleSize)));\n            } else {\n                listLayers.get(i).setRotation(listLayers.get(i).getRotation() + (50.22F * spinSpeedLayer / (radiusAdjustedForParticleSize)));\n            }\n\n            Iterator<PivotingParticle> it = listLayer.iterator();\n            int index = 0;\n            while (it.hasNext()) {\n                particleCount++;\n                PivotingParticle particle = it.next();\n\n                float rot = ((particleSpacingDegrees * index) + listLayers.get(i).getRotation());\n                particle.setPivotRotPrev(particle.getPivotRot());\n                particle.setPivotRot(new Vec3(0, rot, 0));\n                particle.setPivotPrev(particle.getPivot());\n                particle.setPivot(new Vec3(0, radiusAdjustedForParticleSize, 0));\n\n                Vec3 pivotedPosition = particle.getPivotedPosition(0);\n\n                double vecX = 0 - pivotedPosition.x;\n                double vecZ = 0 - pivotedPosition.z;\n\n                particle.prevRotationYaw = particle.rotationYaw;\n                particle.rotationYaw = -(float)(Mth.atan2(vecZ, vecX) * 180.0D / Math.PI) - 90.0F + 180F;\n                int rotationVarianceSize = 90;\n                particle.rotationYaw -= (particle.getEntityId() % rotationVarianceSize) - (rotationVarianceSize/2);\n                particle.rotationPitch = -30;\n\n                //fix interpolation when angle wraps around\n                if (particle.rotationYaw > 0 && particle.prevRotationYaw < 0) {\n                    particle.prevRotationYaw += 360;\n                }/* else if (particle.rotationYaw < 0 && particle.prevRotationYaw > 0) {\n                    particle.prevRotationYaw -= 360;\n                }*/\n\n                Vec3 posLayer = listLayers.get(i).getPos();\n                particle.setPosition(posLayer.x, posLayer.y, posLayer.z);\n                particle.setPrevPosX(particle.x);\n                particle.setPrevPosY(particle.y);\n                particle.setPrevPosZ(particle.z);\n\n                particle.setScale(10F * (radius / radiusMax));\n                if (isBaby) particle.setScale(10F / 3F * (radius / radiusMax));\n                if (isPet) particle.setScale(10F / 3F / 7F * (radius / radiusMax));\n                //allow fade in but stop age after\n                if (particle.getAge() > particle.getTicksFadeInMax()+1) particle.setAge((int)particle.getTicksFadeInMax()+1);\n\n                /*particle.setScale(0.3F);\n\n                if (i % 2 == 0) {\n                    particle.setColor(0, 0, 0);\n                } else {\n                    particle.setColor(1, 1, 1);\n                }*/\n\n                if (stormObject.isFirenado && !wasFirenado) {\n                    if (particle.getSprite() == ParticleRegistry.cloud256) {\n                        particle.setSprite(ParticleRegistry.cloud256_fire);\n                    }\n\n                    float baseBright = 0.8F;\n                    float randFloat = (level.random.nextFloat() * 0.2F);\n                    float finalBright = Math.min(1F, baseBright + randFloat);\n                    particle.setColor(finalBright, finalBright, finalBright);\n                }\n\n                index++;\n            }\n\n            //extra debris\n\n            particlesPerLayer = (int) (20 * adjustedRate);\n            if (isBaby) particlesPerLayer = (int) (10 * adjustedRate);\n            if (isPet) particlesPerLayer = (int) (5 * adjustedRate);\n            if (stormObject.levelCurIntensityStage == StormObject.STATE_FORMING) {\n                particlesPerLayer = 0;\n            }\n\n            cleanupList(listLayerExtra, (int)particlesPerLayer);\n\n            //int particlesPerLayerDynamic = stormObject.getAgeSinceTornadoTouchdown()/20;\n\n            if (i <= layersWithDebris && i >= firstLayerForParticles + 1) {\n                while (listLayerExtra.size() < particlesPerLayer) {\n                    PivotingParticle particle = createParticleDebris((ClientLevel) level, pos.x, pos.y, pos.z);\n                    particle.spawnAsWeatherEffect();\n                    listLayerExtra.add(particle);\n                }\n            }\n\n            particleSpacingDegrees = 360 / particlesPerLayer;\n\n            //TODO: oh god stop the copypasta\n            it = listLayerExtra.iterator();\n            index = 0;\n            while (it.hasNext()) {\n                particleCount++;\n                PivotingParticle particle = it.next();\n\n                float radAdj = (particle.getEntityId() % particlesPerLayer) / particlesPerLayer;\n                radAdj = 0.4F + (radAdj * 0.6F);\n                float moar = i * 0.5F;\n                if (isPet) moar = 0.5F;\n                radiusAdjustedForParticleSize = (radius * (radius / radiusMax) + moar) * radAdj;\n\n                float rot = (particleSpacingDegrees * index) + listLayers.get(i).getRotation();\n                particle.setPivotRotPrev(particle.getPivotRot());\n                particle.setPivotRot(new Vec3(0, rot, 0));\n                particle.setPivotPrev(particle.getPivot());\n                particle.setPivot(new Vec3(0, radiusAdjustedForParticleSize, 0));\n\n                particle.prevRotationYaw = particle.rotationYaw;\n                particle.rotationYaw += 5F;\n                particle.rotationPitch = -30;\n\n                Vec3 posLayer = listLayers.get(i).getPos();\n                particle.setPosition(posLayer.x, posLayer.y, posLayer.z);\n                particle.setPrevPosX(particle.x);\n                particle.setPrevPosY(particle.y);\n                particle.setPrevPosZ(particle.z);\n\n                particle.setScale(8 * 0.15F);\n                if (isPet) particle.setScale(10F / 3F / 15F * (radius / radiusMax));\n\n                if (particle.getAge() > particle.getTicksFadeInMax()+1) {\n                    particle.setAge((int)particle.getTicksFadeInMax()+1);\n                }\n                particle.setGravity(0);\n                //particle.setAlpha(1);\n                index++;\n            }\n\n            //listLayers.get(i).setPos(new Vector3d(pos.x, pos.y, pos.z));\n\n        }\n\n        //CULog.dbg(particleCount + \"\");\n\n        wasFirenado = stormObject.isFirenado;\n    }\n\n    public void cleanupList(List<PivotingParticle> list, int particlesPerLayer) {\n        Iterator<PivotingParticle> it = list.iterator();\n        float index = 0;\n        while (it.hasNext()) {\n            PivotingParticle particle = it.next();\n            if (!particle.isAlive() || index >= particlesPerLayer) {\n                particle.remove();\n                it.remove();\n            } else {\n                index++;\n            }\n        }\n    }\n\n    @OnlyIn(Dist.CLIENT)\n    private PivotingParticle createParticle(ClientLevel world, double x, double y, double z) {\n        //ParticleTexFX particle = new ParticleTexFX(world, x, y, z, 0, 0, 0, ParticleRegistry.square16);\n        TextureAtlasSprite sprite = ParticleRegistry.cloud256;\n        if (stormObject.isFirenado) {\n            sprite = ParticleRegistry.cloud256_fire;\n        }\n        PivotingParticle particle = new PivotingParticle(world, x, y, z, 0, 0, 0, sprite);\n        particle.setMaxAge(300);\n        particle.setTicksFadeInMax(80);\n        //particle.setTicksFadeOutMax(20);\n        particle.setParticleSpeed(0, 0, 0);\n        particle.setScale(0.1F);\n        particle.setScale(5F);\n        particle.setScale(15F);\n        //particle.setColor(world.random.nextFloat(), world.random.nextFloat(), world.random.nextFloat());\n        if (!stormObject.isFirenado) {\n            float baseBright = 0.3F;\n            float randFloat = (world.random.nextFloat() * 0.6F);\n            float finalBright = Math.min(1F, baseBright + randFloat);\n            particle.setColor(finalBright - 0.2F, finalBright - 0.2F, finalBright - 0.2F);\n        } else {\n            float baseBright = 0.6F;\n            float randFloat = (world.random.nextFloat() * 0.3F);\n            float finalBright = Math.min(1F, baseBright + randFloat);\n            particle.setColor(finalBright - 0.2F, finalBright - 0.2F, finalBright - 0.2F);\n        }\n        particle.setGravity(0);\n        particle.rotationYaw = world.random.nextFloat() * 360;\n        particle.setRenderDistanceCull(renderDistCutoff);\n        return particle;\n    }\n\n    @OnlyIn(Dist.CLIENT)\n    private PivotingParticle createParticleDebris(ClientLevel world, double x, double y, double z) {\n        int chance = world.getRandom().nextInt(3);\n        TextureAtlasSprite sprite = ParticleRegistry.debris_1;\n        if (chance == 1) {\n            sprite = ParticleRegistry.debris_2;\n        } else if (chance == 2) {\n            sprite = ParticleRegistry.debris_3;\n        }\n        PivotingParticle particle = new PivotingParticle(world, x, y, z, 0, 0, 0, sprite);\n        particle.setMaxAge(25000);\n        particle.setTicksFadeInMax(80);\n        particle.setParticleSpeed(0, 0, 0);\n\n        particle.setFacePlayer(false);\n        particle.spinFast = true;\n        particle.isTransparent = true;\n        particle.rotationYaw = (float)world.getRandom().nextInt(360);\n        particle.rotationPitch = (float)world.getRandom().nextInt(360);\n\n        particle.setGravity(0F);\n        float brightnessMulti = 1F - (world.getRandom().nextFloat() * 0.5F);\n        particle.setColor(1F * brightnessMulti, 1F * brightnessMulti, 1F * brightnessMulti);\n        particle.setScale(8 * 0.15F);\n        particle.aboveGroundHeight = 0.5D;\n        particle.collisionSpeedDampen = false;\n        particle.bounceSpeed = 0.03D;\n        particle.bounceSpeedAhead = 0.03D;\n\n        particle.setKillOnCollide(false);\n\n        particle.windWeight = 5F;\n        particle.setRenderDistanceCull(renderDistCutoff);\n\n        return particle;\n    }\n\n    public Vec3 getPosTop() {\n        if (listLayers.size() == 0) return pos;\n        return listLayers.get(listLayers.size()-1).getPos();\n    }\n\n    public StormObject getStormObject() {\n        return stormObject;\n    }\n\n    public void setStormObject(StormObject stormObject) {\n        this.stormObject = stormObject;\n    }\n\n    public void cleanup() {\n        listLayers.clear();\n    }\n\n    /**\n     * Dramatic version for effect\n     */\n    public void cleanupClient() {\n        for (int i = 0; i < listLayers.size(); i++) {\n            listLayers.get(i).getListParticles().stream().forEach(pivotingParticle -> disperseParticleSmoothly(pivotingParticle, true));\n            listLayers.get(i).getListParticlesExtra().stream().forEach(pivotingParticle -> disperseParticleSmoothly(pivotingParticle, true));\n            listLayers.get(i).getListParticles().clear();\n            listLayers.get(i).getListParticlesExtra().clear();\n        }\n    }\n\n    public void fadeOut() {\n        for (int i = 0; i < listLayers.size(); i++) {\n            listLayers.get(i).getListParticles().stream().forEach(pivotingParticle -> disperseParticleSmoothly(pivotingParticle, false));\n            listLayers.get(i).getListParticlesExtra().stream().forEach(pivotingParticle -> disperseParticleSmoothly(pivotingParticle, false));\n            listLayers.get(i).getListParticles().clear();\n            listLayers.get(i).getListParticlesExtra().clear();\n        }\n    }\n\n    public void disperseParticleSmoothly(PivotingParticle pivotingParticle, boolean explode) {\n        pivotingParticle.prevRotationYaw = pivotingParticle.rotationYaw;\n        pivotingParticle.setPivotPrev(pivotingParticle.getPivot());\n        pivotingParticle.setPivotRotPrev(pivotingParticle.getPivotRot());\n        Random rand = new Random();\n        if (explode) {\n            pivotingParticle.setMotionX((rand.nextFloat() - rand.nextFloat()) * 2F);\n            pivotingParticle.setMotionZ((rand.nextFloat() - rand.nextFloat()) * 2F);\n        } else {\n            pivotingParticle.setMotionX((rand.nextFloat() - rand.nextFloat()) * 0.4F);\n            pivotingParticle.setMotionZ((rand.nextFloat() - rand.nextFloat()) * 0.4F);\n        }\n        pivotingParticle.setAge(100);\n        pivotingParticle.setMaxAge(200);\n        pivotingParticle.setTicksFadeOutMax(80);\n        pivotingParticle.spinFast = false;\n    }\n\n    public void cleanupClientQuick() {\n        for (int i = 0; i < listLayers.size(); i++) {\n            listLayers.get(i).getListParticles().stream().forEach(pivotingParticle -> pivotingParticle.remove());\n            listLayers.get(i).getListParticlesExtra().stream().forEach(pivotingParticle -> pivotingParticle.remove());\n            listLayers.get(i).getListParticles().clear();\n            listLayers.get(i).getListParticlesExtra().clear();\n        }\n    }\n\n    public ActiveTornadoConfig getConfig() {\n        return config;\n    }\n\n    public void setConfig(ActiveTornadoConfig config) {\n        this.config = config;\n    }\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/wind/WindInfoCache.java",
    "content": "package weather2.weathersystem.wind;\n\npublic class WindInfoCache {\n\n    public long cacheTimeWindSpeedEvent;\n    public float windSpeedEvent;\n\n    public long cacheTimeChunkHeight;\n    public int averageChunkHeightAround;\n\n    public long cacheTimeWindSpeedAtChunkHeight;\n    public float windSpeedAtChunkHeight;\n\n}\n"
  },
  {
    "path": "src/main/java/weather2/weathersystem/wind/WindManager.java",
    "content": "package weather2.weathersystem.wind;\n\nimport com.corosus.coroutil.util.CoroUtilBlock;\nimport com.corosus.coroutil.util.CoroUtilEntOrParticle;\nimport com.corosus.coroutil.util.CoroUtilMisc;\nimport net.minecraft.client.Minecraft;\nimport net.minecraft.core.BlockPos;\nimport net.minecraft.nbt.CompoundTag;\nimport net.minecraft.world.entity.player.Player;\nimport net.minecraft.world.level.levelgen.Heightmap;\nimport net.minecraft.world.level.levelgen.synth.PerlinNoise;\nimport net.minecraft.world.phys.Vec3;\nimport net.minecraft.server.level.ServerLevel;\nimport net.minecraftforge.api.distmarker.Dist;\nimport net.minecraftforge.api.distmarker.OnlyIn;\nimport weather2.PerlinNoiseHelper;\nimport weather2.ServerWeatherProxy;\nimport weather2.Weather;\nimport weather2.config.ConfigMisc;\nimport weather2.config.ConfigWind;\nimport weather2.util.WeatherUtilEntity;\nimport weather2.weathersystem.WeatherManager;\nimport weather2.weathersystem.WeatherManagerServer;\nimport weather2.weathersystem.storm.StormObject;\n\nimport javax.annotation.Nullable;\nimport java.util.HashMap;\nimport java.util.Random;\n\npublic class WindManager {\n\tpublic WeatherManager manager;\n\n\t//global\n\tpublic float windAngleGlobal = 0;\n\tpublic float windSpeedGlobal = 0;\n\tpublic float windSpeedGlobalChangeRate = 0.05F;\n\tpublic int windSpeedGlobalRandChangeTimer = 0;\n\tpublic int windSpeedGlobalRandChangeDelay = 10;\n\n\t//generic?\n\t/*public float windSpeedMin = 0.00001F;\n\tpublic float windSpeedMax = 1F;*/\n\n\t//events - design derp, we're making this client side, so its set based on closest storm to the client side player\n\tpublic float windAngleEvent = 0;\n\tpublic BlockPos windOriginEvent = BlockPos.ZERO;\n\tpublic float windSpeedEvent = 0;\n\t//client side only\n\t//its assumed this will get set by whatever initializes an event, and this class counts it down from a couple seconds, helps wind system know what takes priority\n\tpublic int windTimeEvent = 0;\n\n\t//gusts\n\tpublic float windAngleGust = 0;\n\tpublic float windSpeedGust = 0;\n\tpublic int windTimeGust = 0;\n\t//public float directionGust = 0;\n\t//public float directionBeforeGust = 0;\n\tpublic int windGustEventTimeRand = 60;\n\tpublic float chanceOfWindGustEvent = 0.5F;\n\n\t//low wind event\n\tpublic int lowWindTimer = 0;\n\n\t//high wind event\n\tpublic int highWindTimer = 0;\n\n\tpublic static boolean FORCE_ON_DEBUG_TESTING = false;\n\n\tpublic HashMap<Long, WindInfoCache> lookupChunkToWindInfo = new HashMap<>();\n\t//this one specifically hashes with a y value, so different vertical heights within the same chunk can have different results still\n\tpublic HashMap<Long, WindInfoCache> lookupChunkWithHeightToWindInfo = new HashMap<>();\n\tpublic int cachedWindInfoUpdateFrequency = 100;\n\tpublic int cachedChunkHeightUpdateFrequency = 20*60*5;\n\n\t//used by client particles, and off thread work\n\tpublic float cachedWindSpeedClient = 0;\n\n\tpublic WindManager(WeatherManager parManager) {\n\t\tmanager = parManager;\n\t\t\n\t\tRandom rand = new Random();\n\t\t\n\t\twindAngleGlobal = rand.nextInt(360);\n\t}\n\n\tpublic float getWindSpeed() {\n\t\treturn getWindSpeed(null, 1);\n\t}\n\n\tpublic float getWindSpeed(@Nullable BlockPos pos) {\n\t\treturn getWindSpeed(pos, 1);\n\t}\n\t\n\tpublic float getWindSpeed(@Nullable BlockPos pos, float extraHeightAmpMax) {\n\t\tif (pos != null) {\n\t\t\treturn getWindSpeedPositional(pos, extraHeightAmpMax);\n\t\t}\n\t\tif (windTimeEvent > 0 && (windSpeedEvent > windSpeedGust && windSpeedEvent > windSpeedGlobal)) {\n\t\t\treturn windSpeedEvent;\n\t\t} else if (windTimeGust > 0) {\n\t\t\treturn windSpeedGust;\n\t\t} else {\n\t\t\treturn windSpeedGlobal;\n\t\t}\n\t}\n\n\tpublic float getWindSpeedPositional(BlockPos pos) {\n\t\treturn getWindSpeedPositional(pos, 1);\n\t}\n\n\tpublic float getWindSpeedPositional(BlockPos pos, float extraHeightAmpMax) {\n\t\treturn getWindSpeedPositional(pos, extraHeightAmpMax, true);\n\t}\n\n\t/**\n\t * Uses various caches to factor in event wind speed (server side only), height based wind speed amplifier (cached to 16x16x16 areas)\n\t *\n\t * @param pos\n\t * @param extraHeightAmpMax\n\t * @return\n\t */\n\t//TODO: design flaw: use of extraHeightAmpMax gets cached into WindInfoCache, will mess with results if 2 sources use 2 different extraHeightAmpMax\n\t//workaround for now is that on server side, only turbine is using this cache, need to make the extraHeightAmpMax calculation outside the cache, by fixing the other hacks below\n\tpublic float getWindSpeedPositional(BlockPos pos, float extraHeightAmpMax, boolean useClientCache) {\n\t\tif (manager.getWorld().isClientSide() && useClientCache) {\n\t\t\treturn cachedWindSpeedClient;\n\t\t}\n\t\tthis.manager.getWorld().getProfiler().push(\"weather2_wind_calculation\");\n\t\tboolean eventFastest = false;\n\t\tfloat lastWindSpeed;\n\t\t//dont waste cpu on client using the cache system when we only need the info around the player, especially given all the particles using it\n\t\tif (manager.getWorld().isClientSide()) {\n\t\t\tlastWindSpeed = windTimeEvent > 0 ? windSpeedEvent : 0;\n\t\t} else {\n\t\t\tlastWindSpeed = getCachedWindSpeedEventForChunkPos(pos);\n\t\t}\n\t\tif (lastWindSpeed > windSpeedGlobal && lastWindSpeed > windSpeedGlobal) eventFastest = true;\n\t\tlastWindSpeed = Math.max(lastWindSpeed, windSpeedGlobal);\n\t\tif (windTimeGust > 0) lastWindSpeed = Math.max(lastWindSpeed, windSpeedGust);\n\t\tint averageHeight = getCachedAverageChunkHeightAround(pos);\n\t\tfloat windSpeedHeightAmp = getWindSpeedAmplifierForHeight(pos.getY(), averageHeight, extraHeightAmpMax);\n\t\tlastWindSpeed *= windSpeedHeightAmp;\n\t\t//give a constant speed buff if high enough\n\t\tif (windSpeedHeightAmp > 1.3F) {\n\t\t\tlastWindSpeed += (windSpeedHeightAmp-1F) * 1F;\n\t\t}\n\t\t//TODO: remove the need for this hack, and eventFastest\n\t\t//ok so i wanted the cap for turbines to be at 3, and everything else either 1 or 1.5 as shown above, this hacky if statement and event check will have to do for now\n\t\tfloat cap = 1F;\n\t\tif (eventFastest) {\n\t\t\tcap = 2F;\n\t\t}\n\t\tif (extraHeightAmpMax >= 2) {\n\t\t\tcap = extraHeightAmpMax + 1;\n\t\t}\n\t\tthis.manager.getWorld().getProfiler().pop();\n\t\treturn Math.min(cap, lastWindSpeed);\n\t}\n\n\tpublic void startHighWindEvent() {\n\t\thighWindTimer = ConfigWind.highWindTimerEnableAmountBase + (new Random()).nextInt(ConfigWind.highWindTimerEnableAmountRnd);\n\t}\n\n\tpublic boolean isHighWindEventActive() {\n\t\treturn highWindTimer > 0;\n\t}\n\n\tpublic void stopHighWindEvent() {\n\t\thighWindTimer = 0;\n\t}\n\n\tpublic void startLowWindEvent() {\n\t\tlowWindTimer = ConfigWind.lowWindTimerEnableAmountBase + (new Random()).nextInt(ConfigWind.lowWindTimerEnableAmountRnd);\n\t}\n\n\tpublic void stopLowWindEvent() {\n\t\tlowWindTimer = 0;\n\t}\n\n\tpublic float getWindSpeedForGusts() {\n\t\treturn windSpeedGust;\n\t}\n\n\tpublic float getWindSpeedForClouds() {\n\t\treturn windSpeedGlobal;\n\t}\n\n\tpublic float getWindAngle(Vec3 pos) {\n\t\tif (windTimeEvent > 0) {\n\t\t\treturn getWindAngleForEvents(pos);\n\t\t} else if (windTimeGust > 0) {\n\t\t\treturn windAngleGust;\n\t\t} else {\n\t\t\treturn windAngleGlobal;\n\t\t}\n\t}\n\n\t/**\n\t * Returns angle in degrees, 0-360\n\t *\n\t * @return\n\t */\n\tpublic float getWindAngleForEvents() {\n\t\treturn windAngleEvent;\n\t}\n\n\tpublic float getWindAngleForEvents(Vec3 pos) {\n\t\tif (pos != null && !windOriginEvent.equals(BlockPos.ZERO)) {\n\t\t\tdouble var11 = windOriginEvent.getX() + 0.5D - pos.x;\n\t\t\tdouble var15 = windOriginEvent.getZ() + 0.5D - pos.z;\n\t\t\treturn (-((float)Math.atan2(var11, var15)) * 180.0F / (float)Math.PI) - 45;\n\t\t} else {\n\t\t\treturn windAngleEvent;\n\t\t}\n\t}\n\n\t/**\n\t * Returns angle in degrees, 0-360\n\t *\n\t * @return\n\t */\n\tpublic float getWindAngleForGusts() {\n\t\treturn windAngleGust;\n\t}\n\n\t/**\n\t * Returns angle in degrees, 0-360\n\t *\n\t * @return\n\t */\n\tpublic float getWindAngleForClouds() {\n\t\treturn windAngleGlobal;\n\t}\n\n\tpublic void setWindTimeGust(int time) {\n\t\twindTimeGust = time;\n\t}\n\n\tpublic void setWindTimeEvent(int parVal) {\n\t\twindTimeEvent = parVal;\n\t\t//syncData(); - might be too often\n\t\t//Weather.dbg(\"Wind event time set: \" + parVal);\n\t}\n\n\tpublic void tick() {\n\n\t\tRandom rand = CoroUtilMisc.random();\n\n\t\t//windSpeedGust = 0;\n\n\t\tif (!ConfigWind.Misc_windOn) {\n\t\t\twindSpeedGlobal = 0;\n\t\t\twindSpeedGust = 0;\n\t\t\twindTimeGust = 0;\n\t\t\t//windSpeedSmooth = 0;\n\t\t} else {\n\n\t\t\tif (!manager.getWorld().isClientSide()) {\n\t\t\t\t//WIND SPEED\\\\\n\n\t\t\t\t//global random wind speed change\n\n\t\t\t\tif (!ConfigWind.Wind_LowWindEvents) {\n\t\t\t\t\tlowWindTimer = 0;\n\t\t\t\t}\n\n\t\t\t\tif (lowWindTimer <= 0) {\n\t\t\t\t\tif (windSpeedGlobalRandChangeTimer-- <= 0)\n\t\t\t\t\t{\n\t\t\t\t\t\t//standard wind adjustment\n\t\t\t\t\t\tif (highWindTimer <= 0) {\n\t\t\t\t\t\t\twindSpeedGlobal += (rand.nextDouble() * windSpeedGlobalChangeRate) - (windSpeedGlobalChangeRate / 2);\n\t\t\t\t\t\t\t//only increase for high wind\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\twindSpeedGlobal += (rand.nextDouble() * windSpeedGlobalChangeRate)/* - (windSpeedGlobalChangeRate / 2)*/;\n\t\t\t\t\t\t}\n\t\t\t\t\t\twindSpeedGlobalRandChangeTimer = windSpeedGlobalRandChangeDelay;\n\t\t\t\t\t}\n\n\t\t\t\t\t//only allow for low wind if high wind not active\n\t\t\t\t\tif (highWindTimer <= 0) {\n\t\t\t\t\t\tif (ConfigWind.Wind_LowWindEvents) {\n\t\t\t\t\t\t\tif (rand.nextInt(ConfigWind.lowWindOddsTo1) == 0) {\n\t\t\t\t\t\t\t\tstartLowWindEvent();\n\t\t\t\t\t\t\t\tWeather.dbg(\"low wind event started, for ticks: \" + lowWindTimer);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t//fix edge case where if a high wind event is manually started, low wind could still be trying to take control\n\t\t\t\t\t\tstopLowWindEvent();\n\t\t\t\t\t}\n\n\t\t\t\t\tif (ConfigWind.Wind_HighWindEvents && highWindTimer <= 0) {\n\t\t\t\t\t\tif (rand.nextInt(ConfigWind.highWindOddsTo1) == 0) {\n\t\t\t\t\t\t\tstartHighWindEvent();\n\t\t\t\t\t\t\tWeather.dbg(\"high wind event started, for ticks: \" + highWindTimer);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tlowWindTimer--;\n\t\t\t\t\tif (lowWindTimer <= 0) {\n\t\t\t\t\t\tWeather.dbg(\"low wind event ended\");\n\t\t\t\t\t}\n\t\t\t\t\twindSpeedGlobal -= 0.01F;\n\t\t\t\t}\n\n\t\t\t\tif (highWindTimer > 0) {\n\t\t\t\t\thighWindTimer--;\n\t\t\t\t\tif (highWindTimer <= 0) {\n\t\t\t\t\t\tWeather.dbg(\"high wind event ended\");\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t//enforce mins and maxs of wind speed\n\t\t\t\tif (windSpeedGlobal < ConfigWind.windSpeedMin)\n\t\t\t\t{\n\t\t\t\t\twindSpeedGlobal = (float)ConfigWind.windSpeedMin;\n\t\t\t\t}\n\n\t\t\t\tif (windSpeedGlobal > ConfigWind.windSpeedMax)\n\t\t\t\t{\n\t\t\t\t\twindSpeedGlobal = (float)ConfigWind.windSpeedMax;\n\t\t\t\t}\n\n\t\t\t\tif (windTimeGust > 0) {\n\t\t\t\t\twindTimeGust--;\n\n\t\t\t\t\tif (windTimeGust == 0) {\n\t\t\t\t\t\twindSpeedGust = 0;\n\t\t\t\t\t\tsyncData();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (ConfigMisc.overcastMode && manager.getWorld().isRaining()) {\n\t\t\t\t\tif (windSpeedGlobal < ConfigWind.windSpeedMinGlobalOvercastRaining) {\n\t\t\t\t\t\twindSpeedGlobal = (float) ConfigWind.windSpeedMinGlobalOvercastRaining;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tfloat speedOverride = ServerWeatherProxy.getWindSpeed((ServerLevel) manager.getWorld());\n\t\t\t\tif (speedOverride != -1) {\n\t\t\t\t\twindSpeedGlobal = speedOverride;\n\t\t\t\t}\n\n\t\t\t\t//smooth use\n\t\t\t\t/*if (windSpeed > windSpeedSmooth)\n\t            {\n\t\t\t\t\twindSpeedSmooth += 0.01F;\n\t            }\n\t            else if (windSpeed < windSpeedSmooth)\n\t            {\n\t            \twindSpeedSmooth -= 0.01F;\n\t            }\n\n\t            if (windSpeedSmooth < 0)\n\t            {\n\t            \twindSpeedSmooth = 0F;\n\t            }*/\n\n\t\t\t\t//WIND SPEED //\n\n\t\t\t\t//WIND ANGLE\\\\\n\n\t\t\t\t//windGustEventTimeRand = 100;\n\n\t\t\t\tfloat randGustWindFactor = 1F;\n\n\t\t\t\t//gust data\n\t\t\t\tif (this.windTimeGust == 0 && lowWindTimer <= 0/* && highWindTimer <= 0*/)\n\t\t\t\t{\n\t\t\t\t\tif (chanceOfWindGustEvent > 0F)\n\t\t\t\t\t{\n\t\t\t\t\t\tif (rand.nextInt((int)((100 - chanceOfWindGustEvent) * randGustWindFactor)) == 0)\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\twindSpeedGust = windSpeedGlobal + rand.nextFloat() * 0.6F;\n\t\t\t\t\t\t\tboolean randomDirectionGust = false;\n\t\t\t\t\t\t\tif (randomDirectionGust) {\n\t\t\t\t\t\t\t\twindAngleGust = rand.nextInt(360) - 180;\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\twindAngleGust = windAngleGlobal + rand.nextInt(120) - 60;\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\tsetWindTimeGust(rand.nextInt(windGustEventTimeRand * 3));\n\t\t\t\t\t\t\t//windEventTime += windTime;\n\t\t\t\t\t\t\t//unneeded since priority system determines wind to use\n\t\t\t\t\t\t\t//directionBeforeGust = windAngleGlobal;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t//global wind angle\n\t\t\t\t//windAngleGlobal += ((new Random()).nextInt(5) - 2) * 0.2F;\n\t\t\t\twindAngleGlobal += (rand.nextFloat() * ConfigWind.globalWindAngleChangeAmountRate) - (rand.nextFloat() * ConfigWind.globalWindAngleChangeAmountRate);\n\n\t\t\t\t//windAngleGlobal += 0.1;\n\n\t\t\t\t//windAngleGlobal = 0;\n\n\t\t\t\tif (windAngleGlobal < -180)\n\t\t\t\t{\n\t\t\t\t\twindAngleGlobal += 360;\n\t\t\t\t}\n\n\t\t\t\tif (windAngleGlobal > 180)\n\t\t\t\t{\n\t\t\t\t\twindAngleGlobal -= 360;\n\t\t\t\t}\n\n\t\t\t\t//WIND ANGLE //\n\t\t\t} else {\n\n\t\t\t\ttickClient();\n\t\t\t}\n\t\t}\n\n\t\t/*windSpeedGlobal = 0.9F;\n\t\twindAngleGlobal = 270;*/\n\n\t}\n\n\t@OnlyIn(Dist.CLIENT)\n\tpublic void tickClient() {\n\t\tPlayer entP = Minecraft.getInstance().player;\n\n\t\tif (windTimeEvent > 0) {\n\t\t\twindTimeEvent--;\n\t\t\tif (windTimeEvent == 0) {\n\t\t\t\twindTimeGust = 0;\n\t\t\t}\n\t\t}\n\n\t\t//event data\n\t\tif (entP != null) {\n\n\t\t\tif (entP != null && entP.level().getGameTime() % 5 == 0) {\n\t\t\t\tcachedWindSpeedClient = getWindSpeedPositional(entP.blockPosition(), 1, false);\n\t\t\t}\n\n\t\t\tif (manager.getWorld().getGameTime() % 20 == 0) {\n\t\t\t\tfloat maxDist = 512;\n\t\t\t\tStormObject so = manager.getClosestStorm(new Vec3(entP.getX(), StormObject.layers.get(0), entP.getZ()), maxDist, StormObject.STATE_HIGHWIND);\n\n\t\t\t\tif (so != null) {\n\n\t\t\t\t\twindOriginEvent = CoroUtilBlock.blockPos(so.posGround.x, so.posGround.y, so.posGround.z);\n\n\t\t\t\t\tsetWindTimeEvent(80);\n\n\t\t\t\t\t//player pos aiming at storm\n\t\t\t\t\tdouble var11 = so.posGround.x - entP.getX();\n\t\t\t\t\tdouble var15 = so.posGround.z - entP.getZ();\n\t\t\t\t\tfloat yaw = -((float)Math.atan2(var11, var15)) * 180.0F / (float)Math.PI;\n\n\t\t\t\t\twindAngleEvent = yaw;\n\t\t\t\t\tdouble dist = entP.position().distanceTo(so.posGround);\n\t\t\t\t\twindSpeedEvent = getEventSpeedFactor(dist, maxDist);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic float getEventSpeedFactor(double dist, double maxDist) {\n\t\treturn (float) (1F - (dist / maxDist)) * 2F;\n\t}\n\n\tpublic WindInfoCache getWindInfoCacheForChunk(BlockPos blockPos, boolean forHeightChunks) {\n\t\t//note: the y value hashing here is used when we want data at that height level\n\t\tBlockPos chunkPos = new BlockPos(blockPos.getX() >> 4, forHeightChunks ? blockPos.getY() >> 4 : 0, blockPos.getZ() >> 4);\n\n\t\tHashMap<Long, WindInfoCache> lookup = forHeightChunks ? lookupChunkWithHeightToWindInfo : lookupChunkToWindInfo;\n\t\tlong hash = chunkPos.asLong();\n\t\tif (lookup.containsKey(hash)) {\n\t\t\treturn lookup.get(hash);\n\t\t} else {\n\t\t\tWindInfoCache cache = new WindInfoCache();\n\t\t\tlookup.put(hash, cache);\n\t\t\treturn cache;\n\t\t}\n\t}\n\n\tpublic float getCachedWindSpeedForHeight(BlockPos blockPos, float extraHeightAmpMax) {\n\t\tWindInfoCache cache = getWindInfoCacheForChunk(blockPos, true);\n\t\tif (cache.cacheTimeWindSpeedAtChunkHeight == 0 || cache.cacheTimeWindSpeedAtChunkHeight + cachedWindInfoUpdateFrequency <= manager.getWorld().getGameTime()) {\n\t\t\tcache.cacheTimeWindSpeedAtChunkHeight = manager.getWorld().getGameTime();\n\t\t\tcache.windSpeedAtChunkHeight = getWindSpeed(blockPos, extraHeightAmpMax);\n\t\t}\n\t\treturn cache.windSpeedAtChunkHeight;\n\t}\n\n\tpublic float getCachedWindSpeedEventForChunkPos(BlockPos blockPos) {\n\t\tWindInfoCache cache = getWindInfoCacheForChunk(blockPos, false);\n\t\tif (cache.cacheTimeWindSpeedEvent == 0 || cache.cacheTimeWindSpeedEvent + cachedWindInfoUpdateFrequency <= manager.getWorld().getGameTime()) {\n\t\t\tcache.cacheTimeWindSpeedEvent = manager.getWorld().getGameTime();\n\t\t\tcache.windSpeedEvent = calculateWindSpeedEventForPos(blockPos);\n\t\t}\n\t\treturn cache.windSpeedEvent;\n\t}\n\n\tpublic float calculateWindSpeedEventForPos(BlockPos pos) {\n\t\tfloat maxDist = 512;\n\t\tVec3 posVec = new Vec3(pos.getX(), pos.getY(), pos.getZ());\n\t\tStormObject so = manager.getClosestStorm(posVec, maxDist, StormObject.STATE_HIGHWIND);\n\t\tif (so != null) {\n\t\t\tdouble dist = posVec.distanceTo(so.posGround);\n\t\t\treturn getEventSpeedFactor(dist, maxDist);\n\t\t}\n\t\treturn 0;\n\t}\n\n\tpublic int getCachedAverageChunkHeightAround(BlockPos blockPos) {\n\t\tWindInfoCache cache = getWindInfoCacheForChunk(blockPos, false);\n\t\tif (cache.cacheTimeChunkHeight == 0 || cache.cacheTimeChunkHeight + cachedChunkHeightUpdateFrequency <= manager.getWorld().getGameTime()) {\n\t\t\tcache.cacheTimeChunkHeight = manager.getWorld().getGameTime();\n\t\t\tBlockPos chunkPos = new BlockPos(blockPos.getX() >> 4, 0, blockPos.getZ() >> 4);\n\t\t\tcache.averageChunkHeightAround = calculateAverageChunkHeightAround(chunkPos);\n\t\t}\n\t\treturn cache.averageChunkHeightAround;\n\t}\n\n\t/**\n\t * Gets heights around the chunk in middle of each chunk checked, stepping out 'squareRadius' times, each check spaced out by 'scanSpacing' chunks\n\t *\n\t * @param chunkPos\n\t * @return\n\t */\n\tpublic int calculateAverageChunkHeightAround(BlockPos chunkPos) {\n\t\tint squareRadius = 2;\n\t\tint scanSpacing = 3;\n\t\tint count = 0;\n\t\tint totalHeight = 0;\n\t\tfor (int x = -squareRadius; x <= squareRadius; x++) {\n\t\t\tfor (int z = -squareRadius; z <= squareRadius; z++) {\n\t\t\t\tBlockPos pos = new BlockPos((chunkPos.getX() + (x * scanSpacing)) * 16 + 8, 0, (chunkPos.getZ() + (z * scanSpacing)) * 16 + 8);\n\t\t\t\tint height = manager.getWorld().getHeightmapPos(Heightmap.Types.WORLD_SURFACE, pos).getY();\n\t\t\t\tif (height > -64) {\n\t\t\t\t\ttotalHeight += height;\n\t\t\t\t\tcount++;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (count == 0) return -64;\n\t\tint avgHeight = totalHeight / count;\n\t\t//System.out.println(\"avgHeight: \" + avgHeight);\n\t\treturn avgHeight;\n\t}\n\n\t/**\n\t *\tGet an amp between average height found in area and max build height\n\t *\n\t *  averageHeight is the bottom\n\t *  getMaxBuildHeight is top\n\t *  height is somewhere in between\n\t *\n\t *  dimensions with smaller range between base height and max build height are easier to benefit from, less height needed\n\t *  this is the best option imo considering the alternatives\n\t *\n\t * @param height\n\t * @param averageHeight\n\t * @return\n\t */\n\tpublic float getWindSpeedAmplifierForHeight(int height, int averageHeight, float extraHeightAmpMax) {\n\t\t//prevent weird math using negative numbers\n\t\tint maxSpeedHeight = manager.getWorld().getHeight();\n\t\tif (manager.getWorld().getMinBuildHeight() < 0) {\n\t\t\tint heightAdj = Math.abs(manager.getWorld().getMinBuildHeight());\n\t\t\theight += heightAdj;\n\t\t\taverageHeight += heightAdj;\n\t\t}\n\t\tint range = maxSpeedHeight - averageHeight;\n\t\theight -= averageHeight;\n\t\treturn 1F + Math.max(0, ((float)height / (float)range) * extraHeightAmpMax);\n\t}\n\n\tpublic void applyWindForceNew(Object ent, float multiplier, float maxSpeed) {\n\t\tapplyWindForceNew(ent, multiplier, maxSpeed, true);\n\t}\n\n\t/**\n\t * \n\t * To solve the problem of speed going overkill due to bad formulas\n\t * \n\t * end goal: make object move at speed of wind\n\t * - object has a weight that slows that adjustment\n\t * - conservation of momentum\n\t * \n\t * calculate force based on wind speed vs objects speed\n\t * - use that force to apply to weight of object\n\t * - profit\n\t */\n\tpublic void applyWindForceNew(Object ent, float multiplier, float maxSpeed, boolean dynamicWind) {\n\n\t\tVec3 pos = new Vec3(CoroUtilEntOrParticle.getPosX(ent), CoroUtilEntOrParticle.getPosY(ent), CoroUtilEntOrParticle.getPosZ(ent));\n\n\t\tVec3 motion = applyWindForceImpl(pos, new Vec3(CoroUtilEntOrParticle.getMotionX(ent), CoroUtilEntOrParticle.getMotionY(ent), CoroUtilEntOrParticle.getMotionZ(ent)),\n\t\t\t\tWeatherUtilEntity.getWeight(ent), multiplier, maxSpeed, dynamicWind);\n\t\t\n\t\tCoroUtilEntOrParticle.setMotionX(ent, motion.x);\n    \tCoroUtilEntOrParticle.setMotionZ(ent, motion.z);\n\t}\n\t\n\t/**\n\t * Handle generic uses of wind force, for stuff like weather objects that arent entities or paticles\n\t */\n\tpublic Vec3 applyWindForceImpl(Vec3 pos, Vec3 motion, float weight, float multiplier, float maxSpeed, boolean dynamicWind) {\n\t\tfloat windSpeed = 0;\n\t\tif (pos != null && ConfigWind.Wind_UsePerlinNoise) {\n\t\t\t/*if (windTimeGust > 0) {\n\t\t\t\twindSpeed = getWindSpeedPerlinNoise(pos);\n\t\t\t} else */{\n\t\t\t\twindSpeed = (getWindSpeed(dynamicWind ? CoroUtilBlock.blockPos(pos) : null) * 0.5F) + (getWindSpeedPerlinNoise(pos) * 0.5F);\n\t\t\t}\n\t\t} else {\n\t\t\twindSpeed = getWindSpeed(dynamicWind ? CoroUtilBlock.blockPos(pos) : null);\n\t\t}\n    \tfloat windAngle = getWindAngle(pos);\n\n    \tfloat windX = (float) -Math.sin(Math.toRadians(windAngle)) * windSpeed;\n    \tfloat windZ = (float) Math.cos(Math.toRadians(windAngle)) * windSpeed;\n    \t\n    \tfloat objX = (float) motion.x;\n    \tfloat objZ = (float) motion.z;\n\t\t\n    \tfloat windWeight = 1F;\n    \tfloat objWeight = weight;\n    \t\n    \t//divide by zero protection\n    \tif (objWeight <= 0) {\n    \t\tobjWeight = 0.001F;\n    \t}\n\n    \tfloat weightDiff = windWeight / objWeight;\n    \t\n    \tfloat vecX = (objX - windX) * weightDiff;\n    \tfloat vecZ = (objZ - windZ) * weightDiff;\n    \t\n    \tvecX *= multiplier;\n    \tvecZ *= multiplier;\n    \t\n    \t//copy over existing motion data\n    \tVec3 newMotion = motion;\n    \t\n    \tdouble speedCheck = (Math.abs(vecX) + Math.abs(vecZ)) / 2D;\n        if (speedCheck < maxSpeed) {\n        \tnewMotion = new Vec3(objX - vecX, motion.y, objZ - vecZ);\n        } else {\n        \tfloat speedDampen = (float)(maxSpeed / speedCheck);\n\t\t\tnewMotion = new Vec3(objX - vecX*speedDampen, motion.y, objZ - vecZ*speedDampen);\n\t\t}\n        \n        return newMotion;\n\t}\n\n\tpublic CompoundTag nbtSyncForClient() {\n\t\tCompoundTag data = new CompoundTag();\n\n\t\t//idea: only sync the wind data client cares about (the active priority wind)\n\n\t\tdata.putFloat(\"windSpeedGlobal\", windSpeedGlobal);\n\t\tdata.putFloat(\"windAngleGlobal\", windAngleGlobal);\n\t\tdata.putFloat(\"windSpeedGust\", windSpeedGust);\n\t\tdata.putFloat(\"windAngleGust\", windAngleGust);\n\n\t\t/*data.putFloat(\"windSpeedEvent\", windSpeedEvent);\n\t\tdata.putFloat(\"windAngleEvent\", windAngleEvent);\n\t\tdata.putInt(\"windTimeEvent\", windTimeEvent);*/\n\n\t\tdata.putInt(\"windTimeGust\", windTimeGust);\n\n\t\treturn data;\n\t}\n\n\tpublic void nbtSyncFromServer(CompoundTag parNBT) {\n\n\t\twindSpeedGlobal = parNBT.getFloat(\"windSpeedGlobal\");\n\t\twindAngleGlobal = parNBT.getFloat(\"windAngleGlobal\");\n\t\twindSpeedGust = parNBT.getFloat(\"windSpeedGust\");\n\t\twindAngleGust = parNBT.getFloat(\"windAngleGust\");\n\n\t\t/*windSpeedEvent = parNBT.getFloat(\"windSpeedEvent\");\n\t\twindAngleEvent = parNBT.getFloat(\"windAngleEvent\");\n\t\twindTimeEvent = parNBT.getInt(\"windTimeEvent\");*/\n\n\t\twindTimeGust = parNBT.getInt(\"windTimeGust\");\n\t}\n\n\tpublic Vec3 getWindForce(@Nullable BlockPos pos) {\n\t\tfloat windSpeed = this.getWindSpeed(pos);\n\t\tfloat windAngle = this.getWindAngle(null);\n\t\tfloat windX = (float) -Math.sin(Math.toRadians(windAngle)) * windSpeed;\n\t\tfloat windZ = (float) Math.cos(Math.toRadians(windAngle)) * windSpeed;\n\t\treturn new Vec3(windX, 0, windZ);\n\t}\n\n\tpublic void syncData() {\n\t\tif (manager instanceof WeatherManagerServer) {\n\t\t\t((WeatherManagerServer) manager).syncWindUpdate(this);\n\t\t}\n\t}\n\n\tpublic void reset() {\n\t\tmanager = null;\n\t}\n\n\tpublic void read(CompoundTag data) {\n\t\twindSpeedGlobal = data.getFloat(\"windSpeedGlobal\");\n\t\twindAngleGlobal = data.getFloat(\"windAngleGlobal\");\n\n\t\twindSpeedGust = data.getFloat(\"windSpeedGust\");\n\t\twindAngleGust = data.getFloat(\"windAngleGust\");\n\t\twindTimeGust = data.getInt(\"windTimeGust\");\n\n\t\twindSpeedEvent = data.getFloat(\"windSpeedEvent\");\n\t\twindAngleEvent = data.getFloat(\"windAngleEvent\");\n\t\twindTimeEvent = data.getInt(\"windTimeEvent\");\n\n\t\tlowWindTimer = data.getInt(\"lowWindTimer\");\n\t\thighWindTimer = data.getInt(\"highWindTimer\");\n\t}\n\n\tpublic CompoundTag write(CompoundTag data) {\n\t\tdata.putFloat(\"windSpeedGlobal\", windSpeedGlobal);\n\t\tdata.putFloat(\"windAngleGlobal\", windAngleGlobal);\n\n\t\tdata.putFloat(\"windSpeedGust\", windSpeedGust);\n\t\tdata.putFloat(\"windAngleGust\", windAngleGust);\n\t\tdata.putInt(\"windTimeGust\", windTimeGust);\n\n\t\tdata.putFloat(\"windSpeedEvent\", windSpeedEvent);\n\t\tdata.putFloat(\"windAngleEvent\", windAngleEvent);\n\t\tdata.putInt(\"windTimeEvent\", windTimeEvent);\n\n\t\tdata.putInt(\"lowWindTimer\", lowWindTimer);\n\t\tdata.putInt(\"highWindTimer\", highWindTimer);\n\n\t\treturn data;\n\t}\n\n\tpublic float getWindSpeedPerlinNoise(Vec3 pos) {\n\t\tPerlinNoise perlinNoise = PerlinNoiseHelper.get().getPerlinNoise();\n\t\t/*int indexX = index % xWide;\n\t\tint indexZ = index / xWide;*/\n\t\tint indexX = (int) Math.floor(pos.x);\n\t\tint indexZ = (int) Math.floor(pos.z);\n\t\tdouble scale = 10;\n\t\tlong time = Minecraft.getInstance().level.getGameTime() * 2;\n\t\tdouble posYAdj = 0;\n\t\tdouble noiseVal = perlinNoise.getValue(((indexX) * scale) + time, ((indexZ) * scale) + time, posYAdj)/* + 0.2F*/;\n\t\treturn (float) Math.max(-1.5F, Math.min(1.5F, noiseVal * 4F));\n\t}\n\n}\n"
  },
  {
    "path": "src/main/resources/META-INF/accesstransformer.cfg",
    "content": "# Model hacks for item frames, need forge patch\npublic-f net.minecraft.client.renderer.model.ModelBakery field_209607_C # STATE_CONTAINER_OVERRIDES\n\n# Fixing vanilla stupidity with flower pots\nprotected net.minecraft.block.FlowerPotBlock field_196451_b\n\n# Tweaking canyon gen height\nprotected net.minecraft.world.gen.carver.CanyonWorldCarver func_222729_a(Lnet/minecraft/world/chunk/IChunk;JIIIDDDFFFIIDLjava/util/BitSet;)V # func_222729_a\n\n# Tree helpers\npublic net.minecraft.world.gen.feature.AbstractTreeFeature func_214572_g(Lnet/minecraft/world/gen/IWorldGenerationBaseReader;Lnet/minecraft/util/math/BlockPos;)Z # isAirOrLeaves\n\n# Map of DyeColor to wool block, just useful\npublic net.minecraft.entity.passive.SheepEntity field_200206_bz # WOOL_BY_COLOR\n\n# Overriding chest container name\npublic net.minecraft.block.ChestBlock$InventoryFactory\npublic net.minecraft.block.ChestBlock field_220110_j # field_220110_j\n\n# Loot Tables\nprotected net.minecraft.data.loot.BlockLootTables field_218573_a\nprotected net.minecraft.data.loot.BlockLootTables field_218574_b\nprotected net.minecraft.data.loot.BlockLootTables field_218575_c\nprotected net.minecraft.data.loot.BlockLootTables field_218576_d\nprotected net.minecraft.data.loot.BlockLootTables field_218577_e\nprotected net.minecraft.data.loot.BlockLootTables field_218579_g\nprotected net.minecraft.data.loot.BlockLootTables field_218580_h\n\n# Umbrella Shadow\nprotected net.minecraft.client.renderer.entity.EntityRenderer func_76975_c(Lnet/minecraft/entity/Entity;DDDFF)V # renderShadow\nprotected net.minecraft.client.renderer.entity.EntityRenderer func_76977_a(Lnet/minecraft/entity/Entity;DDDF)V # renderEntityOnFire\n\n# Minigame dimension\npublic net.minecraft.world.GameRules func_234901_a_(Lcom/mojang/serialization/DynamicLike;)V # decode\n\n# Chase camera\npublic net.minecraft.client.renderer.ActiveRenderInfo func_216779_a(D)D # calcCameraDistance\npublic net.minecraft.client.renderer.ActiveRenderInfo func_216782_a(DDD)V # movePosition\npublic net.minecraft.client.renderer.ActiveRenderInfo func_216776_a(FF)V # setDirection\npublic net.minecraft.client.renderer.ActiveRenderInfo func_216775_b(DDD)V # setPosition\n\n# Runtime worlds\npublic net.minecraft.server.MinecraftServer field_71305_c # worlds\npublic net.minecraft.server.MinecraftServer field_71310_m # anvilConverterForAnvilFile\n\n# Creepers\npublic net.minecraft.entity.monster.CreeperEntity func_146077_cc()V # explode\n\npublic net.minecraft.client.particle.Particle field_187126_f # posX\npublic net.minecraft.client.particle.Particle field_187127_g # posY\npublic net.minecraft.client.particle.Particle field_187128_h # posZ\npublic net.minecraft.client.particle.Particle field_187129_i # motionX\npublic net.minecraft.client.particle.Particle field_187130_j # motionY\npublic net.minecraft.client.particle.Particle field_187131_k # motionZ\npublic net.minecraft.client.particle.Particle field_70552_h # particleRed\npublic net.minecraft.client.particle.Particle field_70553_i # particleGreen\npublic net.minecraft.client.particle.Particle field_70551_j # particleBlue\npublic net.minecraft.client.particle.Particle field_70546_d # age\npublic net.minecraft.world.server.ChunkManager func_223491_f()Ljava/lang/Iterable; # getLoadedChunksIterable\n\npublic net.minecraft.client.world.ClientWorld field_217428_a # globalEntities\n\n# Acid rain\npublic-f net.minecraft.client.renderer.WorldRenderer field_228413_h_ # RAIN_TEXTURES\n\n#public net.minecraft.client.renderer.GameRenderer * #all fields public\n#public net.minecraft.client.renderer.GameRenderer func_215311_a(Lnet/minecraft/client/renderer/ActiveRenderInfo;FZ)D # getFOVModifier\n#public net.minecraft.client.renderer.entity.EntityRendererManager * #all fields public\n#public net.minecraft.client.world.ClientWorld field_217428_a #globalEntities\n#public net.minecraft.world.ServerWorld field_73068_P # allPlayersSleeping\n\n#public-f net.minecraft.world.World field_72986_A # worldInfo\n\n\n\n#NEW FOR MOJANG MAPPINGS\npublic net.minecraft.client.particle.Particle f_107227_ # rCol\npublic net.minecraft.client.particle.Particle f_107228_ # gCol\npublic net.minecraft.client.particle.Particle f_107229_ # bCol\n\npublic net.minecraft.client.particle.Particle f_107212_ # x\npublic net.minecraft.client.particle.Particle f_107213_ # y\npublic net.minecraft.client.particle.Particle f_107214_ # z\n\npublic net.minecraft.client.particle.Particle f_107215_ # xd\npublic net.minecraft.client.particle.Particle f_107216_ # yd\npublic net.minecraft.client.particle.Particle f_107217_ # zd\n\npublic net.minecraft.client.particle.Particle f_107224_ # age\n\npublic net.minecraft.world.entity.player.Player f_36077_ # abilities\n\npublic net.minecraft.server.level.ChunkMap m_140416_()Ljava/lang/Iterable; # getChunks\npublic net.minecraft.client.color.block.BlockColors f_92571_ # blockColors\n\npublic com.mojang.math.Matrix4f * # all fields\n\npublic net.minecraft.world.level.biome.Biome m_47505_(Lnet/minecraft/core/BlockPos;)F # getTemperature\n\npublic net.minecraft.server.level.ServerLevel f_8549_ # serverLevelData\n\npublic net.minecraft.server.level.ServerLevel m_143248_(Lnet/minecraft/core/BlockPos;)Ljava/util/Optional; # findLightningRod\n\npublic net.minecraft.client.Options f_92073_ # particles\n\npublic net.minecraft.client.renderer.LevelRenderer m_109703_(Lnet/minecraft/client/renderer/LightTexture;FDDD)V # renderSnowAndRain\n\npublic net.minecraft.client.particle.ParticleEngine m_263560_()V # clearParticles\n\npublic net.minecraft.client.particle.ParticleEngine f_107289_ # particles\n\npublic net.minecraft.client.particle.ParticleEngine f_107290_ # trackingEmitters"
  },
  {
    "path": "src/main/resources/META-INF/mods.toml",
    "content": "# This is an example mods.toml file. It contains the data relating to the loading mods.\n# There are several mandatory fields (#mandatory), and many more that are optional (#optional).\n# The overall format is standard TOML format, v0.5.0.\n# Note that there are a couple of TOML lists in this file.\n# Find more information on toml format here:  https://github.com/toml-lang/toml\n# The name of the mod loader type to load - for regular FML @Mod mods it should be javafml\nmodLoader=\"javafml\" #mandatory\n# A version range to match for said mod loader - for regular FML @Mod it will be the forge version\nloaderVersion=\"${loader_version_range}\" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions.\n# The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties.\n# Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here.\nlicense=\"${mod_license}\"\n# A URL to refer people to when problems occur with this mod\n#issueTrackerURL=\"https://change.me.to.your.issue.tracker.example.invalid/\" #optional\n# A list of mods - how many allowed here is determined by the individual mod loader\n[[mods]] #mandatory\n# The modid of the mod\nmodId=\"${mod_id}\" #mandatory\n# The version number of the mod\nversion=\"${mod_version}\" #mandatory\n# A display name for the mod\ndisplayName=\"${mod_name}\" #mandatory\n# A URL to query for updates for this mod. See the JSON update specification https://docs.minecraftforge.net/en/latest/misc/updatechecker/\n#updateJSONURL=\"https://change.me.example.invalid/updates.json\" #optional\n# A URL for the \"homepage\" for this mod, displayed in the mod UI\n#displayURL=\"https://change.me.to.your.mods.homepage.example.invalid/\" #optional\n# A file name (in the root of the mod JAR) containing a logo for display\n#logoFile=\"examplemod.png\" #optional\n# A text field displayed in the mod UI\n#credits=\"\" #optional\n# A text field displayed in the mod UI\nauthors=\"${mod_authors}\" #optional\n# Display Test controls the display for your mod in the server connection screen\n# MATCH_VERSION means that your mod will cause a red X if the versions on client and server differ. This is the default behaviour and should be what you choose if you have server and client elements to your mod.\n# IGNORE_SERVER_VERSION means that your mod will not cause a red X if it's present on the server but not on the client. This is what you should use if you're a server only mod.\n# IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component.\n# NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value.\n# IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself.\n#displayTest=\"MATCH_VERSION\" # MATCH_VERSION is the default if nothing is specified (#optional)\n\n# The description text for the mod (multi line!) (#mandatory)\ndescription='''${mod_description}'''\n# A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional.\n[[dependencies.${mod_id}]] #optional\n# the modid of the dependency\nmodId=\"forge\" #mandatory\n# Does this dependency have to exist - if not, ordering below must be specified\nmandatory=true #mandatory\n# The version range of the dependency\nversionRange=\"${forge_version_range}\" #mandatory\n# An ordering relationship for the dependency - BEFORE or AFTER required if the dependency is not mandatory\n# BEFORE - This mod is loaded BEFORE the dependency\n# AFTER - This mod is loaded AFTER the dependency\nordering=\"NONE\"\n# Side this dependency is applied on - BOTH, CLIENT, or SERVER\nside=\"BOTH\"\n# Here's another dependency\n[[dependencies.${mod_id}]]\nmodId=\"minecraft\"\nmandatory=true\n# This version range declares a minimum of the current minecraft version up to but not including the next major version\nversionRange=\"${minecraft_version_range}\"\nordering=\"NONE\"\nside=\"BOTH\"\n\n[[dependencies.${mod_id}]]\nmodId=\"coroutil\"\nmandatory=true\nversionRange=\"[1.20.1-1.3.7,)\"\nordering=\"NONE\"\nside=\"BOTH\"\n\n# Features are specific properties of the game environment, that you may want to declare you require. This example declares\n# that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't\n# stop your mod loading on the server for example.\n#[features.${mod_id}]\n#openGLVersion=\"[3.2,)\""
  },
  {
    "path": "src/main/resources/assets/coroutil/blockstates/blank.json",
    "content": "{\n  \"variants\": {\n    \"normal\": {\n      \"model\": \"coroutil:blank\"\n    }\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/coroutil/blockstates/repairing_block.json",
    "content": "{\n  \"variants\": {\n    \"normal\": {\n      \"model\": \"coroutil:repairing_block\"\n    }\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/coroutil/config/loot_tables/testloot.json",
    "content": "{\n    \"pools\": [\n        {\n            \"rolls\": 1,\n            \"entries\": [\n                {\n                    \"type\": \"item\",\n                    \"name\": \"minecraft:rotten_flesh\",\n                    \"weight\": 1,\n                    \"functions\": [\n                        {\n                            \"function\": \"set_count\",\n                            \"count\": {\n                                \"min\": 0,\n                                \"max\": 2\n                            }\n                        },\n                        {\n                            \"function\": \"looting_enchant\",\n                            \"count\": {\n                                \"min\": 0,\n                                \"max\": 1\n                            }\n                        }\n                    ]\n                }\n            ]\n        },\n        {\n            \"conditions\": [\n                {\n                    \"condition\": \"killed_by_player\"\n                },\n                {\n                    \"condition\": \"random_chance_with_looting\",\n                    \"chance\": 0.025,\n                    \"looting_multiplier\": 0.01\n                }\n            ],\n            \"rolls\": 1,\n            \"entries\": [\n                {\n                    \"type\": \"item\",\n                    \"name\": \"minecraft:iron_ingot\",\n                    \"weight\": 1\n                },\n                {\n                    \"type\": \"item\",\n                    \"name\": \"minecraft:carrot\",\n                    \"weight\": 1\n                },\n                {\n                    \"type\": \"item\",\n                    \"name\": \"minecraft:potato\",\n                    \"weight\": 1\n                }\n            ]\n        }\n    ]\n}"
  },
  {
    "path": "src/main/resources/assets/coroutil/config/loot_tables/testlootboss.json",
    "content": "{\n    \"pools\": [\n        {\n            \"rolls\": 1,\n            \"entries\": [\n                {\n                    \"type\": \"item\",\n                    \"name\": \"minecraft:stick\",\n                    \"weight\": 1,\n                    \"functions\": [\n                        {\n                            \"function\": \"set_count\",\n                            \"count\": {\n                                \"min\": 1,\n                                \"max\": 3\n                            }\n                        },\n                        {\n                            \"function\": \"looting_enchant\",\n                            \"count\": {\n                                \"min\": 0,\n                                \"max\": 1\n                            }\n                        }\n                    ]\n                }\n            ]\n        },\n        {\n            \"conditions\": [\n                {\n                    \"condition\": \"killed_by_player\"\n                }\n            ],\n            \"rolls\": 1,\n            \"entries\": [\n                {\n                    \"type\": \"item\",\n                    \"name\": \"minecraft:diamond\",\n                    \"weight\": 3\n                },\n                {\n                    \"type\": \"item\",\n                    \"name\": \"minecraft:emerald\",\n                    \"weight\": 3\n                }\n            ]\n        }\n    ]\n}"
  },
  {
    "path": "src/main/resources/assets/coroutil/config/templates/actions/mob_spawns.json",
    "content": "{\n  \"wiki\": \"//looking to customize your own invasions? see http://coros.us/wiki/index.php?title=Hostile_Worlds_-_Invasions_-_Customizing for helpfull info\",\n  \"format\": \"mob_spawns\",\n  \"templates\": [\n    {\n      \"name\": \"invasion_stage_1\",\n      \"wave_message\": \"§cAn invasion has started! Zombie Miners!\",\n      \"conditions\": [\n        {\n          \"condition\": \"invasion_number\",\n          \"min\": 1,\n          \"max\": 1\n        }\n      ],\n      \"spawns\": [\n        {\n          \"entities\": [\n            \"minecraft:zombie\"\n          ],\n          \"count\": 3,\n          \"count_max\": 5,\n          \"count_difficulty_multiplier\": 2,\n          \"spawn_type\": \"ground\",\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_miner\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"inventory_all_scales\"\n            }\n          ]\n        },\n        {\n          \"entities\": [\n            \"minecraft:zombie\"\n          ],\n          \"count\": 5,\n          \"count_max\": 15,\n          \"count_difficulty_multiplier\": 2,\n          \"spawn_type\": \"ground\",\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_soldier\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"inventory_all_scales\"\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"name\": \"invasion_stage_2\",\n      \"wave_message\": \"§cAn invasion has started! Miners and Skeletons!\",\n      \"conditions\": [\n        {\n          \"condition\": \"invasion_number\",\n          \"min\": 2,\n          \"max\": 2\n        }\n      ],\n      \"spawns\": [\n        {\n          \"entities\": [\n            \"minecraft:zombie\"\n          ],\n          \"count\": 3,\n          \"count_max\": 5,\n          \"count_difficulty_multiplier\": 2,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_miner\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"inventory_all_scales\"\n            }\n          ]\n        },\n        {\n          \"entities\": [\n            \"minecraft:skeleton\"\n          ],\n          \"count\": 5,\n          \"count_max\": 10,\n          \"count_difficulty_multiplier\": 2,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_soldier\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"inventory_all_scales_no_weapon\"\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"name\": \"invasion_stage_3_opt_1_zombies\",\n      \"wave_message\": \"§cAn invasion has started! Miners and Zombies!\",\n      \"conditions\": [\n        {\n          \"condition\": \"invasion_number\",\n          \"min\": 3\n        },\n        {\n          \"condition\": \"random\",\n          \"weight\": 10\n        }\n      ],\n      \"spawns\": [\n        {\n          \"entities\": [\n            \"minecraft:zombie\"\n          ],\n          \"count\": 5,\n          \"count_max\": 10,\n          \"count_difficulty_multiplier\": 2,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_miner\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"inventory_all_scales\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"buffed_attributes\"\n            }\n          ]\n        },\n        {\n          \"entities\": [\n            \"minecraft:zombie\"\n          ],\n          \"count\": 5,\n          \"count_max\": 30,\n          \"count_difficulty_multiplier\": 0.5,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_soldier\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"inventory_all_scales\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"buffed_attributes\"\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"name\": \"invasion_stage_3_opt_2_skeletons\",\n      \"wave_message\": \"§cAn invasion has started! Miners and Skeletons!\",\n      \"conditions\": [\n        {\n          \"condition\": \"invasion_number\",\n          \"min\": 3\n        },\n        {\n          \"condition\": \"random\",\n          \"weight\": 10\n        }\n      ],\n      \"spawns\": [\n        {\n          \"entities\": [\n            \"minecraft:zombie\"\n          ],\n          \"count\": 5,\n          \"count_max\": 10,\n          \"count_difficulty_multiplier\": 2,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_miner\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"inventory_all_scales\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"buffed_attributes\"\n            }\n          ]\n        },\n        {\n          \"entities\": [\n            \"minecraft:skeleton\"\n          ],\n          \"count\": 5,\n          \"count_max\": 30,\n          \"count_difficulty_multiplier\": 1,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_soldier\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"inventory_all_scales_no_weapon\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"buffed_attributes\"\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"name\": \"invasion_stage_3_opt_3_endermen\",\n      \"wave_message\": \"§cAn invasion has started! Miners and Endermen!\",\n      \"conditions\": [\n        {\n          \"condition\": \"invasion_number\",\n          \"min\": 3\n        },\n        {\n          \"condition\": \"random\",\n          \"weight\": 10\n        }\n      ],\n      \"spawns\": [\n        {\n          \"entities\": [\n            \"minecraft:zombie\"\n          ],\n          \"count\": 5,\n          \"count_max\": 10,\n          \"count_difficulty_multiplier\": 2,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_miner\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"inventory_all_scales\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"buffed_attributes\"\n            }\n          ]\n        },\n        {\n          \"entities\": [\n            \"minecraft:enderman\"\n          ],\n          \"count\": 5,\n          \"count_max\": 30,\n          \"count_difficulty_multiplier\": 0.5,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_soldier\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"buffed_attributes\"\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"name\": \"invasion_stage_3_opt_4_creepers\",\n      \"wave_message\": \"§cAn invasion has started! Miners and Creepers!\",\n      \"conditions\": [\n        {\n          \"condition\": \"invasion_number\",\n          \"min\": 3\n        },\n        {\n          \"condition\": \"random\",\n          \"weight\": 10\n        }\n      ],\n      \"spawns\": [\n        {\n          \"entities\": [\n            \"minecraft:zombie\"\n          ],\n          \"count\": 5,\n          \"count_max\": 10,\n          \"count_difficulty_multiplier\": 2,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_miner\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"inventory_all_scales\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"buffed_attributes\"\n            }\n          ]\n        },\n        {\n          \"entities\": [\n            \"minecraft:creeper\"\n          ],\n          \"count\": 5,\n          \"count_max\": 30,\n          \"count_difficulty_multiplier\": 0.5,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_soldier\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"buffed_attributes\"\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"name\": \"invasion_stage_3_opt_5_bats\",\n      \"wave_message\": \"§cAn invasion has started! Miners and Bats!\",\n      \"conditions\": [\n        {\n          \"condition\": \"invasion_number\",\n          \"min\": 3\n        },\n        {\n          \"condition\": \"random\",\n          \"weight\": 10\n        }\n      ],\n      \"spawns\": [\n        {\n          \"entities\": [\n            \"minecraft:zombie\"\n          ],\n          \"count\": 5,\n          \"count_max\": 10,\n          \"count_difficulty_multiplier\": 2,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_miner\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"inventory_all_scales\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"buffed_attributes\"\n            }\n          ]\n        },\n        {\n          \"entities\": [\n            \"minecraft:bat\"\n          ],\n          \"count\": 15,\n          \"count_max\": 40,\n          \"count_difficulty_multiplier\": 0.5,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_soldier\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"buffed_attributes\"\n            },\n            {\n              \"cmod\": \"ai_attack_melee\"\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"name\": \"invasion_stage_3_opt_6_spiders\",\n      \"wave_message\": \"§cAn invasion has started! Miners and Spiders!\",\n      \"conditions\": [\n        {\n          \"condition\": \"invasion_number\",\n          \"min\": 3\n        },\n        {\n          \"condition\": \"random\",\n          \"weight\": 10\n        }\n      ],\n      \"spawns\": [\n        {\n          \"entities\": [\n            \"minecraft:zombie\"\n          ],\n          \"count\": 5,\n          \"count_max\": 10,\n          \"count_difficulty_multiplier\": 2,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_miner\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"inventory_all_scales\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"buffed_attributes\"\n            }\n          ]\n        },\n        {\n          \"entities\": [\n            \"minecraft:spider\"\n          ],\n          \"count\": 15,\n          \"count_max\": 40,\n          \"count_difficulty_multiplier\": 0.5,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_soldier\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"buffed_attributes\"\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"name\": \"invasion_stage_3_opt_7_infernal\",\n      \"wave_message\": \"§cAn invasion has started! Miners and Infernal Mobs!\",\n      \"conditions\": [\n        {\n          \"condition\": \"invasion_number\",\n          \"min\": 7\n        },\n        {\n          \"condition\": \"random\",\n          \"weight\": 3\n        },\n        {\n          \"condition\": \"mod_loaded\",\n          \"mod_id\": \"infernalmobs\"\n        }\n      ],\n      \"spawns\": [\n        {\n          \"entities\": [\n            \"minecraft:zombie\"\n          ],\n          \"count\": 5,\n          \"count_max\": 10,\n          \"count_difficulty_multiplier\": 2,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_miner\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"inventory_all_scales\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"buffed_attributes\"\n            }\n          ]\n        },\n        {\n          \"entities\": [\n            \"minecraft:zombie\", \"minecraft:skeleton\"\n          ],\n          \"count\": 3,\n          \"count_max\": 8,\n          \"count_difficulty_multiplier\": 0.5,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_soldier\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"inventory_all_scales_no_weapon\"\n            },\n            {\n              \"cmod\": \"attribute_attackdamage\",\n              \"base_value\": 5,\n              \"max_value\": 10,\n              \"difficulty_multiplier\": 0.5\n            },\n            {\n              \"cmod\": \"attribute_health\",\n              \"base_value\": 50,\n              \"max_value\": 150,\n              \"difficulty_multiplier\": 1.0\n            },\n            {\n              \"cmod\": \"attribute_speed\",\n              \"base_value\": 0.23,\n              \"max_value\": 0.3,\n              \"difficulty_multiplier\": 0.5\n            },\n            {\n              \"cmod\": \"attribute_speed_flying\",\n              \"base_value\": 3.0,\n              \"max_value\": 5,\n              \"difficulty_multiplier\": 0.5\n            },\n            {\n              \"cmod\": \"xp\",\n              \"base_value\": 50,\n              \"difficulty_multiplier\": 1.5\n            },\n            {\n              \"cmod\": \"ai_infernal\",\n              \"randomly_choose_count\": \"10\",\n              \"difficulty_multiplier\": 1.0,\n              \"modifiers\": [\n                \"1UP\", \"Alchemist\", \"Berserk\", \"Blastoff\", \"Bulwark\", \"Choke\", \"Cloaking\", \"Darkness\", \"Ender\",\n                \"Exhaust\", \"Fiery\", \"Ghastly\", \"Gravity\", \"LifeSteal\", \"Ninja\", \"Poisonous\", \"Quicksand\", \"Regen\",\n                \"Rust\", \"Sapper\", \"Sprint\", \"Sticky\", \"Storm\", \"Vengeance\", \"Weakness\", \"Webber\", \"Wither\"\n              ]\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"name\": \"invasion_stage_3_opt_8_zombie_players\",\n      \"wave_message\": \"§cAn invasion has started! Your zombie player friends have arrived and want to check out your base!\",\n      \"conditions\": [\n        {\n          \"condition\": \"invasion_number\",\n          \"min\": 5\n        },\n        {\n          \"condition\": \"random\",\n          \"weight\": 10\n        },\n        {\n          \"condition\": \"mod_loaded\",\n          \"mod_id\": \"zombie_players\"\n        }\n      ],\n      \"spawns\": [\n        {\n          \"entities\": [\n            \"zombie_players:zombie_player\"\n          ],\n          \"count\": 5,\n          \"count_max\": 20,\n          \"count_difficulty_multiplier\": 2,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_miner\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"buffed_attributes\"\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"name\": \"invasion_stage_3_opt_9_crazy_animals\",\n      \"wave_message\": \"§cAn invasion has started! Animals have gone crazy! Also miners!\",\n      \"conditions\": [\n        {\n          \"condition\": \"invasion_number\",\n          \"min\": 3\n        },\n        {\n          \"condition\": \"random\",\n          \"weight\": 10\n        }\n      ],\n      \"spawns\": [\n        {\n          \"entities\": [\n            \"minecraft:zombie\"\n          ],\n          \"count\": 3,\n          \"count_max\": 6,\n          \"count_difficulty_multiplier\": 2,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_miner\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"inventory_all_scales\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"buffed_attributes\"\n            }\n          ]\n        },\n        {\n          \"entities\": [\n            \"cow\", \"pig\", \"sheep\", \"chicken\", \"wolf\", \"ocelot\", \"parrot\", \"polar_bear\", \"rabbit\", \"horse\", \"donkey\", \"mule\", \"llama\"\n          ],\n          \"count\": 20,\n          \"count_max\": 30,\n          \"count_difficulty_multiplier\": 2,\n          \"cmods\": [\n            {\n              \"cmod\": \"ai_omniscience\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"buffed_attributes\"\n            },\n            {\n              \"cmod\": \"ai_attack_melee\"\n            },\n            {\n              \"cmod\": \"attribute_attackdamage\",\n              \"base_value\": 5,\n              \"max_value\": 10,\n              \"difficulty_multiplier\": 0.5\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"name\": \"invasion_stage_3_opt_10_illagers\",\n      \"wave_message\": \"§cAn invasion has started! The illagers are coming! Also miners!\",\n      \"conditions\": [\n        {\n          \"condition\": \"invasion_number\",\n          \"min\": 5\n        },\n        {\n          \"condition\": \"random\",\n          \"weight\": 10\n        }\n      ],\n      \"spawns\": [\n        {\n          \"entities\": [\n            \"minecraft:zombie\"\n          ],\n          \"count\": 5,\n          \"count_max\": 10,\n          \"count_difficulty_multiplier\": 2,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_miner\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"inventory_all_scales\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"buffed_attributes\"\n            }\n          ]\n        },\n        {\n          \"entities\": [\n            \"vindication_illager\", \"illusion_illager\"\n          ],\n          \"count\": 4,\n          \"count_max\": 15,\n          \"count_difficulty_multiplier\": 2,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_soldier\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"buffed_attributes\"\n            }\n          ]\n        },\n        {\n          \"entities\": [\n            \"evocation_illager\"\n          ],\n          \"count\": 1,\n          \"count_max\": 2,\n          \"count_difficulty_multiplier\": 2,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_soldier\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"buffed_attributes\"\n            }\n          ]\n        }\n      ]\n    }\n  ]\n\n}"
  },
  {
    "path": "src/main/resources/assets/coroutil/config/templates/actions/mob_spawns_example_commented.json",
    "content": "{\n  \"wiki\": \"//looking to customize your own invasions? see http://coros.us/wiki/index.php?title=Hostile_Worlds_-_Invasions_-_Customizing for helpfull info\",\n  \"format\": \"mob_spawns_example_commented\",\n  \"comment\": \"//for your main file, normally this should always be named mob_spawns, if you want to test and switch between json files, name the above field differently and change the config option mobSpawnsProfile in CoroUtil developer config file to the name\",\n  \"templates\": [\n    {\n      \"name\": \"invasion_stage_1_opt_1\",\n      \"comment\": \"//name of this wave option, should be uniquely named for troubleshooting, not used anywhere else\",\n      \"wave_message\": \"miners!\",\n      \"comment\": \"//optional, custom invasion start message\",\n      \"conditions\": [\n        {\n          \"condition\": \"random\",\n          \"weight\": 3\n        },\n        {\n          \"condition\": \"difficulty\",\n          \"min\": 0,\n          \"max\": 0.1\n        }\n      ],\n      \"comment\": \"//conditions are optional, but should be structured like this above, see json file all_conditions for everything you can use\",\n      \"spawns\": [\n        {\n          \"comment0\": \"//you can have multiple object entries in the spawns array, this is the first, both the first and second and so on will be used if all the conditions matched\",\n          \"entities\": [\n            \"minecraft:zombie\", \"minecraft:skeleton\"\n          ],\n          \"comment\": \"//a list of entities spawnable for this profile, minimum entry of 1 required, will randomize between them\",\n          \"count\": 2,\n          \"comment\": \"//the minimum base amount that will spawn at difficulty of 0, if the entities list above has multiple entries, it will not increase the amount spawned\",\n          \"count_max\": 5,\n          \"comment\": \"//optional, a safety hard limit on the max spawnable, even if difficulty multiplier is over this amount\",\n          \"count_difficulty_multiplier\": 2,\n          \"comment\": \"//multiplies the amount that will spawn by local dynamic difficulty * this multiplier\",\n          \"comment\": \"//the formula is: count + (count * difficulty * count_difficulty_multiplier)\",\n          \"comment\": \"//example: with a count of 5, difficulty of 0.5 and count_difficulty_multiplier of 2: 5 + (5 * 0.5 * 2) = 10\",\n          \"comment\": \"//setting count_difficulty_multiplier to 0 will make the amount of mobs spawned always equal the count field\",\n          \"spawn_type\": \"ground\",\n          \"comment\": \"//optional, specifies how to spawn the entities in the list, choices are: ground, surface, cave, air, water\",\n          \"comment\": \"//if ground: will try to spawn either on the surface or in a cave depending on where the player is\",\n          \"comment\": \"//if surface: will try to spawn on the surface, a place that can see the sky\",\n          \"comment\": \"//if cave: will try to spawn in a cave, a dark stoney place that cant see the sky\",\n          \"comment\": \"//if air: will try to spawn in open air, not requiring a block under it\",\n          \"comment\": \"//if water: will try to spawn underwater\",\n\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_miner\"\n            },\n            {\n              \"cmod\": \"attribute_health\",\n              \"base_value\": 40,\n              \"difficulty_multiplier\": 1.5\n            }\n          ],\n          \"comment\": \"//cmods are optional, but should be structured like this above, see json file all_cmods for everything you can use\"\n        },\n        {\n          \"comment\": \"//you can have multiple object entries in the spawns array, this is the second, this is the last comment, everything below is just more examples of the same structures\",\n          \"entities\": [\n            \"minecraft:zombie\"\n          ],\n          \"count\": 5,\n          \"count_max\": 15,\n          \"count_difficulty_multiplier\": 2,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_soldier\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"inventory_leather\"\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"name\": \"invasion_stage_1_opt_2\",\n      \"conditions\": [\n        {\n          \"condition\": \"random\",\n          \"weight\": 1\n        }\n      ],\n      \"spawns\": [\n        {\n          \"entities\": [\n            \"minecraft:zombie\"\n          ],\n          \"count\": 2,\n          \"count_max\": 5,\n          \"count_difficulty_multiplier\": 2,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_miner\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"inventory_leather\"\n            }\n          ]\n        },\n        {\n          \"entities\": [\n            \"minecraft:zombie\"\n          ],\n          \"count\": 2,\n          \"count_max\": 10,\n          \"count_difficulty_multiplier\": 2,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_soldier\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"inventory_leather\"\n            }\n          ]\n        },\n        {\n          \"entities\": [\n            \"minecraft:skeleton\"\n          ],\n          \"count\": 2,\n          \"count_max\": 10,\n          \"count_difficulty_multiplier\": 2,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_soldier\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"inventory_leather_no_weapon\"\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"name\": \"invasion_wave_override_1\",\n      \"conditions\": [\n        {\n          \"condition\": \"random\",\n          \"weight\": 99999\n        },\n        {\n          \"condition\": \"invasion_rate\",\n          \"rate\": 5\n        },\n        {\n          \"condition\": \"mod_loaded\",\n          \"mod_id\": \"infernalmobs\",\n          \"mode_boolean\": \"invert\"\n        }\n      ],\n      \"spawns\": [\n        {\n          \"entities\": [\n            \"minecraft:zombie\"\n          ],\n          \"count\": 1,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_miner\"\n            },\n            {\n              \"cmod\": \"attribute_health\",\n              \"base_value\": 150,\n              \"difficulty_multiplier\": 1.5\n            },\n            {\n              \"cmod\": \"xp\",\n              \"base_value\": 100,\n              \"difficulty_multiplier\": 1.5\n            },\n            {\n              \"cmod\": \"mob_drops\",\n              \"loot_table\": \"testlootboss\"\n            },\n            {\n              \"cmod\": \"ai_counterattack\"\n            },\n            {\n              \"cmod\": \"ai_lunge\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"inventory_iron\"\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"name\": \"invasion_wave_override_2\",\n      \"conditions\": [\n        {\n          \"condition\": \"difficulty\",\n          \"min\": 0,\n          \"max\": 999\n        },\n        {\n          \"condition\": \"invasion_rate\",\n          \"rate\": 5\n        },\n        {\n          \"condition\": \"random\",\n          \"weight\": 99999\n        },\n        {\n          \"condition\": \"mod_loaded\",\n          \"mod_id\": \"infernalmobs\"\n        }\n      ],\n      \"spawns\": [\n        {\n          \"entities\": [\n            \"minecraft:zombie\"\n          ],\n          \"count\": 1,\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_miner\"\n            },\n            {\n              \"cmod\": \"attribute_health\",\n              \"base_value\": 30,\n              \"difficulty_multiplier\": 1.5\n            },\n            {\n              \"cmod\": \"xp\",\n              \"base_value\": 100,\n              \"difficulty_multiplier\": 1.5\n            },\n            {\n              \"cmod\": \"mob_drops\",\n              \"loot_table\": \"testlootboss\"\n            },\n            {\n              \"cmod\": \"ai_counterattack\"\n            },\n            {\n              \"cmod\": \"ai_lunge\"\n            },\n            {\n              \"cmod\": \"ai_infernal\",\n              \"randomly_choose_count\": \"10\",\n              \"difficulty_multiplier\": 1.0,\n              \"modifiers\": [\n                \"1UP\", \"Alchemist\", \"Berserk\", \"Blastoff\", \"Bulwark\", \"Choke\", \"Cloaking\", \"Darkness\", \"Ender\",\n                \"Exhaust\", \"Fiery\", \"Ghastly\", \"Gravity\", \"LifeSteal\", \"Ninja\", \"Poisonous\", \"Quicksand\", \"Regen\",\n                \"Rust\", \"Sapper\", \"Sprint\", \"Sticky\", \"Storm\", \"Vengeance\", \"Weakness\", \"Webber\", \"Wither\"\n              ]\n            }\n          ]\n        }\n      ]\n    }\n  ]\n\n}"
  },
  {
    "path": "src/main/resources/assets/coroutil/config/templates/actions/mob_spawns_testing_miners.json",
    "content": "{\n  \"format\": \"mob_spawns_testing_miners\",\n  \"templates\": [\n    {\n      \"name\": \"invasion_stage_1_opt_1\",\n      \"wave_message\": \"miners!\",\n      \"spawns\": [\n        {\n          \"entities\": [\n            \"minecraft:zombie\"\n          ],\n          \"count\": 10,\n          \"count_difficulty_multiplier\": 0,\n          \"spawn_type\": \"ground\",\n          \"cmods\": [\n            {\n              \"cmod\": \"template\",\n              \"template\": \"invader_miner\"\n            },\n            {\n              \"cmod\": \"template\",\n              \"template\": \"inventory_all_scales\"\n            },\n            {\n              \"cmod\": \"ai_attack_melee\"\n            },\n            {\n              \"cmod\": \"attribute_attackdamage\",\n              \"base_value\": 5,\n              \"max_value\": 10,\n              \"difficulty_multiplier\": 1.0\n            },\n            {\n              \"cmod\": \"attribute_health\",\n              \"base_value\": 20,\n              \"max_value\": 50,\n              \"difficulty_multiplier\": 1.0\n            },\n            {\n              \"cmod\": \"attribute_speed\",\n              \"base_value\": 0.28,\n              \"max_value\": 0.3,\n              \"difficulty_multiplier\": 1.0\n            },\n            {\n              \"cmod\": \"attribute_speed_flying\",\n              \"base_value\": 3.0,\n              \"max_value\": 5,\n              \"difficulty_multiplier\": 1.0\n            }\n          ]\n        }\n      ]\n    }\n  ]\n\n}"
  },
  {
    "path": "src/main/resources/assets/coroutil/config/templates/cmods/all_cmods.json",
    "content": "{\n  \"format\": \"cmods\",\n  \"templates\": [\n    {\n      \"name\": \"all_cmods\",\n      \"cmods\": [\n        {\n          \"cmod\": \"template\",\n\n          \"template\": \"boringvanilla\",\n\n          \"comment\": \"//to save on copying and pasting, you can define templates with a set of cmods and refer to them using the template cmod, see invasions_cmods for my actual uses of them, and mob_spawns_example_commented for usage of them\",\n          \"comment\": \"//you cant do templates within templates, so this entry isnt technically valid its just here to explain things, but within the mob_spawns json it would be valid\"\n        },\n        {\n          \"cmod\": \"inventory\",\n\n          \"inv_hand_main\": \"minecraft:diamond_sword\",\n          \"inv_hand_off\": \"minecraft:shield\",\n          \"inv_head\": \"minecraft:diamond_helmet\",\n          \"inv_chest\": \"minecraft:diamond_chestplate\",\n          \"inv_legs\": \"minecraft:diamond_leggings\",\n          \"inv_feet\": \"minecraft:diamond_boots\",\n\n          \"comment\": \"//give inventory to a mob, up to you to make sure the mob you are giving it to supports it, could crash if they dont, supports modded items but cant be sure how they'll work out\"\n        },\n        {\n          \"cmod\": \"inventory_difficulty_scaled\",\n\n          \"stages\": [\n            {\n              \"min\": 0,\n              \"max\": 0.3,\n\n              \"inv_hand_off\": \"minecraft:shield\",\n              \"inv_hand_main\": \"minecraft:stone_sword\",\n              \"inv_head\": \"minecraft:leather_helmet\",\n              \"inv_chest\": \"minecraft:leather_chestplate\",\n              \"inv_legs\": \"minecraft:leather_leggings\",\n              \"inv_feet\": \"minecraft:leather_boots\"\n            },\n            {\n              \"min\": 0.3,\n              \"max\": 6,\n\n              \"inv_hand_off\": \"minecraft:shield\",\n              \"inv_hand_main\": \"minecraft:diamond_sword\",\n              \"inv_head\": \"minecraft:diamond_helmet\",\n              \"inv_chest\": \"minecraft:diamond_chestplate\",\n              \"inv_legs\": \"minecraft:diamond_leggings\",\n              \"inv_feet\": \"minecraft:diamond_boots\"\n            }\n          ],\n\n          \"comment\": \"//give inventory depending on difficulty of area, for easy re-use within mob spawn profiles\"\n        },\n        {\n          \"cmod\": \"mob_drops\",\n          \"loot_table\": \"testloot\",\n\n          \"comment\": \"//give a mob extra loot table drops, supports vanilla eg minecraft:zombie or custom ones you put in the config/loot_tables folder, currently does not override existing drops of a mob\"\n        },\n        {\n          \"cmod\": \"attribute_health\",\n          \"base_value\": 40,\n          \"max_value\": 80,\n          \"difficulty_multiplier\": 1.5,\n\n          \"comment\": \"//set the base health of a mob, difficulty_multiplier is used to multiply their health based on the rated difficulty of the area, so if difficulty in area was 2.0, the math would be: base value 40 + (base health 40 * local difficulty 2.0 * multiplier 1.5) = 120 health, set difficulty_multiplier to 0 or dont include the tag to make the value always be the base value\",\n          \"comment\": \"//max_value is optional, if local dynamic difficulty combined with difficulty_multiplier puts the health over what max_value is set to, it will cap the health to max_value\"\n        },\n        {\n          \"cmod\": \"attribute_attackdamage\",\n          \"base_value\": 5,\n          \"max_value\": 10,\n          \"difficulty_multiplier\": 1.5,\n\n          \"comment\": \"//set the base damage of a mob, difficulty_multiplier is used to multiply their damage based on the rated difficulty of the area, so if difficulty in area was 2.0, the math would be: base value 5 + (base value 5 * local difficulty 2.0 * multiplier 1.5) = 15 damage\"\n        },\n        {\n          \"cmod\": \"attribute_speed\",\n          \"base_value\": 0.23,\n          \"max_value\": 0.3,\n          \"difficulty_multiplier\": 1.1,\n\n          \"comment\": \"//set the base ground movement speed of the mob, multiplication formula works the same as attribute_health, be carefull with this one so you dont get hyper speed mobs, vanilla zombie speed is 0.23 for reference\"\n        },\n        {\n          \"cmod\": \"attribute_speed_flying\",\n          \"base_value\": 0.4,\n          \"max_value\": 0.5,\n          \"difficulty_multiplier\": 0.0,\n\n          \"comment\": \"//same as attribute_speed but for flying entities when they arent touching the ground, because mojang, youll generally want a value 2-3x higher than the attribute_speed which is used for ground\"\n        },\n        {\n          \"cmod\": \"xp\",\n          \"base_value\": 0,\n          \"difficulty_multiplier\": 1.5,\n\n          \"comment\": \"//set the base xp given of a mob, multiplication formula works the same as attribute_health\"\n        },\n        {\n          \"cmod\": \"ai_antiair\",\n\n          \"comment\": \"//gives a mob the ability to perform mean things to flying players depending on ConfigHWMonsters.antiAirType setting, they either leap very far and grab the player by mounting them in their head, or magically pull them down when they see them, pretty mean, antiAirType mode 0 is a bit experimental still\"\n        },\n        {\n          \"cmod\": \"ai_mining\",\n\n          \"comment\": \"//gives a mob the ability to dig towards their target, only use for mobs the size of a zombie\"\n        },\n        {\n          \"cmod\": \"ai_explodeonstuck\",\n\n          \"comment\": \"//gives a mob the ability to explode once they cant get any closer to their target\"\n        },\n        {\n          \"cmod\": \"ai_counterattack\",\n\n          \"comment\": \"//gives a mob the ability to counter attack with a leap towards the target after theyre hit\"\n        },\n        {\n          \"cmod\": \"ai_lunge\",\n\n          \"comment\": \"//gives a mob the ability to move faster towards the target when they are close\"\n        },\n        {\n          \"cmod\": \"ai_attack_melee\",\n\n          \"comment\": \"//gives a mob the ability to chase and hurt a target, use on mobs that didnt have a melee attack like passive mobs, use attribute_attackdamage cmod to set their attack damage\"\n        },\n        {\n          \"cmod\": \"ai_infernal\",\n          \"randomly_choose_count\": \"10\",\n          \"randomly_choose_count_max\": \"15\",\n          \"difficulty_multiplier\": 1.0,\n\n          \"modifiers\": [\n            \"1UP\", \"Alchemist\", \"Berserk\", \"Blastoff\", \"Bulwark\", \"Choke\", \"Cloaking\", \"Darkness\", \"Ender\",\n            \"Exhaust\", \"Fiery\", \"Ghastly\", \"Gravity\", \"LifeSteal\", \"Ninja\", \"Poisonous\", \"Quicksand\", \"Regen\",\n            \"Rust\", \"Sapper\", \"Sprint\", \"Sticky\", \"Storm\", \"Vengeance\", \"Weakness\", \"Webber\", \"Wither\"\n          ],\n\n          \"comment\": \"//if atomicstrykers Infernal Mobs mod is installed, this gives a mob a random amount of the abilities you list above, the above list is all the options available from the mod\",\n          \"comment\": \"//you can specify the modifiers and a number of them to randomly choose from, in the above example config, at a local difficulty of 0.1, it will choose 1 modifier from the list, at 0.5, it will choose 5, etc\",\n          \"comment\": \"//randomly_choose_count_max is optional, since its set, itll never choose more than 15 no matter the difficulty\",\n          \"comment\": \"//this functionality depends on my code knowing how to interact with their mods code, if they change theirs, this cmod may break\",\n          \"comment\": \"//you can safely try to use this even if Infernal Mobs isnt installed, it will just not be used\"\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "src/main/resources/assets/coroutil/config/templates/cmods/invasions_cmods.json",
    "content": "{\n  \"format\": \"cmods\",\n  \"templates\": [\n    {\n      \"name\": \"invader_miner\",\n      \"cmods\": [\n        {\n          \"cmod\": \"ai_mining\"\n        },\n        {\n          \"cmod\": \"ai_omniscience\"\n        }\n      ]\n    },\n    {\n      \"name\": \"invader_soldier\",\n      \"cmods\": [\n        {\n          \"cmod\": \"ai_omniscience\"\n        },\n        {\n          \"cmod\": \"ai_counterattack\"\n        },\n        {\n          \"cmod\": \"ai_lunge\"\n        },\n        {\n          \"cmod\": \"ai_hoist\"\n        }\n      ]\n    },\n    {\n      \"name\": \"buffed_attributes\",\n      \"cmods\": [\n        {\n          \"cmod\": \"attribute_attackdamage\",\n          \"base_value\": 3,\n          \"max_value\": 5,\n          \"difficulty_multiplier\": 0.5\n        },\n        {\n          \"cmod\": \"attribute_health\",\n          \"base_value\": 20,\n          \"max_value\": 50,\n          \"difficulty_multiplier\": 1.0\n        },\n        {\n          \"cmod\": \"attribute_speed\",\n          \"base_value\": 0.23,\n          \"max_value\": 0.3,\n          \"difficulty_multiplier\": 0.5\n        },\n        {\n          \"cmod\": \"attribute_speed_flying\",\n          \"base_value\": 3.0,\n          \"max_value\": 5,\n          \"difficulty_multiplier\": 0.5\n        },\n        {\n          \"cmod\": \"xp\",\n          \"base_value\": 5,\n          \"difficulty_multiplier\": 1.5\n        }\n      ]\n    },\n    {\n      \"name\": \"infernal_skeleton_1\",\n      \"cmods\": [\n        {\n          \"cmod\": \"ai_infernal\",\n          \"modifiers\": [\n            \"Regen\", \"Cloaking\", \"Storm\"\n          ]\n        }\n      ]\n    },\n    {\n      \"name\": \"inventory_all_scales\",\n      \"cmods\": [\n        {\n          \"cmod\": \"inventory_difficulty_scaled\",\n\n          \"stages\": [\n            {\n              \"min\": 0,\n              \"max\": 0.25,\n\n              \"inv_hand_off\": \"minecraft:shield\",\n              \"inv_hand_main\": \"minecraft:stone_sword\",\n              \"inv_head\": \"minecraft:leather_helmet\",\n              \"inv_chest\": \"minecraft:leather_chestplate\",\n              \"inv_legs\": \"minecraft:leather_leggings\",\n              \"inv_feet\": \"minecraft:leather_boots\"\n            },\n            {\n              \"min\": 0.25,\n              \"max\": 0.5,\n\n              \"inv_hand_off\": \"minecraft:shield\",\n              \"inv_hand_main\": \"minecraft:stone_sword\",\n              \"inv_head\": \"minecraft:chainmail_helmet\",\n              \"inv_chest\": \"minecraft:chainmail_chestplate\",\n              \"inv_legs\": \"minecraft:chainmail_leggings\",\n              \"inv_feet\": \"minecraft:chainmail_boots\"\n            },\n            {\n              \"min\": 0.5,\n              \"max\": 0.75,\n\n              \"inv_hand_off\": \"minecraft:shield\",\n              \"inv_hand_main\": \"minecraft:iron_sword\",\n              \"inv_head\": \"minecraft:iron_helmet\",\n              \"inv_chest\": \"minecraft:iron_chestplate\",\n              \"inv_legs\": \"minecraft:iron_leggings\",\n              \"inv_feet\": \"minecraft:iron_boots\"\n            },\n            {\n              \"min\": 0.75,\n              \"max\": 999,\n\n              \"inv_hand_off\": \"minecraft:shield\",\n              \"inv_hand_main\": \"minecraft:diamond_sword\",\n              \"inv_head\": \"minecraft:diamond_helmet\",\n              \"inv_chest\": \"minecraft:diamond_chestplate\",\n              \"inv_legs\": \"minecraft:diamond_leggings\",\n              \"inv_feet\": \"minecraft:diamond_boots\"\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"name\": \"inventory_all_scales_no_weapon\",\n      \"cmods\": [\n        {\n          \"cmod\": \"inventory_difficulty_scaled\",\n\n          \"stages\": [\n            {\n              \"min\": 0,\n              \"max\": 0.25,\n\n              \"inv_hand_off\": \"minecraft:shield\",\n              \"inv_head\": \"minecraft:leather_helmet\",\n              \"inv_chest\": \"minecraft:leather_chestplate\",\n              \"inv_legs\": \"minecraft:leather_leggings\",\n              \"inv_feet\": \"minecraft:leather_boots\"\n            },\n            {\n              \"min\": 0.25,\n              \"max\": 0.5,\n\n              \"inv_hand_off\": \"minecraft:shield\",\n              \"inv_head\": \"minecraft:chainmail_helmet\",\n              \"inv_chest\": \"minecraft:chainmail_chestplate\",\n              \"inv_legs\": \"minecraft:chainmail_leggings\",\n              \"inv_feet\": \"minecraft:chainmail_boots\"\n            },\n            {\n              \"min\": 0.5,\n              \"max\": 0.75,\n\n              \"inv_hand_off\": \"minecraft:shield\",\n              \"inv_head\": \"minecraft:iron_helmet\",\n              \"inv_chest\": \"minecraft:iron_chestplate\",\n              \"inv_legs\": \"minecraft:iron_leggings\",\n              \"inv_feet\": \"minecraft:iron_boots\"\n            },\n            {\n              \"min\": 0.75,\n              \"max\": 999,\n\n              \"inv_hand_off\": \"minecraft:shield\",\n              \"inv_head\": \"minecraft:diamond_helmet\",\n              \"inv_chest\": \"minecraft:diamond_chestplate\",\n              \"inv_legs\": \"minecraft:diamond_leggings\",\n              \"inv_feet\": \"minecraft:diamond_boots\"\n            }\n          ]\n        }\n      ]\n    },\n    {\n      \"name\": \"inventory_leather\",\n      \"cmods\": [\n        {\n          \"cmod\": \"inventory\",\n\n          \"inv_hand_main\": \"minecraft:stone_sword\",\n          \"inv_head\": \"minecraft:leather_helmet\",\n          \"inv_chest\": \"minecraft:leather_chestplate\",\n          \"inv_legs\": \"minecraft:leather_leggings\",\n          \"inv_feet\": \"minecraft:leather_boots\"\n        }\n      ]\n    },\n    {\n      \"name\": \"inventory_leather_no_weapon\",\n      \"cmods\": [\n        {\n          \"cmod\": \"inventory\",\n\n          \"inv_hand_main\": \"minecraft:stone_sword\",\n          \"inv_head\": \"minecraft:leather_helmet\",\n          \"inv_chest\": \"minecraft:leather_chestplate\",\n          \"inv_legs\": \"minecraft:leather_leggings\",\n          \"inv_feet\": \"minecraft:leather_boots\"\n        }\n      ]\n    },\n    {\n      \"name\": \"inventory_chainmail\",\n      \"cmods\": [\n        {\n          \"cmod\": \"inventory\",\n\n          \"inv_hand_main\": \"minecraft:stone_sword\",\n          \"inv_head\": \"minecraft:chainmail_helmet\",\n          \"inv_chest\": \"minecraft:chainmail_chestplate\",\n          \"inv_legs\": \"minecraft:chainmail_leggings\",\n          \"inv_feet\": \"minecraft:chainmail_boots\"\n        }\n      ]\n    },\n    {\n      \"name\": \"inventory_iron\",\n      \"cmods\": [\n        {\n          \"cmod\": \"inventory\",\n\n          \"inv_hand_main\": \"minecraft:iron_sword\",\n          \"inv_hand_off\": \"minecraft:shield\",\n          \"inv_head\": \"minecraft:iron_helmet\",\n          \"inv_chest\": \"minecraft:iron_chestplate\",\n          \"inv_legs\": \"minecraft:iron_leggings\",\n          \"inv_feet\": \"minecraft:iron_boots\"\n        }\n      ]\n    },\n    {\n      \"name\": \"inventory_diamond\",\n      \"cmods\": [\n        {\n          \"cmod\": \"inventory\",\n\n          \"inv_hand_main\": \"minecraft:diamond_sword\",\n          \"inv_hand_off\": \"minecraft:shield\",\n          \"inv_head\": \"minecraft:diamond_helmet\",\n          \"inv_chest\": \"minecraft:diamond_chestplate\",\n          \"inv_legs\": \"minecraft:diamond_leggings\",\n          \"inv_feet\": \"minecraft:diamond_boots\"\n        }\n      ]\n    },\n    {\n      \"name\": \"infernal_skeleton_1_bad\",\n      \"cmods\": [\n        {\n          \"cmod\": \"ai_infernal\",\n          \"modifiers\": [\n            \"Regen\", \"Claking\", \"Storm\"\n          ]\n        }\n      ]\n    },\n    {\n      \"name\": \"inventory_chainmail_bad\",\n      \"cmods\": [\n        {\n          \"cmod\": \"inventory\",\n\n          \"inv_hand_main\": \"minecraft:diamond_swod\",\n          \"inv_hand_off\": \"minecraft:shield\",\n          \"inv_head\": \"minecraft:diamond_helmet\",\n          \"inv_chest\": \"minecraft:diamond_chestplate\",\n          \"inv_legs\": \"minecraft:diamond_leggings\",\n          \"inv_feet\": \"minecraft:diamond_boots\"\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "src/main/resources/assets/coroutil/config/templates/conditions/all_conditions.json",
    "content": "{\n  \"format\": \"conditions\",\n  \"templates\": [\n    {\n      \"name\": \"all_conditions\",\n      \"conditions\": [\n        {\n          \"condition\": \"context\",\n          \"type\": \"invasion/regular/all\",\n\n          \"comment\": \"//placeholder for future stuff, dont use for now, dev note: is 'all' even needed? its the same as not using this condition, maybe if recursive templates were a thing, top level one could override lower one\"\n        },\n        {\n          \"condition\": \"difficulty\",\n          \"min\": 0,\n          \"max\": 0.1,\n\n          \"comment\": \"//only use when local dynamic difficulty as at or between these numbers, difficulty determined by many things, see CoroUtils DynamicDifficulty.cfg for configuring that\",\n          \"comment\": \"//things that can affect difficulty: damage per second, armor rating, server time (default off), vanilla local chunk occupancy time, max health, distance from spawn, buffed location (unused), debuffed location (unused)\",\n          \"comment\": \"//difficulty starts at 0, and goes up to 1 for vanilla and default config, if difficulty goes above 1 its likely due to other mods (like a high damage weapon or stronger than diamond armor, or health over 20)\"\n        },\n        {\n          \"condition\": \"invasion_number\",\n          \"min\": 1,\n          \"max\": 5,\n\n          \"comment\": \"//only use when the active invasion number/count is at or between these numbers, the counter is global and not per player\"\n        },\n        {\n          \"condition\": \"invasion_rate\",\n          \"rate\": 5,\n\n          \"comment\": \"//only use every x invasions, in this example, this will be true every 5th invasion\"\n        },\n        {\n          \"condition\": \"random\",\n          \"weight\": 5,\n\n          \"comment\": \"//when you want to randomize between multiple invasion wave profiles, more weight = more likely, if this isnt set, a default weight of 1 is set\",\n          \"comment\": \"//random is evaluated last after all other conditions, if there is more than 1 invasion profile still usable after all other conditions met, it will then randomize between them using these weights\",\n          \"comment\": \"//if there is more than 1 invasion profile still usable after all other conditions met, it will then randomize between them using these weights\"\n        },\n        {\n          \"condition\": \"filter_mobs\",\n          \"mode\": \"whitelist/blacklist or allow/deny?\",\n          \"entities\": [\n            \"minecraft:zombie\"\n          ],\n          \"comment\": \"//currently unused\"\n        },\n        {\n          \"condition\": \"template\",\n          \"template\": \"condition_set_1\",\n\n          \"comment\": \"//since this is a template within a template, maybe dont allow it for now\"\n        },\n        {\n          \"condition\": \"mod_loaded\",\n          \"mod_id\": \"infernalmobs\",\n          \"mode_boolean\": \"invert\",\n\n          \"comment\": \"//filter waves to only be used when a certain mod is or isnt installed, depending on if mode_boolean is set to normal or invert, normal = is mod loaded, invert = is mod not loaded\"\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "src/main/resources/assets/coroutil/config/templates/conditions/invasions_stages.json",
    "content": "{\n  \"format\": \"conditions\",\n  \"templates\": [\n    {\n      \"name\": \"invasion_stage_1\",\n      \"conditions\": [\n        {\n          \"condition\": \"context\",\n          \"type\": \"invasion\"\n        },\n        {\n          \"condition\": \"difficulty\",\n          \"min\": 0,\n          \"max\": 0.1\n        }\n      ]\n    },\n    {\n      \"name\": \"invasion_stage_2\",\n      \"conditions\": [\n        {\n          \"condition\": \"context\",\n          \"type\": \"invasion\"\n        },\n        {\n          \"condition\": \"difficulty\",\n          \"min\": 0.1,\n          \"max\": 0.2\n        }\n      ]\n    },\n    {\n      \"name\": \"invasion_stage_3\",\n      \"conditions\": [\n        {\n          \"condition\": \"context\",\n          \"type\": \"invasion\"\n        },\n        {\n          \"condition\": \"difficulty\",\n          \"min\": 0.2,\n          \"max\": 5.0\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "src/main/resources/assets/coroutil/lang/en_us.json",
    "content": "{\n  \"itemGroup.weather2\": \"Weather2 Items\"\n}"
  },
  {
    "path": "src/main/resources/assets/coroutil/models/block/blank.json",
    "content": "{\n\n}"
  },
  {
    "path": "src/main/resources/assets/coroutil/models/block/repairing_block.json",
    "content": "{\n  \"parent\": \"block/cube_all\",\n  \"textures\": {\n    \"all\": \"coroutil:blocks/repairing_block\"\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/coroutil/models/item/item_repairing_gel.json",
    "content": "{\n  \"parent\": \"item/handheld\",\n  \"textures\": {\n    \"layer0\": \"coroutil:items/item_repairing_gel\"\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/coroutil/models/item/repairing_block.json",
    "content": "{\n  \"parent\": \"coroutil:block/repairing_block\",\n  \"display\": {\n    \"thirdperson\": {\n      \"rotation\": [\n        10,\n        -45,\n        170\n      ],\n      \"translation\": [\n        0,\n        1.5,\n        -2.75\n      ],\n      \"scale\": [\n        0.375,\n        0.375,\n        0.375\n      ]\n    }\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/coroutil/shaders/foliage.fs",
    "content": "#version 130\n\nuniform sampler2D texture_sampler;\nuniform int fogmode;\n//uniform int stipple[64];\n\nvarying vec2 outTexCoord;\n//flat varying float outBrightness;\nvarying vec4 outRGBA;\n//varying float outAlphaInt;\n\nvoid main()\n{\n\n    //considering range 0.9 to 1.0 is quite costly, and provides minimal visual difference, this is more efficient\n    //scratch that, stipple is crazy expensive in many scenarios, damn\n    /*if (outRGBA.w < 0.9) {\n\n        ivec2 coord = ivec2(gl_FragCoord.xy - 0.5);\n\n        if (stipple[int(mod(coord.x, 8) + mod(coord.y, 8) * 8)] < outAlphaInt - 1) {\n            discard;\n        }\n    }*/\n\n    float fogFactor = 0;\n    if (fogmode == 0) {\n        // Linear fog\n        fogFactor = (gl_Fog.end - gl_FogFragCoord) * gl_Fog.scale;\n    } else if (fogmode == 1) {\n        // Exp fog\n        fogFactor = exp(-gl_Fog.density * gl_FogFragCoord);\n    }\n\n    //0 = full fog\n    //1 = no fog\n\n    /*int kk = lightmapColors[i] >> 16 & 255;\n    int ll = lightmapColors[i] >> 8 & 255;\n    int ii = lightmapColors[i] & 255;*/\n\n    //int lightMap = int(outBrightness);\n    //full 1 1 1\n    //lightMap = -1;\n    //mostly blue\n    //lightMap = -13421569;\n    /*float r = float((lightMap >> 16) & 255) / 255.0;\n    float g = float((lightMap >> 8) & 255) / 255.0;\n    float b = float(lightMap & 255) / 255.0;*/\n\n    /*r = 0.2F;\n    g = 0.2F;\n    b = 1F;*/\n\n    /*float r = 1F;\n    float g = 1F;\n    float b = 1F;*/\n\n    vec4 fragColor = texture2D(texture_sampler, outTexCoord);\n\tfragColor.x *= outRGBA.x;\n\tfragColor.y *= outRGBA.y;\n\tfragColor.z *= outRGBA.z;\n\tfragColor.w *= outRGBA.w;\n\n\t/*if (stipple[1] == 0) {\n\t    fragColor.w = 1;\n\t}*/\n\n\n\n\n\n\tif (outRGBA.w > 0) {\n        fogFactor = clamp(fogFactor, 0.0, 1.0);\n        gl_FragColor = mix(gl_Fog.color, fragColor, fogFactor);\n        gl_FragColor.w = fragColor.w;\n        //gl_FragColor = fragColor;\n    } else {\n        gl_FragColor = fragColor;\n    }\n\n    //gl_FragColor = fragColor;\n}"
  },
  {
    "path": "src/main/resources/assets/coroutil/shaders/foliage.vs",
    "content": "#version 130\n#extension GL_EXT_gpu_shader4 : enable\n\n//in int gl_VertexID;\n//in int gl_InstanceID;\n//seldom changing or 1 time use data - non instanced:\nattribute vec3 position; //mesh pos\nattribute vec2 texCoord;\nattribute vec3 vertexNormal; //unused\n//seldom - instanced\nattribute mat4 modelMatrix; //used to be modelViewMatrix, separate from view matrix\nattribute vec4 rgba; //4th entry, alpha not used here, might as well leave vec4 unless more efficient to separate things to per float/attrib entries\nattribute vec4 meta;\n//often changed data - instanced\nattribute vec2 alphaBrightness;\n\nvarying vec2 outTexCoord;\n//flat varying float outBrightness;\nvarying vec4 outRGBA;\nvarying float outAlphaInt;\n\nuniform mat4 modelViewMatrixCamera;\n\nuniform int time;\nuniform float partialTick;\nuniform float windDir;\nuniform float windSpeed;\n\nvec3 computeCorner(vec3 sway, vec3 angle, vec3 center) {\n    return center + normalize(cross(sway, angle)) * 0.5;\n}\n\nmat4 rotationMatrix(vec3 axis, float angle) {\n    axis = normalize(axis);\n    float s = sin(angle);\n    float c = cos(angle);\n    float oc = 1.0 - c;\n\n    return mat4(oc * axis.x * axis.x + c,           oc * axis.x * axis.y - axis.z * s,  oc * axis.z * axis.x + axis.y * s,  0.0,\n                oc * axis.x * axis.y + axis.z * s,  oc * axis.y * axis.y + c,           oc * axis.y * axis.z - axis.x * s,  0.0,\n                oc * axis.z * axis.x - axis.y * s,  oc * axis.y * axis.z + axis.x * s,  oc * axis.z * axis.z + c,           0.0,\n                0.0,                                0.0,                                0.0,                                1.0);\n}\n\nvoid main()\n{\n\n    float radian = 0.0174533;\n    int swayLag = 20;\n    float index = meta.x;\n    float animationID = meta.y;\n    float heightIndex = meta.z;\n    float antiStiffness = meta.w;\n    float rotation = rgba.w;\n\n    float baseTimeChangeRate = 60.0 * windSpeed;\n    float timeSmooth = (time-baseTimeChangeRate) + (baseTimeChangeRate * partialTick);\n    timeSmooth += index * 200;\n\n    vec3 pos = vec3(0, 0, 0);\n\n    mat4 finalMat = modelViewMatrixCamera * modelMatrix;\n    vec3 posTestAdj = position;\n    posTestAdj.y = posTestAdj.y + heightIndex + 0.5;\n    vec4 posTest = finalMat * vec4(posTestAdj.x, posTestAdj.y, posTestAdj.z, 1.0);\n\n    if (windSpeed > 0.00001 && posTest.w < 999) {\n\n        //wind hit foliage, 1 high for now\n        if (animationID == 0) {\n\n            //BETTER CODE START\n\n            float variance = 0.6;\n\n            //try offsetting mesh so bottom is 0\n            vec3 usePos = position;\n            usePos.y = usePos.y + 0.5;\n\n            float heightFromBase = heightIndex + usePos.y;\n\n            swayLag = int(heightFromBase * -baseTimeChangeRate * 0.2);\n            //swayLag = int(heightFromBase * -1);\n\n            float windSpeedAdj = windSpeed * 0.05 * (heightFromBase);\n\n            windSpeedAdj = windSpeedAdj * (antiStiffness * 2.0);\n\n            //a bit of hack to make all but reeds be influenced by wind more lower down\n            if (antiStiffness == 1.0) {\n                windSpeedAdj = windSpeed * 0.5;\n            }\n\n            //disable for more variance per height\n            //windSpeedAdj = windSpeed * 0.2;\n\n            float adjDir = windDir/* - rotation*/;\n\n            vec3 windAdj = vec3(-sin(adjDir * radian) * windSpeedAdj, 0, cos(adjDir * radian) * windSpeedAdj);\n\n            float yAdj = windAdj.y;\n            if (antiStiffness == 1.0) {\n                //yAdj = cross(windAdj, vec3(1, 0, 1)).y;\n            }\n\n            //semi hacky fix for rotation being done before we apply sway logic\n            if (rotation == 45.0) {\n                windAdj = vec3(-cos(adjDir * radian) * windSpeedAdj, 0, -sin(adjDir * radian) * windSpeedAdj);\n            }\n\n            //maybe correct, added gap between mesh connections though\n            if (antiStiffness == 1.0) {\n                //windAdj.y = yAdj;\n            }\n\n            //windAdj.y = windAdj.y - 0.2;\n\n            //this.rotationYaw is quaternion is both required but messing with the sway math, rework when its quat rotated?\n            //timeModTop = int(mod((((timeSmooth + ((1) * swayLag))) * 60.0 * windSpeed), 360));\n            //timeModTop = int(mod((((timeSmooth + ((1) * swayLag))) * 60.0), 360));\n            //timeModTop = int((((timeSmooth + ((0.001) * swayLag))) * 1.0));\n            int timeModTop = int(mod((timeSmooth * 0.2/* * antiStiffness*/) + swayLag, 360));\n            //timeModTop = int(mod(int(timeSmooth * windSpeed * 10), 360));\n            //timeModTop = int(mod(90, 360));\n\n            variance = 0.02 + (0.05 * windSpeed);\n\n            variance = variance * antiStiffness;\n\n            //enable for more variance per height\n            //variance = 0.06 * (heightFromBase * heightFromBase * 0.02);\n            vec3 chaosAdj = vec3(-sin(timeModTop * radian) * variance, 0, cos(timeModTop * radian) * variance);\n\n            //semi hacky fix for rotation being done before we apply sway logic\n            if (rotation == 45.0) {\n                chaosAdj = vec3(-cos(timeModTop * radian) * variance, 0, -sin(timeModTop * radian) * variance);\n            }\n\n            windAdj = windAdj * heightFromBase;\n            chaosAdj = chaosAdj * heightFromBase * 1.0;\n\n            pos = usePos;\n\n            pos = pos + windAdj + chaosAdj;\n            pos.y = pos.y + heightIndex;\n\n        //seaweed\n        } else if (false && animationID == 1) {\n\n            //timeSmooth = 1;\n\n            float variance = 0.6;\n\n            vec3 angle = vec3(-1, 0, 1);\n            if (rotation == 1) {\n                angle = vec3(1, 0, 1);\n            }\n\n            //more performant but less accurate algorithm, use unless crazy mesh warping needed\n            vec3 baseHeight = vec3(0, heightIndex-1, 0);\n            vec3 baseHeight2 = vec3(0, heightIndex, 0);\n\n            int timeModBottom = int(mod(((timeSmooth + ((heightIndex - 1 + 1) * swayLag)) * 2) + rotation, 360));\n            vec3 swayBottom = vec3(sin(timeModBottom * radian) * variance, 1, cos(timeModBottom * radian) * variance);\n            vec3 prevSway = swayBottom;\n            vec3 bottom = baseHeight + swayBottom;\n\n            int timeModTop = int(mod(((timeSmooth + ((heightIndex + 1) * swayLag)) * 2) + rotation, 360));\n            vec3 sway = vec3(sin(timeModTop * radian) * variance, 1, cos(timeModTop * radian) * variance);\n            vec3 top = baseHeight2 + sway;\n            if (heightIndex == 0) {\n                bottom = vec3(0, 0, 0);\n                prevSway = vec3(0, 1, 0);\n            }\n\n            //more accurate but more expensive loop\n            /*\n\n            vec3 top = vec3(0, 0, 0);\n            vec3 bottom = vec3(0, 0, 0);\n            vec3 bottomNext = bottom;\n            //verify\n            vec3 sway = vec3(0, 0, 0);\n\n            for (int i = 0; i <= heightIndex; i++) {\n                prevSway = sway;\n                timeMod = int(mod(((timeSmooth + ((i + 1) * swayLag)) * 2) + rotation, 360));\n                sway = vec3(sin(timeMod * radian) * variance, 1, cos(timeMod * radian) * variance);\n                sway = normalize(sway);\n\n                top = bottomNext + sway;\n\n                bottom = bottomNext;\n                bottomNext = top;\n            }*/\n\n            if (gl_VertexID == 0) {\n                pos = computeCorner(sway, angle, top);\n            } else if (gl_VertexID == 1) {\n                pos = computeCorner(prevSway, angle, bottom);\n            } else if (gl_VertexID == 2) {\n                angle = angle * -1;\n                pos = computeCorner(prevSway, angle, bottom);\n            } else if (gl_VertexID == 3) {\n                angle = angle * -1;\n                pos = computeCorner(sway, angle, top);\n            }\n        }\n\n        gl_Position = finalMat * vec4(pos.x, pos.y, pos.z, 1.0);\n    } else {\n        gl_Position = posTest;//finalMat * vec4(posTestAdj.x, posTestAdj.y, posTestAdj.z, 1.0);\n    }\n\n\n\n\n    //lazy, cheap dist to camera\n    gl_FogFragCoord = abs(gl_Position.z);\n\n\toutTexCoord = texCoord;\n\n\t//outBrightness = alphaBrightness.y;\n\tint lightMap = int(alphaBrightness.y);\n    vec3 texMap = vec3(float((lightMap >> 16) & 255) / 255.0, float((lightMap >> 8) & 255) / 255.0, float(lightMap & 255) / 255.0);\n\n\toutRGBA = vec4(rgba.x * texMap.x, rgba.y * texMap.y, rgba.z * texMap.z, alphaBrightness.x);\n\t//outAlphaInt = 255 - int(outRGBA.w * 255);\n}"
  },
  {
    "path": "src/main/resources/assets/coroutil/shaders/particle.fs",
    "content": "#version 130\n\nuniform sampler2D texture_sampler;\nuniform int fogmode;\n\nvarying vec2 outTexCoord;\n//varying float outBrightness;\nvarying vec4 outRGBA;\n\nvoid main()\n{\n\n    float fogFactor = 0;\n\n\n    if (fogmode == 0) {\n        // Linear fog\n        fogFactor = (gl_Fog.end - gl_FogFragCoord) * gl_Fog.scale;\n    } else if (fogmode == 1) {\n        // Exp fog\n        fogFactor = exp(-gl_Fog.density * gl_FogFragCoord);\n    }\n\n    vec4 fragColor = texture2D(texture_sampler, outTexCoord);\n\tfragColor.x *= outRGBA.x;\n\tfragColor.y *= outRGBA.y;\n\tfragColor.z *= outRGBA.z;\n\tfragColor.w *= outRGBA.w;\n\n    if (outRGBA.w > 0) {\n        fogFactor = clamp(fogFactor, 0.0, 1.0);\n        gl_FragColor = mix(gl_Fog.color, fragColor, fogFactor);\n        gl_FragColor.w = fragColor.w;\n        //gl_FragColor = fragColor;\n    } else {\n        gl_FragColor = fragColor;\n    }\n\n    //gl_FragColor.w = 0.1;\n\n    //gl_FragColor = fragColor;\n}"
  },
  {
    "path": "src/main/resources/assets/coroutil/shaders/particle.vs",
    "content": "#version 130\n#extension GL_EXT_gpu_shader4 : enable\n\nattribute vec3 position;\nattribute vec2 texCoord;\nattribute vec3 vertexNormal;\nattribute mat4 modelMatrix;\nattribute float brightness;\nattribute vec4 rgba;\n//attribute vec4 rgbaTest;\n//in vec2 texOffset;\n\nvarying vec2 outTexCoord;\n//flat varying float outBrightness;\nvarying vec4 outRGBA;\n\nuniform mat4 modelViewMatrixCamera;\n//uniform mat4 projectionMatrix;\n\n//uniform int numCols;\n//uniform int numRows;\n\nvoid main()\n{\n\n\n\n\tgl_Position = modelViewMatrixCamera * modelMatrix * vec4(position, 1.0);\n\n\t//vec4 eyePos = gl_ModelViewMatrix * gl_Position;\n    //gl_FogFragCoord = abs(eyePos.z/eyePos.w);\n    gl_FogFragCoord = abs(gl_Position.z);\n\n\t// Support for texture atlas, update texture coordinates\n    //float x = (texCoord.x / numCols + texOffset.x);\n    //float y = (texCoord.y / numRows + texOffset.y);\n\n\toutTexCoord = texCoord;\n\t//outBrightness = brightness;\n\tint lightMap = int(brightness);\n\tvec3 texMap = vec3(float((lightMap >> 16) & 255) / 255.0, float((lightMap >> 8) & 255) / 255.0, float(lightMap & 255) / 255.0);\n\n\t//temp\n\t//rgba.x = 1;\n\t//rgba.y = 1;\n\t//rgba.z = 1;\n\t//rgba.w = 1;\n\n\toutRGBA = rgba;\n\toutRGBA.x = rgba.x * texMap.x;\n    outRGBA.y = rgba.y * texMap.y;\n    outRGBA.z = rgba.z * texMap.z;\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/blockstates/anemometer.json",
    "content": "{\n  \"variants\": {\n    \"\": {\n      \"model\": \"weather2:block/anemometer\"\n    }\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/blockstates/sand_layer.json",
    "content": "{\n    \"variants\": {\n        \"layers=1\":  { \"model\": \"weather2:block/sand_height2\" },\n        \"layers=2\":  { \"model\": \"weather2:block/sand_height4\" },\n        \"layers=3\":  { \"model\": \"weather2:block/sand_height6\" },\n        \"layers=4\":  { \"model\": \"weather2:block/sand_height8\" },\n        \"layers=5\":  { \"model\": \"weather2:block/sand_height10\" },\n        \"layers=6\":  { \"model\": \"weather2:block/sand_height12\" },\n        \"layers=7\":  { \"model\": \"weather2:block/sand_height14\" },\n        \"layers=8\":  { \"model\": \"block/sand\" }\n    }\n}\n"
  },
  {
    "path": "src/main/resources/assets/weather2/blockstates/tornado_sensor.json",
    "content": "{\n  \"variants\": {\n\t\"powered=true\": { \"model\": \"weather2:block/tornado_sensor\" },\n\t\"powered=false\": { \"model\": \"weather2:block/tornado_sensor\" }\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/blockstates/tornado_sensor_new.json",
    "content": "{\n\t\"forge_marker\": 1,\n\t\"defaults\": {\n\t\t\"textures\": {\n\t\t\t\"all\": \"weather2:blocks/tornado_sensor\"\n\t\t},\n\t\t\"model\": \"weather2:block/tornado_sensor\",\n\t\t\"uvlock\": true\n\t},\n\t\"variants\": {\n\t\t\"power\": {\n\t\t\t\"true\": {\n\t\t\t\t\"textures\": {\n\t\t\t\t\t\"all\": \"weather2:blocks/tornado_sensor\"\n\t\t\t\t}\n\t\t\t},\n\t\t\t\"false\": {\n\t\t\t\t\"textures\": {\n\t\t\t\t\t\"all\": \"weather2:blocks/tornado_sensor\"\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t\"normal\": [{\n\n\t\t}]\n\t}\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/blockstates/tornado_siren.json",
    "content": "{\n  \"variants\": {\n    \"\": {\n      \"model\": \"weather2:block/tornado_siren\"\n    }\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/blockstates/tornado_siren_manual.json",
    "content": "{\n  \"forge_marker\": 1,\n  \"defaults\": {\n    \"textures\": {\n      \"all\": \"weather2:blocks/tornado_siren\"\n    },\n    \"model\": \"weather2:block/tornado_siren\",\n    \"uvlock\": true\n  },\n  \"variants\": {\n    \"enabled\": {\n      \"true\": {\n        \"textures\": {\n          \"all\": \"weather2:blocks/tornado_siren_manual_on\"\n        }\n      },\n      \"false\": {\n        \"textures\": {\n          \"all\": \"weather2:blocks/tornado_siren_manual\"\n        }\n      }\n    },\n    \"normal\": [{\n\n    }]\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/blockstates/tornado_siren_old.json",
    "content": "{\n  \"variants\": {\n    \"normal\": {\n      \"model\": \"weather2:block/tornado_siren\"\n    }\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/blockstates/weather_deflector.json",
    "content": "{\n  \"variants\": {\n    \"\": {\n      \"model\": \"weather2:block/weather_deflector\"\n    }\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/blockstates/weather_forecast.json",
    "content": "{\n  \"variants\": {\n    \"\": {\n      \"model\": \"weather2:block/weather_forecast\"\n    }\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/blockstates/weather_machine.json",
    "content": "{\n  \"variants\": {\n    \"normal\": {\n      \"model\": \"weather2:block/weather_machine\"\n    }\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/blockstates/wind_turbine.json",
    "content": "{\n  \"variants\": {\n    \"\": {\n      \"model\": \"weather2:block/wind_turbine\"\n    }\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/blockstates/wind_vane.json",
    "content": "{\n  \"variants\": {\n    \"\": {\n      \"model\": \"weather2:block/wind_vane\"\n    }\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/lang/en_us.json",
    "content": "{\n  \"itemGroup.weather2\": \"Weather2 Items\",\n  \"block.weather2.tornado_sensor\": \"Tornado Sensor\",\n  \"block.weather2.tornado_siren\": \"Weather Siren\",\n  \"block.weather2.tornado_siren_manual\": \"Manual Redstone Siren\",\n  \"block.weather2.wind_vane\": \"Wind Vane\",\n  \"block.weather2.wind_turbine\": \"Wind Turbine\",\n  \"block.weather2.weather_forecast\": \"Weather Forecast\",\n  \"block.weather2.weather_machine\": \"Weather Machine (right click to cycle)\",\n  \"block.weather2.weather_deflector\": \"Weather Deflector\",\n  \"block.weather2.anemometer\": \"Anemometer\",\n  \"block.weather2.sand_layer\": \"Placeable Sand Layer\",\n  \"item.weather2.weather_item\": \"Weather Item\",\n  \"item.weather2.sand_layer_placeable\": \"Placeable Sand Layer\",\n  \"item.weather2.pocket_sand\": \"Pocket Sand!\"\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/models/block/anemometer.json",
    "content": "{\n  \"parent\": \"forge:item/default\",\n  \"display\": {\n    \"gui\": {\n      \"rotation\": [ 30, 225, 0 ],\n      \"translation\": [ 0, 0, 0],\n      \"scale\":[ 0.725, 0.725, 0.725 ]\n    }\n  },\n  \"credit\": \"Made with Blockbench\",\n  \"textures\": {\n    \"particle\": \"block/stone\",\n    \"0\": \"weather2:blocks/anemometer\"\n  },\n  \"elements\": [\n    {\n      \"from\": [7.5, 0, 7.5],\n      \"to\": [8.5, 11, 8.5],\n      \"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 0, 8]},\n      \"faces\": {\n        \"north\": {\"uv\": [2, 2, 3, 13], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [1, 2, 2, 13], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [4, 2, 5, 13], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [3, 2, 4, 13], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [3, 2, 2, 1], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [4, 1, 3, 2], \"texture\": \"#0\"}\n      }\n    },\n    {\n      \"from\": [7, 10.5, 7],\n      \"to\": [9, 12.5, 9],\n      \"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 0, 8]},\n      \"faces\": {\n        \"north\": {\"uv\": [2, 2, 4, 4], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [0, 2, 2, 4], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [6, 2, 8, 4], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [4, 2, 6, 4], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [4, 2, 2, 0], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [6, 0, 4, 2], \"texture\": \"#0\"}\n      }\n    },\n    {\n      \"from\": [7.5, 11, 0],\n      \"to\": [8.5, 12, 7],\n      \"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [16, 0, 0]},\n      \"faces\": {\n        \"north\": {\"uv\": [7, 7, 8, 8], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [0, 7, 7, 8], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [15, 7, 16, 8], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [8, 7, 15, 8], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [8, 7, 7, 0], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [9, 0, 8, 7], \"texture\": \"#0\"}\n      }\n    },\n    {\n      \"from\": [6.5, 11, -0.5],\n      \"to\": [7.5, 12, 0.5],\n      \"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 0, 8]},\n      \"faces\": {\n        \"north\": {\"uv\": [2, 2, 3, 3], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [1, 2, 2, 3], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [4, 2, 5, 3], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [3, 2, 4, 3], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [3, 2, 2, 1], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [4, 1, 3, 2], \"texture\": \"#0\"}\n      }\n    },\n    {\n      \"from\": [6.5, 12, -0.5],\n      \"to\": [7.5, 13, 2.5],\n      \"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 0, 8]},\n      \"faces\": {\n        \"north\": {\"uv\": [8, 6, 9, 7], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [5, 6, 8, 7], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [12, 6, 13, 7], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [9, 6, 12, 7], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [9, 6, 8, 3], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [10, 3, 9, 6], \"texture\": \"#0\"}\n      }\n    },\n    {\n      \"from\": [6.5, 11, 1.5],\n      \"to\": [7.5, 12, 2.5],\n      \"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 0, 8]},\n      \"faces\": {\n        \"north\": {\"uv\": [2, 2, 3, 3], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [1, 2, 2, 3], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [4, 2, 5, 3], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [3, 2, 4, 3], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [3, 2, 2, 1], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [4, 1, 3, 2], \"texture\": \"#0\"}\n      }\n    },\n    {\n      \"from\": [6.5, 10, -0.5],\n      \"to\": [7.5, 11, 2.5],\n      \"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, -2, 8]},\n      \"faces\": {\n        \"north\": {\"uv\": [5, 5, 6, 6], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [2, 5, 5, 6], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [9, 5, 10, 6], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [6, 5, 9, 6], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [6, 5, 5, 2], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [7, 2, 6, 5], \"texture\": \"#0\"}\n      }\n    },\n    {\n      \"from\": [0, 11, 7.5],\n      \"to\": [7, 12, 8.5],\n      \"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.5, 8]},\n      \"faces\": {\n        \"north\": {\"uv\": [1, 2, 8, 3], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [0, 2, 1, 3], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [9, 2, 16, 3], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [8, 2, 9, 3], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [8, 2, 1, 1], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [15, 1, 8, 2], \"texture\": \"#0\"}\n      }\n    },\n    {\n      \"from\": [-0.5, 11, 8.5],\n      \"to\": [0.5, 12, 9.5],\n      \"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.5, 8]},\n      \"faces\": {\n        \"north\": {\"uv\": [11, 12, 12, 13], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [10, 12, 11, 13], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [13, 12, 14, 13], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [12, 12, 13, 13], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [12, 12, 11, 11], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [13, 11, 12, 12], \"texture\": \"#0\"}\n      }\n    },\n    {\n      \"from\": [-0.5, 12, 8.5],\n      \"to\": [2.5, 13, 9.5],\n      \"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.5, 8]},\n      \"faces\": {\n        \"north\": {\"uv\": [9, 10, 12, 11], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [8, 10, 9, 11], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [13, 10, 16, 11], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [12, 10, 13, 11], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [12, 10, 9, 9], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [15, 9, 12, 10], \"texture\": \"#0\"}\n      }\n    },\n    {\n      \"from\": [1.5, 11, 8.5],\n      \"to\": [2.5, 12, 9.5],\n      \"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.5, 8]},\n      \"faces\": {\n        \"north\": {\"uv\": [11, 12, 12, 13], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [10, 12, 11, 13], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [13, 12, 14, 13], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [12, 12, 13, 13], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [12, 12, 11, 11], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [13, 11, 12, 12], \"texture\": \"#0\"}\n      }\n    },\n    {\n      \"from\": [-0.5, 10, 8.5],\n      \"to\": [2.5, 11, 9.5],\n      \"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.5, 8]},\n      \"faces\": {\n        \"north\": {\"uv\": [9, 10, 12, 11], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [8, 10, 9, 11], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [13, 10, 16, 11], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [12, 10, 13, 11], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [12, 10, 9, 9], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [15, 9, 12, 10], \"texture\": \"#0\"}\n      }\n    },\n    {\n      \"from\": [7.5, 11, 9],\n      \"to\": [8.5, 12, 16],\n      \"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.5, 8]},\n      \"faces\": {\n        \"north\": {\"uv\": [7, 7, 8, 8], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [0, 7, 7, 8], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [15, 7, 16, 8], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [8, 7, 15, 8], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [8, 7, 7, 0], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [9, 0, 8, 7], \"texture\": \"#0\"}\n      }\n    },\n    {\n      \"from\": [8.5, 11, 15.5],\n      \"to\": [9.5, 12, 16.5],\n      \"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.5, 8]},\n      \"faces\": {\n        \"north\": {\"uv\": [2, 2, 3, 3], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [1, 2, 2, 3], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [4, 2, 5, 3], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [3, 2, 4, 3], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [3, 2, 2, 1], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [4, 1, 3, 2], \"texture\": \"#0\"}\n      }\n    },\n    {\n      \"from\": [8.5, 12, 13.5],\n      \"to\": [9.5, 13, 16.5],\n      \"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.5, 8]},\n      \"faces\": {\n        \"north\": {\"uv\": [7, 7, 8, 8], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [4, 7, 7, 8], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [11, 7, 12, 8], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [8, 7, 11, 8], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [8, 7, 7, 4], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [9, 4, 8, 7], \"texture\": \"#0\"}\n      }\n    },\n    {\n      \"from\": [8.5, 11, 13.5],\n      \"to\": [9.5, 12, 14.5],\n      \"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.5, 8]},\n      \"faces\": {\n        \"north\": {\"uv\": [2, 2, 3, 3], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [1, 2, 2, 3], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [4, 2, 5, 3], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [3, 2, 4, 3], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [3, 2, 2, 1], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [4, 1, 3, 2], \"texture\": \"#0\"}\n      }\n    },\n    {\n      \"from\": [8.5, 10, 13.5],\n      \"to\": [9.5, 11, 16.5],\n      \"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.5, 8]},\n      \"faces\": {\n        \"north\": {\"uv\": [5, 6, 6, 7], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [2, 6, 5, 7], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [9, 6, 10, 7], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [6, 6, 9, 7], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [6, 6, 5, 3], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [7, 3, 6, 6], \"texture\": \"#0\"}\n      }\n    },\n    {\n      \"from\": [9, 11, 7.5],\n      \"to\": [16, 12, 8.5],\n      \"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.5, 8]},\n      \"faces\": {\n        \"north\": {\"uv\": [1, 1, 8, 2], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [0, 1, 1, 2], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [9, 1, 16, 2], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [8, 1, 9, 2], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [8, 1, 1, 0], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [15, 0, 8, 1], \"texture\": \"#0\"}\n      }\n    },\n    {\n      \"from\": [15.5, 11, 6.5],\n      \"to\": [16.5, 12, 7.5],\n      \"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.5, 8]},\n      \"faces\": {\n        \"north\": {\"uv\": [2, 2, 3, 3], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [1, 2, 2, 3], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [4, 2, 5, 3], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [3, 2, 4, 3], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [3, 2, 2, 1], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [4, 1, 3, 2], \"texture\": \"#0\"}\n      }\n    },\n    {\n      \"from\": [13.5, 12, 6.5],\n      \"to\": [16.5, 13, 7.5],\n      \"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.5, 8]},\n      \"faces\": {\n        \"north\": {\"uv\": [4, 4, 7, 5], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [3, 4, 4, 5], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [8, 4, 11, 5], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [7, 4, 8, 5], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [7, 4, 4, 3], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [10, 3, 7, 4], \"texture\": \"#0\"}\n      }\n    },\n    {\n      \"from\": [13.5, 11, 6.5],\n      \"to\": [14.5, 12, 7.5],\n      \"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.5, 8]},\n      \"faces\": {\n        \"north\": {\"uv\": [2, 2, 3, 3], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [1, 2, 2, 3], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [4, 2, 5, 3], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [3, 2, 4, 3], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [3, 2, 2, 1], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [4, 1, 3, 2], \"texture\": \"#0\"}\n      }\n    },\n    {\n      \"from\": [13.5, 10, 6.5],\n      \"to\": [16.5, 11, 7.5],\n      \"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.5, 8]},\n      \"faces\": {\n        \"north\": {\"uv\": [3, 3, 6, 4], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [2, 3, 3, 4], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [7, 3, 10, 4], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [6, 3, 7, 4], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [6, 3, 3, 2], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [9, 2, 6, 3], \"texture\": \"#0\"}\n      }\n    },\n    {\n      \"from\": [6.5, 0, 6.5],\n      \"to\": [9.5, 1, 9.5],\n      \"faces\": {\n        \"north\": {\"uv\": [3, 3, 6, 4], \"texture\": \"#0\"},\n        \"east\": {\"uv\": [0, 3, 3, 4], \"texture\": \"#0\"},\n        \"south\": {\"uv\": [9, 3, 12, 4], \"texture\": \"#0\"},\n        \"west\": {\"uv\": [6, 3, 9, 4], \"texture\": \"#0\"},\n        \"up\": {\"uv\": [6, 3, 3, 0], \"texture\": \"#0\"},\n        \"down\": {\"uv\": [9, 0, 6, 3], \"texture\": \"#0\"}\n      }\n    }\n  ],\n  \"groups\": [\n    {\n      \"name\": \"root\",\n      \"origin\": [8, 0, 8],\n      \"color\": 0,\n      \"children\": [\n        {\n          \"name\": \"base\",\n          \"origin\": [8, 0, 8],\n          \"color\": 0,\n          \"children\": [\n            0,\n            {\n              \"name\": \"top\",\n              \"origin\": [8, 11.5, 8],\n              \"color\": 0,\n              \"children\": [\n                1,\n                {\n                  \"name\": \"arm1\",\n                  \"origin\": [8, 11.5, 8],\n                  \"color\": 0,\n                  \"children\": [\n                    2,\n                    {\n                      \"name\": \"cup1\",\n                      \"origin\": [8, 0, 8],\n                      \"color\": 0,\n                      \"children\": [3, 4, 5, 6]\n                    }\n                  ]\n                },\n                {\n                  \"name\": \"arm2\",\n                  \"origin\": [8, 11.5, 8],\n                  \"color\": 0,\n                  \"children\": [\n                    7,\n                    {\n                      \"name\": \"cup2\",\n                      \"origin\": [8, 0, 8],\n                      \"color\": 0,\n                      \"children\": [8, 9, 10, 11]\n                    }\n                  ]\n                },\n                {\n                  \"name\": \"arm3\",\n                  \"origin\": [8, 11.5, 8],\n                  \"color\": 0,\n                  \"children\": [\n                    12,\n                    {\n                      \"name\": \"cup3\",\n                      \"origin\": [8, 0, 8],\n                      \"color\": 0,\n                      \"children\": [13, 14, 15, 16]\n                    }\n                  ]\n                },\n                {\n                  \"name\": \"arm4\",\n                  \"origin\": [8, 11.5, 8],\n                  \"color\": 0,\n                  \"children\": [\n                    17,\n                    {\n                      \"name\": \"cup4\",\n                      \"origin\": [8, 0, 8],\n                      \"color\": 0,\n                      \"children\": [18, 19, 20, 21]\n                    }\n                  ]\n                }\n              ]\n            }\n          ]\n        }\n      ]\n    }\n  ]\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/models/block/sand_height10.json",
    "content": "{\n    \"textures\": {\n        \"particle\": \"block/sand\",\n        \"texture\": \"block/sand\"\n    },\n    \"elements\": [\n        {   \"from\": [ 0, 0, 0 ],\n            \"to\": [ 16, 10, 16 ],\n            \"faces\": {\n                \"down\":  { \"uv\": [ 0, 0, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"down\" },\n                \"up\":    { \"uv\": [ 0, 0, 16, 16 ], \"texture\": \"#texture\" },\n                \"north\": { \"uv\": [ 0, 6, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"north\" },\n                \"south\": { \"uv\": [ 0, 6, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"south\" },\n                \"west\":  { \"uv\": [ 0, 6, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"west\" },\n                \"east\":  { \"uv\": [ 0, 6, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"east\" }\n            }\n        }\n    ]\n}\n"
  },
  {
    "path": "src/main/resources/assets/weather2/models/block/sand_height12.json",
    "content": "{\n    \"textures\": {\n        \"particle\": \"block/sand\",\n        \"texture\": \"block/sand\"\n    },\n    \"elements\": [\n        {   \"from\": [ 0, 0, 0 ],\n            \"to\": [ 16, 12, 16 ],\n            \"faces\": {\n                \"down\":  { \"uv\": [ 0, 0, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"down\" },\n                \"up\":    { \"uv\": [ 0, 0, 16, 16 ], \"texture\": \"#texture\" },\n                \"north\": { \"uv\": [ 0, 4, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"north\" },\n                \"south\": { \"uv\": [ 0, 4, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"south\" },\n                \"west\":  { \"uv\": [ 0, 4, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"west\" },\n                \"east\":  { \"uv\": [ 0, 4, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"east\" }\n            }\n        }\n    ]\n}\n"
  },
  {
    "path": "src/main/resources/assets/weather2/models/block/sand_height14.json",
    "content": "{\n    \"textures\": {\n        \"particle\": \"block/sand\",\n        \"texture\": \"block/sand\"\n    },\n    \"elements\": [\n        {   \"from\": [ 0, 0, 0 ],\n            \"to\": [ 16, 14, 16 ],\n            \"faces\": {\n                \"down\":  { \"uv\": [ 0, 0, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"down\" },\n                \"up\":    { \"uv\": [ 0, 0, 16, 16 ], \"texture\": \"#texture\" },\n                \"north\": { \"uv\": [ 0, 2, 16, 16 ], \"texture\": \"#texture\" },\n                \"south\": { \"uv\": [ 0, 2, 16, 16 ], \"texture\": \"#texture\" },\n                \"west\":  { \"uv\": [ 0, 2, 16, 16 ], \"texture\": \"#texture\" },\n                \"east\":  { \"uv\": [ 0, 2, 16, 16 ], \"texture\": \"#texture\" }\n            }\n        }\n    ]\n}\n"
  },
  {
    "path": "src/main/resources/assets/weather2/models/block/sand_height2.json",
    "content": "{   \"parent\": \"block/thin_block\",\n    \"textures\": {\n        \"particle\": \"block/sand\",\n        \"texture\": \"block/sand\"\n    },\n    \"elements\": [\n        {   \"from\": [ 0, 0, 0 ],\n            \"to\": [ 16, 2, 16 ],\n            \"faces\": {\n                \"down\":  { \"uv\": [ 0, 0, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"down\" },\n                \"up\":    { \"uv\": [ 0, 0, 16, 16 ], \"texture\": \"#texture\" },\n                \"north\": { \"uv\": [ 0, 14, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"north\" },\n                \"south\": { \"uv\": [ 0, 14, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"south\" },\n                \"west\":  { \"uv\": [ 0, 14, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"west\" },\n                \"east\":  { \"uv\": [ 0, 14, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"east\" }\n            }\n        }\n    ]\n}\n"
  },
  {
    "path": "src/main/resources/assets/weather2/models/block/sand_height4.json",
    "content": "{\n    \"textures\": {\n        \"particle\": \"block/sand\",\n        \"texture\": \"block/sand\"\n    },\n    \"elements\": [\n        {   \"from\": [ 0, 0, 0 ],\n            \"to\": [ 16, 4, 16 ],\n            \"faces\": {\n                \"down\":  { \"uv\": [ 0, 0, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"down\" },\n                \"up\":    { \"uv\": [ 0, 0, 16, 16 ], \"texture\": \"#texture\" },\n                \"north\": { \"uv\": [ 0, 12, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"north\" },\n                \"south\": { \"uv\": [ 0, 12, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"south\" },\n                \"west\":  { \"uv\": [ 0, 12, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"west\" },\n                \"east\":  { \"uv\": [ 0, 12, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"east\" }\n            }\n        }\n    ]\n}\n"
  },
  {
    "path": "src/main/resources/assets/weather2/models/block/sand_height6.json",
    "content": "{\n    \"textures\": {\n        \"particle\": \"block/sand\",\n        \"texture\": \"block/sand\"\n    },\n    \"elements\": [\n        {   \"from\": [ 0, 0, 0 ],\n            \"to\": [ 16, 6, 16 ],\n            \"faces\": {\n                \"down\":  { \"uv\": [ 0, 0, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"down\" },\n                \"up\":    { \"uv\": [ 0, 0, 16, 16 ], \"texture\": \"#texture\" },\n                \"north\": { \"uv\": [ 0, 10, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"north\" },\n                \"south\": { \"uv\": [ 0, 10, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"south\" },\n                \"west\":  { \"uv\": [ 0, 10, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"west\" },\n                \"east\":  { \"uv\": [ 0, 10, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"east\" }\n            }\n        }\n    ]\n}\n"
  },
  {
    "path": "src/main/resources/assets/weather2/models/block/sand_height8.json",
    "content": "{\n    \"textures\": {\n        \"particle\": \"block/sand\",\n        \"texture\": \"block/sand\"\n    },\n    \"elements\": [\n        {   \"from\": [ 0, 0, 0 ],\n            \"to\": [ 16, 8, 16 ],\n            \"faces\": {\n                \"down\":  { \"uv\": [ 0, 0, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"down\" },\n                \"up\":    { \"uv\": [ 0, 0, 16, 16 ], \"texture\": \"#texture\" },\n                \"north\": { \"uv\": [ 0, 8, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"north\" },\n                \"south\": { \"uv\": [ 0, 8, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"south\" },\n                \"west\":  { \"uv\": [ 0, 8, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"west\" },\n                \"east\":  { \"uv\": [ 0, 8, 16, 16 ], \"texture\": \"#texture\", \"cullface\": \"east\" }\n            }\n        }\n    ]\n}\n"
  },
  {
    "path": "src/main/resources/assets/weather2/models/block/tornado_sensor.json",
    "content": "{\n  \"parent\": \"minecraft:block/cube_all\",\n  \"textures\": {\n    \"all\": \"weather2:blocks/tornado_sensor\"\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/models/block/tornado_siren.json",
    "content": "{\n  \"parent\": \"minecraft:block/cube_all\",\n  \"textures\": {\n    \"all\": \"weather2:blocks/tornado_siren\"\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/models/block/tornado_siren_manual.json",
    "content": "{\n  \"parent\": \"minecraft:block/cube_all\",\n  \"textures\": {\n    \"all\": \"weather2:blocks/tornado_siren_manual\"\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/models/block/weather_deflector.json",
    "content": "{\n  \"parent\": \"minecraft:block/cube_all\",\n  \"textures\": {\n    \"all\": \"weather2:blocks/weather_deflector\"\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/models/block/weather_forecast.json",
    "content": "{\n  \"parent\": \"minecraft:block/cube_all\",\n  \"textures\": {\n    \"all\": \"weather2:blocks/weather_forecast\"\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/models/block/weather_machine.json",
    "content": "{\n  \"parent\": \"minecraft:block/cube_all\",\n  \"textures\": {\n    \"all\": \"weather2:blocks/weather_machine\"\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/models/block/wind_turbine.json",
    "content": "{\n\t\"parent\": \"forge:item/default\",\n\t\"display\": {\n\t\t\"gui\": {\n\t\t\t\"rotation\": [ 30, 225, 0 ],\n\t\t\t\"translation\": [ 0, -3, 0],\n\t\t\t\"scale\":[ 0.425, 0.425, 0.425 ]\n\t\t}\n\t},\n\t\"credit\": \"Made with Blockbench\",\n\t\"texture_size\": [128, 128],\n\t\"textures\": {\n\t\t\"particle\": \"block/stone\",\n\t\t\"2\": \"weather2:blocks/wind_turbine\"\n\t},\n\t\"elements\": [\n\t\t{\n\t\t\t\"from\": [2, 0, 2],\n\t\t\t\"to\": [14, 2, 14],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 0, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [1.5, 1.5, 3, 1.75], \"texture\": \"#2\"},\n\t\t\t\t\"east\": {\"uv\": [0, 1.5, 1.5, 1.75], \"texture\": \"#2\"},\n\t\t\t\t\"south\": {\"uv\": [4.5, 1.5, 6, 1.75], \"texture\": \"#2\"},\n\t\t\t\t\"west\": {\"uv\": [3, 1.5, 4.5, 1.75], \"texture\": \"#2\"},\n\t\t\t\t\"up\": {\"uv\": [3, 1.5, 1.5, 0], \"texture\": \"#2\"},\n\t\t\t\t\"down\": {\"uv\": [4.5, 0, 3, 1.5], \"texture\": \"#2\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [5, 2, 5],\n\t\t\t\"to\": [11, 4, 11],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 0, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [5.25, 1, 6, 1.25], \"texture\": \"#2\"},\n\t\t\t\t\"east\": {\"uv\": [4.5, 1, 5.25, 1.25], \"texture\": \"#2\"},\n\t\t\t\t\"south\": {\"uv\": [6.75, 1, 7.5, 1.25], \"texture\": \"#2\"},\n\t\t\t\t\"west\": {\"uv\": [6, 1, 6.75, 1.25], \"texture\": \"#2\"},\n\t\t\t\t\"up\": {\"uv\": [6, 1, 5.25, 0.25], \"texture\": \"#2\"},\n\t\t\t\t\"down\": {\"uv\": [6.75, 0.25, 6, 1], \"texture\": \"#2\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7, 2, 7],\n\t\t\t\"to\": [9, 31, 9],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 0, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0.25, 4.625, 0.5, 8.25], \"texture\": \"#2\"},\n\t\t\t\t\"east\": {\"uv\": [0, 4.625, 0.25, 8.25], \"texture\": \"#2\"},\n\t\t\t\t\"south\": {\"uv\": [0.75, 4.625, 1, 8.25], \"texture\": \"#2\"},\n\t\t\t\t\"west\": {\"uv\": [0.5, 4.625, 0.75, 8.25], \"texture\": \"#2\"},\n\t\t\t\t\"up\": {\"uv\": [0.5, 4.625, 0.25, 4.375], \"texture\": \"#2\"},\n\t\t\t\t\"down\": {\"uv\": [0.75, 4.375, 0.5, 4.625], \"texture\": \"#2\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [6, 28, 6],\n\t\t\t\"to\": [10, 30, 10],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 0, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0.5, 3, 1, 3.25], \"texture\": \"#2\"},\n\t\t\t\t\"east\": {\"uv\": [0, 3, 0.5, 3.25], \"texture\": \"#2\"},\n\t\t\t\t\"south\": {\"uv\": [1.5, 3, 2, 3.25], \"texture\": \"#2\"},\n\t\t\t\t\"west\": {\"uv\": [1, 3, 1.5, 3.25], \"texture\": \"#2\"},\n\t\t\t\t\"up\": {\"uv\": [1, 3, 0.5, 2.5], \"texture\": \"#2\"},\n\t\t\t\t\"down\": {\"uv\": [1.5, 2.5, 1, 3], \"texture\": \"#2\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [-1, 28.5, 7.5],\n\t\t\t\"to\": [17, 29.5, 8.5],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 0, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0.125, 4.25, 2.375, 4.375], \"texture\": \"#2\"},\n\t\t\t\t\"east\": {\"uv\": [0, 4.25, 0.125, 4.375], \"texture\": \"#2\"},\n\t\t\t\t\"south\": {\"uv\": [2.5, 4.25, 4.75, 4.375], \"texture\": \"#2\"},\n\t\t\t\t\"west\": {\"uv\": [2.375, 4.25, 2.5, 4.375], \"texture\": \"#2\"},\n\t\t\t\t\"up\": {\"uv\": [2.375, 4.25, 0.125, 4.125], \"texture\": \"#2\"},\n\t\t\t\t\"down\": {\"uv\": [4.625, 4.125, 2.375, 4.25], \"texture\": \"#2\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"upper\",\n\t\t\t\"from\": [7.5, 28.5, -1],\n\t\t\t\"to\": [8.5, 29.5, 17],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 0, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [2.25, 4, 2.375, 4.125], \"texture\": \"#2\"},\n\t\t\t\t\"east\": {\"uv\": [0, 4, 2.25, 4.125], \"texture\": \"#2\"},\n\t\t\t\t\"south\": {\"uv\": [4.625, 4, 4.75, 4.125], \"texture\": \"#2\"},\n\t\t\t\t\"west\": {\"uv\": [2.375, 4, 4.625, 4.125], \"texture\": \"#2\"},\n\t\t\t\t\"up\": {\"uv\": [2.375, 4, 2.25, 1.75], \"texture\": \"#2\"},\n\t\t\t\t\"down\": {\"uv\": [2.5, 1.75, 2.375, 4], \"texture\": \"#2\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"upper\",\n\t\t\t\"from\": [7.5, 12.5, 0],\n\t\t\t\"to\": [8.5, 13.5, 16],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 0, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [2.25, 4, 2.375, 4.125], \"texture\": \"#2\"},\n\t\t\t\t\"east\": {\"uv\": [0.25, 4, 2.25, 4.125], \"texture\": \"#2\"},\n\t\t\t\t\"south\": {\"uv\": [4.375, 4, 4.5, 4.125], \"texture\": \"#2\"},\n\t\t\t\t\"west\": {\"uv\": [2.375, 4, 4.375, 4.125], \"texture\": \"#2\"},\n\t\t\t\t\"up\": {\"uv\": [2.375, 4, 2.25, 2], \"texture\": \"#2\"},\n\t\t\t\t\"down\": {\"uv\": [2.5, 2, 2.375, 4], \"texture\": \"#2\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"lower\",\n\t\t\t\"from\": [0, 12.5, 7.5],\n\t\t\t\"to\": [16, 13.5, 8.5],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 0, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [4.625, 0.125, 6.625, 0.25], \"texture\": \"#2\"},\n\t\t\t\t\"east\": {\"uv\": [4.5, 0.125, 4.625, 0.25], \"texture\": \"#2\"},\n\t\t\t\t\"south\": {\"uv\": [6.75, 0.125, 8.75, 0.25], \"texture\": \"#2\"},\n\t\t\t\t\"west\": {\"uv\": [6.625, 0.125, 6.75, 0.25], \"texture\": \"#2\"},\n\t\t\t\t\"up\": {\"uv\": [6.625, 0.125, 4.625, 0], \"texture\": \"#2\"},\n\t\t\t\t\"down\": {\"uv\": [8.625, 0, 6.625, 0.125], \"texture\": \"#2\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"fin\",\n\t\t\t\"from\": [-2, 10, 6],\n\t\t\t\"to\": [0, 32, 10],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"x\", \"origin\": [0, 13, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [4.75, 4.375, 5, 7.125], \"texture\": \"#2\"},\n\t\t\t\t\"east\": {\"uv\": [4.25, 4.375, 4.75, 7.125], \"texture\": \"#2\"},\n\t\t\t\t\"south\": {\"uv\": [5.5, 4.375, 5.75, 7.125], \"texture\": \"#2\"},\n\t\t\t\t\"west\": {\"uv\": [5, 4.375, 5.5, 7.125], \"texture\": \"#2\"},\n\t\t\t\t\"up\": {\"uv\": [5, 4.375, 4.75, 3.875], \"texture\": \"#2\"},\n\t\t\t\t\"down\": {\"uv\": [5.25, 3.875, 5, 4.375], \"texture\": \"#2\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"fin\",\n\t\t\t\"from\": [16, 10, 6],\n\t\t\t\"to\": [18, 32, 10],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"x\", \"origin\": [16, 13, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [4.75, 4.375, 5, 7.125], \"texture\": \"#2\"},\n\t\t\t\t\"east\": {\"uv\": [4.25, 4.375, 4.75, 7.125], \"texture\": \"#2\"},\n\t\t\t\t\"south\": {\"uv\": [5.5, 4.375, 5.75, 7.125], \"texture\": \"#2\"},\n\t\t\t\t\"west\": {\"uv\": [5, 4.375, 5.5, 7.125], \"texture\": \"#2\"},\n\t\t\t\t\"up\": {\"uv\": [5, 4.375, 4.75, 3.875], \"texture\": \"#2\"},\n\t\t\t\t\"down\": {\"uv\": [5.25, 3.875, 5, 4.375], \"texture\": \"#2\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"fin\",\n\t\t\t\"from\": [6, 10, 16],\n\t\t\t\"to\": [10, 32, 18],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"x\", \"origin\": [8, 13, 16]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [1.25, 4.625, 1.75, 7.375], \"texture\": \"#2\"},\n\t\t\t\t\"east\": {\"uv\": [1, 4.625, 1.25, 7.375], \"texture\": \"#2\"},\n\t\t\t\t\"south\": {\"uv\": [2, 4.625, 2.5, 7.375], \"texture\": \"#2\"},\n\t\t\t\t\"west\": {\"uv\": [1.75, 4.625, 2, 7.375], \"texture\": \"#2\"},\n\t\t\t\t\"up\": {\"uv\": [1.75, 4.625, 1.25, 4.375], \"texture\": \"#2\"},\n\t\t\t\t\"down\": {\"uv\": [2.25, 4.375, 1.75, 4.625], \"texture\": \"#2\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"fin\",\n\t\t\t\"from\": [6, 10, -2],\n\t\t\t\"to\": [10, 32, 0],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"x\", \"origin\": [8, 13, 0]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [1.25, 4.625, 1.75, 7.375], \"texture\": \"#2\"},\n\t\t\t\t\"east\": {\"uv\": [1, 4.625, 1.25, 7.375], \"texture\": \"#2\"},\n\t\t\t\t\"south\": {\"uv\": [2, 4.625, 2.5, 7.375], \"texture\": \"#2\"},\n\t\t\t\t\"west\": {\"uv\": [1.75, 4.625, 2, 7.375], \"texture\": \"#2\"},\n\t\t\t\t\"up\": {\"uv\": [1.75, 4.625, 1.25, 4.375], \"texture\": \"#2\"},\n\t\t\t\t\"down\": {\"uv\": [2.25, 4.375, 1.75, 4.625], \"texture\": \"#2\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"name\": \"upper\",\n\t\t\t\"from\": [7.5, 12.5, 0],\n\t\t\t\"to\": [8.5, 13.5, 16],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 0, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [2.25, 4, 2.375, 4.125], \"texture\": \"#2\"},\n\t\t\t\t\"east\": {\"uv\": [0.25, 4, 2.25, 4.125], \"texture\": \"#2\"},\n\t\t\t\t\"south\": {\"uv\": [4.375, 4, 4.5, 4.125], \"texture\": \"#2\"},\n\t\t\t\t\"west\": {\"uv\": [2.375, 4, 4.375, 4.125], \"texture\": \"#2\"},\n\t\t\t\t\"up\": {\"uv\": [2.375, 4, 2.25, 2], \"texture\": \"#2\"},\n\t\t\t\t\"down\": {\"uv\": [2.5, 2, 2.375, 4], \"texture\": \"#2\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [6, 12, 6],\n\t\t\t\"to\": [10, 14, 10],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 0, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0.5, 2.25, 1, 2.5], \"texture\": \"#2\"},\n\t\t\t\t\"east\": {\"uv\": [0, 2.25, 0.5, 2.5], \"texture\": \"#2\"},\n\t\t\t\t\"south\": {\"uv\": [1.5, 2.25, 2, 2.5], \"texture\": \"#2\"},\n\t\t\t\t\"west\": {\"uv\": [1, 2.25, 1.5, 2.5], \"texture\": \"#2\"},\n\t\t\t\t\"up\": {\"uv\": [1, 2.25, 0.5, 1.75], \"texture\": \"#2\"},\n\t\t\t\t\"down\": {\"uv\": [1.5, 1.75, 1, 2.25], \"texture\": \"#2\"}\n\t\t\t}\n\t\t}\n\t],\n\t\"groups\": [\n\t\t{\n\t\t\t\"name\": \"root\",\n\t\t\t\"origin\": [8, 0, 8],\n\t\t\t\"color\": 0,\n\t\t\t\"children\": [\n\t\t\t\t0,\n\t\t\t\t1,\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"shaft\",\n\t\t\t\t\t\"origin\": [8, 0, 8],\n\t\t\t\t\t\"color\": 0,\n\t\t\t\t\t\"children\": [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/models/block/wind_vane.json",
    "content": "{\n\t\"parent\": \"forge:item/default\",\n\t\"display\": {\n\t\t\"gui\": {\n\t\t\t\"rotation\": [ 30, 225, 0 ],\n\t\t\t\"translation\": [ 0, 0, 0],\n\t\t\t\"scale\":[ 1, 1, 1 ]\n\t\t}\n\t},\n\t\"credit\": \"Made with Blockbench\",\n\t\"texture_size\": [32, 32],\n\t\"textures\": {\n\t\t\"particle\": \"block/stone\",\n\t\t\"0\": \"weather2:blocks/wind_vane\"\n\t},\n\t\"elements\": [\n\t\t{\n\t\t\t\"from\": [7.75, 0, 7.75],\n\t\t\t\"to\": [8.25, 7, 8.25],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 0, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [4.5, 3, 4.5, 6.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.5, 3, 4.5, 6.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [4.5, 3, 4.5, 6.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [4.5, 3, 4.5, 6.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [4.5, 3, 4.5, 3], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [4.5, 3, 4.5, 3], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.25, 0, 7.25],\n\t\t\t\"to\": [8.75, 0.5, 8.75],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 7.5, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0.5, 0.5, 1, 0.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 0.5, 0.5, 0.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [1.5, 0.5, 2, 0.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [1, 0.5, 1.5, 0.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [1, 0.5, 0.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [1.5, 0, 1, 0.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.75, 8, 7.75],\n\t\t\t\"to\": [8.25, 11, 8.25],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 7.5, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8.5, 7, 8.5, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [8.5, 7, 8.5, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [8.5, 7, 8.5, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [8.5, 7, 8.5, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.5, 7, 8.5, 7], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8.5, 7, 8.5, 7], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.25, 6.75, 7.25],\n\t\t\t\"to\": [8.75, 8.25, 8.75],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 7.5, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0.5, 0.5, 1, 1], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 0.5, 0.5, 1], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [1.5, 0.5, 2, 1], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [1, 0.5, 1.5, 1], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [1, 0.5, 0.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [1.5, 0, 1, 0.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.25, 6.75, 7.25],\n\t\t\t\"to\": [8.75, 8.25, 8.75],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"x\", \"origin\": [8, 7.5, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0.5, 0.5, 1, 1], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 0.5, 0.5, 1], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [1.5, 0.5, 2, 1], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [1, 0.5, 1.5, 1], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [1, 0.5, 0.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [1.5, 0, 1, 0.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.25, 6.75, 7.25],\n\t\t\t\"to\": [8.75, 8.25, 8.75],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"y\", \"origin\": [8, 7.5, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0.5, 0.5, 1, 1], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 0.5, 0.5, 1], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [1.5, 0.5, 2, 1], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [1, 0.5, 1.5, 1], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [1, 0.5, 0.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [1.5, 0, 1, 0.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.25, 6.75, 7.25],\n\t\t\t\"to\": [8.75, 8.25, 8.75],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"z\", \"origin\": [8, 7.5, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0.5, 0.5, 1, 1], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 0.5, 0.5, 1], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [1.5, 0.5, 2, 1], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [1, 0.5, 1.5, 1], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [1, 0.5, 0.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [1.5, 0, 1, 0.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.75, 7.25, 4],\n\t\t\t\"to\": [8.25, 7.75, 8],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [16, -4, 0]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [4, 8.5, 4, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [2, 8.5, 4, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [6, 8.5, 6, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [4, 8.5, 6, 8.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [4, 8.5, 4, 6.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [4, 6.5, 4, 8.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [8, 6.5, 2],\n\t\t\t\"to\": [8.05, 8.5, 4],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 7.5, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [1, 15, 1, 16], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 15, 1, 16], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [2, 15, 2, 16], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [1, 15, 2, 16], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [1, 15, 1, 14], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [1, 14, 1, 15], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.25, 10.75, 7.25],\n\t\t\t\"to\": [8.75, 12.25, 8.75],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"z\", \"origin\": [8, 11.5, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0.5, 0.5, 1, 1], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 0.5, 0.5, 1], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [1.5, 0.5, 2, 1], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [1, 0.5, 1.5, 1], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [1, 0.5, 0.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [1.5, 0, 1, 0.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.25, 10.75, 7.25],\n\t\t\t\"to\": [8.75, 12.25, 8.75],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.5, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0.5, 0.5, 1, 1], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 0.5, 0.5, 1], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [1.5, 0.5, 2, 1], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [1, 0.5, 1.5, 1], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [1, 0.5, 0.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [1.5, 0, 1, 0.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.25, 10.75, 7.25],\n\t\t\t\"to\": [8.75, 12.25, 8.75],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"x\", \"origin\": [8, 11.5, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0.5, 0.5, 1, 1], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 0.5, 0.5, 1], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [1.5, 0.5, 2, 1], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [1, 0.5, 1.5, 1], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [1, 0.5, 0.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [1.5, 0, 1, 0.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.25, 10.75, 7.25],\n\t\t\t\"to\": [8.75, 12.25, 8.75],\n\t\t\t\"rotation\": {\"angle\": 45, \"axis\": \"y\", \"origin\": [8, 11.5, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [0.5, 0.5, 1, 1], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [0, 0.5, 0.5, 1], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [1.5, 0.5, 2, 1], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [1, 0.5, 1.5, 1], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [1, 0.5, 0.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [1.5, 0, 1, 0.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.75, 11.25, 4],\n\t\t\t\"to\": [8.25, 11.75, 8],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.5, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [8, 2, 8, 2], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [6, 2, 8, 2], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [10, 2, 10, 2], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [8, 2, 10, 2], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8, 2, 8, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [8, 0, 8, 2], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [8, 10.5, 2],\n\t\t\t\"to\": [8.05, 12.5, 4],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.5, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [11, 15, 11, 16], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [10, 15, 11, 16], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [12, 15, 12, 16], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [11, 15, 12, 16], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [11, 15, 11, 14], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [11, 14, 11, 15], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.75, 11.25, 8],\n\t\t\t\"to\": [8.25, 11.75, 12],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.5, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [4, 6.5, 4, 6.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [2, 6.5, 4, 6.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [6, 6.5, 6, 6.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [4, 6.5, 6, 6.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [4, 6.5, 4, 4.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [4, 4.5, 4, 6.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [8, 10.5, 12],\n\t\t\t\"to\": [8.05, 12.5, 14],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 11.5, 13]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [13.5, 15, 13.5, 16], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [14.5, 15, 13.5, 16], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [14.5, 15, 14.5, 16], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [13.5, 15, 12.5, 16], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [13.5, 15, 13.5, 14], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [13.5, 14, 13.5, 15], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [8, 12.5, 5.5],\n\t\t\t\"to\": [8.05, 16.5, 9.5],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 13.5, 12]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [13.5, 2, 13.5, 4], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [11.5, 2, 13.5, 4], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [15.5, 2, 15.5, 4], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [13.5, 2, 15.5, 4], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [13.5, 2, 13.5, 0], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [13.5, 0, 13.5, 2], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [8, 7.25, 7.75],\n\t\t\t\"to\": [12, 7.75, 8.25],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 7, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [1, 5.5, 3, 5.5], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [1, 5.5, 1, 5.5], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [3, 5.5, 5, 5.5], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [3, 5.5, 3, 5.5], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [3, 5.5, 1, 5.5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [5, 5.5, 3, 5.5], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [12, 6.5, 8],\n\t\t\t\"to\": [14, 8.5, 8.05],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [13, 7.5, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [3.5, 15, 2.5, 16], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [3.5, 15, 3.5, 16], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [4.5, 15, 3.5, 16], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [2.5, 15, 2.5, 16], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [2.5, 15, 3.5, 15], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [3.5, 15, 4.5, 15], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [7.75, 7.25, 8],\n\t\t\t\"to\": [8.25, 7.75, 12],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 7, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [6.5, 7, 6.5, 7], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [4.5, 7, 6.5, 7], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [8.5, 7, 8.5, 7], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [6.5, 7, 8.5, 7], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [6.5, 7, 6.5, 5], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [6.5, 5, 6.5, 7], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [8, 6.5, 12],\n\t\t\t\"to\": [8.05, 8.5, 14],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 7.5, 13]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [6, 15, 6, 16], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [7, 15, 6, 16], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [7, 15, 7, 16], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [6, 15, 5, 16], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [6, 15, 6, 14], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [6, 14, 6, 15], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [4, 7.25, 7.75],\n\t\t\t\"to\": [8, 7.75, 8.25],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 7, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [5, 3, 7, 3], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [5, 3, 5, 3], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [7, 3, 9, 3], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [7, 3, 7, 3], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [7, 3, 5, 3], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9, 3, 7, 3], \"texture\": \"#0\"}\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"from\": [2, 6.5, 7.95],\n\t\t\t\"to\": [4, 8.5, 8],\n\t\t\t\"rotation\": {\"angle\": 0, \"axis\": \"y\", \"origin\": [8, 7, 8]},\n\t\t\t\"faces\": {\n\t\t\t\t\"north\": {\"uv\": [7.5, 15, 8.5, 16], \"texture\": \"#0\"},\n\t\t\t\t\"east\": {\"uv\": [7.5, 15, 7.5, 16], \"texture\": \"#0\"},\n\t\t\t\t\"south\": {\"uv\": [8.5, 15, 9.5, 16], \"texture\": \"#0\"},\n\t\t\t\t\"west\": {\"uv\": [8.5, 15, 8.5, 16], \"texture\": \"#0\"},\n\t\t\t\t\"up\": {\"uv\": [8.5, 15, 7.5, 15], \"texture\": \"#0\"},\n\t\t\t\t\"down\": {\"uv\": [9.5, 15, 8.5, 15], \"texture\": \"#0\"}\n\t\t\t}\n\t\t}\n\t],\n\t\"groups\": [\n\t\t{\n\t\t\t\"name\": \"root\",\n\t\t\t\"origin\": [8, 0, 8],\n\t\t\t\"color\": 0,\n\t\t\t\"children\": [\n\t\t\t\t{\n\t\t\t\t\t\"name\": \"base\",\n\t\t\t\t\t\"origin\": [8, 0, 8],\n\t\t\t\t\t\"color\": 0,\n\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t1,\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\"name\": \"middle\",\n\t\t\t\t\t\t\t\"origin\": [8, 11.5, 8],\n\t\t\t\t\t\t\t\"color\": 0,\n\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t2,\n\t\t\t\t\t\t\t\t3,\n\t\t\t\t\t\t\t\t4,\n\t\t\t\t\t\t\t\t5,\n\t\t\t\t\t\t\t\t6,\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"name\": \"arm1\",\n\t\t\t\t\t\t\t\t\t\"origin\": [8, 7.5, 8],\n\t\t\t\t\t\t\t\t\t\"color\": 0,\n\t\t\t\t\t\t\t\t\t\"children\": [7, 8]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"name\": \"top\",\n\t\t\t\t\t\t\t\t\t\"origin\": [8, 11.5, 8],\n\t\t\t\t\t\t\t\t\t\"color\": 0,\n\t\t\t\t\t\t\t\t\t\"children\": [\n\t\t\t\t\t\t\t\t\t\t9,\n\t\t\t\t\t\t\t\t\t\t10,\n\t\t\t\t\t\t\t\t\t\t11,\n\t\t\t\t\t\t\t\t\t\t12,\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"name\": \"toparm1\",\n\t\t\t\t\t\t\t\t\t\t\t\"origin\": [8, 11.5, 8],\n\t\t\t\t\t\t\t\t\t\t\t\"color\": 0,\n\t\t\t\t\t\t\t\t\t\t\t\"children\": [13, 14]\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\t\"name\": \"toparm2\",\n\t\t\t\t\t\t\t\t\t\t\t\"origin\": [8, 11.5, 8],\n\t\t\t\t\t\t\t\t\t\t\t\"color\": 0,\n\t\t\t\t\t\t\t\t\t\t\t\"children\": [15, 16]\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t\t17\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"name\": \"arm2\",\n\t\t\t\t\t\t\t\t\t\"origin\": [8, 7.5, 8],\n\t\t\t\t\t\t\t\t\t\"color\": 0,\n\t\t\t\t\t\t\t\t\t\"children\": [18, 19]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"name\": \"arm3\",\n\t\t\t\t\t\t\t\t\t\"origin\": [8, 7.5, 8],\n\t\t\t\t\t\t\t\t\t\"color\": 0,\n\t\t\t\t\t\t\t\t\t\"children\": [20, 21]\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\"name\": \"arm4\",\n\t\t\t\t\t\t\t\t\t\"origin\": [8, 7.5, 8],\n\t\t\t\t\t\t\t\t\t\"color\": 0,\n\t\t\t\t\t\t\t\t\t\"children\": [22, 23]\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t]\n\t\t\t\t\t\t}\n\t\t\t\t\t]\n\t\t\t\t}\n\t\t\t]\n\t\t}\n\t]\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/models/item/anemometer.json",
    "content": "{\n  \"parent\": \"weather2:block/anemometer\",\n  \"display\": {\n    \"thirdperson\": {\n      \"rotation\": [\n        10,\n        -45,\n        170\n      ],\n      \"translation\": [\n        0,\n        1.5,\n        -2.75\n      ],\n      \"scale\": [\n        0.375,\n        0.375,\n        0.375\n      ]\n    }\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/models/item/pocket_sand.json",
    "content": "{\n  \"parent\": \"item/generated\",\n  \"textures\": {\n    \"layer0\": \"weather2:items/pocket_sand\"\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/models/item/sand_layer.json",
    "content": "{\n  \"parent\": \"item/generated\",\n  \"textures\": {\n    \"layer0\": \"weather2:items/sand_layer\"\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/models/item/sand_layer_placeable.json",
    "content": "{\n\t\"parent\": \"item/handheld\",\n\t\"textures\": {\n\t\t\"layer0\": \"weather2:items/sand_layer_placeable\"\n\t}\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/models/item/tornado_sensor.json",
    "content": "{\n  \"parent\": \"weather2:block/tornado_sensor\",\n  \"display\": {\n    \"thirdperson\": {\n      \"rotation\": [\n        10,\n        -45,\n        170\n      ],\n      \"translation\": [\n        0,\n        1.5,\n        -2.75\n      ],\n      \"scale\": [\n        0.375,\n        0.375,\n        0.375\n      ]\n    }\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/models/item/tornado_siren.json",
    "content": "{\n  \"parent\": \"weather2:block/tornado_siren\",\n  \"display\": {\n    \"thirdperson\": {\n      \"rotation\": [\n        10,\n        -45,\n        170\n      ],\n      \"translation\": [\n        0,\n        1.5,\n        -2.75\n      ],\n      \"scale\": [\n        0.375,\n        0.375,\n        0.375\n      ]\n    }\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/models/item/tornado_siren_manual.json",
    "content": "{\n  \"parent\": \"weather2:block/tornado_siren_manual\",\n  \"display\": {\n    \"thirdperson\": {\n      \"rotation\": [\n        10,\n        -45,\n        170\n      ],\n      \"translation\": [\n        0,\n        1.5,\n        -2.75\n      ],\n      \"scale\": [\n        0.375,\n        0.375,\n        0.375\n      ]\n    }\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/models/item/weather_deflector.json",
    "content": "{\n  \"parent\": \"weather2:block/weather_deflector\",\n  \"display\": {\n    \"thirdperson\": {\n      \"rotation\": [\n        10,\n        -45,\n        170\n      ],\n      \"translation\": [\n        0,\n        1.5,\n        -2.75\n      ],\n      \"scale\": [\n        0.375,\n        0.375,\n        0.375\n      ]\n    }\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/models/item/weather_forecast.json",
    "content": "{\n  \"parent\": \"weather2:block/weather_forecast\",\n  \"display\": {\n    \"thirdperson\": {\n      \"rotation\": [\n        10,\n        -45,\n        170\n      ],\n      \"translation\": [\n        0,\n        1.5,\n        -2.75\n      ],\n      \"scale\": [\n        0.375,\n        0.375,\n        0.375\n      ]\n    }\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/models/item/weather_item.json",
    "content": "{\n\t\"parent\": \"item/handheld\",\n\t\"textures\": {\n\t\t\"layer0\": \"weather2:items/weather_item\"\n\t}\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/models/item/weather_machine.json",
    "content": "{\n  \"parent\": \"weather2:block/weather_machine\",\n  \"display\": {\n    \"thirdperson\": {\n      \"rotation\": [\n        10,\n        -45,\n        170\n      ],\n      \"translation\": [\n        0,\n        1.5,\n        -2.75\n      ],\n      \"scale\": [\n        0.375,\n        0.375,\n        0.375\n      ]\n    }\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/models/item/wind_turbine.json",
    "content": "{\n  \"parent\": \"weather2:block/wind_turbine\",\n  \"display\": {\n    \"thirdperson\": {\n      \"rotation\": [\n        10,\n        -45,\n        170\n      ],\n      \"translation\": [\n        0,\n        1.5,\n        -2.75\n      ],\n      \"scale\": [\n        0.375,\n        0.375,\n        0.375\n      ]\n    }\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/models/item/wind_vane.json",
    "content": "{\n  \"parent\": \"weather2:block/wind_vane\",\n  \"display\": {\n    \"thirdperson\": {\n      \"rotation\": [\n        10,\n        -45,\n        170\n      ],\n      \"translation\": [\n        0,\n        1.5,\n        -2.75\n      ],\n      \"scale\": [\n        0.375,\n        0.375,\n        0.375\n      ]\n    }\n  }\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/particles/acidrain_splash.json",
    "content": "{\n  \"textures\": [\n    \"weather2:splash_0\",\n    \"weather2:splash_1\",\n    \"weather2:splash_2\",\n    \"weather2:splash_3\"\n  ]\n}"
  },
  {
    "path": "src/main/resources/assets/weather2/shaders/core/rendertype_clouds.fsh",
    "content": "#version 150\n\n#moj_import <fog.glsl>\n#moj_import <weather2:classicnoise3d.glsl>\n\nuniform sampler2D Sampler0;\n\nuniform vec4 ColorModulator;\nuniform float FogStart;\nuniform float FogEnd;\nuniform vec4 FogColor;\n\nin vec2 texCoord0;\nin float vertexDistance;\nin vec4 vertexColor;\nin vec4 normal;\n\nout vec4 fragColor;\n\nvoid main() {\n    vec4 color = texture(Sampler0, texCoord0) * vertexColor * ColorModulator;\n    //vec4 color = vertexColor;\n    if (color.a < 0.1) {\n        discard;\n    }\n    //if (color.a == normal.x) {\n        //discard;\n    //}\n    fragColor = linear_fog(color, vertexDistance, FogStart, FogEnd, FogColor);\n    //fragColor = linear_fog(color, vertexDistance, 200, 1200, FogColor);\n    //fragColor = linear_fog(color, vertexDistance, 0, 150, FogColor);\n    //fragColor = color;\n}\n"
  },
  {
    "path": "src/main/resources/assets/weather2/shaders/core/rendertype_clouds.json",
    "content": "{\n    \"blend\": {\n        \"func\": \"add\",\n        \"srcrgb\": \"srcalpha\",\n        \"dstrgb\": \"1-srcalpha\"\n    },\n    \"vertex\": \"weather2:rendertype_clouds\",\n    \"fragment\": \"weather2:rendertype_clouds\",\n    \"attributes\": [\n        \"Position\",\n        \"UV0\",\n        \"Normal\",\n        \"ModelMatrix\",\n        \"Brightness\",\n        \"Color\"\n    ],\n    \"samplers\": [\n        { \"name\": \"Sampler0\" }\n    ],\n    \"uniforms\": [\n        { \"name\": \"ModelViewMat\", \"type\": \"matrix4x4\", \"count\": 16, \"values\": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] },\n        { \"name\": \"ProjMat\", \"type\": \"matrix4x4\", \"count\": 16, \"values\": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] },\n        { \"name\": \"ColorModulator\", \"type\": \"float\", \"count\": 4, \"values\": [ 1.0, 1.0, 1.0, 1.0 ] },\n        { \"name\": \"Light0_Direction\", \"type\": \"float\", \"count\": 3, \"values\": [0.0, 0.0, 0.0] },\n        { \"name\": \"Light1_Direction\", \"type\": \"float\", \"count\": 3, \"values\": [0.0, 0.0, 0.0] },\n        { \"name\": \"FogStart\", \"type\": \"float\", \"count\": 1, \"values\": [ 0.0 ] },\n        { \"name\": \"FogEnd\", \"type\": \"float\", \"count\": 1, \"values\": [ 1.0 ] },\n        { \"name\": \"FogColor\", \"type\": \"float\", \"count\": 4, \"values\": [ 0.0, 0.0, 0.0, 0.0 ] },\n        { \"name\": \"CustomTime\", \"type\": \"float\", \"count\": 1, \"values\": [ 0.0 ] }\n    ]\n}\n"
  },
  {
    "path": "src/main/resources/assets/weather2/shaders/core/rendertype_clouds.vsh",
    "content": "#version 150\n\n#moj_import <light.glsl>\n\nin vec3 Position;\nin vec2 UV0;\nin vec3 Normal;\n\nin mat4 ModelMatrix;\nin float Brightness;\nin vec4 Color;\n\nuniform mat4 ModelViewMat;\nuniform mat4 ProjMat;\n\nout vec2 texCoord0;\nout float vertexDistance;\nout vec4 vertexColor;\nout vec4 normal;\n\nvoid main() {\n    gl_Position = ProjMat * ModelViewMat * ModelMatrix * vec4(Position * Brightness, 1.0);\n\n    texCoord0 = UV0;\n    vertexDistance = length((ModelViewMat * ModelMatrix * vec4(Position, 1.0)).xyz);\n    //vertexColor = Color;\n    vec3 Light0_Direction = vec3(0.16145112, 0.80725557, -0.5650789);\n    vec3 Light1_Direction = vec3(-0.16145112, 0.80725557, 0.5650789);\n    //normal = ProjMat * ModelMatrix * vec4(Normal, 0.0);\n    normal = ProjMat * ModelMatrix * vec4(Normal, 0.0);\n    //normal = vec4(Normal, 0.0);\n    vertexColor = minecraft_mix_light(Light0_Direction, Light1_Direction, normal.xyz, Color);\n}\n\n\n"
  },
  {
    "path": "src/main/resources/assets/weather2/shaders/core/rendertype_clouds_orig.vsh",
    "content": "#version 150\n\n#moj_import <light.glsl>\n\nin vec3 Position;\nin vec2 UV0;\nin vec4 Color;\nin vec3 Normal;\n\nuniform mat4 ModelViewMat;\nuniform mat4 ProjMat;\n\nuniform vec3 Light0_Direction;\nuniform vec3 Light1_Direction;\n\nuniform float CustomTime;\n\nout vec2 texCoord0;\nout float vertexDistance;\nout vec4 vertexColor;\nout vec4 normal;\n\nmat4 rotationX( in float angle ) {\n    return mat4(\t1.0,\t\t0,\t\t\t0,\t\t\t0,\n    0, \tcos(angle),\t-sin(angle),\t\t0,\n    0, \tsin(angle),\t cos(angle),\t\t0,\n    0, \t\t\t0,\t\t\t  0, \t\t1);\n}\n\nmat4 rotationY( in float angle ) {\n    return mat4(\tcos(angle),\t\t0,\t\tsin(angle),\t0,\n    0,\t\t1.0,\t\t\t 0,\t0,\n    -sin(angle),\t0,\t\tcos(angle),\t0,\n    0, \t\t0,\t\t\t\t0,\t1);\n}\n\nmat4 rotationZ( in float angle ) {\n    return mat4(\tcos(angle),\t\t-sin(angle),\t0,\t0,\n    sin(angle),\t\tcos(angle),\t\t0,\t0,\n    0,\t\t\t\t0,\t\t1,\t0,\n    0,\t\t\t\t0,\t\t0,\t1);\n}\n\n//\n// GLSL textureless classic 3D noise \"cnoise\",\n// with an RSL-style periodic variant \"pnoise\".\n// Author:  Stefan Gustavson (stefan.gustavson@liu.se)\n// Version: 2011-10-11\n//\n// Many thanks to Ian McEwan of Ashima Arts for the\n// ideas for permutation and gradient selection.\n//\n// Copyright (c) 2011 Stefan Gustavson. All rights reserved.\n// Distributed under the MIT license. See LICENSE file.\n// https://github.com/stegu/webgl-noise\n//\n\nvec3 mod289(vec3 x)\n{\n    return x - floor(x * (1.0 / 289.0)) * 289.0;\n}\n\nvec4 mod289(vec4 x)\n{\n    return x - floor(x * (1.0 / 289.0)) * 289.0;\n}\n\nvec4 permute(vec4 x)\n{\n    return mod289(((x*34.0)+10.0)*x);\n}\n\nvec4 taylorInvSqrt(vec4 r)\n{\n    return 1.79284291400159 - 0.85373472095314 * r;\n}\n\nvec3 fade(vec3 t) {\n    return t*t*t*(t*(t*6.0-15.0)+10.0);\n}\n\n// Classic Perlin noise\nfloat cnoise(vec3 P)\n{\n    vec3 Pi0 = floor(P); // Integer part for indexing\n    vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1\n    Pi0 = mod289(Pi0);\n    Pi1 = mod289(Pi1);\n    vec3 Pf0 = fract(P); // Fractional part for interpolation\n    vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0\n    vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);\n    vec4 iy = vec4(Pi0.yy, Pi1.yy);\n    vec4 iz0 = Pi0.zzzz;\n    vec4 iz1 = Pi1.zzzz;\n\n    vec4 ixy = permute(permute(ix) + iy);\n    vec4 ixy0 = permute(ixy + iz0);\n    vec4 ixy1 = permute(ixy + iz1);\n\n    vec4 gx0 = ixy0 * (1.0 / 7.0);\n    vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5;\n    gx0 = fract(gx0);\n    vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);\n    vec4 sz0 = step(gz0, vec4(0.0));\n    gx0 -= sz0 * (step(0.0, gx0) - 0.5);\n    gy0 -= sz0 * (step(0.0, gy0) - 0.5);\n\n    vec4 gx1 = ixy1 * (1.0 / 7.0);\n    vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5;\n    gx1 = fract(gx1);\n    vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);\n    vec4 sz1 = step(gz1, vec4(0.0));\n    gx1 -= sz1 * (step(0.0, gx1) - 0.5);\n    gy1 -= sz1 * (step(0.0, gy1) - 0.5);\n\n    vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);\n    vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);\n    vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);\n    vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);\n    vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);\n    vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);\n    vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);\n    vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);\n\n    vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110)));\n    g000 *= norm0.x;\n    g010 *= norm0.y;\n    g100 *= norm0.z;\n    g110 *= norm0.w;\n    vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111)));\n    g001 *= norm1.x;\n    g011 *= norm1.y;\n    g101 *= norm1.z;\n    g111 *= norm1.w;\n\n    float n000 = dot(g000, Pf0);\n    float n100 = dot(g100, vec3(Pf1.x, Pf0.yz));\n    float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z));\n    float n110 = dot(g110, vec3(Pf1.xy, Pf0.z));\n    float n001 = dot(g001, vec3(Pf0.xy, Pf1.z));\n    float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z));\n    float n011 = dot(g011, vec3(Pf0.x, Pf1.yz));\n    float n111 = dot(g111, Pf1);\n\n    vec3 fade_xyz = fade(Pf0);\n    vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z);\n    vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);\n    float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x);\n    return 2.2 * n_xyz;\n}\n\n// Classic Perlin noise, periodic variant\nfloat pnoise(vec3 P, vec3 rep)\n{\n    vec3 Pi0 = mod(floor(P), rep); // Integer part, modulo period\n    vec3 Pi1 = mod(Pi0 + vec3(1.0), rep); // Integer part + 1, mod period\n    Pi0 = mod289(Pi0);\n    Pi1 = mod289(Pi1);\n    vec3 Pf0 = fract(P); // Fractional part for interpolation\n    vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0\n    vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);\n    vec4 iy = vec4(Pi0.yy, Pi1.yy);\n    vec4 iz0 = Pi0.zzzz;\n    vec4 iz1 = Pi1.zzzz;\n\n    vec4 ixy = permute(permute(ix) + iy);\n    vec4 ixy0 = permute(ixy + iz0);\n    vec4 ixy1 = permute(ixy + iz1);\n\n    vec4 gx0 = ixy0 * (1.0 / 7.0);\n    vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5;\n    gx0 = fract(gx0);\n    vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);\n    vec4 sz0 = step(gz0, vec4(0.0));\n    gx0 -= sz0 * (step(0.0, gx0) - 0.5);\n    gy0 -= sz0 * (step(0.0, gy0) - 0.5);\n\n    vec4 gx1 = ixy1 * (1.0 / 7.0);\n    vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5;\n    gx1 = fract(gx1);\n    vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);\n    vec4 sz1 = step(gz1, vec4(0.0));\n    gx1 -= sz1 * (step(0.0, gx1) - 0.5);\n    gy1 -= sz1 * (step(0.0, gy1) - 0.5);\n\n    vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);\n    vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);\n    vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);\n    vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);\n    vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);\n    vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);\n    vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);\n    vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);\n\n    vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110)));\n    g000 *= norm0.x;\n    g010 *= norm0.y;\n    g100 *= norm0.z;\n    g110 *= norm0.w;\n    vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111)));\n    g001 *= norm1.x;\n    g011 *= norm1.y;\n    g101 *= norm1.z;\n    g111 *= norm1.w;\n\n    float n000 = dot(g000, Pf0);\n    float n100 = dot(g100, vec3(Pf1.x, Pf0.yz));\n    float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z));\n    float n110 = dot(g110, vec3(Pf1.xy, Pf0.z));\n    float n001 = dot(g001, vec3(Pf0.xy, Pf1.z));\n    float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z));\n    float n011 = dot(g011, vec3(Pf0.x, Pf1.yz));\n    float n111 = dot(g111, Pf1);\n\n    vec3 fade_xyz = fade(Pf0);\n    vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z);\n    vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);\n    float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x);\n    return 2.2 * n_xyz;\n}\n\n\nfloat turbulence( vec3 p ) {\n\n    float w = 100.0;\n    float t = -.5;\n\n    for (float f = 1.0 ; f <= 10.0 ; f++ ){\n        float power = pow( 2.0, f );\n        t += abs( pnoise( vec3( power * p ), vec3( 10.0, 10.0, 10.0 ) ) / power );\n    }\n\n    return t;\n\n}\n\nfloat rand(vec2 co){\n    return fract(sin(dot(co, vec2(12.9898, 78.233))) * 43758.5453);\n}\n\nvec4 perm(vec4 x){return mod289(((x * 34.0) + 1.0) * x);}\n\nfloat noise2(vec3 p){\n    vec3 a = floor(p);\n    vec3 d = p - a;\n    d = d * d * (3.0 - 2.0 * d);\n\n    vec4 b = a.xxyy + vec4(0.0, 1.0, 0.0, 1.0);\n    vec4 k1 = perm(b.xyxy);\n    vec4 k2 = perm(k1.xyxy + b.zzww);\n\n    vec4 c = k2 + a.zzzz;\n    vec4 k3 = perm(c);\n    vec4 k4 = perm(c + 1.0);\n\n    vec4 o1 = fract(k3 * (1.0 / 41.0));\n    vec4 o2 = fract(k4 * (1.0 / 41.0));\n\n    vec4 o3 = o2 * d.z + o1 * (1.0 - d.z);\n    vec2 o4 = o3.yw * d.x + o3.xz * (1.0 - d.x);\n\n    return o4.y * d.y + o4.x * (1.0 - d.y);\n}\n\nvoid main() {\n\n    vec3 test = vec3(1, 1, 1);\n    normal = ProjMat * ModelViewMat * vec4(Normal, 0.0);\n\n    float noise = 30.0 *  -.10 * turbulence( .5 * test + CustomTime );\n    //float noise = 30.0 *  -.10 * turbulence( .5 * normal.xyz + CustomTime );\n    float b = 5.0 * pnoise( 0.05 * Position + vec3( 2.0 * CustomTime ), vec3( 100.0 ) );\n    //float b = 5.0 * noise2( 0.05 * Position + vec3( 2.0 * CustomTime ) );\n    float displacement = - noise + b;\n\n    //float randomSeed = rand(normal.xy);\n\n    //vec3 newPosition = Position + vec3(sin(CustomTime) * 3, cos(CustomTime) * 3, 0);\n    //vec3 newPosition = Position + normal.xyz * displacement;\n    vec3 newPosition = Position + test;\n    //vec3 newPosition = Position + test * displacement;\n\n    gl_Position = ProjMat * ModelViewMat * vec4(newPosition, 1.0);\n\n    texCoord0 = UV0;\n    vertexDistance = length((ModelViewMat * vec4(newPosition, 1.0)).xyz);\n    vertexColor = Color;\n    //vec3 TestNormal = vec3(0, Normal.y * cos(CustomTime), 0);\n    //mat4 NormalRotate = TestNormal * rotationZ(CustomTime);\n    //normal = vec4(Normal, 0.0);\n    //vec4 normal2 = ModelViewMat * vec4(Normal, 0.0);\n    //vertexColor = minecraft_mix_light(Light0_Direction, Light1_Direction, normal.xyz, Color);\n    //[0.16145112, 0.80725557, -0.5650789]\n    //[-0.16145112, 0.80725557, 0.5650789]\n    vec4 l0 = vec4(Light0_Direction, 0) * -ModelViewMat;\n    vec4 l1 = vec4(Light1_Direction, 0) * -ModelViewMat;\n    vec3 l00 = vec3(0.16145112, 0.80725557, -0.5650789);\n    vec3 l11 = vec3(-0.16145112, 0.80725557, 0.5650789);\n    if (CustomTime == 1F) {\n        //vertexColor = minecraft_mix_light(l0.xyz, l1.xyz, TestNormal, Color);\n    } else {\n\n    }\n\n    vertexColor = minecraft_mix_light(l00, l11, Normal, Color);\n    //vertexColor = minecraft_mix_light(l00, l11, Normal, Color);\n}\n\n\n"
  },
  {
    "path": "src/main/resources/assets/weather2/shaders/core/rendertype_clouds_wip.json",
    "content": "{\n    \"blend\": {\n        \"func\": \"add\",\n        \"srcrgb\": \"srcalpha\",\n        \"dstrgb\": \"1-srcalpha\"\n    },\n    \"vertex\": \"weather2:rendertype_clouds\",\n    \"fragment\": \"weather2:rendertype_clouds\",\n    \"attributes_instanced\": [\n        { \"name\": \"Position\", \"index\": 0 },\n        { \"name\": \"UV0\", \"index\": 1 },\n        { \"name\": \"Color\", \"index\": 2 },\n        { \"name\": \"Normal\", \"index\": 3 }\n    ],\n    \"samplers\": [\n        { \"name\": \"Sampler0\" }\n    ],\n    \"uniforms\": [\n        { \"name\": \"ModelViewMat\", \"type\": \"matrix4x4\", \"count\": 16, \"values\": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] },\n        { \"name\": \"ProjMat\", \"type\": \"matrix4x4\", \"count\": 16, \"values\": [ 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0 ] },\n        { \"name\": \"ColorModulator\", \"type\": \"float\", \"count\": 4, \"values\": [ 1.0, 1.0, 1.0, 1.0 ] },\n        { \"name\": \"Light0_Direction\", \"type\": \"float\", \"count\": 3, \"values\": [0.0, 0.0, 0.0] },\n        { \"name\": \"Light1_Direction\", \"type\": \"float\", \"count\": 3, \"values\": [0.0, 0.0, 0.0] },\n        { \"name\": \"FogStart\", \"type\": \"float\", \"count\": 1, \"values\": [ 0.0 ] },\n        { \"name\": \"FogEnd\", \"type\": \"float\", \"count\": 1, \"values\": [ 1.0 ] },\n        { \"name\": \"FogColor\", \"type\": \"float\", \"count\": 4, \"values\": [ 0.0, 0.0, 0.0, 0.0 ] },\n        { \"name\": \"CustomTime\", \"type\": \"float\", \"count\": 1, \"values\": [ 0.0 ] }\n    ]\n}\n"
  },
  {
    "path": "src/main/resources/assets/weather2/shaders/include/classicnoise3d.glsl",
    "content": "//\n// GLSL textureless classic 3D noise \"cnoise\",\n// with an RSL-style periodic variant \"pnoise\".\n// Author:  Stefan Gustavson (stefan.gustavson@liu.se)\n// Version: 2011-10-11\n//\n// Many thanks to Ian McEwan of Ashima Arts for the\n// ideas for permutation and gradient selection.\n//\n// Copyright (c) 2011 Stefan Gustavson. All rights reserved.\n// Distributed under the MIT license. See LICENSE file.\n// https://github.com/stegu/webgl-noise\n//\n\nvec3 mod289(vec3 x)\n{\n    return x - floor(x * (1.0 / 289.0)) * 289.0;\n}\n\nvec4 mod289(vec4 x)\n{\n    return x - floor(x * (1.0 / 289.0)) * 289.0;\n}\n\nvec4 permute(vec4 x)\n{\n    return mod289(((x*34.0)+10.0)*x);\n}\n\nvec4 taylorInvSqrt(vec4 r)\n{\n    return 1.79284291400159 - 0.85373472095314 * r;\n}\n\nvec3 fade(vec3 t) {\n    return t*t*t*(t*(t*6.0-15.0)+10.0);\n}\n\n// Classic Perlin noise\nfloat cnoise(vec3 P)\n{\n    vec3 Pi0 = floor(P); // Integer part for indexing\n    vec3 Pi1 = Pi0 + vec3(1.0); // Integer part + 1\n    Pi0 = mod289(Pi0);\n    Pi1 = mod289(Pi1);\n    vec3 Pf0 = fract(P); // Fractional part for interpolation\n    vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0\n    vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);\n    vec4 iy = vec4(Pi0.yy, Pi1.yy);\n    vec4 iz0 = Pi0.zzzz;\n    vec4 iz1 = Pi1.zzzz;\n\n    vec4 ixy = permute(permute(ix) + iy);\n    vec4 ixy0 = permute(ixy + iz0);\n    vec4 ixy1 = permute(ixy + iz1);\n\n    vec4 gx0 = ixy0 * (1.0 / 7.0);\n    vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5;\n    gx0 = fract(gx0);\n    vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);\n    vec4 sz0 = step(gz0, vec4(0.0));\n    gx0 -= sz0 * (step(0.0, gx0) - 0.5);\n    gy0 -= sz0 * (step(0.0, gy0) - 0.5);\n\n    vec4 gx1 = ixy1 * (1.0 / 7.0);\n    vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5;\n    gx1 = fract(gx1);\n    vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);\n    vec4 sz1 = step(gz1, vec4(0.0));\n    gx1 -= sz1 * (step(0.0, gx1) - 0.5);\n    gy1 -= sz1 * (step(0.0, gy1) - 0.5);\n\n    vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);\n    vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);\n    vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);\n    vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);\n    vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);\n    vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);\n    vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);\n    vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);\n\n    vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110)));\n    g000 *= norm0.x;\n    g010 *= norm0.y;\n    g100 *= norm0.z;\n    g110 *= norm0.w;\n    vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111)));\n    g001 *= norm1.x;\n    g011 *= norm1.y;\n    g101 *= norm1.z;\n    g111 *= norm1.w;\n\n    float n000 = dot(g000, Pf0);\n    float n100 = dot(g100, vec3(Pf1.x, Pf0.yz));\n    float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z));\n    float n110 = dot(g110, vec3(Pf1.xy, Pf0.z));\n    float n001 = dot(g001, vec3(Pf0.xy, Pf1.z));\n    float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z));\n    float n011 = dot(g011, vec3(Pf0.x, Pf1.yz));\n    float n111 = dot(g111, Pf1);\n\n    vec3 fade_xyz = fade(Pf0);\n    vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z);\n    vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);\n    float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x);\n    return 2.2 * n_xyz;\n}\n\n// Classic Perlin noise, periodic variant\nfloat pnoise(vec3 P, vec3 rep)\n{\n    vec3 Pi0 = mod(floor(P), rep); // Integer part, modulo period\n    vec3 Pi1 = mod(Pi0 + vec3(1.0), rep); // Integer part + 1, mod period\n    Pi0 = mod289(Pi0);\n    Pi1 = mod289(Pi1);\n    vec3 Pf0 = fract(P); // Fractional part for interpolation\n    vec3 Pf1 = Pf0 - vec3(1.0); // Fractional part - 1.0\n    vec4 ix = vec4(Pi0.x, Pi1.x, Pi0.x, Pi1.x);\n    vec4 iy = vec4(Pi0.yy, Pi1.yy);\n    vec4 iz0 = Pi0.zzzz;\n    vec4 iz1 = Pi1.zzzz;\n\n    vec4 ixy = permute(permute(ix) + iy);\n    vec4 ixy0 = permute(ixy + iz0);\n    vec4 ixy1 = permute(ixy + iz1);\n\n    vec4 gx0 = ixy0 * (1.0 / 7.0);\n    vec4 gy0 = fract(floor(gx0) * (1.0 / 7.0)) - 0.5;\n    gx0 = fract(gx0);\n    vec4 gz0 = vec4(0.5) - abs(gx0) - abs(gy0);\n    vec4 sz0 = step(gz0, vec4(0.0));\n    gx0 -= sz0 * (step(0.0, gx0) - 0.5);\n    gy0 -= sz0 * (step(0.0, gy0) - 0.5);\n\n    vec4 gx1 = ixy1 * (1.0 / 7.0);\n    vec4 gy1 = fract(floor(gx1) * (1.0 / 7.0)) - 0.5;\n    gx1 = fract(gx1);\n    vec4 gz1 = vec4(0.5) - abs(gx1) - abs(gy1);\n    vec4 sz1 = step(gz1, vec4(0.0));\n    gx1 -= sz1 * (step(0.0, gx1) - 0.5);\n    gy1 -= sz1 * (step(0.0, gy1) - 0.5);\n\n    vec3 g000 = vec3(gx0.x,gy0.x,gz0.x);\n    vec3 g100 = vec3(gx0.y,gy0.y,gz0.y);\n    vec3 g010 = vec3(gx0.z,gy0.z,gz0.z);\n    vec3 g110 = vec3(gx0.w,gy0.w,gz0.w);\n    vec3 g001 = vec3(gx1.x,gy1.x,gz1.x);\n    vec3 g101 = vec3(gx1.y,gy1.y,gz1.y);\n    vec3 g011 = vec3(gx1.z,gy1.z,gz1.z);\n    vec3 g111 = vec3(gx1.w,gy1.w,gz1.w);\n\n    vec4 norm0 = taylorInvSqrt(vec4(dot(g000, g000), dot(g010, g010), dot(g100, g100), dot(g110, g110)));\n    g000 *= norm0.x;\n    g010 *= norm0.y;\n    g100 *= norm0.z;\n    g110 *= norm0.w;\n    vec4 norm1 = taylorInvSqrt(vec4(dot(g001, g001), dot(g011, g011), dot(g101, g101), dot(g111, g111)));\n    g001 *= norm1.x;\n    g011 *= norm1.y;\n    g101 *= norm1.z;\n    g111 *= norm1.w;\n\n    float n000 = dot(g000, Pf0);\n    float n100 = dot(g100, vec3(Pf1.x, Pf0.yz));\n    float n010 = dot(g010, vec3(Pf0.x, Pf1.y, Pf0.z));\n    float n110 = dot(g110, vec3(Pf1.xy, Pf0.z));\n    float n001 = dot(g001, vec3(Pf0.xy, Pf1.z));\n    float n101 = dot(g101, vec3(Pf1.x, Pf0.y, Pf1.z));\n    float n011 = dot(g011, vec3(Pf0.x, Pf1.yz));\n    float n111 = dot(g111, Pf1);\n\n    vec3 fade_xyz = fade(Pf0);\n    vec4 n_z = mix(vec4(n000, n100, n010, n110), vec4(n001, n101, n011, n111), fade_xyz.z);\n    vec2 n_yz = mix(n_z.xy, n_z.zw, fade_xyz.y);\n    float n_xyz = mix(n_yz.x, n_yz.y, fade_xyz.x);\n    return 2.2 * n_xyz;\n}\n"
  },
  {
    "path": "src/main/resources/assets/weather2/sounds.json",
    "content": "{\n  \"env.waterfall\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      \"weather2:env/waterfall\"\n    ]\n  },\n  \"env.wind_calm\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      \"weather2:env/wind_calm\"\n    ]\n  },\n  \"env.wind_calmfade\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      \"weather2:env/wind_calmfade\"\n    ]\n  },\n  \"streaming.destruction\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/destruction\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.destruction_0_\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/destruction_0_\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.destruction_1_\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/destruction_1_\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.destruction_2_\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/destruction_2_\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.destruction_s\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/destruction_s\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.destructionb\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/destructionb\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.siren\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/siren\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.wind_close\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/wind_close\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.wind_close_0_\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/wind_close_0_\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.wind_close_1_\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/wind_close_1_\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.wind_close_2_\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/wind_close_2_\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.wind_far\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/wind_far\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.wind_far_0_\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/wind_far_0_\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.wind_far_1_\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/wind_far_1_\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.wind_far_2_\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/wind_far_2_\",\n        \"stream\": true\n      }\n    ]\n  }\n  \n  ,\n  \"streaming.sandstorm_high1\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/sandstorm_high1\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.sandstorm_med1\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/sandstorm_med1\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.sandstorm_med2\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/sandstorm_med2\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.sandstorm_low1\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/sandstorm_low1\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.sandstorm_low2\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/sandstorm_low2\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.siren_sandstorm_1\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/siren_sandstorm_1\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.siren_sandstorm_2\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/siren_sandstorm_2\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.siren_sandstorm_3\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/siren_sandstorm_3\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.siren_sandstorm_4\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/siren_sandstorm_4\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.siren_sandstorm_5_extra\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/siren_sandstorm_5_extra\",\n        \"stream\": true\n      }\n    ]\n  },\n  \"streaming.siren_sandstorm_6_extra\": {\n    \"category\": \"weather\",\n    \"sounds\": [\n      {\n        \"name\": \"weather2:streaming/siren_sandstorm_6_extra\",\n        \"stream\": true\n      }\n    ]\n  }\n}"
  },
  {
    "path": "src/main/resources/pack.mcmeta",
    "content": "{\n    \"pack\": {\n        \"description\": \"weather2 resources\",\n        \"pack_format\": 4,\n        \"_comment\": \"A pack_format of 4 requires json lang files. Note: we require v4 pack meta for all mods.\"\n    }\n}\n"
  },
  {
    "path": "src/main/resources/weather2.mixins.json",
    "content": "{\n  \"required\": true,\n  \"package\": \"weather2.mixin\",\n  \"compatibilityLevel\": \"JAVA_17\",\n  \"refmap\": \"weather2.refmap.json\",\n  \"client\": [\"client.RenderParticlesOverride\"],\n  \"injectors\": {\n    \"defaultRequire\": 1\n  },\n  \"minVersion\": \"0.8\"\n}\n"
  }
]