diff --git a/ccl/.git-blame-ignore-revs b/ccl/.git-blame-ignore-revs new file mode 100644 index 0000000..3563c3d --- /dev/null +++ b/ccl/.git-blame-ignore-revs @@ -0,0 +1,2 @@ +# Ignore spotlessApply reformat +a4515fbef8382f0f961ade90ca82c85d924fc06d diff --git a/ccl/.gitattributes b/ccl/.gitattributes new file mode 100644 index 0000000..fd2792b --- /dev/null +++ b/ccl/.gitattributes @@ -0,0 +1,44 @@ +* text eol=lf + +*.[jJ][aA][rR] binary + +*.[pP][nN][gG] binary +*.[jJ][pP][gG] binary +*.[jJ][pP][eE][gG] binary +*.[gG][iI][fF] binary +*.[tT][iI][fF] binary +*.[tT][iI][fF][fF] binary +*.[iI][cC][oO] binary +*.[sS][vV][gG] text +*.[eE][pP][sS] binary +*.[xX][cC][fF] binary + +*.[kK][aA][rR] binary +*.[mM]4[aA] binary +*.[mM][iI][dD] binary +*.[mM][iI][dD][iI] binary +*.[mM][pP]3 binary +*.[oO][gG][gG] binary +*.[rR][aA] binary + +*.7[zZ] binary +*.[gG][zZ] binary +*.[tT][aA][rR] binary +*.[tT][gG][zZ] binary +*.[zZ][iI][pP] binary + +*.[tT][cC][nN] binary +*.[sS][oO] binary +*.[dD][lL][lL] binary +*.[dD][yY][lL][iI][bB] binary +*.[pP][sS][dD] binary +*.[tT][tT][fF] binary +*.[oO][tT][fF] binary + +*.[pP][aA][tT][cC][hH] -text + +*.[bB][aA][tT] text eol=crlf +*.[cC][mM][dD] text eol=crlf +*.[pP][sS]1 text eol=crlf + +*[aA][uU][tT][oO][gG][eE][nN][eE][rR][aA][tT][eE][dD]* binary diff --git a/ccl/.github/workflows/build-and-test.yml b/ccl/.github/workflows/build-and-test.yml new file mode 100644 index 0000000..3ee2f68 --- /dev/null +++ b/ccl/.github/workflows/build-and-test.yml @@ -0,0 +1,13 @@ + +name: Build and test + +on: + pull_request: + branches: [ master, main ] + push: + branches: [ master, main ] + +jobs: + build-and-test: + uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/build-and-test.yml@master + secrets: inherit diff --git a/ccl/.github/workflows/release-tags.yml b/ccl/.github/workflows/release-tags.yml new file mode 100644 index 0000000..e4c0be6 --- /dev/null +++ b/ccl/.github/workflows/release-tags.yml @@ -0,0 +1,14 @@ + +name: Release tagged build + +on: + push: + tags: [ '*' ] + +permissions: + contents: write + +jobs: + release-tags: + uses: GTNewHorizons/GTNH-Actions-Workflows/.github/workflows/release-tags.yml@master + secrets: inherit diff --git a/ccl/.gitignore b/ccl/.gitignore new file mode 100644 index 0000000..5e80e0a --- /dev/null +++ b/ccl/.gitignore @@ -0,0 +1,38 @@ +.gradle +.settings +/.idea/ +/.vscode/ +/run/ +/build/ +/eclipse/ +.classpath +.project +/bin/ +/config/ +/crash-reports/ +/logs/ +options.txt +/saves/ +usernamecache.json +banned-ips.json +banned-players.json +eula.txt +ops.json +server.properties +servers.dat +usercache.json +whitelist.json +/out/ +*.iml +*.ipr +*.iws +src/main/resources/mixins.*([!.]).json +*.bat +*.DS_Store +!gradlew.bat +.factorypath +addon.local.gradle +addon.local.gradle.kts +addon.late.local.gradle +addon.late.local.gradle.kts +layout.json diff --git a/ccl/CODEOWNERS b/ccl/CODEOWNERS new file mode 100644 index 0000000..a6b5f68 --- /dev/null +++ b/ccl/CODEOWNERS @@ -0,0 +1,3 @@ +# Any Github changes require admin approval +/.github/** @GTNewHorizons/admin + diff --git a/ccl/LICENSE b/ccl/LICENSE new file mode 100644 index 0000000..31b791c --- /dev/null +++ b/ccl/LICENSE @@ -0,0 +1,502 @@ +GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 2013 Chicken-Bones + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + {signature of Ty Coon}, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/ccl/README.md b/ccl/README.md new file mode 100644 index 0000000..9864c48 --- /dev/null +++ b/ccl/README.md @@ -0,0 +1,4 @@ +CodeChickenLib +============== +Formerly known as CodeChickenCore-Public, this is a library of systems to help make various aspects of minecraft modding easier. +It contains libraries for 3D math and transformations, model rendering, packets, config, colours, asm and a few other things. \ No newline at end of file diff --git a/ccl/build.gradle b/ccl/build.gradle new file mode 100644 index 0000000..e57a16f --- /dev/null +++ b/ccl/build.gradle @@ -0,0 +1,5 @@ +//version: 1707058017 + +plugins { + id 'com.gtnewhorizons.gtnhconvention' +} diff --git a/ccl/dependencies.gradle b/ccl/dependencies.gradle new file mode 100644 index 0000000..b6456d0 --- /dev/null +++ b/ccl/dependencies.gradle @@ -0,0 +1,5 @@ +// Add your dependencies here + +dependencies { + +} diff --git a/ccl/gradle.properties b/ccl/gradle.properties new file mode 100644 index 0000000..1849951 --- /dev/null +++ b/ccl/gradle.properties @@ -0,0 +1,191 @@ +# ExampleMod tag to use as Blowdryer (Spotless, etc.) settings version, leave empty to disable. +# LOCAL to test local config updates. +gtnh.settings.blowdryerTag = 0.2.0 + +# Human-readable mod name, available for mcmod.info population. +modName = CodeChickenLib + +# Case-sensitive identifier string, available for mcmod.info population and used for automatic mixin JSON generation. +# Conventionally lowercase. +modId = CodeChickenLib + +# Root package of the mod, used to find various classes in other properties, +# mcmod.info substitution, enabling assertions in run tasks, etc. +modGroup = codechicken + +# Whether to use modGroup as the maven publishing group. +# Due to a history of using JitPack, the default is com.github.GTNewHorizons for all mods. +useModGroupForPublishing = false + +# Updates your build.gradle and settings.gradle automatically whenever an update is available. +autoUpdateBuildScript = false + +# Version of Minecraft to target +minecraftVersion = 1.7.10 + +# Version of Minecraft Forge to target +forgeVersion = 10.13.4.1614 + +# Specify an MCP channel for dependency deobfuscation and the deobfParams task. +channel = stable + +# Specify an MCP mappings version for dependency deobfuscation and the deobfParams task. +mappingsVersion = 12 + +# Defines other MCP mappings for dependency deobfuscation. +remoteMappings = https://raw.githubusercontent.com/MinecraftForge/FML/1.7.10/conf/ + +# Select a default username for testing your mod. You can always override this per-run by running +# `./gradlew runClient --username=AnotherPlayer`, or configuring this command in your IDE. +developmentEnvironmentUserName = Developer + +# Enables using modern Java syntax (up to version 17) via Jabel, while still targeting JVM 8. +# See https://github.com/bsideup/jabel for details on how this works. +enableModernJavaSyntax = true + +# Enables injecting missing generics into the decompiled source code for a better coding experience. +# Turns most publicly visible List, Map, etc. into proper List, Map types. +enableGenericInjection = true + +# Generate a class with a String field for the mod version named as defined below. +# If generateGradleTokenClass is empty or not missing, no such class will be generated. +# If gradleTokenVersion is empty or missing, the field will not be present in the class. +generateGradleTokenClass = + +# Name of the token containing the project's current version to generate/replace. +gradleTokenVersion = + +# [DEPRECATED] Mod ID replacement token. +gradleTokenModId = + +# [DEPRECATED] Mod name replacement token. +gradleTokenModName = + +# [DEPRECATED] +# Multiple source files can be defined here by providing a comma-separated list: Class1.java,Class2.java,Class3.java +# public static final String VERSION = "GRADLETOKEN_VERSION"; +# The string's content will be replaced with your mod's version when compiled. You should use this to specify your mod's +# version in @Mod([...], version = VERSION, [...]). +# Leave these properties empty to skip individual token replacements. +replaceGradleTokenInFile = + +# In case your mod provides an API for other mods to implement you may declare its package here. Otherwise, you can +# leave this property empty. +# Example value: (apiPackage = api) + (modGroup = com.myname.mymodid) -> com.myname.mymodid.api +apiPackage = + +# Specify the configuration file for Forge's access transformers here. It must be placed into /src/main/resources/META-INF/ +# There can be multiple files in a space-separated list. +# Example value: mymodid_at.cfg nei_at.cfg +accessTransformersFile = + +# Provides setup for Mixins if enabled. If you don't know what mixins are: Keep it disabled! +usesMixins = false + +# Adds some debug arguments like verbose output and class export. +usesMixinDebug = false + +# Specify the location of your implementation of IMixinConfigPlugin. Leave it empty otherwise. +mixinPlugin = + +# Specify the package that contains all of your Mixins. You may only place Mixins in this package or the build will fail! +mixinsPackage = + +# Specify the core mod entry class if you use a core mod. This class must implement IFMLLoadingPlugin! +# This parameter is for legacy compatibility only +# Example value: (coreModClass = asm.FMLPlugin) + (modGroup = com.myname.mymodid) -> com.myname.mymodid.asm.FMLPlugin +coreModClass = + +# If your project is only a consolidation of mixins or a core mod and does NOT contain a 'normal' mod ( = some class +# that is annotated with @Mod) you want this to be true. When in doubt: leave it on false! +containsMixinsAndOrCoreModOnly = + +# Enables Mixins even if this mod doesn't use them, useful if one of the dependencies uses mixins. +forceEnableMixins = false + +# If enabled, you may use 'shadowCompile' for dependencies. They will be integrated into your jar. It is your +# responsibility to check the license and request permission for distribution if required. +usesShadowedDependencies = false + +# If disabled, won't remove unused classes from shadowed dependencies. Some libraries use reflection to access +# their own classes, making the minimization unreliable. +minimizeShadowedDependencies = true + +# If disabled, won't rename the shadowed classes. +relocateShadowedDependencies = true + +# Adds the GTNH maven, CurseMaven, IC2/Player maven, and some more well-known 1.7.10 repositories. +includeWellKnownRepositories = true + +# Change these to your Maven coordinates if you want to publish to a custom Maven repository instead of the default GTNH Maven. +# Authenticate with the MAVEN_USER and MAVEN_PASSWORD environment variables. +# If you need a more complex setup disable maven publishing here and add a publishing repository to addon.gradle. +usesMavenPublishing = true + +# Maven repository to publish the mod to. +# mavenPublishUrl = https://nexus.gtnewhorizons.com/repository/releases/ + +# Publishing to Modrinth requires you to set the MODRINTH_TOKEN environment variable to your current Modrinth API token. +# +# The project's ID on Modrinth. Can be either the slug or the ID. +# Leave this empty if you don't want to publish to Modrinth. +modrinthProjectId = codechickenlib-unofficial + +# The project's relations on Modrinth. You can use this to refer to other projects on Modrinth. +# Syntax: scope1-type1:name1;scope2-type2:name2;... +# Where scope can be one of [required, optional, incompatible, embedded], +# type can be one of [project, version], +# and the name is the Modrinth project or version slug/id of the other mod. +# Example: required-project:fplib;optional-project:gasstation;incompatible-project:gregtech +# Note: GTNH Mixins is automatically set as a required dependency if usesMixins = true +modrinthRelations = + +# Publishing to CurseForge requires you to set the CURSEFORGE_TOKEN environment variable to one of your CurseForge API tokens. +# +# The project's numeric ID on CurseForge. You can find this in the About Project box. +# Leave this empty if you don't want to publish on CurseForge. +curseForgeProjectId = 746280 + +# The project's relations on CurseForge. You can use this to refer to other projects on CurseForge. +# Syntax: type1:name1;type2:name2;... +# Where type can be one of [requiredDependency, embeddedLibrary, optionalDependency, tool, incompatible], +# and the name is the CurseForge project slug of the other mod. +# Example: requiredDependency:railcraft;embeddedLibrary:cofhlib;incompatible:buildcraft +# Note: UniMixins is automatically set as a required dependency if usesMixins = true. +curseForgeRelations = + +# Optional parameter to customize the produced artifacts. Use this to preserve artifact naming when migrating older +# projects. New projects should not use this parameter. +# customArchiveBaseName = + +# Optional parameter to have the build automatically fail if an illegal version is used. +# This can be useful if you e.g. only want to allow versions in the form of '1.1.xxx'. +# The check is ONLY performed if the version is a git tag. +# Note: the specified string must be escaped, so e.g. 1\\.1\\.\\d+ instead of 1\.1\.\d+ +# versionPattern = + +# Uncomment to prevent the source code from being published. +# noPublishedSources = true + +# Uncomment this to disable Spotless checks. +# This should only be uncommented to keep it easier to sync with upstream/other forks. +# That is, if there is no other active fork/upstream, NEVER change this. +# disableSpotless = true + +# Uncomment this to disable Checkstyle checks (currently wildcard import check). +# disableCheckstyle = true + +# Override the IDEA build type. Valid values are: "" (leave blank, do not override), "idea" (force use native IDEA build), "gradle" +# (force use delegated build). +# This is meant to be set in $HOME/.gradle/gradle.properties. +# e.g. add "systemProp.org.gradle.project.ideaOverrideBuildType=idea" will override the build type to be native build. +# WARNING: If you do use this option, it will overwrite whatever you have in your existing projects. This might not be what you want! +# Usually there is no need to uncomment this here as other developers do not necessarily use the same build type as you. +# ideaOverrideBuildType = idea + +# Whether IDEA should run spotless checks when pressing the Build button. +# This is meant to be set in $HOME/.gradle/gradle.properties. +# ideaCheckSpotlessOnBuild = true + +# Non-GTNH properties +gradleTokenGroupName = diff --git a/ccl/gradle/wrapper/gradle-wrapper.jar b/ccl/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..d64cd49 Binary files /dev/null and b/ccl/gradle/wrapper/gradle-wrapper.jar differ diff --git a/ccl/gradle/wrapper/gradle-wrapper.properties b/ccl/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..a80b22c --- /dev/null +++ b/ccl/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/ccl/gradlew b/ccl/gradlew new file mode 100755 index 0000000..1aa94a4 --- /dev/null +++ b/ccl/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/ccl/gradlew.bat b/ccl/gradlew.bat new file mode 100644 index 0000000..25da30d --- /dev/null +++ b/ccl/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/ccl/jitpack.yml b/ccl/jitpack.yml new file mode 100644 index 0000000..09bbb51 --- /dev/null +++ b/ccl/jitpack.yml @@ -0,0 +1,2 @@ +before_install: + - ./gradlew setupCIWorkspace \ No newline at end of file diff --git a/ccl/repositories.gradle b/ccl/repositories.gradle new file mode 100644 index 0000000..45729e1 --- /dev/null +++ b/ccl/repositories.gradle @@ -0,0 +1,4 @@ +// Add any additional repositiroes for your dependencies here + +repositories { +} diff --git a/ccl/settings.gradle b/ccl/settings.gradle new file mode 100644 index 0000000..e8946ad --- /dev/null +++ b/ccl/settings.gradle @@ -0,0 +1,23 @@ + +pluginManagement { + repositories { + maven { + // RetroFuturaGradle + name "GTNH Maven" + url "https://nexus.gtnewhorizons.com/repository/public/" + mavenContent { + includeGroup("com.gtnewhorizons") + includeGroupByRegex("com\\.gtnewhorizons\\..+") + } + } + gradlePluginPortal() + mavenCentral() + mavenLocal() + } +} + +plugins { + id 'com.gtnewhorizons.gtnhsettingsconvention' version '1.0.8' +} + + diff --git a/ccl/src/main/java/codechicken/lib/asm/ASMBlock.java b/ccl/src/main/java/codechicken/lib/asm/ASMBlock.java new file mode 100644 index 0000000..1f85ae7 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/asm/ASMBlock.java @@ -0,0 +1,177 @@ +package codechicken.lib.asm; + +import static org.objectweb.asm.tree.AbstractInsnNode.FRAME; +import static org.objectweb.asm.tree.AbstractInsnNode.JUMP_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.LABEL; +import static org.objectweb.asm.tree.AbstractInsnNode.LOOKUPSWITCH_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.TABLESWITCH_INSN; + +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; + +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.ImmutableMap; + +public class ASMBlock { + + public InsnListSection list; + private BiMap labels; + + public ASMBlock(InsnListSection list, BiMap labels) { + this.list = list; + this.labels = labels; + } + + public ASMBlock(InsnListSection list) { + this(list, HashBiMap.create()); + } + + public ASMBlock(InsnList list) { + this(new InsnListSection(list)); + } + + public ASMBlock() { + this(new InsnListSection()); + } + + public LabelNode getOrAdd(String s) { + LabelNode l = get(s); + if (l == null) labels.put(s, l = new LabelNode()); + return l; + } + + public LabelNode get(String s) { + return labels.get(s); + } + + public void replaceLabels(Map labelMap, Set usedLabels) { + for (AbstractInsnNode insn : list) switch (insn.getType()) { + case LABEL: + AbstractInsnNode insn2 = insn.clone(labelMap); + if (insn2 == insn) // identity mapping + continue; + if (usedLabels.contains(insn2)) + throw new IllegalStateException("LabelNode cannot be a part of two InsnLists"); + list.replace(insn, insn2); + break; + case JUMP_INSN: + case FRAME: + case LOOKUPSWITCH_INSN: + case TABLESWITCH_INSN: + list.replace(insn, insn.clone(labelMap)); + } + + for (Entry entry : labelMap.entrySet()) { + String key = labels.inverse().get(entry.getKey()); + if (key != null) labels.put(key, entry.getValue()); + } + } + + public void replaceLabels(Map labelMap) { + replaceLabels(labelMap, Collections.EMPTY_SET); + } + + public void replaceLabel(String s, LabelNode l) { + LabelNode old = get(s); + if (old != null) replaceLabels(ImmutableMap.of(old, l)); + } + + /** + * Pulls all common labels from other into this + * + * @return this + */ + public ASMBlock mergeLabels(ASMBlock other) { + if (labels.isEmpty() || other.labels.isEmpty()) return this; + + // common labels, give them our nodes + HashMap labelMap = list.identityLabelMap(); + for (Entry entry : other.labels.entrySet()) { + LabelNode old = labels.get(entry.getKey()); + if (old != null) labelMap.put(old, entry.getValue()); + } + HashSet usedLabels = new HashSet(); + for (AbstractInsnNode insn = other.list.list.getFirst(); insn != null; insn = insn.getNext()) + if (insn.getType() == LABEL) usedLabels.add((LabelNode) insn); + + replaceLabels(labelMap, usedLabels); + return this; + } + + /** + * Like mergeLabels but pulls insns from other list into this so LabelNodes can be transferred + * + * @return this + */ + public ASMBlock pullLabels(ASMBlock other) { + other.list.remove(); + return mergeLabels(other); + } + + public ASMBlock copy() { + BiMap labels = HashBiMap.create(); + Map labelMap = list.cloneLabels(); + + for (Entry entry : this.labels.entrySet()) + labels.put(entry.getKey(), labelMap.get(entry.getValue())); + + return new ASMBlock(list.copy(labelMap), labels); + } + + public ASMBlock applyLabels(InsnListSection list2) { + if (labels.isEmpty()) return new ASMBlock(list2); + + Set cFlowLabels1 = labels.values(); + Set cFlowLabels2 = InsnComparator.getControlFlowLabels(list2); + ASMBlock block = new ASMBlock(list2); + + HashMap labelMap = new HashMap(); + + for (int i = 0, k = 0; i < list.size() && k < list2.size();) { + AbstractInsnNode insn1 = list.get(i); + if (!InsnComparator.insnImportant(insn1, cFlowLabels1)) { + i++; + continue; + } + + AbstractInsnNode insn2 = list2.get(k); + if (!InsnComparator.insnImportant(insn2, cFlowLabels2)) { + k++; + continue; + } + + if (insn1.getOpcode() != insn2.getOpcode()) + throw new IllegalArgumentException("Lists do not match:\n" + list + "\n\n" + list2); + + switch (insn1.getType()) { + case LABEL: + labelMap.put((LabelNode) insn1, (LabelNode) insn2); + break; + case JUMP_INSN: + labelMap.put(((JumpInsnNode) insn1).label, ((JumpInsnNode) insn2).label); + break; + } + i++; + k++; + } + + for (Entry entry : labels.entrySet()) + block.labels.put(entry.getKey(), labelMap.get(entry.getValue())); + + return block; + } + + public InsnList rawListCopy() { + return list.copy().list; + } +} diff --git a/ccl/src/main/java/codechicken/lib/asm/ASMHelper.java b/ccl/src/main/java/codechicken/lib/asm/ASMHelper.java new file mode 100644 index 0000000..e90b99f --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/asm/ASMHelper.java @@ -0,0 +1,203 @@ +package codechicken.lib.asm; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LocalVariableNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TryCatchBlockNode; +import org.objectweb.asm.util.ASMifier; +import org.objectweb.asm.util.Textifier; +import org.objectweb.asm.util.TraceClassVisitor; + +import codechicken.lib.config.ConfigFile; +import codechicken.lib.config.DefaultingConfigFile; + +public class ASMHelper { + + public static ConfigFile config = loadConfig(); + public static Logger logger = LogManager.getLogger("CCL ASM"); + + private static ConfigFile loadConfig() { + try { // weak reference for environments without FML + File mcDir = (File) ((Object[]) Class.forName("cpw.mods.fml.relauncher.FMLInjectionData").getMethod("data") + .invoke(null))[6]; + File file = new File(mcDir, "config/CodeChickenLib.cfg"); + if (ObfMapping.obfuscated) return new DefaultingConfigFile(file); + else return new ConfigFile(file).setComment("CodeChickenLib development configuration file."); + } catch (Exception ignored) { + return null; // no config for these systems + } + } + + public static interface Acceptor { + + public void accept(ClassVisitor cv) throws IOException; + } + + public static MethodNode findMethod(ObfMapping methodmap, ClassNode cnode) { + for (MethodNode mnode : cnode.methods) if (methodmap.matches(mnode)) return mnode; + return null; + } + + public static FieldNode findField(ObfMapping fieldmap, ClassNode cnode) { + for (FieldNode fnode : cnode.fields) if (fieldmap.matches(fnode)) return fnode; + return null; + } + + public static ClassNode createClassNode(byte[] bytes) { + return createClassNode(bytes, 0); + } + + public static ClassNode createClassNode(byte[] bytes, int flags) { + ClassNode cnode = new ClassNode(); + ClassReader reader = new ClassReader(bytes); + reader.accept(cnode, flags); + return cnode; + } + + public static byte[] createBytes(ClassNode cnode, int flags) { + ClassWriter cw = new CC_ClassWriter(flags); + cnode.accept(cw); + return cw.toByteArray(); + } + + public static Map cloneLabels(InsnList list) { + return new InsnListSection(list).cloneLabels(); + } + + public static InsnList cloneInsnList(InsnList list) { + return new InsnListSection(list).copy().list; + } + + public static InsnList cloneInsnList(Map labelMap, InsnList list) { + return new InsnListSection(list).copy(labelMap).list; + } + + public static List cloneTryCatchBlocks(Map labelMap, + List tcblocks) { + ArrayList clone = new ArrayList(); + for (TryCatchBlockNode node : tcblocks) clone.add( + new TryCatchBlockNode( + labelMap.get(node.start), + labelMap.get(node.end), + labelMap.get(node.handler), + node.type)); + + return clone; + } + + public static List cloneLocals(Map labelMap, + List locals) { + ArrayList clone = new ArrayList(locals.size()); + for (LocalVariableNode node : locals) clone.add( + new LocalVariableNode( + node.name, + node.desc, + node.signature, + labelMap.get(node.start), + labelMap.get(node.end), + node.index)); + + return clone; + } + + public static void copy(MethodNode src, MethodNode dst) { + Map labelMap = cloneLabels(src.instructions); + dst.instructions = cloneInsnList(labelMap, src.instructions); + dst.tryCatchBlocks = cloneTryCatchBlocks(labelMap, src.tryCatchBlocks); + if (src.localVariables != null) dst.localVariables = cloneLocals(labelMap, src.localVariables); + dst.visibleAnnotations = src.visibleAnnotations; + dst.invisibleAnnotations = src.invisibleAnnotations; + dst.visitMaxs(src.maxStack, src.maxLocals); + } + + public static String toString(InsnList list) { + return new InsnListSection(list).toString(); + } + + public static int getLocal(List list, String name) { + int found = -1; + for (LocalVariableNode node : list) { + if (node.name.equals(name)) { + if (found >= 0) throw new RuntimeException( + "Duplicate local variable: " + name + " not coded to handle this scenario."); + + found = node.index; + } + } + return found; + } + + public static void replaceMethod(MethodNode original, MethodNode replacement) { + original.instructions.clear(); + if (original.localVariables != null) original.localVariables.clear(); + if (original.tryCatchBlocks != null) original.tryCatchBlocks.clear(); + replacement.accept(original); + } + + public static void dump(Acceptor acceptor, File file, boolean filterImportant, boolean sortLocals, + boolean textify) { + try { + if (!file.getParentFile().exists()) file.getParentFile().mkdirs(); + if (!file.exists()) file.createNewFile(); + + PrintWriter pout = new PrintWriter(file); + ClassVisitor cv = new TraceClassVisitor(null, textify ? new Textifier() : new ASMifier(), pout); + if (filterImportant) cv = new ImportantInsnVisitor(cv); + if (sortLocals) cv = new LocalVariablesSorterVisitor(cv); + acceptor.accept(cv); + pout.close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static void dump(Acceptor acceptor, File file, boolean filterImportant, boolean sortLocals) { + dump(acceptor, file, filterImportant, sortLocals, config.getTag("textify").getBooleanValue(true)); + } + + public static void dump(final byte[] bytes, File file, boolean filterImportant, boolean sortLocals) { + dump(new Acceptor() { + + @Override + public void accept(ClassVisitor cv) { + new ClassReader(bytes).accept(cv, ClassReader.EXPAND_FRAMES); + } + }, file, filterImportant, sortLocals); + } + + public static void dump(final InputStream is, File file, boolean filterImportant, boolean sortLocals) { + dump(new Acceptor() { + + @Override + public void accept(ClassVisitor cv) throws IOException { + new ClassReader(is).accept(cv, ClassReader.EXPAND_FRAMES); + } + }, file, filterImportant, sortLocals); + } + + public static void dump(final ClassNode cnode, File file, boolean filterImportant, boolean sortLocals) { + dump(new Acceptor() { + + @Override + public void accept(ClassVisitor cv) { + cnode.accept(cv); + } + }, file, filterImportant, sortLocals); + } +} diff --git a/ccl/src/main/java/codechicken/lib/asm/ASMInit.java b/ccl/src/main/java/codechicken/lib/asm/ASMInit.java new file mode 100644 index 0000000..baa8a13 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/asm/ASMInit.java @@ -0,0 +1,19 @@ +package codechicken.lib.asm; + +import net.minecraft.launchwrapper.Launch; + +/** + * Initialisation class for using this package. Call this on coremod load + */ +public class ASMInit { + + private static boolean initialised = false; + + public static void init() { + if (!initialised) { + Launch.classLoader.addTransformerExclusion("codechicken.lib.asm"); + Launch.classLoader.addTransformerExclusion("codechicken.lib.config"); + initialised = true; + } + } +} diff --git a/ccl/src/main/java/codechicken/lib/asm/ASMReader.java b/ccl/src/main/java/codechicken/lib/asm/ASMReader.java new file mode 100644 index 0000000..b348649 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/asm/ASMReader.java @@ -0,0 +1,469 @@ +package codechicken.lib.asm; + +import static org.objectweb.asm.Opcodes.AALOAD; +import static org.objectweb.asm.Opcodes.AASTORE; +import static org.objectweb.asm.Opcodes.ACONST_NULL; +import static org.objectweb.asm.Opcodes.ALOAD; +import static org.objectweb.asm.Opcodes.ANEWARRAY; +import static org.objectweb.asm.Opcodes.ARETURN; +import static org.objectweb.asm.Opcodes.ARRAYLENGTH; +import static org.objectweb.asm.Opcodes.ASTORE; +import static org.objectweb.asm.Opcodes.ATHROW; +import static org.objectweb.asm.Opcodes.BALOAD; +import static org.objectweb.asm.Opcodes.BASTORE; +import static org.objectweb.asm.Opcodes.BIPUSH; +import static org.objectweb.asm.Opcodes.CALOAD; +import static org.objectweb.asm.Opcodes.CASTORE; +import static org.objectweb.asm.Opcodes.CHECKCAST; +import static org.objectweb.asm.Opcodes.D2F; +import static org.objectweb.asm.Opcodes.D2I; +import static org.objectweb.asm.Opcodes.D2L; +import static org.objectweb.asm.Opcodes.DADD; +import static org.objectweb.asm.Opcodes.DALOAD; +import static org.objectweb.asm.Opcodes.DASTORE; +import static org.objectweb.asm.Opcodes.DCMPG; +import static org.objectweb.asm.Opcodes.DCMPL; +import static org.objectweb.asm.Opcodes.DCONST_0; +import static org.objectweb.asm.Opcodes.DCONST_1; +import static org.objectweb.asm.Opcodes.DDIV; +import static org.objectweb.asm.Opcodes.DLOAD; +import static org.objectweb.asm.Opcodes.DMUL; +import static org.objectweb.asm.Opcodes.DNEG; +import static org.objectweb.asm.Opcodes.DREM; +import static org.objectweb.asm.Opcodes.DRETURN; +import static org.objectweb.asm.Opcodes.DSTORE; +import static org.objectweb.asm.Opcodes.DSUB; +import static org.objectweb.asm.Opcodes.DUP; +import static org.objectweb.asm.Opcodes.DUP2; +import static org.objectweb.asm.Opcodes.DUP2_X1; +import static org.objectweb.asm.Opcodes.DUP2_X2; +import static org.objectweb.asm.Opcodes.DUP_X1; +import static org.objectweb.asm.Opcodes.DUP_X2; +import static org.objectweb.asm.Opcodes.F2D; +import static org.objectweb.asm.Opcodes.F2I; +import static org.objectweb.asm.Opcodes.F2L; +import static org.objectweb.asm.Opcodes.FADD; +import static org.objectweb.asm.Opcodes.FALOAD; +import static org.objectweb.asm.Opcodes.FASTORE; +import static org.objectweb.asm.Opcodes.FCMPG; +import static org.objectweb.asm.Opcodes.FCMPL; +import static org.objectweb.asm.Opcodes.FCONST_0; +import static org.objectweb.asm.Opcodes.FCONST_1; +import static org.objectweb.asm.Opcodes.FCONST_2; +import static org.objectweb.asm.Opcodes.FDIV; +import static org.objectweb.asm.Opcodes.FLOAD; +import static org.objectweb.asm.Opcodes.FMUL; +import static org.objectweb.asm.Opcodes.FNEG; +import static org.objectweb.asm.Opcodes.FREM; +import static org.objectweb.asm.Opcodes.FRETURN; +import static org.objectweb.asm.Opcodes.FSTORE; +import static org.objectweb.asm.Opcodes.FSUB; +import static org.objectweb.asm.Opcodes.GETFIELD; +import static org.objectweb.asm.Opcodes.GETSTATIC; +import static org.objectweb.asm.Opcodes.GOTO; +import static org.objectweb.asm.Opcodes.I2B; +import static org.objectweb.asm.Opcodes.I2C; +import static org.objectweb.asm.Opcodes.I2D; +import static org.objectweb.asm.Opcodes.I2F; +import static org.objectweb.asm.Opcodes.I2L; +import static org.objectweb.asm.Opcodes.I2S; +import static org.objectweb.asm.Opcodes.IADD; +import static org.objectweb.asm.Opcodes.IALOAD; +import static org.objectweb.asm.Opcodes.IAND; +import static org.objectweb.asm.Opcodes.IASTORE; +import static org.objectweb.asm.Opcodes.ICONST_0; +import static org.objectweb.asm.Opcodes.ICONST_1; +import static org.objectweb.asm.Opcodes.ICONST_2; +import static org.objectweb.asm.Opcodes.ICONST_3; +import static org.objectweb.asm.Opcodes.ICONST_4; +import static org.objectweb.asm.Opcodes.ICONST_5; +import static org.objectweb.asm.Opcodes.ICONST_M1; +import static org.objectweb.asm.Opcodes.IDIV; +import static org.objectweb.asm.Opcodes.IFEQ; +import static org.objectweb.asm.Opcodes.IFGE; +import static org.objectweb.asm.Opcodes.IFGT; +import static org.objectweb.asm.Opcodes.IFLE; +import static org.objectweb.asm.Opcodes.IFLT; +import static org.objectweb.asm.Opcodes.IFNE; +import static org.objectweb.asm.Opcodes.IFNONNULL; +import static org.objectweb.asm.Opcodes.IFNULL; +import static org.objectweb.asm.Opcodes.IF_ACMPEQ; +import static org.objectweb.asm.Opcodes.IF_ACMPNE; +import static org.objectweb.asm.Opcodes.IF_ICMPEQ; +import static org.objectweb.asm.Opcodes.IF_ICMPGE; +import static org.objectweb.asm.Opcodes.IF_ICMPGT; +import static org.objectweb.asm.Opcodes.IF_ICMPLE; +import static org.objectweb.asm.Opcodes.IF_ICMPLT; +import static org.objectweb.asm.Opcodes.IF_ICMPNE; +import static org.objectweb.asm.Opcodes.IINC; +import static org.objectweb.asm.Opcodes.ILOAD; +import static org.objectweb.asm.Opcodes.IMUL; +import static org.objectweb.asm.Opcodes.INEG; +import static org.objectweb.asm.Opcodes.INSTANCEOF; +import static org.objectweb.asm.Opcodes.INVOKEDYNAMIC; +import static org.objectweb.asm.Opcodes.INVOKEINTERFACE; +import static org.objectweb.asm.Opcodes.INVOKESPECIAL; +import static org.objectweb.asm.Opcodes.INVOKESTATIC; +import static org.objectweb.asm.Opcodes.INVOKEVIRTUAL; +import static org.objectweb.asm.Opcodes.IOR; +import static org.objectweb.asm.Opcodes.IREM; +import static org.objectweb.asm.Opcodes.IRETURN; +import static org.objectweb.asm.Opcodes.ISHL; +import static org.objectweb.asm.Opcodes.ISHR; +import static org.objectweb.asm.Opcodes.ISTORE; +import static org.objectweb.asm.Opcodes.ISUB; +import static org.objectweb.asm.Opcodes.IUSHR; +import static org.objectweb.asm.Opcodes.IXOR; +import static org.objectweb.asm.Opcodes.JSR; +import static org.objectweb.asm.Opcodes.L2D; +import static org.objectweb.asm.Opcodes.L2F; +import static org.objectweb.asm.Opcodes.L2I; +import static org.objectweb.asm.Opcodes.LADD; +import static org.objectweb.asm.Opcodes.LALOAD; +import static org.objectweb.asm.Opcodes.LAND; +import static org.objectweb.asm.Opcodes.LASTORE; +import static org.objectweb.asm.Opcodes.LCMP; +import static org.objectweb.asm.Opcodes.LCONST_0; +import static org.objectweb.asm.Opcodes.LCONST_1; +import static org.objectweb.asm.Opcodes.LDC; +import static org.objectweb.asm.Opcodes.LDIV; +import static org.objectweb.asm.Opcodes.LLOAD; +import static org.objectweb.asm.Opcodes.LMUL; +import static org.objectweb.asm.Opcodes.LNEG; +import static org.objectweb.asm.Opcodes.LOOKUPSWITCH; +import static org.objectweb.asm.Opcodes.LOR; +import static org.objectweb.asm.Opcodes.LREM; +import static org.objectweb.asm.Opcodes.LRETURN; +import static org.objectweb.asm.Opcodes.LSHL; +import static org.objectweb.asm.Opcodes.LSHR; +import static org.objectweb.asm.Opcodes.LSTORE; +import static org.objectweb.asm.Opcodes.LSUB; +import static org.objectweb.asm.Opcodes.LUSHR; +import static org.objectweb.asm.Opcodes.LXOR; +import static org.objectweb.asm.Opcodes.MONITORENTER; +import static org.objectweb.asm.Opcodes.MONITOREXIT; +import static org.objectweb.asm.Opcodes.MULTIANEWARRAY; +import static org.objectweb.asm.Opcodes.NEW; +import static org.objectweb.asm.Opcodes.NEWARRAY; +import static org.objectweb.asm.Opcodes.NOP; +import static org.objectweb.asm.Opcodes.POP; +import static org.objectweb.asm.Opcodes.POP2; +import static org.objectweb.asm.Opcodes.PUTFIELD; +import static org.objectweb.asm.Opcodes.PUTSTATIC; +import static org.objectweb.asm.Opcodes.RET; +import static org.objectweb.asm.Opcodes.RETURN; +import static org.objectweb.asm.Opcodes.SALOAD; +import static org.objectweb.asm.Opcodes.SASTORE; +import static org.objectweb.asm.Opcodes.SIPUSH; +import static org.objectweb.asm.Opcodes.SWAP; +import static org.objectweb.asm.Opcodes.TABLESWITCH; +import static org.objectweb.asm.tree.AbstractInsnNode.FIELD_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.FRAME; +import static org.objectweb.asm.tree.AbstractInsnNode.IINC_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.INT_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.INVOKE_DYNAMIC_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.JUMP_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.LABEL; +import static org.objectweb.asm.tree.AbstractInsnNode.LDC_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.LOOKUPSWITCH_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.METHOD_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.MULTIANEWARRAY_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.TABLESWITCH_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.TYPE_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.VAR_INSN; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.HashMap; +import java.util.Map; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.IincInsnNode; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.IntInsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.LineNumberNode; +import org.objectweb.asm.tree.MultiANewArrayInsnNode; +import org.objectweb.asm.tree.VarInsnNode; + +public class ASMReader { + + public static Map opCodes = new HashMap(); + public static byte[] TYPE; + + static { + opCodes.put("NOP", NOP); + opCodes.put("ACONST_NULL", ACONST_NULL); + opCodes.put("ICONST_M1", ICONST_M1); + opCodes.put("ICONST_0", ICONST_0); + opCodes.put("ICONST_1", ICONST_1); + opCodes.put("ICONST_2", ICONST_2); + opCodes.put("ICONST_3", ICONST_3); + opCodes.put("ICONST_4", ICONST_4); + opCodes.put("ICONST_5", ICONST_5); + opCodes.put("LCONST_0", LCONST_0); + opCodes.put("LCONST_1", LCONST_1); + opCodes.put("FCONST_0", FCONST_0); + opCodes.put("FCONST_1", FCONST_1); + opCodes.put("FCONST_2", FCONST_2); + opCodes.put("DCONST_0", DCONST_0); + opCodes.put("DCONST_1", DCONST_1); + opCodes.put("BIPUSH", BIPUSH); + opCodes.put("SIPUSH", SIPUSH); + opCodes.put("LDC", LDC); + opCodes.put("ILOAD", ILOAD); + opCodes.put("LLOAD", LLOAD); + opCodes.put("FLOAD", FLOAD); + opCodes.put("DLOAD", DLOAD); + opCodes.put("ALOAD", ALOAD); + opCodes.put("IALOAD", IALOAD); + opCodes.put("LALOAD", LALOAD); + opCodes.put("FALOAD", FALOAD); + opCodes.put("DALOAD", DALOAD); + opCodes.put("AALOAD", AALOAD); + opCodes.put("BALOAD", BALOAD); + opCodes.put("CALOAD", CALOAD); + opCodes.put("SALOAD", SALOAD); + opCodes.put("ISTORE", ISTORE); + opCodes.put("LSTORE", LSTORE); + opCodes.put("FSTORE", FSTORE); + opCodes.put("DSTORE", DSTORE); + opCodes.put("ASTORE", ASTORE); + opCodes.put("IASTORE", IASTORE); + opCodes.put("LASTORE", LASTORE); + opCodes.put("FASTORE", FASTORE); + opCodes.put("DASTORE", DASTORE); + opCodes.put("AASTORE", AASTORE); + opCodes.put("BASTORE", BASTORE); + opCodes.put("CASTORE", CASTORE); + opCodes.put("SASTORE", SASTORE); + opCodes.put("POP", POP); + opCodes.put("POP2", POP2); + opCodes.put("DUP", DUP); + opCodes.put("DUP_X1", DUP_X1); + opCodes.put("DUP_X2", DUP_X2); + opCodes.put("DUP2", DUP2); + opCodes.put("DUP2_X1", DUP2_X1); + opCodes.put("DUP2_X2", DUP2_X2); + opCodes.put("SWAP", SWAP); + opCodes.put("IADD", IADD); + opCodes.put("LADD", LADD); + opCodes.put("FADD", FADD); + opCodes.put("DADD", DADD); + opCodes.put("ISUB", ISUB); + opCodes.put("LSUB", LSUB); + opCodes.put("FSUB", FSUB); + opCodes.put("DSUB", DSUB); + opCodes.put("IMUL", IMUL); + opCodes.put("LMUL", LMUL); + opCodes.put("FMUL", FMUL); + opCodes.put("DMUL", DMUL); + opCodes.put("IDIV", IDIV); + opCodes.put("LDIV", LDIV); + opCodes.put("FDIV", FDIV); + opCodes.put("DDIV", DDIV); + opCodes.put("IREM", IREM); + opCodes.put("LREM", LREM); + opCodes.put("FREM", FREM); + opCodes.put("DREM", DREM); + opCodes.put("INEG", INEG); + opCodes.put("LNEG", LNEG); + opCodes.put("FNEG", FNEG); + opCodes.put("DNEG", DNEG); + opCodes.put("ISHL", ISHL); + opCodes.put("LSHL", LSHL); + opCodes.put("ISHR", ISHR); + opCodes.put("LSHR", LSHR); + opCodes.put("IUSHR", IUSHR); + opCodes.put("LUSHR", LUSHR); + opCodes.put("IAND", IAND); + opCodes.put("LAND", LAND); + opCodes.put("IOR", IOR); + opCodes.put("LOR", LOR); + opCodes.put("IXOR", IXOR); + opCodes.put("LXOR", LXOR); + opCodes.put("IINC", IINC); + opCodes.put("I2L", I2L); + opCodes.put("I2F", I2F); + opCodes.put("I2D", I2D); + opCodes.put("L2I", L2I); + opCodes.put("L2F", L2F); + opCodes.put("L2D", L2D); + opCodes.put("F2I", F2I); + opCodes.put("F2L", F2L); + opCodes.put("F2D", F2D); + opCodes.put("D2I", D2I); + opCodes.put("D2L", D2L); + opCodes.put("D2F", D2F); + opCodes.put("I2B", I2B); + opCodes.put("I2C", I2C); + opCodes.put("I2S", I2S); + opCodes.put("LCMP", LCMP); + opCodes.put("FCMPL", FCMPL); + opCodes.put("FCMPG", FCMPG); + opCodes.put("DCMPL", DCMPL); + opCodes.put("DCMPG", DCMPG); + opCodes.put("IFEQ", IFEQ); + opCodes.put("IFNE", IFNE); + opCodes.put("IFLT", IFLT); + opCodes.put("IFGE", IFGE); + opCodes.put("IFGT", IFGT); + opCodes.put("IFLE", IFLE); + opCodes.put("IF_ICMPEQ", IF_ICMPEQ); + opCodes.put("IF_ICMPNE", IF_ICMPNE); + opCodes.put("IF_ICMPLT", IF_ICMPLT); + opCodes.put("IF_ICMPGE", IF_ICMPGE); + opCodes.put("IF_ICMPGT", IF_ICMPGT); + opCodes.put("IF_ICMPLE", IF_ICMPLE); + opCodes.put("IF_ACMPEQ", IF_ACMPEQ); + opCodes.put("IF_ACMPNE", IF_ACMPNE); + opCodes.put("GOTO", GOTO); + opCodes.put("JSR", JSR); + opCodes.put("RET", RET); + opCodes.put("TABLESWITCH", TABLESWITCH); + opCodes.put("LOOKUPSWITCH", LOOKUPSWITCH); + opCodes.put("IRETURN", IRETURN); + opCodes.put("LRETURN", LRETURN); + opCodes.put("FRETURN", FRETURN); + opCodes.put("DRETURN", DRETURN); + opCodes.put("ARETURN", ARETURN); + opCodes.put("RETURN", RETURN); + opCodes.put("GETSTATIC", GETSTATIC); + opCodes.put("PUTSTATIC", PUTSTATIC); + opCodes.put("GETFIELD", GETFIELD); + opCodes.put("PUTFIELD", PUTFIELD); + opCodes.put("INVOKEVIRTUAL", INVOKEVIRTUAL); + opCodes.put("INVOKESPECIAL", INVOKESPECIAL); + opCodes.put("INVOKESTATIC", INVOKESTATIC); + opCodes.put("INVOKEINTERFACE", INVOKEINTERFACE); + opCodes.put("INVOKEDYNAMIC", INVOKEDYNAMIC); + opCodes.put("NEW", NEW); + opCodes.put("NEWARRAY", NEWARRAY); + opCodes.put("ANEWARRAY", ANEWARRAY); + opCodes.put("ARRAYLENGTH", ARRAYLENGTH); + opCodes.put("ATHROW", ATHROW); + opCodes.put("CHECKCAST", CHECKCAST); + opCodes.put("INSTANCEOF", INSTANCEOF); + opCodes.put("MONITORENTER", MONITORENTER); + opCodes.put("MONITOREXIT", MONITOREXIT); + opCodes.put("MULTIANEWARRAY", MULTIANEWARRAY); + opCodes.put("IFNULL", IFNULL); + opCodes.put("IFNONNULL", IFNONNULL); + + // derived from classWriter, mapped to AbstractInsnNode + TYPE = new byte[200]; + String s = "AAAAAAAAAAAAAAAABBJ__CCCCC____________________AAAAAAAACC" + + "CCC____________________AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + + "AAAAAAAAAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAHHHHHHHHHHHHHHHHCLMAA" + + "AAAAEEEEFFFFGDBDAADDAA_NHH"; + for (int i = 0; i < s.length(); i++) TYPE[i] = (byte) (s.charAt(i) - 'A'); + } + + public static Map loadResource(String res) { + return loadResource(ASMHelper.class.getResourceAsStream(res), res); + } + + public static Map loadResource(InputStream in, String res) { + HashMap blocks = new HashMap(); + String current = "unnamed"; + ASMBlock block = new ASMBlock(); + try { + BufferedReader r = new BufferedReader(new InputStreamReader(in)); + String line; + while ((line = r.readLine()) != null) { + { + int hpos = line.indexOf('#'); + if (hpos >= 0) line = line.substring(0, hpos); + } + line = line.trim(); + if (line.length() == 0) continue; + if (line.startsWith("list ")) { + if (block.list.size() > 0) blocks.put(current, block); + current = line.substring(5); + block = new ASMBlock(); + continue; + } + + try { + AbstractInsnNode insn = null; + String[] split = line.replace(" : ", ":").split(" "); + Integer i_opcode = opCodes.get(split[0]); + if (i_opcode == null) { + if (split[0].equals("LINENUMBER")) + insn = new LineNumberNode(Integer.parseInt(split[1]), block.getOrAdd(split[2])); + else if (split[0].startsWith("L")) insn = block.getOrAdd(split[0]); + else throw new Exception("Unknown opcode " + split[0]); + } else { + int opcode = i_opcode; + switch (TYPE[opcode]) { + case INSN: + insn = new InsnNode(opcode); + break; + case INT_INSN: + insn = new IntInsnNode(opcode, Integer.parseInt(split[1])); + break; + case VAR_INSN: + insn = new VarInsnNode(opcode, Integer.parseInt(split[1])); + break; + case TYPE_INSN: + insn = new ObfMapping(split[1]).toClassloading().toInsn(opcode); + break; + case FIELD_INSN: + case METHOD_INSN: + StringBuilder sb = new StringBuilder(); + for (int i = 1; i < split.length; i++) sb.append(split[i]); + insn = ObfMapping.fromDesc(sb.toString()).toClassloading().toInsn(opcode); + break; + case INVOKE_DYNAMIC_INSN: + throw new Exception("Found INVOKEDYNAMIC while reading"); + case JUMP_INSN: + insn = new JumpInsnNode(opcode, block.getOrAdd(split[1])); + break; + case LDC_INSN: + String cst = split[1]; + if (cst.equals("*")) insn = new LdcInsnNode(null); + else if (cst.endsWith("\"")) insn = new LdcInsnNode(cst.substring(1, cst.length() - 1)); + else if (cst.endsWith("L")) + insn = new LdcInsnNode(Long.valueOf(cst.substring(0, cst.length() - 1))); + else if (cst.endsWith("F")) + insn = new LdcInsnNode(Float.valueOf(cst.substring(0, cst.length() - 1))); + else if (cst.endsWith("D")) + insn = new LdcInsnNode(Double.valueOf(cst.substring(0, cst.length() - 1))); + else if (cst.contains(".")) insn = new LdcInsnNode(Double.valueOf(cst)); + else insn = new LdcInsnNode(Integer.valueOf(cst)); + break; + case IINC_INSN: + insn = new IincInsnNode(opcode, Integer.parseInt(split[1])); + break; + case LABEL: + throw new Exception("Use L# for labels"); + case TABLESWITCH_INSN: + case LOOKUPSWITCH_INSN: + throw new Exception("I don't know how to deal with this insn type"); + case MULTIANEWARRAY_INSN: + insn = new MultiANewArrayInsnNode(split[1], Integer.parseInt(split[2])); + break; + case FRAME: + throw new Exception("Use ClassWriter.COMPUTE_FRAMES"); + } + } + + if (insn != null) block.list.add(insn); + } catch (Exception e) { + System.err.println("Error while reading ASM Block " + current + " from " + res + ", line: " + line); + e.printStackTrace(); + } + } + + r.close(); + if (block.list.size() > 0) blocks.put(current, block); + } catch (IOException e) { + throw new RuntimeException("Failed to read ASM resource: " + res, e); + } + return blocks; + } +} diff --git a/ccl/src/main/java/codechicken/lib/asm/CC_ClassWriter.java b/ccl/src/main/java/codechicken/lib/asm/CC_ClassWriter.java new file mode 100644 index 0000000..f3e0731 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/asm/CC_ClassWriter.java @@ -0,0 +1,29 @@ +package codechicken.lib.asm; + +import org.objectweb.asm.ClassWriter; + +public class CC_ClassWriter extends ClassWriter { + + private final boolean runtime; + + public CC_ClassWriter(int flags) { + this(flags, false); + } + + public CC_ClassWriter(int flags, boolean runtime) { + super(flags); + this.runtime = runtime; + } + + @Override + protected String getCommonSuperClass(String type1, String type2) { + String c = type1.replace('/', '.'); + String d = type2.replace('/', '.'); + if (ClassHeirachyManager.classExtends(d, c)) return type1; + if (ClassHeirachyManager.classExtends(c, d)) return type2; + do { + c = ClassHeirachyManager.getSuperClass(c, runtime); + } while (!ClassHeirachyManager.classExtends(d, c)); + return c.replace('.', '/'); + } +} diff --git a/ccl/src/main/java/codechicken/lib/asm/ClassConstantPoolParser.java b/ccl/src/main/java/codechicken/lib/asm/ClassConstantPoolParser.java new file mode 100644 index 0000000..299e185 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/asm/ClassConstantPoolParser.java @@ -0,0 +1,109 @@ +package codechicken.lib.asm; + +/*** + * This Class is derived from the ASM ClassReader + *

+ * ASM: a very small and fast Java bytecode manipulation framework Copyright (c) 2000-2011 INRIA, France Telecom All + * rights reserved. + *

+ * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the + * following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation and/or other materials provided with the + * distribution. 3. Neither the name of the copyright holders nor the names of its contributors may be used to endorse + * or promote products derived from this software without specific prior written permission. + *

+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +import java.nio.charset.StandardCharsets; + +import org.objectweb.asm.Opcodes; + +/** + * Using this class to search for a (single) String reference is > 40 times faster than parsing a class with a + * ClassReader + ClassNode while using way less RAM + */ +public class ClassConstantPoolParser { + + private static final int UTF8 = 1; + private static final int INT = 3; + private static final int FLOAT = 4; + private static final int LONG = 5; + private static final int DOUBLE = 6; + private static final int FIELD = 9; + private static final int METH = 10; + private static final int IMETH = 11; + private static final int NAME_TYPE = 12; + private static final int HANDLE = 15; + private static final int INDY = 18; + + private final byte[][] BYTES_TO_SEARCH; + + public ClassConstantPoolParser(String... strings) { + BYTES_TO_SEARCH = new byte[strings.length][]; + for (int i = 0; i < BYTES_TO_SEARCH.length; i++) { + BYTES_TO_SEARCH[i] = strings[i].getBytes(StandardCharsets.UTF_8); + } + } + + /** + * Returns true if the constant pool of the class represented by this byte array contains one of the Strings we are + * looking for + */ + public boolean find(byte[] basicClass) { + if (basicClass == null || basicClass.length == 0) { + return false; + } + // checks the class version + if (readShort(6, basicClass) > Opcodes.V1_8) { + return false; + } + // parses the constant pool + int n = readUnsignedShort(8, basicClass); + int index = 10; + for (int i = 1; i < n; ++i) { + int size; + switch (basicClass[index]) { + case FIELD, METH, IMETH, INT, FLOAT, NAME_TYPE, INDY -> size = 5; + case LONG, DOUBLE -> { + size = 9; + ++i; + } + case UTF8 -> { + final int strLen = readUnsignedShort(index + 1, basicClass); + size = 3 + strLen; + label: for (byte[] bytes : BYTES_TO_SEARCH) { + if (strLen == bytes.length) { + for (int j = index + 3; j < index + 3 + strLen; j++) { + if (basicClass[j] != bytes[j - (index + 3)]) { + break label; + } + } + return true; + } + } + } + case HANDLE -> size = 4; + default -> size = 3; + } + index += size; + } + return false; + } + + private static short readShort(final int index, byte[] basicClass) { + return (short) (((basicClass[index] & 0xFF) << 8) | (basicClass[index + 1] & 0xFF)); + } + + private static int readUnsignedShort(final int index, byte[] basicClass) { + return ((basicClass[index] & 0xFF) << 8) | (basicClass[index + 1] & 0xFF); + } + +} diff --git a/ccl/src/main/java/codechicken/lib/asm/ClassHeirachyManager.java b/ccl/src/main/java/codechicken/lib/asm/ClassHeirachyManager.java new file mode 100644 index 0000000..0566378 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/asm/ClassHeirachyManager.java @@ -0,0 +1,154 @@ +package codechicken.lib.asm; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; + +import net.minecraft.launchwrapper.IClassTransformer; +import net.minecraft.launchwrapper.Launch; +import net.minecraft.launchwrapper.LaunchClassLoader; + +import org.objectweb.asm.tree.ClassNode; + +import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper; + +/** + * This is added as a class transformer if CodeChickenCore is installed. Adding it as a class transformer will speed + * evaluation up slightly by automatically caching superclasses when they are first loaded. + */ +public class ClassHeirachyManager implements IClassTransformer { + + static { + ASMInit.init(); + } + + public static class SuperCache { + + String superclass; + public HashSet parents = new HashSet(); + private boolean flattened; + + public void add(String parent) { + parents.add(parent); + } + + public void flatten() { + if (flattened) return; + + for (String s : new ArrayList(parents)) { + SuperCache c = declareClass(s); + if (c != null) { + c.flatten(); + parents.addAll(c.parents); + } + } + flattened = true; + } + } + + public static HashMap superclasses = new HashMap(); + private static LaunchClassLoader cl = Launch.classLoader; + + public static String toKey(String name) { + if (ObfMapping.obfuscated) + name = FMLDeobfuscatingRemapper.INSTANCE.map(name.replace('.', '/')).replace('/', '.'); + return name; + } + + public static String unKey(String name) { + if (ObfMapping.obfuscated) + name = FMLDeobfuscatingRemapper.INSTANCE.unmap(name.replace('.', '/')).replace('/', '.'); + return name; + } + + /** + * @param name The class in question + * @param superclass The class being extended + * @return true if clazz extends, either directly or indirectly, superclass. + */ + public static boolean classExtends(String name, String superclass) { + name = toKey(name); + superclass = toKey(superclass); + + if (name.equals(superclass)) return true; + + SuperCache cache = declareClass(name); + if (cache == null) // just can't handle this + return false; + + cache.flatten(); + return cache.parents.contains(superclass); + } + + private static SuperCache declareClass(String name) { + name = toKey(name); + SuperCache cache = superclasses.get(name); + + if (cache != null) return cache; + + try { + byte[] bytes = cl.getClassBytes(unKey(name)); + if (bytes != null) cache = declareASM(bytes); + } catch (Exception e) {} + + if (cache != null) return cache; + + try { + cache = declareReflection(name); + } catch (ClassNotFoundException e) {} + + return cache; + } + + private static SuperCache declareReflection(String name) throws ClassNotFoundException { + Class aclass = Class.forName(name); + + SuperCache cache = getOrCreateCache(name); + if (aclass.isInterface()) cache.superclass = "java.lang.Object"; + else if (name.equals("java.lang.Object")) return cache; + else cache.superclass = toKey(aclass.getSuperclass().getName()); + + cache.add(cache.superclass); + for (Class iclass : aclass.getInterfaces()) cache.add(toKey(iclass.getName())); + + return cache; + } + + private static SuperCache declareASM(byte[] bytes) { + ClassNode node = ASMHelper.createClassNode(bytes); + String name = toKey(node.name); + + SuperCache cache = getOrCreateCache(name); + cache.superclass = toKey(node.superName.replace('/', '.')); + cache.add(cache.superclass); + for (String iclass : node.interfaces) cache.add(toKey(iclass.replace('/', '.'))); + + return cache; + } + + @Override + public byte[] transform(String name, String tname, byte[] bytes) { + if (bytes == null) return null; + + if (!superclasses.containsKey(tname)) declareASM(bytes); + + return bytes; + } + + public static SuperCache getOrCreateCache(String name) { + SuperCache cache = superclasses.get(name); + if (cache == null) superclasses.put(name, cache = new SuperCache()); + return cache; + } + + public static String getSuperClass(String name, boolean runtime) { + name = toKey(name); + SuperCache cache = declareClass(name); + if (cache == null) return "java.lang.Object"; + + cache.flatten(); + String s = cache.superclass; + if (!runtime) s = FMLDeobfuscatingRemapper.INSTANCE.unmap(s); + return s; + } +} diff --git a/ccl/src/main/java/codechicken/lib/asm/ImportantInsnVisitor.java b/ccl/src/main/java/codechicken/lib/asm/ImportantInsnVisitor.java new file mode 100644 index 0000000..c6b32ed --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/asm/ImportantInsnVisitor.java @@ -0,0 +1,36 @@ +package codechicken.lib.asm; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.MethodNode; + +public class ImportantInsnVisitor extends ClassVisitor { + + public class ImportantInsnMethodVisitor extends MethodVisitor { + + MethodVisitor delegate; + + public ImportantInsnMethodVisitor(int access, String name, String desc, String signature, String[] exceptions) { + super(Opcodes.ASM4, new MethodNode(access, name, desc, signature, exceptions)); + delegate = cv.visitMethod(access, name, desc, signature, exceptions); + } + + @Override + public void visitEnd() { + super.visitEnd(); + MethodNode mnode = (MethodNode) mv; + mnode.instructions = InsnComparator.getImportantList(mnode.instructions); + mnode.accept(delegate); + } + } + + public ImportantInsnVisitor(ClassVisitor cv) { + super(Opcodes.ASM4, cv); + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + return new ImportantInsnMethodVisitor(access, name, desc, signature, exceptions); + } +} diff --git a/ccl/src/main/java/codechicken/lib/asm/InsnComparator.java b/ccl/src/main/java/codechicken/lib/asm/InsnComparator.java new file mode 100644 index 0000000..f88da92 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/asm/InsnComparator.java @@ -0,0 +1,210 @@ +package codechicken.lib.asm; + +import static org.objectweb.asm.tree.AbstractInsnNode.FIELD_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.FRAME; +import static org.objectweb.asm.tree.AbstractInsnNode.IINC_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.INT_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.JUMP_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.LABEL; +import static org.objectweb.asm.tree.AbstractInsnNode.LDC_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.LINE; +import static org.objectweb.asm.tree.AbstractInsnNode.LOOKUPSWITCH_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.METHOD_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.TABLESWITCH_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.TYPE_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.VAR_INSN; + +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.IincInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.IntInsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LdcInsnNode; +import org.objectweb.asm.tree.LookupSwitchInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.TableSwitchInsnNode; +import org.objectweb.asm.tree.TypeInsnNode; +import org.objectweb.asm.tree.VarInsnNode; + +import com.google.common.base.Function; +import com.google.common.collect.Maps; + +public class InsnComparator { + + public static boolean varInsnEqual(VarInsnNode insn1, VarInsnNode insn2) { + return insn1.var == -1 || insn2.var == -1 || insn1.var == insn2.var; + } + + public static boolean methodInsnEqual(MethodInsnNode insn1, MethodInsnNode insn2) { + return insn1.owner.equals(insn2.owner) && insn1.name.equals(insn2.name) && insn1.desc.equals(insn2.desc); + } + + public static boolean fieldInsnEqual(FieldInsnNode insn1, FieldInsnNode insn2) { + return insn1.owner.equals(insn2.owner) && insn1.name.equals(insn2.name) && insn1.desc.equals(insn2.desc); + } + + public static boolean ldcInsnEqual(LdcInsnNode insn1, LdcInsnNode insn2) { + return insn1.cst == null || insn2.cst == null || insn1.cst.equals(insn2.cst); + } + + public static boolean typeInsnEqual(TypeInsnNode insn1, TypeInsnNode insn2) { + return insn1.desc.equals("*") || insn2.desc.equals("*") || insn1.desc.equals(insn2.desc); + } + + public static boolean iincInsnEqual(IincInsnNode node1, IincInsnNode node2) { + return node1.var == node2.var && node1.incr == node2.incr; + } + + public static boolean intInsnEqual(IntInsnNode node1, IntInsnNode node2) { + return node1.operand == -1 || node2.operand == -1 || node1.operand == node2.operand; + } + + public static boolean insnEqual(AbstractInsnNode node1, AbstractInsnNode node2) { + if (node1.getOpcode() != node2.getOpcode()) return false; + + switch (node2.getType()) { + case VAR_INSN: + return varInsnEqual((VarInsnNode) node1, (VarInsnNode) node2); + case TYPE_INSN: + return typeInsnEqual((TypeInsnNode) node1, (TypeInsnNode) node2); + case FIELD_INSN: + return fieldInsnEqual((FieldInsnNode) node1, (FieldInsnNode) node2); + case METHOD_INSN: + return methodInsnEqual((MethodInsnNode) node1, (MethodInsnNode) node2); + case LDC_INSN: + return ldcInsnEqual((LdcInsnNode) node1, (LdcInsnNode) node2); + case IINC_INSN: + return iincInsnEqual((IincInsnNode) node1, (IincInsnNode) node2); + case INT_INSN: + return intInsnEqual((IntInsnNode) node1, (IntInsnNode) node2); + default: + return true; + } + } + + public static boolean insnImportant(AbstractInsnNode insn, Set controlFlowLabels) { + switch (insn.getType()) { + case LINE: + case FRAME: + return false; + case LABEL: + return controlFlowLabels.contains(insn); + default: + return true; + } + } + + public static Set getControlFlowLabels(InsnListSection list) { + return getControlFlowLabels(list.list); + } + + public static Set getControlFlowLabels(InsnList list) { + HashSet controlFlowLabels = new HashSet(); + for (AbstractInsnNode insn = list.getFirst(); insn != null; insn = insn.getNext()) { + switch (insn.getType()) { + case JUMP_INSN: + JumpInsnNode jinsn = (JumpInsnNode) insn; + controlFlowLabels.add(jinsn.label); + break; + case TABLESWITCH_INSN: + TableSwitchInsnNode tsinsn = (TableSwitchInsnNode) insn; + controlFlowLabels.add(tsinsn.dflt); + for (LabelNode label : tsinsn.labels) controlFlowLabels.add(label); + break; + case LOOKUPSWITCH_INSN: + LookupSwitchInsnNode lsinsn = (LookupSwitchInsnNode) insn; + controlFlowLabels.add(lsinsn.dflt); + for (LabelNode label : lsinsn.labels) controlFlowLabels.add(label); + break; + } + } + return controlFlowLabels; + } + + public static InsnList getImportantList(InsnList list) { + return getImportantList(new InsnListSection(list)).list; + } + + public static InsnListSection getImportantList(InsnListSection list) { + if (list.size() == 0) return list; + + Set controlFlowLabels = getControlFlowLabels(list); + Map labelMap = Maps.asMap(controlFlowLabels, new Function() { + + @Override + public LabelNode apply(LabelNode input) { + return input; + } + }); + + InsnListSection importantNodeList = new InsnListSection(); + for (AbstractInsnNode insn : list) + if (insnImportant(insn, controlFlowLabels)) importantNodeList.add(insn.clone(labelMap)); + + return importantNodeList; + } + + public static List find(InsnListSection haystack, InsnListSection needle) { + Set controlFlowLabels = getControlFlowLabels(haystack); + LinkedList list = new LinkedList(); + for (int start = 0; start <= haystack.size() - needle.size(); start++) { + InsnListSection section = matches(haystack.drop(start), needle, controlFlowLabels); + if (section != null) { + list.add(section); + start = section.end - 1; + } + } + + return list; + } + + public static List find(InsnList haystack, InsnListSection needle) { + return find(new InsnListSection(haystack), needle); + } + + public static InsnListSection matches(InsnListSection haystack, InsnListSection needle, + Set controlFlowLabels) { + int h = 0, n = 0; + for (; h < haystack.size() && n < needle.size(); h++) { + AbstractInsnNode insn = haystack.get(h); + if (!insnImportant(insn, controlFlowLabels)) continue; + + if (!insnEqual(haystack.get(h), needle.get(n))) return null; + n++; + } + if (n != needle.size()) return null; + + return haystack.take(h); + } + + public static InsnListSection findOnce(InsnListSection haystack, InsnListSection needle) { + List list = find(haystack, needle); + if (list.size() != 1) throw new RuntimeException( + "Needle found " + list.size() + " times in Haystack:\n" + haystack + "\n\n" + needle); + + return list.get(0); + } + + public static InsnListSection findOnce(InsnList haystack, InsnListSection needle) { + return findOnce(new InsnListSection(haystack), needle); + } + + public static List findN(InsnListSection haystack, InsnListSection needle) { + List list = find(haystack, needle); + if (list.isEmpty()) throw new RuntimeException("Needle not found in Haystack:\n" + haystack + "\n\n" + needle); + + return list; + } + + public static List findN(InsnList haystack, InsnListSection needle) { + return findN(new InsnListSection(haystack), needle); + } +} diff --git a/ccl/src/main/java/codechicken/lib/asm/InsnListSection.java b/ccl/src/main/java/codechicken/lib/asm/InsnListSection.java new file mode 100644 index 0000000..dbe204a --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/asm/InsnListSection.java @@ -0,0 +1,235 @@ +package codechicken.lib.asm; + +import static org.objectweb.asm.tree.AbstractInsnNode.FRAME; +import static org.objectweb.asm.tree.AbstractInsnNode.JUMP_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.LABEL; +import static org.objectweb.asm.tree.AbstractInsnNode.LOOKUPSWITCH_INSN; +import static org.objectweb.asm.tree.AbstractInsnNode.TABLESWITCH_INSN; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FrameNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LookupSwitchInsnNode; +import org.objectweb.asm.tree.TableSwitchInsnNode; +import org.objectweb.asm.util.Textifier; +import org.objectweb.asm.util.TraceMethodVisitor; + +/** + * A section of an InsnList, may become invalid if the insn list is modified + */ +public class InsnListSection implements Iterable { + + private class InsnListSectionIterator implements Iterator { + + int i = 0; + + @Override + public boolean hasNext() { + return i < size(); + } + + @Override + public AbstractInsnNode next() { + return get(i++); + } + + @Override + public void remove() { + InsnListSection.this.remove(--i); + } + } + + public InsnList list; + public int start; + public int end; + + public InsnListSection(InsnList list, int start, int end) { + this.list = list; + this.start = start; + this.end = end; + } + + public InsnListSection(InsnList list, AbstractInsnNode first, AbstractInsnNode last) { + this(list, list.indexOf(first), list.indexOf(last) + 1); + } + + public InsnListSection(InsnList list) { + this(list, 0, list.size()); + } + + public InsnListSection() { + this(new InsnList()); + } + + public void accept(MethodVisitor mv) { + for (AbstractInsnNode insn : this) insn.accept(mv); + } + + public AbstractInsnNode getFirst() { + return size() == 0 ? null : list.get(start); + } + + public AbstractInsnNode getLast() { + return size() == 0 ? null : list.get(end - 1); + } + + public int size() { + return end - start; + } + + public AbstractInsnNode get(int i) { + return list.get(start + i); + } + + public void set(int i, AbstractInsnNode insn) { + list.set(get(i), insn); + } + + public void remove(int i) { + list.remove(get(i)); + end--; + } + + public void replace(AbstractInsnNode location, AbstractInsnNode insn) { + list.set(location, insn); + } + + public void add(AbstractInsnNode insn) { + list.add(insn); + end++; + } + + public void insertBefore(InsnList insns) { + int s = insns.size(); + if (this.list.size() == 0) list.insert(insns); + else list.insertBefore(list.get(start), insns); + start += s; + end += s; + } + + public void insert(InsnList insns) { + if (end == 0) list.insert(insns); + else list.insert(list.get(end - 1), insns); + } + + public void replace(InsnList insns) { + int s = insns.size(); + remove(); + insert(insns); + end = start + s; + } + + public void remove() { + while (end != start) remove(0); + } + + public void setLast(AbstractInsnNode last) { + end = list.indexOf(last) + 1; + } + + public void setFirst(AbstractInsnNode first) { + start = list.indexOf(first); + } + + public InsnListSection drop(int n) { + return slice(n, size()); + } + + public InsnListSection take(int n) { + return slice(0, n); + } + + public InsnListSection slice(int start, int end) { + return new InsnListSection(list, this.start + start, this.start + end); + } + + /** + * Removes leading and trailing labels and line number nodes that don't affect control flow + * + * @return this + */ + public InsnListSection trim(Set controlFlowLabels) { + while (start < end && !InsnComparator.insnImportant(getFirst(), controlFlowLabels)) start++; + + while (start < end && !InsnComparator.insnImportant(getLast(), controlFlowLabels)) end--; + + return this; + } + + public String toString() { + Textifier t = new Textifier(); + accept(new TraceMethodVisitor(t)); + StringWriter sw = new StringWriter(); + t.print(new PrintWriter(sw)); + return sw.toString(); + } + + public void println() { + System.out.println(toString()); + } + + public HashMap identityLabelMap() { + HashMap labelMap = new HashMap(); + for (AbstractInsnNode insn : this) switch (insn.getType()) { + case LABEL: + labelMap.put((LabelNode) insn, (LabelNode) insn); + break; + case JUMP_INSN: + labelMap.put(((JumpInsnNode) insn).label, ((JumpInsnNode) insn).label); + break; + case LOOKUPSWITCH_INSN: + LookupSwitchInsnNode linsn = (LookupSwitchInsnNode) insn; + labelMap.put(linsn.dflt, linsn.dflt); + for (LabelNode label : linsn.labels) labelMap.put(label, label); + break; + case TABLESWITCH_INSN: + TableSwitchInsnNode tinsn = (TableSwitchInsnNode) insn; + labelMap.put(tinsn.dflt, tinsn.dflt); + for (LabelNode label : tinsn.labels) labelMap.put(label, label); + break; + case FRAME: + FrameNode fnode = (FrameNode) insn; + if (fnode.local != null) + for (Object o : fnode.local) if (o instanceof LabelNode) labelMap.put((LabelNode) o, (LabelNode) o); + if (fnode.stack != null) + for (Object o : fnode.stack) if (o instanceof LabelNode) labelMap.put((LabelNode) o, (LabelNode) o); + break; + } + + return labelMap; + } + + public Map cloneLabels() { + Map labelMap = identityLabelMap(); + for (Entry entry : labelMap.entrySet()) entry.setValue(new LabelNode()); + + return labelMap; + } + + public InsnListSection copy() { + return copy(cloneLabels()); + } + + public InsnListSection copy(Map labelMap) { + InsnListSection copy = new InsnListSection(); + for (AbstractInsnNode insn : this) copy.add(insn.clone(labelMap)); + + return copy; + } + + @Override + public Iterator iterator() { + return new InsnListSectionIterator(); + } +} diff --git a/ccl/src/main/java/codechicken/lib/asm/LocalVariablesSorterVisitor.java b/ccl/src/main/java/codechicken/lib/asm/LocalVariablesSorterVisitor.java new file mode 100644 index 0000000..0157384 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/asm/LocalVariablesSorterVisitor.java @@ -0,0 +1,37 @@ +package codechicken.lib.asm; + +import java.util.Set; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.commons.LocalVariablesSorter; + +public class LocalVariablesSorterVisitor extends ClassVisitor { + + public Set methods; + public String owner; + + public LocalVariablesSorterVisitor(Set methods, ClassVisitor cv) { + super(Opcodes.ASM4, cv); + this.methods = methods; + } + + public LocalVariablesSorterVisitor(ClassVisitor cv) { + this(null, cv); + } + + @Override + public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { + super.visit(version, access, name, signature, superName, interfaces); + owner = name; + } + + @Override + public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { + MethodVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); + return methods == null || methods.contains(new ObfMapping(owner, name, desc)) + ? new LocalVariablesSorter(access, desc, mv) + : mv; + } +} diff --git a/ccl/src/main/java/codechicken/lib/asm/ModularASMTransformer.java b/ccl/src/main/java/codechicken/lib/asm/ModularASMTransformer.java new file mode 100644 index 0000000..baa4eb6 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/asm/ModularASMTransformer.java @@ -0,0 +1,292 @@ +package codechicken.lib.asm; + +import static codechicken.lib.asm.ASMHelper.config; +import static codechicken.lib.asm.ASMHelper.createBytes; +import static codechicken.lib.asm.ASMHelper.dump; +import static codechicken.lib.asm.ASMHelper.findMethod; +import static codechicken.lib.asm.ASMHelper.logger; + +import java.io.File; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; +import java.util.Set; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.MethodNode; + +public class ModularASMTransformer { + + public static class ClassNodeTransformerList { + + List transformers = new LinkedList(); + HashSet methodsToSort = new HashSet(); + + public void add(ClassNodeTransformer t) { + transformers.add(t); + t.addMethodsToSort(methodsToSort); + } + + public byte[] transform(byte[] bytes) { + ClassNode cnode = new ClassNode(); + ClassReader reader = new ClassReader(bytes); + ClassVisitor cv = cnode; + if (!methodsToSort.isEmpty()) cv = new LocalVariablesSorterVisitor(methodsToSort, cv); + reader.accept(cv, ClassReader.EXPAND_FRAMES); + + try { + int writeFlags = 0; + for (ClassNodeTransformer t : transformers) { + t.transform(cnode); + writeFlags |= t.writeFlags; + } + + bytes = createBytes(cnode, writeFlags); + if (config.getTag("dump_asm").getBooleanValue(false)) + dump(bytes, new File("asm/ccl_modular/" + cnode.name.replace('/', '#') + ".txt"), false, false); + return bytes; + } catch (RuntimeException e) { + dump(bytes, new File("asm/ccl_modular/" + cnode.name.replace('/', '#') + ".txt"), false, false); + throw e; + } + } + } + + public abstract static class ClassNodeTransformer { + + public int writeFlags; + + public ClassNodeTransformer(int writeFlags) { + this.writeFlags = writeFlags; + } + + public ClassNodeTransformer() { + this(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + } + + public abstract String className(); + + public abstract void transform(ClassNode cnode); + + public void addMethodsToSort(Set set) {} + } + + public abstract static class MethodTransformer extends ClassNodeTransformer { + + public final ObfMapping method; + + public MethodTransformer(ObfMapping method) { + this.method = method.toClassloading(); + } + + @Override + public String className() { + return method.javaClass(); + } + + @Override + public void transform(ClassNode cnode) { + MethodNode mv = findMethod(method, cnode); + if (mv == null) throw new RuntimeException("Method not found: " + method); + + try { + transform(mv); + } catch (Exception e) { + throw new RuntimeException("Error transforming method: " + method, e); + } + } + + public abstract void transform(MethodNode mv); + } + + public static class MethodWriter extends ClassNodeTransformer { + + public final int access; + public final ObfMapping method; + public final String[] exceptions; + public InsnList list; + + public MethodWriter(int access, ObfMapping method) { + this(access, method, null, (InsnList) null); + } + + public MethodWriter(int access, ObfMapping method, InsnList list) { + this(access, method, null, list); + } + + public MethodWriter(int access, ObfMapping method, ASMBlock block) { + this(access, method, null, block); + } + + public MethodWriter(int access, ObfMapping method, String[] exceptions) { + this(access, method, exceptions, (InsnList) null); + } + + public MethodWriter(int access, ObfMapping method, String[] exceptions, InsnList list) { + this.access = access; + this.method = method.toClassloading(); + this.exceptions = exceptions; + this.list = list; + } + + public MethodWriter(int access, ObfMapping method, String[] exceptions, ASMBlock block) { + this(access, method, exceptions, block.rawListCopy()); + } + + @Override + public String className() { + return method.javaClass(); + } + + @Override + public void transform(ClassNode cnode) { + MethodNode mv = findMethod(method, cnode); + if (mv == null) mv = (MethodNode) method.visitMethod(cnode, access, exceptions); + else { + mv.access = access; + mv.instructions.clear(); + if (mv.localVariables != null) mv.localVariables.clear(); + if (mv.tryCatchBlocks != null) mv.tryCatchBlocks.clear(); + } + + write(mv); + } + + public void write(MethodNode mv) { + logger.debug("Writing method " + method); + list.accept(mv); + } + } + + public static class MethodInjector extends MethodTransformer { + + public ASMBlock needle; + public ASMBlock injection; + public boolean before; + + public MethodInjector(ObfMapping method, ASMBlock needle, ASMBlock injection, boolean before) { + super(method); + this.needle = needle; + this.injection = injection; + this.before = before; + } + + public MethodInjector(ObfMapping method, ASMBlock injection, boolean before) { + this(method, null, injection, before); + } + + public MethodInjector(ObfMapping method, InsnList needle, InsnList injection, boolean before) { + this(method, new ASMBlock(needle), new ASMBlock(injection), before); + } + + public MethodInjector(ObfMapping method, InsnList injection, boolean before) { + this(method, null, new ASMBlock(injection), before); + } + + @Override + public void addMethodsToSort(Set set) { + set.add(method); + } + + @Override + public void transform(MethodNode mv) { + if (needle == null) { + logger.debug("Injecting " + (before ? "before" : "after") + " method " + method); + if (before) mv.instructions.insert(injection.rawListCopy()); + else mv.instructions.add(injection.rawListCopy()); + } else { + for (InsnListSection key : InsnComparator.findN(mv.instructions, needle.list)) { + logger.debug( + "Injecting " + (before ? "before" : "after") + + " method " + + method + + " @ " + + key.start + + " - " + + key.end); + ASMBlock injectBlock = injection.copy().mergeLabels(needle.applyLabels(key)); + + if (before) key.insertBefore(injectBlock.list.list); + else key.insert(injectBlock.list.list); + } + } + } + } + + public static class MethodReplacer extends MethodTransformer { + + public ASMBlock needle; + public ASMBlock replacement; + + public MethodReplacer(ObfMapping method, ASMBlock needle, ASMBlock replacement) { + super(method); + this.needle = needle; + this.replacement = replacement; + } + + public MethodReplacer(ObfMapping method, InsnList needle, InsnList replacement) { + this(method, new ASMBlock(needle), new ASMBlock(replacement)); + } + + @Override + public void addMethodsToSort(Set set) { + set.add(method); + } + + @Override + public void transform(MethodNode mv) { + for (InsnListSection key : InsnComparator.findN(mv.instructions, needle.list)) { + logger.debug("Replacing method " + method + " @ " + key.start + " - " + key.end); + ASMBlock replaceBlock = replacement.copy().pullLabels(needle.applyLabels(key)); + key.insert(replaceBlock.list.list); + } + } + } + + public static class FieldWriter extends ClassNodeTransformer { + + public final ObfMapping field; + public final int access; + public final Object value; + + public FieldWriter(int access, ObfMapping field, Object value) { + this.field = field.toClassloading(); + this.access = access; + this.value = value; + } + + public FieldWriter(int access, ObfMapping field) { + this(access, field, null); + } + + @Override + public String className() { + return field.javaClass(); + } + + @Override + public void transform(ClassNode cnode) { + field.visitField(cnode, access, value); + } + } + + public HashMap transformers = new HashMap(); + + public void add(ClassNodeTransformer t) { + ClassNodeTransformerList list = transformers.get(t.className()); + if (list == null) transformers.put(t.className(), list = new ClassNodeTransformerList()); + list.add(t); + } + + public byte[] transform(String name, byte[] bytes) { + if (bytes == null) return null; + + ClassNodeTransformerList list = transformers.get(name); + return list == null ? bytes : list.transform(bytes); + } +} diff --git a/ccl/src/main/java/codechicken/lib/asm/ObfMapping.java b/ccl/src/main/java/codechicken/lib/asm/ObfMapping.java new file mode 100644 index 0000000..27ca913 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/asm/ObfMapping.java @@ -0,0 +1,401 @@ +package codechicken.lib.asm; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import javax.swing.JFileChooser; + +import net.minecraft.launchwrapper.Launch; +import net.minecraftforge.common.ForgeVersion; + +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.FieldVisitor; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.commons.Remapper; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TypeInsnNode; + +import com.google.common.base.Charsets; +import com.google.common.base.Objects; +import com.google.common.io.LineProcessor; +import com.google.common.io.Resources; + +import codechicken.lib.config.ConfigTag; +import cpw.mods.fml.common.asm.transformers.deobf.FMLDeobfuscatingRemapper; +import cpw.mods.fml.relauncher.FMLInjectionData; + +public class ObfMapping { + + public static class ObfRemapper extends Remapper { + + private final HashMap fields = new HashMap<>(); + private final HashMap funcs = new HashMap<>(); + + public ObfRemapper() { + try { + Field rawFieldMapsField = FMLDeobfuscatingRemapper.class.getDeclaredField("rawFieldMaps"); + Field rawMethodMapsField = FMLDeobfuscatingRemapper.class.getDeclaredField("rawMethodMaps"); + rawFieldMapsField.setAccessible(true); + rawMethodMapsField.setAccessible(true); + Map> rawFieldMaps = (Map>) rawFieldMapsField + .get(FMLDeobfuscatingRemapper.INSTANCE); + Map> rawMethodMaps = (Map>) rawMethodMapsField + .get(FMLDeobfuscatingRemapper.INSTANCE); + + if (rawFieldMaps == null) throw new IllegalStateException( + "codechicken.lib.asm.ObfMapping loaded too early. Make sure all references are in or after the asm transformer load stage"); + + for (Map map : rawFieldMaps.values()) + for (Entry entry : map.entrySet()) if (entry.getValue().startsWith("field")) + fields.put(entry.getValue(), entry.getKey().substring(0, entry.getKey().indexOf(':'))); + for (Map map : rawMethodMaps.values()) + for (Entry entry : map.entrySet()) if (entry.getValue().startsWith("func")) + funcs.put(entry.getValue(), entry.getKey().substring(0, entry.getKey().indexOf('('))); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public String mapMethodName(String owner, String name, String desc) { + String s = funcs.get(name); + return s == null ? name : s; + } + + @Override + public String mapFieldName(String owner, String name, String desc) { + String s = fields.get(name); + return s == null ? name : s; + } + + @Override + public String map(String typeName) { + return FMLDeobfuscatingRemapper.INSTANCE.unmap(typeName); + } + + public String unmap(String typeName) { + return FMLDeobfuscatingRemapper.INSTANCE.map(typeName); + } + + public boolean isObf(String typeName) { + return !map(typeName).equals(typeName) || !unmap(typeName).equals(typeName); + } + } + + public static class MCPRemapper extends Remapper implements LineProcessor { + + public static File[] getConfFiles() { + ConfigTag tag = ASMHelper.config.getTag("mappingDir") + .setComment("Path to directory holding packaged.srg, fields.csv and methods.csv for mcp remapping"); + for (int i = 0; i < DIR_GUESSES + DIR_ASKS; i++) { + File dir = confDirectoryGuess(i, tag); + if (dir == null || dir.isFile()) continue; + + File[] mappings; + try { + mappings = parseConfDir(dir); + } catch (Exception e) { + if (i >= DIR_GUESSES) e.printStackTrace(); + continue; + } + + tag.setValue(dir.getPath()); + return mappings; + } + + throw new RuntimeException("Failed to select mappings directory, set it manually in the config"); + } + + private static final int DIR_GUESSES = 6; + private static final int DIR_ASKS = 3; + + public static File confDirectoryGuess(int i, ConfigTag tag) { + File mcDir = (File) FMLInjectionData.data()[6]; + switch (i) { + case 0: + return tag.value != null ? new File(tag.getValue()) : null; + case 1: + return new File(mcDir, "../conf"); + case 2: + return new File(mcDir, "../build/unpacked/conf"); + case 3: + return new File( + System.getProperty("user.home"), + ".gradle/caches/minecraft/net/minecraftforge/forge/" + FMLInjectionData.data()[4] + + "-" + + ForgeVersion.getVersion() + + "/unpacked/conf"); + case 4: + return new File( + System.getProperty("user.home"), + ".gradle/caches/minecraft/net/minecraftforge/forge/" + FMLInjectionData.data()[4] + + "-" + + ForgeVersion.getVersion() + + "-" + + FMLInjectionData.data()[4] + + "/unpacked/conf"); + case 5: + final String gradleCsvDir = System.getProperty("net.minecraftforge.gradle.GradleStart.csvDir"); + return gradleCsvDir != null ? new File(gradleCsvDir) : null; + default: + JFileChooser fc = new JFileChooser(mcDir); + fc.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + fc.setDialogTitle("Select an mcp conf dir for the deobfuscator."); + int ret = fc.showDialog(null, "Select"); + return ret == JFileChooser.APPROVE_OPTION ? fc.getSelectedFile() : null; + } + } + + public static File[] parseConfDir(File confDir) { + File srgDir = new File(confDir, "conf"); + if (!srgDir.exists()) srgDir = confDir; + + File srgs = new File(srgDir, "packaged.srg"); + if (!srgs.exists()) srgs = new File(srgDir, "joined.srg"); + if (!srgs.exists()) throw new RuntimeException("Could not find packaged.srg or joined.srg"); + + File mapDir = new File(confDir, "mappings"); + if (!mapDir.exists()) mapDir = confDir; + + File methods = new File(mapDir, "methods.csv"); + if (!methods.exists()) throw new RuntimeException("Could not find methods.csv"); + File fields = new File(mapDir, "fields.csv"); + if (!fields.exists()) throw new RuntimeException("Could not find fields.csv"); + + return new File[] { srgs, methods, fields }; + } + + private final HashMap fields = new HashMap<>(); + private final HashMap funcs = new HashMap<>(); + + public MCPRemapper() { + File[] mappings = getConfFiles(); + try { + Resources.readLines(mappings[1].toURI().toURL(), Charsets.UTF_8, this); + Resources.readLines(mappings[2].toURI().toURL(), Charsets.UTF_8, this); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public String mapMethodName(String owner, String name, String desc) { + String s = funcs.get(name); + return s == null ? name : s; + } + + @Override + public String mapFieldName(String owner, String name, String desc) { + String s = fields.get(name); + return s == null ? name : s; + } + + @Override + public boolean processLine(String line) throws IOException { + int i = line.indexOf(','); + String srg = line.substring(0, i); + int i2 = i + 1; + i = line.indexOf(',', i2); + String mcp = line.substring(i2, i); + (srg.startsWith("func") ? funcs : fields).put(srg, mcp); + return true; + } + + @Override + public Void getResult() { + return null; + } + } + + public static ObfRemapper obfMapper = new ObfRemapper(); + public static Remapper mcpMapper = null; + + public static void loadMCPRemapper() { + if (mcpMapper == null) mcpMapper = new MCPRemapper(); + } + + public static final boolean obfuscated; + + static { + boolean obf = true; + try { + obf = Launch.classLoader.getClassBytes("net.minecraft.world.World") == null; + } catch (IOException ignored) {} + obfuscated = obf; + if (!obf) loadMCPRemapper(); + } + + public String s_owner; + public String s_name; + public String s_desc; + + public ObfMapping(String owner) { + this(owner, "", ""); + } + + public ObfMapping(String owner, String name, String desc) { + this.s_owner = owner; + this.s_name = name; + this.s_desc = desc; + + if (s_owner.contains(".")) throw new IllegalArgumentException(s_owner); + } + + public ObfMapping(ObfMapping descmap, String subclass) { + this(subclass, descmap.s_name, descmap.s_desc); + } + + public static ObfMapping fromDesc(String s) { + int lastDot = s.lastIndexOf('.'); + if (lastDot < 0) return new ObfMapping(s, "", ""); + int sep = s.indexOf('('); // methods + int sep_end = sep; + if (sep < 0) { + sep = s.indexOf(' '); // some stuffs + sep_end = sep + 1; + } + if (sep < 0) { + sep = s.indexOf(':'); // fields + sep_end = sep + 1; + } + if (sep < 0) return new ObfMapping(s.substring(0, lastDot), s.substring(lastDot + 1), ""); + + return new ObfMapping(s.substring(0, lastDot), s.substring(lastDot + 1, sep), s.substring(sep_end)); + } + + public ObfMapping subclass(String subclass) { + return new ObfMapping(this, subclass); + } + + public boolean matches(MethodNode node) { + return s_name.equals(node.name) && s_desc.equals(node.desc); + } + + public boolean matches(MethodInsnNode node) { + return s_owner.equals(node.owner) && s_name.equals(node.name) && s_desc.equals(node.desc); + } + + public AbstractInsnNode toInsn(int opcode) { + if (isClass()) return new TypeInsnNode(opcode, s_owner); + else if (isMethod()) return new MethodInsnNode(opcode, s_owner, s_name, s_desc); + else return new FieldInsnNode(opcode, s_owner, s_name, s_desc); + } + + public void visitTypeInsn(MethodVisitor mv, int opcode) { + mv.visitTypeInsn(opcode, s_owner); + } + + public void visitMethodInsn(MethodVisitor mv, int opcode) { + mv.visitMethodInsn(opcode, s_owner, s_name, s_desc); + } + + public void visitFieldInsn(MethodVisitor mv, int opcode) { + mv.visitFieldInsn(opcode, s_owner, s_name, s_desc); + } + + public MethodVisitor visitMethod(ClassVisitor visitor, int access, String[] exceptions) { + return visitor.visitMethod(access, s_name, s_desc, null, exceptions); + } + + public FieldVisitor visitField(ClassVisitor visitor, int access, Object value) { + return visitor.visitField(access, s_name, s_desc, null, value); + } + + public boolean isClass(String name) { + return name.replace('.', '/').equals(s_owner); + } + + public boolean matches(String name, String desc) { + return s_name.equals(name) && s_desc.equals(desc); + } + + public boolean matches(FieldNode node) { + return s_name.equals(node.name) && s_desc.equals(node.desc); + } + + public boolean matches(FieldInsnNode node) { + return s_owner.equals(node.owner) && s_name.equals(node.name) && s_desc.equals(node.desc); + } + + public String javaClass() { + return s_owner.replace('/', '.'); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ObfMapping)) return false; + + ObfMapping desc = (ObfMapping) obj; + return s_owner.equals(desc.s_owner) && s_name.equals(desc.s_name) && s_desc.equals(desc.s_desc); + } + + @Override + public int hashCode() { + return Objects.hashCode(s_desc, s_name, s_owner); + } + + @Override + public String toString() { + if (s_name.length() == 0) return "[" + s_owner + "]"; + if (s_desc.length() == 0) return "[" + s_owner + "." + s_name + "]"; + return "[" + (isMethod() ? methodDesc() : fieldDesc()) + "]"; + } + + public String methodDesc() { + return s_owner + "." + s_name + s_desc; + } + + public String fieldDesc() { + return s_owner + "." + s_name + ":" + s_desc; + } + + public boolean isClass() { + return s_name.length() == 0; + } + + public boolean isMethod() { + return s_desc.contains("("); + } + + public boolean isField() { + return !isClass() && !isMethod(); + } + + public ObfMapping map(Remapper mapper) { + if (mapper == null) return this; + + if (isMethod()) s_name = mapper.mapMethodName(s_owner, s_name, s_desc); + else if (isField()) s_name = mapper.mapFieldName(s_owner, s_name, s_desc); + + s_owner = mapper.mapType(s_owner); + + if (isMethod()) s_desc = mapper.mapMethodDesc(s_desc); + else if (s_desc.length() > 0) s_desc = mapper.mapDesc(s_desc); + + return this; + } + + public ObfMapping toRuntime() { + map(mcpMapper); + return this; + } + + public ObfMapping toClassloading() { + if (!obfuscated) map(mcpMapper); + else if (obfMapper.isObf(s_owner)) map(obfMapper); + return this; + } + + public ObfMapping copy() { + return new ObfMapping(s_owner, s_name, s_desc); + } +} diff --git a/ccl/src/main/java/codechicken/lib/asm/RedirectorTransformer.java b/ccl/src/main/java/codechicken/lib/asm/RedirectorTransformer.java new file mode 100644 index 0000000..0d9d107 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/asm/RedirectorTransformer.java @@ -0,0 +1,207 @@ +package codechicken.lib.asm; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import net.minecraft.launchwrapper.IClassTransformer; +import net.minecraft.launchwrapper.Launch; + +import org.apache.commons.io.FileUtils; +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.util.ASMifier; +import org.objectweb.asm.util.Textifier; +import org.objectweb.asm.util.TraceClassVisitor; + +public class RedirectorTransformer implements IClassTransformer { + + private static final boolean DUMP_CLASSES = Boolean.parseBoolean(System.getProperty("ccl.dumpClass", "false")); + private static final String RenderStateClass = "codechicken/lib/render/CCRenderState"; + private static final Set redirectedFields = new HashSet<>(); + private static final Set redirectedSimpleMethods = new HashSet<>(); + private static final Set redirectedMethods = new HashSet<>(); + private static final ClassConstantPoolParser cstPoolParser; + + static { + Collections.addAll( + redirectedFields, + "pipeline", + "model", + "firstVertexIndex", + "lastVertexIndex", + "vertexIndex", + "baseColour", + "alphaOverride", + "useNormals", + "computeLighting", + "useColour", + "lightMatrix", + "vert", + "hasNormal", + "normal", + "hasColour", + "colour", + "hasBrightness", + "brightness", + "side", + "lc" + + ); + Collections.addAll(redirectedSimpleMethods, "reset", "pullLightmap", "pushLightmap", "setDynamic", "draw"); + Collections.addAll( + redirectedMethods, + "setPipeline", + "bindModel", + "setModel", + "setVertexRange", + "render", + "runPipeline", + "writeVert", + "setNormal", + "setColour", + "setBrightness", + "startDrawing"); + + cstPoolParser = new ClassConstantPoolParser(RenderStateClass); + } + + @Override + public byte[] transform(String name, String transformedName, byte[] basicClass) { + if (!cstPoolParser.find(basicClass)) { + return basicClass; + } + + final ClassReader cr = new ClassReader(basicClass); + final ClassNode cn = new ClassNode(); + cr.accept(cn, 0); + boolean changed = false; + + for (MethodNode mn : cn.methods) { + for (AbstractInsnNode node : mn.instructions.toArray()) { + if (node instanceof FieldInsnNode fNode) { + if (node.getOpcode() == Opcodes.GETSTATIC && redirectedFields.contains(fNode.name) + && fNode.owner.equals(RenderStateClass)) { + mn.instructions.insertBefore( + fNode, + new MethodInsnNode( + Opcodes.INVOKESTATIC, + fNode.owner, + "instance", + "()Lcodechicken/lib/render/CCRenderState;")); + fNode.setOpcode(Opcodes.GETFIELD); + changed = true; + } else if (node.getOpcode() == Opcodes.PUTSTATIC + && (redirectedFields.contains(fNode.name) && fNode.owner.equals(RenderStateClass))) { + InsnList beforePut = new InsnList(); + beforePut.add( + new MethodInsnNode( + Opcodes.INVOKESTATIC, + fNode.owner, + "instance", + "()Lcodechicken/lib/render/CCRenderState;")); + beforePut.add(new InsnNode(Opcodes.SWAP)); + mn.instructions.insertBefore(fNode, beforePut); + fNode.setOpcode(Opcodes.PUTFIELD); + changed = true; + + } + } else if (node.getOpcode() == Opcodes.INVOKESTATIC && node instanceof MethodInsnNode mNode) { + if (redirectedMethods.contains(mNode.name) && mNode.owner.equals(RenderStateClass)) { + mNode.name = mNode.name + "Static"; + changed = true; + } else if (redirectedSimpleMethods.contains(mNode.name) && mNode.owner.equals(RenderStateClass)) { + mn.instructions.insertBefore( + mNode, + new MethodInsnNode( + Opcodes.INVOKESTATIC, + mNode.owner, + "instance", + "()Lcodechicken/lib/render/CCRenderState;")); + mNode.setOpcode(Opcodes.INVOKEVIRTUAL); + changed = true; + } + } + } + } + + if (changed) { + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS); + cn.accept(cw); + final byte[] bytes = cw.toByteArray(); + if (DUMP_CLASSES) { + saveTransformedClass(bytes, transformedName); + saveTransformedClass(basicClass, transformedName + "_original"); + } + return bytes; + } + return basicClass; + } + + private File outputDir = null; + + private void saveTransformedClass(final byte[] data, final String transformedName) { + if (!DUMP_CLASSES) { + return; + } + + if (outputDir == null) { + outputDir = new File(Launch.minecraftHome, "ASM_REDIRECTOR"); + try { + FileUtils.deleteDirectory(outputDir); + } catch (IOException ignored) {} + if (!outputDir.exists()) { + // noinspection ResultOfMethodCallIgnored + outputDir.mkdirs(); + } + } + + final String fileName = transformedName.replace('.', File.separatorChar); + final File classFile = new File(outputDir, fileName + ".class"); + final File bytecodeFile = new File(outputDir, fileName + "_BYTE.txt"); + final File asmifiedFile = new File(outputDir, fileName + "_ASM.txt"); + final File outDir = classFile.getParentFile(); + if (!outDir.exists()) { + outDir.mkdirs(); + } + if (classFile.exists()) { + classFile.delete(); + } + try (final OutputStream output = Files.newOutputStream(classFile.toPath())) { + output.write(data); + } catch (IOException e) { + e.printStackTrace(); + } + if (bytecodeFile.exists()) { + bytecodeFile.delete(); + } + try (final OutputStream output = Files.newOutputStream(bytecodeFile.toPath())) { + final ClassReader classReader = new ClassReader(data); + classReader.accept(new TraceClassVisitor(null, new Textifier(), new PrintWriter(output)), 0); + } catch (IOException e) { + e.printStackTrace(); + } + if (asmifiedFile.exists()) { + asmifiedFile.delete(); + } + try (final OutputStream output = Files.newOutputStream(asmifiedFile.toPath())) { + final ClassReader classReader = new ClassReader(data); + classReader.accept(new TraceClassVisitor(null, new ASMifier(), new PrintWriter(output)), 0); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/ccl/src/main/java/codechicken/lib/colour/Colour.java b/ccl/src/main/java/codechicken/lib/colour/Colour.java new file mode 100644 index 0000000..c73a5e8 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/colour/Colour.java @@ -0,0 +1,158 @@ +package codechicken.lib.colour; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.lwjgl.opengl.GL11; + +import codechicken.lib.config.ConfigTag.IConfigType; +import codechicken.lib.math.MathHelper; +import codechicken.lib.util.Copyable; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public abstract class Colour implements Copyable { + + public static IConfigType configRGB = new IConfigType() { + + @Override + public String configValue(Colour entry) { + String s = Long.toString(((long) entry.rgb()) << 32 >>> 32, 16); + while (s.length() < 6) s = "0" + s; + return "0x" + s.toUpperCase(); + } + + private final Pattern patternRGB = Pattern.compile("(\\d+),(\\d+),(\\d+)"); + + @Override + public Colour valueOf(String text) throws Exception { + Matcher matcherRGB = patternRGB.matcher(text.replaceAll("\\s", "")); + if (matcherRGB.matches()) return new ColourRGBA( + Integer.parseInt(matcherRGB.group(1)), + Integer.parseInt(matcherRGB.group(2)), + Integer.parseInt(matcherRGB.group(3)), + 0xFF); + + int hex = (int) Long.parseLong(text.replace("0x", ""), 16); + return new ColourRGBA(hex << 8 | 0xFF); + } + }; + + public byte r; + public byte g; + public byte b; + public byte a; + + public Colour(int r, int g, int b, int a) { + this.r = (byte) r; + this.g = (byte) g; + this.b = (byte) b; + this.a = (byte) a; + } + + public Colour(Colour colour) { + r = colour.r; + g = colour.g; + b = colour.b; + a = colour.a; + } + + @SideOnly(Side.CLIENT) + public void glColour() { + GL11.glColor4ub(r, g, b, a); + } + + @SideOnly(Side.CLIENT) + public void glColour(int a) { + GL11.glColor4ub(r, g, b, (byte) a); + } + + public abstract int pack(); + + @Override + public String toString() { + return getClass().getSimpleName() + "[0x" + Integer.toHexString(pack()).toUpperCase() + "]"; + } + + public Colour add(Colour colour2) { + a += colour2.a; + r += colour2.r; + g += colour2.g; + b += colour2.b; + return this; + } + + public Colour sub(Colour colour2) { + int ia = (a & 0xFF) - (colour2.a & 0xFF); + int ir = (r & 0xFF) - (colour2.r & 0xFF); + int ig = (g & 0xFF) - (colour2.g & 0xFF); + int ib = (b & 0xFF) - (colour2.b & 0xFF); + a = (byte) (ia < 0 ? 0 : ia); + r = (byte) (ir < 0 ? 0 : ir); + g = (byte) (ig < 0 ? 0 : ig); + b = (byte) (ib < 0 ? 0 : ib); + return this; + } + + public Colour invert() { + a = (byte) (0xFF - (a & 0xFF)); + r = (byte) (0xFF - (r & 0xFF)); + g = (byte) (0xFF - (g & 0xFF)); + b = (byte) (0xFF - (b & 0xFF)); + return this; + } + + public Colour multiply(Colour colour2) { + a = (byte) ((a & 0xFF) * ((colour2.a & 0xFF) / 255D)); + r = (byte) ((r & 0xFF) * ((colour2.r & 0xFF) / 255D)); + g = (byte) ((g & 0xFF) * ((colour2.g & 0xFF) / 255D)); + b = (byte) ((b & 0xFF) * ((colour2.b & 0xFF) / 255D)); + return this; + } + + public Colour scale(double d) { + a = (byte) ((a & 0xFF) * d); + r = (byte) ((r & 0xFF) * d); + g = (byte) ((g & 0xFF) * d); + b = (byte) ((b & 0xFF) * d); + return this; + } + + public Colour interpolate(Colour colour2, double d) { + return this.add(colour2.copy().sub(this).scale(d)); + } + + public Colour multiplyC(double d) { + r = (byte) MathHelper.clip((r & 0xFF) * d, 0, 255); + g = (byte) MathHelper.clip((g & 0xFF) * d, 0, 255); + b = (byte) MathHelper.clip((b & 0xFF) * d, 0, 255); + + return this; + } + + public abstract Colour copy(); + + public int rgb() { + return (r & 0xFF) << 16 | (g & 0xFF) << 8 | (b & 0xFF); + } + + public int argb() { + return (a & 0xFF) << 24 | (r & 0xFF) << 16 | (g & 0xFF) << 8 | (b & 0xFF); + } + + public int rgba() { + return (r & 0xFF) << 24 | (g & 0xFF) << 16 | (b & 0xFF) << 8 | (a & 0xFF); + } + + public Colour set(Colour colour) { + r = colour.r; + g = colour.g; + b = colour.b; + a = colour.a; + return this; + } + + public boolean equals(Colour colour) { + return colour != null && rgba() == colour.rgba(); + } +} diff --git a/ccl/src/main/java/codechicken/lib/colour/ColourARGB.java b/ccl/src/main/java/codechicken/lib/colour/ColourARGB.java new file mode 100644 index 0000000..56483de --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/colour/ColourARGB.java @@ -0,0 +1,28 @@ +package codechicken.lib.colour; + +public class ColourARGB extends Colour { + + public ColourARGB(int colour) { + super((colour >> 16) & 0xFF, (colour >> 8) & 0xFF, colour & 0xFF, (colour >> 24) & 0xFF); + } + + public ColourARGB(int a, int r, int g, int b) { + super(r, g, b, a); + } + + public ColourARGB(ColourARGB colour) { + super(colour); + } + + public ColourARGB copy() { + return new ColourARGB(this); + } + + public int pack() { + return pack(this); + } + + public static int pack(Colour colour) { + return (colour.a & 0xFF) << 24 | (colour.r & 0xFF) << 16 | (colour.g & 0xFF) << 8 | (colour.b & 0xFF); + } +} diff --git a/ccl/src/main/java/codechicken/lib/colour/ColourRGBA.java b/ccl/src/main/java/codechicken/lib/colour/ColourRGBA.java new file mode 100644 index 0000000..a5ff30c --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/colour/ColourRGBA.java @@ -0,0 +1,50 @@ +package codechicken.lib.colour; + +public class ColourRGBA extends Colour { + + public ColourRGBA(int colour) { + super((colour >> 24) & 0xFF, (colour >> 16) & 0xFF, (colour >> 8) & 0xFF, colour & 0xFF); + } + + public ColourRGBA(double r, double g, double b, double a) { + super((int) (255 * r), (int) (255 * g), (int) (255 * b), (int) (255 * a)); + } + + public ColourRGBA(int r, int g, int b, int a) { + super(r, g, b, a); + } + + public ColourRGBA(ColourRGBA colour) { + super(colour); + } + + public int pack() { + return pack(this); + } + + @Override + public Colour copy() { + return new ColourRGBA(this); + } + + public static int pack(Colour colour) { + return (colour.r & 0xFF) << 24 | (colour.g & 0xFF) << 16 | (colour.b & 0xFF) << 8 | (colour.a & 0xFF); + } + + public static int multiply(int c1, int c2) { + if (c1 == -1) return c2; + if (c2 == -1) return c1; + int r = (((c1 >>> 24) * (c2 >>> 24)) & 0xFF00) << 16; + int g = (((c1 >> 16 & 0xFF) * (c2 >> 16 & 0xFF)) & 0xFF00) << 8; + int b = ((c1 >> 8 & 0xFF) * (c2 >> 8 & 0xFF)) & 0xFF00; + int a = ((c1 & 0xFF) * (c2 & 0xFF)) >> 8; + return r | g | b | a; + } + + public static int multiplyC(int c, float f) { + int r = (int) ((c >>> 24) * f); + int g = (int) ((c >> 16 & 0xFF) * f); + int b = (int) ((c >> 8 & 0xFF) * f); + return r << 24 | g << 16 | b << 8 | c & 0xFF; + } +} diff --git a/ccl/src/main/java/codechicken/lib/colour/CustomGradient.java b/ccl/src/main/java/codechicken/lib/colour/CustomGradient.java new file mode 100644 index 0000000..4f228c7 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/colour/CustomGradient.java @@ -0,0 +1,32 @@ +package codechicken.lib.colour; + +import java.awt.image.BufferedImage; + +import net.minecraft.util.ResourceLocation; + +import codechicken.lib.math.MathHelper; +import codechicken.lib.render.TextureUtils; + +public class CustomGradient { + + public int[] gradient; + + public CustomGradient(ResourceLocation textureFile) { + BufferedImage img = TextureUtils.loadBufferedImage(textureFile); + if (img == null) throw new RuntimeException("File not found: " + textureFile.toString()); + + int[] data = new int[img.getWidth()]; + img.getRGB(0, 0, img.getWidth(), 1, data, 0, img.getWidth()); + gradient = new int[img.getWidth()]; + for (int i = 0; i < data.length; i++) gradient[i] = (data[i] << 8) | (((data[i]) >> 24) & 0xFF); + } + + public ColourRGBA getColour(double position) { + return new ColourRGBA(getColourI(position)); + } + + public int getColourI(double position) { + int off = (int) MathHelper.clip(gradient.length * position, 0, gradient.length - 1); + return gradient[off]; + } +} diff --git a/ccl/src/main/java/codechicken/lib/config/ConfigFile.java b/ccl/src/main/java/codechicken/lib/config/ConfigFile.java new file mode 100644 index 0000000..31a18de --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/config/ConfigFile.java @@ -0,0 +1,110 @@ +package codechicken.lib.config; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.io.PrintWriter; + +public class ConfigFile extends ConfigTagParent { + + public static final byte[] crlf = new byte[] { 0xD, 0xA }; + + public File file; + private boolean loading; + + public ConfigFile(File file) { + newlinemode = 2; + load(file); + } + + protected ConfigFile() {} + + protected void load(File file) { + try { + if (!file.getParentFile().exists()) file.getParentFile().mkdirs(); + if (!file.exists()) file.createNewFile(); + } catch (IOException e) { + throw new RuntimeException(e); + } + this.file = file; + loadConfig(); + } + + protected void loadConfig() { + loading = true; + BufferedReader reader; + try { + reader = new BufferedReader(new FileReader(file)); + + while (true) { + reader.mark(2000); + String line = reader.readLine(); + if (line != null && line.startsWith("#")) { + if (comment == null || comment.equals("")) comment = line.substring(1); + else comment = comment + "\n" + line.substring(1); + } else { + reader.reset(); + break; + } + } + loadChildren(reader); + reader.close(); + + } catch (IOException e) { + throw new RuntimeException(e); + } + + loading = false; + } + + @Override + public ConfigFile setComment(String header) { + super.setComment(header); + return this; + } + + @Override + public ConfigFile setSortMode(int mode) { + super.setSortMode(mode); + return this; + } + + @Override + public String getNameQualifier() { + return ""; + } + + public static String readLine(BufferedReader reader) throws IOException { + String line = reader.readLine(); + return line == null ? null : line.replace("\t", ""); + } + + public static void writeLine(PrintWriter writer, String line, int tabs) { + for (int i = 0; i < tabs; i++) writer.print('\t'); + + writer.println(line); + } + + public void saveConfig() { + if (loading) return; + + PrintWriter writer; + try { + writer = new PrintWriter(file); + } catch (FileNotFoundException e) { + throw new RuntimeException(e); + } + + writeComment(writer, 0); + ConfigFile.writeLine(writer, "", 0); + saveTagTree(writer, 0, ""); + writer.flush(); + writer.close(); + } + + public boolean isLoading() { + return loading; + } +} diff --git a/ccl/src/main/java/codechicken/lib/config/ConfigTag.java b/ccl/src/main/java/codechicken/lib/config/ConfigTag.java new file mode 100644 index 0000000..29e8eb3 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/config/ConfigTag.java @@ -0,0 +1,225 @@ +package codechicken.lib.config; + +import java.io.PrintWriter; + +public class ConfigTag extends ConfigTagParent { + + public interface IConfigType { + + public String configValue(T entry); + + public T valueOf(String text) throws Exception; + } + + public ConfigTag(ConfigTagParent parent, String name) { + this.parent = parent; + this.name = name; + qualifiedname = parent.getNameQualifier() + name; + newline = parent.newlinemode == 2; + parent.addChild(this); + } + + @Override + public String getNameQualifier() { + return qualifiedname + "."; + } + + @Override + public void saveConfig() { + parent.saveConfig(); + } + + /** + * Called when the tag is loaded from a config file as opposed to constructed by a mod + * + * @return this + */ + public ConfigTag onLoaded() { + return this; + } + + public void setValue(String value) { + this.value = value; + saveConfig(); + } + + public void setDefaultValue(String defaultValue) { + if (value == null) { + value = defaultValue; + saveConfig(); + } + } + + public void setIntValue(int i) { + setValue(Integer.toString(i)); + } + + public void setBooleanValue(boolean b) { + setValue(Boolean.toString(b)); + } + + public void setHexValue(int i) { + setValue("0x" + Long.toString(((long) i) << 32 >>> 32, 16)); + } + + public void set(IConfigType type, T entry) { + setValue(type.configValue(entry)); + } + + public String getValue() { + return value; + } + + public String getValue(String defaultValue) { + setDefaultValue(defaultValue); + return value; + } + + public int getIntValue() { + return Integer.parseInt(getValue()); + } + + public int getIntValue(int defaultValue) { + try { + if (value != null) return getIntValue(); + } catch (NumberFormatException ignored) {} + + setIntValue(defaultValue); + return defaultValue; + } + + public boolean getBooleanValue() { + String value = getValue(); + if (value != null && (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("yes"))) return true; + else if (value != null && (value.equalsIgnoreCase("false") || value.equalsIgnoreCase("no"))) return false; + + throw new NumberFormatException(qualifiedname + ".value=" + value); + } + + public boolean getBooleanValue(boolean defaultValue) { + try { + if (value != null) return getBooleanValue(); + } catch (NumberFormatException ignored) {} + + setBooleanValue(defaultValue); + return defaultValue; + } + + public int getHexValue() { + return (int) Long.parseLong(getValue().replace("0x", ""), 16); + } + + public int getHexValue(int defaultValue) { + try { + if (value != null) return getHexValue(); + } catch (NumberFormatException ignored) {} + + setHexValue(defaultValue); + return defaultValue; + } + + public T get(IConfigType type) { + try { + return type.valueOf(getValue()); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public T get(IConfigType type, T defaultValue) { + try { + if (value != null) return get(type); + } catch (Exception ignored) {} + + set(type, defaultValue); + return defaultValue; + } + + public void save(PrintWriter writer, int tabs, String bracequalifier, boolean first) { + String vname; + if (qualifiedname.contains(".") && bracequalifier.length() > 0) + vname = qualifiedname.substring(bracequalifier.length() + 1); + else vname = qualifiedname; + + if (newline && !first) ConfigFile.writeLine(writer, "", tabs); + + writeComment(writer, tabs); + if (value != null) ConfigFile.writeLine(writer, vname + "=" + value, tabs); + + if (!hasChildTags()) return; + + if (brace) { + if (value == null) ConfigFile.writeLine(writer, vname, tabs); + ConfigFile.writeLine(writer, "{", tabs); + saveTagTree(writer, tabs + 1, qualifiedname); + ConfigFile.writeLine(writer, "}", tabs); + } else { + saveTagTree(writer, tabs, bracequalifier); + } + } + + @Override + public ConfigTag setComment(String comment) { + super.setComment(comment); + return this; + } + + @Override + public ConfigTag setSortMode(int mode) { + super.setSortMode(mode); + return this; + } + + public ConfigTag setNewLine(boolean b) { + newline = b; + saveConfig(); + return this; + } + + public ConfigTag useBraces() { + brace = true; + if (parent.newlinemode == 1) newline = true; + + saveConfig(); + return this; + } + + public ConfigTag setPosition(int pos) { + position = pos; + saveConfig(); + return this; + } + + public boolean containsTag(String tagname) { + return getTag(tagname, false) != null; + } + + public int getId(String name, int defaultValue) { + return getTag(name).getIntValue(defaultValue); + } + + public int getId(String name) { + int ret = getId(name, IDBase); + IDBase = ret + 1; + return ret; + } + + public int getAcheivementId(String name, int defaultValue) { + return getTag(name).getIntValue(defaultValue); + } + + public ConfigTag setBaseID(int i) { + IDBase = i; + return this; + } + + public ConfigTagParent parent; + public String name; + public String qualifiedname; + public String value; + public boolean brace; + public boolean newline; + public int position = Integer.MAX_VALUE; + + private int IDBase; +} diff --git a/ccl/src/main/java/codechicken/lib/config/ConfigTagParent.java b/ccl/src/main/java/codechicken/lib/config/ConfigTagParent.java new file mode 100644 index 0000000..79514b9 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/config/ConfigTagParent.java @@ -0,0 +1,189 @@ +package codechicken.lib.config; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Map; +import java.util.Map.Entry; +import java.util.TreeMap; + +public abstract class ConfigTagParent { + + public static class TagOrderComparator implements Comparator { + + int sortMode; + + public TagOrderComparator(int sortMode) { + this.sortMode = sortMode; + } + + public int compare(ConfigTag o1, ConfigTag o2) { + if (o1.position != o2.position) return compareInt(o1.position, o2.position); + if (o1.brace != o2.brace) return o1.brace ? 1 : -1; // braced one goes after + switch (sortMode) { + case 1: + if (o1.value == o2.value) return 0; + if (o1.value == null) return 1; + if (o2.value == null) return -1; + return o1.value.compareTo(o2.value); + default: + return o1.name.compareTo(o2.name); + } + } + + private int compareInt(int a, int b) { + return a == b ? 0 : a < b ? -1 : 1; + } + } + + private TreeMap childtags = new TreeMap(); + public String comment; + /** + * 0 = name, 1 = value + */ + public int sortMode = 0; + /** + * The mode for determining when child tags should leave a blank line between them and the one above 0 = never, 1 = + * when braced, 2 = always + */ + public int newlinemode = 1; + + public abstract void saveConfig(); + + public abstract String getNameQualifier(); + + public ConfigTagParent setComment(String comment) { + this.comment = comment; + saveConfig(); + return this; + } + + public ConfigTagParent setSortMode(int mode) { + sortMode = mode; + saveConfig(); + return this; + } + + public ConfigTagParent setNewLineMode(int mode) { + newlinemode = mode; + for (Entry entry : childtags.entrySet()) { + ConfigTag tag = entry.getValue(); + if (newlinemode == 0) tag.newline = false; + else if (newlinemode == 1) tag.newline = tag.brace; + else if (newlinemode == 2) tag.newline = true; + } + saveConfig(); + return this; + } + + public Map childTagMap() { + return childtags; + } + + public boolean hasChildTags() { + return !childtags.isEmpty(); + } + + public boolean containsTag(String tagname) { + return getTag(tagname, false) != null; + } + + public ConfigTag getNewTag(String tagname) { + return new ConfigTag(this, tagname); + } + + public ConfigTag getTag(String tagname, boolean create) { + int dotpos = tagname.indexOf("."); + String basetagname = dotpos == -1 ? tagname : tagname.substring(0, dotpos); + ConfigTag basetag = childtags.get(basetagname); + if (basetag == null) { + if (!create) return null; + + basetag = getNewTag(basetagname); + saveConfig(); + } + if (dotpos == -1) return basetag; + + return basetag.getTag(tagname.substring(dotpos + 1), create); + } + + public ConfigTag getTag(String tagname) { + return getTag(tagname, true); + } + + public boolean removeTag(String tagname) { + ConfigTag tag = getTag(tagname, false); + if (tag == null) return false; + + int dotpos = tagname.lastIndexOf("."); + String lastpart = dotpos == -1 ? tagname : tagname.substring(dotpos + 1, tagname.length()); + if (tag.parent != null) { + boolean ret = tag.parent.childtags.remove(lastpart) != null; + if (ret) saveConfig(); + return ret; + } + + return false; + } + + public void addChild(ConfigTag tag) { + childtags.put(tag.name, tag); + } + + public ArrayList getSortedTagList() { + ArrayList taglist = new ArrayList(childtags.size()); + for (Entry tag : childtags.entrySet()) taglist.add((T) tag.getValue()); + + Collections.sort(taglist, new TagOrderComparator(sortMode)); + return taglist; + } + + public void loadChildren(BufferedReader reader) { + String comment = ""; + String bracequalifier = ""; + try { + while (true) { + String line = ConfigFile.readLine(reader); + if (line == null) break; + if (line.startsWith("#")) { + if (comment.equals("")) comment = line.substring(1); + else comment = comment + "\n" + line.substring(1); + } else if (line.contains("=")) { + String qualifiedname = line.substring(0, line.indexOf("=")); + getTag(qualifiedname).onLoaded().setComment(comment) + .setValue(line.substring(line.indexOf("=") + 1)); + comment = ""; + bracequalifier = qualifiedname; + } else if (line.equals("{")) { + getTag(bracequalifier).setComment(comment).useBraces().loadChildren(reader); + comment = ""; + bracequalifier = ""; + } else if (line.equals("}")) { + break; + } else { + bracequalifier = line; + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public void saveTagTree(PrintWriter writer, int tabs, String bracequalifier) { + boolean first = true; + for (ConfigTag tag : getSortedTagList()) { + tag.save(writer, tabs, bracequalifier, first); + first = false; + } + } + + public void writeComment(PrintWriter writer, int tabs) { + if (comment != null && !comment.equals("")) { + String[] comments = comment.split("\n"); + for (int i = 0; i < comments.length; i++) ConfigFile.writeLine(writer, "#" + comments[i], tabs); + } + } +} diff --git a/ccl/src/main/java/codechicken/lib/config/DefaultingConfigFile.java b/ccl/src/main/java/codechicken/lib/config/DefaultingConfigFile.java new file mode 100644 index 0000000..237b7e5 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/config/DefaultingConfigFile.java @@ -0,0 +1,16 @@ +package codechicken.lib.config; + +import java.io.File; + +public class DefaultingConfigFile extends ConfigFile { + + public DefaultingConfigFile(File file) { + super(); + if (file.exists()) load(file); + } + + @Override + public void saveConfig() { + if (file != null) super.saveConfig(); + } +} diff --git a/ccl/src/main/java/codechicken/lib/config/SimpleProperties.java b/ccl/src/main/java/codechicken/lib/config/SimpleProperties.java new file mode 100644 index 0000000..4ec65e0 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/config/SimpleProperties.java @@ -0,0 +1,125 @@ +package codechicken.lib.config; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.io.PrintStream; +import java.nio.charset.Charset; +import java.util.HashMap; +import java.util.Map.Entry; + +public class SimpleProperties { + + public HashMap propertyMap = new HashMap(); + public File propertyFile; + public boolean saveOnChange = false; + public String encoding; + + private boolean loading = false; + + public SimpleProperties(File file, boolean saveOnChange, String encoding) { + propertyFile = file; + this.saveOnChange = saveOnChange; + this.encoding = encoding; + } + + public SimpleProperties(File file, boolean saveOnChange) { + this(file, saveOnChange, Charset.defaultCharset().name()); + } + + public SimpleProperties(File file) { + this(file, true); + } + + public void load() { + clear(); + loading = true; + + try { + BufferedReader reader = new BufferedReader( + new InputStreamReader(new FileInputStream(propertyFile), encoding)); + while (true) { + String read = reader.readLine(); + if (read == null) break; + + int equalIndex = read.indexOf('='); + if (equalIndex == -1) continue; + + setProperty(read.substring(0, equalIndex), read.substring(equalIndex + 1)); + } + reader.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + loading = false; + } + + public void save() { + try { + PrintStream writer = new PrintStream(propertyFile); + + for (Entry entry : propertyMap.entrySet()) { + writer.println(entry.getKey() + "=" + entry.getValue()); + } + + writer.close(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public void clear() { + propertyMap.clear(); + } + + public boolean hasProperty(String key) { + return propertyMap.containsKey(key); + } + + public void removeProperty(String key) { + if (propertyMap.remove(key) != null && saveOnChange && !loading) save(); + } + + public void setProperty(String key, int value) { + setProperty(key, Integer.toString(value)); + } + + public void setProperty(String key, boolean value) { + setProperty(key, Boolean.toString(value)); + } + + public void setProperty(String key, String value) { + propertyMap.put(key, value); + if (saveOnChange && !loading) save(); + } + + public int getProperty(String property, int defaultvalue) { + try { + return Integer.parseInt(getProperty(property, Integer.toString(defaultvalue))); + } catch (NumberFormatException nfe) { + return defaultvalue; + } + } + + public boolean getProperty(String property, boolean defaultvalue) { + try { + return Boolean.parseBoolean(getProperty(property, Boolean.toString(defaultvalue))); + } catch (NumberFormatException nfe) { + return defaultvalue; + } + } + + public String getProperty(String property, String defaultvalue) { + String value = propertyMap.get(property); + if (value == null) { + setProperty(property, defaultvalue); + return defaultvalue; + } + return value; + } + + public String getProperty(String property) { + return propertyMap.get(property); + } +} diff --git a/ccl/src/main/java/codechicken/lib/data/MCDataInput.java b/ccl/src/main/java/codechicken/lib/data/MCDataInput.java new file mode 100644 index 0000000..df31842 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/data/MCDataInput.java @@ -0,0 +1,46 @@ +package codechicken.lib.data; + +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.fluids.FluidStack; + +import codechicken.lib.vec.BlockCoord; + +public interface MCDataInput { + + public long readLong(); + + public int readInt(); + + public short readShort(); + + public int readUShort(); + + public byte readByte(); + + public short readUByte(); + + public double readDouble(); + + public float readFloat(); + + public boolean readBoolean(); + + public char readChar(); + + public int readVarShort(); + + public int readVarInt(); + + public byte[] readByteArray(int length); + + public String readString(); + + public BlockCoord readCoord(); + + public NBTTagCompound readNBTTagCompound(); + + public ItemStack readItemStack(); + + public FluidStack readFluidStack(); +} diff --git a/ccl/src/main/java/codechicken/lib/data/MCDataInputStream.java b/ccl/src/main/java/codechicken/lib/data/MCDataInputStream.java new file mode 100644 index 0000000..210a36c --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/data/MCDataInputStream.java @@ -0,0 +1,17 @@ +package codechicken.lib.data; + +import java.io.InputStream; + +public class MCDataInputStream extends InputStream { + + private MCDataInput in; + + public MCDataInputStream(MCDataInput in) { + this.in = in; + } + + @Override + public int read() { + return in.readByte() & 0xFF; + } +} diff --git a/ccl/src/main/java/codechicken/lib/data/MCDataOutput.java b/ccl/src/main/java/codechicken/lib/data/MCDataOutput.java new file mode 100644 index 0000000..63f8b17 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/data/MCDataOutput.java @@ -0,0 +1,44 @@ +package codechicken.lib.data; + +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.fluids.FluidStack; + +import codechicken.lib.vec.BlockCoord; + +public interface MCDataOutput { + + public MCDataOutput writeLong(long l); + + public MCDataOutput writeInt(int i); + + public MCDataOutput writeShort(int s); + + public MCDataOutput writeByte(int b); + + public MCDataOutput writeDouble(double d); + + public MCDataOutput writeFloat(float f); + + public MCDataOutput writeBoolean(boolean b); + + public MCDataOutput writeChar(char c); + + public MCDataOutput writeVarInt(int i); + + public MCDataOutput writeVarShort(int s); + + public MCDataOutput writeByteArray(byte[] array); + + public MCDataOutput writeString(String s); + + public MCDataOutput writeCoord(int x, int y, int z); + + public MCDataOutput writeCoord(BlockCoord coord); + + public MCDataOutput writeNBTTagCompound(NBTTagCompound tag); + + public MCDataOutput writeItemStack(ItemStack stack); + + public MCDataOutput writeFluidStack(FluidStack liquid); +} diff --git a/ccl/src/main/java/codechicken/lib/data/MCDataOutputStream.java b/ccl/src/main/java/codechicken/lib/data/MCDataOutputStream.java new file mode 100644 index 0000000..ec9cfd8 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/data/MCDataOutputStream.java @@ -0,0 +1,17 @@ +package codechicken.lib.data; + +import java.io.OutputStream; + +public class MCDataOutputStream extends OutputStream { + + private MCDataOutput out; + + public MCDataOutputStream(MCDataOutput out) { + this.out = out; + } + + @Override + public void write(int b) { + out.writeByte(b); + } +} diff --git a/ccl/src/main/java/codechicken/lib/data/MCDataOutputWrapper.java b/ccl/src/main/java/codechicken/lib/data/MCDataOutputWrapper.java new file mode 100644 index 0000000..baea780 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/data/MCDataOutputWrapper.java @@ -0,0 +1,240 @@ +package codechicken.lib.data; + +import java.io.DataOutput; +import java.io.IOException; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.CompressedStreamTools; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.fluids.FluidStack; + +import org.apache.commons.lang3.Validate; + +import com.google.common.base.Charsets; + +import codechicken.lib.vec.BlockCoord; +import cpw.mods.fml.common.network.ByteBufUtils; + +public class MCDataOutputWrapper implements MCDataOutput { + + /** + * Mimics ByteBufUtils Write an integer using variable length encoding. + * + * @param to The buffer to write to + * @param i The integer to write + */ + public static void writeVarInt(DataOutput to, int i) throws IOException { + while ((i & 0x80) != 0) { + to.writeByte(i & 0x7F | 0x80); + i >>>= 7; + } + + to.writeByte(i); + } + + /** + * Mimics ByteBufUtils Write an extended short using a short and a byte if necessary + * + * @param to The buffer to write to + * @param s The short to write, less than 0x7FFFFF + */ + public static void writeVarShort(DataOutput to, int s) throws IOException { + int low = s & 0x7FFF; + int high = (s & 0x7F8000) >> 15; + if (high != 0) low |= 0x8000; + to.writeShort(low); + if (high != 0) to.writeByte(high); + } + + /** + * Mimics ByteBufUtils Write a String with UTF8 byte encoding to the buffer. It is encoded as [] + * + * @param to the data output to write to + * @param string The string to write + */ + public static void writeUTF8String(DataOutput to, String string) throws IOException { + byte[] utf8Bytes = string.getBytes(Charsets.UTF_8); + Validate.isTrue( + ByteBufUtils.varIntByteCount(utf8Bytes.length) < 3, + "The string is too long for this encoding."); + writeVarInt(to, utf8Bytes.length); + to.write(utf8Bytes); + } + + public DataOutput dataout; + + public MCDataOutputWrapper(DataOutput out) { + dataout = out; + } + + public MCDataOutputWrapper writeBoolean(boolean b) { + try { + dataout.writeBoolean(b); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + public MCDataOutputWrapper writeByte(int b) { + try { + dataout.writeByte(b); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + public MCDataOutputWrapper writeShort(int s) { + try { + dataout.writeShort(s); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + public MCDataOutputWrapper writeInt(int i) { + try { + dataout.writeInt(i); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + public MCDataOutputWrapper writeFloat(float f) { + try { + dataout.writeFloat(f); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + public MCDataOutputWrapper writeDouble(double d) { + try { + dataout.writeDouble(d); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + public MCDataOutputWrapper writeLong(long l) { + try { + dataout.writeLong(l); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + @Override + public MCDataOutputWrapper writeChar(char c) { + try { + dataout.writeChar(c); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + @Override + public MCDataOutput writeVarInt(int i) { + try { + writeVarInt(dataout, i); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + @Override + public MCDataOutput writeVarShort(int s) { + try { + writeVarShort(dataout, s); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + public MCDataOutputWrapper writeByteArray(byte[] barray) { + try { + dataout.write(barray); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + public MCDataOutputWrapper writeCoord(int x, int y, int z) { + writeInt(x); + writeInt(y); + writeInt(z); + return this; + } + + public MCDataOutputWrapper writeCoord(BlockCoord coord) { + writeInt(coord.x); + writeInt(coord.y); + writeInt(coord.z); + return this; + } + + public MCDataOutputWrapper writeString(String s) { + try { + writeUTF8String(dataout, s); + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + public MCDataOutputWrapper writeItemStack(ItemStack stack) { + writeItemStack(stack, false); + return this; + } + + public MCDataOutputWrapper writeItemStack(ItemStack stack, boolean large) { + if (stack == null) { + writeInt(-1); + } else { + writeInt(Item.getIdFromItem(stack.getItem())); + if (large) writeInt(stack.stackSize); + else writeByte(stack.stackSize); + writeShort(stack.getItemDamage()); + writeNBTTagCompound(stack.stackTagCompound); + } + return this; + } + + public MCDataOutputWrapper writeNBTTagCompound(NBTTagCompound compound) { + try { + if (compound == null) { + writeShort(-1); + } else { + byte[] bytes = CompressedStreamTools.compress(compound); + writeShort((short) bytes.length); + writeByteArray(bytes); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + return this; + } + + public MCDataOutputWrapper writeFluidStack(FluidStack fluid) { + if (fluid == null) { + writeShort(-1); + } else { + writeShort(fluid.getFluidID()); + writeVarInt(fluid.amount); + writeNBTTagCompound(fluid.tag); + } + return this; + } +} diff --git a/ccl/src/main/java/codechicken/lib/gui/Canvas9Seg.java b/ccl/src/main/java/codechicken/lib/gui/Canvas9Seg.java new file mode 100644 index 0000000..15b3f22 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/gui/Canvas9Seg.java @@ -0,0 +1,81 @@ +package codechicken.lib.gui; + +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.util.ResourceLocation; + +import codechicken.lib.render.CCRenderState; +import codechicken.lib.render.TextureDataHolder; +import codechicken.lib.render.TextureUtils; + +public class Canvas9Seg { + + public final ResourceLocation tex; + public float[] seg_u = new float[4]; + public float[] seg_v = new float[4]; + public int[] seg_w = new int[3]; + public int[] seg_h = new int[3]; + + public Canvas9Seg(ResourceLocation tex) { + this.tex = tex; + load(); + } + + private int[] readMarkers(TextureDataHolder data, int stride, int size) { + int[] markers = new int[4]; + + int marker = 1; + int prev_col = data.data[0]; + for (int i = 1; i < size; i++) { + if (data.data[i * stride] != prev_col) { + markers[marker] = i; + prev_col = data.data[i * stride]; + if (++marker == 4) break; + } + } + + markers[0] += 1; + markers[3] -= 1; + return markers; + } + + private void parseMarkers(TextureDataHolder data, int stride, int size, int[] sizes, float[] texcoords) { + int[] markers = readMarkers(data, stride, size); + for (int i = 0; i < 4; i++) { + texcoords[i] = markers[i] / (float) size; + if (i > 0) sizes[i - 1] = markers[i] - markers[i - 1]; + } + } + + private void load() { + TextureDataHolder data = TextureUtils.loadTexture(tex); + parseMarkers(data, 1, data.width, seg_w, seg_u); + parseMarkers(data, data.width, data.height, seg_h, seg_v); + } + + private void drawSeg(int[] sw, int[] sh, int seg) { + Tessellator t = Tessellator.instance; + int u = seg % 3; + int v = seg / 3; + t.addVertexWithUV(sw[u], sh[v], 0, seg_u[u], seg_v[v]); + t.addVertexWithUV(sw[u], sh[v + 1], 0, seg_u[u], seg_v[v + 1]); + t.addVertexWithUV(sw[u + 1], sh[v + 1], 0, seg_u[u + 1], seg_v[v + 1]); + t.addVertexWithUV(sw[u + 1], sh[v], 0, seg_u[u + 1], seg_v[v]); + } + + public void draw(CCRenderState state, int x, int y, int w, int h) { + CCRenderState.changeTexture(tex); + state.reset(); + state.startDrawing(); + + int[] sw = new int[] { x, x + seg_w[0], x + w - seg_w[2], x + w }; + int[] sh = new int[] { y, y + seg_h[0], y + h - seg_h[2], y + h }; + + for (int seg = 0; seg < 9; seg++) drawSeg(sw, sh, seg); + + state.draw(); + } + + public void draw(int x, int y, int w, int h) { + draw(CCRenderState.instance(), x, y, w, h); + } +} diff --git a/ccl/src/main/java/codechicken/lib/gui/GuiDraw.java b/ccl/src/main/java/codechicken/lib/gui/GuiDraw.java new file mode 100644 index 0000000..d68e043 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/gui/GuiDraw.java @@ -0,0 +1,227 @@ +package codechicken.lib.gui; + +import java.awt.Dimension; +import java.awt.Point; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.gui.Gui; +import net.minecraft.client.gui.ScaledResolution; +import net.minecraft.client.renderer.RenderHelper; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.util.ResourceLocation; + +import org.lwjgl.input.Mouse; +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; + +import codechicken.lib.math.MathHelper; +import codechicken.lib.render.CCRenderState; + +public class GuiDraw { + + public static class GuiHook extends Gui { + + public void setZLevel(float f) { + zLevel = f; + } + + public float getZLevel() { + return zLevel; + } + + public void incZLevel(float f) { + zLevel += f; + } + + @Override + public void drawGradientRect(int par1, int par2, int par3, int par4, int par5, int par6) { + super.drawGradientRect(par1, par2, par3, par4, par5, par6); + } + } + + public static final GuiHook gui = new GuiHook(); + public static FontRenderer fontRenderer = Minecraft.getMinecraft().fontRenderer; + public static TextureManager renderEngine = Minecraft.getMinecraft().renderEngine; + + public static void drawRect(int x, int y, int w, int h, int colour) { + drawGradientRect(x, y, w, h, colour, colour); + } + + public static void drawGradientRect(int x, int y, int w, int h, int colour1, int colour2) { + gui.drawGradientRect(x, y, x + w, y + h, colour1, colour2); + } + + public static void drawTexturedModalRect(int x, int y, int tx, int ty, int w, int h) { + gui.drawTexturedModalRect(x, y, tx, ty, w, h); + } + + public static void drawString(String text, int x, int y, int colour, boolean shadow) { + if (shadow) fontRenderer.drawStringWithShadow(text, x, y, colour); + else fontRenderer.drawString(text, x, y, colour); + } + + public static void drawString(String text, int x, int y, int colour) { + drawString(text, x, y, colour, true); + } + + public static void drawStringC(String text, int x, int y, int w, int h, int colour, boolean shadow) { + drawString(text, x + (w - getStringWidth(text)) / 2, y + (h - 8) / 2, colour, shadow); + } + + public static void drawStringC(String text, int x, int y, int w, int h, int colour) { + drawStringC(text, x, y, w, h, colour, true); + } + + public static void drawStringC(String text, int x, int y, int colour, boolean shadow) { + drawString(text, x - getStringWidth(text) / 2, y, colour, shadow); + } + + public static void drawStringC(String text, int x, int y, int colour) { + drawStringC(text, x, y, colour, true); + } + + public static void drawStringR(String text, int x, int y, int colour, boolean shadow) { + drawString(text, x - getStringWidth(text), y, colour, shadow); + } + + public static void drawStringR(String text, int x, int y, int colour) { + drawStringR(text, x, y, colour, true); + } + + public static int getStringWidth(String s) { + return fontRenderer.getStringWidth(s); + } + + public static Dimension displaySize() { + Minecraft mc = Minecraft.getMinecraft(); + ScaledResolution res = new ScaledResolution(mc, mc.displayWidth, mc.displayHeight); + return new Dimension(res.getScaledWidth(), res.getScaledHeight()); + } + + public static Dimension displayRes() { + Minecraft mc = Minecraft.getMinecraft(); + return new Dimension(mc.displayWidth, mc.displayHeight); + } + + public static Point getMousePosition(int eventX, int eventY) { + Dimension size = displaySize(); + Dimension res = displayRes(); + return new Point(eventX * size.width / res.width, size.height - eventY * size.height / res.height - 1); + } + + public static Point getMousePosition() { + return getMousePosition(Mouse.getX(), Mouse.getY()); + } + + public static void changeTexture(String s) { + CCRenderState.changeTexture(s); + } + + public static void changeTexture(ResourceLocation r) { + CCRenderState.changeTexture(r); + } + + public static void drawTip(int x, int y, String text) { + drawMultilineTip(x, y, Arrays.asList(text)); + } + + /** + * Append a string in the tooltip list with TOOLTIP_LINESPACE to have a small gap between it and the next line + */ + public static final String TOOLTIP_LINESPACE = "\u00A7h"; + /** + * Have a string in the tooltip list with TOOLTIP_HANDLER + getTipLineId(handler) for a custom handler + */ + public static final String TOOLTIP_HANDLER = "\u00A7x"; + + private static List tipLineHandlers = new ArrayList(); + + public static interface ITooltipLineHandler { + + public Dimension getSize(); + + public void draw(int x, int y); + } + + public static int getTipLineId(ITooltipLineHandler handler) { + tipLineHandlers.add(handler); + return tipLineHandlers.size() - 1; + } + + public static ITooltipLineHandler getTipLine(String line) { + if (!line.startsWith(TOOLTIP_HANDLER)) return null; + return tipLineHandlers.get(Integer.parseInt(line.substring(2))); + } + + public static void drawMultilineTip(int x, int y, List list) { + drawMultilineTip(fontRenderer, x, y, list); + } + + public static void drawMultilineTip(FontRenderer font, int x, int y, List list) { + if (list.isEmpty()) return; + + GL11.glDisable(GL12.GL_RESCALE_NORMAL); + GL11.glDisable(GL11.GL_DEPTH_TEST); + RenderHelper.disableStandardItemLighting(); + + int w = 0; + int h = -2; + for (int i = 0; i < list.size(); i++) { + String s = list.get(i); + ITooltipLineHandler line = getTipLine(s); + Dimension d = line != null ? line.getSize() + : new Dimension( + font.getStringWidth(s), + list.get(i).endsWith(TOOLTIP_LINESPACE) && i + 1 < list.size() ? 12 : 10); + w = Math.max(w, d.width); + h += d.height; + } + + if (x < 8) x = 8; + else if (x > displaySize().width - w - 8) { + x -= 24 + w; // flip side of cursor + if (x < 8) x = 8; + } + y = (int) MathHelper.clip(y, 8, displaySize().height - 8 - h); + + gui.incZLevel(300); + drawTooltipBox(x - 4, y - 4, w + 7, h + 7); + for (String s : list) { + ITooltipLineHandler line = getTipLine(s); + if (line != null) { + line.draw(x, y); + y += line.getSize().height; + } else { + font.drawStringWithShadow(s, x, y, -1); + y += s.endsWith(TOOLTIP_LINESPACE) ? 12 : 10; + } + } + + tipLineHandlers.clear(); + gui.incZLevel(-300); + + GL11.glEnable(GL11.GL_DEPTH_TEST); + GL11.glEnable(GL12.GL_RESCALE_NORMAL); + RenderHelper.enableGUIStandardItemLighting(); + } + + public static void drawTooltipBox(int x, int y, int w, int h) { + int bg = 0xf0100010; + drawGradientRect(x + 1, y, w - 1, 1, bg, bg); + drawGradientRect(x + 1, y + h, w - 1, 1, bg, bg); + drawGradientRect(x + 1, y + 1, w - 1, h - 1, bg, bg); // center + drawGradientRect(x, y + 1, 1, h - 1, bg, bg); + drawGradientRect(x + w, y + 1, 1, h - 1, bg, bg); + int grad1 = 0x505000ff; + int grad2 = 0x5028007F; + drawGradientRect(x + 1, y + 2, 1, h - 3, grad1, grad2); + drawGradientRect(x + w - 1, y + 2, 1, h - 3, grad1, grad2); + + drawGradientRect(x + 1, y + 1, w - 1, 1, grad1, grad1); + drawGradientRect(x + 1, y + h - 1, w - 1, 1, grad2, grad2); + } +} diff --git a/ccl/src/main/java/codechicken/lib/inventory/ContainerExtended.java b/ccl/src/main/java/codechicken/lib/inventory/ContainerExtended.java new file mode 100644 index 0000000..d8448a1 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/inventory/ContainerExtended.java @@ -0,0 +1,274 @@ +package codechicken.lib.inventory; + +import java.util.Arrays; +import java.util.LinkedList; +import java.util.List; + +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.entity.player.InventoryPlayer; +import net.minecraft.inventory.Container; +import net.minecraft.inventory.ICrafting; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; +import net.minecraft.network.play.INetHandlerPlayClient; +import net.minecraft.network.play.INetHandlerPlayServer; + +import org.apache.logging.log4j.LogManager; + +import codechicken.lib.packet.PacketCustom; +import codechicken.lib.packet.PacketCustom.IClientPacketHandler; +import codechicken.lib.packet.PacketCustom.IServerPacketHandler; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.relauncher.Side; + +/** + * Clean container implementation with a few extra features. Easy shift-click handling. Hooks for slot clicks, large + * stack sizes and some networking. + */ +public abstract class ContainerExtended extends Container implements ICrafting { + + private static final String netChannel = "CCL:Container"; + private static int nextNetworkID = 0; + + static { + PacketCustom.assignHandler(netChannel, new IClientPacketHandler() { + + @Override + public void handlePacket(PacketCustom packet, Minecraft mc, INetHandlerPlayClient handler) { + Container cont = mc.thePlayer.openContainer; + if (!(cont instanceof ContainerExtended)) return; + + ContainerExtended c = (ContainerExtended) cont; + if (packet.getType() == 1) c.netID = packet.readInt(); + else if (c.netID == packet.readInt()) c.handleClientPacket(packet); + } + }); + PacketCustom.assignHandler(netChannel, new IServerPacketHandler() { + + @Override + public void handlePacket(PacketCustom packet, EntityPlayerMP sender, INetHandlerPlayServer handler) { + Container cont = sender.openContainer; + if (!(cont instanceof ContainerExtended)) return; + + ContainerExtended c = (ContainerExtended) cont; + if (c.netID == packet.readInt()) c.handleServerPacket(packet); + } + }); + } + + public LinkedList playerCrafters = new LinkedList(); + private int netID; + + public ContainerExtended() { + crafters.add(this); + if (FMLCommonHandler.instance().getEffectiveSide() == Side.SERVER) netID = ++nextNetworkID; + } + + @Override + public void addCraftingToCrafters(ICrafting icrafting) { + if (icrafting instanceof EntityPlayerMP) { + playerCrafters.add((EntityPlayerMP) icrafting); + sendNetID((EntityPlayerMP) icrafting); + sendContainerAndContentsToPlayer(this, getInventory(), Arrays.asList((EntityPlayerMP) icrafting)); + detectAndSendChanges(); + } else super.addCraftingToCrafters(icrafting); + } + + private void sendNetID(EntityPlayerMP player) { + if (netID == 0) LogManager.getLogger("CodeChickenLib").error("Player added to container with 0 network ID"); + else new PacketCustom(netChannel, 1).writeInt(netID).sendToPlayer(player); + } + + @Override + public void removeCraftingFromCrafters(ICrafting icrafting) { + if (icrafting instanceof EntityPlayerMP) playerCrafters.remove(icrafting); + else super.removeCraftingFromCrafters(icrafting); + } + + @Override + public void sendContainerAndContentsToPlayer(Container container, List list) { + sendContainerAndContentsToPlayer(container, list, playerCrafters); + } + + public void sendContainerAndContentsToPlayer(Container container, List list, + List playerCrafters) { + LinkedList largeStacks = new LinkedList(); + for (int i = 0; i < list.size(); i++) { + ItemStack stack = list.get(i); + if (stack != null && stack.stackSize > Byte.MAX_VALUE) { + list.set(i, null); + largeStacks.add(stack); + } else largeStacks.add(null); + } + + for (EntityPlayerMP player : playerCrafters) player.sendContainerAndContentsToPlayer(container, list); + + for (int i = 0; i < largeStacks.size(); i++) { + ItemStack stack = largeStacks.get(i); + if (stack != null) sendLargeStack(stack, i, playerCrafters); + } + } + + public void sendLargeStack(ItemStack stack, int slot, List players) {} + + @Override + public void sendProgressBarUpdate(Container container, int i, int j) { + for (EntityPlayerMP player : playerCrafters) player.sendProgressBarUpdate(container, i, j); + } + + @Override + public void sendSlotContents(Container container, int slot, ItemStack stack) { + if (stack != null && stack.stackSize > Byte.MAX_VALUE) sendLargeStack(stack, slot, playerCrafters); + else for (EntityPlayerMP player : playerCrafters) player.sendSlotContents(container, slot, stack); + } + + @Override + public ItemStack slotClick(int par1, int par2, int par3, EntityPlayer player) { + if (par1 >= 0 && par1 < inventorySlots.size()) { + Slot slot = getSlot(par1); + if (slot instanceof SlotHandleClicks) return ((SlotHandleClicks) slot).slotClick(this, player, par2, par3); + } + return super.slotClick(par1, par2, par3, player); + } + + @Override + public ItemStack transferStackInSlot(EntityPlayer par1EntityPlayer, int slotIndex) { + ItemStack transferredStack = null; + Slot slot = (Slot) inventorySlots.get(slotIndex); + + if (slot != null && slot.getHasStack()) { + ItemStack stack = slot.getStack(); + transferredStack = stack.copy(); + + if (!doMergeStackAreas(slotIndex, stack)) return null; + + if (stack.stackSize == 0) slot.putStack(null); + else slot.onSlotChanged(); + } + + return transferredStack; + } + + @Override + public boolean mergeItemStack(ItemStack stack, int startIndex, int endIndex, boolean reverse) { + boolean merged = false; + int slotIndex = reverse ? endIndex - 1 : startIndex; + + if (stack == null) return false; + + if (stack.isStackable()) // search for stacks to increase + { + while (stack.stackSize > 0 && (reverse ? slotIndex >= startIndex : slotIndex < endIndex)) { + Slot slot = (Slot) inventorySlots.get(slotIndex); + ItemStack slotStack = slot.getStack(); + + if (slotStack != null && slotStack.getItem() == stack.getItem() + && (!stack.getHasSubtypes() || stack.getItemDamage() == slotStack.getItemDamage()) + && ItemStack.areItemStackTagsEqual(stack, slotStack)) { + int totalStackSize = slotStack.stackSize + stack.stackSize; + int maxStackSize = Math.min(stack.getMaxStackSize(), slot.getSlotStackLimit()); + if (totalStackSize <= maxStackSize) { + stack.stackSize = 0; + slotStack.stackSize = totalStackSize; + slot.onSlotChanged(); + merged = true; + } else if (slotStack.stackSize < maxStackSize) { + stack.stackSize -= maxStackSize - slotStack.stackSize; + slotStack.stackSize = maxStackSize; + slot.onSlotChanged(); + merged = true; + } + } + + slotIndex += reverse ? -1 : 1; + } + } + + if (stack.stackSize > 0) // normal transfer :) + { + slotIndex = reverse ? endIndex - 1 : startIndex; + + while (stack.stackSize > 0 && (reverse ? slotIndex >= startIndex : slotIndex < endIndex)) { + Slot slot = (Slot) this.inventorySlots.get(slotIndex); + + if (!slot.getHasStack() && slot.isItemValid(stack)) { + int maxStackSize = Math.min(stack.getMaxStackSize(), slot.getSlotStackLimit()); + if (stack.stackSize <= maxStackSize) { + slot.putStack(stack.copy()); + slot.onSlotChanged(); + stack.stackSize = 0; + merged = true; + } else { + slot.putStack(stack.splitStack(maxStackSize)); + slot.onSlotChanged(); + merged = true; + } + } + + slotIndex += reverse ? -1 : 1; + } + } + + return merged; + } + + /** + * Called when slotIndex is shift clicked on. Recommended implementation is to call mergeItemStack based on + * slotIndex + * + * @param stack The stack in the clicked slot + * @return True if one or more items were moved from this slots into other slots + */ + public boolean doMergeStackAreas(int slotIndex, ItemStack stack) { + return false; + } + + protected void bindPlayerInventory(InventoryPlayer inventoryPlayer) { + bindPlayerInventory(inventoryPlayer, 8, 84); + } + + protected void bindPlayerInventory(InventoryPlayer inventoryPlayer, int x, int y) { + for (int row = 0; row < 3; row++) for (int col = 0; col < 9; col++) + addSlotToContainer(new Slot(inventoryPlayer, col + row * 9 + 9, x + col * 18, y + row * 18)); + for (int slot = 0; slot < 9; slot++) addSlotToContainer(new Slot(inventoryPlayer, slot, x + slot * 18, y + 58)); + } + + @Override + public boolean canInteractWith(EntityPlayer var1) { + return true; + } + + public void sendContainerPacket(PacketCustom packet) { + for (EntityPlayerMP player : playerCrafters) packet.sendToPlayer(player); + } + + /** + * @param type An identifying number for the packet type between 2 and 0x79 inclusive. 1 is reserved for + * synchronising networkIDs + * @return A packet on the CCL inventory channel that will be recieved by this container on the other network side + */ + public PacketCustom getPacket(int type) { + if (netID == 0) + LogManager.getLogger("CodeChickenLib").error("Tried to get packet for container with 0 network ID"); + if (type == 1) throw new IllegalArgumentException( + "Packet type 1 is reserved for network synchronisation in ContainerExtended"); + + return new PacketCustom(netChannel, type).writeInt(netID); + } + + /** + * Handle a packet from the server obtained by getPacket. + */ + public void handleClientPacket(PacketCustom packet) {} + + /** + * Handle a packet from the client obtained by getPacket. + */ + public void handleServerPacket(PacketCustom packet) {} + + public void sendProgressBarUpdate(int barID, int value) { + for (ICrafting crafting : (List) crafters) crafting.sendProgressBarUpdate(this, barID, value); + } +} diff --git a/ccl/src/main/java/codechicken/lib/inventory/ContainerSynchronised.java b/ccl/src/main/java/codechicken/lib/inventory/ContainerSynchronised.java new file mode 100644 index 0000000..8439a3b --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/inventory/ContainerSynchronised.java @@ -0,0 +1,67 @@ +package codechicken.lib.inventory; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.inventory.Container; +import net.minecraft.item.ItemStack; + +import codechicken.lib.packet.PacketCustom; + +public abstract class ContainerSynchronised extends ContainerExtended { + + private ArrayList syncVars = new ArrayList(); + + /** + * Create a packet to be used to send a synced variable update. Calls getPacket. Can be overriden to add extra + * identifying data or change the type (default 2) + */ + public PacketCustom createSyncPacket() { + return getPacket(2); + } + + @Override + public final void detectAndSendChanges() { + super.detectAndSendChanges(); + + for (int i = 0; i < syncVars.size(); i++) { + IContainerSyncVar var = syncVars.get(i); + if (var.changed()) { + PacketCustom packet = createSyncPacket(); + packet.writeByte(i); + var.writeChange(packet); + sendContainerPacket(packet); + var.reset(); + } + } + } + + @Override + public void sendContainerAndContentsToPlayer(Container container, List list, + List playerCrafters) { + super.sendContainerAndContentsToPlayer(container, list, playerCrafters); + for (int i = 0; i < syncVars.size(); i++) { + IContainerSyncVar var = syncVars.get(i); + PacketCustom packet = createSyncPacket(); + packet.writeByte(i); + var.writeChange(packet); + var.reset(); + for (EntityPlayerMP player : playerCrafters) packet.sendToPlayer(player); + } + } + + public void addSyncVar(IContainerSyncVar var) { + syncVars.add(var); + } + + @Override + public final void handleClientPacket(PacketCustom packet) { + syncVars.get(packet.readUByte()).readChange(packet); + } + + public List getSyncedVars() { + return Collections.unmodifiableList(syncVars); + } +} diff --git a/ccl/src/main/java/codechicken/lib/inventory/IContainerSyncVar.java b/ccl/src/main/java/codechicken/lib/inventory/IContainerSyncVar.java new file mode 100644 index 0000000..07b3e25 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/inventory/IContainerSyncVar.java @@ -0,0 +1,14 @@ +package codechicken.lib.inventory; + +import codechicken.lib.packet.PacketCustom; + +public interface IContainerSyncVar { + + public boolean changed(); + + public void reset(); + + public void writeChange(PacketCustom packet); + + public void readChange(PacketCustom packet); +} diff --git a/ccl/src/main/java/codechicken/lib/inventory/IntegerSync.java b/ccl/src/main/java/codechicken/lib/inventory/IntegerSync.java new file mode 100644 index 0000000..9cde12e --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/inventory/IntegerSync.java @@ -0,0 +1,30 @@ +package codechicken.lib.inventory; + +import codechicken.lib.packet.PacketCustom; + +public abstract class IntegerSync implements IContainerSyncVar { + + public int c_value; + + @Override + public boolean changed() { + return getValue() != c_value; + } + + @Override + public void reset() { + c_value = getValue(); + } + + @Override + public void writeChange(PacketCustom packet) { + packet.writeInt(getValue()); + } + + @Override + public void readChange(PacketCustom packet) { + c_value = packet.readInt(); + } + + public abstract int getValue(); +} diff --git a/ccl/src/main/java/codechicken/lib/inventory/InventoryCopy.java b/ccl/src/main/java/codechicken/lib/inventory/InventoryCopy.java new file mode 100644 index 0000000..06e7b71 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/inventory/InventoryCopy.java @@ -0,0 +1,103 @@ +package codechicken.lib.inventory; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; + +/** + * Creates a copy of an IInventory for extended simulation + */ +public class InventoryCopy implements IInventory { + + public boolean[] accessible; + public ItemStack[] items; + public IInventory inv; + + public InventoryCopy(IInventory inv) { + items = new ItemStack[inv.getSizeInventory()]; + accessible = new boolean[inv.getSizeInventory()]; + this.inv = inv; + update(); + } + + public void update() { + for (int i = 0; i < items.length; i++) { + ItemStack stack = inv.getStackInSlot(i); + if (stack != null) items[i] = stack.copy(); + } + } + + public InventoryCopy open(InventoryRange access) { + int lslot = access.lastSlot(); + if (lslot > accessible.length) { + boolean[] l_accessible = new boolean[lslot]; + ItemStack[] l_items = new ItemStack[lslot]; + System.arraycopy(accessible, 0, l_accessible, 0, accessible.length); + System.arraycopy(items, 0, l_items, 0, items.length); + accessible = l_accessible; + items = l_items; + } + + for (int slot : access.slots) accessible[slot] = true; + return this; + } + + @Override + public int getSizeInventory() { + return items.length; + } + + @Override + public ItemStack getStackInSlot(int slot) { + return items[slot]; + } + + public ItemStack decrStackSize(int slot, int amount) { + return InventoryUtils.decrStackSize(this, slot, amount); + } + + @Override + public ItemStack getStackInSlotOnClosing(int slot) { + return InventoryUtils.getStackInSlotOnClosing(this, slot); + } + + @Override + public void setInventorySlotContents(int slot, ItemStack stack) { + items[slot] = stack; + markDirty(); + } + + @Override + public String getInventoryName() { + return "copy"; + } + + @Override + public boolean isUseableByPlayer(EntityPlayer player) { + return true; + } + + @Override + public void openInventory() {} + + @Override + public void closeInventory() {} + + @Override + public int getInventoryStackLimit() { + return 64; + } + + @Override + public void markDirty() {} + + @Override + public boolean isItemValidForSlot(int i, ItemStack itemstack) { + return inv.isItemValidForSlot(i, itemstack); + } + + @Override + public boolean hasCustomInventoryName() { + return true; + } +} diff --git a/ccl/src/main/java/codechicken/lib/inventory/InventoryNBT.java b/ccl/src/main/java/codechicken/lib/inventory/InventoryNBT.java new file mode 100644 index 0000000..b721741 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/inventory/InventoryNBT.java @@ -0,0 +1,91 @@ +package codechicken.lib.inventory; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; + +/** + * IInventory implementation which saves and loads from an NBT tag + */ +public class InventoryNBT implements IInventory { + + protected ItemStack[] items; + protected NBTTagCompound tag; + + public InventoryNBT(int size, NBTTagCompound tag) { + this.tag = tag; + items = new ItemStack[size]; + readNBT(); + } + + private void writeNBT() { + tag.setTag("items", InventoryUtils.writeItemStacksToTag(items, getInventoryStackLimit())); + } + + private void readNBT() { + if (tag.hasKey("items")) InventoryUtils.readItemStacksFromTag(items, tag.getTagList("items", 10)); + } + + @Override + public int getSizeInventory() { + return items.length; + } + + @Override + public ItemStack getStackInSlot(int slot) { + return items[slot]; + } + + @Override + public ItemStack decrStackSize(int slot, int amount) { + return InventoryUtils.decrStackSize(this, slot, amount); + } + + @Override + public ItemStack getStackInSlotOnClosing(int slot) { + return InventoryUtils.getStackInSlotOnClosing(this, slot); + } + + @Override + public void setInventorySlotContents(int slot, ItemStack stack) { + items[slot] = stack; + markDirty(); + } + + @Override + public String getInventoryName() { + return "NBT"; + } + + @Override + public int getInventoryStackLimit() { + return 64; + } + + @Override + public void markDirty() { + writeNBT(); + } + + @Override + public boolean isUseableByPlayer(EntityPlayer var1) { + return true; + } + + @Override + public void openInventory() {} + + @Override + public void closeInventory() {} + + @Override + public boolean isItemValidForSlot(int i, ItemStack itemstack) { + return true; + } + + @Override + public boolean hasCustomInventoryName() { + return true; + } +} diff --git a/ccl/src/main/java/codechicken/lib/inventory/InventoryRange.java b/ccl/src/main/java/codechicken/lib/inventory/InventoryRange.java new file mode 100644 index 0000000..dacb4f5 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/inventory/InventoryRange.java @@ -0,0 +1,59 @@ +package codechicken.lib.inventory; + +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.ISidedInventory; +import net.minecraft.item.ItemStack; + +/** + * Inventory wrapper for unified ISided/IInventory access + */ +public class InventoryRange { + + public IInventory inv; + public int side; + public ISidedInventory sidedInv; + public int[] slots; + + public InventoryRange(IInventory inv, int side) { + this.inv = inv; + this.side = side; + if (inv instanceof ISidedInventory) { + sidedInv = (ISidedInventory) inv; + slots = sidedInv.getAccessibleSlotsFromSide(side); + } else { + slots = new int[inv.getSizeInventory()]; + for (int i = 0; i < slots.length; i++) slots[i] = i; + } + } + + public InventoryRange(IInventory inv) { + this(inv, 0); + } + + public InventoryRange(IInventory inv, int fslot, int size) { + this.inv = inv; + slots = new int[size]; + for (int i = 0; i < slots.length; i++) slots[i] = fslot + i; + } + + public InventoryRange(IInventory inv, InventoryRange access) { + this.inv = inv; + this.slots = access.slots; + this.side = access.side; + if (inv instanceof ISidedInventory) sidedInv = (ISidedInventory) inv; + } + + public boolean canInsertItem(int slot, ItemStack item) { + return sidedInv == null ? inv.isItemValidForSlot(slot, item) : sidedInv.canInsertItem(slot, item, side); + } + + public boolean canExtractItem(int slot, ItemStack item) { + return sidedInv == null ? inv.isItemValidForSlot(slot, item) : sidedInv.canExtractItem(slot, item, side); + } + + public int lastSlot() { + int last = 0; + for (int slot : slots) if (slot > last) last = slot; + return last; + } +} diff --git a/ccl/src/main/java/codechicken/lib/inventory/InventorySimple.java b/ccl/src/main/java/codechicken/lib/inventory/InventorySimple.java new file mode 100644 index 0000000..ffbd604 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/inventory/InventorySimple.java @@ -0,0 +1,109 @@ +package codechicken.lib.inventory; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; + +/** + * Simple IInventory implementation with an array of items, name and maximum stack size + */ +public class InventorySimple implements IInventory { + + public ItemStack[] items; + public int limit; + public String name; + + public InventorySimple(ItemStack[] items, int limit, String name) { + this.items = items; + this.limit = limit; + this.name = name; + } + + public InventorySimple(ItemStack[] items, String name) { + this(items, 64, name); + } + + public InventorySimple(ItemStack[] items, int limit) { + this(items, limit, "inv"); + } + + public InventorySimple(ItemStack[] items) { + this(items, 64, "inv"); + } + + public InventorySimple(int size, int limit, String name) { + this(new ItemStack[size], limit, name); + } + + public InventorySimple(int size, int limit) { + this(size, limit, "inv"); + } + + public InventorySimple(int size, String name) { + this(size, 64, name); + } + + public InventorySimple(int size) { + this(size, 64, "inv"); + } + + @Override + public int getSizeInventory() { + return items.length; + } + + @Override + public ItemStack getStackInSlot(int slot) { + return items[slot]; + } + + @Override + public ItemStack decrStackSize(int slot, int amount) { + return InventoryUtils.decrStackSize(this, slot, amount); + } + + @Override + public ItemStack getStackInSlotOnClosing(int slot) { + return InventoryUtils.getStackInSlotOnClosing(this, slot); + } + + @Override + public void setInventorySlotContents(int slot, ItemStack stack) { + items[slot] = stack; + markDirty(); + } + + @Override + public String getInventoryName() { + return name; + } + + @Override + public int getInventoryStackLimit() { + return limit; + } + + @Override + public boolean isUseableByPlayer(EntityPlayer var1) { + return true; + } + + @Override + public void openInventory() {} + + @Override + public void closeInventory() {} + + @Override + public boolean isItemValidForSlot(int i, ItemStack itemstack) { + return true; + } + + @Override + public boolean hasCustomInventoryName() { + return true; + } + + @Override + public void markDirty() {} +} diff --git a/ccl/src/main/java/codechicken/lib/inventory/InventoryUtils.java b/ccl/src/main/java/codechicken/lib/inventory/InventoryUtils.java new file mode 100644 index 0000000..fa78174 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/inventory/InventoryUtils.java @@ -0,0 +1,323 @@ +package codechicken.lib.inventory; + +import net.minecraft.entity.item.EntityItem; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.init.Items; +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.InventoryLargeChest; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTBase; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.nbt.NBTTagList; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.tileentity.TileEntityChest; +import net.minecraft.world.World; +import net.minecraftforge.common.util.ForgeDirection; + +import com.google.common.base.Objects; + +import codechicken.lib.vec.Vector3; + +public class InventoryUtils { + + /** + * Constructor for ItemStack with tag + */ + public static ItemStack newItemStack(Item item, int size, int damage, NBTTagCompound tag) { + ItemStack stack = new ItemStack(item, size, damage); + stack.stackTagCompound = tag; + return stack; + } + + /** + * Gets the actual damage of an item without asking the Item + */ + public static int actualDamage(ItemStack stack) { + return Items.diamond.getDamage(stack); + } + + /** + * Static default implementation for IInventory method + */ + public static ItemStack decrStackSize(IInventory inv, int slot, int size) { + ItemStack item = inv.getStackInSlot(slot); + + if (item != null) { + if (item.stackSize <= size) { + inv.setInventorySlotContents(slot, null); + inv.markDirty(); + return item; + } + ItemStack itemstack1 = item.splitStack(size); + if (item.stackSize == 0) inv.setInventorySlotContents(slot, null); + else inv.setInventorySlotContents(slot, item); + + inv.markDirty(); + return itemstack1; + } + return null; + } + + /** + * Static default implementation for IInventory method + */ + public static ItemStack getStackInSlotOnClosing(IInventory inv, int slot) { + ItemStack stack = inv.getStackInSlot(slot); + inv.setInventorySlotContents(slot, null); + return stack; + } + + /** + * @return The quantity of items from addition that can be added to base + */ + public static int incrStackSize(ItemStack base, ItemStack addition) { + if (canStack(base, addition)) return incrStackSize(base, addition.stackSize); + + return 0; + } + + /** + * @return The quantity of items from addition that can be added to base + */ + public static int incrStackSize(ItemStack base, int addition) { + int totalSize = base.stackSize + addition; + + if (totalSize <= base.getMaxStackSize()) return addition; + else if (base.stackSize < base.getMaxStackSize()) return base.getMaxStackSize() - base.stackSize; + + return 0; + } + + /** + * NBT item saving function + */ + public static NBTTagList writeItemStacksToTag(ItemStack[] items) { + return writeItemStacksToTag(items, 64); + } + + /** + * NBT item saving function with support for stack sizes > 32K + */ + public static NBTTagList writeItemStacksToTag(ItemStack[] items, int maxQuantity) { + NBTTagList tagList = new NBTTagList(); + for (int i = 0; i < items.length; i++) { + if (items[i] != null) { + NBTTagCompound tag = new NBTTagCompound(); + tag.setShort("Slot", (short) i); + items[i].writeToNBT(tag); + + if (maxQuantity > Short.MAX_VALUE) tag.setInteger("Quantity", items[i].stackSize); + else if (maxQuantity > Byte.MAX_VALUE) tag.setShort("Quantity", (short) items[i].stackSize); + + tagList.appendTag(tag); + } + } + return tagList; + } + + /** + * NBT item loading function with support for stack sizes > 32K + */ + public static void readItemStacksFromTag(ItemStack[] items, NBTTagList tagList) { + for (int i = 0; i < tagList.tagCount(); i++) { + NBTTagCompound tag = tagList.getCompoundTagAt(i); + int b = tag.getShort("Slot"); + items[b] = ItemStack.loadItemStackFromNBT(tag); + if (tag.hasKey("Quantity")) + items[b].stackSize = ((NBTBase.NBTPrimitive) tag.getTag("Quantity")).func_150287_d(); + } + } + + /** + * Spawns an itemstack in the world at a location + */ + public static void dropItem(ItemStack stack, World world, Vector3 dropLocation) { + EntityItem item = new EntityItem(world, dropLocation.x, dropLocation.y, dropLocation.z, stack); + item.motionX = world.rand.nextGaussian() * 0.05; + item.motionY = world.rand.nextGaussian() * 0.05 + 0.2F; + item.motionZ = world.rand.nextGaussian() * 0.05; + world.spawnEntityInWorld(item); + } + + /** + * Copies an itemstack with a new quantity + */ + public static ItemStack copyStack(ItemStack stack, int quantity) { + if (stack == null) return null; + + stack = stack.copy(); + stack.stackSize = quantity; + return stack; + } + + /** + * Gets the maximum quantity of an item that can be inserted into inv + */ + public static int getInsertibleQuantity(InventoryRange inv, ItemStack stack) { + int quantity = 0; + stack = copyStack(stack, Integer.MAX_VALUE); + for (int slot : inv.slots) quantity += fitStackInSlot(inv, slot, stack); + + return quantity; + } + + public static int getInsertibleQuantity(IInventory inv, ItemStack stack) { + return getInsertibleQuantity(new InventoryRange(inv), stack); + } + + public static int fitStackInSlot(InventoryRange inv, int slot, ItemStack stack) { + ItemStack base = inv.inv.getStackInSlot(slot); + if (!canStack(base, stack) || !inv.canInsertItem(slot, stack)) return 0; + + int fit = base != null ? incrStackSize(base, inv.inv.getInventoryStackLimit() - base.stackSize) + : inv.inv.getInventoryStackLimit(); + return Math.min(fit, stack.stackSize); + } + + public static int fitStackInSlot(IInventory inv, int slot, ItemStack stack) { + return fitStackInSlot(new InventoryRange(inv), slot, stack); + } + + /** + * @param simulate If set to true, no items will actually be inserted + * @return The number of items unable to be inserted + */ + public static int insertItem(InventoryRange inv, ItemStack stack, boolean simulate) { + stack = stack.copy(); + for (int pass = 0; pass < 2; pass++) { + for (int slot : inv.slots) { + ItemStack base = inv.inv.getStackInSlot(slot); + if ((pass == 0) == (base == null)) continue; + int fit = fitStackInSlot(inv, slot, stack); + if (fit == 0) continue; + + if (base != null) { + stack.stackSize -= fit; + if (!simulate) { + base.stackSize += fit; + inv.inv.setInventorySlotContents(slot, base); + } + } else { + if (!simulate) inv.inv.setInventorySlotContents(slot, copyStack(stack, fit)); + stack.stackSize -= fit; + } + if (stack.stackSize == 0) return 0; + } + } + return stack.stackSize; + } + + public static int insertItem(IInventory inv, ItemStack stack, boolean simulate) { + return insertItem(new InventoryRange(inv), stack, simulate); + } + + /** + * Gets the stack in slot if it can be extracted + */ + public static ItemStack getExtractableStack(InventoryRange inv, int slot) { + ItemStack stack = inv.inv.getStackInSlot(slot); + if (stack == null || !inv.canExtractItem(slot, stack)) return null; + + return stack; + } + + public static ItemStack getExtractableStack(IInventory inv, int slot) { + return getExtractableStack(new InventoryRange(inv), slot); + } + + public static boolean areStacksIdentical(ItemStack stack1, ItemStack stack2) { + if (stack1 == null || stack2 == null) return stack1 == stack2; + + return stack1.getItem() == stack2.getItem() && stack1.getItemDamage() == stack2.getItemDamage() + && stack1.stackSize == stack2.stackSize + && Objects.equal(stack1.getTagCompound(), stack2.getTagCompound()); + } + + /** + * Gets an IInventory from a coordinate with support for double chests + */ + public static IInventory getInventory(World world, int x, int y, int z) { + TileEntity tile = world.getTileEntity(x, y, z); + if (!(tile instanceof IInventory)) return null; + + if (tile instanceof TileEntityChest) return getChest((TileEntityChest) tile); + return (IInventory) tile; + } + + public static final ForgeDirection[] chestSides = new ForgeDirection[] { ForgeDirection.WEST, ForgeDirection.EAST, + ForgeDirection.NORTH, ForgeDirection.SOUTH }; + + public static IInventory getChest(TileEntityChest chest) { + for (ForgeDirection fside : chestSides) { + if (chest.getWorldObj() + .getBlock(chest.xCoord + fside.offsetX, chest.yCoord + fside.offsetY, chest.zCoord + fside.offsetZ) + == chest.getBlockType()) + return new InventoryLargeChest( + "container.chestDouble", + (TileEntityChest) chest.getWorldObj().getTileEntity( + chest.xCoord + fside.offsetX, + chest.yCoord + fside.offsetY, + chest.zCoord + fside.offsetZ), + chest); + } + return chest; + } + + public static boolean canStack(ItemStack stack1, ItemStack stack2) { + return stack1 == null || stack2 == null + || (stack1.getItem() == stack2.getItem() + && (!stack2.getHasSubtypes() || stack2.getItemDamage() == stack1.getItemDamage()) + && ItemStack.areItemStackTagsEqual(stack2, stack1)) && stack1.isStackable(); + } + + /** + * Consumes one item from slot in inv with support for containers. + */ + public static void consumeItem(IInventory inv, int slot) { + ItemStack stack = inv.getStackInSlot(slot); + Item item = stack.getItem(); + if (item.hasContainerItem(stack)) { + ItemStack container = item.getContainerItem(stack); + inv.setInventorySlotContents(slot, container); + } else { + inv.decrStackSize(slot, 1); + } + } + + /** + * Gets the size of the stack in a slot. Returns 0 on null stacks + */ + public static int stackSize(IInventory inv, int slot) { + ItemStack stack = inv.getStackInSlot(slot); + return stack == null ? 0 : stack.stackSize; + } + + /** + * Drops all items from inv using getStackInSlotOnClosing + */ + public static void dropOnClose(EntityPlayer player, IInventory inv) { + for (int i = 0; i < inv.getSizeInventory(); i++) { + ItemStack stack = inv.getStackInSlotOnClosing(i); + if (stack != null) player.dropPlayerItemWithRandomChoice(stack, false); + } + } + + public static NBTTagCompound savePersistant(ItemStack stack, NBTTagCompound tag) { + stack.writeToNBT(tag); + tag.removeTag("id"); + tag.setString("name", Item.itemRegistry.getNameForObject(stack.getItem())); + return tag; + } + + public static ItemStack loadPersistant(NBTTagCompound tag) { + String name = tag.getString("name"); + Item item = (Item) Item.itemRegistry.getObject(name); + if (item == null) return null; + int count = tag.hasKey("Count") ? tag.getByte("Count") : 1; + int damage = tag.hasKey("Damage") ? tag.getShort("Damage") : 0; + ItemStack stack = new ItemStack(item, count, damage); + if (tag.hasKey("tag", 10)) stack.stackTagCompound = tag.getCompoundTag("tag"); + return stack; + } +} diff --git a/ccl/src/main/java/codechicken/lib/inventory/ItemKey.java b/ccl/src/main/java/codechicken/lib/inventory/ItemKey.java new file mode 100644 index 0000000..dd0881a --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/inventory/ItemKey.java @@ -0,0 +1,63 @@ +package codechicken.lib.inventory; + +import static codechicken.lib.inventory.InventoryUtils.actualDamage; + +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraftforge.oredict.OreDictionary; + +import com.google.common.base.Objects; + +/** + * Comparable ItemStack with a hashCode implementation. + */ +public class ItemKey implements Comparable { + + public ItemStack stack; + private int hashcode = 0; + + public ItemKey(ItemStack k) { + stack = k; + } + + public ItemKey(Item item, int damage) { + this(new ItemStack(item, 1, damage)); + } + + public ItemKey(Item item, NBTTagCompound tag) { + this(item, OreDictionary.WILDCARD_VALUE, tag); + } + + public ItemKey(Item item, int damage, NBTTagCompound tag) { + this(item, damage); + stack.setTagCompound(tag); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ItemKey)) return false; + + ItemKey k = (ItemKey) obj; + return stack.getItem() == k.stack.getItem() && actualDamage(stack) == actualDamage(k.stack) + && Objects.equal(stack.stackTagCompound, k.stack.stackTagCompound); + } + + @Override + public int hashCode() { + return hashcode != 0 ? hashcode + : (hashcode = Objects.hashCode(stack.getItem(), actualDamage(stack), stack.stackTagCompound)); + } + + public int compareInt(int a, int b) { + return a == b ? 0 : a < b ? -1 : 1; + } + + @Override + public int compareTo(ItemKey o) { + if (stack.getItem() != o.stack.getItem()) + return compareInt(Item.getIdFromItem(stack.getItem()), Item.getIdFromItem(o.stack.getItem())); + if (actualDamage(stack) != actualDamage(o.stack)) return compareInt(actualDamage(stack), actualDamage(o.stack)); + return 0; + } +} diff --git a/ccl/src/main/java/codechicken/lib/inventory/SlotDummy.java b/ccl/src/main/java/codechicken/lib/inventory/SlotDummy.java new file mode 100644 index 0000000..43f298a --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/inventory/SlotDummy.java @@ -0,0 +1,55 @@ +package codechicken.lib.inventory; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; + +public class SlotDummy extends SlotHandleClicks { + + public final int stackLimit; + + public SlotDummy(IInventory inv, int slot, int x, int y) { + this(inv, slot, x, y, 64); + } + + public SlotDummy(IInventory inv, int slot, int x, int y, int limit) { + super(inv, slot, x, y); + stackLimit = limit; + } + + @Override + public ItemStack slotClick(ContainerExtended container, EntityPlayer player, int button, int modifier) { + ItemStack held = player.inventory.getItemStack(); + boolean shift = modifier == 1; + slotClick(held, button, shift); + return null; + } + + public void slotClick(ItemStack held, int button, boolean shift) { + ItemStack tstack = getStack(); + if (held != null && (tstack == null || !InventoryUtils.canStack(held, tstack))) { + int quantity = Math.min(held.stackSize, stackLimit); + if (shift) quantity = Math.min(stackLimit, held.getMaxStackSize() * 16); + if (button == 1) quantity = 1; + putStack(InventoryUtils.copyStack(held, quantity)); + } else if (tstack != null) { + int inc; + if (held != null) { + inc = button == 1 ? -held.stackSize : held.stackSize; + if (shift) inc *= 16; + } else { + inc = button == 1 ? -1 : 1; + if (shift) inc *= 16; + } + int quantity = tstack.stackSize + inc; + if (quantity <= 0) putStack(null); + else putStack(InventoryUtils.copyStack(tstack, quantity)); + } + } + + @Override + public void putStack(ItemStack stack) { + if (stack != null && stack.stackSize > stackLimit) stack = InventoryUtils.copyStack(stack, stackLimit); + super.putStack(stack); + } +} diff --git a/ccl/src/main/java/codechicken/lib/inventory/SlotDummyOutput.java b/ccl/src/main/java/codechicken/lib/inventory/SlotDummyOutput.java new file mode 100644 index 0000000..07c77fe --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/inventory/SlotDummyOutput.java @@ -0,0 +1,17 @@ +package codechicken.lib.inventory; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.item.ItemStack; + +public class SlotDummyOutput extends SlotHandleClicks { + + public SlotDummyOutput(IInventory inv, int slot, int x, int y) { + super(inv, slot, x, y); + } + + @Override + public ItemStack slotClick(ContainerExtended container, EntityPlayer player, int button, int modifier) { + return null; + } +} diff --git a/ccl/src/main/java/codechicken/lib/inventory/SlotHandleClicks.java b/ccl/src/main/java/codechicken/lib/inventory/SlotHandleClicks.java new file mode 100644 index 0000000..50e5a70 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/inventory/SlotHandleClicks.java @@ -0,0 +1,15 @@ +package codechicken.lib.inventory; + +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.inventory.IInventory; +import net.minecraft.inventory.Slot; +import net.minecraft.item.ItemStack; + +public abstract class SlotHandleClicks extends Slot { + + public SlotHandleClicks(IInventory inv, int slot, int x, int y) { + super(inv, slot, x, y); + } + + public abstract ItemStack slotClick(ContainerExtended container, EntityPlayer player, int button, int modifier); +} diff --git a/ccl/src/main/java/codechicken/lib/lighting/LC.java b/ccl/src/main/java/codechicken/lib/lighting/LC.java new file mode 100644 index 0000000..d7b8d28 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/lighting/LC.java @@ -0,0 +1,87 @@ +package codechicken.lib.lighting; + +import codechicken.lib.render.CCModel; +import codechicken.lib.util.Copyable; +import codechicken.lib.vec.Rotation; +import codechicken.lib.vec.Vector3; + +public class LC implements Copyable { + + public int side; + public float fa; + public float fb; + public float fc; + public float fd; + + public LC() { + this(0, 0, 0, 0, 0); + } + + public LC(int s, float a, float b, float c, float d) { + side = s; + fa = a; + fb = b; + fc = c; + fd = d; + } + + public LC set(int s, float a, float b, float c, float d) { + side = s; + fa = a; + fb = b; + fc = c; + fd = d; + return this; + } + + public LC set(LC lc) { + return set(lc.side, lc.fa, lc.fb, lc.fc, lc.fd); + } + + public LC compute(Vector3 vec, Vector3 normal) { + int side = CCModel.findSide(normal); + if (side < 0) return set(12, 1, 0, 0, 0); + return compute(vec, side); + } + + public LC compute(Vector3 vec, int side) { + boolean offset = false; + switch (side) { + case 0: + offset = vec.y <= 0; + break; + case 1: + offset = vec.y >= 1; + break; + case 2: + offset = vec.z <= 0; + break; + case 3: + offset = vec.z >= 1; + break; + case 4: + offset = vec.x <= 0; + break; + case 5: + offset = vec.x >= 1; + break; + } + if (!offset) side += 6; + return computeO(vec, side); + } + + public LC computeO(Vector3 vec, int side) { + Vector3 v1 = Rotation.axes[((side & 0xE) + 3) % 6]; + Vector3 v2 = Rotation.axes[((side & 0xE) + 5) % 6]; + float d1 = (float) vec.scalarProject(v1); + float d2 = 1 - d1; + float d3 = (float) vec.scalarProject(v2); + float d4 = 1 - d3; + return set(side, d2 * d4, d2 * d3, d1 * d4, d1 * d3); + } + + @Override + public LC copy() { + return new LC(side, fa, fb, fc, fd); + } +} diff --git a/ccl/src/main/java/codechicken/lib/lighting/LightMatrix.java b/ccl/src/main/java/codechicken/lib/lighting/LightMatrix.java new file mode 100644 index 0000000..d6fe986 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/lighting/LightMatrix.java @@ -0,0 +1,139 @@ +package codechicken.lib.lighting; + +import net.minecraft.block.Block; +import net.minecraft.client.Minecraft; +import net.minecraft.world.IBlockAccess; + +import codechicken.lib.colour.ColourRGBA; +import codechicken.lib.render.CCRenderState; +import codechicken.lib.vec.BlockCoord; + +/** + * Note that when using the class as a vertex transformer, the vertices are assumed to be within the BB (x, y, z) -> + * (x+1, y+1, z+1) + */ +public class LightMatrix implements CCRenderState.IVertexOperation { + + public static final int operationIndex = CCRenderState.registerOperation(); + + public int computed = 0; + public float[][] ao = new float[13][4]; + public int[][] brightness = new int[13][4]; + + public IBlockAccess access; + public BlockCoord pos = new BlockCoord(); + + private int sampled = 0; + private float[] aSamples = new float[27]; + private int[] bSamples = new int[27]; + + /** + * The 9 positions in the sample array for each side, sides >= 6 are centered on sample 13 (the block itself) + */ + public static final int[][] ssamplem = new int[][] { { 0, 1, 2, 3, 4, 5, 6, 7, 8 }, + { 18, 19, 20, 21, 22, 23, 24, 25, 26 }, { 0, 9, 18, 1, 10, 19, 2, 11, 20 }, + { 6, 15, 24, 7, 16, 25, 8, 17, 26 }, { 0, 3, 6, 9, 12, 15, 18, 21, 24 }, + { 2, 5, 8, 11, 14, 17, 20, 23, 26 }, { 9, 10, 11, 12, 13, 14, 15, 16, 17 }, + { 9, 10, 11, 12, 13, 14, 15, 16, 17 }, { 3, 12, 21, 4, 13, 22, 5, 14, 23 }, + { 3, 12, 21, 4, 13, 22, 5, 14, 23 }, { 1, 4, 7, 10, 13, 16, 19, 22, 25 }, + { 1, 4, 7, 10, 13, 16, 19, 22, 25 }, { 13, 13, 13, 13, 13, 13, 13, 13, 13 } }; + + public static final int[][] qsamplem = new int[][] { // the positions in the side sample array for each corner + { 0, 1, 3, 4 }, { 5, 1, 2, 4 }, { 6, 7, 3, 4 }, { 5, 7, 8, 4 } }; + public static final float[] sideao = new float[] { 0.5F, 1F, 0.8F, 0.8F, 0.6F, 0.6F, 0.5F, 1F, 0.8F, 0.8F, 0.6F, + 0.6F, 1F }; + + /* + * static { int[][] os = new int[][]{ {0,-1,0}, {0, 1,0}, {0,0,-1}, {0,0, 1}, {-1,0,0}, { 1,0,0}}; for(int s = 0; s + * < 12; s++) { int[] d0 = s < 6 ? new int[]{os[s][0]+1, os[s][1]+1, os[s][2]+1} : new int[]{1, 1, 1}; int[] d1 = + * os[((s&0xE)+3)%6]; int[] d2 = os[((s&0xE)+5)%6]; for(int a = -1; a <= 1; a++) for(int b = -1; b <= 1; b++) + * ssamplem[s][(a+1)*3+b+1] = (d0[1]+d1[1]*a+d2[1]*b)*9+(d0[2]+d1[2]*a+d2[2]*b)*3+(d0[0]+d1[0]*a+d2[0]*b); } + * System.out.println(Arrays.deepToString(ssamplem)); } + */ + + public void locate(IBlockAccess a, int x, int y, int z) { + access = a; + pos.set(x, y, z); + computed = 0; + sampled = 0; + } + + public void sample(int i) { + if ((sampled & 1 << i) == 0) { + int x = pos.x + (i % 3) - 1; + int y = pos.y + (i / 9) - 1; + int z = pos.z + (i / 3 % 3) - 1; + Block b = access.getBlock(x, y, z); + bSamples[i] = access.getLightBrightnessForSkyBlocks(x, y, z, b.getLightValue(access, x, y, z)); + aSamples[i] = b.getAmbientOcclusionLightValue(); + sampled |= 1 << i; + } + } + + public int[] brightness(int side) { + sideSample(side); + return brightness[side]; + } + + public float[] ao(int side) { + sideSample(side); + return ao[side]; + } + + public void sideSample(int side) { + if ((computed & 1 << side) == 0) { + int[] ssample = ssamplem[side]; + for (int q = 0; q < 4; q++) { + int[] qsample = qsamplem[q]; + if (Minecraft.isAmbientOcclusionEnabled()) + interp(side, q, ssample[qsample[0]], ssample[qsample[1]], ssample[qsample[2]], ssample[qsample[3]]); + else interp(side, q, ssample[4], ssample[4], ssample[4], ssample[4]); + } + computed |= 1 << side; + } + } + + private void interp(int s, int q, int a, int b, int c, int d) { + sample(a); + sample(b); + sample(c); + sample(d); + ao[s][q] = interpAO(aSamples[a], aSamples[b], aSamples[c], aSamples[d]) * sideao[s]; + brightness[s][q] = interpBrightness(bSamples[a], bSamples[b], bSamples[c], bSamples[d]); + } + + public static float interpAO(float a, float b, float c, float d) { + return (a + b + c + d) / 4F; + } + + public static int interpBrightness(int a, int b, int c, int d) { + if (a == 0) a = d; + if (b == 0) b = d; + if (c == 0) c = d; + return (a + b + c + d) >> 2 & 0xFF00FF; + } + + @Override + public boolean load(CCRenderState state) { + if (!state.computeLighting) return false; + + state.pipeline.addDependency(state.colourAttrib); + state.pipeline.addDependency(state.lightCoordAttrib); + return true; + } + + @Override + public void operate(CCRenderState state) { + LC lc = state.lc; + float[] a = ao(lc.side); + float f = (a[0] * lc.fa + a[1] * lc.fb + a[2] * lc.fc + a[3] * lc.fd); + int[] b = brightness(lc.side); + state.setColour(ColourRGBA.multiplyC(state.colour, f)); + state.setBrightness((int) (b[0] * lc.fa + b[1] * lc.fb + b[2] * lc.fc + b[3] * lc.fd) & 0xFF00FF); + } + + @Override + public int operationID() { + return operationIndex; + } +} diff --git a/ccl/src/main/java/codechicken/lib/lighting/LightModel.java b/ccl/src/main/java/codechicken/lib/lighting/LightModel.java new file mode 100644 index 0000000..8766737 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/lighting/LightModel.java @@ -0,0 +1,104 @@ +package codechicken.lib.lighting; + +import codechicken.lib.render.CCRenderState; +import codechicken.lib.vec.Rotation; +import codechicken.lib.vec.Vector3; + +public class LightModel implements CCRenderState.IVertexOperation { + + public static final int operationIndex = CCRenderState.registerOperation(); + + public static class Light { + + public Vector3 ambient = new Vector3(); + public Vector3 diffuse = new Vector3(); + public Vector3 position; + + public Light(Vector3 pos) { + position = pos.copy().normalize(); + } + + public Light setDiffuse(Vector3 vec) { + diffuse.set(vec); + return this; + } + + public Light setAmbient(Vector3 vec) { + ambient.set(vec); + return this; + } + } + + public static LightModel standardLightModel; + + static { + standardLightModel = new LightModel().setAmbient(new Vector3(0.4, 0.4, 0.4)) + .addLight(new Light(new Vector3(0.2, 1, -0.7)).setDiffuse(new Vector3(0.6, 0.6, 0.6))) + .addLight(new Light(new Vector3(-0.2, 1, 0.7)).setDiffuse(new Vector3(0.6, 0.6, 0.6))); + } + + private Vector3 ambient = new Vector3(); + private Light[] lights = new Light[8]; + private int lightCount; + + public LightModel addLight(Light light) { + lights[lightCount++] = light; + return this; + } + + public LightModel setAmbient(Vector3 vec) { + ambient.set(vec); + return this; + } + + /** + * @param colour The pre-lighting vertex colour. RGBA format + * @param normal The normal at the vertex + * @return The lighting applied colour + */ + public int apply(int colour, Vector3 normal) { + Vector3 n_colour = ambient.copy(); + for (int l = 0; l < lightCount; l++) { + Light light = lights[l]; + double n_l = light.position.dotProduct(normal); + double f = n_l > 0 ? 1 : 0; + n_colour.x += light.ambient.x + f * light.diffuse.x * n_l; + n_colour.y += light.ambient.y + f * light.diffuse.y * n_l; + n_colour.z += light.ambient.z + f * light.diffuse.z * n_l; + } + + if (n_colour.x > 1) n_colour.x = 1; + if (n_colour.y > 1) n_colour.y = 1; + if (n_colour.z > 1) n_colour.z = 1; + + n_colour.multiply((colour >>> 24) / 255D, (colour >> 16 & 0xFF) / 255D, (colour >> 8 & 0xFF) / 255D); + return (int) (n_colour.x * 255) << 24 | (int) (n_colour.y * 255) << 16 + | (int) (n_colour.z * 255) << 8 + | colour & 0xFF; + } + + @Override + public boolean load(CCRenderState state) { + if (!state.computeLighting) return false; + + state.pipeline.addDependency(CCRenderState.normalAttrib); + state.pipeline.addDependency(CCRenderState.colourAttrib); + return true; + } + + @Override + public void operate(CCRenderState state) { + state.setColour(apply(state.colour, state.normal)); + } + + @Override + public int operationID() { + return operationIndex; + } + + public PlanarLightModel reducePlanar() { + int[] colours = new int[6]; + for (int i = 0; i < 6; i++) colours[i] = apply(-1, Rotation.axes[i]); + return new PlanarLightModel(colours); + } +} diff --git a/ccl/src/main/java/codechicken/lib/lighting/PlanarLightMatrix.java b/ccl/src/main/java/codechicken/lib/lighting/PlanarLightMatrix.java new file mode 100644 index 0000000..798d382 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/lighting/PlanarLightMatrix.java @@ -0,0 +1,57 @@ +package codechicken.lib.lighting; + +import net.minecraft.block.Block; +import net.minecraft.world.IBlockAccess; + +import codechicken.lib.render.CCRenderState; +import codechicken.lib.vec.BlockCoord; + +public class PlanarLightMatrix extends PlanarLightModel { + + public static final int operationIndex = CCRenderState.registerOperation(); + public static PlanarLightMatrix instance = new PlanarLightMatrix(); + + public IBlockAccess access; + public BlockCoord pos = new BlockCoord(); + + private int sampled = 0; + public int[] brightness = new int[6]; + + public PlanarLightMatrix() { + super(PlanarLightModel.standardLightModel.colours); + } + + public PlanarLightMatrix locate(IBlockAccess a, int x, int y, int z) { + access = a; + pos.set(x, y, z); + sampled = 0; + return this; + } + + public int brightness(int side) { + if ((sampled & 1 << side) == 0) { + Block b = access.getBlock(pos.x, pos.y, pos.z); + brightness[side] = access + .getLightBrightnessForSkyBlocks(pos.x, pos.y, pos.z, b.getLightValue(access, pos.x, pos.y, pos.z)); + sampled |= 1 << side; + } + return brightness[side]; + } + + @Override + public boolean load(CCRenderState state) { + state.pipeline.addDependency(CCRenderState.sideAttrib); + return true; + } + + @Override + public void operate(CCRenderState state) { + super.operate(state); + state.setBrightness(brightness(state.side)); + } + + @Override + public int operationID() { + return operationIndex; + } +} diff --git a/ccl/src/main/java/codechicken/lib/lighting/PlanarLightModel.java b/ccl/src/main/java/codechicken/lib/lighting/PlanarLightModel.java new file mode 100644 index 0000000..280d962 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/lighting/PlanarLightModel.java @@ -0,0 +1,37 @@ +package codechicken.lib.lighting; + +import codechicken.lib.colour.ColourRGBA; +import codechicken.lib.render.CCRenderState; + +/** + * Faster precomputed version of LightModel that only works for axis planar sides + */ +public class PlanarLightModel implements CCRenderState.IVertexOperation { + + public static PlanarLightModel standardLightModel = LightModel.standardLightModel.reducePlanar(); + + public int[] colours; + + public PlanarLightModel(int[] colours) { + this.colours = colours; + } + + @Override + public boolean load(CCRenderState state) { + if (!state.computeLighting) return false; + + state.pipeline.addDependency(CCRenderState.sideAttrib); + state.pipeline.addDependency(CCRenderState.colourAttrib); + return true; + } + + @Override + public void operate(CCRenderState state) { + state.setColour(ColourRGBA.multiply(state.colour, colours[state.side])); + } + + @Override + public int operationID() { + return LightModel.operationIndex; + } +} diff --git a/ccl/src/main/java/codechicken/lib/lighting/SimpleBrightnessModel.java b/ccl/src/main/java/codechicken/lib/lighting/SimpleBrightnessModel.java new file mode 100644 index 0000000..c557993 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/lighting/SimpleBrightnessModel.java @@ -0,0 +1,56 @@ +package codechicken.lib.lighting; + +import net.minecraft.block.Block; +import net.minecraft.world.IBlockAccess; + +import codechicken.lib.render.CCRenderState; +import codechicken.lib.vec.BlockCoord; + +/** + * Faster precomputed version of LightModel that only works for axis planar sides + */ +public class SimpleBrightnessModel implements CCRenderState.IVertexOperation { + + public static final int operationIndex = CCRenderState.registerOperation(); + public static SimpleBrightnessModel instance = new SimpleBrightnessModel(); + + public IBlockAccess access; + public BlockCoord pos = new BlockCoord(); + + private int sampled = 0; + private final int[] samples = new int[6]; + private final BlockCoord c = new BlockCoord(); + + public void locate(IBlockAccess a, int x, int y, int z) { + access = a; + pos.set(x, y, z); + sampled = 0; + } + + public int sample(int side) { + if ((sampled & 1 << side) == 0) { + c.set(pos).offset(side); + Block block = access.getBlock(c.x, c.y, c.z); + samples[side] = access + .getLightBrightnessForSkyBlocks(c.x, c.y, c.z, block.getLightValue(access, c.x, c.y, c.z)); + sampled |= 1 << side; + } + return samples[side]; + } + + @Override + public boolean load(CCRenderState state) { + state.pipeline.addDependency(CCRenderState.sideAttrib); + return true; + } + + @Override + public void operate(CCRenderState state) { + state.setBrightness(sample(state.side)); + } + + @Override + public int operationID() { + return operationIndex; + } +} diff --git a/ccl/src/main/java/codechicken/lib/math/MathHelper.java b/ccl/src/main/java/codechicken/lib/math/MathHelper.java new file mode 100644 index 0000000..94220a0 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/math/MathHelper.java @@ -0,0 +1,152 @@ +package codechicken.lib.math; + +public class MathHelper { + + public static final double phi = 1.618033988749894; + public static final double pi = Math.PI; + public static final double todeg = 57.29577951308232; + public static final double torad = 0.017453292519943; + public static final double sqrt2 = 1.414213562373095; + + public static double[] SIN_TABLE = new double[65536]; + + static { + for (int i = 0; i < 65536; ++i) SIN_TABLE[i] = Math.sin(i / 65536D * 2 * Math.PI); + + SIN_TABLE[0] = 0; + SIN_TABLE[16384] = 1; + SIN_TABLE[32768] = 0; + SIN_TABLE[49152] = 1; + } + + public static double sin(double d) { + return SIN_TABLE[(int) ((float) d * 10430.378F) & 65535]; + } + + public static double cos(double d) { + return SIN_TABLE[(int) ((float) d * 10430.378F + 16384.0F) & 65535]; + } + + /** + * @param a The value + * @param b The value to approach + * @param max The maximum step + * @return the closed value to b no less than max from a + */ + public static float approachLinear(float a, float b, float max) { + return (a > b) ? (a - b < max ? b : a - max) : (b - a < max ? b : a + max); + } + + /** + * @param a The value + * @param b The value to approach + * @param max The maximum step + * @return the closed value to b no less than max from a + */ + public static double approachLinear(double a, double b, double max) { + return (a > b) ? (a - b < max ? b : a - max) : (b - a < max ? b : a + max); + } + + /** + * @param a The first value + * @param b The second value + * @param d The interpolation factor, between 0 and 1 + * @return a+(b-a)*d + */ + public static float interpolate(float a, float b, float d) { + return a + (b - a) * d; + } + + /** + * @param a The first value + * @param b The second value + * @param d The interpolation factor, between 0 and 1 + * @return a+(b-a)*d + */ + public static double interpolate(double a, double b, double d) { + return a + (b - a) * d; + } + + /** + * @param a The value + * @param b The value to approach + * @param ratio The ratio to reduce the difference by + * @return a+(b-a)*ratio + */ + public static double approachExp(double a, double b, double ratio) { + return a + (b - a) * ratio; + } + + /** + * @param a The value + * @param b The value to approach + * @param ratio The ratio to reduce the difference by + * @param cap The maximum amount to advance by + * @return a+(b-a)*ratio + */ + public static double approachExp(double a, double b, double ratio, double cap) { + double d = (b - a) * ratio; + if (Math.abs(d) > cap) d = Math.signum(d) * cap; + return a + d; + } + + /** + * @param a The value + * @param b The value to approach + * @param ratio The ratio to reduce the difference by + * @param c The value to retreat from + * @param kick The difference when a == c + * @return + */ + public static double retreatExp(double a, double b, double c, double ratio, double kick) { + double d = (Math.abs(c - a) + kick) * ratio; + if (d > Math.abs(b - a)) return b; + return a + Math.signum(b - a) * d; + } + + /** + * + * @param value The value + * @param min The min value + * @param max The max value + * @return The clipped value between min and max + */ + public static double clip(double value, double min, double max) { + if (value > max) value = max; + if (value < min) value = min; + return value; + } + + /** + * @return a <= x <= b + */ + public static boolean between(double a, double x, double b) { + return a <= x && x <= b; + } + + public static int approachExpI(int a, int b, double ratio) { + int r = (int) Math.round(approachExp(a, b, ratio)); + return r == a ? b : r; + } + + public static int retreatExpI(int a, int b, int c, double ratio, int kick) { + int r = (int) Math.round(retreatExp(a, b, c, ratio, kick)); + return r == a ? b : r; + } + + public static int floor_double(double d) { + return net.minecraft.util.MathHelper.floor_double(d); + } + + public static int roundAway(double d) { + return (int) (d < 0 ? Math.floor(d) : Math.ceil(d)); + } + + public static int compare(int a, int b) { + return a == b ? 0 : a < b ? -1 : 1; + } + + public static int compare(double a, double b) { + return a == b ? 0 : a < b ? -1 : 1; + } +} diff --git a/ccl/src/main/java/codechicken/lib/packet/ICustomPacketTile.java b/ccl/src/main/java/codechicken/lib/packet/ICustomPacketTile.java new file mode 100644 index 0000000..0758be7 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/packet/ICustomPacketTile.java @@ -0,0 +1,6 @@ +package codechicken.lib.packet; + +public interface ICustomPacketTile { + + public void handleDescriptionPacket(PacketCustom packet); +} diff --git a/ccl/src/main/java/codechicken/lib/packet/PacketCustom.java b/ccl/src/main/java/codechicken/lib/packet/PacketCustom.java new file mode 100644 index 0000000..67753d4 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/packet/PacketCustom.java @@ -0,0 +1,553 @@ +package codechicken.lib.packet; + +import java.util.EnumMap; +import java.util.List; +import java.util.zip.Deflater; +import java.util.zip.Inflater; + +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.item.Item; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.INetHandler; +import net.minecraft.network.NetHandlerPlayServer; +import net.minecraft.network.Packet; +import net.minecraft.network.play.INetHandlerPlayClient; +import net.minecraft.network.play.INetHandlerPlayServer; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.management.PlayerManager; +import net.minecraft.world.World; +import net.minecraft.world.WorldServer; +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidRegistry; +import net.minecraftforge.fluids.FluidStack; + +import com.google.common.collect.Maps; + +import codechicken.lib.data.MCDataInput; +import codechicken.lib.data.MCDataOutput; +import codechicken.lib.vec.BlockCoord; +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.ModContainer; +import cpw.mods.fml.common.network.ByteBufUtils; +import cpw.mods.fml.common.network.FMLEmbeddedChannel; +import cpw.mods.fml.common.network.FMLOutboundHandler; +import cpw.mods.fml.common.network.NetworkHandshakeEstablished; +import cpw.mods.fml.common.network.NetworkRegistry; +import cpw.mods.fml.common.network.handshake.NetworkDispatcher; +import cpw.mods.fml.common.network.internal.FMLProxyPacket; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandler; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.util.AttributeKey; + +public final class PacketCustom implements MCDataInput, MCDataOutput { + + public static interface ICustomPacketHandler { + } + + public interface IClientPacketHandler extends ICustomPacketHandler { + + public void handlePacket(PacketCustom packetCustom, Minecraft mc, INetHandlerPlayClient handler); + } + + public interface IServerPacketHandler extends ICustomPacketHandler { + + public void handlePacket(PacketCustom packetCustom, EntityPlayerMP sender, INetHandlerPlayServer handler); + } + + public static AttributeKey cclHandler = new AttributeKey("ccl:handler"); + + @ChannelHandler.Sharable + public static class CustomInboundHandler extends SimpleChannelInboundHandler { + + public EnumMap handlers = Maps.newEnumMap(Side.class); + + @Override + public void handlerAdded(ChannelHandlerContext ctx) throws Exception { + super.handlerAdded(ctx); + ctx.channel().attr(cclHandler).set(this); + } + + @Override + protected void channelRead0(ChannelHandlerContext ctx, FMLProxyPacket msg) throws Exception { + handlers.get(ctx.channel().attr(NetworkRegistry.CHANNEL_SOURCE).get()).handle( + ctx.channel().attr(NetworkRegistry.NET_HANDLER).get(), + ctx.channel().attr(NetworkRegistry.FML_CHANNEL).get(), + new PacketCustom(msg.payload())); + } + } + + private static interface CustomHandler { + + public void handle(INetHandler handler, String channel, PacketCustom packet) throws Exception; + } + + public static class ClientInboundHandler implements CustomHandler { + + private IClientPacketHandler handler; + + public ClientInboundHandler(ICustomPacketHandler handler) { + this.handler = (IClientPacketHandler) handler; + } + + @Override + public void handle(INetHandler netHandler, String channel, PacketCustom packet) throws Exception { + if (netHandler instanceof INetHandlerPlayClient) + handler.handlePacket(packet, Minecraft.getMinecraft(), (INetHandlerPlayClient) netHandler); + else System.err.println("Invalid INetHandler for PacketCustom on channel: " + channel); + } + } + + public static class ServerInboundHandler implements CustomHandler { + + private IServerPacketHandler handler; + + public ServerInboundHandler(ICustomPacketHandler handler) { + this.handler = (IServerPacketHandler) handler; + } + + @Override + public void handle(INetHandler netHandler, String channel, PacketCustom packet) throws Exception { + if (netHandler instanceof NetHandlerPlayServer) handler.handlePacket( + packet, + ((NetHandlerPlayServer) netHandler).playerEntity, + (INetHandlerPlayServer) netHandler); + else System.err.println("Invalid INetHandler for PacketCustom on channel: " + channel); + } + } + + public static interface IHandshakeHandler { + + public void handshakeRecieved(NetHandlerPlayServer netHandler); + } + + public static class HandshakeInboundHandler extends ChannelInboundHandlerAdapter { + + public IHandshakeHandler handler; + + public HandshakeInboundHandler(IHandshakeHandler handler) { + this.handler = handler; + } + + @Override + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + if (evt instanceof NetworkHandshakeEstablished) { + INetHandler netHandler = ((NetworkDispatcher) ctx.channel() + .attr(FMLOutboundHandler.FML_MESSAGETARGETARGS).get()).getNetHandler(); + if (netHandler instanceof NetHandlerPlayServer) + handler.handshakeRecieved((NetHandlerPlayServer) netHandler); + } else ctx.fireUserEventTriggered(evt); + } + } + + public static String channelName(Object channelKey) { + if (channelKey instanceof String) return (String) channelKey; + + ModContainer mc = channelKey instanceof ModContainer ? (ModContainer) channelKey + : FMLCommonHandler.instance().findContainerFor(channelKey); + if (mc != null) { + String s = mc.getModId(); + if (s.length() > 20) throw new IllegalArgumentException( + "Mod ID (" + s + ") too long for use as channel (20 chars). Use a string identifier"); + return s; + } + + throw new IllegalArgumentException("Invalid channel: " + channelKey); + } + + public static FMLEmbeddedChannel getOrCreateChannel(String channelName, Side side) { + if (!NetworkRegistry.INSTANCE.hasChannel(channelName, side)) + NetworkRegistry.INSTANCE.newChannel(channelName, new CustomInboundHandler()); + return NetworkRegistry.INSTANCE.getChannel(channelName, side); + } + + public static void assignHandler(Object channelKey, ICustomPacketHandler handler) { + String channelName = channelName(channelKey); + Side side = handler instanceof IServerPacketHandler ? Side.SERVER : Side.CLIENT; + FMLEmbeddedChannel channel = getOrCreateChannel(channelName, side); + channel.attr(cclHandler).get().handlers + .put(side, side == Side.SERVER ? new ServerInboundHandler(handler) : new ClientInboundHandler(handler)); + } + + public static void assignHandshakeHandler(Object channelKey, IHandshakeHandler handler) { + FMLEmbeddedChannel channel = getOrCreateChannel(channelName(channelKey), Side.SERVER); + channel.pipeline().addLast(new HandshakeInboundHandler(handler)); + } + + private ByteBuf byteBuf; + private String channel; + private int type; + + public PacketCustom(ByteBuf payload) { + byteBuf = payload; + + type = byteBuf.readUnsignedByte(); + if (type > 0x80) decompress(); + type &= 0x7F; + } + + public PacketCustom(Object channelKey, int type) { + if (type <= 0 || type >= 0x80) + throw new IllegalArgumentException("Packet type: " + type + " is not within required 0 < t < 0x80"); + + this.channel = channelName(channelKey); + this.type = type; + byteBuf = Unpooled.buffer(); + byteBuf.writeByte(type); + } + + /** + * Decompresses the remaining ByteBuf (after type has been read) using Snappy + */ + private void decompress() { + Inflater inflater = new Inflater(); + try { + int len = byteBuf.readInt(); + ByteBuf out = Unpooled.buffer(len); + inflater.setInput(byteBuf.array(), byteBuf.readerIndex(), byteBuf.readableBytes()); + inflater.inflate(out.array()); + out.writerIndex(len); + byteBuf = out; + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + inflater.end(); + } + } + + /** + * Compresses the payload ByteBuf after the type byte + */ + private void do_compress() { + Deflater deflater = new Deflater(); + try { + byteBuf.readerIndex(1); + int len = byteBuf.readableBytes(); + deflater.setInput(byteBuf.array(), byteBuf.readerIndex(), len); + deflater.finish(); + ByteBuf out = Unpooled.buffer(len + 5); + int clen = deflater.deflate(out.array(), 5, len); + if (clen >= len - 5 || !deflater.finished()) // not worth compressing, gets larger + return; + + out.setByte(0, type | 0x80); + out.setInt(1, len); + out.writerIndex(clen + 5); + byteBuf = out; + } catch (Exception e) { + throw new RuntimeException(e); + } finally { + byteBuf.readerIndex(0); + deflater.end(); + } + } + + public boolean incoming() { + return channel == null; + } + + public int getType() { + return type & 0x7F; + } + + public ByteBuf getByteBuf() { + return byteBuf; + } + + public PacketCustom compress() { + if (incoming()) throw new IllegalStateException("Tried to compress an incoming packet"); + if ((type & 0x80) != 0) throw new IllegalStateException("Packet already compressed"); + type |= 0x80; + return this; + } + + public PacketCustom writeBoolean(boolean b) { + byteBuf.writeBoolean(b); + return this; + } + + public PacketCustom writeByte(int b) { + byteBuf.writeByte(b); + return this; + } + + public PacketCustom writeShort(int s) { + byteBuf.writeShort(s); + return this; + } + + public PacketCustom writeInt(int i) { + byteBuf.writeInt(i); + return this; + } + + public PacketCustom writeFloat(float f) { + byteBuf.writeFloat(f); + return this; + } + + public PacketCustom writeDouble(double d) { + byteBuf.writeDouble(d); + return this; + } + + public PacketCustom writeLong(long l) { + byteBuf.writeLong(l); + return this; + } + + @Override + public PacketCustom writeChar(char c) { + byteBuf.writeChar(c); + return this; + } + + public PacketCustom writeVarInt(int i) { + ByteBufUtils.writeVarInt(byteBuf, i, 5); + return this; + } + + public PacketCustom writeVarShort(int s) { + ByteBufUtils.writeVarShort(byteBuf, s); + return this; + } + + public PacketCustom writeByteArray(byte[] barray) { + byteBuf.writeBytes(barray); + return this; + } + + public PacketCustom writeString(String s) { + ByteBufUtils.writeUTF8String(byteBuf, s); + return this; + } + + public PacketCustom writeCoord(int x, int y, int z) { + writeInt(x); + writeInt(y); + writeInt(z); + return this; + } + + public PacketCustom writeCoord(BlockCoord coord) { + writeInt(coord.x); + writeInt(coord.y); + writeInt(coord.z); + return this; + } + + public PacketCustom writeItemStack(ItemStack stack) { + writeItemStack(stack, false); + return this; + } + + public PacketCustom writeItemStack(ItemStack stack, boolean large) { + if (stack == null) { + writeInt(-1); + } else { + writeInt(Item.getIdFromItem(stack.getItem())); + if (large) writeInt(stack.stackSize); + else writeByte(stack.stackSize); + writeShort(stack.getItemDamage()); + writeNBTTagCompound(stack.stackTagCompound); + } + return this; + } + + public PacketCustom writeNBTTagCompound(NBTTagCompound compound) { + ByteBufUtils.writeTag(byteBuf, compound); + return this; + } + + public PacketCustom writeFluidStack(FluidStack fluid) { + if (fluid == null) { + writeShort(-1); + } else { + writeShort(fluid.getFluidID()); + writeVarInt(fluid.amount); + writeNBTTagCompound(fluid.tag); + } + return this; + } + + public boolean readBoolean() { + return byteBuf.readBoolean(); + } + + public short readUByte() { + return byteBuf.readUnsignedByte(); + } + + public int readUShort() { + return byteBuf.readUnsignedShort(); + } + + public byte readByte() { + return byteBuf.readByte(); + } + + public short readShort() { + return byteBuf.readShort(); + } + + public int readInt() { + return byteBuf.readInt(); + } + + public float readFloat() { + return byteBuf.readFloat(); + } + + public double readDouble() { + return byteBuf.readDouble(); + } + + public long readLong() { + return byteBuf.readLong(); + } + + public char readChar() { + return byteBuf.readChar(); + } + + @Override + public int readVarShort() { + return ByteBufUtils.readVarShort(byteBuf); + } + + @Override + public int readVarInt() { + return ByteBufUtils.readVarInt(byteBuf, 5); + } + + public BlockCoord readCoord() { + return new BlockCoord(readInt(), readInt(), readInt()); + } + + public byte[] readByteArray(int length) { + byte[] barray = new byte[length]; + byteBuf.readBytes(barray, 0, length); + return barray; + } + + public String readString() { + return ByteBufUtils.readUTF8String(byteBuf); + } + + public ItemStack readItemStack() { + return readItemStack(false); + } + + public ItemStack readItemStack(boolean large) { + ItemStack item = null; + int itemID = readInt(); + + if (itemID >= 0) { + int stackSize = large ? readInt() : readByte(); + short damage = readShort(); + item = new ItemStack(Item.getItemById(itemID), stackSize, damage); + item.stackTagCompound = readNBTTagCompound(); + } + + return item; + } + + public NBTTagCompound readNBTTagCompound() { + return ByteBufUtils.readTag(byteBuf); + } + + public FluidStack readFluidStack() { + Fluid fluid = FluidRegistry.getFluid(readShort()); + if (fluid == null) fluid = FluidRegistry.WATER; + + return new FluidStack(fluid, readVarInt(), readNBTTagCompound()); + } + + public FMLProxyPacket toPacket() { + if (incoming()) throw new IllegalStateException("Tried to write an incoming packet"); + + if (byteBuf.readableBytes() > 32000 || (type & 0x80) != 0) do_compress(); + + // FML packet impl returns the whole of the backing array, copy used portion of array to another ByteBuf + return new FMLProxyPacket(byteBuf.copy(), channel); + } + + public void sendToPlayer(EntityPlayer player) { + sendToPlayer(toPacket(), player); + } + + public static void sendToPlayer(Packet packet, EntityPlayer player) { + if (player == null) sendToClients(packet); + else((EntityPlayerMP) player).playerNetServerHandler.sendPacket(packet); + } + + public void sendToClients() { + sendToClients(toPacket()); + } + + public static void sendToClients(Packet packet) { + MinecraftServer.getServer().getConfigurationManager().sendPacketToAllPlayers(packet); + } + + public void sendPacketToAllAround(double x, double y, double z, double range, int dim) { + sendToAllAround(toPacket(), x, y, z, range, dim); + } + + public static void sendToAllAround(Packet packet, double x, double y, double z, double range, int dim) { + MinecraftServer.getServer().getConfigurationManager().sendToAllNear(x, y, z, range, dim, packet); + } + + public void sendToDimension(int dim) { + sendToDimension(toPacket(), dim); + } + + public static void sendToDimension(Packet packet, int dim) { + MinecraftServer.getServer().getConfigurationManager().sendPacketToAllPlayersInDimension(packet, dim); + } + + public void sendToChunk(World world, int chunkX, int chunkZ) { + sendToChunk(toPacket(), world, chunkX, chunkZ); + } + + public static void sendToChunk(Packet packet, World world, int chunkX, int chunkZ) { + PlayerManager playerManager = ((WorldServer) world).getPlayerManager(); + for (EntityPlayerMP player : (List) MinecraftServer.getServer() + .getConfigurationManager().playerEntityList) + if (playerManager.isPlayerWatchingChunk(player, chunkX, chunkZ)) sendToPlayer(packet, player); + + /* + * Commented until forge accepts access tranformer request PlayerInstance p = ((WorldServer) + * world).getPlayerManager().getOrCreateChunkWatcher(chunkX, chunkZ, false); if (p != null) + * p.sendToAllPlayersWatchingChunk(packet); + */ + } + + public void sendToOps() { + sendToOps(toPacket()); + } + + public static void sendToOps(Packet packet) { + for (EntityPlayerMP player : (List) MinecraftServer.getServer() + .getConfigurationManager().playerEntityList) + if (MinecraftServer.getServer().getConfigurationManager().func_152596_g(player.getGameProfile())) + sendToPlayer(packet, player); + } + + @SideOnly(Side.CLIENT) + public void sendToServer() { + sendToServer(toPacket()); + } + + @SideOnly(Side.CLIENT) + public static void sendToServer(Packet packet) { + Minecraft.getMinecraft().getNetHandler().addToSendQueue(packet); + } +} diff --git a/ccl/src/main/java/codechicken/lib/raytracer/ExtendedMOP.java b/ccl/src/main/java/codechicken/lib/raytracer/ExtendedMOP.java new file mode 100644 index 0000000..4774d5a --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/raytracer/ExtendedMOP.java @@ -0,0 +1,53 @@ +package codechicken.lib.raytracer; + +import net.minecraft.entity.Entity; +import net.minecraft.util.MovingObjectPosition; +import net.minecraft.util.Vec3; + +public class ExtendedMOP extends MovingObjectPosition implements Comparable { + + public Object data; + /** + * The square distance from the start of the raytrace. + */ + public double dist; + + public ExtendedMOP(Entity entity, Object data) { + super(entity); + setData(data); + } + + public ExtendedMOP(int x, int y, int z, int side, Vec3 hit, Object data) { + super(x, y, z, side, hit); + setData(data); + } + + public ExtendedMOP(MovingObjectPosition mop, Object data, double dist) { + super(0, 0, 0, 0, mop.hitVec); + typeOfHit = mop.typeOfHit; + blockX = mop.blockX; + blockY = mop.blockY; + blockZ = mop.blockZ; + sideHit = mop.sideHit; + subHit = mop.subHit; + setData(data); + this.dist = dist; + } + + public void setData(Object data) { + if (data instanceof Integer) subHit = ((Integer) data).intValue(); + this.data = data; + } + + @SuppressWarnings("unchecked") + public static T getData(MovingObjectPosition mop) { + if (mop instanceof ExtendedMOP) return (T) ((ExtendedMOP) mop).data; + + return (T) Integer.valueOf(mop.subHit); + } + + @Override + public int compareTo(ExtendedMOP o) { + return dist == o.dist ? 0 : dist < o.dist ? -1 : 1; + } +} diff --git a/ccl/src/main/java/codechicken/lib/raytracer/IndexedCuboid6.java b/ccl/src/main/java/codechicken/lib/raytracer/IndexedCuboid6.java new file mode 100644 index 0000000..26651ff --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/raytracer/IndexedCuboid6.java @@ -0,0 +1,13 @@ +package codechicken.lib.raytracer; + +import codechicken.lib.vec.Cuboid6; + +public class IndexedCuboid6 extends Cuboid6 { + + public Object data; + + public IndexedCuboid6(Object data, Cuboid6 cuboid) { + super(cuboid); + this.data = data; + } +} diff --git a/ccl/src/main/java/codechicken/lib/raytracer/RayTracer.java b/ccl/src/main/java/codechicken/lib/raytracer/RayTracer.java new file mode 100644 index 0000000..d06c0fb --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/raytracer/RayTracer.java @@ -0,0 +1,193 @@ +package codechicken.lib.raytracer; + +import java.util.List; + +import net.minecraft.block.Block; +import net.minecraft.client.Minecraft; +import net.minecraft.entity.player.EntityPlayer; +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.util.MovingObjectPosition; +import net.minecraft.util.MovingObjectPosition.MovingObjectType; +import net.minecraft.util.Vec3; +import net.minecraft.world.World; + +import codechicken.lib.math.MathHelper; +import codechicken.lib.vec.BlockCoord; +import codechicken.lib.vec.Cuboid6; +import codechicken.lib.vec.Vector3; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class RayTracer { + + private Vector3 vec = new Vector3(); + private Vector3 vec2 = new Vector3(); + + private Vector3 s_vec = new Vector3(); + private double s_dist; + private int s_side; + private IndexedCuboid6 c_cuboid; + + private static ThreadLocal t_inst = new ThreadLocal(); + + public static RayTracer instance() { + RayTracer inst = t_inst.get(); + if (inst == null) t_inst.set(inst = new RayTracer()); + return inst; + } + + private void traceSide(int side, Vector3 start, Vector3 end, Cuboid6 cuboid) { + vec.set(start); + Vector3 hit = null; + switch (side) { + case 0: + hit = vec.XZintercept(end, cuboid.min.y); + break; + case 1: + hit = vec.XZintercept(end, cuboid.max.y); + break; + case 2: + hit = vec.XYintercept(end, cuboid.min.z); + break; + case 3: + hit = vec.XYintercept(end, cuboid.max.z); + break; + case 4: + hit = vec.YZintercept(end, cuboid.min.x); + break; + case 5: + hit = vec.YZintercept(end, cuboid.max.x); + break; + } + if (hit == null) return; + + switch (side) { + case 0: + case 1: + if (!MathHelper.between(cuboid.min.x, hit.x, cuboid.max.x) + || !MathHelper.between(cuboid.min.z, hit.z, cuboid.max.z)) + return; + break; + case 2: + case 3: + if (!MathHelper.between(cuboid.min.x, hit.x, cuboid.max.x) + || !MathHelper.between(cuboid.min.y, hit.y, cuboid.max.y)) + return; + break; + case 4: + case 5: + if (!MathHelper.between(cuboid.min.y, hit.y, cuboid.max.y) + || !MathHelper.between(cuboid.min.z, hit.z, cuboid.max.z)) + return; + break; + } + + double dist = vec2.set(hit).subtract(start).magSquared(); + if (dist < s_dist) { + s_side = side; + s_dist = dist; + s_vec.set(vec); + } + } + + public MovingObjectPosition rayTraceCuboid(Vector3 start, Vector3 end, Cuboid6 cuboid) { + s_dist = Double.MAX_VALUE; + s_side = -1; + + for (int i = 0; i < 6; i++) traceSide(i, start, end, cuboid); + + if (s_side < 0) return null; + + MovingObjectPosition mop = new MovingObjectPosition(0, 0, 0, s_side, s_vec.toVec3D()); + mop.typeOfHit = null; + return mop; + } + + public MovingObjectPosition rayTraceCuboids(Vector3 start, Vector3 end, List cuboids) { + double c_dist = Double.MAX_VALUE; + MovingObjectPosition c_hit = null; + + for (IndexedCuboid6 cuboid : cuboids) { + MovingObjectPosition mop = rayTraceCuboid(start, end, cuboid); + if (mop != null && s_dist < c_dist) { + mop = new ExtendedMOP(mop, cuboid.data, s_dist); + c_dist = s_dist; + c_hit = mop; + c_cuboid = cuboid; + } + } + + return c_hit; + } + + public MovingObjectPosition rayTraceCuboids(Vector3 start, Vector3 end, List cuboids, + BlockCoord pos, Block block) { + MovingObjectPosition mop = rayTraceCuboids(start, end, cuboids); + if (mop != null) { + mop.typeOfHit = MovingObjectType.BLOCK; + mop.blockX = pos.x; + mop.blockY = pos.y; + mop.blockZ = pos.z; + if (block != null) c_cuboid.add(new Vector3(-pos.x, -pos.y, -pos.z)).setBlockBounds(block); + } + return mop; + } + + public static MovingObjectPosition retraceBlock(World world, EntityPlayer player, int x, int y, int z) { + Block block = world.getBlock(x, y, z); + + Vec3 headVec = getCorrectedHeadVec(player); + Vec3 lookVec = player.getLook(1.0F); + double reach = getBlockReachDistance(player); + Vec3 endVec = headVec.addVector(lookVec.xCoord * reach, lookVec.yCoord * reach, lookVec.zCoord * reach); + return block.collisionRayTrace(world, x, y, z, headVec, endVec); + } + + private static double getBlockReachDistance_server(EntityPlayerMP player) { + return player.theItemInWorldManager.getBlockReachDistance(); + } + + @SideOnly(Side.CLIENT) + private static double getBlockReachDistance_client() { + return Minecraft.getMinecraft().playerController.getBlockReachDistance(); + } + + public static MovingObjectPosition reTrace(World world, EntityPlayer player) { + return reTrace(world, player, getBlockReachDistance(player)); + } + + public static MovingObjectPosition reTrace(World world, EntityPlayer player, double reach) { + Vec3 headVec = getCorrectedHeadVec(player); + Vec3 lookVec = player.getLook(1); + Vec3 endVec = headVec.addVector(lookVec.xCoord * reach, lookVec.yCoord * reach, lookVec.zCoord * reach); + return world.func_147447_a(headVec, endVec, true, false, true); + } + + public static Vec3 getCorrectedHeadVec(EntityPlayer player) { + Vec3 v = Vec3.createVectorHelper(player.posX, player.posY, player.posZ); + if (player.worldObj.isRemote) { + v.yCoord += player.getEyeHeight() - player.getDefaultEyeHeight(); // compatibility with eye height changing + // mods + } else { + v.yCoord += player.getEyeHeight(); + if (player instanceof EntityPlayerMP && player.isSneaking()) v.yCoord -= 0.08; + } + return v; + } + + public static Vec3 getStartVec(EntityPlayer player) { + return getCorrectedHeadVec(player); + } + + public static double getBlockReachDistance(EntityPlayer player) { + return player.worldObj.isRemote ? getBlockReachDistance_client() + : player instanceof EntityPlayerMP ? getBlockReachDistance_server((EntityPlayerMP) player) : 5D; + } + + public static Vec3 getEndVec(EntityPlayer player) { + Vec3 headVec = getCorrectedHeadVec(player); + Vec3 lookVec = player.getLook(1.0F); + double reach = getBlockReachDistance(player); + return headVec.addVector(lookVec.xCoord * reach, lookVec.yCoord * reach, lookVec.zCoord * reach); + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/BlockRenderer.java b/ccl/src/main/java/codechicken/lib/render/BlockRenderer.java new file mode 100644 index 0000000..09f7a8b --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/BlockRenderer.java @@ -0,0 +1,204 @@ +package codechicken.lib.render; + +import codechicken.lib.lighting.LC; +import codechicken.lib.render.CCRenderState.VertexAttribute; +import codechicken.lib.vec.Cuboid6; + +public class BlockRenderer { + + public static class BlockFace implements CCRenderState.IVertexSource { + + public Vertex5[] verts = new Vertex5[] { new Vertex5(), new Vertex5(), new Vertex5(), new Vertex5() }; + public LC[] lightCoords = new LC[] { new LC(), new LC(), new LC(), new LC() }; + public boolean lcComputed = false; + public int side; + + @Override + public Vertex5[] getVertices() { + return verts; + } + + @Override + public T getAttributes(CCRenderState.VertexAttribute attr) { + return attr == CCRenderState.lightCoordAttrib && lcComputed ? (T) lightCoords : null; + } + + @Override + public boolean hasAttribute(CCRenderState.VertexAttribute attr) { + return attr == CCRenderState.sideAttrib || attr == CCRenderState.lightCoordAttrib && lcComputed; + } + + @Override + public void prepareVertex(CCRenderState state) { + state.side = side; + } + + public BlockFace computeLightCoords() { + if (!lcComputed) { + for (int i = 0; i < 4; i++) lightCoords[i].compute(verts[i].vec, side); + lcComputed = true; + } + return this; + } + + public BlockFace loadCuboidFace(Cuboid6 c, int side) { + double x1 = c.min.x; + double x2 = c.max.x; + double y1 = c.min.y; + double y2 = c.max.y; + double z1 = c.min.z; + double z2 = c.max.z; + double u1; + double u2; + double v1; + double v2; + this.side = side; + lcComputed = false; + + switch (side) { + case 0: + u1 = x1; + v1 = z1; + u2 = x2; + v2 = z2; + verts[0].set(x1, y1, z2, u1, v2, 0); + verts[1].set(x1, y1, z1, u1, v1, 0); + verts[2].set(x2, y1, z1, u2, v1, 0); + verts[3].set(x2, y1, z2, u2, v2, 0); + break; + case 1: + u1 = x1; + v1 = z1; + u2 = x2; + v2 = z2; + verts[0].set(x2, y2, z2, u2, v2, 1); + verts[1].set(x2, y2, z1, u2, v1, 1); + verts[2].set(x1, y2, z1, u1, v1, 1); + verts[3].set(x1, y2, z2, u1, v2, 1); + break; + case 2: + u1 = 1 - x1; + v1 = 1 - y2; + u2 = 1 - x2; + v2 = 1 - y1; + verts[0].set(x1, y1, z1, u1, v2, 2); + verts[1].set(x1, y2, z1, u1, v1, 2); + verts[2].set(x2, y2, z1, u2, v1, 2); + verts[3].set(x2, y1, z1, u2, v2, 2); + break; + case 3: + u1 = x1; + v1 = 1 - y2; + u2 = x2; + v2 = 1 - y1; + verts[0].set(x2, y1, z2, u2, v2, 3); + verts[1].set(x2, y2, z2, u2, v1, 3); + verts[2].set(x1, y2, z2, u1, v1, 3); + verts[3].set(x1, y1, z2, u1, v2, 3); + break; + case 4: + u1 = z1; + v1 = 1 - y2; + u2 = z2; + v2 = 1 - y1; + verts[0].set(x1, y1, z2, u2, v2, 4); + verts[1].set(x1, y2, z2, u2, v1, 4); + verts[2].set(x1, y2, z1, u1, v1, 4); + verts[3].set(x1, y1, z1, u1, v2, 4); + break; + case 5: + u1 = 1 - z1; + v1 = 1 - y2; + u2 = 1 - z2; + v2 = 1 - y1; + verts[0].set(x2, y1, z1, u1, v2, 5); + verts[1].set(x2, y2, z1, u1, v1, 5); + verts[2].set(x2, y2, z2, u2, v1, 5); + verts[3].set(x2, y1, z2, u2, v2, 5); + } + return this; + } + } + + public static class FullBlock implements CCRenderState.IVertexSource { + + public Vertex5[] verts = CCModel.quadModel(24).generateBlock(0, Cuboid6.full).verts; + public LC[] lightCoords = new LC[24]; + + public FullBlock() { + for (int i = 0; i < 24; i++) lightCoords[i] = new LC().compute(verts[i].vec, i / 4); + } + + @Override + public Vertex5[] getVertices() { + return verts; + } + + @Override + public T getAttributes(VertexAttribute attr) { + return attr == CCRenderState.lightCoordAttrib ? (T) lightCoords : null; + } + + @Override + public boolean hasAttribute(VertexAttribute attr) { + return attr == CCRenderState.sideAttrib || attr == CCRenderState.lightCoordAttrib; + } + + @Override + public void prepareVertex(CCRenderState state) { + state.side = state.vertexIndex >> 2; + } + } + + public static FullBlock fullBlock = new FullBlock(); + + // public static void renderFullBlock(int sideMask) { + public static void renderFullBlock(CCRenderState state, int sideMask) { + state.setModel(fullBlock); + renderFaces(state, sideMask); + } + + public static void renderFullBlock(int sideMask) { + renderFullBlock(CCRenderState.instance(), sideMask); + } + + /** + * Renders faces of a block-like model based on a sideMask. Eg for side 2, verts 8-11 will be rendered + * + * @param sideMask A mask of faces not to render + */ + // public static void renderFaces(int sideMask) { + public static void renderFaces(CCRenderState state, int sideMask) { + if (sideMask == 0x3F) return; + for (int s = 0; s < 6; s++) if ((sideMask & 1 << s) == 0) { + state.setVertexRange(s * 4, (s + 1) * 4); + state.render(); + } + } + + public static void renderFaces(int sideMask) { + renderFaces(CCRenderState.instance(), sideMask); + } + + private static final BlockFace face = new BlockFace(); + + /** + * Renders faces of a cuboid with texture coordinates mapped to match a standard minecraft block + * + * @param bounds The bounding cuboid to render + * @param sideMask A mask of faces not to render + */ + public static void renderCuboid(CCRenderState state, Cuboid6 bounds, int sideMask) { + if (sideMask == 0x3F) return; + + state.setModel(face); + for (int s = 0; s < 6; s++) if ((sideMask & 1 << s) == 0) { + face.loadCuboidFace(bounds, s); + state.render(); + } + } + + public static void renderCuboid(Cuboid6 bounds, int sideMask) { + renderCuboid(CCRenderState.instance(), bounds, sideMask); + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/CCModel.java b/ccl/src/main/java/codechicken/lib/render/CCModel.java new file mode 100644 index 0000000..b9a5e4b --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/CCModel.java @@ -0,0 +1,1024 @@ +package codechicken.lib.render; + +import static codechicken.lib.vec.Rotation.sideRotations; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import net.minecraft.client.Minecraft; +import net.minecraft.util.ResourceLocation; + +import codechicken.lib.lighting.LC; +import codechicken.lib.lighting.LightModel; +import codechicken.lib.render.uv.UV; +import codechicken.lib.render.uv.UVTransformation; +import codechicken.lib.render.uv.UVTranslation; +import codechicken.lib.util.Copyable; +import codechicken.lib.vec.Cuboid6; +import codechicken.lib.vec.RedundantTransformation; +import codechicken.lib.vec.Transformation; +import codechicken.lib.vec.TransformationList; +import codechicken.lib.vec.Vector3; + +@SuppressWarnings("ForLoopReplaceableByForEach") +public class CCModel implements CCRenderState.IVertexSource, Copyable { + + private static class PositionNormalEntry { + + public Vector3 pos; + public LinkedList normals = new LinkedList(); + + public PositionNormalEntry(Vector3 position) { + pos = position; + } + + public boolean positionEqual(Vector3 v) { + return pos.x == v.x && pos.y == v.y && pos.z == v.z; + } + + public PositionNormalEntry addNormal(Vector3 normal) { + normals.add(normal); + return this; + } + } + + public final int vertexMode; + public final int vp; + public Vertex5[] verts; + public ArrayList attributes = new ArrayList(); + + protected CCModel(int vertexMode) { + if (vertexMode != 7 && vertexMode != 4) + throw new IllegalArgumentException("Models must be GL_QUADS or GL_TRIANGLES"); + + this.vertexMode = vertexMode; + vp = vertexMode == 7 ? 4 : 3; + } + + public Vector3[] normals() { + return getAttributes(CCRenderState.normalAttrib); + } + + @Override + public Vertex5[] getVertices() { + return verts; + } + + @Override + public T getAttributes(CCRenderState.VertexAttribute attr) { + if (attr.attributeIndex < attributes.size()) return (T) attributes.get(attr.attributeIndex); + + return null; + } + + @Override + public boolean hasAttribute(CCRenderState.VertexAttribute attrib) { + return attrib.attributeIndex < attributes.size() && attributes.get(attrib.attributeIndex) != null; + } + + @Override + public void prepareVertex(CCRenderState state) {} + + public T getOrAllocate(CCRenderState.VertexAttribute attrib) { + T array = getAttributes(attrib); + if (array == null) { + while (attributes.size() <= attrib.attributeIndex) attributes.add(null); + attributes.set(attrib.attributeIndex, array = attrib.newArray(verts.length)); + } + return array; + } + + /** + * Each pixel corresponds to one unit of position when generating the model + * + * @param i Vertex index to start generating at + * @param x1 The minX bound of the box + * @param y1 The minY bound of the box + * @param z1 The minZ bound of the box + * @param w The width of the box + * @param h The height of the box + * @param d The depth of the box + * @param tx The distance of the top left corner of the texture map from the left in pixels + * @param ty The distance of the top left corner of the texture map from the top in pixels + * @param tw The width of the texture in pixels + * @param th The height of the texture in pixels + * @param f The scale of the model, pixels per block, normally 16 + * @return The generated model + */ + public CCModel generateBox(int i, double x1, double y1, double z1, double w, double h, double d, double tx, + double ty, double tw, double th, double f) { + double u1, v1, u2, v2; + double x2 = x1 + w; + double y2 = y1 + h; + double z2 = z1 + d; + x1 /= f; + x2 /= f; + y1 /= f; + y2 /= f; + z1 /= f; + z2 /= f; + + // bottom face + u1 = (tx + d + w) / tw; + v1 = (ty + d) / th; + u2 = (tx + d * 2 + w) / tw; + v2 = ty / th; + verts[i++] = new Vertex5(x1, y1, z2, u1, v2); + verts[i++] = new Vertex5(x1, y1, z1, u1, v1); + verts[i++] = new Vertex5(x2, y1, z1, u2, v1); + verts[i++] = new Vertex5(x2, y1, z2, u2, v2); + + // top face + u1 = (tx + d) / tw; + v1 = (ty + d) / th; + u2 = (tx + d + w) / tw; + v2 = ty / th; + verts[i++] = new Vertex5(x2, y2, z2, u2, v2); + verts[i++] = new Vertex5(x2, y2, z1, u2, v1); + verts[i++] = new Vertex5(x1, y2, z1, u1, v1); + verts[i++] = new Vertex5(x1, y2, z2, u1, v2); + + // front face + u1 = (tx + d + w) / tw; + v1 = (ty + d) / th; + u2 = (tx + d) / tw; + v2 = (ty + d + h) / th; + verts[i++] = new Vertex5(x1, y2, z1, u2, v1); + verts[i++] = new Vertex5(x2, y2, z1, u1, v1); + verts[i++] = new Vertex5(x2, y1, z1, u1, v2); + verts[i++] = new Vertex5(x1, y1, z1, u2, v2); + + // back face + u1 = (tx + d * 2 + w * 2) / tw; + v1 = (ty + d) / th; + u2 = (tx + d * 2 + w) / tw; + v2 = (ty + d + h) / th; + verts[i++] = new Vertex5(x1, y2, z2, u1, v1); + verts[i++] = new Vertex5(x1, y1, z2, u1, v2); + verts[i++] = new Vertex5(x2, y1, z2, u2, v2); + verts[i++] = new Vertex5(x2, y2, z2, u2, v1); + + // left face + u1 = (tx + d) / tw; + v1 = (ty + d) / th; + u2 = (tx) / tw; + v2 = (ty + d + h) / th; + verts[i++] = new Vertex5(x1, y2, z2, u2, v1); + verts[i++] = new Vertex5(x1, y2, z1, u1, v1); + verts[i++] = new Vertex5(x1, y1, z1, u1, v2); + verts[i++] = new Vertex5(x1, y1, z2, u2, v2); + + // right face + u1 = (tx + d * 2 + w) / tw; + v1 = (ty + d) / th; + u2 = (tx + d + w) / tw; + v2 = (ty + d + h) / th; + verts[i++] = new Vertex5(x2, y1, z2, u1, v2); + verts[i++] = new Vertex5(x2, y1, z1, u2, v2); + verts[i++] = new Vertex5(x2, y2, z1, u2, v1); + verts[i++] = new Vertex5(x2, y2, z2, u1, v1); + + return this; + } + + /** + * Generates a box, uv mapped to be the same as a minecraft block with the same bounds + * + * @param i The vertex index to start generating at + * @param bounds The bounds of the block, 0 to 1 + * @return The generated model. When rendering an icon will need to be supplied for the UV transformation. + */ + public CCModel generateBlock(int i, Cuboid6 bounds) { + return generateBlock(i, bounds, 0); + } + + public CCModel generateBlock(int i, Cuboid6 bounds, int mask) { + return generateBlock( + i, + bounds.min.x, + bounds.min.y, + bounds.min.z, + bounds.max.x, + bounds.max.y, + bounds.max.z, + mask); + } + + public CCModel generateBlock(int i, double x1, double y1, double z1, double x2, double y2, double z2) { + return generateBlock(i, x1, y1, z1, x2, y2, z2, 0); + } + + /** + * Generates a box, uv mapped to be the same as a minecraft block with the same bounds + * + * @param i The vertex index to start generating at + * @param x1 minX + * @param y1 minY + * @param z1 minZ + * @param x2 maxX + * @param y2 maxY + * @param z2 maxZ + * @param mask A bitmask of sides NOT to generate. I high bit at index s means side s will not be generated + * @return The generated model. When rendering an icon will need to be supplied for the UV transformation. + */ + public CCModel generateBlock(int i, double x1, double y1, double z1, double x2, double y2, double z2, int mask) { + double u1, v1, u2, v2; + + if ((mask & 1) == 0) { // bottom face + u1 = x1; + v1 = z1; + u2 = x2; + v2 = z2; + verts[i++] = new Vertex5(x1, y1, z2, u1, v2, 0); + verts[i++] = new Vertex5(x1, y1, z1, u1, v1, 0); + verts[i++] = new Vertex5(x2, y1, z1, u2, v1, 0); + verts[i++] = new Vertex5(x2, y1, z2, u2, v2, 0); + } + + if ((mask & 2) == 0) { // top face + u1 = x1; + v1 = z1; + u2 = x2; + v2 = z2; + verts[i++] = new Vertex5(x2, y2, z2, u2, v2, 1); + verts[i++] = new Vertex5(x2, y2, z1, u2, v1, 1); + verts[i++] = new Vertex5(x1, y2, z1, u1, v1, 1); + verts[i++] = new Vertex5(x1, y2, z2, u1, v2, 1); + } + + if ((mask & 4) == 0) { // east face + u1 = 1 - x1; + v1 = 1 - y2; + u2 = 1 - x2; + v2 = 1 - y1; + verts[i++] = new Vertex5(x1, y1, z1, u1, v2, 2); + verts[i++] = new Vertex5(x1, y2, z1, u1, v1, 2); + verts[i++] = new Vertex5(x2, y2, z1, u2, v1, 2); + verts[i++] = new Vertex5(x2, y1, z1, u2, v2, 2); + } + + if ((mask & 8) == 0) { // west face + u1 = x1; + v1 = 1 - y2; + u2 = x2; + v2 = 1 - y1; + verts[i++] = new Vertex5(x2, y1, z2, u2, v2, 3); + verts[i++] = new Vertex5(x2, y2, z2, u2, v1, 3); + verts[i++] = new Vertex5(x1, y2, z2, u1, v1, 3); + verts[i++] = new Vertex5(x1, y1, z2, u1, v2, 3); + } + + if ((mask & 0x10) == 0) { // north face + u1 = z1; + v1 = 1 - y2; + u2 = z2; + v2 = 1 - y1; + verts[i++] = new Vertex5(x1, y1, z2, u2, v2, 4); + verts[i++] = new Vertex5(x1, y2, z2, u2, v1, 4); + verts[i++] = new Vertex5(x1, y2, z1, u1, v1, 4); + verts[i++] = new Vertex5(x1, y1, z1, u1, v2, 4); + } + + if ((mask & 0x20) == 0) { // south face + u1 = 1 - z1; + v1 = 1 - y2; + u2 = 1 - z2; + v2 = 1 - y1; + verts[i++] = new Vertex5(x2, y1, z1, u1, v2, 5); + verts[i++] = new Vertex5(x2, y2, z1, u1, v1, 5); + verts[i++] = new Vertex5(x2, y2, z2, u2, v1, 5); + verts[i++] = new Vertex5(x2, y1, z2, u2, v2, 5); + } + + return this; + } + + public CCModel computeNormals() { + return computeNormals(0, verts.length); + } + + /** + * Computes the normals of all faces in the model. Uses the cross product of the vectors along 2 sides of the face + * + * @param start The first vertex to generate normals for + * @param length The number of vertices to generate normals for. Note this must be a multiple of 3 for triangles or + * 4 for quads + * @return The model + */ + public CCModel computeNormals(int start, int length) { + if (length % vp != 0 || start % vp != 0) + throw new IllegalArgumentException("Cannot generate normals across polygons"); + + Vector3[] normals = getOrAllocate(CCRenderState.normalAttrib); + for (int k = 0; k < length; k += vp) { + int i = k + start; + Vector3 diff1 = verts[i + 1].vec.copy().subtract(verts[i].vec); + Vector3 diff2 = verts[i + vp - 1].vec.copy().subtract(verts[i].vec); + normals[i] = diff1.crossProduct(diff2).normalize(); + for (int d = 1; d < vp; d++) normals[i + d] = normals[i].copy(); + } + + return this; + } + + /** + * Computes lighting using the normals add a light model If the model is rotated, the lighting will no longer be + * valid + * + * @return The model + */ + public CCModel computeLighting(LightModel light) { + Vector3[] normals = normals(); + int[] colours = getAttributes(CCRenderState.lightingAttrib); + if (colours == null) { + colours = getOrAllocate(CCRenderState.lightingAttrib); + Arrays.fill(colours, -1); + } + for (int k = 0; k < verts.length; k++) colours[k] = light.apply(colours[k], normals[k]); + return this; + } + + public CCModel setColour(int c) { + int[] colours = getOrAllocate(CCRenderState.colourAttrib); + Arrays.fill(colours, c); + return this; + } + + /** + * Computes the minecraft lighting coordinates for use with a LightMatrix + * + * @return The model + */ + public CCModel computeLightCoords() { + LC[] lcs = getOrAllocate(CCRenderState.lightCoordAttrib); + Vector3[] normals = normals(); + for (int i = 0; i < verts.length; i++) lcs[i] = new LC().compute(verts[i].vec, normals[i]); + return this; + } + + /** + * Averages all normals at the same position to produce a smooth lighting effect. + * + * @return The model + */ + public CCModel smoothNormals() { + ArrayList map = new ArrayList(); + Vector3[] normals = normals(); + nextvert: for (int k = 0; k < verts.length; k++) { + Vector3 vec = verts[k].vec; + for (PositionNormalEntry e : map) if (e.positionEqual(vec)) { + e.addNormal(normals[k]); + continue nextvert; + } + + map.add(new PositionNormalEntry(vec).addNormal(normals[k])); + } + + for (PositionNormalEntry e : map) { + if (e.normals.size() <= 1) continue; + + Vector3 new_n = new Vector3(); + for (Vector3 n : e.normals) new_n.add(n); + + new_n.normalize(); + for (Vector3 n : e.normals) n.set(new_n); + } + + return this; + } + + public CCModel apply(Transformation t) { + for (int k = 0; k < verts.length; k++) verts[k].apply(t); + + Vector3[] normals = normals(); + if (normals != null) for (int k = 0; k < normals.length; k++) t.applyN(normals[k]); + + return this; + } + + public CCModel apply(UVTransformation uvt) { + for (int k = 0; k < verts.length; k++) verts[k].apply(uvt); + + return this; + } + + public CCModel expand(int extraVerts) { + int newLen = verts.length + extraVerts; + verts = Arrays.copyOf(verts, newLen); + for (int i = 0; i < attributes.size(); i++) if (attributes.get(i) != null) attributes.set( + i, + CCRenderState.copyOf( + (CCRenderState.VertexAttribute) CCRenderState.getAttribute(i), + attributes.get(i), + newLen)); + + return this; + } + + public void render(CCRenderState state, double x, double y, double z, double u, double v) { + render(state, new Vector3(x, y, z).translation(), new UVTranslation(u, v)); + } + + public void render(double x, double y, double z, double u, double v) { + render(CCRenderState.instance(), new Vector3(x, y, z).translation(), new UVTranslation(u, v)); + } + + public void render(CCRenderState state, double x, double y, double z, UVTransformation u) { + render(state, new Vector3(x, y, z).translation(), u); + } + + public void render(double x, double y, double z, UVTransformation u) { + render(CCRenderState.instance(), new Vector3(x, y, z).translation(), u); + } + + public void render(CCRenderState state, Transformation t, double u, double v) { + render(state, t, new UVTranslation(u, v)); + } + + public void render(Transformation t, double u, double v) { + render(CCRenderState.instance(), t, new UVTranslation(u, v)); + } + + public void render(CCRenderState state, CCRenderState.IVertexOperation... ops) { + render(state, 0, verts.length, ops); + } + + public void render(CCRenderState.IVertexOperation... ops) { + render(CCRenderState.instance(), 0, verts.length, ops); + } + + /** + * Renders vertices start through start+length-1 of the model + * + * @param start The first vertex index to render + * @param end The vertex index to render until + * @param ops Operations to apply + */ + public void render(CCRenderState state, int start, int end, CCRenderState.IVertexOperation... ops) { + state.setPipeline(this, start, end, ops); + state.render(); + } + + public void render(int start, int end, CCRenderState.IVertexOperation... ops) { + render(CCRenderState.instance(), start, end, ops); + } + + public static CCModel quadModel(int numVerts) { + return newModel(7, numVerts); + } + + public static CCModel triModel(int numVerts) { + return newModel(4, numVerts); + } + + public static CCModel newModel(int vertexMode, int numVerts) { + CCModel model = newModel(vertexMode); + model.verts = new Vertex5[numVerts]; + return model; + } + + public static CCModel newModel(int vertexMode) { + return new CCModel(vertexMode); + } + + public static double[] parseDoubles(String s, String token) { + String[] as = s.split(token); + double[] values = new double[as.length]; + for (int i = 0; i < as.length; i++) values[i] = Double.parseDouble(as[i]); + return values; + } + + public static void illegalAssert(boolean b, String err) { + if (!b) throw new IllegalArgumentException(err); + } + + public static void assertMatch(Matcher m, String s) { + m.reset(s); + illegalAssert(m.matches(), "Malformed line: " + s); + } + + private static final Pattern vertPattern = Pattern.compile("v(?: ([\\d\\.+-]+))+"); + private static final Pattern uvwPattern = Pattern.compile("vt(?: ([\\d\\.+-]+))+"); + private static final Pattern normalPattern = Pattern.compile("vn(?: ([\\d\\.+-]+))+"); + private static final Pattern polyPattern = Pattern.compile("f(?: ((?:\\d*)(?:/\\d*)?(?:/\\d*)?))+"); + public static final Matcher vertMatcher = vertPattern.matcher(""); + public static final Matcher uvwMatcher = uvwPattern.matcher(""); + public static final Matcher normalMatcher = normalPattern.matcher(""); + public static final Matcher polyMatcher = polyPattern.matcher(""); + + /** + * Parses vertices, texture coords, normals and polygons from a WaveFront Obj file + * + * @param input An input stream to a obj file + * @param vertexMode The vertex mode to create the model for (GL_TRIANGLES or GL_QUADS) + * @param coordSystem The cooridnate system transformation to apply + * @return A map of group names to models + * @throws IOException + */ + public static Map parseObjModels(InputStream input, int vertexMode, Transformation coordSystem) + throws IOException { + if (coordSystem == null) coordSystem = new RedundantTransformation(); + int vp = vertexMode == 7 ? 4 : 3; + + HashMap modelMap = new HashMap(); + ArrayList verts = new ArrayList(); + ArrayList uvs = new ArrayList(); + ArrayList normals = new ArrayList(); + ArrayList polys = new ArrayList(); + String modelName = "unnamed"; + + BufferedReader reader = new BufferedReader(new InputStreamReader(input)); + + String line; + while ((line = reader.readLine()) != null) { + line = line.replaceAll("\\s+", " ").trim(); + if (line.startsWith("#") || line.length() == 0) continue; + + if (line.startsWith("v ")) { + assertMatch(vertMatcher, line); + double[] values = parseDoubles(line.substring(2), " "); + illegalAssert(values.length >= 3, "Vertices must have x, y and z components"); + Vector3 vert = new Vector3(values[0], values[1], values[2]); + coordSystem.apply(vert); + verts.add(vert); + continue; + } + if (line.startsWith("vt ")) { + assertMatch(uvwMatcher, line); + double[] values = parseDoubles(line.substring(3), " "); + illegalAssert(values.length >= 2, "Tex Coords must have u, and v components"); + uvs.add(new Vector3(values[0], 1 - values[1], 0)); + continue; + } + if (line.startsWith("vn ")) { + assertMatch(normalMatcher, line); + double[] values = parseDoubles(line.substring(3), " "); + illegalAssert(values.length >= 3, "Normals must have x, y and z components"); + Vector3 norm = new Vector3(values[0], values[1], values[2]).normalize(); + coordSystem.applyN(norm); + normals.add(norm); + continue; + } + if (line.startsWith("f ")) { + assertMatch(polyMatcher, line); + String[] av = line.substring(2).split(" "); + illegalAssert(av.length >= 3, "Polygons must have at least 3 vertices"); + int[][] polyVerts = new int[av.length][3]; + for (int i = 0; i < av.length; i++) { + String[] as = av[i].split("/"); + for (int p = 0; p < as.length; p++) + if (as[p].length() > 0) polyVerts[i][p] = Integer.parseInt(as[p]); + } + if (vp == 3) triangulate(polys, polyVerts); + else quadulate(polys, polyVerts); + } + if (line.startsWith("g ")) { + if (!polys.isEmpty()) { + modelMap.put(modelName, createModel(verts, uvs, normals, vertexMode, polys)); + polys.clear(); + } + modelName = line.substring(2); + } + } + + if (!polys.isEmpty()) modelMap.put(modelName, createModel(verts, uvs, normals, vertexMode, polys)); + + return modelMap; + } + + public static void triangulate(List polys, int[][] polyVerts) { + for (int i = 2; i < polyVerts.length; i++) { + polys.add(polyVerts[0]); + polys.add(polyVerts[i]); + polys.add(polyVerts[i - 1]); + } + } + + public static void quadulate(List polys, int[][] polyVerts) { + if (polyVerts.length == 4) { + polys.add(polyVerts[0]); + polys.add(polyVerts[3]); + polys.add(polyVerts[2]); + polys.add(polyVerts[1]); + } else { + for (int i = 2; i < polyVerts.length; i++) { + polys.add(polyVerts[0]); + polys.add(polyVerts[i]); + polys.add(polyVerts[i - 1]); + polys.add(polyVerts[i - 1]); + } + } + } + + /** + * Parses vertices, texture coords, normals and polygons from a WaveFront Obj file + * + * @param res The resource for the obj file + * @return A map of group names to models + */ + public static Map parseObjModels(ResourceLocation res) { + return parseObjModels(res, 4, null); + } + + /** + * Parses vertices, texture coords, normals and polygons from a WaveFront Obj file + * + * @param res The resource for the obj file + * @param coordSystem The cooridnate system transformation to apply + * @return A map of group names to models + */ + public static Map parseObjModels(ResourceLocation res, Transformation coordSystem) { + try { + return parseObjModels( + Minecraft.getMinecraft().getResourceManager().getResource(res).getInputStream(), + 4, + coordSystem); + } catch (IOException e) { + throw new RuntimeException("failed to load model: " + res, e); + } + } + + /** + * Parses vertices, texture coords, normals and polygons from a WaveFront Obj file + * + * @param res The resource for the obj file + * @param vertexMode The vertex mode to create the model for (GL_TRIANGLES or GL_QUADS) + * @param coordSystem The cooridnate system transformation to apply + * @return A map of group names to models + */ + public static Map parseObjModels(ResourceLocation res, int vertexMode, + Transformation coordSystem) { + try { + return parseObjModels( + Minecraft.getMinecraft().getResourceManager().getResource(res).getInputStream(), + vertexMode, + coordSystem); + } catch (Exception e) { + throw new RuntimeException("failed to load model: " + res, e); + } + } + + public static CCModel createModel(List verts, List uvs, List normals, int vertexMode, + List polys) { + int vp = vertexMode == 7 ? 4 : 3; + if (polys.size() < vp || polys.size() % vp != 0) + throw new IllegalArgumentException("Invalid number of vertices for model: " + polys.size()); + + boolean hasNormals = polys.get(0)[2] > 0; + CCModel model = CCModel.newModel(vertexMode, polys.size()); + if (hasNormals) model.getOrAllocate(CCRenderState.normalAttrib); + + for (int i = 0; i < polys.size(); i++) { + int[] ai = polys.get(i); + Vector3 vert = verts.get(ai[0] - 1).copy(); + Vector3 uv = ai[1] <= 0 ? new Vector3() : uvs.get(ai[1] - 1).copy(); + if (ai[2] > 0 != hasNormals) throw new IllegalArgumentException("Normals are an all or nothing deal here."); + + model.verts[i] = new Vertex5(vert, uv.x, uv.y); + if (hasNormals) model.normals()[i] = normals.get(ai[2] - 1).copy(); + } + + return model; + } + + private static int addIndex(List list, T elem) { + int i = list.indexOf(elem) + 1; + if (i == 0) { + list.add(elem); + i = list.size(); + } + return i; + } + + private static String clean(double d) { + return d == (int) d ? Integer.toString((int) d) : Double.toString(d); + } + + public static void exportObj(Map models, PrintWriter p) { + List verts = new ArrayList(); + List uvs = new ArrayList(); + List normals = new ArrayList(); + List polys = new ArrayList(); + for (Map.Entry e : models.entrySet()) { + p.println("g " + e.getKey()); + CCModel m = e.getValue(); + + int vStart = verts.size(); + int uStart = uvs.size(); + int nStart = normals.size(); + boolean hasNormals = m.normals() != null; + polys.clear(); + + for (int i = 0; i < m.verts.length; i++) { + int[] ia = new int[hasNormals ? 3 : 2]; + ia[0] = addIndex(verts, m.verts[i].vec); + ia[1] = addIndex(uvs, m.verts[i].uv); + if (hasNormals) ia[2] = addIndex(normals, m.normals()[i]); + polys.add(ia); + } + + if (vStart < verts.size()) { + p.println(); + for (int i = vStart; i < verts.size(); i++) { + Vector3 v = verts.get(i); + p.format("v %s %s %s\n", clean(v.x), clean(v.y), clean(v.z)); + } + } + if (uStart < uvs.size()) { + p.println(); + for (int i = uStart; i < uvs.size(); i++) { + UV uv = uvs.get(i); + p.format("vt %s %s\n", clean(uv.u), clean(uv.v)); + } + } + if (nStart < normals.size()) { + p.println(); + for (int i = nStart; i < normals.size(); i++) { + Vector3 n = normals.get(i); + p.format("vn %s %s %s\n", clean(n.x), clean(n.y), clean(n.z)); + } + } + + p.println(); + for (int i = 0; i < polys.size(); i++) { + if (i % m.vp == 0) p.format("f"); + int[] ia = polys.get(i); + if (hasNormals) p.format(" %d/%d/%d", ia[0], ia[1], ia[2]); + else p.format(" %d/%d", ia[0], ia[1]); + if (i % m.vp == m.vp - 1) p.println(); + } + } + } + + /** + * Brings the UV coordinates of each face closer to the center UV by d. Useful for fixing texture seams + */ + public CCModel shrinkUVs(double d) { + for (int k = 0; k < verts.length; k += vp) { + UV uv = new UV(); + for (int i = 0; i < vp; i++) { + uv.add(verts[k + i].uv); + } + uv.multiply(1D / vp); + for (int i = 0; i < vp; i++) { + Vertex5 vert = verts[k + i]; + vert.uv.u += vert.uv.u < uv.u ? d : -d; + vert.uv.v += vert.uv.v < uv.v ? d : -d; + } + } + return this; + } + + /** + * @param side1 The side of this model + * @param side2 The side of the new model + * @param point The point to rotate around + * @return A copy of this model rotated to the appropriate side + */ + public CCModel sidedCopy(int side1, int side2, Vector3 point) { + return copy().apply(new TransformationList(sideRotations[side1].inverse(), sideRotations[side2]).at(point)); + } + + /** + * Copies length vertices and normals + */ + public static void copy(CCModel src, int srcpos, CCModel dst, int destpos, int length) { + for (int k = 0; k < length; k++) dst.verts[destpos + k] = src.verts[srcpos + k].copy(); + + for (int i = 0; i < src.attributes.size(); i++) if (src.attributes.get(i) != null) CCRenderState.arrayCopy( + src.attributes.get(i), + srcpos, + dst.getOrAllocate(CCRenderState.getAttribute(i)), + destpos, + length); + } + + /** + * Generate models rotated to the other 5 sides of the block + * + * @param models An array of 6 models + * @param side The side of this model + * @param point The rotation point + */ + public static void generateSidedModels(CCModel[] models, int side, Vector3 point) { + for (int s = 0; s < 6; s++) { + if (s == side) continue; + + models[s] = models[side].sidedCopy(side, s, point); + } + } + + /** + * Generate models rotated to the other 3 horizontal of the block + * + * @param models An array of 4 models + * @param side The side of this model + * @param point The rotation point + */ + public static void generateSidedModelsH(CCModel[] models, int side, Vector3 point) { + for (int s = 2; s < 6; s++) { + if (s == side) continue; + + models[s] = models[side].sidedCopy(side, s, point); + } + } + + public CCModel backfacedCopy() { + return generateBackface(this, 0, copy(), 0, verts.length); + } + + /** + * Generates copies of faces with clockwise vertices + * + * @return The model + */ + public static CCModel generateBackface(CCModel src, int srcpos, CCModel dst, int destpos, int length) { + int vp = src.vp; + if (srcpos % vp != 0 || destpos % vp != 0 || length % vp != 0) + throw new IllegalArgumentException("Vertices do not align with polygons"); + + int[][] o = new int[][] { { 0, 0 }, { 1, vp - 1 }, { 2, vp - 2 }, { 3, vp - 3 } }; + for (int i = 0; i < length; i++) { + int b = (i / vp) * vp; + int d = i % vp; + int di = destpos + b + o[d][1]; + int si = srcpos + b + o[d][0]; + dst.verts[di] = src.verts[si].copy(); + for (int a = 0; a < src.attributes.size(); a++) if (src.attributes.get(a) != null) CCRenderState + .arrayCopy(src.attributes.get(a), si, dst.getOrAllocate(CCRenderState.getAttribute(a)), di, 1); + + if (dst.normals() != null && dst.normals()[di] != null) dst.normals()[di].negate(); + } + return dst; + } + + /** + * Generates sided copies of vertices into this model. Assumes that your model has been generated at vertex + * side*(numVerts/6) + */ + public CCModel generateSidedParts(int side, Vector3 point) { + if (verts.length % (6 * vp) != 0) + throw new IllegalArgumentException("Invalid number of vertices for sided part generation"); + int length = verts.length / 6; + + for (int s = 0; s < 6; s++) { + if (s == side) continue; + + generateSidedPart(side, s, point, length * side, length * s, length); + } + + return this; + } + + /** + * Generates sided copies of vertices into this model. Assumes that your model has been generated at vertex + * side*(numVerts/4) + */ + public CCModel generateSidedPartsH(int side, Vector3 point) { + if (verts.length % (4 * vp) != 0) + throw new IllegalArgumentException("Invalid number of vertices for sided part generation"); + int length = verts.length / 4; + + for (int s = 2; s < 6; s++) { + if (s == side) continue; + + generateSidedPart(side, s, point, length * (side - 2), length * (s - 2), length); + } + + return this; + } + + /** + * Generates a sided copy of verts into this model + */ + public CCModel generateSidedPart(int side1, int side2, Vector3 point, int srcpos, int destpos, int length) { + return apply( + new TransformationList(sideRotations[side1].inverse(), sideRotations[side2]).at(point), + srcpos, + destpos, + length); + } + + /** + * Generates a rotated copy of verts into this model + */ + public CCModel apply(Transformation t, int srcpos, int destpos, int length) { + for (int k = 0; k < length; k++) { + verts[destpos + k] = verts[srcpos + k].copy(); + verts[destpos + k].vec.apply(t); + } + + Vector3[] normals = normals(); + if (normals != null) for (int k = 0; k < length; k++) { + normals[destpos + k] = normals[srcpos + k].copy(); + t.applyN(normals[destpos + k]); + } + + return this; + } + + public static CCModel combine(Collection models) { + if (models.isEmpty()) return null; + + int numVerts = 0; + int vertexMode = -1; + for (CCModel model : models) { + if (vertexMode == -1) vertexMode = model.vertexMode; + if (vertexMode != model.vertexMode) + throw new IllegalArgumentException("Cannot combine models with different vertex modes"); + + numVerts += model.verts.length; + } + + CCModel c_model = newModel(vertexMode, numVerts); + int i = 0; + for (CCModel model : models) { + copy(model, 0, c_model, i, model.verts.length); + i += model.verts.length; + } + + return c_model; + } + + public CCModel twoFacedCopy() { + CCModel model = newModel(vertexMode, verts.length * 2); + copy(this, 0, model, 0, verts.length); + return generateBackface(model, 0, model, verts.length, verts.length); + } + + public CCModel copy() { + CCModel model = newModel(vertexMode, verts.length); + copy(this, 0, model, 0, verts.length); + return model; + } + + /** + * @return The average of all vertices, for bones. + */ + public Vector3 collapse() { + Vector3 v = new Vector3(); + for (Vertex5 vert : verts) v.add(vert.vec); + v.multiply(1 / (double) verts.length); + return v; + } + + public CCModel zOffset(Cuboid6 offsets) { + for (int k = 0; k < verts.length; k++) { + Vertex5 vert = verts[k]; + Vector3 normal = normals()[k]; + switch (findSide(normal)) { + case 0: + vert.vec.y += offsets.min.y; + break; + case 1: + vert.vec.y += offsets.max.y; + break; + case 2: + vert.vec.z += offsets.min.z; + break; + case 3: + vert.vec.z += offsets.max.z; + break; + case 4: + vert.vec.x += offsets.min.x; + break; + case 5: + vert.vec.x += offsets.max.x; + break; + } + } + return this; + } + + public static int findSide(Vector3 normal) { + if (normal.y <= -0.99) return 0; + if (normal.y >= 0.99) return 1; + if (normal.z <= -0.99) return 2; + if (normal.z >= 0.99) return 3; + if (normal.x <= -0.99) return 4; + if (normal.x >= 0.99) return 5; + return -1; + } + + /** + * @return A Cuboid6 containing all the verts in this model + */ + public Cuboid6 bounds() { + Vector3 vec1 = verts[0].vec; + Cuboid6 c = new Cuboid6(vec1.copy(), vec1.copy()); + for (int i = 1; i < verts.length; i++) c.enclose(verts[i].vec); + return c; + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/CCModelLibrary.java b/ccl/src/main/java/codechicken/lib/render/CCModelLibrary.java new file mode 100644 index 0000000..7806fa7 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/CCModelLibrary.java @@ -0,0 +1,88 @@ +package codechicken.lib.render; + +import static codechicken.lib.math.MathHelper.phi; + +import codechicken.lib.vec.Matrix4; +import codechicken.lib.vec.Quat; +import codechicken.lib.vec.Rotation; +import codechicken.lib.vec.Scale; +import codechicken.lib.vec.Vector3; + +public class CCModelLibrary { + + public static CCModel icosahedron4; + public static CCModel icosahedron7; + + private static int i; + + static { + generateIcosahedron(); + } + + private static void generateIcosahedron() { + Vector3[] verts = new Vector3[12]; + + verts[0] = new Vector3(-1, phi, 0); + verts[1] = new Vector3(1, phi, 0); + verts[2] = new Vector3(1, -phi, 0); + verts[3] = new Vector3(-1, -phi, 0); + + verts[4] = new Vector3(0, -1, phi); + verts[5] = new Vector3(0, 1, phi); + verts[6] = new Vector3(0, 1, -phi); + verts[7] = new Vector3(0, -1, -phi); + + verts[8] = new Vector3(phi, 0, -1); + verts[9] = new Vector3(phi, 0, 1); + verts[10] = new Vector3(-phi, 0, 1); + verts[11] = new Vector3(-phi, 0, -1); + + Quat quat = Quat.aroundAxis(0, 0, 1, Math.atan(1 / phi)); + for (Vector3 vec : verts) quat.rotate(vec); + + icosahedron4 = CCModel.newModel(4, 60); + icosahedron7 = CCModel.newModel(7, 80); + + i = 0; + // top + addIcosahedronTriangle(verts[1], 0.5, 0, verts[0], 0, 0.25, verts[5], 1, 0.25); + addIcosahedronTriangle(verts[1], 0.5, 0, verts[5], 0, 0.25, verts[9], 1, 0.25); + addIcosahedronTriangle(verts[1], 0.5, 0, verts[9], 0, 0.25, verts[8], 1, 0.25); + addIcosahedronTriangle(verts[1], 0.5, 0, verts[8], 0, 0.25, verts[6], 1, 0.25); + addIcosahedronTriangle(verts[1], 0.5, 0, verts[6], 0, 0.25, verts[0], 1, 0.25); + // centre 1vert top + addIcosahedronTriangle(verts[0], 0.5, 0.25, verts[11], 0, 0.75, verts[10], 1, 0.75); + addIcosahedronTriangle(verts[5], 0.5, 0.25, verts[10], 0, 0.75, verts[4], 1, 0.75); + addIcosahedronTriangle(verts[9], 0.5, 0.25, verts[4], 0, 0.75, verts[2], 1, 0.75); + addIcosahedronTriangle(verts[8], 0.5, 0.25, verts[2], 0, 0.75, verts[7], 1, 0.75); + addIcosahedronTriangle(verts[6], 0.5, 0.25, verts[7], 0, 0.75, verts[11], 1, 0.75); + // centre 1vert bottom + addIcosahedronTriangle(verts[2], 0.5, 0.75, verts[8], 0, 0.25, verts[9], 1, 0.25); + addIcosahedronTriangle(verts[7], 0.5, 0.75, verts[6], 0, 0.25, verts[8], 1, 0.25); + addIcosahedronTriangle(verts[11], 0.5, 0.75, verts[0], 0, 0.25, verts[6], 1, 0.25); + addIcosahedronTriangle(verts[10], 0.5, 0.75, verts[5], 0, 0.25, verts[0], 1, 0.25); + addIcosahedronTriangle(verts[4], 0.5, 0.75, verts[9], 0, 0.25, verts[5], 1, 0.25); + // bottom + addIcosahedronTriangle(verts[3], 0.5, 1, verts[2], 0, 0.75, verts[4], 1, 0.75); + addIcosahedronTriangle(verts[3], 0.5, 1, verts[7], 0, 0.75, verts[2], 1, 0.75); + addIcosahedronTriangle(verts[3], 0.5, 1, verts[11], 0, 0.75, verts[7], 1, 0.75); + addIcosahedronTriangle(verts[3], 0.5, 1, verts[10], 0, 0.75, verts[11], 1, 0.75); + addIcosahedronTriangle(verts[3], 0.5, 1, verts[4], 0, 0.75, verts[10], 1, 0.75); + + icosahedron4.computeNormals().smoothNormals(); + icosahedron7.computeNormals().smoothNormals(); + } + + private static void addIcosahedronTriangle(Vector3 vec1, double u1, double v1, Vector3 vec2, double u2, double v2, + Vector3 vec3, double u3, double v3) { + icosahedron4.verts[i * 3] = icosahedron7.verts[i * 4] = new Vertex5(vec1, u1, v1); + icosahedron4.verts[i * 3 + 1] = icosahedron7.verts[i * 4 + 1] = new Vertex5(vec2, u2, v2); + icosahedron4.verts[i * 3 + + 2] = icosahedron7.verts[i * 4 + 2] = icosahedron7.verts[i * 4 + 3] = new Vertex5(vec3, u3, v3); + i++; + } + + public static Matrix4 getRenderMatrix(Vector3 position, Rotation rotation, double scale) { + return new Matrix4().translate(position).apply(new Scale(scale)).apply(rotation); + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/CCRenderPipeline.java b/ccl/src/main/java/codechicken/lib/render/CCRenderPipeline.java new file mode 100644 index 0000000..9d9d521 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/CCRenderPipeline.java @@ -0,0 +1,142 @@ +package codechicken.lib.render; + +import java.util.ArrayList; +import java.util.Collections; + +import codechicken.lib.render.CCRenderState.IVertexOperation; +import codechicken.lib.render.CCRenderState.VertexAttribute; + +@SuppressWarnings("ForLoopReplaceableByForEach") +public class CCRenderPipeline { + + private final CCRenderState renderState; + private final PipelineBuilder builder; + + public CCRenderPipeline(CCRenderState renderState) { + this.renderState = renderState; + builder = new PipelineBuilder(renderState); + } + + public CCRenderPipeline() { + this(CCRenderState.instance()); + } + + public class PipelineBuilder { + + private final CCRenderState renderState; + + public PipelineBuilder(CCRenderState renderState) { + this.renderState = renderState; + } + + public PipelineBuilder() { + this(CCRenderState.instance()); + } + + public PipelineBuilder add(IVertexOperation op) { + ops.add(op); + return this; + } + + public PipelineBuilder add(IVertexOperation... ops) { + Collections.addAll(CCRenderPipeline.this.ops, ops); + return this; + } + + public void build() { + rebuild(); + } + + public void render() { + rebuild(); + renderState.render(); + } + } + + private class PipelineNode { + + public ArrayList deps = new ArrayList<>(); + public IVertexOperation op; + + public void add() { + if (op == null) return; + + for (int i = 0; i < deps.size(); i++) deps.get(i).add(); + deps.clear(); + sorted.add(op); + op = null; + } + } + + private final ArrayList attribs = new ArrayList<>(); + private final ArrayList ops = new ArrayList<>(); + private final ArrayList nodes = new ArrayList<>(); + private final ArrayList sorted = new ArrayList<>(); + private PipelineNode loading; + + public void setPipeline(IVertexOperation... ops) { + this.ops.clear(); + for (int i = 0; i < ops.length; i++) this.ops.add(ops[i]); + rebuild(); + } + + public void reset() { + ops.clear(); + unbuild(); + } + + private void unbuild() { + for (int i = 0; i < attribs.size(); i++) attribs.get(i).active = false; + attribs.clear(); + sorted.clear(); + } + + public void rebuild() { + if (ops.isEmpty() || this.renderState.model == null) return; + + // ensure enough nodes for all ops + while (nodes.size() < this.renderState.operationCount()) nodes.add(new PipelineNode()); + unbuild(); + + if (this.renderState.useNormals) addAttribute(this.renderState.normalAttrib); + if (this.renderState.useColour) addAttribute(this.renderState.colourAttrib); + if (this.renderState.computeLighting) addAttribute(this.renderState.lightingAttrib); + + for (int i = 0; i < ops.size(); i++) { + IVertexOperation op = ops.get(i); + loading = nodes.get(op.operationID()); + boolean loaded = op.load(renderState); + if (loaded) loading.op = op; + + if (op instanceof VertexAttribute) if (loaded) attribs.add((VertexAttribute) op); + else((VertexAttribute) op).active = false; + } + + for (int i = 0; i < nodes.size(); i++) nodes.get(i).add(); + } + + public void addRequirement(int opRef) { + loading.deps.add(nodes.get(opRef)); + } + + public void addDependency(VertexAttribute attrib) { + loading.deps.add(nodes.get(attrib.operationID())); + addAttribute(attrib); + } + + public void addAttribute(VertexAttribute attrib) { + if (!attrib.active) { + ops.add(attrib); + attrib.active = true; + } + } + + public void operate() { + for (int i = 0; i < sorted.size(); i++) sorted.get(i).operate(renderState); + } + + public PipelineBuilder builder() { + ops.clear(); + return builder; + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/CCRenderState.java b/ccl/src/main/java/codechicken/lib/render/CCRenderState.java new file mode 100644 index 0000000..39655f0 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/CCRenderState.java @@ -0,0 +1,552 @@ +package codechicken.lib.render; + +import java.util.ArrayList; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.OpenGlHelper; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.util.ResourceLocation; +import net.minecraft.world.IBlockAccess; + +import codechicken.lib.colour.ColourRGBA; +import codechicken.lib.lighting.LC; +import codechicken.lib.lighting.LightMatrix; +import codechicken.lib.util.Copyable; +import codechicken.lib.vec.Rotation; +import codechicken.lib.vec.Transformation; +import codechicken.lib.vec.Vector3; + +/** + * The core of the CodeChickenLib render system. Where possible assign a local var of CCRenderState to avoid millions of + * calls to instance(); Uses a ThreadLocal system to assign each thread their own CCRenderState so we can use it in + * Multithreaded chunk batching. + *

+ * Backported from CCL - 1.16.x + */ + +public class CCRenderState { + + public final CCRenderPipeline pipeline; + + private static final ThreadLocal instances = ThreadLocal.withInitial(CCRenderState::new); + + private CCRenderState() { + pipeline = new CCRenderPipeline(this); + } + + public static CCRenderState instance() { + return instances.get(); + } + + private static int nextOperationIndex; + + public static int registerOperation() { + return nextOperationIndex++; + } + + public static int operationCount() { + return nextOperationIndex; + } + + /** + * Represents an operation to be run for each vertex that operates on and modifies the current state + */ + public interface IVertexOperation { + + /** + * Load any required references and add dependencies to the pipeline based on the current model (may be null) + * Return false if this operation is redundant in the pipeline with the given model + */ + default boolean load() { + // Existing code will will override this method so it shouldn't infinite loop + // Default it only for new state aware code + return load(CCRenderState.instance()); + } + + default boolean load(CCRenderState state) { + // New code will override this and not inplement load() so we shouldn't infinite loop + return load(); + } + + /** + * Perform the operation on the current render state + */ + default void operate() { + operate(CCRenderState.instance()); + } + + default void operate(CCRenderState state) { + operate(); + } + + /** + * Get the unique id representing this type of operation. Duplicate operation IDs within the pipeline may have + * unexpected results. ID shoulld be obtained from CCRenderState.registerOperation() and stored in a static + * variable + */ + int operationID(); + } + + private static ArrayList> vertexAttributes = new ArrayList<>(); + + private static int registerVertexAttribute(VertexAttribute attr) { + vertexAttributes.add(attr); + return vertexAttributes.size() - 1; + } + + public static VertexAttribute getAttribute(int index) { + return vertexAttributes.get(index); + } + + /** + * Management class for a vertex attrute such as colour, normal etc This class should handle the loading of the + * attrute from an array provided by IVertexSource.getAttributes or the computation of this attrute from others + * + * @param The array type for this attrute eg. int[], Vector3[] + */ + public abstract static class VertexAttribute implements IVertexOperation { + + public final int attributeIndex = registerVertexAttribute(this); + private final int operationIndex = registerOperation(); + /** + * Set to true when the attrute is part of the pipeline. Should only be managed by CCRenderState when + * constructing the pipeline + */ + public boolean active = false; + + /** + * Construct a new array for storage of vertex attrutes in a model + */ + public abstract T newArray(int length); + + @Override + public int operationID() { + return operationIndex; + } + } + + public static void arrayCopy(Object src, int srcPos, Object dst, int destPos, int length) { + System.arraycopy(src, srcPos, dst, destPos, length); + if (dst instanceof Copyable[]) { + Object[] oa = (Object[]) dst; + Copyable[] c = (Copyable[]) dst; + for (int i = destPos; i < destPos + length; i++) if (c[i] != null) oa[i] = c[i].copy(); + } + } + + public static T copyOf(VertexAttribute attr, T src, int length) { + T dst = attr.newArray(length); + arrayCopy(src, 0, dst, 0, ((Object[]) src).length); + return dst; + } + + public interface IVertexSource { + + Vertex5[] getVertices(); + + /** + * Gets an array of vertex attrutes + * + * @param attr The vertex attrute to get + * @param The attrute array type + * @return An array, or null if not computed + */ + T getAttributes(VertexAttribute attr); + + /** + * @return True if the specified attrute is provided by this model, either by returning an array from + * getAttributes or by setting the state in prepareVertex + */ + boolean hasAttribute(VertexAttribute attr); + + /** + * Callback to set CCRenderState for a vertex before the pipeline runs + */ + void prepareVertex(CCRenderState state); + + default void prepareVertex() { + prepareVertex(CCRenderState.instance()); + } + } + + public static VertexAttribute normalAttrib = new VertexAttribute<>() { + + private Vector3[] normalRef; + + @Override + public Vector3[] newArray(int length) { + return new Vector3[length]; + } + + @Override + public boolean load(CCRenderState state) { + normalRef = state.model.getAttributes(this); + if (state.model.hasAttribute(this)) return normalRef != null; + + if (state.model.hasAttribute(sideAttrib)) { + state.pipeline.addDependency(sideAttrib); + return true; + } + throw new IllegalStateException( + "Normals requested but neither normal or side attrutes are provided by the model"); + } + + @Override + public void operate(CCRenderState state) { + if (normalRef != null) state.setNormal(normalRef[state.vertexIndex]); + else state.setNormal(Rotation.axes[state.side]); + } + }; + public static VertexAttribute colourAttrib = new VertexAttribute<>() { + + private int[] colourRef; + + @Override + public int[] newArray(int length) { + return new int[length]; + } + + @Override + public boolean load(CCRenderState state) { + colourRef = state.model.getAttributes(this); + return colourRef != null || !state.model.hasAttribute(this); + } + + @Override + public void operate(CCRenderState state) { + if (colourRef != null) state.setColour(ColourRGBA.multiply(state.baseColour, colourRef[state.vertexIndex])); + else state.setColour(state.baseColour); + } + }; + public static VertexAttribute lightingAttrib = new VertexAttribute<>() { + + private int[] colourRef; + + @Override + public int[] newArray(int length) { + return new int[length]; + } + + @Override + public boolean load(CCRenderState state) { + if (!state.computeLighting || !state.useColour || !state.model.hasAttribute(this)) return false; + + colourRef = state.model.getAttributes(this); + if (colourRef != null) { + state.pipeline.addDependency(colourAttrib); + return true; + } + return false; + } + + @Override + public void operate(CCRenderState state) { + state.setColour(ColourRGBA.multiply(state.colour, colourRef[state.vertexIndex])); + } + }; + public static VertexAttribute sideAttrib = new VertexAttribute<>() { + + private int[] sideRef; + + @Override + public int[] newArray(int length) { + return new int[length]; + } + + @Override + public boolean load(CCRenderState state) { + sideRef = state.model.getAttributes(this); + if (state.model.hasAttribute(this)) return sideRef != null; + + state.pipeline.addDependency(normalAttrib); + return true; + } + + @Override + public void operate(CCRenderState state) { + if (sideRef != null) state.side = sideRef[state.vertexIndex]; + else state.side = CCModel.findSide(state.normal); + } + }; + /** + * Uses the position of the lightmatrix to compute LC if not provided + */ + public static VertexAttribute lightCoordAttrib = new VertexAttribute<>() { + + private LC[] lcRef; + private final Vector3 vec = new Vector3(); // for computation + private final Vector3 pos = new Vector3(); + + @Override + public LC[] newArray(int length) { + return new LC[length]; + } + + @Override + public boolean load(CCRenderState state) { + lcRef = state.model.getAttributes(this); + if (state.model.hasAttribute(this)) return lcRef != null; + + pos.set(state.lightMatrix.pos.x, state.lightMatrix.pos.y, state.lightMatrix.pos.z); + state.pipeline.addDependency(sideAttrib); + state.pipeline.addRequirement(Transformation.operationIndex); + return true; + } + + @Override + public void operate(CCRenderState state) { + if (lcRef != null) state.lc.set(lcRef[state.vertexIndex]); + else state.lc.compute(vec.set(state.vert.vec).sub(pos), state.side); + } + }; + + // pipeline state + public IVertexSource model; + + public int firstVertexIndex; + public int lastVertexIndex; + public int vertexIndex; + + // context + public int baseColour; + public int alphaOverride; + public boolean useNormals; + public boolean computeLighting; + public boolean useColour; + public LightMatrix lightMatrix = new LightMatrix(); + + // vertex outputs + public Vertex5 vert = new Vertex5(); + public boolean hasNormal; + public Vector3 normal = new Vector3(); + + public boolean hasColour; + public int colour; + + public boolean hasBrightness; + public int brightness; + + // attrute storage + public int side; + public LC lc = new LC(); + + public void reset() { + model = null; + pipeline.reset(); + useNormals = hasNormal = hasBrightness = hasColour = false; + useColour = computeLighting = true; + baseColour = alphaOverride = -1; + } + + public void setPipeline(IVertexOperation... ops) { + pipeline.setPipeline(ops); + } + + @Deprecated + public static void setPipelineStatic(IVertexOperation... ops) { + instance().setPipeline(ops); + } + + public void setPipeline(IVertexSource model, int start, int end, IVertexOperation... ops) { + pipeline.reset(); + setModel(model, start, end); + pipeline.setPipeline(ops); + } + + @Deprecated + public static void setPipelineStatic(IVertexSource model, int start, int end, IVertexOperation... ops) { + instance().setPipeline(model, start, end, ops); + } + + public void bindModel(IVertexSource model) { + if (this.model != model) { + this.model = model; + pipeline.rebuild(); + } + } + + @Deprecated + public static void bindModelStatic(IVertexSource model) { + instance().bindModel(model); + } + + public void setModel(IVertexSource source) { + setModel(source, 0, source.getVertices().length); + } + + @Deprecated + public static void setModelStatic(IVertexSource source) { + instance().setModel(source); + } + + public void setModel(IVertexSource source, int start, int end) { + bindModel(source); + setVertexRange(start, end); + } + + @Deprecated + public static void setModelStatic(IVertexSource source, int start, int end) { + instance().setModel(source, start, end); + } + + public void setVertexRange(int start, int end) { + firstVertexIndex = start; + lastVertexIndex = end; + } + + @Deprecated + public static void setVertexRangeStatic(int start, int end) { + instance().setVertexRange(start, end); + } + + public void render(IVertexOperation... ops) { + setPipeline(ops); + render(); + } + + @Deprecated + public static void renderStatic(IVertexOperation... ops) { + instance().render(ops); + } + + public void render() { + Vertex5[] verts = model.getVertices(); + for (vertexIndex = firstVertexIndex; vertexIndex < lastVertexIndex; vertexIndex++) { + model.prepareVertex(this); + vert.set(verts[vertexIndex]); + runPipeline(); + writeVert(); + } + } + + @Deprecated + public static void renderStatic() { + instance().render(); + } + + public void runPipeline() { + pipeline.operate(); + } + + @Deprecated + public static void runPipelineStatic() { + instance().runPipeline(); + } + + public void writeVert() { + if (hasNormal) Tessellator.instance.setNormal((float) normal.x, (float) normal.y, (float) normal.z); + if (hasColour) Tessellator.instance.setColorRGBA( + colour >>> 24, + colour >> 16 & 0xFF, + colour >> 8 & 0xFF, + alphaOverride >= 0 ? alphaOverride : colour & 0xFF); + if (hasBrightness) Tessellator.instance.setBrightness(brightness); + Tessellator.instance.addVertexWithUV(vert.vec.x, vert.vec.y, vert.vec.z, vert.uv.u, vert.uv.v); + } + + @Deprecated + public static void writeVertStatic() { + instance().writeVert(); + } + + public void setNormal(double x, double y, double z) { + hasNormal = true; + normal.set(x, y, z); + } + + @Deprecated + public static void setNormalStatic(double x, double y, double z) { + instance().setNormal(x, y, z); + } + + public void setNormal(Vector3 n) { + hasNormal = true; + normal.set(n); + } + + @Deprecated + public static void setNormalStatic(Vector3 n) { + instance().setNormal(n); + } + + public void setColour(int c) { + hasColour = true; + colour = c; + } + + @Deprecated + public static void setColourStatic(int c) { + instance().setColour(c); + } + + public void setBrightness(int b) { + hasBrightness = true; + brightness = b; + } + + @Deprecated + public static void setBrightnessStatic(int b) { + instance().setBrightness(b); + } + + public void setBrightness(IBlockAccess world, int x, int y, int z) { + setBrightness(world.getBlock(x, y, z).getMixedBrightnessForBlock(world, x, y, z)); + } + + @Deprecated + public static void setBrightnessStatic(IBlockAccess world, int x, int y, int z) { + instance().setBrightness(world, x, y, z); + } + + public void pullLightmap() { + setBrightness((int) OpenGlHelper.lastBrightnessY << 16 | (int) OpenGlHelper.lastBrightnessX); + } + + public void pushLightmap() { + OpenGlHelper.setLightmapTextureCoords(OpenGlHelper.lightmapTexUnit, brightness & 0xFFFF, brightness >>> 16); + } + + /** + * Compact helper for setting dynamic rendering context. Uses normals and doesn't compute lighting + */ + public void setDynamic() { + useNormals = true; + computeLighting = false; + } + + public static void changeTexture(String texture) { + changeTexture(new ResourceLocation(texture)); + } + + public static void changeTexture(ResourceLocation texture) { + Minecraft.getMinecraft().renderEngine.bindTexture(texture); + } + + public void startDrawing() { + startDrawing(7); + } + + @Deprecated + public static void startDrawingStatic() { + instance().startDrawing(); + } + + public void startDrawing(int mode) { + Tessellator.instance.startDrawing(mode); + if (hasColour) Tessellator.instance.setColorRGBA( + colour >>> 24, + colour >> 16 & 0xFF, + colour >> 8 & 0xFF, + alphaOverride >= 0 ? alphaOverride : colour & 0xFF); + if (hasBrightness) Tessellator.instance.setBrightness(brightness); + } + + @Deprecated + public static void startDrawingStatic(int mode) { + instance().startDrawing(mode); + } + + public void draw() { + Tessellator.instance.draw(); + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/ColourMultiplier.java b/ccl/src/main/java/codechicken/lib/render/ColourMultiplier.java new file mode 100644 index 0000000..292048c --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/ColourMultiplier.java @@ -0,0 +1,43 @@ +package codechicken.lib.render; + +import codechicken.lib.colour.ColourRGBA; + +public class ColourMultiplier implements CCRenderState.IVertexOperation { + + private static final ThreadLocal instances = ThreadLocal + .withInitial(() -> new ColourMultiplier(-1)); + + public static ColourMultiplier instance(int colour) { + ColourMultiplier instance = instances.get(); + instance.colour = colour; + return instance; + } + + public static final int operationIndex = CCRenderState.registerOperation(); + public int colour; + + public ColourMultiplier(int colour) { + this.colour = colour; + } + + @Override + public boolean load(CCRenderState state) { + if (colour == -1) { + state.setColour(-1); + return false; + } + + state.pipeline.addDependency(state.colourAttrib); + return true; + } + + @Override + public void operate(CCRenderState state) { + state.setColour(ColourRGBA.multiply(state.colour, colour)); + } + + @Override + public int operationID() { + return operationIndex; + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/EntityDigIconFX.java b/ccl/src/main/java/codechicken/lib/render/EntityDigIconFX.java new file mode 100644 index 0000000..fd2e631 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/EntityDigIconFX.java @@ -0,0 +1,139 @@ +package codechicken.lib.render; + +import net.minecraft.client.particle.EffectRenderer; +import net.minecraft.client.particle.EntityFX; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.util.IIcon; +import net.minecraft.world.World; + +import codechicken.lib.vec.Cuboid6; +import codechicken.lib.vec.Vector3; + +public class EntityDigIconFX extends EntityFX { + + public EntityDigIconFX(World world, double x, double y, double z, double dx, double dy, double dz, IIcon icon) { + super(world, x, y, z, dx, dy, dz); + particleIcon = icon; + particleGravity = 1; + particleRed = particleGreen = particleBlue = 0.6F; + particleScale /= 2.0F; + } + + @Override + public int getFXLayer() { + return 1; + } + + public void setScale(float scale) { + particleScale = scale; + } + + public float getScale() { + return particleScale; + } + + public void setMaxAge(int age) { + particleMaxAge = age; + } + + public int getMaxAge() { + return particleMaxAge; + } + + /** + * copy pasted from EntityDiggingFX + */ + @Override + public void renderParticle(Tessellator par1Tessellator, float par2, float par3, float par4, float par5, float par6, + float par7) { + float f6 = (particleTextureIndexX + particleTextureJitterX / 4.0F) / 16.0F; + float f7 = f6 + 0.015609375F; + float f8 = (particleTextureIndexY + particleTextureJitterY / 4.0F) / 16.0F; + float f9 = f8 + 0.015609375F; + float f10 = 0.1F * particleScale; + + if (particleIcon != null) { + f6 = particleIcon.getInterpolatedU(particleTextureJitterX / 4.0F * 16.0F); + f7 = particleIcon.getInterpolatedU((particleTextureJitterX + 1.0F) / 4.0F * 16.0F); + f8 = particleIcon.getInterpolatedV(particleTextureJitterY / 4.0F * 16.0F); + f9 = particleIcon.getInterpolatedV((particleTextureJitterY + 1.0F) / 4.0F * 16.0F); + } + + float f11 = (float) (prevPosX + (posX - prevPosX) * par2 - interpPosX); + float f12 = (float) (prevPosY + (posY - prevPosY) * par2 - interpPosY); + float f13 = (float) (prevPosZ + (posZ - prevPosZ) * par2 - interpPosZ); + float f14 = 1.0F; + par1Tessellator.setColorOpaque_F(f14 * particleRed, f14 * particleGreen, f14 * particleBlue); + par1Tessellator.addVertexWithUV( + f11 - par3 * f10 - par6 * f10, + f12 - par4 * f10, + f13 - par5 * f10 - par7 * f10, + f6, + f9); + par1Tessellator.addVertexWithUV( + f11 - par3 * f10 + par6 * f10, + f12 + par4 * f10, + f13 - par5 * f10 + par7 * f10, + f6, + f8); + par1Tessellator.addVertexWithUV( + f11 + par3 * f10 + par6 * f10, + f12 + par4 * f10, + f13 + par5 * f10 + par7 * f10, + f7, + f8); + par1Tessellator.addVertexWithUV( + f11 + par3 * f10 - par6 * f10, + f12 - par4 * f10, + f13 + par5 * f10 - par7 * f10, + f7, + f9); + } + + public static void addBlockHitEffects(World world, Cuboid6 bounds, int side, IIcon icon, + EffectRenderer effectRenderer) { + float border = 0.1F; + Vector3 diff = bounds.max.copy().subtract(bounds.min).add(-2 * border); + diff.x *= world.rand.nextDouble(); + diff.y *= world.rand.nextDouble(); + diff.z *= world.rand.nextDouble(); + Vector3 pos = diff.add(bounds.min).add(border); + + if (side == 0) diff.y = bounds.min.y - border; + if (side == 1) diff.y = bounds.max.y + border; + if (side == 2) diff.z = bounds.min.z - border; + if (side == 3) diff.z = bounds.max.z + border; + if (side == 4) diff.x = bounds.min.x - border; + if (side == 5) diff.x = bounds.max.x + border; + + effectRenderer.addEffect( + new EntityDigIconFX(world, pos.x, pos.y, pos.z, 0, 0, 0, icon).multiplyVelocity(0.2F) + .multipleParticleScaleBy(0.6F)); + } + + public static void addBlockDestroyEffects(World world, Cuboid6 bounds, IIcon[] icons, + EffectRenderer effectRenderer) { + Vector3 diff = bounds.max.copy().subtract(bounds.min); + Vector3 center = bounds.min.copy().add(bounds.max).multiply(0.5); + Vector3 density = diff.copy().multiply(4); + density.x = Math.ceil(density.x); + density.y = Math.ceil(density.y); + density.z = Math.ceil(density.z); + + for (int i = 0; i < density.x; ++i) for (int j = 0; j < density.y; ++j) for (int k = 0; k < density.z; ++k) { + double x = bounds.min.x + (i + 0.5) * diff.x / density.x; + double y = bounds.min.y + (j + 0.5) * diff.y / density.y; + double z = bounds.min.z + (k + 0.5) * diff.z / density.z; + effectRenderer.addEffect( + new EntityDigIconFX( + world, + x, + y, + z, + x - center.x, + y - center.y, + z - center.z, + icons[world.rand.nextInt(icons.length)])); + } + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/FontUtils.java b/ccl/src/main/java/codechicken/lib/render/FontUtils.java new file mode 100644 index 0000000..8397d8a --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/FontUtils.java @@ -0,0 +1,61 @@ +package codechicken.lib.render; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.item.ItemStack; + +import org.lwjgl.opengl.GL11; + +public class FontUtils { + + public static FontRenderer fontRenderer = Minecraft.getMinecraft().fontRenderer; + + public static void drawCenteredString(String s, int xCenter, int y, int colour) { + fontRenderer.drawString(s, xCenter - fontRenderer.getStringWidth(s) / 2, y, colour); + } + + public static void drawRightString(String s, int xRight, int y, int colour) { + fontRenderer.drawString(s, xRight - fontRenderer.getStringWidth(s), y, colour); + } + + public static final String[] prefixes = new String[] { "K", "M", "G" }; + + public static void drawItemQuantity(int x, int y, ItemStack item, String quantity, int mode) { + if (item == null || (quantity == null && item.stackSize <= 1)) return; + + if (quantity == null) { + switch (mode) { + case 2: + int q = item.stackSize; + String postfix = ""; + for (int p = 0; p < 3 && q > 1000; p++) { + q /= 1000; + postfix = prefixes[p]; + } + quantity = Integer.toString(q) + postfix; + case 1: + quantity = ""; + if (item.stackSize / 64 > 0) quantity += item.stackSize / 64 + "s"; + if (item.stackSize % 64 > 0) quantity += item.stackSize % 64; + break; + default: + quantity = Integer.toString(item.stackSize); + break; + } + } + + double scale = quantity.length() > 2 ? 0.5 : 1; + double sheight = 8 * scale; + double swidth = fontRenderer.getStringWidth(quantity) * scale; + + GL11.glDisable(GL11.GL_LIGHTING); + GL11.glDisable(GL11.GL_DEPTH_TEST); + GL11.glPushMatrix(); + GL11.glTranslated(x + 16 - swidth, y + 16 - sheight, 0); + GL11.glScaled(scale, scale, 1); + fontRenderer.drawStringWithShadow(quantity, 0, 0, 0xFFFFFF); + GL11.glPopMatrix(); + GL11.glEnable(GL11.GL_LIGHTING); + GL11.glEnable(GL11.GL_DEPTH_TEST); + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/IFaceRenderer.java b/ccl/src/main/java/codechicken/lib/render/IFaceRenderer.java new file mode 100644 index 0000000..2965346 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/IFaceRenderer.java @@ -0,0 +1,6 @@ +package codechicken.lib.render; + +public interface IFaceRenderer { + + public void renderFace(Vertex5[] face, int side); +} diff --git a/ccl/src/main/java/codechicken/lib/render/ManagedTextureFX.java b/ccl/src/main/java/codechicken/lib/render/ManagedTextureFX.java new file mode 100644 index 0000000..80377ca --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/ManagedTextureFX.java @@ -0,0 +1,26 @@ +package codechicken.lib.render; + +public class ManagedTextureFX extends TextureFX { + + public boolean changed; + + public ManagedTextureFX(int size, String name) { + super(size, name); + imageData = new int[size * size]; + } + + @Override + public void setup() {} + + public void setData(int[] data) { + System.arraycopy(data, 0, imageData, 0, imageData.length); + changed = true; + } + + @Override + public boolean changed() { + boolean r = changed; + changed = false; + return r; + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/PlaceholderTexture.java b/ccl/src/main/java/codechicken/lib/render/PlaceholderTexture.java new file mode 100644 index 0000000..3f9b9e2 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/PlaceholderTexture.java @@ -0,0 +1,22 @@ +package codechicken.lib.render; + +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.resources.IResourceManager; +import net.minecraft.util.ResourceLocation; + +public class PlaceholderTexture extends TextureAtlasSprite { + + protected PlaceholderTexture(String par1) { + super(par1); + } + + @Override + public boolean hasCustomLoader(IResourceManager manager, ResourceLocation location) { + return true; + } + + @Override + public boolean load(IResourceManager manager, ResourceLocation location) { + return true; + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/QBImporter.java b/ccl/src/main/java/codechicken/lib/render/QBImporter.java new file mode 100644 index 0000000..5b5d457 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/QBImporter.java @@ -0,0 +1,710 @@ +package codechicken.lib.render; + +import java.awt.image.BufferedImage; +import java.io.DataInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import javax.imageio.ImageIO; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.IIconRegister; +import net.minecraft.util.IIcon; +import net.minecraft.util.ResourceLocation; + +import com.google.common.collect.HashMultimap; +import com.google.common.collect.Multimap; + +import codechicken.lib.render.uv.UV; +import codechicken.lib.render.uv.UVScale; +import codechicken.lib.vec.BlockCoord; +import codechicken.lib.vec.Cuboid6; +import codechicken.lib.vec.CuboidCoord; +import codechicken.lib.vec.Rectangle4i; +import codechicken.lib.vec.Rotation; +import codechicken.lib.vec.Scale; +import codechicken.lib.vec.Transformation; +import codechicken.lib.vec.Translation; +import codechicken.lib.vec.Vector3; + +public class QBImporter { + + private static class ImagePackNode { + + Rectangle4i rect; + ImagePackNode child1; + ImagePackNode child2; + QBImage packed; + + public ImagePackNode(int x, int y, int w, int h) { + rect = new Rectangle4i(x, y, w, h); + } + + public boolean pack(QBImage img) { + if (child1 != null) return child1.pack(img) || child2.pack(img); + + if (packed != null) return false; + + int fit = getFit(img.width(), img.height()); + if (fit == 0) return false; + + if ((fit & 2) != 0) { // exact fit + packed = img; + img.packSlot = rect; + img.packT = new ImageTransform((fit & 1) << 2); + return true; + } + + int w = (fit & 1) == 0 ? img.width() : img.height(); + int h = (fit & 1) == 0 ? img.height() : img.width(); + + if (rect.w - w > rect.h - h) { // create split with biggest leftover space + child1 = new ImagePackNode(rect.x, rect.y, w, rect.h); + child2 = new ImagePackNode(rect.x + w, rect.y, rect.w - w, rect.h); + } else { + child1 = new ImagePackNode(rect.x, rect.y, rect.w, h); + child2 = new ImagePackNode(rect.x, rect.y + h, rect.w, rect.h - h); + } + return child1.pack(img); + } + + private int getFit(int w, int h) { + if (w == rect.w && h == rect.h) return 2; + if (w == rect.h && h == rect.w) return 3; + if (rect.w >= w && rect.h >= h) return 4; + if (rect.w >= h && rect.h >= w) return 5; + + return 0; + } + + private static void nextSize(Rectangle4i rect, boolean square) { + if (square) { + rect.w <<= 1; + rect.h <<= 1; + } else { + if (rect.w == rect.h) rect.w *= 2; + else rect.h *= 2; + } + } + + public static ImagePackNode pack(List images, boolean square) { + Collections.sort(images); + + int area = 0; + for (QBImage img : images) area += img.area(); + + ImagePackNode node = new ImagePackNode(0, 0, 2, 2); + while (node.rect.area() < area) nextSize(node.rect, square); + + while (true) { + boolean packed = true; + for (QBImage img : images) if (!node.pack(img)) { + packed = false; + break; + } + + if (packed) return node; + + node.child1 = node.child2 = null; + nextSize(node.rect, square); + } + } + + public BufferedImage toImage() { + BufferedImage img = new BufferedImage(rect.w, rect.h, BufferedImage.TYPE_INT_ARGB); + write(img); + return img; + } + + private void write(BufferedImage img) { + if (child1 != null) { + child1.write(img); + child2.write(img); + } else if (packed != null) { + ImageTransform t = packed.packT; + for (int u = 0; u < rect.w; u++) for (int v = 0; v < rect.h; v++) { + int rgba = t.access(packed, u, v); + img.setRGB(u + rect.x, v + rect.y, rgba >>> 8 | rgba << 24); + } + } + } + } + + private static class ImageTransform { + + int transform; + + public ImageTransform(int i) { + transform = i; + } + + public ImageTransform() { + this(0); + } + + public boolean transpose() { + return (transform & 4) != 0; + } + + public boolean flipU() { + return (transform & 1) != 0; + } + + public boolean flipV() { + return (transform & 2) != 0; + } + + public int access(QBImage img, int u, int v) { + if (transpose()) { + int tmp = u; + u = v; + v = tmp; + } + if (flipU()) u = img.width() - 1 - u; + if (flipV()) v = img.height() - 1 - v; + + return img.data[u][v]; + } + + public UV transform(UV uv) { + if (transpose()) { + double tmp = uv.u; + uv.u = uv.v; + uv.v = tmp; + } + if (flipU()) uv.u = 1 - uv.u; + if (flipV()) uv.v = 1 - uv.v; + + return uv; + } + } + + public static class QBImage implements Comparable { + + int[][] data; + ImageTransform packT; + Rectangle4i packSlot; + + public int width() { + return data.length; + } + + public int height() { + return data[0].length; + } + + public int area() { + return width() * height(); + } + + @Override + public int compareTo(QBImage o) { + int a = area(); + int b = o.area(); + return a > b ? -1 : a == b ? 0 : 1; + } + + public ImageTransform transformTo(QBImage img) { + if (width() == img.width() && height() == img.height()) for (int i = 0; i < 4; i++) { + ImageTransform t = new ImageTransform(i); + if (equals(img, t)) return t; + } + if (width() == img.height() && height() == img.width()) for (int i = 4; i < 8; i++) { + ImageTransform t = new ImageTransform(i); + if (equals(img, t)) return t; + } + return null; + } + + public boolean equals(QBImage img, ImageTransform t) { + for (int u = 0; u < img.width(); u++) + for (int v = 0; v < img.height(); v++) if (t.access(this, u, v) != img.data[u][v]) return false; + + return true; + } + + public void transform(UV uv) { + packT.transform(uv); + uv.u *= packSlot.w; + uv.v *= packSlot.h; + uv.u += packSlot.x; + uv.v += packSlot.y; + } + } + + private static final int[][] vertOrder = new int[][] { // clockwise because MC is left handed + { 3, 0 }, { 1, 0 }, { 1, 2 }, { 3, 2 } }; + + public static class QBQuad { + + public Vertex5[] verts = new Vertex5[4]; + public QBImage image = new QBImage(); + public ImageTransform t = new ImageTransform(); + public int side; + + public QBQuad(int side) { + this.side = side; + } + + public void applyImageT() { + for (Vertex5 vert : verts) { + t.transform(vert.uv); + image.transform(vert.uv); + } + } + + public static QBQuad restore(Rectangle4i flat, int side, double d, QBImage img) { + QBQuad quad = new QBQuad(side); + quad.image = img; + + Transformation t = new Scale(-1, 1, -1).with(Rotation.sideOrientation(side, 0)) + .with(new Translation(new Vector3().setSide(side, d))); + quad.verts[0] = new Vertex5(flat.x, 0, flat.y, 0, 0); + quad.verts[1] = new Vertex5(flat.x + flat.w, 0, flat.y, 1, 0); + quad.verts[2] = new Vertex5(flat.x + flat.w, 0, flat.y + flat.h, 1, 1); + quad.verts[3] = new Vertex5(flat.x, 0, flat.y + flat.h, 0, 1); + for (Vertex5 vert : quad.verts) vert.apply(t); + + return quad; + } + + public Rectangle4i flatten() { + Transformation t = Rotation.sideOrientation(side, 0).inverse().with(new Scale(-1, 0, -1)); + Vector3 vmin = verts[0].vec.copy().apply(t); + Vector3 vmax = verts[2].vec.copy().apply(t); + return new Rectangle4i((int) vmin.x, (int) vmin.z, (int) (vmax.x - vmin.x), (int) (vmax.z - vmin.z)); + } + } + + public static class QBCuboid { + + public QBMatrix mat; + public CuboidCoord c; + public int sides; + + public QBCuboid(QBMatrix mat, CuboidCoord c) { + this.mat = mat; + this.c = c; + sides = 0; + } + + public static boolean intersects(QBCuboid a, QBCuboid b) { + CuboidCoord c = a.c; + CuboidCoord d = b.c; + return c.min.x <= d.max.x && d.min.x <= c.max.x + && c.min.y <= d.max.y + && d.min.y <= c.max.y + && c.min.z <= d.max.z + && d.min.z <= c.max.z; + } + + public static void clip(QBCuboid a, QBCuboid b) { + if (intersects(a, b)) { + a.clip(b); + b.clip(a); + } + } + + public void clip(QBCuboid o) { + CuboidCoord d = o.c; + for (int a = 0; a < 6; a += 2) { + int a1 = (a + 2) % 6; + int a2 = (a + 4) % 6; + if (c.getSide(a1 + 1) <= d.getSide(a1 + 1) && c.getSide(a1) >= d.getSide(a1) + && c.getSide(a2 + 1) <= d.getSide(a2 + 1) + && c.getSide(a2) >= d.getSide(a2)) { + + if (c.getSide(a) <= d.getSide(a + 1) && c.getSide(a) >= d.getSide(a)) { + c.setSide(a, d.getSide(a + 1) + 1); + sides |= 1 << a; + } + if (c.getSide(a + 1) >= d.getSide(a) && c.getSide(a + 1) <= d.getSide(a + 1)) { + c.setSide(a + 1, d.getSide(a) - 1); + sides |= 2 << a; + } + } + } + } + + public void extractQuads(List quads) { + Cuboid6 box = c.bounds(); + for (int s = 0; s < 6; s++) if ((sides & 1 << s) == 0) quads.add(extractQuad(s, box)); + } + + private QBQuad extractQuad(int side, Cuboid6 box) { + double[] da = new double[3]; + da[side >> 1] = box.getSide(side); + + QBQuad quad = new QBQuad(side); + for (int i = 0; i < 4; i++) { + int rU = vertOrder[i][0]; + int rV = vertOrder[i][1]; + int sideU = Rotation.rotateSide(side, rU); + int sideV = Rotation.rotateSide(side, rV); + da[sideU >> 1] = box.getSide(sideU); + da[sideV >> 1] = box.getSide(sideV); + quad.verts[i] = new Vertex5(Vector3.fromAxes(da), (3 - rU) / 2, rV / 2); + } + + int sideU = Rotation.rotateSide(side, 1); + int sideV = Rotation.rotateSide(side, 2); + quad.image.data = new int[c.size(sideU)][c.size(sideV)]; + QBImage image = quad.image; + + int[] ia = new int[3]; + ia[side >> 1] = c.getSide(side); + ia[sideU >> 1] = c.getSide(sideU ^ 1); + ia[sideV >> 1] = c.getSide(sideV ^ 1); + BlockCoord b = BlockCoord.fromAxes(ia); + BlockCoord bU = BlockCoord.sideOffsets[sideU]; + BlockCoord bV = BlockCoord.sideOffsets[sideV]; + for (int u = 0; u < image.width(); u++) for (int v = 0; v < image.height(); v++) + image.data[u][v] = mat.matrix[b.x + bU.x * u + bV.x * v][b.y + bU.y * u + bV.y * v][b.z + bU.z * u + + bV.z * v]; + + return quad; + } + } + + public static class QBMatrix { + + public String name; + public BlockCoord pos; + public BlockCoord size; + public int[][][] matrix; + + public void readMatrix(DataInputStream din, boolean compressed) throws IOException { + if (compressed) { + int z = 0; + while (z < size.z) { + int index = 0; + + while (true) { + int data = din.readInt(); + + if (data == NEXTSLICEFLAG) break; + + if (data == CODEFLAG) { + int count = readTni(din); + data = din.readInt(); + + for (int j = 0; j < count; j++, index++) matrix[index % size.x][index / size.x][z] = data; + } else { + matrix[index % size.x][index / size.x][z] = data; + index++; + } + } + z++; + } + } else { + for (int z = 0; z < size.z; z++) + for (int y = 0; y < size.y; y++) for (int x = 0; x < size.x; x++) matrix[x][y][z] = din.readInt(); + } + } + + public void convertBGRAtoRGBA() { + for (int z = 0; z < size.z; z++) for (int y = 0; y < size.y; y++) for (int x = 0; x < size.x; x++) { + int i = matrix[x][y][z]; + matrix[x][y][z] = Integer.reverseBytes(i >>> 8) | i & 0xFF; + } + } + + private boolean voxelFull(boolean[][][] solid, CuboidCoord c) { + for (BlockCoord b : c) if (matrix[b.x][b.y][b.z] == 0) return false; + for (BlockCoord b : c) solid[b.x][b.y][b.z] = false; + return true; + } + + private QBCuboid expand(boolean[][][] solid, BlockCoord b) { + CuboidCoord c = new CuboidCoord(b); + solid[b.x][b.y][b.z] = false; + + for (int s = 0; s < 6; s++) { + CuboidCoord slice = c.copy(); + slice.expand(s ^ 1, -(slice.size(s) - 1)); + slice.expand(s, 1); + + while (slice.getSide(s) >= 0 && slice.getSide(s) < size.getSide(s)) { + if (!voxelFull(solid, slice)) break; + slice.expand(s ^ 1, -1); + slice.expand(s, 1); + c.expand(s, 1); + } + } + return new QBCuboid(this, c); + } + + public List rectangulate() { + List list = new ArrayList(); + boolean[][][] solid = new boolean[size.x][size.y][size.z]; + for (int z = 0; z < size.z; z++) + for (int y = 0; y < size.y; y++) for (int x = 0; x < size.x; x++) solid[x][y][z] = matrix[x][y][z] != 0; + + for (int x = 0; x < size.x; x++) for (int z = 0; z < size.z; z++) + for (int y = 0; y < size.y; y++) if (solid[x][y][z]) list.add(expand(solid, new BlockCoord(x, y, z))); + + for (int i = 0; i < list.size(); i++) + for (int j = i + 1; j < list.size(); j++) QBCuboid.clip(list.get(i), list.get(j)); + + return list; + } + + public List extractQuads(boolean texturePlanes) { + List quads = new LinkedList(); + for (QBCuboid c : rectangulate()) c.extractQuads(quads); + + if (texturePlanes) optimisePlanes(quads); + + return quads; + } + + private void optimisePlanes(List quads) { + Multimap map = HashMultimap.create(); + for (QBQuad quad : quads) map.put(quad.side | ((int) quad.verts[0].vec.getSide(quad.side)) << 3, quad); + + quads.clear(); + for (Integer key : map.keySet()) { + Collection plane = map.get(key); + if (plane.size() == 1) { + quads.add(plane.iterator().next()); + continue; + } + + int side = key & 7; + Rectangle4i rect = null; + for (QBQuad q : plane) if (rect == null) rect = q.flatten(); + else rect.include(q.flatten()); + + QBImage img = new QBImage(); + img.data = new int[rect.w][rect.h]; + for (QBQuad q : plane) { + QBImage from = q.image; + Rectangle4i r = q.flatten(); + int du = r.x - rect.x; + int dv = r.y - rect.y; + for (int u = 0; u < from.width(); u++) + for (int v = 0; v < from.height(); v++) img.data[du + u][dv + v] = from.data[u][v]; + } + + quads.add(QBQuad.restore(rect, side, key >> 3, img)); + } + } + + public CCModel buildModel(List quads, BufferedImage img, boolean scaleMC) { + CCModel m = CCModel.quadModel(quads.size() * 4); + int i = 0; + for (QBQuad quad : quads) { + quad.applyImageT(); + m.verts[i++] = quad.verts[0]; + m.verts[i++] = quad.verts[1]; + m.verts[i++] = quad.verts[2]; + m.verts[i++] = quad.verts[3]; + } + m.apply(new UVScale(1D / img.getWidth(), 1D / img.getHeight())); + m.apply(new Translation(pos.x, pos.y, pos.z)); + if (scaleMC) m.apply(new Scale(1 / 16D)); + m.computeNormals(); + return m; + } + + private static void addImages(List quads, List images) { + for (QBQuad q : quads) { + QBImage img = q.image; + boolean matched = false; + for (QBImage img2 : images) { + ImageTransform t = img.transformTo(img2); + if (t != null) { + q.t = t; + q.image = img2; + matched = true; + break; + } + } + if (!matched) images.add(img); + } + } + } + + public static final int TEXTUREPLANES = 1; + public static final int SQUARETEXTURE = 2; + public static final int MERGETEXTURES = 4; + public static final int SCALEMC = 8; + + public static class QBModel { + + public QBMatrix[] matrices; + public boolean rightHanded; + + public RasterisedModel toRasterisedModel(int flags) { + List qbImages = new ArrayList(); + List> modelQuads = new ArrayList>(); + List images = new ArrayList(); + + boolean texturePlanes = (flags & TEXTUREPLANES) != 0; + boolean squareTextures = (flags & SQUARETEXTURE) != 0; + boolean mergeTextures = (flags & MERGETEXTURES) != 0; + boolean scaleMC = (flags & SCALEMC) != 0; + + for (QBMatrix mat : matrices) { + List quads = mat.extractQuads(texturePlanes); + modelQuads.add(quads); + QBMatrix.addImages(quads, qbImages); + if (!mergeTextures) { + images.add(ImagePackNode.pack(qbImages, squareTextures).toImage()); + qbImages.clear(); + } + } + + if (mergeTextures) images.add(ImagePackNode.pack(qbImages, squareTextures).toImage()); + + RasterisedModel m = new RasterisedModel(images); + for (int i = 0; i < matrices.length; i++) { + QBMatrix mat = matrices[i]; + BufferedImage img = images.get(mergeTextures ? 0 : i); + m.add(mat.name, mat.buildModel(modelQuads.get(i), img, scaleMC)); + } + return m; + } + } + + public static class RasterisedModel { + + private class Holder { + + CCModel m; + int img; + + public Holder(CCModel m, int img) { + this.m = m; + this.img = img; + } + } + + private Map map = new HashMap(); + private List images; + private String[] icons; + + public RasterisedModel(List images) { + this.images = images; + icons = new String[images.size()]; + } + + public void add(String name, CCModel m) { + map.put(name, new Holder(m, Math.min(map.size(), images.size() - 1))); + } + + public CCModel getModel(String key) { + return map.get(key).m; + } + + public IIcon getIcon(String key, IIconRegister r, String iconName) { + int img = map.get(key).img; + if (icons[img] != null && !iconName.equals(icons[img])) throw new IllegalArgumentException( + "Attempted to get a previously registered icon by a different name: " + icons[img] + + ", " + + iconName); + if (icons[img] != null) return r.registerIcon(iconName); + + icons[img] = iconName; + return TextureUtils.getTextureSpecial(r, iconName).addTexture(new TextureDataHolder(images.get(img))); + } + + private void exportImg(BufferedImage img, File imgFile) throws IOException { + if (!imgFile.exists()) imgFile.createNewFile(); + ImageIO.write(img, "PNG", imgFile); + } + + public void export(File objFile, File imgDir) { + try { + if (!objFile.exists()) objFile.createNewFile(); + if (!imgDir.exists()) imgDir.mkdirs(); + + Map modelMap = new HashMap(); + for (Map.Entry e : map.entrySet()) modelMap.put(e.getKey(), e.getValue().m); + + PrintWriter p = new PrintWriter(objFile); + CCModel.exportObj(modelMap, p); + p.close(); + + if (images.size() < map.size()) + exportImg(images.get(0), new File(imgDir, objFile.getName().replaceAll("(.+)\\..+", "$1.png"))); + else for (Map.Entry e : map.entrySet()) + exportImg(images.get(e.getValue().img), new File(imgDir, e.getKey() + ".png")); + + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } + + private static String readAsciiString(DataInputStream din) throws IOException { + byte[] bytes = new byte[din.readByte() & 0xFF]; + din.readFully(bytes); + return new String(bytes, "US-ASCII"); + } + + private static int readTni(DataInputStream din) throws IOException { + return Integer.reverseBytes(din.readInt()); + } + + private static final int CODEFLAG = Integer.reverseBytes(2); + private static final int NEXTSLICEFLAG = Integer.reverseBytes(6); + + public static QBModel loadQB(InputStream input) throws IOException { + DataInputStream din = new DataInputStream(input); + + QBModel m = new QBModel(); + int version = din.readInt(); + int colorFormat = din.readInt(); + m.rightHanded = din.readInt() != 0; + boolean compressed = din.readInt() != 0; + boolean visEncoded = din.readInt() != 0; + + if (visEncoded) throw new IllegalArgumentException("Encoded Visiblity States not supported"); + + m.matrices = new QBMatrix[readTni(din)]; + for (int i = 0; i < m.matrices.length; i++) { + QBMatrix mat = new QBMatrix(); + m.matrices[i] = mat; + mat.name = readAsciiString(din); + mat.size = new BlockCoord(readTni(din), readTni(din), readTni(din)); + mat.pos = new BlockCoord(readTni(din), readTni(din), readTni(din)); + mat.matrix = new int[mat.size.x][mat.size.y][mat.size.z]; + mat.readMatrix(din, compressed); + if (colorFormat == 1) mat.convertBGRAtoRGBA(); + } + + return m; + } + + public static QBModel loadQB(ResourceLocation res) { + try { + return loadQB(Minecraft.getMinecraft().getResourceManager().getResource(res).getInputStream()); + } catch (Exception e) { + throw new RuntimeException("failed to load model: " + res, e); + } + } + + public static QBModel loadQB(File file) { + try { + FileInputStream fin = new FileInputStream(file); + try { + return loadQB(fin); + } finally { + fin.close(); + } + } catch (Exception e) { + throw new RuntimeException("failed to load model: " + file.getPath(), e); + } + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/RenderUtils.java b/ccl/src/main/java/codechicken/lib/render/RenderUtils.java new file mode 100644 index 0000000..c0c4a37 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/RenderUtils.java @@ -0,0 +1,377 @@ +package codechicken.lib.render; + +import static net.minecraftforge.client.IItemRenderer.ItemRenderType.ENTITY; +import static net.minecraftforge.client.IItemRenderer.ItemRendererHelper.BLOCK_3D; + +import net.minecraft.block.Block; +import net.minecraft.client.renderer.RenderBlocks; +import net.minecraft.client.renderer.Tessellator; +import net.minecraft.client.renderer.entity.RenderItem; +import net.minecraft.client.renderer.entity.RenderManager; +import net.minecraft.entity.Entity; +import net.minecraft.entity.item.EntityItem; +import net.minecraft.item.ItemBlock; +import net.minecraft.item.ItemStack; +import net.minecraft.util.IIcon; +import net.minecraftforge.client.IItemRenderer; +import net.minecraftforge.client.MinecraftForgeClient; +import net.minecraftforge.fluids.Fluid; +import net.minecraftforge.fluids.FluidStack; + +import org.lwjgl.opengl.GL11; + +import codechicken.lib.vec.Cuboid6; +import codechicken.lib.vec.Rectangle4i; +import codechicken.lib.vec.Vector3; + +public class RenderUtils { + + static Vector3[] vectors = new Vector3[8]; + static RenderItem uniformRenderItem = new RenderItem() { + + public boolean shouldBob() { + return false; + } + }; + static EntityItem entityItem; + + static { + for (int i = 0; i < vectors.length; i++) vectors[i] = new Vector3(); + + uniformRenderItem.setRenderManager(RenderManager.instance); + + entityItem = new EntityItem(null); + entityItem.hoverStart = 0; + } + + public static void renderFluidQuad(Vector3 point1, Vector3 point2, Vector3 point3, Vector3 point4, IIcon icon, + double res) { + renderFluidQuad( + point2, + vectors[0].set(point4).subtract(point1), + vectors[1].set(point1).subtract(point2), + icon, + res); + } + + /** + * Draws a tessellated quadrilateral bottom to top, left to right + * + * @param base The bottom left corner of the quad + * @param wide The bottom of the quad + * @param high The left side of the quad + * @param res Units per icon + */ + public static void renderFluidQuad(Vector3 base, Vector3 wide, Vector3 high, IIcon icon, double res) { + Tessellator t = Tessellator.instance; + + double u1 = icon.getMinU(); + double du = icon.getMaxU() - icon.getMinU(); + double v2 = icon.getMaxV(); + double dv = icon.getMaxV() - icon.getMinV(); + + double wlen = wide.mag(); + double hlen = high.mag(); + + double x = 0; + while (x < wlen) { + double rx = wlen - x; + if (rx > res) rx = res; + + double y = 0; + while (y < hlen) { + double ry = hlen - y; + if (ry > res) ry = res; + + Vector3 dx1 = vectors[2].set(wide).multiply(x / wlen); + Vector3 dx2 = vectors[3].set(wide).multiply((x + rx) / wlen); + Vector3 dy1 = vectors[4].set(high).multiply(y / hlen); + Vector3 dy2 = vectors[5].set(high).multiply((y + ry) / hlen); + + t.addVertexWithUV( + base.x + dx1.x + dy2.x, + base.y + dx1.y + dy2.y, + base.z + dx1.z + dy2.z, + u1, + v2 - ry / res * dv); + t.addVertexWithUV(base.x + dx1.x + dy1.x, base.y + dx1.y + dy1.y, base.z + dx1.z + dy1.z, u1, v2); + t.addVertexWithUV( + base.x + dx2.x + dy1.x, + base.y + dx2.y + dy1.y, + base.z + dx2.z + dy1.z, + u1 + rx / res * du, + v2); + t.addVertexWithUV( + base.x + dx2.x + dy2.x, + base.y + dx2.y + dy2.y, + base.z + dx2.z + dy2.z, + u1 + rx / res * du, + v2 - ry / res * dv); + + y += ry; + } + + x += rx; + } + } + + public static void translateToWorldCoords(Entity entity, float frame) { + double interpPosX = entity.lastTickPosX + (entity.posX - entity.lastTickPosX) * frame; + double interpPosY = entity.lastTickPosY + (entity.posY - entity.lastTickPosY) * frame; + double interpPosZ = entity.lastTickPosZ + (entity.posZ - entity.lastTickPosZ) * frame; + + GL11.glTranslated(-interpPosX, -interpPosY, -interpPosZ); + } + + public static void drawCuboidOutline(Cuboid6 c) { + Tessellator var2 = Tessellator.instance; + var2.startDrawing(3); + var2.addVertex(c.min.x, c.min.y, c.min.z); + var2.addVertex(c.max.x, c.min.y, c.min.z); + var2.addVertex(c.max.x, c.min.y, c.max.z); + var2.addVertex(c.min.x, c.min.y, c.max.z); + var2.addVertex(c.min.x, c.min.y, c.min.z); + var2.draw(); + var2.startDrawing(3); + var2.addVertex(c.min.x, c.max.y, c.min.z); + var2.addVertex(c.max.x, c.max.y, c.min.z); + var2.addVertex(c.max.x, c.max.y, c.max.z); + var2.addVertex(c.min.x, c.max.y, c.max.z); + var2.addVertex(c.min.x, c.max.y, c.min.z); + var2.draw(); + var2.startDrawing(1); + var2.addVertex(c.min.x, c.min.y, c.min.z); + var2.addVertex(c.min.x, c.max.y, c.min.z); + var2.addVertex(c.max.x, c.min.y, c.min.z); + var2.addVertex(c.max.x, c.max.y, c.min.z); + var2.addVertex(c.max.x, c.min.y, c.max.z); + var2.addVertex(c.max.x, c.max.y, c.max.z); + var2.addVertex(c.min.x, c.min.y, c.max.z); + var2.addVertex(c.min.x, c.max.y, c.max.z); + var2.draw(); + } + + public static void renderFluidCuboid(CCRenderState state, Cuboid6 bound, IIcon tex, double res) { + renderFluidQuad( // bottom + new Vector3(bound.min.x, bound.min.y, bound.min.z), + new Vector3(bound.max.x, bound.min.y, bound.min.z), + new Vector3(bound.max.x, bound.min.y, bound.max.z), + new Vector3(bound.min.x, bound.min.y, bound.max.z), + tex, + res); + renderFluidQuad( // top + new Vector3(bound.min.x, bound.max.y, bound.min.z), + new Vector3(bound.min.x, bound.max.y, bound.max.z), + new Vector3(bound.max.x, bound.max.y, bound.max.z), + new Vector3(bound.max.x, bound.max.y, bound.min.z), + tex, + res); + renderFluidQuad( // -x + new Vector3(bound.min.x, bound.max.y, bound.min.z), + new Vector3(bound.min.x, bound.min.y, bound.min.z), + new Vector3(bound.min.x, bound.min.y, bound.max.z), + new Vector3(bound.min.x, bound.max.y, bound.max.z), + tex, + res); + renderFluidQuad( // +x + new Vector3(bound.max.x, bound.max.y, bound.max.z), + new Vector3(bound.max.x, bound.min.y, bound.max.z), + new Vector3(bound.max.x, bound.min.y, bound.min.z), + new Vector3(bound.max.x, bound.max.y, bound.min.z), + tex, + res); + renderFluidQuad( // -z + new Vector3(bound.max.x, bound.max.y, bound.min.z), + new Vector3(bound.max.x, bound.min.y, bound.min.z), + new Vector3(bound.min.x, bound.min.y, bound.min.z), + new Vector3(bound.min.x, bound.max.y, bound.min.z), + tex, + res); + renderFluidQuad( // +z + new Vector3(bound.min.x, bound.max.y, bound.max.z), + new Vector3(bound.min.x, bound.min.y, bound.max.z), + new Vector3(bound.max.x, bound.min.y, bound.max.z), + new Vector3(bound.max.x, bound.max.y, bound.max.z), + tex, + res); + } + + public static void renderFluidCuboid(Cuboid6 bound, IIcon tex, double res) { + renderFluidCuboid(CCRenderState.instance(), bound, tex, res); + } + + public static void renderBlockOverlaySide(int x, int y, int z, int side, double tx1, double tx2, double ty1, + double ty2) { + double[] points = new double[] { x - 0.009, x + 1.009, y - 0.009, y + 1.009, z - 0.009, z + 1.009 }; + + Tessellator tessellator = Tessellator.instance; + switch (side) { + case 0: + tessellator.addVertexWithUV(points[0], points[2], points[4], tx1, ty1); + tessellator.addVertexWithUV(points[1], points[2], points[4], tx2, ty1); + tessellator.addVertexWithUV(points[1], points[2], points[5], tx2, ty2); + tessellator.addVertexWithUV(points[0], points[2], points[5], tx1, ty2); + break; + case 1: + tessellator.addVertexWithUV(points[1], points[3], points[4], tx2, ty1); + tessellator.addVertexWithUV(points[0], points[3], points[4], tx1, ty1); + tessellator.addVertexWithUV(points[0], points[3], points[5], tx1, ty2); + tessellator.addVertexWithUV(points[1], points[3], points[5], tx2, ty2); + break; + case 2: + tessellator.addVertexWithUV(points[0], points[3], points[4], tx2, ty1); + tessellator.addVertexWithUV(points[1], points[3], points[4], tx1, ty1); + tessellator.addVertexWithUV(points[1], points[2], points[4], tx1, ty2); + tessellator.addVertexWithUV(points[0], points[2], points[4], tx2, ty2); + break; + case 3: + tessellator.addVertexWithUV(points[1], points[3], points[5], tx2, ty1); + tessellator.addVertexWithUV(points[0], points[3], points[5], tx1, ty1); + tessellator.addVertexWithUV(points[0], points[2], points[5], tx1, ty2); + tessellator.addVertexWithUV(points[1], points[2], points[5], tx2, ty2); + break; + case 4: + tessellator.addVertexWithUV(points[0], points[3], points[5], tx2, ty1); + tessellator.addVertexWithUV(points[0], points[3], points[4], tx1, ty1); + tessellator.addVertexWithUV(points[0], points[2], points[4], tx1, ty2); + tessellator.addVertexWithUV(points[0], points[2], points[5], tx2, ty2); + break; + case 5: + tessellator.addVertexWithUV(points[1], points[3], points[4], tx2, ty1); + tessellator.addVertexWithUV(points[1], points[3], points[5], tx1, ty1); + tessellator.addVertexWithUV(points[1], points[2], points[5], tx1, ty2); + tessellator.addVertexWithUV(points[1], points[2], points[4], tx2, ty2); + break; + } + } + + public static boolean shouldRenderFluid(FluidStack stack) { + return stack.amount > 0 && stack.getFluid() != null; + } + + /** + * @param stack The fluid stack to render + * @return The icon of the fluid + */ + public static IIcon prepareFluidRender(CCRenderState state, FluidStack stack, int alpha) { + GL11.glDisable(GL11.GL_LIGHTING); + GL11.glEnable(GL11.GL_BLEND); + GL11.glBlendFunc(GL11.GL_SRC_ALPHA, GL11.GL_ONE_MINUS_SRC_ALPHA); + + Fluid fluid = stack.getFluid(); + state.setColour(fluid.getColor(stack) << 8 | alpha); + TextureUtils.bindAtlas(fluid.getSpriteNumber()); + return TextureUtils.safeIcon(fluid.getIcon(stack)); + } + + public static IIcon prepareFluidRender(FluidStack stack, int alpha) { + return prepareFluidRender(CCRenderState.instance(), stack, alpha); + } + + /** + * Re-enables lighting and disables blending. + */ + public static void postFluidRender() { + GL11.glEnable(GL11.GL_LIGHTING); + GL11.glDisable(GL11.GL_BLEND); + } + + public static double fluidDensityToAlpha(double density) { + return Math.pow(density, 0.4); + } + + /** + * Renders a fluid within a bounding box. If the fluid is a liquid it will render as a normal tank with height equal + * to density/bound.height. If the fluid is a gas, it will render the full box with an alpha equal to density. + * Warning, bound will be mutated if the fluid is a liquid + * + * @param stack The fluid to render. + * @param bound The box within which the fluid is contained. + * @param density The volume of fluid / the capacity of the tank. For gases this determines the alpha, for liquids + * this determines the height. + * @param res The resolution to render at. + */ + public static void renderFluidCuboid(CCRenderState state, FluidStack stack, Cuboid6 bound, double density, + double res) { + if (!shouldRenderFluid(stack)) return; + + int alpha = 255; + if (stack.getFluid().isGaseous()) alpha = (int) (fluidDensityToAlpha(density) * 255); + else bound.max.y = bound.min.y + (bound.max.y - bound.min.y) * density; + + IIcon tex = prepareFluidRender(state, stack, alpha); + state.startDrawing(); + renderFluidCuboid(state, bound, tex, res); + state.draw(); + postFluidRender(); + } + + public static void renderFluidCuboid(FluidStack stack, Cuboid6 bound, double density, double res) { + renderFluidCuboid(CCRenderState.instance(), stack, bound, density, res); + } + + public static void renderFluidGauge(CCRenderState state, FluidStack stack, Rectangle4i rect, double density, + double res) { + if (!shouldRenderFluid(stack)) return; + + int alpha = 255; + if (stack.getFluid().isGaseous()) alpha = (int) (fluidDensityToAlpha(density) * 255); + else { + int height = (int) (rect.h * density); + rect.y += rect.h - height; + rect.h = height; + } + + IIcon tex = prepareFluidRender(state, stack, alpha); + state.startDrawing(); + renderFluidQuad( + new Vector3(rect.x, rect.y + rect.h, 0), + new Vector3(rect.w, 0, 0), + new Vector3(0, -rect.h, 0), + tex, + res); + state.draw(); + postFluidRender(); + } + + public static void renderFluidGauge(FluidStack stack, Rectangle4i rect, double density, double res) { + renderFluidGauge(CCRenderState.instance(), stack, rect, density, res); + } + + /** + * Renders items and blocks in the world at 0,0,0 with transformations that size them appropriately + */ + public static void renderItemUniform(ItemStack item) { + renderItemUniform(item, 0); + } + + /** + * Renders items and blocks in the world at 0,0,0 with transformations that size them appropriately + * + * @param spin The spin angle of the item around the y axis in degrees + */ + public static void renderItemUniform(ItemStack item, double spin) { + IItemRenderer customRenderer = MinecraftForgeClient.getItemRenderer(item, ENTITY); + boolean is3D = customRenderer != null && customRenderer.shouldUseRenderHelper(ENTITY, item, BLOCK_3D); + + boolean larger = false; + if (item.getItem() instanceof ItemBlock + && RenderBlocks.renderItemIn3d(Block.getBlockFromItem(item.getItem()).getRenderType())) { + int renderType = Block.getBlockFromItem(item.getItem()).getRenderType(); + larger = !(renderType == 1 || renderType == 19 || renderType == 12 || renderType == 2); + } else if (is3D) { + larger = true; + } + + double d = 2; + double d1 = 1 / d; + if (larger) GL11.glScaled(d, d, d); + + GL11.glColor4f(1, 1, 1, 1); + + entityItem.setEntityItemStack(item); + uniformRenderItem.doRender(entityItem, 0, larger ? 0.09 : 0.06, 0, 0, (float) (spin * 9 / Math.PI)); + + if (larger) GL11.glScaled(d1, d1, d1); + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/ShaderProgram.java b/ccl/src/main/java/codechicken/lib/render/ShaderProgram.java new file mode 100644 index 0000000..60a3d69 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/ShaderProgram.java @@ -0,0 +1,125 @@ +package codechicken.lib.render; + +import static org.lwjgl.opengl.ARBShaderObjects.GL_OBJECT_COMPILE_STATUS_ARB; +import static org.lwjgl.opengl.ARBShaderObjects.GL_OBJECT_INFO_LOG_LENGTH_ARB; +import static org.lwjgl.opengl.ARBShaderObjects.GL_OBJECT_LINK_STATUS_ARB; +import static org.lwjgl.opengl.ARBShaderObjects.GL_OBJECT_VALIDATE_STATUS_ARB; +import static org.lwjgl.opengl.ARBShaderObjects.glAttachObjectARB; +import static org.lwjgl.opengl.ARBShaderObjects.glCompileShaderARB; +import static org.lwjgl.opengl.ARBShaderObjects.glCreateProgramObjectARB; +import static org.lwjgl.opengl.ARBShaderObjects.glCreateShaderObjectARB; +import static org.lwjgl.opengl.ARBShaderObjects.glDeleteObjectARB; +import static org.lwjgl.opengl.ARBShaderObjects.glGetInfoLogARB; +import static org.lwjgl.opengl.ARBShaderObjects.glGetObjectParameteriARB; +import static org.lwjgl.opengl.ARBShaderObjects.glLinkProgramARB; +import static org.lwjgl.opengl.ARBShaderObjects.glShaderSourceARB; +import static org.lwjgl.opengl.ARBShaderObjects.glUseProgramObjectARB; +import static org.lwjgl.opengl.ARBShaderObjects.glValidateProgramARB; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import org.lwjgl.opengl.ARBShaderObjects; +import org.lwjgl.opengl.ARBVertexShader; +import org.lwjgl.opengl.GL11; +import org.lwjgl.util.vector.Matrix4f; + +public class ShaderProgram { + + int programID; + + public ShaderProgram() { + programID = glCreateProgramObjectARB(); + if (programID == 0) throw new RuntimeException("Unable to allocate shader program object."); + } + + public void attach(int shaderType, String resource) { + InputStream stream = ShaderProgram.class.getResourceAsStream(resource); + if (stream == null) throw new RuntimeException("Unable to locate resource: " + resource); + + attach(shaderType, stream); + } + + public void use() { + glUseProgramObjectARB(programID); + } + + public static void restore() { + glUseProgramObjectARB(0); + } + + public void link() { + glLinkProgramARB(programID); + if (glGetObjectParameteriARB(programID, GL_OBJECT_LINK_STATUS_ARB) == GL11.GL_FALSE) + throw new RuntimeException("Error linking program: " + getInfoLog(programID)); + + glValidateProgramARB(programID); + if (glGetObjectParameteriARB(programID, GL_OBJECT_VALIDATE_STATUS_ARB) == GL11.GL_FALSE) + throw new RuntimeException("Error validating program: " + getInfoLog(programID)); + + use(); + onLink(); + restore(); + } + + public void attach(int shaderType, InputStream stream) { + if (stream == null) throw new RuntimeException("Invalid shader inputstream"); + + int shaderID = 0; + try { + shaderID = glCreateShaderObjectARB(shaderType); + if (shaderID == 0) throw new RuntimeException("Unable to allocate shader object."); + + try { + glShaderSourceARB(shaderID, asString(stream)); + } catch (IOException e) { + throw new RuntimeException("Error reading inputstream.", e); + } + + glCompileShaderARB(shaderID); + if (glGetObjectParameteriARB(shaderID, GL_OBJECT_COMPILE_STATUS_ARB) == GL11.GL_FALSE) + throw new RuntimeException("Error compiling shader: " + getInfoLog(shaderID)); + + glAttachObjectARB(programID, shaderID); + } catch (RuntimeException e) { + glDeleteObjectARB(shaderID); + throw e; + } + } + + public static String asString(InputStream stream) throws IOException { + StringBuilder sb = new StringBuilder(); + BufferedReader bin = new BufferedReader(new InputStreamReader(stream)); + String line; + while ((line = bin.readLine()) != null) sb.append(line).append('\n'); + stream.close(); + return sb.toString(); + } + + private static String getInfoLog(int shaderID) { + return glGetInfoLogARB(shaderID, glGetObjectParameteriARB(shaderID, GL_OBJECT_INFO_LOG_LENGTH_ARB)); + } + + public int getUniformLoc(String name) { + return ARBShaderObjects.glGetUniformLocationARB(programID, name); + } + + public int getAttribLoc(String name) { + return ARBVertexShader.glGetAttribLocationARB(programID, name); + } + + public void uniformTexture(String name, int textureIndex) { + ARBShaderObjects.glUniform1iARB(getUniformLoc(name), textureIndex); + } + + public void onLink() {} + + public void glVertexAttributeMat4(int loc, Matrix4f matrix) { + ARBVertexShader.glVertexAttrib4fARB(loc, matrix.m00, matrix.m01, matrix.m02, matrix.m03); + ARBVertexShader.glVertexAttrib4fARB(loc + 1, matrix.m10, matrix.m11, matrix.m12, matrix.m13); + ARBVertexShader.glVertexAttrib4fARB(loc + 2, matrix.m20, matrix.m21, matrix.m22, matrix.m23); + ARBVertexShader.glVertexAttrib4fARB(loc + 3, matrix.m30, matrix.m31, matrix.m32, matrix.m33); + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/SpriteSheetManager.java b/ccl/src/main/java/codechicken/lib/render/SpriteSheetManager.java new file mode 100644 index 0000000..2dedc86 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/SpriteSheetManager.java @@ -0,0 +1,129 @@ +package codechicken.lib.render; + +import java.util.ArrayList; +import java.util.HashMap; + +import net.minecraft.client.renderer.texture.IIconRegister; +import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.util.IIcon; +import net.minecraft.util.ResourceLocation; + +import codechicken.lib.render.TextureUtils.IIconSelfRegister; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class SpriteSheetManager { + + @SideOnly(Side.CLIENT) + public static class SpriteSheet implements IIconSelfRegister { + + private int tilesX; + private int tilesY; + private ArrayList newSprites = new ArrayList(); + private TextureSpecial[] sprites; + private ResourceLocation resource; + private TextureDataHolder texture; + private int spriteWidth; + private int spriteHeight; + + public int atlasIndex; + + private SpriteSheet(int tilesX, int tilesY, ResourceLocation textureFile) { + this.tilesX = tilesX; + this.tilesY = tilesY; + this.resource = textureFile; + sprites = new TextureSpecial[tilesX * tilesY]; + } + + public void requestIndicies(int... indicies) { + for (int i : indicies) setupSprite(i); + } + + public void registerIcons(IIconRegister register) { + TextureMap textureMap = (TextureMap) register; + + if (TextureUtils.refreshTexture(textureMap, resource.getResourcePath())) { + reloadTexture(); + for (TextureSpecial sprite : sprites) + if (sprite != null) textureMap.setTextureEntry(sprite.getIconName(), sprite); + } else { + for (int i : newSprites) textureMap.setTextureEntry(sprites[i].getIconName(), sprites[i]); + } + newSprites.clear(); + } + + public TextureSpecial setupSprite(int i) { + if (sprites[i] == null) { + String name = resource + "_" + i; + sprites[i] = new TextureSpecial(name).baseFromSheet(this, i); + newSprites.add(i); + } + return sprites[i]; + } + + private void reloadTexture() { + texture = TextureUtils.loadTexture(resource); + spriteWidth = texture.width / tilesX; + spriteHeight = texture.height / tilesY; + } + + public IIcon getSprite(int index) { + IIcon i = sprites[index]; + if (i == null) throw new IllegalArgumentException( + "Sprite at index: " + index + " from texture file " + resource + " was not preloaded."); + return i; + } + + public TextureDataHolder createSprite(int spriteIndex) { + int sx = spriteIndex % tilesX; + int sy = spriteIndex / tilesX; + TextureDataHolder sprite = new TextureDataHolder(spriteWidth, spriteHeight); + TextureUtils.copySubImg( + texture.data, + texture.width, + sx * spriteWidth, + sy * spriteHeight, + spriteWidth, + spriteHeight, + sprite.data, + spriteWidth, + 0, + 0); + return sprite; + } + + public int spriteWidth() { + return spriteWidth; + } + + public int spriteHeight() { + return spriteHeight; + } + + public TextureSpecial bindTextureFX(int i, TextureFX textureFX) { + return setupSprite(i).addTextureFX(textureFX); + } + + public SpriteSheet selfRegister(int atlas) { + TextureUtils.addIconRegistrar(this); + return this; + } + + @Override + public int atlasIndex() { + return atlasIndex; + } + } + + private static HashMap spriteSheets = new HashMap(); + + public static SpriteSheet getSheet(ResourceLocation resource) { + return getSheet(16, 16, resource); + } + + public static SpriteSheet getSheet(int tilesX, int tilesY, ResourceLocation resource) { + SpriteSheet sheet = spriteSheets.get(resource.toString()); + if (sheet == null) spriteSheets.put(resource.toString(), sheet = new SpriteSheet(tilesX, tilesY, resource)); + return sheet; + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/TextureDataHolder.java b/ccl/src/main/java/codechicken/lib/render/TextureDataHolder.java new file mode 100644 index 0000000..fd5dccf --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/TextureDataHolder.java @@ -0,0 +1,34 @@ +package codechicken.lib.render; + +import java.awt.image.BufferedImage; + +public class TextureDataHolder { + + public int width; + public int height; + public int[] data; + + public TextureDataHolder(int width, int height) { + this.width = width; + this.height = height; + data = new int[width * height]; + } + + public TextureDataHolder(int[] data, int width) { + this.data = data; + this.width = width; + height = data.length / width; + } + + public TextureDataHolder(BufferedImage img) { + this(img.getWidth(), img.getHeight()); + img.getRGB(0, 0, width, height, data, 0, width); + } + + public TextureDataHolder copyData() { + int[] copy = new int[data.length]; + System.arraycopy(data, 0, copy, 0, data.length); + data = copy; + return this; + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/TextureFX.java b/ccl/src/main/java/codechicken/lib/render/TextureFX.java new file mode 100644 index 0000000..d1cf852 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/TextureFX.java @@ -0,0 +1,59 @@ +package codechicken.lib.render; + +import net.minecraft.client.Minecraft; + +import codechicken.lib.render.SpriteSheetManager.SpriteSheet; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class TextureFX { + + public int[] imageData; + public int tileSizeBase = 16; + public int tileSizeSquare = 256; + public int tileSizeMask = 15; + public int tileSizeSquareMask = 255; + + public boolean anaglyphEnabled; + public TextureSpecial texture; + + public TextureFX(int spriteIndex, SpriteSheet sheet) { + texture = sheet.bindTextureFX(spriteIndex, this); + } + + public TextureFX(int size, String name) { + texture = new TextureSpecial(name).blank(size).selfRegister().addTextureFX(this); + } + + public TextureFX setAtlas(int index) { + texture.atlasIndex = index; + return this; + } + + public void setup() { + imageData = new int[tileSizeSquare]; + } + + public void onTextureDimensionsUpdate(int width, int height) { + if (width != height) + throw new IllegalArgumentException("Non-Square textureFX not supported (" + width + ":" + height + ")"); + + tileSizeBase = width; + tileSizeSquare = tileSizeBase * tileSizeBase; + tileSizeMask = tileSizeBase - 1; + tileSizeSquareMask = tileSizeSquare - 1; + setup(); + } + + public void update() { + anaglyphEnabled = Minecraft.getMinecraft().gameSettings.anaglyph; + onTick(); + } + + public void onTick() {} + + public boolean changed() { + return true; + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/TextureSpecial.java b/ccl/src/main/java/codechicken/lib/render/TextureSpecial.java new file mode 100644 index 0000000..ee797c7 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/TextureSpecial.java @@ -0,0 +1,180 @@ +package codechicken.lib.render; + +import java.awt.image.BufferedImage; +import java.util.ArrayList; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.IIconRegister; +import net.minecraft.client.renderer.texture.TextureAtlasSprite; +import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.client.renderer.texture.TextureUtil; +import net.minecraft.client.resources.IResourceManager; +import net.minecraft.client.resources.data.AnimationMetadataSection; +import net.minecraft.client.settings.GameSettings; +import net.minecraft.util.ResourceLocation; + +import codechicken.lib.render.SpriteSheetManager.SpriteSheet; +import codechicken.lib.render.TextureUtils.IIconSelfRegister; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +@SideOnly(Side.CLIENT) +public class TextureSpecial extends TextureAtlasSprite implements IIconSelfRegister { + + // sprite sheet fields + private int spriteIndex; + private SpriteSheet spriteSheet; + + // textureFX fields + private TextureFX textureFX; + private int mipmapLevels; + private int rawWidth; + private int rawHeight; + + private int blankSize = -1; + private ArrayList baseTextures; + + private boolean selfRegister; + public int atlasIndex; + + protected TextureSpecial(String par1) { + super(par1); + } + + public TextureSpecial addTexture(TextureDataHolder t) { + if (baseTextures == null) baseTextures = new ArrayList(); + baseTextures.add(t); + return this; + } + + public TextureSpecial baseFromSheet(SpriteSheet spriteSheet, int spriteIndex) { + this.spriteSheet = spriteSheet; + this.spriteIndex = spriteIndex; + return this; + } + + public TextureSpecial addTextureFX(TextureFX fx) { + textureFX = fx; + return this; + } + + @Override + public void initSprite(int sheetWidth, int sheetHeight, int originX, int originY, boolean rotated) { + super.initSprite(sheetWidth, sheetHeight, originX, originY, rotated); + if (textureFX != null) textureFX.onTextureDimensionsUpdate(rawWidth, rawHeight); + } + + @Override + public void updateAnimation() { + if (textureFX != null) { + textureFX.update(); + if (textureFX.changed()) { + int[][] mipmaps = new int[mipmapLevels + 1][]; + mipmaps[0] = textureFX.imageData; + mipmaps = prepareAnisotropicFiltering(mipmaps); + mipmaps = TextureUtil.generateMipmapData(mipmapLevels, width, mipmaps); + TextureUtil.uploadTextureMipmap(mipmaps, width, height, originX, originY, false, false); + } + } + } + + /** + * Copy paste mojang code because it's private, and CCL can't have access transformers or reflection + */ + public int[][] prepareAnisotropicFiltering(int[][] mipmaps) { + if (Minecraft.getMinecraft().gameSettings.anisotropicFiltering <= 1) { + return mipmaps; + } else { + int[][] aint1 = new int[mipmaps.length][]; + + for (int k = 0; k < mipmaps.length; ++k) { + int[] aint2 = mipmaps[k]; + + if (aint2 != null) { + int[] aint3 = new int[(rawWidth + 16 >> k) * (rawHeight + 16 >> k)]; + System.arraycopy(aint2, 0, aint3, 0, aint2.length); + aint1[k] = TextureUtil.prepareAnisotropicData(aint3, rawWidth >> k, rawHeight >> k, 8 >> k); + } + } + + return aint1; + } + } + + @Override + public void loadSprite(BufferedImage[] images, AnimationMetadataSection animationMeta, + boolean anisotropicFiltering) { + rawWidth = images[0].getWidth(); + rawHeight = images[0].getHeight(); + super.loadSprite(images, animationMeta, anisotropicFiltering); + } + + @Override + public void generateMipmaps(int p_147963_1_) { + super.generateMipmaps(p_147963_1_); + mipmapLevels = p_147963_1_; + } + + @Override + public boolean hasCustomLoader(IResourceManager manager, ResourceLocation location) { + return true; + } + + public void addFrame(int[] data, int width, int height) { + GameSettings settings = Minecraft.getMinecraft().gameSettings; + BufferedImage[] images = new BufferedImage[settings.mipmapLevels + 1]; + images[0] = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB); + images[0].setRGB(0, 0, width, height, data, 0, width); + + loadSprite(images, null, settings.anisotropicFiltering > 1); + } + + @Override + public boolean load(IResourceManager manager, ResourceLocation location) { + if (baseTextures != null) { + for (TextureDataHolder tex : baseTextures) addFrame(tex.data, tex.width, tex.height); + } else if (spriteSheet != null) { + TextureDataHolder tex = spriteSheet.createSprite(spriteIndex); + addFrame(tex.data, tex.width, tex.height); + } else if (blankSize > 0) { + addFrame(new int[blankSize * blankSize], blankSize, blankSize); + } + + if (framesTextureData.isEmpty()) throw new RuntimeException("No base frame for texture: " + getIconName()); + + return false; + } + + @Override + public boolean hasAnimationMetadata() { + return textureFX != null || super.hasAnimationMetadata(); + } + + @Override + public int getFrameCount() { + if (textureFX != null) return 1; + + return super.getFrameCount(); + } + + public TextureSpecial blank(int size) { + blankSize = size; + return this; + } + + public TextureSpecial selfRegister() { + selfRegister = true; + TextureUtils.addIconRegistrar(this); + return this; + } + + @Override + public void registerIcons(IIconRegister register) { + if (selfRegister) ((TextureMap) register).setTextureEntry(getIconName(), this); + } + + @Override + public int atlasIndex() { + return atlasIndex; + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/TextureUtils.java b/ccl/src/main/java/codechicken/lib/render/TextureUtils.java new file mode 100644 index 0000000..b7ab4bb --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/TextureUtils.java @@ -0,0 +1,168 @@ +package codechicken.lib.render; + +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; + +import javax.imageio.ImageIO; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.renderer.texture.IIconRegister; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.client.renderer.texture.TextureMap; +import net.minecraft.util.IIcon; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.client.event.TextureStitchEvent; +import net.minecraftforge.common.MinecraftForge; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.opengl.GL12; + +import codechicken.lib.colour.Colour; +import codechicken.lib.colour.ColourARGB; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; + +public class TextureUtils { + + public static interface IIconSelfRegister { + + public void registerIcons(IIconRegister register); + + public int atlasIndex(); + } + + static { + MinecraftForge.EVENT_BUS.register(new TextureUtils()); + } + + private static ArrayList iconRegistrars = new ArrayList(); + + public static void addIconRegistrar(IIconSelfRegister registrar) { + iconRegistrars.add(registrar); + } + + @SubscribeEvent + public void textureLoad(TextureStitchEvent.Pre event) { + for (IIconSelfRegister reg : iconRegistrars) + if (reg.atlasIndex() == event.map.getTextureType()) reg.registerIcons(event.map); + } + + /** + * @return an array of ARGB pixel data + */ + public static int[] loadTextureData(ResourceLocation resource) { + return loadTexture(resource).data; + } + + public static Colour[] loadTextureColours(ResourceLocation resource) { + int[] idata = loadTextureData(resource); + Colour[] data = new Colour[idata.length]; + for (int i = 0; i < data.length; i++) data[i] = new ColourARGB(idata[i]); + return data; + } + + public static InputStream getTextureResource(ResourceLocation textureFile) throws IOException { + return Minecraft.getMinecraft().getResourceManager().getResource(textureFile).getInputStream(); + } + + public static BufferedImage loadBufferedImage(ResourceLocation textureFile) { + try { + return loadBufferedImage(getTextureResource(textureFile)); + } catch (Exception e) { + System.err.println("Failed to load texture file: " + textureFile); + e.printStackTrace(); + } + return null; + } + + public static BufferedImage loadBufferedImage(InputStream in) throws IOException { + BufferedImage img = ImageIO.read(in); + in.close(); + return img; + } + + public static TextureManager engine() { + return Minecraft.getMinecraft().renderEngine; + } + + public static void copySubImg(int[] fromTex, int fromWidth, int fromX, int fromY, int width, int height, + int[] toTex, int toWidth, int toX, int toY) { + for (int y = 0; y < height; y++) for (int x = 0; x < width; x++) { + int fp = (y + fromY) * fromWidth + x + fromX; + int tp = (y + toX) * toWidth + x + toX; + + toTex[tp] = fromTex[fp]; + } + } + + public static void bindAtlas(int atlasIndex) { + engine().bindTexture(atlasIndex == 0 ? TextureMap.locationBlocksTexture : TextureMap.locationItemsTexture); + } + + public static IIcon getBlankIcon(int size, IIconRegister iconRegister) { + TextureMap textureMap = (TextureMap) iconRegister; + String s = "blank_" + size; + if (textureMap.getTextureExtry(s) == null) { + TextureSpecial icon = new TextureSpecial(s).blank(size); + textureMap.setTextureEntry(s, icon); + } + return iconRegister.registerIcon(s); + } + + public static TextureSpecial getTextureSpecial(IIconRegister iconRegister, String name) { + TextureMap textureMap = (TextureMap) iconRegister; + IIcon entry = textureMap.getTextureExtry(name); + if (entry != null) throw new IllegalStateException("Texture: " + name + " is already registered"); + + TextureSpecial icon = new TextureSpecial(name); + textureMap.setTextureEntry(name, icon); + return icon; + } + + public static void prepareTexture(int target, int texture, int min_mag_filter, int wrap) { + GL11.glBindTexture(target, texture); + GL11.glTexParameteri(target, GL11.GL_TEXTURE_MIN_FILTER, min_mag_filter); + GL11.glTexParameteri(target, GL11.GL_TEXTURE_MAG_FILTER, min_mag_filter); + switch (target) { + case GL12.GL_TEXTURE_3D: + GL11.glTexParameteri(target, GL12.GL_TEXTURE_WRAP_R, wrap); + case GL11.GL_TEXTURE_2D: + GL11.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_T, wrap); + case GL11.GL_TEXTURE_1D: + GL11.glTexParameteri(target, GL11.GL_TEXTURE_WRAP_S, wrap); + } + } + + public static TextureDataHolder loadTexture(ResourceLocation resource) { + BufferedImage img = loadBufferedImage(resource); + if (img == null) throw new RuntimeException("Texture not found: " + resource); + return new TextureDataHolder(img); + } + + /** + * Uses an empty placeholder texture to tell if the map has been reloaded since the last call to refresh texture and + * the texture with name needs to be reacquired to be valid + */ + public static boolean refreshTexture(TextureMap map, String name) { + if (map.getTextureExtry(name) == null) { + map.setTextureEntry(name, new PlaceholderTexture(name)); + return true; + } + return false; + } + + public static IIcon safeIcon(IIcon icon) { + if (icon == null) + icon = ((TextureMap) engine().getTexture(TextureMap.locationBlocksTexture)).getAtlasSprite("missingno"); + + return icon; + } + + public static boolean isMissing(IIcon icon, ResourceLocation atlas) { + if (icon == null) return true; + + IIcon missing = ((TextureMap) engine().getTexture(atlas)).getAtlasSprite("missingno"); + return icon.getMinU() == missing.getMinU() && icon.getMinV() == missing.getMinV(); + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/Vertex5.java b/ccl/src/main/java/codechicken/lib/render/Vertex5.java new file mode 100644 index 0000000..6c5156b --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/Vertex5.java @@ -0,0 +1,91 @@ +package codechicken.lib.render; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import codechicken.lib.render.uv.UV; +import codechicken.lib.render.uv.UVTransformation; +import codechicken.lib.util.Copyable; +import codechicken.lib.vec.Transformation; +import codechicken.lib.vec.Vector3; + +public class Vertex5 implements Copyable { + + public Vector3 vec; + public UV uv; + + public Vertex5() { + this(new Vector3(), new UV()); + } + + public Vertex5(Vector3 vert, UV uv) { + this.vec = vert; + this.uv = uv; + } + + public Vertex5(Vector3 vert, double u, double v) { + this(vert, new UV(u, v)); + } + + public Vertex5(double x, double y, double z, double u, double v) { + this(x, y, z, u, v, 0); + } + + public Vertex5(double x, double y, double z, double u, double v, int tex) { + this(new Vector3(x, y, z), new UV(u, v, tex)); + } + + public Vertex5 set(double x, double y, double z, double u, double v) { + vec.set(x, y, z); + uv.set(u, v); + return this; + } + + public Vertex5 set(double x, double y, double z, double u, double v, int tex) { + vec.set(x, y, z); + uv.set(u, v, tex); + return this; + } + + public Vertex5 set(Vertex5 vert) { + vec.set(vert.vec); + uv.set(vert.uv); + return this; + } + + public Vertex5(Vertex5 vertex5) { + this(vertex5.vec.copy(), vertex5.uv.copy()); + } + + public Vertex5 copy() { + return new Vertex5(this); + } + + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "Vertex: (" + new BigDecimal(vec.x, cont) + + ", " + + new BigDecimal(vec.y, cont) + + ", " + + new BigDecimal(vec.z, cont) + + ") " + + "(" + + new BigDecimal(uv.u, cont) + + ", " + + new BigDecimal(uv.v, cont) + + ") (" + + uv.tex + + ")"; + } + + public Vertex5 apply(Transformation t) { + vec.apply(t); + return this; + } + + public Vertex5 apply(UVTransformation t) { + uv.apply(t); + return this; + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/uv/IconTransformation.java b/ccl/src/main/java/codechicken/lib/render/uv/IconTransformation.java new file mode 100644 index 0000000..af19b26 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/uv/IconTransformation.java @@ -0,0 +1,25 @@ +package codechicken.lib.render.uv; + +import net.minecraft.util.IIcon; + +import codechicken.lib.vec.IrreversibleTransformationException; + +public class IconTransformation extends UVTransformation { + + public IIcon icon; + + public IconTransformation(IIcon icon) { + this.icon = icon; + } + + @Override + public void apply(UV uv) { + uv.u = icon.getInterpolatedU(uv.u * 16); + uv.v = icon.getInterpolatedV(uv.v * 16); + } + + @Override + public UVTransformation inverse() { + throw new IrreversibleTransformationException(this); + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/uv/MultiIconTransformation.java b/ccl/src/main/java/codechicken/lib/render/uv/MultiIconTransformation.java new file mode 100644 index 0000000..d0521a8 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/uv/MultiIconTransformation.java @@ -0,0 +1,26 @@ +package codechicken.lib.render.uv; + +import net.minecraft.util.IIcon; + +import codechicken.lib.vec.IrreversibleTransformationException; + +public class MultiIconTransformation extends UVTransformation { + + public IIcon[] icons; + + public MultiIconTransformation(IIcon... icons) { + this.icons = icons; + } + + @Override + public void apply(UV uv) { + IIcon icon = icons[uv.tex % icons.length]; + uv.u = icon.getInterpolatedU(uv.u * 16); + uv.v = icon.getInterpolatedV(uv.v * 16); + } + + @Override + public UVTransformation inverse() { + throw new IrreversibleTransformationException(this); + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/uv/UV.java b/ccl/src/main/java/codechicken/lib/render/uv/UV.java new file mode 100644 index 0000000..e4828db --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/uv/UV.java @@ -0,0 +1,78 @@ +package codechicken.lib.render.uv; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import codechicken.lib.util.Copyable; + +public class UV implements Copyable { + + public double u; + public double v; + public int tex; + + public UV() {} + + public UV(double u, double v) { + this(u, v, 0); + } + + public UV(double u, double v, int tex) { + this.u = u; + this.v = v; + this.tex = tex; + } + + public UV(UV uv) { + this(uv.u, uv.v, uv.tex); + } + + public UV set(double u, double v, int tex) { + this.u = u; + this.v = v; + this.tex = tex; + return this; + } + + public UV set(double u, double v) { + return set(u, v, tex); + } + + public UV set(UV uv) { + return set(uv.u, uv.v, uv.tex); + } + + public UV copy() { + return new UV(this); + } + + public UV add(UV uv) { + u += uv.u; + v += uv.v; + return this; + } + + public UV multiply(double d) { + u *= d; + v *= d; + return this; + } + + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "UV(" + new BigDecimal(u, cont) + ", " + new BigDecimal(v, cont) + ")"; + } + + public UV apply(UVTransformation t) { + t.apply(this); + return this; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof UV)) return false; + UV uv = (UV) o; + return u == uv.u && v == uv.v; + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/uv/UVRotation.java b/ccl/src/main/java/codechicken/lib/render/uv/UVRotation.java new file mode 100644 index 0000000..6935603 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/uv/UVRotation.java @@ -0,0 +1,51 @@ +package codechicken.lib.render.uv; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import codechicken.lib.math.MathHelper; + +public class UVRotation extends UVTransformation { + + public double angle; + + /** + * @param angle The angle to rotate counterclockwise in radians + */ + public UVRotation(double angle) { + this.angle = angle; + } + + @Override + public void apply(UV uv) { + double c = MathHelper.cos(angle); + double s = MathHelper.sin(angle); + double u2 = c * uv.u + s * uv.v; + uv.v = -s * uv.u + c * uv.v; + uv.u = u2; + } + + @Override + public UVTransformation inverse() { + return new UVRotation(-angle); + } + + @Override + public UVTransformation merge(UVTransformation next) { + if (next instanceof UVRotation) return new UVRotation(angle + ((UVRotation) next).angle); + + return null; + } + + @Override + public boolean isRedundant() { + return MathHelper.between(-1E-5, angle, 1E-5); + } + + @Override + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "UVRotation(" + new BigDecimal(angle, cont) + ")"; + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/uv/UVScale.java b/ccl/src/main/java/codechicken/lib/render/uv/UVScale.java new file mode 100644 index 0000000..a2de0ef --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/uv/UVScale.java @@ -0,0 +1,37 @@ +package codechicken.lib.render.uv; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +public class UVScale extends UVTransformation { + + double su; + double sv; + + public UVScale(double scaleu, double scalev) { + su = scaleu; + sv = scalev; + } + + public UVScale(double d) { + this(d, d); + } + + @Override + public void apply(UV uv) { + uv.u *= su; + uv.v *= sv; + } + + @Override + public UVTransformation inverse() { + return new UVScale(1 / su, 1 / sv); + } + + @Override + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "UVScale(" + new BigDecimal(su, cont) + ", " + new BigDecimal(sv, cont) + ")"; + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/uv/UVTransformation.java b/ccl/src/main/java/codechicken/lib/render/uv/UVTransformation.java new file mode 100644 index 0000000..d7ca168 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/uv/UVTransformation.java @@ -0,0 +1,39 @@ +package codechicken.lib.render.uv; + +import codechicken.lib.render.CCRenderState; +import codechicken.lib.vec.ITransformation; + +/** + * Abstract supertype for any UV transformation + */ +public abstract class UVTransformation extends ITransformation + implements CCRenderState.IVertexOperation { + + public static final int operationIndex = CCRenderState.registerOperation(); + + public UVTransformation at(UV point) { + return new UVTransformationList( + new UVTranslation(-point.u, -point.v), + this, + new UVTranslation(point.u, point.v)); + } + + public UVTransformationList with(UVTransformation t) { + return new UVTransformationList(this, t); + } + + @Override + public boolean load(CCRenderState state) { + return !isRedundant(); + } + + @Override + public void operate(CCRenderState state) { + apply(state.vert.uv); + } + + @Override + public int operationID() { + return operationIndex; + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/uv/UVTransformationList.java b/ccl/src/main/java/codechicken/lib/render/uv/UVTransformationList.java new file mode 100644 index 0000000..745b22d --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/uv/UVTransformationList.java @@ -0,0 +1,83 @@ +package codechicken.lib.render.uv; + +import java.util.ArrayList; +import java.util.Iterator; + +public class UVTransformationList extends UVTransformation { + + private ArrayList transformations = new ArrayList(); + + public UVTransformationList(UVTransformation... transforms) { + for (UVTransformation t : transforms) + if (t instanceof UVTransformationList) transformations.addAll(((UVTransformationList) t).transformations); + else transformations.add(t); + + compact(); + } + + @Override + public void apply(UV uv) { + for (int i = 0; i < transformations.size(); i++) transformations.get(i).apply(uv); + } + + @Override + public UVTransformationList with(UVTransformation t) { + if (t.isRedundant()) return this; + + if (t instanceof UVTransformationList) transformations.addAll(((UVTransformationList) t).transformations); + else transformations.add(t); + + compact(); + return this; + } + + public UVTransformationList prepend(UVTransformation t) { + if (t.isRedundant()) return this; + + if (t instanceof UVTransformationList) transformations.addAll(0, ((UVTransformationList) t).transformations); + else transformations.add(0, t); + + compact(); + return this; + } + + private void compact() { + ArrayList newList = new ArrayList(transformations.size()); + Iterator iterator = transformations.iterator(); + UVTransformation prev = null; + while (iterator.hasNext()) { + UVTransformation t = iterator.next(); + if (t.isRedundant()) continue; + + if (prev != null) { + UVTransformation m = prev.merge(t); + if (m == null) newList.add(prev); + else if (m.isRedundant()) t = null; + else t = m; + } + prev = t; + } + if (prev != null) newList.add(prev); + + if (newList.size() < transformations.size()) transformations = newList; + } + + @Override + public boolean isRedundant() { + return transformations.size() == 0; + } + + @Override + public UVTransformation inverse() { + UVTransformationList rev = new UVTransformationList(); + for (int i = transformations.size() - 1; i >= 0; i--) rev.with(transformations.get(i).inverse()); + return rev; + } + + @Override + public String toString() { + String s = ""; + for (UVTransformation t : transformations) s += "\n" + t.toString(); + return s.trim(); + } +} diff --git a/ccl/src/main/java/codechicken/lib/render/uv/UVTranslation.java b/ccl/src/main/java/codechicken/lib/render/uv/UVTranslation.java new file mode 100644 index 0000000..2daf58f --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/render/uv/UVTranslation.java @@ -0,0 +1,55 @@ +package codechicken.lib.render.uv; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import codechicken.lib.math.MathHelper; + +public class UVTranslation extends UVTransformation { + + public double du; + public double dv; + + public UVTranslation(double u, double v) { + du = u; + dv = v; + } + + @Override + public void apply(UV uv) { + uv.u += du; + uv.v += dv; + } + + @Override + public UVTransformation at(UV point) { + return this; + } + + @Override + public UVTransformation inverse() { + return new UVTranslation(-du, -dv); + } + + @Override + public UVTransformation merge(UVTransformation next) { + if (next instanceof UVTranslation) { + UVTranslation t = (UVTranslation) next; + return new UVTranslation(du + t.du, dv + t.dv); + } + + return null; + } + + @Override + public boolean isRedundant() { + return MathHelper.between(-1E-5, du, 1E-5) && MathHelper.between(-1E-5, dv, 1E-5); + } + + @Override + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "UVTranslation(" + new BigDecimal(du, cont) + ", " + new BigDecimal(dv, cont) + ")"; + } +} diff --git a/ccl/src/main/java/codechicken/lib/tool/LibDownloader.java b/ccl/src/main/java/codechicken/lib/tool/LibDownloader.java new file mode 100644 index 0000000..ca4a61f --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/tool/LibDownloader.java @@ -0,0 +1,100 @@ +package codechicken.lib.tool; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLConnection; +import java.nio.ByteBuffer; +import java.util.LinkedList; +import java.util.List; + +public class LibDownloader { + + private static String[] libs = new String[] { "org/ow2/asm/asm-debug-all/5.0.3/asm-debug-all-5.0.3.jar", + "com/google/guava/guava/14.0/guava-14.0.jar", "net/sf/jopt-simple/jopt-simple/4.5/jopt-simple-4.5.jar", + "org/apache/logging/log4j/log4j-core/2.0-beta9/log4j-core-2.0-beta9.jar", + "org/apache/logging/log4j/log4j-api/2.0-beta9/log4j-api-2.0-beta9.jar" }; + private static File libDir = new File("lib"); + + private static ByteBuffer downloadBuffer = ByteBuffer.allocateDirect(1 << 23); + + public static void load() { + if (!libDir.exists()) libDir.mkdir(); + if (!libDir.isDirectory()) throw new RuntimeException("/lib is not a directory"); + + List missing = checkExists(); + for (String lib : missing) download(lib); + addPaths(libs); + } + + private static void addPaths(String[] libs) { + try { + URLClassLoader cl = (URLClassLoader) ClassLoader.getSystemClassLoader(); + Method m_addURL = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class }); + m_addURL.setAccessible(true); + for (String lib : libs) m_addURL.invoke(cl, new File(libDir, fileName(lib)).toURI().toURL()); + } catch (Exception e) { + throw new RuntimeException("Failed to add libraries to classpath", e); + } + } + + private static void download(String lib) { + File libFile = new File(libDir, fileName(lib)); + try { + URL libDownload = new URL("http://repo1.maven.org/maven2/" + lib); + URLConnection connection = libDownload.openConnection(); + connection.setConnectTimeout(5000); + connection.setReadTimeout(5000); + connection.setRequestProperty("User-Agent", "CodeChickenLib Downloader"); + int sizeGuess = connection.getContentLength(); + download(connection.getInputStream(), sizeGuess, libFile); + } catch (Exception e) { + libFile.delete(); + throw new RuntimeException("A download error occured", e); + } + } + + private static void download(InputStream is, int sizeGuess, File target) throws Exception { + String name = target.getName(); + if (sizeGuess > downloadBuffer.capacity()) + throw new Exception(String.format("The file %s is too large to be downloaded", name)); + + downloadBuffer.clear(); + + int bytesRead, fullLength = 0; + + System.out.format("Downloading lib %s", name); + byte[] smallBuffer = new byte[1024]; + while ((bytesRead = is.read(smallBuffer)) >= 0) { + downloadBuffer.put(smallBuffer, 0, bytesRead); + fullLength += bytesRead; + System.out.format("\rDownloading lib %s %d%%", name, (int) (fullLength * 100 / sizeGuess)); + } + System.out.format("\rDownloaded lib %s \n", name); + is.close(); + downloadBuffer.limit(fullLength); + + if (!target.exists()) target.createNewFile(); + + downloadBuffer.position(0); + FileOutputStream fos = new FileOutputStream(target); + fos.getChannel().write(downloadBuffer); + fos.close(); + } + + private static String fileName(String lib) { + return lib.replaceAll(".+/", ""); + } + + private static List checkExists() { + LinkedList list = new LinkedList(); + for (String lib : libs) { + File file = new File(libDir, fileName(lib)); + if (!file.exists()) list.add(lib); + } + return list; + } +} diff --git a/ccl/src/main/java/codechicken/lib/tool/MCStripTransformer.java b/ccl/src/main/java/codechicken/lib/tool/MCStripTransformer.java new file mode 100644 index 0000000..5c8e47b --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/tool/MCStripTransformer.java @@ -0,0 +1,45 @@ +package codechicken.lib.tool; + +import java.util.Iterator; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.commons.Remapper; +import org.objectweb.asm.commons.RemappingMethodAdapter; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; + +import codechicken.lib.asm.ASMHelper; + +public class MCStripTransformer { + + public static class ReferenceDetector extends Remapper { + + boolean found = false; + + @Override + public String map(String typeName) { + if (typeName.startsWith("net/minecraft") || !typeName.contains("/")) found = true; + return typeName; + } + } + + public static byte[] transform(byte[] bytes) { + ClassNode cnode = ASMHelper.createClassNode(bytes, ClassReader.EXPAND_FRAMES); + + boolean changed = false; + Iterator it = cnode.methods.iterator(); + while (it.hasNext()) { + MethodNode mnode = it.next(); + ReferenceDetector r = new ReferenceDetector(); + mnode.accept(new RemappingMethodAdapter(mnode.access, mnode.desc, new MethodVisitor(Opcodes.ASM4) {}, r)); + if (r.found) { + it.remove(); + changed = true; + } + } + if (changed) bytes = ASMHelper.createBytes(cnode, 0); + return bytes; + } +} diff --git a/ccl/src/main/java/codechicken/lib/tool/Main.java b/ccl/src/main/java/codechicken/lib/tool/Main.java new file mode 100644 index 0000000..039121c --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/tool/Main.java @@ -0,0 +1,14 @@ +package codechicken.lib.tool; + +public class Main { + + public static void main(String[] args) { + LibDownloader.load(); + try { + Class c_toolMain = new StripClassLoader().loadClass("codechicken.lib.tool.ToolMain"); + c_toolMain.getDeclaredMethod("main", String[].class).invoke(null, new Object[] { args }); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} diff --git a/ccl/src/main/java/codechicken/lib/tool/StripClassLoader.java b/ccl/src/main/java/codechicken/lib/tool/StripClassLoader.java new file mode 100644 index 0000000..92e334b --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/tool/StripClassLoader.java @@ -0,0 +1,47 @@ +package codechicken.lib.tool; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; + +public class StripClassLoader extends URLClassLoader { + + public StripClassLoader() { + super(new URL[0], StripClassLoader.class.getClassLoader()); + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + if (!name.startsWith("codechicken.lib")) return super.loadClass(name); + + try { + String resName = name.replace('.', '/') + ".class"; + InputStream res = getResourceAsStream(resName); + if (res == null) throw new ClassNotFoundException("Could not find resource: " + resName); + byte[] bytes = readFully(res); + bytes = transform(bytes); + return defineClass(name, bytes, 0, bytes.length); + + } catch (IOException e) { + throw new ClassNotFoundException(name, e); + } + } + + public static byte[] readFully(InputStream is) throws IOException { + ByteArrayOutputStream buffer = new ByteArrayOutputStream(); + + int read; + byte[] data = new byte[16384]; + while ((read = is.read(data, 0, data.length)) > 0) buffer.write(data, 0, read); + + return buffer.toByteArray(); + } + + private byte[] transform(byte[] bytes) { + + bytes = MCStripTransformer.transform(bytes); + return bytes; + } +} diff --git a/ccl/src/main/java/codechicken/lib/tool/ToolMain.java b/ccl/src/main/java/codechicken/lib/tool/ToolMain.java new file mode 100644 index 0000000..ff51b7b --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/tool/ToolMain.java @@ -0,0 +1,42 @@ +package codechicken.lib.tool; + +import codechicken.lib.tool.module.ModuleQBConverter; + +public class ToolMain { + + public static interface Module { + + public void main(String[] args); + + public String name(); + + public void printHelp(); + } + + public static Module[] modules = new Module[] { new ModuleQBConverter() }; + + private static void printHelp() { + System.out.println("Usage: [module] [args]"); + System.out.println(" Modules: "); + for (Module m : modules) System.out.println(" - " + m.name()); + System.out.println("-h [module] for module help"); + } + + public static void main(String[] args) { + if (args.length > 0) { + for (Module m : modules) if (args[0].equals(m.name())) { + String[] args2 = new String[args.length - 1]; + System.arraycopy(args, 1, args2, 0, args2.length); + m.main(args2); + return; + } + if (args[0].equals("-h") && args.length >= 2) { + for (Module m : modules) if (args[1].equals(m.name())) { + m.printHelp(); + return; + } + } + } + printHelp(); + } +} diff --git a/ccl/src/main/java/codechicken/lib/tool/module/JOptModule.java b/ccl/src/main/java/codechicken/lib/tool/module/JOptModule.java new file mode 100644 index 0000000..2c9adee --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/tool/module/JOptModule.java @@ -0,0 +1,42 @@ +package codechicken.lib.tool.module; + +import java.io.IOException; + +import codechicken.lib.tool.ToolMain; +import joptsimple.OptionException; +import joptsimple.OptionParser; +import joptsimple.OptionSet; + +public abstract class JOptModule implements ToolMain.Module { + + OptionParser parser = new OptionParser(); + + @Override + public void main(String[] args) { + OptionSet options; + try { + options = parser.parse(args); + } catch (OptionException ex) { + System.err.println(ex.getLocalizedMessage()); + System.exit(-1); + return; + } + + try { + main(parser, options); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + protected abstract void main(OptionParser parser, OptionSet options); + + @Override + public void printHelp() { + try { + parser.printHelpOn(System.out); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/ccl/src/main/java/codechicken/lib/tool/module/ModuleQBConverter.java b/ccl/src/main/java/codechicken/lib/tool/module/ModuleQBConverter.java new file mode 100644 index 0000000..f9d9bda --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/tool/module/ModuleQBConverter.java @@ -0,0 +1,65 @@ +package codechicken.lib.tool.module; + +import static java.util.Arrays.asList; + +import java.io.File; + +import codechicken.lib.render.QBImporter; +import joptsimple.OptionParser; +import joptsimple.OptionSet; + +public class ModuleQBConverter extends JOptModule { + + public ModuleQBConverter() { + parser.acceptsAll(asList("?", "h", "help"), "Show the help"); + parser.acceptsAll(asList("i", "input"), "comma separated list of paths to models (.qb or directories)") + .withRequiredArg().ofType(File.class).withValuesSeparatedBy(',').required(); + parser.acceptsAll(asList("o", "out"), "Output Directory").withRequiredArg().ofType(File.class); + parser.acceptsAll( + asList("o2", "textureplanes"), + "2nd level optimisation. Merges coplanar polygons. Increases texture size"); + parser.acceptsAll(asList("s", "squaretextures"), "Produce square textures"); + parser.acceptsAll(asList("t", "mergetextures"), "Use the same texture for all models"); + parser.acceptsAll(asList("r", "scalemc"), "Resize model to mc standard (shrink by factor of 16)"); + } + + protected void main(OptionParser parser, OptionSet options) { + int flags = 0; + if (options.has("o2")) flags |= QBImporter.TEXTUREPLANES; + if (options.has("s")) flags |= QBImporter.SQUARETEXTURE; + if (options.has("t")) flags |= QBImporter.MERGETEXTURES; + if (options.has("r")) flags |= QBImporter.SCALEMC; + + File[] input = options.valuesOf("input").toArray(new File[0]); + File[] outDir = new File[input.length]; + if (options.has("out")) { + File output = (File) options.valueOf("out"); + if (output.isFile()) throw new RuntimeException("Output Path is not a directory"); + if (!output.exists()) output.mkdirs(); + + for (int i = 0; i < input.length; i++) outDir[i] = output; + } else { + for (int i = 0; i < input.length; i++) + outDir[i] = input[i].isDirectory() ? input[i] : input[i].getParentFile(); + } + + for (int i = 0; i < input.length; i++) { + File file = input[i]; + if (file.isDirectory()) { + for (File file2 : file.listFiles()) + if (file2.getName().endsWith(".qb")) convert(file2, outDir[i], flags); + } else convert(file, outDir[i], flags); + } + } + + private void convert(File in, File outDir, int flags) { + System.out.println("Converting: " + in.getName()); + QBImporter.RasterisedModel m = QBImporter.loadQB(in).toRasterisedModel(flags); + m.export(new File(outDir, in.getName().replace(".qb", ".obj")), outDir); + } + + @Override + public String name() { + return "QBConverter"; + } +} diff --git a/ccl/src/main/java/codechicken/lib/util/Copyable.java b/ccl/src/main/java/codechicken/lib/util/Copyable.java new file mode 100644 index 0000000..ae3de9a --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/util/Copyable.java @@ -0,0 +1,6 @@ +package codechicken.lib.util; + +public interface Copyable { + + public T copy(); +} diff --git a/ccl/src/main/java/codechicken/lib/util/LangProxy.java b/ccl/src/main/java/codechicken/lib/util/LangProxy.java new file mode 100644 index 0000000..0fbebc8 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/util/LangProxy.java @@ -0,0 +1,20 @@ +package codechicken.lib.util; + +import net.minecraft.util.StatCollector; + +public class LangProxy { + + public final String namespace; + + public LangProxy(String namespace) { + this.namespace = namespace + "."; + } + + public String translate(String key) { + return StatCollector.translateToLocal(namespace + key); + } + + public String format(String key, Object... params) { + return StatCollector.translateToLocalFormatted(namespace + key, params); + } +} diff --git a/ccl/src/main/java/codechicken/lib/vec/AxisCycle.java b/ccl/src/main/java/codechicken/lib/vec/AxisCycle.java new file mode 100644 index 0000000..eafcb70 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/vec/AxisCycle.java @@ -0,0 +1,39 @@ +package codechicken.lib.vec; + +public class AxisCycle { + + public static Transformation[] cycles = new Transformation[] { new RedundantTransformation(), + new VariableTransformation(new Matrix4(0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1)) { + + @Override + public void apply(Vector3 vec) { + double d0 = vec.x; + double d1 = vec.y; + double d2 = vec.z; + vec.x = d2; + vec.y = d0; + vec.z = d1; + } + + @Override + public Transformation inverse() { + return cycles[2]; + } + }, new VariableTransformation(new Matrix4(0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1)) { + + @Override + public void apply(Vector3 vec) { + double d0 = vec.x; + double d1 = vec.y; + double d2 = vec.z; + vec.x = d1; + vec.y = d2; + vec.z = d0; + } + + @Override + public Transformation inverse() { + return cycles[1]; + } + } }; +} diff --git a/ccl/src/main/java/codechicken/lib/vec/BlockCoord.java b/ccl/src/main/java/codechicken/lib/vec/BlockCoord.java new file mode 100644 index 0000000..a631eff --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/vec/BlockCoord.java @@ -0,0 +1,206 @@ +package codechicken.lib.vec; + +import net.minecraft.tileentity.TileEntity; + +import codechicken.lib.math.MathHelper; +import codechicken.lib.util.Copyable; + +public class BlockCoord implements Comparable, Copyable { + + public int x; + public int y; + public int z; + + public BlockCoord(int x, int y, int z) { + this.x = x; + this.y = y; + this.z = z; + } + + public BlockCoord(Vector3 v) { + this(MathHelper.floor_double(v.x), MathHelper.floor_double(v.y), MathHelper.floor_double(v.z)); + } + + public BlockCoord(TileEntity tile) { + this(tile.xCoord, tile.yCoord, tile.zCoord); + } + + public BlockCoord(int[] ia) { + this(ia[0], ia[1], ia[2]); + } + + public BlockCoord() {} + + public static BlockCoord fromAxes(int[] ia) { + return new BlockCoord(ia[2], ia[0], ia[1]); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof BlockCoord)) return false; + BlockCoord o2 = (BlockCoord) obj; + return x == o2.x && y == o2.y && z == o2.z; + } + + @Override + public int hashCode() { + return (x ^ z) * 31 + y; + } + + public int compareTo(BlockCoord o) { + if (x != o.x) return x < o.x ? 1 : -1; + if (y != o.y) return y < o.y ? 1 : -1; + if (z != o.z) return z < o.z ? 1 : -1; + return 0; + } + + public Vector3 toVector3Centered() { + return new Vector3(x + 0.5, y + 0.5, z + 0.5); + } + + public BlockCoord multiply(int i) { + x *= i; + y *= i; + z *= i; + return this; + } + + public double mag() { + return Math.sqrt(x * x + y * y + z * z); + } + + public int mag2() { + return x * x + y * y + z * z; + } + + public boolean isZero() { + return x == 0 && y == 0 && z == 0; + } + + public boolean isAxial() { + return x == 0 ? (y == 0 || z == 0) : (y == 0 && z == 0); + } + + public BlockCoord add(BlockCoord coord2) { + x += coord2.x; + y += coord2.y; + z += coord2.z; + return this; + } + + public BlockCoord add(int i, int j, int k) { + x += i; + y += j; + z += k; + return this; + } + + public BlockCoord sub(BlockCoord coord2) { + x -= coord2.x; + y -= coord2.y; + z -= coord2.z; + return this; + } + + public BlockCoord sub(int i, int j, int k) { + x -= i; + y -= j; + z -= k; + return this; + } + + public BlockCoord offset(int side) { + return offset(side, 1); + } + + public BlockCoord offset(int side, int amount) { + BlockCoord offset = sideOffsets[side]; + x += offset.x * amount; + y += offset.y * amount; + z += offset.z * amount; + return this; + } + + public BlockCoord inset(int side) { + return inset(side, 1); + } + + public BlockCoord inset(int side, int amount) { + return offset(side, -amount); + } + + public int getSide(int side) { + switch (side) { + case 0: + case 1: + return y; + case 2: + case 3: + return z; + case 4: + case 5: + return x; + } + throw new IndexOutOfBoundsException("Switch Falloff"); + } + + public BlockCoord setSide(int s, int v) { + switch (s) { + case 0, 1 -> y = v; + case 2, 3 -> z = v; + case 4, 5 -> x = v; + default -> throw new IndexOutOfBoundsException("Switch Falloff"); + } + return this; + } + + public static final BlockCoord[] sideOffsets = new BlockCoord[] { new BlockCoord(0, -1, 0), new BlockCoord(0, 1, 0), + new BlockCoord(0, 0, -1), new BlockCoord(0, 0, 1), new BlockCoord(-1, 0, 0), new BlockCoord(1, 0, 0) }; + + public int[] intArray() { + return new int[] { x, y, z }; + } + + public BlockCoord copy() { + return new BlockCoord(x, y, z); + } + + public BlockCoord set(int i, int j, int k) { + x = i; + y = j; + z = k; + return this; + } + + public BlockCoord set(BlockCoord coord) { + return set(coord.x, coord.y, coord.z); + } + + public BlockCoord set(int[] ia) { + return set(ia[0], ia[1], ia[2]); + } + + public BlockCoord set(TileEntity tile) { + return set(tile.xCoord, tile.yCoord, tile.zCoord); + } + + public int toSide() { + if (!isAxial()) return -1; + if (y < 0) return 0; + if (y > 0) return 1; + if (z < 0) return 2; + if (z > 0) return 3; + if (x < 0) return 4; + if (x > 0) return 5; + + return -1; + } + + public int absSum() { + return (x < 0 ? -x : x) + (y < 0 ? -y : y) + (z < 0 ? -z : z); + } + + public String toString() { + return "(" + x + ", " + y + ", " + z + ")"; + } +} diff --git a/ccl/src/main/java/codechicken/lib/vec/Cuboid6.java b/ccl/src/main/java/codechicken/lib/vec/Cuboid6.java new file mode 100644 index 0000000..2c0e572 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/vec/Cuboid6.java @@ -0,0 +1,212 @@ +package codechicken.lib.vec; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import net.minecraft.block.Block; +import net.minecraft.util.AxisAlignedBB; + +import codechicken.lib.util.Copyable; + +public class Cuboid6 implements Copyable { + + public static Cuboid6 full = new Cuboid6(0, 0, 0, 1, 1, 1); + + public Vector3 min; + public Vector3 max; + + public Cuboid6(Vector3 min, Vector3 max) { + this.min = min; + this.max = max; + } + + public Cuboid6(AxisAlignedBB aabb) { + min = new Vector3(aabb.minX, aabb.minY, aabb.minZ); + max = new Vector3(aabb.maxX, aabb.maxY, aabb.maxZ); + } + + public Cuboid6(Cuboid6 cuboid) { + min = cuboid.min.copy(); + max = cuboid.max.copy(); + } + + public Cuboid6(double minx, double miny, double minz, double maxx, double maxy, double maxz) { + min = new Vector3(minx, miny, minz); + max = new Vector3(maxx, maxy, maxz); + } + + public AxisAlignedBB toAABB() { + return AxisAlignedBB.getBoundingBox(min.x, min.y, min.z, max.x, max.y, max.z); + } + + public Cuboid6 copy() { + return new Cuboid6(this); + } + + public Cuboid6 set(Cuboid6 c) { + return set(c.min, c.max); + } + + public Cuboid6 set(Vector3 min, Vector3 max) { + this.min.set(min); + this.max.set(max); + return this; + } + + public Cuboid6 set(double minx, double miny, double minz, double maxx, double maxy, double maxz) { + min.set(minx, miny, minz); + max.set(maxx, maxy, maxz); + return this; + } + + public Cuboid6 add(Vector3 vec) { + min.add(vec); + max.add(vec); + return this; + } + + public Cuboid6 sub(Vector3 vec) { + min.subtract(vec); + max.subtract(vec); + return this; + } + + public Cuboid6 expand(double d) { + return expand(new Vector3(d, d, d)); + } + + public Cuboid6 expand(Vector3 vec) { + min.sub(vec); + max.add(vec); + return this; + } + + public void setBlockBounds(Block block) { + block.setBlockBounds((float) min.x, (float) min.y, (float) min.z, (float) max.x, (float) max.y, (float) max.z); + } + + public boolean intersects(Cuboid6 b) { + return max.x - 1E-5 > b.min.x && b.max.x - 1E-5 > min.x + && max.y - 1E-5 > b.min.y + && b.max.y - 1E-5 > min.y + && max.z - 1E-5 > b.min.z + && b.max.z - 1E-5 > min.z; + } + + public Cuboid6 offset(Cuboid6 o) { + min.add(o.min); + max.add(o.max); + return this; + } + + public Vector3 center() { + return min.copy().add(max).multiply(0.5); + } + + public static boolean intersects(Cuboid6 a, Cuboid6 b) { + return a != null && b != null && a.intersects(b); + } + + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "Cuboid: (" + new BigDecimal(min.x, cont) + + ", " + + new BigDecimal(min.y, cont) + + ", " + + new BigDecimal(min.z, cont) + + ") -> (" + + new BigDecimal(max.x, cont) + + ", " + + new BigDecimal(max.y, cont) + + ", " + + new BigDecimal(max.z, cont) + + ")"; + } + + public Cuboid6 enclose(Vector3 vec) { + if (min.x > vec.x) min.x = vec.x; + if (min.y > vec.y) min.y = vec.y; + if (min.z > vec.z) min.z = vec.z; + if (max.x < vec.x) max.x = vec.x; + if (max.y < vec.y) max.y = vec.y; + if (max.z < vec.z) max.z = vec.z; + return this; + } + + public Cuboid6 enclose(Cuboid6 c) { + if (min.x > c.min.x) min.x = c.min.x; + if (min.y > c.min.y) min.y = c.min.y; + if (min.z > c.min.z) min.z = c.min.z; + if (max.x < c.max.x) max.x = c.max.x; + if (max.y < c.max.y) max.y = c.max.y; + if (max.z < c.max.z) max.z = c.max.z; + return this; + } + + public Cuboid6 apply(Transformation t) { + t.apply(min); + t.apply(max); + double temp; + if (min.x > max.x) { + temp = min.x; + min.x = max.x; + max.x = temp; + } + if (min.y > max.y) { + temp = min.y; + min.y = max.y; + max.y = temp; + } + if (min.z > max.z) { + temp = min.z; + min.z = max.z; + max.z = temp; + } + return this; + } + + public double getSide(int s) { + switch (s) { + case 0: + return min.y; + case 1: + return max.y; + case 2: + return min.z; + case 3: + return max.z; + case 4: + return min.x; + case 5: + return max.x; + } + throw new IndexOutOfBoundsException("Switch Falloff"); + } + + public Cuboid6 setSide(int s, double d) { + switch (s) { + case 0: + min.y = d; + break; + case 1: + max.y = d; + break; + case 2: + min.z = d; + break; + case 3: + max.z = d; + break; + case 4: + min.x = d; + break; + case 5: + max.x = d; + break; + default: + throw new IndexOutOfBoundsException("Switch Falloff"); + } + return this; + } +} diff --git a/ccl/src/main/java/codechicken/lib/vec/CuboidCoord.java b/ccl/src/main/java/codechicken/lib/vec/CuboidCoord.java new file mode 100644 index 0000000..29448fc --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/vec/CuboidCoord.java @@ -0,0 +1,233 @@ +package codechicken.lib.vec; + +import java.util.Iterator; + +import net.minecraft.util.AxisAlignedBB; + +import codechicken.lib.util.Copyable; + +public class CuboidCoord implements Iterable, Copyable { + + public BlockCoord min; + public BlockCoord max; + + public CuboidCoord() { + min = new BlockCoord(); + max = new BlockCoord(); + } + + public CuboidCoord(BlockCoord min, BlockCoord max) { + this.min = min; + this.max = max; + } + + public CuboidCoord(BlockCoord coord) { + this(coord, coord.copy()); + } + + public CuboidCoord(int[] ia) { + this(ia[0], ia[1], ia[2], ia[3], ia[4], ia[5]); + } + + public CuboidCoord(int x1, int y1, int z1, int x2, int y2, int z2) { + this(new BlockCoord(x1, y1, z1), new BlockCoord(x2, y2, z2)); + } + + public CuboidCoord expand(int amount) { + return expand(amount, amount, amount); + } + + public CuboidCoord expand(int x, int y, int z) { + max.add(x, y, z); + min.sub(x, y, z); + return this; + } + + public CuboidCoord expand(int side, int amount) { + if (side % 2 == 0) // negative side + min = min.offset(side, amount); + else max = max.offset(side, amount); + return this; + } + + public CuboidCoord offset(BlockCoord b) { + min.add(b); + max.add(b); + return this; + } + + public CuboidCoord offset(int x, int y, int z) { + min.add(x, y, z); + max.add(x, y, z); + return this; + } + + public int size(int s) { + switch (s) { + case 0: + case 1: + return max.y - min.y + 1; + case 2: + case 3: + return max.z - min.z + 1; + case 4: + case 5: + return max.x - min.x + 1; + default: + return 0; + } + } + + public int getSide(int s) { + switch (s) { + case 0: + return min.y; + case 1: + return max.y; + case 2: + return min.z; + case 3: + return max.z; + case 4: + return min.x; + case 5: + return max.x; + } + throw new IndexOutOfBoundsException("Switch Falloff"); + } + + public CuboidCoord setSide(int s, int v) { + switch (s) { + case 0: + min.y = v; + break; + case 1: + max.y = v; + break; + case 2: + min.z = v; + break; + case 3: + max.z = v; + break; + case 4: + min.x = v; + break; + case 5: + max.x = v; + break; + default: + throw new IndexOutOfBoundsException("Switch Falloff"); + } + return this; + } + + public int getVolume() { + return (max.x - min.x + 1) * (max.y - min.y + 1) * (max.z - min.z + 1); + } + + public Vector3 getCenterVec() { + return new Vector3( + min.x + (max.x - min.x + 1) / 2D, + min.y + (max.y - min.y + 1) / 2D, + min.z + (max.z - min.z + 1) / 2D); + } + + public BlockCoord getCenter(BlockCoord store) { + store.set(min.x + (max.x - min.x) / 2, min.y + (max.y - min.y) / 2, min.z + (max.z - min.z) / 2); + return store; + } + + public boolean contains(BlockCoord coord) { + return contains(coord.x, coord.y, coord.z); + } + + public boolean contains(int x, int y, int z) { + return x >= min.x && x <= max.x && y >= min.y && y <= max.y && z >= min.z && z <= max.z; + } + + public int[] intArray() { + return new int[] { min.x, min.y, min.z, max.x, max.y, max.z }; + } + + public CuboidCoord copy() { + return new CuboidCoord(min.copy(), max.copy()); + } + + public Cuboid6 bounds() { + return new Cuboid6(min.x, min.y, min.z, max.x + 1, max.y + 1, max.z + 1); + } + + public AxisAlignedBB toAABB() { + return bounds().toAABB(); + } + + public CuboidCoord set(CuboidCoord c) { + return set(c.min, c.max); + } + + public CuboidCoord set(BlockCoord min, BlockCoord max) { + this.min.set(min); + this.max.set(max); + return this; + } + + public CuboidCoord set(int x1, int y1, int z1, int x2, int y2, int z2) { + min.set(x1, y1, z1); + max.set(x2, y2, z2); + return this; + } + + public CuboidCoord set(BlockCoord coord) { + return set(coord, coord); + } + + public CuboidCoord set(int[] ia) { + return set(ia[0], ia[1], ia[2], ia[3], ia[4], ia[5]); + } + + public CuboidCoord include(BlockCoord coord) { + return include(coord.x, coord.y, coord.z); + } + + public CuboidCoord include(int x, int y, int z) { + if (x < min.x) min.x = x; + else if (x > max.x) max.x = x; + if (y < min.y) min.y = y; + else if (y > max.y) max.y = y; + if (z < min.z) min.z = z; + else if (z > max.z) max.z = z; + return this; + } + + public Iterator iterator() { + return new Iterator() { + + BlockCoord b = null; + + public boolean hasNext() { + return b == null || !b.equals(max); + } + + public BlockCoord next() { + if (b == null) b = min.copy(); + else { + if (b.z != max.z) b.z++; + else { + b.z = min.z; + if (b.y != max.y) b.y++; + else { + b.y = min.y; + b.x++; + } + } + } + return b.copy(); + } + + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } +} diff --git a/ccl/src/main/java/codechicken/lib/vec/ITransformation.java b/ccl/src/main/java/codechicken/lib/vec/ITransformation.java new file mode 100644 index 0000000..8df95a9 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/vec/ITransformation.java @@ -0,0 +1,52 @@ +package codechicken.lib.vec; + +/** + * Abstract supertype for any VectorN transformation + * + * @param The vector type + * @param The transformation type + */ +public abstract class ITransformation { + + /** + * Applies this transformation to vec + */ + public abstract void apply(Vector vec); + + /** + * @param point The point to apply this transformation around + * @return Wraps this transformation in a translation to point and then back from point + */ + public abstract Transformation at(Vector point); + + /** + * Creates a TransformationList composed of this transformation followed by t If this is a TransformationList, the + * transformation will be appended and this returned + */ + public abstract Transformation with(Transformation t); + + /** + * Returns a simplified transformation that performs this, followed by next. If such a transformation does not + * exist, returns null + */ + public Transformation merge(Transformation next) { + return null; + } + + /** + * Returns true if this transformation is redundant, eg. Scale(1, 1, 1), Translation(0, 0, 0) or Rotation(0, a, b, + * c) + */ + public boolean isRedundant() { + return false; + } + + public abstract Transformation inverse(); + + /** + * Scala ++ operator + */ + public Transformation $plus$plus(Transformation t) { + return with(t); + } +} diff --git a/ccl/src/main/java/codechicken/lib/vec/IrreversibleTransformationException.java b/ccl/src/main/java/codechicken/lib/vec/IrreversibleTransformationException.java new file mode 100644 index 0000000..ec71e40 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/vec/IrreversibleTransformationException.java @@ -0,0 +1,16 @@ +package codechicken.lib.vec; + +@SuppressWarnings("serial") +public class IrreversibleTransformationException extends RuntimeException { + + public ITransformation t; + + public IrreversibleTransformationException(ITransformation t) { + this.t = t; + } + + @Override + public String getMessage() { + return "The following transformation is irreversible:\n" + t; + } +} diff --git a/ccl/src/main/java/codechicken/lib/vec/Line3.java b/ccl/src/main/java/codechicken/lib/vec/Line3.java new file mode 100644 index 0000000..6b9ae92 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/vec/Line3.java @@ -0,0 +1,46 @@ +package codechicken.lib.vec; + +public class Line3 { + + public static final double tol = 0.0001D; + + public Vector3 pt1; + public Vector3 pt2; + + public Line3(Vector3 pt1, Vector3 pt2) { + this.pt1 = pt1; + this.pt2 = pt2; + } + + public Line3() { + this(new Vector3(), new Vector3()); + } + + public static boolean intersection2D(Line3 line1, Line3 line2, Vector3 store) { + // calculate differences + double xD1 = line1.pt2.x - line1.pt1.x; + double zD1 = line1.pt2.z - line1.pt1.z; + double xD2 = line2.pt2.x - line2.pt1.x; + double zD2 = line2.pt2.z - line2.pt1.z; + + double xD3 = line1.pt1.x - line2.pt1.x; + double zD3 = line1.pt1.z - line2.pt1.z; + + double div = zD2 * xD1 - xD2 * zD1; + if (div == 0) // lines are parallel + return false; + double ua = (xD2 * zD3 - zD2 * xD3) / div; + store.set(line1.pt1.x + ua * xD1, 0, line1.pt1.z + ua * zD1); + + if (store.x >= Math.min(line1.pt1.x, line1.pt2.x) - tol && store.x >= Math.min(line2.pt1.x, line2.pt2.x) - tol + && store.z >= Math.min(line1.pt1.z, line1.pt2.z) - tol + && store.z >= Math.min(line2.pt1.z, line2.pt2.z) - tol + && store.x <= Math.max(line1.pt1.x, line1.pt2.x) + tol + && store.x <= Math.max(line2.pt1.x, line2.pt2.x) + tol + && store.z <= Math.max(line1.pt1.z, line1.pt2.z) + tol + && store.z <= Math.max(line2.pt1.z, line2.pt2.z) + tol) + return true; + + return false; + } +} diff --git a/ccl/src/main/java/codechicken/lib/vec/Matrix4.java b/ccl/src/main/java/codechicken/lib/vec/Matrix4.java new file mode 100644 index 0000000..0d51d52 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/vec/Matrix4.java @@ -0,0 +1,361 @@ +package codechicken.lib.vec; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.DoubleBuffer; + +import org.lwjgl.opengl.GL11; + +import codechicken.lib.util.Copyable; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class Matrix4 extends Transformation implements Copyable { + + private static DoubleBuffer glBuf = ByteBuffer.allocateDirect(16 * 8).order(ByteOrder.nativeOrder()) + .asDoubleBuffer(); + + // m + public double m00, m01, m02, m03, m10, m11, m12, m13, m20, m21, m22, m23, m30, m31, m32, m33; + + public Matrix4() { + m00 = m11 = m22 = m33 = 1; + } + + public Matrix4(double d00, double d01, double d02, double d03, double d10, double d11, double d12, double d13, + double d20, double d21, double d22, double d23, double d30, double d31, double d32, double d33) { + m00 = d00; + m01 = d01; + m02 = d02; + m03 = d03; + m10 = d10; + m11 = d11; + m12 = d12; + m13 = d13; + m20 = d20; + m21 = d21; + m22 = d22; + m23 = d23; + m30 = d30; + m31 = d31; + m32 = d32; + m33 = d33; + } + + public Matrix4(Matrix4 mat) { + set(mat); + } + + public Matrix4 setIdentity() { + m00 = m11 = m22 = m33 = 1; + m01 = m02 = m03 = m10 = m12 = m13 = m20 = m21 = m23 = m30 = m31 = m32 = 0; + + return this; + } + + public Matrix4 translate(Vector3 vec) { + m03 += m00 * vec.x + m01 * vec.y + m02 * vec.z; + m13 += m10 * vec.x + m11 * vec.y + m12 * vec.z; + m23 += m20 * vec.x + m21 * vec.y + m22 * vec.z; + m33 += m30 * vec.x + m31 * vec.y + m32 * vec.z; + + return this; + } + + public Matrix4 scale(Vector3 vec) { + m00 *= vec.x; + m10 *= vec.x; + m20 *= vec.x; + m30 *= vec.x; + m01 *= vec.y; + m11 *= vec.y; + m21 *= vec.y; + m31 *= vec.y; + m02 *= vec.z; + m12 *= vec.z; + m22 *= vec.z; + m32 *= vec.z; + + return this; + } + + public Matrix4 rotate(double angle, Vector3 axis) { + double c = Math.cos(angle); + double s = Math.sin(angle); + double mc = 1.0f - c; + double xy = axis.x * axis.y; + double yz = axis.y * axis.z; + double xz = axis.x * axis.z; + double xs = axis.x * s; + double ys = axis.y * s; + double zs = axis.z * s; + + double f00 = axis.x * axis.x * mc + c; + double f10 = xy * mc + zs; + double f20 = xz * mc - ys; + + double f01 = xy * mc - zs; + double f11 = axis.y * axis.y * mc + c; + double f21 = yz * mc + xs; + + double f02 = xz * mc + ys; + double f12 = yz * mc - xs; + double f22 = axis.z * axis.z * mc + c; + + double t00 = m00 * f00 + m01 * f10 + m02 * f20; + double t10 = m10 * f00 + m11 * f10 + m12 * f20; + double t20 = m20 * f00 + m21 * f10 + m22 * f20; + double t30 = m30 * f00 + m31 * f10 + m32 * f20; + double t01 = m00 * f01 + m01 * f11 + m02 * f21; + double t11 = m10 * f01 + m11 * f11 + m12 * f21; + double t21 = m20 * f01 + m21 * f11 + m22 * f21; + double t31 = m30 * f01 + m31 * f11 + m32 * f21; + m02 = m00 * f02 + m01 * f12 + m02 * f22; + m12 = m10 * f02 + m11 * f12 + m12 * f22; + m22 = m20 * f02 + m21 * f12 + m22 * f22; + m32 = m30 * f02 + m31 * f12 + m32 * f22; + m00 = t00; + m10 = t10; + m20 = t20; + m30 = t30; + m01 = t01; + m11 = t11; + m21 = t21; + m31 = t31; + + return this; + } + + public Matrix4 rotate(Rotation rotation) { + rotation.apply(this); + return this; + } + + public Matrix4 leftMultiply(Matrix4 mat) { + double n00 = m00 * mat.m00 + m10 * mat.m01 + m20 * mat.m02 + m30 * mat.m03; + double n01 = m01 * mat.m00 + m11 * mat.m01 + m21 * mat.m02 + m31 * mat.m03; + double n02 = m02 * mat.m00 + m12 * mat.m01 + m22 * mat.m02 + m32 * mat.m03; + double n03 = m03 * mat.m00 + m13 * mat.m01 + m23 * mat.m02 + m33 * mat.m03; + double n10 = m00 * mat.m10 + m10 * mat.m11 + m20 * mat.m12 + m30 * mat.m13; + double n11 = m01 * mat.m10 + m11 * mat.m11 + m21 * mat.m12 + m31 * mat.m13; + double n12 = m02 * mat.m10 + m12 * mat.m11 + m22 * mat.m12 + m32 * mat.m13; + double n13 = m03 * mat.m10 + m13 * mat.m11 + m23 * mat.m12 + m33 * mat.m13; + double n20 = m00 * mat.m20 + m10 * mat.m21 + m20 * mat.m22 + m30 * mat.m23; + double n21 = m01 * mat.m20 + m11 * mat.m21 + m21 * mat.m22 + m31 * mat.m23; + double n22 = m02 * mat.m20 + m12 * mat.m21 + m22 * mat.m22 + m32 * mat.m23; + double n23 = m03 * mat.m20 + m13 * mat.m21 + m23 * mat.m22 + m33 * mat.m23; + double n30 = m00 * mat.m30 + m10 * mat.m31 + m20 * mat.m32 + m30 * mat.m33; + double n31 = m01 * mat.m30 + m11 * mat.m31 + m21 * mat.m32 + m31 * mat.m33; + double n32 = m02 * mat.m30 + m12 * mat.m31 + m22 * mat.m32 + m32 * mat.m33; + double n33 = m03 * mat.m30 + m13 * mat.m31 + m23 * mat.m32 + m33 * mat.m33; + + m00 = n00; + m01 = n01; + m02 = n02; + m03 = n03; + m10 = n10; + m11 = n11; + m12 = n12; + m13 = n13; + m20 = n20; + m21 = n21; + m22 = n22; + m23 = n23; + m30 = n30; + m31 = n31; + m32 = n32; + m33 = n33; + + return this; + } + + public Matrix4 multiply(Matrix4 mat) { + double n00 = m00 * mat.m00 + m01 * mat.m10 + m02 * mat.m20 + m03 * mat.m30; + double n01 = m00 * mat.m01 + m01 * mat.m11 + m02 * mat.m21 + m03 * mat.m31; + double n02 = m00 * mat.m02 + m01 * mat.m12 + m02 * mat.m22 + m03 * mat.m32; + double n03 = m00 * mat.m03 + m01 * mat.m13 + m02 * mat.m23 + m03 * mat.m33; + double n10 = m10 * mat.m00 + m11 * mat.m10 + m12 * mat.m20 + m13 * mat.m30; + double n11 = m10 * mat.m01 + m11 * mat.m11 + m12 * mat.m21 + m13 * mat.m31; + double n12 = m10 * mat.m02 + m11 * mat.m12 + m12 * mat.m22 + m13 * mat.m32; + double n13 = m10 * mat.m03 + m11 * mat.m13 + m12 * mat.m23 + m13 * mat.m33; + double n20 = m20 * mat.m00 + m21 * mat.m10 + m22 * mat.m20 + m23 * mat.m30; + double n21 = m20 * mat.m01 + m21 * mat.m11 + m22 * mat.m21 + m23 * mat.m31; + double n22 = m20 * mat.m02 + m21 * mat.m12 + m22 * mat.m22 + m23 * mat.m32; + double n23 = m20 * mat.m03 + m21 * mat.m13 + m22 * mat.m23 + m23 * mat.m33; + double n30 = m30 * mat.m00 + m31 * mat.m10 + m32 * mat.m20 + m33 * mat.m30; + double n31 = m30 * mat.m01 + m31 * mat.m11 + m32 * mat.m21 + m33 * mat.m31; + double n32 = m30 * mat.m02 + m31 * mat.m12 + m32 * mat.m22 + m33 * mat.m32; + double n33 = m30 * mat.m03 + m31 * mat.m13 + m32 * mat.m23 + m33 * mat.m33; + + m00 = n00; + m01 = n01; + m02 = n02; + m03 = n03; + m10 = n10; + m11 = n11; + m12 = n12; + m13 = n13; + m20 = n20; + m21 = n21; + m22 = n22; + m23 = n23; + m30 = n30; + m31 = n31; + m32 = n32; + m33 = n33; + + return this; + } + + public Matrix4 transpose() { + double n00 = m00; + double n10 = m01; + double n20 = m02; + double n30 = m03; + double n01 = m10; + double n11 = m11; + double n21 = m12; + double n31 = m13; + double n02 = m20; + double n12 = m21; + double n22 = m22; + double n32 = m23; + double n03 = m30; + double n13 = m31; + double n23 = m32; + double n33 = m33; + + m00 = n00; + m01 = n01; + m02 = n02; + m03 = n03; + m10 = n10; + m11 = n11; + m12 = n12; + m13 = n13; + m20 = n20; + m21 = n21; + m22 = n22; + m23 = n23; + m30 = n30; + m31 = n31; + m32 = n32; + m33 = n33; + + return this; + } + + public Matrix4 copy() { + return new Matrix4(this); + } + + public Matrix4 set(Matrix4 mat) { + m00 = mat.m00; + m01 = mat.m01; + m02 = mat.m02; + m03 = mat.m03; + m10 = mat.m10; + m11 = mat.m11; + m12 = mat.m12; + m13 = mat.m13; + m20 = mat.m20; + m21 = mat.m21; + m22 = mat.m22; + m23 = mat.m23; + m30 = mat.m30; + m31 = mat.m31; + m32 = mat.m32; + m33 = mat.m33; + + return this; + } + + @Override + public void apply(Matrix4 mat) { + mat.multiply(this); + } + + private void mult3x3(Vector3 vec) { + double x = m00 * vec.x + m01 * vec.y + m02 * vec.z; + double y = m10 * vec.x + m11 * vec.y + m12 * vec.z; + double z = m20 * vec.x + m21 * vec.y + m22 * vec.z; + + vec.x = x; + vec.y = y; + vec.z = z; + } + + @Override + public void apply(Vector3 vec) { + mult3x3(vec); + vec.add(m03, m13, m23); + } + + @Override + public void applyN(Vector3 vec) { + mult3x3(vec); + vec.normalize(); + } + + @Override + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "[" + new BigDecimal(m00, cont) + + "," + + new BigDecimal(m01, cont) + + "," + + new BigDecimal(m02, cont) + + "," + + new BigDecimal(m03, cont) + + "]\n" + + "[" + + new BigDecimal(m10, cont) + + "," + + new BigDecimal(m11, cont) + + "," + + new BigDecimal(m12, cont) + + "," + + new BigDecimal(m13, cont) + + "]\n" + + "[" + + new BigDecimal(m20, cont) + + "," + + new BigDecimal(m21, cont) + + "," + + new BigDecimal(m22, cont) + + "," + + new BigDecimal(m23, cont) + + "]\n" + + "[" + + new BigDecimal(m30, cont) + + "," + + new BigDecimal(m31, cont) + + "," + + new BigDecimal(m32, cont) + + "," + + new BigDecimal(m33, cont) + + "]"; + } + + public Matrix4 apply(Transformation t) { + t.apply(this); + return this; + } + + @Override + @SideOnly(Side.CLIENT) + public void glApply() { + glBuf.put(m00).put(m10).put(m20).put(m30).put(m01).put(m11).put(m21).put(m31).put(m02).put(m12).put(m22) + .put(m32).put(m03).put(m13).put(m23).put(m33); + glBuf.flip(); + GL11.glMultMatrix(glBuf); + } + + @Override + public Transformation inverse() { + throw new IrreversibleTransformationException(this); // Don't waste your cpu with matrix inverses + } +} diff --git a/ccl/src/main/java/codechicken/lib/vec/Quat.java b/ccl/src/main/java/codechicken/lib/vec/Quat.java new file mode 100644 index 0000000..da2ac5d --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/vec/Quat.java @@ -0,0 +1,146 @@ +package codechicken.lib.vec; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import codechicken.lib.math.MathHelper; +import codechicken.lib.util.Copyable; + +public class Quat implements Copyable { + + public double x; + public double y; + public double z; + public double s; + + public Quat() { + s = 1; + x = 0; + y = 0; + z = 0; + } + + public Quat(Quat quat) { + x = quat.x; + y = quat.y; + z = quat.z; + s = quat.s; + } + + public Quat(double d, double d1, double d2, double d3) { + x = d1; + y = d2; + z = d3; + s = d; + } + + public Quat set(Quat quat) { + x = quat.x; + y = quat.y; + z = quat.z; + s = quat.s; + + return this; + } + + public Quat set(double d, double d1, double d2, double d3) { + x = d1; + y = d2; + z = d3; + s = d; + + return this; + } + + public static Quat aroundAxis(double ax, double ay, double az, double angle) { + return new Quat().setAroundAxis(ax, ay, az, angle); + } + + public static Quat aroundAxis(Vector3 axis, double angle) { + return aroundAxis(axis.x, axis.y, axis.z, angle); + } + + public Quat setAroundAxis(double ax, double ay, double az, double angle) { + angle *= 0.5; + double d4 = MathHelper.sin(angle); + return set(MathHelper.cos(angle), ax * d4, ay * d4, az * d4); + } + + public Quat setAroundAxis(Vector3 axis, double angle) { + return setAroundAxis(axis.x, axis.y, axis.z, angle); + } + + public Quat multiply(Quat quat) { + double d = s * quat.s - x * quat.x - y * quat.y - z * quat.z; + double d1 = s * quat.x + x * quat.s - y * quat.z + z * quat.y; + double d2 = s * quat.y + x * quat.z + y * quat.s - z * quat.x; + double d3 = s * quat.z - x * quat.y + y * quat.x + z * quat.s; + s = d; + x = d1; + y = d2; + z = d3; + + return this; + } + + public Quat rightMultiply(Quat quat) { + double d = s * quat.s - x * quat.x - y * quat.y - z * quat.z; + double d1 = s * quat.x + x * quat.s + y * quat.z - z * quat.y; + double d2 = s * quat.y - x * quat.z + y * quat.s + z * quat.x; + double d3 = s * quat.z + x * quat.y - y * quat.x + z * quat.s; + s = d; + x = d1; + y = d2; + z = d3; + + return this; + } + + public double mag() { + return Math.sqrt(x * x + y * y + z * z + s * s); + } + + public Quat normalize() { + double d = mag(); + if (d != 0) { + d = 1 / d; + x *= d; + y *= d; + z *= d; + s *= d; + } + + return this; + } + + public Quat copy() { + return new Quat(this); + } + + public void rotate(Vector3 vec) { + double d = -x * vec.x - y * vec.y - z * vec.z; + double d1 = s * vec.x + y * vec.z - z * vec.y; + double d2 = s * vec.y - x * vec.z + z * vec.x; + double d3 = s * vec.z + x * vec.y - y * vec.x; + vec.x = d1 * s - d * x - d2 * z + d3 * y; + vec.y = d2 * s - d * y + d1 * z - d3 * x; + vec.z = d3 * s - d * z - d1 * y + d2 * x; + } + + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "Quat(" + new BigDecimal(s, cont) + + ", " + + new BigDecimal(x, cont) + + ", " + + new BigDecimal(y, cont) + + ", " + + new BigDecimal(z, cont) + + ")"; + } + + public Rotation rotation() { + return new Rotation(this); + } +} diff --git a/ccl/src/main/java/codechicken/lib/vec/Rectangle4i.java b/ccl/src/main/java/codechicken/lib/vec/Rectangle4i.java new file mode 100644 index 0000000..9dd6e81 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/vec/Rectangle4i.java @@ -0,0 +1,91 @@ +package codechicken.lib.vec; + +public class Rectangle4i { + + public int x; + public int y; + public int w; + public int h; + + public Rectangle4i() {} + + public Rectangle4i(int x, int y, int w, int h) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + } + + public int x1() { + return x; + } + + public int y1() { + return y; + } + + public int x2() { + return x + w - 1; + } + + public int y2() { + return y + h - 1; + } + + public void set(int x, int y, int w, int h) { + this.x = x; + this.y = y; + this.w = w; + this.h = h; + } + + public Rectangle4i offset(int dx, int dy) { + x += dx; + y += dy; + return this; + } + + @Deprecated + public Rectangle4i with(int px, int py) { + return include(px, py); + } + + public Rectangle4i include(int px, int py) { + if (px < x) expand(px - x, 0); + if (px >= x + w) expand(px - x - w + 1, 0); + if (py < y) expand(0, py - y); + if (py >= y + h) expand(0, py - y - h + 1); + return this; + } + + public Rectangle4i include(Rectangle4i r) { + include(r.x, r.y); + return include(r.x2(), r.y2()); + } + + public Rectangle4i expand(int px, int py) { + if (px > 0) w += px; + else { + x += px; + w -= px; + } + if (py > 0) h += py; + else { + y += py; + h -= py; + } + return this; + } + + public boolean contains(int px, int py) { + return x <= px && px < x + w && y <= py && py < y + h; + } + + public boolean intersects(Rectangle4i r) { + return r.x + r.w > x && r.x < x + w && r.y + r.h > y && r.y < y + h; + } + + public int area() { + return w * h; + } +} diff --git a/ccl/src/main/java/codechicken/lib/vec/RedundantTransformation.java b/ccl/src/main/java/codechicken/lib/vec/RedundantTransformation.java new file mode 100644 index 0000000..d58da7f --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/vec/RedundantTransformation.java @@ -0,0 +1,45 @@ +package codechicken.lib.vec; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class RedundantTransformation extends Transformation { + + @Override + public void apply(Vector3 vec) {} + + @Override + public void apply(Matrix4 mat) {} + + @Override + public void applyN(Vector3 normal) {} + + @Override + public Transformation at(Vector3 point) { + return this; + } + + @Override + @SideOnly(Side.CLIENT) + public void glApply() {} + + @Override + public Transformation inverse() { + return this; + } + + @Override + public Transformation merge(Transformation next) { + return next; + } + + @Override + public boolean isRedundant() { + return true; + } + + @Override + public String toString() { + return "Nothing()"; + } +} diff --git a/ccl/src/main/java/codechicken/lib/vec/Rotation.java b/ccl/src/main/java/codechicken/lib/vec/Rotation.java new file mode 100644 index 0000000..15c50e2 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/vec/Rotation.java @@ -0,0 +1,295 @@ +package codechicken.lib.vec; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import net.minecraft.entity.EntityLivingBase; +import net.minecraft.entity.player.EntityPlayer; + +import org.lwjgl.opengl.GL11; + +import codechicken.lib.math.MathHelper; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class Rotation extends Transformation { + + /** + * Clockwise pi/2 about y looking down + */ + public static Transformation[] quarterRotations = new Transformation[] { new RedundantTransformation(), + new VariableTransformation(new Matrix4(0, 0, -1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1)) { + + @Override + public void apply(Vector3 vec) { + double d1 = vec.x; + double d2 = vec.z; + vec.x = -d2; + vec.z = d1; + } + + @Override + public Transformation inverse() { + return quarterRotations[3]; + } + }, new VariableTransformation(new Matrix4(-1, 0, 0, 0, 0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1)) { + + @Override + public void apply(Vector3 vec) { + vec.x = -vec.x; + vec.z = -vec.z; + } + + @Override + public Transformation inverse() { + return this; + } + }, new VariableTransformation(new Matrix4(0, 0, 1, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 0, 1)) { + + @Override + public void apply(Vector3 vec) { + double d1 = vec.x; + double d2 = vec.z; + vec.x = d2; + vec.z = -d1; + } + + @Override + public Transformation inverse() { + return quarterRotations[1]; + } + } }; + + public static Transformation[] sideRotations = new Transformation[] { new RedundantTransformation(), + new VariableTransformation(new Matrix4(1, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 1)) { + + @Override + public void apply(Vector3 vec) { + vec.y = -vec.y; + vec.z = -vec.z; + } + + @Override + public Transformation inverse() { + return this; + } + }, new VariableTransformation(new Matrix4(1, 0, 0, 0, 0, 0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1)) { + + @Override + public void apply(Vector3 vec) { + double d1 = vec.y; + double d2 = vec.z; + vec.y = -d2; + vec.z = d1; + } + + @Override + public Transformation inverse() { + return sideRotations[3]; + } + }, new VariableTransformation(new Matrix4(1, 0, 0, 0, 0, 0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1)) { + + @Override + public void apply(Vector3 vec) { + double d1 = vec.y; + double d2 = vec.z; + vec.y = d2; + vec.z = -d1; + } + + @Override + public Transformation inverse() { + return sideRotations[2]; + } + }, new VariableTransformation(new Matrix4(0, 1, 0, 0, -1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)) { + + @Override + public void apply(Vector3 vec) { + double d0 = vec.x; + double d1 = vec.y; + vec.x = d1; + vec.y = -d0; + } + + @Override + public Transformation inverse() { + return sideRotations[5]; + } + }, new VariableTransformation(new Matrix4(0, -1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)) { + + @Override + public void apply(Vector3 vec) { + double d0 = vec.x; + double d1 = vec.y; + vec.x = -d1; + vec.y = d0; + } + + @Override + public Transformation inverse() { + return sideRotations[4]; + } + } }; + + public static Vector3[] axes = new Vector3[] { new Vector3(0, -1, 0), new Vector3(0, 1, 0), new Vector3(0, 0, -1), + new Vector3(0, 0, 1), new Vector3(-1, 0, 0), new Vector3(1, 0, 0) }; + + public static int[] sideRotMap = new int[] { 3, 4, 2, 5, 3, 5, 2, 4, 1, 5, 0, 4, 1, 4, 0, 5, 1, 2, 0, 3, 1, 3, 0, + 2 }; + + public static int[] rotSideMap = new int[] { -1, -1, 2, 0, 1, 3, -1, -1, 2, 0, 3, 1, 2, 0, -1, -1, 3, 1, 2, 0, -1, + -1, 1, 3, 2, 0, 1, 3, -1, -1, 2, 0, 3, 1, -1, -1 }; + + /** + * Rotate pi/2 * this offset for [side] about y axis before rotating to the side for the rotation indicies to line + * up + */ + public static int[] sideRotOffsets = new int[] { 0, 2, 2, 0, 1, 3 }; + + public static int rotateSide(int s, int r) { + return sideRotMap[s << 2 | r]; + } + + /** + * Reverse of rotateSide + */ + public static int rotationTo(int s1, int s2) { + if ((s1 & 6) == (s2 & 6)) throw new IllegalArgumentException("Faces " + s1 + " and " + s2 + " are opposites"); + return rotSideMap[s1 * 6 + s2]; + } + + /** + * @param player The placing player, used for obtaining the look vector + * @param side The side of the block being placed on + * @return The rotation for the face == side^1 + */ + public static int getSidedRotation(EntityPlayer player, int side) { + Vector3 look = new Vector3(player.getLook(1)); + double max = 0; + int maxr = 0; + for (int r = 0; r < 4; r++) { + Vector3 axis = Rotation.axes[rotateSide(side ^ 1, r)]; + double d = look.scalarProject(axis); + if (d > max) { + max = d; + maxr = r; + } + } + return maxr; + } + + /** + * @return The rotation quat for side 0 and rotation 0 to side s with rotation r + */ + public static Transformation sideOrientation(int s, int r) { + return quarterRotations[(r + sideRotOffsets[s]) % 4].with(sideRotations[s]); + } + + /** + * @param entity The placing entity, used for obtaining the look vector + * @return The side towards which the entity is most directly looking. + */ + public static int getSideFromLookAngle(EntityLivingBase entity) { + Vector3 look = new Vector3(entity.getLook(1)); + double max = 0; + int maxs = 0; + for (int s = 0; s < 6; s++) { + double d = look.scalarProject(axes[s]); + if (d > max) { + max = d; + maxs = s; + } + } + return maxs; + } + + public double angle; + public Vector3 axis; + + private Quat quat; + + public Rotation(double angle, Vector3 axis) { + this.angle = angle; + this.axis = axis; + } + + public Rotation(double angle, double x, double y, double z) { + this(angle, new Vector3(x, y, z)); + } + + public Rotation(Quat quat) { + this.quat = quat; + + angle = Math.acos(quat.s) * 2; + if (angle == 0) { + axis = new Vector3(0, 1, 0); + } else { + double sa = Math.sin(angle * 0.5); + axis = new Vector3(quat.x / sa, quat.y / sa, quat.z / sa); + } + } + + @Override + public void apply(Vector3 vec) { + if (quat == null) quat = Quat.aroundAxis(axis, angle); + + vec.rotate(quat); + } + + @Override + public void applyN(Vector3 normal) { + apply(normal); + } + + @Override + public void apply(Matrix4 mat) { + mat.rotate(angle, axis); + } + + public Quat toQuat() { + if (quat == null) quat = Quat.aroundAxis(axis, angle); + return quat; + } + + @Override + @SideOnly(Side.CLIENT) + public void glApply() { + GL11.glRotatef((float) (angle * MathHelper.todeg), (float) axis.x, (float) axis.y, (float) axis.z); + } + + @Override + public Transformation inverse() { + return new Rotation(-angle, axis); + } + + @Override + public Transformation merge(Transformation next) { + if (next instanceof Rotation) { + Rotation r = (Rotation) next; + if (r.axis.equalsT(axis)) return new Rotation(angle + r.angle, axis); + + return new Rotation(toQuat().copy().multiply(r.toQuat())); + } + + return null; + } + + @Override + public boolean isRedundant() { + return MathHelper.between(-1E-5, angle, 1E-5); + } + + @Override + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "Rotation(" + new BigDecimal(angle, cont) + + ", " + + new BigDecimal(axis.x, cont) + + ", " + + new BigDecimal(axis.y, cont) + + ", " + + new BigDecimal(axis.z, cont) + + ")"; + } +} diff --git a/ccl/src/main/java/codechicken/lib/vec/Scale.java b/ccl/src/main/java/codechicken/lib/vec/Scale.java new file mode 100644 index 0000000..19b2e8c --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/vec/Scale.java @@ -0,0 +1,74 @@ +package codechicken.lib.vec; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import org.lwjgl.opengl.GL11; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class Scale extends Transformation { + + public Vector3 factor; + + public Scale(Vector3 factor) { + this.factor = factor; + } + + public Scale(double factor) { + this(new Vector3(factor, factor, factor)); + } + + public Scale(double x, double y, double z) { + this(new Vector3(x, y, z)); + } + + @Override + public void apply(Vector3 vec) { + vec.multiply(factor); + } + + @Override + public void applyN(Vector3 normal) {} + + @Override + public void apply(Matrix4 mat) { + mat.scale(factor); + } + + @Override + @SideOnly(Side.CLIENT) + public void glApply() { + GL11.glScaled(factor.x, factor.y, factor.z); + } + + @Override + public Transformation inverse() { + return new Scale(1 / factor.x, 1 / factor.y, 1 / factor.z); + } + + @Override + public Transformation merge(Transformation next) { + if (next instanceof Scale) return new Scale(factor.copy().multiply(((Scale) next).factor)); + + return null; + } + + @Override + public boolean isRedundant() { + return factor.equalsT(Vector3.one); + } + + @Override + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "Scale(" + new BigDecimal(factor.x, cont) + + ", " + + new BigDecimal(factor.y, cont) + + ", " + + new BigDecimal(factor.z, cont) + + ")"; + } +} diff --git a/ccl/src/main/java/codechicken/lib/vec/SwapYZ.java b/ccl/src/main/java/codechicken/lib/vec/SwapYZ.java new file mode 100644 index 0000000..ad256dd --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/vec/SwapYZ.java @@ -0,0 +1,20 @@ +package codechicken.lib.vec; + +public class SwapYZ extends VariableTransformation { + + public SwapYZ() { + super(new Matrix4(1, 0, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1)); + } + + @Override + public void apply(Vector3 vec) { + double vz = vec.z; + vec.z = vec.y; + vec.y = vz; + } + + @Override + public Transformation inverse() { + return this; + } +} diff --git a/ccl/src/main/java/codechicken/lib/vec/Transformation.java b/ccl/src/main/java/codechicken/lib/vec/Transformation.java new file mode 100644 index 0000000..a7cca46 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/vec/Transformation.java @@ -0,0 +1,56 @@ +package codechicken.lib.vec; + +import codechicken.lib.render.CCRenderState; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +/** + * Abstract supertype for any 3D vector transformation + */ +public abstract class Transformation extends ITransformation + implements CCRenderState.IVertexOperation { + + public static final int operationIndex = CCRenderState.registerOperation(); + + /** + * Applies this transformation to a normal (doesn't translate) + * + * @param normal The normal to transform + */ + public abstract void applyN(Vector3 normal); + + /** + * Applies this transformation to a matrix as a multiplication on the right hand side. + * + * @param mat The matrix to combine this transformation with + */ + public abstract void apply(Matrix4 mat); + + public Transformation at(Vector3 point) { + return new TransformationList(new Translation(-point.x, -point.y, -point.z), this, point.translation()); + } + + public TransformationList with(Transformation t) { + return new TransformationList(this, t); + } + + @SideOnly(Side.CLIENT) + public abstract void glApply(); + + @Override + public boolean load(CCRenderState state) { + state.pipeline.addRequirement(CCRenderState.normalAttrib.operationID()); + return !isRedundant(); + } + + @Override + public void operate(CCRenderState state) { + apply(state.vert.vec); + if (CCRenderState.normalAttrib.active) applyN(state.normal); + } + + @Override + public int operationID() { + return operationIndex; + } +} diff --git a/ccl/src/main/java/codechicken/lib/vec/TransformationList.java b/ccl/src/main/java/codechicken/lib/vec/TransformationList.java new file mode 100644 index 0000000..0e5605e --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/vec/TransformationList.java @@ -0,0 +1,131 @@ +package codechicken.lib.vec; + +import java.util.ArrayList; +import java.util.Iterator; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class TransformationList extends Transformation { + + private ArrayList transformations = new ArrayList(); + private Matrix4 mat; + + public TransformationList(Transformation... transforms) { + for (Transformation t : transforms) + if (t instanceof TransformationList) transformations.addAll(((TransformationList) t).transformations); + else transformations.add(t); + + compact(); + } + + public Matrix4 compile() { + if (mat == null) { + mat = new Matrix4(); + for (int i = transformations.size() - 1; i >= 0; i--) transformations.get(i).apply(mat); + } + return mat; + } + + /** + * Returns a global space matrix as opposed to an object space matrix (reverse application order) + * + * @return + */ + public Matrix4 reverseCompile() { + Matrix4 mat = new Matrix4(); + for (Transformation t : transformations) t.apply(mat); + return mat; + } + + @Override + public void apply(Vector3 vec) { + if (mat != null) mat.apply(vec); + else for (int i = 0; i < transformations.size(); i++) transformations.get(i).apply(vec); + } + + @Override + public void applyN(Vector3 normal) { + if (mat != null) mat.applyN(normal); + else for (int i = 0; i < transformations.size(); i++) transformations.get(i).applyN(normal); + } + + @Override + public void apply(Matrix4 mat) { + mat.multiply(compile()); + } + + @Override + public TransformationList with(Transformation t) { + if (t.isRedundant()) return this; + + mat = null; // matrix invalid + if (t instanceof TransformationList) transformations.addAll(((TransformationList) t).transformations); + else transformations.add(t); + + compact(); + return this; + } + + public TransformationList prepend(Transformation t) { + if (t.isRedundant()) return this; + + mat = null; // matrix invalid + if (t instanceof TransformationList) transformations.addAll(0, ((TransformationList) t).transformations); + else transformations.add(0, t); + + compact(); + return this; + } + + private void compact() { + ArrayList newList = new ArrayList(transformations.size()); + Iterator iterator = transformations.iterator(); + Transformation prev = null; + while (iterator.hasNext()) { + Transformation t = iterator.next(); + if (t.isRedundant()) continue; + + if (prev != null) { + Transformation m = prev.merge(t); + if (m == null) newList.add(prev); + else if (m.isRedundant()) t = null; + else t = m; + } + prev = t; + } + if (prev != null) newList.add(prev); + + if (newList.size() < transformations.size()) { + transformations = newList; + mat = null; + } + + if (transformations.size() > 3 && mat == null) compile(); + } + + @Override + public boolean isRedundant() { + return transformations.size() == 0; + } + + @Override + @SideOnly(Side.CLIENT) + public void glApply() { + for (int i = transformations.size() - 1; i >= 0; i--) transformations.get(i).glApply(); + } + + @Override + public Transformation inverse() { + TransformationList rev = new TransformationList(); + for (int i = transformations.size() - 1; i >= 0; i--) rev.with(transformations.get(i).inverse()); + return rev; + } + + @Override + public String toString() { + String s = ""; + for (Transformation t : transformations) s += "\n" + t.toString(); + return s.trim(); + } +} diff --git a/ccl/src/main/java/codechicken/lib/vec/Translation.java b/ccl/src/main/java/codechicken/lib/vec/Translation.java new file mode 100644 index 0000000..f6f0a5e --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/vec/Translation.java @@ -0,0 +1,75 @@ +package codechicken.lib.vec; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import org.lwjgl.opengl.GL11; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class Translation extends Transformation { + + public Vector3 vec; + + public Translation(Vector3 vec) { + this.vec = vec; + } + + public Translation(double x, double y, double z) { + this(new Vector3(x, y, z)); + } + + @Override + public void apply(Vector3 vec) { + vec.add(this.vec); + } + + @Override + public void applyN(Vector3 normal) {} + + @Override + public void apply(Matrix4 mat) { + mat.translate(vec); + } + + @Override + public Transformation at(Vector3 point) { + return this; + } + + @Override + @SideOnly(Side.CLIENT) + public void glApply() { + GL11.glTranslated(vec.x, vec.y, vec.z); + } + + @Override + public Transformation inverse() { + return new Translation(-vec.x, -vec.y, -vec.z); + } + + @Override + public Transformation merge(Transformation next) { + if (next instanceof Translation) return new Translation(vec.copy().add(((Translation) next).vec)); + + return null; + } + + @Override + public boolean isRedundant() { + return vec.equalsT(Vector3.zero); + } + + @Override + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "Translation(" + new BigDecimal(vec.x, cont) + + ", " + + new BigDecimal(vec.y, cont) + + ", " + + new BigDecimal(vec.z, cont) + + ")"; + } +} diff --git a/ccl/src/main/java/codechicken/lib/vec/VariableTransformation.java b/ccl/src/main/java/codechicken/lib/vec/VariableTransformation.java new file mode 100644 index 0000000..28669a2 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/vec/VariableTransformation.java @@ -0,0 +1,29 @@ +package codechicken.lib.vec; + +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public abstract class VariableTransformation extends Transformation { + + public Matrix4 mat; + + public VariableTransformation(Matrix4 mat) { + this.mat = mat; + } + + @Override + public void applyN(Vector3 normal) { + apply(normal); + } + + @Override + public void apply(Matrix4 mat) { + mat.multiply(this.mat); + } + + @Override + @SideOnly(Side.CLIENT) + public void glApply() { + mat.glApply(); + } +} diff --git a/ccl/src/main/java/codechicken/lib/vec/Vector3.java b/ccl/src/main/java/codechicken/lib/vec/Vector3.java new file mode 100644 index 0000000..5e907c0 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/vec/Vector3.java @@ -0,0 +1,445 @@ +package codechicken.lib.vec; + +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; + +import net.minecraft.entity.Entity; +import net.minecraft.tileentity.TileEntity; +import net.minecraft.util.Vec3; + +import org.lwjgl.opengl.GL11; +import org.lwjgl.util.vector.Vector3f; +import org.lwjgl.util.vector.Vector4f; + +import codechicken.lib.math.MathHelper; +import codechicken.lib.util.Copyable; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class Vector3 implements Copyable { + + public static Vector3 zero = new Vector3(); + public static Vector3 one = new Vector3(1, 1, 1); + public static Vector3 center = new Vector3(0.5, 0.5, 0.5); + + public double x; + public double y; + public double z; + + public Vector3() {} + + public Vector3(double d, double d1, double d2) { + x = d; + y = d1; + z = d2; + } + + public Vector3(Vector3 vec) { + x = vec.x; + y = vec.y; + z = vec.z; + } + + public Vector3(double[] da) { + this(da[0], da[1], da[2]); + } + + public Vector3(Vec3 vec) { + x = vec.xCoord; + y = vec.yCoord; + z = vec.zCoord; + } + + public Vector3(BlockCoord coord) { + x = coord.x; + y = coord.y; + z = coord.z; + } + + public Vector3 copy() { + return new Vector3(this); + } + + public static Vector3 fromEntity(Entity e) { + return new Vector3(e.posX, e.posY, e.posZ); + } + + public static Vector3 fromEntityCenter(Entity e) { + return new Vector3(e.posX, e.posY - e.yOffset + e.height / 2, e.posZ); + } + + public static Vector3 fromTileEntity(TileEntity e) { + return new Vector3(e.xCoord, e.yCoord, e.zCoord); + } + + public static Vector3 fromTileEntityCenter(TileEntity e) { + return new Vector3(e.xCoord + 0.5, e.yCoord + 0.5, e.zCoord + 0.5); + } + + public static Vector3 fromAxes(double[] da) { + return new Vector3(da[2], da[0], da[1]); + } + + public Vector3 set(double d, double d1, double d2) { + x = d; + y = d1; + z = d2; + return this; + } + + public Vector3 set(Vector3 vec) { + x = vec.x; + y = vec.y; + z = vec.z; + return this; + } + + public double getSide(int side) { + switch (side) { + case 0: + case 1: + return y; + case 2: + case 3: + return z; + case 4: + case 5: + return x; + } + throw new IndexOutOfBoundsException("Switch Falloff"); + } + + public Vector3 setSide(int s, double v) { + switch (s) { + case 0: + case 1: + y = v; + break; + case 2: + case 3: + z = v; + break; + case 4: + case 5: + x = v; + break; + default: + throw new IndexOutOfBoundsException("Switch Falloff"); + } + return this; + } + + public double dotProduct(Vector3 vec) { + double d = vec.x * x + vec.y * y + vec.z * z; + + if (d > 1 && d < 1.00001) d = 1; + else if (d < -1 && d > -1.00001) d = -1; + return d; + } + + public double dotProduct(double d, double d1, double d2) { + return d * x + d1 * y + d2 * z; + } + + public Vector3 crossProduct(Vector3 vec) { + double d = y * vec.z - z * vec.y; + double d1 = z * vec.x - x * vec.z; + double d2 = x * vec.y - y * vec.x; + x = d; + y = d1; + z = d2; + return this; + } + + public Vector3 add(double d, double d1, double d2) { + x += d; + y += d1; + z += d2; + return this; + } + + public Vector3 add(Vector3 vec) { + x += vec.x; + y += vec.y; + z += vec.z; + return this; + } + + public Vector3 add(double d) { + return add(d, d, d); + } + + public Vector3 sub(Vector3 vec) { + return subtract(vec); + } + + public Vector3 subtract(Vector3 vec) { + x -= vec.x; + y -= vec.y; + z -= vec.z; + return this; + } + + public Vector3 negate(Vector3 vec) { + x = -x; + y = -y; + z = -z; + return this; + } + + public Vector3 multiply(double d) { + x *= d; + y *= d; + z *= d; + return this; + } + + public Vector3 multiply(Vector3 f) { + x *= f.x; + y *= f.y; + z *= f.z; + return this; + } + + public Vector3 multiply(double fx, double fy, double fz) { + x *= fx; + y *= fy; + z *= fz; + return this; + } + + public double mag() { + return Math.sqrt(x * x + y * y + z * z); + } + + public double magSquared() { + return x * x + y * y + z * z; + } + + public Vector3 normalize() { + double d = mag(); + if (d != 0) { + multiply(1 / d); + } + return this; + } + + public String toString() { + MathContext cont = new MathContext(4, RoundingMode.HALF_UP); + return "Vector3(" + new BigDecimal(x, cont) + + ", " + + new BigDecimal(y, cont) + + ", " + + new BigDecimal(z, cont) + + ")"; + } + + public Vector3 perpendicular() { + if (z == 0) return zCrossProduct(); + return xCrossProduct(); + } + + public Vector3 xCrossProduct() { + double d = z; + double d1 = -y; + x = 0; + y = d; + z = d1; + return this; + } + + public Vector3 zCrossProduct() { + double d = y; + double d1 = -x; + x = d; + y = d1; + z = 0; + return this; + } + + public Vector3 yCrossProduct() { + double d = -z; + double d1 = x; + x = d; + y = 0; + z = d1; + return this; + } + + public Vector3 rotate(double angle, Vector3 axis) { + Quat.aroundAxis(axis.copy().normalize(), angle).rotate(this); + return this; + } + + public Vector3 rotate(Quat rotator) { + rotator.rotate(this); + return this; + } + + public Vec3 toVec3D() { + return Vec3.createVectorHelper(x, y, z); + } + + public double angle(Vector3 vec) { + return Math.acos(copy().normalize().dotProduct(vec.copy().normalize())); + } + + public boolean isZero() { + return x == 0 && y == 0 && z == 0; + } + + public boolean isAxial() { + return x == 0 ? (y == 0 || z == 0) : (y == 0 && z == 0); + } + + @SideOnly(Side.CLIENT) + public Vector3f vector3f() { + return new Vector3f((float) x, (float) y, (float) z); + } + + @SideOnly(Side.CLIENT) + public Vector4f vector4f() { + return new Vector4f((float) x, (float) y, (float) z, 1); + } + + @SideOnly(Side.CLIENT) + public void glVertex() { + GL11.glVertex3d(x, y, z); + } + + public Vector3 YZintercept(Vector3 end, double px) { + double dx = end.x - x; + double dy = end.y - y; + double dz = end.z - z; + + if (dx == 0) return null; + + double d = (px - x) / dx; + if (MathHelper.between(-1E-5, d, 1E-5)) return this; + + if (!MathHelper.between(0, d, 1)) return null; + + x = px; + y += d * dy; + z += d * dz; + return this; + } + + public Vector3 XZintercept(Vector3 end, double py) { + double dx = end.x - x; + double dy = end.y - y; + double dz = end.z - z; + + if (dy == 0) return null; + + double d = (py - y) / dy; + if (MathHelper.between(-1E-5, d, 1E-5)) return this; + + if (!MathHelper.between(0, d, 1)) return null; + + x += d * dx; + y = py; + z += d * dz; + return this; + } + + public Vector3 XYintercept(Vector3 end, double pz) { + double dx = end.x - x; + double dy = end.y - y; + double dz = end.z - z; + + if (dz == 0) return null; + + double d = (pz - z) / dz; + if (MathHelper.between(-1E-5, d, 1E-5)) return this; + + if (!MathHelper.between(0, d, 1)) return null; + + x += d * dx; + y += d * dy; + z = pz; + return this; + } + + public Vector3 negate() { + x = -x; + y = -y; + z = -z; + return this; + } + + public Translation translation() { + return new Translation(this); + } + + public double scalarProject(Vector3 b) { + double l = b.mag(); + return l == 0 ? 0 : dotProduct(b) / l; + } + + public Vector3 project(Vector3 b) { + double l = b.magSquared(); + if (l == 0) { + set(0, 0, 0); + return this; + } + double m = dotProduct(b) / l; + set(b).multiply(m); + return this; + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof Vector3)) return false; + Vector3 v = (Vector3) o; + return x == v.x && y == v.y && z == v.z; + } + + /** + * Equals method with tolerance + * + * @return true if this is equal to v within +-1E-5 + */ + public boolean equalsT(Vector3 v) { + return MathHelper.between(x - 1E-5, v.x, x + 1E-5) && MathHelper.between(y - 1E-5, v.y, y + 1E-5) + && MathHelper.between(z - 1E-5, v.z, z + 1E-5); + } + + public Vector3 apply(Transformation t) { + t.apply(this); + return this; + } + + public Vector3 $tilde() { + return normalize(); + } + + public Vector3 unary_$tilde() { + return normalize(); + } + + public Vector3 $plus(Vector3 v) { + return add(v); + } + + public Vector3 $minus(Vector3 v) { + return subtract(v); + } + + public Vector3 $times(double d) { + return multiply(d); + } + + public Vector3 $div(double d) { + return multiply(1 / d); + } + + public Vector3 $times(Vector3 v) { + return crossProduct(v); + } + + public double $dot$times(Vector3 v) { + return dotProduct(v); + } +} diff --git a/ccl/src/main/java/codechicken/lib/world/ChunkExtension.java b/ccl/src/main/java/codechicken/lib/world/ChunkExtension.java new file mode 100644 index 0000000..d376dd0 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/world/ChunkExtension.java @@ -0,0 +1,64 @@ +package codechicken.lib.world; + +import java.util.HashSet; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.network.Packet; +import net.minecraft.world.ChunkCoordIntPair; +import net.minecraft.world.chunk.Chunk; + +public abstract class ChunkExtension { + + public final Chunk chunk; + public final ChunkCoordIntPair coord; + public final WorldExtension world; + public HashSet watchedPlayers; + + public ChunkExtension(Chunk chunk, WorldExtension world) { + this.chunk = chunk; + coord = chunk.getChunkCoordIntPair(); + this.world = world; + watchedPlayers = new HashSet(); + } + + public void loadData(NBTTagCompound tag) {} + + public void saveData(NBTTagCompound tag) {} + + public void load() {} + + public void unload() {} + + public final void sendPacketToPlayers(Packet packet) { + for (EntityPlayerMP player : watchedPlayers) player.playerNetServerHandler.sendPacket(packet); + } + + public final void watchPlayer(EntityPlayerMP player) { + watchedPlayers.add(player); + onWatchPlayer(player); + } + + public void onWatchPlayer(EntityPlayerMP player) {} + + public final void unwatchPlayer(EntityPlayerMP player) { + watchedPlayers.remove(player); + onUnWatchPlayer(player); + } + + public void onUnWatchPlayer(EntityPlayerMP player) {} + + public void sendUpdatePackets() {} + + @Override + public int hashCode() { + return coord.chunkXPos ^ coord.chunkZPos; + } + + @Override + public boolean equals(Object o) { + return (o instanceof ChunkExtension && ((ChunkExtension) o).coord.equals(coord)) + || (o instanceof ChunkCoordIntPair && coord.equals(o)) + || (o instanceof Long && (Long) o == (((long) coord.chunkXPos) << 32 | coord.chunkZPos)); + } +} diff --git a/ccl/src/main/java/codechicken/lib/world/IChunkLoadTile.java b/ccl/src/main/java/codechicken/lib/world/IChunkLoadTile.java new file mode 100644 index 0000000..5e914df --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/world/IChunkLoadTile.java @@ -0,0 +1,13 @@ +package codechicken.lib.world; + +/** + * Provides a callback for tile entities when a chunk is loaded, as an alternative to validate when the chunk hasn't + * been added to the world. To hook all world join/seperate events. Use this, TileEntity.validate with a + * worldObj.blockExists check, TileEntity.onChunkUnload and TileEntity.invalidate Be sure to call + * TileChunkLoadHook.init() from your mod during initialisation You could easily implement this in your own mod, but + * providing it here reduces the number of times the chunkTileEntityMap needs to be iterated + */ +public interface IChunkLoadTile { + + public void onChunkLoad(); +} diff --git a/ccl/src/main/java/codechicken/lib/world/TileChunkLoadHook.java b/ccl/src/main/java/codechicken/lib/world/TileChunkLoadHook.java new file mode 100644 index 0000000..1ca6bba --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/world/TileChunkLoadHook.java @@ -0,0 +1,28 @@ +package codechicken.lib.world; + +import java.util.ArrayList; +import java.util.List; + +import net.minecraft.tileentity.TileEntity; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.world.ChunkEvent; + +import cpw.mods.fml.common.eventhandler.SubscribeEvent; + +public class TileChunkLoadHook { + + private static boolean init; + + public static void init() { + if (init) return; + init = true; + + MinecraftForge.EVENT_BUS.register(new TileChunkLoadHook()); + } + + @SubscribeEvent + public void onChunkLoad(ChunkEvent.Load event) { + List list = new ArrayList(event.getChunk().chunkTileEntityMap.values()); + for (TileEntity t : list) if (t instanceof IChunkLoadTile) ((IChunkLoadTile) t).onChunkLoad(); + } +} diff --git a/ccl/src/main/java/codechicken/lib/world/WorldExtension.java b/ccl/src/main/java/codechicken/lib/world/WorldExtension.java new file mode 100644 index 0000000..e6ff496 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/world/WorldExtension.java @@ -0,0 +1,76 @@ +package codechicken.lib.world; + +import java.util.HashMap; + +import net.minecraft.entity.player.EntityPlayerMP; +import net.minecraft.nbt.NBTTagCompound; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; + +public abstract class WorldExtension { + + public final World world; + public HashMap chunkMap = new HashMap(); + + public WorldExtension(World world) { + this.world = world; + } + + public void load() {} + + public void unload() {} + + public void save() {} + + public void preTick() {} + + public void postTick() {} + + protected final void addChunk(ChunkExtension extension) { + chunkMap.put(extension.chunk, extension); + } + + protected final void loadChunk(Chunk chunk) { + chunkMap.get(chunk).load(); + } + + protected final void unloadChunk(Chunk chunk) { + if (chunkMap.get(chunk) != null) chunkMap.get(chunk).unload(); + } + + protected final void loadChunkData(Chunk chunk, NBTTagCompound tag) { + chunkMap.get(chunk).loadData(tag); + } + + protected final void saveChunkData(Chunk chunk, NBTTagCompound tag) { + final ChunkExtension extension = chunkMap.get(chunk); + if (extension != null) extension.saveData(tag); + } + + protected final void remChunk(Chunk chunk) { + chunkMap.remove(chunk); + } + + protected final void watchChunk(Chunk chunk, EntityPlayerMP player) { + chunkMap.get(chunk).watchPlayer(player); + } + + protected final void unwatchChunk(Chunk chunk, EntityPlayerMP player) { + ChunkExtension extension = chunkMap.get(chunk); + if (extension != null) extension.unwatchPlayer(player); + } + + protected final void sendChunkUpdates(Chunk chunk) { + chunkMap.get(chunk).sendUpdatePackets(); + } + + public boolean containsChunk(Chunk chunk) { + return chunkMap.containsKey(chunk); + } + + public ChunkExtension getChunkExtension(int chunkXPos, int chunkZPos) { + if (!world.blockExists(chunkXPos << 4, 128, chunkZPos << 4)) return null; + + return chunkMap.get(world.getChunkFromChunkCoords(chunkXPos, chunkZPos)); + } +} diff --git a/ccl/src/main/java/codechicken/lib/world/WorldExtensionInstantiator.java b/ccl/src/main/java/codechicken/lib/world/WorldExtensionInstantiator.java new file mode 100644 index 0000000..38a4582 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/world/WorldExtensionInstantiator.java @@ -0,0 +1,17 @@ +package codechicken.lib.world; + +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; + +public abstract class WorldExtensionInstantiator { + + public int instantiatorID; + + public abstract WorldExtension createWorldExtension(World world); + + public abstract ChunkExtension createChunkExtension(Chunk chunk, WorldExtension world); + + public WorldExtension getExtension(World world) { + return WorldExtensionManager.getWorldExtension(world, instantiatorID); + } +} diff --git a/ccl/src/main/java/codechicken/lib/world/WorldExtensionManager.java b/ccl/src/main/java/codechicken/lib/world/WorldExtensionManager.java new file mode 100644 index 0000000..4831dc5 --- /dev/null +++ b/ccl/src/main/java/codechicken/lib/world/WorldExtensionManager.java @@ -0,0 +1,161 @@ +package codechicken.lib.world; + +import java.util.ArrayList; +import java.util.HashMap; + +import net.minecraft.client.Minecraft; +import net.minecraft.world.World; +import net.minecraft.world.chunk.Chunk; +import net.minecraft.world.chunk.EmptyChunk; +import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.event.world.ChunkDataEvent; +import net.minecraftforge.event.world.ChunkEvent; +import net.minecraftforge.event.world.ChunkWatchEvent.UnWatch; +import net.minecraftforge.event.world.ChunkWatchEvent.Watch; +import net.minecraftforge.event.world.WorldEvent; + +import cpw.mods.fml.common.FMLCommonHandler; +import cpw.mods.fml.common.eventhandler.SubscribeEvent; +import cpw.mods.fml.common.gameevent.TickEvent; +import cpw.mods.fml.relauncher.Side; +import cpw.mods.fml.relauncher.SideOnly; + +public class WorldExtensionManager { + + public static class WorldExtensionEventHandler { + + @SubscribeEvent + public void onChunkDataLoad(ChunkDataEvent.Load event) { + if (!worldMap.containsKey(event.world)) WorldExtensionManager.onWorldLoad(event.world); + + createChunkExtension(event.world, event.getChunk()); + + for (WorldExtension extension : worldMap.get(event.world)) + extension.loadChunkData(event.getChunk(), event.getData()); + } + + @SubscribeEvent + public void onChunkDataSave(ChunkDataEvent.Save event) { + for (WorldExtension extension : worldMap.get(event.world)) + extension.saveChunkData(event.getChunk(), event.getData()); + + if (!event.getChunk().isChunkLoaded) removeChunk(event.world, event.getChunk()); + } + + @SubscribeEvent + public void onChunkLoad(ChunkEvent.Load event) { + if (!worldMap.containsKey(event.world)) WorldExtensionManager.onWorldLoad(event.world); + + createChunkExtension(event.world, event.getChunk()); + + for (WorldExtension extension : worldMap.get(event.world)) extension.loadChunk(event.getChunk()); + } + + @SubscribeEvent + public void onChunkUnLoad(ChunkEvent.Unload event) { + if (event.getChunk() instanceof EmptyChunk) return; + + for (WorldExtension extension : worldMap.get(event.world)) extension.unloadChunk(event.getChunk()); + + if (event.world.isRemote) removeChunk(event.world, event.getChunk()); + } + + @SubscribeEvent + public void onWorldSave(WorldEvent.Save event) { + if (worldMap.containsKey(event.world)) + for (WorldExtension extension : worldMap.get(event.world)) extension.save(); + } + + @SubscribeEvent + public void onWorldLoad(WorldEvent.Load event) { + if (!worldMap.containsKey(event.world)) WorldExtensionManager.onWorldLoad(event.world); + } + + @SubscribeEvent + public void onWorldUnLoad(WorldEvent.Unload event) { + if (worldMap.containsKey(event.world)) // because force closing unloads a world twice + for (WorldExtension extension : worldMap.remove(event.world)) extension.unload(); + } + + @SubscribeEvent + public void onChunkWatch(Watch event) { + Chunk chunk = event.player.worldObj.getChunkFromChunkCoords(event.chunk.chunkXPos, event.chunk.chunkZPos); + for (WorldExtension extension : worldMap.get(event.player.worldObj)) + extension.watchChunk(chunk, event.player); + } + + @SubscribeEvent + @SideOnly(Side.CLIENT) + public void onChunkUnWatch(UnWatch event) { + Chunk chunk = event.player.worldObj.getChunkFromChunkCoords(event.chunk.chunkXPos, event.chunk.chunkZPos); + for (WorldExtension extension : worldMap.get(event.player.worldObj)) + extension.unwatchChunk(chunk, event.player); + } + + @SubscribeEvent + @SideOnly(Side.CLIENT) + public void clientTick(TickEvent.ClientTickEvent event) { + World world = Minecraft.getMinecraft().theWorld; + if (worldMap.containsKey(world)) if (event.phase == TickEvent.Phase.START) preTick(world); + else postTick(world); + } + + @SubscribeEvent + public void serverTick(TickEvent.WorldTickEvent event) { + if (!worldMap.containsKey(event.world)) WorldExtensionManager.onWorldLoad(event.world); + + if (event.phase == TickEvent.Phase.START) preTick(event.world); + else postTick(event.world); + } + } + + private static boolean initialised; + private static ArrayList extensionIntialisers = new ArrayList(); + + public static void registerWorldExtension(WorldExtensionInstantiator init) { + if (!initialised) init(); + + init.instantiatorID = extensionIntialisers.size(); + extensionIntialisers.add(init); + } + + private static void init() { + initialised = true; + MinecraftForge.EVENT_BUS.register(new WorldExtensionEventHandler()); + FMLCommonHandler.instance().bus().register(new WorldExtensionEventHandler()); + } + + private static HashMap worldMap = new HashMap(); + + private static void onWorldLoad(World world) { + WorldExtension[] extensions = new WorldExtension[extensionIntialisers.size()]; + for (int i = 0; i < extensions.length; i++) + extensions[i] = extensionIntialisers.get(i).createWorldExtension(world); + + worldMap.put(world, extensions); + + for (WorldExtension extension : extensions) extension.load(); + } + + private static void createChunkExtension(World world, Chunk chunk) { + WorldExtension[] extensions = worldMap.get(world); + for (int i = 0; i < extensionIntialisers.size(); i++) if (!extensions[i].containsChunk(chunk)) + extensions[i].addChunk(extensionIntialisers.get(i).createChunkExtension(chunk, extensions[i])); + } + + private static void removeChunk(World world, Chunk chunk) { + for (WorldExtension extension : worldMap.get(world)) extension.remChunk(chunk); + } + + private static void preTick(World world) { + for (WorldExtension extension : worldMap.get(world)) extension.preTick(); + } + + private static void postTick(World world) { + for (WorldExtension extension : worldMap.get(world)) extension.postTick(); + } + + public static WorldExtension getWorldExtension(World world, int instantiatorID) { + return worldMap.get(world)[instantiatorID]; + } +}