diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b18a17a --- /dev/null +++ b/.gitignore @@ -0,0 +1,31 @@ +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +.idea +.gradle +build +run + +.DS_Store +/temp/ +runs/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8000a6f --- /dev/null +++ b/LICENSE @@ -0,0 +1,504 @@ + 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) + + 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. + + , 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..272135f --- /dev/null +++ b/build.gradle @@ -0,0 +1,184 @@ +plugins { + id 'java-library' + id 'eclipse' + id 'idea' + id 'maven-publish' + id 'net.neoforged.gradle.userdev' version '7.0.145' +} + +version = mod_version +group = 'dev.kxmc' + +repositories { + mavenLocal() +} + +base { + archivesName = mod_id +} + +java.toolchain.languageVersion = JavaLanguageVersion.of(21) + +minecraft.accessTransformers.file rootProject.file('src/main/resources/META-INF/accesstransformer.cfg') + +runs { + configureEach { + systemProperty 'forge.logging.markers', 'REGISTRIES' + systemProperty 'forge.logging.console.level', 'debug' + modSource project.sourceSets.main + } + client { + systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + } + server { + systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + programArgument '--nogui' + } + gameTestServer { + systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + } + data { + programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() + } +} + +sourceSets.main.resources { srcDir 'src/generated/resources' } + + +dependencies { + implementation "net.neoforged:neoforge:${neo_version}" +} + +tasks.withType(ProcessResources).configureEach { + var replaceProperties = [ + minecraft_version : minecraft_version, minecraft_version_range: minecraft_version_range, + neo_version : neo_version, neo_version_range: neo_version_range, + loader_version_range: loader_version_range, + mod_id : mod_id, mod_name: mod_name, mod_license: mod_license, mod_version: mod_version, + mod_authors : mod_authors, mod_description: mod_description, + ] + inputs.properties replaceProperties + + filesMatching(['META-INF/mods.toml']) { + expand replaceProperties + [project: project] + } +} + +// Example configuration to allow publishing using the maven-publish plugin +publishing { + publications { + register('mavenJava', MavenPublication) { + from components.java + } + } + repositories { + maven { + url "file://${project.projectDir}/repo" + } + } +} + +tasks.withType(JavaCompile).configureEach { + options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation +} + +if (lljij.toBoolean()) jarJar.enable() + +jar { + manifest { + attributes([ + "Specification-Title" : "${mod_id}", + "Specification-Vendor" : "xkmc", + "Specification-Version" : "1", // We are version 1 of ourselves + "Implementation-Title" : project.name, + "Implementation-Version" : project.jar.archiveVersion, + "Implementation-Vendor" : "xkmc", + "Implementation-Timestamp": new Date().format("yyyy-MM-dd'T'HH:mm:ssZ"), + 'MixinConfigs' : "${mod_id}.mixins.json" + ]) + } +} + +if (lljij.toBoolean()) { + + tasks.jarJar.configure { + archiveClassifier.set('') + } + + jar { + archiveClassifier.set('slim') + } + +} + +dependencies { + //annotationProcessor "org.spongepowered:mixin:${mixin_version}:processor" + //compileOnly fg.deobf("com.tterrag.registrate:Registrate:${registrate_version}") + if (rootMod.toBoolean()) { + //jarJar(group: 'com.tterrag.registrate', name: 'Registrate', version: "[MC1.20,MC1.21)") + } +} + +// project specific + +repositories { + flatDir { + dirs 'libs' + } + maven { // Registrate + url "https://maven.tterrag.com/" + } + maven { + url = "https://maven.theillusivec4.top/" + } + maven { + // Location of the maven for vazkii's mods + name 'blamejared' + url 'https://maven.blamejared.com' + } + maven { + url 'https://www.cursemaven.com' + content { + includeGroup "curse.maven" + } + } + maven { + // Location of the maven that hosts JEI files (and TiC) + name 'Progwml6 maven' + url 'https://dvs1.progwml6.com/files/maven' + } + mavenLocal(); +} + +dependencies { + // base dep + implementation "mezz.jei:jei-${jei_minecraft_version}:${jei_version}" + //implementation "top.theillusivec4.curios:curios-neoforge:${curios_version}" + + implementation "dev.xkmc:l2serial:${l2serial_ver}" + + //runtimeOnly fg.deobf("dev.xkmc.l2damagetracker:l2damagetracker:0.2.4") + //runtimeOnly fg.deobf("dev.xkmc.l2backpack:l2backpack:2.4.12-slim") + //runtimeOnly fg.deobf("dev.xkmc.l2complements:l2complements:2.4.18-slim") + //runtimeOnly fg.deobf('dev.xkmc.modulargolems:modulargolems:2.4.16-slim') + //runtimeOnly fg.deobf("dev.xkmc.l2archery:l2archery:2.4.9") + //runtimeOnly fg.deobf("dev.xkmc.l2weaponry:l2weaponry:2.4.18") + //runtimeOnly fg.deobf("dev.xkmc.l2artifacts:l2artifacts:2.4.8-slim") + + //runtimeOnly fg.deobf("curse.maven:create-328085:4626108") + //implementation fg.deobf("curse.maven:just-enough-effect-descriptions-jeed-532286:4599236") + + //runtimeOnly fg.deobf("curse.maven:badpackets-615134:4438956") + //runtimeOnly fg.deobf("curse.maven:wthit-forge-455982:4596739") + //runtimeOnly fg.deobf("curse.maven:attributefix-280510:4588114") + //runtimeOnly fg.deobf("curse.maven:bookshelf-228525:4581675") + //runtimeOnly fg.deobf("curse.maven:enchantment-descriptions-250419:4587429") + //runtimeOnly fg.deobf("curse.maven:appleskin-248787:4605078") + //implementation fg.deobf("curse.maven:patchouli-306770:4636277") + + //runtimeOnly fg.deobf("dev.xkmc.traderefresh:traderefresh:2.1.1-slim") + //runtimeOnly fg.deobf("dev.xkmc.lasertransport:lasertransport:2.2.0.pre5-slim") + + //runtimeOnly fg.deobf('curse.maven:max-health-fix-492246:4447240') + //runtimeOnly fg.deobf('curse.maven:the-twilight-forest-227639:4516391') +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..1929a4b --- /dev/null +++ b/gradle.properties @@ -0,0 +1,33 @@ +# Sets default memory used for gradle commands. Can be overridden by user or command line properties. +org.gradle.jvmargs=-Xmx3G +org.gradle.daemon=false +org.gradle.debug=false + +#read more on this at https://github.com/neoforged/NeoGradle/blob/NG_7.0/README.md#apply-parchment-mappings +# you can also find the latest versions at: https://parchmentmc.org/docs/getting-started +neogradle.subsystems.parchment.minecraftVersion=1.20.6 +neogradle.subsystems.parchment.mappingsVersion=2024.06.02 + +minecraft_version=1.21 +minecraft_version_range=[1.21,1.22) +neo_version=21.0.19-beta +neo_version_range=[21.0,) +loader_version_range=[2,) + +## Mod Properties +mod_id=l2core +mod_name=L2Core +mod_license=LGPL-2.1 +mod_version=3.0.0-pre0 +mod_group_id=dev.xkmc +mod_authors=lcy0x1 +mod_description=Core Library mod for all L2 mods + + +jei_minecraft_version = 1.21-neoforge +jei_version = 19.0.0.7 + +lljij = false +rootMod = false + +l2serial_ver = 3.0.0-pre1 \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..c1962a7 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..2617362 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..aeb74cb --- /dev/null +++ b/gradlew @@ -0,0 +1,245 @@ +#!/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##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && 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 + which java >/dev/null 2>&1 || 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 + +# 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=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=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, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +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/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..93e3f59 --- /dev/null +++ b/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. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +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/libs/l2modularblocks-3.0.0-pre0.jar b/libs/l2modularblocks-3.0.0-pre0.jar new file mode 100644 index 0000000..7e01345 Binary files /dev/null and b/libs/l2modularblocks-3.0.0-pre0.jar differ diff --git a/libs/l2serial-3.0.0-pre1-sources.jar b/libs/l2serial-3.0.0-pre1-sources.jar new file mode 100644 index 0000000..41b9817 Binary files /dev/null and b/libs/l2serial-3.0.0-pre1-sources.jar differ diff --git a/libs/l2serial-3.0.0-pre1.jar b/libs/l2serial-3.0.0-pre1.jar new file mode 100644 index 0000000..e9f4b2f Binary files /dev/null and b/libs/l2serial-3.0.0-pre1.jar differ diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..cb5265b --- /dev/null +++ b/settings.gradle @@ -0,0 +1,11 @@ +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + maven { url = 'https://maven.neoforged.net/releases' } + } +} + +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0' +} \ No newline at end of file diff --git a/src/generated/resources/.cache/f9c3f85ff48465bc14fd4b47e23aa5b26a478db3 b/src/generated/resources/.cache/f9c3f85ff48465bc14fd4b47e23aa5b26a478db3 new file mode 100644 index 0000000..cc2ac32 --- /dev/null +++ b/src/generated/resources/.cache/f9c3f85ff48465bc14fd4b47e23aa5b26a478db3 @@ -0,0 +1 @@ +// 1.20.1 2023-07-17T13:58:00.905748 Registrate Provider for l2library [Recipes, Advancements, Loot Tables, Tags (blocks), Tags (items), Tags (fluids), Tags (entity_types), Blockstates, Item models, Lang (en_us/en_ud)] diff --git a/src/main/java/dev/xkmc/l2core/base/effects/ClientEffectCap.java b/src/main/java/dev/xkmc/l2core/base/effects/ClientEffectCap.java new file mode 100644 index 0000000..950d6f2 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/ClientEffectCap.java @@ -0,0 +1,17 @@ +package dev.xkmc.l2core.base.effects; + +import dev.xkmc.l2core.capability.attachment.GeneralCapabilityTemplate; +import dev.xkmc.l2serial.serialization.SerialClass; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.entity.LivingEntity; + +import java.util.Comparator; +import java.util.Map; +import java.util.TreeMap; + +@SerialClass +public class ClientEffectCap extends GeneralCapabilityTemplate { + + public final Map map = new TreeMap<>(Comparator.comparing(MobEffect::getDescriptionId)); + +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/EffectBuilder.java b/src/main/java/dev/xkmc/l2core/base/effects/EffectBuilder.java new file mode 100644 index 0000000..ae982f0 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/EffectBuilder.java @@ -0,0 +1,43 @@ +package dev.xkmc.l2core.base.effects; + +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectInstance; + +public class EffectBuilder { + + public final MobEffectInstance ins; + + public EffectBuilder(MobEffectInstance ins) { + this.ins = ins; + } + + public EffectBuilder(MobEffect effect) { + this.ins = new MobEffectInstance(effect, 1, 0); + } + + public EffectBuilder setAmplifier(int amplifier) { + ins.amplifier = amplifier; + return this; + } + + public EffectBuilder setDuration(int duration) { + ins.duration = duration; + return this; + } + + public EffectBuilder setVisible(boolean visible) { + ins.visible = visible; + return this; + } + + public EffectBuilder setAmbient(boolean ambient) { + ins.ambient = ambient; + return this; + } + + public EffectBuilder setShowIcon(boolean showIcon) { + ins.showIcon = showIcon; + return this; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/EffectProperties.java b/src/main/java/dev/xkmc/l2core/base/effects/EffectProperties.java new file mode 100644 index 0000000..cd6ddb0 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/EffectProperties.java @@ -0,0 +1,18 @@ +package dev.xkmc.l2core.base.effects; + +import net.minecraft.world.effect.MobEffectInstance; + +public class EffectProperties { + + public Boolean ambient = null; + public Boolean visible = null; + public Boolean showIcon = null; + + public MobEffectInstance set(MobEffectInstance ins) { + if (ambient != null) ins.ambient = ambient; + if (visible != null) ins.visible = visible; + if (showIcon != null) ins.showIcon = showIcon; + return ins; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/EffectToClient.java b/src/main/java/dev/xkmc/l2core/base/effects/EffectToClient.java new file mode 100644 index 0000000..4a322ae --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/EffectToClient.java @@ -0,0 +1,18 @@ +package dev.xkmc.l2core.base.effects; + +import dev.xkmc.l2core.init.events.ClientEffectRenderEvents; +import dev.xkmc.l2serial.network.SerialPacketBase; +import dev.xkmc.l2serial.serialization.SerialClass; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.Nullable; + +public record EffectToClient(int entity, MobEffect effect, boolean exist, int level) + implements SerialPacketBase { + + @Override + public void handle(@Nullable Player player) { + ClientEffectRenderEvents.sync(this); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/EffectUtil.java b/src/main/java/dev/xkmc/l2core/base/effects/EffectUtil.java new file mode 100644 index 0000000..ecb2514 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/EffectUtil.java @@ -0,0 +1,84 @@ +package dev.xkmc.l2core.base.effects; + +import dev.xkmc.l2core.base.effects.api.ForceEffect; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.neoforged.bus.api.Event; +import net.neoforged.neoforge.common.NeoForge; +import net.neoforged.neoforge.event.EventHooks; +import net.neoforged.neoforge.event.entity.living.MobEffectEvent; + +import javax.annotation.Nullable; +import java.util.Iterator; +import java.util.function.Predicate; + +public class EffectUtil { + + public enum AddReason { + NONE, PROF, FORCE, SKILL, SELF + } + + private static final ThreadLocal REASON = new ThreadLocal<>(); + + /** + * force add effect, make hard not override + * for icon use only, such as Arcane Mark on Wither and Ender Dragon + */ + private static void forceAddEffect(LivingEntity e, MobEffectInstance ins, @Nullable Entity source) { + MobEffectInstance effectinstance = e.getActiveEffectsMap().get(ins.getEffect()); + var event = new ForceAddEffectEvent(e, ins); + NeoForge.EVENT_BUS.post(event); + if (event.getResult() == Event.Result.DENY) { + return; + } + NeoForge.EVENT_BUS.post(new MobEffectEvent.Added(e, effectinstance, ins, source)); + if (effectinstance == null) { + e.getActiveEffectsMap().put(ins.getEffect(), ins); + e.onEffectAdded(ins, source); + } else if (effectinstance.update(ins)) { + e.onEffectUpdated(effectinstance, true, source); + } + } + + public static void addEffect(LivingEntity entity, MobEffectInstance ins, AddReason reason, @Nullable Entity source) { + if (entity == source) + reason = AddReason.SELF; + if (ins.getEffect() instanceof ForceEffect) + reason = AddReason.FORCE; + ins = new MobEffectInstance(ins.getEffect(), ins.getDuration(), ins.getAmplifier(), + ins.isAmbient(), reason != AddReason.FORCE && ins.isVisible(), ins.showIcon()); + REASON.set(reason); + if (ins.getEffect() instanceof ForceEffect) + forceAddEffect(entity, ins, source); + else if (ins.getEffect().isInstantenous()) + ins.getEffect().applyInstantenousEffect(null, null, entity, ins.getAmplifier(), 1); + else entity.addEffect(ins, source); + REASON.set(AddReason.NONE); + } + + public static void refreshEffect(LivingEntity entity, MobEffectInstance ins, AddReason reason, Entity source) { + if (ins.duration < 40) ins.duration = 40; + MobEffectInstance cur = entity.getEffect(ins.getEffect()); + if (cur == null || cur.getAmplifier() < ins.getAmplifier() || cur.getAmplifier() == ins.getAmplifier() && cur.getDuration() < ins.getDuration() / 2) + addEffect(entity, ins, reason, source); + } + + public static void removeEffect(LivingEntity entity, Predicate pred) { + Iterator itr = entity.activeEffects.values().iterator(); + while (itr.hasNext()) { + MobEffectInstance effect = itr.next(); + if (pred.test(effect) && EventHooks.onEffectRemoved(entity, effect, null)) { + entity.onEffectRemoved(effect); + itr.remove(); + entity.effectsDirty = true; + } + } + } + + public static AddReason getReason() { + AddReason ans = REASON.get(); + return ans == null ? AddReason.NONE : ans; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/ForceAddEffectEvent.java b/src/main/java/dev/xkmc/l2core/base/effects/ForceAddEffectEvent.java new file mode 100644 index 0000000..fd6dd74 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/ForceAddEffectEvent.java @@ -0,0 +1,22 @@ +package dev.xkmc.l2core.base.effects; + +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.LivingEntity; +import net.neoforged.bus.api.Event; +import net.neoforged.neoforge.event.entity.living.MobEffectEvent; +import org.jetbrains.annotations.NotNull; + +@Event.HasResult +public class ForceAddEffectEvent extends MobEffectEvent { + + public ForceAddEffectEvent(LivingEntity living, @NotNull MobEffectInstance effectInstance) { + super(living, effectInstance); + } + + @Override + @NotNull + public MobEffectInstance getEffectInstance() { + return super.getEffectInstance(); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/api/ClientRenderEffect.java b/src/main/java/dev/xkmc/l2core/base/effects/api/ClientRenderEffect.java new file mode 100644 index 0000000..2e33366 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/api/ClientRenderEffect.java @@ -0,0 +1,11 @@ +package dev.xkmc.l2core.base.effects.api; + +import net.minecraft.world.entity.LivingEntity; + +import java.util.function.Consumer; + +public interface ClientRenderEffect { + + void render(LivingEntity entity, int lv, Consumer adder); + +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/api/DelayedEntityRender.java b/src/main/java/dev/xkmc/l2core/base/effects/api/DelayedEntityRender.java new file mode 100644 index 0000000..baa6fcf --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/api/DelayedEntityRender.java @@ -0,0 +1,20 @@ +package dev.xkmc.l2core.base.effects.api; + +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.LivingEntity; + +public record DelayedEntityRender(LivingEntity entity, IconRenderRegion region, ResourceLocation rl, + float tx, float ty, float tw, float th) { + + public static DelayedEntityRender icon(LivingEntity entity, ResourceLocation rl) { + return icon(entity, IconRenderRegion.identity(), rl); + } + + public static DelayedEntityRender icon(LivingEntity entity, IconRenderRegion r, ResourceLocation rl) { + return new DelayedEntityRender(entity, r, rl, 0, 0, 1, 1); + } + + public DelayedEntityRender resize(IconRenderRegion r) { + return new DelayedEntityRender(entity, r.resize(region), rl, tx, ty, tw, th); + } +} \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/base/effects/api/FirstPlayerRenderEffect.java b/src/main/java/dev/xkmc/l2core/base/effects/api/FirstPlayerRenderEffect.java new file mode 100644 index 0000000..1564daf --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/api/FirstPlayerRenderEffect.java @@ -0,0 +1,10 @@ +package dev.xkmc.l2core.base.effects.api; + +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.world.effect.MobEffectInstance; + +public interface FirstPlayerRenderEffect { + + void onClientLevelRender(AbstractClientPlayer player, MobEffectInstance value); + +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/api/ForceEffect.java b/src/main/java/dev/xkmc/l2core/base/effects/api/ForceEffect.java new file mode 100644 index 0000000..07470f4 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/api/ForceEffect.java @@ -0,0 +1,4 @@ +package dev.xkmc.l2core.base.effects.api; + +public interface ForceEffect { +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/api/IconOverlayEffect.java b/src/main/java/dev/xkmc/l2core/base/effects/api/IconOverlayEffect.java new file mode 100644 index 0000000..7e86a5c --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/api/IconOverlayEffect.java @@ -0,0 +1,16 @@ +package dev.xkmc.l2core.base.effects.api; + +import net.minecraft.world.entity.LivingEntity; + +import java.util.function.Consumer; + +public interface IconOverlayEffect extends ClientRenderEffect { + + @Override + default void render(LivingEntity entity, int lv, Consumer adder) { + adder.accept(getIcon(entity, lv)); + } + + DelayedEntityRender getIcon(LivingEntity entity, int lv); + +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/api/IconRenderRegion.java b/src/main/java/dev/xkmc/l2core/base/effects/api/IconRenderRegion.java new file mode 100644 index 0000000..9044a4f --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/api/IconRenderRegion.java @@ -0,0 +1,19 @@ +package dev.xkmc.l2core.base.effects.api; + +public record IconRenderRegion(float x, float y, float scale) { + + public static IconRenderRegion identity() { + return new IconRenderRegion(0, 0, 1); + } + + public static IconRenderRegion of(int r, int ix, int iy, int w, int h) { + float y = ((r - h) / 2f + iy) / r; + float x = ((r - w) / 2f + ix) / r; + return new IconRenderRegion(x, y, 1f / r); + } + + public IconRenderRegion resize(IconRenderRegion inner) { + return new IconRenderRegion(x + inner.x() * scale, y + inner.y() * scale, scale * inner.scale); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/api/InherentEffect.java b/src/main/java/dev/xkmc/l2core/base/effects/api/InherentEffect.java new file mode 100644 index 0000000..5d42d62 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/api/InherentEffect.java @@ -0,0 +1,20 @@ +package dev.xkmc.l2core.base.effects.api; + +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectCategory; +import net.minecraft.world.effect.MobEffectInstance; +import net.neoforged.neoforge.common.EffectCure; + +import java.util.Set; + +public class InherentEffect extends MobEffect { + + protected InherentEffect(MobEffectCategory category, int color) { + super(category, color); + } + + @Override + public void fillEffectCures(Set cures, MobEffectInstance effectInstance) { + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/effects/package-info.java b/src/main/java/dev/xkmc/l2core/base/effects/package-info.java new file mode 100644 index 0000000..e855819 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/effects/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.base.effects; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/base/entity/BaseEntity.java b/src/main/java/dev/xkmc/l2core/base/entity/BaseEntity.java new file mode 100644 index 0000000..7b9fdde --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/entity/BaseEntity.java @@ -0,0 +1,49 @@ +package dev.xkmc.l2core.base.entity; + +import dev.xkmc.l2serial.serialization.SerialClass; +import dev.xkmc.l2serial.serialization.codec.PacketCodec; +import dev.xkmc.l2serial.serialization.codec.TagCodec; +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.level.Level; +import net.neoforged.neoforge.entity.IEntityWithComplexSpawn; + +import javax.annotation.ParametersAreNonnullByDefault; + +@SerialClass +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +public abstract class BaseEntity extends Entity implements IEntityWithComplexSpawn { + + public BaseEntity(EntityType type, Level world) { + super(type, world); + } + + @Override + protected void addAdditionalSaveData(CompoundTag tag) { + tag.put("auto-serial", TagCodec.toTag(new CompoundTag(), this)); + } + + @Override + protected void readAdditionalSaveData(CompoundTag tag) { + if (!tag.contains("auto-serial")) + return; + Wrappers.run(() -> TagCodec.fromTag(tag.getCompound("auto-serial"), this.getClass(), this, f -> true)); + } + + @Override + public void writeSpawnData(FriendlyByteBuf buffer) { + PacketCodec.to(buffer, this); + } + + @SuppressWarnings({"rawtypes", "unchecked"}) + @Override + public void readSpawnData(FriendlyByteBuf data) { + PacketCodec.from(data, (Class) this.getClass(), this); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/entity/package-info.java b/src/main/java/dev/xkmc/l2core/base/entity/package-info.java new file mode 100644 index 0000000..b2cca45 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/entity/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.base.entity; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/base/explosion/BaseExplosion.java b/src/main/java/dev/xkmc/l2core/base/explosion/BaseExplosion.java new file mode 100644 index 0000000..b163400 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/explosion/BaseExplosion.java @@ -0,0 +1,29 @@ +package dev.xkmc.l2core.base.explosion; + +import net.minecraft.world.entity.Entity; +import net.minecraft.world.level.Explosion; + +public class BaseExplosion extends Explosion { + + public final BaseExplosionContext base; + public final ModExplosionContext mod; + public final VanillaExplosionContext mc; + public final ParticleExplosionContext particle; + + public BaseExplosion(BaseExplosionContext base, VanillaExplosionContext mc, ModExplosionContext mod, ParticleExplosionContext particle) { + super(base.level(), mc.entity(), mc.source(), mc.calculator(), base.x(), base.y(), base.z(), base.r(), mc.fire(), mc.type(), + particle.small(), particle.large(), particle.sound()); + this.base = base; + this.mod = mod; + this.mc = mc; + this.particle = particle; + } + + /** + * return false to cancel hurt + */ + public boolean hurtEntity(Entity entity) { + return mod.hurtEntity(entity); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/explosion/BaseExplosionContext.java b/src/main/java/dev/xkmc/l2core/base/explosion/BaseExplosionContext.java new file mode 100644 index 0000000..e4c636d --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/explosion/BaseExplosionContext.java @@ -0,0 +1,6 @@ +package dev.xkmc.l2core.base.explosion; + +import net.minecraft.world.level.Level; + +public record BaseExplosionContext(Level level, double x, double y, double z, float r) { +} diff --git a/src/main/java/dev/xkmc/l2core/base/explosion/ExplosionHandler.java b/src/main/java/dev/xkmc/l2core/base/explosion/ExplosionHandler.java new file mode 100644 index 0000000..d7231f3 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/explosion/ExplosionHandler.java @@ -0,0 +1,37 @@ +package dev.xkmc.l2core.base.explosion; + +import net.minecraft.network.protocol.game.ClientboundExplodePacket; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Explosion; +import net.minecraft.world.level.Level; +import net.neoforged.neoforge.event.EventHooks; + +public class ExplosionHandler { + + public static void explode(BaseExplosion exp) { + if (exp.base.level().isClientSide()) return; + if (EventHooks.onExplosionStart(exp.base.level(), exp)) return; + exp.explode(); + Level level = exp.base.level(); + exp.finalizeExplosion(level.isClientSide()); + double x = exp.base.x(); + double y = exp.base.y(); + double z = exp.base.z(); + float r = exp.base.r(); + boolean flag = exp.mc.type() == Explosion.BlockInteraction.KEEP; + if (flag) { + exp.clearToBlow(); + } + for (Player player : level.players()) { + if (player instanceof ServerPlayer serverplayer) { + if (serverplayer.distanceToSqr(x, y, z) < 4096.0D) { + serverplayer.connection.send(new ClientboundExplodePacket(x, y, z, r, + exp.getToBlow(), exp.getHitPlayers().get(serverplayer), exp.mc.type(), + exp.particle.small(), exp.particle.large(), exp.particle.sound())); + } + } + } + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/explosion/ModExplosionContext.java b/src/main/java/dev/xkmc/l2core/base/explosion/ModExplosionContext.java new file mode 100644 index 0000000..3e9d641 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/explosion/ModExplosionContext.java @@ -0,0 +1,12 @@ +package dev.xkmc.l2core.base.explosion; + +import net.minecraft.world.entity.Entity; + +public interface ModExplosionContext { + + /** + * return false to cancel damage + */ + boolean hurtEntity(Entity entity); + +} diff --git a/src/main/java/dev/xkmc/l2core/base/explosion/ParticleExplosionContext.java b/src/main/java/dev/xkmc/l2core/base/explosion/ParticleExplosionContext.java new file mode 100644 index 0000000..960dc1c --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/explosion/ParticleExplosionContext.java @@ -0,0 +1,9 @@ +package dev.xkmc.l2core.base.explosion; + +import net.minecraft.core.particles.ParticleOptions; +import net.minecraft.sounds.SoundEvent; + +public record ParticleExplosionContext(ParticleOptions small, + ParticleOptions large, + SoundEvent sound) { +} diff --git a/src/main/java/dev/xkmc/l2core/base/explosion/VanillaExplosionContext.java b/src/main/java/dev/xkmc/l2core/base/explosion/VanillaExplosionContext.java new file mode 100644 index 0000000..c864afd --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/explosion/VanillaExplosionContext.java @@ -0,0 +1,36 @@ +package dev.xkmc.l2core.base.explosion; + +import net.minecraft.world.damagesource.DamageSource; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.level.Explosion; +import net.minecraft.world.level.ExplosionDamageCalculator; +import net.minecraft.world.level.GameRules; +import net.minecraft.world.level.Level; +import net.neoforged.neoforge.event.EventHooks; + +import javax.annotation.Nullable; + +public record VanillaExplosionContext(@Nullable Entity entity, @Nullable DamageSource source, + @Nullable ExplosionDamageCalculator calculator, + boolean fire, Explosion.BlockInteraction type) { + + public VanillaExplosionContext(Level level, @Nullable Entity entity, @Nullable DamageSource source, + @Nullable ExplosionDamageCalculator calculator, + boolean fire, Level.ExplosionInteraction type) { + this(entity, source, calculator, fire, getType(level, entity, type)); + } + + private static Explosion.BlockInteraction getType(Level level, @Nullable Entity entity, Level.ExplosionInteraction type) { + return switch (type) { + case NONE -> Explosion.BlockInteraction.KEEP; + case BLOCK -> level.getDestroyType(GameRules.RULE_BLOCK_EXPLOSION_DROP_DECAY); + case MOB -> EventHooks.getMobGriefingEvent(level, entity instanceof LivingEntity le ? le : null) ? + level.getDestroyType(GameRules.RULE_MOB_EXPLOSION_DROP_DECAY) : + Explosion.BlockInteraction.KEEP; + case TNT -> level.getDestroyType(GameRules.RULE_TNT_EXPLOSION_DROP_DECAY); + case BLOW -> Explosion.BlockInteraction.TRIGGER_BLOCK; + }; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/explosion/package-info.java b/src/main/java/dev/xkmc/l2core/base/explosion/package-info.java new file mode 100644 index 0000000..517acd4 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/explosion/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.base.explosion; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/base/menu/base/BaseContainerMenu.java b/src/main/java/dev/xkmc/l2core/base/menu/base/BaseContainerMenu.java new file mode 100644 index 0000000..201078d --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/base/BaseContainerMenu.java @@ -0,0 +1,298 @@ +package dev.xkmc.l2core.base.menu.base; + +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.Container; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; + +import java.util.Comparator; +import java.util.Map; +import java.util.TreeMap; +import java.util.function.*; + +/** + * Base Class for ContainerMenu. + * Containers multiple helper functions. + */ +public class BaseContainerMenu> extends AbstractContainerMenu { + + private record SlotKey(String name, int i, int j) { + + private static final Comparator COMPARATOR; + + static { + Comparator comp = Comparator.comparing(SlotKey::name); + comp = comp.thenComparingInt(SlotKey::i); + comp = comp.thenComparingInt(SlotKey::j); + COMPARATOR = comp; + } + + } + + /** + * simple container that prevents looping change + */ + public static class BaseContainer> extends SimpleContainer { + + protected final T parent; + private boolean updating = false; + private int max = 64; + + public BaseContainer(int size, T menu) { + super(size); + parent = menu; + } + + + public BaseContainer setMax(int max) { + this.max = max; + return this; + } + + @Override + public int getMaxStackSize() { + return Math.min(max, super.getMaxStackSize()); + } + + @Override + public void setChanged() { + super.setChanged(); + if (!updating) { + updating = true; + parent.slotsChanged(this); + updating = false; + } + } + + } + + /** + * return items in the slot to player + */ + public static void clearSlot(Player pPlayer, Container pContainer, int index) { + if (!pPlayer.isAlive() || pPlayer instanceof ServerPlayer && ((ServerPlayer) pPlayer).hasDisconnected()) { + pPlayer.drop(pContainer.removeItemNoUpdate(index), false); + } else { + Inventory inventory = pPlayer.getInventory(); + if (inventory.player instanceof ServerPlayer) { + inventory.placeItemBackInInventory(pContainer.removeItemNoUpdate(index)); + } + } + } + + public final Inventory inventory; + public final Container container; + public final SpriteManager sprite; + protected int added = 0; + protected final boolean isVirtual; + + private boolean updating = false; + + private final Map slotMap = new TreeMap<>(SlotKey.COMPARATOR); + + /** + * This contructor will bind player inventory first, so player inventory has lower slot index. + * + * @param type registered menu type + * @param wid window id + * @param plInv player inventory + * @param manager sprite manager used for slot positioning and rendering + * @param factory container supplier + * @param isVirtual true if the slots should be cleared and item returned to player on menu close. + */ + protected BaseContainerMenu(MenuType type, int wid, Inventory plInv, SpriteManager manager, Function factory, boolean isVirtual) { + super(type, wid); + this.inventory = plInv; + container = factory.apply(Wrappers.cast(this)); + sprite = manager; + int x = manager.get().getPlInvX(); + int y = manager.get().getPlInvY(); + this.bindPlayerInventory(plInv, x, y); + this.isVirtual = isVirtual; + } + + /** + * Binds player inventory. Should not be called by others, but permits override. + */ + protected void bindPlayerInventory(Inventory plInv, int x, int y) { + for (int i = 0; i < 3; ++i) + for (int j = 0; j < 9; ++j) + addSlot(createSlot(plInv, j + i * 9 + 9, x + j * 18, y + i * 18)); + for (int k = 0; k < 9; ++k) + addSlot(createSlot(plInv, k, x + k * 18, y + 58)); + } + + /** + * Used by bindPlayerInventory only. Create slots as needed. Some slots could be locked. + */ + protected Slot createSlot(Inventory inv, int slot, int x, int y) { + return shouldLock(inv, slot) ? new SlotLocked(inv, slot, x, y) : new Slot(inv, slot, x, y); + } + + /** + * Lock slots you don't want players modifying, such as the slot player is opening backpack in. + */ + protected boolean shouldLock(Inventory inv, int slot) { + return false; + } + + /** + * Add new slots, with item input predicate + */ + protected void addSlot(String name, Predicate pred) { + sprite.get().getSlot(name, (x, y) -> new PredSlot(container, added++, x, y, pred), this::addSlot); + } + + /** + * Add new slots, with index-sensitive item input predicate. + * The index here is relative to the first slot added by this method. + */ + protected void addSlot(String name, BiPredicate pred) { + int current = added; + sprite.get().getSlot(name, (x, y) -> { + int i = added - current; + var ans = new PredSlot(container, added, x, y, e -> pred.test(i, e)); + added++; + return ans; + }, this::addSlot); + } + + /** + * Add new slots, with other modifications to the slot. + */ + protected void addSlot(String name, Predicate pred, Consumer modifier) { + sprite.get().getSlot(name, (x, y) -> { + PredSlot s = new PredSlot(container, added++, x, y, pred); + modifier.accept(s); + return s; + }, this::addSlot); + } + + /** + * Add new slots, with index-sensitive modifications to the slot. + * The index here is relative to the first slot added by this method. + */ + protected void addSlot(String name, BiPredicate pred, BiConsumer modifier) { + int current = added; + sprite.get().getSlot(name, (x, y) -> { + int i = added - current; + var ans = new PredSlot(container, added, x, y, e -> pred.test(i, e)); + modifier.accept(i, ans); + added++; + return ans; + }, this::addSlot); + } + + /** + * internal add slot + */ + protected void addSlot(String name, int i, int j, Slot slot) { + slotMap.put(new SlotKey(name, i, j), slot); + this.addSlot(slot); + } + + /** + * get the slot by name and id in the grid + */ + protected Slot getSlot(String name, int i, int j) { + return slotMap.get(new SlotKey(name, i, j)); + } + + /** + * get a slot as PredSlot, as most slots should be + */ + public PredSlot getAsPredSlot(String name, int i, int j) { + return (PredSlot) getSlot(name, i, j); + } + + /** + * get a slot as PredSlot, as most slots should be + */ + public PredSlot getAsPredSlot(String name) { + return (PredSlot) getSlot(name, 0, 0); + } + + @Override + public ItemStack quickMoveStack(Player pl, int id) { + ItemStack stack = slots.get(id).getItem(); + int n = container.getContainerSize(); + boolean moved; + if (id >= 36) { + moved = moveItemStackTo(stack, 0, 36, true); + } else { + moved = moveItemStackTo(stack, 36, 36 + n, false); + } + if (moved) slots.get(id).setChanged(); + return ItemStack.EMPTY; + } + + @Override + public boolean stillValid(Player player) { + return player.isAlive(); + } + + @Override + public void removed(Player player) { + if (isVirtual && !player.level().isClientSide()) + clearContainerFiltered(player, container); + super.removed(player); + } + + /** + * return true (and when isVirtual is true), clear the corresponding slot on menu close. + */ + protected boolean shouldClear(Container container, int slot) { + return isVirtual; + } + + /** + * clear slots using shouldClear + */ + protected void clearContainerFiltered(Player player, Container container) { + if (!player.isAlive() || player instanceof ServerPlayer && ((ServerPlayer) player).hasDisconnected()) { + for (int j = 0; j < container.getContainerSize(); ++j) { + if (shouldClear(container, j)) { + player.drop(container.removeItemNoUpdate(j), false); + } + } + } else { + Inventory inventory = player.getInventory(); + for (int i = 0; i < container.getContainerSize(); ++i) { + if (shouldClear(container, i)) { + if (inventory.player instanceof ServerPlayer) { + inventory.placeItemBackInInventory(container.removeItemNoUpdate(i)); + } + } + } + + } + } + + @Override + public void slotsChanged(Container cont) { + if (inventory.player.level().isClientSide()) { + super.slotsChanged(cont); + } else { + if (!updating) { + updating = true; + securedServerSlotChange(cont); + updating = false; + } + super.slotsChanged(cont); + } + } + + /** + * server side only slot change detector that will not be called recursively. + */ + protected void securedServerSlotChange(Container cont) { + + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/base/BaseContainerScreen.java b/src/main/java/dev/xkmc/l2core/base/menu/base/BaseContainerScreen.java new file mode 100644 index 0000000..3bb452e --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/base/BaseContainerScreen.java @@ -0,0 +1,32 @@ +package dev.xkmc.l2core.base.menu.base; + +import dev.xkmc.l2core.util.Proxy; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.player.Inventory; + +public abstract class BaseContainerScreen> extends AbstractContainerScreen { + + public BaseContainerScreen(T cont, Inventory plInv, Component title) { + super(cont, plInv, title); + this.imageHeight = menu.sprite.get().getHeight(); + this.inventoryLabelY = menu.sprite.get().getPlInvY() - 11; + } + + @Override + public void render(GuiGraphics g, int mx, int my, float partial) { + super.render(g, mx, my, partial); + renderTooltip(g, mx, my); + } + + protected boolean click(int btn) { + if (menu.clickMenuButton(Proxy.getClientPlayer(), btn) && Minecraft.getInstance().gameMode != null) { + Minecraft.getInstance().gameMode.handleInventoryButtonClick(this.menu.containerId, btn); + return true; + } + return false; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/base/MenuLayoutConfig.java b/src/main/java/dev/xkmc/l2core/base/menu/base/MenuLayoutConfig.java new file mode 100644 index 0000000..7caf7d4 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/base/MenuLayoutConfig.java @@ -0,0 +1,237 @@ +package dev.xkmc.l2core.base.menu.base; + +import dev.xkmc.l2serial.serialization.SerialClass; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.inventory.Slot; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.HashMap; + +@SuppressWarnings("unused") +@SerialClass +public record MenuLayoutConfig(int height, HashMap side, HashMap comp) { + + public static ResourceLocation getTexture(ResourceLocation id) { + return new ResourceLocation(id.getNamespace(), "textures/gui/container/" + id.getPath() + ".png"); + } + + /** + * get the location of the component on the GUI + */ + public Rect getComp(String key) { + return comp.getOrDefault(key, Rect.ZERO); + } + + /** + * Height of this GUI + */ + public int getHeight() { + return height; + } + + /** + * The X position of the player inventory + */ + public int getPlInvX() { + return 8; + } + + /** + * The Y position of the player inventory + */ + public int getPlInvY() { + return height - 82; + } + + @OnlyIn(Dist.CLIENT) + public ScreenRenderer getRenderer(ResourceLocation id, AbstractContainerScreen gui) { + return new ScreenRenderer(id, gui); + } + + @OnlyIn(Dist.CLIENT) + public ScreenRenderer getRenderer(ResourceLocation id, Screen gui, int x, int y, int w, int h) { + return new ScreenRenderer(id, gui, x, y, w, h); + } + + /** + * get the rectangle representing the sprite element on the sprite + */ + public Rect getSide(String key) { + return side.getOrDefault(key, Rect.ZERO); + } + + /** + * configure the coordinate of the slot + */ + public void getSlot(String key, SlotFactory fac, SlotAcceptor con) { + Rect c = getComp(key); + for (int j = 0; j < c.ry; j++) + for (int i = 0; i < c.rx; i++) { + var slot = fac.getSlot(c.x + i * c.w, c.y + j * c.h); + if (slot != null) { + con.addSlot(key, i, j, slot); + } + } + } + + public int getWidth() { + return 176; + } + + /** + * return if the coordinate is within the rectangle represented by the key + */ + public boolean within(String key, double x, double y) { + Rect c = getComp(key); + return x > c.x && x < c.x + c.w && y > c.y && y < c.y + c.h; + } + + public interface SlotFactory { + + @Nullable + T getSlot(int x, int y); + + } + + public interface SlotAcceptor { + + void addSlot(String name, int i, int j, Slot slot); + + } + + @SerialClass + public static class Rect { + + public static final Rect ZERO = new Rect(); + + @SerialClass.SerialField + public int x, y, w, h, rx = 1, ry = 1; + + public Rect() { + } + + } + + @OnlyIn(Dist.CLIENT) + public class ScreenRenderer { + + private final int x, y, w, h; + private final Screen scr; + + public final ResourceLocation id; + + public MenuLayoutConfig parent(){ + return MenuLayoutConfig.this; + } + + public ScreenRenderer(ResourceLocation id, Screen gui, int x, int y, int w, int h) { + scr = gui; + this.x = x; + this.y = y; + this.w = w; + this.h = h; + this.id = id; + } + + private ScreenRenderer(ResourceLocation id, AbstractContainerScreen scrIn) { + x = scrIn.getGuiLeft(); + y = scrIn.getGuiTop(); + w = scrIn.getXSize(); + h = scrIn.getYSize(); + scr = scrIn; + this.id = id; + } + + /** + * Draw a side sprite on the location specified by the component + */ + public void draw(GuiGraphics g, String c, String s) { + Rect cr = getComp(c); + Rect sr = getSide(s); + g.blit(getTexture(id), x + cr.x, y + cr.y, sr.x, sr.y, sr.w, sr.h); + } + + /** + * Draw a side sprite on the location specified by the component with offsets + */ + public void draw(GuiGraphics g, String c, String s, int xoff, int yoff) { + Rect cr = getComp(c); + Rect sr = getSide(s); + g.blit(getTexture(id), x + cr.x + xoff, y + cr.y + yoff, sr.x, sr.y, sr.w, sr.h); + } + + /** + * Draw a side sprite on the location specified by the component. Draw partially + * from bottom to top + */ + public void drawBottomUp(GuiGraphics g, String c, String s, int prog, int max) { + if (prog == 0 || max == 0) + return; + Rect cr = getComp(c); + Rect sr = getSide(s); + int dh = sr.h * prog / max; + g.blit(getTexture(id), x + cr.x, y + cr.y + sr.h - dh, sr.x, sr.y + sr.h - dh, sr.w, dh); + } + + /** + * Draw a side sprite on the location specified by the component. Draw partially + * from left to right + */ + public void drawLeftRight(GuiGraphics g, String c, String s, int prog, int max) { + if (prog == 0 || max == 0) + return; + Rect cr = getComp(c); + Rect sr = getSide(s); + int dw = sr.w * prog / max; + g.blit(getTexture(id), x + cr.x, y + cr.y, sr.x, sr.y, dw, sr.h); + } + + /** + * fill an area with a sprite, repeat as tiles if not enough, start from lower + * left corner + */ + public void drawLiquid(GuiGraphics g, String c, double per, int height, int sw, int sh) { + Rect cr = getComp(c); + int base = cr.y + height; + int h = (int) Math.round(per * height); + circularBlit(g, x + cr.x, base - h, 0, -h, cr.w, h, sw, sh); + } + + /** + * bind texture, draw background color, and GUI background + */ + public void start(GuiGraphics g) { + scr.renderTransparentBackground(g); + g.blit(getTexture(id), x, y, 0, 0, w, h); + } + + private void circularBlit(GuiGraphics g, int sx, int sy, int ix, int iy, int w, int h, int iw, int ih) { + int x0 = ix, yb = iy, x1 = w, x2 = sx; + while (x0 < 0) + x0 += iw; + while (yb < ih) + yb += ih; + while (x1 > 0) { + int dx = Math.min(x1, iw - x0); + int y0 = yb, y1 = h, y2 = sy; + while (y1 > 0) { + int dy = Math.min(y1, ih - y0); + g.blit(getTexture(id), x2, y2, x0, y0, x1, y1); + y1 -= dy; + y0 += dy; + y2 += dy; + } + x1 -= dx; + x0 += dx; + x2 += dx; + } + } + + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/base/PredSlot.java b/src/main/java/dev/xkmc/l2core/base/menu/base/PredSlot.java new file mode 100644 index 0000000..8c689a7 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/base/PredSlot.java @@ -0,0 +1,150 @@ +package dev.xkmc.l2core.base.menu.base; + +import net.minecraft.world.Container; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; + +import javax.annotation.Nullable; +import java.util.function.BooleanSupplier; +import java.util.function.Predicate; + +/** + * Slot added by BaseContainerMenu. Contains multiple helpers + */ +public class PredSlot extends Slot { + + private final Predicate pred; + private final int slotCache; + + @Nullable + private BooleanSupplier pickup; + + @Nullable + private BooleanSupplier inputLockPred; + + private int max = 64; + + private boolean changed = false; + private boolean lockInput = false, lockOutput = false; + + /** + * Should be called by BaseContainerMenu::addSlot only. + * Predicate supplied from subclasses pf BaseContainerMenu. + */ + public PredSlot(Container inv, int ind, int x, int y, Predicate pred) { + super(inv, ind, x, y); + this.pred = pred; + slotCache = ind; + } + + /** + * Set the condition to unlock a slot for input. + * Parallel with manual lock and item predicate. + */ + public PredSlot setInputLockPred(BooleanSupplier pred) { + this.inputLockPred = pred; + return this; + } + + /** + * Set restriction for pickup. + */ + public PredSlot setPickup(BooleanSupplier pickup) { + this.pickup = pickup; + return this; + } + + public PredSlot setMax(int max) { + this.max = max; + return this; + } + + @Override + public int getMaxStackSize() { + return Math.min(max, super.getMaxStackSize()); + } + + @Override + public boolean mayPlace(ItemStack stack) { + if (isInputLocked()) return false; + return pred.test(stack); + } + + @Override + public boolean mayPickup(Player player) { + if (isOutputLocked()) return false; + return pickup == null || pickup.getAsBoolean(); + } + + @Override + public void setChanged() { + changed = true; + super.setChanged(); + } + + /** + * run only if the content of this slot is changed + */ + public boolean clearDirty(Runnable r) { + if (changed) { + r.run(); + changed = false; + return true; + } + return false; + } + + public boolean clearDirty() { + if (changed) { + changed = false; + return true; + } + return false; + } + + /** + * eject the content of this slot if the item is no longer allowed + */ + public void updateEject(Player player) { + if (!mayPlace(getItem())) clearSlot(player); + } + + /** + * Lock the input of this slot. + * Parallel with lock conditions and item predicate + */ + public void setLockInput(boolean lock) { + lockInput = lock; + } + + /** + * See if the input is locked manually or locked by lock conditions + */ + public boolean isInputLocked() { + return lockInput || (inputLockPred != null && inputLockPred.getAsBoolean()); + } + + /** + * Lock the output of this slot. + * Parallel with pickup restrictions. + */ + public void setLockOutput(boolean lock) { + lockOutput = lock; + } + + /** + * See if the output is locked manually. + */ + public boolean isOutputLocked() { + return lockOutput; + } + + /** + * eject the content of this slot. + */ + public void clearSlot(Player player) { + BaseContainerMenu.clearSlot(player, container, slotCache); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/base/SlotLocked.java b/src/main/java/dev/xkmc/l2core/base/menu/base/SlotLocked.java new file mode 100644 index 0000000..1ef9b5c --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/base/SlotLocked.java @@ -0,0 +1,24 @@ +package dev.xkmc.l2core.base.menu.base; + +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.Slot; +import net.minecraft.world.item.ItemStack; + +public class SlotLocked extends Slot { + + public SlotLocked(Inventory inventory, int index, int x, int y) { + super(inventory, index, x, y); + } + + @Override + public boolean mayPickup(Player player) { + return false; + } + + @Override + public boolean mayPlace(ItemStack stack) { + return false; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/base/SpriteManager.java b/src/main/java/dev/xkmc/l2core/base/menu/base/SpriteManager.java new file mode 100644 index 0000000..e608df2 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/base/SpriteManager.java @@ -0,0 +1,16 @@ +package dev.xkmc.l2core.base.menu.base; + +import dev.xkmc.l2core.init.L2LibReg; +import net.minecraft.resources.ResourceLocation; + +public record SpriteManager(ResourceLocation id) { + + public SpriteManager(String modid, String path) { + this(new ResourceLocation(modid, path)); + } + + public MenuLayoutConfig get() { + return L2LibReg.MENU_LAYOUT.get(id); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/base/package-info.java b/src/main/java/dev/xkmc/l2core/base/menu/base/package-info.java new file mode 100644 index 0000000..fedd4dd --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/base/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.base.menu.base; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/base/menu/data/BoolArrayDataSlot.java b/src/main/java/dev/xkmc/l2core/base/menu/data/BoolArrayDataSlot.java new file mode 100644 index 0000000..6302c97 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/data/BoolArrayDataSlot.java @@ -0,0 +1,32 @@ +package dev.xkmc.l2core.base.menu.data; + +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.DataSlot; + +public class BoolArrayDataSlot { + + private final DataSlot[] array; + + public BoolArrayDataSlot(AbstractContainerMenu menu, int size) { + int n = size / 16 + (size % 16 == 0 ? 0 : 1); + array = new DataSlot[n]; + for (int i = 0; i < n; i++) { + array[i] = menu.addDataSlot(DataSlot.standalone()); + } + } + + public boolean get(int i) { + return (array[i >> 4].get() & (1 << (i & 0xf))) != 0; + } + + public void set(boolean pc, int i) { + int val = array[i >> 4].get(); + int mask = 1 << (i & 0xf); + boolean old = (val & mask) != 0; + if (old != pc) { + val ^= mask; + array[i >> 4].set(val); + } + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/data/DoubleDataSlot.java b/src/main/java/dev/xkmc/l2core/base/menu/data/DoubleDataSlot.java new file mode 100644 index 0000000..910f23a --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/data/DoubleDataSlot.java @@ -0,0 +1,21 @@ +package dev.xkmc.l2core.base.menu.data; + +import net.minecraft.world.inventory.AbstractContainerMenu; + +public class DoubleDataSlot { + + private final LongDataSlot data; + + public DoubleDataSlot(AbstractContainerMenu menu) { + data = new LongDataSlot(menu); + } + + public double get() { + return Double.longBitsToDouble(data.get()); + } + + public void set(double pc) { + data.set(Double.doubleToLongBits(pc)); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/data/FloatDataSlot.java b/src/main/java/dev/xkmc/l2core/base/menu/data/FloatDataSlot.java new file mode 100644 index 0000000..5afc733 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/data/FloatDataSlot.java @@ -0,0 +1,21 @@ +package dev.xkmc.l2core.base.menu.data; + +import net.minecraft.world.inventory.AbstractContainerMenu; + +public class FloatDataSlot { + + private final IntDataSlot data; + + public FloatDataSlot(AbstractContainerMenu menu) { + data = new IntDataSlot(menu); + } + + public float get() { + return Float.intBitsToFloat(data.get()); + } + + public void set(float pc) { + data.set(Float.floatToIntBits(pc)); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/data/IntDataSlot.java b/src/main/java/dev/xkmc/l2core/base/menu/data/IntDataSlot.java new file mode 100644 index 0000000..46de0b4 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/data/IntDataSlot.java @@ -0,0 +1,24 @@ +package dev.xkmc.l2core.base.menu.data; + +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.DataSlot; + +public class IntDataSlot { + + private final DataSlot hi, lo; + + public IntDataSlot(AbstractContainerMenu menu) { + hi = menu.addDataSlot(DataSlot.standalone()); + lo = menu.addDataSlot(DataSlot.standalone()); + } + + public int get() { + return hi.get() << 16 | Short.toUnsignedInt((short) lo.get()); + } + + public void set(int pc) { + lo.set((short) (pc & 0xFFFF)); + hi.set(pc >> 16); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/data/LongDataSlot.java b/src/main/java/dev/xkmc/l2core/base/menu/data/LongDataSlot.java new file mode 100644 index 0000000..b9424e7 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/data/LongDataSlot.java @@ -0,0 +1,23 @@ +package dev.xkmc.l2core.base.menu.data; + +import net.minecraft.world.inventory.AbstractContainerMenu; + +public class LongDataSlot { + + private final IntDataSlot lo, hi; + + public LongDataSlot(AbstractContainerMenu menu) { + lo = new IntDataSlot(menu); + hi = new IntDataSlot(menu); + } + + public long get() { + return ((long) hi.get()) << 32 | Integer.toUnsignedLong(lo.get()); + } + + public void set(long pc) { + lo.set((int) (pc)); + hi.set((int) (pc >> 32)); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/data/package-info.java b/src/main/java/dev/xkmc/l2core/base/menu/data/package-info.java new file mode 100644 index 0000000..096314e --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/data/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.base.menu.data; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/base/menu/scroller/Scroller.java b/src/main/java/dev/xkmc/l2core/base/menu/scroller/Scroller.java new file mode 100644 index 0000000..4073443 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/scroller/Scroller.java @@ -0,0 +1,77 @@ +package dev.xkmc.l2core.base.menu.scroller; + +import dev.xkmc.l2core.base.menu.base.MenuLayoutConfig; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.util.Mth; + +public class Scroller { + + private final ScrollerScreen screen; + private final MenuLayoutConfig sprite; + private final String box, light, dark; + private final int bx, by, bw, bh, sh; + + private boolean scrolling; + private double percentage; + + public Scroller(ScrollerScreen screen, MenuLayoutConfig sprite, String slider_middle, String slider_light, String slider_dark) { + this.screen = screen; + this.sprite = sprite; + this.box = slider_middle; + this.light = slider_light; + this.dark = slider_dark; + var scroller = sprite.getComp(box); + bx = scroller.x; + by = scroller.y; + bw = scroller.w; + bh = scroller.ry; + var slider = sprite.getSide(light); + sh = slider.h; + } + + public boolean mouseClicked(double mx, double my, int btn) { + this.scrolling = false; + int cx = screen.getGuiLeft() + bx; + int cy = screen.getGuiTop() + by; + if (mx >= cx && mx < cx + bw && my >= cy && my < cy + bh) { + this.scrolling = true; + return true; + } + return false; + } + + public boolean mouseDragged(double mx, double my, int btn, double dx, double dy) { + if (this.scrolling && screen.getMenu().getMaxScroll() > 0) { + int y0 = screen.getGuiTop() + by; + int y1 = y0 + bh; + percentage = (my - y0 - sh * 0.5) / ((y1 - y0) - 15.0F); + percentage = Mth.clamp(percentage, 0.0F, 1.0F); + updateIndex(); + return true; + } + return false; + } + + public boolean mouseScrolled(double mx, double my, double d) { + if (screen.getMenu().getMaxScroll() > 0) { + int i = screen.getMenu().getMaxScroll(); + double f = d / i; + percentage = Mth.clamp(percentage - f, 0, 1); + updateIndex(); + } + return true; + } + + private void updateIndex() { + screen.scrollTo((int) ((percentage * screen.getMenu().getMaxScroll()) + 0.5D)); + } + + public void render(GuiGraphics g, MenuLayoutConfig.ScreenRenderer sr) { + if (screen.getMenu().getMaxScroll() == 0) { + sr.draw(g, box, dark); + } else { + int off = (int) Math.round((bh - sh) * percentage); + sr.draw(g, box, light, 0, off); + } + } +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/scroller/ScrollerMenu.java b/src/main/java/dev/xkmc/l2core/base/menu/scroller/ScrollerMenu.java new file mode 100644 index 0000000..9817eed --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/scroller/ScrollerMenu.java @@ -0,0 +1,8 @@ +package dev.xkmc.l2core.base.menu.scroller; + +public interface ScrollerMenu { + + int getMaxScroll(); + + int getScroll(); +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/scroller/ScrollerScreen.java b/src/main/java/dev/xkmc/l2core/base/menu/scroller/ScrollerScreen.java new file mode 100644 index 0000000..0eada94 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/scroller/ScrollerScreen.java @@ -0,0 +1,12 @@ +package dev.xkmc.l2core.base.menu.scroller; + +public interface ScrollerScreen { + + ScrollerMenu getMenu(); + + int getGuiLeft(); + + int getGuiTop(); + + void scrollTo(int i); +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/scroller/package-info.java b/src/main/java/dev/xkmc/l2core/base/menu/scroller/package-info.java new file mode 100644 index 0000000..23e5345 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/scroller/package-info.java @@ -0,0 +1,8 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault + +package dev.xkmc.l2core.base.menu.scroller; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/base/menu/stacked/CellEntry.java b/src/main/java/dev/xkmc/l2core/base/menu/stacked/CellEntry.java new file mode 100644 index 0000000..46f44e4 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/stacked/CellEntry.java @@ -0,0 +1,5 @@ +package dev.xkmc.l2core.base.menu.stacked; + +public record CellEntry(int x, int y, int w, int h) { + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/stacked/StackedRenderHandle.java b/src/main/java/dev/xkmc/l2core/base/menu/stacked/StackedRenderHandle.java new file mode 100644 index 0000000..a77d66d --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/stacked/StackedRenderHandle.java @@ -0,0 +1,133 @@ +package dev.xkmc.l2core.base.menu.stacked; + +import dev.xkmc.l2core.base.menu.base.MenuLayoutConfig; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.network.chat.Component; + +import java.util.ArrayList; +import java.util.List; + +public class StackedRenderHandle { + + static final int BTN_X_OFFSET = 3; + static final int TEXT_BASE_HEIGHT = 8; + + private static final int SLOT_X_OFFSET = 7, SLOT_SIZE = 18, SPRITE_OFFSET = 176; + + final Screen scr; + final GuiGraphics g; + final MenuLayoutConfig.ScreenRenderer sm; + final Font font; + final int text_color; + private final int TEXT_Y_OFFSET; + private final int TEXT_HEIGHT; + private final int text_x_offset; + + private int current_y = 3; + private int current_x = 0; + + final List textList = new ArrayList<>(); + + public StackedRenderHandle(Screen scr, GuiGraphics g, MenuLayoutConfig.ScreenRenderer sm) { + this(scr, g, sm, 3); + } + + public StackedRenderHandle(Screen scr, GuiGraphics g, MenuLayoutConfig.ScreenRenderer sm, int ty) { + this(scr, g, 8, 4210752, sm, ty); + } + + public StackedRenderHandle(Screen scr, GuiGraphics g, int x_offset, int color, MenuLayoutConfig.ScreenRenderer sm) { + this(scr, g, x_offset, color, sm, 3); + } + + public StackedRenderHandle(Screen scr, GuiGraphics g, int x_offset, int color, MenuLayoutConfig.ScreenRenderer sm, int ty) { + this.font = Minecraft.getInstance().font; + this.g = g; + this.scr = scr; + this.sm = sm; + this.text_color = color; + this.text_x_offset = x_offset; + this.TEXT_Y_OFFSET = ty; + this.TEXT_HEIGHT = font.lineHeight + ty + 1; + } + + public void drawText(Component text, boolean shadow) { + endCell(); + int y = current_y + TEXT_Y_OFFSET; + textList.add(new TextEntry(text, text_x_offset, y, text_color, shadow)); + current_y += TEXT_HEIGHT; + } + + public void drawTable(Component[][] table, int x_max, boolean shadow) { + endCell(); + int w = table[0].length; + int w1 = 0; + int ws = 0; + for (Component[] c : table) { + w1 = Math.max(w1, font.width(c[0])); + for (int i = 1; i < w; i++) { + ws = Math.max(ws, font.width(c[i])); + } + } + int sumw = w1 + ws * (w - 1); + int x0 = text_x_offset; + int x1 = x_max - text_x_offset; + float space = (x1 - x0 - sumw) * 1.0f / (w - 1); + for (Component[] c : table) { + int y = current_y + TEXT_Y_OFFSET; + float x_start = x0; + for (int i = 0; i < w; i++) { + float wi = i == 0 ? w1 : ws; + int x = Math.round(x_start); + textList.add(new TextEntry(c[i], x, y, text_color, shadow)); + x_start += wi + space; + } + current_y += TEXT_HEIGHT; + } + } + + public TextButtonHandle drawTextWithButtons(Component text, boolean shadow) { + endCell(); + int y = current_y + TEXT_Y_OFFSET; + textList.add(new TextEntry(text, text_x_offset, y, text_color, shadow)); + int x_off = text_x_offset + font.width(text) + BTN_X_OFFSET; + TextButtonHandle ans = new TextButtonHandle(this, x_off, y + font.lineHeight / 2); + current_y += TEXT_HEIGHT; + return ans; + } + + public CellEntry addCell(boolean toggled, boolean disabled) { + startCell(); + int index = toggled ? 1 : disabled ? 2 : 0; + int x = SLOT_X_OFFSET + current_x * SLOT_SIZE; + int u = SPRITE_OFFSET + index * SLOT_SIZE; + g.blit(MenuLayoutConfig.getTexture(sm.id), x, current_y, u, 0, SLOT_SIZE, SLOT_SIZE); + var ans = new CellEntry(x + 1, current_y + 1, 16, 16); + current_x++; + if (current_x == 9) { + endCell(); + } + return ans; + } + + private void startCell() { + if (current_x < 0) { + current_x = 0; + } + } + + private void endCell() { + if (current_x > 0) { + current_x = -1; + current_y += SLOT_SIZE; + } + } + + public void flushText() { + textList.forEach(e -> g.drawString(font, e.text(), e.x(), e.y(), e.color(), e.shadow())); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/stacked/TextButtonHandle.java b/src/main/java/dev/xkmc/l2core/base/menu/stacked/TextButtonHandle.java new file mode 100644 index 0000000..fede7a6 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/stacked/TextButtonHandle.java @@ -0,0 +1,35 @@ +package dev.xkmc.l2core.base.menu.stacked; + +import dev.xkmc.l2core.base.menu.base.MenuLayoutConfig; +import net.minecraft.network.chat.Component; + +public class TextButtonHandle { + + private final StackedRenderHandle parent; + private final int y; + + private int x; + + + protected TextButtonHandle(StackedRenderHandle parent, int x, int y) { + this.parent = parent; + this.x = x; + this.y = y; + } + + public CellEntry addButton(String btn) { + MenuLayoutConfig.Rect r = parent.sm.parent().getSide(btn); + int y0 = y - (r.h + 1) / 2; + parent.g.blit(MenuLayoutConfig.getTexture(parent.sm.id), x, y0, r.x, r.y, r.w, r.h); + CellEntry c1 = new CellEntry(x, y0, r.w, r.h); + x += r.w + StackedRenderHandle.BTN_X_OFFSET; + return c1; + } + + public void drawText(CellEntry cell, Component text, boolean shadow) { + int x0 = cell.x() + (cell.w() - parent.font.width(text) + 1) / 2; + int y0 = cell.y() + (cell.h() + 1) / 2 - parent.font.lineHeight / 2; + parent.textList.add(new TextEntry(text, x0, y0, parent.text_color, shadow)); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/stacked/TextEntry.java b/src/main/java/dev/xkmc/l2core/base/menu/stacked/TextEntry.java new file mode 100644 index 0000000..821d179 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/stacked/TextEntry.java @@ -0,0 +1,6 @@ +package dev.xkmc.l2core.base.menu.stacked; + +import net.minecraft.network.chat.Component; + +public record TextEntry(Component text, int x, int y, int color, boolean shadow) { +} diff --git a/src/main/java/dev/xkmc/l2core/base/menu/stacked/package-info.java b/src/main/java/dev/xkmc/l2core/base/menu/stacked/package-info.java new file mode 100644 index 0000000..f39e92a --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/menu/stacked/package-info.java @@ -0,0 +1,8 @@ +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault + +package dev.xkmc.l2core.base.menu.stacked; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/base/overlay/InfoSideBar.java b/src/main/java/dev/xkmc/l2core/base/overlay/InfoSideBar.java new file mode 100644 index 0000000..0428260 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/overlay/InfoSideBar.java @@ -0,0 +1,40 @@ +package dev.xkmc.l2core.base.overlay; + +import dev.xkmc.l2core.init.L2LibraryConfig; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.network.chat.Component; +import net.neoforged.neoforge.client.gui.overlay.ExtendedGui; +import net.neoforged.neoforge.client.gui.overlay.IGuiOverlay; + +import java.util.List; + +public abstract class InfoSideBar> extends SideBar implements IGuiOverlay { + + public InfoSideBar(float duration, float ease) { + super(duration, ease); + } + + @Override + public void render(ExtendedGui gui, GuiGraphics g, float partialTick, int width, int height) { + if (!ease(gui.getGuiTicks() + partialTick)) + return; + var text = getText(); + if (text.isEmpty()) return; + int anchor = L2LibraryConfig.CLIENT.infoAnchor.get(); + int y = height * anchor / 2; + int w = (int) (width * L2LibraryConfig.CLIENT.infoMaxWidth.get()); + new TextBox(g, 0, anchor, getXOffset(width), y, w) + .renderLongText(Minecraft.getInstance().font, text); + } + + protected abstract List getText(); + + @Override + protected int getXOffset(int width) { + float progress = (max_ease - ease_time) / max_ease; + return Math.round(-progress * width / 2 + 8); + } + +} + diff --git a/src/main/java/dev/xkmc/l2core/base/overlay/ItemSelSideBar.java b/src/main/java/dev/xkmc/l2core/base/overlay/ItemSelSideBar.java new file mode 100644 index 0000000..2b39dda --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/overlay/ItemSelSideBar.java @@ -0,0 +1,46 @@ +package dev.xkmc.l2core.base.overlay; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.world.item.ItemStack; + +import java.util.List; + +public abstract class ItemSelSideBar> extends SelectionSideBar { + + public ItemSelSideBar(float duration, float ease) { + super(duration, ease); + } + + @Override + protected void renderEntry(Context ctx, ItemStack stack, int i, int selected) { + boolean shift = Minecraft.getInstance().options.keyShift.isDown(); + int y = 18 * i + ctx.y0(); + renderSelection(ctx.g(), ctx.x0(), y, shift ? 127 : 64, isAvailable(stack), selected == i); + if (selected == i) { + if (!stack.isEmpty() && ease_time == max_ease) { + boolean onCenter = onCenter(); + ctx.g().renderTooltip(ctx.font(), stack.getHoverName(), 0, 0); + TextBox box = new TextBox(ctx.g(), onCenter ? 0 : 2, 1, ctx.x0() + (onCenter ? 22 : -6), y + 8, -1); + box.renderLongText(ctx.font(), List.of(stack.getHoverName())); + } + } + ctx.renderItem(stack, ctx.x0(), y); + } + + public void renderSelection(GuiGraphics g, int x, int y, int a, boolean available, boolean selected) { + if (available) { + OverlayUtil.fillRect(g, x, y, 16, 16, color(255, 255, 255, a)); + } else { + OverlayUtil.fillRect(g, x, y, 16, 16, color(255, 0, 0, a)); + } + if (selected) { + OverlayUtil.drawRect(g, x, y, 16, 16, color(255, 170, 0, 255)); + } + } + + public static int color(int r, int g, int b, int a) { + return a << 24 | r << 16 | g << 8 | b; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/overlay/L2TooltipRenderUtil.java b/src/main/java/dev/xkmc/l2core/base/overlay/L2TooltipRenderUtil.java new file mode 100644 index 0000000..50a2c84 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/overlay/L2TooltipRenderUtil.java @@ -0,0 +1,43 @@ +package dev.xkmc.l2core.base.overlay; + +import net.minecraft.client.gui.GuiGraphics; + +public record L2TooltipRenderUtil(GuiGraphics fill, int bg, int bs, int be) { + + public void renderTooltipBackground(int x, int y, int w, int h, int z) { + int x1 = x - 3; + int y1 = y - 3; + int w1 = w + 3 + 3; + int h1 = h + 3 + 3; + renderHorizontalLine(x1, y1 - 1, w1, z, bg); + renderHorizontalLine(x1, y1 + h1, w1, z, bg); + renderRectangle(x1, y1, w1, h1, z, bg); + renderVerticalLine(x1 - 1, y1, h1, z, bg); + renderVerticalLine(x1 + w1, y1, h1, z, bg); + renderFrameGradient(x1, y1 + 1, w1, h1, z, bs, be); + } + + private void renderFrameGradient(int x, int y, int w, int h, int z, int c0, int c1) { + renderVerticalLineGradient(x, y, h - 2, z, c0, c1); + renderVerticalLineGradient(x + w - 1, y, h - 2, z, c0, c1); + renderHorizontalLine(x, y - 1, w, z, c0); + renderHorizontalLine(x, y - 1 + h - 1, w, z, c1); + } + + private void renderVerticalLine(int x, int y, int h, int z, int c) { + fill.fillGradient(x, y, x + 1, y + h, z, c, c); + } + + private void renderVerticalLineGradient(int x, int y, int h, int z, int c0, int c1) { + fill.fillGradient(x, y, x + 1, y + h, z, c0, c1); + } + + private void renderHorizontalLine(int x, int y, int w, int z, int c) { + fill.fillGradient(x, y, x + w, y + 1, z, c, c); + } + + private void renderRectangle(int x, int y, int w, int h, int z, int c) { + fill.fillGradient(x, y, x + w, y + h, z, c, c); + } + +} \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/base/overlay/OverlayUtil.java b/src/main/java/dev/xkmc/l2core/base/overlay/OverlayUtil.java new file mode 100644 index 0000000..e781641 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/overlay/OverlayUtil.java @@ -0,0 +1,105 @@ +package dev.xkmc.l2core.base.overlay; + +import dev.xkmc.l2core.init.L2LibraryConfig; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; +import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipPositioner; +import net.minecraft.client.gui.screens.inventory.tooltip.TooltipRenderUtil; +import net.minecraft.network.chat.Component; +import org.joml.Vector2i; +import org.joml.Vector2ic; + +import java.util.List; + +public class OverlayUtil implements ClientTooltipPositioner { + + private static int getBGColor() { + return (int) (Math.round(L2LibraryConfig.CLIENT.infoAlpha.get() * 255)) << 24 | 0x100010; + } + + public int bg = getBGColor(); + public int bs = 0x505000FF; + public int be = 0x5028007f; + public int tc = 0xFFFFFFFF; + + protected final GuiGraphics g; + protected final int x0, y0, maxW; + + public OverlayUtil(GuiGraphics g, int x0, int y0, int maxW) { + this.g = g; + this.x0 = x0; + this.y0 = y0; + this.maxW = maxW < 0 ? getMaxWidth() : maxW; + } + + public int getMaxWidth() { + return g.guiWidth() / 4; + } + + public void renderLongText(Font font, List list) { + List ans = list.stream().flatMap(text -> font.split(text, maxW).stream()) + .map(ClientTooltipComponent::create).toList(); + renderTooltipInternal(font, ans); + } + + public void renderTooltipInternal(Font font, List list) { + if (list.isEmpty()) return; + int w = 0; + int h = list.size() == 1 ? -2 : 0; + for (ClientTooltipComponent c : list) { + int wi = c.getWidth(font); + if (wi > w) { + w = wi; + } + h += c.getHeight(); + } + int wf = w; + int hf = h; + Vector2ic pos = positionTooltip(g.guiWidth(), g.guiHeight(), x0, y0, wf, hf); + int xf = pos.x(); + int yf = pos.y(); + g.pose().pushPose(); + int z = 400; + g.drawManaged(() -> TooltipRenderUtil.renderTooltipBackground(g, xf, yf, wf, hf, z, bg, bg, bs, be)); + g.pose().translate(0.0F, 0.0F, z); + int yi = yf; + for (int i = 0; i < list.size(); ++i) { + ClientTooltipComponent c = list.get(i); + c.renderText(font, xf, yi, g.pose().last().pose(), g.bufferSource()); + yi += c.getHeight() + (i == 0 ? 2 : 0); + } + yi = yf; + for (int i = 0; i < list.size(); ++i) { + ClientTooltipComponent c = list.get(i); + c.renderImage(font, xf, yi, g); + yi += c.getHeight() + (i == 0 ? 2 : 0); + } + g.pose().popPose(); + } + + @Override + public Vector2ic positionTooltip(int gw, int gh, int x, int y, int tw, int th) { + if (x < 0) x = Math.round(gw / 8f); + if (y < 0) y = Math.round((gh - th) / 2f); + return new Vector2i(x, y); + } + + /** + * specifies outer size + */ + public static void fillRect(GuiGraphics g, int x, int y, int w, int h, int col) { + g.fill(x, y, x + w, y + h, col); + } + + /** + * specifies inner size + */ + public static void drawRect(GuiGraphics g, int x, int y, int w, int h, int col) { + fillRect(g, x - 1, y - 1, w + 2, 1, col); + fillRect(g, x - 1, y - 1, 1, h + 2, col); + fillRect(g, x - 1, y + h, w + 2, 1, col); + fillRect(g, x + w, y - 1, 1, h + 2, col); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/overlay/SelectionSideBar.java b/src/main/java/dev/xkmc/l2core/base/overlay/SelectionSideBar.java new file mode 100644 index 0000000..d005acf --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/overlay/SelectionSideBar.java @@ -0,0 +1,61 @@ +package dev.xkmc.l2core.base.overlay; + +import com.mojang.datafixers.util.Pair; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.world.item.ItemStack; +import net.neoforged.neoforge.client.gui.overlay.ExtendedGui; +import net.neoforged.neoforge.client.gui.overlay.IGuiOverlay; + +import java.util.List; + +public abstract class SelectionSideBar> extends SideBar implements IGuiOverlay { + + public SelectionSideBar(float duration, float ease) { + super(duration, ease); + } + + public abstract Pair, Integer> getItems(); + + public abstract boolean isAvailable(T t); + + public abstract boolean onCenter(); + + public void initRender() { + + } + + @Override + public void render(ExtendedGui gui, GuiGraphics g, float partialTick, int width, int height) { + if (!ease(gui.getGuiTicks() + partialTick)) + return; + initRender(); + gui.setupOverlayRenderState(true, false); + int x0 = this.getXOffset(width); + int y0 = this.getYOffset(height); + Context ctx = new Context(gui, g, partialTick, Minecraft.getInstance().font, x0, y0); + renderContent(ctx); + } + + public void renderContent(Context ctx) { + Pair, Integer> content = getItems(); + var list = content.getFirst(); + for (int i = 0; i < list.size(); i++) { + renderEntry(ctx, list.get(i), i, content.getSecond()); + } + } + + protected abstract void renderEntry(Context ctx, T t, int index, int select); + + public record Context(ExtendedGui gui, GuiGraphics g, float pTick, Font font, int x0, int y0) { + + public void renderItem(ItemStack stack, int x, int y) { + if (!stack.isEmpty()) { + g.renderItem(stack, x, y); + g.renderItemDecorations(font, stack, x, y); + } + } + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/overlay/SideBar.java b/src/main/java/dev/xkmc/l2core/base/overlay/SideBar.java new file mode 100644 index 0000000..6d859d5 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/overlay/SideBar.java @@ -0,0 +1,95 @@ +package dev.xkmc.l2core.base.overlay; + +import net.minecraft.client.Minecraft; + +import javax.annotation.Nullable; + +public abstract class SideBar> { + + public interface Signature> { + + boolean shouldRefreshIdle(SideBar sideBar, @Nullable S old); + + } + + public record IntSignature(int val) implements Signature { + + @Override + public boolean shouldRefreshIdle(SideBar sideBar, @Nullable SideBar.IntSignature old) { + return old == null || val != old.val; + } + + } + + protected final float max_time; + protected final float max_ease; + + @Nullable + protected S prev; + + protected float idle = 0; + protected float ease_time = 0; + protected float prev_time = -1; + + public SideBar(float duration, float ease) { + this.max_time = duration; + this.max_ease = ease; + } + + public abstract S getSignature(); + + public abstract boolean isScreenOn(); + + protected boolean isOnHold() { + return Minecraft.getInstance().options.keyShift.isDown(); + } + + protected boolean ease(float current_time) { + if (!isScreenOn()) { + prev = null; + idle = max_time; + ease_time = 0; + prev_time = -1; + return false; + } + float time_diff = prev_time < 0 ? 0 : (current_time - prev_time); + prev_time = current_time; + + S signature = getSignature(); + if (signature.shouldRefreshIdle(this, prev) || isOnHold()) { + idle = 0; + } else { + idle += time_diff; + } + prev = signature; + if (idle < max_time) { + if (ease_time < max_ease) { + ease_time += time_diff; + if (ease_time > max_ease) { + ease_time = max_ease; + } + } + } else { + if (ease_time > 0) { + ease_time -= time_diff; + if (ease_time < 0) { + ease_time = 0; + } + } + } + return ease_time > 0; + } + + public boolean isRendering() { + return isScreenOn() && ease_time > 0; + } + + protected int getXOffset(int width) { + return 0; + } + + protected int getYOffset(int height) { + return 0; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/overlay/TextBox.java b/src/main/java/dev/xkmc/l2core/base/overlay/TextBox.java new file mode 100644 index 0000000..f86880e --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/overlay/TextBox.java @@ -0,0 +1,30 @@ +package dev.xkmc.l2core.base.overlay; + +import net.minecraft.client.gui.GuiGraphics; +import org.joml.Vector2i; +import org.joml.Vector2ic; + +public class TextBox extends OverlayUtil { + + private final int anchorX, anchorY; + + public TextBox(GuiGraphics g, int anchorX, int anchorY, int x, int y, int width) { + super(g, x, y, width); + this.anchorX = anchorX; + this.anchorY = anchorY; + } + + @Override + public Vector2ic positionTooltip(int gw, int gh, int x, int y, int tw, int th) { + return new Vector2i(x - tw * anchorX / 2, y - th * anchorY / 2); + } + + @Override + public int getMaxWidth() { + if (anchorX == 0) return g.guiWidth() - x0 - 8; + if (anchorX == 1) return Math.max(x0 / 2 - 4, g.guiWidth() - x0 / 2 - 4); + if (anchorX == 2) return x0 - 8; + return g.guiWidth(); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/overlay/package-info.java b/src/main/java/dev/xkmc/l2core/base/overlay/package-info.java new file mode 100644 index 0000000..78715cc --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/overlay/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.base.overlay; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/base/tile/BaseBlockEntity.java b/src/main/java/dev/xkmc/l2core/base/tile/BaseBlockEntity.java new file mode 100644 index 0000000..9532bb3 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/tile/BaseBlockEntity.java @@ -0,0 +1,63 @@ +package dev.xkmc.l2core.base.tile; + +import dev.xkmc.l2core.util.ServerOnly; +import dev.xkmc.l2serial.serialization.SerialClass; +import dev.xkmc.l2serial.serialization.codec.TagCodec; +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.protocol.game.ClientboundBlockEntityDataPacket; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; + +import javax.annotation.ParametersAreNonnullByDefault; + +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +@SerialClass +public class BaseBlockEntity extends BlockEntity { + + public BaseBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state); + } + + @Override + public void load(CompoundTag tag) { + super.load(tag); + if (tag.contains("auto-serial")) + Wrappers.run(() -> TagCodec.fromTag(tag.getCompound("auto-serial"), getClass(), this, f -> true)); + } + + @Override + public void saveAdditional(CompoundTag tag) { + super.saveAdditional(tag); + CompoundTag ser = Wrappers.get(() -> TagCodec.toTag(new CompoundTag(), getClass(), this, f -> true)); + if (ser != null) tag.put("auto-serial", ser); + } + + @Override + public ClientboundBlockEntityDataPacket getUpdatePacket() { + return ClientboundBlockEntityDataPacket.create(this); + } + + @ServerOnly + public void sync() { + if (level != null) { + level.sendBlockUpdated(getBlockPos(), getBlockState(), getBlockState(), 3); + } + } + + /** + * Generate data packet from server to client, called from getUpdatePacket() + */ + @Override + public CompoundTag getUpdateTag() { + CompoundTag ans = super.getUpdateTag(); + CompoundTag ser = Wrappers.get(() -> TagCodec.toTag(new CompoundTag(), getClass(), this, SerialClass.SerialField::toClient)); + if (ser != null) ans.put("auto-serial", ser); + return ans; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/base/tile/BaseContainer.java b/src/main/java/dev/xkmc/l2core/base/tile/BaseContainer.java new file mode 100644 index 0000000..15f6ef1 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/tile/BaseContainer.java @@ -0,0 +1,107 @@ +package dev.xkmc.l2core.base.tile; + +import dev.xkmc.l2serial.serialization.codec.AliasCollection; +import net.minecraft.util.Mth; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.item.ItemStack; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.List; +import java.util.function.Predicate; + +@ParametersAreNonnullByDefault +public class BaseContainer> extends SimpleContainer implements AliasCollection { + + private int max = 64; + private Predicate predicate = e -> true; + + public BaseContainer(int size) { + super(size); + } + + public T setMax(int max) { + this.max = Mth.clamp(max, 1, 64); + return getThis(); + } + + public T setPredicate(Predicate predicate) { + this.predicate = predicate; + return getThis(); + } + + public T add(BaseContainerListener t) { + addListener(t); + return getThis(); + } + + public boolean canAddWhileHaveSpace(ItemStack add, int space) { + int ans = 0; + for (ItemStack stack : items) { + if (stack.isEmpty()) + ans++; + else if (ItemStack.isSameItemSameTags(stack, add) && + stack.getCount() + add.getCount() <= + Math.min(stack.getMaxStackSize(), getMaxStackSize())) { + return true; + } + } + return ans - 1 >= space; + } + + @Override + public boolean canPlaceItem(int slot, ItemStack stack) { + return predicate.test(stack); + } + + @Override + public boolean canAddItem(ItemStack stack) { + return predicate.test(stack) && super.canAddItem(stack); + } + + public boolean canRecipeAddItem(ItemStack stack) { + stack = stack.copy(); + for (ItemStack slot : this.items) { + if (slot.isEmpty() || ItemStack.isSameItemSameTags(slot, stack)) { + int cap = Math.min(getMaxStackSize(), slot.getMaxStackSize()); + int amount = Math.min(stack.getCount(), cap - slot.getCount()); + if (amount > 0) { + stack.shrink(amount); + if (stack.getCount() == 0) { + return true; + } + } + } + } + return false; + } + + @Override + public int getMaxStackSize() { + return max; + } + + @Override + public List getAsList() { + return items; + } + + @Override + public void clear() { + super.clearContent(); + } + + @Override + public void set(int n, int i, ItemStack elem) { + items.set(i, elem); + } + + @Override + public Class getElemClass() { + return ItemStack.class; + } + + @SuppressWarnings("unchecked") + public T getThis() { + return (T) this; + } +} diff --git a/src/main/java/dev/xkmc/l2core/base/tile/BaseContainerListener.java b/src/main/java/dev/xkmc/l2core/base/tile/BaseContainerListener.java new file mode 100644 index 0000000..b2526ac --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/tile/BaseContainerListener.java @@ -0,0 +1,18 @@ +package dev.xkmc.l2core.base.tile; + +import net.minecraft.world.Container; +import net.minecraft.world.ContainerListener; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +public interface BaseContainerListener extends ContainerListener { + + void notifyTile(); + + @SuppressWarnings({"unsafe", "unchecked"}) + @Override + default void containerChanged(Container cont) { + notifyTile(); + } +} diff --git a/src/main/java/dev/xkmc/l2core/base/tile/BaseTank.java b/src/main/java/dev/xkmc/l2core/base/tile/BaseTank.java new file mode 100644 index 0000000..07d946a --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/tile/BaseTank.java @@ -0,0 +1,213 @@ +package dev.xkmc.l2core.base.tile; + +import dev.xkmc.l2serial.serialization.SerialClass; +import dev.xkmc.l2serial.serialization.codec.AliasCollection; +import net.minecraft.core.NonNullList; +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.fluids.capability.IFluidHandler; +import org.jetbrains.annotations.NotNull; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BooleanSupplier; +import java.util.function.Predicate; + +@SerialClass +public class BaseTank implements IFluidHandler, AliasCollection { + + private final int size, capacity; + private final List listeners = new ArrayList<>(); + + private Predicate predicate = e -> true; + private BooleanSupplier allowExtract = () -> true; + + @SerialClass.SerialField + public NonNullList list; + + private int click_max; + + public BaseTank(int size, int capacity) { + this.size = size; + this.capacity = capacity; + list = NonNullList.withSize(size, FluidStack.EMPTY); + } + + public BaseTank add(BaseContainerListener listener) { + listeners.add(listener); + return this; + } + + public BaseTank setPredicate(Predicate predicate) { + this.predicate = predicate; + return this; + } + + public BaseTank setExtract(BooleanSupplier allowExtract) { + this.allowExtract = allowExtract; + return this; + } + + public BaseTank setClickMax(int max) { + this.click_max = max; + return this; + } + + @Override + public int getTanks() { + return size; + } + + @NotNull + @Override + public FluidStack getFluidInTank(int tank) { + return list.get(tank); + } + + @Override + public int getTankCapacity(int tank) { + return capacity; + } + + @Override + public boolean isFluidValid(int tank, @NotNull FluidStack stack) { + return true; + } + + @Override + public int fill(FluidStack resource, FluidAction action) { + if (resource.isEmpty()) return 0; + if (!predicate.test(resource)) return 0; + int to_fill = click_max == 0 ? resource.getAmount() : resource.getAmount() >= click_max ? click_max : 0; + if (to_fill == 0) return 0; + int filled = 0; + for (int i = 0; i < size; i++) { + FluidStack stack = list.get(i); + if (stack.isFluidEqual(resource)) { + int remain = capacity - stack.getAmount(); + int fill = Math.min(to_fill, remain); + filled += fill; + to_fill -= fill; + if (action == FluidAction.EXECUTE) { + resource.shrink(fill); + stack.grow(fill); + } + } else if (stack.isEmpty()) { + int fill = Math.min(to_fill, capacity); + filled += fill; + to_fill -= fill; + if (action == FluidAction.EXECUTE) { + FluidStack rep = resource.copy(); + rep.setAmount(fill); + list.set(i, rep); + resource.shrink(fill); + } + } + if (resource.isEmpty() || to_fill == 0) break; + } + if (action == FluidAction.EXECUTE && filled > 0) { + setChanged(); + } + return filled; + } + + @NotNull + @Override + public FluidStack drain(FluidStack resource, FluidAction action) { + if (resource.isEmpty()) return resource; + if (!allowExtract.getAsBoolean()) return FluidStack.EMPTY; + int to_drain = resource.getAmount(); + if (click_max > 0) { + if (to_drain < click_max) return FluidStack.EMPTY; + to_drain = click_max; + } + int drained = 0; + for (int i = 0; i < size; i++) { + FluidStack stack = list.get(i); + if (stack.isFluidEqual(resource)) { + int remain = stack.getAmount(); + int drain = Math.min(to_drain, remain); + drained += drain; + to_drain -= drain; + if (action == FluidAction.EXECUTE) { + stack.shrink(drain); + } + } + if (to_drain == 0) break; + } + if (action == FluidAction.EXECUTE && drained > 0) { + setChanged(); + } + FluidStack ans = resource.copy(); + ans.setAmount(drained); + return ans; + } + + @NotNull + @Override + public FluidStack drain(int maxDrain, FluidAction action) { + if (!allowExtract.getAsBoolean()) return FluidStack.EMPTY; + FluidStack ans = null; + int to_drain = maxDrain; + if (click_max > 0) { + if (to_drain < click_max) return FluidStack.EMPTY; + to_drain = click_max; + } + int drained = 0; + for (int i = 0; i < size; i++) { + FluidStack stack = list.get(i); + if (!stack.isEmpty() && (ans == null || stack.isFluidEqual(ans))) { + int remain = stack.getAmount(); + int drain = Math.min(to_drain, remain); + drained += drain; + to_drain -= drain; + if (ans == null) { + ans = stack.copy(); + } + if (action == FluidAction.EXECUTE) { + stack.shrink(drain); + } + } + if (to_drain == 0) break; + } + if (action == FluidAction.EXECUTE && drained > 0) { + setChanged(); + } + if (ans == null) { + return FluidStack.EMPTY; + } + ans.setAmount(drained); + return ans; + } + + public void setChanged() { + listeners.forEach(BaseContainerListener::notifyTile); + } + + @Override + public List getAsList() { + return list; + } + + @Override + public void clear() { + list.clear(); + } + + @Override + public void set(int n, int i, FluidStack elem) { + list.set(i, elem); + } + + @Override + public Class getElemClass() { + return FluidStack.class; + } + + public boolean isEmpty() { + for (FluidStack stack : list) { + if (!stack.isEmpty()) + return false; + } + return true; + } +} diff --git a/src/main/java/dev/xkmc/l2core/base/tile/CombinedTankWrapper.java b/src/main/java/dev/xkmc/l2core/base/tile/CombinedTankWrapper.java new file mode 100644 index 0000000..5b1b332 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/tile/CombinedTankWrapper.java @@ -0,0 +1,182 @@ +package dev.xkmc.l2core.base.tile; + +import com.mojang.datafixers.util.Pair; +import net.neoforged.neoforge.fluids.FluidStack; +import net.neoforged.neoforge.fluids.capability.IFluidHandler; +import net.neoforged.neoforge.items.wrapper.EmptyHandler; + +import java.util.ArrayList; +import java.util.List; + +/** + * from Create + */ +public class CombinedTankWrapper implements IFluidHandler { + + public enum Type { + INSERT, EXTRACT, ALL + } + + private final List> list = new ArrayList<>(); + + protected int[] baseIndex; + protected int tankCount; + protected boolean enforceVariety; + + public CombinedTankWrapper build() { + this.baseIndex = new int[list.size()]; + int index = 0; + for (int i = 0; i < list.size(); i++) { + index += list.get(i).getFirst().getTanks(); + baseIndex[i] = index; + } + this.tankCount = index; + return this; + } + + public CombinedTankWrapper add(Type type, IFluidHandler... handlers) { + for (IFluidHandler handler : handlers) { + list.add(Pair.of(handler, type)); + } + return this; + } + + + protected Iterable fillable() { + return list.stream().filter(e -> e.getSecond() != Type.EXTRACT).map(Pair::getFirst).toList(); + } + + protected Iterable drainable() { + return list.stream().filter(e -> e.getSecond() != Type.INSERT).map(Pair::getFirst).toList(); + } + + public CombinedTankWrapper enforceVariety() { + enforceVariety = true; + return this; + } + + @Override + public int getTanks() { + return tankCount; + } + + @Override + public FluidStack getFluidInTank(int tank) { + int index = getIndexForSlot(tank); + IFluidHandler handler = getHandlerFromIndex(index); + tank = getSlotFromIndex(tank, index); + return handler.getFluidInTank(tank); + } + + @Override + public int getTankCapacity(int tank) { + int index = getIndexForSlot(tank); + IFluidHandler handler = getHandlerFromIndex(index); + int localSlot = getSlotFromIndex(tank, index); + return handler.getTankCapacity(localSlot); + } + + @Override + public boolean isFluidValid(int tank, FluidStack stack) { + int index = getIndexForSlot(tank); + IFluidHandler handler = getHandlerFromIndex(index); + int localSlot = getSlotFromIndex(tank, index); + return handler.isFluidValid(localSlot, stack); + } + + @Override + public int fill(FluidStack resource, FluidAction action) { + if (resource.isEmpty()) + return 0; + + int filled = 0; + resource = resource.copy(); + + boolean fittingHandlerFound = false; + Outer: + for (boolean searchPass : new boolean[]{true, false}) { + for (IFluidHandler iFluidHandler : fillable()) { + + for (int i = 0; i < iFluidHandler.getTanks(); i++) + if (searchPass && iFluidHandler.getFluidInTank(i) + .isFluidEqual(resource)) + fittingHandlerFound = true; + + if (searchPass && !fittingHandlerFound) + continue; + + int filledIntoCurrent = iFluidHandler.fill(resource, action); + resource.shrink(filledIntoCurrent); + filled += filledIntoCurrent; + + if (resource.isEmpty() || fittingHandlerFound || enforceVariety && filledIntoCurrent != 0) + break Outer; + } + } + + return filled; + } + + @Override + public FluidStack drain(FluidStack resource, FluidAction action) { + if (resource.isEmpty()) + return resource; + + FluidStack drained = FluidStack.EMPTY; + resource = resource.copy(); + + for (IFluidHandler iFluidHandler : drainable()) { + FluidStack drainedFromCurrent = iFluidHandler.drain(resource, action); + int amount = drainedFromCurrent.getAmount(); + resource.shrink(amount); + + if (!drainedFromCurrent.isEmpty() && (drained.isEmpty() || drainedFromCurrent.isFluidEqual(drained))) + drained = new FluidStack(drainedFromCurrent.getFluid(), amount + drained.getAmount(), + drainedFromCurrent.getTag()); + if (resource.isEmpty()) + break; + } + + return drained; + } + + @Override + public FluidStack drain(int maxDrain, FluidAction action) { + FluidStack drained = FluidStack.EMPTY; + + for (IFluidHandler iFluidHandler : drainable()) { + FluidStack drainedFromCurrent = iFluidHandler.drain(maxDrain, action); + int amount = drainedFromCurrent.getAmount(); + maxDrain -= amount; + + if (!drainedFromCurrent.isEmpty() && (drained.isEmpty() || drainedFromCurrent.isFluidEqual(drained))) + drained = new FluidStack(drainedFromCurrent.getFluid(), amount + drained.getAmount(), + drainedFromCurrent.getTag()); + if (maxDrain == 0) + break; + } + + return drained; + } + + protected int getIndexForSlot(int slot) { + if (slot < 0) + return -1; + for (int i = 0; i < baseIndex.length; i++) + if (slot - baseIndex[i] < 0) + return i; + return -1; + } + + protected IFluidHandler getHandlerFromIndex(int index) { + if (index < 0 || index >= list.size()) + return (IFluidHandler) EmptyHandler.INSTANCE; + return list.get(index).getFirst(); + } + + protected int getSlotFromIndex(int slot, int index) { + if (index <= 0 || index >= baseIndex.length) + return slot; + return slot - baseIndex[index - 1]; + } +} diff --git a/src/main/java/dev/xkmc/l2core/base/tile/package-info.java b/src/main/java/dev/xkmc/l2core/base/tile/package-info.java new file mode 100644 index 0000000..53a12f3 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/base/tile/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.base.tile; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/capability/attachment/AttachmentDef.java b/src/main/java/dev/xkmc/l2core/capability/attachment/AttachmentDef.java new file mode 100644 index 0000000..3a1664c --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/attachment/AttachmentDef.java @@ -0,0 +1,55 @@ +package dev.xkmc.l2core.capability.attachment; + +import dev.xkmc.l2serial.serialization.codec.TagCodec; +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.nbt.CompoundTag; +import net.neoforged.neoforge.attachment.AttachmentType; +import net.neoforged.neoforge.attachment.IAttachmentHolder; +import net.neoforged.neoforge.attachment.IAttachmentSerializer; + +import java.util.Objects; +import java.util.function.Supplier; + +public class AttachmentDef implements IAttachmentSerializer { + private final Class cls; + private final Supplier sup; + private AttachmentType type; + + public AttachmentDef(Class cls, Supplier sup) { + this.cls = cls; + this.sup = sup; + } + + public AttachmentType type() { + if (type != null) return type; + var builder = AttachmentType.builder(sup); + builder.serialize(this); + if (copyOnDeath()) + builder.copyOnDeath(); + type = builder.build(); + return type; + } + + protected boolean copyOnDeath() { + return false; + } + + @Override + public E read(IAttachmentHolder holder, CompoundTag tag) { + return Objects.requireNonNull(Wrappers.get(() -> TagCodec.fromTag(tag, cls, null, f -> true))); + } + + @Override + public CompoundTag write(E attachment) { + return Objects.requireNonNull(TagCodec.toTag(new CompoundTag(), attachment)); + } + + public Class cls() { + return cls; + } + + public boolean isFor(IAttachmentHolder holder) { + return true; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/attachment/BaseAttachment.java b/src/main/java/dev/xkmc/l2core/capability/attachment/BaseAttachment.java new file mode 100644 index 0000000..07a74a8 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/attachment/BaseAttachment.java @@ -0,0 +1,7 @@ +package dev.xkmc.l2core.capability.attachment; + +public class BaseAttachment { + + + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/attachment/GeneralCapabilityHolder.java b/src/main/java/dev/xkmc/l2core/capability/attachment/GeneralCapabilityHolder.java new file mode 100644 index 0000000..b206ca8 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/attachment/GeneralCapabilityHolder.java @@ -0,0 +1,45 @@ +package dev.xkmc.l2core.capability.attachment; + +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.attachment.IAttachmentHolder; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Predicate; +import java.util.function.Supplier; + +/** + * only entities will be automatically attached by default for efficiency + */ +public class GeneralCapabilityHolder> extends AttachmentDef { + + public static final Map> INTERNAL_MAP = new ConcurrentHashMap<>(); + + public final ResourceLocation id; + public final Class entity_class; + private final Predicate pred; + + + public GeneralCapabilityHolder(ResourceLocation id, Class holder_class, Supplier sup, + Class entity_class, Predicate pred) { + super(holder_class, sup); + this.id = id; + this.entity_class = entity_class; + this.pred = pred; + INTERNAL_MAP.put(id, this); + } + + public T get(E e) { + return e.getData(type()); + } + + public boolean isFor(IAttachmentHolder holder) { + return entity_class.isInstance(holder) && isProper(Wrappers.cast(holder)); + } + + public boolean isProper(E entity) { + return pred.test(entity); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/attachment/GeneralCapabilityTemplate.java b/src/main/java/dev/xkmc/l2core/capability/attachment/GeneralCapabilityTemplate.java new file mode 100644 index 0000000..4acf0e8 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/attachment/GeneralCapabilityTemplate.java @@ -0,0 +1,14 @@ +package dev.xkmc.l2core.capability.attachment; + +import dev.xkmc.l2serial.util.Wrappers; + +public class GeneralCapabilityTemplate> { + + public T getThis() { + return Wrappers.cast(this); + } + + public void tick(E e) { + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/attachment/package-info.java b/src/main/java/dev/xkmc/l2core/capability/attachment/package-info.java new file mode 100644 index 0000000..6a6e966 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/attachment/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.capability.attachment; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/capability/conditionals/ClientDataHandler.java b/src/main/java/dev/xkmc/l2core/capability/conditionals/ClientDataHandler.java new file mode 100644 index 0000000..9858000 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/conditionals/ClientDataHandler.java @@ -0,0 +1,19 @@ +package dev.xkmc.l2core.capability.conditionals; + +import dev.xkmc.l2core.init.L2LibReg; +import dev.xkmc.l2core.util.Proxy; +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.world.entity.player.Player; + +public class ClientDataHandler { + + public static void handle(TokenKey key, T token) { + Player player = Proxy.getClientPlayer(); + if (player == null) return; + ConditionalToken old = L2LibReg.CONDITIONAL.type().get(player).data.put(key, token); + if (token instanceof NetworkSensitiveToken t) { + t.onSync(Wrappers.cast(old), player); + } + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/conditionals/ConditionalData.java b/src/main/java/dev/xkmc/l2core/capability/conditionals/ConditionalData.java new file mode 100644 index 0000000..bb19f61 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/conditionals/ConditionalData.java @@ -0,0 +1,48 @@ +package dev.xkmc.l2core.capability.conditionals; + +import dev.xkmc.l2core.capability.player.PlayerCapabilityTemplate; +import dev.xkmc.l2core.init.L2LibraryConfig; +import dev.xkmc.l2serial.serialization.SerialClass; +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.world.entity.player.Player; + +import javax.annotation.Nullable; +import java.util.HashMap; + +@SerialClass +public class ConditionalData extends PlayerCapabilityTemplate { + + @SerialClass.SerialField + public HashMap, ConditionalToken> data = new HashMap<>(); + @SerialClass.SerialField + public int tickSinceDeath = 0; + + @Override + public void onClone(boolean isWasDeath) { + tickSinceDeath = 0; + } + + public T getOrCreateData(TokenProvider setEffect, C ent) { + return Wrappers.cast(data.computeIfAbsent(setEffect.getKey(), e -> setEffect.getData(ent))); + } + + @Nullable + public T getData(TokenKey setEffect) { + return Wrappers.cast(data.get(setEffect)); + } + + @Override + public void tick(Player player) { + tickSinceDeath++; + if (L2LibraryConfig.SERVER.restoreFullHealthOnRespawn.get() && + tickSinceDeath < 60 && player.getHealth() < player.getMaxHealth()) { + player.setHealth(player.getMaxHealth()); + } + data.entrySet().removeIf(e -> e.getValue().tick(player)); + } + + public boolean hasData(TokenKey eff) { + return data.containsKey(eff); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/conditionals/ConditionalToken.java b/src/main/java/dev/xkmc/l2core/capability/conditionals/ConditionalToken.java new file mode 100644 index 0000000..0084709 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/conditionals/ConditionalToken.java @@ -0,0 +1,16 @@ +package dev.xkmc.l2core.capability.conditionals; + +import dev.xkmc.l2serial.serialization.SerialClass; +import net.minecraft.world.entity.player.Player; + +@SerialClass +public class ConditionalToken { + + /** + * return true to remove + */ + public boolean tick(Player player) { + return false; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/conditionals/Context.java b/src/main/java/dev/xkmc/l2core/capability/conditionals/Context.java new file mode 100644 index 0000000..426f5c9 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/conditionals/Context.java @@ -0,0 +1,4 @@ +package dev.xkmc.l2core.capability.conditionals; + +public interface Context { +} diff --git a/src/main/java/dev/xkmc/l2core/capability/conditionals/NetworkSensitiveToken.java b/src/main/java/dev/xkmc/l2core/capability/conditionals/NetworkSensitiveToken.java new file mode 100644 index 0000000..b03de89 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/conditionals/NetworkSensitiveToken.java @@ -0,0 +1,16 @@ +package dev.xkmc.l2core.capability.conditionals; + +import dev.xkmc.l2core.init.L2Core; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; + +import javax.annotation.Nullable; + +public interface NetworkSensitiveToken { + void onSync(@Nullable T old, Player player); + + default void sync(TokenKey key, T token, ServerPlayer sp) { + L2Core.PACKET_HANDLER.toClientPlayer(TokenToClient.of(key, token), sp); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/conditionals/TokenKey.java b/src/main/java/dev/xkmc/l2core/capability/conditionals/TokenKey.java new file mode 100644 index 0000000..d03f6c1 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/conditionals/TokenKey.java @@ -0,0 +1,15 @@ +package dev.xkmc.l2core.capability.conditionals; + +import net.minecraft.resources.ResourceLocation; + +public record TokenKey(String type, String id) { + + public static TokenKey of(ResourceLocation id) { + return new TokenKey<>(id.getNamespace(), id.getPath()); + } + + public ResourceLocation asLocation() { + return new ResourceLocation(type, id); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/conditionals/TokenProvider.java b/src/main/java/dev/xkmc/l2core/capability/conditionals/TokenProvider.java new file mode 100644 index 0000000..2165214 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/conditionals/TokenProvider.java @@ -0,0 +1,8 @@ +package dev.xkmc.l2core.capability.conditionals; + +public interface TokenProvider { + + T getData(C ent); + + TokenKey getKey(); +} diff --git a/src/main/java/dev/xkmc/l2core/capability/conditionals/TokenToClient.java b/src/main/java/dev/xkmc/l2core/capability/conditionals/TokenToClient.java new file mode 100644 index 0000000..4e12cbc --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/conditionals/TokenToClient.java @@ -0,0 +1,20 @@ +package dev.xkmc.l2core.capability.conditionals; + +import dev.xkmc.l2serial.network.SerialPacketBase; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.Nullable; + +public record TokenToClient(ResourceLocation id, ConditionalToken token) + implements SerialPacketBase { + + public static TokenToClient of(TokenKey key, T token) { + return new TokenToClient(key.asLocation(), token); + } + + @Override + public void handle(@Nullable Player player) { + ClientDataHandler.handle(TokenKey.of(id), token); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/conditionals/package-info.java b/src/main/java/dev/xkmc/l2core/capability/conditionals/package-info.java new file mode 100644 index 0000000..11ae4e9 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/conditionals/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.capability.conditionals; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/capability/level/BaseSavedData.java b/src/main/java/dev/xkmc/l2core/capability/level/BaseSavedData.java new file mode 100644 index 0000000..754524d --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/level/BaseSavedData.java @@ -0,0 +1,24 @@ +package dev.xkmc.l2core.capability.level; + +import dev.xkmc.l2serial.serialization.SerialClass; +import dev.xkmc.l2serial.serialization.codec.TagCodec; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.level.saveddata.SavedData; + +@SerialClass +public class BaseSavedData extends SavedData { + + @Override + public CompoundTag save(CompoundTag tag) { + TagCodec.toTag(tag, this); + return tag; + } + + @Override + public boolean isDirty() { + return true; + } + + + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/level/package-info.java b/src/main/java/dev/xkmc/l2core/capability/level/package-info.java new file mode 100644 index 0000000..0675df0 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/level/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.capability.level; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/capability/player/ClientSyncHandler.java b/src/main/java/dev/xkmc/l2core/capability/player/ClientSyncHandler.java new file mode 100644 index 0000000..095bbaf --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/player/ClientSyncHandler.java @@ -0,0 +1,15 @@ +package dev.xkmc.l2core.capability.player; + +import dev.xkmc.l2core.util.Proxy; +import dev.xkmc.l2serial.serialization.SerialClass; +import dev.xkmc.l2serial.serialization.codec.PacketCodec; + +import java.util.function.Predicate; + +public class ClientSyncHandler { + + public static > void parse(byte[] tag, PlayerCapabilityHolder holder, Predicate pred) { + PacketCodec.fromBytes(tag, holder.cls(), Proxy.getClientPlayer().getData(holder.type()), pred); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapToClient.java b/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapToClient.java new file mode 100644 index 0000000..56ae49c --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapToClient.java @@ -0,0 +1,44 @@ +package dev.xkmc.l2core.capability.player; + +import dev.xkmc.l2serial.network.SerialPacketBase; +import dev.xkmc.l2serial.serialization.SerialClass; +import dev.xkmc.l2serial.serialization.codec.PacketCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.UUID; +import java.util.function.Predicate; + +@SerialClass +public record PlayerCapToClient(Action action, ResourceLocation holderID, byte[] data, UUID playerID) + implements SerialPacketBase { + + public static > PlayerCapToClient + of(ServerPlayer player, Action action, PlayerCapabilityHolder holder, T handler) { + return new PlayerCapToClient(action, holder.id, + PacketCodec.toBytes(handler, holder.cls(), action.pred), + player.getUUID()); + } + + @Override + public void handle(@Nullable Player player) { + ClientSyncHandler.parse(data, PlayerCapabilityHolder.INTERNAL_MAP.get(holderID), action.pred); + } + + public enum Action { + ALL(e -> true), + CLIENT(SerialClass.SerialField::toClient), + TRACK(SerialClass.SerialField::toTracking), + ; + public final Predicate pred; + + Action(Predicate pred) { + this.pred = pred; + } + + + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapabilityHolder.java b/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapabilityHolder.java new file mode 100644 index 0000000..e699a7b --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapabilityHolder.java @@ -0,0 +1,33 @@ +package dev.xkmc.l2core.capability.player; + +import dev.xkmc.l2core.capability.attachment.GeneralCapabilityHolder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Supplier; + +public class PlayerCapabilityHolder> extends GeneralCapabilityHolder { + + public static final Map> INTERNAL_MAP = new ConcurrentHashMap<>(); + + public final PlayerCapabilityNetworkHandler network; + + public PlayerCapabilityHolder(ResourceLocation id, Class cls, Supplier sup, NetworkFactory network) { + super(id, cls, sup, Player.class, e -> true); + this.network = network.create(this); + INTERNAL_MAP.put(id, this); + } + + protected boolean copyOnDeath() { + return true; + } + + public interface NetworkFactory> { + + PlayerCapabilityNetworkHandler create(PlayerCapabilityHolder holder); + + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapabilityNetworkHandler.java b/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapabilityNetworkHandler.java new file mode 100644 index 0000000..dd972d9 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapabilityNetworkHandler.java @@ -0,0 +1,26 @@ +package dev.xkmc.l2core.capability.player; + +import dev.xkmc.l2core.init.L2Core; +import net.minecraft.server.level.ServerPlayer; + +public class PlayerCapabilityNetworkHandler> { + + public final PlayerCapabilityHolder holder; + + public PlayerCapabilityNetworkHandler(PlayerCapabilityHolder holder) { + this.holder = holder; + } + + public void toClient(ServerPlayer e) { + L2Core.PACKET_HANDLER.toClientPlayer(PlayerCapToClient.of(e, PlayerCapToClient.Action.CLIENT, holder, holder.get(e)), e); + } + + public void toTracking(ServerPlayer e) { + L2Core.PACKET_HANDLER.toTrackingOnly(PlayerCapToClient.of(e, PlayerCapToClient.Action.TRACK, holder, holder.get(e)), e); + } + + public void startTracking(ServerPlayer tracker, ServerPlayer target) { + L2Core.PACKET_HANDLER.toClientPlayer(PlayerCapToClient.of(target, PlayerCapToClient.Action.TRACK, holder, holder.get(target)), tracker); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapabilityTemplate.java b/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapabilityTemplate.java new file mode 100644 index 0000000..184be2b --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/player/PlayerCapabilityTemplate.java @@ -0,0 +1,17 @@ +package dev.xkmc.l2core.capability.player; + +import dev.xkmc.l2core.capability.attachment.GeneralCapabilityTemplate; +import dev.xkmc.l2serial.serialization.SerialClass; +import net.minecraft.world.entity.player.Player; + +@SerialClass +public class PlayerCapabilityTemplate> extends GeneralCapabilityTemplate { + + public void init(Player player) { + } + + public void onClone(boolean isWasDeath) { + + } + +} diff --git a/src/main/java/dev/xkmc/l2core/capability/player/package-info.java b/src/main/java/dev/xkmc/l2core/capability/player/package-info.java new file mode 100644 index 0000000..4fa17f6 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/capability/player/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.capability.player; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/compat/curios/CurioEntityBuilder.java b/src/main/java/dev/xkmc/l2core/compat/curios/CurioEntityBuilder.java new file mode 100644 index 0000000..1e82b34 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/compat/curios/CurioEntityBuilder.java @@ -0,0 +1,11 @@ +package dev.xkmc.l2core.compat.curios; + +import net.minecraft.resources.ResourceLocation; + +import java.util.ArrayList; + +public record CurioEntityBuilder( + ArrayList entities, + ArrayList slots, + ArrayList conditions) { +} diff --git a/src/main/java/dev/xkmc/l2core/compat/curios/CurioSlotBuilder.java b/src/main/java/dev/xkmc/l2core/compat/curios/CurioSlotBuilder.java new file mode 100644 index 0000000..9c0d62a --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/compat/curios/CurioSlotBuilder.java @@ -0,0 +1,28 @@ +package dev.xkmc.l2core.compat.curios; + +import java.util.ArrayList; + +import static dev.xkmc.l2core.compat.curios.CurioSlotBuilder.Operation.SET; + +public record CurioSlotBuilder(int order, String icon, int size, + Operation operation, + boolean add_cosmetic, + boolean use_native_gui, + boolean render_toggle, + boolean replace, ArrayList conditions) { + + public CurioSlotBuilder(int order, String icon) { + this(order, icon, 1, SET); + } + + public CurioSlotBuilder(int order, String icon, int size, + Operation operation) { + this(order, icon, size, operation, + false, true, true, false, SlotCondition.of()); + } + + public enum Operation { + SET, ADD, REMOVE + } + +} diff --git a/src/main/java/dev/xkmc/l2core/compat/curios/SlotCondition.java b/src/main/java/dev/xkmc/l2core/compat/curios/SlotCondition.java new file mode 100644 index 0000000..cd647d7 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/compat/curios/SlotCondition.java @@ -0,0 +1,19 @@ +package dev.xkmc.l2core.compat.curios; + +import java.util.ArrayList; + +public record SlotCondition(String type, String modid) { + + public static ArrayList of(String... ids) { + ArrayList ans = new ArrayList<>(); + for (String id : ids) { + ans.add(new SlotCondition(id)); + } + return ans; + } + + public SlotCondition(String modid) { + this("forge:mod_loaded", modid); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/compat/curios/package-info.java b/src/main/java/dev/xkmc/l2core/compat/curios/package-info.java new file mode 100644 index 0000000..bc47f29 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/compat/curios/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.compat.curios; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/init/L2Core.java b/src/main/java/dev/xkmc/l2core/init/L2Core.java new file mode 100644 index 0000000..cc3cc34 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/L2Core.java @@ -0,0 +1,47 @@ +package dev.xkmc.l2core.init; + +import dev.xkmc.l2core.base.effects.EffectToClient; +import dev.xkmc.l2core.capability.conditionals.TokenToClient; +import dev.xkmc.l2core.capability.player.PlayerCapToClient; +import dev.xkmc.l2core.serial.config.SyncPacket; +import dev.xkmc.l2serial.network.PacketHandler; +import dev.xkmc.l2serial.serialization.custom_handler.Handlers; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.EventBusSubscriber; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlersEvent; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import static dev.xkmc.l2serial.network.PacketHandler.NetDir.PLAY_TO_CLIENT; + +// The value here should match an entry in the META-INF/mods.toml file +@Mod(L2Core.MODID) +@EventBusSubscriber(modid = L2Core.MODID, bus = EventBusSubscriber.Bus.MOD) +public class L2Core { + + public static final String MODID = "l2core"; + public static final Logger LOGGER = LogManager.getLogger(); + + // TODO public static final L2Registrate REGISTRATE = new L2Registrate(MODID); + + public static final PacketHandler PACKET_HANDLER = new PacketHandler(MODID, 1, + e -> e.create(SyncPacket.class, PLAY_TO_CLIENT), + e -> e.create(EffectToClient.class, PLAY_TO_CLIENT), + e -> e.create(PlayerCapToClient.class, PLAY_TO_CLIENT), + e -> e.create(TokenToClient.class, PLAY_TO_CLIENT) + ); + + public L2Core(IEventBus bus) { + Handlers.register(); + L2LibReg.register(bus); + L2LibraryConfig.init(); + } + + @SubscribeEvent + public static void onPacketReg(RegisterPayloadHandlersEvent event) { + PACKET_HANDLER.register(event); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/L2LibReg.java b/src/main/java/dev/xkmc/l2core/init/L2LibReg.java new file mode 100644 index 0000000..7cc0a89 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/L2LibReg.java @@ -0,0 +1,49 @@ +package dev.xkmc.l2core.init; + +import dev.xkmc.l2core.base.effects.ClientEffectCap; +import dev.xkmc.l2core.base.menu.base.MenuLayoutConfig; +import dev.xkmc.l2core.capability.conditionals.ConditionalData; +import dev.xkmc.l2core.capability.player.PlayerCapabilityNetworkHandler; +import dev.xkmc.l2core.init.reg.datapack.DatapackReg; +import dev.xkmc.l2core.init.reg.simple.*; +import dev.xkmc.l2core.serial.conditions.*; +import dev.xkmc.l2core.serial.ingredients.EnchantmentIngredient; +import dev.xkmc.l2core.serial.ingredients.MobEffectIngredient; +import dev.xkmc.l2core.serial.ingredients.PotionIngredient; +import net.minecraft.world.entity.LivingEntity; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.neoforge.common.conditions.ICondition; +import net.neoforged.neoforge.registries.NeoForgeRegistries; + +public class L2LibReg { + + public static final Reg REG = new Reg(L2Core.MODID); + + // ingredients + public static final IngReg INGREDIENT = IngReg.of(REG); + public static final IngVal ING_ENCH = INGREDIENT.reg("enchantment", EnchantmentIngredient.class); + public static final IngVal ING_POTION = INGREDIENT.reg("potion", PotionIngredient.class); + public static final IngVal ING_EFF = INGREDIENT.reg("mob_effect", MobEffectIngredient.class); + + // conditions + public static final CdcReg CONDITION = CdcReg.of(REG, NeoForgeRegistries.CONDITION_SERIALIZERS); + public static final CdcVal CONDITION_BOOL = CONDITION.reg("bool_config", BooleanValueCondition.class); + public static final CdcVal CONDITION_INT = CONDITION.reg("int_config", IntValueCondition.class); + public static final CdcVal CONDITION_DOUBLE = CONDITION.reg("double_config", DoubleValueCondition.class); + public static final CdcVal CONDITION_STR = CONDITION.reg("string_config", StringValueCondition.class); + public static final CdcVal CONDITION_LIST_STR = CONDITION.reg("string_list_config", ListStringValueCondition.class); + + // attachments + public static final AttReg ATTACHMENT = AttReg.of(REG); + public static final AttVal.CapVal EFFECT = ATTACHMENT.entity("effect", + ClientEffectCap.class, ClientEffectCap::new, LivingEntity.class, e -> e.level().isClientSide()); + public static final AttVal.PlayerVal CONDITIONAL = ATTACHMENT.player("conditionals", + ConditionalData.class, ConditionalData::new, PlayerCapabilityNetworkHandler::new); + + public static final DatapackReg MENU_LAYOUT = REG.dataReg("menu_layout", MenuLayoutConfig.class); + + public static void register(IEventBus bus) { + REG.register(bus); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/L2LibraryClient.java b/src/main/java/dev/xkmc/l2core/init/L2LibraryClient.java new file mode 100644 index 0000000..0429579 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/L2LibraryClient.java @@ -0,0 +1,6 @@ +package dev.xkmc.l2core.init; + +//@Mod.EventBusSubscriber(value = Dist.CLIENT, path = L2Library.MODID, bus = Mod.EventBusSubscriber.Bus.MOD) +public class L2LibraryClient { + +} diff --git a/src/main/java/dev/xkmc/l2core/init/L2LibraryConfig.java b/src/main/java/dev/xkmc/l2core/init/L2LibraryConfig.java new file mode 100644 index 0000000..2894cad --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/L2LibraryConfig.java @@ -0,0 +1,81 @@ +package dev.xkmc.l2core.init; + +import net.neoforged.fml.ModLoadingContext; +import net.neoforged.fml.config.IConfigSpec; +import net.neoforged.fml.config.ModConfig; +import net.neoforged.neoforge.common.ModConfigSpec; +import org.apache.commons.lang3.tuple.Pair; + +public class L2LibraryConfig { + + public static class Client { + + public final ModConfigSpec.DoubleValue infoAlpha; + public final ModConfigSpec.IntValue infoAnchor; + public final ModConfigSpec.DoubleValue infoMaxWidth; + + public final ModConfigSpec.BooleanValue selectionDisplayRequireShift; + public final ModConfigSpec.BooleanValue selectionScrollRequireShift; + + + Client(ModConfigSpec.Builder builder) { + infoAlpha = builder.comment("Info background transparency. 1 means opaque.") + .defineInRange("infoAlpha", 0.5, 0, 1); + infoAnchor = builder.comment("Info alignment. 0 means top. 1 means middle. 2 means bottom.") + .defineInRange("infoAnchor", 1, 0, 2); + infoMaxWidth = builder.comment("Info max width. 0.5 means half screen. default: 0.3") + .defineInRange("infoMaxWidth", 0.3, 0, 0.5); + + selectionDisplayRequireShift = builder.comment("Render Selection only when pressing shift") + .define("selectionDisplayRequireShift", false); + selectionScrollRequireShift = builder.comment("Scroll for selection only when pressing shift") + .define("selectionScrollRequireShift", true); + + + } + + } + + public static class Server { + + public final ModConfigSpec.BooleanValue restoreFullHealthOnRespawn; + + Server(ModConfigSpec.Builder builder) { + restoreFullHealthOnRespawn = builder.comment("Restore full health on respawn") + .define("restoreFullHealthOnRespawn", true); + } + + } + + public static final ModConfigSpec CLIENT_SPEC; + public static final Client CLIENT; + + public static final ModConfigSpec SERVER_SPEC; + public static final Server SERVER; + + static { + final Pair client = new ModConfigSpec.Builder().configure(Client::new); + CLIENT_SPEC = client.getRight(); + CLIENT = client.getLeft(); + + final Pair server = new ModConfigSpec.Builder().configure(Server::new); + SERVER_SPEC = server.getRight(); + SERVER = server.getLeft(); + } + + /** + * Registers any relevant listeners for config + */ + public static void init() { + register(ModConfig.Type.CLIENT, CLIENT_SPEC); + register(ModConfig.Type.SERVER, SERVER_SPEC); + } + + private static void register(ModConfig.Type type, IConfigSpec spec) { + var mod = ModLoadingContext.get().getActiveContainer(); + String path = "l2_configs/" + mod.getModId() + "-" + type.extension() + ".toml"; + ModLoadingContext.get().registerConfig(type, spec, path); + } + + +} diff --git a/src/main/java/dev/xkmc/l2core/init/events/BaseCapabilityEvents.java b/src/main/java/dev/xkmc/l2core/init/events/BaseCapabilityEvents.java new file mode 100644 index 0000000..b28f606 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/events/BaseCapabilityEvents.java @@ -0,0 +1,57 @@ +package dev.xkmc.l2core.init.events; + +import dev.xkmc.l2core.capability.attachment.GeneralCapabilityHolder; +import dev.xkmc.l2core.capability.player.PlayerCapabilityHolder; +import dev.xkmc.l2core.init.L2Core; +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.server.level.ServerPlayer; +import net.neoforged.bus.api.EventPriority; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.event.entity.living.LivingEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; + +@Mod.EventBusSubscriber(modid = L2Core.MODID, bus = Mod.EventBusSubscriber.Bus.FORGE) +public class BaseCapabilityEvents { + + @SubscribeEvent + public static void onLivingTick(LivingEvent.LivingTickEvent event) { + if (event.getEntity().isAlive()) { + for (GeneralCapabilityHolder holder : GeneralCapabilityHolder.INTERNAL_MAP.values()) { + if (holder.isFor(event.getEntity())) + holder.get(Wrappers.cast(event.getEntity())).tick(Wrappers.cast(event.getEntity())); + } + } + } + + @SubscribeEvent(priority = EventPriority.LOW) + public static void onPlayerClone(PlayerEvent.Clone event) { + for (PlayerCapabilityHolder holder : PlayerCapabilityHolder.INTERNAL_MAP.values()) { + ServerPlayer e = (ServerPlayer) event.getEntity(); + holder.get(e).onClone(event.isWasDeath()); + holder.network.toClient(e); + holder.network.toTracking(e); + } + } + + @SubscribeEvent + public static void onServerPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) { + ServerPlayer e = (ServerPlayer) event.getEntity(); + if (e != null) { + for (PlayerCapabilityHolder holder : PlayerCapabilityHolder.INTERNAL_MAP.values()) { + holder.get(e).init(e); + holder.network.toClient(e); + holder.network.toTracking(e); + } + } + } + + @SubscribeEvent + public static void onStartTracking(PlayerEvent.StartTracking event) { + for (PlayerCapabilityHolder holder : PlayerCapabilityHolder.INTERNAL_MAP.values()) { + if (!(event.getTarget() instanceof ServerPlayer e)) continue; + holder.network.startTracking((ServerPlayer) event.getEntity(), e); + } + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/events/BaseJsonReloadListener.java b/src/main/java/dev/xkmc/l2core/init/events/BaseJsonReloadListener.java new file mode 100644 index 0000000..cfc5fa4 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/events/BaseJsonReloadListener.java @@ -0,0 +1,31 @@ +package dev.xkmc.l2core.init.events; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonElement; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener; +import net.minecraft.util.profiling.ProfilerFiller; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.Map; +import java.util.function.Consumer; + +@ParametersAreNonnullByDefault +public class BaseJsonReloadListener extends SimpleJsonResourceReloadListener { + + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create(); + + private final Consumer> consumer; + + public BaseJsonReloadListener(String path, Consumer> consumer) { + super(GSON, path); + this.consumer = consumer; + } + + @Override + protected void apply(Map map, ResourceManager manager, ProfilerFiller profiler) { + consumer.accept(map); + } +} diff --git a/src/main/java/dev/xkmc/l2core/init/events/ClientEffectRenderEvents.java b/src/main/java/dev/xkmc/l2core/init/events/ClientEffectRenderEvents.java new file mode 100644 index 0000000..f3d7dc4 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/events/ClientEffectRenderEvents.java @@ -0,0 +1,206 @@ +package dev.xkmc.l2core.init.events; + +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.DefaultVertexFormat; +import com.mojang.blaze3d.vertex.PoseStack; +import com.mojang.blaze3d.vertex.VertexConsumer; +import com.mojang.blaze3d.vertex.VertexFormat; +import com.mojang.datafixers.util.Pair; +import dev.xkmc.l2core.base.effects.ClientEffectCap; +import dev.xkmc.l2core.base.effects.EffectToClient; +import dev.xkmc.l2core.base.effects.api.ClientRenderEffect; +import dev.xkmc.l2core.base.effects.api.DelayedEntityRender; +import dev.xkmc.l2core.base.effects.api.FirstPlayerRenderEffect; +import dev.xkmc.l2core.base.effects.api.IconRenderRegion; +import dev.xkmc.l2core.init.L2Core; +import dev.xkmc.l2core.init.L2LibReg; +import dev.xkmc.l2core.util.Proxy; +import net.minecraft.client.Camera; +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.AbstractClientPlayer; +import net.minecraft.client.renderer.LevelRenderer; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.RenderStateShard; +import net.minecraft.client.renderer.RenderType; +import net.minecraft.client.renderer.entity.EntityRenderDispatcher; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.effect.MobEffectInstance; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.LivingEntity; +import net.minecraft.world.phys.Vec3; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.client.event.RenderLevelStageEvent; +import net.neoforged.neoforge.client.event.RenderLivingEvent; +import net.neoforged.neoforge.event.TickEvent; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Mod.EventBusSubscriber(value = Dist.CLIENT, modid = L2Core.MODID, bus = Mod.EventBusSubscriber.Bus.FORGE) +public class ClientEffectRenderEvents { + + private static final ArrayList ICONS = new ArrayList<>(); + + @SubscribeEvent + public static void clientTick(TickEvent.ClientTickEvent event) { + if (event.phase != TickEvent.Phase.END) return; + AbstractClientPlayer player = Proxy.getClientPlayer(); + if (player != null) { + for (Map.Entry entry : player.getActiveEffectsMap().entrySet()) { + if (entry.getKey() instanceof FirstPlayerRenderEffect effect) { + effect.onClientLevelRender(player, entry.getValue()); + } + } + } + } + + @SubscribeEvent + public static void levelRenderLast(RenderLevelStageEvent event) { + if (event.getStage() != RenderLevelStageEvent.Stage.AFTER_WEATHER) return; + LevelRenderer renderer = event.getLevelRenderer(); + MultiBufferSource.BufferSource buffers = Minecraft.getInstance().renderBuffers().bufferSource(); + Camera camera = Minecraft.getInstance().gameRenderer.getMainCamera(); + PoseStack stack = event.getPoseStack(); + + // cache the previous handler + PoseStack posestack = RenderSystem.getModelViewStack(); + var last = posestack.last(); + posestack.popPose(); + RenderSystem.applyModelViewMatrix(); + + RenderSystem.disableDepthTest(); + for (DelayedEntityRender icon : ICONS) { + renderIcon(stack, buffers, icon, event.getPartialTick(), camera, renderer.entityRenderDispatcher); + } + buffers.endBatch(); + + // restore the previous handler + posestack.pushPose(); + posestack.setIdentity(); + posestack.last().pose().mul(last.pose()); + posestack.last().normal().mul(last.normal()); + RenderSystem.applyModelViewMatrix(); + + RenderSystem.enableDepthTest(); + + ICONS.clear(); + } + + @SubscribeEvent + public static void onLivingEntityRender(RenderLivingEvent.Post event) { + LivingEntity entity = event.getEntity(); + if (!L2LibReg.EFFECT.type().isProper(entity)) return; + if (entity.getTags().contains("ClientOnly")) return; + ClientEffectCap cap = L2LibReg.EFFECT.type().get(entity); + List> l0 = new ArrayList<>(); + for (Map.Entry entry : cap.map.entrySet()) { + if (entry.getKey() instanceof ClientRenderEffect effect) { + l0.add(Pair.of(effect, entry.getValue())); + } + } + if (l0.isEmpty()) return; + List> l1 = new ArrayList<>(); + int index = 0; + + for (var e : l0) { + int lv = e.getSecond(); + int size = l1.size(); + int I = index; + e.getFirst().render(entity, lv, x -> l1.add(Pair.of(I, x))); + if (l1.size() > size) { + index++; + } + } + + + int n = index; + int w = (int) Math.ceil(Math.sqrt(n)); + int h = (int) Math.ceil(n * 1d / w); + + for (var e : l1) { + int i = e.getFirst(); + int iy = i / w; + int iw = Math.min(w, n - iy * w); + int ix = i - iy * w; + ICONS.add(e.getSecond().resize(IconRenderRegion.of(w, ix, iy, iw, h))); + } + + } + + private static void renderIcon(PoseStack pose, MultiBufferSource buffer, DelayedEntityRender icon, + float partial, Camera camera, EntityRenderDispatcher dispatcher) { + LivingEntity entity = icon.entity(); + float f = entity.getBbHeight() / 2; + + double x0 = Mth.lerp(partial, entity.xOld, entity.getX()); + double y0 = Mth.lerp(partial, entity.yOld, entity.getY()); + double z0 = Mth.lerp(partial, entity.zOld, entity.getZ()); + Vec3 offset = dispatcher.getRenderer(entity).getRenderOffset(entity, partial); + Vec3 cam_pos = camera.getPosition(); + double d2 = x0 - cam_pos.x + offset.x(); + double d3 = y0 - cam_pos.y + offset.y(); + double d0 = z0 - cam_pos.z + offset.z(); + + pose.pushPose(); + pose.translate(d2, d3 + f, d0); + pose.mulPose(camera.rotation()); + PoseStack.Pose entry = pose.last(); + VertexConsumer ivertexbuilder = buffer.getBuffer(get2DIcon(icon.rl())); + + float ix0 = -0.5f + icon.region().x(); + float ix1 = ix0 + icon.region().scale(); + float iy0 = -0.5f + icon.region().y(); + float iy1 = iy0 + icon.region().scale(); + float u0 = icon.tx(); + float v0 = icon.ty(); + float u1 = icon.tx() + icon.tw(); + float v1 = icon.ty() + icon.th(); + + iconVertex(entry, ivertexbuilder, ix1, iy0, u0, v1); + iconVertex(entry, ivertexbuilder, ix0, iy0, u1, v1); + iconVertex(entry, ivertexbuilder, ix0, iy1, u1, v0); + iconVertex(entry, ivertexbuilder, ix1, iy1, u0, v0); + pose.popPose(); + } + + private static void iconVertex(PoseStack.Pose entry, VertexConsumer builder, float x, float y, float u, float v) { + builder.vertex(entry.pose(), x, y, 0) + .uv(u, v) + .normal(entry.normal(), 0.0F, 1.0F, 0.0F) + .endVertex(); + } + + public static RenderType get2DIcon(ResourceLocation rl) { + return RenderType.create( + "entity_body_icon", + DefaultVertexFormat.POSITION_TEX, + VertexFormat.Mode.QUADS, 256, false, true, + RenderType.CompositeState.builder() + .setShaderState(RenderStateShard.RENDERTYPE_ENTITY_GLINT_SHADER) + .setTextureState(new RenderStateShard.TextureStateShard(rl, false, false)) + .setTransparencyState(RenderStateShard.ADDITIVE_TRANSPARENCY) + .setDepthTestState(RenderStateShard.NO_DEPTH_TEST) + .createCompositeState(false) + ); + } + + public static void sync(EffectToClient eff) { + if (Minecraft.getInstance().level == null) return; + Entity e = Minecraft.getInstance().level.getEntity(eff.entity()); + if (!(e instanceof LivingEntity le)) return; + if (!L2LibReg.EFFECT.type().isProper(le)) return; + ClientEffectCap cap = L2LibReg.EFFECT.type().get(le); + if (eff.exist()) { + cap.map.put(eff.effect(), eff.level()); + } else { + cap.map.remove(eff.effect()); + } + } + + +} diff --git a/src/main/java/dev/xkmc/l2core/init/events/ClientGeneralEventHandler.java b/src/main/java/dev/xkmc/l2core/init/events/ClientGeneralEventHandler.java new file mode 100644 index 0000000..efbf99d --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/events/ClientGeneralEventHandler.java @@ -0,0 +1,21 @@ +package dev.xkmc.l2core.init.events; + +import dev.xkmc.l2core.init.L2Core; +import dev.xkmc.l2core.util.raytrace.EntityTarget; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.event.TickEvent; + +@Mod.EventBusSubscriber(value = Dist.CLIENT, modid = L2Core.MODID, bus = Mod.EventBusSubscriber.Bus.FORGE) +public class ClientGeneralEventHandler { + + @SubscribeEvent + public static void clientTick(TickEvent.ClientTickEvent event) { + if (event.phase != TickEvent.Phase.END) return; + for (EntityTarget target : EntityTarget.LIST) { + target.tickRender(); + } + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/events/EffectSyncEvents.java b/src/main/java/dev/xkmc/l2core/init/events/EffectSyncEvents.java new file mode 100644 index 0000000..bdd48cc --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/events/EffectSyncEvents.java @@ -0,0 +1,100 @@ +package dev.xkmc.l2core.init.events; + +import dev.xkmc.l2core.base.effects.EffectToClient; +import dev.xkmc.l2core.init.L2Core; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.effect.MobEffect; +import net.minecraft.world.entity.LivingEntity; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.event.entity.living.MobEffectEvent; +import net.neoforged.neoforge.event.entity.player.PlayerEvent; + +import java.util.HashSet; +import java.util.Set; + +@Mod.EventBusSubscriber(modid = L2Core.MODID, bus = Mod.EventBusSubscriber.Bus.FORGE) +public class EffectSyncEvents { + + public static final Set TRACKED = new HashSet<>(); + + @SubscribeEvent + public static void onPotionAddedEvent(MobEffectEvent.Added event) { + if (TRACKED.contains(event.getEffectInstance().getEffect())) { + onEffectAppear(event.getEffectInstance().getEffect(), event.getEntity(), event.getEffectInstance().getAmplifier()); + } + } + + + @SubscribeEvent + public static void onPotionRemoveEvent(MobEffectEvent.Remove event) { + if (event.getEffectInstance() != null && TRACKED.contains(event.getEffectInstance().getEffect())) { + onEffectDisappear(event.getEffectInstance().getEffect(), event.getEntity()); + } + } + + + @SubscribeEvent + public static void onPotionExpiryEvent(MobEffectEvent.Expired event) { + if (event.getEffectInstance() != null && TRACKED.contains(event.getEffectInstance().getEffect())) { + onEffectDisappear(event.getEffectInstance().getEffect(), event.getEntity()); + } + } + + @SubscribeEvent + public static void onPlayerStartTracking(PlayerEvent.StartTracking event) { + if (!(event.getTarget() instanceof LivingEntity le)) + return; + for (MobEffect eff : le.getActiveEffectsMap().keySet()) { + if (TRACKED.contains(eff)) { + onEffectAppear(eff, le, le.getActiveEffectsMap().get(eff).getAmplifier()); + } + } + } + + @SubscribeEvent + public static void onPlayerStopTracking(PlayerEvent.StopTracking event) { + if (!(event.getTarget() instanceof LivingEntity le)) + return; + for (MobEffect eff : le.getActiveEffectsMap().keySet()) { + if (TRACKED.contains(eff)) { + onEffectDisappear(eff, le); + } + } + } + + @SubscribeEvent + public static void onServerPlayerJoin(PlayerEvent.PlayerLoggedInEvent event) { + ServerPlayer e = (ServerPlayer) event.getEntity(); + if (e != null) { + for (MobEffect eff : e.getActiveEffectsMap().keySet()) { + if (TRACKED.contains(eff)) { + onEffectAppear(eff, e, e.getActiveEffectsMap().get(eff).getAmplifier()); + } + } + } + } + + @SubscribeEvent + public static void onServerPlayerLeave(PlayerEvent.PlayerLoggedOutEvent event) { + ServerPlayer e = (ServerPlayer) event.getEntity(); + if (e != null) { + for (MobEffect eff : e.getActiveEffectsMap().keySet()) { + if (TRACKED.contains(eff)) { + onEffectDisappear(eff, e); + } + } + } + } + + private static void onEffectAppear(MobEffect eff, LivingEntity e, int lv) { + if (e.level().isClientSide()) return; + L2Core.PACKET_HANDLER.toTrackingPlayers(new EffectToClient(e.getId(), eff, true, lv), e); + } + + private static void onEffectDisappear(MobEffect eff, LivingEntity e) { + if (e.level().isClientSide()) return; + L2Core.PACKET_HANDLER.toTrackingPlayers(new EffectToClient(e.getId(), eff, false, 0), e); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/events/GeneralEventHandler.java b/src/main/java/dev/xkmc/l2core/init/events/GeneralEventHandler.java new file mode 100644 index 0000000..f14776e --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/events/GeneralEventHandler.java @@ -0,0 +1,67 @@ +package dev.xkmc.l2core.init.events; + +import dev.xkmc.l2core.base.explosion.BaseExplosion; +import dev.xkmc.l2core.init.L2Core; +import dev.xkmc.l2core.serial.config.PacketHandlerWithConfig; +import dev.xkmc.l2core.util.raytrace.RayTraceUtil; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.event.AddReloadListenerEvent; +import net.neoforged.neoforge.event.OnDatapackSyncEvent; +import net.neoforged.neoforge.event.TickEvent; +import net.neoforged.neoforge.event.level.ExplosionEvent; + +import java.util.ArrayList; +import java.util.List; +import java.util.function.BooleanSupplier; + +@Mod.EventBusSubscriber(modid = L2Core.MODID, bus = Mod.EventBusSubscriber.Bus.FORGE) +public class GeneralEventHandler { + + @SubscribeEvent + public static void addReloadListeners(AddReloadListenerEvent event) { + PacketHandlerWithConfig.addReloadListeners(event); + } + + @SubscribeEvent + public static void onDatapackSync(OnDatapackSyncEvent event) { + PacketHandlerWithConfig.onDatapackSync(event); + } + + @SubscribeEvent + public static void serverTick(TickEvent.ServerTickEvent event) { + if (event.phase != TickEvent.Phase.END) return; + RayTraceUtil.serverTick(); + execute(); + } + + @SubscribeEvent + public static void onDetonate(ExplosionEvent.Detonate event) { + if (event.getExplosion() instanceof BaseExplosion exp) { + event.getAffectedEntities().removeIf(e -> !exp.hurtEntity(e)); + } + } + + private static List TASKS = new ArrayList<>(); + + public static synchronized void schedule(Runnable runnable) { + TASKS.add(() -> { + runnable.run(); + return true; + }); + } + + public static synchronized void schedulePersistent(BooleanSupplier runnable) { + TASKS.add(runnable); + } + + private static synchronized void execute() { + if (TASKS.isEmpty()) return; + var temp = TASKS; + TASKS = new ArrayList<>(); + temp.removeIf(BooleanSupplier::getAsBoolean); + temp.addAll(TASKS); + TASKS = temp; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/events/package-info.java b/src/main/java/dev/xkmc/l2core/init/events/package-info.java new file mode 100644 index 0000000..6cea80e --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/events/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.init.events; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/init/package-info.java b/src/main/java/dev/xkmc/l2core/init/package-info.java new file mode 100644 index 0000000..6469391 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.init; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/init/reg/datapack/DataMapReg.java b/src/main/java/dev/xkmc/l2core/init/reg/datapack/DataMapReg.java new file mode 100644 index 0000000..5198eac --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/datapack/DataMapReg.java @@ -0,0 +1,29 @@ +package dev.xkmc.l2core.init.reg.datapack; + +import com.mojang.datafixers.util.Pair; +import dev.xkmc.l2core.util.Proxy; +import net.neoforged.neoforge.registries.datamaps.DataMapType; +import net.neoforged.neoforge.registries.datamaps.RegisterDataMapTypesEvent; +import org.jetbrains.annotations.Nullable; + +import java.util.stream.Stream; + +public record DataMapReg(DataMapType reg) implements ValSet { + + public void register(final RegisterDataMapTypesEvent event) { + event.register(reg); + } + + @Nullable + public V get(K key) { + var registry = Proxy.getRegistryAccess().registry(reg.registryKey()).get(); + return registry.getData(reg, registry.getResourceKey(key).get()); + } + + public Stream> getAll() { + var registry = Proxy.getRegistryAccess().registry(reg.registryKey()).get(); + return registry.getDataMap(reg).entrySet().stream() + .map(e -> Pair.of(registry.get(e.getKey()), e.getValue())); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/datapack/DatapackReg.java b/src/main/java/dev/xkmc/l2core/init/reg/datapack/DatapackReg.java new file mode 100644 index 0000000..e501a12 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/datapack/DatapackReg.java @@ -0,0 +1,28 @@ +package dev.xkmc.l2core.init.reg.datapack; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.Codec; +import dev.xkmc.l2core.util.Proxy; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.registries.DataPackRegistryEvent; + +import java.util.stream.Stream; + +public record DatapackReg(ResourceKey> key, Codec codec) implements ValSet { + + public void onRegister(DataPackRegistryEvent.NewRegistry event) { + event.dataPackRegistry(key, codec, codec); + } + + public T get(ResourceLocation id) { + return Proxy.getRegistryAccess().registry(key).get().get(id); + } + + public Stream> getAll() { + return Proxy.getRegistryAccess().registry(key).get().entrySet() + .stream().map(e -> Pair.of(e.getKey().location(), e.getValue())); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/datapack/ValSet.java b/src/main/java/dev/xkmc/l2core/init/reg/datapack/ValSet.java new file mode 100644 index 0000000..c85ed64 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/datapack/ValSet.java @@ -0,0 +1,15 @@ +package dev.xkmc.l2core.init.reg.datapack; + +import com.mojang.datafixers.util.Pair; +import org.jetbrains.annotations.Nullable; + +import java.util.stream.Stream; + +public interface ValSet { + + @Nullable + V get(K k); + + Stream> getAll(); + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/datapack/package-info.java b/src/main/java/dev/xkmc/l2core/init/reg/datapack/package-info.java new file mode 100644 index 0000000..0ec1b90 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/datapack/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.init.reg.datapack; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/init/reg/registrate/L2Registrate.java b/src/main/java/dev/xkmc/l2core/init/reg/registrate/L2Registrate.java new file mode 100644 index 0000000..fb3c42f --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/registrate/L2Registrate.java @@ -0,0 +1,166 @@ +package dev.xkmc.l2core.init.reg.registrate; + +/*TODO +public class L2Registrate extends AbstractRegistrate { + + public L2Registrate(String modid) { + super(modid); + registerEventListeners(FMLJavaModLoadingContext.get().getModEventBus()); + } + + public , P extends T> GenericBuilder generic(RegistryInstance cls, String id, NonNullSupplier

