From c5316a27638c8b4eb776a6354f736009dcd3d31b Mon Sep 17 00:00:00 2001 From: Zontreck Date: Tue, 13 Feb 2024 22:45:57 -0700 Subject: [PATCH 01/88] Fix compile errors --- src/main/java/dev/zontreck/libzontreck/currency/Bank.java | 8 ++++---- .../libzontreck/currency/events/WalletUpdatedEvent.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/dev/zontreck/libzontreck/currency/Bank.java b/src/main/java/dev/zontreck/libzontreck/currency/Bank.java index a94b4c2..7b38934 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/Bank.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/Bank.java @@ -2,7 +2,6 @@ package dev.zontreck.libzontreck.currency; import com.google.common.collect.Lists; import dev.zontreck.eventsbus.Bus; -import dev.zontreck.eventsbus.Subscribe; import dev.zontreck.libzontreck.LibZontreck; import dev.zontreck.libzontreck.chat.ChatColor; import dev.zontreck.libzontreck.chat.ChatColorFactory; @@ -22,6 +21,7 @@ import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtIo; import net.minecraft.nbt.Tag; import net.minecraftforge.common.MinecraftForge; +import net.minecraftforge.eventbus.api.SubscribeEvent; import java.io.IOException; import java.lang.reflect.InvocationTargetException; @@ -208,9 +208,9 @@ public class Bank Profile.unload(fromProf); - Bus.Post(new WalletUpdatedEvent(from.player_id, fromOld, from.balance, tx)); + MinecraftForge.EVENT_BUS.post(new WalletUpdatedEvent(from.player_id, fromOld, from.balance, tx)); - Bus.Post(new WalletUpdatedEvent(to.player_id, toOld, to.balance, tx)); + MinecraftForge.EVENT_BUS.post(new WalletUpdatedEvent(to.player_id, toOld, to.balance, tx)); if(from.isValidPlayer() && !ServerUtilities.playerIsOffline(from.player_id)) { @@ -232,7 +232,7 @@ public class Bank * This event is fired when wallets get updated. It cannot be cancelled * @param ev The event containing the player ID and new+old wallet data */ - @Subscribe + @SubscribeEvent public static void onWalletUpdate(WalletUpdatedEvent ev) { diff --git a/src/main/java/dev/zontreck/libzontreck/currency/events/WalletUpdatedEvent.java b/src/main/java/dev/zontreck/libzontreck/currency/events/WalletUpdatedEvent.java index 24cdf15..511ec7d 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/events/WalletUpdatedEvent.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/events/WalletUpdatedEvent.java @@ -1,8 +1,8 @@ package dev.zontreck.libzontreck.currency.events; -import dev.zontreck.eventsbus.Event; import dev.zontreck.libzontreck.currency.Transaction; import net.minecraft.world.entity.player.Player; +import net.minecraftforge.eventbus.api.Event; import java.util.UUID; From 8c9fa42619a03309af7cf2b32e899d02b1089b4a Mon Sep 17 00:00:00 2001 From: Zontreck Date: Tue, 13 Feb 2024 22:47:07 -0700 Subject: [PATCH 02/88] Fix compile errors --- src/main/java/dev/zontreck/libzontreck/currency/Bank.java | 2 +- .../libzontreck/currency/events/TransactionEvent.java | 8 +++----- .../networking/packets/S2CWalletUpdatedPacket.java | 3 ++- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/main/java/dev/zontreck/libzontreck/currency/Bank.java b/src/main/java/dev/zontreck/libzontreck/currency/Bank.java index 7b38934..bbc6657 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/Bank.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/Bank.java @@ -143,7 +143,7 @@ public class Bank protected static boolean postTx(Transaction tx) throws InvalidSideException, InvocationTargetException, IllegalAccessException { if(ServerUtilities.isClient())return false; TransactionEvent ev = new TransactionEvent(tx); - if(Bus.Post(ev)) + if(MinecraftForge.EVENT_BUS.post(ev)) { // Send the list of reasons to the user String reasonStr = String.join("\n", ev.reasons); diff --git a/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionEvent.java b/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionEvent.java index 95e2bc9..d0786ea 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionEvent.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionEvent.java @@ -1,15 +1,13 @@ package dev.zontreck.libzontreck.currency.events; -import dev.zontreck.eventsbus.Cancellable; -import dev.zontreck.eventsbus.Event; import dev.zontreck.libzontreck.currency.Transaction; +import net.minecraftforge.eventbus.api.Cancelable; +import net.minecraftforge.eventbus.api.Event; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; import java.util.List; -@Cancellable +@Cancelable public class TransactionEvent extends Event { public Transaction tx; diff --git a/src/main/java/dev/zontreck/libzontreck/networking/packets/S2CWalletUpdatedPacket.java b/src/main/java/dev/zontreck/libzontreck/networking/packets/S2CWalletUpdatedPacket.java index 6d26432..f585f07 100644 --- a/src/main/java/dev/zontreck/libzontreck/networking/packets/S2CWalletUpdatedPacket.java +++ b/src/main/java/dev/zontreck/libzontreck/networking/packets/S2CWalletUpdatedPacket.java @@ -6,6 +6,7 @@ import dev.zontreck.libzontreck.currency.events.WalletUpdatedEvent; import dev.zontreck.libzontreck.util.ServerUtilities; import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; +import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.network.NetworkDirection; import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.simple.SimpleChannel; @@ -63,7 +64,7 @@ public class S2CWalletUpdatedPacket implements IPacket return ServerUtilities.handlePacket(supplier, new Runnable() { @Override public void run() { - Bus.Post(new WalletUpdatedEvent(ID, oldBal, balance, tx)); + MinecraftForge.EVENT_BUS.post(new WalletUpdatedEvent(ID, oldBal, balance, tx)); } }); From 9c4d5ff6db6d41eb756c651ea7e2a6e7629a47fb Mon Sep 17 00:00:00 2001 From: Zontreck Date: Tue, 13 Feb 2024 22:55:17 -0700 Subject: [PATCH 03/88] Remove default forge files --- CREDITS.txt | 65 ------- LICENSE.txt | 520 ---------------------------------------------------- 2 files changed, 585 deletions(-) delete mode 100644 CREDITS.txt delete mode 100644 LICENSE.txt diff --git a/CREDITS.txt b/CREDITS.txt deleted file mode 100644 index a70c53d..0000000 --- a/CREDITS.txt +++ /dev/null @@ -1,65 +0,0 @@ -Minecraft Forge: Credits/Thank You - -Forge is a set of tools and modifications to the Minecraft base game code to assist -mod developers in creating new and exciting content. It has been in development for -several years now, but I would like to take this time thank a few people who have -helped it along it's way. - -First, the people who originally created the Forge projects way back in Minecraft -alpha. Eloraam of RedPower, and SpaceToad of Buildcraft, without their acceptiance -of me taking over the project, who knows what Minecraft modding would be today. - -Secondly, someone who has worked with me, and developed some of the core features -that allow modding to be as functional, and as simple as it is, cpw. For developing -FML, which stabelized the client and server modding ecosystem. As well as the base -loading system that allows us to modify Minecraft's code as elegently as possible. - -Mezz, who has stepped up as the issue and pull request manager. Helping to keep me -sane as well as guiding the community into creating better additions to Forge. - -Searge, Bspks, Fesh0r, ProfMobious, and all the rest over on the MCP team {of which -I am a part}. For creating some of the core tools needed to make Minecraft modding -both possible, and as stable as can be. - On that note, here is some specific information of the MCP data we use: - * Minecraft Coder Pack (MCP) * - Forge Mod Loader and Minecraft Forge have permission to distribute and automatically - download components of MCP and distribute MCP data files. This permission is not - transitive and others wishing to redistribute the Minecraft Forge source independently - should seek permission of MCP or remove the MCP data files and request their users - to download MCP separately. - -And lastly, the countless community members who have spent time submitting bug reports, -pull requests, and just helping out the community in general. Thank you. - ---LexManos - -========================================================================= - -This is Forge Mod Loader. - -You can find the source code at all times at https://github.com/MinecraftForge/MinecraftForge/tree/1.12.x/src/main/java/net/minecraftforge/fml - -This minecraft mod is a clean open source implementation of a mod loader for minecraft servers -and minecraft clients. - -The code is authored by cpw. - -It began by partially implementing an API defined by the client side ModLoader, authored by Risugami. -http://www.minecraftforum.net/topic/75440- -This support has been dropped as of Minecraft release 1.7, as Risugami no longer maintains ModLoader. - -It also contains suggestions and hints and generous helpings of code from LexManos, author of MinecraftForge. -http://www.minecraftforge.net/ - -Additionally, it contains an implementation of topological sort based on that -published at http://keithschwarz.com/interesting/code/?dir=topological-sort - -It also contains code from the Maven project for performing versioned dependency -resolution. http://maven.apache.org/ - -It also contains a partial repackaging of the javaxdelta library from http://sourceforge.net/projects/javaxdelta/ -with credit to it's authors. - -Forge Mod Loader downloads components from the Minecraft Coder Pack -(http://mcp.ocean-labs.de/index.php/Main_Page) with kind permission from the MCP team. - diff --git a/LICENSE.txt b/LICENSE.txt deleted file mode 100644 index b0cbe2b..0000000 --- a/LICENSE.txt +++ /dev/null @@ -1,520 +0,0 @@ -Unless noted below, Minecraft Forge, Forge Mod Loader, and all -parts herein are licensed under the terms of the LGPL 2.1 found -here http://www.gnu.org/licenses/old-licenses/lgpl-2.1.txt and -copied below. - -Homepage: http://minecraftforge.net/ - https://github.com/MinecraftForge/MinecraftForge - - -A note on authorship: -All source artifacts are property of their original author, with -the exclusion of the contents of the patches directory and others -copied from it from time to time. Authorship of the contents of -the patches directory is retained by the Minecraft Forge project. -This is because the patches are partially machine generated -artifacts, and are changed heavily due to the way forge works. -Individual attribution within them is impossible. - -Consent: -All contributions to Forge must consent to the release of any -patch content to the Forge project. - -A note on infectivity: -The LGPL is chosen specifically so that projects may depend on Forge -features without being infected with its license. That is the -purpose of the LGPL. Mods and others using this code via ordinary -Java mechanics for referencing libraries are specifically not bound -by Forge's license for the Mod code. - - -=== MCP Data === -This software includes data from the Minecraft Coder Pack (MCP), with kind permission -from them. The license to MCP data is not transitive - distribution of this data by -third parties requires independent licensing from the MCP team. This data is not -redistributable without permission from the MCP team. - -=== Sharing === -I grant permission for some parts of FML to be redistributed outside the terms of the LGPL, for the benefit of -the minecraft modding community. All contributions to these parts should be licensed under the same additional grant. - --- Runtime patcher -- -License is granted to redistribute the runtime patcher code (src/main/java/net/minecraftforge/fml/common/patcher -and subdirectories) under any alternative open source license as classified by the OSI (http://opensource.org/licenses) - --- ASM transformers -- -License is granted to redistribute the ASM transformer code (src/main/java/net/minecraftforge/common/asm/ and subdirectories) -under any alternative open source license as classified by the OSI (http://opensource.org/licenses) - -========================================================================= -This software includes portions from the Apache Maven project at -http://maven.apache.org/ specifically the ComparableVersion.java code. It is -included based on guidelines at -http://www.softwarefreedom.org/resources/2007/gpl-non-gpl-collaboration.html -with notices intact. The only change is a non-functional change of package name. - -This software contains a partial repackaging of javaxdelta, a BSD licensed program for generating -binary differences and applying them, sourced from the subversion at http://sourceforge.net/projects/javaxdelta/ -authored by genman, heikok, pivot. -The only changes are to replace some Trove collection types with standard Java collections, and repackaged. -========================================================================= - - - 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 From e5ef5713462fa53e26a75ba51458b09e760810da Mon Sep 17 00:00:00 2001 From: Zontreck Date: Wed, 14 Feb 2024 02:50:56 -0700 Subject: [PATCH 04/88] Update to neoforge --- build.gradle | 29 ++++++++---------- gradle.properties | 16 ++++++---- src/main/resources/META-INF/mods.toml | 44 +++++++++++++-------------- src/main/resources/pack.mcmeta | 4 +-- 4 files changed, 46 insertions(+), 47 deletions(-) diff --git a/build.gradle b/build.gradle index 783d07c..9aee3db 100644 --- a/build.gradle +++ b/build.gradle @@ -3,7 +3,8 @@ plugins { id 'idea' id 'maven-publish' id 'java-library' - id 'net.minecraftforge.gradle' version '[6.0,6.2)' + id 'net.neoforged.gradle' version '[6.0.18,6.2)' + id 'org.spongepowered.mixin' version '0.7.+' id 'org.parchmentmc.librarian.forgegradle' version '1.+' } @@ -23,6 +24,9 @@ java { configurations { provided compile.extendsFrom(provided) + implementation.extendsFrom(provided) + runtime.extendsFrom(provided) + minecraftLibrary.extendsFrom(provided) } // Mojang ships Java 17 to end users in 1.18+, so your mod should target Java 17. @@ -73,7 +77,7 @@ minecraft { runs { // applies to all the run configs below configureEach { - workingDirectory project.file('run') + workingDirectory project.file("run/${it.name}") // Recommended logging data for a userdev environment // The markers can be added/remove as needed separated by commas. @@ -112,8 +116,8 @@ minecraft { } data { - // example of overriding the workingDirectory set in configureEach above - workingDirectory project.file('run-data') + // example of overriding the workingDirectory set in configureEach above, uncomment if you want to use it + // workingDirectory project.file('run-data') // Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources. args '--mod', mod_id, '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/') @@ -156,16 +160,12 @@ dependencies { // The "userdev" classifier will be requested and setup by ForgeGradle. // If the group id is "net.minecraft" and the artifact id is one of ["client", "server", "joined"], // then special handling is done to allow a setup of a vanilla dependency without the use of an external repository. - minecraft "net.minecraftforge:forge:${minecraft_version}-${forge_version}" + minecraft "net.neoforged:forge:${minecraft_version}-${neo_version}" provided "dev.zontreck:LibAC:${libac}" - implementation "dev.zontreck:LibAC:${libac}" - minecraftLibrary "dev.zontreck:LibAC:${libac}" provided "dev.zontreck:EventsBus:${eventsbus}" - implementation "dev.zontreck:EventsBus:${eventsbus}" - minecraftLibrary "dev.zontreck:EventsBus:${eventsbus}" // Example mod dependency with JEI - using fg.deobf() ensures the dependency is remapped to your development mappings // The JEI API is declared for compile time use, while the full JEI artifact is used at runtime @@ -187,13 +187,13 @@ dependencies { // A missing property will result in an error. Properties are expanded using ${} Groovy notation. // When "copyIdeResources" is enabled, this will also run before the game launches in IDE environments. // See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html -tasks.named('processResources', ProcessResources).configure { +tasks.withType(ProcessResources).configureEach { var replaceProperties = [ minecraft_version : minecraft_version, minecraft_version_range: minecraft_version_range, - forge_version : forge_version, forge_version_range: forge_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, + mod_authors : mod_authors, mod_description: mod_description, pack_format_number: pack_format_number, ] inputs.properties replaceProperties @@ -204,11 +204,6 @@ tasks.named('processResources', ProcessResources).configure { // Example for how to get properties into the manifest for reading at runtime. tasks.named('jar', Jar).configure { - from { - configurations.provided.asFileTree.collect{zipTree(it)} - } - duplicatesStrategy = DuplicatesStrategy.EXCLUDE - manifest { attributes([ 'Specification-Title' : mod_id, diff --git a/gradle.properties b/gradle.properties index 5ff2ff9..4b1a893 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,12 +16,13 @@ minecraft_version=1.20.1 # The Minecraft version range can use any release version of Minecraft as bounds. # Snapshots, pre-releases, and release candidates are not guaranteed to sort properly # as they do not follow standard versioning conventions. -minecraft_version_range=[1.20.1,1.21) -# The Forge version must agree with the Minecraft version to get a valid artifact -forge_version=47.2.0 -# The Forge version range can use any version of Forge as bounds or match the loader version range -forge_version_range=[47,) -# The loader version range can only use the major version of Forge/FML as bounds + +minecraft_version_range=[1.20,1.21) +# The Neo version must agree with the Minecraft version to get a valid artifact +neo_version=47.1.65 +# The Neo version range can use any version of Neo as bounds or match the loader version range +neo_version_range=[47.1,) +# The loader version range can only use the major version of Neo/FML as bounds loader_version_range=[47,) # The mapping channel to use for mappings. # The default set of supported mapping channels are ["official", "snapshot", "snapshot_nodoc", "stable", "stable_nodoc"]. @@ -62,3 +63,6 @@ mod_group_id=dev.zontreck mod_authors=zontreck # The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list. mod_description=LibZontreck\nLibrary Mod! + +# Pack version - this changes each minecraft release, in general. +pack_format_number=15 \ No newline at end of file diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index a1b1cbb..7cc8ae2 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -5,13 +5,13 @@ # Find more information on toml format here: https://github.com/toml-lang/toml # The name of the mod loader type to load - for regular FML @Mod mods it should be javafml modLoader="javafml" #mandatory -# A version range to match for said mod loader - for regular FML @Mod it will be the forge version -loaderVersion="${loader_version_range}" #mandatory This is typically bumped every Minecraft version by Forge. See our download page for lists of versions. +# A version range to match for said mod loader - for regular FML @Mod it will be the the FML version. This is currently 47. +loaderVersion="${loader_version_range}" #mandatory # The license for you mod. This is mandatory metadata and allows for easier comprehension of your redistributive properties. # Review your options at https://choosealicense.com/. All rights reserved is the default copyright stance, and is thus the default here. license="${mod_license}" # A URL to refer people to when problems occur with this mod -issueTrackerURL="https://github.com/zontreck/LibZontreckMod/issues" #optional +#issueTrackerURL="https://change.me.to.your.issue.tracker.example.invalid/" #optional # A list of mods - how many allowed here is determined by the individual mod loader [[mods]] #mandatory # The modid of the mod @@ -20,7 +20,7 @@ modId="${mod_id}" #mandatory version="${mod_version}" #mandatory # A display name for the mod displayName="${mod_name}" #mandatory -# A URL to query for updates for this mod. See the JSON update specification https://docs.minecraftforge.net/en/latest/misc/updatechecker/ +# A URL to query for updates for this mod. See the JSON update specification https://docs.neoforged.net/docs/misc/updatechecker/ #updateJSONURL="https://change.me.example.invalid/updates.json" #optional # A URL for the "homepage" for this mod, displayed in the mod UI #displayURL="https://change.me.to.your.mods.homepage.example.invalid/" #optional @@ -42,26 +42,26 @@ authors="${mod_authors}" #optional description='''${mod_description}''' # A dependency - use the . to indicate dependency for a specific modid. Dependencies are optional. [[dependencies.${mod_id}]] #optional - # the modid of the dependency - modId="forge" #mandatory - # Does this dependency have to exist - if not, ordering below must be specified - mandatory=true #mandatory - # The version range of the dependency - versionRange="${forge_version_range}" #mandatory - # An ordering relationship for the dependency - BEFORE or AFTER required if the dependency is not mandatory - # BEFORE - This mod is loaded BEFORE the dependency - # AFTER - This mod is loaded AFTER the dependency - ordering="NONE" - # Side this dependency is applied on - BOTH, CLIENT, or SERVER - side="BOTH" +# the modid of the dependency +modId="forge" #mandatory +# Does this dependency have to exist - if not, ordering below must be specified +mandatory=true #mandatory +# The version range of the dependency +versionRange="${neo_version_range}" #mandatory +# An ordering relationship for the dependency - BEFORE or AFTER required if the dependency is not mandatory +# BEFORE - This mod is loaded BEFORE the dependency +# AFTER - This mod is loaded AFTER the dependency +ordering="NONE" +# Side this dependency is applied on - BOTH, CLIENT, or SERVER +side="BOTH" # Here's another dependency [[dependencies.${mod_id}]] - modId="minecraft" - mandatory=true - # This version range declares a minimum of the current minecraft version up to but not including the next major version - versionRange="${minecraft_version_range}" - ordering="NONE" - side="BOTH" +modId="minecraft" +mandatory=true +# This version range declares a minimum of the current minecraft version up to but not including the next major version +versionRange="${minecraft_version_range}" +ordering="NONE" +side="BOTH" # Features are specific properties of the game environment, that you may want to declare you require. This example declares # that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index eca79ae..a7d103a 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -3,6 +3,6 @@ "description": { "text": "${mod_id} resources" }, - "pack_format": 15 - } + "pack_format": ${pack_format_number} +} } \ No newline at end of file From f9b221f5e7d3345a4320a9a6aaae0e64ac006f03 Mon Sep 17 00:00:00 2001 From: Zontreck Date: Wed, 14 Feb 2024 04:02:26 -0700 Subject: [PATCH 05/88] Update the mappings --- build.gradle | 2 +- gradle.properties | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/build.gradle b/build.gradle index 9aee3db..de99d73 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id 'java-library' id 'net.neoforged.gradle' version '[6.0.18,6.2)' id 'org.spongepowered.mixin' version '0.7.+' - id 'org.parchmentmc.librarian.forgegradle' version '1.+' + //id 'org.parchmentmc.librarian.forgegradle' version '1.+' } version = mod_version diff --git a/gradle.properties b/gradle.properties index 4b1a893..bf9ca6c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ org.gradle.jvmargs=-Xmx3G org.gradle.daemon=false -parchment_version=2023.09.03 +# parchment_version=2023.09.03 # luckperms_api_version=5.4 libac=1.4.46 @@ -38,10 +38,10 @@ loader_version_range=[47,) # # Parchment is an unofficial project maintained by ParchmentMC, separate from Minecraft Forge. # Additional setup is needed to use their mappings, see https://parchmentmc.org/docs/getting-started -mapping_channel=parchment +mapping_channel=official # The mapping version to query from the mapping channel. # This must match the format required by the mapping channel. -mapping_version=2023.09.03-1.20.1 +mapping_version=1.20.1 ## Mod Properties @@ -54,7 +54,7 @@ mod_name=Zontreck Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1.10.021324.2257 +mod_version=1.10.021424.0400 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html From 6b90951ab24d028a0c251364ed0adeb112ba9cd5 Mon Sep 17 00:00:00 2001 From: Zontreck Date: Wed, 14 Feb 2024 04:31:25 -0700 Subject: [PATCH 06/88] Remove eventbus dependency --- build.gradle | 13 +++++++++---- gradle.properties | 3 +-- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/build.gradle b/build.gradle index de99d73..d875f62 100644 --- a/build.gradle +++ b/build.gradle @@ -62,7 +62,7 @@ minecraft { // When true, this property will add the folder name of all declared run configurations to generated IDE run configurations. // The folder name can be set on a run configuration using the "folderName" property. // By default, the folder name of a run configuration is the name of the Gradle project containing it. - // generateRunFolders = true + generateRunFolders = true // This property enables access transformers for use in development. // They will be applied to the Minecraft artifact. @@ -164,9 +164,6 @@ dependencies { provided "dev.zontreck:LibAC:${libac}" - - provided "dev.zontreck:EventsBus:${eventsbus}" - // Example mod dependency with JEI - using fg.deobf() ensures the dependency is remapped to your development mappings // The JEI API is declared for compile time use, while the full JEI artifact is used at runtime // compileOnly fg.deobf("mezz.jei:jei-${mc_version}-common-api:${jei_version}") @@ -204,6 +201,14 @@ tasks.withType(ProcessResources).configureEach { // Example for how to get properties into the manifest for reading at runtime. tasks.named('jar', Jar).configure { + + duplicatesStrategy = "exclude" + from { configurations.provided.collect { it.isDirectory() ? it : zipTree(it) } } + + exclude 'META-INF/*' + exclude 'META-INF/*' + exclude 'META-INF/*' + manifest { attributes([ 'Specification-Title' : mod_id, diff --git a/gradle.properties b/gradle.properties index bf9ca6c..46d9712 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,8 +7,7 @@ org.gradle.daemon=false # parchment_version=2023.09.03 # luckperms_api_version=5.4 -libac=1.4.46 -eventsbus=1.0.45 +libac=1.4.48 ## Environment Properties # The Minecraft version must agree with the Forge version to get a valid artifact From a488e0671f3c014941b6ff0072e380b41c7cd5bb Mon Sep 17 00:00:00 2001 From: Zontreck Date: Wed, 14 Feb 2024 04:34:49 -0700 Subject: [PATCH 07/88] Fix compile errors --- src/main/java/dev/zontreck/libzontreck/LibZontreck.java | 7 +++---- .../java/dev/zontreck/libzontreck/currency/Account.java | 4 ++-- src/main/java/dev/zontreck/libzontreck/currency/Bank.java | 8 ++------ .../currency/events/BankAccountCreatedEvent.java | 2 +- .../libzontreck/currency/events/BankReadyEvent.java | 2 +- .../currency/events/TransactionHistoryFlushEvent.java | 2 +- .../networking/packets/S2CWalletUpdatedPacket.java | 2 +- 7 files changed, 11 insertions(+), 16 deletions(-) diff --git a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java index bc5fe5e..a556dc6 100644 --- a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java +++ b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java @@ -9,7 +9,6 @@ import java.util.Map; import java.util.UUID; import dev.zontreck.ariaslib.util.DelayedExecutorService; -import dev.zontreck.eventsbus.Bus; import dev.zontreck.libzontreck.chestgui.ChestGUIRegistry; import dev.zontreck.libzontreck.currency.Bank; import dev.zontreck.libzontreck.currency.CurrencyHelper; @@ -89,14 +88,14 @@ public class LibZontreck { MinecraftForge.EVENT_BUS.register(new NetworkEvents()); MinecraftForge.EVENT_BUS.register(ChestGUIRegistry.class); - Bus.Reset(); ModMenuTypes.REGISTRY.register(bus); //CreativeModeTabs.register(bus); ModItems.register(bus); - Bus.Register(CurrencyHelper.class, null); - Bus.Register(Bank.class, null); + MinecraftForge.EVENT_BUS.register(CurrencyHelper.class); + MinecraftForge.EVENT_BUS.register(Bank.class); + } private void setup(final FMLCommonSetupEvent event) diff --git a/src/main/java/dev/zontreck/libzontreck/currency/Account.java b/src/main/java/dev/zontreck/libzontreck/currency/Account.java index e0b81f8..394be28 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/Account.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/Account.java @@ -1,7 +1,6 @@ package dev.zontreck.libzontreck.currency; -import dev.zontreck.eventsbus.Bus; import dev.zontreck.libzontreck.chat.ChatColor; import dev.zontreck.libzontreck.currency.events.TransactionHistoryFlushEvent; import dev.zontreck.libzontreck.profiles.Profile; @@ -11,6 +10,7 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtUtils; import net.minecraft.nbt.Tag; +import net.minecraftforge.common.MinecraftForge; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; @@ -94,7 +94,7 @@ public class Account LongTermTransactionHistoryRecord rec = LongTermTransactionHistoryRecord.of(player_id); rec.addHistory(history); rec.commit(); - Bus.Post(new TransactionHistoryFlushEvent(this, rec, history)); + MinecraftForge.EVENT_BUS.post(new TransactionHistoryFlushEvent(this, rec, history)); rec = null; history = new ArrayList<>(); } diff --git a/src/main/java/dev/zontreck/libzontreck/currency/Bank.java b/src/main/java/dev/zontreck/libzontreck/currency/Bank.java index bbc6657..2f65b59 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/Bank.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/Bank.java @@ -1,10 +1,6 @@ package dev.zontreck.libzontreck.currency; -import com.google.common.collect.Lists; -import dev.zontreck.eventsbus.Bus; import dev.zontreck.libzontreck.LibZontreck; -import dev.zontreck.libzontreck.chat.ChatColor; -import dev.zontreck.libzontreck.chat.ChatColorFactory; import dev.zontreck.libzontreck.currency.events.BankAccountCreatedEvent; import dev.zontreck.libzontreck.currency.events.BankReadyEvent; import dev.zontreck.libzontreck.currency.events.TransactionEvent; @@ -81,7 +77,7 @@ public class Bank accounts.add(new Account((CompoundTag) t)); } - Bus.Post(new BankReadyEvent()); + MinecraftForge.EVENT_BUS.post(new BankReadyEvent()); } catch (IOException e) { throw new RuntimeException(e); } @@ -128,7 +124,7 @@ public class Bank instance.accounts.add(new Account(ID)); instance.commit(); - Bus.Post(new BankAccountCreatedEvent(getAccount(ID))); + MinecraftForge.EVENT_BUS.post(new BankAccountCreatedEvent(getAccount(ID))); }else { } } diff --git a/src/main/java/dev/zontreck/libzontreck/currency/events/BankAccountCreatedEvent.java b/src/main/java/dev/zontreck/libzontreck/currency/events/BankAccountCreatedEvent.java index 1961f97..6ff98b0 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/events/BankAccountCreatedEvent.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/events/BankAccountCreatedEvent.java @@ -1,8 +1,8 @@ package dev.zontreck.libzontreck.currency.events; -import dev.zontreck.eventsbus.Event; import dev.zontreck.libzontreck.currency.Account; +import net.minecraftforge.eventbus.api.Event; public class BankAccountCreatedEvent extends Event { diff --git a/src/main/java/dev/zontreck/libzontreck/currency/events/BankReadyEvent.java b/src/main/java/dev/zontreck/libzontreck/currency/events/BankReadyEvent.java index c7fcfc0..5dcf914 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/events/BankReadyEvent.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/events/BankReadyEvent.java @@ -1,7 +1,7 @@ package dev.zontreck.libzontreck.currency.events; -import dev.zontreck.eventsbus.Event; +import net.minecraftforge.eventbus.api.Event; /** * Contains no information by itself, it only signals that the Bank is open for business diff --git a/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionHistoryFlushEvent.java b/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionHistoryFlushEvent.java index ef1d1dc..34bb7a8 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionHistoryFlushEvent.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/events/TransactionHistoryFlushEvent.java @@ -1,10 +1,10 @@ package dev.zontreck.libzontreck.currency.events; -import dev.zontreck.eventsbus.Event; import dev.zontreck.libzontreck.currency.Account; import dev.zontreck.libzontreck.currency.LongTermTransactionHistoryRecord; import dev.zontreck.libzontreck.currency.Transaction; +import net.minecraftforge.eventbus.api.Event; import java.util.List; diff --git a/src/main/java/dev/zontreck/libzontreck/networking/packets/S2CWalletUpdatedPacket.java b/src/main/java/dev/zontreck/libzontreck/networking/packets/S2CWalletUpdatedPacket.java index f585f07..059a2ed 100644 --- a/src/main/java/dev/zontreck/libzontreck/networking/packets/S2CWalletUpdatedPacket.java +++ b/src/main/java/dev/zontreck/libzontreck/networking/packets/S2CWalletUpdatedPacket.java @@ -1,6 +1,6 @@ package dev.zontreck.libzontreck.networking.packets; -import dev.zontreck.eventsbus.Bus; + import dev.zontreck.libzontreck.currency.Transaction; import dev.zontreck.libzontreck.currency.events.WalletUpdatedEvent; import dev.zontreck.libzontreck.util.ServerUtilities; From da7bc47e29d20bc4bd208d7714f877c385665146 Mon Sep 17 00:00:00 2001 From: Zontreck Date: Wed, 14 Feb 2024 15:17:41 -0700 Subject: [PATCH 08/88] Update the build, something went wrong? --- gradle.properties | 4 ++-- src/main/resources/pack.mcmeta | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 46d9712..12bdeb2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -49,11 +49,11 @@ mapping_version=1.20.1 # Must match the String constant located in the main mod class annotated with @Mod. mod_id=libzontreck # The human-readable display name for the mod. -mod_name=Zontreck Library Mod +mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1.10.021424.0400 +mod_version=1.10.021424.1517 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/resources/pack.mcmeta b/src/main/resources/pack.mcmeta index a7d103a..7bcc5b9 100644 --- a/src/main/resources/pack.mcmeta +++ b/src/main/resources/pack.mcmeta @@ -4,5 +4,5 @@ "text": "${mod_id} resources" }, "pack_format": ${pack_format_number} -} + } } \ No newline at end of file From aec2a22a5a71418cb149df5991193af57accf03f Mon Sep 17 00:00:00 2001 From: Zontreck Date: Wed, 14 Feb 2024 15:24:21 -0700 Subject: [PATCH 09/88] Reupdate mods.toml - it isnt in the output --- src/main/resources/META-INF/mods.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 7cc8ae2..bd2621f 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -36,7 +36,7 @@ authors="${mod_authors}" #optional # IGNORE_ALL_VERSION means that your mod will not cause a red X if it's present on the client or the server. This is a special case and should only be used if your mod has no server component. # NONE means that no display test is set on your mod. You need to do this yourself, see IExtensionPoint.DisplayTest for more information. You can define any scheme you wish with this value. # IMPORTANT NOTE: this is NOT an instruction as to which environments (CLIENT or DEDICATED SERVER) your mod loads on. Your mod should load (and maybe do nothing!) whereever it finds itself. -#displayTest="MATCH_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional) +displayTest="IGNORE_ALL_VERSION" # MATCH_VERSION is the default if nothing is specified (#optional) # The description text for the mod (multi line!) (#mandatory) description='''${mod_description}''' @@ -67,4 +67,4 @@ side="BOTH" # that your mod requires GL version 3.2 or higher. Other features will be added. They are side aware so declaring this won't # stop your mod loading on the server for example. #[features.${mod_id}] -#openGLVersion="[3.2,)" \ No newline at end of file +#openGLVersion="[3.2,)" From ee8f7386ee3e875ae4cbe3b9b99e47ad5296b310 Mon Sep 17 00:00:00 2001 From: Zontreck Date: Wed, 14 Feb 2024 17:15:13 -0700 Subject: [PATCH 10/88] Fix mods.toml not being compiled into the jar --- build.gradle | 20 ++++++++++++-------- gradle/wrapper/gradle-wrapper.properties | 1 + 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index d875f62..dfa221a 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,6 @@ plugins { id 'eclipse' id 'idea' id 'maven-publish' - id 'java-library' id 'net.neoforged.gradle' version '[6.0.18,6.2)' id 'org.spongepowered.mixin' version '0.7.+' //id 'org.parchmentmc.librarian.forgegradle' version '1.+' @@ -186,11 +185,18 @@ dependencies { // See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html 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, + 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, pack_format_number: pack_format_number, + mod_id : mod_id, + mod_name: mod_name, + mod_license: mod_license, + mod_version: mod_version, + mod_authors : mod_authors, + mod_description: mod_description, + pack_format_number: pack_format_number, ] inputs.properties replaceProperties @@ -205,9 +211,7 @@ tasks.named('jar', Jar).configure { duplicatesStrategy = "exclude" from { configurations.provided.collect { it.isDirectory() ? it : zipTree(it) } } - exclude 'META-INF/*' - exclude 'META-INF/*' - exclude 'META-INF/*' + //exclude 'META-INF/*' manifest { attributes([ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index fae0804..37aef8d 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 112e0d39d114a97fec8f0a18148b56b613354083 Mon Sep 17 00:00:00 2001 From: Zontreck Date: Wed, 14 Feb 2024 17:59:37 -0700 Subject: [PATCH 11/88] Bump version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 12bdeb2..7f2f2cd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1.10.021424.1517 +mod_version=1.10.021424.1759 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html From c5472aef68c55508d6ef6ad836e19bf17de72bac Mon Sep 17 00:00:00 2001 From: Zontreck Date: Thu, 15 Feb 2024 17:08:09 -0700 Subject: [PATCH 12/88] Remove a credits entry --- gradle.properties | 2 +- .../java/dev/zontreck/libzontreck/util/heads/HeadCache.java | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 7f2f2cd..2882034 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1.10.021424.1759 +mod_version=1.10.021524.1704 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/util/heads/HeadCache.java b/src/main/java/dev/zontreck/libzontreck/util/heads/HeadCache.java index 904688c..5e70f7c 100644 --- a/src/main/java/dev/zontreck/libzontreck/util/heads/HeadCache.java +++ b/src/main/java/dev/zontreck/libzontreck/util/heads/HeadCache.java @@ -111,8 +111,7 @@ public class HeadCache new CreditsEntry(HeadUtilities.cachedLookup("zontreck"), "Aria (zontreck)", "Developer, Designer, Artist", "Aria is the primary developer and project maintainer")); creds.add( new CreditsEntry(HeadUtilities.cachedLookup("PossumTheWarrior"), "PossumTheWarrior", "Tester, Adviser, Designer, Artist", "Poss has helped to test the mods from very early on. Poss has also contributed the artwork and mob model for the Possum")); - creds.add( - new CreditsEntry(HeadUtilities.cachedLookup("GemMD"), "GemMD", "Tester, Adviser, Designer", "GemMD has provided advice on marketing and development decisions for various mods")); + creds.add(new CreditsEntry(HeadUtilities.cachedLookup("firesyde424"), "firesyde424", "Tester", "This player has tested and given feedback on my mods")); CREDITS = creds; From fc430f1e116b4e2044970cadf099048ae0894a42 Mon Sep 17 00:00:00 2001 From: Zontreck Date: Fri, 16 Feb 2024 15:05:30 -0700 Subject: [PATCH 13/88] Bump libac version, and change the version numbering scheme to an adapted semver. : mcver.revision.date.time --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 2882034..1bb3321 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ org.gradle.daemon=false # parchment_version=2023.09.03 # luckperms_api_version=5.4 -libac=1.4.48 +libac=1.5.4 ## Environment Properties # The Minecraft version must agree with the Forge version to get a valid artifact @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1.10.021524.1704 +mod_version=1201.11.021624.1504 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html From 86d1e7a26a312f570ec659f19e8bbbe8999a19ce Mon Sep 17 00:00:00 2001 From: Zontreck Date: Fri, 16 Feb 2024 15:38:14 -0700 Subject: [PATCH 14/88] Update the bundled libac --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 1bb3321..937b7cf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ org.gradle.daemon=false # parchment_version=2023.09.03 # luckperms_api_version=5.4 -libac=1.5.4 +libac=1.5.5 ## Environment Properties # The Minecraft version must agree with the Forge version to get a valid artifact @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.11.021624.1504 +mod_version=1201.11.021624.1537 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html From dc99713d30ddbde35f508b412281e23f6aab1d52 Mon Sep 17 00:00:00 2001 From: Zontreck Date: Sun, 18 Feb 2024 04:09:43 -0700 Subject: [PATCH 15/88] Patch libac and bump libzontreck version --- gradle.properties | 4 ++-- .../java/dev/zontreck/libzontreck/util/heads/HeadCache.java | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index 937b7cf..c93186f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ org.gradle.daemon=false # parchment_version=2023.09.03 # luckperms_api_version=5.4 -libac=1.5.5 +libac=1.5.6 ## Environment Properties # The Minecraft version must agree with the Forge version to get a valid artifact @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.11.021624.1537 +mod_version=1201.11.021824.0409 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/util/heads/HeadCache.java b/src/main/java/dev/zontreck/libzontreck/util/heads/HeadCache.java index 5e70f7c..597922a 100644 --- a/src/main/java/dev/zontreck/libzontreck/util/heads/HeadCache.java +++ b/src/main/java/dev/zontreck/libzontreck/util/heads/HeadCache.java @@ -110,8 +110,8 @@ public class HeadCache creds.add( new CreditsEntry(HeadUtilities.cachedLookup("zontreck"), "Aria (zontreck)", "Developer, Designer, Artist", "Aria is the primary developer and project maintainer")); creds.add( - new CreditsEntry(HeadUtilities.cachedLookup("PossumTheWarrior"), "PossumTheWarrior", "Tester, Adviser, Designer, Artist", "Poss has helped to test the mods from very early on. Poss has also contributed the artwork and mob model for the Possum")); - creds.add(new CreditsEntry(HeadUtilities.cachedLookup("firesyde424"), "firesyde424", "Tester", "This player has tested and given feedback on my mods")); + new CreditsEntry(HeadUtilities.cachedLookup("PossumTheWarrior"), "PossumTheWarrior", "Tester, Artist", "Poss has helped to test the mods from very early on. Poss has also contributed the artwork and mob model for the Possum")); + creds.add(new CreditsEntry(HeadUtilities.cachedLookup("firesyde424"), "firesyde424", "Tester", "Firesyde has helped to test my mods and given feedback.")); CREDITS = creds; From 3c58a662caed6bf52e09629cebc9342089325a2f Mon Sep 17 00:00:00 2001 From: Zontreck Date: Sun, 18 Feb 2024 08:34:06 -0700 Subject: [PATCH 16/88] Add a new event for requesting/handling teleports --- .../libzontreck/events/TeleportEvent.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/main/java/dev/zontreck/libzontreck/events/TeleportEvent.java diff --git a/src/main/java/dev/zontreck/libzontreck/events/TeleportEvent.java b/src/main/java/dev/zontreck/libzontreck/events/TeleportEvent.java new file mode 100644 index 0000000..6247793 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/events/TeleportEvent.java @@ -0,0 +1,24 @@ +package dev.zontreck.libzontreck.events; + +import dev.zontreck.libzontreck.vectors.WorldPosition; +import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.eventbus.api.Cancelable; +import net.minecraftforge.eventbus.api.Event; + +/** + * This event should be cancelled if a Teleport Implementation is provided and handles the teleport + *
+ * The event not being cancelled should indicate that the sender should handle teleport themselves. + */ +@Cancelable +public class TeleportEvent extends Event +{ + WorldPosition position; + ServerPlayer player; + + public TeleportEvent(WorldPosition position, ServerPlayer player) + { + this.position=position; + this.player=player; + } +} From b47d24c8b2dc670307355b2049105efa7aa006ea Mon Sep 17 00:00:00 2001 From: Zontreck Date: Sun, 18 Feb 2024 08:34:50 -0700 Subject: [PATCH 17/88] Bump version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index c93186f..b45c6d4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.11.021824.0409 +mod_version=1201.11.021824.0834 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html From 0cfede6187f21305e9b08050c30173296164632e Mon Sep 17 00:00:00 2001 From: Zontreck Date: Sun, 18 Feb 2024 08:38:47 -0700 Subject: [PATCH 18/88] oops, forgot to add getters --- gradle.properties | 2 +- .../dev/zontreck/libzontreck/events/TeleportEvent.java | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index b45c6d4..240d5b5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.11.021824.0834 +mod_version=1201.11.021824.0838 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/events/TeleportEvent.java b/src/main/java/dev/zontreck/libzontreck/events/TeleportEvent.java index 6247793..2e7d77d 100644 --- a/src/main/java/dev/zontreck/libzontreck/events/TeleportEvent.java +++ b/src/main/java/dev/zontreck/libzontreck/events/TeleportEvent.java @@ -21,4 +21,12 @@ public class TeleportEvent extends Event this.position=position; this.player=player; } + + public ServerPlayer getPlayer() { + return player; + } + + public WorldPosition getPosition() { + return position; + } } From d68fa58bf9019164272b6a5ba43e78f650ea3f75 Mon Sep 17 00:00:00 2001 From: Zontreck Date: Sun, 18 Feb 2024 09:26:08 -0700 Subject: [PATCH 19/88] Bundle libac patch --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 240d5b5..9ba5ff5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ org.gradle.daemon=false # parchment_version=2023.09.03 # luckperms_api_version=5.4 -libac=1.5.6 +libac=1.5.8 ## Environment Properties # The Minecraft version must agree with the Forge version to get a valid artifact @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.11.021824.0838 +mod_version=1201.11.021824.0918 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html From d9113f357cdba6f99418f5c093a0486ff35c4e06 Mon Sep 17 00:00:00 2001 From: Zontreck Date: Sun, 18 Feb 2024 10:00:56 -0700 Subject: [PATCH 20/88] Update libac, deprecate delayed executor --- gradle.properties | 4 ++-- src/main/java/dev/zontreck/libzontreck/LibZontreck.java | 1 - .../dev/zontreck/libzontreck/events/ForgeEventHandlers.java | 5 +++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/gradle.properties b/gradle.properties index 9ba5ff5..4535a69 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ org.gradle.daemon=false # parchment_version=2023.09.03 # luckperms_api_version=5.4 -libac=1.5.8 +libac=1.5.9 ## Environment Properties # The Minecraft version must agree with the Forge version to get a valid artifact @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.11.021824.0918 +mod_version=1201.11.021824.0959 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java index a556dc6..0a5190e 100644 --- a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java +++ b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java @@ -116,7 +116,6 @@ public class LibZontreck { public void onServerStopping(final ServerStoppingEvent ev) { ALIVE=false; - DelayedExecutorService.stop(); Iterator iProfile = PROFILES.values().iterator(); while(iProfile.hasNext()) diff --git a/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java b/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java index c2813ec..ba07e63 100644 --- a/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java +++ b/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java @@ -52,7 +52,7 @@ public class ForgeEventHandlers { MinecraftForge.EVENT_BUS.post(new ProfileLoadedEvent(prof, player, level)); - DelayedExecutorService.getInstance().schedule(new Task("send-msg", true) { + Thread tx = new Thread(new Task("send-msg", true) { @Override public void run() { // Check player wallet, then send wallet to client @@ -61,7 +61,8 @@ public class ForgeEventHandlers { S2CServerAvailable avail = new S2CServerAvailable(); avail.send(player); } - }, 10); + }); + tx.start(); } @SubscribeEvent From 5214d31d218e38b2e9b818182a2edb309d2f738f Mon Sep 17 00:00:00 2001 From: Zontreck Date: Tue, 27 Feb 2024 16:06:12 -0700 Subject: [PATCH 21/88] Adopt and update functions from an abandoned library --- gradle.properties | 2 +- .../libzontreck/edlibmc/Auxiliaries.java | 590 +++++++++ .../libzontreck/edlibmc/Containers.java | 138 ++ .../libzontreck/edlibmc/Crafting.java | 440 +++++++ .../libzontreck/edlibmc/Fluidics.java | 485 +++++++ .../zontreck/libzontreck/edlibmc/Guis.java | 466 +++++++ .../libzontreck/edlibmc/Inventories.java | 1134 +++++++++++++++++ .../libzontreck/edlibmc/Networking.java | 409 ++++++ .../edlibmc/OptionalRecipeCondition.java | 191 +++ .../zontreck/libzontreck/edlibmc/Overlay.java | 165 +++ .../zontreck/libzontreck/edlibmc/README.md | 4 + .../libzontreck/edlibmc/Registries.java | 263 ++++ .../libzontreck/edlibmc/RfEnergy.java | 180 +++ .../libzontreck/edlibmc/RsSignals.java | 42 + .../libzontreck/edlibmc/SidedProxy.java | 122 ++ .../libzontreck/edlibmc/SlabSliceBlock.java | 228 ++++ .../libzontreck/edlibmc/StandardBlocks.java | 698 ++++++++++ .../edlibmc/StandardDoorBlock.java | 175 +++ .../edlibmc/StandardEntityBlocks.java | 72 ++ .../edlibmc/StandardFenceBlock.java | 203 +++ .../edlibmc/StandardStairsBlock.java | 59 + .../libzontreck/edlibmc/TooltipDisplay.java | 130 ++ .../libzontreck/edlibmc/VariantSlabBlock.java | 220 ++++ .../libzontreck/edlibmc/VariantWallBlock.java | 200 +++ 24 files changed, 6615 insertions(+), 1 deletion(-) create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Auxiliaries.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Containers.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Crafting.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Fluidics.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Guis.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Inventories.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Networking.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/OptionalRecipeCondition.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Overlay.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/README.md create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/Registries.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/RfEnergy.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/RsSignals.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/SidedProxy.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/SlabSliceBlock.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/StandardBlocks.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/StandardDoorBlock.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/StandardEntityBlocks.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/StandardFenceBlock.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/StandardStairsBlock.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/TooltipDisplay.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/VariantSlabBlock.java create mode 100644 src/main/java/dev/zontreck/libzontreck/edlibmc/VariantWallBlock.java diff --git a/gradle.properties b/gradle.properties index 4535a69..b5ca230 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.11.021824.0959 +mod_version=1201.11.022724.1602 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Auxiliaries.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Auxiliaries.java new file mode 100644 index 0000000..eb57f30 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Auxiliaries.java @@ -0,0 +1,590 @@ +/* + * @file Auxiliaries.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * General commonly used functionality. + */ +package dev.zontreck.libzontreck.edlibmc; + +import com.mojang.blaze3d.platform.InputConstants; +import net.minecraft.ChatFormatting; +import net.minecraft.SharedConstants; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.Registry; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.ComponentUtils; +import net.minecraft.network.chat.MutableComponent; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.shapes.BooleanOp; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.registries.ForgeRegistries; +import org.slf4j.Logger; +import org.lwjgl.glfw.GLFW; + +import javax.annotation.Nullable; +import java.io.BufferedReader; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + + +public class Auxiliaries { + private static String modid; + private static Logger logger; + private static Supplier server_config_supplier = CompoundTag::new; + + public static void init(String modid, Logger logger, Supplier server_config_supplier) { + Auxiliaries.modid = modid; + Auxiliaries.logger = logger; + Auxiliaries.server_config_supplier = server_config_supplier; + } + + // ------------------------------------------------------------------------------------------------------------------- + // Mod specific exports + // ------------------------------------------------------------------------------------------------------------------- + + public static String modid() { + return modid; + } + + public static Logger logger() { + return logger; + } + + // ------------------------------------------------------------------------------------------------------------------- + // Sidedness, system/environment, tagging interfaces + // ------------------------------------------------------------------------------------------------------------------- + + public interface IExperimentalFeature { + } + + public static boolean isModLoaded(final String registry_name) { + return ModList.get().isLoaded(registry_name); + } + + public static boolean isDevelopmentMode() { + return SharedConstants.IS_RUNNING_IN_IDE; + } + + @OnlyIn(Dist.CLIENT) + public static boolean isShiftDown() { + return (InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_LEFT_SHIFT) || + InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_RIGHT_SHIFT)); + } + + @OnlyIn(Dist.CLIENT) + public static boolean isCtrlDown() { + return (InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_LEFT_CONTROL) || + InputConstants.isKeyDown(SidedProxy.mc().getWindow().getWindow(), GLFW.GLFW_KEY_RIGHT_CONTROL)); + } + + // ------------------------------------------------------------------------------------------------------------------- + // Logging + // ------------------------------------------------------------------------------------------------------------------- + + public static void logInfo(final String msg) { + logger.info(msg); + } + + public static void logWarn(final String msg) { + logger.warn(msg); + } + + public static void logError(final String msg) { + logger.error(msg); + } + + public static void logDebug(final String msg) { /*logger.debug(msg);*/ } + + // ------------------------------------------------------------------------------------------------------------------- + // Localization, text formatting + // ------------------------------------------------------------------------------------------------------------------- + + /** + * Text localization wrapper, implicitly prepends `MODID` to the + * translation keys. Forces formatting argument, nullable if no special formatting shall be applied.. + */ + public static MutableComponent localizable(String modtrkey, Object... args) { + return Component.translatable((modtrkey.startsWith("block.") || (modtrkey.startsWith("item."))) ? (modtrkey) : (modid + "." + modtrkey), args); + } + + public static MutableComponent localizable(String modtrkey, @Nullable ChatFormatting color, Object... args) { + final MutableComponent tr = Component.translatable(modid + "." + modtrkey, args); + if (color != null) tr.getStyle().applyFormat(color); + return tr; + } + + public static Component localizable(String modtrkey) { + return localizable(modtrkey, new Object[]{}); + } + + public static Component localizable_block_key(String blocksubkey) { + return Component.translatable("block." + modid + "." + blocksubkey); + } + + @OnlyIn(Dist.CLIENT) + public static String localize(String translationKey, Object... args) { + Component tr = Component.translatable(translationKey, args); + tr.getStyle().applyFormat(ChatFormatting.RESET); + final String ft = tr.getString(); + if (ft.contains("${")) { + // Non-recursive, non-argument lang file entry cross referencing. + Pattern pt = Pattern.compile("\\$\\{([^}]+)\\}"); + Matcher mt = pt.matcher(ft); + StringBuffer sb = new StringBuffer(); + while (mt.find()) { + String m = mt.group(1); + if (m.contains("?")) { + String[] kv = m.split("\\?", 2); + String key = kv[0].trim(); + boolean not = key.startsWith("!"); + if (not) key = key.replaceFirst("!", ""); + m = kv[1].trim(); + if (!server_config_supplier.get().contains(key)) { + m = ""; + } else { + boolean r = server_config_supplier.get().getBoolean(key); + if (not) r = !r; + if (!r) m = ""; + } + } + mt.appendReplacement(sb, Matcher.quoteReplacement((Component.translatable(m)).getString().trim())); + } + mt.appendTail(sb); + return sb.toString(); + } else { + return ft; + } + } + + /** + * Returns true if a given key is translated for the current language. + */ + @OnlyIn(Dist.CLIENT) + public static boolean hasTranslation(String key) { + return net.minecraft.client.resources.language.I18n.exists(key); + } + + public static MutableComponent join(Collection components, String separator) { + return ComponentUtils.formatList(components, Component.literal(separator), Function.identity()); + } + + public static MutableComponent join(Component... components) { + final MutableComponent tc = Component.empty(); + for (Component c : components) { + tc.append(c); + } + return tc; + } + + public static boolean isEmpty(Component component) { + return component.getSiblings().isEmpty() && component.getString().isEmpty(); + } + + public static final class Tooltip { + @OnlyIn(Dist.CLIENT) + public static boolean extendedTipCondition() { + return isShiftDown(); + } + + @OnlyIn(Dist.CLIENT) + public static boolean helpCondition() { + return isShiftDown() && isCtrlDown(); + } + + /** + * Adds an extended tooltip or help tooltip depending on the key states of CTRL and SHIFT. + * Returns true if the localisable help/tip was added, false if not (either not CTL/SHIFT or + * no translation found). + */ + @OnlyIn(Dist.CLIENT) + public static boolean addInformation(@Nullable String advancedTooltipTranslationKey, @Nullable String helpTranslationKey, List tooltip, TooltipFlag flag, boolean addAdvancedTooltipHints) { + // Note: intentionally not using keybinding here, this must be `control` or `shift`. + final boolean help_available = (helpTranslationKey != null) && Auxiliaries.hasTranslation(helpTranslationKey + ".help"); + final boolean tip_available = (advancedTooltipTranslationKey != null) && Auxiliaries.hasTranslation(helpTranslationKey + ".tip"); + if ((!help_available) && (!tip_available)) return false; + String tip_text = ""; + if (helpCondition()) { + if (help_available) tip_text = localize(helpTranslationKey + ".help"); + } else if (extendedTipCondition()) { + if (tip_available) tip_text = localize(advancedTooltipTranslationKey + ".tip"); + } else if (addAdvancedTooltipHints) { + if (tip_available) tip_text += localize(modid + ".tooltip.hint.extended") + (help_available ? " " : ""); + if (help_available) tip_text += localize(modid + ".tooltip.hint.help"); + } + if (tip_text.isEmpty()) return false; + String[] tip_list = tip_text.split("\\r?\\n"); + for (String tip : tip_list) { + tooltip.add(Component.literal(tip.replaceAll("\\s+$", "").replaceAll("^\\s+", "")).withStyle(ChatFormatting.GRAY)); + } + return true; + } + + /** + * Adds an extended tooltip or help tooltip for a given stack depending on the key states of CTRL and SHIFT. + * Format in the lang file is (e.g. for items): "item.MODID.REGISTRYNAME.tip" and "item.MODID.REGISTRYNAME.help". + * Return value see method pattern above. + */ + @OnlyIn(Dist.CLIENT) + public static boolean addInformation(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag, boolean addAdvancedTooltipHints) { + return addInformation(stack.getDescriptionId(), stack.getDescriptionId(), tooltip, flag, addAdvancedTooltipHints); + } + + @OnlyIn(Dist.CLIENT) + public static boolean addInformation(String translation_key, List tooltip) { + if (!Auxiliaries.hasTranslation(translation_key)) return false; + tooltip.add(Component.literal(localize(translation_key).replaceAll("\\s+$", "").replaceAll("^\\s+", "")).withStyle(ChatFormatting.GRAY)); + return true; + } + + } + + @SuppressWarnings("unused") + public static void playerChatMessage(final Player player, final String message) { + player.displayClientMessage(Component.translatable(message.trim()), true); + } + + public static @Nullable Component unserializeTextComponent(String serialized) { + return Component.Serializer.fromJson(serialized); + } + + public static String serializeTextComponent(Component tc) { + return (tc == null) ? ("") : (Component.Serializer.toJson(tc)); + } + + // ------------------------------------------------------------------------------------------------------------------- + // Tag Handling + // ------------------------------------------------------------------------------------------------------------------- + + @SuppressWarnings("deprecation") + public static boolean isInItemTag(Item item, ResourceLocation tag) { + return ForgeRegistries.ITEMS.tags().stream().filter(tg -> tg.getKey().location().equals(tag)).anyMatch(tk -> tk.contains(item)); + } + + @SuppressWarnings("deprecation") + public static boolean isInBlockTag(Block block, ResourceLocation tag) { + return ForgeRegistries.BLOCKS.tags().stream().filter(tg -> tg.getKey().location().equals(tag)).anyMatch(tk -> tk.contains(block)); + } + + @SuppressWarnings("deprecation") + public static ResourceLocation getResourceLocation(Item item) { + return ForgeRegistries.ITEMS.getKey(item); + } + + @SuppressWarnings("deprecation") + public static ResourceLocation getResourceLocation(Block block) { + return ForgeRegistries.BLOCKS.getKey(block); + } + + @SuppressWarnings("deprecation") + public static ResourceLocation getResourceLocation(net.minecraft.world.inventory.MenuType menu) { + return ForgeRegistries.MENU_TYPES.getKey(menu); + } + + @SuppressWarnings("deprecation") + public static ResourceLocation getResourceLocation(net.minecraft.world.level.material.Fluid fluid) { + return ForgeRegistries.FLUIDS.getKey(fluid); + } + + // ------------------------------------------------------------------------------------------------------------------- + // Item NBT data + // ------------------------------------------------------------------------------------------------------------------- + + /** + * Equivalent to getDisplayName(), returns null if no custom name is set. + */ + public static @Nullable Component getItemLabel(ItemStack stack) { + CompoundTag nbt = stack.getTagElement("display"); + if (nbt != null && nbt.contains("Name", 8)) { + try { + Component tc = unserializeTextComponent(nbt.getString("Name")); + if (tc != null) return tc; + nbt.remove("Name"); + } catch (Exception e) { + nbt.remove("Name"); + } + } + return null; + } + + public static ItemStack setItemLabel(ItemStack stack, @Nullable Component name) { + if (name != null) { + CompoundTag nbt = stack.getOrCreateTagElement("display"); + nbt.putString("Name", serializeTextComponent(name)); + } else { + if (stack.hasTag()) stack.removeTagKey("display"); + } + return stack; + } + + // ------------------------------------------------------------------------------------------------------------------- + // Block handling + // ------------------------------------------------------------------------------------------------------------------- + + public static boolean isWaterLogged(BlockState state) { + return state.hasProperty(BlockStateProperties.WATERLOGGED) && state.getValue(BlockStateProperties.WATERLOGGED); + } + + public static AABB getPixeledAABB(double x0, double y0, double z0, double x1, double y1, double z1) { + return new AABB(x0 / 16.0, y0 / 16.0, z0 / 16.0, x1 / 16.0, y1 / 16.0, z1 / 16.0); + } + + public static AABB getRotatedAABB(AABB bb, Direction new_facing) { + return getRotatedAABB(bb, new_facing, false); + } + + public static AABB[] getRotatedAABB(AABB[] bb, Direction new_facing) { + return getRotatedAABB(bb, new_facing, false); + } + + public static AABB getRotatedAABB(AABB bb, Direction new_facing, boolean horizontal_rotation) { + if (!horizontal_rotation) { + switch (new_facing.get3DDataValue()) { + case 0: + return new AABB(1 - bb.maxX, bb.minZ, bb.minY, 1 - bb.minX, bb.maxZ, bb.maxY); // D + case 1: + return new AABB(1 - bb.maxX, 1 - bb.maxZ, 1 - bb.maxY, 1 - bb.minX, 1 - bb.minZ, 1 - bb.minY); // U + case 2: + return new AABB(bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // N --> bb + case 3: + return new AABB(1 - bb.maxX, bb.minY, 1 - bb.maxZ, 1 - bb.minX, bb.maxY, 1 - bb.minZ); // S + case 4: + return new AABB(bb.minZ, bb.minY, 1 - bb.maxX, bb.maxZ, bb.maxY, 1 - bb.minX); // W + case 5: + return new AABB(1 - bb.maxZ, bb.minY, bb.minX, 1 - bb.minZ, bb.maxY, bb.maxX); // E + } + } else { + switch (new_facing.get3DDataValue()) { + case 0: + return new AABB(bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // D --> bb + case 1: + return new AABB(bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // U --> bb + case 2: + return new AABB(bb.minX, bb.minY, bb.minZ, bb.maxX, bb.maxY, bb.maxZ); // N --> bb + case 3: + return new AABB(1 - bb.maxX, bb.minY, 1 - bb.maxZ, 1 - bb.minX, bb.maxY, 1 - bb.minZ); // S + case 4: + return new AABB(bb.minZ, bb.minY, 1 - bb.maxX, bb.maxZ, bb.maxY, 1 - bb.minX); // W + case 5: + return new AABB(1 - bb.maxZ, bb.minY, bb.minX, 1 - bb.minZ, bb.maxY, bb.maxX); // E + } + } + return bb; + } + + public static AABB[] getRotatedAABB(AABB[] bbs, Direction new_facing, boolean horizontal_rotation) { + final AABB[] transformed = new AABB[bbs.length]; + for (int i = 0; i < bbs.length; ++i) transformed[i] = getRotatedAABB(bbs[i], new_facing, horizontal_rotation); + return transformed; + } + + public static AABB getYRotatedAABB(AABB bb, int clockwise_90deg_steps) { + final Direction[] direction_map = new Direction[]{Direction.NORTH, Direction.EAST, Direction.SOUTH, Direction.WEST}; + return getRotatedAABB(bb, direction_map[(clockwise_90deg_steps + 4096) & 0x03], true); + } + + public static AABB[] getYRotatedAABB(AABB[] bbs, int clockwise_90deg_steps) { + final AABB[] transformed = new AABB[bbs.length]; + for (int i = 0; i < bbs.length; ++i) transformed[i] = getYRotatedAABB(bbs[i], clockwise_90deg_steps); + return transformed; + } + + public static AABB getMirroredAABB(AABB bb, Direction.Axis axis) { + return switch (axis) { + case X -> new AABB(1 - bb.maxX, bb.minY, bb.minZ, 1 - bb.minX, bb.maxY, bb.maxZ); + case Y -> new AABB(bb.minX, 1 - bb.maxY, bb.minZ, bb.maxX, 1 - bb.minY, bb.maxZ); + case Z -> new AABB(bb.minX, bb.minY, 1 - bb.maxZ, bb.maxX, bb.maxY, 1 - bb.minZ); + }; + } + + public static AABB[] getMirroredAABB(AABB[] bbs, Direction.Axis axis) { + final AABB[] transformed = new AABB[bbs.length]; + for (int i = 0; i < bbs.length; ++i) transformed[i] = getMirroredAABB(bbs[i], axis); + return transformed; + } + + public static VoxelShape getUnionShape(AABB... aabbs) { + VoxelShape shape = Shapes.empty(); + for (AABB aabb : aabbs) shape = Shapes.joinUnoptimized(shape, Shapes.create(aabb), BooleanOp.OR); + return shape; + } + + public static VoxelShape getUnionShape(AABB[]... aabb_list) { + VoxelShape shape = Shapes.empty(); + for (AABB[] aabbs : aabb_list) { + for (AABB aabb : aabbs) shape = Shapes.joinUnoptimized(shape, Shapes.create(aabb), BooleanOp.OR); + } + return shape; + } + + public static AABB[] getMappedAABB(AABB[] bbs, Function mapper) { + final AABB[] transformed = new AABB[bbs.length]; + for (int i = 0; i < bbs.length; ++i) transformed[i] = mapper.apply(bbs[i]); + return transformed; + } + + public static final class BlockPosRange implements Iterable { + private final int x0, x1, y0, y1, z0, z1; + + public BlockPosRange(int x0, int y0, int z0, int x1, int y1, int z1) { + this.x0 = Math.min(x0, x1); + this.x1 = Math.max(x0, x1); + this.y0 = Math.min(y0, y1); + this.y1 = Math.max(y0, y1); + this.z0 = Math.min(z0, z1); + this.z1 = Math.max(z0, z1); + } + + public static BlockPosRange of(AABB range) { + return new BlockPosRange( + (int) Math.floor(range.minX), + (int) Math.floor(range.minY), + (int) Math.floor(range.minZ), + (int) Math.floor(range.maxX - .0625), + (int) Math.floor(range.maxY - .0625), + (int) Math.floor(range.maxZ - .0625) + ); + } + + public int getXSize() { + return x1 - x0 + 1; + } + + public int getYSize() { + return y1 - y0 + 1; + } + + public int getZSize() { + return z1 - z0 + 1; + } + + public int getArea() { + return getXSize() * getZSize(); + } + + public int getHeight() { + return getYSize(); + } + + public int getVolume() { + return getXSize() * getYSize() * getZSize(); + } + + public BlockPos byXZYIndex(int xyz_index) { + final int xsz = getXSize(), ysz = getYSize(), zsz = getZSize(); + xyz_index = xyz_index % (xsz * ysz * zsz); + final int y = xyz_index / (xsz * zsz); + xyz_index -= y * (xsz * zsz); + final int z = xyz_index / xsz; + xyz_index -= z * xsz; + final int x = xyz_index; + return new BlockPos(x0 + x, y0 + y, z0 + z); + } + + public BlockPos byXZIndex(int xz_index, int y_offset) { + final int xsz = getXSize(), zsz = getZSize(); + xz_index = xz_index % (xsz * zsz); + final int z = xz_index / xsz; + xz_index -= z * xsz; + final int x = xz_index; + return new BlockPos(x0 + x, y0 + y_offset, z0 + z); + } + + public static final class BlockRangeIterator implements Iterator { + private final BlockPosRange range_; + private int x, y, z; + + public BlockRangeIterator(BlockPosRange range) { + range_ = range; + x = range.x0; + y = range.y0; + z = range.z0; + } + + @Override + public boolean hasNext() { + return (z <= range_.z1); + } + + @Override + public BlockPos next() { + if (!hasNext()) throw new NoSuchElementException(); + final BlockPos pos = new BlockPos(x, y, z); + ++x; + if (x > range_.x1) { + x = range_.x0; + ++y; + if (y > range_.y1) { + y = range_.y0; + ++z; + } + } + return pos; + } + } + + @Override + public BlockRangeIterator iterator() { + return new BlockRangeIterator(this); + } + + public Stream stream() { + return java.util.stream.StreamSupport.stream(spliterator(), false); + } + } + + // ------------------------------------------------------------------------------------------------------------------- + // JAR resource related + // ------------------------------------------------------------------------------------------------------------------- + + public static String loadResourceText(InputStream is) { + try { + if (is == null) return ""; + BufferedReader br = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); + return br.lines().collect(Collectors.joining("\n")); + } catch (Throwable e) { + return ""; + } + } + + public static String loadResourceText(String path) { + return loadResourceText(Auxiliaries.class.getResourceAsStream(path)); + } + + public static void logGitVersion(String mod_name) { + try { + // Done during construction to have an exact version in case of a crash while registering. + String version = Auxiliaries.loadResourceText("/.gitversion-" + modid).trim(); + logInfo(mod_name + ((version.isEmpty()) ? (" (dev build)") : (" GIT id #" + version)) + "."); + } catch (Throwable e) { + // (void)e; well, then not. Priority is not to get unneeded crashes because of version logging. + } + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Containers.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Containers.java new file mode 100644 index 0000000..37a1814 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Containers.java @@ -0,0 +1,138 @@ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.util.Mth; +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 net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import java.util.function.BiConsumer; + +public class Containers { + // ------------------------------------------------------------------------------------------------------------------- + // Slots + // ------------------------------------------------------------------------------------------------------------------- + + public static class StorageSlot extends Slot { + protected BiConsumer slot_change_action_ = (oldStack, newStack) -> { + }; + protected int stack_limit_ = 64; + public boolean enabled = true; + + public StorageSlot(Container inventory, int index, int x, int y) { + super(inventory, index, x, y); + } + + public StorageSlot setSlotStackLimit(int limit) { + stack_limit_ = Mth.clamp(limit, 1, 64); + return this; + } + + public int getMaxStackSize() { + return stack_limit_; + } + + public StorageSlot setSlotChangeNotifier(BiConsumer action) { + slot_change_action_ = action; + return this; + } + + @Override + public void onQuickCraft(ItemStack oldStack, ItemStack newStack) { + slot_change_action_.accept(oldStack, newStack); + } // no crafting trigger + + @Override + public void set(ItemStack stack) { + if (stack.is(getItem().getItem())) { + super.set(stack); + } else { + final ItemStack before = getItem().copy(); + super.set(stack); // whatever this does else next to setting inventory. + slot_change_action_.accept(before, getItem()); + } + } + + @Override + public boolean mayPlace(ItemStack stack) { + return enabled && this.container.canPlaceItem(this.getSlotIndex(), stack); + } + + @Override + public int getMaxStackSize(ItemStack stack) { + return Math.min(getMaxStackSize(), stack_limit_); + } + + @OnlyIn(Dist.CLIENT) + public boolean isActive() { + return enabled; + } + } + + public static class LockedSlot extends Slot { + protected int stack_limit_ = 64; + public boolean enabled = true; + + public LockedSlot(Container inventory, int index, int x, int y) { + super(inventory, index, x, y); + } + + public LockedSlot setSlotStackLimit(int limit) { + stack_limit_ = Mth.clamp(limit, 1, 64); + return this; + } + + public int getMaxStackSize() { + return stack_limit_; + } + + @Override + public int getMaxStackSize(ItemStack stack) { + return Math.min(getMaxStackSize(), stack_limit_); + } + + @Override + public boolean mayPlace(ItemStack stack) { + return false; + } + + @Override + public boolean mayPickup(Player player) { + return false; + } + + @OnlyIn(Dist.CLIENT) + public boolean isActive() { + return enabled; + } + } + + public static class HiddenSlot extends Slot { + public HiddenSlot(Container inventory, int index) { + super(inventory, index, 0, 0); + } + + @Override + public int getMaxStackSize(ItemStack stack) { + return getMaxStackSize(); + } + + @Override + public boolean mayPlace(ItemStack stack) { + return false; + } + + @Override + public boolean mayPickup(Player player) { + return false; + } + + @OnlyIn(Dist.CLIENT) + public boolean isActive() { + return false; + } + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Crafting.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Crafting.java new file mode 100644 index 0000000..2708733 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Crafting.java @@ -0,0 +1,440 @@ +/* + * @file Recipes.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Recipe utility functionality. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.NonNullList; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.util.Tuple; +import net.minecraft.world.Container; +import net.minecraft.world.SimpleContainer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.entity.player.StackedContents; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.CraftingContainer; +import net.minecraft.world.item.EnchantedBookItem; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.Items; +import net.minecraft.world.item.crafting.*; +import net.minecraft.world.item.enchantment.Enchantment; +import net.minecraft.world.item.enchantment.EnchantmentHelper; +import net.minecraft.world.item.enchantment.EnchantmentInstance; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.ComposterBlock; +import net.minecraftforge.common.ForgeHooks; +import net.minecraftforge.common.brewing.BrewingRecipeRegistry; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.BiPredicate; + + +public class Crafting { + // ------------------------------------------------------------------------------------------------------------------- + + /** + * Returns a Crafting recipe by registry name. + */ + public static Optional getCraftingRecipe(Level world, ResourceLocation recipe_id) { + Recipe recipe = world.getRecipeManager().byKey(recipe_id).orElse(null); + return (recipe instanceof CraftingRecipe) ? Optional.of((CraftingRecipe) recipe) : Optional.empty(); + } + + /** + * Returns a list of matching recipes by the first N slots (crafting grid slots) of the given inventory. + */ + public static List get3x3CraftingRecipes(Level world, Container crafting_grid_slots) { + return CraftingGrid.instance3x3.getRecipes(world, crafting_grid_slots); + } + + /** + * Returns a recipe by the first N slots (crafting grid slots). + */ + public static Optional get3x3CraftingRecipe(Level world, Container crafting_grid_slots) { + return get3x3CraftingRecipes(world, crafting_grid_slots).stream().findFirst(); + } + + /** + * Returns the result item of the recipe with the given grid layout. + */ + public static ItemStack get3x3CraftingResult(Level world, Container grid, CraftingRecipe recipe) { + return CraftingGrid.instance3x3.getCraftingResult(world, grid, recipe); + } + + /** + * Returns the items remaining in the grid after crafting 3x3. + */ + public static List get3x3RemainingItems(Level world, Container grid, CraftingRecipe recipe) { + return CraftingGrid.instance3x3.getRemainingItems(world, grid, recipe); + } + + public static List get3x3Placement(Level world, CraftingRecipe recipe, Container item_inventory, @Nullable Container crafting_grid) { + final int width = 3; + final int height = 3; + if (!recipe.canCraftInDimensions(width, height)) return Collections.emptyList(); + List used = new ArrayList<>(); //NonNullList.withSize(width*height); + for (int i = width * height; i > 0; --i) used.add(ItemStack.EMPTY); + Container check_inventory = Inventories.copyOf(item_inventory); + Inventories.InventoryRange source = new Inventories.InventoryRange(check_inventory); + final List ingredients = recipe.getIngredients(); + final List preferred = new ArrayList<>(width * height); + if (crafting_grid != null) { + for (int i = 0; i < crafting_grid.getContainerSize(); ++i) { + ItemStack stack = crafting_grid.getItem(i); + if (stack.isEmpty()) continue; + stack = stack.copy(); + stack.setCount(1); + if (!source.extract(stack).isEmpty()) preferred.add(stack); + } + } + for (int i = 0; i < ingredients.size(); ++i) { + final Ingredient ingredient = ingredients.get(i); + if (ingredient == Ingredient.EMPTY) continue; + ItemStack stack = preferred.stream().filter(ingredient).findFirst().orElse(ItemStack.EMPTY); + if (!stack.isEmpty()) { + preferred.remove(stack); + } else { + stack = source.stream().filter(ingredient).findFirst().orElse(ItemStack.EMPTY); + if (stack.isEmpty()) return Collections.emptyList(); + stack = stack.copy(); + stack.setCount(1); + if (source.extract(stack).isEmpty()) return Collections.emptyList(); + } + used.set(i, stack); + } + if (recipe instanceof ShapedRecipe shaped) { + List placement = NonNullList.withSize(width * height, ItemStack.EMPTY); + for (int row = 0; row < shaped.getRecipeHeight(); ++row) { + for (int col = 0; col < shaped.getRecipeWidth(); ++col) { + placement.set(width * row + col, used.get(row * shaped.getRecipeWidth() + col)); + } + } + return placement; + } else { + return used; + } + } + + /** + * Returns the recipe for a given input stack to smelt, null if there is no recipe + * for the given type (SMELTING,BLASTING,SMOKING, etc). + */ + public static > Optional getFurnaceRecipe(RecipeType recipe_type, Level world, ItemStack input_stack) { + if (input_stack.isEmpty()) { + return Optional.empty(); + } else if (recipe_type == RecipeType.SMELTING) { + SimpleContainer inventory = new SimpleContainer(3); + inventory.setItem(0, input_stack); + SmeltingRecipe recipe = world.getRecipeManager().getRecipeFor(RecipeType.SMELTING, inventory, world).orElse(null); + return (recipe == null) ? Optional.empty() : Optional.of(recipe); + } else if (recipe_type == RecipeType.BLASTING) { + SimpleContainer inventory = new SimpleContainer(3); + inventory.setItem(0, input_stack); + BlastingRecipe recipe = world.getRecipeManager().getRecipeFor(RecipeType.BLASTING, inventory, world).orElse(null); + return (recipe == null) ? Optional.empty() : Optional.of(recipe); + } else if (recipe_type == RecipeType.SMOKING) { + SimpleContainer inventory = new SimpleContainer(3); + inventory.setItem(0, input_stack); + SmokingRecipe recipe = world.getRecipeManager().getRecipeFor(RecipeType.SMOKING, inventory, world).orElse(null); + return (recipe == null) ? Optional.empty() : Optional.of(recipe); + } else { + return Optional.empty(); + } + } + + // ------------------------------------------------------------------------------------------------------------------- + + public static > int getSmeltingTimeNeeded(RecipeType recipe_type, Level world, ItemStack stack) { + if (stack.isEmpty()) return 0; + final int t = getFurnaceRecipe(recipe_type, world, stack).map((AbstractCookingRecipe::getCookingTime)).orElse(0); + return (t <= 0) ? 200 : t; + } + + /** + * Returns the burn time of an item when used as fuel, 0 if it is no fuel. + */ + public static int getFuelBurntime(Level world, ItemStack stack) { + if (stack.isEmpty()) return 0; + int t = ForgeHooks.getBurnTime(stack, null); + return Math.max(t, 0); + } + + /** + * Returns true if an item can be used as fuel. + */ + public static boolean isFuel(Level world, ItemStack stack) { + return (getFuelBurntime(world, stack) > 0) || (stack.getItem() == Items.LAVA_BUCKET); + } + + /** + * Returns burntime and remaining stack then the item shall be used as fuel. + */ + public static Tuple consumeFuel(Level world, ItemStack stack) { + if (stack.isEmpty()) return new Tuple<>(0, stack); + int burnime = getFuelBurntime(world, stack); + if ((stack.getItem() == Items.LAVA_BUCKET)) { + if (burnime <= 0) burnime = 1000 * 20; + return new Tuple<>(burnime, new ItemStack(Items.BUCKET)); + } else if (burnime <= 0) { + return new Tuple<>(0, stack); + } else { + ItemStack left_over = stack.copy(); + left_over.shrink(1); + return new Tuple<>(burnime, left_over); + } + } + + /** + * Returns true if the item can be used as brewing fuel. + */ + public static boolean isBrewingFuel(Level world, ItemStack stack) { + return (stack.getItem() == Items.BLAZE_POWDER) || (stack.getItem() == Items.BLAZE_ROD); + } + + // ------------------------------------------------------------------------------------------------------------------- + + /** + * Returns true if the item can be used as brewing ingredient. + */ + public static boolean isBrewingIngredient(Level world, ItemStack stack) { + return BrewingRecipeRegistry.isValidIngredient(stack); + } + + /** + * Returns true if the item can be used as brewing bottle. + */ + public static boolean isBrewingInput(Level world, ItemStack stack) { + return BrewingRecipeRegistry.isValidInput(stack); + } + + /** + * Returns the burn time for brewing of the given stack. + */ + public static int getBrewingFuelBurntime(Level world, ItemStack stack) { + if (stack.isEmpty()) return 0; + if (stack.getItem() == Items.BLAZE_POWDER) return (400 * 20); + if (stack.getItem() == Items.BLAZE_ROD) return (400 * 40); + return 0; + } + + /** + * Returns brewing burn time and remaining stack if the item shall be used as fuel. + */ + public static Tuple consumeBrewingFuel(Level world, ItemStack stack) { + int burntime = getBrewingFuelBurntime(world, stack); + if (burntime <= 0) return new Tuple<>(0, stack.copy()); + stack = stack.copy(); + stack.shrink(1); + return new Tuple<>(burntime, stack.isEmpty() ? ItemStack.EMPTY : stack); + } + + public static double getCompostingChance(ItemStack stack) { + return ComposterBlock.COMPOSTABLES.getOrDefault(stack.getItem(), 0); + } + + /** + * Returns the enchtments bound to the given stack. + */ + public static Map getEnchantmentsOnItem(Level world, ItemStack stack) { + return (stack.isEmpty() || (stack.getTag() == null)) ? Collections.emptyMap() : EnchantmentHelper.getEnchantments(stack); + } + + // ------------------------------------------------------------------------------------------------------------------- + + /** + * Returns an enchanted book with the given enchantment, emtpy stack if not applicable. + */ + public static ItemStack getEnchantmentBook(Level world, Enchantment enchantment, int level) { + return ((!enchantment.isAllowedOnBooks()) || (level <= 0)) ? ItemStack.EMPTY : EnchantedBookItem.createForEnchantment(new EnchantmentInstance(enchantment, level)); + } + + // ------------------------------------------------------------------------------------------------------------------- + + /** + * Returns the accumulated repair cost for the given enchantments. + */ + public static int getEnchantmentRepairCost(Level world, Map enchantments) { + int repair_cost = 0; + for (Map.Entry e : enchantments.entrySet()) + repair_cost = repair_cost * 2 + 1; // @see: RepairContainer.getNewRepairCost() + return repair_cost; + } + + /** + * Trys to add an enchtment to the given stack, returns boolean success. + */ + public static boolean addEnchantmentOnItem(Level world, ItemStack stack, Enchantment enchantment, int level) { + if (stack.isEmpty() || (level <= 0) || (!stack.isEnchantable()) || (level >= enchantment.getMaxLevel())) + return false; + final Map on_item = getEnchantmentsOnItem(world, stack); + if (on_item.keySet().stream().anyMatch(ench -> ench.isCompatibleWith(enchantment))) return false; + final ItemStack book = EnchantedBookItem.createForEnchantment(new EnchantmentInstance(enchantment, level)); + if ((!(stack.isBookEnchantable(book) && enchantment.isAllowedOnBooks())) && (!stack.canApplyAtEnchantingTable(enchantment)) && (!enchantment.canEnchant(stack))) + return false; + final int existing_level = on_item.getOrDefault(enchantment, 0); + if (existing_level > 0) level = Mth.clamp(level + existing_level, 1, enchantment.getMaxLevel()); + on_item.put(enchantment, level); + EnchantmentHelper.setEnchantments(on_item, stack); + stack.setRepairCost(getEnchantmentRepairCost(world, on_item)); + return true; + } + + /** + * Removes enchantments from a stack, returns the removed enchantments. + */ + public static Map removeEnchantmentsOnItem(Level world, ItemStack stack, BiPredicate filter) { + if (stack.isEmpty()) return Collections.emptyMap(); + final Map on_item = getEnchantmentsOnItem(world, stack); + final Map removed = new HashMap<>(); + for (Map.Entry e : on_item.entrySet()) { + if (filter.test(e.getKey(), e.getValue())) { + removed.put(e.getKey(), e.getValue()); + } + } + for (Enchantment e : removed.keySet()) { + on_item.remove(e); + } + EnchantmentHelper.setEnchantments(on_item, stack); + stack.setRepairCost(getEnchantmentRepairCost(world, on_item)); + return removed; + } + + public static final class CraftingGrid implements CraftingContainer { + private static final CraftingGrid instance3x3 = new CraftingGrid(3, 3); + + final int _width; + + private CraftingGrid(int width, int height) { + _width=width; + } + + private void fill(Container grid) { + for (int i = 0; i < getContainerSize(); ++i) + setItem(i, i >= grid.getContainerSize() ? ItemStack.EMPTY : grid.getItem(i)); + } + + public List getRecipes(Level world, Container grid) { + fill(grid); + return world.getRecipeManager().getRecipesFor(RecipeType.CRAFTING, this, world); + } + + public List getRemainingItems(Level world, Container grid, CraftingRecipe recipe) { + fill(grid); + return recipe.getRemainingItems(this); + } + + public ItemStack getCraftingResult(Level world, Container grid, CraftingRecipe recipe) { + fill(grid); + return recipe.assemble(this, world.registryAccess()); + } + + @Override + public int getWidth() { + return _width; + } + + @Override + public int getHeight() { + return 0; + } + + @Override + public List getItems() { + return null; + } + + @Override + public int getContainerSize() { + return 0; + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public ItemStack getItem(int i) { + return null; + } + + @Override + public ItemStack removeItem(int i, int i1) { + return null; + } + + @Override + public ItemStack removeItemNoUpdate(int i) { + return null; + } + + @Override + public void setItem(int i, ItemStack itemStack) { + + } + + @Override + public void setChanged() { + + } + + @Override + public boolean stillValid(Player player) { + return false; + } + + @Override + public void clearContent() { + + } + + @Override + public void fillStackedContents(StackedContents stackedContents) { + + } + } + + public static final class BrewingOutput { + public static final int DEFAULT_BREWING_TIME = 400; + public static final BrewingOutput EMPTY = new BrewingOutput(ItemStack.EMPTY, new SimpleContainer(1), new SimpleContainer(1), 0, 0, DEFAULT_BREWING_TIME); + public final ItemStack item; + public final Container potionInventory; + public final Container ingredientInventory; + public final int potionSlot; + public final int ingredientSlot; + public final int brewTime; + + public BrewingOutput(ItemStack output_potion, Container potion_inventory, Container ingredient_inventory, int potion_slot, int ingredient_slot, int time_needed) { + item = output_potion; + potionInventory = potion_inventory; + ingredientInventory = ingredient_inventory; + potionSlot = potion_slot; + ingredientSlot = ingredient_slot; + brewTime = time_needed; + } + + public static BrewingOutput find(Level world, Container potion_inventory, Container ingredient_inventory) { + for (int potion_slot = 0; potion_slot < potion_inventory.getContainerSize(); ++potion_slot) { + final ItemStack pstack = potion_inventory.getItem(potion_slot); + if (!isBrewingInput(world, pstack)) continue; + for (int ingredient_slot = 0; ingredient_slot < ingredient_inventory.getContainerSize(); ++ingredient_slot) { + final ItemStack istack = ingredient_inventory.getItem(ingredient_slot); + if ((!isBrewingIngredient(world, istack)) || (ingredient_slot == potion_slot) || (isBrewingFuel(world, istack))) + continue; + final ItemStack result = BrewingRecipeRegistry.getOutput(pstack, istack); + if (result.isEmpty()) continue; + return new BrewingOutput(result, potion_inventory, ingredient_inventory, potion_slot, ingredient_slot, DEFAULT_BREWING_TIME); + } + } + return BrewingOutput.EMPTY; + } + } + + +} \ No newline at end of file diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Fluidics.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Fluidics.java new file mode 100644 index 0000000..6ac9dbd --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Fluidics.java @@ -0,0 +1,485 @@ +/* + * @file Fluidics.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * General fluid handling functionality. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.util.Mth; +import net.minecraft.util.Tuple; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.material.Fluid; +import net.minecraftforge.common.capabilities.Capability; +import net.minecraftforge.common.capabilities.ForgeCapabilities; +import net.minecraftforge.common.capabilities.ICapabilityProvider; +import net.minecraft.nbt.Tag; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.fluids.FluidActionResult; +import net.minecraftforge.fluids.FluidStack; +import net.minecraftforge.fluids.FluidUtil; +import net.minecraftforge.fluids.IFluidTank; +import net.minecraftforge.fluids.capability.IFluidHandler; +import net.minecraftforge.fluids.capability.IFluidHandler.FluidAction; +import net.minecraftforge.fluids.capability.IFluidHandlerItem; +import net.minecraftforge.items.IItemHandler; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Predicate; + + +public class Fluidics { + public static class SingleTankFluidHandler implements IFluidHandler { + private final IFluidTank tank_; + + public SingleTankFluidHandler(IFluidTank tank) { + tank_ = tank; + } + + @Override + public int getTanks() { + return 1; + } + + @Override + public FluidStack getFluidInTank(int tank) { + return tank_.getFluid(); + } + + @Override + public int getTankCapacity(int tank) { + return tank_.getCapacity(); + } + + @Override + public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { + return tank_.isFluidValid(stack); + } + + @Override + public int fill(FluidStack resource, FluidAction action) { + return tank_.fill(resource, action); + } + + @Override + public FluidStack drain(FluidStack resource, FluidAction action) { + return tank_.drain(resource, action); + } + + @Override + public FluidStack drain(int maxDrain, FluidAction action) { + return tank_.drain(maxDrain, action); + } + } + + private static class SingleTankOutputFluidHandler implements IFluidHandler { + private final IFluidTank tank_; + + public SingleTankOutputFluidHandler(IFluidTank tank) { + tank_ = tank; + } + + @Override + public int getTanks() { + return 1; + } + + @Override + public FluidStack getFluidInTank(int tank) { + return tank_.getFluid().copy(); + } + + @Override + public int getTankCapacity(int tank) { + return tank_.getCapacity(); + } + + @Override + public boolean isFluidValid(int tank, @Nonnull FluidStack stack) { + return true; + } + + @Override + public int fill(FluidStack resource, FluidAction action) { + return 0; + } + + @Override + public FluidStack drain(FluidStack resource, FluidAction action) { + return tank_.drain(resource, action); + } + + @Override + public FluidStack drain(int maxDrain, FluidAction action) { + return tank_.drain(maxDrain, action); + } + } + + public static class Tank implements IFluidTank { + private Predicate validator_ = ((e) -> true); + private BiConsumer interaction_notifier_ = ((tank, diff) -> { + }); + private FluidStack fluid_ = FluidStack.EMPTY; + private int capacity_; + private int fill_rate_; + private int drain_rate_; + + public Tank(int capacity) { + this(capacity, capacity, capacity); + } + + public Tank(int capacity, int fill_rate, int drain_rate) { + this(capacity, fill_rate, drain_rate, e -> true); + } + + public Tank(int capacity, int fill_rate, int drain_rate, Predicate validator) { + capacity_ = capacity; + setMaxFillRate(fill_rate); + setMaxDrainRate(drain_rate); + setValidator(validator); + } + + public Tank load(CompoundTag nbt) { + if (nbt.contains("tank", Tag.TAG_COMPOUND)) { + setFluid(FluidStack.loadFluidStackFromNBT(nbt.getCompound("tank"))); + } else { + clear(); + } + return this; + } + + public CompoundTag save(CompoundTag nbt) { + if (!isEmpty()) { + nbt.put("tank", fluid_.writeToNBT(new CompoundTag())); + } + return nbt; + } + + public void reset() { + clear(); + } + + public Tank clear() { + setFluid(null); + return this; + } + + public int getCapacity() { + return capacity_; + } + + public Tank setCapacity(int capacity) { + capacity_ = capacity; + return this; + } + + public int getMaxDrainRate() { + return drain_rate_; + } + + public Tank setMaxDrainRate(int rate) { + drain_rate_ = Mth.clamp(rate, 0, capacity_); + return this; + } + + public int getMaxFillRate() { + return fill_rate_; + } + + public Tank setMaxFillRate(int rate) { + fill_rate_ = Mth.clamp(rate, 0, capacity_); + return this; + } + + public Tank setValidator(Predicate validator) { + validator_ = (validator != null) ? validator : ((e) -> true); + return this; + } + + public Tank setInteractionNotifier(BiConsumer notifier) { + interaction_notifier_ = (notifier != null) ? notifier : ((tank, diff) -> { + }); + return this; + } + + public LazyOptional createFluidHandler() { + return LazyOptional.of(() -> new Fluidics.SingleTankFluidHandler(this)); + } + + public LazyOptional createOutputFluidHandler() { + return LazyOptional.of(() -> new Fluidics.SingleTankOutputFluidHandler(this)); + } + + // IFluidTank ------------------------------------------------------------------------------------ + + @Nonnull + public FluidStack getFluid() { + return fluid_; + } + + public void setFluid(@Nullable FluidStack stack) { + fluid_ = (stack == null) ? FluidStack.EMPTY : stack; + } + + public int getFluidAmount() { + return fluid_.getAmount(); + } + + public boolean isEmpty() { + return fluid_.isEmpty(); + } + + public boolean isFull() { + return getFluidAmount() >= getCapacity(); + } + + public boolean isFluidValid(FluidStack stack) { + return validator_.test(stack); + } + + public boolean isFluidEqual(FluidStack stack) { + return (stack == null) ? (fluid_.isEmpty()) : fluid_.isFluidEqual(stack); + } + + @Override + public int fill(FluidStack fs, FluidAction action) { + if ((fs == null) || fs.isEmpty() || (!isFluidValid(fs))) { + return 0; + } else if (action.simulate()) { + if (fluid_.isEmpty()) return Math.min(capacity_, fs.getAmount()); + if (!fluid_.isFluidEqual(fs)) return 0; + return Math.min(capacity_ - fluid_.getAmount(), fs.getAmount()); + } else if (fluid_.isEmpty()) { + fluid_ = new FluidStack(fs, Math.min(capacity_, fs.getAmount())); + return fluid_.getAmount(); + } else if (!fluid_.isFluidEqual(fs)) { + return 0; + } else { + int amount = capacity_ - fluid_.getAmount(); + if (fs.getAmount() < amount) { + fluid_.grow(fs.getAmount()); + amount = fs.getAmount(); + } else { + fluid_.setAmount(capacity_); + } + if (amount != 0) interaction_notifier_.accept(this, amount); + return amount; + } + } + + @Nonnull + public FluidStack drain(int maxDrain) { + return drain(maxDrain, FluidAction.EXECUTE); + } + + @Nonnull + @Override + public FluidStack drain(FluidStack fs, FluidAction action) { + return ((fs.isEmpty()) || (!fs.isFluidEqual(fluid_))) ? FluidStack.EMPTY : drain(fs.getAmount(), action); + } + + @Nonnull + @Override + public FluidStack drain(int maxDrain, FluidAction action) { + final int amount = Math.min(fluid_.getAmount(), maxDrain); + final FluidStack stack = new FluidStack(fluid_, amount); + if ((amount > 0) && action.execute()) { + fluid_.shrink(amount); + if (fluid_.isEmpty()) fluid_ = FluidStack.EMPTY; + if (amount != 0) interaction_notifier_.accept(this, -amount); + } + return stack; + } + } + + // ------------------------------------------------------------------------------------------------------------------- + + public static @Nullable IFluidHandler handler(Level world, BlockPos pos, @Nullable Direction side) { + return FluidUtil.getFluidHandler(world, pos, side).orElse(null); + } + + /** + * Fills or drains items with fluid handlers from or into tile blocks with fluid handlers. + */ + public static boolean manualFluidHandlerInteraction(Level world, BlockPos pos, @Nullable Direction side, Player player, InteractionHand hand) { + return manualTrackedFluidHandlerInteraction(world, pos, side, player, hand) != null; + } + + public static boolean manualFluidHandlerInteraction(Player player, InteractionHand hand, IFluidHandler handler) { + return FluidUtil.interactWithFluidHandler(player, hand, handler); + } + + /** + * Fills or drains items with fluid handlers from or into tile blocks with fluid handlers. + * Returns the fluid and (possibly negative) amount that transferred from the item into the block. + */ + public static @Nullable Tuple manualTrackedFluidHandlerInteraction(Level world, BlockPos pos, @Nullable Direction side, Player player, InteractionHand hand) { + if (world.isClientSide()) return null; + final ItemStack held = player.getItemInHand(hand); + if (held.isEmpty()) return null; + final IFluidHandler fh = handler(world, pos, side); + if (fh == null) return null; + final IItemHandler ih = player.getCapability(ForgeCapabilities.ITEM_HANDLER).orElse(null); + if (ih == null) return null; + FluidActionResult far = FluidUtil.tryFillContainerAndStow(held, fh, ih, Integer.MAX_VALUE, player, true); + if (!far.isSuccess()) far = FluidUtil.tryEmptyContainerAndStow(held, fh, ih, Integer.MAX_VALUE, player, true); + if (!far.isSuccess()) return null; + final ItemStack rstack = far.getResult().copy(); + player.setItemInHand(hand, far.getResult()); + final IFluidHandler fh_before = FluidUtil.getFluidHandler(held).orElse(null); + final IFluidHandler fh_after = FluidUtil.getFluidHandler(rstack).orElse(null); + if ((fh_before == null) || (fh_after == null) || (fh_after.getTanks() != fh_before.getTanks())) + return null; // should not be, but y'never know. + for (int i = 0; i < fh_before.getTanks(); ++i) { + final int vol_before = fh_before.getFluidInTank(i).getAmount(); + final int vol_after = fh_after.getFluidInTank(i).getAmount(); + if (vol_before != vol_after) { + return new Tuple<>( + (vol_before > 0) ? (fh_before.getFluidInTank(i).getFluid()) : (fh_after.getFluidInTank(i).getFluid()), + (vol_before - vol_after) + ); + } + } + return null; + } + + public static boolean manualFluidHandlerInteraction(Player player, InteractionHand hand, Level world, BlockPos pos, @Nullable Direction side) { + return FluidUtil.interactWithFluidHandler(player, hand, world, pos, side); + } + + public static int fill(Level world, BlockPos pos, Direction side, FluidStack fs, FluidAction action) { + IFluidHandler fh = FluidUtil.getFluidHandler(world, pos, side).orElse(null); + return (fh == null) ? (0) : (fh.fill(fs, action)); + } + + public static int fill(Level world, BlockPos pos, Direction side, FluidStack fs) { + return fill(world, pos, side, fs, FluidAction.EXECUTE); + } + + /** + * Fluid tank access when itemized. + */ + public static class FluidContainerItemCapabilityWrapper implements IFluidHandlerItem, ICapabilityProvider { + private final LazyOptional handler_ = LazyOptional.of(() -> this); + private final Function nbt_getter_; + private final BiConsumer nbt_setter_; + private final Predicate validator_; + private final ItemStack container_; + private final int capacity_; + private final int transfer_rate_; + + public FluidContainerItemCapabilityWrapper(ItemStack container, int capacity, int transfer_rate, + Function nbt_getter, + BiConsumer nbt_setter, + Predicate validator) { + container_ = container; + capacity_ = capacity; + transfer_rate_ = transfer_rate; + nbt_getter_ = nbt_getter; + nbt_setter_ = nbt_setter; + validator_ = (validator != null) ? validator : (e -> true); + } + + @Override + public LazyOptional getCapability(Capability capability, @Nullable Direction side) { + return (capability == ForgeCapabilities.FLUID_HANDLER) ? handler_.cast() : LazyOptional.empty(); + } + + protected FluidStack readnbt() { + final CompoundTag nbt = nbt_getter_.apply(container_); + return ((nbt == null) || (nbt.isEmpty())) ? FluidStack.EMPTY : FluidStack.loadFluidStackFromNBT(nbt); + } + + protected void writenbt(FluidStack fs) { + CompoundTag nbt = new CompoundTag(); + if (!fs.isEmpty()) fs.writeToNBT(nbt); + nbt_setter_.accept(container_, nbt); + } + + @Override + public ItemStack getContainer() { + return container_; + } + + @Override + public int getTanks() { + return 1; + } + + @Override + public FluidStack getFluidInTank(int tank) { + return readnbt(); + } + + @Override + public int getTankCapacity(int tank) { + return capacity_; + } + + @Override + public boolean isFluidValid(int tank, FluidStack fs) { + return isFluidValid(fs); + } + + public boolean isFluidValid(FluidStack fs) { + return validator_.test(fs); + } + + @Override + public int fill(FluidStack fs, FluidAction action) { + if ((fs.isEmpty()) || (!isFluidValid(fs) || (container_.getCount() != 1))) return 0; + FluidStack tank = readnbt(); + final int amount = Math.min(Math.min(fs.getAmount(), transfer_rate_), capacity_ - tank.getAmount()); + if (amount <= 0) return 0; + if (tank.isEmpty()) { + if (action.execute()) { + tank = new FluidStack(fs.getFluid(), amount, fs.getTag()); + writenbt(tank); + } + } else { + if (!tank.isFluidEqual(fs)) { + return 0; + } else if (action.execute()) { + tank.grow(amount); + writenbt(tank); + } + } + return amount; + } + + @Override + public FluidStack drain(FluidStack fs, FluidAction action) { + if ((fs.isEmpty()) || (container_.getCount() != 1)) return FluidStack.EMPTY; + final FluidStack tank = readnbt(); + if ((!tank.isEmpty()) && (!tank.isFluidEqual(fs))) return FluidStack.EMPTY; + return drain(fs.getAmount(), action); + } + + @Override + public FluidStack drain(int max, FluidAction action) { + if ((max <= 0) || (container_.getCount() != 1)) return FluidStack.EMPTY; + FluidStack tank = readnbt(); + if (tank.isEmpty()) return FluidStack.EMPTY; + final int amount = Math.min(Math.min(tank.getAmount(), max), transfer_rate_); + final FluidStack fs = tank.copy(); + fs.setAmount(amount); + if (action.execute()) { + tank.shrink(amount); + writenbt(tank); + } + return fs; + } + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Guis.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Guis.java new file mode 100644 index 0000000..1374a19 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Guis.java @@ -0,0 +1,466 @@ +/* + * @file Guis.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Gui Wrappers and Widgets. + */ +package dev.zontreck.libzontreck.edlibmc; + +import com.mojang.blaze3d.platform.Window; +import com.mojang.blaze3d.systems.RenderSystem; +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.Font; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.components.AbstractWidget; +import net.minecraft.client.gui.narration.NarrationElementOutput; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.client.renderer.GameRenderer; +import net.minecraft.client.renderer.entity.ItemRenderer; +import net.minecraft.client.sounds.SoundManager; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import java.util.Arrays; +import java.util.function.Consumer; +import java.util.function.Function; + + +public class Guis { + // ------------------------------------------------------------------------------------------------------------------- + // Gui base + // ------------------------------------------------------------------------------------------------------------------- + + @OnlyIn(Dist.CLIENT) + public static abstract class ContainerGui extends AbstractContainerScreen { + protected final ResourceLocation background_image_; + protected final Player player_; + protected final Guis.BackgroundImage gui_background_; + protected final TooltipDisplay tooltip_ = new TooltipDisplay(); + + public ContainerGui(T menu, Inventory player_inv, Component title, String background_image, int width, int height) { + super(menu, player_inv, title); + this.background_image_ = new ResourceLocation(Auxiliaries.modid(), background_image); + this.player_ = player_inv.player; + this.imageWidth = width; + this.imageHeight = height; + gui_background_ = new Guis.BackgroundImage(background_image_, width, height, Coord2d.ORIGIN); + } + + public ContainerGui(T menu, Inventory player_inv, Component title, String background_image) { + super(menu, player_inv, title); + this.background_image_ = new ResourceLocation(Auxiliaries.modid(), background_image); + this.player_ = player_inv.player; + gui_background_ = new Guis.BackgroundImage(background_image_, imageWidth, imageHeight, Coord2d.ORIGIN); + } + + @Override + public void init() { + super.init(); + gui_background_.init(this, Coord2d.ORIGIN).show(); + } + + @Override + public void render(GuiGraphics mx, int mouseX, int mouseY, float partialTicks) { + renderBackground(mx); + super.render(mx, mouseX, mouseY, partialTicks); + if (!tooltip_.render(mx, this, mouseX, mouseY)) renderTooltip(mx, mouseX, mouseY); + } + + @Override + protected void renderLabels(GuiGraphics mx, int x, int y) { + } + + @Override + @SuppressWarnings("deprecation") + protected final void renderBg(GuiGraphics mx, float partialTicks, int mouseX, int mouseY) { + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, 1.0F); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + gui_background_.draw(mx, this); + renderBgWidgets(mx, partialTicks, mouseX, mouseY); + RenderSystem.disableBlend(); + } + + public final ResourceLocation getBackgroundImage() { + return background_image_; + } + + protected void renderBgWidgets(GuiGraphics mx, float partialTicks, int mouseX, int mouseY) { + } + + protected void renderItemTemplate(GuiGraphics mx, ItemStack stack, int x, int y) { + final int x0 = getGuiLeft(); + final int y0 = getGuiTop(); + + mx.renderFakeItem(stack, x0 + x, y0 + y); + RenderSystem.disableColorLogicOp(); //RenderSystem.disableColorMaterial(); + RenderSystem.enableDepthTest(); //RenderSystem.enableAlphaTest(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableBlend(); + RenderSystem.colorMask(true, true, true, true); + RenderSystem.setShaderColor(0.7f, 0.7f, 0.7f, 0.8f); + RenderSystem.setShaderTexture(0, background_image_); + mx.blit(background_image_,x0 + x, y0 + y, x, y, 16, 16); + RenderSystem.setShaderColor(1f, 1f, 1f, 1f); + } + } + + // ------------------------------------------------------------------------------------------------------------------- + // Gui elements + // ------------------------------------------------------------------------------------------------------------------- + + @OnlyIn(Dist.CLIENT) + public static class Coord2d { + public static final Coord2d ORIGIN = new Coord2d(0, 0); + public final int x, y; + + public Coord2d(int x, int y) { + this.x = x; + this.y = y; + } + + public static Coord2d of(int x, int y) { + return new Coord2d(x, y); + } + + public String toString() { + return "[" + x + "," + y + "]"; + } + } + + @OnlyIn(Dist.CLIENT) + public static class UiWidget extends AbstractWidget { + protected static final Component EMPTY_TEXT = Component.literal(""); + protected static final Function NO_TOOLTIP = (uiw) -> EMPTY_TEXT; + + private final Minecraft mc_; + private Function tooltip_ = NO_TOOLTIP; + private Screen parent_; + + public UiWidget(int x, int y, int width, int height, Component title) { + super(x, y, width, height, title); + mc_ = Minecraft.getInstance(); + } + + public UiWidget init(Screen parent) { + this.parent_ = parent; + this.setX(((parent instanceof AbstractContainerScreen) ? ((AbstractContainerScreen) parent).getGuiLeft() : 0)); + this.setY(((parent instanceof AbstractContainerScreen) ? ((AbstractContainerScreen) parent).getGuiTop() : 0)); + return this; + } + + public UiWidget init(Screen parent, Coord2d position) { + this.parent_ = parent; + this.setX(position.x + ((parent instanceof AbstractContainerScreen) ? ((AbstractContainerScreen) parent).getGuiLeft() : 0)); + this.setY(position.y + ((parent instanceof AbstractContainerScreen) ? ((AbstractContainerScreen) parent).getGuiTop() : 0)); + return this; + } + + public final UiWidget tooltip(Function tip) { + tooltip_ = tip; + return this; + } + + public final UiWidget tooltip(Component tip) { + tooltip_ = (o) -> tip; + return this; + } + + public final int getWidth() { + return this.width; + } + + @Override + protected void updateWidgetNarration(NarrationElementOutput narrationElementOutput) { + + } + + public final int getHeight() { + return this.height; + } + + public Coord2d getMousePosition() { + final Window win = mc_.getWindow(); + return Coord2d.of( + Mth.clamp(((int) (mc_.mouseHandler.xpos() * (double) win.getGuiScaledWidth() / (double) win.getScreenWidth())) - this.getX(), -1, this.width + 1), + Mth.clamp(((int) (mc_.mouseHandler.ypos() * (double) win.getGuiScaledHeight() / (double) win.getScreenHeight())) - this.getY(), -1, this.height + 1) + ); + } + + protected final Coord2d screenCoordinates(Coord2d xy, boolean reverse) { + return (reverse) ? (Coord2d.of(xy.x + getX(), xy.y + getY())) : (Coord2d.of(xy.x - getX(), xy.y - getY())); + } + + public UiWidget show() { + visible = true; + return this; + } + + public UiWidget hide() { + visible = false; + return this; + } + + @Override + public void renderWidget(GuiGraphics mxs, int mouseX, int mouseY, float partialTicks) { + //super.renderWidget(mxs, mouseX, mouseY, partialTicks); + if (isHovered) renderToolTip(mxs, mouseX, mouseY); + } + + @SuppressWarnings("all") + public void renderToolTip(GuiGraphics mx, int mouseX, int mouseY) { + if (!visible || (!active) || (tooltip_ == NO_TOOLTIP)) return; + final Component tip = tooltip_.apply(this); + if (tip.getString().trim().isEmpty()) return; + mx.renderTooltip(mc_.font, Arrays.asList(tip.getVisualOrderText()), mouseX, mouseY); + } + } + + @OnlyIn(Dist.CLIENT) + public static class HorizontalProgressBar extends UiWidget { + private final Coord2d texture_position_base_; + private final Coord2d texture_position_filled_; + private final ResourceLocation atlas_; + private double progress_max_ = 100; + private double progress_ = 0; + + public HorizontalProgressBar(ResourceLocation atlas, int width, int height, Coord2d base_texture_xy, Coord2d filled_texture_xy) { + super(0, 0, width, height, EMPTY_TEXT); + atlas_ = atlas; + texture_position_base_ = base_texture_xy; + texture_position_filled_ = filled_texture_xy; + } + + public HorizontalProgressBar setProgress(double progress) { + progress_ = Mth.clamp(progress, 0, progress_max_); + return this; + } + + public double getProgress() { + return progress_; + } + + public HorizontalProgressBar setMaxProgress(double progress) { + progress_max_ = Math.max(progress, 0); + return this; + } + + public double getMaxProgress() { + return progress_max_; + } + + public HorizontalProgressBar show() { + visible = true; + return this; + } + + public HorizontalProgressBar hide() { + visible = false; + return this; + } + + @Override + public void playDownSound(SoundManager handler) { + } + + @Override + public void renderWidget(GuiGraphics mxs, int mouseX, int mouseY, float partialTicks) { + RenderSystem.setShaderTexture(0, atlas_); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + mxs.blit(atlas_, getX(), getY(), texture_position_base_.x, texture_position_base_.y, width, height); + if ((progress_max_ > 0) && (progress_ > 0)) { + int w = Mth.clamp((int) Math.round((progress_ * width) / progress_max_), 0, width); + mxs.blit(atlas_, getX(), getY(), texture_position_filled_.x, texture_position_filled_.y, w, height); + } + if (isHovered) renderToolTip(mxs, mouseX, mouseY); + } + } + + @OnlyIn(Dist.CLIENT) + public static class BackgroundImage extends UiWidget { + private final ResourceLocation atlas_; + private final Coord2d atlas_position_; + public boolean visible; + + public BackgroundImage(ResourceLocation atlas, int width, int height, Coord2d atlas_position) { + super(0, 0, width, height, EMPTY_TEXT); + atlas_ = atlas; + atlas_position_ = atlas_position; + this.width = width; + this.height = height; + visible = true; + } + + public void draw(GuiGraphics mx, Screen parent) { + if (!visible) return; + RenderSystem.setShaderTexture(0, atlas_); + mx.blit(atlas_, getX(), getY(), atlas_position_.x, atlas_position_.y, width, height); + } + } + + @OnlyIn(Dist.CLIENT) + public static class CheckBox extends UiWidget { + private final Coord2d texture_position_off_; + private final Coord2d texture_position_on_; + private final ResourceLocation atlas_; + private boolean checked_ = false; + private Consumer on_click_ = (checkbox) -> { + }; + + public CheckBox(ResourceLocation atlas, int width, int height, Coord2d atlas_texture_position_off, Coord2d atlas_texture_position_on) { + super(0, 0, width, height, EMPTY_TEXT); + texture_position_off_ = atlas_texture_position_off; + texture_position_on_ = atlas_texture_position_on; + atlas_ = atlas; + } + + public boolean checked() { + return checked_; + } + + public CheckBox checked(boolean on) { + checked_ = on; + return this; + } + + public CheckBox onclick(Consumer action) { + on_click_ = action; + return this; + } + + @Override + public void onClick(double mouseX, double mouseY) { + checked_ = !checked_; + on_click_.accept(this); + } + + @Override + public void renderWidget(GuiGraphics mxs, int mouseX, int mouseY, float partialTicks) { + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderTexture(0, atlas_); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + Coord2d pos = checked_ ? texture_position_on_ : texture_position_off_; + mxs.blit(atlas_, getX(), getY(), pos.x, pos.y, width, height); + if (isHovered) renderToolTip(mxs, mouseX, mouseY); + } + } + + @OnlyIn(Dist.CLIENT) + public static class ImageButton extends UiWidget { + private final Coord2d texture_position_; + private final ResourceLocation atlas_; + private Consumer on_click_ = (bt) -> { + }; + + + public ImageButton(ResourceLocation atlas, int width, int height, Coord2d atlas_texture_position) { + super(0, 0, width, height, Component.empty()); + texture_position_ = atlas_texture_position; + atlas_ = atlas; + } + + public ImageButton onclick(Consumer action) { + on_click_ = action; + return this; + } + + @Override + public void onClick(double mouseX, double mouseY) { + on_click_.accept(this); + } + + @Override + public void renderWidget(GuiGraphics mxs, int mouseX, int mouseY, float partialTicks) { + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderTexture(0, atlas_); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + Coord2d pos = texture_position_; + mxs.blit(atlas_, getX(), getY(), pos.x, pos.y, width, height); + if (isHovered) renderToolTip(mxs, mouseX, mouseY); + } + } + + @OnlyIn(Dist.CLIENT) + public static class Image extends UiWidget { + private final Coord2d texture_position_; + private final ResourceLocation atlas_; + + public Image(ResourceLocation atlas, int width, int height, Coord2d atlas_texture_position) { + super(0, 0, width, height, Component.empty()); + texture_position_ = atlas_texture_position; + atlas_ = atlas; + } + + @Override + public void onClick(double mouseX, double mouseY) { + } + + @Override + public void renderWidget(GuiGraphics mxs, int mouseX, int mouseY, float partialTicks) { + RenderSystem.setShader(GameRenderer::getPositionTexShader); + RenderSystem.setShaderTexture(0, atlas_); + RenderSystem.setShaderColor(1.0F, 1.0F, 1.0F, this.alpha); + RenderSystem.enableBlend(); + RenderSystem.defaultBlendFunc(); + RenderSystem.enableDepthTest(); + Coord2d pos = texture_position_; + mxs.blit(atlas_, getX(), getY(), pos.x, pos.y, width, height); + if (isHovered) renderToolTip(mxs, mouseX, mouseY); + } + } + + @OnlyIn(Dist.CLIENT) + public static class TextBox extends net.minecraft.client.gui.components.EditBox { + public TextBox(int x, int y, int width, int height, Component title, Font font) { + super(font, x, y, width, height, title); + setBordered(false); + } + + public TextBox withMaxLength(int len) { + super.setMaxLength(len); + return this; + } + + public TextBox withBordered(boolean b) { + super.setBordered(b); + return this; + } + + public TextBox withValue(String s) { + super.setValue(s); + return this; + } + + public TextBox withEditable(boolean e) { + super.setEditable(e); + return this; + } + + public TextBox withResponder(Consumer r) { + super.setResponder(r); + return this; + } + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Inventories.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Inventories.java new file mode 100644 index 0000000..e7442e0 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Inventories.java @@ -0,0 +1,1134 @@ +/* + * @file Inventories.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * General inventory item handling functionality. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.core.NonNullList; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.util.Mth; +import net.minecraft.world.*; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.item.ItemEntity; +import net.minecraft.world.entity.player.Inventory; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import net.minecraft.nbt.Tag; +import net.minecraftforge.common.capabilities.ForgeCapabilities; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.items.IItemHandler; +import net.minecraftforge.items.ItemHandlerHelper; +import net.minecraftforge.items.wrapper.InvWrapper; +import net.minecraftforge.items.wrapper.PlayerMainInvWrapper; +import net.minecraftforge.items.wrapper.SidedInvWrapper; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.Consumer; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + + +public class Inventories { + public static boolean areItemStacksIdentical(ItemStack a, ItemStack b) { + return (a.getItem() == b.getItem()) && ItemStack.isSameItemSameTags(a, b); + } + + public static boolean areItemStacksDifferent(ItemStack a, ItemStack b) { + return (a.getItem() != b.getItem()) || (!ItemStack.isSameItemSameTags(a, b)); + } + + public static IItemHandler itemhandler(Level world, BlockPos pos, @Nullable Direction side) { + BlockEntity te = world.getBlockEntity(pos); + if (te == null) return null; + IItemHandler ih = te.getCapability(ForgeCapabilities.ITEM_HANDLER, side).orElse(null); + if (ih != null) return ih; + if ((side != null) && (te instanceof WorldlyContainer)) return new SidedInvWrapper((WorldlyContainer) te, side); + if (te instanceof Container) return new InvWrapper((Container) te); + return null; + } + + public static IItemHandler itemhandler(Level world, BlockPos pos, @Nullable Direction side, boolean including_entities) { + IItemHandler ih = itemhandler(world, pos, side); + if (ih != null) return ih; + if (!including_entities) return null; + Entity entity = world.getEntitiesOfClass(Entity.class, new AABB(pos), (e) -> (e instanceof Container)).stream().findFirst().orElse(null); + return (entity == null) ? (null) : (itemhandler(entity, side)); + } + + public static IItemHandler itemhandler(Player player) { + return new PlayerMainInvWrapper(player.getInventory()); + } + + public static IItemHandler itemhandler(Entity entity) { + return itemhandler(entity, null); + } + + public static IItemHandler itemhandler(Entity entity, @Nullable Direction side) { + if (entity == null) return null; + final IItemHandler ih = entity.getCapability(ForgeCapabilities.ITEM_HANDLER, side).orElse(null); + if (ih != null) return ih; + if (entity instanceof Container container) return (new InvWrapper(container)); + return null; + } + + public static boolean insertionPossible(Level world, BlockPos pos, @Nullable Direction side, boolean including_entities) { + return itemhandler(world, pos, side, including_entities) != null; + } + + public static ItemStack insert(IItemHandler handler, ItemStack stack, boolean simulate) { + return ItemHandlerHelper.insertItemStacked(handler, stack, simulate); + } + + public static ItemStack insert(BlockEntity te, @Nullable Direction side, ItemStack stack, boolean simulate) { + if (te == null) return stack; + IItemHandler hnd = te.getCapability(ForgeCapabilities.ITEM_HANDLER, side).orElse(null); + if (hnd == null) { + if ((side != null) && (te instanceof WorldlyContainer)) { + hnd = new SidedInvWrapper((WorldlyContainer) te, side); + } else if (te instanceof Container) { + hnd = new InvWrapper((Container) te); + } + } + return (hnd == null) ? stack : insert(hnd, stack, simulate); + } + + public static ItemStack insert(Level world, BlockPos pos, @Nullable Direction side, ItemStack stack, boolean simulate, boolean including_entities) { + return insert(itemhandler(world, pos, side, including_entities), stack, simulate); + } + + public static ItemStack insert(Level world, BlockPos pos, @Nullable Direction side, ItemStack stack, boolean simulate) { + return insert(world, pos, side, stack, simulate, false); + } + + public static ItemStack extract(IItemHandler inventory, @Nullable ItemStack match, int amount, boolean simulate) { + if ((inventory == null) || (amount <= 0) || ((match != null) && (match.isEmpty()))) return ItemStack.EMPTY; + final int max = inventory.getSlots(); + ItemStack out_stack = ItemStack.EMPTY; + for (int i = 0; i < max; ++i) { + final ItemStack stack = inventory.getStackInSlot(i); + if (stack.isEmpty()) continue; + if (out_stack.isEmpty()) { + if ((match != null) && areItemStacksDifferent(stack, match)) continue; + out_stack = inventory.extractItem(i, amount, simulate); + } else if (areItemStacksIdentical(stack, out_stack)) { + ItemStack es = inventory.extractItem(i, (amount - out_stack.getCount()), simulate); + out_stack.grow(es.getCount()); + } + if (out_stack.getCount() >= amount) break; + } + return out_stack; + } + + private static ItemStack checked(ItemStack stack) { + return stack.isEmpty() ? ItemStack.EMPTY : stack; + } + + public static Container copyOf(Container src) { + final int size = src.getContainerSize(); + SimpleContainer dst = new SimpleContainer(size); + for (int i = 0; i < size; ++i) dst.setItem(i, src.getItem(i).copy()); + return dst; + } + + //-------------------------------------------------------------------------------------------------------------------- + + public static ItemStack insert(InventoryRange[] to_ranges, ItemStack stack) { + ItemStack remaining = stack.copy(); + for (InventoryRange range : to_ranges) { + remaining = range.insert(remaining, false, 0, false, true); + if (remaining.isEmpty()) return remaining; + } + return remaining; + } + + //-------------------------------------------------------------------------------------------------------------------- + + public static class MappedItemHandler implements IItemHandler { + private final BiPredicate extraction_predicate_; + private final BiPredicate insertion_predicate_; + private final BiConsumer insertion_notifier_; + private final BiConsumer extraction_notifier_; + private final List slot_map_; + private final Container inv_; + + public MappedItemHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate, BiConsumer insertion_notifier, BiConsumer extraction_notifier) { + inv_ = inv; + extraction_predicate_ = extraction_predicate; + insertion_predicate_ = insertion_predicate; + insertion_notifier_ = insertion_notifier; + extraction_notifier_ = extraction_notifier; + slot_map_ = IntStream.range(0, inv.getContainerSize()).boxed().collect(Collectors.toList()); + } + + public MappedItemHandler(Container inv, List slot_map, BiPredicate extraction_predicate, BiPredicate insertion_predicate, BiConsumer insertion_notifier, BiConsumer extraction_notifier) { + inv_ = inv; + extraction_predicate_ = extraction_predicate; + insertion_predicate_ = insertion_predicate; + insertion_notifier_ = insertion_notifier; + extraction_notifier_ = extraction_notifier; + slot_map_ = slot_map; + } + + public MappedItemHandler(Container inv, List slot_map, BiPredicate extraction_predicate, BiPredicate insertion_predicate) { + this(inv, slot_map, extraction_predicate, insertion_predicate, (i, s) -> { + }, (i, s) -> { + }); + } + + public MappedItemHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate) { + this(inv, IntStream.range(0, inv.getContainerSize()).boxed().collect(Collectors.toList()), extraction_predicate, insertion_predicate); + } + + public MappedItemHandler(Container inv) { + this(inv, (i, s) -> true, (i, s) -> true); + } + + @Override + public int hashCode() { + return inv_.hashCode(); + } + + @Override + public boolean equals(Object o) { + return (o == this) || ((o != null) && (getClass() == o.getClass()) && (inv_.equals(((MappedItemHandler) o).inv_))); + } + + // IItemHandler ----------------------------------------------------------------------------------------------- + + @Override + public int getSlots() { + return slot_map_.size(); + } + + @Override + @Nonnull + public ItemStack getStackInSlot(int slot) { + return (slot >= slot_map_.size()) ? ItemStack.EMPTY : inv_.getItem(slot_map_.get(slot)); + } + + @Override + public int getSlotLimit(int slot) { + return inv_.getMaxStackSize(); + } + + @Override + public boolean isItemValid(int slot, @Nonnull ItemStack stack) { + if (slot >= slot_map_.size()) return false; + slot = slot_map_.get(slot); + return insertion_predicate_.test(slot, stack) && inv_.canPlaceItem(slot, stack); + } + + @Override + @Nonnull + public ItemStack insertItem(int slot, @Nonnull ItemStack stack, boolean simulate) { + if (stack.isEmpty()) return ItemStack.EMPTY; + if (slot >= slot_map_.size()) return stack; + slot = slot_map_.get(slot); + if (!insertion_predicate_.test(slot, stack)) return stack; + if (!inv_.canPlaceItem(slot, stack)) return stack; + ItemStack sst = inv_.getItem(slot); + final int slot_limit = inv_.getMaxStackSize(); + if (!sst.isEmpty()) { + if (sst.getCount() >= Math.min(sst.getMaxStackSize(), slot_limit)) return stack; + if (!ItemHandlerHelper.canItemStacksStack(stack, sst)) return stack; + final int limit = Math.min(stack.getMaxStackSize(), slot_limit) - sst.getCount(); + if (stack.getCount() <= limit) { + if (!simulate) { + stack = stack.copy(); + stack.grow(sst.getCount()); + inv_.setItem(slot, stack); + inv_.setChanged(); + insertion_notifier_.accept(slot, sst); + } + return ItemStack.EMPTY; + } else { + stack = stack.copy(); + if (simulate) { + stack.shrink(limit); + } else { + final ItemStack diff = stack.split(limit); + sst.grow(diff.getCount()); + inv_.setItem(slot, sst); + inv_.setChanged(); + insertion_notifier_.accept(slot, diff); + } + return stack; + } + } else { + final int limit = Math.min(slot_limit, stack.getMaxStackSize()); + if (stack.getCount() >= limit) { + stack = stack.copy(); + final ItemStack ins = stack.split(limit); + if (!simulate) { + inv_.setItem(slot, ins); + inv_.setChanged(); + insertion_notifier_.accept(slot, ins.copy()); + } + if (stack.isEmpty()) { + stack = ItemStack.EMPTY; + } + return stack; + } else { + if (!simulate) { + inv_.setItem(slot, stack.copy()); + inv_.setChanged(); + insertion_notifier_.accept(slot, stack.copy()); + } + return ItemStack.EMPTY; + } + } + } + + @Override + public ItemStack extractItem(int slot, int amount, boolean simulate) { + if (amount <= 0) return ItemStack.EMPTY; + if (slot >= slot_map_.size()) return ItemStack.EMPTY; + slot = slot_map_.get(slot); + ItemStack stack = inv_.getItem(slot); + if (!extraction_predicate_.test(slot, stack)) return ItemStack.EMPTY; + if (simulate) { + stack = stack.copy(); + if (amount < stack.getCount()) stack.setCount(amount); + } else { + stack = inv_.removeItem(slot, Math.min(stack.getCount(), amount)); + inv_.setChanged(); + extraction_notifier_.accept(slot, stack.copy()); + } + return stack; + } + + // Factories -------------------------------------------------------------------------------------------- + + public static LazyOptional createGenericHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate, BiConsumer insertion_notifier, BiConsumer extraction_notifier) { + return LazyOptional.of(() -> new MappedItemHandler(inv, extraction_predicate, insertion_predicate, insertion_notifier, extraction_notifier)); + } + + public static LazyOptional createGenericHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate, BiConsumer insertion_notifier, BiConsumer extraction_notifier, List slot_map) { + return LazyOptional.of(() -> new MappedItemHandler(inv, slot_map, extraction_predicate, insertion_predicate, insertion_notifier, extraction_notifier)); + } + + public static LazyOptional createGenericHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate, List slot_map) { + return LazyOptional.of(() -> new MappedItemHandler(inv, slot_map, extraction_predicate, insertion_predicate)); + } + + public static LazyOptional createGenericHandler(Container inv, BiPredicate extraction_predicate, BiPredicate insertion_predicate) { + return LazyOptional.of(() -> new MappedItemHandler(inv, extraction_predicate, insertion_predicate)); + } + + public static LazyOptional createGenericHandler(Container inv) { + return LazyOptional.of(() -> new MappedItemHandler(inv)); + } + + public static LazyOptional createExtractionHandler(Container inv, BiPredicate extraction_predicate, List slot_map) { + return LazyOptional.of(() -> new MappedItemHandler(inv, slot_map, extraction_predicate, (i, s) -> false)); + } + + public static LazyOptional createExtractionHandler(Container inv, BiPredicate extraction_predicate) { + return LazyOptional.of(() -> new MappedItemHandler(inv, extraction_predicate, (i, s) -> false)); + } + + public static LazyOptional createExtractionHandler(Container inv, Integer... slots) { + return LazyOptional.of(() -> new MappedItemHandler(inv, Arrays.asList(slots), (i, s) -> true, (i, s) -> false)); + } + + public static LazyOptional createExtractionHandler(Container inv) { + return LazyOptional.of(() -> new MappedItemHandler(inv, (i, s) -> true, (i, s) -> false)); + } + + public static LazyOptional createInsertionHandler(Container inv, BiPredicate insertion_predicate, List slot_map) { + return LazyOptional.of(() -> new MappedItemHandler(inv, slot_map, (i, s) -> false, insertion_predicate)); + } + + public static LazyOptional createInsertionHandler(Container inv, Integer... slots) { + return LazyOptional.of(() -> new MappedItemHandler(inv, Arrays.asList(slots), (i, s) -> false, (i, s) -> true)); + } + + public static LazyOptional createInsertionHandler(Container inv, BiPredicate insertion_predicate) { + return LazyOptional.of(() -> new MappedItemHandler(inv, (i, s) -> false, insertion_predicate)); + } + + public static LazyOptional createInsertionHandler(Container inv) { + return LazyOptional.of(() -> new MappedItemHandler(inv, (i, s) -> false, (i, s) -> true)); + } + } + + //-------------------------------------------------------------------------------------------------------------------- + + public static class InventoryRange implements Container, Iterable { + protected final Container inventory_; + protected final int offset_, size_, num_rows; + protected int max_stack_size_ = 64; + protected BiPredicate validator_ = (index, stack) -> true; + + public static InventoryRange fromPlayerHotbar(Player player) { + return new InventoryRange(player.getInventory(), 0, 9, 1); + } + + public static InventoryRange fromPlayerStorage(Player player) { + return new InventoryRange(player.getInventory(), 9, 27, 3); + } + + public static InventoryRange fromPlayerInventory(Player player) { + return new InventoryRange(player.getInventory(), 0, 36, 4); + } + + public InventoryRange(Container inventory, int offset, int size, int num_rows) { + this.inventory_ = inventory; + this.offset_ = Mth.clamp(offset, 0, inventory.getContainerSize() - 1); + this.size_ = Mth.clamp(size, 0, inventory.getContainerSize() - this.offset_); + this.num_rows = num_rows; + } + + public InventoryRange(Container inventory, int offset, int size) { + this(inventory, offset, size, 1); + } + + public InventoryRange(Container inventory) { + this(inventory, 0, inventory.getContainerSize(), 1); + } + + public final Container inventory() { + return inventory_; + } + + public final int size() { + return size_; + } + + public final int offset() { + return offset_; + } + + public final ItemStack get(int index) { + return inventory_.getItem(offset_ + index); + } + + public final void set(int index, ItemStack stack) { + inventory_.setItem(offset_ + index, stack); + } + + public final InventoryRange setValidator(BiPredicate validator) { + validator_ = validator; + return this; + } + + public final BiPredicate getValidator() { + return validator_; + } + + public final InventoryRange setMaxStackSize(int count) { + max_stack_size_ = Math.max(count, 1); + return this; + } + + // Container ------------------------------------------------------------------------------------------------------ + + @Override + public void clearContent() { + for (int i = 0; i < size_; ++i) setItem(i, ItemStack.EMPTY); + } + + @Override + public int getContainerSize() { + return size_; + } + + @Override + public boolean isEmpty() { + for (int i = 0; i < size_; ++i) + if (!inventory_.getItem(offset_ + i).isEmpty()) { + return false; + } + return true; + } + + @Override + public ItemStack getItem(int index) { + return inventory_.getItem(offset_ + index); + } + + @Override + public ItemStack removeItem(int index, int count) { + return inventory_.removeItem(offset_ + index, count); + } + + @Override + public ItemStack removeItemNoUpdate(int index) { + return inventory_.removeItemNoUpdate(offset_ + index); + } + + @Override + public void setItem(int index, ItemStack stack) { + inventory_.setItem(offset_ + index, stack); + } + + @Override + public int getMaxStackSize() { + return Math.min(max_stack_size_, inventory_.getMaxStackSize()); + } + + @Override + public void setChanged() { + inventory_.setChanged(); + } + + @Override + public boolean stillValid(Player player) { + return inventory_.stillValid(player); + } + + @Override + public void startOpen(Player player) { + inventory_.startOpen(player); + } + + @Override + public void stopOpen(Player player) { + inventory_.stopOpen(player); + } + + @Override + public boolean canPlaceItem(int index, ItemStack stack) { + return validator_.test(offset_ + index, stack) && inventory_.canPlaceItem(offset_ + index, stack); + } + + //------------------------------------------------------------------------------------------------------------------ + + /** + * Iterates using a function (slot, stack) -> bool until the function matches (returns true). + */ + public boolean iterate(BiPredicate fn) { + for (int i = 0; i < size_; ++i) { + if (fn.test(i, getItem(i))) { + return true; + } + } + return false; + } + + public boolean contains(ItemStack stack) { + for (int i = 0; i < size_; ++i) { + if (areItemStacksIdentical(stack, getItem(i))) { + return true; + } + } + return false; + } + + public int indexOf(ItemStack stack) { + for (int i = 0; i < size_; ++i) { + if (areItemStacksIdentical(stack, getItem(i))) { + return i; + } + } + return -1; + } + + public Optional find(BiFunction> fn) { + for (int i = 0; i < size_; ++i) { + Optional r = fn.apply(i, getItem(i)); + if (r.isPresent()) return r; + } + return Optional.empty(); + } + + public List collect(BiFunction> fn) { + List data = new ArrayList<>(); + for (int i = 0; i < size_; ++i) { + fn.apply(i, getItem(i)).ifPresent(data::add); + } + return data; + } + + public Stream stream() { + return java.util.stream.StreamSupport.stream(this.spliterator(), false); + } + + public Iterator iterator() { + return new InventoryRangeIterator(this); + } + + public static class InventoryRangeIterator implements Iterator { + private final InventoryRange parent_; + private int index = 0; + + public InventoryRangeIterator(InventoryRange range) { + parent_ = range; + } + + public boolean hasNext() { + return index < parent_.size_; + } + + public ItemStack next() { + if (index >= parent_.size_) throw new NoSuchElementException(); + return parent_.getItem(index++); + } + } + + //------------------------------------------------------------------------------------------------------------------ + + /** + * Returns the number of stacks that match the given stack with NBT. + */ + public int stackMatchCount(final ItemStack ref_stack) { + int n = 0; // ... std::accumulate() the old school way. + for (int i = 0; i < size_; ++i) { + if (areItemStacksIdentical(ref_stack, getItem(i))) ++n; + } + return n; + } + + public int totalMatchingItemCount(final ItemStack ref_stack) { + int n = 0; + for (int i = 0; i < size_; ++i) { + ItemStack stack = getItem(i); + if (areItemStacksIdentical(ref_stack, stack)) n += stack.getCount(); + } + return n; + } + + //------------------------------------------------------------------------------------------------------------------ + + /** + * Moves as much items from the stack to the slots in range [offset_, end_slot] of the inventory_, + * filling up existing stacks first, then (player inventory_ only) checks appropriate empty slots next + * to stacks that have that item already, and last uses any empty slot that can be found. + * Returns the stack that is still remaining in the referenced `stack`. + */ + public ItemStack insert(final ItemStack input_stack, boolean only_fillup, int limit, boolean reverse, boolean force_group_stacks) { + final ItemStack mvstack = input_stack.copy(); + //final int end_slot = offset_ + size; + if (mvstack.isEmpty()) return checked(mvstack); + int limit_left = (limit > 0) ? (Math.min(limit, mvstack.getMaxStackSize())) : (mvstack.getMaxStackSize()); + boolean[] matches = new boolean[size_]; + boolean[] empties = new boolean[size_]; + int num_matches = 0; + for (int i = 0; i < size_; ++i) { + final int sno = reverse ? (size_ - 1 - i) : (i); + final ItemStack stack = getItem(sno); + if (stack.isEmpty()) { + empties[sno] = true; + } else if (areItemStacksIdentical(stack, mvstack)) { + matches[sno] = true; + ++num_matches; + } + } + // first iteration: fillup existing stacks + for (int i = 0; i < size_; ++i) { + final int sno = reverse ? (size_ - 1 - i) : (i); + if ((empties[sno]) || (!matches[sno])) continue; + final ItemStack stack = getItem(sno); + int nmax = Math.min(limit_left, stack.getMaxStackSize() - stack.getCount()); + if (mvstack.getCount() <= nmax) { + stack.setCount(stack.getCount() + mvstack.getCount()); + setItem(sno, stack); + return ItemStack.EMPTY; + } else { + stack.grow(nmax); + mvstack.shrink(nmax); + setItem(sno, stack); + limit_left -= nmax; + } + } + if (only_fillup) return checked(mvstack); + if ((num_matches > 0) && ((force_group_stacks) || (inventory_ instanceof Inventory))) { + // second iteration: use appropriate empty slots, + // a) between + { + int insert_start = -1; + int insert_end = -1; + int i = 1; + for (; i < size_ - 1; ++i) { + final int sno = reverse ? (size_ - 1 - i) : (i); + if (insert_start < 0) { + if (matches[sno]) insert_start = sno; + } else if (matches[sno]) { + insert_end = sno; + } + } + for (i = insert_start; i < insert_end; ++i) { + final int sno = reverse ? (size_ - 1 - i) : (i); + if ((!empties[sno]) || (!canPlaceItem(sno, mvstack))) continue; + int nmax = Math.min(limit_left, mvstack.getCount()); + ItemStack moved = mvstack.copy(); + moved.setCount(nmax); + mvstack.shrink(nmax); + setItem(sno, moved); + return checked(mvstack); + } + } + // b) before/after + { + for (int i = 1; i < size_ - 1; ++i) { + final int sno = reverse ? (size_ - 1 - i) : (i); + if (!matches[sno]) continue; + int ii = (empties[sno - 1]) ? (sno - 1) : (empties[sno + 1] ? (sno + 1) : -1); + if ((ii >= 0) && (canPlaceItem(ii, mvstack))) { + int nmax = Math.min(limit_left, mvstack.getCount()); + ItemStack moved = mvstack.copy(); + moved.setCount(nmax); + mvstack.shrink(nmax); + setItem(ii, moved); + return checked(mvstack); + } + } + } + } + // third iteration: use any empty slots + for (int i = 0; i < size_; ++i) { + final int sno = reverse ? (size_ - 1 - i) : (i); + if ((!empties[sno]) || (!canPlaceItem(sno, mvstack))) continue; + int nmax = Math.min(limit_left, mvstack.getCount()); + ItemStack placed = mvstack.copy(); + placed.setCount(nmax); + mvstack.shrink(nmax); + setItem(sno, placed); + return checked(mvstack); + } + return checked(mvstack); + } + + public ItemStack insert(final ItemStack stack_to_move) { + return insert(stack_to_move, false, 0, false, true); + } + + public ItemStack insert(final int index, final ItemStack stack_to_move) { + if (stack_to_move.isEmpty()) return stack_to_move; + final ItemStack stack = getItem(index); + final int limit = Math.min(getMaxStackSize(), stack.getMaxStackSize()); + if (stack.isEmpty()) { + setItem(index, stack_to_move.copy()); + return ItemStack.EMPTY; + } else if ((stack.getCount() >= limit) || !areItemStacksIdentical(stack, stack_to_move)) { + return stack_to_move; + } else { + final int amount = Math.min(limit - stack.getCount(), stack_to_move.getCount()); + ItemStack remaining = stack_to_move.copy(); + remaining.shrink(amount); + stack.grow(amount); + return remaining.isEmpty() ? ItemStack.EMPTY : remaining; + } + } + + //------------------------------------------------------------------------------------------------------------------ + + /** + * Extracts maximum amount of items from the inventory_. + * The first non-empty stack defines the item. + */ + public ItemStack extract(int amount) { + return extract(amount, false); + } + + public ItemStack extract(int amount, boolean random) { + ItemStack out_stack = ItemStack.EMPTY; + int offset = random ? (int) (Math.random() * size_) : 0; + for (int k = 0; k < size_; ++k) { + int i = (offset + k) % size_; + final ItemStack stack = getItem(i); + if (stack.isEmpty()) continue; + if (out_stack.isEmpty()) { + if (stack.getCount() < amount) { + out_stack = stack; + setItem(i, ItemStack.EMPTY); + if (!out_stack.isStackable()) break; + amount -= out_stack.getCount(); + } else { + out_stack = stack.split(amount); + break; + } + } else if (areItemStacksIdentical(stack, out_stack)) { + if (stack.getCount() <= amount) { + out_stack.grow(stack.getCount()); + amount -= stack.getCount(); + setItem(i, ItemStack.EMPTY); + } else { + out_stack.grow(amount); + stack.shrink(amount); + if (stack.isEmpty()) setItem(i, ItemStack.EMPTY); + break; + } + } + } + if (!out_stack.isEmpty()) setChanged(); + return out_stack; + } + + /** + * Moves as much items from the slots in range [offset_, end_slot] of the inventory_ into a new stack. + * Implicitly shrinks the inventory_ stacks and the `request_stack`. + */ + public ItemStack extract(final ItemStack request_stack) { + if (request_stack.isEmpty()) return ItemStack.EMPTY; + List matches = new ArrayList<>(); + for (int i = 0; i < size_; ++i) { + final ItemStack stack = getItem(i); + if ((!stack.isEmpty()) && (areItemStacksIdentical(stack, request_stack))) { + if (stack.hasTag()) { + final CompoundTag nbt = stack.getOrCreateTag(); + int n = nbt.size(); + if ((n > 0) && (nbt.contains("Damage"))) --n; + if (n > 0) continue; + } + matches.add(stack); + } + } + matches.sort(Comparator.comparingInt(ItemStack::getCount)); + if (matches.isEmpty()) return ItemStack.EMPTY; + int n_left = request_stack.getCount(); + ItemStack fetched_stack = matches.get(0).split(n_left); + n_left -= fetched_stack.getCount(); + for (int i = 1; (i < matches.size()) && (n_left > 0); ++i) { + ItemStack stack = matches.get(i).split(n_left); + n_left -= stack.getCount(); + fetched_stack.grow(stack.getCount()); + } + return checked(fetched_stack); + } + + //------------------------------------------------------------------------------------------------------------------ + + /** + * Moves items from this inventory_ range to another. Returns true if something was moved + * (if the inventories should be marked dirty). + */ + public boolean move(int index, final InventoryRange target_range, boolean all_identical_stacks, boolean only_fillup, boolean reverse, boolean force_group_stacks) { + final ItemStack source_stack = getItem(index); + if (source_stack.isEmpty()) return false; + if (!all_identical_stacks) { + ItemStack remaining = target_range.insert(source_stack, only_fillup, 0, reverse, force_group_stacks); + setItem(index, remaining); + return (remaining.getCount() != source_stack.getCount()); + } else { + ItemStack remaining = source_stack.copy(); + setItem(index, ItemStack.EMPTY); + final ItemStack ref_stack = remaining.copy(); + ref_stack.setCount(ref_stack.getMaxStackSize()); + for (int i = size_; (i > 0) && (!remaining.isEmpty()); --i) { + remaining = target_range.insert(remaining, only_fillup, 0, reverse, force_group_stacks); + if (!remaining.isEmpty()) break; + remaining = this.extract(ref_stack); + } + if (!remaining.isEmpty()) { + setItem(index, remaining); // put back + } + return (remaining.getCount() != source_stack.getCount()); + } + } + + public boolean move(int index, final InventoryRange target_range) { + return move(index, target_range, false, false, false, true); + } + + /** + * Moves/clears the complete range to another range if possible. Returns true if something was moved + * (if the inventories should be marked dirty). + */ + public boolean move(final InventoryRange target_range, boolean only_fillup, boolean reverse, boolean force_group_stacks) { + boolean changed = false; + for (int i = 0; i < size_; ++i) + changed |= move(i, target_range, false, only_fillup, reverse, force_group_stacks); + return changed; + } + + public boolean move(final InventoryRange target_range, boolean only_fillup) { + return move(target_range, only_fillup, false, true); + } + + public boolean move(final InventoryRange target_range) { + return move(target_range, false, false, true); + } + + } + + //-------------------------------------------------------------------------------------------------------------------- + + public static class StorageInventory implements Container, Iterable { + protected final NonNullList stacks_; + protected final BlockEntity te_; + protected final int size_; + protected final int num_rows_; + protected int stack_limit_ = 64; + protected BiPredicate validator_ = (index, stack) -> true; + protected Consumer open_action_ = (player) -> { + }; + protected Consumer close_action_ = (player) -> { + }; + protected BiConsumer slot_set_action_ = (index, stack) -> { + }; + + public StorageInventory(BlockEntity te, int size) { + this(te, size, 1); + } + + public StorageInventory(BlockEntity te, int size, int num_rows) { + te_ = te; + size_ = Math.max(size, 1); + stacks_ = NonNullList.withSize(size_, ItemStack.EMPTY); + num_rows_ = Mth.clamp(num_rows, 1, size_); + } + + public CompoundTag save(CompoundTag nbt, String key) { + nbt.put(key, save(new CompoundTag(), false)); + return nbt; + } + + public CompoundTag save(CompoundTag nbt) { + return ContainerHelper.saveAllItems(nbt, stacks_); + } + + public CompoundTag save(CompoundTag nbt, boolean save_empty) { + return ContainerHelper.saveAllItems(nbt, stacks_, save_empty); + } + + public CompoundTag save(boolean save_empty) { + return save(new CompoundTag(), save_empty); + } + + public StorageInventory load(CompoundTag nbt, String key) { + if (!nbt.contains("key", Tag.TAG_COMPOUND)) { + stacks_.clear(); + return this; + } else { + return load(nbt.getCompound(key)); + } + } + + public StorageInventory load(CompoundTag nbt) { + stacks_.clear(); + ContainerHelper.loadAllItems(nbt, stacks_); + return this; + } + + public List stacks() { + return stacks_; + } + + public BlockEntity getBlockEntity() { + return te_; + } + + public StorageInventory setOpenAction(Consumer fn) { + open_action_ = fn; + return this; + } + + public StorageInventory setCloseAction(Consumer fn) { + close_action_ = fn; + return this; + } + + public StorageInventory setSlotChangeAction(BiConsumer fn) { + slot_set_action_ = fn; + return this; + } + + public StorageInventory setStackLimit(int max_slot_stack_size) { + stack_limit_ = Math.max(max_slot_stack_size, 1); + return this; + } + + public StorageInventory setValidator(BiPredicate validator) { + validator_ = validator; + return this; + } + + public BiPredicate getValidator() { + return validator_; + } + + // Iterable --------------------------------------------------------------------- + + public Iterator iterator() { + return stacks_.iterator(); + } + + public Stream stream() { + return stacks_.stream(); + } + + // Container ------------------------------------------------------------------------------ + + @Override + public int getContainerSize() { + return size_; + } + + @Override + public boolean isEmpty() { + for (ItemStack stack : stacks_) { + if (!stack.isEmpty()) return false; + } + return true; + } + + @Override + public ItemStack getItem(int index) { + return (index < size_) ? stacks_.get(index) : ItemStack.EMPTY; + } + + @Override + public ItemStack removeItem(int index, int count) { + return ContainerHelper.removeItem(stacks_, index, count); + } + + @Override + public ItemStack removeItemNoUpdate(int index) { + return ContainerHelper.takeItem(stacks_, index); + } + + @Override + public void setItem(int index, ItemStack stack) { + stacks_.set(index, stack); + if ((stack.getCount() != stacks_.get(index).getCount()) || !areItemStacksDifferent(stacks_.get(index), stack)) { + slot_set_action_.accept(index, stack); + } + } + + @Override + public int getMaxStackSize() { + return stack_limit_; + } + + @Override + public void setChanged() { + te_.setChanged(); + } + + @Override + public boolean stillValid(Player player) { + return ((te_.getLevel().getBlockEntity(te_.getBlockPos()) == te_)) && (te_.getBlockPos().distSqr(player.blockPosition()) < 64); + } + + @Override + public void startOpen(Player player) { + open_action_.accept(player); + } + + @Override + public void stopOpen(Player player) { + setChanged(); + close_action_.accept(player); + } + + @Override + public boolean canPlaceItem(int index, ItemStack stack) { + return validator_.test(index, stack); + } + + @Override + public void clearContent() { + stacks_.clear(); + setChanged(); + } + + } + + //-------------------------------------------------------------------------------------------------------------------- + + public static void give(Player entity, ItemStack stack) { + ItemHandlerHelper.giveItemToPlayer(entity, stack); + } + + public static void setItemInPlayerHand(Player player, InteractionHand hand, ItemStack stack) { + if (stack.isEmpty()) stack = ItemStack.EMPTY; + if (hand == InteractionHand.MAIN_HAND) { + player.getInventory().items.set(player.getInventory().selected, stack); + } else { + player.getInventory().offhand.set(0, stack); + } + } + + //-------------------------------------------------------------------------------------------------------------------- + + public static Container readNbtStacks(CompoundTag nbt, String key, Container target) { + NonNullList stacks = Inventories.readNbtStacks(nbt, key, target.getContainerSize()); + for (int i = 0; i < stacks.size(); ++i) target.setItem(i, stacks.get(i)); + return target; + } + + public static NonNullList readNbtStacks(CompoundTag nbt, String key, int size) { + NonNullList stacks = NonNullList.withSize(size, ItemStack.EMPTY); + if ((nbt == null) || (!nbt.contains(key, Tag.TAG_COMPOUND))) return stacks; + CompoundTag stacknbt = nbt.getCompound(key); + ContainerHelper.loadAllItems(stacknbt, stacks); + return stacks; + } + + public static NonNullList readNbtStacks(CompoundTag nbt, int size) { + NonNullList stacks = NonNullList.withSize(size, ItemStack.EMPTY); + if ((nbt == null) || (!nbt.contains("Items", Tag.TAG_LIST))) return stacks; + ContainerHelper.loadAllItems(nbt, stacks); + return stacks; + } + + public static CompoundTag writeNbtStacks(CompoundTag nbt, String key, NonNullList stacks, boolean omit_trailing_empty) { + CompoundTag stacknbt = new CompoundTag(); + if (omit_trailing_empty) { + for (int i = stacks.size() - 1; i >= 0; --i) { + if (!stacks.get(i).isEmpty()) break; + stacks.remove(i); + } + } + ContainerHelper.saveAllItems(stacknbt, stacks); + if (nbt == null) nbt = new CompoundTag(); + nbt.put(key, stacknbt); + return nbt; + } + + public static CompoundTag writeNbtStacks(CompoundTag nbt, String key, NonNullList stacks) { + return writeNbtStacks(nbt, key, stacks, false); + } + + //-------------------------------------------------------------------------------------------------------------------- + + public static void dropStack(Level world, Vec3 pos, ItemStack stack, Vec3 velocity, double position_noise, double velocity_noise) { + if (stack.isEmpty()) return; + if (position_noise > 0) { + position_noise = Math.min(position_noise, 0.8); + pos = pos.add( + position_noise * (world.getRandom().nextDouble() - .5), + position_noise * (world.getRandom().nextDouble() - .5), + position_noise * (world.getRandom().nextDouble() - .5) + ); + } + if (velocity_noise > 0) { + velocity_noise = Math.min(velocity_noise, 1.0); + velocity = velocity.add( + (velocity_noise) * (world.getRandom().nextDouble() - .5), + (velocity_noise) * (world.getRandom().nextDouble() - .5), + (velocity_noise) * (world.getRandom().nextDouble() - .5) + ); + } + ItemEntity e = new ItemEntity(world, pos.x, pos.y, pos.z, stack); + e.setDeltaMovement((float) velocity.x, (float) velocity.y, (float) velocity.z); + e.setDefaultPickUpDelay(); + world.addFreshEntity(e); + } + + public static void dropStack(Level world, Vec3 pos, ItemStack stack, Vec3 velocity) { + dropStack(world, pos, stack, velocity, 0.3, 0.2); + } + + public static void dropStack(Level world, Vec3 pos, ItemStack stack) { + dropStack(world, pos, stack, Vec3.ZERO, 0.3, 0.2); + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Networking.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Networking.java new file mode 100644 index 0000000..67a3540 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Networking.java @@ -0,0 +1,409 @@ +/* + * @file Networking.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Main client/server message handling. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraftforge.common.util.FakePlayer; +import net.minecraftforge.network.NetworkDirection; +import net.minecraftforge.network.NetworkEvent; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.simple.SimpleChannel; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; +import java.util.function.Consumer; +import java.util.function.Supplier; + + +public class Networking { + private static final String PROTOCOL = "1"; + private static SimpleChannel DEFAULT_CHANNEL; + + public static void init(String modid) { + DEFAULT_CHANNEL = NetworkRegistry.ChannelBuilder + .named(new ResourceLocation(modid, "default_ch")) + .clientAcceptedVersions(PROTOCOL::equals).serverAcceptedVersions(PROTOCOL::equals).networkProtocolVersion(() -> PROTOCOL) + .simpleChannel(); + int discr = -1; + DEFAULT_CHANNEL.registerMessage(++discr, PacketTileNotifyClientToServer.class, PacketTileNotifyClientToServer::compose, PacketTileNotifyClientToServer::parse, PacketTileNotifyClientToServer.Handler::handle); + DEFAULT_CHANNEL.registerMessage(++discr, PacketTileNotifyServerToClient.class, PacketTileNotifyServerToClient::compose, PacketTileNotifyServerToClient::parse, PacketTileNotifyServerToClient.Handler::handle); + DEFAULT_CHANNEL.registerMessage(++discr, PacketContainerSyncClientToServer.class, PacketContainerSyncClientToServer::compose, PacketContainerSyncClientToServer::parse, PacketContainerSyncClientToServer.Handler::handle); + DEFAULT_CHANNEL.registerMessage(++discr, PacketContainerSyncServerToClient.class, PacketContainerSyncServerToClient::compose, PacketContainerSyncServerToClient::parse, PacketContainerSyncServerToClient.Handler::handle); + DEFAULT_CHANNEL.registerMessage(++discr, PacketNbtNotifyClientToServer.class, PacketNbtNotifyClientToServer::compose, PacketNbtNotifyClientToServer::parse, PacketNbtNotifyClientToServer.Handler::handle); + DEFAULT_CHANNEL.registerMessage(++discr, PacketNbtNotifyServerToClient.class, PacketNbtNotifyServerToClient::compose, PacketNbtNotifyServerToClient::parse, PacketNbtNotifyServerToClient.Handler::handle); + DEFAULT_CHANNEL.registerMessage(++discr, OverlayTextMessage.class, OverlayTextMessage::compose, OverlayTextMessage::parse, OverlayTextMessage.Handler::handle); + } + + //-------------------------------------------------------------------------------------------------------------------- + // Tile entity notifications + //-------------------------------------------------------------------------------------------------------------------- + + public interface IPacketTileNotifyReceiver { + default void onServerPacketReceived(CompoundTag nbt) { + } + + default void onClientPacketReceived(Player player, CompoundTag nbt) { + } + } + + public static class PacketTileNotifyClientToServer { + CompoundTag nbt = null; + BlockPos pos = BlockPos.ZERO; + + public static void sendToServer(BlockPos pos, CompoundTag nbt) { + if ((pos != null) && (nbt != null)) + DEFAULT_CHANNEL.sendToServer(new PacketTileNotifyClientToServer(pos, nbt)); + } + + public static void sendToServer(BlockEntity te, CompoundTag nbt) { + if ((te != null) && (nbt != null)) + DEFAULT_CHANNEL.sendToServer(new PacketTileNotifyClientToServer(te, nbt)); + } + + public PacketTileNotifyClientToServer() { + } + + public PacketTileNotifyClientToServer(BlockPos pos, CompoundTag nbt) { + this.nbt = nbt; + this.pos = pos; + } + + public PacketTileNotifyClientToServer(BlockEntity te, CompoundTag nbt) { + this.nbt = nbt; + pos = te.getBlockPos(); + } + + public static PacketTileNotifyClientToServer parse(final FriendlyByteBuf buf) { + return new PacketTileNotifyClientToServer(buf.readBlockPos(), buf.readNbt()); + } + + public static void compose(final PacketTileNotifyClientToServer pkt, final FriendlyByteBuf buf) { + buf.writeBlockPos(pkt.pos); + buf.writeNbt(pkt.nbt); + } + + @SuppressWarnings("all") + public static class Handler { + public static void handle(final PacketTileNotifyClientToServer pkt, final Supplier ctx) { + ctx.get().enqueueWork(() -> { + Player player = ctx.get().getSender(); + if (player == null) return; + Level world = player.level(); + final BlockEntity te = world.getBlockEntity(pkt.pos); + if (!(te instanceof IPacketTileNotifyReceiver)) return; + ((IPacketTileNotifyReceiver) te).onClientPacketReceived(ctx.get().getSender(), pkt.nbt); + }); + ctx.get().setPacketHandled(true); + } + } + } + + public static class PacketTileNotifyServerToClient { + CompoundTag nbt = null; + BlockPos pos = BlockPos.ZERO; + + public static void sendToPlayer(Player player, BlockEntity te, CompoundTag nbt) { + if ((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || (te == null) || (nbt == null)) + return; + DEFAULT_CHANNEL.sendTo(new PacketTileNotifyServerToClient(te, nbt), ((ServerPlayer) player).connection.connection, NetworkDirection.PLAY_TO_CLIENT); + } + + public static void sendToPlayers(BlockEntity te, CompoundTag nbt) { + if (te == null || te.getLevel() == null) return; + for (Player player : te.getLevel().players()) sendToPlayer(player, te, nbt); + } + + public PacketTileNotifyServerToClient() { + } + + public PacketTileNotifyServerToClient(BlockPos pos, CompoundTag nbt) { + this.nbt = nbt; + this.pos = pos; + } + + public PacketTileNotifyServerToClient(BlockEntity te, CompoundTag nbt) { + this.nbt = nbt; + pos = te.getBlockPos(); + } + + public static PacketTileNotifyServerToClient parse(final FriendlyByteBuf buf) { + return new PacketTileNotifyServerToClient(buf.readBlockPos(), buf.readNbt()); + } + + public static void compose(final PacketTileNotifyServerToClient pkt, final FriendlyByteBuf buf) { + buf.writeBlockPos(pkt.pos); + buf.writeNbt(pkt.nbt); + } + + public static class Handler { + public static void handle(final PacketTileNotifyServerToClient pkt, final Supplier ctx) { + ctx.get().enqueueWork(() -> { + Level world = SidedProxy.getWorldClientSide(); + if (world == null) return; + final BlockEntity te = world.getBlockEntity(pkt.pos); + if (!(te instanceof IPacketTileNotifyReceiver)) return; + ((IPacketTileNotifyReceiver) te).onServerPacketReceived(pkt.nbt); + }); + ctx.get().setPacketHandled(true); + } + } + } + + //-------------------------------------------------------------------------------------------------------------------- + // (GUI) Container synchronization + //-------------------------------------------------------------------------------------------------------------------- + + public interface INetworkSynchronisableContainer { + void onServerPacketReceived(int windowId, CompoundTag nbt); + + void onClientPacketReceived(int windowId, Player player, CompoundTag nbt); + } + + public static class PacketContainerSyncClientToServer { + int id = -1; + CompoundTag nbt = null; + + public static void sendToServer(int windowId, CompoundTag nbt) { + if (nbt != null) DEFAULT_CHANNEL.sendToServer(new PacketContainerSyncClientToServer(windowId, nbt)); + } + + public static void sendToServer(AbstractContainerMenu container, CompoundTag nbt) { + if (nbt != null) + DEFAULT_CHANNEL.sendToServer(new PacketContainerSyncClientToServer(container.containerId, nbt)); + } + + public PacketContainerSyncClientToServer() { + } + + public PacketContainerSyncClientToServer(int id, CompoundTag nbt) { + this.nbt = nbt; + this.id = id; + } + + public static PacketContainerSyncClientToServer parse(final FriendlyByteBuf buf) { + return new PacketContainerSyncClientToServer(buf.readInt(), buf.readNbt()); + } + + public static void compose(final PacketContainerSyncClientToServer pkt, final FriendlyByteBuf buf) { + buf.writeInt(pkt.id); + buf.writeNbt(pkt.nbt); + } + + public static class Handler { + public static void handle(final PacketContainerSyncClientToServer pkt, final Supplier ctx) { + ctx.get().enqueueWork(() -> { + Player player = ctx.get().getSender(); + if ((player == null) || !(player.containerMenu instanceof INetworkSynchronisableContainer)) return; + if (player.containerMenu.containerId != pkt.id) return; + ((INetworkSynchronisableContainer) player.containerMenu).onClientPacketReceived(pkt.id, player, pkt.nbt); + }); + ctx.get().setPacketHandled(true); + } + } + } + + public static class PacketContainerSyncServerToClient { + int id = -1; + CompoundTag nbt = null; + + public static void sendToPlayer(Player player, int windowId, CompoundTag nbt) { + if ((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || (nbt == null)) return; + DEFAULT_CHANNEL.sendTo(new PacketContainerSyncServerToClient(windowId, nbt), ((ServerPlayer) player).connection.connection, NetworkDirection.PLAY_TO_CLIENT); + } + + public static void sendToPlayer(Player player, AbstractContainerMenu container, CompoundTag nbt) { + if ((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || (nbt == null)) return; + DEFAULT_CHANNEL.sendTo(new PacketContainerSyncServerToClient(container.containerId, nbt), ((ServerPlayer) player).connection.connection, NetworkDirection.PLAY_TO_CLIENT); + } + + public static + void sendToListeners(Level world, C container, CompoundTag nbt) { + for (Player player : world.players()) { + if (player.containerMenu.containerId != container.containerId) continue; + sendToPlayer(player, container.containerId, nbt); + } + } + + public PacketContainerSyncServerToClient() { + } + + public PacketContainerSyncServerToClient(int id, CompoundTag nbt) { + this.nbt = nbt; + this.id = id; + } + + public static PacketContainerSyncServerToClient parse(final FriendlyByteBuf buf) { + return new PacketContainerSyncServerToClient(buf.readInt(), buf.readNbt()); + } + + public static void compose(final PacketContainerSyncServerToClient pkt, final FriendlyByteBuf buf) { + buf.writeInt(pkt.id); + buf.writeNbt(pkt.nbt); + } + + public static class Handler { + public static void handle(final PacketContainerSyncServerToClient pkt, final Supplier ctx) { + ctx.get().enqueueWork(() -> { + Player player = SidedProxy.getPlayerClientSide(); + if ((player == null) || !(player.containerMenu instanceof INetworkSynchronisableContainer)) return; + if (player.containerMenu.containerId != pkt.id) return; + ((INetworkSynchronisableContainer) player.containerMenu).onServerPacketReceived(pkt.id, pkt.nbt); + }); + ctx.get().setPacketHandled(true); + } + } + } + + //-------------------------------------------------------------------------------------------------------------------- + // World notifications + //-------------------------------------------------------------------------------------------------------------------- + + public static class PacketNbtNotifyClientToServer { + public static final Map> handlers = new HashMap<>(); + final CompoundTag nbt; + + public static void sendToServer(CompoundTag nbt) { + if (nbt != null) DEFAULT_CHANNEL.sendToServer(new PacketNbtNotifyClientToServer(nbt)); + } + + public PacketNbtNotifyClientToServer(CompoundTag nbt) { + this.nbt = nbt; + } + + public static PacketNbtNotifyClientToServer parse(final FriendlyByteBuf buf) { + return new PacketNbtNotifyClientToServer(buf.readNbt()); + } + + public static void compose(final PacketNbtNotifyClientToServer pkt, final FriendlyByteBuf buf) { + buf.writeNbt(pkt.nbt); + } + + public static class Handler { + public static void handle(final PacketNbtNotifyClientToServer pkt, final Supplier ctx) { + ctx.get().enqueueWork(() -> { + final ServerPlayer player = ctx.get().getSender(); + if (player == null) return; + final String hnd = pkt.nbt.getString("hnd"); + if (hnd.isEmpty()) return; + if (handlers.containsKey(hnd)) handlers.get(hnd).accept(player, pkt.nbt); + }); + ctx.get().setPacketHandled(true); + } + } + } + + public static class PacketNbtNotifyServerToClient { + public static final Map> handlers = new HashMap<>(); + final CompoundTag nbt; + + public static void sendToPlayer(Player player, CompoundTag nbt) { + if ((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || (nbt == null)) return; + DEFAULT_CHANNEL.sendTo(new PacketNbtNotifyServerToClient(nbt), ((ServerPlayer) player).connection.connection, NetworkDirection.PLAY_TO_CLIENT); + } + + public static void sendToPlayers(Level world, CompoundTag nbt) { + for (Player player : world.players()) sendToPlayer(player, nbt); + } + + public PacketNbtNotifyServerToClient(CompoundTag nbt) { + this.nbt = nbt; + } + + public static PacketNbtNotifyServerToClient parse(final FriendlyByteBuf buf) { + return new PacketNbtNotifyServerToClient(buf.readNbt()); + } + + public static void compose(final PacketNbtNotifyServerToClient pkt, final FriendlyByteBuf buf) { + buf.writeNbt(pkt.nbt); + } + + public static class Handler { + public static void handle(final PacketNbtNotifyServerToClient pkt, final Supplier ctx) { + ctx.get().enqueueWork(() -> { + final String hnd = pkt.nbt.getString("hnd"); + if (hnd.isEmpty()) return; + if (handlers.containsKey(hnd)) handlers.get(hnd).accept(pkt.nbt); + }); + ctx.get().setPacketHandled(true); + } + } + } + + //-------------------------------------------------------------------------------------------------------------------- + // Main window GUI text message + //-------------------------------------------------------------------------------------------------------------------- + + public static class OverlayTextMessage { + public static final int DISPLAY_TIME_MS = 3000; + private static BiConsumer handler_ = null; + private final Component data_; + private int delay_ = DISPLAY_TIME_MS; + + private Component data() { + return data_; + } + + private int delay() { + return delay_; + } + + public static void setHandler(BiConsumer handler) { + if (handler_ == null) handler_ = handler; + } + + public static void sendToPlayer(Player player, Component message, int delay) { + if ((!(player instanceof ServerPlayer)) || (player instanceof FakePlayer) || Auxiliaries.isEmpty(message)) + return; + DEFAULT_CHANNEL.sendTo(new OverlayTextMessage(message, delay), ((ServerPlayer) player).connection.connection, NetworkDirection.PLAY_TO_CLIENT); + } + + public OverlayTextMessage() { + data_ = Component.translatable("[unset]"); + } + + public OverlayTextMessage(final Component tct, int delay) { + data_ = tct.copy(); + delay_ = delay; + } + + public static OverlayTextMessage parse(final FriendlyByteBuf buf) { + try { + return new OverlayTextMessage(buf.readComponent(), DISPLAY_TIME_MS); + } catch (Throwable e) { + return new OverlayTextMessage(Component.translatable("[incorrect translation]"), DISPLAY_TIME_MS); + } + } + + public static void compose(final OverlayTextMessage pkt, final FriendlyByteBuf buf) { + try { + buf.writeComponent(pkt.data()); + } catch (Throwable e) { + Auxiliaries.logger().error("OverlayTextMessage.toBytes() failed: " + e); + } + } + + public static class Handler { + public static void handle(final OverlayTextMessage pkt, final Supplier ctx) { + if (handler_ != null) ctx.get().enqueueWork(() -> handler_.accept(pkt.data(), pkt.delay())); + ctx.get().setPacketHandled(true); + } + } + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/OptionalRecipeCondition.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/OptionalRecipeCondition.java new file mode 100644 index 0000000..681daff --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/OptionalRecipeCondition.java @@ -0,0 +1,191 @@ +/* + * @file OptionalRecipeCondition.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Recipe condition to enable opt'ing out JSON based recipes. + */ +package dev.zontreck.libzontreck.edlibmc; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.GsonHelper; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraftforge.common.crafting.conditions.ICondition; +import net.minecraftforge.common.crafting.conditions.IConditionSerializer; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.IForgeRegistry; +import org.slf4j.Logger; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Predicate; + + +public class OptionalRecipeCondition implements ICondition { + private static ResourceLocation NAME; + + private final List all_required; + private final List any_missing; + private final List all_required_tags; + private final List any_missing_tags; + private final @Nullable ResourceLocation result; + private final boolean result_is_tag; + private final boolean experimental; + + private static boolean with_experimental = false; + private static boolean without_recipes = false; + private static Predicate block_optouts = (block) -> false; + private static Predicate item_optouts = (item) -> false; + + public static void init(String modid, Logger logger) { + NAME = new ResourceLocation(modid, "optional"); + } + + public static void on_config(boolean enable_experimental, boolean disable_all_recipes, + Predicate block_optout_provider, + Predicate item_optout_provider) { + with_experimental = enable_experimental; + without_recipes = disable_all_recipes; + block_optouts = block_optout_provider; + item_optouts = item_optout_provider; + } + + public OptionalRecipeCondition(ResourceLocation result, List required, List missing, List required_tags, List missing_tags, boolean isexperimental, boolean result_is_tag) { + all_required = required; + any_missing = missing; + all_required_tags = required_tags; + any_missing_tags = missing_tags; + this.result = result; + this.result_is_tag = result_is_tag; + experimental = isexperimental; + } + + @Override + public ResourceLocation getID() { + return NAME; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append("Optional recipe, all-required: ["); + for (ResourceLocation e : all_required) sb.append(e.toString()).append(","); + for (ResourceLocation e : all_required_tags) sb.append("#").append(e.toString()).append(","); + sb.delete(sb.length() - 1, sb.length()).append("], any-missing: ["); + for (ResourceLocation e : any_missing) sb.append(e.toString()).append(","); + for (ResourceLocation e : any_missing_tags) sb.append("#").append(e.toString()).append(","); + sb.delete(sb.length() - 1, sb.length()).append("]"); + if (experimental) sb.append(" EXPERIMENTAL"); + return sb.toString(); + } + + @Override + public boolean test(IContext context) { + if (without_recipes) return false; + if ((experimental) && (!with_experimental)) return false; + final IForgeRegistry item_registry = ForgeRegistries.ITEMS; + //final Collection item_tags = SerializationTags.getInstance().getOrEmpty(Registry.ITEM_REGISTRY).getAvailableTags(); + if (result != null) { + boolean item_registered = item_registry.containsKey(result); + if (!item_registered) return false; // required result not registered + if (item_optouts.test(item_registry.getValue(result))) return false; + if (ForgeRegistries.BLOCKS.containsKey(result) && block_optouts.test(ForgeRegistries.BLOCKS.getValue(result))) + return false; + } + if (!all_required.isEmpty()) { + for (ResourceLocation rl : all_required) { + if (!item_registry.containsKey(rl)) return false; + } + } + if (!all_required_tags.isEmpty()) { + for (ResourceLocation rl : all_required_tags) { + if (item_registry.tags().getTagNames().noneMatch(tk -> tk.location().equals(rl))) + return false; // if(!item_tags.contains(rl)) return false; + } + } + if (!any_missing.isEmpty()) { + for (ResourceLocation rl : any_missing) { + if (!item_registry.containsKey(rl)) return true; + } + return false; + } + if (!any_missing_tags.isEmpty()) { + for (ResourceLocation rl : any_missing_tags) { + if (item_registry.tags().getTagNames().noneMatch(tk -> tk.location().equals(rl))) + return true; // if(!item_tags.contains(rl)) return true; + } + return false; + } + return true; + } + + public static class Serializer implements IConditionSerializer { + public static final Serializer INSTANCE = new Serializer(); + + @Override + public ResourceLocation getID() { + return OptionalRecipeCondition.NAME; + } + + @Override + public void write(JsonObject json, OptionalRecipeCondition condition) { + JsonArray required = new JsonArray(); + JsonArray missing = new JsonArray(); + for (ResourceLocation e : condition.all_required) required.add(e.toString()); + for (ResourceLocation e : condition.any_missing) missing.add(e.toString()); + json.add("required", required); + json.add("missing", missing); + if (condition.result != null) { + json.addProperty("result", (condition.result_is_tag ? "#" : "") + condition.result); + } + } + + @Override + public OptionalRecipeCondition read(JsonObject json) { + List required = new ArrayList<>(); + List missing = new ArrayList<>(); + List required_tags = new ArrayList<>(); + List missing_tags = new ArrayList<>(); + ResourceLocation result = null; + boolean experimental = false; + boolean result_is_tag = false; + if (json.has("result")) { + String s = json.get("result").getAsString(); + if (s.startsWith("#")) { + result = new ResourceLocation(s.substring(1)); + result_is_tag = true; + } else { + result = new ResourceLocation(s); + } + } + if (json.has("required")) { + for (JsonElement e : GsonHelper.getAsJsonArray(json, "required")) { + String s = e.getAsString(); + if (s.startsWith("#")) { + required_tags.add(new ResourceLocation(s.substring(1))); + } else { + required.add(new ResourceLocation(s)); + } + } + } + if (json.has("missing")) { + for (JsonElement e : GsonHelper.getAsJsonArray(json, "missing")) { + String s = e.getAsString(); + if (s.startsWith("#")) { + missing_tags.add(new ResourceLocation(s.substring(1))); + } else { + missing.add(new ResourceLocation(s)); + } + } + } + if (json.has("experimental")) experimental = json.get("experimental").getAsBoolean(); + return new OptionalRecipeCondition(result, required, missing, required_tags, missing_tags, experimental, result_is_tag); + } + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Overlay.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Overlay.java new file mode 100644 index 0000000..43f71cc --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Overlay.java @@ -0,0 +1,165 @@ +/* + * @file Overlay.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Renders status messages in one line. + */ +package dev.zontreck.libzontreck.edlibmc; + +import com.mojang.blaze3d.platform.Window; +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.client.multiplayer.ClientLevel; +import net.minecraft.client.player.LocalPlayer; +import net.minecraft.client.renderer.LightTexture; +import net.minecraft.client.renderer.MultiBufferSource; +import net.minecraft.client.renderer.texture.OverlayTexture; +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.util.Mth; +import net.minecraft.util.Tuple; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.LightLayer; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; +import net.minecraftforge.fml.DistExecutor; + +import javax.annotation.Nullable; +import java.util.Optional; + + +public class Overlay { + public static void show(Player player, final Component message) { + Networking.OverlayTextMessage.sendToPlayer(player, message, 3000); + } + + public static void show(Player player, final Component message, int delay) { + Networking.OverlayTextMessage.sendToPlayer(player, message, delay); + } + + public static void show(BlockState state, BlockPos pos) { + show(state, pos, 100); + } + + public static void show(BlockState state, BlockPos pos, int displayTimeoutMs) { + DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> (() -> TextOverlayGui.show(state, pos, displayTimeoutMs))); + } // Only called when client side + + // ----------------------------------------------------------------------------- + // Client side handler + // ----------------------------------------------------------------------------- + + @OnlyIn(Dist.CLIENT) + public static class TextOverlayGui extends Screen { + public static final TextOverlayGui INSTANCE = new TextOverlayGui(); + private static final Component EMPTY_TEXT = Component.literal(""); + private static final BlockState EMPTY_STATE = null; + private static double overlay_y_ = 0.75; + private static int text_color_ = 0x00ffaa00; + private static int border_color_ = 0xaa333333; + private static int background_color1_ = 0xaa333333; + private static int background_color2_ = 0xaa444444; + private static long text_deadline_ = 0; + private static Component text_ = EMPTY_TEXT; + private static long state_deadline_ = 0; + private static @Nullable BlockState state_ = EMPTY_STATE; + private static BlockPos pos_ = BlockPos.ZERO; + + public static void on_config(double overlay_y) { + on_config(overlay_y, 0x00ffaa00, 0xaa333333, 0xaa333333, 0xaa444444); + } + + public static void on_config(double overlay_y, int text_color, int border_color, int background_color1, int background_color2) { + overlay_y_ = overlay_y; + text_color_ = text_color; + border_color_ = border_color; + background_color1_ = background_color1; + background_color2_ = background_color2; + } + + public static synchronized Component text() { + return text_; + } + + public static synchronized long deadline() { + return text_deadline_; + } + + public static synchronized void hide() { + text_deadline_ = 0; + text_ = EMPTY_TEXT; + } + + public static synchronized void show(Component s, int displayTimeoutMs) { + text_ = (s == null) ? (EMPTY_TEXT) : (s.copy()); + text_deadline_ = System.currentTimeMillis() + displayTimeoutMs; + } + + public static synchronized void show(String s, int displayTimeoutMs) { + text_ = ((s == null) || (s.isEmpty())) ? (EMPTY_TEXT) : (Component.literal(s)); + text_deadline_ = System.currentTimeMillis() + displayTimeoutMs; + } + + public static synchronized void show(BlockState state, BlockPos pos, int displayTimeoutMs) { + pos_ = new BlockPos(pos); + state_ = state; + state_deadline_ = System.currentTimeMillis() + displayTimeoutMs; + } + + private static synchronized Optional> state_pos() { + return ((state_deadline_ < System.currentTimeMillis()) || (state_ == EMPTY_STATE)) ? Optional.empty() : Optional.of(new Tuple<>(state_, pos_)); + } + + TextOverlayGui() { + super(Component.literal("")); + } + + public void onRenderGui(final GuiGraphics mxs) { + if (deadline() < System.currentTimeMillis()) return; + if (text() == EMPTY_TEXT) return; + String txt = text().getString(); + if (txt.isEmpty()) return; + final net.minecraft.client.Minecraft mc = net.minecraft.client.Minecraft.getInstance(); + final Window win = mc.getWindow(); + final Font fr = mc.font; + final boolean was_unicode = fr.isBidirectional(); + final int cx = win.getGuiScaledWidth() / 2; + final int cy = (int) (win.getGuiScaledHeight() * overlay_y_); + final int w = fr.width(txt); + final int h = fr.lineHeight; + mxs.fillGradient(cx - (w / 2) - 3, cy - 2, cx + (w / 2) + 2, cy + h + 2, 0xaa333333, 0xaa444444); + mxs.hLine(cx - (w / 2) - 3, cx + (w / 2) + 2, cy - 2, 0xaa333333); + mxs.hLine(cx - (w / 2) - 3, cx + (w / 2) + 2, cy + h + 2, 0xaa333333); + mxs.vLine(cx - (w / 2) - 3, cy - 2, cy + h + 2, 0xaa333333); + mxs.vLine(cx + (w / 2) + 2, cy - 2, cy + h + 2, 0xaa333333); + mxs.drawCenteredString(fr, text(), cx, cy + 1, 0x00ffaa00); + } + + @SuppressWarnings("deprecation") + public void onRenderWorldOverlay(final com.mojang.blaze3d.vertex.PoseStack mxs, final double partialTick) { + final Optional> sp = state_pos(); + if (sp.isEmpty()) return; + final ClientLevel world = Minecraft.getInstance().level; + final LocalPlayer player = Minecraft.getInstance().player; + if ((player == null) || (world == null)) return; + final BlockState state = sp.get().getA(); + final BlockPos pos = sp.get().getB(); + final int light = (world.hasChunkAt(pos)) ? LightTexture.pack(world.getBrightness(LightLayer.BLOCK, pos), world.getBrightness(LightLayer.SKY, pos)) : LightTexture.pack(15, 15); + final MultiBufferSource buffer = Minecraft.getInstance().renderBuffers().bufferSource(); + final double px = Mth.lerp(partialTick, player.xo, player.getX()); + final double py = Mth.lerp(partialTick, player.yo, player.getY()); + final double pz = Mth.lerp(partialTick, player.zo, player.getZ()); + mxs.pushPose(); + mxs.translate((pos.getX() - px), (pos.getY() - py - player.getEyeHeight()), (pos.getZ() - pz)); + Minecraft.getInstance().getBlockRenderer().renderSingleBlock(state, mxs, buffer, light, OverlayTexture.NO_OVERLAY); + mxs.popPose(); + } + + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/README.md b/src/main/java/dev/zontreck/libzontreck/edlibmc/README.md new file mode 100644 index 0000000..f4bf684 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/README.md @@ -0,0 +1,4 @@ +# Engineer's Decor LibMC +____________ + +As Engineer's Decor has been abandoned and discontinued, i've adopted the LibMC, and split the mod up among my various mods. Engineer's Decor blocks, and items, will now reside in Thresholds. \ No newline at end of file diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/Registries.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/Registries.java new file mode 100644 index 0000000..02542bc --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/Registries.java @@ -0,0 +1,263 @@ +/* + * @file Registries.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Common game registry handling. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.tags.TagKey; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.flag.FeatureFlagSet; +import net.minecraft.world.flag.FeatureFlags; +import net.minecraft.world.inventory.MenuType; +import net.minecraft.world.item.BlockItem; +import net.minecraft.world.item.CreativeModeTab; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.crafting.RecipeSerializer; +import net.minecraft.world.level.ItemLike; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraftforge.eventbus.api.IEventBus; +import net.minecraftforge.registries.DeferredRegister; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.registries.RegistryObject; + +import javax.annotation.Nonnull; +import java.util.*; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class Registries { + private static String modid = null; + private static String creative_tab_icon = ""; + private static CreativeModeTab creative_tab = null; + private static final Map> registered_block_tag_keys = new HashMap<>(); + private static final Map> registered_item_tag_keys = new HashMap<>(); + + private static final Map> registered_blocks = new HashMap<>(); + private static final Map> registered_items = new HashMap<>(); + private static final Map>> registered_block_entity_types = new HashMap<>(); + private static final Map>> registered_entity_types = new HashMap<>(); + private static final Map>> registered_menu_types = new HashMap<>(); + private static final Map>> recipe_serializers = new HashMap<>(); + private static final Map> registered_tabs = new HashMap<>(); + + public static final List> tabItems = new ArrayList<>(); + + + + private static DeferredRegister BLOCKS; + private static DeferredRegister ITEMS; + private static DeferredRegister TABS; + private static DeferredRegister> BLOCK_ENTITIES; + private static DeferredRegister> MENUS; + private static DeferredRegister> ENTITIES; + private static DeferredRegister> RECIPE_SERIALIZERS; + private static List> MOD_REGISTRIES; + + public static void init(String mod_id, String creative_tab_icon_item_name, IEventBus bus) { + modid = mod_id; + creative_tab_icon = creative_tab_icon_item_name; + BLOCKS = DeferredRegister.create(ForgeRegistries.BLOCKS, modid); + ITEMS = DeferredRegister.create(ForgeRegistries.ITEMS, modid); + BLOCK_ENTITIES = DeferredRegister.create(ForgeRegistries.BLOCK_ENTITY_TYPES, modid); + MENUS = DeferredRegister.create(ForgeRegistries.MENU_TYPES, modid); + ENTITIES = DeferredRegister.create(ForgeRegistries.ENTITY_TYPES, modid); + RECIPE_SERIALIZERS = DeferredRegister.create(ForgeRegistries.RECIPE_SERIALIZERS, modid); + TABS = DeferredRegister.create(net.minecraft.core.registries.Registries.CREATIVE_MODE_TAB, modid); + + Consumer> consumer = (X)->{ + X.register(bus); + }; + + List.of(BLOCKS, ITEMS, BLOCK_ENTITIES, MENUS, ENTITIES, RECIPE_SERIALIZERS, TABS).forEach(consumer); + } + + // ------------------------------------------------------------------------------------------------------------- + + public static Block getBlock(String block_name) { + return registered_blocks.get(block_name).get(); + } + + public static RegistryObject withTab(RegistryObject item) + { + tabItems.add(item); + + return item; + } + + public static Item getItem(String name) { + return registered_items.get(name).get(); + } + + public static EntityType getEntityType(String name) { + return registered_entity_types.get(name).get(); + } + + public static BlockEntityType getBlockEntityType(String block_name) { + return registered_block_entity_types.get(block_name).get(); + } + + public static MenuType getMenuType(String name) { + return registered_menu_types.get(name).get(); + } + + public static RecipeSerializer getRecipeSerializer(String name) { + return recipe_serializers.get(name).get(); + } + + public static BlockEntityType getBlockEntityTypeOfBlock(String block_name) { + return getBlockEntityType("tet_" + block_name); + } + + public static BlockEntityType getBlockEntityTypeOfBlock(Block block) { + return getBlockEntityTypeOfBlock(ForgeRegistries.BLOCKS.getKey(block).getPath()); + } + + public static MenuType getMenuTypeOfBlock(String name) { + return getMenuType("ct_" + name); + } + + public static MenuType getMenuTypeOfBlock(Block block) { + return getMenuTypeOfBlock(ForgeRegistries.BLOCKS.getKey(block).getPath()); + } + + public static TagKey getBlockTagKey(String name) { + return registered_block_tag_keys.get(name); + } + + public static TagKey getItemTagKey(String name) { + return registered_item_tag_keys.get(name); + } + + // ------------------------------------------------------------------------------------------------------------- + + @Nonnull + public static List getRegisteredBlocks() { + return Collections.unmodifiableList(registered_blocks.values().stream().map(RegistryObject::get).toList()); + } + + @Nonnull + public static List getRegisteredItems() { + return Collections.unmodifiableList(registered_items.values().stream().map(RegistryObject::get).toList()); + } + + @Nonnull + public static List> getRegisteredBlockEntityTypes() { + return Collections.unmodifiableList(registered_block_entity_types.values().stream().map(RegistryObject::get).toList()); + } + + @Nonnull + public static List> getRegisteredEntityTypes() { + return Collections.unmodifiableList(registered_entity_types.values().stream().map(RegistryObject::get).toList()); + } + + // ------------------------------------------------------------------------------------------------------------- + + public static void addItem(String registry_name, Supplier supplier) { + registered_items.put(registry_name, withTab(ITEMS.register(registry_name, supplier))); + + } + + public static void addBlock(String registry_name, Supplier block_supplier) { + registered_blocks.put(registry_name, BLOCKS.register(registry_name, block_supplier)); + registered_items.put(registry_name, withTab(ITEMS.register(registry_name, () -> new BlockItem(registered_blocks.get(registry_name).get(), (new Item.Properties()))))); + } + + public static void addCreativeModeTab(String id, Component title, ItemStack icon) + { + registered_tabs.put(id, TABS.register(id, ()-> CreativeModeTab + .builder() + .icon(()->icon) + .title(title) + .withSearchBar() + + .displayItems((display, output) -> tabItems.forEach(it->output.accept(it.get()))) + + .build() + )); + } + + public static void addBlock(String registry_name, Supplier block_supplier, Supplier item_supplier) { + registered_blocks.put(registry_name, BLOCKS.register(registry_name, block_supplier)); + registered_items.put(registry_name, ITEMS.register(registry_name, item_supplier)); + } + + public static void addBlockEntityType(String registry_name, BlockEntityType.BlockEntitySupplier ctor, String... block_names) { + registered_block_entity_types.put(registry_name, BLOCK_ENTITIES.register(registry_name, () -> { + final Block[] blocks = Arrays.stream(block_names).map(s -> { + Block b = BLOCKS.getEntries().stream().filter((ro) -> ro.getId().getPath().equals(s)).findFirst().map(RegistryObject::get).orElse(null); + if (b == null) Auxiliaries.logError("registered_blocks does not encompass '" + s + "'"); + return b; + }).filter(Objects::nonNull).collect(Collectors.toList()).toArray(new Block[]{}); + return BlockEntityType.Builder.of(ctor, blocks).build(null); + })); + } + + public static > void addEntityType(String registry_name, Supplier> supplier) { + registered_entity_types.put(registry_name, ENTITIES.register(registry_name, supplier)); + } + + public static > void addMenuType(String registry_name, MenuType.MenuSupplier supplier, FeatureFlagSet flags) { + registered_menu_types.put(registry_name, MENUS.register(registry_name, () -> new MenuType<>(supplier, flags))); + } + + public static void addRecipeSerializer(String registry_name, Supplier> serializer_supplier) { + recipe_serializers.put(registry_name, RECIPE_SERIALIZERS.register(registry_name, serializer_supplier)); + } + + public static void addOptionalBlockTag(String tag_name, ResourceLocation... default_blocks) { + final Set> default_suppliers = new HashSet<>(); + for (ResourceLocation rl : default_blocks) default_suppliers.add(() -> ForgeRegistries.BLOCKS.getValue(rl)); + final TagKey key = ForgeRegistries.BLOCKS.tags().createOptionalTagKey(new ResourceLocation(modid, tag_name), default_suppliers); + registered_block_tag_keys.put(tag_name, key); + } + + public static void addOptionaItemTag(String tag_name, ResourceLocation... default_items) { + final Set> default_suppliers = new HashSet<>(); + for (ResourceLocation rl : default_items) default_suppliers.add(() -> ForgeRegistries.ITEMS.getValue(rl)); + final TagKey key = ForgeRegistries.ITEMS.tags().createOptionalTagKey(new ResourceLocation(modid, tag_name), default_suppliers); + registered_item_tag_keys.put(tag_name, key); + } + + // ------------------------------------------------------------------------------------------------------------- + + @Deprecated + /** + * This function is to be removed as it cannot add items to a tab anymore + */ + public static void addBlock(String registry_name, Supplier block_supplier, BiFunction item_builder) { + addBlock(registry_name, block_supplier, () -> item_builder.apply(registered_blocks.get(registry_name).get(), (new Item.Properties()))); + } + + public static void addBlock(String registry_name, Supplier block_supplier, BlockEntityType.BlockEntitySupplier block_entity_ctor) { + addBlock(registry_name, block_supplier); + addBlockEntityType("tet_" + registry_name, block_entity_ctor, registry_name); + } + + public static void addBlock(String registry_name, Supplier block_supplier, BiFunction item_builder, BlockEntityType.BlockEntitySupplier block_entity_ctor) { + addBlock(registry_name, block_supplier, item_builder); + addBlockEntityType("tet_" + registry_name, block_entity_ctor, registry_name); + } + + public static void addBlock(String registry_name, Supplier block_supplier, BiFunction item_builder, BlockEntityType.BlockEntitySupplier block_entity_ctor, MenuType.MenuSupplier menu_type_supplier, FeatureFlagSet menuFlags) { + addBlock(registry_name, block_supplier, item_builder); + addBlockEntityType("tet_" + registry_name, block_entity_ctor, registry_name); + addMenuType("ct_" + registry_name, menu_type_supplier, menuFlags); + } + + public static void addBlock(String registry_name, Supplier block_supplier, BlockEntityType.BlockEntitySupplier block_entity_ctor, MenuType.MenuSupplier menu_type_supplier, FeatureFlagSet menuFlags) { + addBlock(registry_name, block_supplier, block_entity_ctor); + addMenuType("ct_" + registry_name, menu_type_supplier, menuFlags); + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/RfEnergy.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/RfEnergy.java new file mode 100644 index 0000000..8636926 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/RfEnergy.java @@ -0,0 +1,180 @@ +/* + * @file RfEnergy.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * General RF/FE energy handling functionality. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.util.Mth; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraftforge.common.capabilities.ForgeCapabilities; +import net.minecraftforge.common.util.LazyOptional; +import net.minecraftforge.energy.IEnergyStorage; + +import javax.annotation.Nullable; + +public class RfEnergy { + public static int feed(Level world, BlockPos pos, @Nullable Direction side, int rf_energy) { + final BlockEntity te = world.getBlockEntity(pos); + if (te == null) return 0; + final IEnergyStorage es = te.getCapability(ForgeCapabilities.ENERGY, side).orElse(null); + if (es == null) return 0; + return es.receiveEnergy(rf_energy, false); + } + + public static class Battery implements IEnergyStorage { + protected int capacity_; + protected int charge_rate_; + protected int discharge_rate_; + protected int energy_; + + public Battery(int capacity) { + this(capacity, capacity); + } + + public Battery(int capacity, int transfer_rate) { + this(capacity, transfer_rate, transfer_rate, 0); + } + + public Battery(int capacity, int charge_rate, int discharge_rate) { + this(capacity, charge_rate, discharge_rate, 0); + } + + public Battery(int capacity, int charge_rate, int discharge_rate, int energy) { + capacity_ = Math.max(capacity, 1); + charge_rate_ = Mth.clamp(charge_rate, 0, capacity_); + discharge_rate_ = Mth.clamp(discharge_rate, 0, capacity_); + energy_ = Mth.clamp(energy, 0, capacity_); + } + + // --------------------------------------------------------------------------------------------------- + + public Battery setMaxEnergyStored(int capacity) { + capacity_ = Math.max(capacity, 1); + return this; + } + + public Battery setEnergyStored(int energy) { + energy_ = Mth.clamp(energy, 0, capacity_); + return this; + } + + public Battery setChargeRate(int in_rate) { + charge_rate_ = Mth.clamp(in_rate, 0, capacity_); + return this; + } + + public Battery setDischargeRate(int out_rate) { + discharge_rate_ = Mth.clamp(out_rate, 0, capacity_); + return this; + } + + public int getChargeRate() { + return charge_rate_; + } + + public int getDischargeRate() { + return discharge_rate_; + } + + public boolean isEmpty() { + return energy_ <= 0; + } + + public boolean isFull() { + return energy_ >= capacity_; + } + + public int getSOC() { + return (int) Mth.clamp((100.0 * energy_ / capacity_ + .5), 0, 100); + } + + public int getComparatorOutput() { + return (int) Mth.clamp((15.0 * energy_ / capacity_ + .2), 0, 15); + } + + public boolean draw(int energy) { + if (energy_ < energy) return false; + energy_ -= energy; + return true; + } + + public boolean feed(int energy) { + energy_ = Math.min(energy_ + energy, capacity_); + return energy_ >= capacity_; + } + + public Battery clear() { + energy_ = 0; + return this; + } + + public Battery load(CompoundTag nbt, String key) { + setEnergyStored(nbt.getInt(key)); + return this; + } + + public Battery load(CompoundTag nbt) { + return load(nbt, "Energy"); + } + + public CompoundTag save(CompoundTag nbt, String key) { + nbt.putInt(key, energy_); + return nbt; + } + + public CompoundTag save(CompoundTag nbt) { + return save(nbt, "Energy"); + } + + public LazyOptional createEnergyHandler() { + return LazyOptional.of(() -> this); + } + + // IEnergyStorage ------------------------------------------------------------------------------------ + + @Override + public int receiveEnergy(int feed_energy, boolean simulate) { + if (!canReceive()) return 0; + int e = Math.min(Math.min(charge_rate_, feed_energy), capacity_ - energy_); + if (!simulate) energy_ += e; + return e; + } + + @Override + public int extractEnergy(int draw_energy, boolean simulate) { + if (!canExtract()) return 0; + int e = Math.min(Math.min(discharge_rate_, draw_energy), energy_); + if (!simulate) energy_ -= e; + return e; + } + + @Override + public int getEnergyStored() { + return energy_; + } + + @Override + public int getMaxEnergyStored() { + return capacity_; + } + + @Override + public boolean canExtract() { + return discharge_rate_ > 0; + } + + @Override + public boolean canReceive() { + return charge_rate_ > 0; + } + + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/RsSignals.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/RsSignals.java new file mode 100644 index 0000000..70509bd --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/RsSignals.java @@ -0,0 +1,42 @@ +/* + * @file RsSignals.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * General redstone signal related functionality. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.Container; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.state.BlockState; + +import javax.annotation.Nullable; + +public class RsSignals { + + public static boolean hasSignalConnector(BlockState state, BlockGetter world, BlockPos pos, @Nullable Direction realSide) { + return state.isSignalSource(); + } + + public static int fromContainer(@Nullable Container container) { + if (container == null) return 0; + final double max = container.getMaxStackSize(); + if (max <= 0) return 0; + boolean nonempty = false; + double fill_level = 0; + for (int i = 0; i < container.getContainerSize(); ++i) { + ItemStack stack = container.getItem(i); + if (stack.isEmpty() || (stack.getMaxStackSize() <= 0)) continue; + fill_level += ((double) stack.getCount()) / Math.min(max, stack.getMaxStackSize()); + nonempty = true; + } + fill_level /= container.getContainerSize(); + return (int) (Math.floor(fill_level * 14) + (nonempty ? 1 : 0)); // vanilla compliant calculation. + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/SidedProxy.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/SidedProxy.java new file mode 100644 index 0000000..ba5f04b --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/SidedProxy.java @@ -0,0 +1,122 @@ +/* + * @file SidedProxy.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * General client/server sidedness selection proxy. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.client.Minecraft; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraftforge.fml.DistExecutor; + +import javax.annotation.Nullable; +import java.util.Optional; + +public class SidedProxy { + @Nullable + public static Player getPlayerClientSide() { + return proxy.getPlayerClientSide(); + } + + @Nullable + public static Level getWorldClientSide() { + return proxy.getWorldClientSide(); + } + + @Nullable + public static Minecraft mc() { + return proxy.mc(); + } + + public static Optional isCtrlDown() { + return proxy.isCtrlDown(); + } + + public static Optional isShiftDown() { + return proxy.isShiftDown(); + } + + public static Optional getClipboard() { + return proxy.getClipboard(); + } + + public static boolean setClipboard(String text) { + return proxy.setClipboard(text); + } + + // -------------------------------------------------------------------------------------------------------- + + private static final ISidedProxy proxy = DistExecutor.unsafeRunForDist(() -> ClientProxy::new, () -> ServerProxy::new); + + private interface ISidedProxy { + default @Nullable Player getPlayerClientSide() { + return null; + } + + default @Nullable Level getWorldClientSide() { + return null; + } + + default @Nullable Minecraft mc() { + return null; + } + + default Optional isCtrlDown() { + return Optional.empty(); + } + + default Optional isShiftDown() { + return Optional.empty(); + } + + default Optional getClipboard() { + return Optional.empty(); + } + + default boolean setClipboard(String text) { + return false; + } + } + + private static final class ClientProxy implements ISidedProxy { + public @Nullable Player getPlayerClientSide() { + return Minecraft.getInstance().player; + } + + public @Nullable Level getWorldClientSide() { + return Minecraft.getInstance().level; + } + + public @Nullable Minecraft mc() { + return Minecraft.getInstance(); + } + + public Optional isCtrlDown() { + return Optional.of(Auxiliaries.isCtrlDown()); + } + + public Optional isShiftDown() { + return Optional.of(Auxiliaries.isShiftDown()); + } + + public Optional getClipboard() { + return (mc() == null) ? Optional.empty() : Optional.of(net.minecraft.client.gui.font.TextFieldHelper.getClipboardContents(mc())); + } + + public boolean setClipboard(String text) { + if (mc() == null) { + return false; + } + net.minecraft.client.gui.font.TextFieldHelper.setClipboardContents(Minecraft.getInstance(), text); + return true; + } + } + + private static final class ServerProxy implements ISidedProxy { + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/SlabSliceBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/SlabSliceBlock.java new file mode 100644 index 0000000..40e3401 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/SlabSliceBlock.java @@ -0,0 +1,228 @@ +/* + * @file SlabSliceBlock.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Half slab ("slab slices") characteristics class. Actually + * it's now a quarter slab, but who cares. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.IntegerProperty; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class SlabSliceBlock extends StandardBlocks.WaterLoggable implements StandardBlocks.IStandardBlock { + public static final IntegerProperty PARTS = IntegerProperty.create("parts", 0, 14); + + protected static final VoxelShape[] AABBs = { + Shapes.create(new AABB(0, 0. / 16, 0, 1, 2. / 16, 1)), + Shapes.create(new AABB(0, 0. / 16, 0, 1, 4. / 16, 1)), + Shapes.create(new AABB(0, 0. / 16, 0, 1, 6. / 16, 1)), + Shapes.create(new AABB(0, 0. / 16, 0, 1, 8. / 16, 1)), + Shapes.create(new AABB(0, 0. / 16, 0, 1, 10. / 16, 1)), + Shapes.create(new AABB(0, 0. / 16, 0, 1, 12. / 16, 1)), + Shapes.create(new AABB(0, 0. / 16, 0, 1, 14. / 16, 1)), + Shapes.create(new AABB(0, 0. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 2. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 4. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 6. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 8. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 10. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 12. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 14. / 16, 0, 1, 16. / 16, 1)), + Shapes.create(new AABB(0, 0, 0, 1, 1, 1)) // <- with 4bit fill + }; + + protected static final int[] num_slabs_contained_in_parts_ = {1, 2, 3, 4, 5, 6, 7, 8, 7, 6, 5, 4, 3, 2, 1, 0x1}; // <- with 4bit fill + private static boolean with_pickup = false; + + public static void on_config(boolean direct_slab_pickup) { + with_pickup = direct_slab_pickup; + } + + public SlabSliceBlock(long config, BlockBehaviour.Properties builder) { + super(config, builder); + } + + protected boolean is_cube(BlockState state) { + return state.getValue(PARTS) == 0x07; + } + + @Override + @OnlyIn(Dist.CLIENT) + public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { + if (!Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true)) return; + if (with_pickup) Auxiliaries.Tooltip.addInformation("engineersdecor.tooltip.slabpickup", tooltip); + } + + @Override + public RenderTypeHint getRenderTypeHint() { + return (((config & StandardBlocks.CFG_TRANSLUCENT) != 0) ? (RenderTypeHint.TRANSLUCENT) : (RenderTypeHint.CUTOUT)); + } + + @Override + public boolean isPossibleToRespawnInThis(BlockState p_279289_) { + return false; + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { + return AABBs[state.getValue(PARTS) & 0xf]; + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return getShape(state, world, pos, selectionContext); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(PARTS); + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + final BlockPos pos = context.getClickedPos(); + BlockState state = context.getLevel().getBlockState(pos); + if (state.getBlock() == this) { + int parts = state.getValue(PARTS); + if (parts == 7) return null; // -> is already a full block. + parts += (parts < 7) ? 1 : -1; + if (parts == 7) state = state.setValue(WATERLOGGED, false); + return state.setValue(PARTS, parts); + } else { + final Direction face = context.getClickedFace(); + final BlockState placement_state = super.getStateForPlacement(context); // fluid state + if (face == Direction.UP) return placement_state.setValue(PARTS, 0); + if (face == Direction.DOWN) return placement_state.setValue(PARTS, 14); + if (!face.getAxis().isHorizontal()) return placement_state; + final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5); + return placement_state.setValue(PARTS, isupper ? 14 : 0); + } + } + + @Override + @SuppressWarnings("deprecation") + public boolean canBeReplaced(BlockState state, BlockPlaceContext context) { + if (context.getItemInHand().getItem() != this.asItem()) return false; + if (!context.replacingClickedOnBlock()) return true; + final Direction face = context.getClickedFace(); + final int parts = state.getValue(PARTS); + if (parts == 7) return false; + if ((face == Direction.UP) && (parts < 7)) return true; + if ((face == Direction.DOWN) && (parts > 7)) return true; + if (!face.getAxis().isHorizontal()) return false; + final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5); + return isupper ? (parts == 0) : (parts == 1); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState rotate(BlockState state, Rotation rot) { + return state; + } + + @Override + @SuppressWarnings("deprecation") + public BlockState mirror(BlockState state, Mirror mirrorIn) { + return state; + } + + @Override + public boolean hasDynamicDropList() { + return true; + } + + @Override + public List dropList(BlockState state, Level world, BlockEntity te, boolean explosion) { + return new ArrayList<>(Collections.singletonList(new ItemStack(this.asItem(), num_slabs_contained_in_parts_[state.getValue(PARTS) & 0xf]))); + } + + @Override + @SuppressWarnings("deprecation") + public void attack(BlockState state, Level world, BlockPos pos, Player player) { + if ((world.isClientSide) || (!with_pickup)) return; + final ItemStack stack = player.getMainHandItem(); + if (stack.isEmpty() || (Block.byItem(stack.getItem()) != this)) return; + if (stack.getCount() >= stack.getMaxStackSize()) return; + Vec3 lv = player.getLookAngle(); + Direction facing = Direction.getNearest((float) lv.x, (float) lv.y, (float) lv.z); + if ((facing != Direction.UP) && (facing != Direction.DOWN)) return; + if (state.getBlock() != this) return; + int parts = state.getValue(PARTS); + if ((facing == Direction.DOWN) && (parts <= 7)) { + if (parts > 0) { + world.setBlock(pos, state.setValue(PARTS, parts - 1), 3); + } else { + world.removeBlock(pos, false); + } + } else if ((facing == Direction.UP) && (parts >= 7)) { + if (parts < 14) { + world.setBlock(pos, state.setValue(PARTS, parts + 1), 3); + } else { + world.removeBlock(pos, false); + } + } else { + return; + } + if (!player.isCreative()) { + stack.grow(1); + if (player.getInventory() != null) player.getInventory().setChanged(); + } + SoundType st = this.getSoundType(state, world, pos, null); + world.playSound(player, pos, st.getPlaceSound(), SoundSource.BLOCKS, (st.getVolume() + 1f) / 2.5f, 0.9f * st.getPitch()); + } + + @Override + public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { + return (state.getValue(PARTS) != 14) && (super.placeLiquid(world, pos, state, fluidState)); + } + + @Override + public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { + return (state.getValue(PARTS) != 14) && (super.canPlaceLiquid(world, pos, state, fluid)); + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardBlocks.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardBlocks.java new file mode 100644 index 0000000..69343b1 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardBlocks.java @@ -0,0 +1,698 @@ +/* + * @file StandardBlocks.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Common functionality class for decor blocks. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.SimpleWaterloggedBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import net.minecraft.world.level.block.state.properties.DirectionProperty; +import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; +import net.minecraft.world.level.material.PushReaction; +import net.minecraft.world.level.pathfinder.PathComputationType; +import net.minecraft.world.level.storage.loot.LootContext; +import net.minecraft.world.level.storage.loot.LootParams; +import net.minecraft.world.level.storage.loot.parameters.LootContextParams; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.shapes.BooleanOp; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.*; +import java.util.function.Function; +import java.util.function.Supplier; + + +@SuppressWarnings("deprecation") +public class StandardBlocks { + public static final long CFG_DEFAULT = 0x0000000000000000L; // no special config + public static final long CFG_CUTOUT = 0x0000000000000001L; // cutout rendering + public static final long CFG_MIPPED = 0x0000000000000002L; // cutout mipped rendering + public static final long CFG_TRANSLUCENT = 0x0000000000000004L; // indicates a block/pane is glass like (transparent, etc) + public static final long CFG_WATERLOGGABLE = 0x0000000000000008L; // The derived block extends IWaterLoggable + public static final long CFG_HORIZIONTAL = 0x0000000000000010L; // horizontal block, affects bounding box calculation at construction time and placement + public static final long CFG_LOOK_PLACEMENT = 0x0000000000000020L; // placed in direction the player is looking when placing. + public static final long CFG_FACING_PLACEMENT = 0x0000000000000040L; // placed on the facing the player has clicked. + public static final long CFG_OPPOSITE_PLACEMENT = 0x0000000000000080L; // placed placed in the opposite direction of the face the player clicked. + public static final long CFG_FLIP_PLACEMENT_IF_SAME = 0x0000000000000100L; // placement direction flipped if an instance of the same class was clicked + public static final long CFG_FLIP_PLACEMENT_SHIFTCLICK = 0x0000000000000200L; // placement direction flipped if player is sneaking + public static final long CFG_STRICT_CONNECTIONS = 0x0000000000000400L; // blocks do not connect to similar blocks around (implementation details may vary a bit) + public static final long CFG_AI_PASSABLE = 0x0000000000000800L; // does not block movement path for AI, needed for non-opaque blocks with collision shapes not thin at the bottom or one side. + + public interface IStandardBlock { + default long config() { + return 0; + } + + default boolean hasDynamicDropList() { + return false; + } + + default List dropList(BlockState state, Level world, @Nullable BlockEntity te, boolean explosion) { + return Collections.singletonList((!world.isClientSide()) ? (new ItemStack(state.getBlock().asItem())) : (ItemStack.EMPTY)); + } + + enum RenderTypeHint {SOLID, CUTOUT, CUTOUT_MIPPED, TRANSLUCENT, TRANSLUCENT_NO_CRUMBLING} + + default RenderTypeHint getRenderTypeHint() { + return getRenderTypeHint(config()); + } + + default RenderTypeHint getRenderTypeHint(long config) { + if ((config & CFG_CUTOUT) != 0) return RenderTypeHint.CUTOUT; + if ((config & CFG_MIPPED) != 0) return RenderTypeHint.CUTOUT_MIPPED; + if ((config & CFG_TRANSLUCENT) != 0) return RenderTypeHint.TRANSLUCENT; + return RenderTypeHint.SOLID; + } + } + + public static class BaseBlock extends Block implements IStandardBlock, SimpleWaterloggedBlock { + public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; + public final long config; + + public BaseBlock(long conf, BlockBehaviour.Properties properties) { + super(properties); + config = conf; + BlockState state = getStateDefinition().any(); + if ((conf & CFG_WATERLOGGABLE) != 0) state = state.setValue(WATERLOGGED, false); + registerDefaultState(state); + } + + @Override + public long config() { + return config; + } + + @Override + @OnlyIn(Dist.CLIENT) + public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { + Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); + } + + @Override + public RenderTypeHint getRenderTypeHint() { + return getRenderTypeHint(config); + } + + @Override + @SuppressWarnings("deprecation") + public boolean isPathfindable(BlockState state, BlockGetter world, BlockPos pos, PathComputationType type) { + return ((config & CFG_AI_PASSABLE) != 0) && (super.isPathfindable(state, world, pos, type)); + } + + public boolean hasSignalConnector(BlockState state, BlockGetter world, BlockPos pos, @Nullable Direction side) { + return state.isSignalSource(); + } + + @Override + @SuppressWarnings("deprecation") + public void onRemove(BlockState state, Level world, BlockPos pos, BlockState newState, boolean isMoving) { + final boolean rsup = (state.hasBlockEntity() && (state.getBlock() != newState.getBlock())); + super.onRemove(state, world, pos, newState, isMoving); + if (rsup) world.updateNeighbourForOutputSignal(pos, this); + } + + @Override + public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos) { + return (((config & CFG_WATERLOGGABLE) == 0) || (!state.getValue(WATERLOGGED))) && super.propagatesSkylightDown(state, reader, pos); + } + + @Override + @SuppressWarnings("deprecation") + public FluidState getFluidState(BlockState state) { + return (((config & CFG_WATERLOGGABLE) != 0) && state.getValue(WATERLOGGED)) ? Fluids.WATER.getSource(false) : super.getFluidState(state); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos) { + if (((config & CFG_WATERLOGGABLE) != 0) && (state.getValue(WATERLOGGED))) + world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world)); + return state; + } + + @Override // SimpleWaterloggedBlock + public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { + return ((config & CFG_WATERLOGGABLE) != 0) && SimpleWaterloggedBlock.super.canPlaceLiquid(world, pos, state, fluid); + } + + @Override // SimpleWaterloggedBlock + public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { + return ((config & CFG_WATERLOGGABLE) != 0) && SimpleWaterloggedBlock.super.placeLiquid(world, pos, state, fluidState); + } + + @Override // SimpleWaterloggedBlock + public ItemStack pickupBlock(LevelAccessor world, BlockPos pos, BlockState state) { + return ((config & CFG_WATERLOGGABLE) != 0) ? (SimpleWaterloggedBlock.super.pickupBlock(world, pos, state)) : (ItemStack.EMPTY); + } + + @Override // SimpleWaterloggedBlock + public Optional getPickupSound() { + return ((config & CFG_WATERLOGGABLE) != 0) ? (SimpleWaterloggedBlock.super.getPickupSound()) : Optional.empty(); + } + } + + public static class Cutout extends BaseBlock implements IStandardBlock { + private final VoxelShape vshape; + + public Cutout(long conf, BlockBehaviour.Properties properties) { + this(conf, properties, Auxiliaries.getPixeledAABB(0, 0, 0, 16, 16, 16)); + } + + public Cutout(long conf, BlockBehaviour.Properties properties, AABB aabb) { + this(conf, properties, Shapes.create(aabb)); + } + + public Cutout(long conf, BlockBehaviour.Properties properties, AABB[] aabbs) { + this(conf, properties, Arrays.stream(aabbs).map(Shapes::create).reduce(Shapes.empty(), (shape, aabb) -> Shapes.joinUnoptimized(shape, aabb, BooleanOp.OR))); + } + + public Cutout(long conf, BlockBehaviour.Properties properties, VoxelShape voxel_shape) { + super(conf, properties); + vshape = voxel_shape; + } + + @Override + @SuppressWarnings("deprecation") + public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { + return vshape; + } + + @Override + @SuppressWarnings("deprecation") + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return vshape; + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + BlockState state = super.getStateForPlacement(context); + if ((config & CFG_WATERLOGGABLE) != 0) { + FluidState fs = context.getLevel().getFluidState(context.getClickedPos()); + state = state.setValue(WATERLOGGED, fs.getType() == Fluids.WATER); + } + return state; + } + + + @Override + public boolean isPossibleToRespawnInThis(BlockState p_279289_) { + return false; + } + + @Override + @SuppressWarnings("deprecation") + public PushReaction getPistonPushReaction(BlockState state) { + return PushReaction.NORMAL; + } + + @Override + public boolean propagatesSkylightDown(BlockState state, BlockGetter reader, BlockPos pos) { + if ((config & CFG_WATERLOGGABLE) != 0) { + if (state.getValue(WATERLOGGED)) return false; + } + return super.propagatesSkylightDown(state, reader, pos); + } + + @Override + @SuppressWarnings("deprecation") + public FluidState getFluidState(BlockState state) { + if ((config & CFG_WATERLOGGABLE) != 0) { + return state.getValue(WATERLOGGED) ? Fluids.WATER.getSource(false) : super.getFluidState(state); + } + return super.getFluidState(state); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState updateShape(BlockState state, Direction facing, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos) { + if ((config & CFG_WATERLOGGABLE) != 0) { + if (state.getValue(WATERLOGGED)) + world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world)); + } + return state; + } + } + + public static class WaterLoggable extends Cutout implements IStandardBlock { + public WaterLoggable(long config, BlockBehaviour.Properties properties) { + super(config | CFG_WATERLOGGABLE, properties); + } + + public WaterLoggable(long config, BlockBehaviour.Properties properties, AABB aabb) { + super(config | CFG_WATERLOGGABLE, properties, aabb); + } + + public WaterLoggable(long config, BlockBehaviour.Properties properties, VoxelShape voxel_shape) { + super(config | CFG_WATERLOGGABLE, properties, voxel_shape); + } + + public WaterLoggable(long config, BlockBehaviour.Properties properties, AABB[] aabbs) { + super(config | CFG_WATERLOGGABLE, properties, aabbs); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(WATERLOGGED); + } + } + + public static class Directed extends Cutout implements IStandardBlock { + public static final DirectionProperty FACING = BlockStateProperties.FACING; + protected final Map vshapes; + + public Directed(long config, BlockBehaviour.Properties properties, final Function, Map> shape_supplier) { + super(config, properties); + registerDefaultState(super.defaultBlockState().setValue(FACING, Direction.UP)); + vshapes = shape_supplier.apply(getStateDefinition().getPossibleStates()); + } + + public Directed(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { + this(config, properties, (states) -> { + final Map vshapes = new HashMap<>(); + final ArrayList indexed_shapes = shape_supplier.get(); + for (BlockState state : states) + vshapes.put(state, indexed_shapes.get(state.getValue(FACING).get3DDataValue())); + return vshapes; + }); + } + + public Directed(long config, BlockBehaviour.Properties properties, final AABB[] unrotatedAABBs) { + this(config, properties, (states) -> { + final boolean is_horizontal = ((config & CFG_HORIZIONTAL) != 0); + Map vshapes = new HashMap<>(); + for (BlockState state : states) { + vshapes.put(state, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(unrotatedAABBs, state.getValue(FACING), is_horizontal))); + } + return vshapes; + }); + } + + public Directed(long config, BlockBehaviour.Properties properties, final AABB unrotatedAABB) { + this(config, properties, new AABB[]{unrotatedAABB}); + } + + + @Override + public boolean isPossibleToRespawnInThis(BlockState p_279289_) { + return false; + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { + return vshapes.get(state); + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return getShape(state, world, pos, selectionContext); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(FACING); + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + BlockState state = super.getStateForPlacement(context); + if (state == null) return null; + Direction facing = context.getClickedFace(); + if ((config & (CFG_HORIZIONTAL | CFG_LOOK_PLACEMENT)) == (CFG_HORIZIONTAL | CFG_LOOK_PLACEMENT)) { + // horizontal placement in direction the player is looking + facing = context.getHorizontalDirection(); + } else if ((config & (CFG_HORIZIONTAL | CFG_LOOK_PLACEMENT)) == (CFG_HORIZIONTAL)) { + // horizontal placement on a face + if (((facing == Direction.UP) || (facing == Direction.DOWN))) return null; + } else if ((config & CFG_LOOK_PLACEMENT) != 0) { + // placement in direction the player is looking, with up and down + facing = context.getNearestLookingDirection(); + } + if ((config & CFG_OPPOSITE_PLACEMENT) != 0) facing = facing.getOpposite(); + if (((config & CFG_FLIP_PLACEMENT_SHIFTCLICK) != 0) && (context.getPlayer() != null) && (context.getPlayer().isShiftKeyDown())) + facing = facing.getOpposite(); + return state.setValue(FACING, facing); + } + } + + public static class AxisAligned extends Cutout implements IStandardBlock { + public static final EnumProperty AXIS = BlockStateProperties.AXIS; + protected final ArrayList vshapes; + + public AxisAligned(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { + super(config, properties); + registerDefaultState(super.defaultBlockState().setValue(AXIS, Direction.Axis.X)); + vshapes = shape_supplier.get(); + } + + public AxisAligned(long config, BlockBehaviour.Properties properties, final AABB[] unrotatedAABBs) { + this(config, properties, () -> new ArrayList<>(Arrays.asList( + Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(unrotatedAABBs, Direction.EAST, false)), + Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(unrotatedAABBs, Direction.UP, false)), + Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(unrotatedAABBs, Direction.SOUTH, false)), + Shapes.block() + ))); + } + + public AxisAligned(long config, BlockBehaviour.Properties properties, final AABB unrotatedAABB) { + this(config, properties, new AABB[]{unrotatedAABB}); + } + + + @Override + public boolean isPossibleToRespawnInThis(BlockState p_279289_) { + return false; + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { + return vshapes.get((state.getValue(AXIS)).ordinal() & 0x3); + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return getShape(state, world, pos, selectionContext); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(AXIS); + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + Direction facing; + if ((config & CFG_LOOK_PLACEMENT) != 0) { + facing = context.getNearestLookingDirection(); + } else { + facing = context.getClickedFace(); + } + return super.getStateForPlacement(context).setValue(AXIS, facing.getAxis()); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState rotate(BlockState state, Rotation rotation) { + switch (rotation) { + case CLOCKWISE_90: + case COUNTERCLOCKWISE_90: + switch (state.getValue(AXIS)) { + case X: + return state.setValue(AXIS, Direction.Axis.Z); + case Z: + return state.setValue(AXIS, Direction.Axis.X); + } + } + return state; + } + } + + public static class Horizontal extends Cutout implements IStandardBlock { + public static final DirectionProperty HORIZONTAL_FACING = BlockStateProperties.HORIZONTAL_FACING; + protected final Map vshapes; + protected final Map cshapes; + + public Horizontal(long config, BlockBehaviour.Properties properties, final Function, Map> shape_supplier) { + super(config | CFG_HORIZIONTAL, properties); + registerDefaultState(super.defaultBlockState().setValue(HORIZONTAL_FACING, Direction.NORTH)); + vshapes = shape_supplier.apply(getStateDefinition().getPossibleStates()); + cshapes = shape_supplier.apply(getStateDefinition().getPossibleStates()); + } + + public Horizontal(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { + this(config, properties, (states) -> { + final Map vshapes = new HashMap<>(); + final ArrayList indexed_shapes = shape_supplier.get(); + for (BlockState state : states) + vshapes.put(state, indexed_shapes.get(state.getValue(HORIZONTAL_FACING).get3DDataValue())); + return vshapes; + }); + } + + public Horizontal(long config, BlockBehaviour.Properties properties, final AABB unrotatedAABB) { + this(config, properties, new AABB[]{unrotatedAABB}); + } + + public Horizontal(long config, BlockBehaviour.Properties properties, final AABB[] unrotatedAABBs) { + this(config, properties, (states) -> { + Map vshapes = new HashMap<>(); + for (BlockState state : states) { + vshapes.put(state, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(unrotatedAABBs, state.getValue(HORIZONTAL_FACING), true))); + } + return vshapes; + }); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(HORIZONTAL_FACING); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { + return vshapes.get(state); + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return cshapes.get(state); + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + BlockState state = super.getStateForPlacement(context); + if (state == null) return null; + Direction facing = context.getClickedFace(); + if ((config & CFG_LOOK_PLACEMENT) != 0) { + // horizontal placement in direction the player is looking + facing = context.getHorizontalDirection(); + } else { + // horizontal placement on a face + facing = ((facing == Direction.UP) || (facing == Direction.DOWN)) ? (context.getHorizontalDirection()) : facing; + } + if ((config & CFG_OPPOSITE_PLACEMENT) != 0) facing = facing.getOpposite(); + if (((config & CFG_FLIP_PLACEMENT_SHIFTCLICK) != 0) && (context.getPlayer() != null) && (context.getPlayer().isShiftKeyDown())) + facing = facing.getOpposite(); + return state.setValue(HORIZONTAL_FACING, facing); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState rotate(BlockState state, Rotation rot) { + return state.setValue(HORIZONTAL_FACING, rot.rotate(state.getValue(HORIZONTAL_FACING))); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState mirror(BlockState state, Mirror mirrorIn) { + return state.rotate(mirrorIn.getRotation(state.getValue(HORIZONTAL_FACING))); + } + } + + public static class DirectedWaterLoggable extends Directed implements IStandardBlock { + public DirectedWaterLoggable(long config, BlockBehaviour.Properties properties, AABB aabb) { + super(config | CFG_WATERLOGGABLE, properties, aabb); + } + + public DirectedWaterLoggable(long config, BlockBehaviour.Properties properties, AABB[] aabbs) { + super(config | CFG_WATERLOGGABLE, properties, aabbs); + } + + public DirectedWaterLoggable(long config, BlockBehaviour.Properties properties, final Function, Map> shape_supplier) { + super(config | CFG_WATERLOGGABLE, properties, shape_supplier); + } + + public DirectedWaterLoggable(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { + super(config | CFG_WATERLOGGABLE, properties, shape_supplier); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(WATERLOGGED); + } + } + + public static class AxisAlignedWaterLoggable extends AxisAligned implements IStandardBlock { + public AxisAlignedWaterLoggable(long config, BlockBehaviour.Properties properties, AABB aabb) { + super(config | CFG_WATERLOGGABLE, properties, aabb); + } + + public AxisAlignedWaterLoggable(long config, BlockBehaviour.Properties properties, AABB[] aabbs) { + super(config | CFG_WATERLOGGABLE, properties, aabbs); + } + + public AxisAlignedWaterLoggable(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { + super(config | CFG_WATERLOGGABLE, properties, shape_supplier); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(WATERLOGGED); + } + } + + public static class HorizontalWaterLoggable extends Horizontal implements IStandardBlock { + public HorizontalWaterLoggable(long config, BlockBehaviour.Properties properties, AABB aabb) { + super(config | CFG_WATERLOGGABLE | CFG_HORIZIONTAL, properties, aabb); + } + + public HorizontalWaterLoggable(long config, BlockBehaviour.Properties properties, AABB[] aabbs) { + super(config | CFG_WATERLOGGABLE | CFG_HORIZIONTAL, properties, aabbs); + } + + public HorizontalWaterLoggable(long config, BlockBehaviour.Properties properties, final Supplier> shape_supplier) { + super(config | CFG_WATERLOGGABLE | CFG_HORIZIONTAL, properties, shape_supplier); + } + + public HorizontalWaterLoggable(long config, BlockBehaviour.Properties properties, final Function, Map> shape_supplier) { + super(config | CFG_WATERLOGGABLE | CFG_HORIZIONTAL, properties, shape_supplier); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(WATERLOGGED); + } + } + + static public class HorizontalFourWayWaterLoggable extends WaterLoggable implements IStandardBlock { + public static final BooleanProperty NORTH = BlockStateProperties.NORTH; + public static final BooleanProperty EAST = BlockStateProperties.EAST; + public static final BooleanProperty SOUTH = BlockStateProperties.SOUTH; + public static final BooleanProperty WEST = BlockStateProperties.WEST; + protected final Map shapes; + protected final Map collision_shapes; + + public HorizontalFourWayWaterLoggable(long config, BlockBehaviour.Properties properties, AABB base_aabb, final AABB[] side_aabb, int railing_height_extension) { + super(config, properties, base_aabb); + Map build_shapes = new HashMap<>(); + Map build_collision_shapes = new HashMap<>(); + for (BlockState state : getStateDefinition().getPossibleStates()) { + { + VoxelShape shape = ((base_aabb.getXsize() == 0) || (base_aabb.getYsize() == 0) || (base_aabb.getZsize() == 0)) ? Shapes.empty() : Shapes.create(base_aabb); + if (state.getValue(NORTH)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(side_aabb, Direction.NORTH, true)), BooleanOp.OR); + if (state.getValue(EAST)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(side_aabb, Direction.EAST, true)), BooleanOp.OR); + if (state.getValue(SOUTH)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(side_aabb, Direction.SOUTH, true)), BooleanOp.OR); + if (state.getValue(WEST)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getRotatedAABB(side_aabb, Direction.WEST, true)), BooleanOp.OR); + if (shape.isEmpty()) shape = Shapes.block(); + build_shapes.put(state.setValue(WATERLOGGED, false), shape); + build_shapes.put(state.setValue(WATERLOGGED, true), shape); + } + { + // how the hack to extend a shape, these are the above with y+4px. + VoxelShape shape = ((base_aabb.getXsize() == 0) || (base_aabb.getYsize() == 0) || (base_aabb.getZsize() == 0)) ? Shapes.empty() : Shapes.create(base_aabb); + if (state.getValue(NORTH)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getMappedAABB(Auxiliaries.getRotatedAABB(side_aabb, + Direction.NORTH, true), bb -> bb.expandTowards(0, railing_height_extension, 0))), BooleanOp.OR); + if (state.getValue(EAST)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getMappedAABB(Auxiliaries.getRotatedAABB(side_aabb, + Direction.EAST, true), bb -> bb.expandTowards(0, railing_height_extension, 0))), BooleanOp.OR); + if (state.getValue(SOUTH)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getMappedAABB(Auxiliaries.getRotatedAABB(side_aabb, + Direction.SOUTH, true), bb -> bb.expandTowards(0, railing_height_extension, 0))), BooleanOp.OR); + if (state.getValue(WEST)) + shape = Shapes.joinUnoptimized(shape, Auxiliaries.getUnionShape(Auxiliaries.getMappedAABB(Auxiliaries.getRotatedAABB(side_aabb, + Direction.WEST, true), bb -> bb.expandTowards(0, railing_height_extension, 0))), BooleanOp.OR); + if (shape.isEmpty()) shape = Shapes.block(); + build_collision_shapes.put(state.setValue(WATERLOGGED, false), shape); + build_collision_shapes.put(state.setValue(WATERLOGGED, true), shape); + } + } + shapes = build_shapes; + collision_shapes = build_collision_shapes; + registerDefaultState(super.defaultBlockState().setValue(NORTH, false).setValue(EAST, false).setValue(SOUTH, false).setValue(WEST, false).setValue(WATERLOGGED, false)); + } + + public HorizontalFourWayWaterLoggable(long config, BlockBehaviour.Properties properties, AABB base_aabb, final AABB side_aabb, int railing_height_extension) { + this(config, properties, base_aabb, new AABB[]{side_aabb}, railing_height_extension); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(NORTH, EAST, SOUTH, WEST); + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + return super.getStateForPlacement(context).setValue(NORTH, false).setValue(EAST, false).setValue(SOUTH, false).setValue(WEST, false); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter worldIn, BlockPos pos, CollisionContext context) { + return shapes.getOrDefault(state, Shapes.block()); + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter worldIn, BlockPos pos, CollisionContext context) { + return collision_shapes.getOrDefault(state, Shapes.block()); + } + + public static BooleanProperty getDirectionProperty(Direction face) { + return switch (face) { + case EAST -> HorizontalFourWayWaterLoggable.EAST; + case SOUTH -> HorizontalFourWayWaterLoggable.SOUTH; + case WEST -> HorizontalFourWayWaterLoggable.WEST; + default -> HorizontalFourWayWaterLoggable.NORTH; + }; + } + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardDoorBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardDoorBlock.java new file mode 100644 index 0000000..8c7f10b --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardDoorBlock.java @@ -0,0 +1,175 @@ +/* + * @file StandardDoorBlock.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Door blocks, almost entirely based on vanilla. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.DoorBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.properties.BlockSetType; +import net.minecraft.world.level.block.state.properties.DoorHingeSide; +import net.minecraft.world.level.block.state.properties.DoubleBlockHalf; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.BlockHitResult; +import net.minecraft.world.phys.shapes.BooleanOp; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.List; + + +public class StandardDoorBlock extends DoorBlock implements StandardBlocks.IStandardBlock { + private final long config_; + protected final VoxelShape[][][][] shapes_; + protected final SoundEvent open_sound_; + protected final SoundEvent close_sound_; + + public StandardDoorBlock(long config, BlockBehaviour.Properties properties, AABB[] open_aabbs_top, AABB[] open_aabbs_bottom, AABB[] closed_aabbs_top, AABB[] closed_aabbs_bottom, SoundEvent open_sound, SoundEvent close_sound, BlockSetType blockSetType) { + super(properties, blockSetType); + VoxelShape[][][][] shapes = new VoxelShape[Direction.values().length][2][2][2]; + for (Direction facing : Direction.values()) { + for (boolean open : new boolean[]{false, true}) { + for (DoubleBlockHalf half : new DoubleBlockHalf[]{DoubleBlockHalf.UPPER, DoubleBlockHalf.LOWER}) { + for (boolean hinge_right : new boolean[]{false, true}) { + VoxelShape shape = Shapes.empty(); + if (facing.getAxis() == Direction.Axis.Y) { + shape = Shapes.block(); + } else { + final AABB[] aabbs = (open) ? ((half == DoubleBlockHalf.UPPER) ? open_aabbs_top : open_aabbs_bottom) : ((half == DoubleBlockHalf.UPPER) ? closed_aabbs_top : closed_aabbs_bottom); + for (AABB e : aabbs) { + AABB aabb = Auxiliaries.getRotatedAABB(e, facing, true); + if (!hinge_right) + aabb = Auxiliaries.getMirroredAABB(aabb, facing.getClockWise().getAxis()); + shape = Shapes.join(shape, Shapes.create(aabb), BooleanOp.OR); + } + } + shapes[facing.ordinal()][open ? 1 : 0][hinge_right ? 1 : 0][half == DoubleBlockHalf.UPPER ? 0 : 1] = shape; + } + } + } + } + config_ = config; + shapes_ = shapes; + open_sound_ = open_sound; + close_sound_ = close_sound; + } + + public StandardDoorBlock(long config, BlockBehaviour.Properties properties, AABB open_aabb, AABB closed_aabb, SoundEvent open_sound, SoundEvent close_sound, BlockSetType blockSetType) { + this(config, properties, new AABB[]{open_aabb}, new AABB[]{open_aabb}, new AABB[]{closed_aabb}, new AABB[]{closed_aabb}, open_sound, close_sound, blockSetType); + } + + public StandardDoorBlock(long config, BlockBehaviour.Properties properties, SoundEvent open_sound, SoundEvent close_sound, BlockSetType blockSetType) { + this( + config, properties, + Auxiliaries.getPixeledAABB(13, 0, 0, 16, 16, 16), + Auxiliaries.getPixeledAABB(0, 0, 13, 16, 16, 16), + open_sound, + close_sound, + blockSetType + ); + } + + public StandardDoorBlock(long config, BlockBehaviour.Properties properties) { + this( + config, properties, + Auxiliaries.getPixeledAABB(13, 0, 0, 16, 16, 16), + Auxiliaries.getPixeledAABB(0, 0, 13, 16, 16, 16), + SoundEvents.WOODEN_DOOR_OPEN, + SoundEvents.WOODEN_DOOR_CLOSE, + BlockSetType.OAK + ); + } + + @Override + public long config() { + return config_; + } + + protected void sound(BlockGetter world, BlockPos pos, boolean open) { + if (world instanceof Level) + ((Level) world).playSound(null, pos, open ? open_sound_ : close_sound_, SoundSource.BLOCKS, 0.7f, 1f); + } + + protected void actuate_adjacent_wing(BlockState state, BlockGetter world_ro, BlockPos pos, boolean open) { + if (!(world_ro instanceof final Level world)) return; + final BlockPos adjecent_pos = pos.relative((state.getValue(HINGE) == DoorHingeSide.LEFT) ? (state.getValue(FACING).getClockWise()) : (state.getValue(FACING).getCounterClockWise())); + if (!world.isLoaded(adjecent_pos)) return; + BlockState adjacent_state = world.getBlockState(adjecent_pos); + if (adjacent_state.getBlock() != this) return; + if (adjacent_state.getValue(OPEN) == open) return; + world.setBlock(adjecent_pos, adjacent_state.setValue(OPEN, open), 2 | 10); + } + + @Override + @OnlyIn(Dist.CLIENT) + public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { + Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); + } + + + @Override + public boolean isPossibleToRespawnInThis(BlockState p_279289_) { + return false; + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) { + return shapes_[state.getValue(FACING).ordinal()][state.getValue(OPEN) ? 1 : 0][state.getValue(HINGE) == DoorHingeSide.RIGHT ? 1 : 0][state.getValue(HALF) == DoubleBlockHalf.UPPER ? 0 : 1]; + } + + @Override + public InteractionResult use(BlockState state, Level world, BlockPos pos, Player player, InteractionHand hand, BlockHitResult hit) { + setOpen(player, world, state, pos, !state.getValue(OPEN)); + return InteractionResult.sidedSuccess(world.isClientSide()); + } + + @Override + public void neighborChanged(BlockState state, Level world, BlockPos pos, Block block, BlockPos fromPos, boolean isMoving) { + boolean powered = world.hasNeighborSignal(pos) || world.hasNeighborSignal(pos.relative(state.getValue(HALF) == DoubleBlockHalf.LOWER ? Direction.UP : Direction.DOWN)); + if ((block == this) || (powered == state.getValue(POWERED))) return; + world.setBlock(pos, state.setValue(POWERED, powered).setValue(OPEN, powered), 2); + actuate_adjacent_wing(state, world, pos, powered); + if (powered != state.getValue(OPEN)) sound(world, pos, powered); + } + + @Override + public void setOpen(@Nullable Entity entity, Level world, BlockState state, BlockPos pos, boolean open) { + if (!state.is(this) || (state.getValue(OPEN) == open)) return; + state = state.setValue(OPEN, open); + world.setBlock(pos, state, 2 | 8); + sound(world, pos, open); + actuate_adjacent_wing(state, world, pos, open); + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardEntityBlocks.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardEntityBlocks.java new file mode 100644 index 0000000..78bf385 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardEntityBlocks.java @@ -0,0 +1,72 @@ +/* + * @file StandardEntityBlocks.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Common functionality class for blocks with block entities. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.MenuProvider; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.EntityBlock; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.entity.BlockEntityTicker; +import net.minecraft.world.level.block.entity.BlockEntityType; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.gameevent.GameEventListener; +import net.minecraftforge.common.util.FakePlayer; + +import javax.annotation.Nullable; + + +public class StandardEntityBlocks { + public interface IStandardEntityBlock extends EntityBlock { + + default boolean isBlockEntityTicking(Level world, BlockState state) { + return false; + } + + default InteractionResult useOpenGui(BlockState state, Level world, BlockPos pos, Player player) { + if (world.isClientSide()) return InteractionResult.SUCCESS; + final BlockEntity te = world.getBlockEntity(pos); + if (!(te instanceof MenuProvider) || ((player instanceof FakePlayer))) return InteractionResult.FAIL; + player.openMenu((MenuProvider) te); + return InteractionResult.CONSUME; + } + + @Override + @Nullable + default BlockEntity newBlockEntity(BlockPos pos, BlockState state) { + BlockEntityType tet = Registries.getBlockEntityTypeOfBlock(state.getBlock()); + return (tet == null) ? null : tet.create(pos, state); + } + + @Override + @Nullable + default BlockEntityTicker getTicker(Level world, BlockState state, BlockEntityType te_type) { + return (world.isClientSide || (!isBlockEntityTicking(world, state))) ? (null) : ((Level w, BlockPos p, BlockState s, T te) -> ((StandardBlockEntity) te).tick()); + } + + @Override + @Nullable + default GameEventListener getListener(ServerLevel world, T te) { + return null; + } + } + + public static abstract class StandardBlockEntity extends BlockEntity { + public StandardBlockEntity(BlockEntityType type, BlockPos pos, BlockState state) { + super(type, pos, state); + } + + public void tick() { + } + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardFenceBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardFenceBlock.java new file mode 100644 index 0000000..0097d76 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardFenceBlock.java @@ -0,0 +1,203 @@ +/* + * @file StandardFenceBlock.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Wall blocks. + */ +package dev.zontreck.libzontreck.edlibmc; + +import com.google.common.collect.ImmutableMap; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.FenceGateBlock; +import net.minecraft.world.level.block.WallBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.level.block.state.properties.WallSide; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; +import net.minecraft.world.level.material.PushReaction; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.Map; + + +public class StandardFenceBlock extends WallBlock implements StandardBlocks.IStandardBlock { + public static final BooleanProperty UP = BlockStateProperties.UP; + public static final EnumProperty WALL_EAST = BlockStateProperties.EAST_WALL; + public static final EnumProperty WALL_NORTH = BlockStateProperties.NORTH_WALL; + public static final EnumProperty WALL_SOUTH = BlockStateProperties.SOUTH_WALL; + public static final EnumProperty WALL_WEST = BlockStateProperties.WEST_WALL; + public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; + private final Map shape_voxels; + private final Map collision_shape_voxels; + private final long config; + + public StandardFenceBlock(long config, BlockBehaviour.Properties properties) { + this(config, properties, 1.5, 16, 1.5, 0, 14, 16); + } + + public StandardFenceBlock(long config, BlockBehaviour.Properties properties, double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y) { + super(properties); + shape_voxels = buildShapes(pole_width, pole_height, side_width, side_min_y, side_max_low_y, side_max_tall_y); + collision_shape_voxels = buildShapes(pole_width, 24, pole_width, 0, 24, 24); + this.config = config; + } + + @Override + public long config() { + return config; + } + + @Override + @OnlyIn(Dist.CLIENT) + public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { + Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); + } + + private static VoxelShape combinedShape(VoxelShape pole, WallSide height, VoxelShape low, VoxelShape high) { + if (height == WallSide.TALL) return Shapes.or(pole, high); + if (height == WallSide.LOW) return Shapes.or(pole, low); + return pole; + } + + protected Map buildShapes(double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y) { + final double px0 = 8.0 - pole_width, px1 = 8.0 + pole_width, sx0 = 8.0 - side_width, sx1 = 8.0 + side_width; + VoxelShape vp = Block.box(px0, 0, px0, px1, pole_height, px1); + VoxelShape vs1 = Block.box(sx0, side_min_y, 0, sx1, side_max_low_y, sx1); + VoxelShape vs2 = Block.box(sx0, side_min_y, sx0, sx1, side_max_low_y, 16); + VoxelShape vs3 = Block.box(0, side_min_y, sx0, sx1, side_max_low_y, sx1); + VoxelShape vs4 = Block.box(sx0, side_min_y, sx0, 16, side_max_low_y, sx1); + VoxelShape vs5 = Block.box(sx0, side_min_y, 0, sx1, side_max_tall_y, sx1); + VoxelShape vs6 = Block.box(sx0, side_min_y, sx0, sx1, side_max_tall_y, 16); + VoxelShape vs7 = Block.box(0, side_min_y, sx0, sx1, side_max_tall_y, sx1); + VoxelShape vs8 = Block.box(sx0, side_min_y, sx0, 16, side_max_tall_y, sx1); + ImmutableMap.Builder builder = ImmutableMap.builder(); + for (Boolean up : UP.getPossibleValues()) { + for (WallSide wh_east : WALL_EAST.getPossibleValues()) { + for (WallSide wh_north : WALL_NORTH.getPossibleValues()) { + for (WallSide wh_west : WALL_WEST.getPossibleValues()) { + for (WallSide wh_south : WALL_SOUTH.getPossibleValues()) { + VoxelShape shape = Shapes.empty(); + shape = combinedShape(shape, wh_east, vs4, vs8); + shape = combinedShape(shape, wh_west, vs3, vs7); + shape = combinedShape(shape, wh_north, vs1, vs5); + shape = combinedShape(shape, wh_south, vs2, vs6); + if (up) shape = Shapes.or(shape, vp); + BlockState bs = defaultBlockState().setValue(UP, up) + .setValue(WALL_EAST, wh_east) + .setValue(WALL_NORTH, wh_north) + .setValue(WALL_WEST, wh_west) + .setValue(WALL_SOUTH, wh_south); + builder.put(bs.setValue(WATERLOGGED, false), shape); + builder.put(bs.setValue(WATERLOGGED, true), shape); + } + } + } + } + } + return builder.build(); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return shape_voxels.getOrDefault(state, Shapes.block()); + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return collision_shape_voxels.getOrDefault(state, Shapes.block()); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + } + + protected boolean attachesTo(BlockState facingState, LevelReader world, BlockPos facingPos, Direction side) { + final Block block = facingState.getBlock(); + if ((block instanceof FenceGateBlock) || (block instanceof StandardFenceBlock) || (block instanceof VariantWallBlock)) + return true; + final BlockState oppositeState = world.getBlockState(facingPos.relative(side, 2)); + if (!(oppositeState.getBlock() instanceof StandardFenceBlock)) return false; + return facingState.isRedstoneConductor(world, facingPos) && canSupportCenter(world, facingPos, side); + } + + protected WallSide selectWallHeight(LevelReader world, BlockPos pos, Direction direction) { + return WallSide.LOW; // @todo: implement + } + + public BlockState getStateForPlacement(BlockPlaceContext context) { + LevelReader world = context.getLevel(); + BlockPos pos = context.getClickedPos(); + FluidState fs = context.getLevel().getFluidState(context.getClickedPos()); + boolean n = attachesTo(world.getBlockState(pos.north()), world, pos.north(), Direction.SOUTH); + boolean e = attachesTo(world.getBlockState(pos.east()), world, pos.east(), Direction.WEST); + boolean s = attachesTo(world.getBlockState(pos.south()), world, pos.south(), Direction.NORTH); + boolean w = attachesTo(world.getBlockState(pos.west()), world, pos.west(), Direction.EAST); + boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w); + return defaultBlockState() + .setValue(UP, not_straight) + .setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE) + .setValue(WALL_EAST, e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE) + .setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE) + .setValue(WALL_WEST, w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE) + .setValue(WATERLOGGED, fs.getType() == Fluids.WATER); + } + + @Override + public BlockState updateShape(BlockState state, Direction side, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos) { + if (state.getValue(BlockStateProperties.WATERLOGGED)) + world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world)); + if (side == Direction.DOWN) return super.updateShape(state, side, facingState, world, pos, facingPos); + boolean n = (side == Direction.NORTH) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_NORTH) != WallSide.NONE); + boolean e = (side == Direction.EAST) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_EAST) != WallSide.NONE); + boolean s = (side == Direction.SOUTH) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_SOUTH) != WallSide.NONE); + boolean w = (side == Direction.WEST) ? attachesTo(facingState, world, facingPos, side) : (state.getValue(WALL_WEST) != WallSide.NONE); + boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w); + return state.setValue(UP, not_straight) + .setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE) + .setValue(WALL_EAST, e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE) + .setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE) + .setValue(WALL_WEST, w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE); + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + + @Override + public boolean isPossibleToRespawnInThis(BlockState p_279289_) { + return false; + } + + @Override + @SuppressWarnings("deprecation") + public PushReaction getPistonPushReaction(BlockState state) { + return PushReaction.NORMAL; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardStairsBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardStairsBlock.java new file mode 100644 index 0000000..64a9f05 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/StandardStairsBlock.java @@ -0,0 +1,59 @@ +/* + * @file StandardStairsBlock.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Stairs and roof blocks, almost entirely based on vanilla stairs. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.network.chat.Component; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.StairBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.material.PushReaction; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.List; + + +public class StandardStairsBlock extends StairBlock implements StandardBlocks.IStandardBlock { + private final long config; + + public StandardStairsBlock(long config, java.util.function.Supplier state, BlockBehaviour.Properties properties) { + super(state, properties); + this.config = config; + } + + @Override + @OnlyIn(Dist.CLIENT) + public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { + Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); + } + + + @Override + public boolean isPossibleToRespawnInThis(BlockState p_279289_) { + return false; + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + @Override + @SuppressWarnings("deprecation") + public PushReaction getPistonPushReaction(BlockState state) { + return PushReaction.NORMAL; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/TooltipDisplay.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/TooltipDisplay.java new file mode 100644 index 0000000..76eb760 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/TooltipDisplay.java @@ -0,0 +1,130 @@ +/* + * @file Tooltip.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Delayed tooltip for a selected area. Constructed with a + * GUI, invoked in `render()`. + */ +package dev.zontreck.libzontreck.edlibmc; + +import com.mojang.blaze3d.vertex.PoseStack; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphics; +import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen; +import net.minecraft.network.chat.*; +import net.minecraft.util.Mth; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Supplier; + + +@OnlyIn(Dist.CLIENT) +public class TooltipDisplay { + private static long default_delay = 800; + private static int default_max_deviation = 1; + + public static void config(long delay, int max_deviation) { + default_delay = clamp(delay, 500, 5000); + default_max_deviation = Mth.clamp(max_deviation, 1, 5); + } + private static long clamp(long p1, long a, long b) { + return p1 < a ? a : Math.min(p1, b); + } + + // --------------------------------------------------------------------------------------------------- + + public static class TipRange { + public final int x0, y0, x1, y1; + public final Supplier text; + + public TipRange(int x, int y, int w, int h, Component text) { + this(x, y, w, h, () -> text); + } + + public TipRange(int x, int y, int w, int h, Supplier text) { + this.text = text; + this.x0 = x; + this.y0 = y; + this.x1 = x0 + w - 1; + this.y1 = y0 + h - 1; + } + + } + + // --------------------------------------------------------------------------------------------------- + + private List ranges = new ArrayList<>(); + private long delay = default_delay; + private int max_deviation = default_max_deviation; + private int x_last, y_last; + private long t; + private static boolean had_render_exception = false; + + public TooltipDisplay() { + t = System.currentTimeMillis(); + } + + public TooltipDisplay init(List ranges, long delay_ms, int max_deviation_xy) { + this.ranges = ranges; + this.delay = delay_ms; + this.max_deviation = max_deviation_xy; + t = System.currentTimeMillis(); + x_last = y_last = 0; + return this; + } + + public TooltipDisplay init(List ranges) { + return init(ranges, default_delay, default_max_deviation); + } + + public TooltipDisplay init(TipRange... ranges) { + return init(Arrays.asList(ranges), default_delay, default_max_deviation); + } + + public TooltipDisplay delay(int ms) { + delay = (ms <= 0) ? default_delay : ms; + return this; + } + + public void resetTimer() { + t = System.currentTimeMillis(); + } + + public boolean render(GuiGraphics mx, final AbstractContainerScreen gui, int x, int y) { + if (had_render_exception) return false; + if ((Math.abs(x - x_last) > max_deviation) || (Math.abs(y - y_last) > max_deviation)) { + x_last = x; + y_last = y; + resetTimer(); + return false; + } else if (Math.abs(System.currentTimeMillis() - t) < delay) { + return false; + } else if (ranges.stream().noneMatch( + (tip) -> { + if ((x < tip.x0) || (x > tip.x1) || (y < tip.y0) || (y > tip.y1)) return false; + String text = tip.text.get().toString(); + if (text.isEmpty()) return false; + try { + mx.renderComponentTooltip(Minecraft.getInstance().font, tip.text.get().toFlatList(Style.EMPTY), x, y); + } catch (Exception ex) { + had_render_exception = true; + Auxiliaries.logError("Tooltip rendering disabled due to exception: '" + ex.getMessage() + "'"); + return false; + } + return true; + }) + ) { + resetTimer(); + return false; + } else { + return true; + } + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantSlabBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantSlabBlock.java new file mode 100644 index 0000000..4f2eeb7 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantSlabBlock.java @@ -0,0 +1,220 @@ +/* + * @file VariantSlabBlock.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Standard half block horizontal slab characteristics class. + */ +package dev.zontreck.libzontreck.edlibmc; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.sounds.SoundSource; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; +import net.minecraft.world.level.block.SoundType; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; +import net.minecraft.world.level.block.state.properties.EnumProperty; +import net.minecraft.world.level.block.state.properties.IntegerProperty; +import net.minecraft.world.level.block.state.properties.SlabType; +import net.minecraft.world.level.material.Fluid; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.phys.AABB; +import net.minecraft.world.phys.Vec3; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + + +public class VariantSlabBlock extends StandardBlocks.WaterLoggable implements StandardBlocks.IStandardBlock { + public static final EnumProperty TYPE = BlockStateProperties.SLAB_TYPE; + public static final IntegerProperty TEXTURE_VARIANT = IntegerProperty.create("tvariant", 0, 3); + + protected static final VoxelShape[] AABBs = { + Shapes.create(new AABB(0, 8. / 16, 0, 1, 16. / 16, 1)), // top slab + Shapes.create(new AABB(0, 0. / 16, 0, 1, 8. / 16, 1)), // bottom slab + Shapes.create(new AABB(0, 0. / 16, 0, 1, 16. / 16, 1)), // both slabs + Shapes.create(new AABB(0, 0. / 16, 0, 1, 16. / 16, 1)) // << 2bit fill + }; + protected static final int[] num_slabs_contained_in_parts_ = {1, 1, 2, 2}; + private static boolean with_pickup = false; + + public static void on_config(boolean direct_slab_pickup) { + with_pickup = direct_slab_pickup; + } + + protected boolean is_cube(BlockState state) { + return state.getValue(TYPE) == SlabType.DOUBLE; + } + + public VariantSlabBlock(long config, BlockBehaviour.Properties builder) { + super(config, builder); + registerDefaultState(defaultBlockState().setValue(TYPE, SlabType.BOTTOM)); + } + + @Override + public RenderTypeHint getRenderTypeHint() { + return (((config & StandardBlocks.CFG_TRANSLUCENT) != 0) ? (RenderTypeHint.TRANSLUCENT) : (RenderTypeHint.CUTOUT)); + } + + @Override + @OnlyIn(Dist.CLIENT) + public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { + if (!Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true)) return; + if (with_pickup && Auxiliaries.Tooltip.helpCondition()) + Auxiliaries.Tooltip.addInformation("engineersdecor.tooltip.slabpickup", "engineersdecor.tooltip.slabpickup", tooltip, flag, true); + } + + @Override + @OnlyIn(Dist.CLIENT) + @SuppressWarnings("deprecation") + public boolean skipRendering(BlockState state, BlockState adjacentBlockState, Direction side) { + return (adjacentBlockState == state) || (super.skipRendering(state, adjacentBlockState, side)); + } + + @Override + public boolean isPossibleToRespawnInThis(BlockState state) { + return false; + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter source, BlockPos pos, CollisionContext selectionContext) { + return AABBs[state.getValue(TYPE).ordinal() & 0x3]; + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return getShape(state, world, pos, selectionContext); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(TYPE, TEXTURE_VARIANT); + } + + @Override + @Nullable + public BlockState getStateForPlacement(BlockPlaceContext context) { + BlockPos pos = context.getClickedPos(); + if (context.getLevel().getBlockState(pos).getBlock() == this) + return context.getLevel().getBlockState(pos).setValue(TYPE, SlabType.DOUBLE).setValue(WATERLOGGED, false); + final int rnd = Mth.clamp((int) (Mth.getSeed(context.getClickedPos()) & 0x3), 0, 3); + final Direction face = context.getClickedFace(); + final BlockState placement_state = super.getStateForPlacement(context).setValue(TEXTURE_VARIANT, rnd); // fluid state + if (face == Direction.UP) return placement_state.setValue(TYPE, SlabType.BOTTOM); + if (face == Direction.DOWN) return placement_state.setValue(TYPE, SlabType.TOP); + if (!face.getAxis().isHorizontal()) return placement_state; + final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5); + return placement_state.setValue(TYPE, isupper ? SlabType.TOP : SlabType.BOTTOM); + } + + @Override + @SuppressWarnings("deprecation") + public boolean canBeReplaced(BlockState state, BlockPlaceContext context) { + if (context.getItemInHand().getItem() != this.asItem()) return false; + if (!context.replacingClickedOnBlock()) return true; + final Direction face = context.getClickedFace(); + final SlabType type = state.getValue(TYPE); + if ((face == Direction.UP) && (type == SlabType.BOTTOM)) return true; + if ((face == Direction.DOWN) && (type == SlabType.TOP)) return true; + if (!face.getAxis().isHorizontal()) return false; + final boolean isupper = ((context.getClickLocation().y() - context.getClickedPos().getY()) > 0.5); + return isupper ? (type == SlabType.BOTTOM) : (type == SlabType.TOP); + } + + @Override + @SuppressWarnings("deprecation") + public BlockState rotate(BlockState state, Rotation rot) { + return state; + } + + @Override + @SuppressWarnings("deprecation") + public BlockState mirror(BlockState state, Mirror mirrorIn) { + return state; + } + + @Override + public boolean hasDynamicDropList() { + return true; + } + + @Override + public List dropList(BlockState state, Level world, BlockEntity te, boolean explosion) { + return new ArrayList<>(Collections.singletonList(new ItemStack(this.asItem(), num_slabs_contained_in_parts_[state.getValue(TYPE).ordinal() & 0x3]))); + } + + @Override + @SuppressWarnings("deprecation") + public void attack(BlockState state, Level world, BlockPos pos, Player player) { + if ((world.isClientSide) || (!with_pickup)) return; + final ItemStack stack = player.getMainHandItem(); + if (stack.isEmpty() || (Block.byItem(stack.getItem()) != this)) return; + if (stack.getCount() >= stack.getMaxStackSize()) return; + Vec3 lv = player.getLookAngle(); + Direction facing = Direction.getNearest((float) lv.x, (float) lv.y, (float) lv.z); + if ((facing != Direction.UP) && (facing != Direction.DOWN)) return; + if (state.getBlock() != this) return; + SlabType type = state.getValue(TYPE); + if (facing == Direction.DOWN) { + if (type == SlabType.DOUBLE) { + world.setBlock(pos, state.setValue(TYPE, SlabType.BOTTOM), 3); + } else { + world.removeBlock(pos, false); + } + } else if (facing == Direction.UP) { + if (type == SlabType.DOUBLE) { + world.setBlock(pos, state.setValue(TYPE, SlabType.TOP), 3); + } else { + world.removeBlock(pos, false); + } + } + if (!player.isCreative()) { + stack.grow(1); + if (player.getInventory() != null) player.getInventory().setChanged(); + } + SoundType st = this.getSoundType(state, world, pos, null); + world.playSound(player, pos, st.getPlaceSound(), SoundSource.BLOCKS, (st.getVolume() + 1f) / 2.5f, 0.9f * st.getPitch()); + } + + @Override + public boolean placeLiquid(LevelAccessor world, BlockPos pos, BlockState state, FluidState fluidState) { + return (state.getValue(TYPE) != SlabType.DOUBLE) && super.placeLiquid(world, pos, state, fluidState); + } + + @Override + public boolean canPlaceLiquid(BlockGetter world, BlockPos pos, BlockState state, Fluid fluid) { + return (state.getValue(TYPE) != SlabType.DOUBLE) && super.canPlaceLiquid(world, pos, state, fluid); + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantWallBlock.java b/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantWallBlock.java new file mode 100644 index 0000000..92af666 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/edlibmc/VariantWallBlock.java @@ -0,0 +1,200 @@ +/* + * @file VariantWallBlock.java + * @author Stefan Wilhelm (wile) + * @copyright (C) 2020 Stefan Wilhelm + * @license MIT (see https://opensource.org/licenses/MIT) + * + * Wall blocks. + */ +package dev.zontreck.libzontreck.edlibmc; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.network.chat.Component; +import net.minecraft.util.Mth; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.SpawnPlacements; +import net.minecraft.world.item.ItemStack; +import net.minecraft.world.item.TooltipFlag; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.LevelAccessor; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.FenceGateBlock; +import net.minecraft.world.level.block.WallBlock; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.*; +import net.minecraft.world.level.material.FluidState; +import net.minecraft.world.level.material.Fluids; +import net.minecraft.world.level.material.PushReaction; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; +import net.minecraftforge.api.distmarker.Dist; +import net.minecraftforge.api.distmarker.OnlyIn; + +import javax.annotation.Nullable; +import java.util.List; +import java.util.Map; + + +public class VariantWallBlock extends WallBlock implements StandardBlocks.IStandardBlock { + public static final BooleanProperty UP = BlockStateProperties.UP; + public static final EnumProperty WALL_EAST = BlockStateProperties.EAST_WALL; + public static final EnumProperty WALL_NORTH = BlockStateProperties.NORTH_WALL; + public static final EnumProperty WALL_SOUTH = BlockStateProperties.SOUTH_WALL; + public static final EnumProperty WALL_WEST = BlockStateProperties.WEST_WALL; + public static final BooleanProperty WATERLOGGED = BlockStateProperties.WATERLOGGED; + public static final IntegerProperty TEXTURE_VARIANT = IntegerProperty.create("tvariant", 0, 7); + private final Map shape_voxels; + private final Map collision_shape_voxels; + private final long config; + + public VariantWallBlock(long config, BlockBehaviour.Properties builder) { + super(builder); + shape_voxels = buildWallShapes(4, 16, 4, 0, 16, 16); + collision_shape_voxels = buildWallShapes(6, 16, 5, 0, 24, 24); + this.config = config; + } + + @Override + public long config() { + return config; + } + + @Override + @OnlyIn(Dist.CLIENT) + public void appendHoverText(ItemStack stack, @Nullable BlockGetter world, List tooltip, TooltipFlag flag) { + Auxiliaries.Tooltip.addInformation(stack, world, tooltip, flag, true); + } + + private static VoxelShape combinedShape(VoxelShape pole, WallSide height, VoxelShape low, VoxelShape high) { + if (height == WallSide.TALL) return Shapes.or(pole, high); + if (height == WallSide.LOW) return Shapes.or(pole, low); + return pole; + } + + protected Map buildWallShapes(double pole_width, double pole_height, double side_width, double side_min_y, double side_max_low_y, double side_max_tall_y) { + final double px0 = 8.0 - pole_width, px1 = 8.0 + pole_width, sx0 = 8.0 - side_width, sx1 = 8.0 + side_width; + VoxelShape vp = Block.box(px0, 0, px0, px1, pole_height, px1); + VoxelShape vs1 = Block.box(sx0, side_min_y, 0, sx1, side_max_low_y, sx1); + VoxelShape vs2 = Block.box(sx0, side_min_y, sx0, sx1, side_max_low_y, 16); + VoxelShape vs3 = Block.box(0, side_min_y, sx0, sx1, side_max_low_y, sx1); + VoxelShape vs4 = Block.box(sx0, side_min_y, sx0, 16, side_max_low_y, sx1); + VoxelShape vs5 = Block.box(sx0, side_min_y, 0, sx1, side_max_tall_y, sx1); + VoxelShape vs6 = Block.box(sx0, side_min_y, sx0, sx1, side_max_tall_y, 16); + VoxelShape vs7 = Block.box(0, side_min_y, sx0, sx1, side_max_tall_y, sx1); + VoxelShape vs8 = Block.box(sx0, side_min_y, sx0, 16, side_max_tall_y, sx1); + Builder builder = ImmutableMap.builder(); + for (Boolean up : UP.getPossibleValues()) { + for (WallSide wh_east : WALL_EAST.getPossibleValues()) { + for (WallSide wh_north : WALL_NORTH.getPossibleValues()) { + for (WallSide wh_west : WALL_WEST.getPossibleValues()) { + for (WallSide wh_south : WALL_SOUTH.getPossibleValues()) { + VoxelShape shape = Shapes.empty(); + shape = combinedShape(shape, wh_east, vs4, vs8); + shape = combinedShape(shape, wh_west, vs3, vs7); + shape = combinedShape(shape, wh_north, vs1, vs5); + shape = combinedShape(shape, wh_south, vs2, vs6); + if (up) shape = Shapes.or(shape, vp); + BlockState bs = defaultBlockState().setValue(UP, up) + .setValue(WALL_EAST, wh_east) + .setValue(WALL_NORTH, wh_north) + .setValue(WALL_WEST, wh_west) + .setValue(WALL_SOUTH, wh_south); + final VoxelShape tvs = shape; + TEXTURE_VARIANT.getPossibleValues().forEach((tv) -> { + builder.put(bs.setValue(TEXTURE_VARIANT, tv).setValue(WATERLOGGED, false), tvs); + builder.put(bs.setValue(TEXTURE_VARIANT, tv).setValue(WATERLOGGED, true), tvs); + }); + } + } + } + } + } + return builder.build(); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return shape_voxels.getOrDefault(state, Shapes.block()); + } + + @Override + public VoxelShape getCollisionShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext selectionContext) { + return collision_shape_voxels.getOrDefault(state, Shapes.block()); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder builder) { + super.createBlockStateDefinition(builder); + builder.add(TEXTURE_VARIANT); + } + + protected boolean attachesTo(BlockState facingState, LevelReader world, BlockPos facingPos, Direction side) { + final Block block = facingState.getBlock(); + if ((block instanceof FenceGateBlock) || (block instanceof WallBlock)) return true; + final BlockState oppositeState = world.getBlockState(facingPos.relative(side, 2)); + if (!(oppositeState.getBlock() instanceof VariantWallBlock)) return false; + return facingState.isRedstoneConductor(world, facingPos) && Block.canSupportCenter(world, facingPos, side); + } + + protected WallSide selectWallHeight(LevelReader world, BlockPos pos, Direction direction) { + return WallSide.LOW; + } + + public BlockState getStateForPlacement(BlockPlaceContext context) { + LevelReader world = context.getLevel(); + BlockPos pos = context.getClickedPos(); + FluidState fs = context.getLevel().getFluidState(context.getClickedPos()); + boolean n = attachesTo(world.getBlockState(pos.north()), world, pos.north(), Direction.SOUTH); + boolean e = attachesTo(world.getBlockState(pos.east()), world, pos.east(), Direction.WEST); + boolean s = attachesTo(world.getBlockState(pos.south()), world, pos.south(), Direction.NORTH); + boolean w = attachesTo(world.getBlockState(pos.west()), world, pos.west(), Direction.EAST); + boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w); + return defaultBlockState().setValue(UP, not_straight) + .setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE) + .setValue(WALL_EAST, e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE) + .setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE) + .setValue(WALL_WEST, w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE) + .setValue(WATERLOGGED, fs.getType() == Fluids.WATER); + } + + @Override + public BlockState updateShape(BlockState state, Direction side, BlockState facingState, LevelAccessor world, BlockPos pos, BlockPos facingPos) { + if (state.getValue(WATERLOGGED)) world.scheduleTick(pos, Fluids.WATER, Fluids.WATER.getTickDelay(world)); + if (side == Direction.DOWN) return super.updateShape(state, side, facingState, world, pos, facingPos); + boolean n = (side == Direction.NORTH) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_NORTH) != WallSide.NONE; + boolean e = (side == Direction.EAST) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_EAST) != WallSide.NONE; + boolean s = (side == Direction.SOUTH) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_SOUTH) != WallSide.NONE; + boolean w = (side == Direction.WEST) ? this.attachesTo(facingState, world, facingPos, side) : state.getValue(WALL_WEST) != WallSide.NONE; + boolean not_straight = (!n || !s || e || w) && (n || s || !e || !w); + return state.setValue(UP, not_straight) + .setValue(WALL_NORTH, n ? selectWallHeight(world, pos, Direction.NORTH) : WallSide.NONE) + .setValue(WALL_EAST, e ? selectWallHeight(world, pos, Direction.EAST) : WallSide.NONE) + .setValue(WALL_SOUTH, s ? selectWallHeight(world, pos, Direction.SOUTH) : WallSide.NONE) + .setValue(WALL_WEST, w ? selectWallHeight(world, pos, Direction.WEST) : WallSide.NONE) + .setValue(TEXTURE_VARIANT, ((int) Mth.getSeed(pos)) & 0x7); + } + + @Override + public boolean isValidSpawn(BlockState state, BlockGetter world, BlockPos pos, SpawnPlacements.Type type, @Nullable EntityType entityType) { + return false; + } + + @Override + public boolean isPossibleToRespawnInThis(BlockState state) { + return false; + } + + @Override + @SuppressWarnings("deprecation") + public PushReaction getPistonPushReaction(BlockState state) { + return PushReaction.NORMAL; + } +} From c82c15f48b47ea4ad29d46ac3fa406fca423d679 Mon Sep 17 00:00:00 2001 From: zontreck Date: Mon, 1 Apr 2024 02:28:10 -0700 Subject: [PATCH 22/88] Remove poss from credits. Add Ember --- .../java/dev/zontreck/libzontreck/util/heads/HeadCache.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/dev/zontreck/libzontreck/util/heads/HeadCache.java b/src/main/java/dev/zontreck/libzontreck/util/heads/HeadCache.java index 597922a..84d9070 100644 --- a/src/main/java/dev/zontreck/libzontreck/util/heads/HeadCache.java +++ b/src/main/java/dev/zontreck/libzontreck/util/heads/HeadCache.java @@ -109,9 +109,8 @@ public class HeadCache creds.add( new CreditsEntry(HeadUtilities.cachedLookup("zontreck"), "Aria (zontreck)", "Developer, Designer, Artist", "Aria is the primary developer and project maintainer")); - creds.add( - new CreditsEntry(HeadUtilities.cachedLookup("PossumTheWarrior"), "PossumTheWarrior", "Tester, Artist", "Poss has helped to test the mods from very early on. Poss has also contributed the artwork and mob model for the Possum")); creds.add(new CreditsEntry(HeadUtilities.cachedLookup("firesyde424"), "firesyde424", "Tester", "Firesyde has helped to test my mods and given feedback.")); + creds.add(new CreditsEntry(HeadUtilities.cachedLookup("EmberCat42"), "EmberCat42", "Tester", "EmberCat42 has helped to test and reported on a major bug in Night Vision")); CREDITS = creds; From a4e3ef44ed4cad77b092fb5dd658e5c454b58f77 Mon Sep 17 00:00:00 2001 From: zontreck Date: Mon, 1 Apr 2024 02:28:29 -0700 Subject: [PATCH 23/88] Add two variations of a read only item stack handler for input and output --- .../items/InputItemStackHandler.java | 47 +++++++++++++++++++ .../items/OutputItemStackHandler.java | 45 ++++++++++++++++++ 2 files changed, 92 insertions(+) create mode 100644 src/main/java/dev/zontreck/libzontreck/items/InputItemStackHandler.java create mode 100644 src/main/java/dev/zontreck/libzontreck/items/OutputItemStackHandler.java diff --git a/src/main/java/dev/zontreck/libzontreck/items/InputItemStackHandler.java b/src/main/java/dev/zontreck/libzontreck/items/InputItemStackHandler.java new file mode 100644 index 0000000..8a38e42 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/items/InputItemStackHandler.java @@ -0,0 +1,47 @@ +package dev.zontreck.libzontreck.items; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.items.ItemStackHandler; + +public class InputItemStackHandler extends ItemStackHandler { + private final ItemStackHandler internalSlot; + + public InputItemStackHandler(ItemStackHandler hidden) { + super(); + internalSlot = hidden; + } + + @Override + public void setSize(int size) { + stacks = NonNullList.withSize(size, ItemStack.EMPTY); + } + + @Override + public void setStackInSlot(int slot, ItemStack stack) { + internalSlot.setStackInSlot(slot, stack); + } + + @Override + public int getSlots() { + return internalSlot.getSlots(); + } + + @Override + public ItemStack getStackInSlot(int slot) { + return internalSlot.getStackInSlot(slot); + } + + @Override + public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) { + setStackInSlot(slot, stack); + + return ItemStack.EMPTY; + } + + @Override + public ItemStack extractItem(int slot, int amount, boolean simulate) { + return ItemStack.EMPTY; + } +} + diff --git a/src/main/java/dev/zontreck/libzontreck/items/OutputItemStackHandler.java b/src/main/java/dev/zontreck/libzontreck/items/OutputItemStackHandler.java new file mode 100644 index 0000000..38edacf --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/items/OutputItemStackHandler.java @@ -0,0 +1,45 @@ +package dev.zontreck.libzontreck.items; + +import net.minecraft.core.NonNullList; +import net.minecraft.world.item.ItemStack; +import net.minecraftforge.items.ItemStackHandler; + +public class OutputItemStackHandler extends ItemStackHandler { + private final ItemStackHandler internalSlot; + + public OutputItemStackHandler(ItemStackHandler hidden) { + super(); + internalSlot = hidden; + } + + @Override + public void setSize(int size) { + stacks = NonNullList.withSize(size, ItemStack.EMPTY); + } + + @Override + public void setStackInSlot(int slot, ItemStack stack) { + internalSlot.setStackInSlot(slot, stack); + } + + @Override + public int getSlots() { + return internalSlot.getSlots(); + } + + @Override + public ItemStack getStackInSlot(int slot) { + return internalSlot.getStackInSlot(slot); + } + + @Override + public ItemStack insertItem(int slot, ItemStack stack, boolean simulate) { + return stack; + } + + @Override + public ItemStack extractItem(int slot, int amount, boolean simulate) { + return internalSlot.extractItem(slot, amount, simulate); + } +} + From f0fa9fbcbde3c1a78d1d7c0598ee242b1c7481d6 Mon Sep 17 00:00:00 2001 From: zontreck Date: Mon, 1 Apr 2024 02:29:13 -0700 Subject: [PATCH 24/88] Bump version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index b5ca230..9bfc623 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.11.022724.1602 +mod_version=1201.11.040124.0228 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html From 0f01475ec382f060d0088ffe480237524015fd7a Mon Sep 17 00:00:00 2001 From: zontreck Date: Mon, 1 Apr 2024 02:32:16 -0700 Subject: [PATCH 25/88] Update bundled LibAC --- gradle.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 9bfc623..3ab324e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,7 +7,7 @@ org.gradle.daemon=false # parchment_version=2023.09.03 # luckperms_api_version=5.4 -libac=1.5.9 +libac=1.5.11 ## Environment Properties # The Minecraft version must agree with the Forge version to get a valid artifact @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.11.040124.0228 +mod_version=1201.11.040124.0231 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html From 81286767f4914be38d8f0157b7d4a4882388049b Mon Sep 17 00:00:00 2001 From: zontreck Date: Sun, 7 Apr 2024 15:53:04 -0700 Subject: [PATCH 26/88] Migrate vectors to a dedicated API interface --- .../dev/zontreck/libzontreck/LibZontreck.java | 2 - .../dev/zontreck/libzontreck/api/Vector2.java | 126 ++++++++ .../dev/zontreck/libzontreck/api/Vector3.java | 143 +++++++++ .../libzontreck/chestgui/ChestGUI.java | 8 +- .../libzontreck/chestgui/ChestGUIButton.java | 2 - .../zontreck/libzontreck/util/BlocksUtil.java | 6 +- .../libzontreck/util/PositionUtil.java | 115 +++++++ .../libzontreck/util/ServerUtilities.java | 6 +- .../libzontreck/vectors/ChunkPos.java | 10 +- .../libzontreck/vectors/NonAbsVector3.java | 2 +- .../zontreck/libzontreck/vectors/Points.java | 10 +- .../zontreck/libzontreck/vectors/Vector2.java | 120 -------- .../libzontreck/vectors/Vector2d.java | 176 +++++++++++ .../libzontreck/vectors/Vector2i.java | 124 ++++++-- .../zontreck/libzontreck/vectors/Vector3.java | 285 ------------------ .../libzontreck/vectors/Vector3d.java | 222 ++++++++++++++ .../libzontreck/vectors/Vector3i.java | 215 +++++++++++++ .../libzontreck/vectors/WorldPosition.java | 21 +- 18 files changed, 1124 insertions(+), 469 deletions(-) create mode 100644 src/main/java/dev/zontreck/libzontreck/api/Vector2.java create mode 100644 src/main/java/dev/zontreck/libzontreck/api/Vector3.java create mode 100644 src/main/java/dev/zontreck/libzontreck/util/PositionUtil.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/vectors/Vector2.java create mode 100644 src/main/java/dev/zontreck/libzontreck/vectors/Vector2d.java delete mode 100644 src/main/java/dev/zontreck/libzontreck/vectors/Vector3.java create mode 100644 src/main/java/dev/zontreck/libzontreck/vectors/Vector3d.java create mode 100644 src/main/java/dev/zontreck/libzontreck/vectors/Vector3i.java diff --git a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java index 0a5190e..11a53a7 100644 --- a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java +++ b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java @@ -44,7 +44,6 @@ public class LibZontreck { public static final Logger LOGGER = LogUtils.getLogger(); public static final String MOD_ID = "libzontreck"; public static final Map PROFILES; - public static MinecraftServer THE_SERVER; public static VolatilePlayerStorage playerStorage; public static boolean ALIVE=true; public static final String FILESTORE = FileTreeDatastore.get(); @@ -107,7 +106,6 @@ public class LibZontreck { @SubscribeEvent public void onServerStarted(final ServerStartedEvent event) { - THE_SERVER = event.getServer(); ALIVE=true; CURRENT_SIDE = LogicalSide.SERVER; } diff --git a/src/main/java/dev/zontreck/libzontreck/api/Vector2.java b/src/main/java/dev/zontreck/libzontreck/api/Vector2.java new file mode 100644 index 0000000..670f786 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/api/Vector2.java @@ -0,0 +1,126 @@ +package dev.zontreck.libzontreck.api; + +import dev.zontreck.libzontreck.vectors.Vector2d; +import dev.zontreck.libzontreck.vectors.Vector2i; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.phys.Vec2; + +public interface Vector2 +{ + /** + * Converts the current Vector2 representation into a minecraft Vec2 + * @return Minecraft equivalent Vec2 + */ + Vec2 asMinecraftVector(); + + /** + * Parses a string in serialized format. + * @param vector2 Expects it in the same format returned by Vector2#toString + * @return New Vector2, or a null Vector2 initialized with zeros if invalid data + */ + static Vector2 parseString(String vector2){ + throw new UnsupportedOperationException("This method is not implemented by this implementation"); + } + + /** + * Copies the values to a new and detached instance + * @return New Vector2 + */ + Vector2 Clone(); + + /** + * Saves the X and Y positions to a NBT tag + * @return NBT compound tag + */ + CompoundTag serialize(); + + /** + * Loads a Vector2 from a NBT tag + * @param tag The NBT tag to load + */ + static Vector2 deserialize(CompoundTag tag) + { + throw new UnsupportedOperationException("This method is not implemented by this implementation"); + } + + /** + * Compares the two vector2 instances + * @param other The position to check + * @return True if same position + */ + boolean Same(Vector2 other); + + /** + * True if the current position is inside the two points + * @param point1 Lowest point + * @param point2 Hightest Point + * @return True if inside + */ + boolean Inside(Vector2 point1, Vector2 point2); + + /** + * Converts, if necessary, to Vector2d + * @return A vector2d instance + */ + Vector2d asVector2d(); + + /** + * Converts, if necessary, to Vector2i + * @return A vector2i instance + */ + Vector2i asVector2i(); + + /** + * Checks if the current vector is greater than the provided one + * @param other The other vector to check + * @return True if greater + */ + boolean greater(Vector2 other); + + /** + * Checks if the current vector is less than the provided one + * @param other The vector to check + * @return True if less than other + */ + boolean less(Vector2 other); + + /** + * Alias for Vector2#same + * @param other Vector to check + * @return True if same position + */ + boolean equal(Vector2 other); + + /** + * Adds the two vectors together + * @param other Vector to add + * @return New instance after adding the other vector + */ + Vector2 add(Vector2 other); + + /** + * Subtracts the other vector from this one + * @param other Vector to subtract + * @return New instance after subtracting + */ + Vector2 subtract(Vector2 other); + + /** + * Calculates the distance between the two vectors + * @param other + * @return The distance + */ + double distance(Vector2 other); + + /** + * Increments the Y axis by 1 + * @return New instance + */ + Vector2 moveUp(); + + /** + * Decrements the Y axis by 1 + * @return New instance + */ + Vector2 moveDown(); +} diff --git a/src/main/java/dev/zontreck/libzontreck/api/Vector3.java b/src/main/java/dev/zontreck/libzontreck/api/Vector3.java new file mode 100644 index 0000000..f7679f5 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/api/Vector3.java @@ -0,0 +1,143 @@ +package dev.zontreck.libzontreck.api; + +import dev.zontreck.libzontreck.vectors.Vector2d; +import dev.zontreck.libzontreck.vectors.Vector2i; +import dev.zontreck.libzontreck.vectors.Vector3d; +import dev.zontreck.libzontreck.vectors.Vector3i; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Vec3i; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.phys.Vec2; +import net.minecraft.world.phys.Vec3; + +public interface Vector3 +{ + /** + * Converts the current Vector3 representation into a minecraft Vec3 + * @return Minecraft equivalent Vec3 + */ + Vec3 asMinecraftVector(); + + /** + * Converts to a vec3i position + * @return Equivalent vec3i + */ + Vec3i asVec3i(); + + /** + * Converts to a block position + * @return Equivalent block position + */ + BlockPos asBlockPos(); + + /** + * Parses a string in serialized format. + * @param vector3 Expects it in the same format returned by Vector3#toString + * @return New Vector3, or a null Vector3 initialized with zeros if invalid data + */ + static Vector3 parseString(String vector3){ + throw new UnsupportedOperationException("This method is not implemented by this implementation"); + } + + /** + * Copies the values to a new and detached instance + * @return New Vector3 + */ + Vector3 Clone(); + + /** + * Saves the X, Y, and Z positions to a NBT tag + * @return NBT compound tag + */ + CompoundTag serialize(); + + /** + * Loads a Vector3 from a NBT tag + * @param tag The NBT tag to load + */ + static Vector3 deserialize(CompoundTag tag) + { + throw new UnsupportedOperationException("This method is not implemented by this implementation"); + } + + /** + * Compares the two vector3 instances + * @param other The position to check + * @return True if same position + */ + boolean Same(Vector3 other); + + /** + * True if the current position is inside the two points + * @param point1 Lowest point + * @param point2 Hightest Point + * @return True if inside + */ + boolean Inside(Vector3 point1, Vector3 point2); + + /** + * Converts, if necessary, to Vector3d + * @return A vector2d instance + */ + Vector3d asVector3d(); + + /** + * Converts, if necessary, to Vector3i + * @return A vector3i instance + */ + Vector3i asVector3i(); + + /** + * Checks if the current vector is greater than the provided one + * @param other The other vector to check + * @return True if greater + */ + boolean greater(Vector3 other); + + /** + * Checks if the current vector is less than the provided one + * @param other The vector to check + * @return True if less than other + */ + boolean less(Vector3 other); + + /** + * Alias for Vector3#same + * @param other Vector to check + * @return True if same position + */ + boolean equal(Vector3 other); + + /** + * Adds the two vectors together + * @param other Vector to add + * @return New instance after adding the other vector + */ + Vector3 add(Vector3 other); + + /** + * Subtracts the other vector from this one + * @param other Vector to subtract + * @return New instance after subtracting + */ + Vector3 subtract(Vector3 other); + + /** + * Calculates the distance between the two vectors + * @param other + * @return The distance + */ + double distance(Vector3 other); + + /** + * Increments the Y axis by 1 + * @return New instance + */ + Vector3 moveUp(); + + /** + * Decrements the Y axis by 1 + * @return New instance + */ + Vector3 moveDown(); +} diff --git a/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java b/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java index 8e41c10..ed1cd74 100644 --- a/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java +++ b/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java @@ -9,10 +9,8 @@ import dev.zontreck.libzontreck.networking.ModMessages; import dev.zontreck.libzontreck.networking.packets.S2CCloseChestGUI; import dev.zontreck.libzontreck.util.ChatHelpers; import dev.zontreck.libzontreck.util.ServerUtilities; -import dev.zontreck.libzontreck.vectors.Vector2; +import dev.zontreck.libzontreck.vectors.Vector2d; import dev.zontreck.libzontreck.vectors.Vector2i; -import net.minecraft.network.chat.Component; -import net.minecraft.resources.ResourceLocation; import net.minecraft.world.SimpleMenuProvider; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; @@ -24,8 +22,6 @@ import net.minecraftforge.network.NetworkHooks; import java.util.ArrayList; import java.util.List; import java.util.UUID; -import java.util.concurrent.Callable; -import java.util.function.Function; /** * Zontreck's ChestGUI Interface @@ -232,7 +228,7 @@ public class ChestGUI return this.id.equals(id); } - public void handleButtonClicked(int slot, Vector2 pos, Item item) { + public void handleButtonClicked(int slot, Vector2d pos, Item item) { for(ChestGUIButton button : buttons) { if(button.getSlotNum() == slot) diff --git a/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUIButton.java b/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUIButton.java index 4368831..3e7cb91 100644 --- a/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUIButton.java +++ b/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUIButton.java @@ -3,10 +3,8 @@ package dev.zontreck.libzontreck.chestgui; import dev.zontreck.libzontreck.lore.LoreContainer; import dev.zontreck.libzontreck.lore.LoreEntry; import dev.zontreck.libzontreck.util.ChatHelpers; -import dev.zontreck.libzontreck.vectors.Vector2; import dev.zontreck.libzontreck.vectors.Vector2i; import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.NbtUtils; import net.minecraft.world.item.Item; import net.minecraft.world.item.ItemStack; import net.minecraftforge.items.ItemStackHandler; diff --git a/src/main/java/dev/zontreck/libzontreck/util/BlocksUtil.java b/src/main/java/dev/zontreck/libzontreck/util/BlocksUtil.java index 4439147..92edc5e 100644 --- a/src/main/java/dev/zontreck/libzontreck/util/BlocksUtil.java +++ b/src/main/java/dev/zontreck/libzontreck/util/BlocksUtil.java @@ -1,6 +1,6 @@ package dev.zontreck.libzontreck.util; -import dev.zontreck.libzontreck.vectors.Vector3; +import dev.zontreck.libzontreck.vectors.Vector3d; import net.minecraft.server.level.ServerLevel; import java.util.ArrayList; @@ -18,9 +18,9 @@ public class BlocksUtil * @param limit The applicable limit for vein detection * @return List of positions for the vein */ - public static List VeinOf(ServerLevel level, Vector3 start, int limit) + public static List VeinOf(ServerLevel level, Vector3d start, int limit) { - List ret = new ArrayList<>(); + List ret = new ArrayList<>(); diff --git a/src/main/java/dev/zontreck/libzontreck/util/PositionUtil.java b/src/main/java/dev/zontreck/libzontreck/util/PositionUtil.java new file mode 100644 index 0000000..332deae --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/util/PositionUtil.java @@ -0,0 +1,115 @@ +package dev.zontreck.libzontreck.util; + +import dev.zontreck.libzontreck.api.Vector3; +import dev.zontreck.libzontreck.vectors.Vector3d; + +import java.util.ArrayList; +import java.util.List; + +/** + * Provides helper functions for position related things + */ +public class PositionUtil +{ + + public List makeCube(Vector3 p1, Vector3 p2) + { + List vecs = new ArrayList<>(); + Vector3 work = new Vector3d(); + Vector3d v1 = p1.asVector3d(); + Vector3d v2 = p2.asVector3d(); + + double xx = v1.x; + double yy = v1.y; + double zz = v1.z; + + int yState = 0; + int zState = 0; + int xState = 0; + + for(xx = Math.round(v1.x); (xx != Math.round(v2.x) && xState != 2);) + { + for(zz = Math.round(v1.z); (zz != Math.round(v2.z) && zState != 2);) + { + for(yy = Math.round(v1.y); (yy != Math.round(v2.y) && yState != 2);) + { + work = new Vector3d(xx, yy, zz); + + if(!vecs.contains(work)) vecs.add(work); + + if(yy > v2.y) + { + yy -= 1.0; + if(yy == Math.round(v2.y) && yState == 0) + { + yState++; + }else{ + if(yState == 1) + { + yState ++; + } + } + } else if(yy < v2.y) + { + yy += 1.0; + if(yy == Math.round(v2.y) && yState == 0){ + yState ++; + }else { + if(yState == 1)yState++; + } + } + } + + yState=0; + work = new Vector3d(xx,yy,zz); + + if(!vecs.contains(work)) vecs.add(work); + + if(zz > v2.z) + { + zz -= 1.0; + + if(zz == Math.round(v2.z) && zState == 0)zState++; + else{ + if(zState == 1)zState++; + } + }else if(zz < v2.z) + { + zz += 1.0; + + if(zz == Math.round(v2.z) && zState == 0)zState++; + else { + if(zState==1)zState++; + } + } + } + + zState=0; + work = new Vector3d(xx,yy,zz); + + if(!vecs.contains(work)) vecs.add(work); + + if(xx > v2.x) + { + xx -= 1.0; + + if(xx == Math.round(v2.x) && xState == 0) xState++; + else{ + if(xState == 1)xState++; + } + }else if(xx < v2.x) + { + xx += 1.0; + + if(xx == Math.round(v2.x) && xState==0)xState++; + else{ + if(xState==1)xState++; + } + } + } + + return vecs; + } + + +} diff --git a/src/main/java/dev/zontreck/libzontreck/util/ServerUtilities.java b/src/main/java/dev/zontreck/libzontreck/util/ServerUtilities.java index abd8d57..8f35d3b 100644 --- a/src/main/java/dev/zontreck/libzontreck/util/ServerUtilities.java +++ b/src/main/java/dev/zontreck/libzontreck/util/ServerUtilities.java @@ -1,5 +1,6 @@ package dev.zontreck.libzontreck.util; +import java.util.List; import java.util.UUID; import java.util.function.Function; import java.util.function.Supplier; @@ -13,6 +14,7 @@ import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.fml.LogicalSide; import net.minecraftforge.network.NetworkEvent; import net.minecraftforge.network.simple.SimpleChannel; +import net.minecraftforge.server.ServerLifecycleHooks; public class ServerUtilities { @@ -23,7 +25,7 @@ public class ServerUtilities */ public static ServerPlayer getPlayerByID(String id) { - return LibZontreck.THE_SERVER.getPlayerList().getPlayer(UUID.fromString(id)); + return ServerLifecycleHooks.getCurrentServer().getPlayerList().getPlayer(UUID.fromString(id)); } /** @@ -79,7 +81,7 @@ public class ServerUtilities public static boolean playerIsOffline(UUID ID) throws InvalidSideException { if(isClient())throw new InvalidSideException("This can only be called on the server"); - if(LibZontreck.THE_SERVER.getPlayerList().getPlayer(ID) == null) return true; + if(ServerLifecycleHooks.getCurrentServer().getPlayerList().getPlayer(ID) == null) return true; else return false; } diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/ChunkPos.java b/src/main/java/dev/zontreck/libzontreck/vectors/ChunkPos.java index 9cb8fc6..e88fba8 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/ChunkPos.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/ChunkPos.java @@ -5,10 +5,10 @@ import net.minecraft.server.level.ServerLevel; public class ChunkPos { public Points points; - public Vector2 centerPoints; + public Vector2d centerPoints; public String dim; - public ChunkPos(Vector3 point1, Vector3 point2, ServerLevel lvl) + public ChunkPos(Vector3d point1, Vector3d point2, ServerLevel lvl) { points = new Points(point1, point2, lvl); dim = WorldPosition.getDim(lvl); @@ -17,12 +17,12 @@ public class ChunkPos { public ChunkPos(CompoundTag tag) { points = new Points(tag.getCompound("points")); - centerPoints = new Vector2(tag.getCompound("center")); + centerPoints = Vector2d.deserialize(tag.getCompound("center")); } - public boolean isWithin(Vector3 point) + public boolean isWithin(Vector3d point) { - return point.inside(points.Min, points.Max); + return point.Inside(points.Min, points.Max); } public static ChunkPos getChunkPos(WorldPosition pos) diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/NonAbsVector3.java b/src/main/java/dev/zontreck/libzontreck/vectors/NonAbsVector3.java index d484e1b..bce839f 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/NonAbsVector3.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/NonAbsVector3.java @@ -11,7 +11,7 @@ public class NonAbsVector3 public long y; public long z; - public NonAbsVector3(Vector3 origin) + public NonAbsVector3(Vector3d origin) { x = Math.round(origin.x); y = Math.round(origin.y); diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Points.java b/src/main/java/dev/zontreck/libzontreck/vectors/Points.java index fdb0cf9..93fba5f 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/Points.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/Points.java @@ -7,8 +7,8 @@ import net.minecraft.server.level.ServerLevel; * Two points within the same dimension */ public class Points { - public Vector3 Min = Vector3.ZERO; - public Vector3 Max = Vector3.ZERO; + public Vector3d Min = Vector3d.ZERO; + public Vector3d Max = Vector3d.ZERO; public String dimension = ""; /** @@ -17,7 +17,7 @@ public class Points { * @param max * @param lvl */ - public Points(Vector3 min, Vector3 max, ServerLevel lvl) + public Points(Vector3d min, Vector3d max, ServerLevel lvl) { dimension = WorldPosition.getDimSafe(lvl); if(min.less(max)) @@ -48,8 +48,8 @@ public class Points { public void deserialize(CompoundTag tag) { - Min = new Vector3(tag.getCompound("min")); - Max = new Vector3(tag.getCompound("max")); + Min = Vector3d.deserialize(tag.getCompound("min")); + Max = Vector3d.deserialize(tag.getCompound("max")); dimension = tag.getString("dim"); } } diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2.java b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2.java deleted file mode 100644 index 100ae27..0000000 --- a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2.java +++ /dev/null @@ -1,120 +0,0 @@ -package dev.zontreck.libzontreck.vectors; - -import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.world.phys.Vec2; - -public class Vector2 -{ - public static final Vector2 ZERO = new Vector2(0, 0); - - public float x; - public float y; - - public Vec2 asMinecraftVector(){ - return new Vec2(x, y); - } - - public Vector2() - { - - } - - public Vector2(float x, float y) - { - this.x=x; - this.y=y; - } - - public Vector2(Vec2 pos) - { - x=pos.x; - y=pos.y; - } - - public Vector2(String pos) throws InvalidDeserialization - { - // This will be serialized most likely from the ToString method - // Parse - if(pos.startsWith("<")) - { - pos=pos.substring(1, pos.length()-1); // Rip off the ending bracket too - String[] positions = pos.split(", "); - if(positions.length!=2) - { - positions = pos.split(","); - } - - if(positions.length!=2) - { - throw new InvalidDeserialization("Positions must be in the same format provided by ToString() (ex. <1,1> or <1, 1>"); - } - - this.x = Float.parseFloat(positions[0]); - this.y = Float.parseFloat(positions[1]); - // We are done now - } - } - - public Vector2 Clone() - { - Vector2 n = new Vector2(x, y); - return n; - } - - @Override - public String toString() - { - return "<"+String.valueOf(x)+", "+String.valueOf(y) + ">"; - } - - - public CompoundTag serialize() - { - CompoundTag tag = new CompoundTag(); - tag.putFloat("x", x); - tag.putFloat("y", y); - - return tag; - } - - public Vector2(CompoundTag tag) { - this.deserialize(tag); - } - public void deserialize(CompoundTag tag) - { - x=tag.getFloat("x"); - y=tag.getFloat("y"); - } - - public boolean same(Vector2 other) - { - if(x == other.x && y==other.y)return true; - else return false; - } - - public boolean inside(Vector2 point1, Vector2 point2) - { - if(point1.x <= x && point2.x >= x){ - if(point1.y <= y && point2.y >= y) - { - return true; - } - } - - return false; - } - - public boolean greater(Vector2 other) - { - return ((x>other.x) && (y>other.y)); - } - public boolean less(Vector2 other) - { - return ((x>other.x) && (y>other.y)); - } - public boolean equal(Vector2 other) - { - return same(other); - } -} diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2d.java b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2d.java new file mode 100644 index 0000000..17b50bd --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2d.java @@ -0,0 +1,176 @@ +package dev.zontreck.libzontreck.vectors; + +import dev.zontreck.libzontreck.api.Vector2; +import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.world.phys.Vec2; + +public class Vector2d implements Vector2 +{ + public static final Vector2d ZERO = new Vector2d(0, 0); + + public float x; + public float y; + + @Override + public Vec2 asMinecraftVector(){ + return new Vec2(x, y); + } + + public Vector2d() + { + + } + + public Vector2d(float x, float y) + { + this.x=x; + this.y=y; + } + + public Vector2d(Vec2 pos) + { + x=pos.x; + y=pos.y; + } + + public static Vector2d parseString(String vector2) + { + Vector2d vec = new Vector2d(); + // This will be serialized most likely from the ToString method + // Parse + if(vector2.startsWith("<")) + { + vector2=vector2.substring(1, vector2.length()-1); // Rip off the ending bracket too + String[] positions = vector2.split(", "); + if(positions.length!=2) + { + positions = vector2.split(","); + } + + if(positions.length!=2) + { + return ZERO; + } + + vec.x = Float.parseFloat(positions[0]); + vec.y = Float.parseFloat(positions[1]); + // We are done now + } + + return vec; + } + + @Override + public Vector2d Clone() + { + Vector2d n = new Vector2d(x, y); + return n; + } + + @Override + public String toString() + { + return "<"+String.valueOf(x)+", "+String.valueOf(y) + ">"; + } + + @Override + public CompoundTag serialize() + { + CompoundTag tag = new CompoundTag(); + tag.putFloat("x", x); + tag.putFloat("y", y); + + return tag; + } + + public static Vector2d deserialize(CompoundTag tag) + { + Vector2d vec = new Vector2d(); + vec.x=tag.getFloat("x"); + vec.y=tag.getFloat("y"); + + return vec; + } + + @Override + public boolean Same(Vector2 other) + { + Vector2d ov = other.asVector2d(); + if(x == ov.x && y == ov.y) return true; + return false; + } + + @Override + public boolean Inside(Vector2 point1, Vector2 point2) + { + Vector2d p1 = point1.asVector2d(); + Vector2d p2 = point2.asVector2d(); + + if(p1.x <= x && p2.x >= x){ + if(p1.y <= y && p2.y >= y) + { + return true; + } + } + + return false; + } + + @Override + public Vector2d asVector2d() + { + return this; + } + + @Override + public Vector2i asVector2i() { + return new Vector2i(Math.round(x), Math.round(y)); + } + + @Override + public boolean greater(Vector2 other) + { + Vector2d vec = other.asVector2d(); + return ((x > vec.x) && (y > vec.y)); + } + + @Override + public boolean less(Vector2 other) + { + Vector2d vec = other.asVector2d(); + return ((x > vec.x) && (y > vec.y)); + } + public boolean equal(Vector2 other) + { + return Same(other); + } + + @Override + public Vector2d add(Vector2 other) { + Vector2d vec = other.asVector2d(); + return new Vector2d(x + vec.x, y + vec.y); + } + + @Override + public Vector2d subtract(Vector2 other) { + Vector2d vec = other.asVector2d(); + return new Vector2d(x - vec.x, y - vec.y); + } + + @Override + public double distance(Vector2 other) { + Vector2d vec = subtract(other); + return Math.sqrt((vec.x * vec.x + vec.y * vec.y)); + } + + @Override + public Vector2d moveUp() { + return add(new Vector2d(0,1)); + } + + @Override + public Vector2d moveDown() { + return subtract(new Vector2d(0,1)); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2i.java b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2i.java index bce3c94..e782c7b 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2i.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2i.java @@ -1,16 +1,18 @@ package dev.zontreck.libzontreck.vectors; +import dev.zontreck.libzontreck.api.Vector2; import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.phys.Vec2; -public class Vector2i +public class Vector2i implements Vector2 { public static final Vector2i ZERO = new Vector2i(0, 0); public int x; public int y; + @Override public Vec2 asMinecraftVector(){ return new Vec2(x, y); } @@ -32,30 +34,34 @@ public class Vector2i y=(int)Math.floor(pos.y); } - public Vector2i(String pos) throws InvalidDeserialization + public static Vector2i parseString(String vector2) { + Vector2i vec = new Vector2i(); // This will be serialized most likely from the ToString method // Parse - if(pos.startsWith("<")) + if(vector2.startsWith("<")) { - pos=pos.substring(1, pos.length()-1); // Rip off the ending bracket too - String[] positions = pos.split(", "); + vector2=vector2.substring(1, vector2.length()-1); // Rip off the ending bracket too + String[] positions = vector2.split(", "); if(positions.length!=2) { - positions = pos.split(","); + positions = vector2.split(","); } if(positions.length!=2) { - throw new InvalidDeserialization("Positions must be in the same format provided by ToString() (ex. <1,1> or <1, 1>"); + return ZERO; } - this.x = Integer.parseInt(positions[0]); - this.y = Integer.parseInt(positions[1]); + vec.x = Integer.parseInt(positions[0]); + vec.y = Integer.parseInt(positions[1]); // We are done now } + + return vec; } + @Override public Vector2i Clone() { Vector2i n = new Vector2i(x, y); @@ -69,6 +75,7 @@ public class Vector2i } + @Override public CompoundTag serialize() { CompoundTag tag = new CompoundTag(); @@ -78,25 +85,33 @@ public class Vector2i return tag; } - public Vector2i(CompoundTag tag) { - this.deserialize(tag); - } - public void deserialize(CompoundTag tag) + public static Vector2i deserialize(CompoundTag tag) { - x=tag.getInt("x"); - y=tag.getInt("y"); + Vector2i vec = new Vector2i(); + + vec.x=tag.getInt("x"); + vec.y=tag.getInt("y"); + + return vec; } - public boolean same(Vector2i other) + @Override + public boolean Same(Vector2 other) { - if(x == other.x && y==other.y)return true; + Vector2i v2i = other.asVector2i(); + + if(x == v2i.x && y==v2i.y)return true; else return false; } - public boolean inside(Vector2i point1, Vector2i point2) + @Override + public boolean Inside(Vector2 point1, Vector2 point2) { - if(point1.x <= x && point2.x >= x){ - if(point1.y <= y && point2.y >= y) + Vector2i v1i = point1.asVector2i(); + Vector2i v2i = point2.asVector2i(); + + if(v1i.x <= x && v2i.x >= x){ + if(v1i.y <= y && v2i.y >= y) { return true; } @@ -105,16 +120,69 @@ public class Vector2i return false; } - public boolean greater(Vector2i other) - { - return ((x>other.x) && (y>other.y)); + @Override + public Vector2d asVector2d() { + return new Vector2d(x,y); } - public boolean less(Vector2i other) - { - return ((x>other.x) && (y>other.y)); + + @Override + public Vector2i asVector2i() { + return this; } - public boolean equal(Vector2i other) + + @Override + public boolean greater(Vector2 other) { - return same(other); + Vector2i vec = other.asVector2i(); + return ((x > vec.x) && (y > vec.y)); + } + + @Override + public boolean less(Vector2 other) + { + Vector2i vec = other.asVector2i(); + return ((x > vec.x) && (y > vec.y)); + } + + @Override + public boolean equal(Vector2 other) + { + return Same(other); + } + + @Override + public Vector2i add(Vector2 other) { + Vector2i vec = other.asVector2i(); + x += vec.x; + y += vec.y; + + return this; + } + + @Override + public Vector2i subtract(Vector2 other) { + Vector2i vec = other.asVector2i(); + + x -= vec.x; + y -= vec.y; + + return this; + } + + @Override + public double distance(Vector2 other) { + Vector2i vec = subtract(other); + return Math.sqrt((vec.x * vec.x + vec.y * vec.y)); + } + + + @Override + public Vector2i moveUp() { + return add(new Vector2i(0,1)); + } + + @Override + public Vector2i moveDown() { + return subtract(new Vector2i(0,1)); } } diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Vector3.java b/src/main/java/dev/zontreck/libzontreck/vectors/Vector3.java deleted file mode 100644 index c1cf9ee..0000000 --- a/src/main/java/dev/zontreck/libzontreck/vectors/Vector3.java +++ /dev/null @@ -1,285 +0,0 @@ -package dev.zontreck.libzontreck.vectors; - -import java.util.ArrayList; -import java.util.List; - -import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; -import net.minecraft.core.BlockPos; -import net.minecraft.core.Vec3i; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.world.phys.Vec3; - -public class Vector3 -{ - public static final Vector3 ZERO = new Vector3(0, 0, 0); - - - public double x; - public double y; - public double z; - - public Vec3 asMinecraftVector(){ - return new Vec3(x, y, z); - } - - public Vec3i asMinecraftVec3i() - { - return new Vec3i((int) Math.round(x), (int) Math.round(y), (int) Math.round(z)); - } - - public BlockPos asBlockPos() - { - return new BlockPos(asMinecraftVec3i()); - } - - public Vector3() - { - - } - - public Vector3(double x, double y, double z) - { - this.x=x; - this.y=y; - this.z=z; - } - - public Vector3(Vec3 pos) - { - x=pos.x; - y=pos.y; - z=pos.z; - } - - public Vector3(BlockPos pos) - { - x=pos.getX(); - y=pos.getY(); - z=pos.getZ(); - } - - public Vector3(String pos) throws InvalidDeserialization - { - // This will be serialized most likely from the ToString method - // Parse - if(pos.startsWith("<")) - { - pos=pos.substring(1, pos.length()-1); // Rip off the ending bracket too - String[] positions = pos.split(", "); - if(positions.length!=3) - { - positions = pos.split(","); - } - - if(positions.length!=3) - { - throw new InvalidDeserialization("Positions must be in the same format provided by ToString() (ex. <1,1,1> or <1, 1, 1>"); - } - - this.x = Double.parseDouble(positions[0]); - this.y = Double.parseDouble(positions[1]); - this.z = Double.parseDouble(positions[2]); - // We are done now - } - } - - public List makeCube(Vector3 other) - { - List vecs = new ArrayList<>(); - Vector3 work = new Vector3(); - - double xx = x; - double yy = y; - double zz = z; - - int yState = 0; - int zState = 0; - int xState = 0; - - for(xx = Math.round(x); (xx != Math.round(other.x) && xState != 2);) - { - for(zz = Math.round(z); (zz != Math.round(other.z) && zState != 2);) - { - for(yy = Math.round(y); (yy != Math.round(other.y) && yState != 2);) - { - work = new Vector3(xx, yy, zz); - - if(!vecs.contains(work)) vecs.add(work); - - if(yy > other.y) - { - yy -= 1.0; - if(yy == Math.round(other.y) && yState == 0) - { - yState++; - }else{ - if(yState == 1) - { - yState ++; - } - } - } else if(yy < other.y) - { - yy += 1.0; - if(yy == Math.round(other.y) && yState == 0){ - yState ++; - }else { - if(yState == 1)yState++; - } - } - } - - yState=0; - work = new Vector3(xx,yy,zz); - - if(!vecs.contains(work)) vecs.add(work); - - if(zz > other.z) - { - zz -= 1.0; - - if(zz == Math.round(other.z) && zState == 0)zState++; - else{ - if(zState == 1)zState++; - } - }else if(zz < other.z) - { - zz += 1.0; - - if(zz == Math.round(other.z) && zState == 0)zState++; - else { - if(zState==1)zState++; - } - } - } - - zState=0; - work = new Vector3(xx,yy,zz); - - if(!vecs.contains(work)) vecs.add(work); - - if(xx > other.x) - { - xx -= 1.0; - - if(xx == Math.round(other.x) && xState == 0) xState++; - else{ - if(xState == 1)xState++; - } - }else if(xx < other.x) - { - xx += 1.0; - - if(xx == Math.round(other.x) && xState==0)xState++; - else{ - if(xState==1)xState++; - } - } - } - - return vecs; - } - - public Vector3 subtract(Vector3 other) - { - return new Vector3(x-other.x, y-other.y, z-other.z); - } - public Vector3 add(Vector3 other) - { - return new Vector3(x+other.x, y+other.y, z +other.z); - } - - public double distance(Vector3 other) - { - Vector3 sub = subtract(other); - return Math.sqrt((sub.x * sub.x + sub.y * sub.y + sub.z * sub.z)); - } - - public Vector3 moveUp() - { - Vector3 up = Clone(); - up.y+=1; - return up; - } - public Vector3 moveDown() - { - Vector3 up = Clone(); - up.y-=1; - return up; - } - - - public Vector3 Clone() - { - Vector3 n = new Vector3(x, y, z); - return n; - } - - @Override - public String toString() - { - return "<"+String.valueOf(x)+", "+String.valueOf(y)+", "+String.valueOf(z)+">"; - } - - public NonAbsVector3 rounded() - { - NonAbsVector3 cl = new NonAbsVector3(this); - return cl; - } - - public CompoundTag serialize() - { - CompoundTag tag = new CompoundTag(); - tag.putDouble("x", x); - tag.putDouble("y", y); - tag.putDouble("z", z); - - return tag; - } - - public Vector3(CompoundTag tag) { - this.deserialize(tag); - } - public void deserialize(CompoundTag tag) - { - x=tag.getDouble("x"); - y=tag.getDouble("y"); - z=tag.getDouble("z"); - } - - - public boolean same(Vector3 other) - { - if(x == other.x && y==other.y && z==other.z)return true; - else return false; - } - - - public boolean inside(Vector3 point1, Vector3 point2) - { - if(point1.x <= x && point2.x >= x){ - if(point1.y <= y && point2.y >= y) - { - if(point1.z <= z && point2.z >= z) - { - return true; - } - } - } - - return false; - } - - public boolean greater(Vector3 other) - { - return ((x>other.x) && (y>other.y) && (z>other.z)); - } - public boolean less(Vector3 other) - { - return ((x"; + } + + public NonAbsVector3 rounded() + { + NonAbsVector3 cl = new NonAbsVector3(this); + return cl; + } + + @Override + public CompoundTag serialize() + { + CompoundTag tag = new CompoundTag(); + tag.putDouble("x", x); + tag.putDouble("y", y); + tag.putDouble("z", z); + + return tag; + } + + public static Vector3d deserialize(CompoundTag tag) + { + Vector3d vec = new Vector3d(); + + vec.x=tag.getDouble("x"); + vec.y=tag.getDouble("y"); + vec.z=tag.getDouble("z"); + + return vec; + } + + @Override + public boolean Same(Vector3 other) + { + Vector3d vec = other.asVector3d(); + if(x == vec.x && y==vec.y && z==vec.z)return true; + else return false; + } + + + @Override + public boolean Inside(Vector3 point1, Vector3 point2) + { + Vector3d v1 = point1.asVector3d(); + Vector3d v2 = point2.asVector3d(); + + if(v1.x <= x && v2.x >= x){ + if(v1.y <= y && v2.y >= y) + { + if(v1.z <= z && v2.z >= z) + { + return true; + } + } + } + + return false; + } + + @Override + public Vector3d asVector3d() { + return this; + } + + @Override + public Vector3i asVector3i() { + return new Vector3i((int) Math.round(x), (int) Math.round(y), (int) Math.round(z)); + } + + @Override + public boolean greater(Vector3 other) + { + Vector3d vec = other.asVector3d(); + return ((x>vec.x) && (y>vec.y) && (z>vec.z)); + } + + @Override + public boolean less(Vector3 other) + { + Vector3d vec = other.asVector3d(); + return ((x or <1, 1, 1>"); + } + + vec.x = Integer.parseInt(positions[0]); + vec.y = Integer.parseInt(positions[1]); + vec.z = Integer.parseInt(positions[2]); + // We are done now + } + + return vec; + } + + @Override + public Vector3i subtract(Vector3 other) + { + Vector3i vec = other.asVector3i(); + return new Vector3i(x-vec.x, y-vec.y, z-vec.z); + } + + @Override + public Vector3i add(Vector3 other) + { + Vector3i vec = other.asVector3i(); + return new Vector3i(x+vec.x, y+vec.y, z +vec.z); + } + + @Override + public Vector3i asVector3i() { + return this; + } + + @Override + public Vector3d asVector3d() { + return new Vector3d(x, y, z); + } + + @Override + public double distance(Vector3 other) + { + Vector3i sub = subtract(other); + return Math.sqrt((sub.x * sub.x + sub.y * sub.y + sub.z * sub.z)); + } + + @Override + public Vector3i moveUp() + { + return add(new Vector3i(0,1,0)); + } + public Vector3i moveDown() + { + return subtract(new Vector3i(0,1,0)); + } + + @Override + public Vector3i Clone() + { + Vector3i n = new Vector3i(x, y, z); + return n; + } + + @Override + public String toString() + { + return "<"+String.valueOf(x)+", "+String.valueOf(y)+", "+String.valueOf(z)+">"; + } + + @Override + public CompoundTag serialize() + { + CompoundTag tag = new CompoundTag(); + tag.putInt("x", x); + tag.putInt("y", y); + tag.putInt("z", z); + + return tag; + } + + public static Vector3i deserialize(CompoundTag tag) + { + Vector3i vec = new Vector3i(); + + vec.x=tag.getInt("x"); + vec.y=tag.getInt("y"); + vec.z=tag.getInt("z"); + + return vec; + } + + @Override + public boolean Same(Vector3 other) + { + Vector3i vec = other.asVector3i(); + if(x == vec.x && y==vec.y && z==vec.z)return true; + else return false; + } + + @Override + public boolean Inside(Vector3 point1, Vector3 point2) + { + Vector3i v1 = point1.asVector3i(); + Vector3i v2 = point2.asVector3i(); + + if(v1.x <= x && v2.x >= x){ + if(v1.y <= y && v2.y >= y) + { + if(v1.z <= z && v2.z >= z) + { + return true; + } + } + } + + return false; + } + + @Override + public boolean greater(Vector3 other) + { + Vector3i v3 = other.asVector3i(); + return ((x>v3.x) && (y>v3.y) && (z>v3.z)); + } + + @Override + public boolean less(Vector3 other) + { + Vector3i vec = other.asVector3i(); + return ((x Date: Sun, 7 Apr 2024 15:53:49 -0700 Subject: [PATCH 27/88] Bump version num to next major API --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 3ab324e..db4679a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.11.040124.0231 +mod_version=1201.13.040724.1553 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html From ed9d9bd2497e01ea06f2c1025caa7a753028212b Mon Sep 17 00:00:00 2001 From: zontreck Date: Sun, 7 Apr 2024 16:01:28 -0700 Subject: [PATCH 28/88] Fix compile errors --- .../libzontreck/chestgui/ChestGUIButton.java | 2 +- .../dev/zontreck/libzontreck/currency/Bank.java | 14 ++++++++++---- .../libzontreck/memory/PlayerComponent.java | 3 ++- .../libzontreck/memory/PlayerContainer.java | 3 ++- 4 files changed, 15 insertions(+), 7 deletions(-) diff --git a/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUIButton.java b/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUIButton.java index 3e7cb91..0ed5b46 100644 --- a/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUIButton.java +++ b/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUIButton.java @@ -142,7 +142,7 @@ public class ChestGUIButton */ public boolean matchesSlot(Vector2i slot) { - return position.same(slot); + return position.Same(slot); } /** diff --git a/src/main/java/dev/zontreck/libzontreck/currency/Bank.java b/src/main/java/dev/zontreck/libzontreck/currency/Bank.java index 2f65b59..4473d88 100644 --- a/src/main/java/dev/zontreck/libzontreck/currency/Bank.java +++ b/src/main/java/dev/zontreck/libzontreck/currency/Bank.java @@ -16,8 +16,10 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.nbt.NbtIo; import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.server.ServerLifecycleHooks; import java.io.IOException; import java.lang.reflect.InvocationTargetException; @@ -139,6 +141,9 @@ public class Bank protected static boolean postTx(Transaction tx) throws InvalidSideException, InvocationTargetException, IllegalAccessException { if(ServerUtilities.isClient())return false; TransactionEvent ev = new TransactionEvent(tx); + + MinecraftServer server = ServerLifecycleHooks.getCurrentServer(); + if(MinecraftForge.EVENT_BUS.post(ev)) { // Send the list of reasons to the user @@ -147,13 +152,14 @@ public class Bank Account from = ev.tx.from.get(); Account to = ev.tx.to.get(); + if(from.isValidPlayer()) { - ChatHelpers.broadcastTo(from.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] !Dark_Red!The transaction could not be completed because of the following reasons: " + reasonStr), LibZontreck.THE_SERVER); + ChatHelpers.broadcastTo(from.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] !Dark_Red!The transaction could not be completed because of the following reasons: " + reasonStr), server); } if(to.isValidPlayer()) { - ChatHelpers.broadcastTo(to.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] !Dark_Red!The transaction could not be completed because of the following reasons: " + reasonStr), LibZontreck.THE_SERVER); + ChatHelpers.broadcastTo(to.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] !Dark_Red!The transaction could not be completed because of the following reasons: " + reasonStr), server); } return false; @@ -194,10 +200,10 @@ public class Bank } if(from.isValidPlayer()) - ChatHelpers.broadcastTo(from.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] !Dark_Green!You sent !White!${0} !Dark_green!to {1}", String.valueOf(tx.amount), toProf.name_color+toProf.nickname), LibZontreck.THE_SERVER); + ChatHelpers.broadcastTo(from.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] !Dark_Green!You sent !White!${0} !Dark_green!to {1}", String.valueOf(tx.amount), toProf.name_color+toProf.nickname), server); if(to.isValidPlayer()) - ChatHelpers.broadcastTo(from.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] {0} !Dark_Green!paid you ${1}", String.valueOf(tx.amount), toProf.name_color+toProf.nickname), LibZontreck.THE_SERVER); + ChatHelpers.broadcastTo(from.player_id, ChatHelpers.macro("!Dark_Gray![!Dark_Blue!Bank!Dark_Gray!] {0} !Dark_Green!paid you ${1}", String.valueOf(tx.amount), toProf.name_color+toProf.nickname), server); if(to.isValidPlayer() && ServerUtilities.playerIsOffline(to.player_id)) Profile.unload(toProf); if(from.isValidPlayer() && ServerUtilities.playerIsOffline(from.player_id)) diff --git a/src/main/java/dev/zontreck/libzontreck/memory/PlayerComponent.java b/src/main/java/dev/zontreck/libzontreck/memory/PlayerComponent.java index b169ec1..886ae53 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/PlayerComponent.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/PlayerComponent.java @@ -7,6 +7,7 @@ import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; import dev.zontreck.libzontreck.vectors.WorldPosition; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.server.ServerLifecycleHooks; public class PlayerComponent { @@ -34,7 +35,7 @@ public class PlayerComponent public static PlayerComponent fromID(UUID ID) { - return new PlayerComponent(LibZontreck.THE_SERVER.getPlayerList().getPlayer(ID)); + return new PlayerComponent(ServerLifecycleHooks.getCurrentServer().getPlayerList().getPlayer(ID)); } public CompoundTag serialize() diff --git a/src/main/java/dev/zontreck/libzontreck/memory/PlayerContainer.java b/src/main/java/dev/zontreck/libzontreck/memory/PlayerContainer.java index b898ba5..218fb30 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/PlayerContainer.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/PlayerContainer.java @@ -5,6 +5,7 @@ import java.util.UUID; import dev.zontreck.libzontreck.LibZontreck; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerPlayer; +import net.minecraftforge.server.ServerLifecycleHooks; public class PlayerContainer { public UUID ID; @@ -13,7 +14,7 @@ public class PlayerContainer { public PlayerContainer(UUID ID) { - this(LibZontreck.THE_SERVER.getPlayerList().getPlayer(ID)); + this(ServerLifecycleHooks.getCurrentServer().getPlayerList().getPlayer(ID)); } public PlayerContainer(ServerPlayer player) { From a4da2bb52b9a74936eb41bdebb198429f3facc54 Mon Sep 17 00:00:00 2001 From: zontreck Date: Sun, 7 Apr 2024 19:50:32 -0700 Subject: [PATCH 29/88] Add some more utility functions --- .../dev/zontreck/libzontreck/api/Vector2.java | 18 ++++- .../dev/zontreck/libzontreck/api/Vector3.java | 23 +++++-- .../libzontreck/chestgui/ChestGUI.java | 4 +- .../libzontreck/util/PositionUtil.java | 41 ++++++++++++ .../libzontreck/vectors/ChunkPos.java | 4 +- .../vectors/{Vector2d.java => Vector2f.java} | 67 +++++++++++-------- .../libzontreck/vectors/Vector2i.java | 18 +++-- .../libzontreck/vectors/Vector3d.java | 17 ++++- .../libzontreck/vectors/Vector3i.java | 17 ++++- .../libzontreck/vectors/WorldPosition.java | 2 +- 10 files changed, 164 insertions(+), 47 deletions(-) rename src/main/java/dev/zontreck/libzontreck/vectors/{Vector2d.java => Vector2f.java} (66%) diff --git a/src/main/java/dev/zontreck/libzontreck/api/Vector2.java b/src/main/java/dev/zontreck/libzontreck/api/Vector2.java index 670f786..a0645ec 100644 --- a/src/main/java/dev/zontreck/libzontreck/api/Vector2.java +++ b/src/main/java/dev/zontreck/libzontreck/api/Vector2.java @@ -1,11 +1,11 @@ package dev.zontreck.libzontreck.api; -import dev.zontreck.libzontreck.vectors.Vector2d; +import dev.zontreck.libzontreck.vectors.Vector2f; import dev.zontreck.libzontreck.vectors.Vector2i; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.phys.Vec2; -public interface Vector2 +public interface Vector2 { /** * Converts the current Vector2 representation into a minecraft Vec2 @@ -62,7 +62,7 @@ public interface Vector2 * Converts, if necessary, to Vector2d * @return A vector2d instance */ - Vector2d asVector2d(); + Vector2f asVector2f(); /** * Converts, if necessary, to Vector2i @@ -123,4 +123,16 @@ public interface Vector2 * @return New instance */ Vector2 moveDown(); + + /** + * Gets the X value + * @return X + */ + T getX(); + + /** + * Gets the Y value + * @return Y + */ + T getY(); } diff --git a/src/main/java/dev/zontreck/libzontreck/api/Vector3.java b/src/main/java/dev/zontreck/libzontreck/api/Vector3.java index f7679f5..ce23cc1 100644 --- a/src/main/java/dev/zontreck/libzontreck/api/Vector3.java +++ b/src/main/java/dev/zontreck/libzontreck/api/Vector3.java @@ -1,16 +1,13 @@ package dev.zontreck.libzontreck.api; -import dev.zontreck.libzontreck.vectors.Vector2d; -import dev.zontreck.libzontreck.vectors.Vector2i; import dev.zontreck.libzontreck.vectors.Vector3d; import dev.zontreck.libzontreck.vectors.Vector3i; import net.minecraft.core.BlockPos; import net.minecraft.core.Vec3i; import net.minecraft.nbt.CompoundTag; -import net.minecraft.world.phys.Vec2; import net.minecraft.world.phys.Vec3; -public interface Vector3 +public interface Vector3 { /** * Converts the current Vector3 representation into a minecraft Vec3 @@ -140,4 +137,22 @@ public interface Vector3 * @return New instance */ Vector3 moveDown(); + + /** + * Gets the X value + * @return X + */ + T getX(); + + /** + * Gets the Y value + * @return Y + */ + T getY(); + + /** + * Gets the Z value + * @return Z + */ + T getZ(); } diff --git a/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java b/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java index ed1cd74..d41046b 100644 --- a/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java +++ b/src/main/java/dev/zontreck/libzontreck/chestgui/ChestGUI.java @@ -9,7 +9,7 @@ import dev.zontreck.libzontreck.networking.ModMessages; import dev.zontreck.libzontreck.networking.packets.S2CCloseChestGUI; import dev.zontreck.libzontreck.util.ChatHelpers; import dev.zontreck.libzontreck.util.ServerUtilities; -import dev.zontreck.libzontreck.vectors.Vector2d; +import dev.zontreck.libzontreck.vectors.Vector2f; import dev.zontreck.libzontreck.vectors.Vector2i; import net.minecraft.world.SimpleMenuProvider; import net.minecraft.world.item.Item; @@ -228,7 +228,7 @@ public class ChestGUI return this.id.equals(id); } - public void handleButtonClicked(int slot, Vector2d pos, Item item) { + public void handleButtonClicked(int slot, Vector2f pos, Item item) { for(ChestGUIButton button : buttons) { if(button.getSlotNum() == slot) diff --git a/src/main/java/dev/zontreck/libzontreck/util/PositionUtil.java b/src/main/java/dev/zontreck/libzontreck/util/PositionUtil.java index 332deae..fdcb9f6 100644 --- a/src/main/java/dev/zontreck/libzontreck/util/PositionUtil.java +++ b/src/main/java/dev/zontreck/libzontreck/util/PositionUtil.java @@ -2,8 +2,10 @@ package dev.zontreck.libzontreck.util; import dev.zontreck.libzontreck.api.Vector3; import dev.zontreck.libzontreck.vectors.Vector3d; +import dev.zontreck.libzontreck.vectors.Vector3i; import java.util.ArrayList; +import java.util.Iterator; import java.util.List; /** @@ -112,4 +114,43 @@ public class PositionUtil } + public List sortAscending(List vecs) + { + + List copy = new ArrayList<>(vecs); + List sorted = new ArrayList<>(); + + + while(copy.size()>0) + { + Vector3 lowest = findFirstLowestPosition(copy); + copy.remove(lowest); + sorted.add(lowest); + } + + return sorted; + } + + public static Vector3 findFirstLowestPosition(List vecs) + { + + Vector3i lowest = new Vector3i(0, 500, 0); + List copy = new ArrayList<>(vecs); + + Iterator it = copy.iterator(); + while(it.hasNext()) + { + Vector3i entry = it.next().asVector3i(); + if(entry.y < lowest.y) + { + lowest = entry; + it.remove(); + }else it.remove(); + } + + return lowest; + + } + + } diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/ChunkPos.java b/src/main/java/dev/zontreck/libzontreck/vectors/ChunkPos.java index e88fba8..0e60b5a 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/ChunkPos.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/ChunkPos.java @@ -5,7 +5,7 @@ import net.minecraft.server.level.ServerLevel; public class ChunkPos { public Points points; - public Vector2d centerPoints; + public Vector2f centerPoints; public String dim; public ChunkPos(Vector3d point1, Vector3d point2, ServerLevel lvl) @@ -17,7 +17,7 @@ public class ChunkPos { public ChunkPos(CompoundTag tag) { points = new Points(tag.getCompound("points")); - centerPoints = Vector2d.deserialize(tag.getCompound("center")); + centerPoints = Vector2f.deserialize(tag.getCompound("center")); } public boolean isWithin(Vector3d point) diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2d.java b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2f.java similarity index 66% rename from src/main/java/dev/zontreck/libzontreck/vectors/Vector2d.java rename to src/main/java/dev/zontreck/libzontreck/vectors/Vector2f.java index 17b50bd..112686c 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2d.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2f.java @@ -1,13 +1,12 @@ package dev.zontreck.libzontreck.vectors; import dev.zontreck.libzontreck.api.Vector2; -import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.phys.Vec2; -public class Vector2d implements Vector2 +public class Vector2f implements Vector2 { - public static final Vector2d ZERO = new Vector2d(0, 0); + public static final Vector2f ZERO = new Vector2f(0, 0); public float x; public float y; @@ -17,26 +16,26 @@ public class Vector2d implements Vector2 return new Vec2(x, y); } - public Vector2d() + public Vector2f() { } - public Vector2d(float x, float y) + public Vector2f(float x, float y) { this.x=x; this.y=y; } - public Vector2d(Vec2 pos) + public Vector2f(Vec2 pos) { x=pos.x; y=pos.y; } - public static Vector2d parseString(String vector2) + public static Vector2f parseString(String vector2) { - Vector2d vec = new Vector2d(); + Vector2f vec = new Vector2f(); // This will be serialized most likely from the ToString method // Parse if(vector2.startsWith("<")) @@ -62,9 +61,9 @@ public class Vector2d implements Vector2 } @Override - public Vector2d Clone() + public Vector2f Clone() { - Vector2d n = new Vector2d(x, y); + Vector2f n = new Vector2f(x, y); return n; } @@ -84,9 +83,9 @@ public class Vector2d implements Vector2 return tag; } - public static Vector2d deserialize(CompoundTag tag) + public static Vector2f deserialize(CompoundTag tag) { - Vector2d vec = new Vector2d(); + Vector2f vec = new Vector2f(); vec.x=tag.getFloat("x"); vec.y=tag.getFloat("y"); @@ -96,7 +95,7 @@ public class Vector2d implements Vector2 @Override public boolean Same(Vector2 other) { - Vector2d ov = other.asVector2d(); + Vector2f ov = other.asVector2f(); if(x == ov.x && y == ov.y) return true; return false; } @@ -104,8 +103,8 @@ public class Vector2d implements Vector2 @Override public boolean Inside(Vector2 point1, Vector2 point2) { - Vector2d p1 = point1.asVector2d(); - Vector2d p2 = point2.asVector2d(); + Vector2f p1 = point1.asVector2f(); + Vector2f p2 = point2.asVector2f(); if(p1.x <= x && p2.x >= x){ if(p1.y <= y && p2.y >= y) @@ -118,7 +117,7 @@ public class Vector2d implements Vector2 } @Override - public Vector2d asVector2d() + public Vector2f asVector2f() { return this; } @@ -131,14 +130,14 @@ public class Vector2d implements Vector2 @Override public boolean greater(Vector2 other) { - Vector2d vec = other.asVector2d(); + Vector2f vec = other.asVector2f(); return ((x > vec.x) && (y > vec.y)); } @Override public boolean less(Vector2 other) { - Vector2d vec = other.asVector2d(); + Vector2f vec = other.asVector2f(); return ((x > vec.x) && (y > vec.y)); } public boolean equal(Vector2 other) @@ -147,30 +146,40 @@ public class Vector2d implements Vector2 } @Override - public Vector2d add(Vector2 other) { - Vector2d vec = other.asVector2d(); - return new Vector2d(x + vec.x, y + vec.y); + public Vector2f add(Vector2 other) { + Vector2f vec = other.asVector2f(); + return new Vector2f(x + vec.x, y + vec.y); } @Override - public Vector2d subtract(Vector2 other) { - Vector2d vec = other.asVector2d(); - return new Vector2d(x - vec.x, y - vec.y); + public Vector2f subtract(Vector2 other) { + Vector2f vec = other.asVector2f(); + return new Vector2f(x - vec.x, y - vec.y); } @Override public double distance(Vector2 other) { - Vector2d vec = subtract(other); + Vector2f vec = subtract(other); return Math.sqrt((vec.x * vec.x + vec.y * vec.y)); } @Override - public Vector2d moveUp() { - return add(new Vector2d(0,1)); + public Vector2f moveUp() { + return add(new Vector2f(0,1)); } @Override - public Vector2d moveDown() { - return subtract(new Vector2d(0,1)); + public Vector2f moveDown() { + return subtract(new Vector2f(0,1)); + } + + @Override + public Float getX() { + return x; + } + + @Override + public Float getY() { + return y; } } diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2i.java b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2i.java index e782c7b..a812be6 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2i.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2i.java @@ -1,11 +1,10 @@ package dev.zontreck.libzontreck.vectors; import dev.zontreck.libzontreck.api.Vector2; -import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.phys.Vec2; -public class Vector2i implements Vector2 +public class Vector2i implements Vector2 { public static final Vector2i ZERO = new Vector2i(0, 0); @@ -121,8 +120,8 @@ public class Vector2i implements Vector2 } @Override - public Vector2d asVector2d() { - return new Vector2d(x,y); + public Vector2f asVector2f() { + return new Vector2f(x,y); } @Override @@ -185,4 +184,15 @@ public class Vector2i implements Vector2 public Vector2i moveDown() { return subtract(new Vector2i(0,1)); } + + + @Override + public Integer getX() { + return x; + } + + @Override + public Integer getY() { + return y; + } } diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Vector3d.java b/src/main/java/dev/zontreck/libzontreck/vectors/Vector3d.java index 09b90e4..eaaa44e 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/Vector3d.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/Vector3d.java @@ -10,7 +10,7 @@ import net.minecraft.core.Vec3i; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.phys.Vec3; -public class Vector3d implements Vector3 +public class Vector3d implements Vector3 { public static final Vector3d ZERO = new Vector3d(0, 0, 0); @@ -219,4 +219,19 @@ public class Vector3d implements Vector3 { return Same(other); } + + @Override + public Double getX() { + return x; + } + + @Override + public Double getY() { + return y; + } + + @Override + public Double getZ() { + return z; + } } diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Vector3i.java b/src/main/java/dev/zontreck/libzontreck/vectors/Vector3i.java index 9916470..5efecaf 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/Vector3i.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/Vector3i.java @@ -10,7 +10,7 @@ import net.minecraft.world.phys.Vec3; import java.util.ArrayList; import java.util.List; -public class Vector3i implements Vector3 +public class Vector3i implements Vector3 { public static final Vector3i ZERO = new Vector3i(0, 0, 0); @@ -212,4 +212,19 @@ public class Vector3i implements Vector3 { return Same(other); } + + @Override + public Integer getX() { + return x; + } + + @Override + public Integer getY() { + return y; + } + + @Override + public Integer getZ() { + return z; + } } diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java b/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java index 1fc09cd..add4d2b 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java @@ -121,7 +121,7 @@ public class WorldPosition { public ChunkPos getChunkPos() { net.minecraft.world.level.ChunkPos mcChunk = getActualDimension().getChunkAt(Position.asBlockPos()).getPos(); ChunkPos pos = new ChunkPos(new Vector3d(mcChunk.getMinBlockX(), -70, mcChunk.getMinBlockZ()), new Vector3d(mcChunk.getMaxBlockX(), 400, mcChunk.getMaxBlockZ()), getActualDimension()); - pos.centerPoints = new Vector2d(mcChunk.getMiddleBlockX(), mcChunk.getMiddleBlockZ()); + pos.centerPoints = new Vector2f(mcChunk.getMiddleBlockX(), mcChunk.getMiddleBlockZ()); return pos; } From 06cbd4fbf55a896e478b9bf24d60b574a622b610 Mon Sep 17 00:00:00 2001 From: zontreck Date: Sun, 7 Apr 2024 19:52:03 -0700 Subject: [PATCH 30/88] Bump build number --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index db4679a..f526872 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.040724.1553 +mod_version=1201.13.040724.1951 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html From 4247ae62463465990a7caba9a8d834dc56632117 Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 9 Apr 2024 13:22:41 -0700 Subject: [PATCH 31/88] Push current work --- .../java/dev/zontreck/libzontreck/LibZontreck.java | 4 +--- .../libzontreck/events/ForgeEventHandlers.java | 5 +---- .../memory/{ => player}/PlayerComponent.java | 3 +-- .../memory/{ => player}/PlayerContainer.java | 3 +-- .../memory/{ => player}/VolatilePlayerStorage.java | 2 +- .../libzontreck/memory/world/SavedBlock.java | 12 ++++++++++++ 6 files changed, 17 insertions(+), 12 deletions(-) rename src/main/java/dev/zontreck/libzontreck/memory/{ => player}/PlayerComponent.java (94%) rename src/main/java/dev/zontreck/libzontreck/memory/{ => player}/PlayerContainer.java (87%) rename src/main/java/dev/zontreck/libzontreck/memory/{ => player}/VolatilePlayerStorage.java (97%) create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java diff --git a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java index 11a53a7..66a7af4 100644 --- a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java +++ b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java @@ -8,7 +8,6 @@ import java.util.Iterator; import java.util.Map; import java.util.UUID; -import dev.zontreck.ariaslib.util.DelayedExecutorService; import dev.zontreck.libzontreck.chestgui.ChestGUIRegistry; import dev.zontreck.libzontreck.currency.Bank; import dev.zontreck.libzontreck.currency.CurrencyHelper; @@ -23,11 +22,10 @@ import com.mojang.logging.LogUtils; import dev.zontreck.libzontreck.commands.Commands; import dev.zontreck.libzontreck.events.ForgeEventHandlers; -import dev.zontreck.libzontreck.memory.VolatilePlayerStorage; +import dev.zontreck.libzontreck.memory.player.VolatilePlayerStorage; import dev.zontreck.libzontreck.networking.ModMessages; import dev.zontreck.libzontreck.profiles.Profile; import dev.zontreck.libzontreck.util.FileTreeDatastore; -import net.minecraft.server.MinecraftServer; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.server.ServerStartedEvent; import net.minecraftforge.event.server.ServerStoppingEvent; diff --git a/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java b/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java index ba07e63..ee795e6 100644 --- a/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java +++ b/src/main/java/dev/zontreck/libzontreck/events/ForgeEventHandlers.java @@ -1,13 +1,10 @@ package dev.zontreck.libzontreck.events; import dev.zontreck.ariaslib.terminal.Task; -import dev.zontreck.ariaslib.util.DelayedExecutorService; import dev.zontreck.libzontreck.LibZontreck; import dev.zontreck.libzontreck.exceptions.InvalidSideException; -import dev.zontreck.libzontreck.memory.PlayerContainer; -import dev.zontreck.libzontreck.networking.ModMessages; +import dev.zontreck.libzontreck.memory.player.PlayerContainer; import dev.zontreck.libzontreck.networking.packets.S2CServerAvailable; -import dev.zontreck.libzontreck.networking.packets.S2CWalletInitialSyncPacket; import dev.zontreck.libzontreck.profiles.Profile; import dev.zontreck.libzontreck.profiles.UserProfileNotYetExistsException; import dev.zontreck.libzontreck.util.ServerUtilities; diff --git a/src/main/java/dev/zontreck/libzontreck/memory/PlayerComponent.java b/src/main/java/dev/zontreck/libzontreck/memory/player/PlayerComponent.java similarity index 94% rename from src/main/java/dev/zontreck/libzontreck/memory/PlayerComponent.java rename to src/main/java/dev/zontreck/libzontreck/memory/player/PlayerComponent.java index 886ae53..3c86f7e 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/PlayerComponent.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/player/PlayerComponent.java @@ -1,8 +1,7 @@ -package dev.zontreck.libzontreck.memory; +package dev.zontreck.libzontreck.memory.player; import java.util.UUID; -import dev.zontreck.libzontreck.LibZontreck; import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; import dev.zontreck.libzontreck.vectors.WorldPosition; import net.minecraft.nbt.CompoundTag; diff --git a/src/main/java/dev/zontreck/libzontreck/memory/PlayerContainer.java b/src/main/java/dev/zontreck/libzontreck/memory/player/PlayerContainer.java similarity index 87% rename from src/main/java/dev/zontreck/libzontreck/memory/PlayerContainer.java rename to src/main/java/dev/zontreck/libzontreck/memory/player/PlayerContainer.java index 218fb30..fcbaa38 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/PlayerContainer.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/player/PlayerContainer.java @@ -1,8 +1,7 @@ -package dev.zontreck.libzontreck.memory; +package dev.zontreck.libzontreck.memory.player; import java.util.UUID; -import dev.zontreck.libzontreck.LibZontreck; import net.minecraft.nbt.CompoundTag; import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.server.ServerLifecycleHooks; diff --git a/src/main/java/dev/zontreck/libzontreck/memory/VolatilePlayerStorage.java b/src/main/java/dev/zontreck/libzontreck/memory/player/VolatilePlayerStorage.java similarity index 97% rename from src/main/java/dev/zontreck/libzontreck/memory/VolatilePlayerStorage.java rename to src/main/java/dev/zontreck/libzontreck/memory/player/VolatilePlayerStorage.java index d1abae8..630f35e 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/VolatilePlayerStorage.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/player/VolatilePlayerStorage.java @@ -1,4 +1,4 @@ -package dev.zontreck.libzontreck.memory; +package dev.zontreck.libzontreck.memory.player; import java.util.ArrayList; import java.util.Iterator; diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java b/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java new file mode 100644 index 0000000..45cfa62 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java @@ -0,0 +1,12 @@ +package dev.zontreck.libzontreck.memory.world; + +import net.minecraft.nbt.CompoundTag; + +public class SavedBlock +{ + private CompoundTag blockState; + private CompoundTag blockEntity; + private boolean hasBlockEntity; + + +} From 99e38893cab9c9224bc38575c847c88d5c0e8448 Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 9 Apr 2024 14:23:03 -0700 Subject: [PATCH 32/88] Do more work on implementing a new saved block system --- .../memory/world/PrimitiveBlock.java | 62 ++++++++++++ .../libzontreck/memory/world/SavedBlock.java | 95 +++++++++++++++++++ 2 files changed, 157 insertions(+) create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/PrimitiveBlock.java diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/PrimitiveBlock.java b/src/main/java/dev/zontreck/libzontreck/memory/world/PrimitiveBlock.java new file mode 100644 index 0000000..dff455f --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/PrimitiveBlock.java @@ -0,0 +1,62 @@ +package dev.zontreck.libzontreck.memory.world; + +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; + +public class PrimitiveBlock +{ + public SavedBlock block; + public Block blockType; + public BlockState blockState; + public CompoundTag blockEntity; + public BlockPos position; + public ServerLevel level; + + /** + * Alias method + * @see SavedBlock#serialize() + * @return NBT Tag + */ + public CompoundTag serialize() + { + return block.serialize(); + } + + /** + * Alias Method + * @see SavedBlock#deserialize(CompoundTag) + * @see SavedBlock#getBlockPrimitive() + * @param tag NBT Tag + * @return A Primitive Block + */ + public static PrimitiveBlock deserialize(CompoundTag tag) + { + return SavedBlock.deserialize(tag).getBlockPrimitive(); + } + + /** + * Compare a block with this primitive block + * @param block The block type to compare + * @param state The block state to compare + * @param entity The block entity to compare + * @return True if identical + */ + public boolean is(Block block, BlockState state, BlockEntity entity) + { + if(block == this.blockType) + { + // Check the block state + if(this.blockState.equals(state)) + { + if(blockEntity == null) return true; // Not all blocks have a block entity. + if(blockEntity.equals(entity.serializeNBT())){ + return true; + }else return false; + } else return false; + }else return false; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java b/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java index 45cfa62..7c9e928 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java @@ -1,12 +1,107 @@ package dev.zontreck.libzontreck.memory.world; +import dev.zontreck.libzontreck.api.Vector3; +import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; +import dev.zontreck.libzontreck.vectors.Vector3i; +import dev.zontreck.libzontreck.vectors.WorldPosition; +import net.minecraft.core.BlockPos; +import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.entity.BlockEntity; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.registries.ForgeRegistries; +import net.minecraftforge.server.ServerLifecycleHooks; public class SavedBlock { private CompoundTag blockState; private CompoundTag blockEntity; private boolean hasBlockEntity; + private WorldPosition position; + /** + * Take a snapshot of the block, and save as primitive type SavedBlock + * @param position The block's position + * @param level The level the position relates to + * @return A instance of the saved block + */ + public static SavedBlock takeSnapshot(Vector3 position, Level level) + { + SavedBlock savedBlock = new SavedBlock(); + BlockPos pos = position.asBlockPos(); + + BlockState state = level.getBlockState(pos); + savedBlock.blockState = NbtUtils.writeBlockState(state); + BlockEntity entity = level.getBlockEntity(pos); + if(entity == null) + { + savedBlock.hasBlockEntity = false; + }else { + savedBlock.hasBlockEntity = true; + savedBlock.blockEntity = entity.serializeNBT(); + } + + savedBlock.position = new WorldPosition(position.asVector3d(), (ServerLevel) level); + + return savedBlock; + } + + /** + * Saves the stored block as a NBT Tag + * @return CompoundTag + */ + public CompoundTag serialize() + { + CompoundTag tag = new CompoundTag(); + tag.put("state", blockState); + if(hasBlockEntity) tag.put("entity", blockEntity); + + tag.put("position", position.serialize()); + + return tag; + } + + /** + * Reads a NBT Tag that represents a stored block, and returns the StoredBlock object + * @param tag Saved NBT + * @return SavedBlock instance + */ + public static SavedBlock deserialize(CompoundTag tag) + { + SavedBlock savedBlock = new SavedBlock(); + savedBlock.blockState = tag.getCompound("state"); + if(tag.contains("entity")) { + savedBlock.blockEntity = tag.getCompound("entity"); + savedBlock.hasBlockEntity=true; + } + + try { + savedBlock.position = new WorldPosition(tag.getCompound("position"), false); + } catch (InvalidDeserialization e) { + throw new RuntimeException(e); + } + + return savedBlock; + } + + public PrimitiveBlock getBlockPrimitive() + { + PrimitiveBlock prim = new PrimitiveBlock(); + ServerLevel level = position.getActualDimension(); + + BlockState state = NbtUtils.readBlockState(level.holderLookup(Registries.BLOCK), blockState); + prim.blockState = state; + prim.block = this; + prim.blockType = state.getBlock(); + prim.blockEntity = blockEntity; + prim.position = position.Position.asBlockPos(); + prim.level = level; + + return prim; + } } From 3302ab14b8941435dd9346fbc2b01f3e367c22c9 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 11 Apr 2024 00:47:31 -0700 Subject: [PATCH 33/88] Start to add in a block queue system to allow my other mods to have separate queues for different things. --- .../memory/world/BlockRestoreQueue.java | 44 +++++++++++++++++++ .../memory/world/PrimitiveBlock.java | 28 +++++++----- .../libzontreck/memory/world/SavedBlock.java | 13 +----- 3 files changed, 63 insertions(+), 22 deletions(-) create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java new file mode 100644 index 0000000..53ede6b --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java @@ -0,0 +1,44 @@ +package dev.zontreck.libzontreck.memory.world; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.block.Block; + +import java.util.ArrayList; +import java.util.List; + +public abstract class BlockRestoreQueue +{ + private List BLOCK_QUEUE = new ArrayList<>(); + + /** + * Returns the restore queue name + * @return Name of the restore queue + */ + public abstract String getRestoreQueueName(); + + /** + * @return The number of blocks remaining in this queue + */ + public int getQueuedBlocks() + { + return BLOCK_QUEUE.size(); + } + + /** + * Queues a block to be restored + * @param block + */ + public void enqueueBlock(SavedBlock block) + { + BLOCK_QUEUE.add(block.getBlockPrimitive()); + } + + /** + * Queues a block to be restored + * @param block + */ + public void enqueueBlock(PrimitiveBlock block) + { + BLOCK_QUEUE.add(block); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/PrimitiveBlock.java b/src/main/java/dev/zontreck/libzontreck/memory/world/PrimitiveBlock.java index dff455f..7d11970 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/PrimitiveBlock.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/PrimitiveBlock.java @@ -9,13 +9,22 @@ import net.minecraft.world.level.block.state.BlockState; public class PrimitiveBlock { - public SavedBlock block; - public Block blockType; - public BlockState blockState; - public CompoundTag blockEntity; - public BlockPos position; - public ServerLevel level; + public final SavedBlock savedBlock; + public final Block blockType; + public final BlockState blockState; + public final CompoundTag blockEntity; + public final BlockPos position; + public final ServerLevel level; + public PrimitiveBlock(SavedBlock savedBlock, Block blockType, BlockState blockState, CompoundTag blockEntity, BlockPos position, ServerLevel level) + { + this.savedBlock = savedBlock; + this.blockType = blockType; + this.blockEntity = blockEntity; + this.position = position; + this.level = level; + this.blockState = blockState; + } /** * Alias method * @see SavedBlock#serialize() @@ -23,7 +32,7 @@ public class PrimitiveBlock */ public CompoundTag serialize() { - return block.serialize(); + return savedBlock.serialize(); } /** @@ -40,14 +49,13 @@ public class PrimitiveBlock /** * Compare a block with this primitive block - * @param block The block type to compare * @param state The block state to compare * @param entity The block entity to compare * @return True if identical */ - public boolean is(Block block, BlockState state, BlockEntity entity) + public boolean is(BlockState state, BlockEntity entity) { - if(block == this.blockType) + if(state.getBlock() == this.blockType) { // Check the block state if(this.blockState.equals(state)) diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java b/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java index 7c9e928..652e2a1 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java @@ -2,19 +2,15 @@ package dev.zontreck.libzontreck.memory.world; import dev.zontreck.libzontreck.api.Vector3; import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; -import dev.zontreck.libzontreck.vectors.Vector3i; import dev.zontreck.libzontreck.vectors.WorldPosition; import net.minecraft.core.BlockPos; import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtUtils; -import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; -import net.minecraftforge.registries.ForgeRegistries; -import net.minecraftforge.server.ServerLifecycleHooks; public class SavedBlock { @@ -90,18 +86,11 @@ public class SavedBlock public PrimitiveBlock getBlockPrimitive() { - PrimitiveBlock prim = new PrimitiveBlock(); ServerLevel level = position.getActualDimension(); BlockState state = NbtUtils.readBlockState(level.holderLookup(Registries.BLOCK), blockState); - prim.blockState = state; - prim.block = this; - prim.blockType = state.getBlock(); - prim.blockEntity = blockEntity; - prim.position = position.Position.asBlockPos(); - prim.level = level; - return prim; + return new PrimitiveBlock(this, state.getBlock(), state, blockEntity, position.Position.asBlockPos(), level); } } From c03261fd7aa5f5766d0cfd19b22f7a526fbf468d Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 11 Apr 2024 01:21:13 -0700 Subject: [PATCH 34/88] LZ-17: Add initial block restore queue registry, and a registration event --- .../BlockRestoreQueueRegistrationEvent.java | 17 +++++++++++++++ .../world/BlockRestoreQueueRegistry.java | 21 +++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 src/main/java/dev/zontreck/libzontreck/events/BlockRestoreQueueRegistrationEvent.java create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java diff --git a/src/main/java/dev/zontreck/libzontreck/events/BlockRestoreQueueRegistrationEvent.java b/src/main/java/dev/zontreck/libzontreck/events/BlockRestoreQueueRegistrationEvent.java new file mode 100644 index 0000000..151f007 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/events/BlockRestoreQueueRegistrationEvent.java @@ -0,0 +1,17 @@ +package dev.zontreck.libzontreck.events; + +import dev.zontreck.libzontreck.memory.world.BlockRestoreQueue; +import dev.zontreck.libzontreck.memory.world.BlockRestoreQueueRegistry; +import net.minecraftforge.eventbus.api.Event; + +public class BlockRestoreQueueRegistrationEvent extends Event +{ + /** + * Registers the provided queue to be able to be ticked + * @param queue + */ + public void register(BlockRestoreQueue queue) + { + BlockRestoreQueueRegistry.addQueue(queue); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java new file mode 100644 index 0000000..7adf988 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java @@ -0,0 +1,21 @@ +package dev.zontreck.libzontreck.memory.world; + +import java.util.*; + +/** + * DANGER: DO NOT USE THIS CLASS DIRECTLY + */ +public class BlockRestoreQueueRegistry +{ + private static Map QUEUES = new HashMap<>(); + + /** + * Internal use only + * + * @see dev.zontreck.libzontreck.events.BlockRestoreQueueRegistrationEvent + * @param queue The queue to register + */ + public static void addQueue(BlockRestoreQueue queue) { + QUEUES.put(queue.getRestoreQueueName(), queue); + } +} From 01f269e123d4216a45d05bfa174d076b3282157a Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 11 Apr 2024 03:04:26 -0700 Subject: [PATCH 35/88] #17: Start implementing more of the block restore and queue systems. --- .../memory/world/BlockRestore.java | 14 +++++ .../memory/world/BlockRestoreQueue.java | 58 ++++++++++++++++++- .../memory/world/BlockRestoreRunner.java | 15 +++++ .../memory/world/SortedBlockQueue.java | 54 +++++++++++++++++ .../libzontreck/util/PositionUtil.java | 4 +- 5 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestore.java create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/SortedBlockQueue.java diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestore.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestore.java new file mode 100644 index 0000000..a3d4d20 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestore.java @@ -0,0 +1,14 @@ +package dev.zontreck.libzontreck.memory.world; + +public class BlockRestore extends BlockRestoreQueue +{ + @Override + public String getRestoreQueueName() { + return "BasicBlockSnapshots"; + } + + @Override + public void notifyDirtyQueue(boolean blockAdded) { + return; // We dont care. This is a basic queue + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java index 53ede6b..a322007 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java @@ -30,7 +30,7 @@ public abstract class BlockRestoreQueue */ public void enqueueBlock(SavedBlock block) { - BLOCK_QUEUE.add(block.getBlockPrimitive()); + enqueueBlock(block.getBlockPrimitive()); } /** @@ -40,5 +40,61 @@ public abstract class BlockRestoreQueue public void enqueueBlock(PrimitiveBlock block) { BLOCK_QUEUE.add(block); + + notifyDirtyQueue(true); + } + + /** + * Executed when the queue is modified. + * @param blockAdded Whether a block was added or removed from the queue + */ + public abstract void notifyDirtyQueue(boolean blockAdded); + + /** + * Pops a block off the queue, and returns it + * @return A PrimitiveBlock instance of the SavedBlock + */ + public PrimitiveBlock getNextBlock() + { + PrimitiveBlock blk = BLOCK_QUEUE.get(0); + BLOCK_QUEUE.remove(0); + notifyDirtyQueue(false); + return blk; + } + + /** + * Clears the entire queue, discarding the saved blocks permanently. + */ + public void clear() + { + BLOCK_QUEUE.clear(); + notifyDirtyQueue(false); + } + + /** + * Returns the raw block queue instance + * @return + */ + public List getQueue() { + return BLOCK_QUEUE; + } + + /** + * Sets the block queue, without notifying listeners + * @param queue + */ + public void setQueueNoNotify(List queue) + { + BLOCK_QUEUE = queue; + } + + /** + * Sets the block queue, and notifies any listeners that blocks were potentially added + * @param queue + */ + public void setQueue(List queue) + { + BLOCK_QUEUE = queue; + notifyDirtyQueue(true); } } diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java new file mode 100644 index 0000000..bcfa76b --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java @@ -0,0 +1,15 @@ +package dev.zontreck.libzontreck.memory.world; + +public class BlockRestoreRunner implements Runnable +{ + private BlockRestoreQueue queue; + + @Override + public void run() { + if(queue.getQueuedBlocks() == 0) return; // We'll be queued back up later + + PrimitiveBlock prim = queue.getNextBlock(); + + + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/SortedBlockQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/SortedBlockQueue.java new file mode 100644 index 0000000..efe7f65 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/SortedBlockQueue.java @@ -0,0 +1,54 @@ +package dev.zontreck.libzontreck.memory.world; + +import dev.zontreck.libzontreck.api.Vector3; +import dev.zontreck.libzontreck.util.PositionUtil; +import dev.zontreck.libzontreck.vectors.Vector3i; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +public class SortedBlockQueue extends BlockRestoreQueue +{ + @Override + public String getRestoreQueueName() { + return "SortedBlockQueue"; + } + + @Override + public void notifyDirtyQueue(boolean blockAdded) { + if(blockAdded) { + // Perform sorting + + List queue = getQueue(); + List positions = new ArrayList<>(); + + Iterator it = queue.iterator(); + + while(it.hasNext()) { + PrimitiveBlock blk = it.next(); + positions.add(new Vector3i(blk.position)); + } + + positions = PositionUtil.sortAscending(positions); + + List retQueue = new ArrayList<>(); + + it = queue.iterator(); + for(Vector3 pos : positions) { + it = queue.iterator(); + while(it.hasNext()) { + PrimitiveBlock blk = it.next(); + if(blk.position.equals(pos.asBlockPos())) { + retQueue.add(blk); + it.remove(); + break; + } + } + } + + setQueueNoNotify(retQueue); + + } + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/util/PositionUtil.java b/src/main/java/dev/zontreck/libzontreck/util/PositionUtil.java index fdcb9f6..39ee999 100644 --- a/src/main/java/dev/zontreck/libzontreck/util/PositionUtil.java +++ b/src/main/java/dev/zontreck/libzontreck/util/PositionUtil.java @@ -14,7 +14,7 @@ import java.util.List; public class PositionUtil { - public List makeCube(Vector3 p1, Vector3 p2) + public static List makeCube(Vector3 p1, Vector3 p2) { List vecs = new ArrayList<>(); Vector3 work = new Vector3d(); @@ -114,7 +114,7 @@ public class PositionUtil } - public List sortAscending(List vecs) + public static List sortAscending(List vecs) { List copy = new ArrayList<>(vecs); From 7472db98d55975468c5fa6318f9d704c6a415653 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 11 Apr 2024 03:11:55 -0700 Subject: [PATCH 36/88] Adds the block restore code to the Runner --- .../memory/world/BlockRestoreRunner.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java index bcfa76b..118299e 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java @@ -1,8 +1,20 @@ package dev.zontreck.libzontreck.memory.world; +import dev.zontreck.libzontreck.vectors.Vector3d; +import net.minecraft.core.BlockPos; +import net.minecraft.sounds.SoundEvent; +import net.minecraft.sounds.SoundEvents; +import net.minecraft.sounds.SoundSource; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.entity.BlockEntity; + +import java.util.Random; + public class BlockRestoreRunner implements Runnable { private BlockRestoreQueue queue; + public final SoundEvent pop = SoundEvents.ITEM_PICKUP; @Override public void run() { @@ -10,6 +22,23 @@ public class BlockRestoreRunner implements Runnable PrimitiveBlock prim = queue.getNextBlock(); + Level level = prim.level; + + // Everything is restored, play sound + SoundSource ss = SoundSource.NEUTRAL; + BlockPos pos = prim.position; + Random rng = new Random(); + + level.playSound(null, pos, pop, ss, rng.nextFloat(0.75f,1.0f), rng.nextFloat(1)); + + level.setBlock(pos, prim.blockState, Block.UPDATE_NONE, 0); + + BlockEntity entity = level.getBlockEntity(pos); + if(entity != null) + { + entity.load(prim.blockEntity); + } + } } From 059055044d86b68a75b069f2d288497ca98a5781 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 11 Apr 2024 03:15:51 -0700 Subject: [PATCH 37/88] Last commit for the night. Hook up some more components of the snapshotting system --- .../memory/world/BlockRestoreQueue.java | 15 +++++++++++++++ .../memory/world/BlockRestoreRunner.java | 5 +++++ 2 files changed, 20 insertions(+) diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java index a322007..2eeff38 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java @@ -9,6 +9,12 @@ import java.util.List; public abstract class BlockRestoreQueue { private List BLOCK_QUEUE = new ArrayList<>(); + private BlockRestoreRunner RUNNER; + + public BlockRestoreQueue() + { + RUNNER = new BlockRestoreRunner(this); + } /** * Returns the restore queue name @@ -97,4 +103,13 @@ public abstract class BlockRestoreQueue BLOCK_QUEUE = queue; notifyDirtyQueue(true); } + + /** + * Executes a tick. Spawns a thread which will modify 1 block from the queue + */ + public void tick() + { + Thread tx = new Thread(RUNNER); + tx.start(); + } } diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java index 118299e..d569751 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java @@ -13,6 +13,11 @@ import java.util.Random; public class BlockRestoreRunner implements Runnable { + public BlockRestoreRunner(BlockRestoreQueue queue) + { + this.queue = queue; + } + private BlockRestoreQueue queue; public final SoundEvent pop = SoundEvents.ITEM_PICKUP; From 7d924f77404ab42c531e05dcae436151ea7b3516 Mon Sep 17 00:00:00 2001 From: zontreck Date: Fri, 12 Apr 2024 07:52:00 -0700 Subject: [PATCH 38/88] LZ-#17 - Finish initial implementation --- .../dev/zontreck/libzontreck/api/Vector2.java | 2 +- .../dev/zontreck/libzontreck/api/Vector3.java | 2 +- .../memory/world/BlockRestoreRunner.java | 2 +- .../memory/world/PrimitiveBlock.java | 10 + .../memory/world/SaveDataCoordinates.java | 20 ++ .../memory/world/SaveDataFactory.java | 201 ++++++++++++++++++ .../libzontreck/memory/world/SavedBlock.java | 18 +- .../memory/world/SortedBlockQueue.java | 34 +-- .../libzontreck/vectors/Vector2f.java | 15 ++ .../libzontreck/vectors/Vector2i.java | 15 ++ .../libzontreck/vectors/Vector3d.java | 16 ++ .../libzontreck/vectors/Vector3i.java | 16 ++ .../libzontreck/vectors/WorldPosition.java | 16 +- 13 files changed, 336 insertions(+), 31 deletions(-) create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataCoordinates.java create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataFactory.java diff --git a/src/main/java/dev/zontreck/libzontreck/api/Vector2.java b/src/main/java/dev/zontreck/libzontreck/api/Vector2.java index a0645ec..e251c81 100644 --- a/src/main/java/dev/zontreck/libzontreck/api/Vector2.java +++ b/src/main/java/dev/zontreck/libzontreck/api/Vector2.java @@ -5,7 +5,7 @@ import dev.zontreck.libzontreck.vectors.Vector2i; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.phys.Vec2; -public interface Vector2 +public interface Vector2 extends Cloneable, Comparable> { /** * Converts the current Vector2 representation into a minecraft Vec2 diff --git a/src/main/java/dev/zontreck/libzontreck/api/Vector3.java b/src/main/java/dev/zontreck/libzontreck/api/Vector3.java index ce23cc1..ffca2a8 100644 --- a/src/main/java/dev/zontreck/libzontreck/api/Vector3.java +++ b/src/main/java/dev/zontreck/libzontreck/api/Vector3.java @@ -7,7 +7,7 @@ import net.minecraft.core.Vec3i; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.phys.Vec3; -public interface Vector3 +public interface Vector3 extends Cloneable, Comparable> { /** * Converts the current Vector3 representation into a minecraft Vec3 diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java index d569751..9ae8005 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java @@ -11,7 +11,7 @@ import net.minecraft.world.level.block.entity.BlockEntity; import java.util.Random; -public class BlockRestoreRunner implements Runnable +class BlockRestoreRunner implements Runnable { public BlockRestoreRunner(BlockRestoreQueue queue) { diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/PrimitiveBlock.java b/src/main/java/dev/zontreck/libzontreck/memory/world/PrimitiveBlock.java index 7d11970..7cfef67 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/PrimitiveBlock.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/PrimitiveBlock.java @@ -25,6 +25,7 @@ public class PrimitiveBlock this.level = level; this.blockState = blockState; } + /** * Alias method * @see SavedBlock#serialize() @@ -67,4 +68,13 @@ public class PrimitiveBlock } else return false; }else return false; } + + /** + * Clones the PrimitiveBlock into a new instance + * @return + */ + public PrimitiveBlock copy() + { + return savedBlock.clone().getBlockPrimitive(); + } } diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataCoordinates.java b/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataCoordinates.java new file mode 100644 index 0000000..2d652b0 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataCoordinates.java @@ -0,0 +1,20 @@ +package dev.zontreck.libzontreck.memory.world; + +import net.minecraft.core.BlockPos; + +public class SaveDataCoordinates +{ + public int X; + public int Z; + + public SaveDataCoordinates(BlockPos pos) + { + int X = pos.getX() >> 4 >> 5; + int Z = pos.getZ() >> 4 >> 5; + } + + public String getFileName() + { + return "r." + X + "." + Z + ".dat"; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataFactory.java b/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataFactory.java new file mode 100644 index 0000000..d4ac680 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataFactory.java @@ -0,0 +1,201 @@ +package dev.zontreck.libzontreck.memory.world; + +import dev.zontreck.libzontreck.LibZontreck; +import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.Tag; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.Level; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class SaveDataFactory +{ + static Map datas = new HashMap<>(); + + protected static boolean hasSaveDataInstance(SaveDataFile file) + { + return datas.containsKey(file.getSaveDataPath().toString()); + } + + protected static SaveData getSaveDataInstance(SaveDataFile file) + { + return datas.get(file.getSaveDataPath().toString()); + } + + public static Builder builder() + { + return new Builder(); + } + static class Builder + { + private String modID = "minecraft"; + private String levelName; + private String queueID; + private boolean DBMode; + private BlockPos position; + + public Builder withDimension(Level level) + { + ResourceLocation lv = level.dimension().location(); + this.modID = lv.getNamespace(); + this.levelName = lv.getPath(); + + return this; + } + + public Builder withQueueID(BlockRestoreQueue queue) + { + queueID = queue.getRestoreQueueName(); + + return this; + } + + public Builder withDatabaseMode() + { + DBMode=true; + + return this; + } + + public Builder withPosition(BlockPos pos) + { + position = pos; + + return this; + } + + public SaveDataFile build() + { + return new SaveDataFile(modID, levelName, queueID, DBMode, position); + } + } + + static class SaveDataFile + { + String mod; + String dimension; + String queue; + boolean database; + BlockPos position; + + public SaveDataFile(String modID, String levelName, String queueID, boolean DBMode, BlockPos pos) + { + mod = modID; + dimension = levelName; + queue = queueID; + database = DBMode; + position = pos; + } + + public Path getSaveDataPath() + { + Path path = LibZontreck.BASE_CONFIG.resolve("block_snapshots"); + if(mod != null) path = path.resolve(mod); + if(dimension != null) path = path.resolve(dimension); + if(queue != null) path = path.resolve(queue); + + path.toFile().mkdirs(); + + SaveDataCoordinates coordinates = new SaveDataCoordinates(position); + path = path.resolve(coordinates.getFileName()); + return path; + } + + /** + * Reads the save data, or initializes a new instance. + *

+ * Additionally, this will check for a pre-existing POJO instance and return if it exists. + * @return + * @throws IOException + */ + public SaveData getInstance() throws IOException { + if(SaveDataFactory.hasSaveDataInstance(this)) return SaveDataFactory.getSaveDataInstance(this); + Path data = getSaveDataPath(); + if(data.toFile().exists()) + { + CompoundTag tag = NbtIo.read(data.toFile()); + + return SaveData.deserialize(tag, this); + } else { + + return new SaveData(this); + } + } + } + + static class SaveData { + SaveDataFile myFile; + public static final String TAG_SAVED_BLOCKS = "sb"; + + public List blocks = new ArrayList<>(); + + public SaveData(SaveDataFile file) + { + myFile = file; + } + + /** + * Read a NBT Tag and reconstruct the SaveData POJO + * @param tag + * @param file + * @return + */ + public static SaveData deserialize(CompoundTag tag, SaveDataFile file) { + SaveData data = new SaveData(file); + ListTag lst = tag.getList(TAG_SAVED_BLOCKS, ListTag.TAG_COMPOUND); + for(Tag xTag : lst) + { + if(xTag instanceof CompoundTag ct) + { + SavedBlock sb = SavedBlock.deserialize(ct); + data.blocks.add(sb); + } + } + + return data; + } + + /** + * Write the current save data to NBT + * @return + */ + public CompoundTag serialize() + { + CompoundTag tag = new CompoundTag(); + ListTag lst = new ListTag(); + for(SavedBlock block : blocks) + { + lst.add(block.serialize()); + } + + tag.put(TAG_SAVED_BLOCKS, lst); + return tag; + } + + /** + * Imports a full queue to the save data file. + * ! WARNING ! This method will overwrite the SaveDataFile's Queue ID + * @param queue Queue to import + * @return The current SaveData instance + */ + public SaveData importQueue(BlockRestoreQueue queue) + { + for(PrimitiveBlock blk : queue.getQueue()) + { + blocks.add(blk.savedBlock); + } + + myFile.queue = queue.getRestoreQueueName(); + return this; + } + + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java b/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java index 652e2a1..539d890 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/SavedBlock.java @@ -12,7 +12,7 @@ import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; -public class SavedBlock +public class SavedBlock implements Cloneable { private CompoundTag blockState; private CompoundTag blockEntity; @@ -93,4 +93,20 @@ public class SavedBlock return new PrimitiveBlock(this, state.getBlock(), state, blockEntity, position.Position.asBlockPos(), level); } + @Override + public SavedBlock clone() { + try { + SavedBlock clone = (SavedBlock) super.clone(); + if(blockEntity != null) + clone.blockEntity = blockEntity.copy(); + if(blockState != null) + clone.blockState = blockState.copy(); + if(position != null) + clone.position = position.clone(); + + return clone; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } } diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/SortedBlockQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/SortedBlockQueue.java index efe7f65..8cced50 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/SortedBlockQueue.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/SortedBlockQueue.java @@ -5,6 +5,7 @@ import dev.zontreck.libzontreck.util.PositionUtil; import dev.zontreck.libzontreck.vectors.Vector3i; import java.util.ArrayList; +import java.util.Comparator; import java.util.Iterator; import java.util.List; @@ -14,41 +15,22 @@ public class SortedBlockQueue extends BlockRestoreQueue public String getRestoreQueueName() { return "SortedBlockQueue"; } - @Override public void notifyDirtyQueue(boolean blockAdded) { if(blockAdded) { - // Perform sorting - List queue = getQueue(); - List positions = new ArrayList<>(); + List retQueue = new ArrayList<>(queue.size()); - Iterator it = queue.iterator(); + // Sort the queue based on block positions + queue.sort(Comparator.comparing(block -> new Vector3i(block.position))); - while(it.hasNext()) { - PrimitiveBlock blk = it.next(); - positions.add(new Vector3i(blk.position)); - } - - positions = PositionUtil.sortAscending(positions); - - List retQueue = new ArrayList<>(); - - it = queue.iterator(); - for(Vector3 pos : positions) { - it = queue.iterator(); - while(it.hasNext()) { - PrimitiveBlock blk = it.next(); - if(blk.position.equals(pos.asBlockPos())) { - retQueue.add(blk); - it.remove(); - break; - } - } + // Add blocks in sorted order to the new queue + for (PrimitiveBlock blk : queue) { + retQueue.add(blk.copy()); // Copy block if necessary } setQueueNoNotify(retQueue); - } } + } diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2f.java b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2f.java index 112686c..a6c33ed 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2f.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2f.java @@ -3,6 +3,7 @@ package dev.zontreck.libzontreck.vectors; import dev.zontreck.libzontreck.api.Vector2; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.phys.Vec2; +import org.jetbrains.annotations.NotNull; public class Vector2f implements Vector2 { @@ -182,4 +183,18 @@ public class Vector2f implements Vector2 public Float getY() { return y; } + + @Override + public int compareTo(@NotNull Vector2 other) { + if(other instanceof Vector2f v2f){ + + // Compare x coordinates first + int cmp = Float.compare(this.x, v2f.x); + if (cmp != 0) { + return cmp; + } + // If x coordinates are equal, compare y coordinates + return Float.compare(this.y, v2f.y); + } else return -1; + } } diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2i.java b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2i.java index a812be6..b5e90a7 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/Vector2i.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/Vector2i.java @@ -3,6 +3,7 @@ package dev.zontreck.libzontreck.vectors; import dev.zontreck.libzontreck.api.Vector2; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.phys.Vec2; +import org.jetbrains.annotations.NotNull; public class Vector2i implements Vector2 { @@ -195,4 +196,18 @@ public class Vector2i implements Vector2 public Integer getY() { return y; } + + @Override + public int compareTo(@NotNull Vector2 other) { + if(other instanceof Vector2i v2i){ + + // Compare x coordinates first + int cmp = Integer.compare(this.x, v2i.x); + if (cmp != 0) { + return cmp; + } + // If x coordinates are equal, compare y coordinates + return Integer.compare(this.y, v2i.y); + } else return -1; + } } diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Vector3d.java b/src/main/java/dev/zontreck/libzontreck/vectors/Vector3d.java index eaaa44e..6f1477c 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/Vector3d.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/Vector3d.java @@ -9,6 +9,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Vec3i; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.NotNull; public class Vector3d implements Vector3 { @@ -234,4 +235,19 @@ public class Vector3d implements Vector3 public Double getZ() { return z; } + + @Override + public int compareTo(@NotNull Vector3 doubleVector3) { + if(doubleVector3 instanceof Vector3d v3d) + { + int Ycomp = Double.compare(y, v3d.y); + if(Ycomp!=0)return Ycomp; + int Zcomp = Double.compare(z, v3d.z); + if(Zcomp!=0)return Zcomp; + int Xcomp = Double.compare(x, v3d.x); + if(Xcomp!=0)return Xcomp; + + return 0; + } else return -1; + } } diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/Vector3i.java b/src/main/java/dev/zontreck/libzontreck/vectors/Vector3i.java index 5efecaf..4cc52b8 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/Vector3i.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/Vector3i.java @@ -6,6 +6,7 @@ import net.minecraft.core.BlockPos; import net.minecraft.core.Vec3i; import net.minecraft.nbt.CompoundTag; import net.minecraft.world.phys.Vec3; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; @@ -227,4 +228,19 @@ public class Vector3i implements Vector3 public Integer getZ() { return z; } + + @Override + public int compareTo(@NotNull Vector3 integerVector3) { + if(integerVector3 instanceof Vector3i v3i) + { + int Ycomp = Integer.compare(y, v3i.y); + if(Ycomp!=0)return Ycomp; + int Zcomp = Integer.compare(z, v3i.z); + if(Zcomp!=0)return Zcomp; + int Xcomp = Integer.compare(x, v3i.x); + if(Xcomp!=0)return Xcomp; + + return 0; + } else return -1; + } } diff --git a/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java b/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java index add4d2b..3d62956 100644 --- a/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java +++ b/src/main/java/dev/zontreck/libzontreck/vectors/WorldPosition.java @@ -4,12 +4,14 @@ import dev.zontreck.libzontreck.LibZontreck; import dev.zontreck.libzontreck.exceptions.InvalidDeserialization; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtUtils; +import net.minecraft.network.protocol.game.ClientboundMoveEntityPacket; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraftforge.server.ServerLifecycleHooks; -public class WorldPosition { +public class WorldPosition implements Cloneable +{ public Vector3d Position; public String Dimension; @@ -125,4 +127,16 @@ public class WorldPosition { return pos; } + + @Override + public WorldPosition clone() { + try { + WorldPosition clone = (WorldPosition) super.clone(); + if(Position != null) + clone.Position = Position.Clone(); + return clone; + } catch (CloneNotSupportedException e) { + throw new AssertionError(); + } + } } From 25aa4b2f9d065b93c22f78fc7d534daabe13cb02 Mon Sep 17 00:00:00 2001 From: zontreck Date: Fri, 12 Apr 2024 07:52:46 -0700 Subject: [PATCH 39/88] Bump version number and push to build server to get a test version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index f526872..0a903e4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.040724.1951 +mod_version=1201.13.041224.0752 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html From 20a756277daab0435c7dd0d2a3b1b53fa2ecf9d8 Mon Sep 17 00:00:00 2001 From: zontreck Date: Fri, 12 Apr 2024 08:28:40 -0700 Subject: [PATCH 40/88] Bump version. Add event for restore queue registration --- gradle.properties | 2 +- .../java/dev/zontreck/libzontreck/LibZontreck.java | 3 +++ .../libzontreck/events/RegisterPacketsEvent.java | 6 +----- .../libzontreck/memory/world/BlockRestoreQueue.java | 11 +++++++++++ .../memory/world/BlockRestoreQueueRegistry.java | 9 +++++++++ .../zontreck/libzontreck/networking/ModMessages.java | 9 --------- 6 files changed, 25 insertions(+), 15 deletions(-) diff --git a/gradle.properties b/gradle.properties index 0a903e4..246ba34 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.041224.0752 +mod_version=1201.13.041224.0827 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java index 66a7af4..803d1f9 100644 --- a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java +++ b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java @@ -11,6 +11,7 @@ import java.util.UUID; import dev.zontreck.libzontreck.chestgui.ChestGUIRegistry; import dev.zontreck.libzontreck.currency.Bank; import dev.zontreck.libzontreck.currency.CurrencyHelper; +import dev.zontreck.libzontreck.events.BlockRestoreQueueRegistrationEvent; import dev.zontreck.libzontreck.items.ModItems; import dev.zontreck.libzontreck.menus.ChestGUIScreen; import dev.zontreck.libzontreck.types.ModMenuTypes; @@ -106,6 +107,8 @@ public class LibZontreck { { ALIVE=true; CURRENT_SIDE = LogicalSide.SERVER; + + MinecraftForge.EVENT_BUS.post(new BlockRestoreQueueRegistrationEvent()); } @SubscribeEvent diff --git a/src/main/java/dev/zontreck/libzontreck/events/RegisterPacketsEvent.java b/src/main/java/dev/zontreck/libzontreck/events/RegisterPacketsEvent.java index 34a126b..344d8d2 100644 --- a/src/main/java/dev/zontreck/libzontreck/events/RegisterPacketsEvent.java +++ b/src/main/java/dev/zontreck/libzontreck/events/RegisterPacketsEvent.java @@ -6,11 +6,7 @@ import java.util.List; import dev.zontreck.libzontreck.networking.packets.IPacket; import net.minecraftforge.eventbus.api.Event; -/** - * Used to register your packets with LibZontreck. Packets must extend IPacket and implement PacketSerializable. This is dispatched on both logical sides, and is considered a final event. It is not cancelable - * @see IPacket - */ +@Deprecated public class RegisterPacketsEvent extends Event { - public final List packets = new ArrayList<>(); } diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java index 2eeff38..11a2129 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java @@ -2,6 +2,8 @@ package dev.zontreck.libzontreck.memory.world; import net.minecraft.core.BlockPos; import net.minecraft.world.level.block.Block; +import net.minecraftforge.event.TickEvent; +import net.minecraftforge.eventbus.api.SubscribeEvent; import java.util.ArrayList; import java.util.List; @@ -112,4 +114,13 @@ public abstract class BlockRestoreQueue Thread tx = new Thread(RUNNER); tx.start(); } + + @SubscribeEvent + public void onTick(TickEvent.LevelTickEvent event) + { + if(event.phase == TickEvent.Phase.END) + { + tick(); + } + } } diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java index 7adf988..11e0cd4 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java @@ -18,4 +18,13 @@ public class BlockRestoreQueueRegistry public static void addQueue(BlockRestoreQueue queue) { QUEUES.put(queue.getRestoreQueueName(), queue); } + + /** + * Retrieves a registered restore queue by its name + * @param restoreQueueName Queue Name + * @return + */ + public static BlockRestoreQueue getQueue(String restoreQueueName) { + return QUEUES.get(restoreQueueName); + } } diff --git a/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java b/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java index 002a9ac..2d8af87 100644 --- a/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java +++ b/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java @@ -36,16 +36,7 @@ public class ModMessages { .clientAcceptedVersions(s->true) .serverAcceptedVersions(s->true) .simpleChannel(); - - RegisterPacketsEvent event = new RegisterPacketsEvent(); - MinecraftForge.EVENT_BUS.post(event); - INSTANCE=net; - - for(IPacket packet : event.packets) - { - packet.register(net); - } net.messageBuilder(S2CPlaySoundPacket.class, PACKET_ID.getAndIncrement(), NetworkDirection.PLAY_TO_CLIENT) .decoder(S2CPlaySoundPacket::new) From a9885635c1941e92806818ff7aca5a336317ec0a Mon Sep 17 00:00:00 2001 From: zontreck Date: Fri, 12 Apr 2024 08:32:32 -0700 Subject: [PATCH 41/88] Remove old events that are not used anymore --- .../dev/zontreck/libzontreck/networking/NetworkEvents.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/dev/zontreck/libzontreck/networking/NetworkEvents.java b/src/main/java/dev/zontreck/libzontreck/networking/NetworkEvents.java index 13ef44a..d6f1eea 100644 --- a/src/main/java/dev/zontreck/libzontreck/networking/NetworkEvents.java +++ b/src/main/java/dev/zontreck/libzontreck/networking/NetworkEvents.java @@ -8,10 +8,4 @@ import net.minecraftforge.eventbus.api.SubscribeEvent; public class NetworkEvents { - @SubscribeEvent - public void onRegisterPackets(RegisterPacketsEvent ev) - { - ev.packets.add(new S2CWalletUpdatedPacket()); - ev.packets.add(new S2CWalletInitialSyncPacket()); - } } From b20e56a7d8f157c4729b3f3bbfbfb880a327128b Mon Sep 17 00:00:00 2001 From: zontreck Date: Fri, 12 Apr 2024 08:33:26 -0700 Subject: [PATCH 42/88] Fix compile error --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 246ba34..290d77a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.041224.0827 +mod_version=1201.13.041224.0832 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html From 40ce8774fb9472aa9c1526eb01ca24853e928565 Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 23 Apr 2024 15:40:43 -0700 Subject: [PATCH 43/88] More implementation of the block restore queue functionality --- .../dev/zontreck/libzontreck/LibZontreck.java | 11 +++ .../libzontreck/config/ServerConfig.java | 35 +++++++ .../config/sections/DatabaseSection.java | 91 +++++++++++++++++++ .../BlockRestoreQueueRegistrationEvent.java | 3 + .../memory/world/BlockRestoreQueue.java | 34 ++++++- .../world/BlockRestoreQueueRegistry.java | 35 +++++++ .../memory/world/DatabaseWrapper.java | 57 ++++++++++++ .../memory/world/SaveDataCoordinates.java | 27 ++++++ .../memory/world/SaveDataFactory.java | 76 +++++++++++++++- 9 files changed, 363 insertions(+), 6 deletions(-) create mode 100644 src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java create mode 100644 src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java diff --git a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java index 803d1f9..af3c290 100644 --- a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java +++ b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java @@ -9,14 +9,18 @@ import java.util.Map; import java.util.UUID; import dev.zontreck.libzontreck.chestgui.ChestGUIRegistry; +import dev.zontreck.libzontreck.config.ServerConfig; import dev.zontreck.libzontreck.currency.Bank; import dev.zontreck.libzontreck.currency.CurrencyHelper; import dev.zontreck.libzontreck.events.BlockRestoreQueueRegistrationEvent; import dev.zontreck.libzontreck.items.ModItems; +import dev.zontreck.libzontreck.memory.world.BlockRestoreQueue; +import dev.zontreck.libzontreck.memory.world.BlockRestoreQueueRegistry; import dev.zontreck.libzontreck.menus.ChestGUIScreen; import dev.zontreck.libzontreck.types.ModMenuTypes; import dev.zontreck.libzontreck.networking.NetworkEvents; import net.minecraft.client.gui.screens.MenuScreens; +import net.minecraft.server.level.ServerLevel; import org.slf4j.Logger; import com.mojang.logging.LogUtils; @@ -106,9 +110,16 @@ public class LibZontreck { public void onServerStarted(final ServerStartedEvent event) { ALIVE=true; + + ServerConfig.init(); CURRENT_SIDE = LogicalSide.SERVER; MinecraftForge.EVENT_BUS.post(new BlockRestoreQueueRegistrationEvent()); + + for(ServerLevel level : event.getServer().getAllLevels()) + { + // Queues have been registered, but we now need to initialize the queue's data from saveddata + } } @SubscribeEvent diff --git a/src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java b/src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java new file mode 100644 index 0000000..3d0c3b8 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java @@ -0,0 +1,35 @@ +package dev.zontreck.libzontreck.config; + +import dev.zontreck.libzontreck.LibZontreck; +import dev.zontreck.libzontreck.config.sections.DatabaseSection; +import dev.zontreck.libzontreck.util.SNbtIo; +import net.minecraft.nbt.CompoundTag; + +import java.nio.file.Path; + +public class ServerConfig +{ + public static final Path BASE = LibZontreck.BASE_CONFIG.resolve("server.snbt"); + + public static DatabaseSection database; + + public static void init() + { + if(BASE.toFile().exists()) + { + var config = SNbtIo.loadSnbt(BASE); + + database = DatabaseSection.deserialize(config.getCompound(DatabaseSection.TAG_NAME)); + } else { + database = new DatabaseSection(); + } + } + + public static void commit() + { + CompoundTag tag = new CompoundTag(); + tag.put(DatabaseSection.TAG_NAME, database.serialize()); + + SNbtIo.writeSnbt(BASE, tag); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java b/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java new file mode 100644 index 0000000..9787ecd --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java @@ -0,0 +1,91 @@ +package dev.zontreck.libzontreck.config.sections; + +import dev.zontreck.libzontreck.config.ServerConfig; +import net.minecraft.nbt.CompoundTag; + +public class DatabaseSection +{ + public static final String TAG_NAME = "database"; + public static final String TAG_USER = "username"; + public static final String TAG_PASSWORD = "password"; + public static final String TAG_HOST = "host"; + public static final String TAG_DATABASE = "database"; + public static final String TAG_VERSION = "rev"; + + + public String user = "root"; + public String password = ""; + public String host = "localhost:3306"; // IP:port + public String database = "savedBlocks"; + public int version; + + public static final int VERSION = 1; + + private boolean migrated=false; + + public static DatabaseSection deserialize(CompoundTag tag) + { + int ver = tag.getInt(TAG_VERSION); + DatabaseSection section = new DatabaseSection(); + section.doMigrations(ver, tag); + + + return section; + } + + public void doMigrations(int ver, CompoundTag tag) + { + switch (ver) + { + case 0: + { + ver++; + user = tag.getString(TAG_USER); + password = tag.getString(TAG_PASSWORD); + host = tag.getString(TAG_HOST); + database = tag.getString(TAG_DATABASE); + version = ver; + migrated=true; + break; + } + default:{ + if(migrated) + { + ServerConfig.commit(); + migrated=false; + return; + } + break; + } + } + + doMigrations(ver, tag); + } + + public DatabaseSection(){ + + } + + public DatabaseSection(String user, String password, String host, String database) + { + this.user = user; + this.password = password; + this.host = host; + this.database = database; + } + + public String getAsSQLFileName() + { + return database + ".sql"; + } + + public CompoundTag serialize() + { + CompoundTag tag = new CompoundTag(); + tag.putString(TAG_USER, user); + tag.putString(TAG_PASSWORD, password); + tag.putString(TAG_HOST, host); + tag.putString(TAG_DATABASE, database); + return tag; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/events/BlockRestoreQueueRegistrationEvent.java b/src/main/java/dev/zontreck/libzontreck/events/BlockRestoreQueueRegistrationEvent.java index 151f007..4ae35c5 100644 --- a/src/main/java/dev/zontreck/libzontreck/events/BlockRestoreQueueRegistrationEvent.java +++ b/src/main/java/dev/zontreck/libzontreck/events/BlockRestoreQueueRegistrationEvent.java @@ -2,6 +2,7 @@ package dev.zontreck.libzontreck.events; import dev.zontreck.libzontreck.memory.world.BlockRestoreQueue; import dev.zontreck.libzontreck.memory.world.BlockRestoreQueueRegistry; +import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.eventbus.api.Event; public class BlockRestoreQueueRegistrationEvent extends Event @@ -13,5 +14,7 @@ public class BlockRestoreQueueRegistrationEvent extends Event public void register(BlockRestoreQueue queue) { BlockRestoreQueueRegistry.addQueue(queue); + + MinecraftForge.EVENT_BUS.register(queue); } } diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java index 11a2129..46612a3 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java @@ -1,13 +1,21 @@ package dev.zontreck.libzontreck.memory.world; import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtIo; +import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.block.Block; +import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.common.Mod; +import java.io.File; +import java.io.IOException; import java.util.ArrayList; import java.util.List; + public abstract class BlockRestoreQueue { private List BLOCK_QUEUE = new ArrayList<>(); @@ -109,7 +117,7 @@ public abstract class BlockRestoreQueue /** * Executes a tick. Spawns a thread which will modify 1 block from the queue */ - public void tick() + private void tick() { Thread tx = new Thread(RUNNER); tx.start(); @@ -123,4 +131,28 @@ public abstract class BlockRestoreQueue tick(); } } + + /** + * Initialize a restore Queue for a specific level. This will load and import the blocks in the save data into this queue + * @param level The level to load for + * @throws IOException On failure to read a file + */ + public void initialize(ServerLevel level) throws IOException { + var file = SaveDataFactory.builder().withDimension(level).withQueueID(this).withPosition(null).build(); + + CompoundTag tag = NbtIo.read(file.getSaveDataPath().toFile()); + SaveDataFactory.SaveDataManifest manifest = SaveDataFactory.SaveDataManifest.deserialize(tag); + + List files = manifest.get(); + for(SaveDataCoordinates chunk : files) + { + var saved = SaveDataFactory.builder().withDimension(level).withQueueID(this).withPosition(chunk.toBlockPos()).build(); + var saveData = saved.getInstance(); + for(SavedBlock sb : saveData.blocks) + { + enqueueBlock(sb); + } + } + + } } diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java index 11e0cd4..57e18e0 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueueRegistry.java @@ -1,5 +1,9 @@ package dev.zontreck.libzontreck.memory.world; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.world.level.Level; + +import java.io.IOException; import java.util.*; /** @@ -27,4 +31,35 @@ public class BlockRestoreQueueRegistry public static BlockRestoreQueue getQueue(String restoreQueueName) { return QUEUES.get(restoreQueueName); } + + /** + * Returns a iterator for a list of all the queues. This cannot remove items from the main queue. + * @return + */ + public static Iterator getReadOnlyQueue() { + List queues = new ArrayList<>(); + queues.addAll(QUEUES.values()); + return queues.iterator(); + } + + /** + * Initialize a block restore queue. + *

+ * Block Restore Queues are level independent, but blocks are not. This loads the blocks saved for this queue in that particular level's hierarchy + * @param level The level to load the queue for + */ + public static void init(ServerLevel level) + { + + Iterator it = BlockRestoreQueueRegistry.getReadOnlyQueue(); + while(it.hasNext()) + { + BlockRestoreQueue queue = it.next(); + try { + queue.initialize(level); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + } } diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java new file mode 100644 index 0000000..18ace42 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java @@ -0,0 +1,57 @@ +package dev.zontreck.libzontreck.memory.world; + + +import java.sql.*; + +public class DatabaseWrapper { + private Connection connection; + + public DatabaseWrapper() { + connection = null; + } + + public void connect(String url, String username, String password) throws SQLException { + try { + // Try MariaDB JDBC driver + Class.forName("org.mariadb.jdbc.Driver"); + connection = DriverManager.getConnection("jdbc:mariadb://" + url, username, password); + } catch (ClassNotFoundException | SQLException e) { + // MariaDB not found or failed to connect, try MySQL + try { + Class.forName("com.mysql.cj.jdbc.Driver"); + connection = DriverManager.getConnection("jdbc:mysql://" + url, username, password); + } catch (ClassNotFoundException | SQLException ex) { + // MySQL not found or failed to connect, try SQLite + try { + Class.forName("org.sqlite.JDBC"); + connection = DriverManager.getConnection("jdbc:sqlite:" + url); + } catch (ClassNotFoundException | SQLException exc) { + throw new SQLException("Failed to connect to database: " + exc.getMessage()); + } + } + } + } + + public ResultSet executeQuery(String query) throws SQLException { + if (connection == null) { + throw new SQLException("Connection not established."); + } + Statement statement = connection.createStatement(); + return statement.executeQuery(query); + } + + public int executeUpdate(String statement) throws SQLException { + if (connection == null) { + throw new SQLException("Connection not established."); + } + Statement stmt = connection.createStatement(); + return stmt.executeUpdate(statement); + } + + public void disconnect() throws SQLException { + if (connection != null && !connection.isClosed()) { + connection.close(); + } + } + +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataCoordinates.java b/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataCoordinates.java index 2d652b0..0b95612 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataCoordinates.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataCoordinates.java @@ -1,20 +1,47 @@ package dev.zontreck.libzontreck.memory.world; import net.minecraft.core.BlockPos; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtUtils; +import net.minecraft.tags.BlockTags; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockBehaviour; +import net.minecraftforge.common.Tags; public class SaveDataCoordinates { public int X; public int Z; + private BlockPos source; public SaveDataCoordinates(BlockPos pos) { int X = pos.getX() >> 4 >> 5; int Z = pos.getZ() >> 4 >> 5; + + source = pos; } public String getFileName() { return "r." + X + "." + Z + ".dat"; } + + public BlockPos toBlockPos() + { + return source; + } + + public static final String TAG_COORDS = "sdc"; + public CompoundTag toNBT() + { + return NbtUtils.writeBlockPos(source); + } + + public static SaveDataCoordinates deserialize(CompoundTag tag) + { + BlockPos pos = NbtUtils.readBlockPos(tag); + + return new SaveDataCoordinates(pos); + } } diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataFactory.java b/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataFactory.java index d4ac680..0effe5f 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataFactory.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/SaveDataFactory.java @@ -1,11 +1,9 @@ package dev.zontreck.libzontreck.memory.world; import dev.zontreck.libzontreck.LibZontreck; +import dev.zontreck.libzontreck.vectors.WorldPosition; import net.minecraft.core.BlockPos; -import net.minecraft.nbt.CompoundTag; -import net.minecraft.nbt.ListTag; -import net.minecraft.nbt.NbtIo; -import net.minecraft.nbt.Tag; +import net.minecraft.nbt.*; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.Level; @@ -72,6 +70,11 @@ public class SaveDataFactory return this; } + public SaveDataManifest getManifest() + { + return new SaveDataManifest(); + } + public SaveDataFile build() { return new SaveDataFile(modID, levelName, queueID, DBMode, position); @@ -85,6 +88,7 @@ public class SaveDataFactory String queue; boolean database; BlockPos position; + boolean useManifest = false; public SaveDataFile(String modID, String levelName, String queueID, boolean DBMode, BlockPos pos) { @@ -93,8 +97,17 @@ public class SaveDataFactory queue = queueID; database = DBMode; position = pos; + + if(pos == null) + { + useManifest=true; + } } + /** + * config/LibZontreck/block_snapshots/[mod]/[dimension]/[queueNickName]/r.x.z.dat + * @return + */ public Path getSaveDataPath() { Path path = LibZontreck.BASE_CONFIG.resolve("block_snapshots"); @@ -104,6 +117,8 @@ public class SaveDataFactory path.toFile().mkdirs(); + if(useManifest) return path.resolve("manifest.nbt"); + SaveDataCoordinates coordinates = new SaveDataCoordinates(position); path = path.resolve(coordinates.getFileName()); return path; @@ -131,6 +146,52 @@ public class SaveDataFactory } } + static class SaveDataManifest { + private List SAVE_DATA = new ArrayList<>(); + public static final String TAG_MANIFEST = "manifest"; + + private SaveDataManifest() + { + } + + public void add(SaveDataCoordinates pos){ + SAVE_DATA.add(pos); + } + + public List get() + { + return new ArrayList<>(SAVE_DATA); + } + + public CompoundTag save() + { + CompoundTag tag = new CompoundTag(); + ListTag lst = new ListTag(); + for(SaveDataCoordinates str : SAVE_DATA) + { + lst.add(str.toNBT()); + } + tag.put(TAG_MANIFEST, lst); + return tag; + + } + + public static SaveDataManifest deserialize(CompoundTag tag) + { + SaveDataManifest ret = new SaveDataManifest(); + ListTag lst = tag.getList(TAG_MANIFEST, Tag.TAG_COMPOUND); + for(Tag entry : lst) + { + if(entry instanceof CompoundTag ct) + { + ret.add(SaveDataCoordinates.deserialize(ct)); + } + } + + return ret; + } + } + static class SaveData { SaveDataFile myFile; public static final String TAG_SAVED_BLOCKS = "sb"; @@ -183,6 +244,8 @@ public class SaveDataFactory /** * Imports a full queue to the save data file. * ! WARNING ! This method will overwrite the SaveDataFile's Queue ID + * * * * + * This will only import for the correct dimension. This method is invoked automatically for each level for each queue when the server is shutting down prior to level unload. * @param queue Queue to import * @return The current SaveData instance */ @@ -190,7 +253,10 @@ public class SaveDataFactory { for(PrimitiveBlock blk : queue.getQueue()) { - blocks.add(blk.savedBlock); + if(WorldPosition.getDim(blk.level) == this.myFile.dimension) + blocks.add(blk.savedBlock); + else + continue; // We only want to add to a save data file, the blocks for the dimension in question. } myFile.queue = queue.getRestoreQueueName(); From c8fc5f4c81d8a2566a6858f6ba570f3473fe39e6 Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 23 Apr 2024 18:39:02 -0700 Subject: [PATCH 44/88] Finish implementation 1 of Block Snapshotting --- gradle.properties | 2 +- .../dev/zontreck/libzontreck/LibZontreck.java | 12 ++ .../events/RegisterMigrationsEvent.java | 22 +++ .../memory/world/BlockRestoreQueue.java | 70 ++++++++- .../memory/world/DatabaseMigrations.java | 148 ++++++++++++++++++ .../memory/world/DatabaseWrapper.java | 75 ++++++++- 6 files changed, 320 insertions(+), 9 deletions(-) create mode 100644 src/main/java/dev/zontreck/libzontreck/events/RegisterMigrationsEvent.java create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java diff --git a/gradle.properties b/gradle.properties index 290d77a..754a4a2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.041224.0832 +mod_version=1201.13.042324.1837 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java index af3c290..982e02d 100644 --- a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java +++ b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java @@ -3,6 +3,7 @@ package dev.zontreck.libzontreck; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.sql.SQLException; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @@ -16,6 +17,8 @@ import dev.zontreck.libzontreck.events.BlockRestoreQueueRegistrationEvent; import dev.zontreck.libzontreck.items.ModItems; import dev.zontreck.libzontreck.memory.world.BlockRestoreQueue; import dev.zontreck.libzontreck.memory.world.BlockRestoreQueueRegistry; +import dev.zontreck.libzontreck.memory.world.DatabaseMigrations; +import dev.zontreck.libzontreck.memory.world.DatabaseWrapper; import dev.zontreck.libzontreck.menus.ChestGUIScreen; import dev.zontreck.libzontreck.types.ModMenuTypes; import dev.zontreck.libzontreck.networking.NetworkEvents; @@ -112,6 +115,8 @@ public class LibZontreck { ALIVE=true; ServerConfig.init(); + DatabaseWrapper.start(); + CURRENT_SIDE = LogicalSide.SERVER; MinecraftForge.EVENT_BUS.post(new BlockRestoreQueueRegistrationEvent()); @@ -119,6 +124,13 @@ public class LibZontreck { for(ServerLevel level : event.getServer().getAllLevels()) { // Queues have been registered, but we now need to initialize the queue's data from saveddata + BlockRestoreQueueRegistry.init(level); + } + + try { + DatabaseMigrations.initMigrations(); + } catch (SQLException e) { + e.printStackTrace(); } } diff --git a/src/main/java/dev/zontreck/libzontreck/events/RegisterMigrationsEvent.java b/src/main/java/dev/zontreck/libzontreck/events/RegisterMigrationsEvent.java new file mode 100644 index 0000000..ee5fd93 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/events/RegisterMigrationsEvent.java @@ -0,0 +1,22 @@ +package dev.zontreck.libzontreck.events; + +import dev.zontreck.libzontreck.memory.world.DatabaseMigrations; +import net.minecraftforge.eventbus.api.Event; + +import java.util.ArrayList; +import java.util.List; + +public class RegisterMigrationsEvent extends Event +{ + private List migrations = new ArrayList<>(); + + public void register(DatabaseMigrations.Migration migration) + { + migrations.add(migration); + } + + public DatabaseMigrations.Migration[] getMigrations() + { + return (DatabaseMigrations.Migration[]) migrations.toArray(); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java index 46612a3..a98a854 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java @@ -1,8 +1,10 @@ package dev.zontreck.libzontreck.memory.world; +import dev.zontreck.libzontreck.vectors.WorldPosition; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NbtUtils; import net.minecraft.server.level.ServerLevel; import net.minecraft.world.level.block.Block; import net.minecraftforge.common.MinecraftForge; @@ -10,8 +12,9 @@ import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod; -import java.io.File; -import java.io.IOException; +import java.io.*; +import java.sql.PreparedStatement; +import java.sql.SQLException; import java.util.ArrayList; import java.util.List; @@ -26,6 +29,15 @@ public abstract class BlockRestoreQueue RUNNER = new BlockRestoreRunner(this); } + /** + * When true, uses the database to store blocks. The blocks stored in the database will not be loaded into memory at runtime + * @return + */ + public boolean usesDatabase() + { + return false; + } + /** * Returns the restore queue name * @return Name of the restore queue @@ -55,11 +67,57 @@ public abstract class BlockRestoreQueue */ public void enqueueBlock(PrimitiveBlock block) { + if(usesDatabase()) + { + databaseUpdate(block); + return; + } BLOCK_QUEUE.add(block); notifyDirtyQueue(true); } + /** + * Called when enqueuing a block, this is a special handler to insert the block to the database. Custom queues should override this to put into a different table, or add additional data + * @param block + */ + public void databaseUpdate(PrimitiveBlock block) + { + + PreparedStatement pstmt = null; + try { + pstmt = DatabaseWrapper.get().prepareStatement("REPLACE INTO `blocks` (queueName, posX, posY, posZ, state, entity, dimension, snapshotID) VALUES (?, ?, ?, ?, ?, ?, ?, ?);"); + pstmt.setString(0, getRestoreQueueName()); + pstmt.setInt(1, block.position.getX()); + pstmt.setInt(2, block.position.getY()); + pstmt.setInt(3, block.position.getZ()); + ByteArrayOutputStream blockState = new ByteArrayOutputStream(); + DataOutputStream dos0 = new DataOutputStream(blockState); + NbtIo.write(NbtUtils.writeBlockState(block.blockState), dos0); + pstmt.setBytes(4, blockState.toByteArray()); + ByteArrayOutputStream blockEntity = new ByteArrayOutputStream(); + + if(block.blockEntity == null) + { + pstmt.setObject(5, null); + } else { + dos0 = new DataOutputStream(blockEntity); + NbtIo.write(block.blockEntity, dos0); + pstmt.setBytes(5, blockEntity.toByteArray()); + } + + pstmt.setString(6, WorldPosition.getDim(block.level)); + pstmt.setInt(7, 0); + + DatabaseWrapper.get().executePreparedStatement(pstmt); + + } catch (SQLException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + /** * Executed when the queue is modified. * @param blockAdded Whether a block was added or removed from the queue @@ -138,8 +196,16 @@ public abstract class BlockRestoreQueue * @throws IOException On failure to read a file */ public void initialize(ServerLevel level) throws IOException { + if(usesDatabase()) + { + return; + } var file = SaveDataFactory.builder().withDimension(level).withQueueID(this).withPosition(null).build(); + if(!file.getSaveDataPath().toFile().exists()) + { + return; + } CompoundTag tag = NbtIo.read(file.getSaveDataPath().toFile()); SaveDataFactory.SaveDataManifest manifest = SaveDataFactory.SaveDataManifest.deserialize(tag); diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java new file mode 100644 index 0000000..a0175ae --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java @@ -0,0 +1,148 @@ +package dev.zontreck.libzontreck.memory.world; + +import com.google.common.collect.Lists; +import dev.zontreck.libzontreck.events.RegisterMigrationsEvent; +import net.minecraftforge.common.MinecraftForge; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; + +public class DatabaseMigrations +{ + public static class Migration + { + String tableID; + int version; + List migrationActions = new ArrayList<>(); + + private Migration(){ + tableID = ""; + version = 0; + } + + /** + * Builder pattern function - Sets the table ID for the migration + * @param tableID + * @return + */ + public Migration withTableID(String tableID) + { + this.tableID = tableID; + return this; + } + + /** + * Builder pattern function - Sets the table version for the migration + * @param version + * @return + */ + public Migration withVersion(int version) + { + this.version = version; + return this; + } + + /** + * Builder pattern function - Adds the action to be executed. The list will operate as FILO. + * @param pstat + * @return + */ + public Migration withMigrationAction(PreparedStatement pstat) + { + migrationActions.add(pstat); + return this; + } + + /** + * Executes the migration as defined by the builder pattern. + */ + public void execute() + { + for(PreparedStatement pstmt : migrationActions) + { + try { + DatabaseWrapper.get().executePreparedStatement(pstmt); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + + try { + + PreparedStatement pstat = DatabaseWrapper.get().prepareStatement("REPLACE INTO `migrations` (tableID, version) VALUES (?,?);"); + pstat.setString(0, tableID); + pstat.setInt(1, version); + } catch (SQLException ex) + { + ex.printStackTrace(); + } + + + } + + } + private static List migrations = new ArrayList<>(); + + public static void initMigrations() throws SQLException { + Migration migrationsTable = builder() + .withVersion(0) + .withTableID("migrations"); + + PreparedStatement statement = DatabaseWrapper.get().prepareStatement("CREATE TABLE `migrations` (" + + " `tableID` varchar(255) NOT NULL," + + " `version` int(11) NOT NULL," + + " PRIMARY KEY (`tableID`)," + + " UNIQUE KEY `tableID` (`tableID`)" + + ") ;"); + migrations.add(migrationsTable.withMigrationAction(statement)); + + Migration blocksTable = builder() + .withTableID("blocks") + .withVersion(0); + + PreparedStatement makeBlocksTable = DatabaseWrapper.get().prepareStatement("CREATE TABLE `blocks` (" + + " `time` timestamp NOT NULL DEFAULT current_timestamp()," + + " `queueName` varchar(255) NOT NULL," + + " `posX` int(11) NOT NULL," + + " `posY` int(11) NOT NULL," + + " `posZ` int(11) NOT NULL," + + " `state` blob NOT NULL," + + " `entity` blob," + + " `dimension` varchar(255) NOT NULL," + + " `snapshotID` int(11) NOT NULL DEFAULT 0 COMMENT 'Enables multiple blocks existing at the same position'," + + " PRIMARY KEY (`time`)," + + " UNIQUE KEY `posX` (`posX`)," + + " UNIQUE KEY `posY` (`posY`)," + + " UNIQUE KEY `posZ` (`posZ`)" + + "); "); + + migrations.add(blocksTable.withMigrationAction(makeBlocksTable)); + + RegisterMigrationsEvent rme = new RegisterMigrationsEvent(); + MinecraftForge.EVENT_BUS.post(rme); + + + migrations.addAll(Lists.reverse(List.of(rme.getMigrations()))); + + + executeMigrations(); + } + + private static void executeMigrations() + { + + List migration = Lists.reverse(migrations); + + for(Migration m : migration) + { + m.execute(); + } + } + + public static Migration builder() + { + return new Migration(); + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java index 18ace42..dc7b3ca 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java @@ -1,15 +1,54 @@ package dev.zontreck.libzontreck.memory.world; +import dev.zontreck.libzontreck.LibZontreck; +import dev.zontreck.libzontreck.config.ServerConfig; + import java.sql.*; public class DatabaseWrapper { private Connection connection; + private static DatabaseWrapper instance; + private static boolean hasDatabase=false; + + private static void setHasDatabase(boolean value) + { + hasDatabase = value; + } + + public static DatabaseWrapper get() + { + if(instance==null) + start(); + return instance; + } + + /** + * This function will return true if the database drivers are available. + * @return + */ + public static boolean databaseIsAvailable() + { + return hasDatabase; + } + public DatabaseWrapper() { connection = null; } + public static void start() + { + instance = new DatabaseWrapper(); + try { + instance.connect(ServerConfig.database.host, ServerConfig.database.user, ServerConfig.database.password); + setHasDatabase(true); + } catch (SQLException e) { + setHasDatabase(false); + throw new RuntimeException(e); + } + } + public void connect(String url, String username, String password) throws SQLException { try { // Try MariaDB JDBC driver @@ -17,16 +56,28 @@ public class DatabaseWrapper { connection = DriverManager.getConnection("jdbc:mariadb://" + url, username, password); } catch (ClassNotFoundException | SQLException e) { // MariaDB not found or failed to connect, try MySQL + LibZontreck.LOGGER.warn("Failed to connect via MariaDB: " + e.getMessage() + "; Attempting to fall back to mysql"); try { Class.forName("com.mysql.cj.jdbc.Driver"); connection = DriverManager.getConnection("jdbc:mysql://" + url, username, password); } catch (ClassNotFoundException | SQLException ex) { // MySQL not found or failed to connect, try SQLite try { - Class.forName("org.sqlite.JDBC"); - connection = DriverManager.getConnection("jdbc:sqlite:" + url); - } catch (ClassNotFoundException | SQLException exc) { - throw new SQLException("Failed to connect to database: " + exc.getMessage()); + + Class.forName("com.mysql.jdbc.Driver"); + connection = DriverManager.getConnection("jdbc:mysql://" + url, username, password); + }catch (ClassNotFoundException | SQLException ex1) + { + + LibZontreck.LOGGER.warn("Failed to connect via MySQL: " + e.getMessage() + "; " + ex1.getMessage()+ "; Attempting to fall back to sqlite"); + + try { + Class.forName("org.sqlite.JDBC"); + connection = DriverManager.getConnection("jdbc:sqlite:" + url); + } catch (ClassNotFoundException | SQLException exc) { + LibZontreck.LOGGER.warn("Failed to connect via SQLite: " + e.getMessage() + "; If you require the use of the block queues, please check the above warnings for explanation on cause. It could be that you do not have the relevant JDBC installed in your server mods list."); + throw new SQLException("Failed to connect to database: " + exc.getMessage()); + } } } } @@ -44,8 +95,7 @@ public class DatabaseWrapper { if (connection == null) { throw new SQLException("Connection not established."); } - Statement stmt = connection.createStatement(); - return stmt.executeUpdate(statement); + return connection.createStatement().executeUpdate(statement); } public void disconnect() throws SQLException { @@ -54,4 +104,17 @@ public class DatabaseWrapper { } } + public int executePreparedStatement(PreparedStatement preparedStatement) throws SQLException { + if (connection == null) { + throw new SQLException("Connection not established."); + } + return preparedStatement.executeUpdate(); + } + + public PreparedStatement prepareStatement(String query) throws SQLException { + if (connection == null) { + throw new SQLException("Connection not established."); + } + return connection.prepareStatement(query); + } } From b91ce4e9121b02c8d3d89ebef5293ad7519ef016 Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 23 Apr 2024 20:25:52 -0700 Subject: [PATCH 45/88] Add some missing calls in restore queue with database addition --- gradle.properties | 2 +- .../memory/world/BlockRestore.java | 5 ++ .../memory/world/BlockRestoreQueue.java | 77 ++++++++++++++----- .../memory/world/DatabaseMigrations.java | 6 +- .../memory/world/DatabaseWrapper.java | 29 +++---- .../memory/world/SortedBlockQueue.java | 5 ++ 6 files changed, 85 insertions(+), 39 deletions(-) diff --git a/gradle.properties b/gradle.properties index 754a4a2..3015eaf 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042324.1837 +mod_version=1201.13.042324.2018 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestore.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestore.java index a3d4d20..4e85f16 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestore.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestore.java @@ -11,4 +11,9 @@ public class BlockRestore extends BlockRestoreQueue public void notifyDirtyQueue(boolean blockAdded) { return; // We dont care. This is a basic queue } + + @Override + public boolean sorted() { + return false; + } } diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java index a98a854..44ab47d 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java @@ -1,19 +1,14 @@ package dev.zontreck.libzontreck.memory.world; -import dev.zontreck.libzontreck.vectors.WorldPosition; -import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtIo; -import net.minecraft.nbt.NbtUtils; import net.minecraft.server.level.ServerLevel; -import net.minecraft.world.level.block.Block; -import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.common.Mod; import java.io.*; import java.sql.PreparedStatement; +import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; @@ -70,6 +65,7 @@ public abstract class BlockRestoreQueue if(usesDatabase()) { databaseUpdate(block); + notifyDirtyQueue(true); return; } BLOCK_QUEUE.add(block); @@ -86,28 +82,17 @@ public abstract class BlockRestoreQueue PreparedStatement pstmt = null; try { - pstmt = DatabaseWrapper.get().prepareStatement("REPLACE INTO `blocks` (queueName, posX, posY, posZ, state, entity, dimension, snapshotID) VALUES (?, ?, ?, ?, ?, ?, ?, ?);"); + pstmt = DatabaseWrapper.get().prepareStatement("INSERT INTO `blocks` (queueName, posX, posY, posZ, snapshotID, block) VALUES (?, ?, ?, ?, ?, ?);"); pstmt.setString(0, getRestoreQueueName()); pstmt.setInt(1, block.position.getX()); pstmt.setInt(2, block.position.getY()); pstmt.setInt(3, block.position.getZ()); + pstmt.setInt(4, 0); ByteArrayOutputStream blockState = new ByteArrayOutputStream(); DataOutputStream dos0 = new DataOutputStream(blockState); - NbtIo.write(NbtUtils.writeBlockState(block.blockState), dos0); - pstmt.setBytes(4, blockState.toByteArray()); - ByteArrayOutputStream blockEntity = new ByteArrayOutputStream(); + NbtIo.write(block.serialize(), dos0); + pstmt.setBytes(5, blockState.toByteArray()); - if(block.blockEntity == null) - { - pstmt.setObject(5, null); - } else { - dos0 = new DataOutputStream(blockEntity); - NbtIo.write(block.blockEntity, dos0); - pstmt.setBytes(5, blockEntity.toByteArray()); - } - - pstmt.setString(6, WorldPosition.getDim(block.level)); - pstmt.setInt(7, 0); DatabaseWrapper.get().executePreparedStatement(pstmt); @@ -130,12 +115,62 @@ public abstract class BlockRestoreQueue */ public PrimitiveBlock getNextBlock() { + if (usesDatabase()) { + // Send a query to the database to retrieve the block, and reconstruct here + try { + PreparedStatement sel; + if (sorted()) { + sel = DatabaseWrapper.get().prepareStatement("SELECT * FROM `blocks` WHERE queueName=? ORDER BY posY ASC LIMIT 1;"); + } else + sel = DatabaseWrapper.get().prepareStatement("SELECT * FROM `blocks` WHERE queueName=? LIMIT 1;"); + + sel.setString(0, getRestoreQueueName()); + ResultSet res = DatabaseWrapper.get().executePreparedStatementQuery(sel); + // Now retrieve the block from the database + if (res.next()) { + byte[] data = res.getBytes("block"); + ByteArrayInputStream bais = new ByteArrayInputStream(data); + DataInputStream dis = new DataInputStream(bais); + + PrimitiveBlock block = PrimitiveBlock.deserialize(NbtIo.read(dis)); + + try { + res.deleteRow(); + if (!res.rowDeleted()) { + + } + } catch (SQLException e001) { + PreparedStatement pstat = DatabaseWrapper.get().prepareStatement("DELETE FROM `blocks` WHERE queueName=? AND posX=? AND posY=? AND posZ=?;"); + pstat.setString(0, getRestoreQueueName()); + pstat.setInt(1, block.position.getX()); + pstat.setInt(2, block.position.getY()); + pstat.setInt(3, block.position.getZ()); + DatabaseWrapper.get().executePreparedStatement(pstat); + } + + + return block; + } else return null; + + } catch (SQLException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } PrimitiveBlock blk = BLOCK_QUEUE.get(0); BLOCK_QUEUE.remove(0); notifyDirtyQueue(false); return blk; } + /** + * Override to indicate if the list should be sorted by lowest Y value + * + * @return + */ + public abstract boolean sorted(); + /** * Clears the entire queue, discarding the saved blocks permanently. */ diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java index a0175ae..5626fbd 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java @@ -108,15 +108,13 @@ public class DatabaseMigrations " `posX` int(11) NOT NULL," + " `posY` int(11) NOT NULL," + " `posZ` int(11) NOT NULL," + - " `state` blob NOT NULL," + - " `entity` blob," + - " `dimension` varchar(255) NOT NULL," + " `snapshotID` int(11) NOT NULL DEFAULT 0 COMMENT 'Enables multiple blocks existing at the same position'," + + " `block` blob NOT NULL COMMENT 'NBT Data representing a SavedBlock'," + " PRIMARY KEY (`time`)," + " UNIQUE KEY `posX` (`posX`)," + " UNIQUE KEY `posY` (`posY`)," + " UNIQUE KEY `posZ` (`posZ`)" + - "); "); + ") ;"); migrations.add(blocksTable.withMigrationAction(makeBlocksTable)); diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java index dc7b3ca..8902578 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java @@ -10,26 +10,24 @@ public class DatabaseWrapper { private Connection connection; private static DatabaseWrapper instance; - private static boolean hasDatabase=false; + private static boolean hasDatabase = false; - private static void setHasDatabase(boolean value) - { + private static void setHasDatabase(boolean value) { hasDatabase = value; } - public static DatabaseWrapper get() - { - if(instance==null) + public static DatabaseWrapper get() { + if (instance == null) start(); return instance; } /** * This function will return true if the database drivers are available. + * * @return */ - public static boolean databaseIsAvailable() - { + public static boolean databaseIsAvailable() { return hasDatabase; } @@ -37,8 +35,7 @@ public class DatabaseWrapper { connection = null; } - public static void start() - { + public static void start() { instance = new DatabaseWrapper(); try { instance.connect(ServerConfig.database.host, ServerConfig.database.user, ServerConfig.database.password); @@ -66,10 +63,9 @@ public class DatabaseWrapper { Class.forName("com.mysql.jdbc.Driver"); connection = DriverManager.getConnection("jdbc:mysql://" + url, username, password); - }catch (ClassNotFoundException | SQLException ex1) - { + } catch (ClassNotFoundException | SQLException ex1) { - LibZontreck.LOGGER.warn("Failed to connect via MySQL: " + e.getMessage() + "; " + ex1.getMessage()+ "; Attempting to fall back to sqlite"); + LibZontreck.LOGGER.warn("Failed to connect via MySQL: " + e.getMessage() + "; " + ex1.getMessage() + "; Attempting to fall back to sqlite"); try { Class.forName("org.sqlite.JDBC"); @@ -111,6 +107,13 @@ public class DatabaseWrapper { return preparedStatement.executeUpdate(); } + public ResultSet executePreparedStatementQuery(PreparedStatement query) throws SQLException { + if (connection == null) { + throw new SQLException("Connection not established."); + } + return query.executeQuery(); + } + public PreparedStatement prepareStatement(String query) throws SQLException { if (connection == null) { throw new SQLException("Connection not established."); diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/SortedBlockQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/SortedBlockQueue.java index 8cced50..a098a21 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/SortedBlockQueue.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/SortedBlockQueue.java @@ -33,4 +33,9 @@ public class SortedBlockQueue extends BlockRestoreQueue } } + @Override + public boolean sorted() { + return true; + } + } From 1275ff422f0b35fb7520301988b549c3f7c0930d Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 23 Apr 2024 20:51:16 -0700 Subject: [PATCH 46/88] Add missing NBT entry in the server config --- gradle.properties | 2 +- .../zontreck/libzontreck/config/sections/DatabaseSection.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 3015eaf..23d1db4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042324.2018 +mod_version=1201.13.042324.2051 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java b/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java index 9787ecd..64e5caf 100644 --- a/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java +++ b/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java @@ -86,6 +86,8 @@ public class DatabaseSection tag.putString(TAG_PASSWORD, password); tag.putString(TAG_HOST, host); tag.putString(TAG_DATABASE, database); + tag.putInt(TAG_VERSION, version); + return tag; } } From 378f44b76963e3d40874b12898727d3dde8ac298 Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 23 Apr 2024 22:02:28 -0700 Subject: [PATCH 47/88] Fix a crash --- gradle.properties | 2 +- .../libzontreck/memory/world/DatabaseWrapper.java | 12 ++---------- 2 files changed, 3 insertions(+), 11 deletions(-) diff --git a/gradle.properties b/gradle.properties index 23d1db4..771ae1a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042324.2051 +mod_version=1201.13.042324.2158 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java index 8902578..2e870b1 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java @@ -10,11 +10,6 @@ public class DatabaseWrapper { private Connection connection; private static DatabaseWrapper instance; - private static boolean hasDatabase = false; - - private static void setHasDatabase(boolean value) { - hasDatabase = value; - } public static DatabaseWrapper get() { if (instance == null) @@ -28,7 +23,7 @@ public class DatabaseWrapper { * @return */ public static boolean databaseIsAvailable() { - return hasDatabase; + return instance.connection!=null; } public DatabaseWrapper() { @@ -39,9 +34,7 @@ public class DatabaseWrapper { instance = new DatabaseWrapper(); try { instance.connect(ServerConfig.database.host, ServerConfig.database.user, ServerConfig.database.password); - setHasDatabase(true); } catch (SQLException e) { - setHasDatabase(false); throw new RuntimeException(e); } } @@ -71,8 +64,7 @@ public class DatabaseWrapper { Class.forName("org.sqlite.JDBC"); connection = DriverManager.getConnection("jdbc:sqlite:" + url); } catch (ClassNotFoundException | SQLException exc) { - LibZontreck.LOGGER.warn("Failed to connect via SQLite: " + e.getMessage() + "; If you require the use of the block queues, please check the above warnings for explanation on cause. It could be that you do not have the relevant JDBC installed in your server mods list."); - throw new SQLException("Failed to connect to database: " + exc.getMessage()); + LibZontreck.LOGGER.error("Failed to connect via SQLite: " + e.getMessage() + "; If you require the use of the block queues, please check the above warnings for explanation on cause. It could be that you do not have the relevant JDBC installed in your server mods list."); } } } From fe383b2973baa3d26f22757c3a2a6b17862d24a1 Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 23 Apr 2024 22:24:41 -0700 Subject: [PATCH 48/88] Settings file was not being initialized --- gradle.properties | 2 +- src/main/java/dev/zontreck/libzontreck/LibZontreck.java | 2 ++ src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java | 2 ++ 3 files changed, 5 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 771ae1a..7578974 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042324.2158 +mod_version=1201.13.042324.2223 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java index 982e02d..d51f00b 100644 --- a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java +++ b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java @@ -85,6 +85,8 @@ public class LibZontreck { IEventBus bus = FMLJavaModLoadingContext.get().getModEventBus(); // Register the setup method for modloading bus.addListener(this::setup); + + ServerConfig.init(); MinecraftForge.EVENT_BUS.register(this); diff --git a/src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java b/src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java index 3d0c3b8..4ef3755 100644 --- a/src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java +++ b/src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java @@ -22,6 +22,8 @@ public class ServerConfig database = DatabaseSection.deserialize(config.getCompound(DatabaseSection.TAG_NAME)); } else { database = new DatabaseSection(); + + commit(); } } From 09426dc0b48bf1038cee9f53835db25b7d3448a7 Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 23 Apr 2024 22:40:21 -0700 Subject: [PATCH 49/88] Database config was null and not loading properly --- gradle.properties | 2 +- .../libzontreck/config/sections/DatabaseSection.java | 8 ++++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 7578974..8ba1612 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042324.2223 +mod_version=1201.13.042324.2239 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java b/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java index 64e5caf..7321bcd 100644 --- a/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java +++ b/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java @@ -49,12 +49,20 @@ public class DatabaseSection break; } default:{ + + user = tag.getString(TAG_USER); + password = tag.getString(TAG_PASSWORD); + host = tag.getString(TAG_HOST); + database = tag.getString(TAG_DATABASE); + version = ver; + if(migrated) { ServerConfig.commit(); migrated=false; return; } + break; } } From f039eb2abf77a3d85cd4a4a3d181a9c0d3d99a54 Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 23 Apr 2024 22:53:07 -0700 Subject: [PATCH 50/88] Again... try to fix null config sections --- gradle.properties | 2 +- .../libzontreck/config/ServerConfig.java | 2 + .../config/sections/DatabaseSection.java | 53 +++++-------------- 3 files changed, 17 insertions(+), 40 deletions(-) diff --git a/gradle.properties b/gradle.properties index 8ba1612..1a38fa9 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042324.2239 +mod_version=1201.13.042324.2250 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java b/src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java index 4ef3755..404f834 100644 --- a/src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java +++ b/src/main/java/dev/zontreck/libzontreck/config/ServerConfig.java @@ -20,6 +20,8 @@ public class ServerConfig var config = SNbtIo.loadSnbt(BASE); database = DatabaseSection.deserialize(config.getCompound(DatabaseSection.TAG_NAME)); + + commit(); } else { database = new DatabaseSection(); diff --git a/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java b/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java index 7321bcd..a89a298 100644 --- a/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java +++ b/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java @@ -25,49 +25,24 @@ public class DatabaseSection public static DatabaseSection deserialize(CompoundTag tag) { - int ver = tag.getInt(TAG_VERSION); - DatabaseSection section = new DatabaseSection(); - section.doMigrations(ver, tag); + DatabaseSection ret = new DatabaseSection(); + ret.version = tag.getInt(TAG_VERSION); - - return section; - } - - public void doMigrations(int ver, CompoundTag tag) - { - switch (ver) + if(ret.version == 0) { - case 0: - { - ver++; - user = tag.getString(TAG_USER); - password = tag.getString(TAG_PASSWORD); - host = tag.getString(TAG_HOST); - database = tag.getString(TAG_DATABASE); - version = ver; - migrated=true; - break; - } - default:{ - - user = tag.getString(TAG_USER); - password = tag.getString(TAG_PASSWORD); - host = tag.getString(TAG_HOST); - database = tag.getString(TAG_DATABASE); - version = ver; - - if(migrated) - { - ServerConfig.commit(); - migrated=false; - return; - } - - break; - } + ret.version = VERSION; + return ret; } - doMigrations(ver, tag); + if(ret.version >= 1) + { + ret.user = tag.getString(TAG_USER); + ret.password = tag.getString(TAG_PASSWORD); + ret.host = tag.getString(TAG_HOST); + ret.database = tag.getString(TAG_DATABASE); + } + + return ret; } public DatabaseSection(){ From 965bea298225761d477daf52b6965974c703379e Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 23 Apr 2024 23:29:50 -0700 Subject: [PATCH 51/88] Fix libzontreck's packets not being functional --- gradle.properties | 2 +- .../java/dev/zontreck/libzontreck/networking/ModMessages.java | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 1a38fa9..8b16c62 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042324.2250 +mod_version=1201.13.042324.2323 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java b/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java index 2d8af87..5dd07f4 100644 --- a/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java +++ b/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java @@ -37,6 +37,8 @@ public class ModMessages { .serverAcceptedVersions(s->true) .simpleChannel(); + INSTANCE=net; + net.messageBuilder(S2CPlaySoundPacket.class, PACKET_ID.getAndIncrement(), NetworkDirection.PLAY_TO_CLIENT) .decoder(S2CPlaySoundPacket::new) From a3d4ae321ed9c719cb8755dae2091d1ba4a46389 Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 23 Apr 2024 23:40:54 -0700 Subject: [PATCH 52/88] Fix null errors related to Migrations --- gradle.properties | 2 +- .../zontreck/libzontreck/events/RegisterMigrationsEvent.java | 4 ++-- .../zontreck/libzontreck/memory/world/DatabaseMigrations.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index 8b16c62..7b9bead 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042324.2323 +mod_version=1201.13.042324.2339 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/events/RegisterMigrationsEvent.java b/src/main/java/dev/zontreck/libzontreck/events/RegisterMigrationsEvent.java index ee5fd93..f305c8f 100644 --- a/src/main/java/dev/zontreck/libzontreck/events/RegisterMigrationsEvent.java +++ b/src/main/java/dev/zontreck/libzontreck/events/RegisterMigrationsEvent.java @@ -15,8 +15,8 @@ public class RegisterMigrationsEvent extends Event migrations.add(migration); } - public DatabaseMigrations.Migration[] getMigrations() + public List getMigrations() { - return (DatabaseMigrations.Migration[]) migrations.toArray(); + return new ArrayList<>(migrations); } } diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java index 5626fbd..f248fda 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java @@ -122,7 +122,7 @@ public class DatabaseMigrations MinecraftForge.EVENT_BUS.post(rme); - migrations.addAll(Lists.reverse(List.of(rme.getMigrations()))); + migrations.addAll(rme.getMigrations()); executeMigrations(); From b534287c51c749552b06320ca221deb8dea39fc5 Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 23 Apr 2024 23:48:28 -0700 Subject: [PATCH 53/88] Attempt to fix database not being set --- gradle.properties | 2 +- .../libzontreck/memory/world/DatabaseWrapper.java | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/gradle.properties b/gradle.properties index 7b9bead..bd69812 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042324.2339 +mod_version=1201.13.042324.2345 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java index 2e870b1..17fc901 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java @@ -33,36 +33,36 @@ public class DatabaseWrapper { public static void start() { instance = new DatabaseWrapper(); try { - instance.connect(ServerConfig.database.host, ServerConfig.database.user, ServerConfig.database.password); + instance.connect(ServerConfig.database.host, ServerConfig.database.user, ServerConfig.database.password, ServerConfig.database.database); } catch (SQLException e) { throw new RuntimeException(e); } } - public void connect(String url, String username, String password) throws SQLException { + public void connect(String url, String username, String password, String database) throws SQLException { try { // Try MariaDB JDBC driver Class.forName("org.mariadb.jdbc.Driver"); - connection = DriverManager.getConnection("jdbc:mariadb://" + url, username, password); + connection = DriverManager.getConnection("jdbc:mariadb://" + url + "/" + database, username, password); } catch (ClassNotFoundException | SQLException e) { // MariaDB not found or failed to connect, try MySQL LibZontreck.LOGGER.warn("Failed to connect via MariaDB: " + e.getMessage() + "; Attempting to fall back to mysql"); try { Class.forName("com.mysql.cj.jdbc.Driver"); - connection = DriverManager.getConnection("jdbc:mysql://" + url, username, password); + connection = DriverManager.getConnection("jdbc:mysql://" + url + "/" + database, username, password); } catch (ClassNotFoundException | SQLException ex) { // MySQL not found or failed to connect, try SQLite try { Class.forName("com.mysql.jdbc.Driver"); - connection = DriverManager.getConnection("jdbc:mysql://" + url, username, password); + connection = DriverManager.getConnection("jdbc:mysql://" + url + "/" + database, username, password); } catch (ClassNotFoundException | SQLException ex1) { LibZontreck.LOGGER.warn("Failed to connect via MySQL: " + e.getMessage() + "; " + ex1.getMessage() + "; Attempting to fall back to sqlite"); try { Class.forName("org.sqlite.JDBC"); - connection = DriverManager.getConnection("jdbc:sqlite:" + url); + connection = DriverManager.getConnection("jdbc:sqlite:" + database + ".db"); } catch (ClassNotFoundException | SQLException exc) { LibZontreck.LOGGER.error("Failed to connect via SQLite: " + e.getMessage() + "; If you require the use of the block queues, please check the above warnings for explanation on cause. It could be that you do not have the relevant JDBC installed in your server mods list."); } From e7f289d7098fad7492cea77e922c5fe575d294a0 Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 24 Apr 2024 00:19:41 -0700 Subject: [PATCH 54/88] Add a sanity check for migrations, only execute when migration is newer than current table --- gradle.properties | 2 +- .../memory/world/DatabaseMigrations.java | 46 ++++++++++++++++++- 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index bd69812..26dc9d2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042324.2345 +mod_version=1201.13.042424.0010 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java index f248fda..756ada2 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java @@ -1,6 +1,7 @@ package dev.zontreck.libzontreck.memory.world; import com.google.common.collect.Lists; +import dev.zontreck.libzontreck.LibZontreck; import dev.zontreck.libzontreck.events.RegisterMigrationsEvent; import net.minecraftforge.common.MinecraftForge; @@ -133,9 +134,52 @@ public class DatabaseMigrations List migration = Lists.reverse(migrations); + Migration lastTableChecked = null; for(Migration m : migration) { - m.execute(); + if(lastTableChecked == null) lastTableChecked = getCurrentTable(m.tableID); + else { + if(lastTableChecked.tableID != m.tableID) lastTableChecked = getCurrentTable(m.tableID); + } + + if(m.version > lastTableChecked.version) { + + LibZontreck.LOGGER.info("Executing migration " + m.tableID + ":" + m.version); + m.execute(); + } else { + LibZontreck.LOGGER.info("Skipping migration on table " + m.tableID + "; Current table version is " + lastTableChecked.version); + } + } + } + + /** + * Gets the current table's version using the Migration structure for data fields. Will be null if there is an error on any table except for migrations. + * @return + */ + private static Migration getCurrentTable(String tableID) + { + try{ + PreparedStatement pst = DatabaseWrapper.get().prepareStatement("SELECT * FROM `migrations` WHERE tableID=?;"); + pst.setString(0, tableID); + + var result = pst.executeQuery(); + if(!result.next()) + { + return builder().withTableID(tableID).withVersion(0); + }else { + return builder().withTableID(tableID).withVersion(result.getInt("version")); + } + + }catch (SQLException ex) + { + if(tableID == "migrations") + { + return builder().withTableID(tableID) + .withVersion(0); + } + ex.printStackTrace(); + + return null; } } From 4fefc79f5f5a901e70f26d657124498da85ffcf4 Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 24 Apr 2024 00:46:08 -0700 Subject: [PATCH 55/88] Migrations were not being properly updated --- gradle.properties | 2 +- .../libzontreck/memory/world/DatabaseMigrations.java | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 26dc9d2..37ae86b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042424.0010 +mod_version=1201.13.042424.0045 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java index 756ada2..82c8128 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java @@ -75,6 +75,8 @@ public class DatabaseMigrations PreparedStatement pstat = DatabaseWrapper.get().prepareStatement("REPLACE INTO `migrations` (tableID, version) VALUES (?,?);"); pstat.setString(0, tableID); pstat.setInt(1, version); + + pstat.execute(); } catch (SQLException ex) { ex.printStackTrace(); @@ -142,7 +144,7 @@ public class DatabaseMigrations if(lastTableChecked.tableID != m.tableID) lastTableChecked = getCurrentTable(m.tableID); } - if(m.version > lastTableChecked.version) { + if(lastTableChecked == null || m.version > lastTableChecked.version) { LibZontreck.LOGGER.info("Executing migration " + m.tableID + ":" + m.version); m.execute(); @@ -170,7 +172,7 @@ public class DatabaseMigrations return builder().withTableID(tableID).withVersion(result.getInt("version")); } - }catch (SQLException ex) + }catch (Exception ex) { if(tableID == "migrations") { From e0545ce9afa33271dc26afbfd1d1359c33207b6f Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 24 Apr 2024 01:27:12 -0700 Subject: [PATCH 56/88] Update indexes for the prepared statement --- gradle.properties | 2 +- .../memory/world/BlockRestoreQueue.java | 22 +++++++++---------- .../memory/world/DatabaseMigrations.java | 6 ++--- 3 files changed, 15 insertions(+), 15 deletions(-) diff --git a/gradle.properties b/gradle.properties index 37ae86b..4dcbef4 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042424.0045 +mod_version=1201.13.042424.0126 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java index 44ab47d..8bf0c9e 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java @@ -83,15 +83,15 @@ public abstract class BlockRestoreQueue PreparedStatement pstmt = null; try { pstmt = DatabaseWrapper.get().prepareStatement("INSERT INTO `blocks` (queueName, posX, posY, posZ, snapshotID, block) VALUES (?, ?, ?, ?, ?, ?);"); - pstmt.setString(0, getRestoreQueueName()); - pstmt.setInt(1, block.position.getX()); - pstmt.setInt(2, block.position.getY()); - pstmt.setInt(3, block.position.getZ()); - pstmt.setInt(4, 0); + pstmt.setString(1, getRestoreQueueName()); + pstmt.setInt(2, block.position.getX()); + pstmt.setInt(3, block.position.getY()); + pstmt.setInt(4, block.position.getZ()); + pstmt.setInt(5, 0); ByteArrayOutputStream blockState = new ByteArrayOutputStream(); DataOutputStream dos0 = new DataOutputStream(blockState); NbtIo.write(block.serialize(), dos0); - pstmt.setBytes(5, blockState.toByteArray()); + pstmt.setBytes(6, blockState.toByteArray()); DatabaseWrapper.get().executePreparedStatement(pstmt); @@ -124,7 +124,7 @@ public abstract class BlockRestoreQueue } else sel = DatabaseWrapper.get().prepareStatement("SELECT * FROM `blocks` WHERE queueName=? LIMIT 1;"); - sel.setString(0, getRestoreQueueName()); + sel.setString(1, getRestoreQueueName()); ResultSet res = DatabaseWrapper.get().executePreparedStatementQuery(sel); // Now retrieve the block from the database if (res.next()) { @@ -141,10 +141,10 @@ public abstract class BlockRestoreQueue } } catch (SQLException e001) { PreparedStatement pstat = DatabaseWrapper.get().prepareStatement("DELETE FROM `blocks` WHERE queueName=? AND posX=? AND posY=? AND posZ=?;"); - pstat.setString(0, getRestoreQueueName()); - pstat.setInt(1, block.position.getX()); - pstat.setInt(2, block.position.getY()); - pstat.setInt(3, block.position.getZ()); + pstat.setString(1, getRestoreQueueName()); + pstat.setInt(2, block.position.getX()); + pstat.setInt(3, block.position.getY()); + pstat.setInt(4, block.position.getZ()); DatabaseWrapper.get().executePreparedStatement(pstat); } diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java index 82c8128..b507c2d 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java @@ -73,8 +73,8 @@ public class DatabaseMigrations try { PreparedStatement pstat = DatabaseWrapper.get().prepareStatement("REPLACE INTO `migrations` (tableID, version) VALUES (?,?);"); - pstat.setString(0, tableID); - pstat.setInt(1, version); + pstat.setString(1, tableID); + pstat.setInt(2, version); pstat.execute(); } catch (SQLException ex) @@ -162,7 +162,7 @@ public class DatabaseMigrations { try{ PreparedStatement pst = DatabaseWrapper.get().prepareStatement("SELECT * FROM `migrations` WHERE tableID=?;"); - pst.setString(0, tableID); + pst.setString(1, tableID); var result = pst.executeQuery(); if(!result.next()) From 5895772c5834e5ba6efe235ab35c98662ea00573 Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 24 Apr 2024 02:17:18 -0700 Subject: [PATCH 57/88] Remove the reverse list instruction --- gradle.properties | 2 +- .../libzontreck/memory/world/DatabaseMigrations.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index 4dcbef4..9cee1dd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042424.0126 +mod_version=1201.13.042424.0216 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java index b507c2d..4704c7b 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java @@ -76,6 +76,8 @@ public class DatabaseMigrations pstat.setString(1, tableID); pstat.setInt(2, version); + LibZontreck.LOGGER.info("SQL QUERY: " + pstat); + pstat.execute(); } catch (SQLException ex) { @@ -134,10 +136,8 @@ public class DatabaseMigrations private static void executeMigrations() { - List migration = Lists.reverse(migrations); - Migration lastTableChecked = null; - for(Migration m : migration) + for(Migration m : migrations) { if(lastTableChecked == null) lastTableChecked = getCurrentTable(m.tableID); else { From 62baeb3e15a943e9045573ff8802c245632851db Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 24 Apr 2024 03:06:49 -0700 Subject: [PATCH 58/88] Set the version on the existing migrations --- .../zontreck/libzontreck/memory/world/DatabaseMigrations.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java index 4704c7b..a850cb1 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java @@ -92,7 +92,7 @@ public class DatabaseMigrations public static void initMigrations() throws SQLException { Migration migrationsTable = builder() - .withVersion(0) + .withVersion(1) .withTableID("migrations"); PreparedStatement statement = DatabaseWrapper.get().prepareStatement("CREATE TABLE `migrations` (" + @@ -105,7 +105,7 @@ public class DatabaseMigrations Migration blocksTable = builder() .withTableID("blocks") - .withVersion(0); + .withVersion(1); PreparedStatement makeBlocksTable = DatabaseWrapper.get().prepareStatement("CREATE TABLE `blocks` (" + " `time` timestamp NOT NULL DEFAULT current_timestamp()," + From 0e4d9a832a9687274c7b056febd6500be463064a Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 24 Apr 2024 03:07:26 -0700 Subject: [PATCH 59/88] Fix class visibility --- gradle.properties | 2 +- .../zontreck/libzontreck/memory/world/BlockRestoreRunner.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 9cee1dd..9691c5f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042424.0216 +mod_version=1201.13.042424.0307 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java index 9ae8005..d569751 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java @@ -11,7 +11,7 @@ import net.minecraft.world.level.block.entity.BlockEntity; import java.util.Random; -class BlockRestoreRunner implements Runnable +public class BlockRestoreRunner implements Runnable { public BlockRestoreRunner(BlockRestoreQueue queue) { From 43a1a44a8fc67466849459833a1f0dd4a3eabf0d Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 24 Apr 2024 03:40:23 -0700 Subject: [PATCH 60/88] Add a new migration to the blocks table --- gradle.properties | 2 +- .../libzontreck/memory/world/DatabaseMigrations.java | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 9691c5f..cc8e72a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042424.0307 +mod_version=1201.13.042424.0339 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java index a850cb1..8020df9 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java @@ -123,6 +123,17 @@ public class DatabaseMigrations migrations.add(blocksTable.withMigrationAction(makeBlocksTable)); + Migration blocksUpdate = builder() + .withTableID("blocks") + .withVersion(2); + PreparedStatement removePKey = DatabaseWrapper.get().prepareStatement("ALTER TABLE `blocks` DROP PRIMARY KEY;"); + blocksUpdate.withMigrationAction(removePKey); + PreparedStatement addIDColumn = DatabaseWrapper.get().prepareStatement("ALTER TABLE `blocks` ADD `ID` INT NOT NULL AUTO_INCREMENT FIRST, ADD PRIMARY KEY (`ID`);"); + blocksUpdate.withMigrationAction(addIDColumn); + + migrations.add(blocksUpdate); + + RegisterMigrationsEvent rme = new RegisterMigrationsEvent(); MinecraftForge.EVENT_BUS.post(rme); From ab6f9130f0da32261f14b442dcc89f09f411b25f Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 24 Apr 2024 04:15:31 -0700 Subject: [PATCH 61/88] Fix the blocks table index --- gradle.properties | 2 +- .../libzontreck/memory/world/DatabaseMigrations.java | 9 +++++++++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index cc8e72a..d01acf0 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042424.0339 +mod_version=1201.13.042424.0415 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java index 8020df9..feb2415 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java @@ -133,6 +133,15 @@ public class DatabaseMigrations migrations.add(blocksUpdate); + migrations.add(builder() + .withTableID("blocks") + .withVersion(3) + .withMigrationAction(DatabaseWrapper.get().prepareStatement(" ALTER TABLE `blocks` DROP INDEX `posX`; ")) + .withMigrationAction(DatabaseWrapper.get().prepareStatement(" ALTER TABLE `blocks` DROP INDEX `posY`; ")) + .withMigrationAction(DatabaseWrapper.get().prepareStatement(" ALTER TABLE `blocks` DROP INDEX `posZ`; ")) + .withMigrationAction(DatabaseWrapper.get().prepareStatement("ALTER TABLE `savedBlocks`.`blocks` ADD UNIQUE (`posX`, `posY`, `posZ`); "))); + + RegisterMigrationsEvent rme = new RegisterMigrationsEvent(); MinecraftForge.EVENT_BUS.post(rme); From 995e78dec8cf85038e3ea9d70b0d795b58a787a9 Mon Sep 17 00:00:00 2001 From: zontreck Date: Wed, 24 Apr 2024 23:53:43 -0700 Subject: [PATCH 62/88] Don't crash the server when duplicate block at position --- gradle.properties | 2 +- .../libzontreck/memory/world/BlockRestoreQueue.java | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/gradle.properties b/gradle.properties index d01acf0..00b3f10 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042424.0415 +mod_version=1201.13.042424.2353 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java index 8bf0c9e..5cb654b 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java @@ -96,10 +96,9 @@ public abstract class BlockRestoreQueue DatabaseWrapper.get().executePreparedStatement(pstmt); - } catch (SQLException e) { - throw new RuntimeException(e); - } catch (IOException e) { - throw new RuntimeException(e); + } catch (Exception e) + { + // Duplicate block insertion, we only cache each block one time by default. If this function is overridden to use a different table, perhaps multiple blocks for the same position could be cached. } } From 05e6fb1e29a8709f8d88eb43e8f9ff2dff554d52 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 25 Apr 2024 00:24:02 -0700 Subject: [PATCH 63/88] Queue runner was not running when in database mode --- gradle.properties | 2 +- .../zontreck/libzontreck/memory/world/BlockRestoreRunner.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 00b3f10..5773e70 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042424.2353 +mod_version=1201.13.042524.0023 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java index d569751..cfb6c01 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java @@ -23,7 +23,7 @@ public class BlockRestoreRunner implements Runnable @Override public void run() { - if(queue.getQueuedBlocks() == 0) return; // We'll be queued back up later + if(queue.getQueuedBlocks() == 0 && !queue.usesDatabase()) return; // We'll be queued back up later PrimitiveBlock prim = queue.getNextBlock(); From 5af11674cd0b039f6b52580f77cd92f3e395b2de Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 25 Apr 2024 00:39:12 -0700 Subject: [PATCH 64/88] Prevent null exceptions. Check if no more blocks. --- gradle.properties | 2 +- .../zontreck/libzontreck/memory/world/BlockRestoreRunner.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 5773e70..5a3376d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042524.0023 +mod_version=1201.13.042524.0038 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java index cfb6c01..23bc9dd 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java @@ -26,6 +26,7 @@ public class BlockRestoreRunner implements Runnable if(queue.getQueuedBlocks() == 0 && !queue.usesDatabase()) return; // We'll be queued back up later PrimitiveBlock prim = queue.getNextBlock(); + if(prim == null)return; // No more blocks. Level level = prim.level; From f54faa6f9ebe1877de0773e7e6fffdde673b4031 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 25 Apr 2024 02:01:43 -0700 Subject: [PATCH 65/88] Fix restore runner being executed too frequently --- gradle.properties | 2 +- .../dev/zontreck/libzontreck/LibZontreck.java | 3 ++ .../memory/world/BlockRestoreQueue.java | 41 ++++++++++++++----- 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/gradle.properties b/gradle.properties index 5a3376d..5cdc71b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042524.0038 +mod_version=1201.13.042524.0151 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java index d51f00b..9356f31 100644 --- a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java +++ b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java @@ -8,6 +8,8 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.UUID; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; import dev.zontreck.libzontreck.chestgui.ChestGUIRegistry; import dev.zontreck.libzontreck.config.ServerConfig; @@ -59,6 +61,7 @@ public class LibZontreck { public static final UUID NULL_ID; public static boolean LIBZONTRECK_SERVER_AVAILABLE=false; + public static ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); public static LogicalSide CURRENT_SIDE; diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java index 5cb654b..525da6c 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java @@ -1,9 +1,12 @@ package dev.zontreck.libzontreck.memory.world; +import dev.zontreck.libzontreck.LibZontreck; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtIo; import net.minecraft.server.level.ServerLevel; +import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.TickEvent; +import net.minecraftforge.event.server.ServerStoppingEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; import java.io.*; @@ -12,16 +15,21 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; public abstract class BlockRestoreQueue { private List BLOCK_QUEUE = new ArrayList<>(); - private BlockRestoreRunner RUNNER; + private final BlockRestoreRunner RUNNER; + private ScheduledFuture RUNNING_TASK; public BlockRestoreQueue() { RUNNER = new BlockRestoreRunner(this); + + MinecraftForge.EVENT_BUS.register(this); } /** @@ -207,21 +215,34 @@ public abstract class BlockRestoreQueue } /** - * Executes a tick. Spawns a thread which will modify 1 block from the queue + * Gets the restore runner instance initialized for this queue + * @return */ - private void tick() + public BlockRestoreRunner getRunner() { - Thread tx = new Thread(RUNNER); - tx.start(); + return RUNNER; + } + + /** + * Must be called manually to register a restore queue. This will have a 2 second fixed delay before initial execution + */ + public void schedule(long interval, TimeUnit unit) + { + RUNNING_TASK = LibZontreck.executor.scheduleAtFixedRate(RUNNER, 2000, interval, unit); + } + + /** + * Cancels the restore job + */ + public void cancel() + { + RUNNING_TASK.cancel(false); } @SubscribeEvent - public void onTick(TickEvent.LevelTickEvent event) + public void onServerStopping(ServerStoppingEvent event) { - if(event.phase == TickEvent.Phase.END) - { - tick(); - } + cancel(); } /** From aa6cb0e3670f43ae74f9dfc3c9b6cf1485663bc8 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 25 Apr 2024 02:15:31 -0700 Subject: [PATCH 66/88] Add some more sanity checks to cut down on database query spam --- gradle.properties | 2 +- .../memory/world/BlockRestoreQueue.java | 16 ++++++++++++++++ .../memory/world/BlockRestoreRunner.java | 3 +++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 5cdc71b..64c253f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042524.0151 +mod_version=1201.13.042524.0215 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java index 525da6c..ffe91a9 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java @@ -25,6 +25,11 @@ public abstract class BlockRestoreQueue private final BlockRestoreRunner RUNNER; private ScheduledFuture RUNNING_TASK; + /** + * When in database mode, this flag will be checked by the Restore Runner so a database call is not made unnecessarily. + */ + private boolean hasBlocks = true; + public BlockRestoreQueue() { RUNNER = new BlockRestoreRunner(this); @@ -74,6 +79,7 @@ public abstract class BlockRestoreQueue { databaseUpdate(block); notifyDirtyQueue(true); + hasBlocks=true; return; } BLOCK_QUEUE.add(block); @@ -178,6 +184,16 @@ public abstract class BlockRestoreQueue */ public abstract boolean sorted(); + /** + * Whether the queue has blocks or not + * @return + */ + public boolean hasBlocks() + { + if(usesDatabase()) return hasBlocks; + else return getQueuedBlocks() != 0; + } + /** * Clears the entire queue, discarding the saved blocks permanently. */ diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java index 23bc9dd..05ade1c 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java @@ -25,6 +25,9 @@ public class BlockRestoreRunner implements Runnable public void run() { if(queue.getQueuedBlocks() == 0 && !queue.usesDatabase()) return; // We'll be queued back up later + if(!queue.hasBlocks()) + return; + PrimitiveBlock prim = queue.getNextBlock(); if(prim == null)return; // No more blocks. From 4bf127bc14778d470d04ab8973682c244a26adf0 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 25 Apr 2024 02:30:31 -0700 Subject: [PATCH 67/88] Adds some Tag helper functions --- gradle.properties | 2 +- .../zontreck/libzontreck/util/TagUtils.java | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 src/main/java/dev/zontreck/libzontreck/util/TagUtils.java diff --git a/gradle.properties b/gradle.properties index 64c253f..9f4a898 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042524.0215 +mod_version=1201.13.042524.0230 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/util/TagUtils.java b/src/main/java/dev/zontreck/libzontreck/util/TagUtils.java new file mode 100644 index 0000000..fe8fc0c --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/util/TagUtils.java @@ -0,0 +1,32 @@ +package dev.zontreck.libzontreck.util; + +import net.minecraft.nbt.CompoundTag; + +public class TagUtils +{ + /** + * Get either the entry, or supply a default value + * @param tag + * @param entry + * @param other + * @return + */ + public static int intOr(CompoundTag tag, String entry, int other) + { + if(tag.contains(entry)) return tag.getInt(entry); + else return other; + } + + /** + * Get either the entry, or supply a default value + * @param tag + * @param entry + * @param other + * @return + */ + public static String strOr(CompoundTag tag, String entry, String other) + { + if(tag.contains(entry)) return tag.getString(entry); + else return other; + } +} From 0dcf38ce2aa5b4b1a1036fe462a98cc2c3753b1a Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 25 Apr 2024 03:21:32 -0700 Subject: [PATCH 68/88] Fix the block queue not setting the hasBlocks flag to false --- gradle.properties | 2 +- .../libzontreck/memory/world/BlockRestoreQueue.java | 8 ++++++++ .../libzontreck/memory/world/BlockRestoreRunner.java | 5 ++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 9f4a898..f216427 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042524.0230 +mod_version=1201.13.042524.0321 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java index ffe91a9..9c8c2ab 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java @@ -177,6 +177,14 @@ public abstract class BlockRestoreQueue return blk; } + /** + * Sets the hasBlocks flag to false to reduce DB Spam + */ + public void setNoBlocks() + { + hasBlocks=false; + } + /** * Override to indicate if the list should be sorted by lowest Y value * diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java index 05ade1c..3750189 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java @@ -29,7 +29,10 @@ public class BlockRestoreRunner implements Runnable return; PrimitiveBlock prim = queue.getNextBlock(); - if(prim == null)return; // No more blocks. + if(prim == null){ + queue.setNoBlocks(); + return; // No more blocks. + } Level level = prim.level; From c19e971e922dbe7d0a1980a63c5ba1c640af3e05 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 25 Apr 2024 03:39:29 -0700 Subject: [PATCH 69/88] Restore blocks until correct block has been restored and verified --- gradle.properties | 2 +- .../memory/world/BlockRestoreQueue.java | 25 +++++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/gradle.properties b/gradle.properties index f216427..40969a3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042524.0321 +mod_version=1201.13.042524.0327 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java index 9c8c2ab..530b61c 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java @@ -147,21 +147,24 @@ public abstract class BlockRestoreQueue PrimitiveBlock block = PrimitiveBlock.deserialize(NbtIo.read(dis)); - try { - res.deleteRow(); - if (!res.rowDeleted()) { + if(block.level.getBlockState(block.position).is(block.blockType)) + { + try { + res.deleteRow(); + if (!res.rowDeleted()) { + + } + } catch (SQLException e001) { + PreparedStatement pstat = DatabaseWrapper.get().prepareStatement("DELETE FROM `blocks` WHERE queueName=? AND posX=? AND posY=? AND posZ=?;"); + pstat.setString(1, getRestoreQueueName()); + pstat.setInt(2, block.position.getX()); + pstat.setInt(3, block.position.getY()); + pstat.setInt(4, block.position.getZ()); + DatabaseWrapper.get().executePreparedStatement(pstat); } - } catch (SQLException e001) { - PreparedStatement pstat = DatabaseWrapper.get().prepareStatement("DELETE FROM `blocks` WHERE queueName=? AND posX=? AND posY=? AND posZ=?;"); - pstat.setString(1, getRestoreQueueName()); - pstat.setInt(2, block.position.getX()); - pstat.setInt(3, block.position.getY()); - pstat.setInt(4, block.position.getZ()); - DatabaseWrapper.get().executePreparedStatement(pstat); } - return block; } else return null; From c6e6ed9ffe44ced04ec88cbb977d22ce885e86a3 Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 25 Apr 2024 04:10:53 -0700 Subject: [PATCH 70/88] Switch the block queue to cache blocks locally, then upload, to reduce lag when snapshotting. --- gradle.properties | 2 +- .../memory/world/BlockRestoreQueue.java | 29 ++++++++++++++++++- .../memory/world/DatabaseUploadRunner.java | 26 +++++++++++++++++ .../zontreck/libzontreck/util/TagUtils.java | 13 +++++++++ 4 files changed, 68 insertions(+), 2 deletions(-) create mode 100644 src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseUploadRunner.java diff --git a/gradle.properties b/gradle.properties index 40969a3..7950d8d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042524.0327 +mod_version=1201.13.042524.0409 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java index 530b61c..f8b309a 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java @@ -24,6 +24,7 @@ public abstract class BlockRestoreQueue private List BLOCK_QUEUE = new ArrayList<>(); private final BlockRestoreRunner RUNNER; private ScheduledFuture RUNNING_TASK; + private ScheduledFuture DATABASE_UPLOAD_RUNNER; /** * When in database mode, this flag will be checked by the Restore Runner so a database call is not made unnecessarily. @@ -75,13 +76,14 @@ public abstract class BlockRestoreQueue */ public void enqueueBlock(PrimitiveBlock block) { + /* if(usesDatabase()) { databaseUpdate(block); notifyDirtyQueue(true); hasBlocks=true; return; - } + }*/ BLOCK_QUEUE.add(block); notifyDirtyQueue(true); @@ -94,6 +96,7 @@ public abstract class BlockRestoreQueue public void databaseUpdate(PrimitiveBlock block) { + hasBlocks=true; PreparedStatement pstmt = null; try { pstmt = DatabaseWrapper.get().prepareStatement("INSERT INTO `blocks` (queueName, posX, posY, posZ, snapshotID, block) VALUES (?, ?, ?, ?, ?, ?);"); @@ -256,6 +259,10 @@ public abstract class BlockRestoreQueue public void schedule(long interval, TimeUnit unit) { RUNNING_TASK = LibZontreck.executor.scheduleAtFixedRate(RUNNER, 2000, interval, unit); + + DATABASE_UPLOAD_RUNNER = LibZontreck.executor.scheduleAtFixedRate(new DatabaseUploadRunner(this), 2000, 250, TimeUnit.MILLISECONDS); + + isCancelled=false; } /** @@ -263,9 +270,29 @@ public abstract class BlockRestoreQueue */ public void cancel() { + isCancelled=true; RUNNING_TASK.cancel(false); } + public boolean isCancelled=false; + + /** + * Remove a block from the local queue. This does not impact the database and is used internally + * @param block + */ + public void dequeue(PrimitiveBlock block) + { + BLOCK_QUEUE.remove(block); + } + + /** + * Cancels the repeating upload to database task. This is automatically invoked when cancel has been invoked, and no more tasks are to be uploaded. + */ + public void cancelUploader() + { + DATABASE_UPLOAD_RUNNER.cancel(true); + } + @SubscribeEvent public void onServerStopping(ServerStoppingEvent event) { diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseUploadRunner.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseUploadRunner.java new file mode 100644 index 0000000..2430114 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseUploadRunner.java @@ -0,0 +1,26 @@ +package dev.zontreck.libzontreck.memory.world; + +public class DatabaseUploadRunner implements Runnable { + private BlockRestoreQueue QUEUE; + public DatabaseUploadRunner(BlockRestoreQueue queue) + { + QUEUE = queue; + } + + @Override + public void run() { + if(QUEUE.getQueuedBlocks() == 0) + { + if(QUEUE.isCancelled) + { + QUEUE.cancelUploader(); + return; + } + } else { + PrimitiveBlock block = QUEUE.getQueue().get(0); + QUEUE.dequeue(block); + + QUEUE.databaseUpdate(block); + } + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/util/TagUtils.java b/src/main/java/dev/zontreck/libzontreck/util/TagUtils.java index fe8fc0c..d2eca8a 100644 --- a/src/main/java/dev/zontreck/libzontreck/util/TagUtils.java +++ b/src/main/java/dev/zontreck/libzontreck/util/TagUtils.java @@ -29,4 +29,17 @@ public class TagUtils if(tag.contains(entry)) return tag.getString(entry); else return other; } + + /** + * Get either the entry, or supply a default value + * @param tag + * @param entry + * @param other + * @return + */ + public static boolean boolOr(CompoundTag tag, String entry, boolean other) + { + if(tag.contains(entry)) return tag.getBoolean(entry); + else return other; + } } From dd592ebe88b5adb4a08b1cf4bb29b0de8f6ba62c Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 25 Apr 2024 05:11:05 -0700 Subject: [PATCH 71/88] Fix clients not being notified of block restores --- gradle.properties | 2 +- .../zontreck/libzontreck/memory/world/BlockRestoreRunner.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 7950d8d..83e047c 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042524.0409 +mod_version=1201.13.042524.0510 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java index 3750189..08570dd 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java @@ -43,7 +43,7 @@ public class BlockRestoreRunner implements Runnable level.playSound(null, pos, pop, ss, rng.nextFloat(0.75f,1.0f), rng.nextFloat(1)); - level.setBlock(pos, prim.blockState, Block.UPDATE_NONE, 0); + level.setBlock(pos, prim.blockState, Block.UPDATE_CLIENTS | Block.UPDATE_NONE, 0); BlockEntity entity = level.getBlockEntity(pos); if(entity != null) From b6987978d1df7ef4849e5c393fb117031a1e83be Mon Sep 17 00:00:00 2001 From: zontreck Date: Thu, 25 Apr 2024 05:28:11 -0700 Subject: [PATCH 72/88] Clients were still not being notified of block changes --- gradle.properties | 2 +- .../zontreck/libzontreck/memory/world/BlockRestoreRunner.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 83e047c..6abd168 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042524.0510 +mod_version=1201.13.042524.0527 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java index 08570dd..8f1fdad 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreRunner.java @@ -43,7 +43,7 @@ public class BlockRestoreRunner implements Runnable level.playSound(null, pos, pop, ss, rng.nextFloat(0.75f,1.0f), rng.nextFloat(1)); - level.setBlock(pos, prim.blockState, Block.UPDATE_CLIENTS | Block.UPDATE_NONE, 0); + level.setBlock(pos, prim.blockState, Block.UPDATE_CLIENTS, 0); BlockEntity entity = level.getBlockEntity(pos); if(entity != null) From 840fa0a1571a9d1921bad4eb9481f3a78bd33137 Mon Sep 17 00:00:00 2001 From: zontreck Date: Fri, 26 Apr 2024 05:48:00 -0700 Subject: [PATCH 73/88] Moves commonly used block prototypes into the library --- .../libzontreck/blocks/BlockCustomVoxels.java | 22 ++++++++++++ .../blocks/PartialTransparentBlock.java | 18 ++++++++++ .../blocks/PartialTransparentSlabBlock.java | 19 ++++++++++ .../libzontreck/blocks/RotatableBlock.java | 26 ++++++++++++++ .../blocks/RotatableBlockCustomVoxels.java | 35 +++++++++++++++++++ 5 files changed, 120 insertions(+) create mode 100644 src/main/java/dev/zontreck/libzontreck/blocks/BlockCustomVoxels.java create mode 100644 src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentBlock.java create mode 100644 src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentSlabBlock.java create mode 100644 src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlock.java create mode 100644 src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlockCustomVoxels.java diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/BlockCustomVoxels.java b/src/main/java/dev/zontreck/libzontreck/blocks/BlockCustomVoxels.java new file mode 100644 index 0000000..85981b5 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/blocks/BlockCustomVoxels.java @@ -0,0 +1,22 @@ +package dev.zontreck.libzontreck.blocks; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.VoxelShape; + +public class BlockCustomVoxels extends PartialTransparentBlock +{ + private VoxelShape superShape; + protected BlockCustomVoxels(Properties p_54120_, VoxelShape shape) { + super(p_54120_); + this.superShape = shape; + } + + @Override + public VoxelShape getShape(BlockState p_60555_, BlockGetter p_60556_, BlockPos p_60557_, CollisionContext p_60558_) { + return superShape; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentBlock.java b/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentBlock.java new file mode 100644 index 0000000..edae2fb --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentBlock.java @@ -0,0 +1,18 @@ +package dev.zontreck.libzontreck.blocks; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.AbstractGlassBlock; +import net.minecraft.world.level.block.state.BlockState; + +public class PartialTransparentBlock extends AbstractGlassBlock +{ + protected PartialTransparentBlock(Properties p_48729_) { + super(p_48729_); + } + + @Override + public boolean propagatesSkylightDown(BlockState p_48740_, BlockGetter p_48741_, BlockPos p_48742_) { + return true; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentSlabBlock.java b/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentSlabBlock.java new file mode 100644 index 0000000..ab3e90f --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentSlabBlock.java @@ -0,0 +1,19 @@ +package dev.zontreck.libzontreck.blocks; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.AbstractGlassBlock; +import net.minecraft.world.level.block.SlabBlock; +import net.minecraft.world.level.block.state.BlockState; + +public class PartialTransparentSlabBlock extends SlabBlock +{ + protected PartialTransparentSlabBlock(Properties p_48729_) { + super(p_48729_); + } + + @Override + public boolean propagatesSkylightDown(BlockState p_48740_, BlockGetter p_48741_, BlockPos p_48742_) { + return true; + } +} diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlock.java b/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlock.java new file mode 100644 index 0000000..acfcefc --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlock.java @@ -0,0 +1,26 @@ +package dev.zontreck.libzontreck.blocks; + +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.HorizontalDirectionalBlock; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; + +public class RotatableBlock extends HorizontalDirectionalBlock +{ + protected RotatableBlock(Properties pProperties) { + super(pProperties); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder pBuilder) { + super.createBlockStateDefinition(pBuilder); + pBuilder.add(FACING); + } + + + @Override + public BlockState getStateForPlacement(BlockPlaceContext pContext) { + return defaultBlockState().setValue(FACING, pContext.getHorizontalDirection()); + } +} \ No newline at end of file diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlockCustomVoxels.java b/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlockCustomVoxels.java new file mode 100644 index 0000000..cc9d4f2 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlockCustomVoxels.java @@ -0,0 +1,35 @@ +package dev.zontreck.libzontreck.blocks; + +import dev.zontreck.ariaslib.util.Maps; +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.phys.shapes.CollisionContext; +import net.minecraft.world.phys.shapes.Shapes; +import net.minecraft.world.phys.shapes.VoxelShape; + +import java.util.HashMap; +import java.util.Map; + + +public class RotatableBlockCustomVoxels extends RotatableBlock +{ + private Map rotatedShapes = new HashMap<>(); + + protected RotatableBlockCustomVoxels(Properties properties, VoxelShape north, VoxelShape south, VoxelShape east, VoxelShape west) { + super(properties); + rotatedShapes = Maps.of(new Maps.Entry<>(Direction.NORTH, north), new Maps.Entry<>(Direction.SOUTH, south), new Maps.Entry<>(Direction.WEST, west), new Maps.Entry<>(Direction.EAST, east), new Maps.Entry<>(Direction.NORTH, north), new Maps.Entry<>(Direction.DOWN, north)); + } + + @Override + public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) { + Direction facing = state.getValue(FACING); + return rotatedShapes.get(facing); + } + + @Override + public boolean propagatesSkylightDown(BlockState p_49928_, BlockGetter p_49929_, BlockPos p_49930_) { + return true; + } +} From a747846585893eab4f7c707bff855ee719cc884c Mon Sep 17 00:00:00 2001 From: zontreck Date: Fri, 26 Apr 2024 06:34:38 -0700 Subject: [PATCH 74/88] Add a redstone prototype for making blocks that use redstone --- gradle.properties | 2 +- .../libzontreck/blocks/RedstoneBlock.java | 60 +++++++++++++++++++ 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java diff --git a/gradle.properties b/gradle.properties index 6abd168..83a3a22 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042524.0527 +mod_version=1201.13.042624.0634 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java b/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java new file mode 100644 index 0000000..2fea128 --- /dev/null +++ b/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java @@ -0,0 +1,60 @@ +package dev.zontreck.libzontreck.blocks; + +import net.minecraft.core.BlockPos; +import net.minecraft.core.Direction; +import net.minecraft.world.item.context.BlockPlaceContext; +import net.minecraft.world.level.BlockGetter; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.LevelReader; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BooleanProperty; +import org.jetbrains.annotations.Nullable; + +import java.util.List; + +public abstract class RedstoneBlock extends RotatableBlock +{ + public static final BooleanProperty INPUT_POWER = BooleanProperty.create("inputpower"); + private List sides; + + protected RedstoneBlock(Properties pProperties, Direction... validSides) { + super(pProperties); + sides=List.of(validSides); + } + + @Override + public BlockState getStateForPlacement(BlockPlaceContext pContext) { + return super.getStateForPlacement(pContext).setValue(INPUT_POWER, false); + } + + @Override + protected void createBlockStateDefinition(StateDefinition.Builder pBuilder) { + super.createBlockStateDefinition(pBuilder); + pBuilder.add(INPUT_POWER); + } + + @Override + public boolean canConnectRedstone(BlockState state, BlockGetter level, BlockPos pos, @Nullable Direction direction) { + if(sides.contains(direction)) return true; + else return false; + } + + private boolean redstoneIsActivated(LevelReader level, BlockPos pos) + { + if(level.hasNeighborSignal(pos)) + return true; + else return false; + } + + protected abstract void onRedstone(LevelReader level, BlockPos pos, boolean on); + + @Override + public void onNeighborChange(BlockState state, LevelReader level, BlockPos pos, BlockPos neighbor) { + + onRedstone(level, pos, redstoneIsActivated(level, pos)); + } + + +} From ee74638b9f01b9fdee36f88b0b33d5159ff1eb32 Mon Sep 17 00:00:00 2001 From: zontreck Date: Fri, 26 Apr 2024 06:35:24 -0700 Subject: [PATCH 75/88] Update input power blockstate in prototype class --- .../java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java b/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java index 2fea128..e90c6a9 100644 --- a/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java +++ b/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java @@ -52,8 +52,8 @@ public abstract class RedstoneBlock extends RotatableBlock @Override public void onNeighborChange(BlockState state, LevelReader level, BlockPos pos, BlockPos neighbor) { - onRedstone(level, pos, redstoneIsActivated(level, pos)); + state.setValue(INPUT_POWER, redstoneIsActivated(level, pos)); } From 6ac212a0b7d9416c8d685d190ec0a116006667cd Mon Sep 17 00:00:00 2001 From: zontreck Date: Fri, 26 Apr 2024 11:15:49 -0700 Subject: [PATCH 76/88] Update class visibility --- gradle.properties | 2 +- .../java/dev/zontreck/libzontreck/blocks/BlockCustomVoxels.java | 2 +- .../zontreck/libzontreck/blocks/PartialTransparentBlock.java | 2 +- .../libzontreck/blocks/PartialTransparentSlabBlock.java | 2 +- .../java/dev/zontreck/libzontreck/blocks/RotatableBlock.java | 2 +- .../zontreck/libzontreck/blocks/RotatableBlockCustomVoxels.java | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/gradle.properties b/gradle.properties index 83a3a22..dd0e8b5 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042624.0634 +mod_version=1201.13.042624.1115 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/BlockCustomVoxels.java b/src/main/java/dev/zontreck/libzontreck/blocks/BlockCustomVoxels.java index 85981b5..f567242 100644 --- a/src/main/java/dev/zontreck/libzontreck/blocks/BlockCustomVoxels.java +++ b/src/main/java/dev/zontreck/libzontreck/blocks/BlockCustomVoxels.java @@ -10,7 +10,7 @@ import net.minecraft.world.phys.shapes.VoxelShape; public class BlockCustomVoxels extends PartialTransparentBlock { private VoxelShape superShape; - protected BlockCustomVoxels(Properties p_54120_, VoxelShape shape) { + public BlockCustomVoxels(Properties p_54120_, VoxelShape shape) { super(p_54120_); this.superShape = shape; } diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentBlock.java b/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentBlock.java index edae2fb..0ee305d 100644 --- a/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentBlock.java +++ b/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentBlock.java @@ -7,7 +7,7 @@ import net.minecraft.world.level.block.state.BlockState; public class PartialTransparentBlock extends AbstractGlassBlock { - protected PartialTransparentBlock(Properties p_48729_) { + public PartialTransparentBlock(Properties p_48729_) { super(p_48729_); } diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentSlabBlock.java b/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentSlabBlock.java index ab3e90f..ff74562 100644 --- a/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentSlabBlock.java +++ b/src/main/java/dev/zontreck/libzontreck/blocks/PartialTransparentSlabBlock.java @@ -8,7 +8,7 @@ import net.minecraft.world.level.block.state.BlockState; public class PartialTransparentSlabBlock extends SlabBlock { - protected PartialTransparentSlabBlock(Properties p_48729_) { + public PartialTransparentSlabBlock(Properties p_48729_) { super(p_48729_); } diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlock.java b/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlock.java index acfcefc..ebffa1d 100644 --- a/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlock.java +++ b/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlock.java @@ -8,7 +8,7 @@ import net.minecraft.world.level.block.state.StateDefinition; public class RotatableBlock extends HorizontalDirectionalBlock { - protected RotatableBlock(Properties pProperties) { + public RotatableBlock(Properties pProperties) { super(pProperties); } diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlockCustomVoxels.java b/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlockCustomVoxels.java index cc9d4f2..6bf6ac4 100644 --- a/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlockCustomVoxels.java +++ b/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlockCustomVoxels.java @@ -17,7 +17,7 @@ public class RotatableBlockCustomVoxels extends RotatableBlock { private Map rotatedShapes = new HashMap<>(); - protected RotatableBlockCustomVoxels(Properties properties, VoxelShape north, VoxelShape south, VoxelShape east, VoxelShape west) { + public RotatableBlockCustomVoxels(Properties properties, VoxelShape north, VoxelShape south, VoxelShape east, VoxelShape west) { super(properties); rotatedShapes = Maps.of(new Maps.Entry<>(Direction.NORTH, north), new Maps.Entry<>(Direction.SOUTH, south), new Maps.Entry<>(Direction.WEST, west), new Maps.Entry<>(Direction.EAST, east), new Maps.Entry<>(Direction.NORTH, north), new Maps.Entry<>(Direction.DOWN, north)); } From 7bbafe58fee2f8c7c79de38e1afd6e922b7a2128 Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 30 Apr 2024 01:41:19 -0700 Subject: [PATCH 77/88] Update restore queue uploader --- gradle.properties | 2 +- .../zontreck/libzontreck/blocks/RedstoneBlock.java | 4 ++++ .../libzontreck/events/RegisterPacketsEvent.java | 12 ------------ .../libzontreck/memory/world/BlockRestoreQueue.java | 2 +- .../zontreck/libzontreck/networking/ModMessages.java | 3 --- .../libzontreck/networking/NetworkEvents.java | 6 ------ 6 files changed, 6 insertions(+), 23 deletions(-) delete mode 100644 src/main/java/dev/zontreck/libzontreck/events/RegisterPacketsEvent.java diff --git a/gradle.properties b/gradle.properties index dd0e8b5..751f9ca 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.042624.1115 +mod_version=1201.13.043024.0139 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java b/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java index e90c6a9..740ef1d 100644 --- a/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java +++ b/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java @@ -7,6 +7,8 @@ import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.DiodeBlock; +import net.minecraft.world.level.block.PoweredBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.properties.BooleanProperty; @@ -57,4 +59,6 @@ public abstract class RedstoneBlock extends RotatableBlock } + + } diff --git a/src/main/java/dev/zontreck/libzontreck/events/RegisterPacketsEvent.java b/src/main/java/dev/zontreck/libzontreck/events/RegisterPacketsEvent.java deleted file mode 100644 index 344d8d2..0000000 --- a/src/main/java/dev/zontreck/libzontreck/events/RegisterPacketsEvent.java +++ /dev/null @@ -1,12 +0,0 @@ -package dev.zontreck.libzontreck.events; - -import java.util.ArrayList; -import java.util.List; - -import dev.zontreck.libzontreck.networking.packets.IPacket; -import net.minecraftforge.eventbus.api.Event; - -@Deprecated -public class RegisterPacketsEvent extends Event -{ -} diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java index f8b309a..287c95c 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/BlockRestoreQueue.java @@ -260,7 +260,7 @@ public abstract class BlockRestoreQueue { RUNNING_TASK = LibZontreck.executor.scheduleAtFixedRate(RUNNER, 2000, interval, unit); - DATABASE_UPLOAD_RUNNER = LibZontreck.executor.scheduleAtFixedRate(new DatabaseUploadRunner(this), 2000, 250, TimeUnit.MILLISECONDS); + DATABASE_UPLOAD_RUNNER = LibZontreck.executor.scheduleAtFixedRate(new DatabaseUploadRunner(this), 2000, 50, TimeUnit.MILLISECONDS); isCancelled=false; } diff --git a/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java b/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java index 5dd07f4..371b85b 100644 --- a/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java +++ b/src/main/java/dev/zontreck/libzontreck/networking/ModMessages.java @@ -1,14 +1,11 @@ package dev.zontreck.libzontreck.networking; import dev.zontreck.libzontreck.LibZontreck; -import dev.zontreck.libzontreck.events.RegisterPacketsEvent; -import dev.zontreck.libzontreck.networking.packets.IPacket; import dev.zontreck.libzontreck.networking.packets.S2CCloseChestGUI; import dev.zontreck.libzontreck.networking.packets.S2CPlaySoundPacket; import dev.zontreck.libzontreck.networking.packets.S2CServerAvailable; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.level.ServerPlayer; -import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.network.NetworkDirection; import net.minecraftforge.network.NetworkRegistry; import net.minecraftforge.network.PacketDistributor; diff --git a/src/main/java/dev/zontreck/libzontreck/networking/NetworkEvents.java b/src/main/java/dev/zontreck/libzontreck/networking/NetworkEvents.java index d6f1eea..c87b891 100644 --- a/src/main/java/dev/zontreck/libzontreck/networking/NetworkEvents.java +++ b/src/main/java/dev/zontreck/libzontreck/networking/NetworkEvents.java @@ -1,11 +1,5 @@ package dev.zontreck.libzontreck.networking; -import dev.zontreck.libzontreck.events.RegisterPacketsEvent; -import dev.zontreck.libzontreck.networking.packets.S2CPlaySoundPacket; -import dev.zontreck.libzontreck.networking.packets.S2CWalletInitialSyncPacket; -import dev.zontreck.libzontreck.networking.packets.S2CWalletUpdatedPacket; -import net.minecraftforge.eventbus.api.SubscribeEvent; - public class NetworkEvents { } From 29245e45cffe9d2eaef1d6e4650917bab9259168 Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 30 Apr 2024 02:47:50 -0700 Subject: [PATCH 78/88] Attempt to fix the Redstone block implementation --- gradle.properties | 2 +- .../zontreck/libzontreck/blocks/RedstoneBlock.java | 13 ++++++++++--- .../zontreck/libzontreck/blocks/RotatableBlock.java | 13 +++++++++++++ 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index 751f9ca..b2b5c09 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.043024.0139 +mod_version=1201.13.043024.0247 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java b/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java index 740ef1d..bde4725 100644 --- a/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java +++ b/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java @@ -7,8 +7,6 @@ import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.DiodeBlock; -import net.minecraft.world.level.block.PoweredBlock; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; import net.minecraft.world.level.block.state.properties.BooleanProperty; @@ -59,6 +57,15 @@ public abstract class RedstoneBlock extends RotatableBlock } + @Override + public void neighborChanged(BlockState state, Level level, BlockPos pos, Block block, BlockPos other, boolean unknown) { + onRedstone(level, pos, redstoneIsActivated(level, pos)); + state.setValue(INPUT_POWER, redstoneIsActivated(level, pos)); + } - + @Override + public void onPlace(BlockState state, Level level, BlockPos pos, BlockState p_60569_, boolean p_60570_) { + onRedstone(level, pos, redstoneIsActivated(level, pos)); + state.setValue(INPUT_POWER, redstoneIsActivated(level, pos)); + } } diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlock.java b/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlock.java index ebffa1d..8526891 100644 --- a/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlock.java +++ b/src/main/java/dev/zontreck/libzontreck/blocks/RotatableBlock.java @@ -3,6 +3,8 @@ package dev.zontreck.libzontreck.blocks; import net.minecraft.world.item.context.BlockPlaceContext; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.HorizontalDirectionalBlock; +import net.minecraft.world.level.block.Mirror; +import net.minecraft.world.level.block.Rotation; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; @@ -19,6 +21,17 @@ public class RotatableBlock extends HorizontalDirectionalBlock } + @Override + public BlockState rotate(BlockState p_55115_, Rotation p_55116_) { + return p_55115_.setValue(FACING, p_55116_.rotate(p_55115_.getValue(FACING))); + } + + @Override + public BlockState mirror(BlockState p_55112_, Mirror p_55113_) { + return p_55112_.rotate(p_55113_.getRotation(p_55112_.getValue(FACING))); + } + + @Override public BlockState getStateForPlacement(BlockPlaceContext pContext) { return defaultBlockState().setValue(FACING, pContext.getHorizontalDirection()); From 65a21bcefcdfb10ae75d4ae6482ee34ee48896c3 Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 30 Apr 2024 03:13:19 -0700 Subject: [PATCH 79/88] Add another helper method for redstone --- gradle.properties | 2 +- .../libzontreck/blocks/RedstoneBlock.java | 28 +++++++++++++++++-- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index b2b5c09..543c180 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.043024.0247 +mod_version=1201.13.043024.0307 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java b/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java index bde4725..04d2ae9 100644 --- a/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java +++ b/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java @@ -9,6 +9,7 @@ import net.minecraft.world.level.LevelReader; import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.StateDefinition; +import net.minecraft.world.level.block.state.properties.BlockStateProperties; import net.minecraft.world.level.block.state.properties.BooleanProperty; import org.jetbrains.annotations.Nullable; @@ -17,6 +18,7 @@ import java.util.List; public abstract class RedstoneBlock extends RotatableBlock { public static final BooleanProperty INPUT_POWER = BooleanProperty.create("inputpower"); + public static final BooleanProperty POWERED = BlockStateProperties.POWERED; private List sides; protected RedstoneBlock(Properties pProperties, Direction... validSides) { @@ -26,13 +28,13 @@ public abstract class RedstoneBlock extends RotatableBlock @Override public BlockState getStateForPlacement(BlockPlaceContext pContext) { - return super.getStateForPlacement(pContext).setValue(INPUT_POWER, false); + return super.getStateForPlacement(pContext).setValue(INPUT_POWER, false).setValue(POWERED, false); } @Override protected void createBlockStateDefinition(StateDefinition.Builder pBuilder) { super.createBlockStateDefinition(pBuilder); - pBuilder.add(INPUT_POWER); + pBuilder.add(INPUT_POWER, POWERED); } @Override @@ -48,24 +50,46 @@ public abstract class RedstoneBlock extends RotatableBlock else return false; } + @Override + public int getSignal(BlockState p_60483_, BlockGetter p_60484_, BlockPos p_60485_, Direction p_60486_) { + if(!sides.contains(p_60486_)) return 0; + + if(p_60483_.getValue(POWERED)) + return 15; + else return 0; + } + protected abstract void onRedstone(LevelReader level, BlockPos pos, boolean on); + protected abstract void onRedstoneInputChanged(LevelReader level, BlockPos pos, boolean on); @Override public void onNeighborChange(BlockState state, LevelReader level, BlockPos pos, BlockPos neighbor) { onRedstone(level, pos, redstoneIsActivated(level, pos)); + boolean inp = state.getValue(INPUT_POWER); state.setValue(INPUT_POWER, redstoneIsActivated(level, pos)); + + if(inp != redstoneIsActivated(level,pos)) + onRedstoneInputChanged(level, pos, state.getValue(INPUT_POWER)); } @Override public void neighborChanged(BlockState state, Level level, BlockPos pos, Block block, BlockPos other, boolean unknown) { onRedstone(level, pos, redstoneIsActivated(level, pos)); + boolean inp = state.getValue(INPUT_POWER); state.setValue(INPUT_POWER, redstoneIsActivated(level, pos)); + + if(inp != redstoneIsActivated(level,pos)) + onRedstoneInputChanged(level, pos, state.getValue(INPUT_POWER)); } @Override public void onPlace(BlockState state, Level level, BlockPos pos, BlockState p_60569_, boolean p_60570_) { onRedstone(level, pos, redstoneIsActivated(level, pos)); + boolean inp = state.getValue(INPUT_POWER); state.setValue(INPUT_POWER, redstoneIsActivated(level, pos)); + + if(inp != redstoneIsActivated(level,pos)) + onRedstoneInputChanged(level, pos, state.getValue(INPUT_POWER)); } } From 58a732782d07dd45dd4ddbdd6c05dd7d2181dcb7 Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 30 Apr 2024 04:04:48 -0700 Subject: [PATCH 80/88] Handle the redstone property different --- gradle.properties | 2 +- .../libzontreck/blocks/RedstoneBlock.java | 27 ++++++++++--------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/gradle.properties b/gradle.properties index 543c180..71124e1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.043024.0307 +mod_version=1201.13.043024.0404 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java b/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java index 04d2ae9..6b498b6 100644 --- a/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java +++ b/src/main/java/dev/zontreck/libzontreck/blocks/RedstoneBlock.java @@ -64,32 +64,35 @@ public abstract class RedstoneBlock extends RotatableBlock @Override public void onNeighborChange(BlockState state, LevelReader level, BlockPos pos, BlockPos neighbor) { - onRedstone(level, pos, redstoneIsActivated(level, pos)); + boolean rs = redstoneIsActivated(level, pos); + onRedstone(level, pos, rs); boolean inp = state.getValue(INPUT_POWER); - state.setValue(INPUT_POWER, redstoneIsActivated(level, pos)); + state.setValue(INPUT_POWER, rs); - if(inp != redstoneIsActivated(level,pos)) - onRedstoneInputChanged(level, pos, state.getValue(INPUT_POWER)); + if(inp != rs) + onRedstoneInputChanged(level, pos, rs); } @Override public void neighborChanged(BlockState state, Level level, BlockPos pos, Block block, BlockPos other, boolean unknown) { - onRedstone(level, pos, redstoneIsActivated(level, pos)); + boolean rs = redstoneIsActivated(level, pos); + onRedstone(level, pos, rs); boolean inp = state.getValue(INPUT_POWER); - state.setValue(INPUT_POWER, redstoneIsActivated(level, pos)); + state.setValue(INPUT_POWER, rs); - if(inp != redstoneIsActivated(level,pos)) - onRedstoneInputChanged(level, pos, state.getValue(INPUT_POWER)); + if(inp != rs) + onRedstoneInputChanged(level, pos, rs); } @Override public void onPlace(BlockState state, Level level, BlockPos pos, BlockState p_60569_, boolean p_60570_) { - onRedstone(level, pos, redstoneIsActivated(level, pos)); + boolean rs = redstoneIsActivated(level, pos); + onRedstone(level, pos, rs); boolean inp = state.getValue(INPUT_POWER); - state.setValue(INPUT_POWER, redstoneIsActivated(level, pos)); + state.setValue(INPUT_POWER, rs); - if(inp != redstoneIsActivated(level,pos)) - onRedstoneInputChanged(level, pos, state.getValue(INPUT_POWER)); + if(inp != rs) + onRedstoneInputChanged(level, pos, rs); } } From bd15d1f244fe8fe58eb984a6c6277a967c8f08dd Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 30 Apr 2024 15:13:01 -0700 Subject: [PATCH 81/88] Produce a test build to attempt to fix database not being properly set --- gradle.properties | 2 +- .../config/sections/DatabaseSection.java | 6 +++--- .../libzontreck/memory/world/DatabaseWrapper.java | 14 ++++++++++++++ 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/gradle.properties b/gradle.properties index 71124e1..1631c86 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.043024.0404 +mod_version=1201.13.043024.1512 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java b/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java index a89a298..774b5a5 100644 --- a/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java +++ b/src/main/java/dev/zontreck/libzontreck/config/sections/DatabaseSection.java @@ -16,13 +16,11 @@ public class DatabaseSection public String user = "root"; public String password = ""; public String host = "localhost:3306"; // IP:port - public String database = "savedBlocks"; + public String database = ""; public int version; public static final int VERSION = 1; - private boolean migrated=false; - public static DatabaseSection deserialize(CompoundTag tag) { DatabaseSection ret = new DatabaseSection(); @@ -42,6 +40,8 @@ public class DatabaseSection ret.database = tag.getString(TAG_DATABASE); } + + ret.version = VERSION; return ret; } diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java index 17fc901..f0c1bc8 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java @@ -33,6 +33,9 @@ public class DatabaseWrapper { public static void start() { instance = new DatabaseWrapper(); try { + LibZontreck.LOGGER.info("Connecting to database..."); + LibZontreck.LOGGER.info("jdbc:db ://" + ServerConfig.database.user + "@" + ServerConfig.database.host + "/" + ServerConfig.database.database); + instance.connect(ServerConfig.database.host, ServerConfig.database.user, ServerConfig.database.password, ServerConfig.database.database); } catch (SQLException e) { throw new RuntimeException(e); @@ -40,6 +43,17 @@ public class DatabaseWrapper { } public void connect(String url, String username, String password, String database) throws SQLException { + if(database.isBlank()) + { + ServerConfig.init(); + if(ServerConfig.database.database.isBlank()) + { + throw new SQLException("Failed to connect to database"); + } else { + start(); + return; + } + } try { // Try MariaDB JDBC driver Class.forName("org.mariadb.jdbc.Driver"); From 0d735ec2e8141ae7f35a46981f89b4bca320343f Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 30 Apr 2024 15:24:29 -0700 Subject: [PATCH 82/88] Fix a exception being thrown and crashing the server --- gradle.properties | 2 +- .../zontreck/libzontreck/memory/world/DatabaseMigrations.java | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 1631c86..490c2bb 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.043024.1512 +mod_version=1201.13.043024.1524 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java index feb2415..5ff1277 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java @@ -66,7 +66,7 @@ public class DatabaseMigrations try { DatabaseWrapper.get().executePreparedStatement(pstmt); } catch (SQLException e) { - throw new RuntimeException(e); + LibZontreck.LOGGER.warn("There was a problem executing a migration. The migration is " + pstmt+"\n\nThis does not necessarily mean a failure. If everything seems to work fine, this migration might not have been necessary."); } } @@ -159,6 +159,7 @@ public class DatabaseMigrations Migration lastTableChecked = null; for(Migration m : migrations) { + if(lastTableChecked == null) lastTableChecked = getCurrentTable(m.tableID); else { if(lastTableChecked.tableID != m.tableID) lastTableChecked = getCurrentTable(m.tableID); From aa9f0c52356d8cd1a728324a2c2ddd1fccea78f8 Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 30 Apr 2024 15:31:53 -0700 Subject: [PATCH 83/88] Go back and revise the database migrations. Breaking change --- gradle.properties | 2 +- .../libzontreck/memory/world/DatabaseMigrations.java | 11 +++-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/gradle.properties b/gradle.properties index 490c2bb..7540185 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.043024.1524 +mod_version=1201.13.043024.1530 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java index 5ff1277..7b34300 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java @@ -66,7 +66,8 @@ public class DatabaseMigrations try { DatabaseWrapper.get().executePreparedStatement(pstmt); } catch (SQLException e) { - LibZontreck.LOGGER.warn("There was a problem executing a migration. The migration is " + pstmt+"\n\nThis does not necessarily mean a failure. If everything seems to work fine, this migration might not have been necessary."); + LibZontreck.LOGGER.warn("There was a problem executing a migration. The migration is " + pstmt+"\n\nThis does not necessarily mean a failure. If everything seems to work fine, this migration might not have been necessary.\n\n"); + e.printStackTrace(); } } @@ -115,10 +116,7 @@ public class DatabaseMigrations " `posZ` int(11) NOT NULL," + " `snapshotID` int(11) NOT NULL DEFAULT 0 COMMENT 'Enables multiple blocks existing at the same position'," + " `block` blob NOT NULL COMMENT 'NBT Data representing a SavedBlock'," + - " PRIMARY KEY (`time`)," + - " UNIQUE KEY `posX` (`posX`)," + - " UNIQUE KEY `posY` (`posY`)," + - " UNIQUE KEY `posZ` (`posZ`)" + + " PRIMARY KEY (`time`)" + ") ;"); migrations.add(blocksTable.withMigrationAction(makeBlocksTable)); @@ -136,9 +134,6 @@ public class DatabaseMigrations migrations.add(builder() .withTableID("blocks") .withVersion(3) - .withMigrationAction(DatabaseWrapper.get().prepareStatement(" ALTER TABLE `blocks` DROP INDEX `posX`; ")) - .withMigrationAction(DatabaseWrapper.get().prepareStatement(" ALTER TABLE `blocks` DROP INDEX `posY`; ")) - .withMigrationAction(DatabaseWrapper.get().prepareStatement(" ALTER TABLE `blocks` DROP INDEX `posZ`; ")) .withMigrationAction(DatabaseWrapper.get().prepareStatement("ALTER TABLE `savedBlocks`.`blocks` ADD UNIQUE (`posX`, `posY`, `posZ`); "))); From a85c032253489d8aa2c20add0733fcb4120df485 Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 30 Apr 2024 15:34:34 -0700 Subject: [PATCH 84/88] Fix a typo --- .../zontreck/libzontreck/memory/world/DatabaseMigrations.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java index 7b34300..d535384 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseMigrations.java @@ -134,7 +134,7 @@ public class DatabaseMigrations migrations.add(builder() .withTableID("blocks") .withVersion(3) - .withMigrationAction(DatabaseWrapper.get().prepareStatement("ALTER TABLE `savedBlocks`.`blocks` ADD UNIQUE (`posX`, `posY`, `posZ`); "))); + .withMigrationAction(DatabaseWrapper.get().prepareStatement("ALTER TABLE `blocks` ADD UNIQUE (`posX`, `posY`, `posZ`); "))); From 1ad275a970e447a61dbc8cd3971ff9bf0cfc17bd Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 18 Jun 2024 14:45:41 -0700 Subject: [PATCH 85/88] Adds sanity checks --- gradle.properties | 2 +- .../memory/world/DatabaseWrapper.java | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 7540185..2e5096f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.043024.1530 +mod_version=1201.13.050624.1254 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java index f0c1bc8..06baa78 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java @@ -14,6 +14,12 @@ public class DatabaseWrapper { public static DatabaseWrapper get() { if (instance == null) start(); + + try { + instance.sanityCheck(); + } catch (SQLException e) { + throw new RuntimeException(e); + } return instance; } @@ -42,6 +48,18 @@ public class DatabaseWrapper { } } + private void restart() { + + LibZontreck.LOGGER.info("Reconnecting to database..."); + LibZontreck.LOGGER.info("jdbc:db ://" + ServerConfig.database.user + "@" + ServerConfig.database.host + "/" + ServerConfig.database.database); + + try { + instance.connect(ServerConfig.database.host, ServerConfig.database.user, ServerConfig.database.password, ServerConfig.database.database); + } catch (SQLException e) { + throw new RuntimeException(e); + } + } + public void connect(String url, String username, String password, String database) throws SQLException { if(database.isBlank()) { @@ -85,6 +103,12 @@ public class DatabaseWrapper { } } + private void sanityCheck() throws SQLException { + if(connection.isClosed() || connection == null) { + restart(); + } + } + public ResultSet executeQuery(String query) throws SQLException { if (connection == null) { throw new SQLException("Connection not established."); From c3e2cc23c16e6798ffcb513c8fb648aa14927db3 Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 18 Jun 2024 15:03:57 -0700 Subject: [PATCH 86/88] Do not crash when no DB is present. --- gradle.properties | 2 +- .../java/dev/zontreck/libzontreck/LibZontreck.java | 9 ++++++++- .../libzontreck/memory/world/DatabaseWrapper.java | 10 ++++++++++ 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 2e5096f..24aeb5d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -53,7 +53,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.050624.1254 +mod_version=1201.13.061824.1459 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html diff --git a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java index 9356f31..7a8271e 100644 --- a/src/main/java/dev/zontreck/libzontreck/LibZontreck.java +++ b/src/main/java/dev/zontreck/libzontreck/LibZontreck.java @@ -120,7 +120,12 @@ public class LibZontreck { ALIVE=true; ServerConfig.init(); - DatabaseWrapper.start(); + try { + DatabaseWrapper.start(); + }catch(RuntimeException e) { + LOGGER.warn("Database not configured properly, it will not be available."); + DatabaseWrapper.invalidate(); + } CURRENT_SIDE = LogicalSide.SERVER; @@ -132,6 +137,8 @@ public class LibZontreck { BlockRestoreQueueRegistry.init(level); } + if(!DatabaseWrapper.hasDB)return; + try { DatabaseMigrations.initMigrations(); } catch (SQLException e) { diff --git a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java index 06baa78..35c8d59 100644 --- a/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java +++ b/src/main/java/dev/zontreck/libzontreck/memory/world/DatabaseWrapper.java @@ -8,10 +8,15 @@ import java.sql.*; public class DatabaseWrapper { private Connection connection; + public static boolean hasDB = true; private static DatabaseWrapper instance; public static DatabaseWrapper get() { + if(!hasDB) { + throw new RuntimeException("Error: Database is not set up"); + } + if (instance == null) start(); @@ -48,6 +53,11 @@ public class DatabaseWrapper { } } + public static void invalidate() { + instance=null; + hasDB=false; + } + private void restart() { LibZontreck.LOGGER.info("Reconnecting to database..."); From ee9dc0a985002b35db0a10c32cb95c422d849a4f Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 18 Jun 2024 15:30:01 -0700 Subject: [PATCH 87/88] Update project structure to 1.21 --- build.gradle | 172 ++++++++---------- gradle.properties | 48 ++--- gradle/wrapper/gradle-wrapper.jar | Bin 61574 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 3 +- settings.gradle | 2 +- .../{mods.toml => neoforge.mods.toml} | 0 6 files changed, 90 insertions(+), 135 deletions(-) rename src/main/resources/META-INF/{mods.toml => neoforge.mods.toml} (100%) diff --git a/build.gradle b/build.gradle index dfa221a..51de99d 100644 --- a/build.gradle +++ b/build.gradle @@ -2,11 +2,19 @@ plugins { id 'eclipse' id 'idea' id 'maven-publish' - id 'net.neoforged.gradle' version '[6.0.18,6.2)' - id 'org.spongepowered.mixin' version '0.7.+' + id 'net.neoforged.gradle.userdev' version '7.0.142' //id 'org.parchmentmc.librarian.forgegradle' version '1.+' } +tasks.named('wrapper', Wrapper).configure { + // Define wrapper values here so as to not have to always do so when updating gradlew.properties. + // Switching this to Wrapper.DistributionType.ALL will download the full gradle sources that comes with + // documentation attached on cursor hover of gradle classes and methods. However, this comes with increased + // file size for Gradle. If you do switch this to ALL, run the Gradle wrapper task twice afterwards. + // (Verify by checking gradle/wrapper/gradle-wrapper.properties to see if distributionUrl now points to `-all`) + distributionType = Wrapper.DistributionType.BIN +} + version = mod_version group = mod_group_id @@ -28,102 +36,55 @@ configurations { minecraftLibrary.extendsFrom(provided) } -// Mojang ships Java 17 to end users in 1.18+, so your mod should target Java 17. -java.toolchain.languageVersion = JavaLanguageVersion.of(17) +// Mojang ships Java 21 to end users starting in 1.20.5, so mods should target Java 21. +java.toolchain.languageVersion = JavaLanguageVersion.of(21) println "Java: ${System.getProperty 'java.version'}, JVM: ${System.getProperty 'java.vm.version'} (${System.getProperty 'java.vendor'}), Arch: ${System.getProperty 'os.arch'}" -minecraft { - // The mappings can be changed at any time and must be in the following format. - // Channel: Version: - // official MCVersion Official field/method names from Mojang mapping files - // parchment YYYY.MM.DD-MCVersion Open community-sourced parameter names and javadocs layered on top of official - // - // You must be aware of the Mojang license when using the 'official' or 'parchment' mappings. - // See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md - // - // Parchment is an unofficial project maintained by ParchmentMC, separate from MinecraftForge - // Additional setup is needed to use their mappings: https://parchmentmc.org/docs/getting-started - // - // Use non-default mappings at your own risk. They may not always work. - // Simply re-run your setup task after changing the mappings to update your workspace. - mappings channel: mapping_channel, version: mapping_version +// Default run configurations. +// These can be tweaked, removed, or duplicated as needed. +runs { + // applies to all the run configs below + configureEach { + // Recommended logging data for a userdev environment + // The markers can be added/remove as needed separated by commas. + // "SCAN": For mods scan. + // "REGISTRIES": For firing of registry events. + // "REGISTRYDUMP": For getting the contents of all registries. + systemProperty 'forge.logging.markers', 'REGISTRIES' - // When true, this property will have all Eclipse/IntelliJ IDEA run configurations run the "prepareX" task for the given run configuration before launching the game. - // In most cases, it is not necessary to enable. - // enableEclipsePrepareRuns = true - // enableIdeaPrepareRuns = true + // Recommended logging level for the console + // You can set various levels here. + // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels + systemProperty 'forge.logging.console.level', 'debug' - // This property allows configuring Gradle's ProcessResources task(s) to run on IDE output locations before launching the game. - // It is REQUIRED to be set to true for this template to function. - // See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html - copyIdeResources = true + modSource project.sourceSets.main + } - // When true, this property will add the folder name of all declared run configurations to generated IDE run configurations. - // The folder name can be set on a run configuration using the "folderName" property. - // By default, the folder name of a run configuration is the name of the Gradle project containing it. - generateRunFolders = true + client { + // Comma-separated list of namespaces to load gametests from. Empty = all namespaces. + systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + } - // This property enables access transformers for use in development. - // They will be applied to the Minecraft artifact. - // The access transformer file can be anywhere in the project. - // However, it must be at "META-INF/accesstransformer.cfg" in the final mod jar to be loaded by Forge. - // This default location is a best practice to automatically put the file in the right place in the final jar. - // See https://docs.minecraftforge.net/en/latest/advanced/accesstransformers/ for more information. - // accessTransformer = file('src/main/resources/META-INF/accesstransformer.cfg') + server { + systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + programArgument '--nogui' + } - // Default run configurations. - // These can be tweaked, removed, or duplicated as needed. - runs { - // applies to all the run configs below - configureEach { - workingDirectory project.file("run/${it.name}") + // This run config launches GameTestServer and runs all registered gametests, then exits. + // By default, the server will crash when no gametests are provided. + // The gametest system is also enabled by default for other run configs under the /test command. + gameTestServer { + systemProperty 'forge.enabledGameTestNamespaces', project.mod_id + } - // Recommended logging data for a userdev environment - // The markers can be added/remove as needed separated by commas. - // "SCAN": For mods scan. - // "REGISTRIES": For firing of registry events. - // "REGISTRYDUMP": For getting the contents of all registries. - property 'forge.logging.markers', 'REGISTRIES' + data { + // example of overriding the workingDirectory set in configureEach above, uncomment if you want to use it + // workingDirectory project.file('run-data') - // Recommended logging level for the console - // You can set various levels here. - // Please read: https://stackoverflow.com/questions/2031163/when-to-use-the-different-log-levels - property 'forge.logging.console.level', 'debug' - - mods { - "${mod_id}" { - source sourceSets.main - } - } - } - - client { - // Comma-separated list of namespaces to load gametests from. Empty = all namespaces. - property 'forge.enabledGameTestNamespaces', mod_id - } - - server { - property 'forge.enabledGameTestNamespaces', mod_id - args '--nogui' - } - - // This run config launches GameTestServer and runs all registered gametests, then exits. - // By default, the server will crash when no gametests are provided. - // The gametest system is also enabled by default for other run configs under the /test command. - gameTestServer { - property 'forge.enabledGameTestNamespaces', mod_id - } - - data { - // example of overriding the workingDirectory set in configureEach above, uncomment if you want to use it - // workingDirectory project.file('run-data') - - // Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources. - args '--mod', mod_id, '--all', '--output', file('src/generated/resources/'), '--existing', file('src/main/resources/') - } + // Specify the modid for data generation, where to output the resulting resource, and where to look for existing resources. + programArguments.addAll '--mod', project.mod_id, '--all', '--output', file('src/generated/resources/').getAbsolutePath(), '--existing', file('src/main/resources/').getAbsolutePath() } } - // Include resources generated by data generators. sourceSets.main.resources { srcDir 'src/generated/resources' } @@ -159,7 +120,7 @@ dependencies { // The "userdev" classifier will be requested and setup by ForgeGradle. // If the group id is "net.minecraft" and the artifact id is one of ["client", "server", "joined"], // then special handling is done to allow a setup of a vanilla dependency without the use of an external repository. - minecraft "net.neoforged:forge:${minecraft_version}-${neo_version}" + implementation "net.neoforged:neoforge:${neo_version}" provided "dev.zontreck:LibAC:${libac}" @@ -179,29 +140,29 @@ dependencies { // http://www.gradle.org/docs/current/userguide/dependency_management.html } + // This block of code expands all declared replace properties in the specified resource targets. // A missing property will result in an error. Properties are expanded using ${} Groovy notation. // When "copyIdeResources" is enabled, this will also run before the game launches in IDE environments. // See https://docs.gradle.org/current/dsl/org.gradle.language.jvm.tasks.ProcessResources.html tasks.withType(ProcessResources).configureEach { var replaceProperties = [ - minecraft_version : minecraft_version, + 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, - pack_format_number: pack_format_number, + 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', 'pack.mcmeta']) { - expand replaceProperties + [project: project] + filesMatching(['META-INF/neoforge.mods.toml']) { + expand replaceProperties } } @@ -258,6 +219,15 @@ publishing { } } + tasks.withType(JavaCompile).configureEach { options.encoding = 'UTF-8' // Use the UTF-8 charset for Java compilation } + +// IDEA no longer automatically downloads sources/javadoc jars for dependencies, so we need to explicitly enable the behavior. +idea { + module { + downloadSources = true + downloadJavadoc = true + } +} diff --git a/gradle.properties b/gradle.properties index 24aeb5d..9c342a1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -10,37 +10,24 @@ org.gradle.daemon=false libac=1.5.11 ## Environment Properties -# The Minecraft version must agree with the Forge version to get a valid artifact -minecraft_version=1.20.1 +#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.05.01 +# Environment Properties +# You can find the latest versions here: https://projects.neoforged.net/neoforged/neoforge +# The Minecraft version must agree with the Neo version to get a valid artifact +minecraft_version=1.21 # The Minecraft version range can use any release version of Minecraft as bounds. # Snapshots, pre-releases, and release candidates are not guaranteed to sort properly # as they do not follow standard versioning conventions. - -minecraft_version_range=[1.20,1.21) +minecraft_version_range=[1.21,1.21.1) # The Neo version must agree with the Minecraft version to get a valid artifact -neo_version=47.1.65 -# The Neo version range can use any version of Neo as bounds or match the loader version range -neo_version_range=[47.1,) -# The loader version range can only use the major version of Neo/FML as bounds -loader_version_range=[47,) -# The mapping channel to use for mappings. -# The default set of supported mapping channels are ["official", "snapshot", "snapshot_nodoc", "stable", "stable_nodoc"]. -# Additional mapping channels can be registered through the "channelProviders" extension in a Gradle plugin. -# -# | Channel | Version | | -# |-----------|----------------------|--------------------------------------------------------------------------------| -# | official | MCVersion | Official field/method names from Mojang mapping files | -# | parchment | YYYY.MM.DD-MCVersion | Open community-sourced parameter names and javadocs layered on top of official | -# -# You must be aware of the Mojang license when using the 'official' or 'parchment' mappings. -# See more information here: https://github.com/MinecraftForge/MCPConfig/blob/master/Mojang.md -# -# Parchment is an unofficial project maintained by ParchmentMC, separate from Minecraft Forge. -# Additional setup is needed to use their mappings, see https://parchmentmc.org/docs/getting-started -mapping_channel=official -# The mapping version to query from the mapping channel. -# This must match the format required by the mapping channel. -mapping_version=1.20.1 +neo_version=21.0.0-beta +# The Neo version range can use any version of Neo as bounds +neo_version_range=[21.0.0-beta,) +# The loader version range can only use the major version of FML as bounds +loader_version_range=[4,) ## Mod Properties @@ -53,7 +40,7 @@ mod_name=Zontreck's Library Mod # The license of the mod. Review your options at https://choosealicense.com/. All Rights Reserved is the default. mod_license=GPLv3 # The mod version. See https://semver.org/ -mod_version=1201.13.061824.1459 +mod_version=1210.1.061824.1524 # The group ID for the mod. It is only important when publishing as an artifact to a Maven repository. # This should match the base package used for the mod sources. # See https://maven.apache.org/guides/mini/guide-naming-conventions.html @@ -61,7 +48,4 @@ mod_group_id=dev.zontreck # The authors of the mod. This is a simple text string that is used for display purposes in the mod list. mod_authors=zontreck # The description of the mod. This is a simple multiline text string that is used for display purposes in the mod list. -mod_description=LibZontreck\nLibrary Mod! - -# Pack version - this changes each minecraft release, in general. -pack_format_number=15 \ No newline at end of file +mod_description=LibZontreck\nLibrary Mod! \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbfa754578e88a3dae77fce6e3dea56edbf..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ zQccdaaVK$5bz;*rnyK{_SQYM>;aES6Qs^lj9lEs6_J+%nIiuQC*fN;z8md>r_~Mfl zU%p5Dt_YT>gQqfr@`cR!$NWr~+`CZb%dn;WtzrAOI>P_JtsB76PYe*<%H(y>qx-`Kq!X_; z<{RpAqYhE=L1r*M)gNF3B8r(<%8mo*SR2hu zccLRZwGARt)Hlo1euqTyM>^!HK*!Q2P;4UYrysje@;(<|$&%vQekbn|0Ruu_Io(w4#%p6ld2Yp7tlA`Y$cciThP zKzNGIMPXX%&Ud0uQh!uQZz|FB`4KGD?3!ND?wQt6!n*f4EmCoJUh&b?;B{|lxs#F- z31~HQ`SF4x$&v00@(P+j1pAaj5!s`)b2RDBp*PB=2IB>oBF!*6vwr7Dp%zpAx*dPr zb@Zjq^XjN?O4QcZ*O+8>)|HlrR>oD*?WQl5ri3R#2?*W6iJ>>kH%KnnME&TT@ZzrHS$Q%LC?n|e>V+D+8D zYc4)QddFz7I8#}y#Wj6>4P%34dZH~OUDb?uP%-E zwjXM(?Sg~1!|wI(RVuxbu)-rH+O=igSho_pDCw(c6b=P zKk4ATlB?bj9+HHlh<_!&z0rx13K3ZrAR8W)!@Y}o`?a*JJsD+twZIv`W)@Y?Amu_u zz``@-e2X}27$i(2=9rvIu5uTUOVhzwu%mNazS|lZb&PT;XE2|B&W1>=B58#*!~D&) zfVmJGg8UdP*fx(>Cj^?yS^zH#o-$Q-*$SnK(ZVFkw+er=>N^7!)FtP3y~Xxnu^nzY zikgB>Nj0%;WOltWIob|}%lo?_C7<``a5hEkx&1ku$|)i>Rh6@3h*`slY=9U}(Ql_< zaNG*J8vb&@zpdhAvv`?{=zDedJ23TD&Zg__snRAH4eh~^oawdYi6A3w8<Ozh@Kw)#bdktM^GVb zrG08?0bG?|NG+w^&JvD*7LAbjED{_Zkc`3H!My>0u5Q}m!+6VokMLXxl`Mkd=g&Xx z-a>m*#G3SLlhbKB!)tnzfWOBV;u;ftU}S!NdD5+YtOjLg?X}dl>7m^gOpihrf1;PY zvll&>dIuUGs{Qnd- zwIR3oIrct8Va^Tm0t#(bJD7c$Z7DO9*7NnRZorrSm`b`cxz>OIC;jSE3DO8`hX955ui`s%||YQtt2 z5DNA&pG-V+4oI2s*x^>-$6J?p=I>C|9wZF8z;VjR??Icg?1w2v5Me+FgAeGGa8(3S z4vg*$>zC-WIVZtJ7}o9{D-7d>zCe|z#<9>CFve-OPAYsneTb^JH!Enaza#j}^mXy1 z+ULn^10+rWLF6j2>Ya@@Kq?26>AqK{A_| zQKb*~F1>sE*=d?A?W7N2j?L09_7n+HGi{VY;MoTGr_)G9)ot$p!-UY5zZ2Xtbm=t z@dpPSGwgH=QtIcEulQNI>S-#ifbnO5EWkI;$A|pxJd885oM+ zGZ0_0gDvG8q2xebj+fbCHYfAXuZStH2j~|d^sBAzo46(K8n59+T6rzBwK)^rfPT+B zyIFw)9YC-V^rhtK`!3jrhmW-sTmM+tPH+;nwjL#-SjQPUZ53L@A>y*rt(#M(qsiB2 zx6B)dI}6Wlsw%bJ8h|(lhkJVogQZA&n{?Vgs6gNSXzuZpEyu*xySy8ro07QZ7Vk1!3tJphN_5V7qOiyK8p z#@jcDD8nmtYi1^l8ml;AF<#IPK?!pqf9D4moYk>d99Im}Jtwj6c#+A;f)CQ*f-hZ< z=p_T86jog%!p)D&5g9taSwYi&eP z#JuEK%+NULWus;0w32-SYFku#i}d~+{Pkho&^{;RxzP&0!RCm3-9K6`>KZpnzS6?L z^H^V*s!8<>x8bomvD%rh>Zp3>Db%kyin;qtl+jAv8Oo~1g~mqGAC&Qi_wy|xEt2iz zWAJEfTV%cl2Cs<1L&DLRVVH05EDq`pH7Oh7sR`NNkL%wi}8n>IXcO40hp+J+sC!W?!krJf!GJNE8uj zg-y~Ns-<~D?yqbzVRB}G>0A^f0!^N7l=$m0OdZuqAOQqLc zX?AEGr1Ht+inZ-Qiwnl@Z0qukd__a!C*CKuGdy5#nD7VUBM^6OCpxCa2A(X;e0&V4 zM&WR8+wErQ7UIc6LY~Q9x%Sn*Tn>>P`^t&idaOEnOd(Ufw#>NoR^1QdhJ8s`h^|R_ zXX`c5*O~Xdvh%q;7L!_!ohf$NfEBmCde|#uVZvEo>OfEq%+Ns7&_f$OR9xsihRpBb z+cjk8LyDm@U{YN>+r46?nn{7Gh(;WhFw6GAxtcKD+YWV?uge>;+q#Xx4!GpRkVZYu zzsF}1)7$?%s9g9CH=Zs+B%M_)+~*j3L0&Q9u7!|+T`^O{xE6qvAP?XWv9_MrZKdo& z%IyU)$Q95AB4!#hT!_dA>4e@zjOBD*Y=XjtMm)V|+IXzjuM;(l+8aA5#Kaz_$rR6! zj>#&^DidYD$nUY(D$mH`9eb|dtV0b{S>H6FBfq>t5`;OxA4Nn{J(+XihF(stSche7$es&~N$epi&PDM_N`As;*9D^L==2Q7Z2zD+CiU(|+-kL*VG+&9!Yb3LgPy?A zm7Z&^qRG_JIxK7-FBzZI3Q<;{`DIxtc48k> zc|0dmX;Z=W$+)qE)~`yn6MdoJ4co;%!`ddy+FV538Y)j(vg}5*k(WK)KWZ3WaOG!8 z!syGn=s{H$odtpqFrT#JGM*utN7B((abXnpDM6w56nhw}OY}0TiTG1#f*VFZr+^-g zbP10`$LPq_;PvrA1XXlyx2uM^mrjTzX}w{yuLo-cOClE8MMk47T25G8M!9Z5ypOSV zAJUBGEg5L2fY)ZGJb^E34R2zJ?}Vf>{~gB!8=5Z) z9y$>5c)=;o0HeHHSuE4U)#vG&KF|I%-cF6f$~pdYJWk_dD}iOA>iA$O$+4%@>JU08 zS`ep)$XLPJ+n0_i@PkF#ri6T8?ZeAot$6JIYHm&P6EB=BiaNY|aA$W0I+nz*zkz_z zkEru!tj!QUffq%)8y0y`T&`fuus-1p>=^hnBiBqD^hXrPs`PY9tU3m0np~rISY09> z`P3s=-kt_cYcxWd{de@}TwSqg*xVhp;E9zCsnXo6z z?f&Sv^U7n4`xr=mXle94HzOdN!2kB~4=%)u&N!+2;z6UYKUDqi-s6AZ!haB;@&B`? z_TRX0%@suz^TRdCb?!vNJYPY8L_}&07uySH9%W^Tc&1pia6y1q#?*Drf}GjGbPjBS zbOPcUY#*$3sL2x4v_i*Y=N7E$mR}J%|GUI(>WEr+28+V z%v5{#e!UF*6~G&%;l*q*$V?&r$Pp^sE^i-0$+RH3ERUUdQ0>rAq2(2QAbG}$y{de( z>{qD~GGuOk559Y@%$?N^1ApVL_a704>8OD%8Y%8B;FCt%AoPu8*D1 zLB5X>b}Syz81pn;xnB}%0FnwazlWfUV)Z-~rZg6~b z6!9J$EcE&sEbzcy?CI~=boWA&eeIa%z(7SE^qgVLz??1Vbc1*aRvc%Mri)AJaAG!p z$X!_9Ds;Zz)f+;%s&dRcJt2==P{^j3bf0M=nJd&xwUGlUFn?H=2W(*2I2Gdu zv!gYCwM10aeus)`RIZSrCK=&oKaO_Ry~D1B5!y0R=%!i2*KfXGYX&gNv_u+n9wiR5 z*e$Zjju&ODRW3phN925%S(jL+bCHv6rZtc?!*`1TyYXT6%Ju=|X;6D@lq$8T zW{Y|e39ioPez(pBH%k)HzFITXHvnD6hw^lIoUMA;qAJ^CU?top1fo@s7xT13Fvn1H z6JWa-6+FJF#x>~+A;D~;VDs26>^oH0EI`IYT2iagy23?nyJ==i{g4%HrAf1-*v zK1)~@&(KkwR7TL}L(A@C_S0G;-GMDy=MJn2$FP5s<%wC)4jC5PXoxrQBFZ_k0P{{s@sz+gX`-!=T8rcB(=7vW}^K6oLWMmp(rwDh}b zwaGGd>yEy6fHv%jM$yJXo5oMAQ>c9j`**}F?MCry;T@47@r?&sKHgVe$MCqk#Z_3S z1GZI~nOEN*P~+UaFGnj{{Jo@16`(qVNtbU>O0Hf57-P>x8Jikp=`s8xWs^dAJ9lCQ z)GFm+=OV%AMVqVATtN@|vp61VVAHRn87}%PC^RAzJ%JngmZTasWBAWsoAqBU+8L8u z4A&Pe?fmTm0?mK-BL9t+{y7o(7jm+RpOhL9KnY#E&qu^}B6=K_dB}*VlSEiC9fn)+V=J;OnN)Ta5v66ic1rG+dGAJ1 z1%Zb_+!$=tQ~lxQrzv3x#CPb?CekEkA}0MYSgx$Jdd}q8+R=ma$|&1a#)TQ=l$1tQ z=tL9&_^vJ)Pk}EDO-va`UCT1m#Uty1{v^A3P~83_#v^ozH}6*9mIjIr;t3Uv%@VeW zGL6(CwCUp)Jq%G0bIG%?{_*Y#5IHf*5M@wPo6A{$Um++Co$wLC=J1aoG93&T7Ho}P z=mGEPP7GbvoG!uD$k(H3A$Z))+i{Hy?QHdk>3xSBXR0j!11O^mEe9RHmw!pvzv?Ua~2_l2Yh~_!s1qS`|0~0)YsbHSz8!mG)WiJE| z2f($6TQtt6L_f~ApQYQKSb=`053LgrQq7G@98#igV>y#i==-nEjQ!XNu9 z~;mE+gtj4IDDNQJ~JVk5Ux6&LCSFL!y=>79kE9=V}J7tD==Ga+IW zX)r7>VZ9dY=V&}DR))xUoV!u(Z|%3ciQi_2jl}3=$Agc(`RPb z8kEBpvY>1FGQ9W$n>Cq=DIpski};nE)`p3IUw1Oz0|wxll^)4dq3;CCY@RyJgFgc# zKouFh!`?Xuo{IMz^xi-h=StCis_M7yq$u) z?XHvw*HP0VgR+KR6wI)jEMX|ssqYvSf*_3W8zVTQzD?3>H!#>InzpSO)@SC8q*ii- z%%h}_#0{4JG;Jm`4zg};BPTGkYamx$Xo#O~lBirRY)q=5M45n{GCfV7h9qwyu1NxOMoP4)jjZMxmT|IQQh0U7C$EbnMN<3)Kk?fFHYq$d|ICu>KbY_hO zTZM+uKHe(cIZfEqyzyYSUBZa8;Fcut-GN!HSA9ius`ltNebF46ZX_BbZNU}}ZOm{M2&nANL9@0qvih15(|`S~z}m&h!u4x~(%MAO$jHRWNfuxWF#B)E&g3ghSQ9|> z(MFaLQj)NE0lowyjvg8z0#m6FIuKE9lDO~Glg}nSb7`~^&#(Lw{}GVOS>U)m8bF}x zVjbXljBm34Cs-yM6TVusr+3kYFjr28STT3g056y3cH5Tmge~ASxBj z%|yb>$eF;WgrcOZf569sDZOVwoo%8>XO>XQOX1OyN9I-SQgrm;U;+#3OI(zrWyow3 zk==|{lt2xrQ%FIXOTejR>;wv(Pb8u8}BUpx?yd(Abh6? zsoO3VYWkeLnF43&@*#MQ9-i-d0t*xN-UEyNKeyNMHw|A(k(_6QKO=nKMCxD(W(Yop zsRQ)QeL4X3Lxp^L%wzi2-WVSsf61dqliPUM7srDB?Wm6Lzn0&{*}|IsKQW;02(Y&| zaTKv|`U(pSzuvR6Rduu$wzK_W-Y-7>7s?G$)U}&uK;<>vU}^^ns@Z!p+9?St1s)dG zK%y6xkPyyS1$~&6v{kl?Md6gwM|>mt6Upm>oa8RLD^8T{0?HC!Z>;(Bob7el(DV6x zi`I)$&E&ngwFS@bi4^xFLAn`=fzTC;aimE^!cMI2n@Vo%Ae-ne`RF((&5y6xsjjAZ zVguVoQ?Z9uk$2ON;ersE%PU*xGO@T*;j1BO5#TuZKEf(mB7|g7pcEA=nYJ{s3vlbg zd4-DUlD{*6o%Gc^N!Nptgay>j6E5;3psI+C3Q!1ZIbeCubW%w4pq9)MSDyB{HLm|k zxv-{$$A*pS@csolri$Ge<4VZ}e~78JOL-EVyrbxKra^d{?|NnPp86!q>t<&IP07?Z z^>~IK^k#OEKgRH+LjllZXk7iA>2cfH6+(e&9ku5poo~6y{GC5>(bRK7hwjiurqAiZ zg*DmtgY}v83IjE&AbiWgMyFbaRUPZ{lYiz$U^&Zt2YjG<%m((&_JUbZcfJ22(>bi5 z!J?<7AySj0JZ&<-qXX;mcV!f~>G=sB0KnjWca4}vrtunD^1TrpfeS^4dvFr!65knK zZh`d;*VOkPs4*-9kL>$GP0`(M!j~B;#x?Ba~&s6CopvO86oM?-? zOw#dIRc;6A6T?B`Qp%^<U5 z19x(ywSH$_N+Io!6;e?`tWaM$`=Db!gzx|lQ${DG!zb1Zl&|{kX0y6xvO1o z220r<-oaS^^R2pEyY;=Qllqpmue|5yI~D|iI!IGt@iod{Opz@*ml^w2bNs)p`M(Io z|E;;m*Xpjd9l)4G#KaWfV(t8YUn@A;nK^#xgv=LtnArX|vWQVuw3}B${h+frU2>9^ z!l6)!Uo4`5k`<<;E(ido7M6lKTgWezNLq>U*=uz&s=cc$1%>VrAeOoUtA|T6gO4>UNqsdK=NF*8|~*sl&wI=x9-EGiq*aqV!(VVXA57 zw9*o6Ir8Lj1npUXvlevtn(_+^X5rzdR>#(}4YcB9O50q97%rW2me5_L=%ffYPUSRc z!vv?Kv>dH994Qi>U(a<0KF6NH5b16enCp+mw^Hb3Xs1^tThFpz!3QuN#}KBbww`(h z7GO)1olDqy6?T$()R7y%NYx*B0k_2IBiZ14&8|JPFxeMF{vSTxF-Vi3+ZOI=Thq2} zyQgjYY1_7^ZQHh{?P))4+qUiQJLi1&{yE>h?~jU%tjdV0h|FENbM3X(KnJdPKc?~k zh=^Ixv*+smUll!DTWH!jrV*wSh*(mx0o6}1@JExzF(#9FXgmTXVoU+>kDe68N)dkQ zH#_98Zv$}lQwjKL@yBd;U(UD0UCl322=pav<=6g>03{O_3oKTq;9bLFX1ia*lw;#K zOiYDcBJf)82->83N_Y(J7Kr_3lE)hAu;)Q(nUVydv+l+nQ$?|%MWTy`t>{havFSQloHwiIkGK9YZ79^9?AZo0ZyQlVR#}lF%dn5n%xYksXf8gnBm=wO7g_^! zauQ-bH1Dc@3ItZ-9D_*pH}p!IG7j8A_o94#~>$LR|TFq zZ-b00*nuw|-5C2lJDCw&8p5N~Z1J&TrcyErds&!l3$eSz%`(*izc;-?HAFD9AHb-| z>)id`QCrzRws^9(#&=pIx9OEf2rmlob8sK&xPCWS+nD~qzU|qG6KwA{zbikcfQrdH z+ zQg>O<`K4L8rN7`GJB0*3<3`z({lWe#K!4AZLsI{%z#ja^OpfjU{!{)x0ZH~RB0W5X zTwN^w=|nA!4PEU2=LR05x~}|B&ZP?#pNgDMwD*ajI6oJqv!L81gu=KpqH22avXf0w zX3HjbCI!n9>l046)5rr5&v5ja!xkKK42zmqHzPx$9Nn_MZk`gLeSLgC=LFf;H1O#B zn=8|^1iRrujHfbgA+8i<9jaXc;CQBAmQvMGQPhFec2H1knCK2x!T`e6soyrqCamX% zTQ4dX_E*8so)E*TB$*io{$c6X)~{aWfaqdTh=xEeGvOAN9H&-t5tEE-qso<+C!2>+ zskX51H-H}#X{A75wqFe-J{?o8Bx|>fTBtl&tcbdR|132Ztqu5X0i-pisB-z8n71%q%>EF}yy5?z=Ve`}hVh{Drv1YWL zW=%ug_&chF11gDv3D6B)Tz5g54H0mDHNjuKZ+)CKFk4Z|$RD zfRuKLW`1B>B?*RUfVd0+u8h3r-{@fZ{k)c!93t1b0+Q9vOaRnEn1*IL>5Z4E4dZ!7 ztp4GP-^1d>8~LMeb}bW!(aAnB1tM_*la=Xx)q(I0Y@__Zd$!KYb8T2VBRw%e$iSdZ zkwdMwd}eV9q*;YvrBFTv1>1+}{H!JK2M*C|TNe$ZSA>UHKk);wz$(F$rXVc|sI^lD zV^?_J!3cLM;GJuBMbftbaRUs$;F}HDEDtIeHQ)^EJJ1F9FKJTGH<(Jj`phE6OuvE) zqK^K`;3S{Y#1M@8yRQwH`?kHMq4tHX#rJ>5lY3DM#o@or4&^_xtBC(|JpGTfrbGkA z2Tu+AyT^pHannww!4^!$5?@5v`LYy~T`qs7SYt$JgrY(w%C+IWA;ZkwEF)u5sDvOK zGk;G>Mh&elvXDcV69J_h02l&O;!{$({fng9Rlc3ID#tmB^FIG^w{HLUpF+iB`|
NnX)EH+Nua)3Y(c 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 ScNgau(U?ep-K_E5zy1%ZQTdPn diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37aef8d..b82aa23 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle index 8a72e7b..addbe03 100644 --- a/settings.gradle +++ b/settings.gradle @@ -6,5 +6,5 @@ pluginManagement { } plugins { - id 'org.gradle.toolchains.foojay-resolver-convention' version '0.5.0' + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' } diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/neoforge.mods.toml similarity index 100% rename from src/main/resources/META-INF/mods.toml rename to src/main/resources/META-INF/neoforge.mods.toml From 8d5846183a48e3d0fe81a3fa59b11ef256f584a2 Mon Sep 17 00:00:00 2001 From: zontreck Date: Tue, 18 Jun 2024 15:53:57 -0700 Subject: [PATCH 88/88] Do some more updating of project files --- build.gradle | 1 + gradle.properties | 4 +- gradlew | 29 +++++--- gradlew.bat | 184 +++++++++++++++++++++++----------------------- settings.gradle | 3 +- 5 files changed, 114 insertions(+), 107 deletions(-) diff --git a/build.gradle b/build.gradle index 51de99d..4e7901f 100644 --- a/build.gradle +++ b/build.gradle @@ -89,6 +89,7 @@ runs { sourceSets.main.resources { srcDir 'src/generated/resources' } repositories { + mavenLocal() //mavenCentral() maven { name = "Aria's Creations Caches" diff --git a/gradle.properties b/gradle.properties index 9c342a1..d81f921 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ # Sets default memory used for gradle commands. Can be overridden by user or command line properties. -# This is required to provide enough memory for the Minecraft decompilation process. -org.gradle.jvmargs=-Xmx3G +org.gradle.jvmargs=-Xmx1G org.gradle.daemon=false +org.gradle.debug=false # parchment_version=2023.09.03 diff --git a/gradlew b/gradlew index 65dcd68..1aa94a4 100755 --- a/gradlew +++ b/gradlew @@ -83,10 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# 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"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +131,13 @@ 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. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +145,7 @@ 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 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# 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. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/gradlew.bat b/gradlew.bat index 6689b85..25da30d 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,92 +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 +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle index addbe03..bc385b9 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,7 +1,8 @@ pluginManagement { repositories { + mavenLocal() gradlePluginPortal() - maven { url = "https://maven.zontreck.com/repository/internal" } + maven { url = "https://maven.neoforged.net/releases" } } }