sup) { + return entry(id, cb -> new GenericBuilder<>(this, id, cb, cls.key(), sup)); + } + + public > RegistryEntry> recipe(String id) { + return simple(id, ForgeRegistries.Keys.RECIPE_TYPES, () -> new RecipeType<>() { + }); + } + + @Deprecated + @Override + public EnchantmentBuilder enchantment(String name, EnchantmentCategory type, EnchantmentBuilder.EnchantmentFactory factory) { + return super.enchantment(name, type, factory); + } + + public EnchantmentBuilder enchantment(String name, EnchantmentCategory type, EnchantmentBuilder.EnchantmentFactory factory, String desc) { + addRawLang("enchantment." + getModid() + "." + name + ".desc", desc); + return super.enchantment(name, type, factory); + } + + public NoConfigBuilder effect(String name, NonNullSupplier sup, String desc) { + addRawLang("effect." + getModid() + "." + name + ".description", desc); + return entry(name, cb -> new NoConfigBuilder<>(this, this, name, cb, ForgeRegistries.Keys.MOB_EFFECTS, sup)); + } + + @SuppressWarnings({"unchecked", "unsafe"}) + public > RegistryInstance newRegistry(String id, Class cls, Consumer> cons) { + ResourceKey> key = makeRegistry(id, () -> { + var ans = new RegistryBuilder(); + ans.onCreate((r, s) -> new RLClassHandler<>((Class) cls, () -> r)); + cons.accept(ans); + return ans; + }); + return new RegistryInstance<>(Suppliers.memoize(() -> RegistryManager.ACTIVE.getRegistry(key)), key); + } + + public > RegistryInstance newRegistry(String id, Class cls) { + return newRegistry(id, cls, e -> { + }); + } + + public synchronized RegistryEntry buildModCreativeTab(String name, String def, Consumer config) { + ResourceLocation id = new ResourceLocation(getModid(), name); + defaultCreativeTab(ResourceKey.create(Registries.CREATIVE_MODE_TAB, id)); + return buildCreativeTabImpl(name, this.addLang("itemGroup", id, def), config); + } + + public synchronized RegistryEntry buildL2CreativeTab(String name, String def, Consumer config) { + ResourceLocation id = new ResourceLocation(L2Library.MODID, name); + defaultCreativeTab(ResourceKey.create(Registries.CREATIVE_MODE_TAB, id)); + TabSorter sorter = new TabSorter(getModid() + ":" + name, id); + return L2Library.REGISTRATE.buildCreativeTabImpl(name, this.addLang("itemGroup", id, def), b -> { + config.accept(b); + sorter.sort(b); + }); + } + + private synchronized RegistryEntry buildCreativeTabImpl(String name, Component comp, Consumer config) { + return this.generic(self(), name, Registries.CREATIVE_MODE_TAB, () -> { + var builder = CreativeModeTab.builder().title(comp) + .withTabsBefore(CreativeModeTabs.SPAWN_EGGS); + config.accept(builder); + return builder.build(); + }).register(); + } + + public record RegistryInstance>(Supplier> supplier, + ResourceKey> key) implements Supplier> { + + @Override + public IForgeRegistry get() { + return supplier().get(); + } + } + + public static class GenericBuilder, P extends T> extends AbstractBuilder> { + + private final NonNullSupplier

sup; + + GenericBuilder(L2Registrate parent, String name, BuilderCallback callback, ResourceKey> registryType, NonNullSupplier

sup) { + super(parent, parent, name, callback, registryType); + this.sup = sup; + } + + @Override + protected @NonnullType @NotNull P createEntry() { + return sup.get(); + } + + public GenericBuilder defaultLang() { + return lang(NamedEntry::getDescriptionId, RegistrateLangProvider.toEnglishName(this.getName())); + } + + } + + private static class TabSorter { + + private static final TreeMap MAP = new TreeMap<>(); + private static final HashSet SET = new HashSet<>(); + + private final ResourceLocation id; + + private TabSorter(String str, ResourceLocation id) { + MAP.put(str, this); + SET.add(id); + this.id = id; + } + + public void sort(CreativeModeTab.Builder b) { + var list = new ArrayList<>(MAP.values()); + boolean after = false; + ResourceLocation before = null; + for (var e : list) { + if (e == this) { + after = true; + if (before != null) { + b.withTabsBefore(before); + } + continue; + } + if (after) { + b.withTabsAfter(e.id); + return; + } else { + before = e.id; + } + } + for (var e : BuiltInRegistries.CREATIVE_MODE_TAB.entrySet()) { + var id = e.getKey().location(); + if (known(id) || known(e.getValue())) { + continue; + } + b.withTabsAfter(id); + } + } + + private static boolean known(ResourceLocation id) { + if (id.getNamespace().equals("minecraft")) { + return true; + } + return SET.contains(id); + } + + private static boolean known(CreativeModeTab tab) { + for (var other : tab.tabsAfter) { + if (known(other)) { + return true; + } + } + return false; + } + + } + +} +*/ \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/init/reg/registrate/NamedEntry.java b/src/main/java/dev/xkmc/l2core/init/reg/registrate/NamedEntry.java new file mode 100644 index 0000000..d450f05 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/registrate/NamedEntry.java @@ -0,0 +1,40 @@ +package dev.xkmc.l2core.init.reg.registrate; + +/*TODO +public class NamedEntry> { + + private final L2Registrate.RegistryInstance registry; + + private String desc = null; + + public NamedEntry(L2Registrate.RegistryInstance registry) { + this.registry = registry; + } + + public @NotNull String getDescriptionId() { + if (desc != null) + return desc; + ResourceLocation rl = getRegistryName(); + ResourceLocation reg = registry.get().getRegistryName(); + desc = reg.getPath() + "." + rl.getNamespace() + "." + rl.getPath(); + return desc; + } + + public MutableComponent getDesc() { + return Component.translatable(getDescriptionId()); + } + + public ResourceLocation getRegistryName() { + return Objects.requireNonNull(registry.get().getKey(getThis())); + } + + public String getID() { + return getRegistryName().toString(); + } + + public T getThis() { + return Wrappers.cast(this); + } + +} +*/ diff --git a/src/main/java/dev/xkmc/l2core/init/reg/registrate/package-info.java b/src/main/java/dev/xkmc/l2core/init/reg/registrate/package-info.java new file mode 100644 index 0000000..1d6012c --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/registrate/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.init.reg.registrate; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/init/reg/simple/AttReg.java b/src/main/java/dev/xkmc/l2core/init/reg/simple/AttReg.java new file mode 100644 index 0000000..b465c53 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/simple/AttReg.java @@ -0,0 +1,82 @@ +package dev.xkmc.l2core.init.reg.simple; + +import dev.xkmc.l2core.capability.attachment.AttachmentDef; +import dev.xkmc.l2core.capability.attachment.GeneralCapabilityHolder; +import dev.xkmc.l2core.capability.attachment.GeneralCapabilityTemplate; +import dev.xkmc.l2core.capability.player.PlayerCapabilityHolder; +import dev.xkmc.l2core.capability.player.PlayerCapabilityTemplate; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.attachment.AttachmentHolder; +import net.neoforged.neoforge.attachment.AttachmentType; +import net.neoforged.neoforge.registries.DeferredHolder; +import net.neoforged.neoforge.registries.DeferredRegister; +import net.neoforged.neoforge.registries.NeoForgeRegistries; + +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.function.Supplier; + +public record AttReg(DeferredRegister> att) { + + public static AttReg of(Reg reg) { + return new AttReg(reg.make(NeoForgeRegistries.ATTACHMENT_TYPES)); + } + + public > AttVal reg(String id, T type) { + return new AttValImpl<>(att.register(id, type::type), type); + } + + public > AttVal reg(String id, Function factory) { + ResourceLocation rl = new ResourceLocation(att.getNamespace(), id); + T type = factory.apply(rl); + return reg(id, type); + } + + public , H extends AttachmentHolder> AttVal.CapVal + entity(String id, Class holder_class, Supplier sup, Class entity_class, Predicate pred) { + ResourceLocation rl = new ResourceLocation(att.getNamespace(), id); + var type = new GeneralCapabilityHolder<>(rl, holder_class, sup, entity_class, pred); + return new CapValImpl<>(att.register(id, type::type), type); + } + + public > AttVal.PlayerVal + player(String id, Class holder_class, Supplier sup, PlayerCapabilityHolder.NetworkFactory network) { + ResourceLocation rl = new ResourceLocation(att.getNamespace(), id); + var type = new PlayerCapabilityHolder<>(rl, holder_class, sup, network); + return new PlayerValImpl<>(att.register(id, type::type), type); + } + + private record AttValImpl>( + DeferredHolder, AttachmentType> val, T type + ) implements AttVal { + + @Override + public AttachmentType get() { + return val.get(); + } + + } + + private record CapValImpl>( + DeferredHolder, AttachmentType> val, GeneralCapabilityHolder type + ) implements AttVal.CapVal { + + @Override + public AttachmentType get() { + return val.get(); + } + + } + + private record PlayerValImpl>( + DeferredHolder, AttachmentType> val, PlayerCapabilityHolder type + ) implements AttVal.PlayerVal { + + @Override + public AttachmentType get() { + return val.get(); + } + + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/simple/AttVal.java b/src/main/java/dev/xkmc/l2core/init/reg/simple/AttVal.java new file mode 100644 index 0000000..dc47c58 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/simple/AttVal.java @@ -0,0 +1,25 @@ +package dev.xkmc.l2core.init.reg.simple; + +import dev.xkmc.l2core.capability.attachment.AttachmentDef; +import dev.xkmc.l2core.capability.attachment.GeneralCapabilityHolder; +import dev.xkmc.l2core.capability.attachment.GeneralCapabilityTemplate; +import dev.xkmc.l2core.capability.player.PlayerCapabilityHolder; +import dev.xkmc.l2core.capability.player.PlayerCapabilityTemplate; +import net.neoforged.neoforge.attachment.AttachmentHolder; +import net.neoforged.neoforge.attachment.AttachmentType; + +public interface AttVal> extends Val> { + + H type(); + + interface CapVal> + extends AttVal> { + + } + + interface PlayerVal> + extends AttVal> { + + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/simple/CdcReg.java b/src/main/java/dev/xkmc/l2core/init/reg/simple/CdcReg.java new file mode 100644 index 0000000..d661788 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/simple/CdcReg.java @@ -0,0 +1,32 @@ +package dev.xkmc.l2core.init.reg.simple; + +import com.mojang.serialization.MapCodec; +import dev.xkmc.l2serial.serialization.codec.MapCodecAdaptor; +import net.minecraft.core.Registry; +import net.neoforged.neoforge.registries.DeferredHolder; +import net.neoforged.neoforge.registries.DeferredRegister; + +public record CdcReg(DeferredRegister> reg) { + + public static CdcReg of(Reg parent, Registry> reg) { + return new CdcReg<>(parent.make(reg)); + } + + public CdcVal reg(String id, MapCodec codec) { + return new CdcValImpl<>(reg.register(id, () -> codec)); + } + + public CdcVal reg(String id, Class cls) { + return new CdcValImpl<>(reg.register(id, () -> MapCodecAdaptor.of(cls))); + } + + private record CdcValImpl(DeferredHolder, MapCodec> val) + implements CdcVal { + + @Override + public MapCodec get() { + return val.get(); + } + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/simple/CdcVal.java b/src/main/java/dev/xkmc/l2core/init/reg/simple/CdcVal.java new file mode 100644 index 0000000..8ec0f4d --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/simple/CdcVal.java @@ -0,0 +1,7 @@ +package dev.xkmc.l2core.init.reg.simple; + +import com.mojang.serialization.MapCodec; + +public interface CdcVal extends Val> { + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/simple/IngReg.java b/src/main/java/dev/xkmc/l2core/init/reg/simple/IngReg.java new file mode 100644 index 0000000..8169644 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/simple/IngReg.java @@ -0,0 +1,35 @@ +package dev.xkmc.l2core.init.reg.simple; + +import com.mojang.serialization.MapCodec; +import dev.xkmc.l2serial.serialization.codec.MapCodecAdaptor; +import net.neoforged.neoforge.common.crafting.ICustomIngredient; +import net.neoforged.neoforge.common.crafting.IngredientType; +import net.neoforged.neoforge.registries.DeferredHolder; +import net.neoforged.neoforge.registries.DeferredRegister; +import net.neoforged.neoforge.registries.NeoForgeRegistries; + +public record IngReg(DeferredRegister> reg) { + + public static IngReg of(Reg reg) { + return new IngReg(reg.make(NeoForgeRegistries.INGREDIENT_TYPES)); + } + + public IngVal reg(String id, MapCodec codec) { + return new IngValImpl<>(reg.register(id, () -> new IngredientType<>(codec))); + } + + public IngVal reg(String id, Class cls) { + return new IngValImpl<>(reg.register(id, () -> new IngredientType<>(MapCodecAdaptor.of(cls)))); + } + + private record IngValImpl(DeferredHolder, IngredientType> val) + implements IngVal { + + @Override + public IngredientType get() { + return val.get(); + } + + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/simple/IngVal.java b/src/main/java/dev/xkmc/l2core/init/reg/simple/IngVal.java new file mode 100644 index 0000000..55da7a6 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/simple/IngVal.java @@ -0,0 +1,9 @@ +package dev.xkmc.l2core.init.reg.simple; + +import net.minecraft.world.item.crafting.Ingredient; +import net.neoforged.neoforge.common.crafting.ICustomIngredient; +import net.neoforged.neoforge.common.crafting.IngredientType; + +public interface IngVal extends Val> { + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/simple/Reg.java b/src/main/java/dev/xkmc/l2core/init/reg/simple/Reg.java new file mode 100644 index 0000000..3fe18ae --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/simple/Reg.java @@ -0,0 +1,72 @@ +package dev.xkmc.l2core.init.reg.simple; + +import com.mojang.serialization.Codec; +import dev.xkmc.l2core.init.reg.datapack.DataMapReg; +import dev.xkmc.l2core.init.reg.datapack.DatapackReg; +import dev.xkmc.l2serial.serialization.codec.CodecAdaptor; +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.bus.api.IEventBus; +import net.neoforged.neoforge.registries.DeferredRegister; +import net.neoforged.neoforge.registries.datamaps.DataMapType; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +public final class Reg { + + + + private final String modid; + + private final Map, DeferredRegister> map = new LinkedHashMap<>(); + private final List> list = new ArrayList<>(); + + public Reg(String modid) { + this.modid = modid; + } + + public DeferredRegister make(Registry reg) { + return Wrappers.cast(map.computeIfAbsent(reg, r -> DeferredRegister.create(r, modid))); + } + + public DatapackReg dataReg(String id, Codec codec) { + var ans = new DatapackReg<>(ResourceKey.createRegistryKey(id(id)), codec); + list.add(bus -> bus.addListener(ans::onRegister)); + return ans; + } + + public DatapackReg dataReg(String id, Class cls) { + return dataReg(id, new CodecAdaptor<>(cls)); + } + + public DataMapReg dataMap(DataMapType type) { + var ans = new DataMapReg<>(type); + list.add(bus -> bus.addListener(ans::register)); + return ans; + } + + public DataMapReg dataMap(String id, ResourceKey> k, Codec codec, Codec network) { + return dataMap(DataMapType.builder(id(id), k, codec).synced(network, true).build()); + } + + public DataMapReg dataMap(String id, ResourceKey> k, Class cls) { + CodecAdaptor codec = new CodecAdaptor<>(cls); + return dataMap(id, k, codec, codec); + } + + public void register(IEventBus bus) { + for (var e : map.values()) e.register(bus); + for (var e : list) e.accept(bus); + } + + public ResourceLocation id(String id) { + return ResourceLocation.fromNamespaceAndPath(modid, id); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/simple/SR.java b/src/main/java/dev/xkmc/l2core/init/reg/simple/SR.java new file mode 100644 index 0000000..26a0c54 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/simple/SR.java @@ -0,0 +1,28 @@ +package dev.xkmc.l2core.init.reg.simple; + +import net.minecraft.core.Registry; +import net.neoforged.neoforge.registries.DeferredHolder; +import net.neoforged.neoforge.registries.DeferredRegister; + +import java.util.function.Supplier; + +public record SR(DeferredRegister reg) { + + public static SR of(Reg parent, Registry reg) { + return new SR<>(parent.make(reg)); + } + + public Val reg(String id, Supplier sup) { + return new ValImpl<>(reg.register(id, sup)); + } + + private record ValImpl(DeferredHolder val) implements Val { + + @Override + public T get() { + return val.get(); + } + + } + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/simple/Val.java b/src/main/java/dev/xkmc/l2core/init/reg/simple/Val.java new file mode 100644 index 0000000..71313b3 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/simple/Val.java @@ -0,0 +1,7 @@ +package dev.xkmc.l2core.init.reg.simple; + +public interface Val { + + T get(); + +} diff --git a/src/main/java/dev/xkmc/l2core/init/reg/simple/package-info.java b/src/main/java/dev/xkmc/l2core/init/reg/simple/package-info.java new file mode 100644 index 0000000..6519e00 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/init/reg/simple/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.init.reg.simple; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/serial/conditions/BooleanValueCondition.java b/src/main/java/dev/xkmc/l2core/serial/conditions/BooleanValueCondition.java new file mode 100644 index 0000000..63e24a9 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/conditions/BooleanValueCondition.java @@ -0,0 +1,31 @@ +package dev.xkmc.l2core.serial.conditions; + +import com.mojang.serialization.MapCodec; +import dev.xkmc.l2core.init.L2LibReg; +import net.neoforged.fml.config.ConfigTracker; +import net.neoforged.neoforge.common.ModConfigSpec; +import net.neoforged.neoforge.common.conditions.ICondition; + +import java.util.ArrayList; + +public record BooleanValueCondition(String path, ArrayList line, boolean expected) implements ICondition { + + public static BooleanValueCondition of(String file, ModConfigSpec.ConfigValue config, boolean value) { + return new BooleanValueCondition(file, new ArrayList<>(config.getPath()), value); + } + + @Override + public boolean test(IContext context) { + var file = ConfigTracker.INSTANCE.fileMap().get(path); + if (file == null) return false; + var line = file.getConfigData().get(line()); + if (line == null) return false; + return line instanceof Boolean bool && bool == expected; + } + + @Override + public MapCodec codec() { + return L2LibReg.CONDITION_BOOL.get(); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/conditions/DoubleValueCondition.java b/src/main/java/dev/xkmc/l2core/serial/conditions/DoubleValueCondition.java new file mode 100644 index 0000000..c32844f --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/conditions/DoubleValueCondition.java @@ -0,0 +1,31 @@ +package dev.xkmc.l2core.serial.conditions; + +import com.mojang.serialization.Codec; +import dev.xkmc.l2core.init.L2LibReg; +import net.neoforged.fml.config.ConfigTracker; +import net.neoforged.neoforge.common.ModConfigSpec; +import net.neoforged.neoforge.common.conditions.ICondition; + +import java.util.ArrayList; + +public record DoubleValueCondition(String path, ArrayList line, double low, double high) implements ICondition { + + public static DoubleValueCondition of(String file, ModConfigSpec.ConfigValue config, double low, double high) { + return new DoubleValueCondition(file, new ArrayList<>(config.getPath()), low, high); + } + + @Override + public boolean test(IContext context) { + var file = ConfigTracker.INSTANCE.fileMap().get(path); + if (file == null) return false; + var line = file.getConfigData().get(line()); + if (line == null) return false; + return line instanceof Double val && low <= val && val <= high; + } + + @Override + public Codec codec() { + return L2LibReg.CONDITION_DOUBLE.get(); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/conditions/IntValueCondition.java b/src/main/java/dev/xkmc/l2core/serial/conditions/IntValueCondition.java new file mode 100644 index 0000000..0555835 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/conditions/IntValueCondition.java @@ -0,0 +1,37 @@ +package dev.xkmc.l2core.serial.conditions; + +import com.mojang.serialization.Codec; +import dev.xkmc.l2core.init.L2Core; +import dev.xkmc.l2core.init.L2LibReg; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.fml.config.ConfigTracker; +import net.neoforged.neoforge.common.ModConfigSpec; +import net.neoforged.neoforge.common.conditions.ICondition; + +import java.util.ArrayList; + +public record IntValueCondition(String path, ArrayList line, int low, int high) implements ICondition { + + public static final ResourceLocation ID = new ResourceLocation(L2Core.MODID, "int_config"); + + public static IntValueCondition of(String file, ModConfigSpec.ConfigValue config, int low, int high) { + return new IntValueCondition(file, new ArrayList<>(config.getPath()), low, high); + } + + @Override + public boolean test(IContext context) { + var file = ConfigTracker.INSTANCE.fileMap().get(path); + if (file == null) return false; + var line = file.getConfigData().get(line()); + if (line == null) return false; + return line instanceof Integer val && low <= val && val <= high; + } + + + @Override + public Codec codec() { + return L2LibReg.CONDITION_INT.get(); + } + + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/conditions/ListStringValueCondition.java b/src/main/java/dev/xkmc/l2core/serial/conditions/ListStringValueCondition.java new file mode 100644 index 0000000..45c955e --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/conditions/ListStringValueCondition.java @@ -0,0 +1,36 @@ +package dev.xkmc.l2core.serial.conditions; + +import com.mojang.serialization.Codec; +import dev.xkmc.l2core.init.L2Core; +import dev.xkmc.l2core.init.L2LibReg; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.fml.config.ConfigTracker; +import net.neoforged.neoforge.common.ModConfigSpec; +import net.neoforged.neoforge.common.conditions.ICondition; + +import java.util.ArrayList; +import java.util.List; + +public record ListStringValueCondition(String path, ArrayList line, String key) implements ICondition { + + public static final ResourceLocation ID = new ResourceLocation(L2Core.MODID, "string_list_config"); + + public static ListStringValueCondition of(String file, ModConfigSpec.ConfigValue> config, String key) { + return new ListStringValueCondition(file, new ArrayList<>(config.getPath()), key); + } + + @Override + public boolean test(IContext context) { + var file = ConfigTracker.INSTANCE.fileMap().get(path); + if (file == null) return false; + var line = file.getConfigData().get(line()); + if (line == null) return false; + return line instanceof List val && val.contains(key); + } + + @Override + public Codec codec() { + return L2LibReg.CONDITION_LIST_STR.get(); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/conditions/StringValueCondition.java b/src/main/java/dev/xkmc/l2core/serial/conditions/StringValueCondition.java new file mode 100644 index 0000000..8005ad5 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/conditions/StringValueCondition.java @@ -0,0 +1,35 @@ +package dev.xkmc.l2core.serial.conditions; + +import com.mojang.serialization.Codec; +import dev.xkmc.l2core.init.L2Core; +import dev.xkmc.l2core.init.L2LibReg; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.fml.config.ConfigTracker; +import net.neoforged.neoforge.common.ModConfigSpec; +import net.neoforged.neoforge.common.conditions.ICondition; + +import java.util.ArrayList; + +public record StringValueCondition(String path, ArrayList line, String key) implements ICondition { + + public static final ResourceLocation ID = new ResourceLocation(L2Core.MODID, "string_config"); + + public static StringValueCondition of(String file, ModConfigSpec.ConfigValue config, String key) { + return new StringValueCondition(file, new ArrayList<>(config.getPath()), key); + } + + @Override + public boolean test(IContext context) { + var file = ConfigTracker.INSTANCE.fileMap().get(path); + if (file == null) return false; + var line = file.getConfigData().get(line()); + if (line == null) return false; + return line instanceof String val && val.equals(key); + } + + @Override + public Codec codec() { + return L2LibReg.CONDITION_STR.get(); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/conditions/package-info.java b/src/main/java/dev/xkmc/l2core/serial/conditions/package-info.java new file mode 100644 index 0000000..2f1dbb0 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/conditions/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.serial.conditions; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/serial/config/BaseConfig.java b/src/main/java/dev/xkmc/l2core/serial/config/BaseConfig.java new file mode 100644 index 0000000..59da859 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/BaseConfig.java @@ -0,0 +1,65 @@ +package dev.xkmc.l2core.serial.config; + +import dev.xkmc.l2serial.serialization.SerialClass; +import net.minecraft.resources.ResourceLocation; + +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; + +@Deprecated(forRemoval = true) +@SerialClass +public class BaseConfig { + + protected ResourceLocation id; + + public ResourceLocation getID() { + return id; + } + + /** + * called by Config Merger after it's merged + */ + protected void postMerge() { + } + + public static HashSet collectSet(List list, Function> getter) { + return list.stream().reduce(new HashSet(), (a, c) -> { + a.addAll(getter.apply(c)); + return a; + }, (a, b) -> { + a.addAll(b); + return a; + }); + } + + public static ArrayList collectList(List list, Function> getter) { + return list.stream().reduce(new ArrayList<>(), (a, c) -> { + a.addAll(getter.apply(c)); + return a; + }, (a, b) -> { + a.addAll(b); + return a; + }); + } + + public static HashMap collectMap(List list, Function> getter, Supplier gen, BiConsumer merger) { + return list.stream().reduce(new HashMap<>(), (a, c) -> { + getter.apply(c).forEach((k, v) -> merger.accept(a.computeIfAbsent(k, e -> gen.get()), v)); + return a; + }, (a, b) -> { + b.forEach((k, v) -> merger.accept(a.computeIfAbsent(k, e -> gen.get()), v)); + return a; + }); + } + + public static HashMap overrideMap(List list, Function> getter) { + HashMap ans = new HashMap<>(); + for (C c : list) { + ans.putAll(getter.apply(c)); + } + return ans; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/BaseConfigType.java b/src/main/java/dev/xkmc/l2core/serial/config/BaseConfigType.java new file mode 100644 index 0000000..4e56d5f --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/BaseConfigType.java @@ -0,0 +1,29 @@ +package dev.xkmc.l2core.serial.config; + +import net.minecraft.resources.ResourceLocation; + +import java.util.HashMap; +import java.util.Map; + +public class BaseConfigType { + + public final Class cls; + public final String id; + public final PacketHandlerWithConfig parent; + + final Map configs = new HashMap<>(); + + protected BaseConfigType(PacketHandlerWithConfig parent, String id, Class cls) { + this.parent = parent; + this.id = id; + this.cls = cls; + } + + public void beforeReload() { + configs.clear(); + } + + public void afterReload() { + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/CollectType.java b/src/main/java/dev/xkmc/l2core/serial/config/CollectType.java new file mode 100644 index 0000000..07bbc34 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/CollectType.java @@ -0,0 +1,5 @@ +package dev.xkmc.l2core.serial.config; + +public enum CollectType { + OVERWRITE, COLLECT, MAP_COLLECT, MAP_OVERWRITE +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/ConfigCollect.java b/src/main/java/dev/xkmc/l2core/serial/config/ConfigCollect.java new file mode 100644 index 0000000..c56cb5d --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/ConfigCollect.java @@ -0,0 +1,17 @@ +package dev.xkmc.l2core.serial.config; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +@Documented +@Retention(RUNTIME) +@Target(FIELD) +public @interface ConfigCollect { + + CollectType value(); + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/ConfigDataProvider.java b/src/main/java/dev/xkmc/l2core/serial/config/ConfigDataProvider.java new file mode 100644 index 0000000..4e62ee7 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/ConfigDataProvider.java @@ -0,0 +1,69 @@ +package dev.xkmc.l2core.serial.config; + +import com.google.gson.JsonElement; +import dev.xkmc.l2serial.serialization.codec.JsonCodec; +import net.minecraft.data.CachedOutput; +import net.minecraft.data.DataGenerator; +import net.minecraft.data.DataProvider; +import net.minecraft.resources.ResourceLocation; + +import javax.annotation.Nullable; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public abstract class ConfigDataProvider implements DataProvider { + + private final DataGenerator generator; + private final String name; + + private final Map> map = new HashMap<>(); + + public ConfigDataProvider(DataGenerator generator, String name) { + this.generator = generator; + this.name = name; + } + + public abstract void add(Collector map); + + @Override + public CompletableFuture run(CachedOutput cache) { + Path folder = generator.getPackOutput().getOutputFolder(); + add(new Collector(map)); + List> list = new ArrayList<>(); + map.forEach((k, v) -> { + JsonElement elem = v.serialize(); + if (elem != null) { + Path path = folder.resolve(k + ".json"); + list.add(DataProvider.saveStable(cache, elem, path)); + } + }); + return CompletableFuture.allOf(list.toArray(CompletableFuture[]::new)); + } + + @Override + public String getName() { + return name; + } + + public record Collector(Map> map) { + + public void add(ConfigTypeEntry type, ResourceLocation id, T config) { + map.put(type.asPath(id), new ConfigEntry<>(type, id, config)); + } + + } + + public record ConfigEntry(ConfigTypeEntry type, ResourceLocation id, T config) { + + @Nullable + public JsonElement serialize() { + return JsonCodec.toJson(config, type.cls()); + } + + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/ConfigLoadOnStart.java b/src/main/java/dev/xkmc/l2core/serial/config/ConfigLoadOnStart.java new file mode 100644 index 0000000..b2d416f --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/ConfigLoadOnStart.java @@ -0,0 +1,8 @@ +package dev.xkmc.l2core.serial.config; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +public @interface ConfigLoadOnStart { +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/ConfigMerger.java b/src/main/java/dev/xkmc/l2core/serial/config/ConfigMerger.java new file mode 100644 index 0000000..8ce2c19 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/ConfigMerger.java @@ -0,0 +1,98 @@ +package dev.xkmc.l2core.serial.config; + +import dev.xkmc.l2serial.serialization.type_cache.ClassCache; +import dev.xkmc.l2serial.serialization.type_cache.FieldCache; +import dev.xkmc.l2serial.serialization.type_cache.TypeInfo; +import dev.xkmc.l2serial.util.Wrappers; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +@SuppressWarnings({"rawtypes", "unchecked"}) +public class ConfigMerger { + + private final ClassCache cache; + + public ConfigMerger(Class cls) { + this.cache = ClassCache.get(cls); + } + + public T merge(List list) throws Exception { + T ans = (T) cache.create(); + for (FieldCache field : cache.getFields()) { + ConfigCollect collect = field.getAnnotation(ConfigCollect.class); + if (collect == null) continue; + switch (collect.value()) { + case OVERWRITE -> { + int n = list.size(); + if (n > 0) { + field.set(ans, field.get(list.get(n - 1))); + } + } + case COLLECT -> { + TypeInfo info = field.toType(); + assert Collection.class.isAssignableFrom(info.getAsClass()); + Collection val = (Collection) info.toCache().create(); + for (T t : list) { + val.addAll((Collection) field.get(t)); + } + field.set(ans, val); + } + case MAP_COLLECT -> { + TypeInfo info = field.toType(); + TypeInfo sub = info.getGenericType(1); + assert Map.class.isAssignableFrom(info.getAsClass()); + Map val = (Map) info.toCache().create(); + + if (Collection.class.isAssignableFrom(sub.getAsClass())) { + for (T t : list) { + Map map = (Map) field.get(t); + for (Object e : map.entrySet()) { + Map.Entry ent = (Map.Entry) e; + Collection col; + if (val.containsKey(ent.getKey())) { + col = (Collection) val.get(ent.getKey()); + } else { + val.put(ent.getKey(), col = (Collection) sub.toCache().create()); + } + col.addAll((Collection) ent.getValue()); + } + } + } else if (Map.class.isAssignableFrom(sub.getAsClass())) { + for (T t : list) { + Map map = (Map) field.get(t); + for (Object e : map.entrySet()) { + Map.Entry ent = (Map.Entry) e; + Map col; + if (val.containsKey(ent.getKey())) { + col = (Map) val.get(ent.getKey()); + } else { + val.put(ent.getKey(), col = (Map) sub.toCache().create()); + } + col.putAll((Map) ent.getValue()); + } + } + } + field.set(ans, val); + } + case MAP_OVERWRITE -> { + TypeInfo info = field.toType(); + assert Map.class.isAssignableFrom(info.getAsClass()); + Map val = (Map) info.toCache().create(); + for (T t : list) { + val.putAll((Map) field.get(t)); + } + field.set(ans, val); + } + } + } + ans.postMerge(); + return ans; + } + + public T apply(Collection s) { + return Wrappers.get(() -> this.merge(new ArrayList<>(s))); + } +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/ConfigTypeEntry.java b/src/main/java/dev/xkmc/l2core/serial/config/ConfigTypeEntry.java new file mode 100644 index 0000000..1e7b7f9 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/ConfigTypeEntry.java @@ -0,0 +1,36 @@ +package dev.xkmc.l2core.serial.config; + +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.resources.ResourceLocation; + +import java.util.Collection; + +public record ConfigTypeEntry(PacketHandlerWithConfig channel, String name, Class cls) { + + public ConfigTypeEntry(PacketHandlerWithConfig channel, String name, Class cls) { + this.channel = channel; + this.name = name; + this.cls = cls; + channel.addCachedConfig(name, cls); + } + + public String asPath(ResourceLocation rl) { + return "data/" + rl.getNamespace() + "/" + channel.config_path + "/" + name + "/" + rl.getPath(); + } + + public T getMerged() { + MergedConfigType type = Wrappers.cast(channel.types.get(name)); + return type.load(); + } + + public Collection getAll() { + MergedConfigType type = Wrappers.cast(channel.types.get(name)); + return type.configs.values(); + } + + public T getEntry(ResourceLocation id) { + MergedConfigType type = Wrappers.cast(channel.types.get(name)); + return type.configs.get(id); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/MergedConfigType.java b/src/main/java/dev/xkmc/l2core/serial/config/MergedConfigType.java new file mode 100644 index 0000000..9f0efd4 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/MergedConfigType.java @@ -0,0 +1,30 @@ +package dev.xkmc.l2core.serial.config; + +import net.minecraft.resources.ResourceLocation; + +public class MergedConfigType extends BaseConfigType { + + private T result; + + MergedConfigType(PacketHandlerWithConfig parent, String id, Class cls) { + super(parent, id, cls); + } + + T load() { + if (result != null) { + return result; + } + result = new ConfigMerger<>(cls).apply(configs.values()); + result.id = new ResourceLocation(parent.name, id); + return result; + } + + @Override + public void afterReload() { + result = null; + if (cls.isAnnotationPresent(ConfigLoadOnStart.class)) { + load(); + } + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/PacketHandlerWithConfig.java b/src/main/java/dev/xkmc/l2core/serial/config/PacketHandlerWithConfig.java new file mode 100644 index 0000000..de912cb --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/PacketHandlerWithConfig.java @@ -0,0 +1,143 @@ +package dev.xkmc.l2core.serial.config; + +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import dev.xkmc.l2core.init.L2Core; +import dev.xkmc.l2serial.network.PacketHandler; +import dev.xkmc.l2serial.serialization.codec.JsonCodec; +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.packs.resources.ResourceManager; +import net.minecraft.server.packs.resources.SimpleJsonResourceReloadListener; +import net.minecraft.util.profiling.ProfilerFiller; +import net.neoforged.fml.ModList; +import net.neoforged.neoforge.event.AddReloadListenerEvent; +import net.neoforged.neoforge.event.OnDatapackSyncEvent; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +@SuppressWarnings("unused") +public class PacketHandlerWithConfig extends PacketHandler { + + static final Map INTERNAL = new ConcurrentHashMap<>(); + + public static void onDatapackSync(OnDatapackSyncEvent event) { + for (PacketHandlerWithConfig handler : INTERNAL.values()) { + SyncPacket packet = new SyncPacket(handler.name, handler.configs); + if (event.getPlayer() == null) L2Core.PACKET_HANDLER.toAllClient(packet); + else L2Core.PACKET_HANDLER.toClientPlayer(packet, event.getPlayer()); + } + } + + public static void addReloadListeners(AddReloadListenerEvent event) { + for (PacketHandlerWithConfig handler : INTERNAL.values()) { + if (handler.listener != null) + event.addListener(handler.listener); + } + } + + public ArrayList configs = new ArrayList<>(); + + public final String config_path; + + final ConfigReloadListener listener; + final List listener_before = new ArrayList<>(); + final List listener_after = new ArrayList<>(); + final Map> types = new HashMap<>(); + + @SafeVarargs + public PacketHandlerWithConfig(String id, int version, Function>... values) { + super(id, version, values); + INTERNAL.put(id, this); + config_path = id + "_config"; + listener = new ConfigReloadListener(config_path); + listener_before.add(configs::clear); + } + + public void addBeforeReloadListener(Runnable runnable) { + listener_before.add(runnable); + } + + public void addAfterReloadListener(Runnable runnable) { + listener_after.add(runnable); + } + + public void addConfig(String id, Class loader) { + BaseConfigType c = new BaseConfigType<>(this, id, loader); + types.put(id, c); + addBeforeReloadListener(c::beforeReload); + addAfterReloadListener(c::afterReload); + } + + public void addCachedConfig(String id, Class loader) { + MergedConfigType c = new MergedConfigType<>(this, id, loader); + types.put(id, c); + addBeforeReloadListener(c::beforeReload); + addAfterReloadListener(c::afterReload); + } + + T getCachedConfig(String id) { + MergedConfigType type = Wrappers.cast(types.get(id)); + return type.load(); + } + + class ConfigReloadListener extends SimpleJsonResourceReloadListener { + + public ConfigReloadListener(String path) { + super(new Gson(), path); + } + + @Override + protected void apply(Map map, ResourceManager manager, ProfilerFiller filler) { + listener_before.forEach(Runnable::run); + map.forEach((k, v) -> { + if (!k.getNamespace().startsWith("_")) { + if (!ModList.get().isLoaded(k.getNamespace())) { + return; + } + } + String id = k.getPath().split("/")[0]; + if (types.containsKey(id)) { + String name = k.getPath().substring(id.length() + 1); + ResourceLocation nk = new ResourceLocation(k.getNamespace(), name); + addJson(types.get(id), nk, v); + } + }); + listener_after.forEach(Runnable::run); + } + + private void addJson(BaseConfigType type, ResourceLocation k, JsonElement v) { + T config = JsonCodec.from(v, type.cls, null); + if (config != null) { + addConfig(type, k, config); + } + } + + private void addConfig(BaseConfigType type, ResourceLocation k, T config) { + config.id = k; + type.configs.put(k, config); + configs.add(new ConfigInstance(type.id, k, config)); + } + + /** + * Called on client side only + */ + public void apply(ArrayList list) { + listener_before.forEach(Runnable::run); + for (var e : list) { + addConfig(types.get(e.name), e.id(), Wrappers.cast(e.config)); + } + listener_after.forEach(Runnable::run); + } + } + + public record ConfigInstance(String name, ResourceLocation id, BaseConfig config) { + + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/RecordDataProvider.java b/src/main/java/dev/xkmc/l2core/serial/config/RecordDataProvider.java new file mode 100644 index 0000000..bf54bbe --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/RecordDataProvider.java @@ -0,0 +1,48 @@ +package dev.xkmc.l2core.serial.config; + +import com.google.gson.JsonElement; +import dev.xkmc.l2serial.serialization.codec.JsonCodec; +import net.minecraft.data.CachedOutput; +import net.minecraft.data.DataGenerator; +import net.minecraft.data.DataProvider; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.function.BiConsumer; + +public abstract class RecordDataProvider implements DataProvider { + private final DataGenerator generator; + private final String name; + private final Map map = new HashMap<>(); + + public RecordDataProvider(DataGenerator generator, String name) { + this.generator = generator; + this.name = name; + } + + public abstract void add(BiConsumer map); + + public CompletableFuture run(CachedOutput cache) { + Path folder = this.generator.getPackOutput().getOutputFolder(); + this.add(this.map::put); + List> list = new ArrayList<>(); + this.map.forEach((k, v) -> { + JsonElement elem = JsonCodec.toJson(v); + if (elem != null) { + Path path = folder.resolve("data/" + k + ".json"); + list.add(DataProvider.saveStable(cache, elem, path)); + } + + }); + return CompletableFuture.allOf(list.toArray(CompletableFuture[]::new)); + } + + public String getName() { + return this.name; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/SyncPacket.java b/src/main/java/dev/xkmc/l2core/serial/config/SyncPacket.java new file mode 100644 index 0000000..4055863 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/SyncPacket.java @@ -0,0 +1,20 @@ +package dev.xkmc.l2core.serial.config; + +import dev.xkmc.l2serial.network.SerialPacketBase; +import net.minecraft.world.entity.player.Player; +import org.jetbrains.annotations.Nullable; + +import java.util.ArrayList; + +public record SyncPacket(String id, ArrayList map) + implements SerialPacketBase { + + @Override + public void handle(@Nullable Player player) { + if (map != null) { + var handler = PacketHandlerWithConfig.INTERNAL.get(id); + handler.listener.apply(map); + } + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/config/package-info.java b/src/main/java/dev/xkmc/l2core/serial/config/package-info.java new file mode 100644 index 0000000..f08f722 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/config/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.serial.config; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/serial/ingredients/EnchantmentIngredient.java b/src/main/java/dev/xkmc/l2core/serial/ingredients/EnchantmentIngredient.java new file mode 100644 index 0000000..79356bc --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/ingredients/EnchantmentIngredient.java @@ -0,0 +1,45 @@ +package dev.xkmc.l2core.serial.ingredients; + +import dev.xkmc.l2core.init.L2LibReg; +import net.minecraft.core.Holder; +import net.minecraft.world.item.EnchantedBookItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.item.enchantment.EnchantmentInstance; +import net.neoforged.neoforge.common.crafting.ICustomIngredient; +import net.neoforged.neoforge.common.crafting.IngredientType; + +import java.util.stream.IntStream; +import java.util.stream.Stream; + +public record EnchantmentIngredient(Holder enchantment, int minLevel) implements ICustomIngredient { + + public static Ingredient of(Holder ench, int min) { + return new EnchantmentIngredient(ench, min).toVanilla(); + } + + @Override + public Stream getItems() { + var ench = enchantment.value(); + return IntStream.range(minLevel, ench.getMaxLevel() + 1) + .mapToObj(i -> EnchantedBookItem.createForEnchantment( + new EnchantmentInstance(enchantment, i))); + } + + @Override + public boolean isSimple() { + return false; + } + + @Override + public IngredientType getType() { + return L2LibReg.ING_ENCH.get(); + } + + public boolean test(ItemStack stack) { + return EnchantmentHelper.getEnchantmentsForCrafting(stack).getLevel(enchantment()) >= minLevel(); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/ingredients/PotionIngredient.java b/src/main/java/dev/xkmc/l2core/serial/ingredients/PotionIngredient.java new file mode 100644 index 0000000..0f61235 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/ingredients/PotionIngredient.java @@ -0,0 +1,39 @@ +package dev.xkmc.l2core.serial.ingredients; + +import dev.xkmc.l2core.init.L2LibReg; +import net.minecraft.core.Holder; +import net.minecraft.core.component.DataComponents; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.alchemy.Potion; +import net.minecraft.world.item.alchemy.PotionContents; +import net.neoforged.neoforge.common.crafting.ICustomIngredient; +import net.neoforged.neoforge.common.crafting.IngredientType; + +import java.util.stream.Stream; + +public record PotionIngredient(Holder potion) implements ICustomIngredient { + + @Override + public Stream getItems() { + ItemStack stack = new ItemStack(Items.POTION); + stack.set(DataComponents.POTION_CONTENTS, new PotionContents(potion)); + return Stream.of(stack); + } + + @Override + public boolean isSimple() { + return false; + } + + @Override + public IngredientType getType() { + return L2LibReg.ING_POTION.get(); + } + + public boolean test(ItemStack stack) { + PotionContents val = stack.get(DataComponents.POTION_CONTENTS); + return val != null && val.is(potion); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/ingredients/package-info.java b/src/main/java/dev/xkmc/l2core/serial/ingredients/package-info.java new file mode 100644 index 0000000..4d057cd --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/ingredients/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.serial.ingredients; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/AbstractShapedRecipe.java b/src/main/java/dev/xkmc/l2core/serial/recipe/AbstractShapedRecipe.java new file mode 100644 index 0000000..d4d059e --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/AbstractShapedRecipe.java @@ -0,0 +1,54 @@ +package dev.xkmc.l2core.serial.recipe; + +import com.mojang.serialization.Codec; +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.CraftingBookCategory; +import net.minecraft.world.item.crafting.ShapedRecipe; +import net.minecraft.world.item.crafting.ShapedRecipePattern; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +public abstract class AbstractShapedRecipe> extends ShapedRecipe { + + public AbstractShapedRecipe(String group, ShapedRecipePattern pattern, ItemStack result) { + super(group, CraftingBookCategory.MISC, pattern, result); + } + + @Override + public abstract Serializer getSerializer(); + + @FunctionalInterface + public interface RecipeFactory> { + + T create(String group, ShapedRecipePattern pattern, ItemStack result); + + default T map(ShapedRecipe r) { + return create(r.getGroup(), r.pattern, r.result); + } + + } + + public static class Serializer> extends ShapedRecipe.Serializer { + + private final RecipeFactory factory; + + public Serializer(RecipeFactory factory) { + this.factory = factory; + } + + @Override + public Codec codec() { + return super.codec().xmap(factory::map, e -> e); + } + + public T fromNetwork(FriendlyByteBuf obj) { + return factory.map(super.fromNetwork(obj)); + } + + } + +} \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/AbstractShapelessRecipe.java b/src/main/java/dev/xkmc/l2core/serial/recipe/AbstractShapelessRecipe.java new file mode 100644 index 0000000..7a03753 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/AbstractShapelessRecipe.java @@ -0,0 +1,61 @@ +package dev.xkmc.l2core.serial.recipe; + +import com.mojang.serialization.Codec; +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.core.NonNullList; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.CraftingBookCategory; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.ShapelessRecipe; + +import javax.annotation.ParametersAreNonnullByDefault; +import java.util.List; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +public abstract class AbstractShapelessRecipe> extends ShapelessRecipe { + + public AbstractShapelessRecipe(String group, ItemStack result, NonNullList ingredients) { + super(group, CraftingBookCategory.MISC, result, ingredients); + } + + public List getJEIResult() { + return List.of(result); + } + + @Override + public abstract Serializer getSerializer(); + + @FunctionalInterface + public interface RecipeFactory> { + + T create(String group, ItemStack result, NonNullList ingredients); + + default T map(ShapelessRecipe r) { + return create(r.getGroup(), r.result, r.getIngredients()); + } + + } + + public static class Serializer> extends ShapelessRecipe.Serializer { + + private final RecipeFactory factory; + + public Serializer(RecipeFactory factory) { + this.factory = factory; + } + + @Override + public Codec codec() { + return super.codec().xmap(factory::map, e -> e); + } + + public T fromNetwork(FriendlyByteBuf obj) { + return factory.map(super.fromNetwork(obj)); + } + + } + +} + diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/AbstractSmithingRecipe.java b/src/main/java/dev/xkmc/l2core/serial/recipe/AbstractSmithingRecipe.java new file mode 100644 index 0000000..5dde7e5 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/AbstractSmithingRecipe.java @@ -0,0 +1,55 @@ +package dev.xkmc.l2core.serial.recipe; + +import com.mojang.serialization.Codec; +import net.minecraft.MethodsReturnNonnullByDefault; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.SmithingTransformRecipe; + +import javax.annotation.ParametersAreNonnullByDefault; + +@ParametersAreNonnullByDefault +@MethodsReturnNonnullByDefault +public abstract class AbstractSmithingRecipe> extends SmithingTransformRecipe { + + public static final Ingredient TEMPLATE_PLACEHOLDER = Ingredient.EMPTY; + + public AbstractSmithingRecipe(Ingredient template, Ingredient base, Ingredient addition, ItemStack result) { + super(template, base, addition, result); + } + + @Override + public abstract Serializer getSerializer(); + + @FunctionalInterface + public interface RecipeFactory> { + + T create(Ingredient template, Ingredient base, Ingredient addition, ItemStack result); + + default T map(SmithingTransformRecipe r) { + return create(r.template, r.base, r.addition, r.result); + } + + } + + public static class Serializer> extends SmithingTransformRecipe.Serializer { + + private final RecipeFactory factory; + + public Serializer(RecipeFactory factory) { + this.factory = factory; + } + + @Override + public Codec codec() { + return super.codec().xmap(factory::map, e -> e); + } + + public T fromNetwork(FriendlyByteBuf obj) { + return factory.map(super.fromNetwork(obj)); + } + + } + +} \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/BaseRecipe.java b/src/main/java/dev/xkmc/l2core/serial/recipe/BaseRecipe.java new file mode 100644 index 0000000..48e17ad --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/BaseRecipe.java @@ -0,0 +1,57 @@ +package dev.xkmc.l2core.serial.recipe; + +import net.minecraft.core.RegistryAccess; +import net.minecraft.world.Container; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.item.crafting.RecipeType; +import net.minecraft.world.level.Level; + +import java.util.function.Supplier; + +public abstract class BaseRecipe, Inv extends Container> implements Recipe { + + private final RecType factory; + + public BaseRecipe(RecType fac) { + factory = fac; + } + + @Override + public abstract boolean matches(Inv inv, Level world); + + @Override + public abstract ItemStack assemble(Inv inv, RegistryAccess access); + + @Override + public abstract boolean canCraftInDimensions(int r, int c); + + public abstract ItemStack getResultItem(RegistryAccess access); + + @Override + public final RecipeSerializer getSerializer() { + return factory; + } + + @Override + public final RecipeType getType() { + return factory.type.get(); + } + + public interface RecInv> extends Container { + + } + + public static class RecType, Inv extends Container> extends RecSerializer { + + public final Supplier> type; + + public RecType(Class rec, Supplier> type) { + super(rec); + this.type = type; + } + + } + +} \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/BaseRecipeBuilder.java b/src/main/java/dev/xkmc/l2core/serial/recipe/BaseRecipeBuilder.java new file mode 100644 index 0000000..e52baec --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/BaseRecipeBuilder.java @@ -0,0 +1,72 @@ +package dev.xkmc.l2core.serial.recipe; + +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.AdvancementRequirements; +import net.minecraft.advancements.AdvancementRewards; +import net.minecraft.advancements.Criterion; +import net.minecraft.advancements.critereon.RecipeUnlockedTrigger; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.data.recipes.RecipeBuilder; +import net.minecraft.data.recipes.RecipeOutput; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.Container; +import net.minecraft.world.item.Item; +import org.jetbrains.annotations.Nullable; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class BaseRecipeBuilder< + T extends BaseRecipeBuilder, + Rec extends SRec, + SRec extends BaseRecipe, + Inv extends Container + > implements RecipeBuilder { + + protected final BaseRecipe.RecType type; + protected final Rec recipe; + protected final Item result; + protected final Advancement.Builder advancement = Advancement.Builder.advancement(); + protected final Map> criteria = new LinkedHashMap<>(); + + public BaseRecipeBuilder(BaseRecipe.RecType type, Item result) { + this.type = type; + this.recipe = type.blank(); + this.result = result; + } + + @SuppressWarnings({"unchecked", "unsafe"}) + public T getThis() { + return (T) this; + } + + @Override + public T unlockedBy(String name, Criterion trigger) { + criteria.put(name, trigger); + return getThis(); + } + + @Override + public T group(@Nullable String pGroupName) { + return getThis(); + } + + @Override + public Item getResult() { + return result; + } + + @Override + public void save(RecipeOutput pvd, ResourceLocation id) { + Advancement.Builder builder = pvd.advancement() + .addCriterion("has_the_recipe", RecipeUnlockedTrigger.unlocked(id)) + .rewards(AdvancementRewards.Builder.recipe(id)) + .requirements(AdvancementRequirements.Strategy.OR); + this.criteria.forEach(builder::addCriterion); + id = new ResourceLocation(id.getNamespace(), "recipes/" + + BuiltInRegistries.RECIPE_SERIALIZER.getKey(type).getPath() + "/" + id.getPath()); + pvd.accept(id, recipe, builder.build(id)); + } + +} + diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/BaseRecipeCategory.java b/src/main/java/dev/xkmc/l2core/serial/recipe/BaseRecipeCategory.java new file mode 100644 index 0000000..107d2ef --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/BaseRecipeCategory.java @@ -0,0 +1,48 @@ +package dev.xkmc.l2core.serial.recipe; + +import mezz.jei.api.gui.builder.IRecipeLayoutBuilder; +import mezz.jei.api.gui.drawable.IDrawable; +import mezz.jei.api.recipe.IFocusGroup; +import mezz.jei.api.recipe.RecipeType; +import mezz.jei.api.recipe.category.IRecipeCategory; +import net.minecraft.resources.ResourceLocation; + +public abstract class BaseRecipeCategory> implements IRecipeCategory { + + @SuppressWarnings("unchecked") + public static Class cast(Class cls) { + return (Class) cls; + } + + private final RecipeType type; + + protected IDrawable background, icon; + + public BaseRecipeCategory(ResourceLocation name, Class cls) { + this.type = new RecipeType<>(name, cls); + } + + @SuppressWarnings("unchecked") + public final C getThis() { + return (C) this; + } + + @Override + public final RecipeType getRecipeType() { + return type; + } + + @Override + public final IDrawable getBackground() { + return background; + } + + @Override + public final IDrawable getIcon() { + return icon; + } + + @Override + public abstract void setRecipe(IRecipeLayoutBuilder builder, T recipe, IFocusGroup focuses); + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/ConditionalRecipeWrapper.java b/src/main/java/dev/xkmc/l2core/serial/recipe/ConditionalRecipeWrapper.java new file mode 100644 index 0000000..bae95a5 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/ConditionalRecipeWrapper.java @@ -0,0 +1,24 @@ +package dev.xkmc.l2core.serial.recipe; + +import dev.xkmc.l2core.util.MathHelper; +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.AdvancementHolder; +import net.minecraft.data.recipes.RecipeOutput; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.crafting.Recipe; +import net.neoforged.neoforge.common.conditions.ICondition; +import org.jetbrains.annotations.Nullable; + +public record ConditionalRecipeWrapper(RecipeOutput pvd, ICondition... conditions) implements RecipeOutput { + + @Override + public Advancement.Builder advancement() { + return pvd.advancement(); + } + + @Override + public void accept(ResourceLocation id, Recipe recipe, @Nullable AdvancementHolder advancement, ICondition... conditions) { + pvd.accept(id, recipe, advancement, MathHelper.merge(conditions(), conditions)); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/CustomShapedBuilder.java b/src/main/java/dev/xkmc/l2core/serial/recipe/CustomShapedBuilder.java new file mode 100644 index 0000000..f7db038 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/CustomShapedBuilder.java @@ -0,0 +1,47 @@ +package dev.xkmc.l2core.serial.recipe; + +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.AdvancementRequirements; +import net.minecraft.advancements.AdvancementRewards; +import net.minecraft.advancements.critereon.RecipeUnlockedTrigger; +import net.minecraft.data.recipes.RecipeBuilder; +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.data.recipes.RecipeOutput; +import net.minecraft.data.recipes.ShapedRecipeBuilder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.ShapedRecipe; +import net.minecraft.world.item.crafting.ShapedRecipePattern; +import net.minecraft.world.level.ItemLike; + +import java.util.Objects; + +public class CustomShapedBuilder> extends ShapedRecipeBuilder { + + private final AbstractShapedRecipe.RecipeFactory factory; + + public CustomShapedBuilder(AbstractShapedRecipe.RecipeFactory factory, ItemLike result, int count) { + super(RecipeCategory.MISC, result, count); + this.factory = factory; + } + + @Override + public void save(RecipeOutput pRecipeOutput, ResourceLocation pId) { + ShapedRecipePattern shapedrecipepattern = this.ensureValid(pId); + Advancement.Builder advancement$builder = pRecipeOutput.advancement() + .addCriterion("has_the_recipe", RecipeUnlockedTrigger.unlocked(pId)) + .rewards(AdvancementRewards.Builder.recipe(pId)) + .requirements(AdvancementRequirements.Strategy.OR); + this.criteria.forEach(advancement$builder::addCriterion); + ShapedRecipe shapedrecipe = new ShapedRecipe( + Objects.requireNonNullElse(this.group, ""), + RecipeBuilder.determineBookCategory(this.category), + shapedrecipepattern, + new ItemStack(this.result, this.count), + this.showNotification + ); + T rec = factory.map(shapedrecipe); + pRecipeOutput.accept(pId, rec, advancement$builder.build(pId.withPrefix("recipes/" + this.category.getFolderName() + "/"))); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/CustomShapelessBuilder.java b/src/main/java/dev/xkmc/l2core/serial/recipe/CustomShapelessBuilder.java new file mode 100644 index 0000000..c8f28fe --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/CustomShapelessBuilder.java @@ -0,0 +1,44 @@ +package dev.xkmc.l2core.serial.recipe; + +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.AdvancementRequirements; +import net.minecraft.advancements.AdvancementRewards; +import net.minecraft.advancements.critereon.RecipeUnlockedTrigger; +import net.minecraft.data.recipes.RecipeBuilder; +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.data.recipes.RecipeOutput; +import net.minecraft.data.recipes.ShapelessRecipeBuilder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.ShapelessRecipe; +import net.minecraft.world.level.ItemLike; + +import java.util.Objects; + +public class CustomShapelessBuilder> extends ShapelessRecipeBuilder { + + private final AbstractShapelessRecipe.RecipeFactory factory; + + public CustomShapelessBuilder(AbstractShapelessRecipe.RecipeFactory factory, ItemLike result, int count) { + super(RecipeCategory.MISC, result, count); + this.factory = factory; + } + + @Override + public void save(RecipeOutput pRecipeOutput, ResourceLocation pId) { + this.ensureValid(pId); + Advancement.Builder advancement$builder = pRecipeOutput.advancement() + .addCriterion("has_the_recipe", RecipeUnlockedTrigger.unlocked(pId)) + .rewards(AdvancementRewards.Builder.recipe(pId)) + .requirements(AdvancementRequirements.Strategy.OR); + this.criteria.forEach(advancement$builder::addCriterion); + ShapelessRecipe shapelessrecipe = new ShapelessRecipe( + Objects.requireNonNullElse(this.group, ""), + RecipeBuilder.determineBookCategory(this.category), + new ItemStack(this.result, this.count), + this.ingredients + ); + pRecipeOutput.accept(pId, factory.map(shapelessrecipe), advancement$builder.build(pId.withPrefix("recipes/" + this.category.getFolderName() + "/"))); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/CustomSmithingBuilder.java b/src/main/java/dev/xkmc/l2core/serial/recipe/CustomSmithingBuilder.java new file mode 100644 index 0000000..e393484 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/CustomSmithingBuilder.java @@ -0,0 +1,40 @@ +package dev.xkmc.l2core.serial.recipe; + +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.AdvancementRequirements; +import net.minecraft.advancements.AdvancementRewards; +import net.minecraft.advancements.critereon.RecipeUnlockedTrigger; +import net.minecraft.data.recipes.RecipeCategory; +import net.minecraft.data.recipes.RecipeOutput; +import net.minecraft.data.recipes.SmithingTransformRecipeBuilder; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Ingredient; +import net.minecraft.world.item.crafting.SmithingTransformRecipe; + +public class CustomSmithingBuilder> extends SmithingTransformRecipeBuilder { + + private final AbstractSmithingRecipe.RecipeFactory factory; + + public CustomSmithingBuilder(AbstractSmithingRecipe.RecipeFactory factory, + Ingredient template, + Ingredient base, + Ingredient add, + Item result) { + super(template, base, add, RecipeCategory.MISC, result); + this.factory = factory; + } + + public void save(RecipeOutput pRecipeOutput, ResourceLocation pRecipeId) { + this.ensureValid(pRecipeId); + Advancement.Builder advancement$builder = pRecipeOutput.advancement() + .addCriterion("has_the_recipe", RecipeUnlockedTrigger.unlocked(pRecipeId)) + .rewards(AdvancementRewards.Builder.recipe(pRecipeId)) + .requirements(AdvancementRequirements.Strategy.OR); + this.criteria.forEach(advancement$builder::addCriterion); + SmithingTransformRecipe smithingtransformrecipe = new SmithingTransformRecipe(this.template, this.base, this.addition, new ItemStack(this.result)); + pRecipeOutput.accept(pRecipeId, factory.map(smithingtransformrecipe), advancement$builder.build(pRecipeId.withPrefix("recipes/" + this.category.getFolderName() + "/"))); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/NBTRecipeWrapper.java b/src/main/java/dev/xkmc/l2core/serial/recipe/NBTRecipeWrapper.java new file mode 100644 index 0000000..511b35b --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/NBTRecipeWrapper.java @@ -0,0 +1,32 @@ +package dev.xkmc.l2core.serial.recipe; + +import net.minecraft.advancements.Advancement; +import net.minecraft.advancements.AdvancementHolder; +import net.minecraft.data.recipes.RecipeOutput; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.ShapedRecipe; +import net.minecraft.world.item.crafting.ShapelessRecipe; +import net.neoforged.neoforge.common.conditions.ICondition; +import org.jetbrains.annotations.Nullable; + +public record NBTRecipeWrapper(RecipeOutput pvd, ItemStack stack) implements RecipeOutput { + + @Override + public Advancement.Builder advancement() { + return pvd.advancement(); + } + + @Override + public void accept(ResourceLocation id, Recipe recipe, @Nullable AdvancementHolder advancement, ICondition... conditions) { + if (recipe instanceof ShapedRecipe r) { + r.result.setTag(stack.getTag()); + } + if (recipe instanceof ShapelessRecipe r) { + r.result.setTag(stack.getTag()); + } + pvd.accept(id, recipe, advancement, conditions); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/RecSerializer.java b/src/main/java/dev/xkmc/l2core/serial/recipe/RecSerializer.java new file mode 100644 index 0000000..2ed0bd5 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/RecSerializer.java @@ -0,0 +1,43 @@ +package dev.xkmc.l2core.serial.recipe; + +import com.mojang.serialization.MapCodec; +import dev.xkmc.l2serial.serialization.codec.CodecAdaptor; +import dev.xkmc.l2serial.serialization.codec.MapCodecAdaptor; +import dev.xkmc.l2serial.util.Wrappers; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.Container; +import net.minecraft.world.item.crafting.Recipe; +import net.minecraft.world.item.crafting.RecipeInput; +import net.minecraft.world.item.crafting.RecipeSerializer; + +public class RecSerializer, I extends RecipeInput> implements RecipeSerializer { + + public final Class cls; + private final MapCodecAdaptor codec; + private final StreamCodec stream; + + public RecSerializer(Class cls) { + this.cls = cls; + this.codec = MapCodecAdaptor.of(cls); + this.stream = new CodecAdaptor<>(cls).toNetwork(); + } + + @Override + public MapCodec codec() { + return codec; + } + + @Override + public StreamCodec streamCodec() { + return stream; + } + + @SuppressWarnings("ConstantConditions") + public R blank() { + return Wrappers.get(() -> cls.getConstructor(ResourceLocation.class) + .newInstance(ResourceLocation.withDefaultNamespace("dummy"))); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/serial/recipe/package-info.java b/src/main/java/dev/xkmc/l2core/serial/recipe/package-info.java new file mode 100644 index 0000000..681097e --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/serial/recipe/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.serial.recipe; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/java/dev/xkmc/l2core/util/DataGenOnly.java b/src/main/java/dev/xkmc/l2core/util/DataGenOnly.java new file mode 100644 index 0000000..8316e88 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/util/DataGenOnly.java @@ -0,0 +1,11 @@ +package dev.xkmc.l2core.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.METHOD, ElementType.CONSTRUCTOR, ElementType.TYPE}) +public @interface DataGenOnly { +} diff --git a/src/main/java/dev/xkmc/l2core/util/DoubleSidedCall.java b/src/main/java/dev/xkmc/l2core/util/DoubleSidedCall.java new file mode 100644 index 0000000..ada7555 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/util/DoubleSidedCall.java @@ -0,0 +1,14 @@ +package dev.xkmc.l2core.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Tells if such method / field is and should only be used in logical server + */ +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.FIELD, ElementType.METHOD}) +public @interface DoubleSidedCall { +} diff --git a/src/main/java/dev/xkmc/l2core/util/LootTableTemplate.java b/src/main/java/dev/xkmc/l2core/util/LootTableTemplate.java new file mode 100644 index 0000000..9f458dd --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/util/LootTableTemplate.java @@ -0,0 +1,126 @@ +package dev.xkmc.l2core.util; + +import net.minecraft.advancements.critereon.EnchantmentPredicate; +import net.minecraft.advancements.critereon.ItemPredicate; +import net.minecraft.advancements.critereon.MinMaxBounds; +import net.minecraft.advancements.critereon.StatePropertiesPredicate; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.item.enchantment.Enchantments; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.properties.Property; +import net.minecraft.world.level.storage.loot.LootPool; +import net.minecraft.world.level.storage.loot.LootTable; +import net.minecraft.world.level.storage.loot.entries.LootItem; +import net.minecraft.world.level.storage.loot.entries.LootPoolSingletonContainer; +import net.minecraft.world.level.storage.loot.functions.ApplyBonusCount; +import net.minecraft.world.level.storage.loot.functions.LootingEnchantFunction; +import net.minecraft.world.level.storage.loot.functions.SetItemCountFunction; +import net.minecraft.world.level.storage.loot.predicates.*; +import net.minecraft.world.level.storage.loot.providers.number.ConstantValue; +import net.minecraft.world.level.storage.loot.providers.number.UniformGenerator; +import net.neoforged.neoforge.common.Tags; + +import java.util.Optional; + +@SuppressWarnings("unused") +public class LootTableTemplate { + + public static LootPool.Builder getPool(int roll, int bonus) { + return LootPool.lootPool().setRolls(ConstantValue.exactly(roll)).setBonusRolls(ConstantValue.exactly(0)); + } + + public static LootPoolSingletonContainer.Builder getItem(Item item, int count) { + return LootItem.lootTableItem(item) + .apply(SetItemCountFunction.setCount(ConstantValue.exactly(count))); + } + + public static LootPoolSingletonContainer.Builder getItem(Item item, int min, int max) { + return LootItem.lootTableItem(item) + .apply(SetItemCountFunction.setCount(UniformGenerator.between(min, max))); + } + + public static LootPoolSingletonContainer.Builder getItem(Item item, int min, int max, int add) { + return LootItem.lootTableItem(item) + .apply(SetItemCountFunction.setCount(UniformGenerator.between(min, max))) + .apply(LootingEnchantFunction.lootingMultiplier(UniformGenerator.between(0, add))); + } + + public static LootItemCondition.Builder byPlayer() { + return LootItemKilledByPlayerCondition.killedByPlayer(); + } + + public static LootItemCondition.Builder chance(float chance) { + return LootItemRandomChanceCondition.randomChance(chance); + } + + public static LootItemCondition.Builder chance(float chance, float add) { + return LootItemRandomChanceWithLootingCondition.randomChanceAndLootingBoost(chance, add); + } + + public static LootItemBlockStatePropertyCondition.Builder withBlockState(Block block, Property prop, int low, int high) { + StatePropertiesPredicate.Builder builder = StatePropertiesPredicate.Builder.properties(); + builder.matchers.add(new StatePropertiesPredicate.PropertyMatcher(prop.getName(), + new StatePropertiesPredicate.RangedMatcher( + Optional.of(Integer.toString(low)), + Optional.of(Integer.toString(high))))); + return LootItemBlockStatePropertyCondition.hasBlockStateProperties(block).setProperties(builder); + } + + public static LootItemBlockStatePropertyCondition.Builder withBlockState(Block block, Property prop, int val) { + return LootItemBlockStatePropertyCondition.hasBlockStateProperties(block).setProperties( + StatePropertiesPredicate.Builder.properties().hasProperty(prop, val) + ); + } + + public static LootItemBlockStatePropertyCondition.Builder withBlockState(Block block, Property prop, boolean val) { + return LootItemBlockStatePropertyCondition.hasBlockStateProperties(block).setProperties( + StatePropertiesPredicate.Builder.properties().hasProperty(prop, val) + ); + } + + public static LootItemBlockStatePropertyCondition.Builder withBlockState(Block block, Property prop, String val) { + return LootItemBlockStatePropertyCondition.hasBlockStateProperties(block).setProperties( + StatePropertiesPredicate.Builder.properties().hasProperty(prop, val) + ); + } + + public static LootPoolSingletonContainer.Builder cropDrop(Item item) { + return LootItem.lootTableItem(item).apply(ApplyBonusCount + .addBonusBinomialDistributionCount(Enchantments.BLOCK_FORTUNE, 0.57f, 3)); + } + + public static EnchantmentPredicate hasEnchantment(Enchantment enchant, int min) { + return new EnchantmentPredicate(enchant, MinMaxBounds.Ints.atLeast(min)); + } + + public static LootItemCondition.Builder shearOrSilk(boolean inverted) { + var ans = AnyOfCondition.anyOf( + MatchTool.toolMatches(ItemPredicate.Builder.item().of(Tags.Items.SHEARS)), + MatchTool.toolMatches(ItemPredicate.Builder.item().hasEnchantment(hasEnchantment(Enchantments.SILK_TOUCH, 1))) + ); + return inverted ? ans.invert() : ans; + } + + public static LootItemCondition.Builder silk(boolean inverted) { + LootItemCondition.Builder ans = MatchTool.toolMatches(ItemPredicate.Builder.item().hasEnchantment(hasEnchantment(Enchantments.SILK_TOUCH, 1))); + return inverted ? InvertedLootItemCondition.invert(ans) : ans; + } + + public static LootTable.Builder selfOrOther(Block block, Block base, Item other, int count) { + return LootTable.lootTable() + .withPool(LootTableTemplate.getPool(1, 0) + .add(LootTableTemplate.getItem(base.asItem(), 1)) + .when(ExplosionCondition.survivesExplosion()) + .when(LootTableTemplate.silk(true))) + .withPool(LootTableTemplate.getPool(1, 0) + .add(LootTableTemplate.getItem(other, count)) + .when(ExplosionCondition.survivesExplosion()) + .when(LootTableTemplate.silk(true))) + .withPool(LootTableTemplate.getPool(1, 0) + .add(LootTableTemplate.getItem(block.asItem(), 1)) + .when(LootTableTemplate.silk(false)) + ); + } + +} diff --git a/src/main/java/dev/xkmc/l2core/util/MathHelper.java b/src/main/java/dev/xkmc/l2core/util/MathHelper.java new file mode 100644 index 0000000..3ba4186 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/util/MathHelper.java @@ -0,0 +1,50 @@ +package dev.xkmc.l2core.util; + +import net.minecraft.world.phys.Vec3; + +import javax.annotation.Nullable; +import java.util.Arrays; +import java.util.List; +import java.util.Random; +import java.util.UUID; +import java.util.function.Function; + +public class MathHelper { + + public static double horSq(Vec3 vec3) { + return vec3.x * vec3.x + vec3.z * vec3.z; + } + + public static UUID getUUIDFromString(String str) { + int hash = str.hashCode(); + Random r = new Random(hash); + long l0 = r.nextLong(); + long l1 = r.nextLong(); + return new UUID(l0, l1); + } + + @SafeVarargs + public static T[] merge(T[] arr, T... vals) { + var ans = Arrays.copyOf(arr, arr.length + vals.length); + System.arraycopy(vals, 0, ans, arr.length, vals.length); + return ans; + } + + @Nullable + public static T pick(List list, Function func, double random) { + int total = 0; + for (T t : list) { + total += func.apply(t); + } + double val = random * total; + for (T t : list) { + val -= func.apply(t); + if (val < 0) { + return t; + } + } + return null; + } + + +} diff --git a/src/main/java/dev/xkmc/l2core/util/Proxy.java b/src/main/java/dev/xkmc/l2core/util/Proxy.java new file mode 100644 index 0000000..f6bbea3 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/util/Proxy.java @@ -0,0 +1,30 @@ +package dev.xkmc.l2core.util; + +import net.minecraft.client.Minecraft; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.core.RegistryAccess; +import net.minecraft.server.MinecraftServer; +import net.neoforged.api.distmarker.Dist; +import net.neoforged.fml.loading.FMLEnvironment; +import net.neoforged.neoforge.server.ServerLifecycleHooks; + +import java.util.Optional; + +public class Proxy { + + public static RegistryAccess getRegistryAccess() { + if (FMLEnvironment.dist == Dist.CLIENT) { + return Minecraft.getInstance().level.registryAccess(); + } + return ServerLifecycleHooks.getCurrentServer().registryAccess(); + } + + public static Optional getServer() { + return Optional.ofNullable(ServerLifecycleHooks.getCurrentServer()); + } + + public static LocalPlayer getClientPlayer() { + return Minecraft.getInstance().player; + } + +} diff --git a/src/main/java/dev/xkmc/l2core/util/ServerOnly.java b/src/main/java/dev/xkmc/l2core/util/ServerOnly.java new file mode 100644 index 0000000..4c0fbd5 --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/util/ServerOnly.java @@ -0,0 +1,14 @@ +package dev.xkmc.l2core.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Tells if such method / field is and should only be used in logical server + */ +@Retention(RetentionPolicy.SOURCE) +@Target({ElementType.FIELD, ElementType.METHOD, ElementType.CONSTRUCTOR}) +public @interface ServerOnly { +} diff --git a/src/main/java/dev/xkmc/l2core/util/package-info.java b/src/main/java/dev/xkmc/l2core/util/package-info.java new file mode 100644 index 0000000..8d6d04d --- /dev/null +++ b/src/main/java/dev/xkmc/l2core/util/package-info.java @@ -0,0 +1,8 @@ +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault + +package dev.xkmc.l2core.util; + +import net.minecraft.MethodsReturnNonnullByDefault; + +import javax.annotation.ParametersAreNonnullByDefault; \ No newline at end of file diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg new file mode 100644 index 0000000..d476d69 --- /dev/null +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -0,0 +1,67 @@ +public net.minecraft.world.item.crafting.ShapedRecipe result +public net.minecraft.world.item.crafting.ShapedRecipe pattern +public net.minecraft.world.item.crafting.ShapelessRecipe result +public net.minecraft.world.item.crafting.SmithingTransformRecipe result +public net.minecraft.world.item.crafting.SmithingTransformRecipe template # template +public net.minecraft.world.item.crafting.SmithingTransformRecipe base +public net.minecraft.world.item.crafting.SmithingTransformRecipe addition + +public net.minecraft.data.recipes.ShapedRecipeBuilder criteria # criteria +public net.minecraft.data.recipes.ShapedRecipeBuilder group # group +public net.minecraft.data.recipes.ShapedRecipeBuilder category # category +public net.minecraft.data.recipes.ShapedRecipeBuilder result # result +public net.minecraft.data.recipes.ShapedRecipeBuilder count # count +public net.minecraft.data.recipes.ShapedRecipeBuilder showNotification # showNotification +public net.minecraft.data.recipes.ShapedRecipeBuilder ensureValid(Lnet/minecraft/resources/ResourceLocation;)Lnet/minecraft/world/item/crafting/ShapedRecipePattern; # ensureValid +public net.minecraft.data.recipes.ShapelessRecipeBuilder count # count +public net.minecraft.data.recipes.ShapelessRecipeBuilder result # result +public net.minecraft.data.recipes.ShapelessRecipeBuilder category # category +public net.minecraft.data.recipes.ShapelessRecipeBuilder criteria # criteria +public net.minecraft.data.recipes.ShapelessRecipeBuilder ingredients # ingredients +public net.minecraft.data.recipes.ShapelessRecipeBuilder group # group +public net.minecraft.data.recipes.ShapelessRecipeBuilder ensureValid(Lnet/minecraft/resources/ResourceLocation;)V # ensureValid +public net.minecraft.data.recipes.SmithingTransformRecipeBuilder criteria # criteria +public net.minecraft.data.recipes.SmithingTransformRecipeBuilder category # category +public net.minecraft.data.recipes.SmithingTransformRecipeBuilder result # result +public net.minecraft.data.recipes.SmithingTransformRecipeBuilder addition # addition +public net.minecraft.data.recipes.SmithingTransformRecipeBuilder base # base +public net.minecraft.data.recipes.SmithingTransformRecipeBuilder template # template +public net.minecraft.data.recipes.SmithingTransformRecipeBuilder ensureValid(Lnet/minecraft/resources/ResourceLocation;)V # ensureValid + +public net.minecraft.advancements.critereon.StatePropertiesPredicate$Builder matchers # matchers +public net.minecraft.advancements.critereon.StatePropertiesPredicate$PropertyMatcher +public net.minecraft.advancements.critereon.StatePropertiesPredicate$RangedMatcher + +public net.minecraft.world.entity.LivingEntity onEffectAdded(Lnet/minecraft/world/effect/MobEffectInstance;Lnet/minecraft/world/entity/Entity;)V # onEffectAdded +public net.minecraft.world.entity.LivingEntity onEffectUpdated(Lnet/minecraft/world/effect/MobEffectInstance;ZLnet/minecraft/world/entity/Entity;)V # onEffectUpdated +public net.minecraft.world.entity.LivingEntity onEffectRemoved(Lnet/minecraft/world/effect/MobEffectInstance;)V # onEffectRemoved +public net.minecraft.world.entity.LivingEntity activeEffects # activeEffects +public net.minecraft.world.entity.LivingEntity effectsDirty # effectsDirty +public net.minecraft.server.level.ServerPlayer onEffectAdded(Lnet/minecraft/world/effect/MobEffectInstance;Lnet/minecraft/world/entity/Entity;)V # onEffectAdded +public net.minecraft.server.level.ServerPlayer onEffectUpdated(Lnet/minecraft/world/effect/MobEffectInstance;ZLnet/minecraft/world/entity/Entity;)V # onEffectUpdated +public net.minecraft.server.level.ServerPlayer onEffectRemoved(Lnet/minecraft/world/effect/MobEffectInstance;)V # onEffectRemoved +public net.minecraft.world.effect.MobEffectInstance duration # duration +public net.minecraft.world.effect.MobEffectInstance amplifier # amplifier +public net.minecraft.world.effect.MobEffectInstance ambient # ambient +public net.minecraft.world.effect.MobEffectInstance visible # visible +public net.minecraft.world.effect.MobEffectInstance showIcon # showIcon + +public net.minecraft.client.renderer.LevelRenderer entityRenderDispatcher # entityRenderDispatcher +public net.minecraft.client.renderer.RenderStateShard ADDITIVE_TRANSPARENCY # ADDITIVE_TRANSPARENCY +public net.minecraft.client.renderer.RenderStateShard NO_DEPTH_TEST # NO_DEPTH_TEST +public net.minecraft.client.renderer.RenderStateShard RENDERTYPE_ENTITY_GLINT_SHADER # RENDERTYPE_ENTITY_GLINT_SHADER + + +# public net.minecraft.advancements.Advancement$Builder (Z)V # Builder +# public net.minecraft.advancements.Advancement$Builder (Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/advancements/DisplayInfo;Lnet/minecraft/advancements/AdvancementRewards;Ljava/util/Map;[[Ljava/lang/String;Z)V # Builder + +# public net.minecraft.client.gui.Gui f_92986_ # minecraft +# public net.minecraft.client.gui.Gui f_92977_ # screenWidth +# public net.minecraft.client.gui.Gui f_92978_ # screenHeight + +public net.minecraft.world.SimpleContainer items # items +public net.minecraft.world.inventory.AbstractContainerMenu addDataSlot(Lnet/minecraft/world/inventory/DataSlot;)Lnet/minecraft/world/inventory/DataSlot; # addDataSlot +public net.minecraft.world.entity.player.Inventory hasRemainingSpaceForItem(Lnet/minecraft/world/item/ItemStack;Lnet/minecraft/world/item/ItemStack;)Z # hasRemainingSpaceForItem + +public net.minecraft.world.level.Level getDestroyType(Lnet/minecraft/world/level/GameRules$Key;)Lnet/minecraft/world/level/Explosion$BlockInteraction; # getDestroyType + diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml new file mode 100644 index 0000000..5955eec --- /dev/null +++ b/src/main/resources/META-INF/mods.toml @@ -0,0 +1,23 @@ +modLoader = "javafml" #mandatory +loaderVersion = "${loader_version_range}" #mandatory +license = "${mod_license}" +[[mods]] #mandatory +modId = "${mod_id}" #mandatory +version = "${mod_version}" #mandatory +displayName = "${mod_name}" #mandatory +authors = "${mod_authors}" #optional +description = '''${mod_description}''' +[[mixins]] +config="${mod_id}.mixins.json" +[[dependencies.${ mod_id }]] #optional +modId = "neoforge" #mandatory +type = "required" #mandatory +versionRange = "${neo_version_range}" #mandatory +ordering = "NONE" +side = "BOTH" +[[dependencies.${ mod_id }]] +modId = "minecraft" +type = "required" +versionRange = "${minecraft_version_range}" +ordering = "NONE" +side = "BOTH" \ No newline at end of file diff --git a/src/main/resources/assets/l2library/lang/zh_cn.json b/src/main/resources/assets/l2library/lang/zh_cn.json new file mode 100644 index 0000000..3b78dd5 --- /dev/null +++ b/src/main/resources/assets/l2library/lang/zh_cn.json @@ -0,0 +1,5 @@ +{ + "generic.attack_range": "攻击距离", + "generic.reachDistance": "触及距离", + "forge.swimSpeed": "游泳速度" +} \ No newline at end of file diff --git a/src/main/resources/l2core.mixins.json b/src/main/resources/l2core.mixins.json new file mode 100644 index 0000000..2c6ac9c --- /dev/null +++ b/src/main/resources/l2core.mixins.json @@ -0,0 +1,14 @@ +{ + "required": true, + "priority": 1000, + "package": "dev.xkmc.l2core.mixin", + "compatibilityLevel": "JAVA_21", + "mixins": [ + ], + "client": [ + ], + "injectors": { + "defaultRequire": 1 + }, + "minVersion": "0.8" +} diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta new file mode 100644 index 0000000..b18e3cd --- /dev/null +++ b/src/main/resources/pack.mcmeta @@ -0,0 +1,6 @@ +{ + "pack": { + "description": "examplemod resources", + "pack_format": 8 + } +} diff --git a/src/test/java/organize/GUIGenerator.java b/src/test/java/organize/GUIGenerator.java new file mode 100644 index 0000000..910e5e7 --- /dev/null +++ b/src/test/java/organize/GUIGenerator.java @@ -0,0 +1,267 @@ +package organize; + +import com.google.common.io.Files; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.google.gson.internal.Streams; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class GUIGenerator { + + public static void main(String[] args) throws Exception { + new GUIGenerator("l2library").gen(); + } + + private class Comp { + + private final String name; + private final Item it; + private final int x, y, rx, ry; + + private Comp(String str, JsonObject e) { + name = str; + it = ITEM_MAP.get(e.get("sprite").getAsString()); + x = e.get("x").getAsInt(); + y = e.get("y").getAsInt(); + rx = getInt(e, "rx", 1); + ry = getInt(e, "ry", 1); + } + + @Override + public String toString() { + return name; + } + + private void draw(Graphics g, int cx, int cy) throws IOException { + for (int i = 0; i < rx; i++) + for (int j = 0; j < ry; j++) + g.drawImage(it.getImg(), cx + i * it.w, cy + j * it.h, null); + } + + private int gety0() { + return y - it.h / 2; + } + + private int gety1() { + return gety0() + ry * it.h; + } + + } + + private class Item { + + private final String name, app; + private final int w, h, dx, dy; + + private BufferedImage bimg; + + private Item(String str, String appe, JsonObject e) { + ITEM_MAP.put(appe == null ? str : str + appe, this); + name = str; + app = appe; + w = e.get("w").getAsInt(); + h = e.get("h").getAsInt(); + dx = getInt(e, "dx", 0); + dy = getInt(e, "dy", 0); + } + + @Override + public String toString() { + return app == null ? name : name + app; + } + + private BufferedImage getImg() throws IOException { + if (bimg != null) + return bimg; + String path = GUI + "-templates/sprites/" + name; + if (app != null) + path += "/" + app; + path += ".png"; + return bimg = ImageIO.read(new File(path)); + } + + } + + private final String GUI, DST, CONT, CDST; + + GUIGenerator(String modid) { + GUI = "./src/test/resources/" + modid + "/gui/"; + DST = "./src/test/resources/" + modid + "/assets/textures/gui/"; + CDST = "./src/test/resources/" + modid + "/data/" + modid + "/gui/"; + CONT = GUI + "-templates/container/" + modid + "/"; + } + + private final Map ITEM_MAP = new HashMap<>(); + + void gen() throws IOException { + readSprites(); + File f = new File(CONT); + Item top = ITEM_MAP.get("top"); + Item middle = ITEM_MAP.get("middle"); + for (File fi : f.listFiles()) { + JsonObject e = readJsonFile(fi.getPath()).getAsJsonObject(); + JsonObject out = new JsonObject(); + List side = new ArrayList<>(); + List comp = new ArrayList<>(); + int height = 0; + if (e.has("height")) { + height = e.get("height").getAsInt(); + } + Item bottom = ITEM_MAP.get(e.get("isContainer").getAsBoolean() ? "bottom" : "bottom_screen"); + e.get("side").getAsJsonArray().forEach(s -> side.add(ITEM_MAP.get(s.getAsString()))); + for (Map.Entry ent : e.get("comp").getAsJsonObject().entrySet()) + comp.add(new Comp(ent.getKey(), ent.getValue().getAsJsonObject())); + int y0 = 0, y1 = 0; + for (Comp c : comp) { + y0 = Math.min(y0, c.gety0()); + y1 = Math.max(y1, c.gety1()); + } + if (top.h + y1 - y0 + bottom.h < height) { + y1 = height - bottom.h - top.h + y0; + } + out.addProperty("height", top.h + y1 - y0 + bottom.h); + BufferedImage bimg = new BufferedImage(256, 256, BufferedImage.TYPE_INT_ARGB); + Graphics g = bimg.getGraphics(); + g.drawImage(top.getImg(), 0, 0, null); + for (int i = 0; i < y1 - y0; i++) + g.drawImage(middle.getImg(), 0, top.h + i, null); + g.drawImage(bottom.getImg(), 0, top.h + y1 - y0, null); + JsonObject jarr = new JsonObject(); + for (Comp c : comp) { + int cx = c.x - c.it.w / 2; + int cy = c.y - c.it.h / 2 - y0 + top.h; + c.draw(g, cx, cy); + JsonObject co = new JsonObject(); + co.addProperty("x", cx + c.it.dx); + co.addProperty("y", cy + c.it.dy); + co.addProperty("w", c.it.w); + co.addProperty("h", c.it.h); + co.addProperty("rx", c.rx); + co.addProperty("ry", c.ry); + jarr.add(c.name, co); + } + out.add("comp", jarr); + int dx = 0, dy = 0; + Item pre = null; + JsonObject jside = new JsonObject(); + for (Item s : side) { + JsonObject so = new JsonObject(); + if (pre != null) { + if (pre.h == s.h && top.w + dx + pre.w + s.w < 256) { + dx += pre.w; + } else { + dx = 0; + dy += pre.h; + } + } + so.addProperty("x", top.w + dx); + so.addProperty("y", dy); + so.addProperty("w", s.w); + so.addProperty("h", s.h); + jside.add(s.toString(), so); + g.drawImage(s.getImg(), top.w + dx, dy, null); + pre = s; + } + out.add("side", jside); + g.dispose(); + File fx = new File(DST + "container/" + fi.getName().split("\\.")[0] + ".png"); + check(fx); + ImageIO.write(bimg, "PNG", fx); + write(DST + "coords/" + fi.getName(), out); + write(CDST + "coords/" + fi.getName(), out); + + } + + } + + private int getInt(JsonObject e, String key, int def) { + return e.has(key) ? e.get(key).getAsInt() : def; + } + + private void readSprites() throws IOException { + JsonElement e = readJsonFile(GUI + "-templates/info.json"); + e.getAsJsonObject().entrySet().forEach(ent -> { + String name = ent.getKey(); + JsonObject o = ent.getValue().getAsJsonObject(); + if (o.has("ids")) + o.get("ids").getAsJsonArray().forEach(ele -> new Item(name, ele.getAsString(), o)); + else + new Item(name, null, o); + }); + } + + private void write(String path, JsonObject obj) throws IOException { + File fy = new File(path); + check(fy); + JsonWriter jw = new JsonWriter(Files.newWriter(fy, Charset.defaultCharset())); + jw.setLenient(true); + jw.setIndent("\t"); + Streams.write(obj, jw); + jw.close(); + } + + public static void check(File f) throws IOException { + if (!f.getParentFile().exists()) + f.getParentFile().mkdirs(); + if (!f.exists()) + f.createNewFile(); + } + + private static void delete(File f) { + if (!f.exists()) + return; + if (f.isDirectory()) + for (File fi : f.listFiles()) + delete(fi); + f.delete(); + } + + private static Map> readJson(String path) throws IOException { + JsonElement e = readJsonFile(path); + Map> ans = new HashMap<>(); + e.getAsJsonObject().entrySet().forEach(ent0 -> ent0.getValue().getAsJsonObject().entrySet().forEach(ent1 -> { + String key = ent1.getKey(); + List list; + if (ans.containsKey(key)) + list = ans.get(key); + else + ans.put(key, list = new ArrayList<>()); + ent1.getValue().getAsJsonObject().entrySet().forEach(ent2 -> { + String group = ent2.getKey(); + ent2.getValue().getAsJsonArray().forEach(ent3 -> { + String name = ent3.isJsonObject() ? ent3.toString() : ent3.getAsString(); + if (name.startsWith("_") || name.startsWith("^")) + list.add(group + name); + else if (name.endsWith("_")) + list.add(name + group); + else + list.add(name); + }); + }); + })); + return ans; + } + + private static JsonElement readJsonFile(String path) throws IOException { + File f = new File(path); + JsonReader r = new JsonReader(Files.newReader(f, Charset.defaultCharset())); + JsonElement e = new JsonParser().parse(r); + r.close(); + return e; + } + + +} diff --git a/src/test/java/organize/ResourceOrganizer.java b/src/test/java/organize/ResourceOrganizer.java new file mode 100644 index 0000000..cb6ba71 --- /dev/null +++ b/src/test/java/organize/ResourceOrganizer.java @@ -0,0 +1,122 @@ +package organize; + +import com.google.common.io.Files; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import organize.sub.AssetMisc; +import organize.sub.DataMisc; +import organize.sub.LangFileOrganizer; + +import java.io.File; +import java.io.PrintStream; +import java.nio.charset.StandardCharsets; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public abstract class ResourceOrganizer { + + public static final Gson GSON = new GsonBuilder().setPrettyPrinting().setLenient().create(); + + public static final Map MAP = new LinkedHashMap<>(); + public static String MODID; + public final Type type; + public final String folder; + public final String target; + + public ResourceOrganizer(Type type, String folder, String target) { + this.type = type; + this.folder = folder; + this.target = target; + MAP.put(folder, this); + } + + public static void main(String[] args) throws Exception { + new LangFileOrganizer(); + //new ItemFileOrganizer(); + //new BlockFileOrganizer(); + //new ArmorFileOrganizer(); + //new RecipeFileOrganizer(); + new AssetMisc(); + new DataMisc(); + //new ConfigFileOrganizer(); + //new GeckoMisc(); + File f = new File("./src/test/resources"); + for (File fi : f.listFiles()) { + MODID = fi.getName(); + if (!fi.isDirectory()) + continue; + for (ResourceOrganizer obj : MAP.values()) { + File fo = new File(fi.getPath() + "/" + obj.folder); + if (!fo.exists()) + continue; + obj.organize(fo); + } + } + } + + public static void delete(File f) throws Exception { + if (f.exists()) { + if (f.isDirectory()) + for (File fi : f.listFiles()) + delete(fi); + f.delete(); + } + } + + public static void check(File f) throws Exception { + if (f.exists()) { + f.delete(); + } + if (!f.getParentFile().exists()) + f.getParentFile().mkdirs(); + f.createNewFile(); + } + + public abstract void organize(File f) throws Exception; + + public final String getTargetFolder() { + return getResourceFolder(true) + type + "/" + MODID + "/" + target; + } + + public final String getResourceFolder(boolean main) { + return (main ? "./src/main/resources/" : "./src/test/resources/"); + } + + protected String readFile(String path) { + List list = null; + try { + list = Files.readLines(new File(path), StandardCharsets.UTF_8); + } catch (Exception e) { + e.printStackTrace(); + return ""; + } + String str = ""; + for (String s : list) + str += s + "\n"; + return str.replaceAll("\\^m", MODID); + } + + protected void write(String name, String cont) throws Exception { + File f = new File(name); + check(f); + PrintStream ps = new PrintStream(f, StandardCharsets.UTF_8); + ps.println(cont); + ps.close(); + } + + public enum Type { + ASSETS("assets"), DATA("data"); + + public final String side; + + Type(String side) { + this.side = side; + } + + public String toString() { + return side; + } + } + +} diff --git a/src/test/java/organize/sub/ArmorFileOrganizer.java b/src/test/java/organize/sub/ArmorFileOrganizer.java new file mode 100644 index 0000000..279f56b --- /dev/null +++ b/src/test/java/organize/sub/ArmorFileOrganizer.java @@ -0,0 +1,22 @@ +package organize.sub; + +import com.google.common.io.Files; +import organize.ResourceOrganizer; + +import java.io.File; + +public class ArmorFileOrganizer extends ResourceOrganizer { + + public ArmorFileOrganizer() { + super(Type.ASSETS, "armor", "textures/models/armor/"); + } + + @Override + public void organize(File f) throws Exception { + for (File fi : f.listFiles()) { + File ti = new File(getResourceFolder(true) + "assets/minecraft/" + target + fi.getName()); + check(ti); + Files.copy(fi, ti); + } + } +} diff --git a/src/test/java/organize/sub/AssetMisc.java b/src/test/java/organize/sub/AssetMisc.java new file mode 100644 index 0000000..ae26c07 --- /dev/null +++ b/src/test/java/organize/sub/AssetMisc.java @@ -0,0 +1,38 @@ +package organize.sub; + +import com.google.common.io.Files; +import organize.ResourceOrganizer; + +import java.io.File; + +public class AssetMisc extends ResourceOrganizer { + + public AssetMisc() { + super(Type.ASSETS, "assets", ""); + } + + @Override + public void organize(File f) throws Exception { + for (File fi : f.listFiles()) + process(fi, getTargetFolder(), ""); + } + + private void process(File f, String path, String pre) throws Exception { + if (f.getName().startsWith(".")) + return; + if (f.isDirectory()) { + for (File fi : f.listFiles()) { + String next = f.getName().startsWith("-") || f.getName().startsWith("@") ? path : path + f.getName() + "/"; + String npre = f.getName().startsWith("_") ? pre + f.getName() : + f.getName().endsWith("_") ? f.getName() + pre : + f.getName().startsWith("@") ? f.getName().substring(1) + : pre; + process(fi, next, npre); + } + } else { + File t = new File(path + pre + f.getName()); + check(t); + Files.copy(f, t); + } + } +} diff --git a/src/test/java/organize/sub/BlockFileOrganizer.java b/src/test/java/organize/sub/BlockFileOrganizer.java new file mode 100644 index 0000000..5ce49ed --- /dev/null +++ b/src/test/java/organize/sub/BlockFileOrganizer.java @@ -0,0 +1,44 @@ +package organize.sub; + +import com.google.common.io.Files; +import organize.ResourceOrganizer; + +import java.io.File; + +public class BlockFileOrganizer extends ResourceOrganizer { + + public String texture; + + public BlockFileOrganizer() { + super(Type.ASSETS, "blocks", ""); + } + + + @Override + public void organize(File f) throws Exception { + texture = getTargetFolder() + "textures/block/"; + process("", f); + } + + private void process(String prefix, File f) throws Exception { + String filename = f.getName(); + if (filename.startsWith("-") || filename.startsWith(".")) + return; + filename = f.isDirectory() ? filename : filename.split("\\.")[0]; + String name = filename.startsWith("_") ? prefix + filename : filename.endsWith("_") ? filename + prefix : filename; + if (f.isDirectory()) { + for (File fi : f.listFiles()) { + String file = fi.getName(); + if (file.startsWith("-") || file.startsWith(".")) + continue; + process(name, fi); + } + return; + } + String ext = f.getName().substring(filename.length()); + File ti = new File(texture + name + ext); + check(ti); + Files.copy(f, ti); + } + +} diff --git a/src/test/java/organize/sub/DataMisc.java b/src/test/java/organize/sub/DataMisc.java new file mode 100644 index 0000000..1de2eca --- /dev/null +++ b/src/test/java/organize/sub/DataMisc.java @@ -0,0 +1,32 @@ +package organize.sub; + +import com.google.common.io.Files; +import organize.ResourceOrganizer; + +import java.io.File; + +public class DataMisc extends ResourceOrganizer { + + public DataMisc() { + super(Type.DATA, "data", ""); + } + + @Override + public void organize(File f) throws Exception { + for (File fi : f.listFiles()) + process(fi, getResourceFolder(true) + type + "/"); + } + + private void process(File f, String pre) throws Exception { + if (f.getName().startsWith(".")) + return; + if (f.isDirectory()) { + for (File fi : f.listFiles()) + process(fi, pre + f.getName() + "/"); + } else { + File t = new File(pre + f.getName()); + check(t); + Files.copy(f, t); + } + } +} diff --git a/src/test/java/organize/sub/GeckoMisc.java b/src/test/java/organize/sub/GeckoMisc.java new file mode 100644 index 0000000..216cc29 --- /dev/null +++ b/src/test/java/organize/sub/GeckoMisc.java @@ -0,0 +1,40 @@ +package organize.sub; + +import com.google.common.io.Files; +import organize.ResourceOrganizer; + +import java.io.File; + +public class GeckoMisc extends ResourceOrganizer { + + public GeckoMisc() { + super(Type.ASSETS, "gecko", ""); + } + + @Override + public void organize(File f) throws Exception { + for (File fi : f.listFiles()) + process(fi); + } + + private void process(File f) throws Exception { + if (f.getName().startsWith(".")) + return; + if (f.isDirectory()) { + for (File fi : f.listFiles()) { + process(fi); + } + } else { + String name = f.getName(); + String path = getTargetFolder(); + if (name.endsWith("animation.json")) + path += "animations/"; + else if (name.endsWith("geo.json")) + path += "geo/"; + else path += "textures/gecko/"; + File t = new File(path + f.getName()); + check(t); + Files.copy(f, t); + } + } +} diff --git a/src/test/java/organize/sub/ItemFileOrganizer.java b/src/test/java/organize/sub/ItemFileOrganizer.java new file mode 100644 index 0000000..d64230c --- /dev/null +++ b/src/test/java/organize/sub/ItemFileOrganizer.java @@ -0,0 +1,43 @@ +package organize.sub; + +import com.google.common.io.Files; +import organize.ResourceOrganizer; + +import java.io.File; + +public class ItemFileOrganizer extends ResourceOrganizer { + + public String texture; + + public ItemFileOrganizer() { + super(Type.ASSETS, "items", ""); + } + + + @Override + public void organize(File f) throws Exception { + texture = getTargetFolder() + "textures/item/"; + process("", f); + } + + private void process(String prefix, File f) throws Exception { + String filename = f.getName(); + if (filename.startsWith("-") || filename.startsWith(".")) + return; + filename = f.isDirectory() ? filename : filename.split("\\.")[0]; + String name = filename.startsWith("_") ? prefix + filename : filename.endsWith("_") ? filename + prefix : filename; + if (f.isDirectory()) { + for (File fi : f.listFiles()) { + String file = fi.getName(); + if (file.startsWith("-") || file.startsWith(".")) + continue; + process(name, fi); + } + return; + } + File ti = new File(texture + name + ".png"); + check(ti); + Files.copy(f, ti); + } + +} diff --git a/src/test/java/organize/sub/LangFileOrganizer.java b/src/test/java/organize/sub/LangFileOrganizer.java new file mode 100644 index 0000000..8998afe --- /dev/null +++ b/src/test/java/organize/sub/LangFileOrganizer.java @@ -0,0 +1,82 @@ +package organize.sub; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.mojang.datafixers.util.Pair; +import organize.ResourceOrganizer; + +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class LangFileOrganizer extends ResourceOrganizer { + + public LangFileOrganizer() { + super(Type.ASSETS, "lang", "lang/"); + } + + @Override + public void organize(File f) throws Exception { + for (File fi : f.listFiles()) { + if (!fi.isDirectory()) + continue; + String name = fi.getName(); + File target = new File(getTargetFolder() + name + ".json"); + check(target); + JsonObject dst_json = new JsonObject(); + for (File fj : fi.listFiles()) { + if (!fj.getName().endsWith(".json")) continue; + JsonObject json = new JsonParser().parse(new FileReader(fj.getPath(), StandardCharsets.UTF_8)).getAsJsonObject(); + inject("", json, dst_json); + if (json.has("-cartesian")) { + JsonObject block_list = json.get("-cartesian").getAsJsonObject(); + block_list.entrySet().forEach(ent0 -> { + JsonObject block = ent0.getValue().getAsJsonObject(); + String path = block.get("path").getAsString(); + boolean reverse = block.has("reverse") && block.get("reverse").getAsBoolean(); + boolean dot = block.has("use_dot") && block.get("use_dot").getAsBoolean(); + String con = dot ? "." : "_"; + List> map = new ArrayList<>(); + for (JsonElement vector : block.get("list").getAsJsonArray()) { + if (map.isEmpty()) { + List> finalMap = map; + vector.getAsJsonObject().entrySet().forEach(ent1 -> + finalMap.add(Pair.of(ent1.getKey(), ent1.getValue().getAsString()))); + } else { + map = map.stream().flatMap(ent1 -> vector.getAsJsonObject().entrySet().stream() + .map(ent2 -> Pair.of(ent1.getFirst() + con + ent2.getKey(), + reverse ? ent2.getValue().getAsString() + ent1.getSecond() : + ent1.getSecond() + ent2.getValue().getAsString()))) + .collect(Collectors.toList()); + } + } + for (Pair pair : map) { + dst_json.addProperty(path + "." + pair.getFirst(), pair.getSecond()); + } + + }); + } + } + FileWriter w = new FileWriter(target, StandardCharsets.UTF_8); + w.write(GSON.toJson(dst_json)); + w.close(); + } + } + + private void inject(String path, JsonObject src, JsonObject dst) { + for (Map.Entry ent : src.entrySet()) { + if (ent.getKey().startsWith("-")) continue; + if (ent.getValue().isJsonObject()) { + inject(path + ent.getKey() + ".", ent.getValue().getAsJsonObject(), dst); + } else { + dst.add(path + ent.getKey(), ent.getValue()); + } + } + } +} diff --git a/src/test/java/organize/sub/RecipeFileOrganizer.java b/src/test/java/organize/sub/RecipeFileOrganizer.java new file mode 100644 index 0000000..d12cd57 --- /dev/null +++ b/src/test/java/organize/sub/RecipeFileOrganizer.java @@ -0,0 +1,118 @@ +package organize.sub; + +import com.google.common.io.Files; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import organize.ResourceOrganizer; + +import java.io.File; +import java.io.FileReader; +import java.io.PrintStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class RecipeFileOrganizer extends ResourceOrganizer { + + public RecipeFileOrganizer() { + super(Type.DATA, "recipes", "recipes/"); + } + + @Override + public void organize(File f) throws Exception { + generate(new File(f.getPath() + "/-template")); + process("", "", f, (name, file) -> { + String fs = getTargetFolder() + name; + File ti = new File(fs + ".json"); + check(ti); + Files.copy(file, ti); + }, false); + } + + private void generate(File file) throws Exception { + if (!file.exists()) + return; + File info = new File(file.getPath() + "/-info.json"); + if (!info.exists()) + return; + Map map = new HashMap<>(); + process("", "", file, (name, f) -> { + map.put(name, readFile(f.getPath())); + }, false); + JsonElement elem = new JsonParser().parse(new FileReader(info)); + for (Map.Entry layer_0 : elem.getAsJsonObject().entrySet()) { + List list = new ArrayList<>(); + if (layer_0.getKey().startsWith("-")) { + JsonArray arr = layer_0.getValue().getAsJsonObject().get("-list").getAsJsonArray(); + for (JsonElement e : arr) { + list.add(new Pair(e.getAsString(), map)); + } + } else { + list.add(new Pair(layer_0.getKey(), map)); + } + for (Map.Entry layer_1 : layer_0.getValue().getAsJsonObject().entrySet()) { + String _name = layer_1.getKey(); + if (_name.startsWith("-")) + continue; + for (Pair pair : list) { + String name = _name; + if (name.endsWith("_")) + name = name + pair.name; + else if (name.startsWith("_")) + name = pair.name + name; + else name = name + "_" + pair.name; + File dst = new File(getTargetFolder() + name + ".json"); + check(dst); + String ans = pair.template; + for (Map.Entry layer_2 : layer_1.getValue().getAsJsonObject().entrySet()) { + ans = ans.replaceAll("\\^" + layer_2.getKey(), layer_2.getValue().getAsString()); + } + ans = ans.replaceAll("\\^m", MODID); + ans = ans.replaceAll("\\^n", _name); + PrintStream ps = new PrintStream(dst); + ps.println(ans); + ps.close(); + } + } + } + } + + private void process(String folder, String prefix, File f, ExcCons cons, boolean skip_dash) throws Exception { + String filename = f.getName(); + if (skip_dash && filename.startsWith("-") || filename.startsWith(".")) + return; + filename = f.isDirectory() ? filename : filename.split("\\.")[0]; + String name = filename.startsWith("_") ? prefix + filename : filename.endsWith("_") ? filename + prefix : filename; + String subfolder = skip_dash && !prefix.startsWith("-") && name.equals(filename) ? folder.length() == 0 ? prefix : folder + "/" + prefix : folder; + if (f.isDirectory()) { + for (File fi : f.listFiles()) { + String file = fi.getName(); + if (file.startsWith("-") || file.startsWith(".")) + continue; + process(subfolder, skip_dash ? name : "", fi, cons, true); + } + return; + } + cons.accept(subfolder.length() == 0 ? name : subfolder + "/" + name, f); + } + + private static class Pair { + + private final String name; + private final String template; + + private Pair(String name, Map map) { + this.name = name; + this.template = map.get(name); + } + } + + private interface ExcCons { + + void accept(String name, File file) throws Exception; + + } + +} diff --git a/src/test/java/util/ScalePic.java b/src/test/java/util/ScalePic.java new file mode 100644 index 0000000..b811d78 --- /dev/null +++ b/src/test/java/util/ScalePic.java @@ -0,0 +1,65 @@ + +package util; + +import javax.imageio.ImageIO; +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.IOException; + +public class ScalePic { + + public static void main(String[] args) throws IOException { + //scale(4, "logo1", "logo1"); + //scale(18, "run_bow"); + //scale(16, "sonic_shooter"); + + //resize(64, 0,0,"iron"); + scale(25, "curse_of_spell", "curse_of_spell"); + //resize(18, 1,1,"moonwalk"); + } + + private static void scale(String name) throws IOException { + scale(2, name, name); + scale(8, name, name + "_large"); + } + + private static void resize(int size, int x0, int y0, String name) throws IOException { + File in = new File("./temp/in/" + name + ".png"); + File out = new File("./temp/out/" + name + ".png"); + BufferedImage img = ImageIO.read(in); + int sx = img.getWidth(); + int sy = img.getHeight(); + BufferedImage ans = new BufferedImage(size, size, BufferedImage.TYPE_INT_ARGB); + for (int x = 0; x < sx; x++) + for (int y = 0; y < sy; y++) { + int col = img.getRGB(x, y); + ans.setRGB(x0 + x, y0 + y, col); + } + if (!out.exists()) { + out.createNewFile(); + } + ImageIO.write(ans, "PNG", out); + } + + private static void scale(int scale, String name, String out_name) throws IOException { + File in = new File("./temp/in/" + name + ".png"); + File out = new File("./temp/out/" + out_name + ".png"); + BufferedImage img = ImageIO.read(in); + int sx = img.getWidth(); + int sy = img.getHeight(); + BufferedImage ans = new BufferedImage(sx * scale, sy * scale, BufferedImage.TYPE_INT_ARGB); + for (int x = 0; x < sx; x++) + for (int y = 0; y < sy; y++) { + int col = img.getRGB(x, y); + for (int i = 0; i < scale; i++) + for (int j = 0; j < scale; j++) { + ans.setRGB(x * scale + i, y * scale + j, col); + } + } + if (!out.exists()) { + out.createNewFile(); + } + ImageIO.write(ans, "PNG", out); + } + +} diff --git a/src/test/resources/l2library/assets/textures/gui/container/curios_3.png b/src/test/resources/l2library/assets/textures/gui/container/curios_3.png new file mode 100644 index 0000000..c492f17 Binary files /dev/null and b/src/test/resources/l2library/assets/textures/gui/container/curios_3.png differ diff --git a/src/test/resources/l2library/assets/textures/gui/container/curios_4.png b/src/test/resources/l2library/assets/textures/gui/container/curios_4.png new file mode 100644 index 0000000..765cac4 Binary files /dev/null and b/src/test/resources/l2library/assets/textures/gui/container/curios_4.png differ diff --git a/src/test/resources/l2library/assets/textures/gui/container/curios_5.png b/src/test/resources/l2library/assets/textures/gui/container/curios_5.png new file mode 100644 index 0000000..1e47df4 Binary files /dev/null and b/src/test/resources/l2library/assets/textures/gui/container/curios_5.png differ diff --git a/src/test/resources/l2library/assets/textures/gui/container/curios_6.png b/src/test/resources/l2library/assets/textures/gui/container/curios_6.png new file mode 100644 index 0000000..5fe91c5 Binary files /dev/null and b/src/test/resources/l2library/assets/textures/gui/container/curios_6.png differ diff --git a/src/test/resources/l2library/assets/textures/gui/coords/curios_3.json b/src/test/resources/l2library/assets/textures/gui/coords/curios_3.json new file mode 100644 index 0000000..e5c1e15 --- /dev/null +++ b/src/test/resources/l2library/assets/textures/gui/coords/curios_3.json @@ -0,0 +1,21 @@ +{ + "height": 166, + "comp": { + "grid": { + "x": 8, + "y": 17, + "w": 18, + "h": 18, + "rx": 9, + "ry": 3 + } + }, + "side": { + "slot": { + "x": 176, + "y": 0, + "w": 18, + "h": 18 + } + } +} \ No newline at end of file diff --git a/src/test/resources/l2library/assets/textures/gui/coords/curios_4.json b/src/test/resources/l2library/assets/textures/gui/coords/curios_4.json new file mode 100644 index 0000000..9909d1a --- /dev/null +++ b/src/test/resources/l2library/assets/textures/gui/coords/curios_4.json @@ -0,0 +1,21 @@ +{ + "height": 184, + "comp": { + "grid": { + "x": 8, + "y": 17, + "w": 18, + "h": 18, + "rx": 9, + "ry": 4 + } + }, + "side": { + "slot": { + "x": 176, + "y": 0, + "w": 18, + "h": 18 + } + } +} \ No newline at end of file diff --git a/src/test/resources/l2library/assets/textures/gui/coords/curios_5.json b/src/test/resources/l2library/assets/textures/gui/coords/curios_5.json new file mode 100644 index 0000000..4312008 --- /dev/null +++ b/src/test/resources/l2library/assets/textures/gui/coords/curios_5.json @@ -0,0 +1,21 @@ +{ + "height": 202, + "comp": { + "grid": { + "x": 8, + "y": 17, + "w": 18, + "h": 18, + "rx": 9, + "ry": 5 + } + }, + "side": { + "slot": { + "x": 176, + "y": 0, + "w": 18, + "h": 18 + } + } +} \ No newline at end of file diff --git a/src/test/resources/l2library/assets/textures/gui/coords/curios_6.json b/src/test/resources/l2library/assets/textures/gui/coords/curios_6.json new file mode 100644 index 0000000..f4d143b --- /dev/null +++ b/src/test/resources/l2library/assets/textures/gui/coords/curios_6.json @@ -0,0 +1,21 @@ +{ + "height": 220, + "comp": { + "grid": { + "x": 8, + "y": 17, + "w": 18, + "h": 18, + "rx": 9, + "ry": 6 + } + }, + "side": { + "slot": { + "x": 176, + "y": 0, + "w": 18, + "h": 18 + } + } +} \ No newline at end of file diff --git a/src/test/resources/l2library/data/l2library/gui/coords/curios_3.json b/src/test/resources/l2library/data/l2library/gui/coords/curios_3.json new file mode 100644 index 0000000..e5c1e15 --- /dev/null +++ b/src/test/resources/l2library/data/l2library/gui/coords/curios_3.json @@ -0,0 +1,21 @@ +{ + "height": 166, + "comp": { + "grid": { + "x": 8, + "y": 17, + "w": 18, + "h": 18, + "rx": 9, + "ry": 3 + } + }, + "side": { + "slot": { + "x": 176, + "y": 0, + "w": 18, + "h": 18 + } + } +} \ No newline at end of file diff --git a/src/test/resources/l2library/data/l2library/gui/coords/curios_4.json b/src/test/resources/l2library/data/l2library/gui/coords/curios_4.json new file mode 100644 index 0000000..9909d1a --- /dev/null +++ b/src/test/resources/l2library/data/l2library/gui/coords/curios_4.json @@ -0,0 +1,21 @@ +{ + "height": 184, + "comp": { + "grid": { + "x": 8, + "y": 17, + "w": 18, + "h": 18, + "rx": 9, + "ry": 4 + } + }, + "side": { + "slot": { + "x": 176, + "y": 0, + "w": 18, + "h": 18 + } + } +} \ No newline at end of file diff --git a/src/test/resources/l2library/data/l2library/gui/coords/curios_5.json b/src/test/resources/l2library/data/l2library/gui/coords/curios_5.json new file mode 100644 index 0000000..4312008 --- /dev/null +++ b/src/test/resources/l2library/data/l2library/gui/coords/curios_5.json @@ -0,0 +1,21 @@ +{ + "height": 202, + "comp": { + "grid": { + "x": 8, + "y": 17, + "w": 18, + "h": 18, + "rx": 9, + "ry": 5 + } + }, + "side": { + "slot": { + "x": 176, + "y": 0, + "w": 18, + "h": 18 + } + } +} \ No newline at end of file diff --git a/src/test/resources/l2library/data/l2library/gui/coords/curios_6.json b/src/test/resources/l2library/data/l2library/gui/coords/curios_6.json new file mode 100644 index 0000000..f4d143b --- /dev/null +++ b/src/test/resources/l2library/data/l2library/gui/coords/curios_6.json @@ -0,0 +1,21 @@ +{ + "height": 220, + "comp": { + "grid": { + "x": 8, + "y": 17, + "w": 18, + "h": 18, + "rx": 9, + "ry": 6 + } + }, + "side": { + "slot": { + "x": 176, + "y": 0, + "w": 18, + "h": 18 + } + } +} \ No newline at end of file diff --git a/src/test/resources/l2library/gui/-templates/container/l2library/curios_3.json b/src/test/resources/l2library/gui/-templates/container/l2library/curios_3.json new file mode 100644 index 0000000..e617bb3 --- /dev/null +++ b/src/test/resources/l2library/gui/-templates/container/l2library/curios_3.json @@ -0,0 +1,15 @@ +{ + "side": [ + "slot" + ], + "comp": { + "grid": { + "sprite": "empty_slot", + "x": 16, + "y": 0, + "rx": 9, + "ry": 3 + } + }, + "isContainer": true +} \ No newline at end of file diff --git a/src/test/resources/l2library/gui/-templates/container/l2library/curios_4.json b/src/test/resources/l2library/gui/-templates/container/l2library/curios_4.json new file mode 100644 index 0000000..92ddafe --- /dev/null +++ b/src/test/resources/l2library/gui/-templates/container/l2library/curios_4.json @@ -0,0 +1,15 @@ +{ + "side": [ + "slot" + ], + "comp": { + "grid": { + "sprite": "empty_slot", + "x": 16, + "y": 0, + "rx": 9, + "ry": 4 + } + }, + "isContainer": true +} \ No newline at end of file diff --git a/src/test/resources/l2library/gui/-templates/container/l2library/curios_5.json b/src/test/resources/l2library/gui/-templates/container/l2library/curios_5.json new file mode 100644 index 0000000..f515bb4 --- /dev/null +++ b/src/test/resources/l2library/gui/-templates/container/l2library/curios_5.json @@ -0,0 +1,15 @@ +{ + "side": [ + "slot" + ], + "comp": { + "grid": { + "sprite": "empty_slot", + "x": 16, + "y": 0, + "rx": 9, + "ry": 5 + } + }, + "isContainer": true +} \ No newline at end of file diff --git a/src/test/resources/l2library/gui/-templates/container/l2library/curios_6.json b/src/test/resources/l2library/gui/-templates/container/l2library/curios_6.json new file mode 100644 index 0000000..c270943 --- /dev/null +++ b/src/test/resources/l2library/gui/-templates/container/l2library/curios_6.json @@ -0,0 +1,15 @@ +{ + "side": [ + "slot" + ], + "comp": { + "grid": { + "sprite": "empty_slot", + "x": 16, + "y": 0, + "rx": 9, + "ry": 6 + } + }, + "isContainer": true +} \ No newline at end of file diff --git a/src/test/resources/l2library/gui/-templates/info.json b/src/test/resources/l2library/gui/-templates/info.json new file mode 100644 index 0000000..52fb6c3 --- /dev/null +++ b/src/test/resources/l2library/gui/-templates/info.json @@ -0,0 +1,148 @@ +{ + "toggle_slot": { + "ids": [ + "_0", + "_1", + "_2" + ], + "w": 18, + "h": 18, + "dx": 1, + "dy": 1 + }, + "altas": { + "ids": [ + "_boost_main", + "_boost_sub", + "_stat_container", + "_head", + "_necklace", + "_body", + "_bracelet", + "_belt", + "_disabled" + ], + "w": 16, + "h": 16 + }, + "button": { + "ids": [ + "_1", + "_1p", + "_2", + "_2p" + ], + "w": 8, + "h": 8 + }, + "sort": { + "ids": [ + "_1", + "_1p" + ], + "w": 9, + "h": 11 + }, + "delete": { + "ids": [ + "_on", + "_off" + ], + "w": 18, + "h": 18 + }, + "upgrade": { + "ids": [ + "_on", + "_off" + ], + "w": 18, + "h": 18 + }, + "slider/_top": { + "w": 14, + "h": 1, + "dx": 1, + "dy": 1 + }, + "slider/_bottom": { + "w": 14, + "h": 1, + "dx": 1, + "dy": -1 + }, + "slider/_middle": { + "w": 14, + "h": 1, + "dx": 1, + "dy": 0 + }, + "slider": { + "ids": [ + "_light", + "_dark" + ], + "w": 12, + "h": 15 + }, + "exchange": { + "ids": [ + "_in", + "_out" + ], + "w": 31, + "h": 14 + }, + "slot": { + "w": 18, + "h": 18, + "dx": 1, + "dy": 1 + }, + "empty_slot": { + "w": 18, + "h": 18, + "dx": 1, + "dy": 1 + }, + "result_slot": { + "w": 26, + "h": 26, + "dx": 5, + "dy": 5 + }, + "arrow": { + "ids": [ + "_0", + "_1", + "_2", + "_3" + ], + "w": 22, + "h": 16 + }, + "fire": { + "ids": [ + "_0", + "_1" + ], + "w": 14, + "h": 14 + }, + "top": { + "w": 176, + "h": 16 + }, + "bottom": { + "w": 176, + "h": 96 + }, + "bottom_screen": { + "w": 176, + "h": 7 + }, + "middle": { + "w": 176, + "h": 1 + } +} \ No newline at end of file diff --git a/src/test/resources/l2library/gui/-templates/sprites/altas/_belt.png b/src/test/resources/l2library/gui/-templates/sprites/altas/_belt.png new file mode 100644 index 0000000..3ab24bb Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/altas/_belt.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/altas/_body.png b/src/test/resources/l2library/gui/-templates/sprites/altas/_body.png new file mode 100644 index 0000000..ad9755f Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/altas/_body.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/altas/_boost_main.png b/src/test/resources/l2library/gui/-templates/sprites/altas/_boost_main.png new file mode 100644 index 0000000..c6dc583 Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/altas/_boost_main.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/altas/_boost_sub.png b/src/test/resources/l2library/gui/-templates/sprites/altas/_boost_sub.png new file mode 100644 index 0000000..4e498dc Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/altas/_boost_sub.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/altas/_bracelet.png b/src/test/resources/l2library/gui/-templates/sprites/altas/_bracelet.png new file mode 100644 index 0000000..3c9057c Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/altas/_bracelet.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/altas/_disabled.png b/src/test/resources/l2library/gui/-templates/sprites/altas/_disabled.png new file mode 100644 index 0000000..511c6d1 Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/altas/_disabled.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/altas/_head.png b/src/test/resources/l2library/gui/-templates/sprites/altas/_head.png new file mode 100644 index 0000000..e8be58d Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/altas/_head.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/altas/_necklace.png b/src/test/resources/l2library/gui/-templates/sprites/altas/_necklace.png new file mode 100644 index 0000000..db98a1b Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/altas/_necklace.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/altas/_stat_container.png b/src/test/resources/l2library/gui/-templates/sprites/altas/_stat_container.png new file mode 100644 index 0000000..8c9df8a Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/altas/_stat_container.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/arrow/_0.png b/src/test/resources/l2library/gui/-templates/sprites/arrow/_0.png new file mode 100644 index 0000000..02b2ae3 Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/arrow/_0.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/arrow/_0r.png b/src/test/resources/l2library/gui/-templates/sprites/arrow/_0r.png new file mode 100644 index 0000000..119e811 Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/arrow/_0r.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/arrow/_1.png b/src/test/resources/l2library/gui/-templates/sprites/arrow/_1.png new file mode 100644 index 0000000..25a3f8a Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/arrow/_1.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/arrow/_2.png b/src/test/resources/l2library/gui/-templates/sprites/arrow/_2.png new file mode 100644 index 0000000..16074a0 Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/arrow/_2.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/arrow/_3.png b/src/test/resources/l2library/gui/-templates/sprites/arrow/_3.png new file mode 100644 index 0000000..a1dea1c Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/arrow/_3.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/bottom.png b/src/test/resources/l2library/gui/-templates/sprites/bottom.png new file mode 100644 index 0000000..50b4f84 Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/bottom.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/bottom_screen.png b/src/test/resources/l2library/gui/-templates/sprites/bottom_screen.png new file mode 100644 index 0000000..2ae5fd6 Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/bottom_screen.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/button/_1.png b/src/test/resources/l2library/gui/-templates/sprites/button/_1.png new file mode 100644 index 0000000..bc14818 Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/button/_1.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/button/_1p.png b/src/test/resources/l2library/gui/-templates/sprites/button/_1p.png new file mode 100644 index 0000000..39abeae Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/button/_1p.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/button/_2.png b/src/test/resources/l2library/gui/-templates/sprites/button/_2.png new file mode 100644 index 0000000..569b121 Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/button/_2.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/button/_2p.png b/src/test/resources/l2library/gui/-templates/sprites/button/_2p.png new file mode 100644 index 0000000..c79d54e Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/button/_2p.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/delete/_off.png b/src/test/resources/l2library/gui/-templates/sprites/delete/_off.png new file mode 100644 index 0000000..8535ffe Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/delete/_off.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/delete/_on.png b/src/test/resources/l2library/gui/-templates/sprites/delete/_on.png new file mode 100644 index 0000000..d35e17e Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/delete/_on.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/empty_slot.png b/src/test/resources/l2library/gui/-templates/sprites/empty_slot.png new file mode 100644 index 0000000..7ad1486 Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/empty_slot.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/exchange/_in.png b/src/test/resources/l2library/gui/-templates/sprites/exchange/_in.png new file mode 100644 index 0000000..5f37c9d Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/exchange/_in.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/exchange/_out.png b/src/test/resources/l2library/gui/-templates/sprites/exchange/_out.png new file mode 100644 index 0000000..4ded50e Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/exchange/_out.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/fire/_0.png b/src/test/resources/l2library/gui/-templates/sprites/fire/_0.png new file mode 100644 index 0000000..33ba616 Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/fire/_0.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/fire/_1.png b/src/test/resources/l2library/gui/-templates/sprites/fire/_1.png new file mode 100644 index 0000000..ab4d6ee Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/fire/_1.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/middle.png b/src/test/resources/l2library/gui/-templates/sprites/middle.png new file mode 100644 index 0000000..53c6ef8 Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/middle.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/result_slot.png b/src/test/resources/l2library/gui/-templates/sprites/result_slot.png new file mode 100644 index 0000000..fea036c Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/result_slot.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/slider/_bottom.png b/src/test/resources/l2library/gui/-templates/sprites/slider/_bottom.png new file mode 100644 index 0000000..76f36ab Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/slider/_bottom.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/slider/_dark.png b/src/test/resources/l2library/gui/-templates/sprites/slider/_dark.png new file mode 100644 index 0000000..61dd4b4 Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/slider/_dark.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/slider/_light.png b/src/test/resources/l2library/gui/-templates/sprites/slider/_light.png new file mode 100644 index 0000000..ede330c Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/slider/_light.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/slider/_middle.png b/src/test/resources/l2library/gui/-templates/sprites/slider/_middle.png new file mode 100644 index 0000000..e718f53 Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/slider/_middle.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/slider/_top.png b/src/test/resources/l2library/gui/-templates/sprites/slider/_top.png new file mode 100644 index 0000000..0524056 Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/slider/_top.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/slot.png b/src/test/resources/l2library/gui/-templates/sprites/slot.png new file mode 100644 index 0000000..95d25da Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/slot.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/sort/_1.png b/src/test/resources/l2library/gui/-templates/sprites/sort/_1.png new file mode 100644 index 0000000..2ebcdf5 Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/sort/_1.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/sort/_1p.png b/src/test/resources/l2library/gui/-templates/sprites/sort/_1p.png new file mode 100644 index 0000000..0bc6ff4 Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/sort/_1p.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/toggle_slot/_0.png b/src/test/resources/l2library/gui/-templates/sprites/toggle_slot/_0.png new file mode 100644 index 0000000..95d25da Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/toggle_slot/_0.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/toggle_slot/_1.png b/src/test/resources/l2library/gui/-templates/sprites/toggle_slot/_1.png new file mode 100644 index 0000000..02dccd8 Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/toggle_slot/_1.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/toggle_slot/_2.png b/src/test/resources/l2library/gui/-templates/sprites/toggle_slot/_2.png new file mode 100644 index 0000000..a51e75e Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/toggle_slot/_2.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/top.png b/src/test/resources/l2library/gui/-templates/sprites/top.png new file mode 100644 index 0000000..962d490 Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/top.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/upgrade/_off.png b/src/test/resources/l2library/gui/-templates/sprites/upgrade/_off.png new file mode 100644 index 0000000..8fbe98e Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/upgrade/_off.png differ diff --git a/src/test/resources/l2library/gui/-templates/sprites/upgrade/_on.png b/src/test/resources/l2library/gui/-templates/sprites/upgrade/_on.png new file mode 100644 index 0000000..2c972b8 Binary files /dev/null and b/src/test/resources/l2library/gui/-templates/sprites/upgrade/_on.png differ