diff --git a/gradle.properties b/gradle.properties index 384d36492..3924a9eb8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,10 +1,10 @@ group = net.pl3x.purpur -version = 1.17-R0.1-SNAPSHOT +version = 1.17.1-R0.1-SNAPSHOT -mcVersion = 1.17 +mcVersion = 1.17.1 packageVersion = 1_17_R1 -paperCommit = cc063e1f09c116c9fc9786b40f7c36577a6a7264 +paperCommit = 091319d165bbc311443790aa03ff95a4ccc01780 org.gradle.parallel = true org.gradle.vfs.watch = false diff --git a/patches/api/0001-Tuinity-API-Changes.patch b/patches/api/0001-Tuinity-API-Changes.patch index 26787ba04..99624a52c 100644 --- a/patches/api/0001-Tuinity-API-Changes.patch +++ b/patches/api/0001-Tuinity-API-Changes.patch @@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 07c9635ea2e09e823ed7fa164ed854af41fab7dc..8482c4a664a02614fd59f74a4f9dd6d2e32003f5 100644 +index f05edac8cdd33daaf1d15a526be4d2ac2b08846d..8776b8368d2046dee02e927de8249030bdddf2ee 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java @@ -1626,6 +1626,14 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi @@ -35,106 +35,3 @@ index 07c9635ea2e09e823ed7fa164ed854af41fab7dc..8482c4a664a02614fd59f74a4f9dd6d2 /** * Sends the component to the player * -diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java -index 8ae9198ba7fdb006dc420504a984627add20dbb5..4017cc64532a9a8e42c3a6492878cd96db13fcb3 100644 ---- a/src/main/java/org/bukkit/World.java -+++ b/src/main/java/org/bukkit/World.java -@@ -3639,6 +3639,26 @@ public interface World extends PluginMessageRecipient, Metadatable, net.kyori.ad - * @param viewDistance view distance in [2, 32] - */ - void setNoTickViewDistance(int viewDistance); -+ -+ // Tuinity start - add view distances -+ /** -+ * Gets the sending view distance for this world. -+ *

-+ * Sending view distance is the view distance where chunks will load in for players in this world. -+ *

-+ * @return The sending view distance for this world. -+ */ -+ public int getSendViewDistance(); -+ -+ /** -+ * Sets the sending view distance for this world. -+ *

-+ * Sending view distance is the view distance where chunks will load in for players in this world. -+ *

-+ * @param viewDistance view distance in [2, 32] or -1 -+ */ -+ public void setSendViewDistance(int viewDistance); -+ // Tuinity end - add view distances - // Paper end - view distance api - - // Spigot start -diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index da83b4cbed0be6f693c7cbb1cc032356f12d7883..51c334f68052f58fbb9c10fb9ed31ab42780ceac 100644 ---- a/src/main/java/org/bukkit/entity/Player.java -+++ b/src/main/java/org/bukkit/entity/Player.java -@@ -1818,23 +1818,63 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM - * Gets the view distance for this player - * - * @return the player's view distance -- * @deprecated This is unimplemented and will throw an exception at runtime. The {@link org.bukkit.World World}-based methods still work. -+ * // Tuinity - implemented - * @see org.bukkit.World#getViewDistance() - * @see org.bukkit.World#getNoTickViewDistance() - */ -- @Deprecated -+ //@Deprecated // Tuinity - implemented - public int getViewDistance(); - - /** - * Sets the view distance for this player - * - * @param viewDistance the player's view distance -- * @deprecated This is unimplemented and will throw an exception at runtime. The {@link org.bukkit.World World}-based methods still work. -+ * // Tuinity - implemented - * @see org.bukkit.World#setViewDistance(int) - * @see org.bukkit.World#setNoTickViewDistance(int) - */ -- @Deprecated -+ //@Deprecated // Tuinity - implemented - public void setViewDistance(int viewDistance); -+ -+ // Tuinity start - add view distances api -+ /** -+ * Gets the no-ticking view distance for this player. -+ *

-+ * No-tick view distance is the view distance where chunks will load, however the chunks and their entities will not -+ * be set to tick. -+ *

-+ * @return The no-tick view distance for this player. -+ */ -+ public int getNoTickViewDistance(); -+ -+ /** -+ * Sets the no-ticking view distance for this player. -+ *

-+ * No-tick view distance is the view distance where chunks will load, however the chunks and their entities will not -+ * be set to tick. -+ *

-+ * @param viewDistance view distance in [2, 32] or -1 -+ */ -+ public void setNoTickViewDistance(int viewDistance); -+ -+ /** -+ * Gets the sending view distance for this player. -+ *

-+ * Sending view distance is the view distance where chunks will load in for players. -+ *

-+ * @return The sending view distance for this player. -+ */ -+ public int getSendViewDistance(); -+ -+ /** -+ * Sets the sending view distance for this player. -+ *

-+ * Sending view distance is the view distance where chunks will load in for players. -+ *

-+ * @param viewDistance view distance in [2, 32] or -1 -+ */ -+ public void setSendViewDistance(int viewDistance); -+ // Tuinity end - add view distances api - // Paper end - - /** diff --git a/patches/api/0002-Build-System-Changes.patch b/patches/api/0002-Build-System-Changes.patch index 9b46f9429..c5f0d5db5 100644 --- a/patches/api/0002-Build-System-Changes.patch +++ b/patches/api/0002-Build-System-Changes.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Build System Changes todo: merge with rebrand patch diff --git a/build.gradle.kts b/build.gradle.kts -index e142072f31a41b25ac637970f79e71ab70c2f28c..407bf7c09304c76ef1dbec43bb5d665f02ce4840 100644 +index 7ad3e5153718f6d4ce8293a9790bc3c1158aeb8e..309d201b7d551efd4a5903e6d990b0e718af6a78 100644 --- a/build.gradle.kts +++ b/build.gradle.kts -@@ -27,6 +27,7 @@ dependencies { +@@ -29,6 +29,7 @@ dependencies { api("org.ow2.asm:asm:9.0") api("org.ow2.asm:asm-commons:9.0") api("org.apache.logging.log4j:log4j-api:2.14.1") // Paper diff --git a/patches/api/0009-AFK-API.patch b/patches/api/0009-AFK-API.patch index 6f2ad3095..bc5bf72d5 100644 --- a/patches/api/0009-AFK-API.patch +++ b/patches/api/0009-AFK-API.patch @@ -81,10 +81,10 @@ index 0000000000000000000000000000000000000000..0c8b3e5e4ba412624357ea5662a78862 + } +} diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 51c334f68052f58fbb9c10fb9ed31ab42780ceac..13970eb20ed6bbc12720cf6c0b609e6cf2952933 100644 +index 37ad0c478b83ecf63edfe62b5b2dcd81d6fe1e77..40782217f40146e2509e7808d7b354269a56dc1c 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -2166,4 +2166,25 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -2126,4 +2126,25 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM @Override Spigot spigot(); // Spigot end diff --git a/patches/api/0018-Player-invulnerabilities.patch b/patches/api/0018-Player-invulnerabilities.patch index 6195d067d..ab0033bfb 100644 --- a/patches/api/0018-Player-invulnerabilities.patch +++ b/patches/api/0018-Player-invulnerabilities.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Player invulnerabilities diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 13970eb20ed6bbc12720cf6c0b609e6cf2952933..4f9a182f777712b6d266b7c1acee541f85740085 100644 +index 40782217f40146e2509e7808d7b354269a56dc1c..d212021efd579bf5a527b6ef923279b055eb7754 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -2186,5 +2186,26 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -2146,5 +2146,26 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * Reset the idle timer back to 0 */ void resetIdleTimer(); diff --git a/patches/api/0032-Fix-javadoc-warnings-missing-param-and-return.patch b/patches/api/0032-Fix-javadoc-warnings-missing-param-and-return.patch index 07df90df4..67bd8d6f8 100644 --- a/patches/api/0032-Fix-javadoc-warnings-missing-param-and-return.patch +++ b/patches/api/0032-Fix-javadoc-warnings-missing-param-and-return.patch @@ -489,7 +489,7 @@ index 3afd5f5c0208a4ee93b5dbfc2aab2b9d2e8a7544..7838731e0e16bdccfb79e74ceb64148f /** diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index bab0c0d660a01e912d0a88aefbacf20895f4ef61..5fcf5e0e746d579b6c3abf7148554463770332bd 100644 +index 59734f9d2a2aa59f683b8fae5b6c19e61c2bc40d..5ca1d420a14797d8b2a867f867d19188084e7fa5 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java @@ -1599,6 +1599,9 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi @@ -566,7 +566,7 @@ index afb7b136b461202026290624836446cff9f9e45d..087579fdff09237409c9f80446e7a15a /** diff --git a/src/main/java/org/bukkit/WorldCreator.java b/src/main/java/org/bukkit/WorldCreator.java -index e6a83252f42da31ad38f8dc1beccc7aa2c3f54b8..f3b107210473f1707b051c15771ce3bf2a62f171 100644 +index c0454d977119b28115b7698a2c4287f0f56efa55..445c1c13a85f7abb5fd319f9aeb05572bb1f63f6 100644 --- a/src/main/java/org/bukkit/WorldCreator.java +++ b/src/main/java/org/bukkit/WorldCreator.java @@ -71,6 +71,8 @@ public class WorldCreator { @@ -587,20 +587,6 @@ index e6a83252f42da31ad38f8dc1beccc7aa2c3f54b8..f3b107210473f1707b051c15771ce3bf */ @NotNull public static WorldCreator ofKey(@NotNull NamespacedKey worldKey) { -@@ -293,11 +297,8 @@ public class WorldCreator { - * is as follows: - * {"structures": {"structures": {"village": {"salt": 8015723, "spacing": 32, "separation": 8}}}, "layers": [{"block": "stone", "height": 1}, {"block": "grass", "height": 1}], "biome":"plains"} - * -- * @see Custom -- * dimension (scroll to "When the generator ID type is -- * minecraft:flat)" -- * @param generatorSettings The settings that should be used by the -- * generator -+ * @see Custom dimension -+ * @param generatorSettings The settings that should be used by the generator - * @return This object, for chaining - */ - @NotNull diff --git a/src/main/java/org/bukkit/advancement/AdvancementDisplay.java b/src/main/java/org/bukkit/advancement/AdvancementDisplay.java index c2e161e8e14d9949165055b6051708c048e68338..2bb3b525a3974b6ccd223b2ba272933c1617ceac 100644 --- a/src/main/java/org/bukkit/advancement/AdvancementDisplay.java @@ -766,7 +752,7 @@ index 2e17b2d4f759531fbe9ee8e9b00c839186af09ca..9382234722792b5920a2456187e07958 /** diff --git a/src/main/java/org/bukkit/entity/ArmorStand.java b/src/main/java/org/bukkit/entity/ArmorStand.java -index 2f0c6af7fa6688a98d6aa0bd3f0e6556af8330d0..b38c69482e3112e0cd626bcb17f4523c541b748f 100644 +index 21c8ec6d8b795799b5b110be57ffd27fc8dcabe3..d3eb843fb6a7257152120371c4f317133f63de4d 100644 --- a/src/main/java/org/bukkit/entity/ArmorStand.java +++ b/src/main/java/org/bukkit/entity/ArmorStand.java @@ -7,6 +7,9 @@ import org.bukkit.util.EulerAngle; @@ -963,10 +949,10 @@ index a6a7429ed2e1eefb2b12b7480ed74fcc3963a864..e8027e1d505dda6effbb1698550016e8 NORMAL(false), diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 4f9a182f777712b6d266b7c1acee541f85740085..93d9e1b7b4ac9b9ccee7006375d5a96eaf9508ca 100644 +index d212021efd579bf5a527b6ef923279b055eb7754..bc03bbf9fa61b98bc6c208ab4a0e653f4b0ea472 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -1988,6 +1988,8 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1948,6 +1948,8 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM void resetCooldown(); /** @@ -975,7 +961,7 @@ index 4f9a182f777712b6d266b7c1acee541f85740085..93d9e1b7b4ac9b9ccee7006375d5a96e * @return the client option value of the player */ @NotNull -@@ -2027,6 +2029,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -1987,6 +1989,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM // Paper end // Spigot start @@ -1137,7 +1123,7 @@ index 25d26e3fe713311e66d7e634a6c32af61f4cef59..2825263c102d3f9ed37f6884e09ec5ef /** diff --git a/src/main/java/org/bukkit/event/block/CauldronLevelChangeEvent.java b/src/main/java/org/bukkit/event/block/CauldronLevelChangeEvent.java -index 2519e3eb9c6274476310913fdcb765c490d50962..44b07ced26449983a58e936c5d6d5ed2f7022fb4 100644 +index 668cc4b7d8a15ae345d130f8164107f000b6fe22..ebfeb3c8ebaf53a7fb6349f0f1480efc34c971e9 100644 --- a/src/main/java/org/bukkit/event/block/CauldronLevelChangeEvent.java +++ b/src/main/java/org/bukkit/event/block/CauldronLevelChangeEvent.java @@ -12,6 +12,9 @@ import org.bukkit.event.HandlerList; @@ -1315,7 +1301,7 @@ index 418f9391d86fff0d0a75da0574edccbb29aa9931..921d964d7e40e7710b5a5db18bd9329c /** * Triggered by the /summon command. diff --git a/src/main/java/org/bukkit/help/HelpTopicComparator.java b/src/main/java/org/bukkit/help/HelpTopicComparator.java -index 75bb69283f509e8f4fec772714a509a51be9de19..e156847f5b7b86155a7a0a0b8cefd8ac1530171e 100644 +index e1f4930f4d7cf657a75282b4c3480cabaaee2765..00b3b1d65f80d8e17ca1a40b704ece57a4776f8e 100644 --- a/src/main/java/org/bukkit/help/HelpTopicComparator.java +++ b/src/main/java/org/bukkit/help/HelpTopicComparator.java @@ -31,6 +31,9 @@ public final class HelpTopicComparator implements Comparator { @@ -1326,7 +1312,7 @@ index 75bb69283f509e8f4fec772714a509a51be9de19..e156847f5b7b86155a7a0a0b8cefd8ac + * Topic name comparator + */ public static final class TopicNameComparator implements Comparator { - private TopicNameComparator(){} + private TopicNameComparator() {} diff --git a/src/main/java/org/bukkit/inventory/ArmoredHorseInventory.java b/src/main/java/org/bukkit/inventory/ArmoredHorseInventory.java index 163ffe8ff76ded6265d865901d5110fb6a56950d..36145294db34d273bb767cc928453b765a30e9db 100644 diff --git a/patches/api/0038-Conflict-on-change-for-adventure-deprecations.patch b/patches/api/0038-Conflict-on-change-for-adventure-deprecations.patch index be84d0be3..068c8d39b 100644 --- a/patches/api/0038-Conflict-on-change-for-adventure-deprecations.patch +++ b/patches/api/0038-Conflict-on-change-for-adventure-deprecations.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Conflict on change for adventure deprecations diff --git a/src/main/java/org/bukkit/Bukkit.java b/src/main/java/org/bukkit/Bukkit.java -index bf86f402bc099ee190c809220f211ed59b3e9613..82978d66cc7d6214cf84f566918bfaf6bcbe1118 100644 +index cc739092191c0d1736b1b25622104d23ab171ce8..c6b3bf5b16c26f652c0649c7a2c41f700ac2c01e 100644 --- a/src/main/java/org/bukkit/Bukkit.java +++ b/src/main/java/org/bukkit/Bukkit.java @@ -346,7 +346,7 @@ public final class Bukkit { @@ -72,7 +72,7 @@ index bf86f402bc099ee190c809220f211ed59b3e9613..82978d66cc7d6214cf84f566918bfaf6 return server.getShutdownMessage(); } diff --git a/src/main/java/org/bukkit/Server.java b/src/main/java/org/bukkit/Server.java -index 5fcf5e0e746d579b6c3abf7148554463770332bd..2f9ee4cf3b2163dbe086b4d40179bfa64ebfdecf 100644 +index 5ca1d420a14797d8b2a867f867d19188084e7fa5..f82023103a328f234c990d6787c9b97fd1e82590 100644 --- a/src/main/java/org/bukkit/Server.java +++ b/src/main/java/org/bukkit/Server.java @@ -270,7 +270,7 @@ public interface Server extends PluginMessageRecipient, net.kyori.adventure.audi @@ -139,7 +139,7 @@ index 5fcf5e0e746d579b6c3abf7148554463770332bd..2f9ee4cf3b2163dbe086b4d40179bfa6 /** diff --git a/src/main/java/org/bukkit/block/Sign.java b/src/main/java/org/bukkit/block/Sign.java -index cdcf02ff9e80f5908a8fa22e82701445d5e2d298..83eba2421cdfa56c2f5b9ebaac18d56360164fed 100644 +index c8d37184d8e882a4084a1bfef85faa330588600b..46bae5c13ce2b973b114682f6a338981eb8d95bf 100644 --- a/src/main/java/org/bukkit/block/Sign.java +++ b/src/main/java/org/bukkit/block/Sign.java @@ -48,7 +48,7 @@ public interface Sign extends TileState, Colorable { @@ -170,7 +170,7 @@ index cdcf02ff9e80f5908a8fa22e82701445d5e2d298..83eba2421cdfa56c2f5b9ebaac18d563 /** diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 93d9e1b7b4ac9b9ccee7006375d5a96eaf9508ca..2c9860d7e55d254e3b201f4580ac400090ffe6e1 100644 +index bc03bbf9fa61b98bc6c208ab4a0e653f4b0ea472..5bb39f78a7e87dddc38b1a641438ebcc2de945b7 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java @@ -75,7 +75,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM @@ -370,7 +370,7 @@ index 09f0fa8f2aac16b2c2d848089e228af2d09f9090..e10ff4ab0c649289af9eafe83ef9268e return title; } diff --git a/src/main/java/org/bukkit/event/player/AsyncPlayerPreLoginEvent.java b/src/main/java/org/bukkit/event/player/AsyncPlayerPreLoginEvent.java -index 77aefda5aac4602bf5bf71c29600e7450defdd4e..240552d61ae12fbec826f771f0f366500e72d941 100644 +index 694a81769076ea58aae9f14f076ab80c9952c957..2d5066a0e24b671a6b287c34603b371ed51d7be7 100644 --- a/src/main/java/org/bukkit/event/player/AsyncPlayerPreLoginEvent.java +++ b/src/main/java/org/bukkit/event/player/AsyncPlayerPreLoginEvent.java @@ -179,7 +179,7 @@ public class AsyncPlayerPreLoginEvent extends Event { @@ -472,10 +472,10 @@ index 05ecfd8c133e72d198faeeded8c757c231c871cc..e57020767879b51f212d7a3e563a386a this.leaveMessage = org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().deserialize(leaveMessage); // Paper } diff --git a/src/main/java/org/bukkit/event/player/PlayerLocaleChangeEvent.java b/src/main/java/org/bukkit/event/player/PlayerLocaleChangeEvent.java -index 84521186404b8e43c81a2f9513dce2be40d27840..8c65e9e1476d27fc55419290fb53e46dee9b304d 100644 +index ebd499c1a2d11ea068e8c374edbc3967e4cece3d..61895d8d9f01f7ad0409a1cbd902e8a21472d6d4 100644 --- a/src/main/java/org/bukkit/event/player/PlayerLocaleChangeEvent.java +++ b/src/main/java/org/bukkit/event/player/PlayerLocaleChangeEvent.java -@@ -37,7 +37,7 @@ public class PlayerLocaleChangeEvent extends PlayerEvent { +@@ -36,7 +36,7 @@ public class PlayerLocaleChangeEvent extends PlayerEvent { * @deprecated in favour of {@link #locale()} */ @NotNull @@ -746,7 +746,7 @@ index ed0bc2024a0bb85837e25f75ae89d1fe257b2e60..f6e831f844e1fe99a2617bd64c2290d1 this.caption = caption == null ? null : org.bukkit.Bukkit.getUnsafe().legacyComponentSerializer().deserialize(caption); // Paper } diff --git a/src/main/java/org/bukkit/scoreboard/Objective.java b/src/main/java/org/bukkit/scoreboard/Objective.java -index 58bddb11fd534e7c33a4ffd7b72b055ba92c767a..a1b6b1123808378d58c855cacac391ce97df6f19 100644 +index 6279957b9bc6d22881f092eabf3a99831d85e3ee..24019ed4ff289b9d33da39cee66ea91831bc33c0 100644 --- a/src/main/java/org/bukkit/scoreboard/Objective.java +++ b/src/main/java/org/bukkit/scoreboard/Objective.java @@ -47,7 +47,7 @@ public interface Objective { @@ -768,7 +768,7 @@ index 58bddb11fd534e7c33a4ffd7b72b055ba92c767a..a1b6b1123808378d58c855cacac391ce /** diff --git a/src/main/java/org/bukkit/scoreboard/Scoreboard.java b/src/main/java/org/bukkit/scoreboard/Scoreboard.java -index f09ff32cc3ffc16af379a378b1948991435393e8..e9db79d10522895e6f119c0cc87eec1cbc45ba6e 100644 +index 93089ce61d2e1888df13b7c9629a79cd6f5f767a..ea0480064068f34ea34d4b68ef12d0f860e58d80 100644 --- a/src/main/java/org/bukkit/scoreboard/Scoreboard.java +++ b/src/main/java/org/bukkit/scoreboard/Scoreboard.java @@ -89,7 +89,7 @@ public interface Scoreboard { @@ -790,7 +790,7 @@ index f09ff32cc3ffc16af379a378b1948991435393e8..e9db79d10522895e6f119c0cc87eec1c /** diff --git a/src/main/java/org/bukkit/scoreboard/Team.java b/src/main/java/org/bukkit/scoreboard/Team.java -index f0af10a5b9ad048be197ed5ec6c8ed2672eb3dd5..705b2268b1c227b34852c14601381230dc626a08 100644 +index 30fce0df75494eb9b7409f08ea3d6ff894f7c79f..12789cd0ee7d6e29fa122f040e8ce0cac57945a7 100644 --- a/src/main/java/org/bukkit/scoreboard/Team.java +++ b/src/main/java/org/bukkit/scoreboard/Team.java @@ -110,7 +110,7 @@ public interface Team { diff --git a/patches/api/0042-Flying-Fall-Damage-API.patch b/patches/api/0042-Flying-Fall-Damage-API.patch index 6fabd5900..e7d50ea97 100644 --- a/patches/api/0042-Flying-Fall-Damage-API.patch +++ b/patches/api/0042-Flying-Fall-Damage-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Flying Fall Damage API diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java -index 2c9860d7e55d254e3b201f4580ac400090ffe6e1..fd57b3ebadce95c9534fa532b7a5c6c57660bc3e 100644 +index 5bb39f78a7e87dddc38b1a641438ebcc2de945b7..87d036d914fc2ca9b3056d7c5103d2e1ceb6e51e 100644 --- a/src/main/java/org/bukkit/entity/Player.java +++ b/src/main/java/org/bukkit/entity/Player.java -@@ -2212,5 +2212,19 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM +@@ -2172,5 +2172,19 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM * @param invulnerableTicks Invulnerable ticks remaining */ void setSpawnInvulnerableTicks(int invulnerableTicks); diff --git a/patches/server/0001-Tuinity-Server-Changes.patch b/patches/server/0001-Tuinity-Server-Changes.patch index fcbf9cf73..f19470a64 100644 --- a/patches/server/0001-Tuinity-Server-Changes.patch +++ b/patches/server/0001-Tuinity-Server-Changes.patch @@ -89,19039 +89,6 @@ index 5540da58e66f83b283863d3158a9b4ab5ba636db..ab8de6c4e3c0bea2b9f498da00adf88e standardInput = System.`in` workingDir = rootProject.layout.projectDirectory.dir( providers.gradleProperty("runWorkDir").forUseAtConfigurationTime().orElse("run") -diff --git a/src/main/java/ca/spottedleaf/starlight/light/BlockStarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/light/BlockStarLightEngine.java -new file mode 100644 -index 0000000000000000000000000000000000000000..9efbdba758aebcad3454a9a52c8a7eae4b7fc7eb ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/light/BlockStarLightEngine.java -@@ -0,0 +1,283 @@ -+package ca.spottedleaf.starlight.light; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.*; -+import net.minecraft.world.phys.shapes.Shapes; -+import net.minecraft.world.phys.shapes.VoxelShape; -+ -+import java.util.ArrayList; -+import java.util.Iterator; -+import java.util.List; -+import java.util.Set; -+import java.util.stream.Collectors; -+ -+public final class BlockStarLightEngine extends StarLightEngine { -+ -+ public BlockStarLightEngine(final Level world) { -+ super(false, world); -+ } -+ -+ @Override -+ protected boolean[] getEmptinessMap(final ChunkAccess chunk) { -+ return chunk.getBlockEmptinessMap(); -+ } -+ -+ @Override -+ protected void setEmptinessMap(final ChunkAccess chunk, final boolean[] to) { -+ chunk.setBlockEmptinessMap(to); -+ } -+ -+ @Override -+ protected SWMRNibbleArray[] getNibblesOnChunk(final ChunkAccess chunk) { -+ return chunk.getBlockNibbles(); -+ } -+ -+ @Override -+ protected void setNibbles(final ChunkAccess chunk, final SWMRNibbleArray[] to) { -+ chunk.setBlockNibbles(to); -+ } -+ -+ @Override -+ protected boolean canUseChunk(final ChunkAccess chunk) { -+ return chunk.getStatus().isOrAfter(ChunkStatus.LIGHT) && (this.isClientSide || chunk.isLightCorrect()); -+ } -+ -+ @Override -+ protected void setNibbleNull(final int chunkX, final int chunkY, final int chunkZ) { -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (nibble != null) { -+ // de-initialisation is not as straightforward as with sky data, since deinit of block light is typically -+ // because a block was removed - which can decrease light. with sky data, block breaking can only result -+ // in increases, and thus the existing sky block check will actually correctly propagate light through -+ // a null section. so in order to propagate decreases correctly, we can do a couple of things: not remove -+ // the data section, or do edge checks on ALL axis (x, y, z). however I do not want edge checks running -+ // for clients at all, as they are expensive. so we don't remove the section, but to maintain the appearence -+ // of vanilla data management we "hide" them. -+ nibble.setHidden(); -+ } -+ } -+ -+ @Override -+ protected void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles) { -+ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.getChunkInCache(chunkX, chunkZ) == null) { -+ return; -+ } -+ -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (nibble == null) { -+ if (!initRemovedNibbles) { -+ throw new IllegalStateException(); -+ } else { -+ this.setNibbleInCache(chunkX, chunkY, chunkZ, new SWMRNibbleArray()); -+ } -+ } else { -+ nibble.setNonNull(); -+ } -+ } -+ -+ @Override -+ protected final void checkBlock(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ) { -+ // blocks can change opacity -+ // blocks can change emitted light -+ // blocks can change direction of propagation -+ -+ final int encodeOffset = this.coordinateOffset; -+ final int emittedMask = this.emittedLightMask; -+ -+ final int currentLevel = this.getLightLevel(worldX, worldY, worldZ); -+ final BlockState blockState = this.getBlockState(worldX, worldY, worldZ); -+ final int emittedLevel = blockState.getLightEmission() & emittedMask; -+ -+ this.setLightLevel(worldX, worldY, worldZ, emittedLevel); -+ // this accounts for change in emitted light that would cause an increase -+ if (emittedLevel != 0) { -+ this.appendToIncreaseQueue( -+ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (emittedLevel & 0xFL) << (6 + 6 + 16) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (blockState.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0) -+ ); -+ } -+ // this also accounts for a change in emitted light that would cause a decrease -+ // this also accounts for the change of direction of propagation (i.e old block was full transparent, new block is full opaque or vice versa) -+ // as it checks all neighbours (even if current level is 0) -+ this.appendToDecreaseQueue( -+ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (currentLevel & 0xFL) << (6 + 6 + 16) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ // always keep sided transparent false here, new block might be conditionally transparent which would -+ // prevent us from decreasing sources in the directions where the new block is opaque -+ // if it turns out we were wrong to de-propagate the source, the re-propagate logic WILL always -+ // catch that and fix it. -+ ); -+ // re-propagating neighbours (done by the decrease queue) will also account for opacity changes in this block -+ } -+ -+ protected final BlockPos.MutableBlockPos recalcCenterPos = new BlockPos.MutableBlockPos(); -+ protected final BlockPos.MutableBlockPos recalcNeighbourPos = new BlockPos.MutableBlockPos(); -+ -+ @Override -+ protected int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ, -+ final int expect) { -+ final BlockState centerState = this.getBlockState(worldX, worldY, worldZ); -+ int level = centerState.getLightEmission() & 0xF; -+ -+ if (level >= (15 - 1) || level > expect) { -+ return level; -+ } -+ -+ final int sectionOffset = this.chunkSectionIndexOffset; -+ final BlockState conditionallyOpaqueState; -+ int opacity = centerState.getOpacityIfCached(); -+ -+ if (opacity == -1) { -+ this.recalcCenterPos.set(worldX, worldY, worldZ); -+ opacity = centerState.getLightBlock(lightAccess.getLevel(), this.recalcCenterPos); -+ if (centerState.isConditionallyFullOpaque()) { -+ conditionallyOpaqueState = centerState; -+ } else { -+ conditionallyOpaqueState = null; -+ } -+ } else if (opacity >= 15) { -+ return level; -+ } else { -+ conditionallyOpaqueState = null; -+ } -+ opacity = Math.max(1, opacity); -+ -+ for (final AxisDirection direction : AXIS_DIRECTIONS) { -+ final int offX = worldX + direction.x; -+ final int offY = worldY + direction.y; -+ final int offZ = worldZ + direction.z; -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ -+ final int neighbourLevel = this.getLightLevel(sectionIndex, (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8)); -+ -+ if ((neighbourLevel - 1) <= level) { -+ // don't need to test transparency, we know it wont affect the result. -+ continue; -+ } -+ -+ final BlockState neighbourState = this.getBlockState(offX, offY, offZ); -+ if (neighbourState.isConditionallyFullOpaque()) { -+ // here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that -+ // we don't read the blockstate because most of the time this is false, so using the faster -+ // known transparency lookup results in a net win -+ this.recalcNeighbourPos.set(offX, offY, offZ); -+ final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcNeighbourPos, direction.opposite.nms); -+ final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcCenterPos, direction.nms); -+ if (Shapes.faceShapeOccludes(thisFace, neighbourFace)) { -+ // not allowed to propagate -+ continue; -+ } -+ } -+ -+ // passed transparency, -+ -+ final int calculated = neighbourLevel - opacity; -+ level = Math.max(calculated, level); -+ if (level > expect) { -+ return level; -+ } -+ } -+ -+ return level; -+ } -+ -+ @Override -+ protected void propagateBlockChanges(final LightChunkGetter lightAccess, final ChunkAccess atChunk, final Set positions) { -+ for (final BlockPos pos : positions) { -+ this.checkBlock(lightAccess, pos.getX(), pos.getY(), pos.getZ()); -+ } -+ -+ this.performLightDecrease(lightAccess); -+ } -+ -+ protected Iterator getSources(final LightChunkGetter lightAccess, final ChunkAccess chunk) { -+ if (chunk instanceof ImposterProtoChunk || chunk instanceof LevelChunk) { -+ // implementation on Chunk is pretty awful, so write our own here. The big optimisation is -+ // skipping empty sections, and the far more optimised reading of types. -+ List sources = new ArrayList<>(); -+ -+ int offX = chunk.getPos().x << 4; -+ int offZ = chunk.getPos().z << 4; -+ -+ final LevelChunkSection[] sections = chunk.getSections(); -+ for (int sectionY = this.minSection; sectionY <= this.maxSection; ++sectionY) { -+ final LevelChunkSection section = sections[sectionY - this.minSection]; -+ if (section == null || section.isEmpty()) { -+ // no sources in empty sections -+ continue; -+ } -+ final PalettedContainer states = section.states; -+ final int offY = sectionY << 4; -+ -+ for (int index = 0; index < (16 * 16 * 16); ++index) { -+ final BlockState state = states.get(index); -+ if (state.getLightEmission() <= 0) { -+ continue; -+ } -+ -+ // index = x | (z << 4) | (y << 8) -+ sources.add(new BlockPos(offX | (index & 15), offY | (index >>> 8), offZ | ((index >>> 4) & 15))); -+ } -+ } -+ -+ return sources.iterator(); -+ } else { -+ // world gen and lighting run in parallel, and if lighting keeps up it can be lighting chunks that are -+ // being generated. In the nether, lava will add a lot of sources. This resulted in quite a few CME crashes. -+ // So all we do spinloop until we can collect a list of sources, and even if it is out of date we will pick up -+ // the missing sources from checkBlock. -+ for (;;) { -+ try { -+ return chunk.getLights().collect(Collectors.toList()).iterator(); -+ } catch (final Exception cme) { -+ continue; -+ } -+ } -+ } -+ } -+ -+ @Override -+ public void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks) { -+ // setup sources -+ final int emittedMask = this.emittedLightMask; -+ for (final Iterator positions = this.getSources(lightAccess, chunk); positions.hasNext();) { -+ final BlockPos pos = positions.next(); -+ final BlockState blockState = this.getBlockState(pos.getX(), pos.getY(), pos.getZ()); -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ -+ if (emittedLight <= this.getLightLevel(pos.getX(), pos.getY(), pos.getZ())) { -+ // some other source is brighter -+ continue; -+ } -+ -+ this.appendToIncreaseQueue( -+ ((pos.getX() + (pos.getZ() << 6) + (pos.getY() << (6 + 6)) + this.coordinateOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (emittedLight & 0xFL) << (6 + 6 + 16) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (blockState.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0) -+ ); -+ -+ -+ // propagation wont set this for us -+ this.setLightLevel(pos.getX(), pos.getY(), pos.getZ(), emittedLight); -+ } -+ -+ if (needsEdgeChecks) { -+ // not required to propagate here, but this will reduce the hit of the edge checks -+ this.performLightIncrease(lightAccess); -+ -+ // verify neighbour edges -+ this.checkChunkEdges(lightAccess, chunk, this.minLightSection, this.maxLightSection); -+ } else { -+ this.propagateNeighbourLevels(lightAccess, chunk, this.minLightSection, this.maxLightSection); -+ -+ this.performLightIncrease(lightAccess); -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/light/SWMRNibbleArray.java b/src/main/java/ca/spottedleaf/starlight/light/SWMRNibbleArray.java -new file mode 100644 -index 0000000000000000000000000000000000000000..174dc7ffa66258da0b867fba5c54880e81daa6ce ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/light/SWMRNibbleArray.java -@@ -0,0 +1,439 @@ -+package ca.spottedleaf.starlight.light; -+ -+import net.minecraft.world.level.chunk.DataLayer; -+ -+import java.util.ArrayDeque; -+import java.util.Arrays; -+ -+// SWMR -> Single Writer Multi Reader Nibble Array -+public final class SWMRNibbleArray { -+ -+ /* -+ * Null nibble - nibble does not exist, and should not be written to. Just like vanilla - null -+ * nibbles are always 0 - and they are never written to directly. Only initialised/uninitialised -+ * nibbles can be written to. -+ * -+ * Uninitialised nibble - They are all 0, but the backing array isn't initialised. -+ * -+ * Initialised nibble - Has light data. -+ */ -+ -+ protected static final int INIT_STATE_NULL = 0; // null -+ protected static final int INIT_STATE_UNINIT = 1; // uninitialised -+ protected static final int INIT_STATE_INIT = 2; // initialised -+ protected static final int INIT_STATE_HIDDEN = 3; // initialised, but conversion to Vanilla data should be treated as if NULL -+ -+ public static final int ARRAY_SIZE = 16 * 16 * 16 / (8/4); // blocks / bytes per block -+ // this allows us to maintain only 1 byte array when we're not updating -+ static final ThreadLocal> WORKING_BYTES_POOL = ThreadLocal.withInitial(ArrayDeque::new); -+ -+ private static byte[] allocateBytes() { -+ final byte[] inPool = WORKING_BYTES_POOL.get().pollFirst(); -+ if (inPool != null) { -+ return inPool; -+ } -+ -+ return new byte[ARRAY_SIZE]; -+ } -+ -+ private static void freeBytes(final byte[] bytes) { -+ WORKING_BYTES_POOL.get().addFirst(bytes); -+ } -+ -+ public static SWMRNibbleArray fromVanilla(final DataLayer nibble) { -+ if (nibble == null) { -+ return new SWMRNibbleArray(null, true); -+ } else if (nibble.isEmpty()) { -+ return new SWMRNibbleArray(); -+ } else { -+ return new SWMRNibbleArray(nibble.getData().clone()); // make sure we don't write to the parameter later -+ } -+ } -+ -+ protected int stateUpdating; -+ protected volatile int stateVisible; -+ -+ protected byte[] storageUpdating; -+ protected boolean updatingDirty; // only returns whether storageUpdating is dirty -+ protected byte[] storageVisible; -+ -+ public SWMRNibbleArray() { -+ this(null, false); // lazy init -+ } -+ -+ public SWMRNibbleArray(final byte[] bytes) { -+ this(bytes, false); -+ } -+ -+ public SWMRNibbleArray(final byte[] bytes, final boolean isNullNibble) { -+ if (bytes != null && bytes.length != ARRAY_SIZE) { -+ throw new IllegalArgumentException("Data of wrong length: " + bytes.length); -+ } -+ this.stateVisible = this.stateUpdating = bytes == null ? (isNullNibble ? INIT_STATE_NULL : INIT_STATE_UNINIT) : INIT_STATE_INIT; -+ this.storageUpdating = this.storageVisible = bytes; -+ } -+ -+ public SWMRNibbleArray(final byte[] bytes, final int state) { -+ if (bytes != null && bytes.length != ARRAY_SIZE) { -+ throw new IllegalArgumentException("Data of wrong length: " + bytes.length); -+ } -+ if (bytes == null && (state == INIT_STATE_INIT || state == INIT_STATE_HIDDEN)) { -+ throw new IllegalArgumentException("Data cannot be null and have state be initialised"); -+ } -+ this.stateUpdating = this.stateVisible = state; -+ this.storageUpdating = this.storageVisible = bytes; -+ } -+ -+ @Override -+ public String toString() { -+ StringBuilder stringBuilder = new StringBuilder(); -+ stringBuilder.append("State: "); -+ switch (this.stateVisible) { -+ case INIT_STATE_NULL: -+ stringBuilder.append("null"); -+ break; -+ case INIT_STATE_UNINIT: -+ stringBuilder.append("uninitialised"); -+ break; -+ case INIT_STATE_INIT: -+ stringBuilder.append("initialised"); -+ break; -+ case INIT_STATE_HIDDEN: -+ stringBuilder.append("hidden"); -+ break; -+ default: -+ stringBuilder.append("unknown"); -+ break; -+ } -+ stringBuilder.append("\nData:\n"); -+ -+ final byte[] data = this.storageVisible; -+ if (data != null) { -+ for (int i = 0; i < 4096; ++i) { -+ // Copied from NibbleArray#toString -+ final int level = ((data[i >>> 1] >>> ((i & 1) << 2)) & 0xF); -+ -+ stringBuilder.append(Integer.toHexString(level)); -+ if ((i & 15) == 15) { -+ stringBuilder.append("\n"); -+ } -+ -+ if ((i & 255) == 255) { -+ stringBuilder.append("\n"); -+ } -+ } -+ } else { -+ stringBuilder.append("null"); -+ } -+ -+ return stringBuilder.toString(); -+ } -+ -+ public SaveState getSaveState() { -+ synchronized (this) { -+ final int state = this.stateVisible; -+ final byte[] data = this.storageVisible; -+ if (state == INIT_STATE_NULL) { -+ return null; -+ } -+ if (state == INIT_STATE_UNINIT) { -+ return new SaveState(null, state); -+ } -+ final boolean zero = isAllZero(data); -+ if (zero) { -+ return state == INIT_STATE_INIT ? new SaveState(null, INIT_STATE_UNINIT) : null; -+ } else { -+ return new SaveState(data.clone(), state); -+ } -+ } -+ } -+ -+ protected static boolean isAllZero(final byte[] data) { -+ for (int i = 0; i < (ARRAY_SIZE >>> 4); ++i) { -+ byte whole = data[i << 4]; -+ -+ for (int k = 1; k < (1 << 4); ++k) { -+ whole |= data[(i << 4) | k]; -+ } -+ -+ if (whole != 0) { -+ return false; -+ } -+ } -+ -+ return true; -+ } -+ -+ // operation type: updating on src, updating on other -+ public void extrudeLower(final SWMRNibbleArray other) { -+ if (other.stateUpdating == INIT_STATE_NULL) { -+ throw new IllegalArgumentException(); -+ } -+ -+ if (other.storageUpdating == null) { -+ this.setUninitialised(); -+ return; -+ } -+ -+ final byte[] src = other.storageUpdating; -+ final byte[] into; -+ -+ if (this.storageUpdating != null) { -+ into = this.storageUpdating; -+ } else { -+ this.storageUpdating = into = allocateBytes(); -+ this.stateUpdating = INIT_STATE_INIT; -+ } -+ this.updatingDirty = true; -+ -+ final int start = 0; -+ final int end = (15 | (15 << 4)) >>> 1; -+ -+ /* x | (z << 4) | (y << 8) */ -+ for (int y = 0; y <= 15; ++y) { -+ System.arraycopy(src, start, into, y << (8 - 1), end - start + 1); -+ } -+ } -+ -+ // operation type: updating -+ public void setFull() { -+ if (this.stateUpdating != INIT_STATE_HIDDEN) { -+ this.stateUpdating = INIT_STATE_INIT; -+ } -+ Arrays.fill(this.storageUpdating == null || !this.updatingDirty ? this.storageUpdating = allocateBytes() : this.storageUpdating, (byte)-1); -+ this.updatingDirty = true; -+ } -+ -+ // operation type: updating -+ public void setZero() { -+ if (this.stateUpdating != INIT_STATE_HIDDEN) { -+ this.stateUpdating = INIT_STATE_INIT; -+ } -+ Arrays.fill(this.storageUpdating == null || !this.updatingDirty ? this.storageUpdating = allocateBytes() : this.storageUpdating, (byte)0); -+ this.updatingDirty = true; -+ } -+ -+ // operation type: updating -+ public void setNonNull() { -+ if (this.stateUpdating == INIT_STATE_HIDDEN) { -+ this.stateUpdating = INIT_STATE_INIT; -+ return; -+ } -+ if (this.stateUpdating != INIT_STATE_NULL) { -+ return; -+ } -+ this.stateUpdating = INIT_STATE_UNINIT; -+ } -+ -+ // operation type: updating -+ public void setNull() { -+ this.stateUpdating = INIT_STATE_NULL; -+ if (this.updatingDirty && this.storageUpdating != null) { -+ freeBytes(this.storageUpdating); -+ } -+ this.storageUpdating = null; -+ this.updatingDirty = false; -+ } -+ -+ // operation type: updating -+ public void setUninitialised() { -+ this.stateUpdating = INIT_STATE_UNINIT; -+ if (this.storageUpdating != null && this.updatingDirty) { -+ freeBytes(this.storageUpdating); -+ } -+ this.storageUpdating = null; -+ this.updatingDirty = false; -+ } -+ -+ // operation type: updating -+ public void setHidden() { -+ if (this.stateUpdating == INIT_STATE_HIDDEN) { -+ return; -+ } -+ if (this.stateUpdating != INIT_STATE_INIT) { -+ this.setNull(); -+ } else { -+ this.stateUpdating = INIT_STATE_HIDDEN; -+ } -+ } -+ -+ // operation type: updating -+ public boolean isDirty() { -+ return this.stateUpdating != this.stateVisible || this.updatingDirty; -+ } -+ -+ // operation type: updating -+ public boolean isNullNibbleUpdating() { -+ return this.stateUpdating == INIT_STATE_NULL; -+ } -+ -+ // operation type: visible -+ public boolean isNullNibbleVisible() { -+ return this.stateVisible == INIT_STATE_NULL; -+ } -+ -+ // opeartion type: updating -+ public boolean isUninitialisedUpdating() { -+ return this.stateUpdating == INIT_STATE_UNINIT; -+ } -+ -+ // operation type: visible -+ public boolean isUninitialisedVisible() { -+ return this.stateVisible == INIT_STATE_UNINIT; -+ } -+ -+ // operation type: updating -+ public boolean isInitialisedUpdating() { -+ return this.stateUpdating == INIT_STATE_INIT; -+ } -+ -+ // operation type: visible -+ public boolean isInitialisedVisible() { -+ return this.stateVisible == INIT_STATE_INIT; -+ } -+ -+ // operation type: updating -+ public boolean isHiddenUpdating() { -+ return this.stateUpdating == INIT_STATE_HIDDEN; -+ } -+ -+ // operation type: updating -+ public boolean isHiddenVisible() { -+ return this.stateVisible == INIT_STATE_HIDDEN; -+ } -+ -+ // operation type: updating -+ protected void swapUpdatingAndMarkDirty() { -+ if (this.updatingDirty) { -+ return; -+ } -+ -+ if (this.storageUpdating == null) { -+ this.storageUpdating = allocateBytes(); -+ Arrays.fill(this.storageUpdating, (byte)0); -+ } else { -+ System.arraycopy(this.storageUpdating, 0, this.storageUpdating = allocateBytes(), 0, ARRAY_SIZE); -+ } -+ -+ if (this.stateUpdating != INIT_STATE_HIDDEN) { -+ this.stateUpdating = INIT_STATE_INIT; -+ } -+ this.updatingDirty = true; -+ } -+ -+ // operation type: updating -+ public boolean updateVisible() { -+ if (!this.isDirty()) { -+ return false; -+ } -+ -+ synchronized (this) { -+ if (this.stateUpdating == INIT_STATE_NULL || this.stateUpdating == INIT_STATE_UNINIT) { -+ this.storageVisible = null; -+ } else { -+ if (this.storageVisible == null) { -+ this.storageVisible = this.storageUpdating.clone(); -+ } else { -+ if (this.storageUpdating != this.storageVisible) { -+ System.arraycopy(this.storageUpdating, 0, this.storageVisible, 0, ARRAY_SIZE); -+ } -+ } -+ -+ if (this.storageUpdating != this.storageVisible) { -+ freeBytes(this.storageUpdating); -+ } -+ this.storageUpdating = this.storageVisible; -+ } -+ this.updatingDirty = false; -+ this.stateVisible = this.stateUpdating; -+ } -+ -+ return true; -+ } -+ -+ // operation type: visible -+ public DataLayer toVanillaNibble() { -+ synchronized (this) { -+ switch (this.stateVisible) { -+ case INIT_STATE_HIDDEN: -+ case INIT_STATE_NULL: -+ return null; -+ case INIT_STATE_UNINIT: -+ return new DataLayer(); -+ case INIT_STATE_INIT: -+ return new DataLayer(this.storageVisible.clone()); -+ default: -+ throw new IllegalStateException(); -+ } -+ } -+ } -+ -+ /* x | (z << 4) | (y << 8) */ -+ -+ // operation type: updating -+ public int getUpdating(final int x, final int y, final int z) { -+ return this.getUpdating((x & 15) | ((z & 15) << 4) | ((y & 15) << 8)); -+ } -+ -+ // operation type: updating -+ public int getUpdating(final int index) { -+ // indices range from 0 -> 4096 -+ final byte[] bytes = this.storageUpdating; -+ if (bytes == null) { -+ return 0; -+ } -+ final byte value = bytes[index >>> 1]; -+ -+ // if we are an even index, we want lower 4 bits -+ // if we are an odd index, we want upper 4 bits -+ return ((value >>> ((index & 1) << 2)) & 0xF); -+ } -+ -+ // operation type: visible -+ public int getVisible(final int x, final int y, final int z) { -+ return this.getVisible((x & 15) | ((z & 15) << 4) | ((y & 15) << 8)); -+ } -+ -+ // operation type: visible -+ public int getVisible(final int index) { -+ synchronized (this) { -+ // indices range from 0 -> 4096 -+ final byte[] visibleBytes = this.storageVisible; -+ if (visibleBytes == null) { -+ return 0; -+ } -+ final byte value = visibleBytes[index >>> 1]; -+ -+ // if we are an even index, we want lower 4 bits -+ // if we are an odd index, we want upper 4 bits -+ return ((value >>> ((index & 1) << 2)) & 0xF); -+ } -+ } -+ -+ // operation type: updating -+ public void set(final int x, final int y, final int z, final int value) { -+ this.set((x & 15) | ((z & 15) << 4) | ((y & 15) << 8), value); -+ } -+ -+ // operation type: updating -+ public void set(final int index, final int value) { -+ if (!this.updatingDirty) { -+ this.swapUpdatingAndMarkDirty(); -+ } -+ final int shift = (index & 1) << 2; -+ final int i = index >>> 1; -+ -+ this.storageUpdating[i] = (byte)((this.storageUpdating[i] & (0xF0 >>> shift)) | (value << shift)); -+ } -+ -+ public static final class SaveState { -+ -+ public final byte[] data; -+ public final int state; -+ -+ public SaveState(final byte[] data, final int state) { -+ this.data = data; -+ this.state = state; -+ } -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/light/SkyStarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/light/SkyStarLightEngine.java -new file mode 100644 -index 0000000000000000000000000000000000000000..143810dc53782a9a6d870089d7bd5c3006a565b3 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/light/SkyStarLightEngine.java -@@ -0,0 +1,715 @@ -+package ca.spottedleaf.starlight.light; -+ -+import com.tuinity.tuinity.util.WorldUtil; -+import it.unimi.dsi.fastutil.shorts.ShortCollection; -+import it.unimi.dsi.fastutil.shorts.ShortIterator; -+import net.minecraft.core.BlockPos; -+import net.minecraft.world.level.BlockGetter; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; -+import net.minecraft.world.level.chunk.LevelChunkSection; -+import net.minecraft.world.level.chunk.LightChunkGetter; -+import net.minecraft.world.phys.shapes.Shapes; -+import net.minecraft.world.phys.shapes.VoxelShape; -+import java.util.Arrays; -+import java.util.Set; -+ -+public final class SkyStarLightEngine extends StarLightEngine { -+ -+ /* -+ Specification for managing the initialisation and de-initialisation of skylight nibble arrays: -+ -+ Skylight nibble initialisation requires that non-empty chunk sections have 1 radius nibbles non-null. -+ -+ This presents some problems, as vanilla is only guaranteed to have 0 radius neighbours loaded when editing blocks. -+ However starlight fixes this so that it has 1 radius loaded. Still, we don't actually have guarantees -+ that we have the necessary chunks loaded to de-initialise neighbour sections (but we do have enough to de-initialise -+ our own) - we need a radius of 2 to de-initialise neighbour nibbles. -+ How do we solve this? -+ -+ Each chunk will store the last known "emptiness" of sections for each of their 1 radius neighbour chunk sections. -+ If the chunk does not have full data, then its nibbles are NOT de-initialised. This is because obviously the -+ chunk did not go through the light stage yet - or its neighbours are not lit. In either case, once the last -+ known "emptiness" of neighbouring sections is filled with data, the chunk will run a full check of the data -+ to see if any of its nibbles need to be de-initialised. -+ -+ The emptiness map allows us to de-initialise neighbour nibbles if the neighbour has it filled with data, -+ and if it doesn't have data then we know it will correctly de-initialise once it fills up. -+ -+ Unlike vanilla, we store whether nibbles are uninitialised on disk - so we don't need any dumb hacking -+ around those. -+ */ -+ -+ protected final int[] heightMapBlockChange = new int[16 * 16]; -+ { -+ Arrays.fill(this.heightMapBlockChange, Integer.MIN_VALUE); // clear heightmap -+ } -+ -+ protected final boolean[] nullPropagationCheckCache; -+ -+ public SkyStarLightEngine(final Level world) { -+ super(true, world); -+ this.nullPropagationCheckCache = new boolean[WorldUtil.getTotalLightSections(world)]; -+ } -+ -+ @Override -+ protected void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles) { -+ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.getChunkInCache(chunkX, chunkZ) == null) { -+ return; -+ } -+ SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (nibble == null) { -+ if (!initRemovedNibbles) { -+ throw new IllegalStateException(); -+ } else { -+ this.setNibbleInCache(chunkX, chunkY, chunkZ, nibble = new SWMRNibbleArray(null, true)); -+ } -+ } -+ this.initNibble(nibble, chunkX, chunkY, chunkZ, extrude); -+ } -+ -+ @Override -+ protected void setNibbleNull(final int chunkX, final int chunkY, final int chunkZ) { -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (nibble != null) { -+ nibble.setNull(); -+ } -+ } -+ -+ protected final void initNibble(final SWMRNibbleArray currNibble, final int chunkX, final int chunkY, final int chunkZ, final boolean extrude) { -+ if (!currNibble.isNullNibbleUpdating()) { -+ // already initialised -+ return; -+ } -+ -+ final boolean[] emptinessMap = this.getEmptinessMap(chunkX, chunkZ); -+ -+ // are we above this chunk's lowest empty section? -+ int lowestY = this.minLightSection - 1; -+ for (int currY = this.maxSection; currY >= this.minSection; --currY) { -+ if (emptinessMap == null) { -+ // cannot delay nibble init for lit chunks, as we need to init to propagate into them. -+ final LevelChunkSection current = this.getChunkSection(chunkX, currY, chunkZ); -+ if (current == null || current == EMPTY_CHUNK_SECTION) { -+ continue; -+ } -+ } else { -+ if (emptinessMap[currY - this.minSection]) { -+ continue; -+ } -+ } -+ -+ // should always be full lit here -+ lowestY = currY; -+ break; -+ } -+ -+ if (chunkY > lowestY) { -+ // we need to set this one to full -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ nibble.setNonNull(); -+ nibble.setFull(); -+ return; -+ } -+ -+ if (extrude) { -+ // this nibble is going to depend solely on the skylight data above it -+ // find first non-null data above (there does exist one, as we just found it above) -+ for (int currY = chunkY + 1; currY <= this.maxLightSection; ++currY) { -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, currY, chunkZ); -+ if (nibble != null && !nibble.isNullNibbleUpdating()) { -+ currNibble.setNonNull(); -+ currNibble.extrudeLower(nibble); -+ break; -+ } -+ } -+ } else { -+ currNibble.setNonNull(); -+ } -+ } -+ -+ protected final void rewriteNibbleCacheForSkylight(final ChunkAccess chunk) { -+ for (int index = 0, max = this.nibbleCache.length; index < max; ++index) { -+ final SWMRNibbleArray nibble = this.nibbleCache[index]; -+ if (nibble != null && nibble.isNullNibbleUpdating()) { -+ // stop propagation in these areas -+ this.nibbleCache[index] = null; -+ nibble.updateVisible(); -+ } -+ } -+ } -+ -+ // rets whether neighbours were init'd -+ -+ protected final boolean checkNullSection(final int chunkX, final int chunkY, final int chunkZ, -+ final boolean extrudeInitialised) { -+ // null chunk sections may have nibble neighbours in the horizontal 1 radius that are -+ // non-null. Propagation to these neighbours is necessary. -+ // What makes this easy is we know none of these neighbours are non-empty (otherwise -+ // this nibble would be initialised). So, we don't have to initialise -+ // the neighbours in the full 1 radius, because there's no worry that any "paths" -+ // to the neighbours on this horizontal plane are blocked. -+ if (chunkY < this.minLightSection || chunkY > this.maxLightSection || this.nullPropagationCheckCache[chunkY - this.minLightSection]) { -+ return false; -+ } -+ this.nullPropagationCheckCache[chunkY - this.minLightSection] = true; -+ -+ // check horizontal neighbours -+ boolean needInitNeighbours = false; -+ neighbour_search: -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(dx + chunkX, chunkY, dz + chunkZ); -+ if (nibble != null && !nibble.isNullNibbleUpdating()) { -+ needInitNeighbours = true; -+ break neighbour_search; -+ } -+ } -+ } -+ -+ if (needInitNeighbours) { -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ this.initNibble(dx + chunkX, chunkY, dz + chunkZ, (dx | dz) == 0 ? extrudeInitialised : true, true); -+ } -+ } -+ } -+ -+ return needInitNeighbours; -+ } -+ -+ protected final int getLightLevelExtruded(final int worldX, final int worldY, final int worldZ) { -+ final int chunkX = worldX >> 4; -+ int chunkY = worldY >> 4; -+ final int chunkZ = worldZ >> 4; -+ -+ SWMRNibbleArray nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (nibble != null) { -+ return nibble.getUpdating(worldX, worldY, worldZ); -+ } -+ -+ for (;;) { -+ if (++chunkY > this.maxLightSection) { -+ return 15; -+ } -+ -+ nibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ -+ if (nibble != null) { -+ return nibble.getUpdating(worldX, 0, worldZ); -+ } -+ } -+ } -+ -+ @Override -+ protected boolean[] getEmptinessMap(final ChunkAccess chunk) { -+ return chunk.getSkyEmptinessMap(); -+ } -+ -+ @Override -+ protected void setEmptinessMap(final ChunkAccess chunk, final boolean[] to) { -+ chunk.setSkyEmptinessMap(to); -+ } -+ -+ @Override -+ protected SWMRNibbleArray[] getNibblesOnChunk(final ChunkAccess chunk) { -+ return chunk.getSkyNibbles(); -+ } -+ -+ @Override -+ protected void setNibbles(final ChunkAccess chunk, final SWMRNibbleArray[] to) { -+ chunk.setSkyNibbles(to); -+ } -+ -+ @Override -+ protected boolean canUseChunk(final ChunkAccess chunk) { -+ // can only use chunks for sky stuff if their sections have been init'd -+ return chunk.getStatus().isOrAfter(ChunkStatus.LIGHT) && (this.isClientSide || chunk.isLightCorrect()); -+ } -+ -+ @Override -+ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final int fromSection, -+ final int toSection) { -+ Arrays.fill(this.nullPropagationCheckCache, false); -+ this.rewriteNibbleCacheForSkylight(chunk); -+ final int chunkX = chunk.getPos().x; -+ final int chunkZ = chunk.getPos().z; -+ for (int y = toSection; y >= fromSection; --y) { -+ this.checkNullSection(chunkX, y, chunkZ, true); -+ } -+ -+ super.checkChunkEdges(lightAccess, chunk, fromSection, toSection); -+ } -+ -+ @Override -+ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final ShortCollection sections) { -+ Arrays.fill(this.nullPropagationCheckCache, false); -+ this.rewriteNibbleCacheForSkylight(chunk); -+ final int chunkX = chunk.getPos().x; -+ final int chunkZ = chunk.getPos().z; -+ for (final ShortIterator iterator = sections.iterator(); iterator.hasNext();) { -+ final int y = (int)iterator.nextShort(); -+ this.checkNullSection(chunkX, y, chunkZ, true); -+ } -+ -+ super.checkChunkEdges(lightAccess, chunk, sections); -+ } -+ -+ @Override -+ protected void checkBlock(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ) { -+ // blocks can change opacity -+ // blocks can change direction of propagation -+ -+ // same logic applies from BlockStarLightEngine#checkBlock -+ -+ final int encodeOffset = this.coordinateOffset; -+ -+ final int currentLevel = this.getLightLevel(worldX, worldY, worldZ); -+ -+ if (currentLevel == 15) { -+ // must re-propagate clobbered source -+ this.appendToIncreaseQueue( -+ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (currentLevel & 0xFL) << (6 + 6 + 16) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS // don't know if the block is conditionally transparent -+ ); -+ } else { -+ this.setLightLevel(worldX, worldY, worldZ, 0); -+ } -+ -+ this.appendToDecreaseQueue( -+ ((worldX + (worldZ << 6) + (worldY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (currentLevel & 0xFL) << (6 + 6 + 16) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ ); -+ } -+ -+ protected final BlockPos.MutableBlockPos recalcCenterPos = new BlockPos.MutableBlockPos(); -+ protected final BlockPos.MutableBlockPos recalcNeighbourPos = new BlockPos.MutableBlockPos(); -+ -+ @Override -+ protected int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ, -+ final int expect) { -+ if (expect == 15) { -+ return expect; -+ } -+ -+ final int sectionOffset = this.chunkSectionIndexOffset; -+ final BlockState centerState = this.getBlockState(worldX, worldY, worldZ); -+ int opacity = centerState.getOpacityIfCached(); -+ -+ BlockState conditionallyOpaqueState; -+ if (opacity < 0) { -+ this.recalcCenterPos.set(worldX, worldY, worldZ); -+ opacity = Math.max(1, centerState.getLightBlock(lightAccess.getLevel(), this.recalcCenterPos)); -+ if (centerState.isConditionallyFullOpaque()) { -+ conditionallyOpaqueState = centerState; -+ } else { -+ conditionallyOpaqueState = null; -+ } -+ } else { -+ conditionallyOpaqueState = null; -+ opacity = Math.max(1, opacity); -+ } -+ -+ int level = 0; -+ -+ for (final AxisDirection direction : AXIS_DIRECTIONS) { -+ final int offX = worldX + direction.x; -+ final int offY = worldY + direction.y; -+ final int offZ = worldZ + direction.z; -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ -+ final int neighbourLevel = this.getLightLevel(sectionIndex, (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8)); -+ -+ if ((neighbourLevel - 1) <= level) { -+ // don't need to test transparency, we know it wont affect the result. -+ continue; -+ } -+ -+ final BlockState neighbourState = this.getBlockState(offX, offY, offZ); -+ -+ if (neighbourState.isConditionallyFullOpaque()) { -+ // here the block can be conditionally opaque (i.e light cannot propagate from it), so we need to test that -+ // we don't read the blockstate because most of the time this is false, so using the faster -+ // known transparency lookup results in a net win -+ this.recalcNeighbourPos.set(offX, offY, offZ); -+ final VoxelShape neighbourFace = neighbourState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcNeighbourPos, direction.opposite.nms); -+ final VoxelShape thisFace = conditionallyOpaqueState == null ? Shapes.empty() : conditionallyOpaqueState.getFaceOcclusionShape(lightAccess.getLevel(), this.recalcCenterPos, direction.nms); -+ if (Shapes.faceShapeOccludes(thisFace, neighbourFace)) { -+ // not allowed to propagate -+ continue; -+ } -+ } -+ -+ final int calculated = neighbourLevel - opacity; -+ level = Math.max(calculated, level); -+ if (level > expect) { -+ return level; -+ } -+ } -+ -+ return level; -+ } -+ -+ @Override -+ protected void propagateBlockChanges(final LightChunkGetter lightAccess, final ChunkAccess atChunk, final Set positions) { -+ this.rewriteNibbleCacheForSkylight(atChunk); -+ Arrays.fill(this.nullPropagationCheckCache, false); -+ -+ final BlockGetter world = lightAccess.getLevel(); -+ final int chunkX = atChunk.getPos().x; -+ final int chunkZ = atChunk.getPos().z; -+ final int heightMapOffset = chunkX * -16 + (chunkZ * (-16 * 16)); -+ -+ // setup heightmap for changes -+ for (final BlockPos pos : positions) { -+ final int index = pos.getX() + (pos.getZ() << 4) + heightMapOffset; -+ final int curr = this.heightMapBlockChange[index]; -+ if (pos.getY() > curr) { -+ this.heightMapBlockChange[index] = pos.getY(); -+ } -+ } -+ -+ // note: light sets are delayed while processing skylight source changes due to how -+ // nibbles are initialised, as we want to avoid clobbering nibble values so what when -+ // below nibbles are initialised they aren't reading from partially modified nibbles -+ -+ // now we can recalculate the sources for the changed columns -+ for (int index = 0; index < (16 * 16); ++index) { -+ final int maxY = this.heightMapBlockChange[index]; -+ if (maxY == Integer.MIN_VALUE) { -+ // not changed -+ continue; -+ } -+ this.heightMapBlockChange[index] = Integer.MIN_VALUE; // restore default for next caller -+ -+ final int columnX = (index & 15) | (chunkX << 4); -+ final int columnZ = (index >>> 4) | (chunkZ << 4); -+ -+ // try and propagate from the above y -+ // delay light set until after processing all sources to setup -+ final int maxPropagationY = this.tryPropagateSkylight(world, columnX, maxY, columnZ, true, true); -+ -+ // maxPropagationY is now the highest block that could not be propagated to -+ -+ // remove all sources below that are 15 -+ final long propagateDirection = AxisDirection.POSITIVE_Y.everythingButThisDirection; -+ final int encodeOffset = this.coordinateOffset; -+ -+ if (this.getLightLevelExtruded(columnX, maxPropagationY, columnZ) == 15) { -+ // ensure section is checked -+ this.checkNullSection(columnX >> 4, maxPropagationY >> 4, columnZ >> 4, true); -+ -+ for (int currY = maxPropagationY; currY >= (this.minLightSection << 4); --currY) { -+ if ((currY & 15) == 15) { -+ // ensure section is checked -+ this.checkNullSection(columnX >> 4, (currY >> 4), columnZ >> 4, true); -+ } -+ -+ // ensure section below is always checked -+ final SWMRNibbleArray nibble = this.getNibbleFromCache(columnX >> 4, currY >> 4, columnZ >> 4); -+ if (nibble == null) { -+ // advance currY to the the top of the section below -+ currY = (currY) & (~15); -+ // note: this value ^ is actually 1 above the top, but the loop decrements by 1 so we actually -+ // end up there -+ continue; -+ } -+ -+ if (nibble.getUpdating(columnX, currY, columnZ) != 15) { -+ break; -+ } -+ -+ // delay light set until after processing all sources to setup -+ this.appendToDecreaseQueue( -+ ((columnX + (columnZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (15L << (6 + 6 + 16)) -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ // do not set transparent blocks for the same reason we don't in the checkBlock method -+ ); -+ } -+ } -+ } -+ -+ // delayed light sets are processed here, and must be processed before checkBlock as checkBlock reads -+ // immediate light value -+ this.processDelayedIncreases(); -+ this.processDelayedDecreases(); -+ -+ for (final BlockPos pos : positions) { -+ this.checkBlock(lightAccess, pos.getX(), pos.getY(), pos.getZ()); -+ } -+ -+ this.performLightDecrease(lightAccess); -+ } -+ -+ protected final int[] heightMapGen = new int[32 * 32]; -+ -+ @Override -+ protected void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks) { -+ this.rewriteNibbleCacheForSkylight(chunk); -+ Arrays.fill(this.nullPropagationCheckCache, false); -+ -+ final BlockGetter world = lightAccess.getLevel(); -+ final ChunkPos chunkPos = chunk.getPos(); -+ final int chunkX = chunkPos.x; -+ final int chunkZ = chunkPos.z; -+ -+ final LevelChunkSection[] sections = chunk.getSections(); -+ -+ int highestNonEmptySection = this.maxSection; -+ while (highestNonEmptySection == (this.minSection - 1) || -+ sections[highestNonEmptySection - this.minSection] == null || sections[highestNonEmptySection - this.minSection].isEmpty()) { -+ this.checkNullSection(chunkX, highestNonEmptySection, chunkZ, false); -+ // try propagate FULL to neighbours -+ -+ // check neighbours to see if we need to propagate into them -+ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { -+ final int neighbourX = chunkX + direction.x; -+ final int neighbourZ = chunkZ + direction.z; -+ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(neighbourX, highestNonEmptySection, neighbourZ); -+ if (neighbourNibble == null) { -+ // unloaded neighbour -+ // most of the time we fall here -+ continue; -+ } -+ -+ // it looks like we need to propagate into the neighbour -+ -+ final int incX; -+ final int incZ; -+ final int startX; -+ final int startZ; -+ -+ if (direction.x != 0) { -+ // x direction -+ incX = 0; -+ incZ = 1; -+ -+ if (direction.x < 0) { -+ // negative -+ startX = chunkX << 4; -+ } else { -+ startX = chunkX << 4 | 15; -+ } -+ startZ = chunkZ << 4; -+ } else { -+ // z direction -+ incX = 1; -+ incZ = 0; -+ -+ if (direction.z < 0) { -+ // negative -+ startZ = chunkZ << 4; -+ } else { -+ startZ = chunkZ << 4 | 15; -+ } -+ startX = chunkX << 4; -+ } -+ -+ final int encodeOffset = this.coordinateOffset; -+ final long propagateDirection = 1L << direction.ordinal(); // we only want to check in this direction -+ -+ for (int currY = highestNonEmptySection << 4, maxY = currY | 15; currY <= maxY; ++currY) { -+ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { -+ this.appendToIncreaseQueue( -+ ((currX + (currZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (15L << (6 + 6 + 16)) // we know we're at full lit here -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ // no transparent flag, we know for a fact there are no blocks here that could be directionally transparent (as the section is EMPTY) -+ ); -+ } -+ } -+ } -+ -+ if (highestNonEmptySection-- == (this.minSection - 1)) { -+ break; -+ } -+ } -+ -+ if (highestNonEmptySection >= this.minSection) { -+ // fill out our other sources -+ final int minX = chunkPos.x << 4; -+ final int maxX = chunkPos.x << 4 | 15; -+ final int minZ = chunkPos.z << 4; -+ final int maxZ = chunkPos.z << 4 | 15; -+ final int startY = highestNonEmptySection << 4 | 15; -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ this.tryPropagateSkylight(world, currX, startY + 1, currZ, false, false); -+ } -+ } -+ } // else: apparently the chunk is empty -+ -+ if (needsEdgeChecks) { -+ // not required to propagate here, but this will reduce the hit of the edge checks -+ this.performLightIncrease(lightAccess); -+ -+ for (int y = highestNonEmptySection; y >= this.minLightSection; --y) { -+ this.checkNullSection(chunkX, y, chunkZ, false); -+ } -+ // no need to rewrite the nibble cache again -+ super.checkChunkEdges(lightAccess, chunk, this.minLightSection, highestNonEmptySection); -+ } else { -+ for (int y = highestNonEmptySection; y >= this.minLightSection; --y) { -+ this.checkNullSection(chunkX, y, chunkZ, false); -+ } -+ this.propagateNeighbourLevels(lightAccess, chunk, this.minLightSection, highestNonEmptySection); -+ -+ this.performLightIncrease(lightAccess); -+ } -+ } -+ -+ protected final void processDelayedIncreases() { -+ // copied from performLightIncrease -+ final long[] queue = this.increaseQueue; -+ final int decodeOffsetX = -this.encodeOffsetX; -+ final int decodeOffsetY = -this.encodeOffsetY; -+ final int decodeOffsetZ = -this.encodeOffsetZ; -+ -+ for (int i = 0, len = this.increaseQueueInitialLength; i < len; ++i) { -+ final long queueValue = queue[i]; -+ -+ final int posX = ((int)queueValue & 63) + decodeOffsetX; -+ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; -+ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; -+ final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xF); -+ -+ this.setLightLevel(posX, posY, posZ, propagatedLightLevel); -+ } -+ } -+ -+ protected final void processDelayedDecreases() { -+ // copied from performLightDecrease -+ final long[] queue = this.decreaseQueue; -+ final int decodeOffsetX = -this.encodeOffsetX; -+ final int decodeOffsetY = -this.encodeOffsetY; -+ final int decodeOffsetZ = -this.encodeOffsetZ; -+ -+ for (int i = 0, len = this.decreaseQueueInitialLength; i < len; ++i) { -+ final long queueValue = queue[i]; -+ -+ final int posX = ((int)queueValue & 63) + decodeOffsetX; -+ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; -+ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; -+ -+ this.setLightLevel(posX, posY, posZ, 0); -+ } -+ } -+ -+ // delaying the light set is useful for block changes since they need to worry about initialising nibblearrays -+ // while also queueing light at the same time (initialising nibblearrays might depend on nibbles above, so -+ // clobbering the light values will result in broken propagation) -+ protected final int tryPropagateSkylight(final BlockGetter world, final int worldX, int startY, final int worldZ, -+ final boolean extrudeInitialised, final boolean delayLightSet) { -+ final BlockPos.MutableBlockPos mutablePos = this.mutablePos3; -+ final int encodeOffset = this.coordinateOffset; -+ final long propagateDirection = AxisDirection.POSITIVE_Y.everythingButThisDirection; // just don't check upwards. -+ -+ if (this.getLightLevelExtruded(worldX, startY + 1, worldZ) != 15) { -+ return startY; -+ } -+ -+ // ensure this section is always checked -+ this.checkNullSection(worldX >> 4, startY >> 4, worldZ >> 4, extrudeInitialised); -+ -+ BlockState above = this.getBlockState(worldX, startY + 1, worldZ); -+ if (above == null) { -+ above = AIR_BLOCK_STATE; -+ } -+ -+ for (;startY >= (this.minLightSection << 4); --startY) { -+ if ((startY & 15) == 15) { -+ // ensure this section is always checked -+ this.checkNullSection(worldX >> 4, startY >> 4, worldZ >> 4, extrudeInitialised); -+ } -+ BlockState current = this.getBlockState(worldX, startY, worldZ); -+ if (current == null) { -+ current = AIR_BLOCK_STATE; -+ } -+ -+ final VoxelShape fromShape; -+ if (above.isConditionallyFullOpaque()) { -+ this.mutablePos2.set(worldX, startY + 1, worldZ); -+ fromShape = above.getFaceOcclusionShape(world, this.mutablePos2, AxisDirection.NEGATIVE_Y.nms); -+ if (Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { -+ // above wont let us propagate -+ break; -+ } -+ } else { -+ fromShape = Shapes.empty(); -+ } -+ -+ final int opacityIfCached = current.getOpacityIfCached(); -+ // does light propagate from the top down? -+ if (opacityIfCached != -1) { -+ if (opacityIfCached != 0) { -+ // we cannot propagate 15 through this -+ break; -+ } -+ // most of the time it falls here. -+ // add to propagate -+ // light set delayed until we determine if this nibble section is null -+ this.appendToIncreaseQueue( -+ ((worldX + (worldZ << 6) + (startY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (15L << (6 + 6 + 16)) // we know we're at full lit here -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ ); -+ } else { -+ mutablePos.set(worldX, startY, worldZ); -+ long flags = 0L; -+ if (current.isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = current.getFaceOcclusionShape(world, mutablePos, AxisDirection.POSITIVE_Y.nms); -+ -+ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { -+ // can't propagate here, we're done on this column. -+ break; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = current.getLightBlock(world, mutablePos); -+ if (opacity > 0) { -+ // let the queued value (if any) handle it from here. -+ break; -+ } -+ -+ // light set delayed until we determine if this nibble section is null -+ this.appendToIncreaseQueue( -+ ((worldX + (worldZ << 6) + (startY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | (15L << (6 + 6 + 16)) // we know we're at full lit here -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ | flags -+ ); -+ } -+ -+ above = current; -+ -+ if (this.getNibbleFromCache(worldX >> 4, startY >> 4, worldZ >> 4) == null) { -+ // we skip empty sections here, as this is just an easy way of making sure the above block -+ // can propagate through air. -+ -+ // nothing can propagate in null sections, remove the queue entry for it -+ --this.increaseQueueInitialLength; -+ -+ // advance currY to the the top of the section below -+ startY = (startY) & (~15); -+ // note: this value ^ is actually 1 above the top, but the loop decrements by 1 so we actually -+ // end up there -+ -+ // make sure this is marked as AIR -+ above = AIR_BLOCK_STATE; -+ } else if (!delayLightSet) { -+ this.setLightLevel(worldX, startY, worldZ, 15); -+ } -+ } -+ -+ return startY; -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/light/StarLightEngine.java b/src/main/java/ca/spottedleaf/starlight/light/StarLightEngine.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ef930395407533a2c669499c02989bbbe0ac6101 ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/light/StarLightEngine.java -@@ -0,0 +1,1573 @@ -+package ca.spottedleaf.starlight.light; -+ -+import com.tuinity.tuinity.util.CoordinateUtils; -+import com.tuinity.tuinity.util.IntegerUtil; -+import com.tuinity.tuinity.util.WorldUtil; -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.shorts.ShortCollection; -+import it.unimi.dsi.fastutil.shorts.ShortIterator; -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.Direction; -+import net.minecraft.core.SectionPos; -+import net.minecraft.world.level.*; -+import net.minecraft.world.level.block.Blocks; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.LevelChunkSection; -+import net.minecraft.world.level.chunk.LightChunkGetter; -+import net.minecraft.world.phys.shapes.Shapes; -+import net.minecraft.world.phys.shapes.VoxelShape; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.List; -+import java.util.Set; -+import java.util.function.Consumer; -+import java.util.function.IntConsumer; -+ -+public abstract class StarLightEngine { -+ -+ protected static final BlockState AIR_BLOCK_STATE = Blocks.AIR.defaultBlockState(); -+ -+ protected static final LevelChunkSection EMPTY_CHUNK_SECTION = new LevelChunkSection(0); -+ -+ protected static final AxisDirection[] DIRECTIONS = AxisDirection.values(); -+ protected static final AxisDirection[] AXIS_DIRECTIONS = DIRECTIONS; -+ protected static final AxisDirection[] ONLY_HORIZONTAL_DIRECTIONS = new AxisDirection[] { -+ AxisDirection.POSITIVE_X, AxisDirection.NEGATIVE_X, -+ AxisDirection.POSITIVE_Z, AxisDirection.NEGATIVE_Z -+ }; -+ -+ protected static enum AxisDirection { -+ -+ // Declaration order is important and relied upon. Do not change without modifying propagation code. -+ POSITIVE_X(1, 0, 0), NEGATIVE_X(-1, 0, 0), -+ POSITIVE_Z(0, 0, 1), NEGATIVE_Z(0, 0, -1), -+ POSITIVE_Y(0, 1, 0), NEGATIVE_Y(0, -1, 0); -+ -+ static { -+ POSITIVE_X.opposite = NEGATIVE_X; NEGATIVE_X.opposite = POSITIVE_X; -+ POSITIVE_Z.opposite = NEGATIVE_Z; NEGATIVE_Z.opposite = POSITIVE_Z; -+ POSITIVE_Y.opposite = NEGATIVE_Y; NEGATIVE_Y.opposite = POSITIVE_Y; -+ } -+ -+ protected AxisDirection opposite; -+ -+ public final int x; -+ public final int y; -+ public final int z; -+ public final Direction nms; -+ public final long everythingButThisDirection; -+ public final long everythingButTheOppositeDirection; -+ -+ AxisDirection(final int x, final int y, final int z) { -+ this.x = x; -+ this.y = y; -+ this.z = z; -+ this.nms = Direction.fromNormal(x, y, z); -+ this.everythingButThisDirection = (long)(ALL_DIRECTIONS_BITSET ^ (1 << this.ordinal())); -+ // positive is always even, negative is always odd. Flip the 1 bit to get the negative direction. -+ this.everythingButTheOppositeDirection = (long)(ALL_DIRECTIONS_BITSET ^ (1 << (this.ordinal() ^ 1))); -+ } -+ -+ public AxisDirection getOpposite() { -+ return this.opposite; -+ } -+ } -+ -+ // I'd like to thank https://www.seedofandromeda.com/blogs/29-fast-flood-fill-lighting-in-a-blocky-voxel-game-pt-1 -+ // for explaining how light propagates via breadth-first search -+ -+ // While the above is a good start to understanding the general idea of what the general principles are, it's not -+ // exactly how the vanilla light engine should behave for minecraft. -+ -+ // similar to the above, except the chunk section indices vary from [-1, 1], or [0, 2] -+ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection] -+ // index = x + (z * 5) + (y * 25) -+ // null index indicates the chunk section doesn't exist (empty or out of bounds) -+ protected final LevelChunkSection[] sectionCache; -+ -+ // the exact same as above, except for storing fast access to SWMRNibbleArray -+ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection] -+ // index = x + (z * 5) + (y * 25) -+ protected final SWMRNibbleArray[] nibbleCache; -+ -+ // the exact same as above, except for storing fast access to nibbles to call change callbacks for -+ // for the y chunk section it's from [minLightSection, maxLightSection] or [0, maxLightSection - minLightSection] -+ // index = x + (z * 5) + (y * 25) -+ protected final boolean[] notifyUpdateCache; -+ -+ // always initialsed during start of lighting. -+ // index = x + (z * 5) -+ protected final ChunkAccess[] chunkCache = new ChunkAccess[5 * 5]; -+ -+ // index = x + (z * 5) -+ protected final boolean[][] emptinessMapCache = new boolean[5 * 5][]; -+ -+ protected final BlockPos.MutableBlockPos mutablePos1 = new BlockPos.MutableBlockPos(); -+ protected final BlockPos.MutableBlockPos mutablePos2 = new BlockPos.MutableBlockPos(); -+ protected final BlockPos.MutableBlockPos mutablePos3 = new BlockPos.MutableBlockPos(); -+ -+ protected int encodeOffsetX; -+ protected int encodeOffsetY; -+ protected int encodeOffsetZ; -+ -+ protected int coordinateOffset; -+ -+ protected int chunkOffsetX; -+ protected int chunkOffsetY; -+ protected int chunkOffsetZ; -+ -+ protected int chunkIndexOffset; -+ protected int chunkSectionIndexOffset; -+ -+ protected final boolean skylightPropagator; -+ protected final int emittedLightMask; -+ protected final boolean isClientSide; -+ -+ protected final Level world; -+ protected final int minLightSection; -+ protected final int maxLightSection; -+ protected final int minSection; -+ protected final int maxSection; -+ -+ protected StarLightEngine(final boolean skylightPropagator, final Level world) { -+ this.skylightPropagator = skylightPropagator; -+ this.emittedLightMask = skylightPropagator ? 0 : 0xF; -+ this.isClientSide = world.isClientSide; -+ this.world = world; -+ this.minLightSection = WorldUtil.getMinLightSection(world); -+ this.maxLightSection = WorldUtil.getMaxLightSection(world); -+ this.minSection = WorldUtil.getMinSection(world); -+ this.maxSection = WorldUtil.getMaxSection(world); -+ -+ this.sectionCache = new LevelChunkSection[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer -+ this.nibbleCache = new SWMRNibbleArray[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer -+ this.notifyUpdateCache = new boolean[5 * 5 * ((this.maxLightSection - this.minLightSection + 1) + 2)]; // add two extra sections for buffer -+ } -+ -+ protected final void setupEncodeOffset(final int centerX, final int centerY, final int centerZ) { -+ // 31 = center + encodeOffset -+ this.encodeOffsetX = 31 - centerX; -+ this.encodeOffsetY = (-(this.minLightSection - 1) << 4); // we want 0 to be the smallest encoded value -+ this.encodeOffsetZ = 31 - centerZ; -+ -+ // coordinateIndex = x | (z << 6) | (y << 12) -+ this.coordinateOffset = this.encodeOffsetX + (this.encodeOffsetZ << 6) + (this.encodeOffsetY << 12); -+ -+ // 2 = (centerX >> 4) + chunkOffset -+ this.chunkOffsetX = 2 - (centerX >> 4); -+ this.chunkOffsetY = -(this.minLightSection - 1); // lowest should be 0 -+ this.chunkOffsetZ = 2 - (centerZ >> 4); -+ -+ // chunk index = x + (5 * z) -+ this.chunkIndexOffset = this.chunkOffsetX + (5 * this.chunkOffsetZ); -+ -+ // chunk section index = x + (5 * z) + ((5*5) * y) -+ this.chunkSectionIndexOffset = this.chunkIndexOffset + ((5 * 5) * this.chunkOffsetY); -+ } -+ -+ protected final void setupCaches(final LightChunkGetter chunkProvider, final int centerX, final int centerY, final int centerZ, -+ final boolean relaxed, final boolean tryToLoadChunksFor2Radius) { -+ final int centerChunkX = centerX >> 4; -+ final int centerChunkY = centerY >> 4; -+ final int centerChunkZ = centerZ >> 4; -+ -+ this.setupEncodeOffset(centerChunkX * 16 + 7, centerChunkY * 16 + 7, centerChunkZ * 16 + 7); -+ -+ final int radius = tryToLoadChunksFor2Radius ? 2 : 1; -+ -+ for (int dz = -radius; dz <= radius; ++dz) { -+ for (int dx = -radius; dx <= radius; ++dx) { -+ final int cx = centerChunkX + dx; -+ final int cz = centerChunkZ + dz; -+ final boolean isTwoRadius = Math.max(IntegerUtil.branchlessAbs(dx), IntegerUtil.branchlessAbs(dz)) == 2; -+ final ChunkAccess chunk = (ChunkAccess)chunkProvider.getChunkForLighting(cx, cz); -+ -+ if (chunk == null) { -+ if (relaxed | isTwoRadius) { -+ continue; -+ } -+ throw new IllegalArgumentException("Trying to propagate light update before 1 radius neighbours ready"); -+ } -+ -+ if (!this.canUseChunk(chunk)) { -+ continue; -+ } -+ -+ this.setChunkInCache(cx, cz, chunk); -+ this.setEmptinessMapCache(cx, cz, this.getEmptinessMap(chunk)); -+ if (!isTwoRadius) { -+ this.setBlocksForChunkInCache(cx, cz, chunk.getSections()); -+ this.setNibblesForChunkInCache(cx, cz, this.getNibblesOnChunk(chunk)); -+ } -+ } -+ } -+ } -+ -+ protected final ChunkAccess getChunkInCache(final int chunkX, final int chunkZ) { -+ return this.chunkCache[chunkX + 5*chunkZ + this.chunkIndexOffset]; -+ } -+ -+ protected final void setChunkInCache(final int chunkX, final int chunkZ, final ChunkAccess chunk) { -+ this.chunkCache[chunkX + 5*chunkZ + this.chunkIndexOffset] = chunk; -+ } -+ -+ protected final LevelChunkSection getChunkSection(final int chunkX, final int chunkY, final int chunkZ) { -+ return this.sectionCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset]; -+ } -+ -+ protected final void setChunkSectionInCache(final int chunkX, final int chunkY, final int chunkZ, final LevelChunkSection section) { -+ this.sectionCache[chunkX + 5*chunkZ + 5*5*chunkY + this.chunkSectionIndexOffset] = section; -+ } -+ -+ protected final void setBlocksForChunkInCache(final int chunkX, final int chunkZ, final LevelChunkSection[] sections) { -+ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) { -+ this.setChunkSectionInCache(chunkX, cy, chunkZ, -+ sections == null ? null : (cy >= this.minSection && cy <= this.maxSection ? (sections[cy - this.minSection] == null || sections[cy - this.minSection].isEmpty() ? EMPTY_CHUNK_SECTION : sections[cy - this.minSection]) : EMPTY_CHUNK_SECTION)); -+ } -+ } -+ -+ protected final SWMRNibbleArray getNibbleFromCache(final int chunkX, final int chunkY, final int chunkZ) { -+ return this.nibbleCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset]; -+ } -+ -+ protected final SWMRNibbleArray[] getNibblesForChunkFromCache(final int chunkX, final int chunkZ) { -+ final SWMRNibbleArray[] ret = new SWMRNibbleArray[this.maxLightSection - this.minLightSection + 1]; -+ -+ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) { -+ ret[cy - this.minLightSection] = this.nibbleCache[chunkX + 5*chunkZ + (cy * (5 * 5)) + this.chunkSectionIndexOffset]; -+ } -+ -+ return ret; -+ } -+ -+ protected final void setNibbleInCache(final int chunkX, final int chunkY, final int chunkZ, final SWMRNibbleArray nibble) { -+ this.nibbleCache[chunkX + 5*chunkZ + (5 * 5) * chunkY + this.chunkSectionIndexOffset] = nibble; -+ } -+ -+ protected final void setNibblesForChunkInCache(final int chunkX, final int chunkZ, final SWMRNibbleArray[] nibbles) { -+ for (int cy = this.minLightSection; cy <= this.maxLightSection; ++cy) { -+ this.setNibbleInCache(chunkX, cy, chunkZ, nibbles == null ? null : nibbles[cy - this.minLightSection]); -+ } -+ } -+ -+ protected final void updateVisible(final LightChunkGetter lightAccess) { -+ for (int index = 0, max = this.nibbleCache.length; index < max; ++index) { -+ final SWMRNibbleArray nibble = this.nibbleCache[index]; -+ if (!this.notifyUpdateCache[index] && (nibble == null || !nibble.isDirty())) { -+ continue; -+ } -+ -+ final int chunkX = (index % 5) - this.chunkOffsetX; -+ final int chunkZ = ((index / 5) % 5) - this.chunkOffsetZ; -+ final int chunkY = ((index / (5*5)) % (16 + 2 + 2)) - this.chunkOffsetY; -+ if ((nibble != null && nibble.updateVisible()) || this.notifyUpdateCache[index]) { -+ lightAccess.onLightUpdate(this.skylightPropagator ? LightLayer.SKY : LightLayer.BLOCK, SectionPos.of(chunkX, chunkY, chunkZ)); -+ } -+ } -+ } -+ -+ protected final void destroyCaches() { -+ Arrays.fill(this.sectionCache, null); -+ Arrays.fill(this.nibbleCache, null); -+ Arrays.fill(this.chunkCache, null); -+ Arrays.fill(this.emptinessMapCache, null); -+ if (this.isClientSide) { -+ Arrays.fill(this.notifyUpdateCache, false); -+ } -+ } -+ -+ protected final BlockState getBlockState(final int worldX, final int worldY, final int worldZ) { -+ final LevelChunkSection section = this.sectionCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset]; -+ -+ if (section != null) { -+ return section == EMPTY_CHUNK_SECTION ? AIR_BLOCK_STATE : section.getBlockState(worldX & 15, worldY & 15, worldZ & 15); -+ } -+ -+ return null; -+ } -+ -+ protected final BlockState getBlockState(final int sectionIndex, final int localIndex) { -+ final LevelChunkSection section = this.sectionCache[sectionIndex]; -+ -+ if (section != null) { -+ return section == EMPTY_CHUNK_SECTION ? AIR_BLOCK_STATE : section.states.get(localIndex); -+ } -+ -+ return null; -+ } -+ -+ protected final int getLightLevel(final int worldX, final int worldY, final int worldZ) { -+ final SWMRNibbleArray nibble = this.nibbleCache[(worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset]; -+ -+ return nibble == null ? 0 : nibble.getUpdating((worldX & 15) | ((worldZ & 15) << 4) | ((worldY & 15) << 8)); -+ } -+ -+ protected final int getLightLevel(final int sectionIndex, final int localIndex) { -+ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; -+ -+ return nibble == null ? 0 : nibble.getUpdating(localIndex); -+ } -+ -+ protected final void setLightLevel(final int worldX, final int worldY, final int worldZ, final int level) { -+ final int sectionIndex = (worldX >> 4) + 5 * (worldZ >> 4) + (5 * 5) * (worldY >> 4) + this.chunkSectionIndexOffset; -+ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; -+ -+ if (nibble != null) { -+ nibble.set((worldX & 15) | ((worldZ & 15) << 4) | ((worldY & 15) << 8), level); -+ if (this.isClientSide) { -+ int cx1 = (worldX - 1) >> 4; -+ int cx2 = (worldX + 1) >> 4; -+ int cy1 = (worldY - 1) >> 4; -+ int cy2 = (worldY + 1) >> 4; -+ int cz1 = (worldZ - 1) >> 4; -+ int cz2 = (worldZ + 1) >> 4; -+ for (int x = cx1; x <= cx2; ++x) { -+ for (int y = cy1; y <= cy2; ++y) { -+ for (int z = cz1; z <= cz2; ++z) { -+ this.notifyUpdateCache[x + 5 * z + (5 * 5) * y + this.chunkSectionIndexOffset] = true; -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ protected final void postLightUpdate(final int worldX, final int worldY, final int worldZ) { -+ if (this.isClientSide) { -+ int cx1 = (worldX - 1) >> 4; -+ int cx2 = (worldX + 1) >> 4; -+ int cy1 = (worldY - 1) >> 4; -+ int cy2 = (worldY + 1) >> 4; -+ int cz1 = (worldZ - 1) >> 4; -+ int cz2 = (worldZ + 1) >> 4; -+ for (int x = cx1; x <= cx2; ++x) { -+ for (int y = cy1; y <= cy2; ++y) { -+ for (int z = cz1; z <= cz2; ++z) { -+ this.notifyUpdateCache[x + (5 * z) + (5 * 5 * y) + this.chunkSectionIndexOffset] = true; -+ } -+ } -+ } -+ } -+ } -+ -+ protected final void setLightLevel(final int sectionIndex, final int localIndex, final int worldX, final int worldY, final int worldZ, final int level) { -+ final SWMRNibbleArray nibble = this.nibbleCache[sectionIndex]; -+ -+ if (nibble != null) { -+ nibble.set(localIndex, level); -+ if (this.isClientSide) { -+ int cx1 = (worldX - 1) >> 4; -+ int cx2 = (worldX + 1) >> 4; -+ int cy1 = (worldY - 1) >> 4; -+ int cy2 = (worldY + 1) >> 4; -+ int cz1 = (worldZ - 1) >> 4; -+ int cz2 = (worldZ + 1) >> 4; -+ for (int x = cx1; x <= cx2; ++x) { -+ for (int y = cy1; y <= cy2; ++y) { -+ for (int z = cz1; z <= cz2; ++z) { -+ this.notifyUpdateCache[x + (5 * z) + (5 * 5 * y) + this.chunkSectionIndexOffset] = true; -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ protected final boolean[] getEmptinessMap(final int chunkX, final int chunkZ) { -+ return this.emptinessMapCache[chunkX + 5*chunkZ + this.chunkIndexOffset]; -+ } -+ -+ protected final void setEmptinessMapCache(final int chunkX, final int chunkZ, final boolean[] emptinessMap) { -+ this.emptinessMapCache[chunkX + 5*chunkZ + this.chunkIndexOffset] = emptinessMap; -+ } -+ -+ protected final long getKnownTransparency(final int worldX, final int worldY, final int worldZ) { -+ throw new UnsupportedOperationException(); // :( -+ } -+ -+ // warn: localIndex = y | (x << 4) | (z << 8) -+ protected final long getKnownTransparency(final int sectionIndex, final int localIndex) { -+ throw new UnsupportedOperationException(); // :( -+ } -+ -+ public static SWMRNibbleArray[] getFilledEmptyLight(final LevelHeightAccessor world) { -+ return getFilledEmptyLight(WorldUtil.getTotalLightSections(world)); -+ } -+ -+ private static SWMRNibbleArray[] getFilledEmptyLight(final int totalLightSections) { -+ final SWMRNibbleArray[] ret = new SWMRNibbleArray[totalLightSections]; -+ -+ for (int i = 0, len = ret.length; i < len; ++i) { -+ ret[i] = new SWMRNibbleArray(null, true); -+ } -+ -+ return ret; -+ } -+ -+ protected abstract boolean[] getEmptinessMap(final ChunkAccess chunk); -+ -+ protected abstract void setEmptinessMap(final ChunkAccess chunk, final boolean[] to); -+ -+ protected abstract SWMRNibbleArray[] getNibblesOnChunk(final ChunkAccess chunk); -+ -+ protected abstract void setNibbles(final ChunkAccess chunk, final SWMRNibbleArray[] to); -+ -+ protected abstract boolean canUseChunk(final ChunkAccess chunk); -+ -+ public final void blocksChangedInChunk(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ, -+ final Set positions, final Boolean[] changedSections) { -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); -+ try { -+ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); -+ if (chunk == null) { -+ return; -+ } -+ if (changedSections != null) { -+ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, changedSections, false); -+ if (ret != null) { -+ this.setEmptinessMap(chunk, ret); -+ } -+ } -+ if (!positions.isEmpty()) { -+ this.propagateBlockChanges(lightAccess, chunk, positions); -+ } -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ // subclasses should not initialise caches, as this will always be done by the super call -+ // subclasses should not invoke updateVisible, as this will always be done by the super call -+ protected abstract void propagateBlockChanges(final LightChunkGetter lightAccess, final ChunkAccess atChunk, final Set positions); -+ -+ protected abstract void checkBlock(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ); -+ -+ // if ret > expect, then the real value is at least ret (early returns if ret > expect, rather than calculating actual) -+ // if ret == expect, then expect is the correct light value for pos -+ // if ret < expect, then ret is the real light value -+ protected abstract int calculateLightValue(final LightChunkGetter lightAccess, final int worldX, final int worldY, final int worldZ, -+ final int expect); -+ -+ protected final int[] chunkCheckDelayedUpdatesCenter = new int[16 * 16]; -+ protected final int[] chunkCheckDelayedUpdatesNeighbour = new int[16 * 16]; -+ -+ protected void checkChunkEdge(final LightChunkGetter lightAccess, final ChunkAccess chunk, -+ final int chunkX, final int chunkY, final int chunkZ) { -+ final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, chunkY, chunkZ); -+ if (currNibble == null) { -+ return; -+ } -+ -+ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { -+ final int neighbourOffX = direction.x; -+ final int neighbourOffZ = direction.z; -+ -+ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(chunkX + neighbourOffX, -+ chunkY, chunkZ + neighbourOffZ); -+ -+ if (neighbourNibble == null) { -+ continue; -+ } -+ -+ if (!currNibble.isInitialisedUpdating() && !neighbourNibble.isInitialisedUpdating()) { -+ // both are zero, nothing to check. -+ continue; -+ } -+ -+ // this chunk -+ final int incX; -+ final int incZ; -+ final int startX; -+ final int startZ; -+ -+ if (neighbourOffX != 0) { -+ // x direction -+ incX = 0; -+ incZ = 1; -+ -+ if (direction.x < 0) { -+ // negative -+ startX = chunkX << 4; -+ } else { -+ startX = chunkX << 4 | 15; -+ } -+ startZ = chunkZ << 4; -+ } else { -+ // z direction -+ incX = 1; -+ incZ = 0; -+ -+ if (neighbourOffZ < 0) { -+ // negative -+ startZ = chunkZ << 4; -+ } else { -+ startZ = chunkZ << 4 | 15; -+ } -+ startX = chunkX << 4; -+ } -+ -+ int centerDelayedChecks = 0; -+ int neighbourDelayedChecks = 0; -+ for (int currY = chunkY << 4, maxY = currY | 15; currY <= maxY; ++currY) { -+ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { -+ final int neighbourX = currX + neighbourOffX; -+ final int neighbourZ = currZ + neighbourOffZ; -+ -+ final int currentIndex = (currX & 15) | -+ ((currZ & 15)) << 4 | -+ ((currY & 15) << 8); -+ final int currentLevel = currNibble.getUpdating(currentIndex); -+ -+ final int neighbourIndex = -+ (neighbourX & 15) | -+ ((neighbourZ & 15)) << 4 | -+ ((currY & 15) << 8); -+ final int neighbourLevel = neighbourNibble.getUpdating(neighbourIndex); -+ -+ // the checks are delayed because the checkBlock method clobbers light values - which then -+ // affect later calculate light value operations. While they don't affect it in a behaviourly significant -+ // way, they do have a negative performance impact due to simply queueing more values -+ -+ if (this.calculateLightValue(lightAccess, currX, currY, currZ, currentLevel) != currentLevel) { -+ this.chunkCheckDelayedUpdatesCenter[centerDelayedChecks++] = currentIndex; -+ } -+ -+ if (this.calculateLightValue(lightAccess, neighbourX, currY, neighbourZ, neighbourLevel) != neighbourLevel) { -+ this.chunkCheckDelayedUpdatesNeighbour[neighbourDelayedChecks++] = neighbourIndex; -+ } -+ } -+ } -+ -+ final int currentChunkOffX = chunkX << 4; -+ final int currentChunkOffZ = chunkZ << 4; -+ final int neighbourChunkOffX = (chunkX + direction.x) << 4; -+ final int neighbourChunkOffZ = (chunkZ + direction.z) << 4; -+ final int chunkOffY = chunkY << 4; -+ for (int i = 0, len = Math.max(centerDelayedChecks, neighbourDelayedChecks); i < len; ++i) { -+ // try to queue neighbouring data together -+ // index = x | (z << 4) | (y << 8) -+ if (i < centerDelayedChecks) { -+ final int value = this.chunkCheckDelayedUpdatesCenter[i]; -+ this.checkBlock(lightAccess, currentChunkOffX | (value & 15), -+ chunkOffY | (value >>> 8), -+ currentChunkOffZ | ((value >>> 4) & 0xF)); -+ } -+ if (i < neighbourDelayedChecks) { -+ final int value = this.chunkCheckDelayedUpdatesNeighbour[i]; -+ this.checkBlock(lightAccess, neighbourChunkOffX | (value & 15), -+ chunkOffY | (value >>> 8), -+ neighbourChunkOffZ | ((value >>> 4) & 0xF)); -+ } -+ } -+ } -+ } -+ -+ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final ShortCollection sections) { -+ final ChunkPos chunkPos = chunk.getPos(); -+ final int chunkX = chunkPos.x; -+ final int chunkZ = chunkPos.z; -+ -+ for (final ShortIterator iterator = sections.iterator(); iterator.hasNext();) { -+ this.checkChunkEdge(lightAccess, chunk, chunkX, iterator.nextShort(), chunkZ); -+ } -+ -+ this.performLightDecrease(lightAccess); -+ } -+ -+ // subclasses should not initialise caches, as this will always be done by the super call -+ // subclasses should not invoke updateVisible, as this will always be done by the super call -+ // verifies that light levels on this chunks edges are consistent with this chunk's neighbours -+ // edges. if they are not, they are decreased (effectively performing the logic in checkBlock). -+ // This does not resolve skylight source problems. -+ protected void checkChunkEdges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final int fromSection, final int toSection) { -+ final ChunkPos chunkPos = chunk.getPos(); -+ final int chunkX = chunkPos.x; -+ final int chunkZ = chunkPos.z; -+ -+ for (int currSectionY = toSection; currSectionY >= fromSection; --currSectionY) { -+ this.checkChunkEdge(lightAccess, chunk, chunkX, currSectionY, chunkZ); -+ } -+ -+ this.performLightDecrease(lightAccess); -+ } -+ -+ // pulls light from neighbours, and adds them into the increase queue. does not actually propagate. -+ protected final void propagateNeighbourLevels(final LightChunkGetter lightAccess, final ChunkAccess chunk, final int fromSection, final int toSection) { -+ final ChunkPos chunkPos = chunk.getPos(); -+ final int chunkX = chunkPos.x; -+ final int chunkZ = chunkPos.z; -+ -+ for (int currSectionY = toSection; currSectionY >= fromSection; --currSectionY) { -+ final SWMRNibbleArray currNibble = this.getNibbleFromCache(chunkX, currSectionY, chunkZ); -+ if (currNibble == null) { -+ continue; -+ } -+ for (final AxisDirection direction : ONLY_HORIZONTAL_DIRECTIONS) { -+ final int neighbourOffX = direction.x; -+ final int neighbourOffZ = direction.z; -+ -+ final SWMRNibbleArray neighbourNibble = this.getNibbleFromCache(chunkX + neighbourOffX, -+ currSectionY, chunkZ + neighbourOffZ); -+ -+ if (neighbourNibble == null || !neighbourNibble.isInitialisedUpdating()) { -+ // can't pull from 0 -+ continue; -+ } -+ -+ // neighbour chunk -+ final int incX; -+ final int incZ; -+ final int startX; -+ final int startZ; -+ -+ if (neighbourOffX != 0) { -+ // x direction -+ incX = 0; -+ incZ = 1; -+ -+ if (direction.x < 0) { -+ // negative -+ startX = (chunkX << 4) - 1; -+ } else { -+ startX = (chunkX << 4) + 16; -+ } -+ startZ = chunkZ << 4; -+ } else { -+ // z direction -+ incX = 1; -+ incZ = 0; -+ -+ if (neighbourOffZ < 0) { -+ // negative -+ startZ = (chunkZ << 4) - 1; -+ } else { -+ startZ = (chunkZ << 4) + 16; -+ } -+ startX = chunkX << 4; -+ } -+ -+ final long propagateDirection = 1L << direction.getOpposite().ordinal(); // we only want to check in this direction towards this chunk -+ final int encodeOffset = this.coordinateOffset; -+ -+ for (int currY = currSectionY << 4, maxY = currY | 15; currY <= maxY; ++currY) { -+ for (int i = 0, currX = startX, currZ = startZ; i < 16; ++i, currX += incX, currZ += incZ) { -+ final int level = neighbourNibble.getUpdating( -+ (currX & 15) -+ | ((currZ & 15) << 4) -+ | ((currY & 15) << 8) -+ ); -+ -+ if (level <= 1) { -+ // nothing to propagate -+ continue; -+ } -+ -+ this.appendToIncreaseQueue( -+ ((currX + (currZ << 6) + (currY << (6 + 6)) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((level & 0xFL) << (6 + 6 + 16)) -+ | (propagateDirection << (6 + 6 + 16 + 4)) -+ | FLAG_HAS_SIDED_TRANSPARENT_BLOCKS // don't know if the current block is transparent, must check. -+ ); -+ } -+ } -+ } -+ } -+ } -+ -+ public static Boolean[] getEmptySectionsForChunk(final ChunkAccess chunk) { -+ final LevelChunkSection[] sections = chunk.getSections(); -+ final Boolean[] ret = new Boolean[sections.length]; -+ -+ for (int i = 0; i < sections.length; ++i) { -+ if (sections[i] == null || sections[i].isEmpty()) { -+ ret[i] = Boolean.TRUE; -+ } else { -+ ret[i] = Boolean.FALSE; -+ } -+ } -+ -+ return ret; -+ } -+ -+ public final void forceHandleEmptySectionChanges(final LightChunkGetter lightAccess, final ChunkAccess chunk, final Boolean[] emptinessChanges) { -+ final int chunkX = chunk.getPos().x; -+ final int chunkZ = chunk.getPos().z; -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); -+ try { -+ // force current chunk into cache -+ this.setChunkInCache(chunkX, chunkZ, chunk); -+ this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSections()); -+ this.setNibblesForChunkInCache(chunkX, chunkZ, this.getNibblesOnChunk(chunk)); -+ this.setEmptinessMapCache(chunkX, chunkZ, this.getEmptinessMap(chunk)); -+ -+ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptinessChanges, false); -+ if (ret != null) { -+ this.setEmptinessMap(chunk, ret); -+ } -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ public final void handleEmptySectionChanges(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ, -+ final Boolean[] emptinessChanges) { -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); -+ try { -+ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); -+ if (chunk == null) { -+ return; -+ } -+ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptinessChanges, false); -+ if (ret != null) { -+ this.setEmptinessMap(chunk, ret); -+ } -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ protected abstract void initNibble(final int chunkX, final int chunkY, final int chunkZ, final boolean extrude, final boolean initRemovedNibbles); -+ -+ protected abstract void setNibbleNull(final int chunkX, final int chunkY, final int chunkZ); -+ -+ // subclasses should not initialise caches, as this will always be done by the super call -+ // subclasses should not invoke updateVisible, as this will always be done by the super call -+ // subclasses are guaranteed that this is always called before a changed block set -+ // newChunk specifies whether the changes describe a "first load" of a chunk or changes to existing, already loaded chunks -+ // rets non-null when the emptiness map changed and needs to be updated -+ protected final boolean[] handleEmptySectionChanges(final LightChunkGetter lightAccess, final ChunkAccess chunk, -+ final Boolean[] emptinessChanges, final boolean unlit) { -+ final Level world = (Level)lightAccess.getLevel(); -+ final int chunkX = chunk.getPos().x; -+ final int chunkZ = chunk.getPos().z; -+ -+ boolean[] chunkEmptinessMap = this.getEmptinessMap(chunkX, chunkZ); -+ boolean[] ret = null; -+ final boolean needsInit = unlit || chunkEmptinessMap == null; -+ if (needsInit) { -+ this.setEmptinessMapCache(chunkX, chunkZ, ret = chunkEmptinessMap = new boolean[WorldUtil.getTotalSections(world)]); -+ } -+ -+ // update emptiness map -+ for (int sectionIndex = (emptinessChanges.length - 1); sectionIndex >= 0; --sectionIndex) { -+ final Boolean valueBoxed = emptinessChanges[sectionIndex]; -+ if (valueBoxed == null) { -+ if (needsInit) { -+ throw new IllegalStateException("Current chunk has not initialised emptiness map yet supplied emptiness map isn't filled?"); -+ } -+ continue; -+ } -+ chunkEmptinessMap[sectionIndex] = valueBoxed.booleanValue(); -+ } -+ -+ // now init neighbour nibbles -+ for (int sectionIndex = (emptinessChanges.length - 1); sectionIndex >= 0; --sectionIndex) { -+ final Boolean valueBoxed = emptinessChanges[sectionIndex]; -+ final int sectionY = sectionIndex + this.minSection; -+ if (valueBoxed == null) { -+ continue; -+ } -+ -+ final boolean empty = valueBoxed.booleanValue(); -+ -+ if (empty) { -+ continue; -+ } -+ -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ // if we're not empty, we also need to initialise nibbles -+ // note: if we're unlit, we absolutely do not want to extrude, as light data isn't set up -+ final boolean extrude = (dx | dz) != 0 || !unlit; -+ for (int dy = 1; dy >= -1; --dy) { -+ this.initNibble(dx + chunkX, dy + sectionY, dz + chunkZ, extrude, false); -+ } -+ } -+ } -+ } -+ -+ // check for de-init and lazy-init -+ // lazy init is when chunks are being lit, so at the time they weren't loaded when their neighbours were running -+ // init checks. -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ // does this neighbour have 1 radius loaded? -+ boolean neighboursLoaded = true; -+ neighbour_loaded_search: -+ for (int dz2 = -1; dz2 <= 1; ++dz2) { -+ for (int dx2 = -1; dx2 <= 1; ++dx2) { -+ if (this.getEmptinessMap(dx + dx2 + chunkX, dz + dz2 + chunkZ) == null) { -+ neighboursLoaded = false; -+ break neighbour_loaded_search; -+ } -+ } -+ } -+ -+ for (int sectionY = this.maxLightSection; sectionY >= this.minLightSection; --sectionY) { -+ // check neighbours to see if we need to de-init this one -+ boolean allEmpty = true; -+ neighbour_search: -+ for (int dy2 = -1; dy2 <= 1; ++dy2) { -+ for (int dz2 = -1; dz2 <= 1; ++dz2) { -+ for (int dx2 = -1; dx2 <= 1; ++dx2) { -+ final int y = sectionY + dy2; -+ if (y < this.minSection || y > this.maxSection) { -+ // empty -+ continue; -+ } -+ final boolean[] emptinessMap = this.getEmptinessMap(dx + dx2 + chunkX, dz + dz2 + chunkZ); -+ if (emptinessMap != null) { -+ if (!emptinessMap[y - this.minSection]) { -+ allEmpty = false; -+ break neighbour_search; -+ } -+ } else { -+ final LevelChunkSection section = this.getChunkSection(dx + dx2 + chunkX, y, dz + dz2 + chunkZ); -+ if (section != null && section != EMPTY_CHUNK_SECTION) { -+ allEmpty = false; -+ break neighbour_search; -+ } -+ } -+ } -+ } -+ } -+ -+ if (allEmpty & neighboursLoaded) { -+ // can only de-init when neighbours are loaded -+ // de-init is fine to delay, as de-init is just an optimisation - it's not required for lighting -+ // to be correct -+ -+ // all were empty, so de-init -+ this.setNibbleNull(dx + chunkX, sectionY, dz + chunkZ); -+ } else if (!allEmpty) { -+ // must init -+ final boolean extrude = (dx | dz) != 0 || !unlit; -+ this.initNibble(dx + chunkX, sectionY, dz + chunkZ, extrude, false); -+ } -+ } -+ } -+ } -+ -+ return ret; -+ } -+ -+ public final void checkChunkEdges(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ) { -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, false); -+ try { -+ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); -+ if (chunk == null) { -+ return; -+ } -+ this.checkChunkEdges(lightAccess, chunk, this.minLightSection, this.maxLightSection); -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ public final void checkChunkEdges(final LightChunkGetter lightAccess, final int chunkX, final int chunkZ, final ShortCollection sections) { -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, false); -+ try { -+ final ChunkAccess chunk = this.getChunkInCache(chunkX, chunkZ); -+ if (chunk == null) { -+ return; -+ } -+ this.checkChunkEdges(lightAccess, chunk, sections); -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ // subclasses should not initialise caches, as this will always be done by the super call -+ // subclasses should not invoke updateVisible, as this will always be done by the super call -+ // needsEdgeChecks applies when possibly loading vanilla data, which means we need to validate the current -+ // chunks light values with respect to neighbours -+ // subclasses should note that the emptiness changes are propagated BEFORE this is called, so this function -+ // does not need to detect empty chunks itself (and it should do no handling for them either!) -+ protected abstract void lightChunk(final LightChunkGetter lightAccess, final ChunkAccess chunk, final boolean needsEdgeChecks); -+ -+ public final void light(final LightChunkGetter lightAccess, final ChunkAccess chunk, final Boolean[] emptySections) { -+ final int chunkX = chunk.getPos().x; -+ final int chunkZ = chunk.getPos().z; -+ this.setupCaches(lightAccess, chunkX * 16 + 7, 128, chunkZ * 16 + 7, true, true); -+ -+ try { -+ final SWMRNibbleArray[] nibbles = getFilledEmptyLight(this.maxLightSection - this.minLightSection + 1); -+ // force current chunk into cache -+ this.setChunkInCache(chunkX, chunkZ, chunk); -+ this.setBlocksForChunkInCache(chunkX, chunkZ, chunk.getSections()); -+ this.setNibblesForChunkInCache(chunkX, chunkZ, nibbles); -+ this.setEmptinessMapCache(chunkX, chunkZ, this.getEmptinessMap(chunk)); -+ -+ final boolean[] ret = this.handleEmptySectionChanges(lightAccess, chunk, emptySections, true); -+ if (ret != null) { -+ this.setEmptinessMap(chunk, ret); -+ } -+ this.lightChunk(lightAccess, chunk, true); -+ this.setNibbles(chunk, nibbles); -+ this.updateVisible(lightAccess); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ public final void relightChunks(final LightChunkGetter lightAccess, final Set chunks, -+ final Consumer chunkLightCallback, final IntConsumer onComplete) { -+ // it's recommended for maximum performance that the set is ordered according to a BFS from the center of -+ // the region of chunks to relight -+ // it's required that tickets are added for each chunk to keep them loaded -+ final Long2ObjectOpenHashMap nibblesByChunk = new Long2ObjectOpenHashMap<>(); -+ final Long2ObjectOpenHashMap emptinessMapByChunk = new Long2ObjectOpenHashMap<>(); -+ -+ final int[] neighbourLightOrder = new int[] { -+ // d = 0 -+ 0, 0, -+ // d = 1 -+ -1, 0, -+ 0, -1, -+ 1, 0, -+ 0, 1, -+ // d = 2 -+ -1, 1, -+ 1, 1, -+ -1, -1, -+ 1, -1, -+ }; -+ -+ int lightCalls = 0; -+ -+ for (final ChunkPos chunkPos : chunks) { -+ final int chunkX = chunkPos.x; -+ final int chunkZ = chunkPos.z; -+ final ChunkAccess chunk = (ChunkAccess)lightAccess.getChunkForLighting(chunkX, chunkZ); -+ if (chunk == null || !this.canUseChunk(chunk)) { -+ throw new IllegalStateException(); -+ } -+ -+ for (int i = 0, len = neighbourLightOrder.length; i < len; i += 2) { -+ final int dx = neighbourLightOrder[i]; -+ final int dz = neighbourLightOrder[i + 1]; -+ final int neighbourX = dx + chunkX; -+ final int neighbourZ = dz + chunkZ; -+ -+ final ChunkAccess neighbour = (ChunkAccess)lightAccess.getChunkForLighting(neighbourX, neighbourZ); -+ if (neighbour == null || !this.canUseChunk(neighbour)) { -+ continue; -+ } -+ -+ if (nibblesByChunk.get(CoordinateUtils.getChunkKey(neighbourX, neighbourZ)) != null) { -+ // lit already called for neighbour, no need to light it now -+ continue; -+ } -+ -+ // light neighbour chunk -+ this.setupEncodeOffset(neighbourX * 16 + 7, 128, neighbourZ * 16 + 7); -+ try { -+ // insert all neighbouring chunks for this neighbour that we have data for -+ for (int dz2 = -1; dz2 <= 1; ++dz2) { -+ for (int dx2 = -1; dx2 <= 1; ++dx2) { -+ final int neighbourX2 = neighbourX + dx2; -+ final int neighbourZ2 = neighbourZ + dz2; -+ final long key = CoordinateUtils.getChunkKey(neighbourX2, neighbourZ2); -+ final ChunkAccess neighbour2 = (ChunkAccess)lightAccess.getChunkForLighting(neighbourX2, neighbourZ2); -+ if (neighbour2 == null || !this.canUseChunk(neighbour2)) { -+ continue; -+ } -+ -+ final SWMRNibbleArray[] nibbles = nibblesByChunk.get(key); -+ if (nibbles == null) { -+ // we haven't lit this chunk -+ continue; -+ } -+ -+ this.setChunkInCache(neighbourX2, neighbourZ2, neighbour2); -+ this.setBlocksForChunkInCache(neighbourX2, neighbourZ2, neighbour2.getSections()); -+ this.setNibblesForChunkInCache(neighbourX2, neighbourZ2, nibbles); -+ this.setEmptinessMapCache(neighbourX2, neighbourZ2, emptinessMapByChunk.get(key)); -+ } -+ } -+ -+ final long key = CoordinateUtils.getChunkKey(neighbourX, neighbourZ); -+ -+ // now insert the neighbour chunk and light it -+ final SWMRNibbleArray[] nibbles = getFilledEmptyLight(this.world); -+ nibblesByChunk.put(key, nibbles); -+ -+ this.setChunkInCache(neighbourX, neighbourZ, neighbour); -+ this.setBlocksForChunkInCache(neighbourX, neighbourZ, neighbour.getSections()); -+ this.setNibblesForChunkInCache(neighbourX, neighbourZ, nibbles); -+ -+ final boolean[] neighbourEmptiness = this.handleEmptySectionChanges(lightAccess, neighbour, getEmptySectionsForChunk(neighbour), true); -+ emptinessMapByChunk.put(key, neighbourEmptiness); -+ if (chunks.contains(new ChunkPos(neighbourX, neighbourZ))) { -+ this.setEmptinessMap(neighbour, neighbourEmptiness); -+ } -+ -+ this.lightChunk(lightAccess, neighbour, false); -+ } finally { -+ this.destroyCaches(); -+ } -+ } -+ -+ // done lighting all neighbours, so the chunk is now fully lit -+ -+ // make sure nibbles are fully updated before calling back -+ final SWMRNibbleArray[] nibbles = nibblesByChunk.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ for (final SWMRNibbleArray nibble : nibbles) { -+ nibble.updateVisible(); -+ } -+ -+ this.setNibbles(chunk, nibbles); -+ -+ for (int y = this.minLightSection; y <= this.maxLightSection; ++y) { -+ lightAccess.onLightUpdate(this.skylightPropagator ? LightLayer.SKY : LightLayer.BLOCK, SectionPos.of(chunkX, y, chunkX)); -+ } -+ -+ // now do callback -+ if (chunkLightCallback != null) { -+ chunkLightCallback.accept(chunkPos); -+ } -+ ++lightCalls; -+ } -+ -+ if (onComplete != null) { -+ onComplete.accept(lightCalls); -+ } -+ } -+ -+ // old algorithm for propagating -+ // this is also the basic algorithm, the optimised algorithm is always going to be tested against this one -+ // and this one is always tested against vanilla -+ // contains: -+ // lower (6 + 6 + 16) = 28 bits: encoded coordinate position (x | (z << 6) | (y << (6 + 6)))) -+ // next 4 bits: propagated light level (0, 15] -+ // next 6 bits: propagation direction bitset -+ // next 24 bits: unused -+ // last 4 bits: state flags -+ // state flags: -+ // whether the propagation must set the current position's light value (0 if decrease, propagated light level if increase) -+ // whether the propagation needs to check if its current level is equal to the expected level -+ // used only in increase propagation -+ protected static final long FLAG_RECHECK_LEVEL = Long.MIN_VALUE >>> 1; -+ // whether the propagation needs to consider if its block is conditionally transparent -+ protected static final long FLAG_HAS_SIDED_TRANSPARENT_BLOCKS = Long.MIN_VALUE; -+ -+ protected long[] increaseQueue = new long[16 * 16 * 16]; -+ protected int increaseQueueInitialLength; -+ protected long[] decreaseQueue = new long[16 * 16 * 16]; -+ protected int decreaseQueueInitialLength; -+ -+ protected final long[] resizeIncreaseQueue() { -+ return this.increaseQueue = Arrays.copyOf(this.increaseQueue, this.increaseQueue.length * 2); -+ } -+ -+ protected final long[] resizeDecreaseQueue() { -+ return this.decreaseQueue = Arrays.copyOf(this.decreaseQueue, this.decreaseQueue.length * 2); -+ } -+ -+ protected final void appendToIncreaseQueue(final long value) { -+ final int idx = this.increaseQueueInitialLength++; -+ long[] queue = this.increaseQueue; -+ if (idx >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ queue[idx] = value; -+ } else { -+ queue[idx] = value; -+ } -+ } -+ -+ protected final void appendToDecreaseQueue(final long value) { -+ final int idx = this.decreaseQueueInitialLength++; -+ long[] queue = this.decreaseQueue; -+ if (idx >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ queue[idx] = value; -+ } else { -+ queue[idx] = value; -+ } -+ } -+ -+ protected static final AxisDirection[][] OLD_CHECK_DIRECTIONS = new AxisDirection[1 << 6][]; -+ protected static final int ALL_DIRECTIONS_BITSET = (1 << 6) - 1; -+ static { -+ for (int i = 0; i < OLD_CHECK_DIRECTIONS.length; ++i) { -+ final List directions = new ArrayList<>(); -+ for (int bitset = i, len = Integer.bitCount(i), index = 0; index < len; ++index, bitset ^= IntegerUtil.getTrailingBit(bitset)) { -+ directions.add(AXIS_DIRECTIONS[IntegerUtil.trailingZeros(bitset)]); -+ } -+ OLD_CHECK_DIRECTIONS[i] = directions.toArray(new AxisDirection[0]); -+ } -+ } -+ -+ protected final void performLightIncrease(final LightChunkGetter lightAccess) { -+ final BlockGetter world = lightAccess.getLevel(); -+ long[] queue = this.increaseQueue; -+ int queueReadIndex = 0; -+ int queueLength = this.increaseQueueInitialLength; -+ this.increaseQueueInitialLength = 0; -+ final int decodeOffsetX = -this.encodeOffsetX; -+ final int decodeOffsetY = -this.encodeOffsetY; -+ final int decodeOffsetZ = -this.encodeOffsetZ; -+ final int encodeOffset = this.coordinateOffset; -+ final int sectionOffset = this.chunkSectionIndexOffset; -+ -+ while (queueReadIndex < queueLength) { -+ final long queueValue = queue[queueReadIndex++]; -+ -+ final int posX = ((int)queueValue & 63) + decodeOffsetX; -+ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; -+ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; -+ final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xFL); -+ final AxisDirection[] checkDirections = OLD_CHECK_DIRECTIONS[(int)((queueValue >>> (6 + 6 + 16 + 4)) & 63L)]; -+ -+ if ((queueValue & FLAG_RECHECK_LEVEL) != 0L) { -+ if (this.getLightLevel(posX, posY, posZ) != propagatedLightLevel) { -+ // not at the level we expect, so something changed. -+ continue; -+ } -+ } -+ -+ if ((queueValue & FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) == 0L) { -+ // we don't need to worry about our state here. -+ for (final AxisDirection propagate : checkDirections) { -+ final int offX = posX + propagate.x; -+ final int offY = posY + propagate.y; -+ final int offZ = posZ + propagate.z; -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); -+ -+ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; -+ final int currentLevel; -+ if (currentNibble == null || (currentLevel = currentNibble.getUpdating(localIndex)) >= (propagatedLightLevel - 1)) { -+ continue; // already at the level we want or unloaded -+ } -+ -+ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); -+ if (blockState == null) { -+ continue; -+ } -+ final int opacityCached = blockState.getOpacityIfCached(); -+ if (opacityCached != -1) { -+ final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached); -+ if (targetLevel > currentLevel) { -+ -+ currentNibble.set(localIndex, targetLevel); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 1) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)); -+ continue; -+ } -+ } -+ continue; -+ } else { -+ this.mutablePos1.set(offX, offY, offZ); -+ long flags = 0; -+ if (blockState.isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -+ -+ if (Shapes.faceShapeOccludes(Shapes.empty(), cullingFace)) { -+ continue; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = blockState.getLightBlock(world, this.mutablePos1); -+ final int targetLevel = propagatedLightLevel - Math.max(1, opacity); -+ if (targetLevel <= currentLevel) { -+ continue; -+ } -+ -+ currentNibble.set(localIndex, targetLevel); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 1) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) -+ | (flags); -+ } -+ continue; -+ } -+ } -+ } else { -+ // we actually need to worry about our state here -+ final BlockState fromBlock = this.getBlockState(posX, posY, posZ); -+ this.mutablePos2.set(posX, posY, posZ); -+ for (final AxisDirection propagate : checkDirections) { -+ final int offX = posX + propagate.x; -+ final int offY = posY + propagate.y; -+ final int offZ = posZ + propagate.z; -+ -+ final VoxelShape fromShape = fromBlock.isConditionallyFullOpaque() ? fromBlock.getFaceOcclusionShape(world, this.mutablePos2, propagate.nms) : Shapes.empty(); -+ -+ if (fromShape != Shapes.empty() && Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { -+ continue; -+ } -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); -+ -+ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; -+ final int currentLevel; -+ -+ if (currentNibble == null || (currentLevel = currentNibble.getUpdating(localIndex)) >= (propagatedLightLevel - 1)) { -+ continue; // already at the level we want -+ } -+ -+ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); -+ if (blockState == null) { -+ continue; -+ } -+ final int opacityCached = blockState.getOpacityIfCached(); -+ if (opacityCached != -1) { -+ final int targetLevel = propagatedLightLevel - Math.max(1, opacityCached); -+ if (targetLevel > currentLevel) { -+ -+ currentNibble.set(localIndex, targetLevel); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 1) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)); -+ continue; -+ } -+ } -+ continue; -+ } else { -+ this.mutablePos1.set(offX, offY, offZ); -+ long flags = 0; -+ if (blockState.isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -+ -+ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { -+ continue; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = blockState.getLightBlock(world, this.mutablePos1); -+ final int targetLevel = propagatedLightLevel - Math.max(1, opacity); -+ if (targetLevel <= currentLevel) { -+ continue; -+ } -+ -+ currentNibble.set(localIndex, targetLevel); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 1) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeIncreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | (propagate.everythingButTheOppositeDirection << (6 + 6 + 16 + 4)) -+ | (flags); -+ } -+ continue; -+ } -+ } -+ } -+ } -+ } -+ -+ protected final void performLightDecrease(final LightChunkGetter lightAccess) { -+ final BlockGetter world = lightAccess.getLevel(); -+ long[] queue = this.decreaseQueue; -+ long[] increaseQueue = this.increaseQueue; -+ int queueReadIndex = 0; -+ int queueLength = this.decreaseQueueInitialLength; -+ this.decreaseQueueInitialLength = 0; -+ int increaseQueueLength = this.increaseQueueInitialLength; -+ final int decodeOffsetX = -this.encodeOffsetX; -+ final int decodeOffsetY = -this.encodeOffsetY; -+ final int decodeOffsetZ = -this.encodeOffsetZ; -+ final int encodeOffset = this.coordinateOffset; -+ final int sectionOffset = this.chunkSectionIndexOffset; -+ final int emittedMask = this.emittedLightMask; -+ -+ while (queueReadIndex < queueLength) { -+ final long queueValue = queue[queueReadIndex++]; -+ -+ final int posX = ((int)queueValue & 63) + decodeOffsetX; -+ final int posZ = (((int)queueValue >>> 6) & 63) + decodeOffsetZ; -+ final int posY = (((int)queueValue >>> 12) & ((1 << 16) - 1)) + decodeOffsetY; -+ final int propagatedLightLevel = (int)((queueValue >>> (6 + 6 + 16)) & 0xF); -+ final AxisDirection[] checkDirections = OLD_CHECK_DIRECTIONS[(int)((queueValue >>> (6 + 6 + 16 + 4)) & 63)]; -+ -+ if ((queueValue & FLAG_HAS_SIDED_TRANSPARENT_BLOCKS) == 0L) { -+ // we don't need to worry about our state here. -+ for (final AxisDirection propagate : checkDirections) { -+ final int offX = posX + propagate.x; -+ final int offY = posY + propagate.y; -+ final int offZ = posZ + propagate.z; -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); -+ -+ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; -+ final int lightLevel; -+ -+ if (currentNibble == null || (lightLevel = currentNibble.getUpdating(localIndex)) == 0) { -+ // already at lowest (or unloaded), nothing we can do -+ continue; -+ } -+ -+ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); -+ if (blockState == null) { -+ continue; -+ } -+ final int opacityCached = blockState.getOpacityIfCached(); -+ if (opacityCached != -1) { -+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached)); -+ if (lightLevel > targetLevel) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((lightLevel & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | FLAG_RECHECK_LEVEL; -+ continue; -+ } -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ if (emittedLight != 0) { -+ // re-propagate source -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((emittedLight & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (blockState.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0L); -+ } -+ -+ currentNibble.set(localIndex, emittedLight); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)); -+ continue; -+ } -+ continue; -+ } else { -+ this.mutablePos1.set(offX, offY, offZ); -+ long flags = 0; -+ if (blockState.isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -+ -+ if (Shapes.faceShapeOccludes(Shapes.empty(), cullingFace)) { -+ continue; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = blockState.getLightBlock(world, this.mutablePos1); -+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); -+ if (lightLevel > targetLevel) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((lightLevel & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (FLAG_RECHECK_LEVEL | flags); -+ continue; -+ } -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ if (emittedLight != 0) { -+ // re-propagate source -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((emittedLight & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | flags; -+ } -+ -+ currentNibble.set(localIndex, emittedLight); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 0) { -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) -+ | flags; -+ } -+ continue; -+ } -+ } -+ } else { -+ // we actually need to worry about our state here -+ final BlockState fromBlock = this.getBlockState(posX, posY, posZ); -+ this.mutablePos2.set(posX, posY, posZ); -+ for (final AxisDirection propagate : checkDirections) { -+ final int offX = posX + propagate.x; -+ final int offY = posY + propagate.y; -+ final int offZ = posZ + propagate.z; -+ -+ final int sectionIndex = (offX >> 4) + 5 * (offZ >> 4) + (5 * 5) * (offY >> 4) + sectionOffset; -+ final int localIndex = (offX & 15) | ((offZ & 15) << 4) | ((offY & 15) << 8); -+ -+ final VoxelShape fromShape = fromBlock.isConditionallyFullOpaque() ? fromBlock.getFaceOcclusionShape(world, this.mutablePos2, propagate.nms) : Shapes.empty(); -+ -+ if (fromShape != Shapes.empty() && Shapes.faceShapeOccludes(Shapes.empty(), fromShape)) { -+ continue; -+ } -+ -+ final SWMRNibbleArray currentNibble = this.nibbleCache[sectionIndex]; -+ final int lightLevel; -+ -+ if (currentNibble == null || (lightLevel = currentNibble.getUpdating(localIndex)) == 0) { -+ // already at lowest (or unloaded), nothing we can do -+ continue; -+ } -+ -+ final BlockState blockState = this.getBlockState(sectionIndex, localIndex); -+ if (blockState == null) { -+ continue; -+ } -+ final int opacityCached = blockState.getOpacityIfCached(); -+ if (opacityCached != -1) { -+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacityCached)); -+ if (lightLevel > targetLevel) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((lightLevel & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | FLAG_RECHECK_LEVEL; -+ continue; -+ } -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ if (emittedLight != 0) { -+ // re-propagate source -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((emittedLight & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (blockState.isConditionallyFullOpaque() ? FLAG_HAS_SIDED_TRANSPARENT_BLOCKS : 0L); -+ } -+ -+ currentNibble.set(localIndex, emittedLight); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)); -+ continue; -+ } -+ continue; -+ } else { -+ this.mutablePos1.set(offX, offY, offZ); -+ long flags = 0; -+ if (blockState.isConditionallyFullOpaque()) { -+ final VoxelShape cullingFace = blockState.getFaceOcclusionShape(world, this.mutablePos1, propagate.getOpposite().nms); -+ -+ if (Shapes.faceShapeOccludes(fromShape, cullingFace)) { -+ continue; -+ } -+ flags |= FLAG_HAS_SIDED_TRANSPARENT_BLOCKS; -+ } -+ -+ final int opacity = blockState.getLightBlock(world, this.mutablePos1); -+ final int targetLevel = Math.max(0, propagatedLightLevel - Math.max(1, opacity)); -+ if (lightLevel > targetLevel) { -+ // it looks like another source propagated here, so re-propagate it -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((lightLevel & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | (FLAG_RECHECK_LEVEL | flags); -+ continue; -+ } -+ final int emittedLight = blockState.getLightEmission() & emittedMask; -+ if (emittedLight != 0) { -+ // re-propagate source -+ if (increaseQueueLength >= increaseQueue.length) { -+ increaseQueue = this.resizeIncreaseQueue(); -+ } -+ increaseQueue[increaseQueueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((emittedLight & 0xFL) << (6 + 6 + 16)) -+ | (((long)ALL_DIRECTIONS_BITSET) << (6 + 6 + 16 + 4)) -+ | flags; -+ } -+ -+ currentNibble.set(localIndex, emittedLight); -+ this.postLightUpdate(offX, offY, offZ); -+ -+ if (targetLevel > 0) { // we actually need to propagate 0 just in case we find a neighbour... -+ if (queueLength >= queue.length) { -+ queue = this.resizeDecreaseQueue(); -+ } -+ queue[queueLength++] = -+ ((offX + (offZ << 6) + (offY << 12) + encodeOffset) & ((1L << (6 + 6 + 16)) - 1)) -+ | ((targetLevel & 0xFL) << (6 + 6 + 16)) -+ | ((propagate.everythingButTheOppositeDirection) << (6 + 6 + 16 + 4)) -+ | flags; -+ } -+ continue; -+ } -+ } -+ } -+ } -+ -+ // propagate sources we clobbered -+ this.increaseQueueInitialLength = increaseQueueLength; -+ this.performLightIncrease(lightAccess); -+ } -+} -diff --git a/src/main/java/ca/spottedleaf/starlight/light/StarLightInterface.java b/src/main/java/ca/spottedleaf/starlight/light/StarLightInterface.java -new file mode 100644 -index 0000000000000000000000000000000000000000..300364f693583be802a71d94cda5d96c77c7b67c ---- /dev/null -+++ b/src/main/java/ca/spottedleaf/starlight/light/StarLightInterface.java -@@ -0,0 +1,635 @@ -+package ca.spottedleaf.starlight.light; -+ -+import com.tuinity.tuinity.util.CoordinateUtils; -+import com.tuinity.tuinity.util.WorldUtil; -+import it.unimi.dsi.fastutil.longs.Long2ObjectLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.shorts.ShortCollection; -+import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.SectionPos; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.TicketType; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.Level; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.ChunkStatus; -+import net.minecraft.world.level.chunk.DataLayer; -+import net.minecraft.world.level.chunk.LightChunkGetter; -+import net.minecraft.world.level.lighting.LayerLightEventListener; -+import net.minecraft.world.level.lighting.LevelLightEngine; -+import java.util.*; -+import java.util.concurrent.CompletableFuture; -+import java.util.function.Consumer; -+import java.util.function.IntConsumer; -+ -+public final class StarLightInterface { -+ -+ public static final TicketType CHUNK_WORK_TICKET = TicketType.create("starlight_chunk_work_ticket", (p1, p2) -> Long.compare(p1.toLong(), p2.toLong())); -+ -+ /** -+ * Can be {@code null}, indicating the light is all empty. -+ */ -+ protected final Level world; -+ protected final LightChunkGetter lightAccess; -+ -+ protected final ArrayDeque cachedSkyPropagators; -+ protected final ArrayDeque cachedBlockPropagators; -+ -+ protected final LightQueue lightQueue = new LightQueue(this); -+ -+ protected final LayerLightEventListener skyReader; -+ protected final LayerLightEventListener blockReader; -+ protected final boolean isClientSide; -+ -+ protected final int minSection; -+ protected final int maxSection; -+ protected final int minLightSection; -+ protected final int maxLightSection; -+ -+ public final LevelLightEngine lightEngine; -+ -+ public StarLightInterface(final LightChunkGetter lightAccess, final boolean hasSkyLight, final boolean hasBlockLight, final LevelLightEngine lightEngine) { -+ this.lightAccess = lightAccess; -+ this.world = lightAccess == null ? null : (Level)lightAccess.getLevel(); -+ this.cachedSkyPropagators = hasSkyLight && lightAccess != null ? new ArrayDeque<>() : null; -+ this.cachedBlockPropagators = hasBlockLight && lightAccess != null ? new ArrayDeque<>() : null; -+ this.isClientSide = !(this.world instanceof ServerLevel); -+ if (this.world == null) { -+ this.minSection = 0; -+ this.maxSection = 15; -+ this.minLightSection = -1; -+ this.maxLightSection = 16; -+ } else { -+ this.minSection = WorldUtil.getMinSection(this.world); -+ this.maxSection = WorldUtil.getMaxSection(this.world); -+ this.minLightSection = WorldUtil.getMinLightSection(this.world); -+ this.maxLightSection = WorldUtil.getMaxLightSection(this.world); -+ } -+ this.lightEngine = lightEngine; -+ this.skyReader = !hasSkyLight ? LayerLightEventListener.DummyLightLayerEventListener.INSTANCE : new LayerLightEventListener() { -+ @Override -+ public void checkBlock(final BlockPos blockPos) { -+ StarLightInterface.this.lightEngine.checkBlock(blockPos.immutable()); -+ } -+ -+ @Override -+ public void onBlockEmissionIncrease(final BlockPos blockPos, final int i) { -+ // skylight doesn't care -+ } -+ -+ @Override -+ public boolean hasLightWork() { -+ // not really correct... -+ return StarLightInterface.this.hasUpdates(); -+ } -+ -+ @Override -+ public int runUpdates(final int i, final boolean bl, final boolean bl2) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void enableLightSources(final ChunkPos chunkPos, final boolean bl) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public DataLayer getDataLayerData(final SectionPos pos) { -+ final ChunkAccess chunk = StarLightInterface.this.getAnyChunkNow(pos.getX(), pos.getZ()); -+ if (chunk == null || (!StarLightInterface.this.isClientSide && !chunk.isLightCorrect()) || !chunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) { -+ return null; -+ } -+ -+ final int sectionY = pos.getY(); -+ -+ if (sectionY > StarLightInterface.this.maxLightSection || sectionY < StarLightInterface.this.minLightSection) { -+ return null; -+ } -+ -+ if (chunk.getSkyEmptinessMap() == null) { -+ return null; -+ } -+ -+ return chunk.getSkyNibbles()[sectionY - StarLightInterface.this.minLightSection].toVanillaNibble(); -+ } -+ -+ @Override -+ public int getLightValue(final BlockPos blockPos) { -+ final int x = blockPos.getX(); -+ int y = blockPos.getY(); -+ final int z = blockPos.getZ(); -+ -+ final ChunkAccess chunk = StarLightInterface.this.getAnyChunkNow(x >> 4, z >> 4); -+ if (chunk == null || (!StarLightInterface.this.isClientSide && !chunk.isLightCorrect()) || !chunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) { -+ return 15; -+ } -+ -+ int sectionY = y >> 4; -+ -+ if (sectionY > StarLightInterface.this.maxLightSection) { -+ return 15; -+ } -+ -+ if (sectionY < StarLightInterface.this.minLightSection) { -+ sectionY = StarLightInterface.this.minLightSection; -+ y = sectionY << 4; -+ } -+ -+ final SWMRNibbleArray[] nibbles = chunk.getSkyNibbles(); -+ final SWMRNibbleArray immediate = nibbles[sectionY - StarLightInterface.this.minLightSection]; -+ -+ if (StarLightInterface.this.isClientSide) { -+ if (!immediate.isNullNibbleUpdating()) { -+ return immediate.getUpdating(x, y, z); -+ } -+ } else { -+ if (!immediate.isNullNibbleVisible()) { -+ return immediate.getVisible(x, y, z); -+ } -+ } -+ -+ final boolean[] emptinessMap = chunk.getSkyEmptinessMap(); -+ -+ if (emptinessMap == null) { -+ return 15; -+ } -+ -+ // are we above this chunk's lowest empty section? -+ int lowestY = StarLightInterface.this.minLightSection - 1; -+ for (int currY = StarLightInterface.this.maxSection; currY >= StarLightInterface.this.minSection; --currY) { -+ if (emptinessMap[currY - StarLightInterface.this.minSection]) { -+ continue; -+ } -+ -+ // should always be full lit here -+ lowestY = currY; -+ break; -+ } -+ -+ if (sectionY > lowestY) { -+ return 15; -+ } -+ -+ // this nibble is going to depend solely on the skylight data above it -+ // find first non-null data above (there does exist one, as we just found it above) -+ for (int currY = sectionY + 1; currY <= StarLightInterface.this.maxLightSection; ++currY) { -+ final SWMRNibbleArray nibble = nibbles[currY - StarLightInterface.this.minLightSection]; -+ if (StarLightInterface.this.isClientSide) { -+ if (!nibble.isNullNibbleUpdating()) { -+ return nibble.getUpdating(x, 0, z); -+ } -+ } else { -+ if (!nibble.isNullNibbleVisible()) { -+ return nibble.getVisible(x, 0, z); -+ } -+ } -+ } -+ -+ // should never reach here -+ return 15; -+ } -+ -+ @Override -+ public void updateSectionStatus(final SectionPos pos, final boolean notReady) { -+ StarLightInterface.this.sectionChange(pos, notReady); -+ } -+ }; -+ this.blockReader = !hasBlockLight ? LayerLightEventListener.DummyLightLayerEventListener.INSTANCE : new LayerLightEventListener() { -+ @Override -+ public void checkBlock(final BlockPos blockPos) { -+ StarLightInterface.this.lightEngine.checkBlock(blockPos.immutable()); -+ } -+ -+ @Override -+ public void onBlockEmissionIncrease(final BlockPos blockPos, final int i) { -+ this.checkBlock(blockPos); -+ } -+ -+ @Override -+ public boolean hasLightWork() { -+ // not really correct... -+ return StarLightInterface.this.hasUpdates(); -+ } -+ -+ @Override -+ public int runUpdates(final int i, final boolean bl, final boolean bl2) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public void enableLightSources(final ChunkPos chunkPos, final boolean bl) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public DataLayer getDataLayerData(final SectionPos pos) { -+ final ChunkAccess chunk = StarLightInterface.this.getAnyChunkNow(pos.getX(), pos.getZ()); -+ -+ if (chunk == null || pos.getY() < StarLightInterface.this.minLightSection || pos.getY() > StarLightInterface.this.maxLightSection) { -+ return null; -+ } -+ -+ return chunk.getBlockNibbles()[pos.getY() - StarLightInterface.this.minLightSection].toVanillaNibble(); -+ } -+ -+ @Override -+ public int getLightValue(final BlockPos blockPos) { -+ final int cx = blockPos.getX() >> 4; -+ final int cy = blockPos.getY() >> 4; -+ final int cz = blockPos.getZ() >> 4; -+ -+ if (cy < StarLightInterface.this.minLightSection || cy > StarLightInterface.this.maxLightSection) { -+ return 0; -+ } -+ -+ final ChunkAccess chunk = StarLightInterface.this.getAnyChunkNow(cx, cz); -+ -+ if (chunk == null) { -+ return 0; -+ } -+ -+ final SWMRNibbleArray nibble = chunk.getBlockNibbles()[cy - StarLightInterface.this.minLightSection]; -+ if (StarLightInterface.this.isClientSide) { -+ return nibble.getUpdating(blockPos.getX(), blockPos.getY(), blockPos.getZ()); -+ } else { -+ return nibble.getVisible(blockPos.getX(), blockPos.getY(), blockPos.getZ()); -+ } -+ } -+ -+ @Override -+ public void updateSectionStatus(final SectionPos pos, final boolean notReady) { -+ StarLightInterface.this.sectionChange(pos, notReady); -+ } -+ }; -+ } -+ -+ public LayerLightEventListener getSkyReader() { -+ return this.skyReader; -+ } -+ -+ public LayerLightEventListener getBlockReader() { -+ return this.blockReader; -+ } -+ -+ public boolean isClientSide() { -+ return this.isClientSide; -+ } -+ -+ public ChunkAccess getAnyChunkNow(final int chunkX, final int chunkZ) { -+ if (this.world == null) { -+ // empty world -+ return null; -+ } -+ return ((ServerLevel)this.world).getChunkSource().getChunkAtImmediately(chunkX, chunkZ); -+ } -+ -+ public boolean hasUpdates() { -+ return !this.lightQueue.isEmpty(); -+ } -+ -+ public Level getWorld() { -+ return this.world; -+ } -+ -+ public LightChunkGetter getLightAccess() { -+ return this.lightAccess; -+ } -+ -+ protected final SkyStarLightEngine getSkyLightEngine() { -+ if (this.cachedSkyPropagators == null) { -+ return null; -+ } -+ final SkyStarLightEngine ret; -+ synchronized (this.cachedSkyPropagators) { -+ ret = this.cachedSkyPropagators.pollFirst(); -+ } -+ -+ if (ret == null) { -+ return new SkyStarLightEngine(this.world); -+ } -+ return ret; -+ } -+ -+ protected final void releaseSkyLightEngine(final SkyStarLightEngine engine) { -+ if (this.cachedSkyPropagators == null) { -+ return; -+ } -+ synchronized (this.cachedSkyPropagators) { -+ this.cachedSkyPropagators.addFirst(engine); -+ } -+ } -+ -+ protected final BlockStarLightEngine getBlockLightEngine() { -+ if (this.cachedBlockPropagators == null) { -+ return null; -+ } -+ final BlockStarLightEngine ret; -+ synchronized (this.cachedBlockPropagators) { -+ ret = this.cachedBlockPropagators.pollFirst(); -+ } -+ -+ if (ret == null) { -+ return new BlockStarLightEngine(this.world); -+ } -+ return ret; -+ } -+ -+ protected final void releaseBlockLightEngine(final BlockStarLightEngine engine) { -+ if (this.cachedBlockPropagators == null) { -+ return; -+ } -+ synchronized (this.cachedBlockPropagators) { -+ this.cachedBlockPropagators.addFirst(engine); -+ } -+ } -+ -+ public CompletableFuture blockChange(final BlockPos pos) { -+ if (this.world == null || pos.getY() < WorldUtil.getMinBlockY(this.world) || pos.getY() > WorldUtil.getMaxBlockY(this.world)) { // empty world -+ return null; -+ } -+ -+ return this.lightQueue.queueBlockChange(pos); -+ } -+ -+ public CompletableFuture sectionChange(final SectionPos pos, final boolean newEmptyValue) { -+ if (this.world == null) { // empty world -+ return null; -+ } -+ -+ return this.lightQueue.queueSectionChange(pos, newEmptyValue); -+ } -+ -+ public void forceLoadInChunk(final ChunkAccess chunk, final Boolean[] emptySections) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.forceHandleEmptySectionChanges(this.lightAccess, chunk, emptySections); -+ } -+ if (blockEngine != null) { -+ blockEngine.forceHandleEmptySectionChanges(this.lightAccess, chunk, emptySections); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void loadInChunk(final int chunkX, final int chunkZ, final Boolean[] emptySections) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.handleEmptySectionChanges(this.lightAccess, chunkX, chunkZ, emptySections); -+ } -+ if (blockEngine != null) { -+ blockEngine.handleEmptySectionChanges(this.lightAccess, chunkX, chunkZ, emptySections); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void lightChunk(final ChunkAccess chunk, final Boolean[] emptySections) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.light(this.lightAccess, chunk, emptySections); -+ } -+ if (blockEngine != null) { -+ blockEngine.light(this.lightAccess, chunk, emptySections); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void relightChunks(final Set chunks, final Consumer chunkLightCallback, -+ final IntConsumer onComplete) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.relightChunks(this.lightAccess, chunks, blockEngine == null ? chunkLightCallback : null, -+ blockEngine == null ? onComplete : null); -+ } -+ if (blockEngine != null) { -+ blockEngine.relightChunks(this.lightAccess, chunks, chunkLightCallback, onComplete); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void checkChunkEdges(final int chunkX, final int chunkZ) { -+ this.checkSkyEdges(chunkX, chunkZ); -+ this.checkBlockEdges(chunkX, chunkZ); -+ } -+ -+ public void checkSkyEdges(final int chunkX, final int chunkZ) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ } -+ } -+ -+ public void checkBlockEdges(final int chunkX, final int chunkZ) { -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ try { -+ if (blockEngine != null) { -+ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ); -+ } -+ } finally { -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void checkSkyEdges(final int chunkX, final int chunkZ, final ShortCollection sections) { -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ -+ try { -+ if (skyEngine != null) { -+ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, sections); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ } -+ } -+ -+ public void checkBlockEdges(final int chunkX, final int chunkZ, final ShortCollection sections) { -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ try { -+ if (blockEngine != null) { -+ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, sections); -+ } -+ } finally { -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ public void scheduleChunkLight(final ChunkPos pos, final Runnable run) { -+ this.lightQueue.queueChunkLighting(pos, run); -+ } -+ -+ public void removeChunkTasks(final ChunkPos pos) { -+ this.lightQueue.removeChunk(pos); -+ } -+ -+ public void propagateChanges() { -+ if (this.lightQueue.isEmpty()) { -+ return; -+ } -+ -+ final SkyStarLightEngine skyEngine = this.getSkyLightEngine(); -+ final BlockStarLightEngine blockEngine = this.getBlockLightEngine(); -+ -+ try { -+ LightQueue.ChunkTasks task; -+ while ((task = this.lightQueue.removeFirstTask()) != null) { -+ if (task.lightTasks != null) { -+ for (final Runnable run : task.lightTasks) { -+ run.run(); -+ } -+ } -+ -+ final long coordinate = task.chunkCoordinate; -+ final int chunkX = CoordinateUtils.getChunkX(coordinate); -+ final int chunkZ = CoordinateUtils.getChunkZ(coordinate); -+ -+ final Set positions = task.changedPositions; -+ final Boolean[] sectionChanges = task.changedSectionSet; -+ -+ if (skyEngine != null && (!positions.isEmpty() || sectionChanges != null)) { -+ skyEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges); -+ } -+ if (blockEngine != null && (!positions.isEmpty() || sectionChanges != null)) { -+ blockEngine.blocksChangedInChunk(this.lightAccess, chunkX, chunkZ, positions, sectionChanges); -+ } -+ -+ if (skyEngine != null && task.queuedEdgeChecksSky != null) { -+ skyEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, task.queuedEdgeChecksSky); -+ } -+ if (blockEngine != null && task.queuedEdgeChecksBlock != null) { -+ blockEngine.checkChunkEdges(this.lightAccess, chunkX, chunkZ, task.queuedEdgeChecksBlock); -+ } -+ -+ task.onComplete.complete(null); -+ } -+ } finally { -+ this.releaseSkyLightEngine(skyEngine); -+ this.releaseBlockLightEngine(blockEngine); -+ } -+ } -+ -+ protected static final class LightQueue { -+ -+ protected final Long2ObjectLinkedOpenHashMap chunkTasks = new Long2ObjectLinkedOpenHashMap<>(); -+ protected final StarLightInterface manager; -+ -+ public LightQueue(final StarLightInterface manager) { -+ this.manager = manager; -+ } -+ -+ public synchronized boolean isEmpty() { -+ return this.chunkTasks.isEmpty(); -+ } -+ -+ public synchronized CompletableFuture queueBlockChange(final BlockPos pos) { -+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); -+ tasks.changedPositions.add(pos.immutable()); -+ return tasks.onComplete; -+ } -+ -+ public synchronized CompletableFuture queueSectionChange(final SectionPos pos, final boolean newEmptyValue) { -+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); -+ -+ if (tasks.changedSectionSet == null) { -+ tasks.changedSectionSet = new Boolean[this.manager.maxSection - this.manager.minSection + 1]; -+ } -+ tasks.changedSectionSet[pos.getY() - this.manager.minSection] = Boolean.valueOf(newEmptyValue); -+ -+ return tasks.onComplete; -+ } -+ -+ public synchronized CompletableFuture queueChunkLighting(final ChunkPos pos, final Runnable lightTask) { -+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); -+ if (tasks.lightTasks == null) { -+ tasks.lightTasks = new ArrayList<>(); -+ } -+ tasks.lightTasks.add(lightTask); -+ -+ return tasks.onComplete; -+ } -+ -+ public synchronized CompletableFuture queueChunkSkylightEdgeCheck(final SectionPos pos, final ShortCollection sections) { -+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); -+ -+ ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksSky; -+ if (queuedEdges == null) { -+ queuedEdges = tasks.queuedEdgeChecksSky = new ShortOpenHashSet(); -+ } -+ queuedEdges.addAll(sections); -+ -+ return tasks.onComplete; -+ } -+ -+ public synchronized CompletableFuture queueChunkBlocklightEdgeCheck(final SectionPos pos, final ShortCollection sections) { -+ final ChunkTasks tasks = this.chunkTasks.computeIfAbsent(CoordinateUtils.getChunkKey(pos), ChunkTasks::new); -+ -+ ShortOpenHashSet queuedEdges = tasks.queuedEdgeChecksBlock; -+ if (queuedEdges == null) { -+ queuedEdges = tasks.queuedEdgeChecksBlock = new ShortOpenHashSet(); -+ } -+ queuedEdges.addAll(sections); -+ -+ return tasks.onComplete; -+ } -+ -+ public void removeChunk(final ChunkPos pos) { -+ final ChunkTasks tasks; -+ synchronized (this) { -+ tasks = this.chunkTasks.remove(CoordinateUtils.getChunkKey(pos)); -+ } -+ if (tasks != null) { -+ tasks.onComplete.complete(null); -+ } -+ } -+ -+ public synchronized ChunkTasks removeFirstTask() { -+ if (this.chunkTasks.isEmpty()) { -+ return null; -+ } -+ return this.chunkTasks.removeFirst(); -+ } -+ -+ protected static final class ChunkTasks { -+ -+ public final Set changedPositions = new HashSet<>(); -+ public Boolean[] changedSectionSet; -+ public ShortOpenHashSet queuedEdgeChecksSky; -+ public ShortOpenHashSet queuedEdgeChecksBlock; -+ public List lightTasks; -+ -+ public final CompletableFuture onComplete = new CompletableFuture<>(); -+ -+ public final long chunkCoordinate; -+ -+ public ChunkTasks(final long chunkCoordinate) { -+ this.chunkCoordinate = chunkCoordinate; -+ } -+ } -+ } -+} -diff --git a/src/main/java/co/aikar/timings/MinecraftTimings.java b/src/main/java/co/aikar/timings/MinecraftTimings.java -index b9cdbf8acccfd6b207a0116f068168f3b8c8e17d..7404989c37ee1b7aa4e6999a063180d099532f7e 100644 ---- a/src/main/java/co/aikar/timings/MinecraftTimings.java -+++ b/src/main/java/co/aikar/timings/MinecraftTimings.java -@@ -45,6 +45,8 @@ public final class MinecraftTimings { - - public static final Timing antiXrayUpdateTimer = Timings.ofSafe("anti-xray - update"); - public static final Timing antiXrayObfuscateTimer = Timings.ofSafe("anti-xray - obfuscate"); -+ public static final Timing distanceManagerTick = Timings.ofSafe("Distance Manager Tick"); // Tuinity - add timings for distance manager -+ public static final Timing scoreboardScoreSearch = Timings.ofSafe("Scoreboard score search"); // Tuinity - add timings for scoreboard search - - private static final Map, String> taskNameCache = new MapMaker().weakKeys().makeMap(); - -diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java -index 2ff4d4921e2076abf415bd3c8f5173ecd6222168..9d920565ff65a84b1b9a2a4777fd8bc8f07e0153 100644 ---- a/src/main/java/co/aikar/timings/TimingsExport.java -+++ b/src/main/java/co/aikar/timings/TimingsExport.java -@@ -153,7 +153,7 @@ public class TimingsExport extends Thread { - return pair(rule, world.getWorld().getGameRuleValue(rule)); - })), - pair("ticking-distance", world.getChunkSource().chunkMap.getEffectiveViewDistance()), -- pair("notick-viewdistance", world.getChunkSource().chunkMap.getEffectiveNoTickViewDistance()) -+ pair("notick-viewdistance", world.getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance()) // Tuinity - replace old player chunk management - )); - })); - -@@ -228,7 +228,8 @@ public class TimingsExport extends Thread { - parent.put("config", createObject( - pair("spigot", mapAsJSON(Bukkit.spigot().getSpigotConfig(), null)), - pair("bukkit", mapAsJSON(Bukkit.spigot().getBukkitConfig(), null)), -- pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null)) -+ pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null)), // Tuinity - add config to timings report -+ pair("tuinity", mapAsJSON(Bukkit.spigot().getTuinityConfig(), null)) // Tuinity - add config to timings report - )); - - new TimingsExport(listeners, parent, history).start(); -diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java -index 218f5bafeed8551b55b91c7fccaf6935c8b631ca..3918b24c98faa5232c7ffd733ba8000562132785 100644 ---- a/src/main/java/com/destroystokyo/paper/Metrics.java -+++ b/src/main/java/com/destroystokyo/paper/Metrics.java -@@ -593,7 +593,7 @@ public class Metrics { - boolean logFailedRequests = config.getBoolean("logFailedRequests", false); - // Only start Metrics, if it's enabled in the config - if (config.getBoolean("enabled", true)) { -- Metrics metrics = new Metrics("Paper", serverUUID, logFailedRequests, Bukkit.getLogger()); -+ Metrics metrics = new Metrics("Tuinity", serverUUID, logFailedRequests, Bukkit.getLogger()); // Tuinity - we have our own bstats page - - metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> { - String minecraftVersion = Bukkit.getVersion(); -@@ -603,7 +603,7 @@ public class Metrics { - - metrics.addCustomChart(new Metrics.SingleLineChart("players", () -> Bukkit.getOnlinePlayers().size())); - metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : "offline")); -- metrics.addCustomChart(new Metrics.SimplePie("paper_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown")); -+ metrics.addCustomChart(new Metrics.SimplePie("tuinity_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown")); // Tuinity - we have our own bstats page - - metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> { - Map> map = new HashMap<>(); -diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java -index ba8395435482fc4d6e02fc86794cdb0d35d4399c..af66c6d863a57b2c34006b06852e9811a74d7dfa 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperCommand.java -+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java -@@ -272,7 +272,7 @@ public class PaperCommand extends Command { - int ticking = 0; - int entityTicking = 0; - -- for (ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunkMap.values()) { -+ for (ChunkHolder chunk : world.getChunkSource().chunkMap.updatingChunks.getUpdatingMap().values()) { // Tuinity - change updating chunks map - if (chunk.getFullChunkUnchecked() == null) { - continue; - } -@@ -496,6 +496,46 @@ public class PaperCommand extends Command { - } - } - -+ // Tuinity start - rewrite light engine -+ private void starlightFixLight(ServerPlayer sender, ServerLevel world, ThreadedLevelLightEngine lightengine, int radius) { -+ long start = System.nanoTime(); -+ java.util.LinkedHashSet chunks = new java.util.LinkedHashSet<>(MCUtil.getSpiralOutChunks(sender.blockPosition(), radius)); // getChunkCoordinates is actually just bad mappings, this function rets position as blockpos -+ -+ int[] pending = new int[1]; -+ for (java.util.Iterator iterator = chunks.iterator(); iterator.hasNext();) { -+ final ChunkPos chunkPos = iterator.next(); -+ -+ final net.minecraft.world.level.chunk.ChunkAccess chunk = world.getChunkSource().getChunkAtImmediately(chunkPos.x, chunkPos.z); -+ if (chunk == null || !chunk.isLightCorrect() || !chunk.getStatus().isOrAfter(net.minecraft.world.level.chunk.ChunkStatus.LIGHT)) { -+ // cannot relight this chunk -+ iterator.remove(); -+ continue; -+ } -+ -+ ++pending[0]; -+ } -+ -+ int[] relitChunks = new int[1]; -+ lightengine.relight(chunks, -+ (ChunkPos chunkPos) -> { -+ ++relitChunks[0]; -+ sender.getBukkitEntity().sendMessage( -+ ChatColor.BLUE + "Relit chunk " + ChatColor.DARK_AQUA + chunkPos + ChatColor.BLUE + -+ ", progress: " + ChatColor.DARK_AQUA + (int)(Math.round(100.0 * (double)(relitChunks[0])/(double)pending[0])) + "%" -+ ); -+ }, -+ (int totalRelit) -> { -+ final long end = System.nanoTime(); -+ final long diff = Math.round(1.0e-6*(end - start)); -+ sender.getBukkitEntity().sendMessage( -+ ChatColor.BLUE + "Relit " + ChatColor.DARK_AQUA + totalRelit + ChatColor.BLUE + " chunks. Took " + -+ ChatColor.DARK_AQUA + diff + "ms" -+ ); -+ }); -+ sender.getBukkitEntity().sendMessage(ChatColor.BLUE + "Relighting " + ChatColor.DARK_AQUA + pending[0] + ChatColor.BLUE + " chunks"); -+ } -+ // Tuinity end - rewrite light engine -+ - private void doFixLight(CommandSender sender, String[] args) { - if (!(sender instanceof Player)) { - sender.sendMessage("Only players can use this command"); -@@ -504,7 +544,7 @@ public class PaperCommand extends Command { - int radius = 2; - if (args.length > 1) { - try { -- radius = Math.min(5, Integer.parseInt(args[1])); -+ radius = Math.min(32, Integer.parseInt(args[1])); // Tuinity - MOOOOOORE - } catch (Exception e) { - sender.sendMessage("Not a number"); - return; -@@ -517,6 +557,13 @@ public class PaperCommand extends Command { - ServerLevel world = (ServerLevel) handle.level; - ThreadedLevelLightEngine lightengine = world.getChunkSource().getLightEngine(); - -+ // Tuinity start - rewrite light engine -+ if (true) { -+ this.starlightFixLight(handle, world, lightengine, radius); -+ return; -+ } -+ // Tuinity end - rewrite light engine -+ - net.minecraft.core.BlockPos center = MCUtil.toBlockPosition(player.getLocation()); - Deque queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius)); - updateLight(sender, world, lightengine, queue); -diff --git a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -index 580bae0d414d371a07a6bfeefc41fdd989dc0083..d50b61876f15d95b836b3dd81d9c3492c91a8448 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -+++ b/src/main/java/com/destroystokyo/paper/PaperVersionFetcher.java -@@ -29,8 +29,8 @@ public class PaperVersionFetcher implements VersionFetcher { - @Nonnull - @Override - public Component getVersionMessage(@Nonnull String serverVersion) { -- String[] parts = serverVersion.substring("git-Paper-".length()).split("[-\\s]"); -- final Component updateMessage = getUpdateStatusMessage("PaperMC/Paper", GITHUB_BRANCH_NAME, parts[0]); -+ String[] parts = serverVersion.substring("git-Tuinity-".length()).split("[-\\s]"); // Tuinity -+ final Component updateMessage = getUpdateStatusMessage("Spottedleaf/Tuinity", GITHUB_BRANCH_NAME, parts[0]); // Tuinity - final Component history = getHistory(); - - return history != null ? TextComponent.ofChildren(updateMessage, Component.newline(), history) : updateMessage; -@@ -54,13 +54,10 @@ public class PaperVersionFetcher implements VersionFetcher { - - private static Component getUpdateStatusMessage(@Nonnull String repo, @Nonnull String branch, @Nonnull String versionInfo) { - int distance; -- try { -- int jenkinsBuild = Integer.parseInt(versionInfo); -- distance = fetchDistanceFromSiteApi(jenkinsBuild, getMinecraftVersion()); -- } catch (NumberFormatException ignored) { -+ // Tuinity - we don't have jenkins setup - versionInfo = versionInfo.replace("\"", ""); - distance = fetchDistanceFromGitHub(repo, branch, versionInfo); -- } -+ // Tuinity - we don't have jenkins setup - - switch (distance) { - case -1: -diff --git a/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java -index da13ff17609b7bc8076d9297edf8decf01a2ed88..b4c69d39eee19339b1de295151d7ed3bf61635c1 100644 ---- a/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java -+++ b/src/main/java/com/destroystokyo/paper/server/ticklist/PaperTickList.java -@@ -312,6 +312,7 @@ public final class PaperTickList extends ServerTickList { // extend to avo - toTick.tickState = STATE_SCHEDULED; - this.addToNotTickingReady(toTick); - } -+ MinecraftServer.getServer().executeMidTickTasks(); // Tuinity - exec chunk tasks during world tick - } catch (final Throwable thr) { - // start copy from TickListServer // TODO check on update - CrashReport crashreport = CrashReport.forThrowable(thr, "Exception while ticking"); -diff --git a/src/main/java/com/tuinity/tuinity/chunk/PlayerChunkLoader.java b/src/main/java/com/tuinity/tuinity/chunk/PlayerChunkLoader.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d9bfe05d4f86229ed743113bfb0bbd983adb7e68 ---- /dev/null -+++ b/src/main/java/com/tuinity/tuinity/chunk/PlayerChunkLoader.java -@@ -0,0 +1,965 @@ -+package com.tuinity.tuinity.chunk; -+ -+import com.destroystokyo.paper.util.misc.PlayerAreaMap; -+import com.destroystokyo.paper.util.misc.PooledLinkedHashSets; -+import com.tuinity.tuinity.config.TuinityConfig; -+import com.tuinity.tuinity.util.CoordinateUtils; -+import com.tuinity.tuinity.util.TickThread; -+import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; -+import it.unimi.dsi.fastutil.objects.Reference2ObjectLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; -+import net.minecraft.network.protocol.Packet; -+import net.minecraft.network.protocol.game.ClientboundSetChunkCacheCenterPacket; -+import net.minecraft.network.protocol.game.ClientboundSetChunkCacheRadiusPacket; -+import net.minecraft.server.MCUtil; -+import net.minecraft.server.MinecraftServer; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.ChunkMap; -+import net.minecraft.server.level.ServerPlayer; -+import net.minecraft.server.level.TicketType; -+import net.minecraft.util.Mth; -+import net.minecraft.world.level.ChunkPos; -+import net.minecraft.world.level.chunk.LevelChunk; -+import java.util.ArrayDeque; -+import java.util.ArrayList; -+import java.util.List; -+import java.util.TreeSet; -+import java.util.concurrent.atomic.AtomicInteger; -+ -+public final class PlayerChunkLoader { -+ -+ public static final int MIN_VIEW_DISTANCE = 2; -+ public static final int MAX_VIEW_DISTANCE = 32; -+ -+ public static final int TICK_TICKET_LEVEL = 31; -+ public static final int LOADED_TICKET_LEVEL = 33; -+ -+ protected final ChunkMap chunkMap; -+ protected final Reference2ObjectLinkedOpenHashMap playerMap = new Reference2ObjectLinkedOpenHashMap<>(512, 0.7f); -+ protected final ReferenceLinkedOpenHashSet chunkSendQueue = new ReferenceLinkedOpenHashSet<>(512, 0.7f); -+ -+ protected final TreeSet chunkLoadQueue = new TreeSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> { -+ if (p1 == p2) { -+ return 0; -+ } -+ -+ final ChunkPriorityHolder holder1 = p1.loadQueue.peekFirst(); -+ final ChunkPriorityHolder holder2 = p2.loadQueue.peekFirst(); -+ -+ final int priorityCompare = Double.compare(holder1 == null ? Double.MAX_VALUE : holder1.priority, holder2 == null ? Double.MAX_VALUE : holder2.priority); -+ -+ if (priorityCompare != 0) { -+ return priorityCompare; -+ } -+ -+ final int idCompare = Integer.compare(p1.player.getId(), p2.player.getId()); -+ -+ if (idCompare != 0) { -+ return idCompare; -+ } -+ -+ // last resort -+ return Integer.compare(System.identityHashCode(p1), System.identityHashCode(p2)); -+ }); -+ -+ protected final TreeSet chunkSendWaitQueue = new TreeSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> { -+ if (p1 == p2) { -+ return 0; -+ } -+ -+ final int timeCompare = Long.compare(p1.nextChunkSendTarget, p2.nextChunkSendTarget); -+ if (timeCompare != 0) { -+ return timeCompare; -+ } -+ -+ final int idCompare = Integer.compare(p1.player.getId(), p2.player.getId()); -+ -+ if (idCompare != 0) { -+ return idCompare; -+ } -+ -+ // last resort -+ return Integer.compare(System.identityHashCode(p1), System.identityHashCode(p2)); -+ }); -+ -+ -+ // no throttling is applied below this VD for loading -+ -+ /** -+ * The chunks to be sent to players, provided they're send-ready. Send-ready means the chunk and its 1 radius neighbours are loaded. -+ */ -+ public final PlayerAreaMap broadcastMap; -+ -+ /** -+ * The chunks to be brought up to send-ready status. Send-ready means the chunk and its 1 radius neighbours are loaded. -+ */ -+ public final PlayerAreaMap loadMap; -+ -+ /** -+ * Areamap used only to remove tickets for send-ready chunks. View distance is always + 1 of load view distance. Thus, -+ * this map is always representing the chunks we are actually going to load. -+ */ -+ public final PlayerAreaMap loadTicketCleanup; -+ -+ /** -+ * The chunks to brought to ticking level. Each chunk must have 2 radius neighbours loaded before this can happen. -+ */ -+ public final PlayerAreaMap tickMap; -+ -+ /** -+ * -1 if defaulting to [load distance], else always in [2, load distance] -+ */ -+ protected int rawSendDistance = -1; -+ -+ /** -+ * -1 if defaulting to [tick view distance + 1], else always in [tick view distance + 1, 32 + 1] -+ */ -+ protected int rawLoadDistance = -1; -+ -+ /** -+ * Never -1, always in [2, 32] -+ */ -+ protected int rawTickDistance = -1; -+ -+ // methods to bridge for API -+ -+ public int getTargetViewDistance() { -+ return this.getTickDistance(); -+ } -+ -+ public void setTargetViewDistance(final int distance) { -+ this.setTickDistance(distance); -+ } -+ -+ public int getTargetNoTickViewDistance() { -+ return this.getLoadDistance() - 1; -+ } -+ -+ public void setTargetNoTickViewDistance(final int distance) { -+ this.setLoadDistance(distance == -1 ? -1 : distance + 1); -+ } -+ -+ public int getTargetSendDistance() { -+ return this.rawSendDistance == -1 ? this.getLoadDistance() : this.rawSendDistance; -+ } -+ -+ public void setTargetSendDistance(final int distance) { -+ this.setSendDistance(distance); -+ } -+ -+ // internal methods -+ -+ public int getSendDistance() { -+ final int loadDistance = this.getLoadDistance(); -+ return this.rawSendDistance == -1 ? loadDistance : Math.min(this.rawSendDistance, loadDistance); -+ } -+ -+ public void setSendDistance(final int distance) { -+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE + 1)) { -+ throw new IllegalArgumentException(Integer.toString(distance)); -+ } -+ this.rawSendDistance = distance; -+ } -+ -+ public int getLoadDistance() { -+ final int tickDistance = this.getTickDistance(); -+ return this.rawLoadDistance == -1 ? tickDistance + 1 : Math.max(tickDistance + 1, this.rawLoadDistance); -+ } -+ -+ public void setLoadDistance(final int distance) { -+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE + 1)) { -+ throw new IllegalArgumentException(Integer.toString(distance)); -+ } -+ this.rawLoadDistance = distance; -+ } -+ -+ public int getTickDistance() { -+ return this.rawTickDistance; -+ } -+ -+ public void setTickDistance(final int distance) { -+ if (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE) { -+ throw new IllegalArgumentException(Integer.toString(distance)); -+ } -+ this.rawTickDistance = distance; -+ } -+ -+ /* -+ Players have 3 different types of view distance: -+ 1. Sending view distance -+ 2. Loading view distance -+ 3. Ticking view distance -+ -+ But for configuration purposes (and API) there are: -+ 1. No-tick view distance -+ 2. Tick view distance -+ 3. Broadcast view distance -+ -+ These aren't always the same as the types we represent internally. -+ -+ Loading view distance is always max(no-tick + 1, tick + 1) -+ - no-tick has 1 added because clients need an extra radius to render chunks -+ - tick has 1 added because it needs an extra radius of chunks to load before they can be marked ticking -+ -+ Loading view distance is defined as the radius of chunks that will be brought to send-ready status, which means -+ it loads chunks in radius load-view-distance + 1. -+ -+ The maximum value for send view distance is the load view distance. API can set it lower. -+ */ -+ -+ public PlayerChunkLoader(final ChunkMap chunkMap, final PooledLinkedHashSets pooledHashSets) { -+ this.chunkMap = chunkMap; -+ this.broadcastMap = new PlayerAreaMap(pooledHashSets, -+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { -+ if (player.needsChunkCenterUpdate) { -+ player.needsChunkCenterUpdate = false; -+ player.connection.send(new ClientboundSetChunkCacheCenterPacket(currPosX, currPosZ)); -+ } -+ PlayerChunkLoader.this.onChunkEnter(player, rangeX, rangeZ); -+ }, -+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { -+ PlayerChunkLoader.this.onChunkLeave(player, rangeX, rangeZ); -+ }); -+ this.loadMap = new PlayerAreaMap(pooledHashSets, -+ null, -+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { -+ if (newState != null) { -+ return; -+ } -+ PlayerChunkLoader.this.isTargetedForPlayerLoad.remove(CoordinateUtils.getChunkKey(rangeX, rangeZ)); -+ }); -+ this.loadTicketCleanup = new PlayerAreaMap(pooledHashSets, -+ null, -+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { -+ if (newState != null) { -+ return; -+ } -+ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ); -+ PlayerChunkLoader.this.chunkMap.level.getChunkSource().removeTicketAtLevel(TicketType.PLAYER, chunkPos, LOADED_TICKET_LEVEL, chunkPos); -+ if (PlayerChunkLoader.this.chunkTicketTracker.remove(chunkPos.toLong())) { -+ --PlayerChunkLoader.this.concurrentChunkLoads; -+ } -+ }); -+ this.tickMap = new PlayerAreaMap(pooledHashSets, -+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { -+ if (newState.size() != 1) { -+ return; -+ } -+ LevelChunk chunk = PlayerChunkLoader.this.chunkMap.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(rangeX, rangeZ); -+ if (chunk == null || !chunk.areNeighboursLoaded(2)) { -+ return; -+ } -+ -+ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ); -+ PlayerChunkLoader.this.chunkMap.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, TICK_TICKET_LEVEL, chunkPos); -+ }, -+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { -+ if (newState != null) { -+ return; -+ } -+ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ); -+ PlayerChunkLoader.this.chunkMap.level.getChunkSource().removeTicketAtLevel(TicketType.PLAYER, chunkPos, TICK_TICKET_LEVEL, chunkPos); -+ }); -+ } -+ -+ protected final LongOpenHashSet isTargetedForPlayerLoad = new LongOpenHashSet(); -+ protected final LongOpenHashSet chunkTicketTracker = new LongOpenHashSet(); -+ -+ // rets whether the chunk is at a loaded stage that is ready to be sent to players -+ public boolean isChunkPlayerLoaded(final int chunkX, final int chunkZ) { -+ final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ final ChunkHolder chunk = this.chunkMap.getVisibleChunkIfPresent(key); -+ -+ if (chunk == null) { -+ return false; -+ } -+ -+ return chunk.getSendingChunk() != null && this.isTargetedForPlayerLoad.contains(key); -+ } -+ -+ public boolean isChunkSent(final ServerPlayer player, final int chunkX, final int chunkZ) { -+ final PlayerLoaderData data = this.playerMap.get(player); -+ if (data == null) { -+ return false; -+ } -+ -+ return data.hasSentChunk(chunkX, chunkZ); -+ } -+ -+ protected int getMaxConcurrentChunkSends() { -+ double config = TuinityConfig.playerMaxConcurrentChunkSends; -+ return Math.max(1, config <= 0 ? (int)Math.ceil(-config * this.chunkMap.level.players().size()) : (int)config); -+ } -+ -+ protected int getMaxChunkLoads() { -+ double config = TuinityConfig.playerMaxConcurrentChunkLoads; -+ return Math.max(1, (config <= 0 ? (int)Math.ceil(-config * MinecraftServer.getServer().getPlayerCount()) : (int)config) * 9); -+ } -+ -+ protected double getTargetSendRatePerPlayer() { -+ double config = TuinityConfig.playerTargetChunkSendRate; -+ return config <= 0 ? -config : config / MinecraftServer.getServer().getPlayerCount(); -+ } -+ -+ public void onChunkPlayerTickReady(final int chunkX, final int chunkZ) { -+ final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); -+ this.chunkMap.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, TICK_TICKET_LEVEL, chunkPos); -+ } -+ -+ public void onChunkSendReady(final int chunkX, final int chunkZ) { -+ final long chunkKey = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ -+ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInSendRange = this.broadcastMap.getObjectsInRange(chunkX, chunkZ); -+ -+ if (playersInSendRange == null) { -+ return; -+ } -+ -+ final Object[] rawData = playersInSendRange.getBackingSet(); -+ for (int i = 0, len = rawData.length; i < len; ++i) { -+ final Object raw = rawData[i]; -+ -+ if (!(raw instanceof ServerPlayer)) { -+ continue; -+ } -+ this.onChunkEnter((ServerPlayer)raw, chunkX, chunkZ); -+ } -+ -+ // now let's try and queue mid tick logic again -+ } -+ -+ public void onChunkEnter(final ServerPlayer player, final int chunkX, final int chunkZ) { -+ final PlayerLoaderData data = this.playerMap.get(player); -+ -+ if (data == null) { -+ return; -+ } -+ -+ if (data.hasSentChunk(chunkX, chunkZ) || !this.isChunkPlayerLoaded(chunkX, chunkZ)) { -+ // if we don't have player tickets, then the load logic will pick this up and queue to send -+ return; -+ } -+ -+ final long playerPos = this.broadcastMap.getLastCoordinate(player); -+ final int playerChunkX = CoordinateUtils.getChunkX(playerPos); -+ final int playerChunkZ = CoordinateUtils.getChunkZ(playerPos); -+ final int manhattanDistance = Math.abs(playerChunkX - chunkX) + Math.abs(playerChunkZ - chunkZ); -+ -+ final ChunkPriorityHolder holder = new ChunkPriorityHolder(chunkX, chunkZ, manhattanDistance, 0.0); -+ data.sendQueue.add(holder); -+ } -+ -+ public void onChunkLoad(final int chunkX, final int chunkZ) { -+ if (this.chunkTicketTracker.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { -+ --this.concurrentChunkLoads; -+ } -+ } -+ -+ public void onChunkLeave(final ServerPlayer player, final int chunkX, final int chunkZ) { -+ final PlayerLoaderData data = this.playerMap.get(player); -+ -+ if (data == null) { -+ return; -+ } -+ -+ data.unloadChunk(chunkX, chunkZ); -+ } -+ -+ public void addPlayer(final ServerPlayer player) { -+ TickThread.ensureTickThread("Cannot add player async"); -+ if (!player.isRealPlayer) { -+ return; -+ } -+ final PlayerLoaderData data = new PlayerLoaderData(player, this); -+ if (this.playerMap.putIfAbsent(player, data) == null) { -+ data.update(); -+ } -+ } -+ -+ public void removePlayer(final ServerPlayer player) { -+ TickThread.ensureTickThread("Cannot remove player async"); -+ if (!player.isRealPlayer) { -+ return; -+ } -+ -+ final PlayerLoaderData loaderData = this.playerMap.remove(player); -+ if (loaderData == null) { -+ return; -+ } -+ loaderData.remove(); -+ this.chunkLoadQueue.remove(loaderData); -+ this.chunkSendQueue.remove(loaderData); -+ this.chunkSendWaitQueue.remove(loaderData); -+ synchronized (this.sendingChunkCounts) { -+ final int count = this.sendingChunkCounts.removeInt(loaderData); -+ if (count != 0) { -+ concurrentChunkSends.getAndAdd(-count); -+ } -+ } -+ } -+ -+ public void updatePlayer(final ServerPlayer player) { -+ TickThread.ensureTickThread("Cannot update player async"); -+ if (!player.isRealPlayer) { -+ return; -+ } -+ final PlayerLoaderData loaderData = this.playerMap.get(player); -+ if (loaderData != null) { -+ loaderData.update(); -+ } -+ } -+ -+ public PlayerLoaderData getData(final ServerPlayer player) { -+ return this.playerMap.get(player); -+ } -+ -+ public void tick() { -+ TickThread.ensureTickThread("Cannot tick async"); -+ for (final PlayerLoaderData data : this.playerMap.values()) { -+ data.update(); -+ } -+ this.tickMidTick(); -+ } -+ -+ protected static final AtomicInteger concurrentChunkSends = new AtomicInteger(); -+ protected final Reference2IntOpenHashMap sendingChunkCounts = new Reference2IntOpenHashMap<>(); -+ private void trySendChunks() { -+ final long time = System.nanoTime(); -+ // drain entries from wait queue -+ while (!this.chunkSendWaitQueue.isEmpty()) { -+ final PlayerLoaderData data = this.chunkSendWaitQueue.first(); -+ -+ if (data.nextChunkSendTarget > time) { -+ break; -+ } -+ -+ this.chunkSendWaitQueue.pollFirst(); -+ -+ this.chunkSendQueue.add(data); -+ } -+ -+ if (this.chunkSendQueue.isEmpty()) { -+ return; -+ } -+ -+ final int maxSends = this.getMaxConcurrentChunkSends(); -+ final double sendRate = this.getTargetSendRatePerPlayer(); -+ final long nextDeadline = (long)((1 / sendRate) * 1.0e9) + time; -+ for (;;) { -+ if (this.chunkSendQueue.isEmpty()) { -+ break; -+ } -+ final int currSends = concurrentChunkSends.get(); -+ if (currSends >= maxSends) { -+ break; -+ } -+ -+ if (!concurrentChunkSends.compareAndSet(currSends, currSends + 1)) { -+ continue; -+ } -+ -+ // send chunk -+ -+ final PlayerLoaderData data = this.chunkSendQueue.removeFirst(); -+ -+ final ChunkPriorityHolder queuedSend = data.sendQueue.pollFirst(); -+ if (queuedSend == null) { -+ concurrentChunkSends.getAndDecrement(); // we never sent, so decrease -+ // stop iterating over players who have nothing to send -+ if (this.chunkSendQueue.isEmpty()) { -+ // nothing left -+ break; -+ } -+ continue; -+ } -+ -+ if (!this.isChunkPlayerLoaded(queuedSend.chunkX, queuedSend.chunkZ)) { -+ throw new IllegalStateException(); -+ } -+ -+ data.nextChunkSendTarget = nextDeadline; -+ this.chunkSendWaitQueue.add(data); -+ -+ synchronized (this.sendingChunkCounts) { -+ this.sendingChunkCounts.addTo(data, 1); -+ } -+ -+ data.sendChunk(queuedSend.chunkX, queuedSend.chunkZ, () -> { -+ synchronized (this.sendingChunkCounts) { -+ final int count = this.sendingChunkCounts.getInt(data); -+ if (count == 0) { -+ // disconnected, so we don't need to decrement: it will be decremented for us -+ return; -+ } -+ if (count == 1) { -+ this.sendingChunkCounts.removeInt(data); -+ } else { -+ this.sendingChunkCounts.put(data, count - 1); -+ } -+ } -+ -+ concurrentChunkSends.getAndDecrement(); -+ }); -+ } -+ } -+ -+ protected int concurrentChunkLoads; -+ private void tryLoadChunks() { -+ if (this.chunkLoadQueue.isEmpty()) { -+ return; -+ } -+ -+ final int maxLoads = this.getMaxChunkLoads(); -+ for (;;) { -+ final PlayerLoaderData data = this.chunkLoadQueue.pollFirst(); -+ -+ final ChunkPriorityHolder queuedLoad = data.loadQueue.peekFirst(); -+ if (queuedLoad == null) { -+ if (this.chunkLoadQueue.isEmpty()) { -+ break; -+ } -+ continue; -+ } -+ -+ if (this.isChunkPlayerLoaded(queuedLoad.chunkX, queuedLoad.chunkZ)) { -+ // already loaded! -+ data.loadQueue.pollFirst(); // already loaded so we just skip -+ this.chunkLoadQueue.add(data); -+ -+ // ensure the chunk is queued to send -+ this.onChunkSendReady(queuedLoad.chunkX, queuedLoad.chunkZ); -+ continue; -+ } -+ -+ final long chunkKey = CoordinateUtils.getChunkKey(queuedLoad.chunkX, queuedLoad.chunkZ); -+ -+ final double priority = queuedLoad.priority; -+ // while we do need to rate limit chunk loads, the logic for sending chunks requires that tickets are present. -+ // when chunks are loaded (i.e spawn) but do not have this player's tickets, they have to wait behind the -+ // load queue. To avoid this problem, we check early here if tickets are required to load the chunk - if they -+ // aren't required, it bypasses the limiter system. -+ boolean unloadedTargetChunk = false; -+ unloaded_check: -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ final int offX = queuedLoad.chunkX + dx; -+ final int offZ = queuedLoad.chunkZ + dz; -+ if (this.chunkMap.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(offX, offZ) == null) { -+ unloadedTargetChunk = true; -+ break unloaded_check; -+ } -+ } -+ } -+ if (unloadedTargetChunk && priority > 0.0) { -+ // priority > 0.0 implies rate limited chunks -+ -+ final int currentChunkLoads = this.concurrentChunkLoads; -+ if (currentChunkLoads >= maxLoads) { -+ // don't poll, we didn't load it -+ this.chunkLoadQueue.add(data); -+ break; -+ } -+ } -+ -+ // can only poll after we decide to load -+ data.loadQueue.pollFirst(); -+ -+ // now that we've polled we can re-add to load queue -+ this.chunkLoadQueue.add(data); -+ -+ // add necessary tickets to load chunk up to send-ready -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ final int offX = queuedLoad.chunkX + dx; -+ final int offZ = queuedLoad.chunkZ + dz; -+ final ChunkPos chunkPos = new ChunkPos(offX, offZ); -+ -+ this.chunkMap.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, LOADED_TICKET_LEVEL, chunkPos); -+ if (this.chunkMap.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(offX, offZ) != null) { -+ continue; -+ } -+ -+ if (priority > 0.0 && this.chunkTicketTracker.add(CoordinateUtils.getChunkKey(offX, offZ))) { -+ // wont reach here if unloadedTargetChunk is false -+ ++this.concurrentChunkLoads; -+ } -+ } -+ } -+ -+ // mark that we've added tickets here -+ this.isTargetedForPlayerLoad.add(chunkKey); -+ -+ // it's possible all we needed was the player tickets to queue up the send. -+ if (this.isChunkPlayerLoaded(queuedLoad.chunkX, queuedLoad.chunkZ)) { -+ // yup, all we needed. -+ this.onChunkSendReady(queuedLoad.chunkX, queuedLoad.chunkZ); -+ } -+ } -+ } -+ -+ public void tickMidTick() { -+ // try to send more chunks -+ this.trySendChunks(); -+ -+ // try to queue more chunks to load -+ this.tryLoadChunks(); -+ } -+ -+ static final class ChunkPriorityHolder { -+ public final int chunkX; -+ public final int chunkZ; -+ public final int manhattanDistanceToPlayer; -+ public final double priority; -+ -+ public ChunkPriorityHolder(final int chunkX, final int chunkZ, final int manhattanDistanceToPlayer, final double priority) { -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.manhattanDistanceToPlayer = manhattanDistanceToPlayer; -+ this.priority = priority; -+ } -+ } -+ -+ public static final class PlayerLoaderData { -+ -+ protected static final float FOV = 110.0f; -+ protected static final double PRIORITISED_DISTANCE = 12.0 * 16.0; -+ -+ // Player max sprint speed is approximately 8m/s -+ protected static final double LOOK_PRIORITY_SPEED_THRESHOLD = (10.0/20.0) * (10.0/20.0); -+ protected static final double LOOK_PRIORITY_YAW_DELTA_RECALC_THRESHOLD = 3.0f; -+ -+ protected double lastLocX = Double.NEGATIVE_INFINITY; -+ protected double lastLocZ = Double.NEGATIVE_INFINITY; -+ -+ protected int lastChunkX; -+ protected int lastChunkZ; -+ -+ // this is corrected so that 0 is along the positive x-axis -+ protected float lastYaw = Float.NEGATIVE_INFINITY; -+ -+ protected int lastSendDistance = Integer.MIN_VALUE; -+ protected int lastLoadDistance = Integer.MIN_VALUE; -+ protected int lastTickDistance = Integer.MIN_VALUE; -+ protected boolean usingLookingPriority; -+ -+ protected final ServerPlayer player; -+ protected final PlayerChunkLoader loader; -+ -+ // warning: modifications of this field must be aware that the loadQueue inside PlayerChunkLoader uses this field -+ // in a comparator! -+ protected final ArrayDeque loadQueue = new ArrayDeque<>(); -+ protected final LongOpenHashSet sentChunks = new LongOpenHashSet(); -+ -+ protected final TreeSet sendQueue = new TreeSet<>((final ChunkPriorityHolder p1, final ChunkPriorityHolder p2) -> { -+ final int distanceCompare = Integer.compare(p1.manhattanDistanceToPlayer, p2.manhattanDistanceToPlayer); -+ if (distanceCompare != 0) { -+ return distanceCompare; -+ } -+ -+ final int coordinateXCompare = Integer.compare(p1.chunkX, p2.chunkX); -+ if (coordinateXCompare != 0) { -+ return coordinateXCompare; -+ } -+ -+ return Integer.compare(p1.chunkZ, p2.chunkZ); -+ }); -+ -+ protected int sendViewDistance = -1; -+ protected int loadViewDistance = -1; -+ protected int tickViewDistance = -1; -+ -+ protected long nextChunkSendTarget; -+ -+ public PlayerLoaderData(final ServerPlayer player, final PlayerChunkLoader loader) { -+ this.player = player; -+ this.loader = loader; -+ } -+ -+ // these view distance methods are for api -+ public int getTargetSendViewDistance() { -+ final int tickViewDistance = this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance; -+ final int loadViewDistance = Math.max(tickViewDistance + 1, this.loadViewDistance == -1 ? this.loader.getLoadDistance() : this.loadViewDistance); -+ final int clientViewDistance = this.getClientViewDistance(); -+ final int sendViewDistance = Math.min(loadViewDistance, this.sendViewDistance == -1 ? (!TuinityConfig.playerAutoConfigureSendViewDistance || clientViewDistance == -1 ? this.loader.getSendDistance() : clientViewDistance + 1) : this.sendViewDistance); -+ return sendViewDistance; -+ } -+ -+ public void setTargetSendViewDistance(final int distance) { -+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE + 1)) { -+ throw new IllegalArgumentException(Integer.toString(distance)); -+ } -+ this.sendViewDistance = distance; -+ } -+ -+ public int getTargetNoTickViewDistance() { -+ return (this.loadViewDistance == -1 ? this.getLoadDistance() : this.loadViewDistance) - 1; -+ } -+ -+ public void setTargetNoTickViewDistance(final int distance) { -+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE)) { -+ throw new IllegalArgumentException(Integer.toString(distance)); -+ } -+ this.loadViewDistance = distance == -1 ? -1 : distance + 1; -+ } -+ -+ public int getTargetTickViewDistance() { -+ return this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance; -+ } -+ -+ public void setTargetTickViewDistance(final int distance) { -+ if (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE) { -+ throw new IllegalArgumentException(Integer.toString(distance)); -+ } -+ this.tickViewDistance = distance; -+ } -+ -+ protected int getLoadDistance() { -+ final int tickViewDistance = this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance; -+ -+ return Math.max(tickViewDistance + 1, this.loadViewDistance == -1 ? this.loader.getLoadDistance() : this.loadViewDistance); -+ } -+ -+ public boolean hasSentChunk(final int chunkX, final int chunkZ) { -+ return this.sentChunks.contains(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } -+ -+ public void sendChunk(final int chunkX, final int chunkZ, final Runnable onChunkSend) { -+ if (this.sentChunks.add(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { -+ this.player.getLevel().getChunkSource().chunkMap.updateChunkTracking(this.player, -+ new ChunkPos(chunkX, chunkZ), new Packet[2], false, true); // unloaded, loaded -+ this.player.connection.connection.execute(onChunkSend); -+ } else { -+ throw new IllegalStateException(); -+ } -+ } -+ -+ public void unloadChunk(final int chunkX, final int chunkZ) { -+ if (this.sentChunks.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { -+ this.player.getLevel().getChunkSource().chunkMap.updateChunkTracking(this.player, -+ new ChunkPos(chunkX, chunkZ), null, true, false); // unloaded, loaded -+ } -+ } -+ -+ protected static boolean triangleIntersects(final double p1x, final double p1z, // triangle point -+ final double p2x, final double p2z, // triangle point -+ final double p3x, final double p3z, // triangle point -+ -+ final double targetX, final double targetZ) { // point -+ // from barycentric coordinates: -+ // targetX = a*p1x + b*p2x + c*p3x -+ // targetZ = a*p1z + b*p2z + c*p3z -+ // 1.0 = a*1.0 + b*1.0 + c*1.0 -+ // where a, b, c >= 0.0 -+ // so, if any of a, b, c are less-than zero then there is no intersection. -+ -+ // d = ((p2z - p3z)(p1x - p3x) + (p3x - p2x)(p1z - p3z)) -+ // a = ((p2z - p3z)(targetX - p3x) + (p3x - p2x)(targetZ - p3z)) / d -+ // b = ((p3z - p1z)(targetX - p3x) + (p1x - p3x)(targetZ - p3z)) / d -+ // c = 1.0 - a - b -+ -+ final double d = (p2z - p3z)*(p1x - p3x) + (p3x - p2x)*(p1z - p3z); -+ final double a = ((p2z - p3z)*(targetX - p3x) + (p3x - p2x)*(targetZ - p3z)) / d; -+ -+ if (a < 0.0 || a > 1.0) { -+ return false; -+ } -+ -+ final double b = ((p3z - p1z)*(targetX - p3x) + (p1x - p3x)*(targetZ - p3z)) / d; -+ if (b < 0.0 || b > 1.0) { -+ return false; -+ } -+ -+ final double c = 1.0 - a - b; -+ -+ return c >= 0.0 && c <= 1.0; -+ } -+ -+ public void remove() { -+ this.loader.broadcastMap.remove(this.player); -+ this.loader.loadMap.remove(this.player); -+ this.loader.loadTicketCleanup.remove(this.player); -+ this.loader.tickMap.remove(this.player); -+ } -+ -+ protected int getClientViewDistance() { -+ return this.player.clientViewDistance == null ? -1 : this.player.clientViewDistance.intValue(); -+ } -+ -+ public void update() { -+ final int tickViewDistance = this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance; -+ // load view cannot be less-than tick view + 1 -+ final int loadViewDistance = Math.max(tickViewDistance + 1, this.loadViewDistance == -1 ? this.loader.getLoadDistance() : this.loadViewDistance); -+ // send view cannot be greater-than load view -+ final int clientViewDistance = this.getClientViewDistance(); -+ final int sendViewDistance = Math.min(loadViewDistance, this.sendViewDistance == -1 ? (!TuinityConfig.playerAutoConfigureSendViewDistance || clientViewDistance == -1 ? this.loader.getSendDistance() : clientViewDistance + 1) : this.sendViewDistance); -+ -+ final double posX = this.player.getX(); -+ final double posZ = this.player.getZ(); -+ final float yaw = MCUtil.normalizeYaw(this.player.yRot + 90.0f); // mc yaw 0 is along the positive z axis, but obviously this is really dumb - offset so we are at positive x-axis -+ -+ // in general, we really only want to prioritise chunks in front if we know we're moving pretty fast into them. -+ final boolean useLookPriority = TuinityConfig.playerFrustumPrioritisation && (this.player.getDeltaMovement().horizontalDistanceSqr() > LOOK_PRIORITY_SPEED_THRESHOLD || -+ this.player.getAbilities().flying); -+ -+ // make sure we're in the send queue -+ this.loader.chunkSendWaitQueue.add(this); -+ -+ if ( -+ // has view distance stayed the same? -+ sendViewDistance == this.lastSendDistance -+ && loadViewDistance == this.lastLoadDistance -+ && tickViewDistance == this.lastTickDistance -+ -+ && (this.usingLookingPriority ? ( -+ // has our block stayed the same (this also accounts for chunk change)? -+ Mth.floor(this.lastLocX) == Mth.floor(posX) -+ && Mth.floor(this.lastLocZ) == Mth.floor(posZ) -+ ) : ( -+ // has our chunk stayed the same -+ (Mth.floor(this.lastLocX) >> 4) == (Mth.floor(posX) >> 4) -+ && (Mth.floor(this.lastLocZ) >> 4) == (Mth.floor(posZ) >> 4) -+ )) -+ -+ // has our decision about look priority changed? -+ && this.usingLookingPriority == useLookPriority -+ -+ // if we are currently using look priority, has our yaw stayed within recalc threshold? -+ && (!this.usingLookingPriority || Math.abs(yaw - this.lastYaw) <= LOOK_PRIORITY_YAW_DELTA_RECALC_THRESHOLD) -+ ) { -+ // nothing we care about changed, so we're not re-calculating -+ return; -+ } -+ -+ final int centerChunkX = Mth.floor(posX) >> 4; -+ final int centerChunkZ = Mth.floor(posZ) >> 4; -+ -+ this.player.needsChunkCenterUpdate = true; -+ this.loader.broadcastMap.addOrUpdate(this.player, centerChunkX, centerChunkZ, sendViewDistance); -+ this.player.needsChunkCenterUpdate = false; -+ this.loader.loadMap.addOrUpdate(this.player, centerChunkX, centerChunkZ, loadViewDistance); -+ this.loader.loadTicketCleanup.addOrUpdate(this.player, centerChunkX, centerChunkZ, loadViewDistance + 1); -+ this.loader.tickMap.addOrUpdate(this.player, centerChunkX, centerChunkZ, tickViewDistance); -+ -+ if (sendViewDistance != this.lastSendDistance) { -+ // update the view radius for client -+ // note that this should be after the map calls because the client wont expect unload calls not in its VD -+ // and it's possible we decreased VD here -+ this.player.connection.send(new ClientboundSetChunkCacheRadiusPacket(sendViewDistance - 1)); // client already expects the 1 radius neighbours, so subtract 1. -+ } -+ -+ this.lastLocX = posX; -+ this.lastLocZ = posZ; -+ this.lastYaw = yaw; -+ this.lastSendDistance = sendViewDistance; -+ this.lastLoadDistance = loadViewDistance; -+ this.lastTickDistance = tickViewDistance; -+ this.usingLookingPriority = useLookPriority; -+ -+ this.lastChunkX = centerChunkX; -+ this.lastChunkZ = centerChunkZ; -+ -+ // points for player "view" triangle: -+ -+ // obviously, the player pos is a vertex -+ final double p1x = posX; -+ final double p1z = posZ; -+ -+ // to the left of the looking direction -+ final double p2x = PRIORITISED_DISTANCE * Math.cos(Math.toRadians(yaw + (double)(FOV / 2.0))) // calculate rotated vector -+ + p1x; // offset vector -+ final double p2z = PRIORITISED_DISTANCE * Math.sin(Math.toRadians(yaw + (double)(FOV / 2.0))) // calculate rotated vector -+ + p1z; // offset vector -+ -+ // to the right of the looking direction -+ final double p3x = PRIORITISED_DISTANCE * Math.cos(Math.toRadians(yaw - (double)(FOV / 2.0))) // calculate rotated vector -+ + p1x; // offset vector -+ final double p3z = PRIORITISED_DISTANCE * Math.sin(Math.toRadians(yaw - (double)(FOV / 2.0))) // calculate rotated vector -+ + p1z; // offset vector -+ -+ // now that we have all of our points, we can recalculate the load queue -+ -+ final List loadQueue = new ArrayList<>(); -+ -+ // clear send queue, we are re-sorting -+ this.sendQueue.clear(); -+ -+ final int searchViewDistance = Math.max(loadViewDistance, sendViewDistance); -+ -+ for (int dx = -searchViewDistance; dx <= searchViewDistance; ++dx) { -+ for (int dz = -searchViewDistance; dz <= searchViewDistance; ++dz) { -+ final int chunkX = dx + centerChunkX; -+ final int chunkZ = dz + centerChunkZ; -+ final int squareDistance = Math.max(Math.abs(dx), Math.abs(dz)); -+ -+ if (this.hasSentChunk(chunkX, chunkZ)) { -+ // already sent (which means it is also loaded) -+ continue; -+ } -+ -+ final boolean loadChunk = squareDistance <= loadViewDistance; -+ final boolean sendChunk = squareDistance <= sendViewDistance; -+ -+ final boolean prioritised = useLookPriority && triangleIntersects( -+ // prioritisation triangle -+ p1x, p1z, p2x, p2z, p3x, p3z, -+ -+ // center of chunk -+ (double)((chunkX << 4) | 8), (double)((chunkZ << 4) | 8) -+ ); -+ -+ -+ final int manhattanDistance = (Math.abs(dx) + Math.abs(dz)); -+ -+ final double priority; -+ -+ if (squareDistance <= TuinityConfig.playerMinChunkLoadRadius) { -+ // priority should be negative, and we also want to order it from center outwards -+ // so we want (0,0) to be the smallest, and (minLoadedRadius,minLoadedRadius) to be the greatest -+ priority = -((2 * TuinityConfig.playerMinChunkLoadRadius + 1) - (dx + dz)); -+ } else { -+ if (prioritised) { -+ // we don't prioritise these chunks above others because we also want to make sure some chunks -+ // will be loaded if the player changes direction -+ priority = (double)manhattanDistance / 6.0; -+ } else { -+ priority = (double)manhattanDistance; -+ } -+ } -+ -+ final ChunkPriorityHolder holder = new ChunkPriorityHolder(chunkX, chunkZ, manhattanDistance, priority); -+ -+ if (!this.loader.isChunkPlayerLoaded(chunkX, chunkZ)) { -+ if (loadChunk) { -+ loadQueue.add(holder); -+ } -+ } else { -+ // loaded but not sent: so queue it! -+ if (sendChunk) { -+ this.sendQueue.add(holder); -+ } -+ } -+ } -+ } -+ -+ loadQueue.sort((final ChunkPriorityHolder p1, final ChunkPriorityHolder p2) -> { -+ return Double.compare(p1.priority, p2.priority); -+ }); -+ -+ // we're modifying loadQueue, must remove -+ this.loader.chunkLoadQueue.remove(this); -+ -+ this.loadQueue.clear(); -+ this.loadQueue.addAll(loadQueue); -+ -+ // must re-add -+ this.loader.chunkLoadQueue.add(this); -+ } -+ } -+} -diff --git a/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java b/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..49dc783a312ed62415d28cdd801dad6a96f3cc16 ---- /dev/null -+++ b/src/main/java/com/tuinity/tuinity/chunk/SingleThreadChunkRegionManager.java -@@ -0,0 +1,477 @@ -+package com.tuinity.tuinity.chunk; -+ -+import com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet; -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -+import net.minecraft.server.MCUtil; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.world.level.ChunkPos; -+import java.util.ArrayList; -+import java.util.Arrays; -+import java.util.Iterator; -+import java.util.List; -+import java.util.function.Supplier; -+ -+public final class SingleThreadChunkRegionManager { -+ -+ protected final int regionSectionMergeRadius; -+ protected final int regionSectionChunkSize; -+ public final int regionChunkShift; // log2(REGION_CHUNK_SIZE) -+ -+ public final ServerLevel world; -+ public final String name; -+ -+ protected final Long2ObjectOpenHashMap regionsBySection = new Long2ObjectOpenHashMap<>(); -+ protected final ReferenceLinkedOpenHashSet needsRecalculation = new ReferenceLinkedOpenHashSet<>(); -+ protected final int minSectionRecalcCount; -+ protected final double maxDeadRegionPercent; -+ protected final Supplier regionDataSupplier; -+ protected final Supplier regionSectionDataSupplier; -+ -+ public SingleThreadChunkRegionManager(final ServerLevel world, final int minSectionRecalcCount, -+ final double maxDeadRegionPercent, final int sectionMergeRadius, -+ final int regionSectionChunkShift, -+ final String name, final Supplier regionDataSupplier, -+ final Supplier regionSectionDataSupplier) { -+ this.regionSectionMergeRadius = sectionMergeRadius; -+ this.regionSectionChunkSize = 1 << regionSectionChunkShift; -+ this.regionChunkShift = regionSectionChunkShift; -+ this.world = world; -+ this.name = name; -+ this.minSectionRecalcCount = Math.max(2, minSectionRecalcCount); -+ this.maxDeadRegionPercent = maxDeadRegionPercent; -+ this.regionDataSupplier = regionDataSupplier; -+ this.regionSectionDataSupplier = regionSectionDataSupplier; -+ } -+ -+ // tested via https://gist.github.com/Spottedleaf/aa7ade3451c37b4cac061fc77074db2f -+ -+ /* -+ protected void check() { -+ ReferenceOpenHashSet> checked = new ReferenceOpenHashSet<>(); -+ -+ for (RegionSection section : this.regionsBySection.values()) { -+ if (!checked.add(section.region)) { -+ section.region.check(); -+ } -+ } -+ for (Region region : this.needsRecalculation) { -+ region.check(); -+ } -+ } -+ */ -+ -+ protected void addToRecalcQueue(final Region region) { -+ this.needsRecalculation.add(region); -+ } -+ -+ protected void removeFromRecalcQueue(final Region region) { -+ this.needsRecalculation.remove(region); -+ } -+ -+ public RegionSection getRegionSection(final int chunkX, final int chunkZ) { -+ return this.regionsBySection.get(MCUtil.getCoordinateKey(chunkX >> this.regionChunkShift, chunkZ >> this.regionChunkShift)); -+ } -+ -+ public Region getRegion(final int chunkX, final int chunkZ) { -+ final RegionSection section = this.regionsBySection.get(MCUtil.getCoordinateKey(chunkX >> regionChunkShift, chunkZ >> regionChunkShift)); -+ return section != null ? section.region : null; -+ } -+ -+ private final List toMerge = new ArrayList<>(); -+ -+ protected RegionSection getOrCreateAndMergeSection(final int sectionX, final int sectionZ, final RegionSection force) { -+ final long sectionKey = MCUtil.getCoordinateKey(sectionX, sectionZ); -+ -+ if (force == null) { -+ RegionSection region = this.regionsBySection.get(sectionKey); -+ if (region != null) { -+ return region; -+ } -+ } -+ -+ int mergeCandidateSectionSize = -1; -+ Region mergeIntoCandidate = null; -+ -+ // find optimal candidate to merge into -+ -+ final int minX = sectionX - this.regionSectionMergeRadius; -+ final int maxX = sectionX + this.regionSectionMergeRadius; -+ final int minZ = sectionZ - this.regionSectionMergeRadius; -+ final int maxZ = sectionZ + this.regionSectionMergeRadius; -+ for (int currX = minX; currX <= maxX; ++currX) { -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ final RegionSection section = this.regionsBySection.get(MCUtil.getCoordinateKey(currX, currZ)); -+ if (section == null) { -+ continue; -+ } -+ final Region region = section.region; -+ if (region.dead) { -+ throw new IllegalStateException("Dead region should not be in live region manager state: " + region); -+ } -+ final int sections = region.sections.size(); -+ -+ if (sections > mergeCandidateSectionSize) { -+ mergeCandidateSectionSize = sections; -+ mergeIntoCandidate = region; -+ } -+ this.toMerge.add(region); -+ } -+ } -+ -+ // merge -+ if (mergeIntoCandidate != null) { -+ for (int i = 0; i < this.toMerge.size(); ++i) { -+ final Region region = this.toMerge.get(i); -+ if (region.dead || mergeIntoCandidate == region) { -+ continue; -+ } -+ region.mergeInto(mergeIntoCandidate); -+ } -+ this.toMerge.clear(); -+ } else { -+ mergeIntoCandidate = new Region(this); -+ } -+ -+ final RegionSection section; -+ if (force == null) { -+ this.regionsBySection.put(sectionKey, section = new RegionSection(sectionKey, this)); -+ } else { -+ final RegionSection existing = this.regionsBySection.putIfAbsent(sectionKey, force); -+ if (existing != null) { -+ throw new IllegalStateException("Attempting to override section '" + existing.toStringWithRegion() + -+ ", with " + force.toStringWithRegion()); -+ } -+ -+ section = force; -+ } -+ -+ mergeIntoCandidate.addRegionSection(section); -+ //mergeIntoCandidate.check(); -+ //this.check(); -+ -+ return section; -+ } -+ -+ public void addChunk(final int chunkX, final int chunkZ) { -+ this.getOrCreateAndMergeSection(chunkX >> this.regionChunkShift, chunkZ >> this.regionChunkShift, null).addChunk(chunkX, chunkZ); -+ } -+ -+ public void removeChunk(final int chunkX, final int chunkZ) { -+ final RegionSection section = this.regionsBySection.get( -+ MCUtil.getCoordinateKey(chunkX >> this.regionChunkShift, chunkZ >> this.regionChunkShift) -+ ); -+ if (section != null) { -+ section.removeChunk(chunkX, chunkZ); -+ } else { -+ throw new IllegalStateException("Cannot remove chunk at (" + chunkX + "," + chunkZ + ") from region state, section does not exist"); -+ } -+ } -+ -+ public void recalculateRegions() { -+ for (int i = 0, len = this.needsRecalculation.size(); i < len; ++i) { -+ final Region region = this.needsRecalculation.removeFirst(); -+ -+ this.recalculateRegion(region); -+ //this.check(); -+ } -+ } -+ -+ protected void recalculateRegion(final Region region) { -+ region.markedForRecalc = false; -+ //region.check(); -+ // clear unused regions -+ for (final Iterator iterator = region.deadSections.iterator(); iterator.hasNext();) { -+ final RegionSection deadSection = iterator.next(); -+ -+ if (deadSection.hasChunks()) { -+ throw new IllegalStateException("Dead section '" + deadSection.toStringWithRegion() + "' is marked dead but has chunks!"); -+ } -+ if (!region.removeRegionSection(deadSection)) { -+ throw new IllegalStateException("Region " + region + " has inconsistent state, it should contain section " + deadSection); -+ } -+ if (!this.regionsBySection.remove(deadSection.regionCoordinate, deadSection)) { -+ throw new IllegalStateException("Cannot remove dead section '" + -+ deadSection.toStringWithRegion() + "' from section state! State at section coordinate: " + -+ this.regionsBySection.get(deadSection.regionCoordinate)); -+ } -+ } -+ region.deadSections.clear(); -+ -+ // implicitly cover cases where size == 0 -+ if (region.sections.size() < this.minSectionRecalcCount) { -+ //region.check(); -+ return; -+ } -+ -+ // run a test to see if we actually need to recalculate -+ // TODO -+ -+ // destroy and rebuild the region -+ region.dead = true; -+ -+ // destroy region state -+ for (final Iterator iterator = region.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { -+ final RegionSection aliveSection = iterator.next(); -+ if (!aliveSection.hasChunks()) { -+ throw new IllegalStateException("Alive section '" + aliveSection.toStringWithRegion() + "' has no chunks!"); -+ } -+ if (!this.regionsBySection.remove(aliveSection.regionCoordinate, aliveSection)) { -+ throw new IllegalStateException("Cannot remove alive section '" + -+ aliveSection.toStringWithRegion() + "' from section state! State at section coordinate: " + -+ this.regionsBySection.get(aliveSection.regionCoordinate)); -+ } -+ } -+ -+ // rebuild regions -+ for (final Iterator iterator = region.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { -+ final RegionSection aliveSection = iterator.next(); -+ this.getOrCreateAndMergeSection(aliveSection.getSectionX(), aliveSection.getSectionZ(), aliveSection); -+ } -+ } -+ -+ public static final class Region { -+ protected final IteratorSafeOrderedReferenceSet sections = new IteratorSafeOrderedReferenceSet<>(); -+ protected final ReferenceOpenHashSet deadSections = new ReferenceOpenHashSet<>(16, 0.7f); -+ protected boolean dead; -+ protected boolean markedForRecalc; -+ -+ public final SingleThreadChunkRegionManager regionManager; -+ public final RegionData regionData; -+ -+ protected Region(final SingleThreadChunkRegionManager regionManager) { -+ this.regionManager = regionManager; -+ this.regionData = regionManager.regionDataSupplier.get(); -+ } -+ -+ public IteratorSafeOrderedReferenceSet.Iterator getSections() { -+ return this.sections.iterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); -+ } -+ -+ protected final double getDeadSectionPercent() { -+ return (double)this.deadSections.size() / (double)this.sections.size(); -+ } -+ -+ /* -+ protected void check() { -+ if (this.dead) { -+ throw new IllegalStateException("Dead region!"); -+ } -+ for (final Iterator> iterator = this.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { -+ final RegionSection section = iterator.next(); -+ if (section.region != this) { -+ throw new IllegalStateException("Region section must point to us!"); -+ } -+ if (this.regionManager.regionsBySection.get(section.regionCoordinate) != section) { -+ throw new IllegalStateException("Region section must match the regionmanager state!"); -+ } -+ } -+ } -+ */ -+ -+ // note: it is not true that the region at this point is not in any region. use the region field on the section -+ // to see if it is currently in another region. -+ protected final boolean addRegionSection(final RegionSection section) { -+ if (!this.sections.add(section)) { -+ return false; -+ } -+ -+ section.sectionData.addToRegion(section, section.region, this); -+ -+ section.region = this; -+ return true; -+ } -+ -+ protected final boolean removeRegionSection(final RegionSection section) { -+ if (!this.sections.remove(section)) { -+ return false; -+ } -+ -+ section.sectionData.removeFromRegion(section, this); -+ -+ return true; -+ } -+ -+ protected void mergeInto(final Region mergeTarget) { -+ if (this == mergeTarget) { -+ throw new IllegalStateException("Cannot merge a region onto itself"); -+ } -+ if (this.dead) { -+ throw new IllegalStateException("Source region is dead! Source " + this + ", target " + mergeTarget); -+ } else if (mergeTarget.dead) { -+ throw new IllegalStateException("Target region is dead! Source " + this + ", target " + mergeTarget); -+ } -+ this.dead = true; -+ if (this.markedForRecalc) { -+ this.regionManager.removeFromRecalcQueue(this); -+ } -+ -+ for (final Iterator iterator = this.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { -+ final RegionSection section = iterator.next(); -+ -+ if (!mergeTarget.addRegionSection(section)) { -+ throw new IllegalStateException("Target cannot contain source's sections! Source " + this + ", target " + mergeTarget); -+ } -+ } -+ -+ for (final RegionSection deadSection : this.deadSections) { -+ if (!this.sections.contains(deadSection)) { -+ throw new IllegalStateException("Source region does not even contain its own dead sections! Missing " + deadSection + " from region " + this); -+ } -+ mergeTarget.deadSections.add(deadSection); -+ } -+ //mergeTarget.check(); -+ } -+ -+ protected void markSectionAlive(final RegionSection section) { -+ this.deadSections.remove(section); -+ if (this.markedForRecalc && (this.sections.size() < this.regionManager.minSectionRecalcCount || this.getDeadSectionPercent() < this.regionManager.maxDeadRegionPercent)) { -+ this.regionManager.removeFromRecalcQueue(this); -+ this.markedForRecalc = false; -+ } -+ } -+ -+ protected void markSectionDead(final RegionSection section) { -+ this.deadSections.add(section); -+ if (!this.markedForRecalc && (this.sections.size() >= this.regionManager.minSectionRecalcCount || this.sections.size() == this.deadSections.size()) && this.getDeadSectionPercent() >= this.regionManager.maxDeadRegionPercent) { -+ this.regionManager.addToRecalcQueue(this); -+ this.markedForRecalc = true; -+ } -+ } -+ -+ @Override -+ public String toString() { -+ final StringBuilder ret = new StringBuilder(128); -+ -+ ret.append("Region{"); -+ ret.append("dead=").append(this.dead).append(','); -+ ret.append("markedForRecalc=").append(this.markedForRecalc).append(','); -+ -+ ret.append("sectionCount=").append(this.sections.size()).append(','); -+ ret.append("sections=["); -+ for (final Iterator iterator = this.sections.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { -+ final RegionSection section = iterator.next(); -+ ret.append(section); -+ if (iterator.hasNext()) { -+ ret.append(','); -+ } -+ } -+ ret.append(']'); -+ -+ ret.append('}'); -+ return ret.toString(); -+ } -+ } -+ -+ public static final class RegionSection { -+ protected final long regionCoordinate; -+ protected final long[] chunksBitset; -+ protected int chunkCount; -+ protected Region region; -+ -+ public final SingleThreadChunkRegionManager regionManager; -+ public final RegionSectionData sectionData; -+ -+ protected RegionSection(final long regionCoordinate, final SingleThreadChunkRegionManager regionManager) { -+ this.regionCoordinate = regionCoordinate; -+ this.regionManager = regionManager; -+ this.chunksBitset = new long[Math.max(1, regionManager.regionSectionChunkSize * regionManager.regionSectionChunkSize / Long.SIZE)]; -+ this.sectionData = regionManager.regionSectionDataSupplier.get(); -+ } -+ -+ public int getSectionX() { -+ return MCUtil.getCoordinateX(this.regionCoordinate); -+ } -+ -+ public int getSectionZ() { -+ return MCUtil.getCoordinateZ(this.regionCoordinate); -+ } -+ -+ public Region getRegion() { -+ return this.region; -+ } -+ -+ private int getChunkIndex(final int chunkX, final int chunkZ) { -+ return (chunkX & (this.regionManager.regionSectionChunkSize - 1)) | ((chunkZ & (this.regionManager.regionSectionChunkSize - 1)) << this.regionManager.regionChunkShift); -+ } -+ -+ protected boolean hasChunks() { -+ return this.chunkCount != 0; -+ } -+ -+ protected void addChunk(final int chunkX, final int chunkZ) { -+ final int index = this.getChunkIndex(chunkX, chunkZ); -+ final long bitset = this.chunksBitset[index >>> 6]; // index / Long.SIZE -+ final long after = this.chunksBitset[index >>> 6] = bitset | (1L << (index & (Long.SIZE - 1))); -+ if (after == bitset) { -+ throw new IllegalStateException("Cannot add a chunk to a section which already has the chunk! RegionSection: " + this + ", global chunk: " + new ChunkPos(chunkX, chunkZ).toString()); -+ } -+ if (++this.chunkCount != 1) { -+ return; -+ } -+ this.region.markSectionAlive(this); -+ } -+ -+ protected void removeChunk(final int chunkX, final int chunkZ) { -+ final int index = this.getChunkIndex(chunkX, chunkZ); -+ final long before = this.chunksBitset[index >>> 6]; // index / Long.SIZE -+ final long bitset = this.chunksBitset[index >>> 6] = before & ~(1L << (index & (Long.SIZE - 1))); -+ if (before == bitset) { -+ throw new IllegalStateException("Cannot remove a chunk from a section which does not have that chunk! RegionSection: " + this + ", global chunk: " + new ChunkPos(chunkX, chunkZ).toString()); -+ } -+ if (--this.chunkCount != 0) { -+ return; -+ } -+ this.region.markSectionDead(this); -+ } -+ -+ @Override -+ public String toString() { -+ return "RegionSection{" + -+ "regionCoordinate=" + new ChunkPos(this.regionCoordinate).toString() + "," + -+ "chunkCount=" + this.chunkCount + "," + -+ "chunksBitset=" + toString(this.chunksBitset) + "," + -+ "hash=" + this.hashCode() + -+ "}"; -+ } -+ -+ public String toStringWithRegion() { -+ return "RegionSection{" + -+ "regionCoordinate=" + new ChunkPos(this.regionCoordinate).toString() + "," + -+ "chunkCount=" + this.chunkCount + "," + -+ "chunksBitset=" + toString(this.chunksBitset) + "," + -+ "hash=" + this.hashCode() + "," + -+ "region=" + this.region + -+ "}"; -+ } -+ -+ private static String toString(final long[] array) { -+ final StringBuilder ret = new StringBuilder(); -+ for (final long value : array) { -+ // zero pad the hex string -+ final char[] zeros = new char[Long.SIZE / 4]; -+ Arrays.fill(zeros, '0'); -+ final String string = Long.toHexString(value); -+ System.arraycopy(string.toCharArray(), 0, zeros, zeros.length - string.length(), string.length()); -+ -+ ret.append(zeros); -+ } -+ -+ return ret.toString(); -+ } -+ } -+ -+ public static interface RegionData { -+ -+ } -+ -+ public static interface RegionSectionData { -+ -+ public void removeFromRegion(final RegionSection section, final Region from); -+ -+ // removal from the old region is handled via removeFromRegion -+ public void addToRegion(final RegionSection section, final Region oldRegion, final Region newRegion); -+ -+ } -+} -diff --git a/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java -new file mode 100644 -index 0000000000000000000000000000000000000000..356a6118f1b0b091f7527aec747659025562eafc ---- /dev/null -+++ b/src/main/java/com/tuinity/tuinity/config/TuinityConfig.java -@@ -0,0 +1,432 @@ -+package com.tuinity.tuinity.config; -+ -+import com.destroystokyo.paper.util.SneakyThrow; -+import net.minecraft.server.MinecraftServer; -+import org.bukkit.Bukkit; -+import org.bukkit.configuration.ConfigurationSection; -+import org.bukkit.configuration.file.YamlConfiguration; -+import java.io.File; -+import java.lang.reflect.Method; -+import java.lang.reflect.Modifier; -+import java.util.List; -+import java.util.logging.Level; -+ -+public final class TuinityConfig { -+ -+ public static final String CONFIG_HEADER = "Configuration file for Tuinity."; -+ public static final int CURRENT_CONFIG_VERSION = 2; -+ -+ private static final Object[] EMPTY = new Object[0]; -+ -+ private static File configFile; -+ public static YamlConfiguration config; -+ private static int configVersion; -+ public static boolean createWorldSections = true; -+ -+ public static void init(final File file) { -+ // TODO remove this in the future... -+ final File tuinityConfig = new File(file.getParent(), "tuinity.yml"); -+ if (!tuinityConfig.exists()) { -+ final File oldConfig = new File(file.getParent(), "concrete.yml"); -+ oldConfig.renameTo(tuinityConfig); -+ } -+ TuinityConfig.configFile = file; -+ final YamlConfiguration config = new YamlConfiguration(); -+ config.options().header(CONFIG_HEADER); -+ config.options().copyDefaults(true); -+ -+ if (!file.exists()) { -+ try { -+ file.createNewFile(); -+ } catch (final Exception ex) { -+ Bukkit.getLogger().log(Level.SEVERE, "Failure to create tuinity config", ex); -+ } -+ } else { -+ try { -+ config.load(file); -+ } catch (final Exception ex) { -+ Bukkit.getLogger().log(Level.SEVERE, "Failure to load tuinity config", ex); -+ SneakyThrow.sneaky(ex); /* Rethrow, this is critical */ -+ throw new RuntimeException(ex); // unreachable -+ } -+ } -+ -+ TuinityConfig.load(config); -+ } -+ -+ public static void load(final YamlConfiguration config) { -+ TuinityConfig.config = config; -+ TuinityConfig.configVersion = TuinityConfig.getInt("config-version-please-do-not-modify-me", CURRENT_CONFIG_VERSION); -+ TuinityConfig.set("config-version-please-do-not-modify-me", CURRENT_CONFIG_VERSION); -+ -+ for (final Method method : TuinityConfig.class.getDeclaredMethods()) { -+ if (method.getReturnType() != void.class || method.getParameterCount() != 0 || -+ !Modifier.isPrivate(method.getModifiers()) || !Modifier.isStatic(method.getModifiers())) { -+ continue; -+ } -+ -+ try { -+ method.setAccessible(true); -+ method.invoke(null, EMPTY); -+ } catch (final Exception ex) { -+ SneakyThrow.sneaky(ex); /* Rethrow, this is critical */ -+ throw new RuntimeException(ex); // unreachable -+ } -+ } -+ -+ /* We re-save to add new options */ -+ try { -+ config.save(TuinityConfig.configFile); -+ } catch (final Exception ex) { -+ Bukkit.getLogger().log(Level.SEVERE, "Unable to save tuinity config", ex); -+ } -+ } -+ -+ static void set(final String path, final Object value) { -+ TuinityConfig.config.set(path, value); -+ } -+ -+ static boolean getBoolean(final String path, final boolean dfl) { -+ TuinityConfig.config.addDefault(path, Boolean.valueOf(dfl)); -+ return TuinityConfig.config.getBoolean(path, dfl); -+ } -+ -+ static int getInt(final String path, final int dfl) { -+ TuinityConfig.config.addDefault(path, Integer.valueOf(dfl)); -+ return TuinityConfig.config.getInt(path, dfl); -+ } -+ -+ static long getLong(final String path, final long dfl) { -+ TuinityConfig.config.addDefault(path, Long.valueOf(dfl)); -+ return TuinityConfig.config.getLong(path, dfl); -+ } -+ -+ static double getDouble(final String path, final double dfl) { -+ TuinityConfig.config.addDefault(path, Double.valueOf(dfl)); -+ return TuinityConfig.config.getDouble(path, dfl); -+ } -+ -+ static String getString(final String path, final String dfl) { -+ TuinityConfig.config.addDefault(path, dfl); -+ return TuinityConfig.config.getString(path, dfl); -+ } -+ -+ public static int playerMinChunkLoadRadius; -+ public static double playerMaxConcurrentChunkSends; -+ public static double playerMaxConcurrentChunkLoads; -+ public static boolean playerAutoConfigureSendViewDistance; -+ public static boolean enableMC162253Workaround; -+ public static double playerTargetChunkSendRate; -+ public static boolean playerFrustumPrioritisation; -+ -+ private static void newPlayerChunkManagement() { -+ playerMinChunkLoadRadius = TuinityConfig.getInt("player-chunks.min-load-radius", 2); -+ playerMaxConcurrentChunkSends = TuinityConfig.getDouble("player-chunks.max-concurrent-sends", 5.0); -+ playerMaxConcurrentChunkLoads = TuinityConfig.getDouble("player-chunks.max-concurrent-loads", -6.0); -+ playerAutoConfigureSendViewDistance = TuinityConfig.getBoolean("player-chunks.autoconfig-send-distance", true); -+ // this costs server bandwidth. latest phosphor or starlight on the client fixes mc162253 anyways. -+ enableMC162253Workaround = TuinityConfig.getBoolean("player-chunks.enable-mc162253-workaround", true); -+ playerTargetChunkSendRate = TuinityConfig.getDouble("player-chunks.target-chunk-send-rate", -35.0); -+ playerFrustumPrioritisation = TuinityConfig.getBoolean("player-chunks.enable-frustum-priority", false); -+ } -+ -+ public static final class PacketLimit { -+ public final double packetLimitInterval; -+ public final double maxPacketRate; -+ public final ViolateAction violateAction; -+ -+ public PacketLimit(final double packetLimitInterval, final double maxPacketRate, final ViolateAction violateAction) { -+ this.packetLimitInterval = packetLimitInterval; -+ this.maxPacketRate = maxPacketRate; -+ this.violateAction = violateAction; -+ } -+ -+ public static enum ViolateAction { -+ KICK, DROP; -+ } -+ } -+ -+ public static String kickMessage; -+ public static PacketLimit allPacketsLimit; -+ public static java.util.Map>, PacketLimit> packetSpecificLimits = new java.util.HashMap<>(); -+ -+ private static void packetLimiter() { -+ packetSpecificLimits.clear(); -+ kickMessage = org.bukkit.ChatColor.translateAlternateColorCodes('&', TuinityConfig.getString("packet-limiter.kick-message", "&cSent too many packets")); -+ allPacketsLimit = new PacketLimit( -+ TuinityConfig.getDouble("packet-limiter.limits.all.interval", 7.0), -+ TuinityConfig.getDouble("packet-limiter.limits.all.max-packet-rate", 500.0), -+ PacketLimit.ViolateAction.KICK -+ ); -+ if (allPacketsLimit.maxPacketRate <= 0.0 || allPacketsLimit.packetLimitInterval <= 0.0) { -+ allPacketsLimit = null; -+ } -+ final ConfigurationSection section = TuinityConfig.config.getConfigurationSection("packet-limiter.limits"); -+ -+ // add default packets -+ -+ // auto recipe limiting -+ TuinityConfig.getDouble("packet-limiter.limits." + -+ "PacketPlayInAutoRecipe" + ".interval", 4.0); -+ TuinityConfig.getDouble("packet-limiter.limits." + -+ "PacketPlayInAutoRecipe" + ".max-packet-rate", 5.0); -+ TuinityConfig.getString("packet-limiter.limits." + -+ "PacketPlayInAutoRecipe" + ".action", PacketLimit.ViolateAction.DROP.name()); -+ -+ final String canonicalName = MinecraftServer.class.getCanonicalName(); -+ final String nmsPackage = canonicalName.substring(0, canonicalName.lastIndexOf(".")); -+ for (final String packetClassName : section.getKeys(false)) { -+ if (packetClassName.equals("all")) { -+ continue; -+ } -+ Class packetClazz = null; -+ -+ try { -+ packetClazz = Class.forName(nmsPackage + "." + packetClassName); -+ } catch (final ClassNotFoundException ex) { -+ for (final String subpackage : java.util.Arrays.asList("game", "handshake", "login", "status")) { -+ try { -+ packetClazz = Class.forName("net.minecraft.network.protocol." + subpackage + "." + packetClassName); -+ } catch (final ClassNotFoundException ignore) {} -+ } -+ if (packetClazz == null) { -+ MinecraftServer.LOGGER.warn("Packet '" + packetClassName + "' does not exist, cannot limit it! Please update tuinity.yml"); -+ continue; -+ } -+ } -+ -+ if (!net.minecraft.network.protocol.Packet.class.isAssignableFrom(packetClazz)) { -+ MinecraftServer.LOGGER.warn("Packet '" + packetClassName + "' does not exist, cannot limit it! Please update tuinity.yml"); -+ continue; -+ } -+ -+ if (!(section.get(packetClassName.concat(".interval")) instanceof Number) || !(section.get(packetClassName.concat(".max-packet-rate")) instanceof Number)) { -+ throw new RuntimeException("Packet limit setting " + packetClassName + " is missing interval or max-packet-rate!"); -+ } -+ -+ final String actionString = section.getString(packetClassName.concat(".action"), "KICK"); -+ PacketLimit.ViolateAction action = PacketLimit.ViolateAction.KICK; -+ for (PacketLimit.ViolateAction test : PacketLimit.ViolateAction.values()) { -+ if (actionString.equalsIgnoreCase(test.name())) { -+ action = test; -+ break; -+ } -+ } -+ -+ final double interval = section.getDouble(packetClassName.concat(".interval")); -+ final double rate = section.getDouble(packetClassName.concat(".max-packet-rate")); -+ -+ if (interval > 0.0 && rate > 0.0) { -+ packetSpecificLimits.put((Class)packetClazz, new PacketLimit(interval, rate, action)); -+ } -+ } -+ } -+ -+ public static boolean lagCompensateBlockBreaking; -+ -+ private static void lagCompensateBlockBreaking() { -+ lagCompensateBlockBreaking = TuinityConfig.getBoolean("lag-compensate-block-breaking", true); -+ } -+ -+ public static boolean sendFullPosForHardCollidingEntities; -+ -+ private static void sendFullPosForHardCollidingEntities() { -+ sendFullPosForHardCollidingEntities = TuinityConfig.getBoolean("send-full-pos-for-hard-colliding-entities", true); -+ } -+ -+ public static final class WorldConfig { -+ -+ public final String worldName; -+ public String configPath; -+ ConfigurationSection worldDefaults; -+ -+ public WorldConfig(final String worldName) { -+ this.worldName = worldName; -+ this.init(); -+ } -+ -+ public void init() { -+ this.worldDefaults = TuinityConfig.config.getConfigurationSection("world-settings.default"); -+ if (this.worldDefaults == null) { -+ this.worldDefaults = TuinityConfig.config.createSection("world-settings.default"); -+ } -+ -+ String worldSectionPath = TuinityConfig.configVersion < 1 ? this.worldName : "world-settings.".concat(this.worldName); -+ ConfigurationSection section = TuinityConfig.config.getConfigurationSection(worldSectionPath); -+ this.configPath = worldSectionPath; -+ if (TuinityConfig.createWorldSections) { -+ if (section == null) { -+ section = TuinityConfig.config.createSection(worldSectionPath); -+ } -+ TuinityConfig.config.set(worldSectionPath, section); -+ } -+ -+ this.load(); -+ } -+ -+ public void load() { -+ for (final Method method : TuinityConfig.WorldConfig.class.getDeclaredMethods()) { -+ if (method.getReturnType() != void.class || method.getParameterCount() != 0 || -+ !Modifier.isPrivate(method.getModifiers()) || Modifier.isStatic(method.getModifiers())) { -+ continue; -+ } -+ -+ try { -+ method.setAccessible(true); -+ method.invoke(this, EMPTY); -+ } catch (final Exception ex) { -+ SneakyThrow.sneaky(ex); /* Rethrow, this is critical */ -+ throw new RuntimeException(ex); // unreachable -+ } -+ } -+ -+ if (TuinityConfig.configVersion < 1) { -+ ConfigurationSection oldSection = TuinityConfig.config.getConfigurationSection(this.worldName); -+ TuinityConfig.config.set("world-settings.".concat(this.worldName), oldSection); -+ TuinityConfig.config.set(this.worldName, null); -+ } -+ -+ /* We re-save to add new options */ -+ try { -+ TuinityConfig.config.save(TuinityConfig.configFile); -+ } catch (final Exception ex) { -+ Bukkit.getLogger().log(Level.SEVERE, "Unable to save tuinity config", ex); -+ } -+ } -+ -+ /** -+ * update world defaults for the specified path, but also sets this world's config value for the path -+ * if it exists -+ */ -+ void set(final String path, final Object val) { -+ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath); -+ this.worldDefaults.set(path, val); -+ if (config != null && config.get(path) != null) { -+ config.set(path, val); -+ } -+ } -+ -+ boolean getBoolean(final String path, final boolean dfl) { -+ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath); -+ this.worldDefaults.addDefault(path, Boolean.valueOf(dfl)); -+ if (TuinityConfig.configVersion < 1) { -+ if (config != null && config.getBoolean(path) == dfl) { -+ config.set(path, null); -+ } -+ } -+ return config == null ? this.worldDefaults.getBoolean(path) : config.getBoolean(path, this.worldDefaults.getBoolean(path)); -+ } -+ -+ boolean getBooleanRaw(final String path, final boolean dfl) { -+ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath); -+ if (TuinityConfig.configVersion < 1) { -+ if (config != null && config.getBoolean(path) == dfl) { -+ config.set(path, null); -+ } -+ } -+ return config == null ? this.worldDefaults.getBoolean(path, dfl) : config.getBoolean(path, this.worldDefaults.getBoolean(path, dfl)); -+ } -+ -+ int getInt(final String path, final int dfl) { -+ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath); -+ this.worldDefaults.addDefault(path, Integer.valueOf(dfl)); -+ if (TuinityConfig.configVersion < 1) { -+ if (config != null && config.getInt(path) == dfl) { -+ config.set(path, null); -+ } -+ } -+ return config == null ? this.worldDefaults.getInt(path) : config.getInt(path, this.worldDefaults.getInt(path)); -+ } -+ -+ int getIntRaw(final String path, final int dfl) { -+ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath); -+ if (TuinityConfig.configVersion < 1) { -+ if (config != null && config.getInt(path) == dfl) { -+ config.set(path, null); -+ } -+ } -+ return config == null ? this.worldDefaults.getInt(path, dfl) : config.getInt(path, this.worldDefaults.getInt(path, dfl)); -+ } -+ -+ long getLong(final String path, final long dfl) { -+ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath); -+ this.worldDefaults.addDefault(path, Long.valueOf(dfl)); -+ if (TuinityConfig.configVersion < 1) { -+ if (config != null && config.getLong(path) == dfl) { -+ config.set(path, null); -+ } -+ } -+ return config == null ? this.worldDefaults.getLong(path) : config.getLong(path, this.worldDefaults.getLong(path)); -+ } -+ -+ long getLongRaw(final String path, final long dfl) { -+ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath); -+ if (TuinityConfig.configVersion < 1) { -+ if (config != null && config.getLong(path) == dfl) { -+ config.set(path, null); -+ } -+ } -+ return config == null ? this.worldDefaults.getLong(path, dfl) : config.getLong(path, this.worldDefaults.getLong(path, dfl)); -+ } -+ -+ double getDouble(final String path, final double dfl) { -+ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath); -+ this.worldDefaults.addDefault(path, Double.valueOf(dfl)); -+ if (TuinityConfig.configVersion < 1) { -+ if (config != null && config.getDouble(path) == dfl) { -+ config.set(path, null); -+ } -+ } -+ return config == null ? this.worldDefaults.getDouble(path) : config.getDouble(path, this.worldDefaults.getDouble(path)); -+ } -+ -+ double getDoubleRaw(final String path, final double dfl) { -+ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath); -+ if (TuinityConfig.configVersion < 1) { -+ if (config != null && config.getDouble(path) == dfl) { -+ config.set(path, null); -+ } -+ } -+ return config == null ? this.worldDefaults.getDouble(path, dfl) : config.getDouble(path, this.worldDefaults.getDouble(path, dfl)); -+ } -+ -+ String getString(final String path, final String dfl) { -+ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath); -+ this.worldDefaults.addDefault(path, dfl); -+ return config == null ? this.worldDefaults.getString(path) : config.getString(path, this.worldDefaults.getString(path)); -+ } -+ -+ String getStringRaw(final String path, final String dfl) { -+ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath); -+ return config == null ? this.worldDefaults.getString(path, dfl) : config.getString(path, this.worldDefaults.getString(path, dfl)); -+ } -+ -+ List getList(final String path, final List dfl) { -+ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath); -+ this.worldDefaults.addDefault(path, dfl); -+ return config == null ? this.worldDefaults.getList(path) : config.getList(path, this.worldDefaults.getList(path)); -+ } -+ -+ List getListRaw(final String path, final List dfl) { -+ final ConfigurationSection config = TuinityConfig.config.getConfigurationSection(this.configPath); -+ return config == null ? this.worldDefaults.getList(path, dfl) : config.getList(path, this.worldDefaults.getList(path, dfl)); -+ } -+ -+ public int spawnLimitMonsters; -+ public int spawnLimitAnimals; -+ public int spawnLimitWaterAmbient; -+ public int spawnLimitWaterAnimals; -+ public int spawnLimitAmbient; -+ -+ private void perWorldSpawnLimit() { -+ final String path = "spawn-limits"; -+ -+ this.spawnLimitMonsters = this.getIntRaw(path + ".monsters", -1); -+ this.spawnLimitAnimals = this.getIntRaw(path + ".animals", -1); -+ this.spawnLimitWaterAmbient = this.getIntRaw(path + ".water-ambient", -1); -+ this.spawnLimitWaterAnimals = this.getIntRaw(path + ".water-animals", -1); -+ this.spawnLimitAmbient = this.getIntRaw(path + ".ambient", -1); -+ } -+ } -+ -+} -diff --git a/src/main/java/com/tuinity/tuinity/util/CachedLists.java b/src/main/java/com/tuinity/tuinity/util/CachedLists.java -new file mode 100644 -index 0000000000000000000000000000000000000000..01320aea07b51c97ae5f0654b81d2332f545d42e ---- /dev/null -+++ b/src/main/java/com/tuinity/tuinity/util/CachedLists.java -@@ -0,0 +1,57 @@ -+package com.tuinity.tuinity.util; -+ -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.phys.AABB; -+import org.bukkit.Bukkit; -+import org.bukkit.craftbukkit.util.UnsafeList; -+import java.util.List; -+ -+public final class CachedLists { -+ -+ // Tuinity start - optimise collisions -+ static final UnsafeList TEMP_COLLISION_LIST = new UnsafeList<>(1024); -+ static boolean tempCollisionListInUse; -+ -+ public static UnsafeList getTempCollisionList() { -+ if (!Bukkit.isPrimaryThread() || tempCollisionListInUse) { -+ return new UnsafeList<>(16); -+ } -+ tempCollisionListInUse = true; -+ return TEMP_COLLISION_LIST; -+ } -+ -+ public static void returnTempCollisionList(List list) { -+ if (list != TEMP_COLLISION_LIST) { -+ return; -+ } -+ ((UnsafeList)list).setSize(0); -+ tempCollisionListInUse = false; -+ } -+ -+ static final UnsafeList TEMP_GET_ENTITIES_LIST = new UnsafeList<>(1024); -+ static boolean tempGetEntitiesListInUse; -+ -+ public static UnsafeList getTempGetEntitiesList() { -+ if (!Bukkit.isPrimaryThread() || tempGetEntitiesListInUse) { -+ return new UnsafeList<>(16); -+ } -+ tempGetEntitiesListInUse = true; -+ return TEMP_GET_ENTITIES_LIST; -+ } -+ -+ public static void returnTempGetEntitiesList(List list) { -+ if (list != TEMP_GET_ENTITIES_LIST) { -+ return; -+ } -+ ((UnsafeList)list).setSize(0); -+ tempGetEntitiesListInUse = false; -+ } -+ // Tuinity end - optimise collisions -+ -+ public static void reset() { -+ // Tuinity start - optimise collisions -+ TEMP_COLLISION_LIST.completeReset(); -+ TEMP_GET_ENTITIES_LIST.completeReset(); -+ // Tuinity end - optimise collisions -+ } -+} -diff --git a/src/main/java/com/tuinity/tuinity/util/CollisionUtil.java b/src/main/java/com/tuinity/tuinity/util/CollisionUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..089d66ce4913e97c5fc79daee0f2fd932664f28f ---- /dev/null -+++ b/src/main/java/com/tuinity/tuinity/util/CollisionUtil.java -@@ -0,0 +1,600 @@ -+package com.tuinity.tuinity.util; -+ -+import com.tuinity.tuinity.voxel.AABBVoxelShape; -+import net.minecraft.core.BlockPos; -+import net.minecraft.server.level.ServerChunkCache; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.server.level.WorldGenRegion; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.level.CollisionGetter; -+import net.minecraft.world.level.EntityGetter; -+import net.minecraft.world.level.block.Blocks; -+import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.border.WorldBorder; -+import net.minecraft.world.level.chunk.ChunkAccess; -+import net.minecraft.world.level.chunk.LevelChunkSection; -+import net.minecraft.world.phys.AABB; -+import net.minecraft.world.phys.Vec3; -+import net.minecraft.world.phys.shapes.ArrayVoxelShape; -+import net.minecraft.world.phys.shapes.CollisionContext; -+import net.minecraft.world.phys.shapes.Shapes; -+import net.minecraft.world.phys.shapes.VoxelShape; -+import java.util.List; -+import java.util.function.BiPredicate; -+import java.util.function.Predicate; -+ -+public final class CollisionUtil { -+ -+ public static final double COLLISION_EPSILON = 1.0E-7; -+ -+ public static boolean isEmpty(final AABB aabb) { -+ return (aabb.maxX - aabb.minX) < COLLISION_EPSILON && (aabb.maxY - aabb.minY) < COLLISION_EPSILON && (aabb.maxZ - aabb.minZ) < COLLISION_EPSILON; -+ } -+ -+ public static boolean isEmpty(final double minX, final double minY, final double minZ, -+ final double maxX, final double maxY, final double maxZ) { -+ return (maxX - minX) < COLLISION_EPSILON && (maxY - minY) < COLLISION_EPSILON && (maxZ - minZ) < COLLISION_EPSILON; -+ } -+ -+ public static AABB getBoxForChunk(final int chunkX, final int chunkZ) { -+ double x = (double)(chunkX << 4); -+ double z = (double)(chunkZ << 4); -+ // use a bounding box bigger than the chunk to prevent entities from entering it on move -+ return new AABB(x - 3*COLLISION_EPSILON, Double.NEGATIVE_INFINITY, z - 3*COLLISION_EPSILON, -+ x + (16.0 + 3*COLLISION_EPSILON), Double.POSITIVE_INFINITY, z + (16.0 + 3*COLLISION_EPSILON), false); -+ } -+ -+ /* -+ A couple of rules for VoxelShape collisions: -+ Two shapes only intersect if they are actually more than EPSILON units into each other. This also applies to movement -+ checks. -+ If the two shapes strictly collide, then the return value of a collide call will return a value in the opposite -+ direction of the source move. However, this value will not be greater in magnitude than EPSILON. Collision code -+ will automatically round it to 0. -+ */ -+ -+ public static boolean voxelShapeIntersect(final double minX1, final double minY1, final double minZ1, final double maxX1, -+ final double maxY1, final double maxZ1, final double minX2, final double minY2, -+ final double minZ2, final double maxX2, final double maxY2, final double maxZ2) { -+ return (minX1 - maxX2) < -COLLISION_EPSILON && (maxX1 - minX2) > COLLISION_EPSILON && -+ (minY1 - maxY2) < -COLLISION_EPSILON && (maxY1 - minY2) > COLLISION_EPSILON && -+ (minZ1 - maxZ2) < -COLLISION_EPSILON && (maxZ1 - minZ2) > COLLISION_EPSILON; -+ } -+ -+ public static boolean voxelShapeIntersect(final AABB box, final double minX, final double minY, final double minZ, -+ final double maxX, final double maxY, final double maxZ) { -+ return (box.minX - maxX) < -COLLISION_EPSILON && (box.maxX - minX) > COLLISION_EPSILON && -+ (box.minY - maxY) < -COLLISION_EPSILON && (box.maxY - minY) > COLLISION_EPSILON && -+ (box.minZ - maxZ) < -COLLISION_EPSILON && (box.maxZ - minZ) > COLLISION_EPSILON; -+ } -+ -+ public static boolean voxelShapeIntersect(final AABB box1, final AABB box2) { -+ return (box1.minX - box2.maxX) < -COLLISION_EPSILON && (box1.maxX - box2.minX) > COLLISION_EPSILON && -+ (box1.minY - box2.maxY) < -COLLISION_EPSILON && (box1.maxY - box2.minY) > COLLISION_EPSILON && -+ (box1.minZ - box2.maxZ) < -COLLISION_EPSILON && (box1.maxZ - box2.minZ) > COLLISION_EPSILON; -+ } -+ -+ public static double collideX(final AABB target, final AABB source, final double source_move) { -+ if (source_move == 0.0) { -+ return 0.0; -+ } -+ -+ if ((source.minY - target.maxY) < -COLLISION_EPSILON && (source.maxY - target.minY) > COLLISION_EPSILON && -+ (source.minZ - target.maxZ) < -COLLISION_EPSILON && (source.maxZ - target.minZ) > COLLISION_EPSILON) { -+ if (source_move >= 0.0) { -+ final double max_move = target.minX - source.maxX; // < 0.0 if no strict collision -+ if (max_move < -COLLISION_EPSILON) { -+ return source_move; -+ } -+ return Math.min(max_move, source_move); -+ } else { -+ final double max_move = target.maxX - source.minX; // > 0.0 if no strict collision -+ if (max_move > COLLISION_EPSILON) { -+ return source_move; -+ } -+ return Math.max(max_move, source_move); -+ } -+ } -+ return source_move; -+ } -+ -+ public static double collideY(final AABB target, final AABB source, final double source_move) { -+ if (source_move == 0.0) { -+ return 0.0; -+ } -+ -+ if ((source.minX - target.maxX) < -COLLISION_EPSILON && (source.maxX - target.minX) > COLLISION_EPSILON && -+ (source.minZ - target.maxZ) < -COLLISION_EPSILON && (source.maxZ - target.minZ) > COLLISION_EPSILON) { -+ if (source_move >= 0.0) { -+ final double max_move = target.minY - source.maxY; // < 0.0 if no strict collision -+ if (max_move < -COLLISION_EPSILON) { -+ return source_move; -+ } -+ return Math.min(max_move, source_move); -+ } else { -+ final double max_move = target.maxY - source.minY; // > 0.0 if no strict collision -+ if (max_move > COLLISION_EPSILON) { -+ return source_move; -+ } -+ return Math.max(max_move, source_move); -+ } -+ } -+ return source_move; -+ } -+ -+ public static double collideZ(final AABB target, final AABB source, final double source_move) { -+ if (source_move == 0.0) { -+ return 0.0; -+ } -+ -+ if ((source.minX - target.maxX) < -COLLISION_EPSILON && (source.maxX - target.minX) > COLLISION_EPSILON && -+ (source.minY - target.maxY) < -COLLISION_EPSILON && (source.maxY - target.minY) > COLLISION_EPSILON) { -+ if (source_move >= 0.0) { -+ final double max_move = target.minZ - source.maxZ; // < 0.0 if no strict collision -+ if (max_move < -COLLISION_EPSILON) { -+ return source_move; -+ } -+ return Math.min(max_move, source_move); -+ } else { -+ final double max_move = target.maxZ - source.minZ; // > 0.0 if no strict collision -+ if (max_move > COLLISION_EPSILON) { -+ return source_move; -+ } -+ return Math.max(max_move, source_move); -+ } -+ } -+ return source_move; -+ } -+ -+ public static AABB offsetX(final AABB box, final double dx) { -+ return new AABB(box.minX + dx, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false); -+ } -+ -+ public static AABB offsetY(final AABB box, final double dy) { -+ return new AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false); -+ } -+ -+ public static AABB offsetZ(final AABB box, final double dz) { -+ return new AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.maxZ + dz, false); -+ } -+ -+ public static AABB expandRight(final AABB box, final double dx) { // dx > 0.0 -+ return new AABB(box.minX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false); -+ } -+ -+ public static AABB expandLeft(final AABB box, final double dx) { // dx < 0.0 -+ return new AABB(box.minX - dx, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ, false); -+ } -+ -+ public static AABB expandUpwards(final AABB box, final double dy) { // dy > 0.0 -+ return new AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false); -+ } -+ -+ public static AABB expandDownwards(final AABB box, final double dy) { // dy < 0.0 -+ return new AABB(box.minX, box.minY - dy, box.minZ, box.maxX, box.maxY, box.maxZ, false); -+ } -+ -+ public static AABB expandForwards(final AABB box, final double dz) { // dz > 0.0 -+ return new AABB(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ + dz, false); -+ } -+ -+ public static AABB expandBackwards(final AABB box, final double dz) { // dz < 0.0 -+ return new AABB(box.minX, box.minY, box.minZ - dz, box.maxX, box.maxY, box.maxZ, false); -+ } -+ -+ public static AABB cutRight(final AABB box, final double dx) { // dx > 0.0 -+ return new AABB(box.maxX, box.minY, box.minZ, box.maxX + dx, box.maxY, box.maxZ, false); -+ } -+ -+ public static AABB cutLeft(final AABB box, final double dx) { // dx < 0.0 -+ return new AABB(box.minX + dx, box.minY, box.minZ, box.minX, box.maxY, box.maxZ, false); -+ } -+ -+ public static AABB cutUpwards(final AABB box, final double dy) { // dy > 0.0 -+ return new AABB(box.minX, box.maxY, box.minZ, box.maxX, box.maxY + dy, box.maxZ, false); -+ } -+ -+ public static AABB cutDownwards(final AABB box, final double dy) { // dy < 0.0 -+ return new AABB(box.minX, box.minY + dy, box.minZ, box.maxX, box.minY, box.maxZ, false); -+ } -+ -+ public static AABB cutForwards(final AABB box, final double dz) { // dz > 0.0 -+ return new AABB(box.minX, box.minY, box.maxZ, box.maxX, box.maxY, box.maxZ + dz, false); -+ } -+ -+ public static AABB cutBackwards(final AABB box, final double dz) { // dz < 0.0 -+ return new AABB(box.minX, box.minY, box.minZ + dz, box.maxX, box.maxY, box.minZ, false); -+ } -+ -+ public static double performCollisionsX(final AABB currentBoundingBox, double value, final List potentialCollisions) { -+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { -+ final AABB target = potentialCollisions.get(i); -+ value = collideX(target, currentBoundingBox, value); -+ } -+ -+ return value; -+ } -+ -+ public static double performCollisionsY(final AABB currentBoundingBox, double value, final List potentialCollisions) { -+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { -+ final AABB target = potentialCollisions.get(i); -+ value = collideY(target, currentBoundingBox, value); -+ } -+ -+ return value; -+ } -+ -+ public static double performCollisionsZ(final AABB currentBoundingBox, double value, final List potentialCollisions) { -+ for (int i = 0, len = potentialCollisions.size(); i < len; ++i) { -+ final AABB target = potentialCollisions.get(i); -+ value = collideZ(target, currentBoundingBox, value); -+ } -+ -+ return value; -+ } -+ -+ public static Vec3 performCollisions(final Vec3 moveVector, AABB axisalignedbb, final List potentialCollisions) { -+ double x = moveVector.x; -+ double y = moveVector.y; -+ double z = moveVector.z; -+ -+ if (y != 0.0) { -+ y = performCollisionsY(axisalignedbb, y, potentialCollisions); -+ if (y != 0.0) { -+ axisalignedbb = offsetY(axisalignedbb, y); -+ } -+ } -+ -+ final boolean xSmaller = Math.abs(x) < Math.abs(z); -+ -+ if (xSmaller && z != 0.0) { -+ z = performCollisionsZ(axisalignedbb, z, potentialCollisions); -+ if (z != 0.0) { -+ axisalignedbb = offsetZ(axisalignedbb, z); -+ } -+ } -+ -+ if (x != 0.0) { -+ x = performCollisionsX(axisalignedbb, x, potentialCollisions); -+ if (!xSmaller && x != 0.0) { -+ axisalignedbb = offsetX(axisalignedbb, x); -+ } -+ } -+ -+ if (!xSmaller && z != 0.0) { -+ z = performCollisionsZ(axisalignedbb, z, potentialCollisions); -+ } -+ -+ return new Vec3(x, y, z); -+ } -+ -+ public static boolean addBoxesToIfIntersects(final VoxelShape shape, final AABB aabb, final List list) { -+ if (shape instanceof AABBVoxelShape) { -+ final AABBVoxelShape shapeCasted = (AABBVoxelShape)shape; -+ if (voxelShapeIntersect(shapeCasted.aabb, aabb) && !isEmpty(shapeCasted.aabb)) { -+ list.add(shapeCasted.aabb); -+ return true; -+ } -+ return false; -+ } else if (shape instanceof ArrayVoxelShape) { -+ final ArrayVoxelShape shapeCasted = (ArrayVoxelShape)shape; -+ // this can be optimised by checking an "overall shape" first, but not needed -+ -+ final double offX = shapeCasted.getOffsetX(); -+ final double offY = shapeCasted.getOffsetY(); -+ final double offZ = shapeCasted.getOffsetZ(); -+ -+ boolean ret = false; -+ -+ for (final AABB boundingBox : shapeCasted.getBoundingBoxesRepresentation()) { -+ final double minX, minY, minZ, maxX, maxY, maxZ; -+ if (voxelShapeIntersect(aabb, minX = boundingBox.minX + offX, minY = boundingBox.minY + offY, minZ = boundingBox.minZ + offZ, -+ maxX = boundingBox.maxX + offX, maxY = boundingBox.maxY + offY, maxZ = boundingBox.maxZ + offZ) -+ && !isEmpty(minX, minY, minZ, maxX, maxY, maxZ)) { -+ list.add(new AABB(minX, minY, minZ, maxX, maxY, maxZ, false)); -+ ret = true; -+ } -+ } -+ -+ return ret; -+ } else { -+ final List boxes = shape.toAabbs(); -+ -+ boolean ret = false; -+ -+ for (int i = 0, len = boxes.size(); i < len; ++i) { -+ final AABB box = boxes.get(i); -+ if (voxelShapeIntersect(box, aabb) && !isEmpty(box)) { -+ list.add(box); -+ ret = true; -+ } -+ } -+ -+ return ret; -+ } -+ } -+ -+ public static void addBoxesTo(final VoxelShape shape, final List list) { -+ if (shape instanceof AABBVoxelShape) { -+ final AABBVoxelShape shapeCasted = (AABBVoxelShape)shape; -+ if (!isEmpty(shapeCasted.aabb)) { -+ list.add(shapeCasted.aabb); -+ } -+ } else if (shape instanceof ArrayVoxelShape) { -+ final ArrayVoxelShape shapeCasted = (ArrayVoxelShape)shape; -+ -+ final double offX = shapeCasted.getOffsetX(); -+ final double offY = shapeCasted.getOffsetY(); -+ final double offZ = shapeCasted.getOffsetZ(); -+ -+ for (final AABB boundingBox : shapeCasted.getBoundingBoxesRepresentation()) { -+ final AABB box = boundingBox.move(offX, offY, offZ); -+ if (!isEmpty(box)) { -+ list.add(box); -+ } -+ } -+ } else { -+ final List boxes = shape.toAabbs(); -+ for (int i = 0, len = boxes.size(); i < len; ++i) { -+ final AABB box = boxes.get(i); -+ if (!isEmpty(box)) { -+ list.add(box); -+ } -+ } -+ } -+ } -+ -+ public static boolean isAlmostCollidingOnBorder(final WorldBorder worldborder, final AABB boundingBox) { -+ return isAlmostCollidingOnBorder(worldborder, boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ); -+ } -+ -+ public static boolean isAlmostCollidingOnBorder(final WorldBorder worldborder, final double boxMinX, final double boxMaxX, -+ final double boxMinZ, final double boxMaxZ) { -+ final double borderMinX = worldborder.getMinX(); // -X -+ final double borderMaxX = worldborder.getMaxX(); // +X -+ -+ final double borderMinZ = worldborder.getMinZ(); // -Z -+ final double borderMaxZ = worldborder.getMaxZ(); // +Z -+ -+ return -+ // Not intersecting if we're smaller -+ !voxelShapeIntersect( -+ boxMinX + COLLISION_EPSILON, Double.NEGATIVE_INFINITY, boxMinZ + COLLISION_EPSILON, -+ boxMaxX - COLLISION_EPSILON, Double.POSITIVE_INFINITY, boxMaxZ - COLLISION_EPSILON, -+ borderMinX, Double.NEGATIVE_INFINITY, borderMinZ, borderMaxX, Double.POSITIVE_INFINITY, borderMaxZ -+ ) -+ && -+ -+ // Are intersecting if we're larger -+ voxelShapeIntersect( -+ boxMinX - COLLISION_EPSILON, Double.NEGATIVE_INFINITY, boxMinZ - COLLISION_EPSILON, -+ boxMaxX + COLLISION_EPSILON, Double.POSITIVE_INFINITY, boxMaxZ + COLLISION_EPSILON, -+ borderMinX, Double.NEGATIVE_INFINITY, borderMinZ, borderMaxX, Double.POSITIVE_INFINITY, borderMaxZ -+ ); -+ } -+ -+ public static boolean isCollidingWithBorderEdge(final WorldBorder worldborder, final AABB boundingBox) { -+ return isCollidingWithBorderEdge(worldborder, boundingBox.minX, boundingBox.maxX, boundingBox.minZ, boundingBox.maxZ); -+ } -+ -+ public static boolean isCollidingWithBorderEdge(final WorldBorder worldborder, final double boxMinX, final double boxMaxX, -+ final double boxMinZ, final double boxMaxZ) { -+ final double borderMinX = worldborder.getMinX() + COLLISION_EPSILON; // -X -+ final double borderMaxX = worldborder.getMaxX() - COLLISION_EPSILON; // +X -+ -+ final double borderMinZ = worldborder.getMinZ() + COLLISION_EPSILON; // -Z -+ final double borderMaxZ = worldborder.getMaxZ() - COLLISION_EPSILON; // +Z -+ -+ return boxMinX < borderMinX || boxMaxX > borderMaxX || boxMinZ < borderMinZ || boxMaxZ > borderMaxZ; -+ } -+ -+ public static boolean getCollisionsForBlocksOrWorldBorder(final CollisionGetter getter, final Entity entity, final AABB aabb, -+ final List into, final boolean loadChunks, final boolean collidesWithUnloaded, -+ final boolean checkBorder, final boolean checkOnly, final BiPredicate predicate) { -+ boolean ret = false; -+ -+ if (checkBorder) { -+ if (CollisionUtil.isAlmostCollidingOnBorder(getter.getWorldBorder(), aabb)) { -+ if (checkOnly) { -+ return true; -+ } else { -+ CollisionUtil.addBoxesTo(getter.getWorldBorder().getCollisionShape(), into); -+ ret = true; -+ } -+ } -+ } -+ -+ int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1; -+ int maxBlockX = Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1; -+ -+ int minBlockY = Mth.floor(aabb.minY - COLLISION_EPSILON) - 1; -+ int maxBlockY = Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1; -+ -+ int minBlockZ = Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1; -+ int maxBlockZ = Mth.floor(aabb.maxZ + COLLISION_EPSILON) + 1; -+ -+ final int minSection = WorldUtil.getMinSection(getter); -+ final int maxSection = WorldUtil.getMaxSection(getter); -+ final int minBlock = minSection << 4; -+ final int maxBlock = (maxSection << 4) | 15; -+ -+ BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos(); -+ CollisionContext collisionShape = null; -+ -+ // special cases: -+ if (minBlockY > maxBlock || maxBlockY < minBlock) { -+ // no point in checking -+ return ret; -+ } -+ -+ int minYIterate = Math.max(minBlock, minBlockY); -+ int maxYIterate = Math.min(maxBlock, maxBlockY); -+ -+ int minChunkX = minBlockX >> 4; -+ int maxChunkX = maxBlockX >> 4; -+ -+ int minChunkZ = minBlockZ >> 4; -+ int maxChunkZ = maxBlockZ >> 4; -+ -+ ServerChunkCache chunkProvider; -+ if (getter instanceof WorldGenRegion) { -+ chunkProvider = null; -+ } else if (getter instanceof ServerLevel) { -+ chunkProvider = ((ServerLevel)getter).getChunkSource(); -+ } else { -+ chunkProvider = null; -+ } -+ // TODO special case single chunk? -+ -+ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) { -+ int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk -+ int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk -+ -+ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) { -+ int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk -+ int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk -+ -+ int chunkXGlobalPos = currChunkX << 4; -+ int chunkZGlobalPos = currChunkZ << 4; -+ ChunkAccess chunk; -+ if (chunkProvider == null) { -+ chunk = (ChunkAccess)getter.getChunkForCollisions(currChunkX, currChunkZ); -+ } else { -+ chunk = loadChunks ? chunkProvider.getChunk(currChunkX, currChunkZ, true) : chunkProvider.getChunkAtIfLoadedImmediately(currChunkX, currChunkZ); -+ } -+ -+ -+ if (chunk == null) { -+ if (collidesWithUnloaded) { -+ if (checkOnly) { -+ return true; -+ } else { -+ into.add(getBoxForChunk(currChunkX, currChunkZ)); -+ ret = true; -+ } -+ } -+ continue; -+ } -+ -+ LevelChunkSection[] sections = chunk.getSections(); -+ -+ // bound y -+ -+ for (int currY = minYIterate; currY <= maxYIterate; ++currY) { -+ LevelChunkSection section = sections[(currY >> 4) - minSection]; -+ if (section == null || section.isEmpty()) { -+ // empty -+ // skip to next section -+ currY = (currY & ~(15)) + 15; // increment by 15: iterator loop increments by the extra one -+ continue; -+ } -+ -+ net.minecraft.world.level.chunk.PalettedContainer blocks = section.states; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ int localBlockIndex = (currX) | (currZ << 4) | ((currY & 15) << 8); -+ int blockX = currX | chunkXGlobalPos; -+ int blockY = currY; -+ int blockZ = currZ | chunkZGlobalPos; -+ -+ int edgeCount = ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) + -+ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) + -+ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0); -+ if (edgeCount == 3) { -+ continue; -+ } -+ -+ BlockState blockData = blocks.get(localBlockIndex); -+ if (blockData.isAir()) { -+ continue; -+ } -+ -+ if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) { -+ mutablePos.set(blockX, blockY, blockZ); -+ if (collisionShape == null) { -+ collisionShape = entity == null ? CollisionContext.empty() : CollisionContext.of(entity); -+ } -+ VoxelShape voxelshape2 = blockData.getCollisionShape(getter, mutablePos, collisionShape); -+ if (voxelshape2 != Shapes.empty()) { -+ VoxelShape voxelshape3 = voxelshape2.move((double)blockX, (double)blockY, (double)blockZ); -+ -+ if (predicate != null && !predicate.test(blockData, mutablePos)) { -+ continue; -+ } -+ -+ if (checkOnly) { -+ if (voxelshape3.intersects(aabb)) { -+ return true; -+ } -+ } else { -+ ret |= addBoxesToIfIntersects(voxelshape3, aabb, into); -+ } -+ } -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ return ret; -+ } -+ -+ public static boolean getEntityHardCollisions(final CollisionGetter getter, final Entity entity, AABB aabb, -+ final List into, final boolean checkOnly, final Predicate predicate) { -+ if (isEmpty(aabb) || !(getter instanceof EntityGetter entityGetter)) { -+ return false; -+ } -+ -+ boolean ret = false; -+ -+ // to comply with vanilla intersection rules, expand by -epsilon so we only get stuff we definitely collide with. -+ // Vanilla for hard collisions has this backwards, and they expand by +epsilon but this causes terrible problems -+ // specifically with boat collisions. -+ aabb = aabb.inflate(-COLLISION_EPSILON, -COLLISION_EPSILON, -COLLISION_EPSILON); -+ final List entities = CachedLists.getTempGetEntitiesList(); -+ try { -+ if (entity != null && entity.hardCollides()) { -+ entityGetter.getEntities(entity, aabb, predicate, entities); -+ } else { -+ entityGetter.getHardCollidingEntities(entity, aabb, predicate, entities); -+ } -+ -+ for (int i = 0, len = entities.size(); i < len; ++i) { -+ final Entity otherEntity = entities.get(i); -+ -+ if ((entity == null && otherEntity.canBeCollidedWith()) || (entity != null && entity.canCollideWith(otherEntity))) { -+ if (checkOnly) { -+ return true; -+ } else { -+ into.add(otherEntity.getBoundingBox()); -+ ret = true; -+ } -+ } -+ } -+ } finally { -+ CachedLists.returnTempGetEntitiesList(entities); -+ } -+ -+ return ret; -+ } -+ -+ public static boolean getCollisions(final CollisionGetter view, final Entity entity, final AABB aabb, -+ final List into, final boolean loadChunks, final boolean collidesWithUnloadedChunks, -+ final boolean checkBorder, final boolean checkOnly, final BiPredicate blockPredicate, -+ final Predicate entityPredicate) { -+ if (checkOnly) { -+ return getCollisionsForBlocksOrWorldBorder(view, entity, aabb, into, loadChunks, collidesWithUnloadedChunks, checkBorder, checkOnly, blockPredicate) -+ || getEntityHardCollisions(view, entity, aabb, into, checkOnly, entityPredicate); -+ } else { -+ return getCollisionsForBlocksOrWorldBorder(view, entity, aabb, into, loadChunks, collidesWithUnloadedChunks, checkBorder, checkOnly, blockPredicate) -+ | getEntityHardCollisions(view, entity, aabb, into, checkOnly, entityPredicate); -+ } -+ } -+ -+ private CollisionUtil() { -+ throw new RuntimeException(); -+ } -+} -diff --git a/src/main/java/com/tuinity/tuinity/util/CoordinateUtils.java b/src/main/java/com/tuinity/tuinity/util/CoordinateUtils.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f84060952c947d79bf2dffc61c96a300e8d7fac2 ---- /dev/null -+++ b/src/main/java/com/tuinity/tuinity/util/CoordinateUtils.java -@@ -0,0 +1,128 @@ -+package com.tuinity.tuinity.util; -+ -+import net.minecraft.core.BlockPos; -+import net.minecraft.core.SectionPos; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.level.ChunkPos; -+ -+public final class CoordinateUtils { -+ -+ // dx, dz are relative to the target chunk -+ // dx, dz in [-radius, radius] -+ public static int getNeighbourMappedIndex(final int dx, final int dz, final int radius) { -+ return (dx + radius) + (2 * radius + 1)*(dz + radius); -+ } -+ -+ // the chunk keys are compatible with vanilla -+ -+ public static long getChunkKey(final BlockPos pos) { -+ return ((long)(pos.getZ() >> 4) << 32) | ((pos.getX() >> 4) & 0xFFFFFFFFL); -+ } -+ -+ public static long getChunkKey(final Entity entity) { -+ return ((Mth.lfloor(entity.getZ()) >> 4) << 32) | ((Mth.lfloor(entity.getX()) >> 4) & 0xFFFFFFFFL); -+ } -+ -+ public static long getChunkKey(final ChunkPos pos) { -+ return ((long)pos.z << 32) | (pos.x & 0xFFFFFFFFL); -+ } -+ -+ public static long getChunkKey(final SectionPos pos) { -+ return ((long)pos.getZ() << 32) | (pos.getX() & 0xFFFFFFFFL); -+ } -+ -+ public static long getChunkKey(final int x, final int z) { -+ return ((long)z << 32) | (x & 0xFFFFFFFFL); -+ } -+ -+ public static int getChunkX(final long chunkKey) { -+ return (int)chunkKey; -+ } -+ -+ public static int getChunkZ(final long chunkKey) { -+ return (int)(chunkKey >>> 32); -+ } -+ -+ public static int getChunkCoordinate(final double blockCoordinate) { -+ return Mth.floor(blockCoordinate) >> 4; -+ } -+ -+ // the section keys are compatible with vanilla's -+ -+ static final int SECTION_X_BITS = 22; -+ static final long SECTION_X_MASK = (1L << SECTION_X_BITS) - 1; -+ static final int SECTION_Y_BITS = 20; -+ static final long SECTION_Y_MASK = (1L << SECTION_Y_BITS) - 1; -+ static final int SECTION_Z_BITS = 22; -+ static final long SECTION_Z_MASK = (1L << SECTION_Z_BITS) - 1; -+ // format is y,z,x (in order of LSB to MSB) -+ static final int SECTION_Y_SHIFT = 0; -+ static final int SECTION_Z_SHIFT = SECTION_Y_SHIFT + SECTION_Y_BITS; -+ static final int SECTION_X_SHIFT = SECTION_Z_SHIFT + SECTION_X_BITS; -+ static final int SECTION_TO_BLOCK_SHIFT = 4; -+ -+ public static long getChunkSectionKey(final int x, final int y, final int z) { -+ return ((x & SECTION_X_MASK) << SECTION_X_SHIFT) -+ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT) -+ | ((z & SECTION_Z_MASK) << SECTION_Z_SHIFT); -+ } -+ -+ public static long getChunkSectionKey(final SectionPos pos) { -+ return ((pos.getX() & SECTION_X_MASK) << SECTION_X_SHIFT) -+ | ((pos.getY() & SECTION_Y_MASK) << SECTION_Y_SHIFT) -+ | ((pos.getZ() & SECTION_Z_MASK) << SECTION_Z_SHIFT); -+ } -+ -+ public static long getChunkSectionKey(final ChunkPos pos, final int y) { -+ return ((pos.x & SECTION_X_MASK) << SECTION_X_SHIFT) -+ | ((y & SECTION_Y_MASK) << SECTION_Y_SHIFT) -+ | ((pos.z & SECTION_Z_MASK) << SECTION_Z_SHIFT); -+ } -+ -+ public static long getChunkSectionKey(final BlockPos pos) { -+ return (((long)pos.getX() << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) | -+ ((pos.getY() >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) | -+ (((long)pos.getZ() << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT)); -+ } -+ -+ public static long getChunkSectionKey(final Entity entity) { -+ return ((Mth.lfloor(entity.getX()) << (SECTION_X_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_X_MASK << SECTION_X_SHIFT)) | -+ ((Mth.lfloor(entity.getY()) >> SECTION_TO_BLOCK_SHIFT) & (SECTION_Y_MASK << SECTION_Y_SHIFT)) | -+ ((Mth.lfloor(entity.getZ()) << (SECTION_Z_SHIFT - SECTION_TO_BLOCK_SHIFT)) & (SECTION_Z_MASK << SECTION_Z_SHIFT)); -+ } -+ -+ public static int getChunkSectionX(final long key) { -+ return (int)(key << (Long.SIZE - (SECTION_X_SHIFT + SECTION_X_BITS)) >> (Long.SIZE - SECTION_X_BITS)); -+ } -+ -+ public static int getChunkSectionY(final long key) { -+ return (int)(key << (Long.SIZE - (SECTION_Y_SHIFT + SECTION_Y_BITS)) >> (Long.SIZE - SECTION_Y_BITS)); -+ } -+ -+ public static int getChunkSectionZ(final long key) { -+ return (int)(key << (Long.SIZE - (SECTION_Z_SHIFT + SECTION_Z_BITS)) >> (Long.SIZE - SECTION_Z_BITS)); -+ } -+ -+ // the block coordinates are not necessarily compatible with vanilla's -+ -+ public static int getBlockCoordinate(final double blockCoordinate) { -+ return Mth.floor(blockCoordinate); -+ } -+ -+ public static long getBlockKey(final int x, final int y, final int z) { -+ return ((long)x & 0x7FFFFFF) | (((long)z & 0x7FFFFFF) << 27) | ((long)y << 54); -+ } -+ -+ public static long getBlockKey(final BlockPos pos) { -+ return ((long)pos.getX() & 0x7FFFFFF) | (((long)pos.getZ() & 0x7FFFFFF) << 27) | ((long)pos.getY() << 54); -+ } -+ -+ public static long getBlockKey(final Entity entity) { -+ return ((long)entity.getX() & 0x7FFFFFF) | (((long)entity.getZ() & 0x7FFFFFF) << 27) | ((long)entity.getY() << 54); -+ } -+ -+ private CoordinateUtils() { -+ throw new RuntimeException(); -+ } -+} -diff --git a/src/main/java/com/tuinity/tuinity/util/IntegerUtil.java b/src/main/java/com/tuinity/tuinity/util/IntegerUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..695444a510e616180734f5fd284f1a00a2d73ea6 ---- /dev/null -+++ b/src/main/java/com/tuinity/tuinity/util/IntegerUtil.java -@@ -0,0 +1,226 @@ -+package com.tuinity.tuinity.util; -+ -+public final class IntegerUtil { -+ -+ public static final int HIGH_BIT_U32 = Integer.MIN_VALUE; -+ public static final long HIGH_BIT_U64 = Long.MIN_VALUE; -+ -+ public static int ceilLog2(final int value) { -+ return Integer.SIZE - Integer.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros -+ } -+ -+ public static long ceilLog2(final long value) { -+ return Long.SIZE - Long.numberOfLeadingZeros(value - 1); // see doc of numberOfLeadingZeros -+ } -+ -+ public static int floorLog2(final int value) { -+ // xor is optimized subtract for 2^n -1 -+ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) -+ return (Integer.SIZE - 1) ^ Integer.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros -+ } -+ -+ public static int floorLog2(final long value) { -+ // xor is optimized subtract for 2^n -1 -+ // note that (2^n -1) - k = (2^n -1) ^ k for k <= (2^n - 1) -+ return (Long.SIZE - 1) ^ Long.numberOfLeadingZeros(value); // see doc of numberOfLeadingZeros -+ } -+ -+ public static int roundCeilLog2(final int value) { -+ // optimized variant of 1 << (32 - leading(val - 1)) -+ // given -+ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) -+ // 1 << (32 - leading(val - 1)) = HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) -+ // HIGH_BIT_32 >>> (31 - (32 - leading(val - 1))) -+ // HIGH_BIT_32 >>> (31 - 32 + leading(val - 1)) -+ // HIGH_BIT_32 >>> (-1 + leading(val - 1)) -+ return HIGH_BIT_U32 >>> (Integer.numberOfLeadingZeros(value - 1) - 1); -+ } -+ -+ public static long roundCeilLog2(final long value) { -+ // see logic documented above -+ return HIGH_BIT_U64 >>> (Long.numberOfLeadingZeros(value - 1) - 1); -+ } -+ -+ public static int roundFloorLog2(final int value) { -+ // optimized variant of 1 << (31 - leading(val)) -+ // given -+ // 1 << n = HIGH_BIT_32 >>> (31 - n) for n [0, 32) -+ // 1 << (31 - leading(val)) = HIGH_BIT_32 >> (31 - (31 - leading(val))) -+ // HIGH_BIT_32 >> (31 - (31 - leading(val))) -+ // HIGH_BIT_32 >> (31 - 31 + leading(val)) -+ return HIGH_BIT_U32 >>> Integer.numberOfLeadingZeros(value); -+ } -+ -+ public static long roundFloorLog2(final long value) { -+ // see logic documented above -+ return HIGH_BIT_U64 >>> Long.numberOfLeadingZeros(value); -+ } -+ -+ public static boolean isPowerOfTwo(final int n) { -+ // 2^n has one bit -+ // note: this rets true for 0 still -+ return IntegerUtil.getTrailingBit(n) == n; -+ } -+ -+ public static boolean isPowerOfTwo(final long n) { -+ // 2^n has one bit -+ // note: this rets true for 0 still -+ return IntegerUtil.getTrailingBit(n) == n; -+ } -+ -+ public static int getTrailingBit(final int n) { -+ return -n & n; -+ } -+ -+ public static long getTrailingBit(final long n) { -+ return -n & n; -+ } -+ -+ public static int trailingZeros(final int n) { -+ return Integer.numberOfTrailingZeros(n); -+ } -+ -+ public static int trailingZeros(final long n) { -+ return Long.numberOfTrailingZeros(n); -+ } -+ -+ // from hacker's delight (signed division magic value) -+ public static int getDivisorMultiple(final long numbers) { -+ return (int)(numbers >>> 32); -+ } -+ -+ // from hacker's delight (signed division magic value) -+ public static int getDivisorShift(final long numbers) { -+ return (int)numbers; -+ } -+ -+ // copied from hacker's delight (signed division magic value) -+ // http://www.hackersdelight.org/hdcodetxt/magic.c.txt -+ public static long getDivisorNumbers(final int d) { -+ final int ad = IntegerUtil.branchlessAbs(d); -+ -+ if (ad < 2) { -+ throw new IllegalArgumentException("|number| must be in [2, 2^31 -1], not: " + d); -+ } -+ -+ final int two31 = 0x80000000; -+ final long mask = 0xFFFFFFFFL; // mask for enforcing unsigned behaviour -+ -+ int p = 31; -+ -+ // all these variables are UNSIGNED! -+ int t = two31 + (d >>> 31); -+ int anc = t - 1 - t%ad; -+ int q1 = (int)((two31 & mask)/(anc & mask)); -+ int r1 = two31 - q1*anc; -+ int q2 = (int)((two31 & mask)/(ad & mask)); -+ int r2 = two31 - q2*ad; -+ int delta; -+ -+ do { -+ p = p + 1; -+ q1 = 2*q1; // Update q1 = 2**p/|nc|. -+ r1 = 2*r1; // Update r1 = rem(2**p, |nc|). -+ if ((r1 & mask) >= (anc & mask)) {// (Must be an unsigned comparison here) -+ q1 = q1 + 1; -+ r1 = r1 - anc; -+ } -+ q2 = 2*q2; // Update q2 = 2**p/|d|. -+ r2 = 2*r2; // Update r2 = rem(2**p, |d|). -+ if ((r2 & mask) >= (ad & mask)) {// (Must be an unsigned comparison here) -+ q2 = q2 + 1; -+ r2 = r2 - ad; -+ } -+ delta = ad - r2; -+ } while ((q1 & mask) < (delta & mask) || (q1 == delta && r1 == 0)); -+ -+ int magicNum = q2 + 1; -+ if (d < 0) { -+ magicNum = -magicNum; -+ } -+ int shift = p - 32; -+ return ((long)magicNum << 32) | shift; -+ } -+ -+ public static int branchlessAbs(final int val) { -+ // -n = -1 ^ n + 1 -+ final int mask = val >> (Integer.SIZE - 1); // -1 if < 0, 0 if >= 0 -+ return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 -+ } -+ -+ public static long branchlessAbs(final long val) { -+ // -n = -1 ^ n + 1 -+ final long mask = val >> (Long.SIZE - 1); // -1 if < 0, 0 if >= 0 -+ return (mask ^ val) - mask; // if val < 0, then (0 ^ val) - 0 else (-1 ^ val) + 1 -+ } -+ -+ //https://github.com/skeeto/hash-prospector for hash functions -+ -+ //score = ~590.47984224483832 -+ public static int hash0(int x) { -+ x *= 0x36935555; -+ x ^= x >>> 16; -+ return x; -+ } -+ -+ //score = ~310.01596637036749 -+ public static int hash1(int x) { -+ x ^= x >>> 15; -+ x *= 0x356aaaad; -+ x ^= x >>> 17; -+ return x; -+ } -+ -+ public static int hash2(int x) { -+ x ^= x >>> 16; -+ x *= 0x7feb352d; -+ x ^= x >>> 15; -+ x *= 0x846ca68b; -+ x ^= x >>> 16; -+ return x; -+ } -+ -+ public static int hash3(int x) { -+ x ^= x >>> 17; -+ x *= 0xed5ad4bb; -+ x ^= x >>> 11; -+ x *= 0xac4c1b51; -+ x ^= x >>> 15; -+ x *= 0x31848bab; -+ x ^= x >>> 14; -+ return x; -+ } -+ -+ //score = ~365.79959673201887 -+ public static long hash1(long x) { -+ x ^= x >>> 27; -+ x *= 0xb24924b71d2d354bL; -+ x ^= x >>> 28; -+ return x; -+ } -+ -+ //h2 hash -+ public static long hash2(long x) { -+ x ^= x >>> 32; -+ x *= 0xd6e8feb86659fd93L; -+ x ^= x >>> 32; -+ x *= 0xd6e8feb86659fd93L; -+ x ^= x >>> 32; -+ return x; -+ } -+ -+ public static long hash3(long x) { -+ x ^= x >>> 45; -+ x *= 0xc161abe5704b6c79L; -+ x ^= x >>> 41; -+ x *= 0xe3e5389aedbc90f7L; -+ x ^= x >>> 56; -+ x *= 0x1f9aba75a52db073L; -+ x ^= x >>> 53; -+ return x; -+ } -+ -+ private IntegerUtil() { -+ throw new RuntimeException(); -+ } -+} -diff --git a/src/main/java/com/tuinity/tuinity/util/IntervalledCounter.java b/src/main/java/com/tuinity/tuinity/util/IntervalledCounter.java -new file mode 100644 -index 0000000000000000000000000000000000000000..d2c7d2c7920324d7207225ed19484e804368489d ---- /dev/null -+++ b/src/main/java/com/tuinity/tuinity/util/IntervalledCounter.java -@@ -0,0 +1,100 @@ -+package com.tuinity.tuinity.util; -+ -+public final class IntervalledCounter { -+ -+ protected long[] times; -+ protected final long interval; -+ protected long minTime; -+ protected int sum; -+ protected int head; // inclusive -+ protected int tail; // exclusive -+ -+ public IntervalledCounter(final long interval) { -+ this.times = new long[8]; -+ this.interval = interval; -+ } -+ -+ public void updateCurrentTime() { -+ this.updateCurrentTime(System.nanoTime()); -+ } -+ -+ public void updateCurrentTime(final long currentTime) { -+ int sum = this.sum; -+ int head = this.head; -+ final int tail = this.tail; -+ final long minTime = currentTime - this.interval; -+ -+ final int arrayLen = this.times.length; -+ -+ // guard against overflow by using subtraction -+ while (head != tail && this.times[head] - minTime < 0) { -+ head = (head + 1) % arrayLen; -+ --sum; -+ } -+ -+ this.sum = sum; -+ this.head = head; -+ this.minTime = minTime; -+ } -+ -+ public void addTime(final long currTime) { -+ // guard against overflow by using subtraction -+ if (currTime - this.minTime < 0) { -+ return; -+ } -+ int nextTail = (this.tail + 1) % this.times.length; -+ if (nextTail == this.head) { -+ this.resize(); -+ nextTail = (this.tail + 1) % this.times.length; -+ } -+ -+ this.times[this.tail] = currTime; -+ this.tail = nextTail; -+ } -+ -+ public void updateAndAdd(final int count) { -+ final long currTime = System.nanoTime(); -+ this.updateCurrentTime(currTime); -+ for (int i = 0; i < count; ++i) { -+ this.addTime(currTime); -+ } -+ } -+ -+ public void updateAndAdd(final int count, final long currTime) { -+ this.updateCurrentTime(currTime); -+ for (int i = 0; i < count; ++i) { -+ this.addTime(currTime); -+ } -+ } -+ -+ private void resize() { -+ final long[] oldElements = this.times; -+ final long[] newElements = new long[this.times.length * 2]; -+ this.times = newElements; -+ -+ final int head = this.head; -+ final int tail = this.tail; -+ final int size = tail >= head ? (tail - head) : (tail + (oldElements.length - head)); -+ this.head = 0; -+ this.tail = size; -+ -+ if (tail >= head) { -+ System.arraycopy(oldElements, head, newElements, 0, size); -+ } else { -+ System.arraycopy(oldElements, head, newElements, 0, oldElements.length - head); -+ System.arraycopy(oldElements, 0, newElements, oldElements.length - head, tail); -+ } -+ } -+ -+ // returns in units per second -+ public double getRate() { -+ return this.size() / (this.interval * 1.0e-9); -+ } -+ -+ public int size() { -+ final int head = this.head; -+ final int tail = this.tail; -+ -+ return tail >= head ? (tail - head) : (tail + (this.times.length - head)); -+ } -+} -diff --git a/src/main/java/com/tuinity/tuinity/util/PoiAccess.java b/src/main/java/com/tuinity/tuinity/util/PoiAccess.java -new file mode 100644 -index 0000000000000000000000000000000000000000..e99583529a2cbdf8b764be3dff4373ec0ffaecd7 ---- /dev/null -+++ b/src/main/java/com/tuinity/tuinity/util/PoiAccess.java -@@ -0,0 +1,748 @@ -+package com.tuinity.tuinity.util; -+ -+import it.unimi.dsi.fastutil.doubles.Double2ObjectMap; -+import it.unimi.dsi.fastutil.doubles.Double2ObjectRBTreeMap; -+import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; -+import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -+import net.minecraft.core.BlockPos; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.ai.village.poi.PoiManager; -+import net.minecraft.world.entity.ai.village.poi.PoiRecord; -+import net.minecraft.world.entity.ai.village.poi.PoiSection; -+import net.minecraft.world.entity.ai.village.poi.PoiType; -+import java.util.ArrayList; -+import java.util.HashSet; -+import java.util.Iterator; -+import java.util.List; -+import java.util.Map; -+import java.util.Optional; -+import java.util.Set; -+import java.util.function.Predicate; -+ -+/** -+ * Provides optimised access to POI data. All returned values will be identical to vanilla. -+ */ -+public final class PoiAccess { -+ -+ protected static double clamp(final double val, final double min, final double max) { -+ return (val < min ? min : (val > max ? max : val)); -+ } -+ -+ protected static double getSmallestDistanceSquared(final double boxMinX, final double boxMinY, final double boxMinZ, -+ final double boxMaxX, final double boxMaxY, final double boxMaxZ, -+ -+ final double circleX, final double circleY, final double circleZ) { -+ // is the circle center inside the box? -+ if (circleX >= boxMinX && circleX <= boxMaxX && circleY >= boxMinY && circleY <= boxMaxY && circleZ >= boxMinZ && circleZ <= boxMaxZ) { -+ return 0.0; -+ } -+ -+ final double boxWidthX = (boxMaxX - boxMinX) / 2.0; -+ final double boxWidthY = (boxMaxY - boxMinY) / 2.0; -+ final double boxWidthZ = (boxMaxZ - boxMinZ) / 2.0; -+ -+ final double boxCenterX = (boxMinX + boxMaxX) / 2.0; -+ final double boxCenterY = (boxMinY + boxMaxY) / 2.0; -+ final double boxCenterZ = (boxMinZ + boxMaxZ) / 2.0; -+ -+ double centerDiffX = circleX - boxCenterX; -+ double centerDiffY = circleY - boxCenterY; -+ double centerDiffZ = circleZ - boxCenterZ; -+ -+ centerDiffX = circleX - (clamp(centerDiffX, -boxWidthX, boxWidthX) + boxCenterX); -+ centerDiffY = circleY - (clamp(centerDiffY, -boxWidthY, boxWidthY) + boxCenterY); -+ centerDiffZ = circleZ - (clamp(centerDiffZ, -boxWidthZ, boxWidthZ) + boxCenterZ); -+ -+ return (centerDiffX * centerDiffX) + (centerDiffY * centerDiffY) + (centerDiffZ * centerDiffZ); -+ } -+ -+ -+ // key is: -+ // upper 32 bits: -+ // upper 16 bits: max y section -+ // lower 16 bits: min y section -+ // lower 32 bits: -+ // upper 16 bits: section -+ // lower 16 bits: radius -+ protected static long getKey(final int minSection, final int maxSection, final int section, final int radius) { -+ return ( -+ (maxSection & 0xFFFFL) << (64 - 16) -+ | (minSection & 0xFFFFL) << (64 - 32) -+ | (section & 0xFFFFL) << (64 - 48) -+ | (radius & 0xFFFFL) << (64 - 64) -+ ); -+ } -+ -+ // only includes x/z axis -+ // finds the closest poi data by distance. -+ public static BlockPos findClosestPoiDataPosition(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load) { -+ final PoiRecord ret = findClosestPoiDataRecord( -+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load -+ ); -+ -+ return ret == null ? null : ret.getPos(); -+ } -+ -+ // only includes x/z axis -+ // finds the closest poi data by distance. if multiple match the same distance, then they all are returned. -+ public static void findClosestPoiDataPositions(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load, -+ final Set ret) { -+ final Set positions = new HashSet<>(); -+ // pos predicate is last thing that runs before adding to ret. -+ final Predicate newPredicate = (final BlockPos pos) -> { -+ if (positionPredicate != null && !positionPredicate.test(pos)) { -+ return false; -+ } -+ return positions.add(pos.immutable()); -+ }; -+ -+ final List toConvert = new ArrayList<>(); -+ findClosestPoiDataRecords( -+ poiStorage, villagePlaceType, newPredicate, sourcePosition, range, maxDistance, occupancy, load, toConvert -+ ); -+ -+ for (final PoiRecord record : toConvert) { -+ ret.add(record.getPos()); -+ } -+ } -+ -+ // only includes x/z axis -+ // finds the closest poi data by distance. -+ public static PoiRecord findClosestPoiDataRecord(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load) { -+ final List ret = new ArrayList<>(); -+ findClosestPoiDataRecords( -+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load, ret -+ ); -+ return ret.isEmpty() ? null : ret.get(0); -+ } -+ -+ // only includes x/z axis -+ // finds the closest poi data by distance. if multiple match the same distance, then they all are returned. -+ public static void findClosestPoiDataRecords(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load, -+ final List ret) { -+ final Predicate occupancyFilter = occupancy.getTest(); -+ -+ final List closestRecords = new ArrayList<>(); -+ double closestDistanceSquared = maxDistance * maxDistance; -+ -+ final int lowerX = Mth.floor(sourcePosition.getX() - range) >> 4; -+ final int lowerY = WorldUtil.getMinSection(poiStorage.world); -+ final int lowerZ = Mth.floor(sourcePosition.getZ() - range) >> 4; -+ final int upperX = Mth.floor(sourcePosition.getX() + range) >> 4; -+ final int upperY = WorldUtil.getMaxSection(poiStorage.world); -+ final int upperZ = Mth.floor(sourcePosition.getZ() + range) >> 4; -+ -+ final int centerX = sourcePosition.getX() >> 4; -+ final int centerY = sourcePosition.getY() >> 4; -+ final int centerZ = sourcePosition.getZ() >> 4; -+ -+ final LongArrayFIFOQueue queue = new LongArrayFIFOQueue(); -+ queue.enqueue(CoordinateUtils.getChunkSectionKey(centerX, centerY, centerZ)); -+ final LongOpenHashSet seen = new LongOpenHashSet(); -+ -+ while (!queue.isEmpty()) { -+ final long key = queue.dequeueLong(); -+ final int sectionX = CoordinateUtils.getChunkSectionX(key); -+ final int sectionY = CoordinateUtils.getChunkSectionY(key); -+ final int sectionZ = CoordinateUtils.getChunkSectionZ(key); -+ -+ if (sectionX < lowerX || sectionX > upperX || sectionY < lowerY || sectionY > upperY || sectionZ < lowerZ || sectionZ > upperZ) { -+ // out of bound chunk -+ continue; -+ } -+ -+ final double sectionDistanceSquared = getSmallestDistanceSquared( -+ (sectionX << 4) + 0.5, -+ (sectionY << 4) + 0.5, -+ (sectionZ << 4) + 0.5, -+ (sectionX << 4) + 15.5, -+ (sectionY << 4) + 15.5, -+ (sectionZ << 4) + 15.5, -+ (double)sourcePosition.getX(), (double)sourcePosition.getY(), (double)sourcePosition.getZ() -+ ); -+ if (sectionDistanceSquared > closestDistanceSquared) { -+ continue; -+ } -+ -+ // queue all neighbours -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ for (int dy = -1; dy <= 1; ++dy) { -+ // -1 and 1 have the 1st bit set. so just add up the first bits, and it will tell us how many -+ // values are set. we only care about cardinal neighbours, so, we only care if one value is set -+ if ((dx & 1) + (dy & 1) + (dz & 1) != 1) { -+ continue; -+ } -+ -+ final int neighbourX = sectionX + dx; -+ final int neighbourY = sectionY + dy; -+ final int neighbourZ = sectionZ + dz; -+ -+ final long neighbourKey = CoordinateUtils.getChunkSectionKey(neighbourX, neighbourY, neighbourZ); -+ if (seen.add(neighbourKey)) { -+ queue.enqueue(neighbourKey); -+ } -+ } -+ } -+ } -+ -+ final Optional poiSectionOptional = load ? poiStorage.getOrLoad(key) : poiStorage.get(key); -+ -+ if (poiSectionOptional == null || !poiSectionOptional.isPresent()) { -+ continue; -+ } -+ -+ final PoiSection poiSection = poiSectionOptional.orElse(null); -+ -+ final Map> sectionData = poiSection.getData(); -+ if (sectionData.isEmpty()) { -+ continue; -+ } -+ -+ // now we search the section data -+ for (final Map.Entry> entry : sectionData.entrySet()) { -+ if (!villagePlaceType.test(entry.getKey())) { -+ // filter out by poi type -+ continue; -+ } -+ -+ // now we can look at the poi data -+ for (final PoiRecord poiData : entry.getValue()) { -+ if (!occupancyFilter.test(poiData)) { -+ // filter by occupancy -+ continue; -+ } -+ -+ final BlockPos poiPosition = poiData.getPos(); -+ -+ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range -+ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) { -+ // out of range for square radius -+ continue; -+ } -+ -+ // it's important that it's poiPosition.distSqr(source) : the value actually is different IF the values are swapped! -+ final double dataRange = poiPosition.distSqr(sourcePosition); -+ -+ if (dataRange > closestDistanceSquared) { -+ // out of range for distance check -+ continue; -+ } -+ -+ if (positionPredicate != null && !positionPredicate.test(poiPosition)) { -+ // filter by position -+ continue; -+ } -+ -+ if (dataRange < closestDistanceSquared) { -+ closestRecords.clear(); -+ closestDistanceSquared = dataRange; -+ } -+ closestRecords.add(poiData); -+ } -+ } -+ } -+ -+ // uh oh! we might have multiple records that match the distance sorting! -+ // we need to re-order our results by the way vanilla would have iterated over them. -+ closestRecords.sort((record1, record2) -> { -+ // vanilla iterates the same way we do for data inside sections, so we know the ordering inside a section -+ // is fine and should be preserved (this sort is stable so we're good there) -+ // but they iterate sections by x then by z (like the following) -+ // for (int x = -dx; x <= dx; ++x) -+ // for (int z = -dz; z <= dz; ++z) -+ // .... -+ // so we need to reorder such that records with lower chunk z, then lower chunk x come first -+ final BlockPos pos1 = record1.getPos(); -+ final BlockPos pos2 = record2.getPos(); -+ -+ final int cx1 = pos1.getX() >> 4; -+ final int cz1 = pos1.getZ() >> 4; -+ -+ final int cx2 = pos2.getX() >> 4; -+ final int cz2 = pos2.getZ() >> 4; -+ -+ if (cz2 != cz1) { -+ // want smaller z -+ return Integer.compare(cz1, cz2); -+ } -+ -+ if (cx2 != cx1) { -+ // want smaller x -+ return Integer.compare(cx1, cx2); -+ } -+ -+ // same chunk -+ // once vanilla has the chunk, it will iterate from all of the chunk sections starting from smaller y -+ // so now we just compare section y, wanting smaller y -+ -+ return Integer.compare(pos1.getY() >> 4, pos2.getY() >> 4); -+ }); -+ -+ // now we match perfectly what vanilla would have outputted, without having to search the whole radius (hopefully). -+ ret.addAll(closestRecords); -+ } -+ -+ // finds the closest poi entry pos. -+ public static BlockPos findNearestPoiPosition(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load) { -+ final PoiRecord ret = findNearestPoiRecord( -+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load -+ ); -+ return ret == null ? null : ret.getPos(); -+ } -+ -+ // finds the closest `max` poi entry positions. -+ public static void findNearestPoiPositions(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load, -+ final int max, -+ final List ret) { -+ final Set positions = new HashSet<>(); -+ // pos predicate is last thing that runs before adding to ret. -+ final Predicate newPredicate = (final BlockPos pos) -> { -+ if (positionPredicate != null && !positionPredicate.test(pos)) { -+ return false; -+ } -+ return positions.add(pos.immutable()); -+ }; -+ -+ final List toConvert = new ArrayList<>(); -+ findNearestPoiRecords( -+ poiStorage, villagePlaceType, newPredicate, sourcePosition, range, maxDistance, occupancy, load, max, toConvert -+ ); -+ -+ for (final PoiRecord record : toConvert) { -+ ret.add(record.getPos()); -+ } -+ } -+ -+ // finds the closest poi entry. -+ public static PoiRecord findNearestPoiRecord(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load) { -+ final List ret = new ArrayList<>(); -+ findNearestPoiRecords( -+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load, -+ 1, ret -+ ); -+ return ret.isEmpty() ? null : ret.get(0); -+ } -+ -+ // finds the closest `max` poi entries. -+ public static void findNearestPoiRecords(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load, -+ final int max, -+ final List ret) { -+ final Predicate occupancyFilter = occupancy.getTest(); -+ -+ final double maxDistanceSquared = maxDistance * maxDistance; -+ final Double2ObjectRBTreeMap> closestRecords = new Double2ObjectRBTreeMap<>(); -+ int totalRecords = 0; -+ double furthestDistanceSquared = maxDistanceSquared; -+ -+ final int lowerX = Mth.floor(sourcePosition.getX() - range) >> 4; -+ final int lowerY = WorldUtil.getMinSection(poiStorage.world); -+ final int lowerZ = Mth.floor(sourcePosition.getZ() - range) >> 4; -+ final int upperX = Mth.floor(sourcePosition.getX() + range) >> 4; -+ final int upperY = WorldUtil.getMaxSection(poiStorage.world); -+ final int upperZ = Mth.floor(sourcePosition.getZ() + range) >> 4; -+ -+ final int centerX = sourcePosition.getX() >> 4; -+ final int centerY = sourcePosition.getY() >> 4; -+ final int centerZ = sourcePosition.getZ() >> 4; -+ -+ final LongArrayFIFOQueue queue = new LongArrayFIFOQueue(); -+ queue.enqueue(CoordinateUtils.getChunkSectionKey(centerX, centerY, centerZ)); -+ final LongOpenHashSet seen = new LongOpenHashSet(); -+ -+ while (!queue.isEmpty()) { -+ final long key = queue.dequeueLong(); -+ final int sectionX = CoordinateUtils.getChunkSectionX(key); -+ final int sectionY = CoordinateUtils.getChunkSectionY(key); -+ final int sectionZ = CoordinateUtils.getChunkSectionZ(key); -+ -+ if (sectionX < lowerX || sectionX > upperX || sectionY < lowerY || sectionY > upperY || sectionZ < lowerZ || sectionZ > upperZ) { -+ // out of bound chunk -+ continue; -+ } -+ -+ final double sectionDistanceSquared = getSmallestDistanceSquared( -+ (sectionX << 4) + 0.5, -+ (sectionY << 4) + 0.5, -+ (sectionZ << 4) + 0.5, -+ (sectionX << 4) + 15.5, -+ (sectionY << 4) + 15.5, -+ (sectionZ << 4) + 15.5, -+ (double) sourcePosition.getX(), (double) sourcePosition.getY(), (double) sourcePosition.getZ() -+ ); -+ -+ if (sectionDistanceSquared > (totalRecords >= max ? furthestDistanceSquared : maxDistanceSquared)) { -+ continue; -+ } -+ -+ // queue all neighbours -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ for (int dy = -1; dy <= 1; ++dy) { -+ // -1 and 1 have the 1st bit set. so just add up the first bits, and it will tell us how many -+ // values are set. we only care about cardinal neighbours, so, we only care if one value is set -+ if ((dx & 1) + (dy & 1) + (dz & 1) != 1) { -+ continue; -+ } -+ -+ final int neighbourX = sectionX + dx; -+ final int neighbourY = sectionY + dy; -+ final int neighbourZ = sectionZ + dz; -+ -+ final long neighbourKey = CoordinateUtils.getChunkSectionKey(neighbourX, neighbourY, neighbourZ); -+ if (seen.add(neighbourKey)) { -+ queue.enqueue(neighbourKey); -+ } -+ } -+ } -+ } -+ -+ final Optional poiSectionOptional = load ? poiStorage.getOrLoad(key) : poiStorage.get(key); -+ -+ if (poiSectionOptional == null || !poiSectionOptional.isPresent()) { -+ continue; -+ } -+ -+ final PoiSection poiSection = poiSectionOptional.orElse(null); -+ -+ final Map> sectionData = poiSection.getData(); -+ if (sectionData.isEmpty()) { -+ continue; -+ } -+ -+ // now we search the section data -+ for (final Map.Entry> entry : sectionData.entrySet()) { -+ if (!villagePlaceType.test(entry.getKey())) { -+ // filter out by poi type -+ continue; -+ } -+ -+ // now we can look at the poi data -+ for (final PoiRecord poiData : entry.getValue()) { -+ if (!occupancyFilter.test(poiData)) { -+ // filter by occupancy -+ continue; -+ } -+ -+ final BlockPos poiPosition = poiData.getPos(); -+ -+ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range -+ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) { -+ // out of range for square radius -+ continue; -+ } -+ -+ // it's important that it's poiPosition.distSqr(source) : the value actually is different IF the values are swapped! -+ final double dataRange = poiPosition.distSqr(sourcePosition); -+ -+ if (dataRange > maxDistanceSquared) { -+ // out of range for distance check -+ continue; -+ } -+ -+ if (dataRange > furthestDistanceSquared && totalRecords >= max) { -+ // out of range for distance check -+ continue; -+ } -+ -+ if (positionPredicate != null && !positionPredicate.test(poiPosition)) { -+ // filter by position -+ continue; -+ } -+ -+ if (dataRange > furthestDistanceSquared) { -+ // we know totalRecords < max, so this entry is now our furthest -+ furthestDistanceSquared = dataRange; -+ } -+ -+ closestRecords.computeIfAbsent(dataRange, (final double unused) -> { -+ return new ArrayList<>(); -+ }).add(poiData); -+ -+ if (++totalRecords >= max) { -+ if (closestRecords.size() >= 2) { -+ int entriesInClosest = 0; -+ final Iterator>> iterator = closestRecords.double2ObjectEntrySet().iterator(); -+ double nextFurthestDistanceSquared = 0.0; -+ -+ for (int i = 0, len = closestRecords.size() - 1; i < len; ++i) { -+ final Double2ObjectMap.Entry> recordEntry = iterator.next(); -+ entriesInClosest += recordEntry.getValue().size(); -+ nextFurthestDistanceSquared = recordEntry.getDoubleKey(); -+ } -+ -+ if (entriesInClosest >= max) { -+ // the last set of entries at range wont even be considered for sure... nuke em -+ final Double2ObjectMap.Entry> recordEntry = iterator.next(); -+ totalRecords -= recordEntry.getValue().size(); -+ iterator.remove(); -+ -+ furthestDistanceSquared = nextFurthestDistanceSquared; -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ final List closestRecordsUnsorted = new ArrayList<>(); -+ -+ // we're done here, so now just flatten the map and sort it. -+ -+ for (final List records : closestRecords.values()) { -+ closestRecordsUnsorted.addAll(records); -+ } -+ -+ // uh oh! we might have multiple records that match the distance sorting! -+ // we need to re-order our results by the way vanilla would have iterated over them. -+ closestRecordsUnsorted.sort((record1, record2) -> { -+ // vanilla iterates the same way we do for data inside sections, so we know the ordering inside a section -+ // is fine and should be preserved (this sort is stable so we're good there) -+ // but they iterate sections by x then by z (like the following) -+ // for (int x = -dx; x <= dx; ++x) -+ // for (int z = -dz; z <= dz; ++z) -+ // .... -+ // so we need to reorder such that records with lower chunk z, then lower chunk x come first -+ final BlockPos pos1 = record1.getPos(); -+ final BlockPos pos2 = record2.getPos(); -+ -+ final int cx1 = pos1.getX() >> 4; -+ final int cz1 = pos1.getZ() >> 4; -+ -+ final int cx2 = pos2.getX() >> 4; -+ final int cz2 = pos2.getZ() >> 4; -+ -+ if (cz2 != cz1) { -+ // want smaller z -+ return Integer.compare(cz1, cz2); -+ } -+ -+ if (cx2 != cx1) { -+ // want smaller x -+ return Integer.compare(cx1, cx2); -+ } -+ -+ // same chunk -+ // once vanilla has the chunk, it will iterate from all of the chunk sections starting from smaller y -+ // so now we just compare section y, wanting smaller section y -+ -+ return Integer.compare(pos1.getY() >> 4, pos2.getY() >> 4); -+ }); -+ -+ // trim out any entries exceeding our maximum -+ for (int i = closestRecordsUnsorted.size() - 1; i >= max; --i) { -+ closestRecordsUnsorted.remove(i); -+ } -+ -+ // now we match perfectly what vanilla would have outputted, without having to search the whole radius (hopefully). -+ ret.addAll(closestRecordsUnsorted); -+ } -+ -+ public static BlockPos findAnyPoiPosition(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final PoiManager.Occupancy occupancy, -+ final boolean load) { -+ final PoiRecord ret = findAnyPoiRecord( -+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, occupancy, load -+ ); -+ -+ return ret == null ? null : ret.getPos(); -+ } -+ -+ public static void findAnyPoiPositions(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final PoiManager.Occupancy occupancy, -+ final boolean load, -+ final int max, -+ final List ret) { -+ final Set positions = new HashSet<>(); -+ // pos predicate is last thing that runs before adding to ret. -+ final Predicate newPredicate = (final BlockPos pos) -> { -+ if (positionPredicate != null && !positionPredicate.test(pos)) { -+ return false; -+ } -+ return positions.add(pos.immutable()); -+ }; -+ -+ final List toConvert = new ArrayList<>(); -+ findAnyPoiRecords( -+ poiStorage, villagePlaceType, newPredicate, sourcePosition, range, occupancy, load, max, toConvert -+ ); -+ -+ for (final PoiRecord record : toConvert) { -+ ret.add(record.getPos()); -+ } -+ } -+ -+ public static PoiRecord findAnyPoiRecord(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final PoiManager.Occupancy occupancy, -+ final boolean load) { -+ final List ret = new ArrayList<>(); -+ findAnyPoiRecords(poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, occupancy, load, 1, ret); -+ return ret.isEmpty() ? null : ret.get(0); -+ } -+ -+ public static void findAnyPoiRecords(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final PoiManager.Occupancy occupancy, -+ final boolean load, -+ final int max, -+ final List ret) { -+ // the biggest issue with the original mojang implementation is that they chain so many streams together -+ // the amount of streams chained just rolls performance, even if nothing is iterated over -+ final Predicate occupancyFilter = occupancy.getTest(); -+ final double rangeSquared = range * range; -+ -+ int added = 0; -+ -+ // First up, we need to iterate the chunks -+ // all the values here are in chunk sections -+ final int lowerX = Mth.floor(sourcePosition.getX() - range) >> 4; -+ final int lowerY = Math.max(WorldUtil.getMinSection(poiStorage.world), Mth.floor(sourcePosition.getY() - range) >> 4); -+ final int lowerZ = Mth.floor(sourcePosition.getZ() - range) >> 4; -+ final int upperX = Mth.floor(sourcePosition.getX() + range) >> 4; -+ final int upperY = Math.min(WorldUtil.getMaxSection(poiStorage.world), Mth.floor(sourcePosition.getY() + range) >> 4); -+ final int upperZ = Mth.floor(sourcePosition.getZ() + range) >> 4; -+ -+ // Vanilla iterates by x until max is reached then increases z -+ // vanilla also searches by increasing Y section value -+ for (int currZ = lowerZ; currZ <= upperZ; ++currZ) { -+ for (int currX = lowerX; currX <= upperX; ++currX) { -+ for (int currY = lowerY; currY <= upperY; ++currY) { // vanilla searches the entire chunk because they're actually stupid. just search the sections we need -+ final Optional poiSectionOptional = load ? poiStorage.getOrLoad(CoordinateUtils.getChunkSectionKey(currX, currY, currZ)) : -+ poiStorage.get(CoordinateUtils.getChunkSectionKey(currX, currY, currZ)); -+ final PoiSection poiSection = poiSectionOptional == null ? null : poiSectionOptional.orElse(null); -+ if (poiSection == null) { -+ continue; -+ } -+ -+ final Map> sectionData = poiSection.getData(); -+ if (sectionData.isEmpty()) { -+ continue; -+ } -+ -+ // now we search the section data -+ for (final Map.Entry> entry : sectionData.entrySet()) { -+ if (!villagePlaceType.test(entry.getKey())) { -+ // filter out by poi type -+ continue; -+ } -+ -+ // now we can look at the poi data -+ for (final PoiRecord poiData : entry.getValue()) { -+ if (!occupancyFilter.test(poiData)) { -+ // filter by occupancy -+ continue; -+ } -+ -+ final BlockPos poiPosition = poiData.getPos(); -+ -+ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range -+ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) { -+ // out of range for square radius -+ continue; -+ } -+ -+ if (poiPosition.distSqr(sourcePosition) > rangeSquared) { -+ // out of range for distance check -+ continue; -+ } -+ -+ if (positionPredicate != null && !positionPredicate.test(poiPosition)) { -+ // filter by position -+ continue; -+ } -+ -+ // found one! -+ ret.add(poiData); -+ if (++added >= max) { -+ return; -+ } -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ private PoiAccess() { -+ throw new RuntimeException(); -+ } -+} -diff --git a/src/main/java/com/tuinity/tuinity/util/TickThread.java b/src/main/java/com/tuinity/tuinity/util/TickThread.java -new file mode 100644 -index 0000000000000000000000000000000000000000..a08377e4b0d9c2d78cf851e2c72770cf623de51a ---- /dev/null -+++ b/src/main/java/com/tuinity/tuinity/util/TickThread.java -@@ -0,0 +1,41 @@ -+package com.tuinity.tuinity.util; -+ -+import net.minecraft.server.MinecraftServer; -+import org.bukkit.Bukkit; -+ -+public final class TickThread extends Thread { -+ -+ public static final boolean STRICT_THREAD_CHECKS = Boolean.getBoolean("tuinity.strict-thread-checks"); -+ -+ static { -+ if (STRICT_THREAD_CHECKS) { -+ MinecraftServer.LOGGER.warn("Strict thread checks enabled - performance may suffer"); -+ } -+ } -+ -+ public static void softEnsureTickThread(final String reason) { -+ if (!STRICT_THREAD_CHECKS) { -+ return; -+ } -+ ensureTickThread(reason); -+ } -+ -+ -+ public static void ensureTickThread(final String reason) { -+ if (!Bukkit.isPrimaryThread()) { -+ MinecraftServer.LOGGER.fatal("Thread " + Thread.currentThread().getName() + " failed main thread check: " + reason, new Throwable()); -+ throw new IllegalStateException(reason); -+ } -+ } -+ -+ public final int id; /* We don't override getId as the spec requires that it be unique (with respect to all other threads) */ -+ -+ public TickThread(final Runnable run, final String name, final int id) { -+ super(run, name); -+ this.id = id; -+ } -+ -+ public static TickThread getCurrentTickThread() { -+ return (TickThread)Thread.currentThread(); -+ } -+} -diff --git a/src/main/java/com/tuinity/tuinity/util/WorldUtil.java b/src/main/java/com/tuinity/tuinity/util/WorldUtil.java -new file mode 100644 -index 0000000000000000000000000000000000000000..958b3aff3dda64323456d7e0ef0346a72d43f3f1 ---- /dev/null -+++ b/src/main/java/com/tuinity/tuinity/util/WorldUtil.java -@@ -0,0 +1,46 @@ -+package com.tuinity.tuinity.util; -+ -+import net.minecraft.world.level.LevelHeightAccessor; -+ -+public final class WorldUtil { -+ -+ // min, max are inclusive -+ -+ public static int getMaxSection(final LevelHeightAccessor world) { -+ return world.getMaxSection() - 1; // getMaxSection() is exclusive -+ } -+ -+ public static int getMinSection(final LevelHeightAccessor world) { -+ return world.getMinSection(); -+ } -+ -+ public static int getMaxLightSection(final LevelHeightAccessor world) { -+ return getMaxSection(world) + 1; -+ } -+ -+ public static int getMinLightSection(final LevelHeightAccessor world) { -+ return getMinSection(world) - 1; -+ } -+ -+ -+ -+ public static int getTotalSections(final LevelHeightAccessor world) { -+ return getMaxSection(world) - getMinSection(world) + 1; -+ } -+ -+ public static int getTotalLightSections(final LevelHeightAccessor world) { -+ return getMaxLightSection(world) - getMinLightSection(world) + 1; -+ } -+ -+ public static int getMinBlockY(final LevelHeightAccessor world) { -+ return getMinSection(world) << 4; -+ } -+ -+ public static int getMaxBlockY(final LevelHeightAccessor world) { -+ return (getMaxSection(world) << 4) | 15; -+ } -+ -+ private WorldUtil() { -+ throw new RuntimeException(); -+ } -+} -diff --git a/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java b/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java -new file mode 100644 -index 0000000000000000000000000000000000000000..98a1be343d81d6431476fea6a68014def8ce923b ---- /dev/null -+++ b/src/main/java/com/tuinity/tuinity/util/maplist/IteratorSafeOrderedReferenceSet.java -@@ -0,0 +1,334 @@ -+package com.tuinity.tuinity.util.maplist; -+ -+import it.unimi.dsi.fastutil.objects.Reference2IntLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.objects.Reference2IntMap; -+import org.bukkit.Bukkit; -+import java.util.Arrays; -+import java.util.NoSuchElementException; -+ -+public final class IteratorSafeOrderedReferenceSet { -+ -+ public static final int ITERATOR_FLAG_SEE_ADDITIONS = 1 << 0; -+ -+ protected final Reference2IntLinkedOpenHashMap indexMap; -+ protected int firstInvalidIndex = -1; -+ -+ /* list impl */ -+ protected E[] listElements; -+ protected int listSize; -+ -+ protected final double maxFragFactor; -+ -+ protected int iteratorCount; -+ -+ private final boolean threadRestricted; -+ -+ public IteratorSafeOrderedReferenceSet() { -+ this(16, 0.75f, 16, 0.2); -+ } -+ -+ public IteratorSafeOrderedReferenceSet(final boolean threadRestricted) { -+ this(16, 0.75f, 16, 0.2, threadRestricted); -+ } -+ -+ public IteratorSafeOrderedReferenceSet(final int setCapacity, final float setLoadFactor, final int arrayCapacity, -+ final double maxFragFactor) { -+ this(setCapacity, setLoadFactor, arrayCapacity, maxFragFactor, false); -+ } -+ public IteratorSafeOrderedReferenceSet(final int setCapacity, final float setLoadFactor, final int arrayCapacity, -+ final double maxFragFactor, final boolean threadRestricted) { -+ this.indexMap = new Reference2IntLinkedOpenHashMap<>(setCapacity, setLoadFactor); -+ this.indexMap.defaultReturnValue(-1); -+ this.maxFragFactor = maxFragFactor; -+ this.listElements = (E[])new Object[arrayCapacity]; -+ this.threadRestricted = threadRestricted; -+ } -+ -+ /* -+ public void check() { -+ int iterated = 0; -+ ReferenceOpenHashSet check = new ReferenceOpenHashSet<>(); -+ if (this.listElements != null) { -+ for (int i = 0; i < this.listSize; ++i) { -+ Object obj = this.listElements[i]; -+ if (obj != null) { -+ iterated++; -+ if (!check.add((E)obj)) { -+ throw new IllegalStateException("contains duplicate"); -+ } -+ if (!this.contains((E)obj)) { -+ throw new IllegalStateException("desync"); -+ } -+ } -+ } -+ } -+ -+ if (iterated != this.size()) { -+ throw new IllegalStateException("Size is mismatched! Got " + iterated + ", expected " + this.size()); -+ } -+ -+ check.clear(); -+ iterated = 0; -+ for (final java.util.Iterator iterator = this.unsafeIterator(IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { -+ final E element = iterator.next(); -+ iterated++; -+ if (!check.add(element)) { -+ throw new IllegalStateException("contains duplicate (iterator is wrong)"); -+ } -+ if (!this.contains(element)) { -+ throw new IllegalStateException("desync (iterator is wrong)"); -+ } -+ } -+ -+ if (iterated != this.size()) { -+ throw new IllegalStateException("Size is mismatched! (iterator is wrong) Got " + iterated + ", expected " + this.size()); -+ } -+ } -+ */ -+ -+ protected final boolean allowSafeIteration() { -+ return !this.threadRestricted || Bukkit.isPrimaryThread(); -+ } -+ -+ protected final double getFragFactor() { -+ return 1.0 - ((double)this.indexMap.size() / (double)this.listSize); -+ } -+ -+ public int createRawIterator() { -+ if (this.allowSafeIteration()) { -+ ++this.iteratorCount; -+ } -+ if (this.indexMap.isEmpty()) { -+ return -1; -+ } else { -+ return this.firstInvalidIndex == 0 ? this.indexMap.getInt(this.indexMap.firstKey()) : 0; -+ } -+ } -+ -+ public int advanceRawIterator(final int index) { -+ final E[] elements = this.listElements; -+ int ret = index + 1; -+ for (int len = this.listSize; ret < len; ++ret) { -+ if (elements[ret] != null) { -+ return ret; -+ } -+ } -+ -+ return -1; -+ } -+ -+ public void finishRawIterator() { -+ if (this.allowSafeIteration() && --this.iteratorCount == 0) { -+ if (this.getFragFactor() >= this.maxFragFactor) { -+ this.defrag(); -+ } -+ } -+ } -+ -+ public boolean remove(final E element) { -+ final int index = this.indexMap.removeInt(element); -+ if (index >= 0) { -+ if (this.firstInvalidIndex < 0 || index < this.firstInvalidIndex) { -+ this.firstInvalidIndex = index; -+ } -+ if (this.listElements[index] != element) { -+ throw new IllegalStateException(); -+ } -+ this.listElements[index] = null; -+ if (this.allowSafeIteration() && this.iteratorCount == 0 && this.getFragFactor() >= this.maxFragFactor) { -+ this.defrag(); -+ } -+ //this.check(); -+ return true; -+ } -+ return false; -+ } -+ -+ public boolean contains(final E element) { -+ return this.indexMap.containsKey(element); -+ } -+ -+ public boolean add(final E element) { -+ final int listSize = this.listSize; -+ -+ final int previous = this.indexMap.putIfAbsent(element, listSize); -+ if (previous != -1) { -+ return false; -+ } -+ -+ if (listSize >= this.listElements.length) { -+ this.listElements = Arrays.copyOf(this.listElements, listSize * 2); -+ } -+ this.listElements[listSize] = element; -+ this.listSize = listSize + 1; -+ -+ //this.check(); -+ return true; -+ } -+ -+ protected void defrag() { -+ if (this.firstInvalidIndex < 0) { -+ return; // nothing to do -+ } -+ -+ if (this.indexMap.isEmpty()) { -+ Arrays.fill(this.listElements, 0, this.listSize, null); -+ this.listSize = 0; -+ this.firstInvalidIndex = -1; -+ //this.check(); -+ return; -+ } -+ -+ final E[] backingArray = this.listElements; -+ -+ int lastValidIndex; -+ java.util.Iterator> iterator; -+ -+ if (this.firstInvalidIndex == 0) { -+ iterator = this.indexMap.reference2IntEntrySet().fastIterator(); -+ lastValidIndex = 0; -+ } else { -+ lastValidIndex = this.firstInvalidIndex; -+ final E key = backingArray[lastValidIndex - 1]; -+ iterator = this.indexMap.reference2IntEntrySet().fastIterator(new Reference2IntMap.Entry() { -+ @Override -+ public int getIntValue() { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public int setValue(int i) { -+ throw new UnsupportedOperationException(); -+ } -+ -+ @Override -+ public E getKey() { -+ return key; -+ } -+ }); -+ } -+ -+ while (iterator.hasNext()) { -+ final Reference2IntMap.Entry entry = iterator.next(); -+ -+ final int newIndex = lastValidIndex++; -+ backingArray[newIndex] = entry.getKey(); -+ entry.setValue(newIndex); -+ } -+ -+ // cleanup end -+ Arrays.fill(backingArray, lastValidIndex, this.listSize, null); -+ this.listSize = lastValidIndex; -+ this.firstInvalidIndex = -1; -+ //this.check(); -+ } -+ -+ public E rawGet(final int index) { -+ return this.listElements[index]; -+ } -+ -+ public int size() { -+ // always returns the correct amount - listSize can be different -+ return this.indexMap.size(); -+ } -+ -+ public IteratorSafeOrderedReferenceSet.Iterator iterator() { -+ return this.iterator(0); -+ } -+ -+ public IteratorSafeOrderedReferenceSet.Iterator iterator(final int flags) { -+ if (this.allowSafeIteration()) { -+ ++this.iteratorCount; -+ } -+ return new BaseIterator<>(this, true, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize); -+ } -+ -+ public java.util.Iterator unsafeIterator() { -+ return this.unsafeIterator(0); -+ } -+ public java.util.Iterator unsafeIterator(final int flags) { -+ return new BaseIterator<>(this, false, (flags & ITERATOR_FLAG_SEE_ADDITIONS) != 0 ? Integer.MAX_VALUE : this.listSize); -+ } -+ -+ public static interface Iterator extends java.util.Iterator { -+ -+ public void finishedIterating(); -+ -+ } -+ -+ protected static final class BaseIterator implements IteratorSafeOrderedReferenceSet.Iterator { -+ -+ protected final IteratorSafeOrderedReferenceSet set; -+ protected final boolean canFinish; -+ protected final int maxIndex; -+ protected int nextIndex; -+ protected E pendingValue; -+ protected boolean finished; -+ protected E lastReturned; -+ -+ protected BaseIterator(final IteratorSafeOrderedReferenceSet set, final boolean canFinish, final int maxIndex) { -+ this.set = set; -+ this.canFinish = canFinish; -+ this.maxIndex = maxIndex; -+ } -+ -+ @Override -+ public boolean hasNext() { -+ if (this.finished) { -+ return false; -+ } -+ if (this.pendingValue != null) { -+ return true; -+ } -+ -+ final E[] elements = this.set.listElements; -+ int index, len; -+ for (index = this.nextIndex, len = Math.min(this.maxIndex, this.set.listSize); index < len; ++index) { -+ final E element = elements[index]; -+ if (element != null) { -+ this.pendingValue = element; -+ this.nextIndex = index + 1; -+ return true; -+ } -+ } -+ -+ this.nextIndex = index; -+ return false; -+ } -+ -+ @Override -+ public E next() { -+ if (!this.hasNext()) { -+ throw new NoSuchElementException(); -+ } -+ final E ret = this.pendingValue; -+ -+ this.pendingValue = null; -+ this.lastReturned = ret; -+ -+ return ret; -+ } -+ -+ @Override -+ public void remove() { -+ final E lastReturned = this.lastReturned; -+ if (lastReturned == null) { -+ throw new IllegalStateException(); -+ } -+ this.lastReturned = null; -+ this.set.remove(lastReturned); -+ } -+ -+ @Override -+ public void finishedIterating() { -+ if (this.finished || !this.canFinish) { -+ throw new IllegalStateException(); -+ } -+ this.lastReturned = null; -+ this.finished = true; -+ if (this.set.allowSafeIteration()) { -+ this.set.finishRawIterator(); -+ } -+ } -+ } -+} -diff --git a/src/main/java/com/tuinity/tuinity/util/math/ThreadUnsafeRandom.java b/src/main/java/com/tuinity/tuinity/util/math/ThreadUnsafeRandom.java -new file mode 100644 -index 0000000000000000000000000000000000000000..7f2aeb5ed6775ddf38f2561aae8a82f99c8413a7 ---- /dev/null -+++ b/src/main/java/com/tuinity/tuinity/util/math/ThreadUnsafeRandom.java -@@ -0,0 +1,46 @@ -+package com.tuinity.tuinity.util.math; -+ -+import java.util.Random; -+ -+public final class ThreadUnsafeRandom extends Random { -+ -+ // See javadoc and internal comments for java.util.Random where these values come from, how they are used, and the author for them. -+ private static final long multiplier = 0x5DEECE66DL; -+ private static final long addend = 0xBL; -+ private static final long mask = (1L << 48) - 1; -+ -+ private static long initialScramble(long seed) { -+ return (seed ^ multiplier) & mask; -+ } -+ -+ private long seed; -+ -+ @Override -+ public void setSeed(long seed) { -+ // note: called by Random constructor -+ this.seed = initialScramble(seed); -+ } -+ -+ @Override -+ protected int next(int bits) { -+ // avoid the expensive CAS logic used by superclass -+ return (int) (((this.seed = this.seed * multiplier + addend) & mask) >>> (48 - bits)); -+ } -+ -+ // Taken from -+ // https://lemire.me/blog/2016/06/27/a-fast-alternative-to-the-modulo-reduction/ -+ // https://github.com/lemire/Code-used-on-Daniel-Lemire-s-blog/blob/master/2016/06/25/fastrange.c -+ // Original license is public domain -+ public static int fastRandomBounded(final long randomInteger, final long limit) { -+ // randomInteger must be [0, pow(2, 32)) -+ // limit must be [0, pow(2, 32)) -+ return (int)((randomInteger * limit) >>> 32); -+ } -+ -+ @Override -+ public int nextInt(int bound) { -+ // yes this breaks random's spec -+ // however there's nothing that uses this class that relies on it -+ return fastRandomBounded(this.next(32) & 0xFFFFFFFFL, bound); -+ } -+} -diff --git a/src/main/java/com/tuinity/tuinity/util/misc/Delayed26WayDistancePropagator3D.java b/src/main/java/com/tuinity/tuinity/util/misc/Delayed26WayDistancePropagator3D.java -new file mode 100644 -index 0000000000000000000000000000000000000000..b8fa9cd9bce312fc85b90e17094241216c620a9e ---- /dev/null -+++ b/src/main/java/com/tuinity/tuinity/util/misc/Delayed26WayDistancePropagator3D.java -@@ -0,0 +1,297 @@ -+package com.tuinity.tuinity.util.misc; -+ -+import com.tuinity.tuinity.util.CoordinateUtils; -+import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; -+import it.unimi.dsi.fastutil.longs.LongIterator; -+import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; -+ -+public final class Delayed26WayDistancePropagator3D { -+ -+ // this map is considered "stale" unless updates are propagated. -+ protected final Delayed8WayDistancePropagator2D.LevelMap levels = new Delayed8WayDistancePropagator2D.LevelMap(8192*2, 0.6f); -+ -+ // this map is never stale -+ protected final Long2ByteOpenHashMap sources = new Long2ByteOpenHashMap(4096, 0.6f); -+ -+ // Generally updates to positions are made close to other updates, so we link to decrease cache misses when -+ // propagating updates -+ protected final LongLinkedOpenHashSet updatedSources = new LongLinkedOpenHashSet(); -+ -+ @FunctionalInterface -+ public static interface LevelChangeCallback { -+ -+ /** -+ * This can be called for intermediate updates. So do not rely on newLevel being close to or -+ * the exact level that is expected after a full propagation has occured. -+ */ -+ public void onLevelUpdate(final long coordinate, final byte oldLevel, final byte newLevel); -+ -+ } -+ -+ protected final LevelChangeCallback changeCallback; -+ -+ public Delayed26WayDistancePropagator3D() { -+ this(null); -+ } -+ -+ public Delayed26WayDistancePropagator3D(final LevelChangeCallback changeCallback) { -+ this.changeCallback = changeCallback; -+ } -+ -+ public int getLevel(final long pos) { -+ return this.levels.get(pos); -+ } -+ -+ public int getLevel(final int x, final int y, final int z) { -+ return this.levels.get(CoordinateUtils.getChunkSectionKey(x, y, z)); -+ } -+ -+ public void setSource(final int x, final int y, final int z, final int level) { -+ this.setSource(CoordinateUtils.getChunkSectionKey(x, y, z), level); -+ } -+ -+ public void setSource(final long coordinate, final int level) { -+ if ((level & 63) != level || level == 0) { -+ throw new IllegalArgumentException("Level must be in (0, 63], not " + level); -+ } -+ -+ final byte byteLevel = (byte)level; -+ final byte oldLevel = this.sources.put(coordinate, byteLevel); -+ -+ if (oldLevel == byteLevel) { -+ return; // nothing to do -+ } -+ -+ // queue to update later -+ this.updatedSources.add(coordinate); -+ } -+ -+ public void removeSource(final int x, final int y, final int z) { -+ this.removeSource(CoordinateUtils.getChunkSectionKey(x, y, z)); -+ } -+ -+ public void removeSource(final long coordinate) { -+ if (this.sources.remove(coordinate) != 0) { -+ this.updatedSources.add(coordinate); -+ } -+ } -+ -+ // queues used for BFS propagating levels -+ protected final Delayed8WayDistancePropagator2D.WorkQueue[] levelIncreaseWorkQueues = new Delayed8WayDistancePropagator2D.WorkQueue[64]; -+ { -+ for (int i = 0; i < this.levelIncreaseWorkQueues.length; ++i) { -+ this.levelIncreaseWorkQueues[i] = new Delayed8WayDistancePropagator2D.WorkQueue(); -+ } -+ } -+ protected final Delayed8WayDistancePropagator2D.WorkQueue[] levelRemoveWorkQueues = new Delayed8WayDistancePropagator2D.WorkQueue[64]; -+ { -+ for (int i = 0; i < this.levelRemoveWorkQueues.length; ++i) { -+ this.levelRemoveWorkQueues[i] = new Delayed8WayDistancePropagator2D.WorkQueue(); -+ } -+ } -+ protected long levelIncreaseWorkQueueBitset; -+ protected long levelRemoveWorkQueueBitset; -+ -+ protected final void addToIncreaseWorkQueue(final long coordinate, final byte level) { -+ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[level]; -+ queue.queuedCoordinates.enqueue(coordinate); -+ queue.queuedLevels.enqueue(level); -+ -+ this.levelIncreaseWorkQueueBitset |= (1L << level); -+ } -+ -+ protected final void addToIncreaseWorkQueue(final long coordinate, final byte index, final byte level) { -+ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[index]; -+ queue.queuedCoordinates.enqueue(coordinate); -+ queue.queuedLevels.enqueue(level); -+ -+ this.levelIncreaseWorkQueueBitset |= (1L << index); -+ } -+ -+ protected final void addToRemoveWorkQueue(final long coordinate, final byte level) { -+ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelRemoveWorkQueues[level]; -+ queue.queuedCoordinates.enqueue(coordinate); -+ queue.queuedLevels.enqueue(level); -+ -+ this.levelRemoveWorkQueueBitset |= (1L << level); -+ } -+ -+ public boolean propagateUpdates() { -+ if (this.updatedSources.isEmpty()) { -+ return false; -+ } -+ -+ boolean ret = false; -+ -+ for (final LongIterator iterator = this.updatedSources.iterator(); iterator.hasNext();) { -+ final long coordinate = iterator.nextLong(); -+ -+ final byte currentLevel = this.levels.get(coordinate); -+ final byte updatedSource = this.sources.get(coordinate); -+ -+ if (currentLevel == updatedSource) { -+ continue; -+ } -+ ret = true; -+ -+ if (updatedSource > currentLevel) { -+ // level increase -+ this.addToIncreaseWorkQueue(coordinate, updatedSource); -+ } else { -+ // level decrease -+ this.addToRemoveWorkQueue(coordinate, currentLevel); -+ // if the current coordinate is a source, then the decrease propagation will detect that and queue -+ // the source propagation -+ } -+ } -+ -+ this.updatedSources.clear(); -+ -+ // propagate source level increases first for performance reasons (in crowded areas hopefully the additions -+ // make the removes remove less) -+ this.propagateIncreases(); -+ -+ // now we propagate the decreases (which will then re-propagate clobbered sources) -+ this.propagateDecreases(); -+ -+ return ret; -+ } -+ -+ protected void propagateIncreases() { -+ for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset); -+ this.levelIncreaseWorkQueueBitset != 0L; -+ this.levelIncreaseWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset)) { -+ -+ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelIncreaseWorkQueues[queueIndex]; -+ while (!queue.queuedLevels.isEmpty()) { -+ final long coordinate = queue.queuedCoordinates.removeFirstLong(); -+ byte level = queue.queuedLevels.removeFirstByte(); -+ -+ final boolean neighbourCheck = level < 0; -+ -+ final byte currentLevel; -+ if (neighbourCheck) { -+ level = (byte)-level; -+ currentLevel = this.levels.get(coordinate); -+ } else { -+ currentLevel = this.levels.putIfGreater(coordinate, level); -+ } -+ -+ if (neighbourCheck) { -+ // used when propagating from decrease to indicate that this level needs to check its neighbours -+ // this means the level at coordinate could be equal, but would still need neighbours checked -+ -+ if (currentLevel != level) { -+ // something caused the level to change, which means something propagated to it (which means -+ // us propagating here is redundant), or something removed the level (which means we -+ // cannot propagate further) -+ continue; -+ } -+ } else if (currentLevel >= level) { -+ // something higher/equal propagated -+ continue; -+ } -+ if (this.changeCallback != null) { -+ this.changeCallback.onLevelUpdate(coordinate, currentLevel, level); -+ } -+ -+ if (level == 1) { -+ // can't propagate 0 to neighbours -+ continue; -+ } -+ -+ // propagate to neighbours -+ final byte neighbourLevel = (byte)(level - 1); -+ final int x = CoordinateUtils.getChunkSectionX(coordinate); -+ final int y = CoordinateUtils.getChunkSectionY(coordinate); -+ final int z = CoordinateUtils.getChunkSectionZ(coordinate); -+ -+ for (int dy = -1; dy <= 1; ++dy) { -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ if ((dy | dz | dx) == 0) { -+ // already propagated to coordinate -+ continue; -+ } -+ -+ // sure we can check the neighbour level in the map right now and avoid a propagation, -+ // but then we would still have to recheck it when popping the value off of the queue! -+ // so just avoid the double lookup -+ final long neighbourCoordinate = CoordinateUtils.getChunkSectionKey(dx + x, dy + y, dz + z); -+ this.addToIncreaseWorkQueue(neighbourCoordinate, neighbourLevel); -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ protected void propagateDecreases() { -+ for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset); -+ this.levelRemoveWorkQueueBitset != 0L; -+ this.levelRemoveWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset)) { -+ -+ final Delayed8WayDistancePropagator2D.WorkQueue queue = this.levelRemoveWorkQueues[queueIndex]; -+ while (!queue.queuedLevels.isEmpty()) { -+ final long coordinate = queue.queuedCoordinates.removeFirstLong(); -+ final byte level = queue.queuedLevels.removeFirstByte(); -+ -+ final byte currentLevel = this.levels.removeIfGreaterOrEqual(coordinate, level); -+ if (currentLevel == 0) { -+ // something else removed -+ continue; -+ } -+ -+ if (currentLevel > level) { -+ // something higher propagated here or we hit the propagation of another source -+ // in the second case we need to re-propagate because we could have just clobbered another source's -+ // propagation -+ this.addToIncreaseWorkQueue(coordinate, currentLevel, (byte)-currentLevel); // indicate to the increase code that the level's neighbours need checking -+ continue; -+ } -+ -+ if (this.changeCallback != null) { -+ this.changeCallback.onLevelUpdate(coordinate, currentLevel, (byte)0); -+ } -+ -+ final byte source = this.sources.get(coordinate); -+ if (source != 0) { -+ // must re-propagate source later -+ this.addToIncreaseWorkQueue(coordinate, source); -+ } -+ -+ if (level == 0) { -+ // can't propagate -1 to neighbours -+ // we have to check neighbours for removing 1 just in case the neighbour is 2 -+ continue; -+ } -+ -+ // propagate to neighbours -+ final byte neighbourLevel = (byte)(level - 1); -+ final int x = CoordinateUtils.getChunkSectionX(coordinate); -+ final int y = CoordinateUtils.getChunkSectionY(coordinate); -+ final int z = CoordinateUtils.getChunkSectionZ(coordinate); -+ -+ for (int dy = -1; dy <= 1; ++dy) { -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ if ((dy | dz | dx) == 0) { -+ // already propagated to coordinate -+ continue; -+ } -+ -+ // sure we can check the neighbour level in the map right now and avoid a propagation, -+ // but then we would still have to recheck it when popping the value off of the queue! -+ // so just avoid the double lookup -+ final long neighbourCoordinate = CoordinateUtils.getChunkSectionKey(dx + x, dy + y, dz + z); -+ this.addToRemoveWorkQueue(neighbourCoordinate, neighbourLevel); -+ } -+ } -+ } -+ } -+ } -+ -+ // propagate sources we clobbered in the process -+ this.propagateIncreases(); -+ } -+} -diff --git a/src/main/java/com/tuinity/tuinity/util/misc/Delayed8WayDistancePropagator2D.java b/src/main/java/com/tuinity/tuinity/util/misc/Delayed8WayDistancePropagator2D.java -new file mode 100644 -index 0000000000000000000000000000000000000000..cdd3c4032c1d6b34a10ba415bd4d0e377aa9af3c ---- /dev/null -+++ b/src/main/java/com/tuinity/tuinity/util/misc/Delayed8WayDistancePropagator2D.java -@@ -0,0 +1,718 @@ -+package com.tuinity.tuinity.util.misc; -+ -+import it.unimi.dsi.fastutil.HashCommon; -+import it.unimi.dsi.fastutil.bytes.ByteArrayFIFOQueue; -+import it.unimi.dsi.fastutil.longs.Long2ByteOpenHashMap; -+import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; -+import it.unimi.dsi.fastutil.longs.LongIterator; -+import it.unimi.dsi.fastutil.longs.LongLinkedOpenHashSet; -+import net.minecraft.server.MCUtil; -+ -+public final class Delayed8WayDistancePropagator2D { -+ -+ // Test -+ /* -+ protected static void test(int x, int z, com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap reference, Delayed8WayDistancePropagator2D test) { -+ int got = test.getLevel(x, z); -+ -+ int expect = 0; -+ Object[] nearest = reference.getObjectsInRange(x, z) == null ? null : reference.getObjectsInRange(x, z).getBackingSet(); -+ if (nearest != null) { -+ for (Object _obj : nearest) { -+ if (_obj instanceof Ticket) { -+ Ticket ticket = (Ticket)_obj; -+ long ticketCoord = reference.getLastCoordinate(ticket); -+ int viewDistance = reference.getLastViewDistance(ticket); -+ int distance = Math.max(com.destroystokyo.paper.util.math.IntegerUtil.branchlessAbs(MCUtil.getCoordinateX(ticketCoord) - x), -+ com.destroystokyo.paper.util.math.IntegerUtil.branchlessAbs(MCUtil.getCoordinateZ(ticketCoord) - z)); -+ int level = viewDistance - distance; -+ if (level > expect) { -+ expect = level; -+ } -+ } -+ } -+ } -+ -+ if (expect != got) { -+ throw new IllegalStateException("Expected " + expect + " at pos (" + x + "," + z + ") but got " + got); -+ } -+ } -+ -+ static class Ticket { -+ -+ int x; -+ int z; -+ -+ final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet empty -+ = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<>(this); -+ -+ } -+ -+ public static void main(final String[] args) { -+ com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap reference = new com.destroystokyo.paper.util.misc.DistanceTrackingAreaMap() { -+ @Override -+ protected com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getEmptySetFor(Ticket object) { -+ return object.empty; -+ } -+ }; -+ Delayed8WayDistancePropagator2D test = new Delayed8WayDistancePropagator2D(); -+ -+ final int maxDistance = 64; -+ // test origin -+ { -+ Ticket originTicket = new Ticket(); -+ int originDistance = 31; -+ // test single source -+ reference.add(originTicket, 0, 0, originDistance); -+ test.setSource(0, 0, originDistance); test.propagateUpdates(); // set and propagate -+ for (int dx = -originDistance; dx <= originDistance; ++dx) { -+ for (int dz = -originDistance; dz <= originDistance; ++dz) { -+ test(dx, dz, reference, test); -+ } -+ } -+ // test single source decrease -+ reference.update(originTicket, 0, 0, originDistance/2); -+ test.setSource(0, 0, originDistance/2); test.propagateUpdates(); // set and propagate -+ for (int dx = -originDistance; dx <= originDistance; ++dx) { -+ for (int dz = -originDistance; dz <= originDistance; ++dz) { -+ test(dx, dz, reference, test); -+ } -+ } -+ // test source increase -+ originDistance = 2*originDistance; -+ reference.update(originTicket, 0, 0, originDistance); -+ test.setSource(0, 0, originDistance); test.propagateUpdates(); // set and propagate -+ for (int dx = -4*originDistance; dx <= 4*originDistance; ++dx) { -+ for (int dz = -4*originDistance; dz <= 4*originDistance; ++dz) { -+ test(dx, dz, reference, test); -+ } -+ } -+ -+ reference.remove(originTicket); -+ test.removeSource(0, 0); test.propagateUpdates(); -+ } -+ -+ // test multiple sources at origin -+ { -+ int originDistance = 31; -+ java.util.List list = new java.util.ArrayList<>(); -+ for (int i = 0; i < 10; ++i) { -+ Ticket a = new Ticket(); -+ list.add(a); -+ a.x = (i & 1) == 1 ? -i : i; -+ a.z = (i & 1) == 1 ? -i : i; -+ } -+ for (Ticket ticket : list) { -+ reference.add(ticket, ticket.x, ticket.z, originDistance); -+ test.setSource(ticket.x, ticket.z, originDistance); -+ } -+ test.propagateUpdates(); -+ -+ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) { -+ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) { -+ test(dx, dz, reference, test); -+ } -+ } -+ -+ // test ticket level decrease -+ -+ for (Ticket ticket : list) { -+ reference.update(ticket, ticket.x, ticket.z, originDistance/2); -+ test.setSource(ticket.x, ticket.z, originDistance/2); -+ } -+ test.propagateUpdates(); -+ -+ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) { -+ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) { -+ test(dx, dz, reference, test); -+ } -+ } -+ -+ // test ticket level increase -+ -+ for (Ticket ticket : list) { -+ reference.update(ticket, ticket.x, ticket.z, originDistance*2); -+ test.setSource(ticket.x, ticket.z, originDistance*2); -+ } -+ test.propagateUpdates(); -+ -+ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) { -+ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) { -+ test(dx, dz, reference, test); -+ } -+ } -+ -+ // test ticket remove -+ for (int i = 0, len = list.size(); i < len; ++i) { -+ if ((i & 3) != 0) { -+ continue; -+ } -+ Ticket ticket = list.get(i); -+ reference.remove(ticket); -+ test.removeSource(ticket.x, ticket.z); -+ } -+ test.propagateUpdates(); -+ -+ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) { -+ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) { -+ test(dx, dz, reference, test); -+ } -+ } -+ } -+ -+ // now test at coordinate offsets -+ // test offset -+ { -+ Ticket originTicket = new Ticket(); -+ int originDistance = 31; -+ int offX = 54432; -+ int offZ = -134567; -+ // test single source -+ reference.add(originTicket, offX, offZ, originDistance); -+ test.setSource(offX, offZ, originDistance); test.propagateUpdates(); // set and propagate -+ for (int dx = -originDistance; dx <= originDistance; ++dx) { -+ for (int dz = -originDistance; dz <= originDistance; ++dz) { -+ test(dx + offX, dz + offZ, reference, test); -+ } -+ } -+ // test single source decrease -+ reference.update(originTicket, offX, offZ, originDistance/2); -+ test.setSource(offX, offZ, originDistance/2); test.propagateUpdates(); // set and propagate -+ for (int dx = -originDistance; dx <= originDistance; ++dx) { -+ for (int dz = -originDistance; dz <= originDistance; ++dz) { -+ test(dx + offX, dz + offZ, reference, test); -+ } -+ } -+ // test source increase -+ originDistance = 2*originDistance; -+ reference.update(originTicket, offX, offZ, originDistance); -+ test.setSource(offX, offZ, originDistance); test.propagateUpdates(); // set and propagate -+ for (int dx = -4*originDistance; dx <= 4*originDistance; ++dx) { -+ for (int dz = -4*originDistance; dz <= 4*originDistance; ++dz) { -+ test(dx + offX, dz + offZ, reference, test); -+ } -+ } -+ -+ reference.remove(originTicket); -+ test.removeSource(offX, offZ); test.propagateUpdates(); -+ } -+ -+ // test multiple sources at origin -+ { -+ int originDistance = 31; -+ int offX = 54432; -+ int offZ = -134567; -+ java.util.List list = new java.util.ArrayList<>(); -+ for (int i = 0; i < 10; ++i) { -+ Ticket a = new Ticket(); -+ list.add(a); -+ a.x = offX + ((i & 1) == 1 ? -i : i); -+ a.z = offZ + ((i & 1) == 1 ? -i : i); -+ } -+ for (Ticket ticket : list) { -+ reference.add(ticket, ticket.x, ticket.z, originDistance); -+ test.setSource(ticket.x, ticket.z, originDistance); -+ } -+ test.propagateUpdates(); -+ -+ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) { -+ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) { -+ test(dx, dz, reference, test); -+ } -+ } -+ -+ // test ticket level decrease -+ -+ for (Ticket ticket : list) { -+ reference.update(ticket, ticket.x, ticket.z, originDistance/2); -+ test.setSource(ticket.x, ticket.z, originDistance/2); -+ } -+ test.propagateUpdates(); -+ -+ for (int dx = -8*originDistance; dx <= 8*originDistance; ++dx) { -+ for (int dz = -8*originDistance; dz <= 8*originDistance; ++dz) { -+ test(dx, dz, reference, test); -+ } -+ } -+ -+ // test ticket level increase -+ -+ for (Ticket ticket : list) { -+ reference.update(ticket, ticket.x, ticket.z, originDistance*2); -+ test.setSource(ticket.x, ticket.z, originDistance*2); -+ } -+ test.propagateUpdates(); -+ -+ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) { -+ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) { -+ test(dx, dz, reference, test); -+ } -+ } -+ -+ // test ticket remove -+ for (int i = 0, len = list.size(); i < len; ++i) { -+ if ((i & 3) != 0) { -+ continue; -+ } -+ Ticket ticket = list.get(i); -+ reference.remove(ticket); -+ test.removeSource(ticket.x, ticket.z); -+ } -+ test.propagateUpdates(); -+ -+ for (int dx = -16*originDistance; dx <= 16*originDistance; ++dx) { -+ for (int dz = -16*originDistance; dz <= 16*originDistance; ++dz) { -+ test(dx, dz, reference, test); -+ } -+ } -+ } -+ } -+ */ -+ -+ // this map is considered "stale" unless updates are propagated. -+ protected final LevelMap levels = new LevelMap(8192*2, 0.6f); -+ -+ // this map is never stale -+ protected final Long2ByteOpenHashMap sources = new Long2ByteOpenHashMap(4096, 0.6f); -+ -+ // Generally updates to positions are made close to other updates, so we link to decrease cache misses when -+ // propagating updates -+ protected final LongLinkedOpenHashSet updatedSources = new LongLinkedOpenHashSet(); -+ -+ @FunctionalInterface -+ public static interface LevelChangeCallback { -+ -+ /** -+ * This can be called for intermediate updates. So do not rely on newLevel being close to or -+ * the exact level that is expected after a full propagation has occured. -+ */ -+ public void onLevelUpdate(final long coordinate, final byte oldLevel, final byte newLevel); -+ -+ } -+ -+ protected final LevelChangeCallback changeCallback; -+ -+ public Delayed8WayDistancePropagator2D() { -+ this(null); -+ } -+ -+ public Delayed8WayDistancePropagator2D(final LevelChangeCallback changeCallback) { -+ this.changeCallback = changeCallback; -+ } -+ -+ public int getLevel(final long pos) { -+ return this.levels.get(pos); -+ } -+ -+ public int getLevel(final int x, final int z) { -+ return this.levels.get(MCUtil.getCoordinateKey(x, z)); -+ } -+ -+ public void setSource(final int x, final int z, final int level) { -+ this.setSource(MCUtil.getCoordinateKey(x, z), level); -+ } -+ -+ public void setSource(final long coordinate, final int level) { -+ if ((level & 63) != level || level == 0) { -+ throw new IllegalArgumentException("Level must be in (0, 63], not " + level); -+ } -+ -+ final byte byteLevel = (byte)level; -+ final byte oldLevel = this.sources.put(coordinate, byteLevel); -+ -+ if (oldLevel == byteLevel) { -+ return; // nothing to do -+ } -+ -+ // queue to update later -+ this.updatedSources.add(coordinate); -+ } -+ -+ public void removeSource(final int x, final int z) { -+ this.removeSource(MCUtil.getCoordinateKey(x, z)); -+ } -+ -+ public void removeSource(final long coordinate) { -+ if (this.sources.remove(coordinate) != 0) { -+ this.updatedSources.add(coordinate); -+ } -+ } -+ -+ // queues used for BFS propagating levels -+ protected final WorkQueue[] levelIncreaseWorkQueues = new WorkQueue[64]; -+ { -+ for (int i = 0; i < this.levelIncreaseWorkQueues.length; ++i) { -+ this.levelIncreaseWorkQueues[i] = new WorkQueue(); -+ } -+ } -+ protected final WorkQueue[] levelRemoveWorkQueues = new WorkQueue[64]; -+ { -+ for (int i = 0; i < this.levelRemoveWorkQueues.length; ++i) { -+ this.levelRemoveWorkQueues[i] = new WorkQueue(); -+ } -+ } -+ protected long levelIncreaseWorkQueueBitset; -+ protected long levelRemoveWorkQueueBitset; -+ -+ protected final void addToIncreaseWorkQueue(final long coordinate, final byte level) { -+ final WorkQueue queue = this.levelIncreaseWorkQueues[level]; -+ queue.queuedCoordinates.enqueue(coordinate); -+ queue.queuedLevels.enqueue(level); -+ -+ this.levelIncreaseWorkQueueBitset |= (1L << level); -+ } -+ -+ protected final void addToIncreaseWorkQueue(final long coordinate, final byte index, final byte level) { -+ final WorkQueue queue = this.levelIncreaseWorkQueues[index]; -+ queue.queuedCoordinates.enqueue(coordinate); -+ queue.queuedLevels.enqueue(level); -+ -+ this.levelIncreaseWorkQueueBitset |= (1L << index); -+ } -+ -+ protected final void addToRemoveWorkQueue(final long coordinate, final byte level) { -+ final WorkQueue queue = this.levelRemoveWorkQueues[level]; -+ queue.queuedCoordinates.enqueue(coordinate); -+ queue.queuedLevels.enqueue(level); -+ -+ this.levelRemoveWorkQueueBitset |= (1L << level); -+ } -+ -+ public boolean propagateUpdates() { -+ if (this.updatedSources.isEmpty()) { -+ return false; -+ } -+ -+ boolean ret = false; -+ -+ for (final LongIterator iterator = this.updatedSources.iterator(); iterator.hasNext();) { -+ final long coordinate = iterator.nextLong(); -+ -+ final byte currentLevel = this.levels.get(coordinate); -+ final byte updatedSource = this.sources.get(coordinate); -+ -+ if (currentLevel == updatedSource) { -+ continue; -+ } -+ ret = true; -+ -+ if (updatedSource > currentLevel) { -+ // level increase -+ this.addToIncreaseWorkQueue(coordinate, updatedSource); -+ } else { -+ // level decrease -+ this.addToRemoveWorkQueue(coordinate, currentLevel); -+ // if the current coordinate is a source, then the decrease propagation will detect that and queue -+ // the source propagation -+ } -+ } -+ -+ this.updatedSources.clear(); -+ -+ // propagate source level increases first for performance reasons (in crowded areas hopefully the additions -+ // make the removes remove less) -+ this.propagateIncreases(); -+ -+ // now we propagate the decreases (which will then re-propagate clobbered sources) -+ this.propagateDecreases(); -+ -+ return ret; -+ } -+ -+ protected void propagateIncreases() { -+ for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset); -+ this.levelIncreaseWorkQueueBitset != 0L; -+ this.levelIncreaseWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelIncreaseWorkQueueBitset)) { -+ -+ final WorkQueue queue = this.levelIncreaseWorkQueues[queueIndex]; -+ while (!queue.queuedLevels.isEmpty()) { -+ final long coordinate = queue.queuedCoordinates.removeFirstLong(); -+ byte level = queue.queuedLevels.removeFirstByte(); -+ -+ final boolean neighbourCheck = level < 0; -+ -+ final byte currentLevel; -+ if (neighbourCheck) { -+ level = (byte)-level; -+ currentLevel = this.levels.get(coordinate); -+ } else { -+ currentLevel = this.levels.putIfGreater(coordinate, level); -+ } -+ -+ if (neighbourCheck) { -+ // used when propagating from decrease to indicate that this level needs to check its neighbours -+ // this means the level at coordinate could be equal, but would still need neighbours checked -+ -+ if (currentLevel != level) { -+ // something caused the level to change, which means something propagated to it (which means -+ // us propagating here is redundant), or something removed the level (which means we -+ // cannot propagate further) -+ continue; -+ } -+ } else if (currentLevel >= level) { -+ // something higher/equal propagated -+ continue; -+ } -+ if (this.changeCallback != null) { -+ this.changeCallback.onLevelUpdate(coordinate, currentLevel, level); -+ } -+ -+ if (level == 1) { -+ // can't propagate 0 to neighbours -+ continue; -+ } -+ -+ // propagate to neighbours -+ final byte neighbourLevel = (byte)(level - 1); -+ final int x = (int)coordinate; -+ final int z = (int)(coordinate >>> 32); -+ -+ for (int dx = -1; dx <= 1; ++dx) { -+ for (int dz = -1; dz <= 1; ++dz) { -+ if ((dx | dz) == 0) { -+ // already propagated to coordinate -+ continue; -+ } -+ -+ // sure we can check the neighbour level in the map right now and avoid a propagation, -+ // but then we would still have to recheck it when popping the value off of the queue! -+ // so just avoid the double lookup -+ final long neighbourCoordinate = MCUtil.getCoordinateKey(x + dx, z + dz); -+ this.addToIncreaseWorkQueue(neighbourCoordinate, neighbourLevel); -+ } -+ } -+ } -+ } -+ } -+ -+ protected void propagateDecreases() { -+ for (int queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset); -+ this.levelRemoveWorkQueueBitset != 0L; -+ this.levelRemoveWorkQueueBitset ^= (1L << queueIndex), queueIndex = 63 ^ Long.numberOfLeadingZeros(this.levelRemoveWorkQueueBitset)) { -+ -+ final WorkQueue queue = this.levelRemoveWorkQueues[queueIndex]; -+ while (!queue.queuedLevels.isEmpty()) { -+ final long coordinate = queue.queuedCoordinates.removeFirstLong(); -+ final byte level = queue.queuedLevels.removeFirstByte(); -+ -+ final byte currentLevel = this.levels.removeIfGreaterOrEqual(coordinate, level); -+ if (currentLevel == 0) { -+ // something else removed -+ continue; -+ } -+ -+ if (currentLevel > level) { -+ // something higher propagated here or we hit the propagation of another source -+ // in the second case we need to re-propagate because we could have just clobbered another source's -+ // propagation -+ this.addToIncreaseWorkQueue(coordinate, currentLevel, (byte)-currentLevel); // indicate to the increase code that the level's neighbours need checking -+ continue; -+ } -+ -+ if (this.changeCallback != null) { -+ this.changeCallback.onLevelUpdate(coordinate, currentLevel, (byte)0); -+ } -+ -+ final byte source = this.sources.get(coordinate); -+ if (source != 0) { -+ // must re-propagate source later -+ this.addToIncreaseWorkQueue(coordinate, source); -+ } -+ -+ if (level == 0) { -+ // can't propagate -1 to neighbours -+ // we have to check neighbours for removing 1 just in case the neighbour is 2 -+ continue; -+ } -+ -+ // propagate to neighbours -+ final byte neighbourLevel = (byte)(level - 1); -+ final int x = (int)coordinate; -+ final int z = (int)(coordinate >>> 32); -+ -+ for (int dx = -1; dx <= 1; ++dx) { -+ for (int dz = -1; dz <= 1; ++dz) { -+ if ((dx | dz) == 0) { -+ // already propagated to coordinate -+ continue; -+ } -+ -+ // sure we can check the neighbour level in the map right now and avoid a propagation, -+ // but then we would still have to recheck it when popping the value off of the queue! -+ // so just avoid the double lookup -+ final long neighbourCoordinate = MCUtil.getCoordinateKey(x + dx, z + dz); -+ this.addToRemoveWorkQueue(neighbourCoordinate, neighbourLevel); -+ } -+ } -+ } -+ } -+ -+ // propagate sources we clobbered in the process -+ this.propagateIncreases(); -+ } -+ -+ protected static final class LevelMap extends Long2ByteOpenHashMap { -+ public LevelMap() { -+ super(); -+ } -+ -+ public LevelMap(final int expected, final float loadFactor) { -+ super(expected, loadFactor); -+ } -+ -+ // copied from superclass -+ private int find(final long k) { -+ if (k == 0L) { -+ return this.containsNullKey ? this.n : -(this.n + 1); -+ } else { -+ final long[] key = this.key; -+ long curr; -+ int pos; -+ if ((curr = key[pos = (int)HashCommon.mix(k) & this.mask]) == 0L) { -+ return -(pos + 1); -+ } else if (k == curr) { -+ return pos; -+ } else { -+ while((curr = key[pos = pos + 1 & this.mask]) != 0L) { -+ if (k == curr) { -+ return pos; -+ } -+ } -+ -+ return -(pos + 1); -+ } -+ } -+ } -+ -+ // copied from superclass -+ private void insert(final int pos, final long k, final byte v) { -+ if (pos == this.n) { -+ this.containsNullKey = true; -+ } -+ -+ this.key[pos] = k; -+ this.value[pos] = v; -+ if (this.size++ >= this.maxFill) { -+ this.rehash(HashCommon.arraySize(this.size + 1, this.f)); -+ } -+ } -+ -+ // copied from superclass -+ public byte putIfGreater(final long key, final byte value) { -+ final int pos = this.find(key); -+ if (pos < 0) { -+ if (this.defRetValue < value) { -+ this.insert(-pos - 1, key, value); -+ } -+ return this.defRetValue; -+ } else { -+ final byte curr = this.value[pos]; -+ if (value > curr) { -+ this.value[pos] = value; -+ return curr; -+ } -+ return curr; -+ } -+ } -+ -+ // copied from superclass -+ private void removeEntry(final int pos) { -+ --this.size; -+ this.shiftKeys(pos); -+ if (this.n > this.minN && this.size < this.maxFill / 4 && this.n > 16) { -+ this.rehash(this.n / 2); -+ } -+ } -+ -+ // copied from superclass -+ private void removeNullEntry() { -+ this.containsNullKey = false; -+ --this.size; -+ if (this.n > this.minN && this.size < this.maxFill / 4 && this.n > 16) { -+ this.rehash(this.n / 2); -+ } -+ } -+ -+ // copied from superclass -+ public byte removeIfGreaterOrEqual(final long key, final byte value) { -+ if (key == 0L) { -+ if (!this.containsNullKey) { -+ return this.defRetValue; -+ } -+ final byte current = this.value[this.n]; -+ if (value >= current) { -+ this.removeNullEntry(); -+ return current; -+ } -+ return current; -+ } else { -+ long[] keys = this.key; -+ byte[] values = this.value; -+ long curr; -+ int pos; -+ if ((curr = keys[pos = (int)HashCommon.mix(key) & this.mask]) == 0L) { -+ return this.defRetValue; -+ } else if (key == curr) { -+ final byte current = values[pos]; -+ if (value >= current) { -+ this.removeEntry(pos); -+ return current; -+ } -+ return current; -+ } else { -+ while((curr = keys[pos = pos + 1 & this.mask]) != 0L) { -+ if (key == curr) { -+ final byte current = values[pos]; -+ if (value >= current) { -+ this.removeEntry(pos); -+ return current; -+ } -+ return current; -+ } -+ } -+ -+ return this.defRetValue; -+ } -+ } -+ } -+ } -+ -+ protected static final class WorkQueue { -+ -+ public final NoResizeLongArrayFIFODeque queuedCoordinates = new NoResizeLongArrayFIFODeque(); -+ public final NoResizeByteArrayFIFODeque queuedLevels = new NoResizeByteArrayFIFODeque(); -+ -+ } -+ -+ protected static final class NoResizeLongArrayFIFODeque extends LongArrayFIFOQueue { -+ -+ /** -+ * Assumes non-empty. If empty, undefined behaviour. -+ */ -+ public long removeFirstLong() { -+ // copied from superclass -+ long t = this.array[this.start]; -+ if (++this.start == this.length) { -+ this.start = 0; -+ } -+ -+ return t; -+ } -+ } -+ -+ protected static final class NoResizeByteArrayFIFODeque extends ByteArrayFIFOQueue { -+ -+ /** -+ * Assumes non-empty. If empty, undefined behaviour. -+ */ -+ public byte removeFirstByte() { -+ // copied from superclass -+ byte t = this.array[this.start]; -+ if (++this.start == this.length) { -+ this.start = 0; -+ } -+ -+ return t; -+ } -+ } -+} -diff --git a/src/main/java/com/tuinity/tuinity/util/table/ZeroCollidingReferenceStateTable.java b/src/main/java/com/tuinity/tuinity/util/table/ZeroCollidingReferenceStateTable.java -new file mode 100644 -index 0000000000000000000000000000000000000000..3bd1bfab37c8a3b981c86ff09941590f028d24bc ---- /dev/null -+++ b/src/main/java/com/tuinity/tuinity/util/table/ZeroCollidingReferenceStateTable.java -@@ -0,0 +1,160 @@ -+package com.tuinity.tuinity.util.table; -+ -+import com.google.common.collect.Table; -+import net.minecraft.world.level.block.state.StateHolder; -+import net.minecraft.world.level.block.state.properties.Property; -+import java.util.Collection; -+import java.util.HashSet; -+import java.util.Map; -+import java.util.Set; -+ -+public final class ZeroCollidingReferenceStateTable { -+ -+ // upper 32 bits: starting index -+ // lower 32 bits: bitset for contained ids -+ protected final long[] this_index_table; -+ protected final Comparable[] this_table; -+ protected final StateHolder this_state; -+ -+ protected long[] index_table; -+ protected StateHolder[][] value_table; -+ -+ public ZeroCollidingReferenceStateTable(final StateHolder state, final Map, Comparable> this_map) { -+ this.this_state = state; -+ this.this_index_table = this.create_table(this_map.keySet()); -+ -+ int max_id = -1; -+ for (final Property property : this_map.keySet()) { -+ final int id = lookup_vindex(property, this.this_index_table); -+ if (id > max_id) { -+ max_id = id; -+ } -+ } -+ -+ this.this_table = new Comparable[max_id + 1]; -+ for (final Map.Entry, Comparable> entry : this_map.entrySet()) { -+ this.this_table[lookup_vindex(entry.getKey(), this.this_index_table)] = entry.getValue(); -+ } -+ } -+ -+ public void loadInTable(final Table, Comparable, StateHolder> table, -+ final Map, Comparable> this_map) { -+ final Set> combined = new HashSet<>(table.rowKeySet()); -+ combined.addAll(this_map.keySet()); -+ -+ this.index_table = this.create_table(combined); -+ -+ int max_id = -1; -+ for (final Property property : combined) { -+ final int id = lookup_vindex(property, this.index_table); -+ if (id > max_id) { -+ max_id = id; -+ } -+ } -+ -+ this.value_table = new StateHolder[max_id + 1][]; -+ -+ final Map, Map, StateHolder>> map = table.rowMap(); -+ for (final Property property : map.keySet()) { -+ final Map, StateHolder> propertyMap = map.get(property); -+ -+ final int id = lookup_vindex(property, this.index_table); -+ final StateHolder[] states = this.value_table[id] = new StateHolder[property.getPossibleValues().size()]; -+ -+ for (final Map.Entry, StateHolder> entry : propertyMap.entrySet()) { -+ if (entry.getValue() == null) { -+ // TODO what -+ continue; -+ } -+ -+ states[((Property)property).getIdFor(entry.getKey())] = entry.getValue(); -+ } -+ } -+ -+ -+ for (final Map.Entry, Comparable> entry : this_map.entrySet()) { -+ final Property property = entry.getKey(); -+ final int index = lookup_vindex(property, this.index_table); -+ -+ if (this.value_table[index] == null) { -+ this.value_table[index] = new StateHolder[property.getPossibleValues().size()]; -+ } -+ -+ this.value_table[index][((Property)property).getIdFor(entry.getValue())] = this.this_state; -+ } -+ } -+ -+ -+ protected long[] create_table(final Collection> collection) { -+ int max_id = -1; -+ for (final Property property : collection) { -+ final int id = property.getId(); -+ if (id > max_id) { -+ max_id = id; -+ } -+ } -+ -+ final long[] ret = new long[((max_id + 1) + 31) >>> 5]; // ceil((max_id + 1) / 32) -+ -+ for (final Property property : collection) { -+ final int id = property.getId(); -+ -+ ret[id >>> 5] |= (1L << (id & 31)); -+ } -+ -+ int total = 0; -+ for (int i = 1, len = ret.length; i < len; ++i) { -+ ret[i] |= (long)(total += Long.bitCount(ret[i - 1] & 0xFFFFFFFFL)) << 32; -+ } -+ -+ return ret; -+ } -+ -+ public Comparable get(final Property state) { -+ final Comparable[] table = this.this_table; -+ final int index = lookup_vindex(state, this.this_index_table); -+ -+ if (index < 0 || index >= table.length) { -+ return null; -+ } -+ return table[index]; -+ } -+ -+ public StateHolder get(final Property property, final Comparable with) { -+ final int withId = ((Property)property).getIdFor(with); -+ if (withId < 0) { -+ return null; -+ } -+ -+ final int index = lookup_vindex(property, this.index_table); -+ final StateHolder[][] table = this.value_table; -+ if (index < 0 || index >= table.length) { -+ return null; -+ } -+ -+ final StateHolder[] values = table[index]; -+ -+ if (withId >= values.length) { -+ return null; -+ } -+ -+ return values[withId]; -+ } -+ -+ protected static int lookup_vindex(final Property property, final long[] index_table) { -+ final int id = property.getId(); -+ final long bitset_mask = (1L << (id & 31)); -+ final long lower_mask = bitset_mask - 1; -+ final int index = id >>> 5; -+ if (index >= index_table.length) { -+ return -1; -+ } -+ final long index_value = index_table[index]; -+ final long contains_check = ((index_value & bitset_mask) - 1) >> (Long.SIZE - 1); // -1L if doesn't contain -+ -+ // index = total bits set in lower table values (upper 32 bits of index_value) plus total bits set in lower indices below id -+ // contains_check is 0 if the bitset had id set, else it's -1: so index is unaffected if contains_check == 0, -+ // otherwise it comes out as -1. -+ return (int)(((index_value >>> 32) + Long.bitCount(index_value & lower_mask)) | contains_check); -+ } -+} -diff --git a/src/main/java/com/tuinity/tuinity/voxel/AABBVoxelShape.java b/src/main/java/com/tuinity/tuinity/voxel/AABBVoxelShape.java -new file mode 100644 -index 0000000000000000000000000000000000000000..370c070dedd169fe85ad2cb488dae7aa1dcf28fc ---- /dev/null -+++ b/src/main/java/com/tuinity/tuinity/voxel/AABBVoxelShape.java -@@ -0,0 +1,200 @@ -+package com.tuinity.tuinity.voxel; -+ -+import com.tuinity.tuinity.util.CollisionUtil; -+import it.unimi.dsi.fastutil.doubles.DoubleArrayList; -+import it.unimi.dsi.fastutil.doubles.DoubleList; -+import net.minecraft.core.Direction; -+import net.minecraft.world.phys.AABB; -+import net.minecraft.world.phys.shapes.Shapes; -+import net.minecraft.world.phys.shapes.VoxelShape; -+import java.util.ArrayList; -+import java.util.List; -+ -+public final class AABBVoxelShape extends VoxelShape { -+ -+ public final AABB aabb; -+ -+ public AABBVoxelShape(AABB aabb) { -+ super(Shapes.getFullUnoptimisedCube().shape); -+ this.aabb = aabb; -+ } -+ -+ @Override -+ public boolean isEmpty() { -+ return CollisionUtil.isEmpty(this.aabb); -+ } -+ -+ @Override -+ public double min(Direction.Axis enumdirection_enumaxis) { -+ switch (enumdirection_enumaxis.ordinal()) { -+ case 0: -+ return this.aabb.minX; -+ case 1: -+ return this.aabb.minY; -+ case 2: -+ return this.aabb.minZ; -+ default: -+ throw new IllegalStateException("Unknown axis requested"); -+ } -+ } -+ -+ @Override -+ public double max(Direction.Axis enumdirection_enumaxis) { -+ switch (enumdirection_enumaxis.ordinal()) { -+ case 0: -+ return this.aabb.maxX; -+ case 1: -+ return this.aabb.maxY; -+ case 2: -+ return this.aabb.maxZ; -+ default: -+ throw new IllegalStateException("Unknown axis requested"); -+ } -+ } -+ -+ @Override -+ public AABB bounds() { -+ return this.aabb; -+ } -+ -+ // enum direction axis is from 0 -> 2, so we keep the lower bits for direction axis. -+ @Override -+ protected double get(Direction.Axis enumdirection_enumaxis, int i) { -+ switch (enumdirection_enumaxis.ordinal() | (i << 2)) { -+ case (0 | (0 << 2)): -+ return this.aabb.minX; -+ case (1 | (0 << 2)): -+ return this.aabb.minY; -+ case (2 | (0 << 2)): -+ return this.aabb.minZ; -+ case (0 | (1 << 2)): -+ return this.aabb.maxX; -+ case (1 | (1 << 2)): -+ return this.aabb.maxY; -+ case (2 | (1 << 2)): -+ return this.aabb.maxZ; -+ default: -+ throw new IllegalStateException("Unknown axis requested"); -+ } -+ } -+ -+ private DoubleList cachedListX; -+ private DoubleList cachedListY; -+ private DoubleList cachedListZ; -+ -+ @Override -+ protected DoubleList getCoords(Direction.Axis enumdirection_enumaxis) { -+ switch (enumdirection_enumaxis.ordinal()) { -+ case 0: -+ return this.cachedListX == null ? this.cachedListX = DoubleArrayList.wrap(new double[] { this.aabb.minX, this.aabb.maxX }) : this.cachedListX; -+ case 1: -+ return this.cachedListY == null ? this.cachedListY = DoubleArrayList.wrap(new double[] { this.aabb.minY, this.aabb.maxY }) : this.cachedListY; -+ case 2: -+ return this.cachedListZ == null ? this.cachedListZ = DoubleArrayList.wrap(new double[] { this.aabb.minZ, this.aabb.maxZ }) : this.cachedListZ; -+ default: -+ throw new IllegalStateException("Unknown axis requested"); -+ } -+ } -+ -+ @Override -+ public VoxelShape move(double d0, double d1, double d2) { -+ return new AABBVoxelShape(this.aabb.move(d0, d1, d2)); -+ } -+ -+ @Override -+ public VoxelShape optimize() { -+ if (this.isEmpty()) { -+ return Shapes.empty(); -+ } else if (this == Shapes.BLOCK_OPTIMISED || this.aabb.equals(Shapes.BLOCK_OPTIMISED.aabb)) { -+ return Shapes.BLOCK_OPTIMISED; -+ } -+ return this; -+ } -+ -+ @Override -+ public void forAllBoxes(Shapes.DoubleLineConsumer voxelshapes_a) { -+ voxelshapes_a.consume(this.aabb.minX, this.aabb.minY, this.aabb.minZ, this.aabb.maxX, this.aabb.maxY, this.aabb.maxZ); -+ } -+ -+ @Override -+ public List toAabbs() { // getAABBs -+ List ret = new ArrayList<>(1); -+ ret.add(this.aabb); -+ return ret; -+ } -+ -+ @Override -+ protected int findIndex(Direction.Axis enumdirection_enumaxis, double d0) { // findPointIndexAfterOffset -+ switch (enumdirection_enumaxis.ordinal()) { -+ case 0: -+ return d0 < this.aabb.maxX ? (d0 < this.aabb.minX ? -1 : 0) : 1; -+ case 1: -+ return d0 < this.aabb.maxY ? (d0 < this.aabb.minY ? -1 : 0) : 1; -+ case 2: -+ return d0 < this.aabb.maxZ ? (d0 < this.aabb.minZ ? -1 : 0) : 1; -+ default: -+ throw new IllegalStateException("Unknown axis requested"); -+ } -+ } -+ -+ @Override -+ protected VoxelShape calculateFace(Direction direction) { -+ if (this.isEmpty()) { -+ return Shapes.empty(); -+ } -+ if (this == Shapes.BLOCK_OPTIMISED) { -+ return this; -+ } -+ switch (direction) { -+ case EAST: // +X -+ case WEST: { // -X -+ final double from = direction == Direction.EAST ? 1.0 - CollisionUtil.COLLISION_EPSILON : CollisionUtil.COLLISION_EPSILON; -+ if (from > this.aabb.maxX || this.aabb.minX > from) { -+ return Shapes.empty(); -+ } -+ return new AABBVoxelShape(new AABB(0.0, this.aabb.minY, this.aabb.minZ, 1.0, this.aabb.maxY, this.aabb.maxZ)).optimize(); -+ } -+ case UP: // +Y -+ case DOWN: { // -Y -+ final double from = direction == Direction.UP ? 1.0 - CollisionUtil.COLLISION_EPSILON : CollisionUtil.COLLISION_EPSILON; -+ if (from > this.aabb.maxY || this.aabb.minY > from) { -+ return Shapes.empty(); -+ } -+ return new AABBVoxelShape(new AABB(this.aabb.minX, 0.0, this.aabb.minZ, this.aabb.maxX, 1.0, this.aabb.maxZ)).optimize(); -+ } -+ case SOUTH: // +Z -+ case NORTH: { // -Z -+ final double from = direction == Direction.SOUTH ? 1.0 - CollisionUtil.COLLISION_EPSILON : CollisionUtil.COLLISION_EPSILON; -+ if (from > this.aabb.maxZ || this.aabb.minZ > from) { -+ return Shapes.empty(); -+ } -+ return new AABBVoxelShape(new AABB(this.aabb.minX, this.aabb.minY, 0.0, this.aabb.maxX, this.aabb.maxY, 1.0)).optimize(); -+ } -+ default: { -+ throw new IllegalStateException("Unknown axis requested"); -+ } -+ } -+ } -+ -+ @Override -+ public double collide(Direction.Axis enumdirection_enumaxis, AABB axisalignedbb, double d0) { -+ if (CollisionUtil.isEmpty(this.aabb) || CollisionUtil.isEmpty(axisalignedbb)) { -+ return d0; -+ } -+ switch (enumdirection_enumaxis.ordinal()) { -+ case 0: -+ return CollisionUtil.collideX(this.aabb, axisalignedbb, d0); -+ case 1: -+ return CollisionUtil.collideY(this.aabb, axisalignedbb, d0); -+ case 2: -+ return CollisionUtil.collideZ(this.aabb, axisalignedbb, d0); -+ default: -+ throw new IllegalStateException("Unknown axis requested"); -+ } -+ } -+ -+ @Override -+ public boolean intersects(AABB axisalingedbb) { -+ return CollisionUtil.voxelShapeIntersect(this.aabb, axisalingedbb); -+ } -+} -diff --git a/src/main/java/com/tuinity/tuinity/world/ChunkEntitySlices.java b/src/main/java/com/tuinity/tuinity/world/ChunkEntitySlices.java -new file mode 100644 -index 0000000000000000000000000000000000000000..ad711b6c0628a9cd93ff0d5484769807e5e5b9c0 ---- /dev/null -+++ b/src/main/java/com/tuinity/tuinity/world/ChunkEntitySlices.java -@@ -0,0 +1,500 @@ -+package com.tuinity.tuinity.world; -+ -+import com.destroystokyo.paper.util.maplist.EntityList; -+import it.unimi.dsi.fastutil.objects.Reference2ObjectMap; -+import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.entity.boss.EnderDragonPart; -+import net.minecraft.world.entity.boss.enderdragon.EnderDragon; -+import net.minecraft.world.phys.AABB; -+import java.util.Arrays; -+import java.util.Iterator; -+import java.util.List; -+import java.util.function.Predicate; -+ -+public final class ChunkEntitySlices { -+ -+ protected final int minSection; -+ protected final int maxSection; -+ protected final int chunkX; -+ protected final int chunkZ; -+ protected final ServerLevel world; -+ -+ protected final EntityCollectionBySection allEntities; -+ protected final EntityCollectionBySection hardCollidingEntities; -+ protected final Reference2ObjectOpenHashMap, EntityCollectionBySection> entitiesByClass; -+ protected final EntityList entities = new EntityList(); -+ -+ public ChunkHolder.FullChunkStatus status; -+ -+ // TODO implement container search optimisations -+ -+ public ChunkEntitySlices(final ServerLevel world, final int chunkX, final int chunkZ, final ChunkHolder.FullChunkStatus status, -+ final int minSection, final int maxSection) { // inclusive, inclusive -+ this.minSection = minSection; -+ this.maxSection = maxSection; -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.world = world; -+ -+ this.allEntities = new EntityCollectionBySection(this); -+ this.hardCollidingEntities = new EntityCollectionBySection(this); -+ this.entitiesByClass = new Reference2ObjectOpenHashMap<>(); -+ -+ this.status = status; -+ } -+ -+ // Tuinity start - optimise CraftChunk#getEntities -+ public org.bukkit.entity.Entity[] getChunkEntities() { -+ List ret = new java.util.ArrayList<>(); -+ final Entity[] entities = this.entities.getRawData(); -+ for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) { -+ final Entity entity = entities[i]; -+ if (entity == null) { -+ continue; -+ } -+ final org.bukkit.entity.Entity bukkit = entity.getBukkitEntity(); -+ if (bukkit != null && bukkit.isValid()) { -+ ret.add(bukkit); -+ } -+ } -+ -+ return ret.toArray(new org.bukkit.entity.Entity[0]); -+ } -+ // Tuinity end - optimise CraftChunk#getEntities -+ -+ public boolean isEmpty() { -+ return this.entities.size() == 0; -+ } -+ -+ private void updateTicketLevels() { -+ final Entity[] entities = this.entities.getRawData(); -+ for (int i = 0, size = Math.min(entities.length, this.entities.size()); i < size; ++i) { -+ final Entity entity = entities[i]; -+ entity.chunkStatus = this.status; -+ } -+ } -+ -+ public synchronized void updateStatus(final ChunkHolder.FullChunkStatus status) { -+ this.status = status; -+ this.updateTicketLevels(); -+ } -+ -+ public synchronized void addEntity(final Entity entity, final int chunkSection) { -+ if (!this.entities.add(entity)) { -+ return; -+ } -+ entity.chunkStatus = this.status; -+ final int sectionIndex = chunkSection - this.minSection; -+ -+ this.allEntities.addEntity(entity, sectionIndex); -+ -+ if (entity.hardCollides()) { -+ this.hardCollidingEntities.addEntity(entity, sectionIndex); -+ } -+ -+ for (final Iterator, EntityCollectionBySection>> iterator = -+ this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { -+ final Reference2ObjectMap.Entry, EntityCollectionBySection> entry = iterator.next(); -+ -+ if (entry.getKey().isInstance(entity)) { -+ entry.getValue().addEntity(entity, sectionIndex); -+ } -+ } -+ } -+ -+ public synchronized void removeEntity(final Entity entity, final int chunkSection) { -+ if (!this.entities.remove(entity)) { -+ return; -+ } -+ entity.chunkStatus = ChunkHolder.FullChunkStatus.INACCESSIBLE; -+ final int sectionIndex = chunkSection - this.minSection; -+ -+ this.allEntities.removeEntity(entity, sectionIndex); -+ -+ if (entity.hardCollides()) { -+ this.hardCollidingEntities.removeEntity(entity, sectionIndex); -+ } -+ -+ for (final Iterator, EntityCollectionBySection>> iterator = -+ this.entitiesByClass.reference2ObjectEntrySet().fastIterator(); iterator.hasNext();) { -+ final Reference2ObjectMap.Entry, EntityCollectionBySection> entry = iterator.next(); -+ -+ if (entry.getKey().isInstance(entity)) { -+ entry.getValue().removeEntity(entity, sectionIndex); -+ } -+ } -+ } -+ -+ public void getHardCollidingEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ this.hardCollidingEntities.getEntities(except, box, into, predicate); -+ } -+ -+ public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ this.allEntities.getEntitiesWithEnderDragonParts(except, box, into, predicate); -+ } -+ -+ public void getEntities(final EntityType type, final AABB box, final List into, -+ final Predicate predicate) { -+ this.allEntities.getEntities(type, box, (List)into, (Predicate)predicate); -+ } -+ -+ protected EntityCollectionBySection initClass(final Class clazz) { -+ final EntityCollectionBySection ret = new EntityCollectionBySection(this); -+ -+ for (int sectionIndex = 0; sectionIndex < this.allEntities.entitiesBySection.length; ++sectionIndex) { -+ final BasicEntityList sectionEntities = this.allEntities.entitiesBySection[sectionIndex]; -+ if (sectionEntities == null) { -+ continue; -+ } -+ -+ final Entity[] storage = sectionEntities.storage; -+ -+ for (int i = 0, len = Math.min(storage.length, sectionEntities.size()); i < len; ++i) { -+ final Entity entity = storage[i]; -+ -+ if (clazz.isInstance(entity)) { -+ ret.addEntity(entity, sectionIndex); -+ } -+ } -+ } -+ -+ return ret; -+ } -+ -+ public void getEntities(final Class clazz, final Entity except, final AABB box, final List into, -+ final Predicate predicate) { -+ EntityCollectionBySection collection = this.entitiesByClass.get(clazz); -+ if (collection != null) { -+ collection.getEntitiesWithEnderDragonParts(except, clazz, box, (List)into, (Predicate)predicate); -+ } else { -+ synchronized (this) { -+ this.entitiesByClass.putIfAbsent(clazz, collection = this.initClass(clazz)); -+ } -+ collection.getEntitiesWithEnderDragonParts(except, clazz, box, (List)into, (Predicate)predicate); -+ } -+ } -+ -+ public synchronized void updateEntity(final Entity entity) { -+ /*// TODO -+ if (prev aabb != entity.getBoundingBox()) { -+ this.entityMap.delete(entity, prev aabb); -+ this.entityMap.insert(entity, prev aabb = entity.getBoundingBox()); -+ }*/ -+ } -+ -+ protected static final class BasicEntityList { -+ -+ protected static final Entity[] EMPTY = new Entity[0]; -+ protected static final int DEFAULT_CAPACITY = 4; -+ -+ protected E[] storage; -+ protected int size; -+ -+ public BasicEntityList() { -+ this(0); -+ } -+ -+ public BasicEntityList(final int cap) { -+ this.storage = (E[])(cap <= 0 ? EMPTY : new Entity[cap]); -+ } -+ -+ public boolean isEmpty() { -+ return this.size == 0; -+ } -+ -+ public int size() { -+ return this.size; -+ } -+ -+ private void resize() { -+ if (this.storage == EMPTY) { -+ this.storage = (E[])new Entity[DEFAULT_CAPACITY]; -+ } else { -+ this.storage = Arrays.copyOf(this.storage, this.storage.length * 2); -+ } -+ } -+ -+ public void add(final E entity) { -+ final int idx = this.size++; -+ if (idx >= this.storage.length) { -+ this.resize(); -+ this.storage[idx] = entity; -+ } else { -+ this.storage[idx] = entity; -+ } -+ } -+ -+ public int indexOf(final E entity) { -+ final E[] storage = this.storage; -+ -+ for (int i = 0, len = Math.min(this.storage.length, this.size); i < len; ++i) { -+ if (storage[i] == entity) { -+ return i; -+ } -+ } -+ -+ return -1; -+ } -+ -+ public boolean remove(final E entity) { -+ final int idx = this.indexOf(entity); -+ if (idx == -1) { -+ return false; -+ } -+ -+ final int size = --this.size; -+ final E[] storage = this.storage; -+ if (idx != size) { -+ System.arraycopy(storage, idx + 1, storage, idx, size - idx); -+ } -+ -+ storage[size] = null; -+ -+ return true; -+ } -+ -+ public boolean has(final E entity) { -+ return this.indexOf(entity) != -1; -+ } -+ } -+ -+ protected static final class EntityCollectionBySection { -+ -+ protected final ChunkEntitySlices manager; -+ protected final long[] nonEmptyBitset; -+ protected final BasicEntityList[] entitiesBySection; -+ protected int count; -+ -+ public EntityCollectionBySection(final ChunkEntitySlices manager) { -+ this.manager = manager; -+ -+ final int sectionCount = manager.maxSection - manager.minSection + 1; -+ -+ this.nonEmptyBitset = new long[(sectionCount + (Long.SIZE - 1)) >>> 6]; // (sectionCount + (Long.SIZE - 1)) / Long.SIZE -+ this.entitiesBySection = new BasicEntityList[sectionCount]; -+ } -+ -+ public void addEntity(final Entity entity, final int sectionIndex) { -+ BasicEntityList list = this.entitiesBySection[sectionIndex]; -+ -+ if (list != null && list.has(entity)) { -+ return; -+ } -+ -+ if (list == null) { -+ this.entitiesBySection[sectionIndex] = list = new BasicEntityList<>(); -+ this.nonEmptyBitset[sectionIndex >>> 6] |= (1L << (sectionIndex & (Long.SIZE - 1))); -+ } -+ -+ list.add(entity); -+ ++this.count; -+ } -+ -+ public void removeEntity(final Entity entity, final int sectionIndex) { -+ final BasicEntityList list = this.entitiesBySection[sectionIndex]; -+ -+ if (list == null || !list.remove(entity)) { -+ return; -+ } -+ -+ --this.count; -+ -+ if (list.isEmpty()) { -+ this.entitiesBySection[sectionIndex] = null; -+ this.nonEmptyBitset[sectionIndex >>> 6] ^= (1L << (sectionIndex & (Long.SIZE - 1))); -+ } -+ } -+ -+ public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ if (this.count == 0) { -+ return; -+ } -+ -+ final int minSection = this.manager.minSection; -+ final int maxSection = this.manager.maxSection; -+ -+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); -+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); -+ -+ // TODO use the bitset -+ -+ final BasicEntityList[] entitiesBySection = this.entitiesBySection; -+ -+ for (int section = min; section <= max; ++section) { -+ final BasicEntityList list = entitiesBySection[section - minSection]; -+ -+ if (list == null) { -+ continue; -+ } -+ -+ final Entity[] storage = list.storage; -+ -+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { -+ final Entity entity = storage[i]; -+ -+ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { -+ continue; -+ } -+ -+ if (predicate != null && !predicate.test(entity)) { -+ continue; -+ } -+ -+ into.add(entity); -+ } -+ } -+ } -+ -+ public void getEntitiesWithEnderDragonParts(final Entity except, final AABB box, final List into, -+ final Predicate predicate) { -+ if (this.count == 0) { -+ return; -+ } -+ -+ final int minSection = this.manager.minSection; -+ final int maxSection = this.manager.maxSection; -+ -+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); -+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); -+ -+ // TODO use the bitset -+ -+ final BasicEntityList[] entitiesBySection = this.entitiesBySection; -+ -+ for (int section = min; section <= max; ++section) { -+ final BasicEntityList list = entitiesBySection[section - minSection]; -+ -+ if (list == null) { -+ continue; -+ } -+ -+ final Entity[] storage = list.storage; -+ -+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { -+ final Entity entity = storage[i]; -+ -+ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { -+ continue; -+ } -+ -+ if (predicate == null || predicate.test(entity)) { -+ into.add(entity); -+ } // else: continue to test the ender dragon parts -+ -+ if (entity instanceof EnderDragon) { -+ for (final EnderDragonPart part : ((EnderDragon)entity).subEntities) { -+ if (part == except || !part.getBoundingBox().intersects(box)) { -+ continue; -+ } -+ -+ if (predicate != null && !predicate.test(part)) { -+ continue; -+ } -+ -+ into.add(part); -+ } -+ } -+ } -+ } -+ } -+ -+ public void getEntitiesWithEnderDragonParts(final Entity except, final Class clazz, final AABB box, final List into, -+ final Predicate predicate) { -+ if (this.count == 0) { -+ return; -+ } -+ -+ final int minSection = this.manager.minSection; -+ final int maxSection = this.manager.maxSection; -+ -+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); -+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); -+ -+ // TODO use the bitset -+ -+ final BasicEntityList[] entitiesBySection = this.entitiesBySection; -+ -+ for (int section = min; section <= max; ++section) { -+ final BasicEntityList list = entitiesBySection[section - minSection]; -+ -+ if (list == null) { -+ continue; -+ } -+ -+ final Entity[] storage = list.storage; -+ -+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { -+ final Entity entity = storage[i]; -+ -+ if (entity == null || entity == except || !entity.getBoundingBox().intersects(box)) { -+ continue; -+ } -+ -+ if (predicate == null || predicate.test(entity)) { -+ into.add(entity); -+ } // else: continue to test the ender dragon parts -+ -+ if (entity instanceof EnderDragon) { -+ for (final EnderDragonPart part : ((EnderDragon)entity).subEntities) { -+ if (part == except || !part.getBoundingBox().intersects(box) || !clazz.isInstance(part)) { -+ continue; -+ } -+ -+ if (predicate != null && !predicate.test(part)) { -+ continue; -+ } -+ -+ into.add(part); -+ } -+ } -+ } -+ } -+ } -+ -+ public void getEntities(final EntityType type, final AABB box, final List into, -+ final Predicate predicate) { -+ if (this.count == 0) { -+ return; -+ } -+ -+ final int minSection = this.manager.minSection; -+ final int maxSection = this.manager.maxSection; -+ -+ final int min = Mth.clamp(Mth.floor(box.minY - 2.0) >> 4, minSection, maxSection); -+ final int max = Mth.clamp(Mth.floor(box.maxY + 2.0) >> 4, minSection, maxSection); -+ -+ // TODO use the bitset -+ -+ final BasicEntityList[] entitiesBySection = this.entitiesBySection; -+ -+ for (int section = min; section <= max; ++section) { -+ final BasicEntityList list = entitiesBySection[section - minSection]; -+ -+ if (list == null) { -+ continue; -+ } -+ -+ final Entity[] storage = list.storage; -+ -+ for (int i = 0, len = Math.min(storage.length, list.size()); i < len; ++i) { -+ final Entity entity = storage[i]; -+ -+ if (entity == null || (type != null && entity.getType() != type) || !entity.getBoundingBox().intersects(box)) { -+ continue; -+ } -+ -+ if (predicate != null && !predicate.test((T)entity)) { -+ continue; -+ } -+ -+ into.add((T)entity); -+ } -+ } -+ } -+ } -+} -diff --git a/src/main/java/com/tuinity/tuinity/world/EntitySliceManager.java b/src/main/java/com/tuinity/tuinity/world/EntitySliceManager.java -new file mode 100644 -index 0000000000000000000000000000000000000000..f188ff6b08abddd06a3120fb15825e0f71196893 ---- /dev/null -+++ b/src/main/java/com/tuinity/tuinity/world/EntitySliceManager.java -@@ -0,0 +1,391 @@ -+package com.tuinity.tuinity.world; -+ -+import com.tuinity.tuinity.util.CoordinateUtils; -+import com.tuinity.tuinity.util.WorldUtil; -+import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; -+import net.minecraft.core.BlockPos; -+import net.minecraft.server.level.ChunkHolder; -+import net.minecraft.server.level.ServerLevel; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.Entity; -+import net.minecraft.world.entity.EntityType; -+import net.minecraft.world.phys.AABB; -+import java.util.List; -+import java.util.concurrent.locks.StampedLock; -+import java.util.function.Predicate; -+ -+public final class EntitySliceManager { -+ -+ protected static final int REGION_SHIFT = 5; -+ protected static final int REGION_MASK = (1 << REGION_SHIFT) - 1; -+ protected static final int REGION_SIZE = 1 << REGION_SHIFT; -+ -+ public final ServerLevel world; -+ -+ private final StampedLock stateLock = new StampedLock(); -+ protected final Long2ObjectOpenHashMap regions = new Long2ObjectOpenHashMap<>(64, 0.7f); -+ -+ private final int minSection; // inclusive -+ private final int maxSection; // inclusive -+ -+ protected final Long2ObjectOpenHashMap statusMap = new Long2ObjectOpenHashMap<>(); -+ { -+ this.statusMap.defaultReturnValue(ChunkHolder.FullChunkStatus.INACCESSIBLE); -+ } -+ -+ public EntitySliceManager(final ServerLevel world) { -+ this.world = world; -+ this.minSection = WorldUtil.getMinSection(world); -+ this.maxSection = WorldUtil.getMaxSection(world); -+ } -+ -+ public void chunkStatusChange(final int x, final int z, final ChunkHolder.FullChunkStatus newStatus) { -+ if (newStatus == ChunkHolder.FullChunkStatus.INACCESSIBLE) { -+ this.statusMap.remove(CoordinateUtils.getChunkKey(x, z)); -+ } else { -+ this.statusMap.put(CoordinateUtils.getChunkKey(x, z), newStatus); -+ final ChunkEntitySlices slices = this.getChunk(x, z); -+ if (slices != null) { -+ slices.updateStatus(newStatus); -+ } -+ } -+ } -+ -+ public synchronized void addEntity(final Entity entity) { -+ final BlockPos pos = entity.blockPosition(); -+ final int sectionX = pos.getX() >> 4; -+ final int sectionY = Mth.clamp(pos.getY() >> 4, this.minSection, this.maxSection); -+ final int sectionZ = pos.getZ() >> 4; -+ final ChunkEntitySlices slices = this.getOrCreateChunk(sectionX, sectionZ); -+ slices.addEntity(entity, sectionY); -+ -+ entity.sectionX = sectionX; -+ entity.sectionY = sectionY; -+ entity.sectionZ = sectionZ; -+ } -+ -+ public synchronized void removeEntity(final Entity entity) { -+ final ChunkEntitySlices slices = this.getChunk(entity.sectionX, entity.sectionZ); -+ slices.removeEntity(entity, entity.sectionY); -+ if (slices.isEmpty()) { -+ this.removeChunk(entity.sectionX, entity.sectionZ); -+ } -+ } -+ -+ public void moveEntity(final Entity entity) { -+ final BlockPos newPos = entity.blockPosition(); -+ final int newSectionX = newPos.getX() >> 4; -+ final int newSectionY = Mth.clamp(newPos.getY() >> 4, this.minSection, this.maxSection); -+ final int newSectionZ = newPos.getZ() >> 4; -+ -+ if (newSectionX == entity.sectionX && newSectionY == entity.sectionY && newSectionZ == entity.sectionZ) { -+ return; -+ } -+ -+ synchronized (this) { -+ // are we changing chunks? -+ if (newSectionX != entity.sectionX || newSectionZ != entity.sectionZ) { -+ final ChunkEntitySlices slices = this.getOrCreateChunk(newSectionX, newSectionZ); -+ final ChunkEntitySlices old = this.getChunk(entity.sectionX, entity.sectionZ); -+ synchronized (old) { -+ old.removeEntity(entity, entity.sectionY); -+ if (old.isEmpty()) { -+ this.removeChunk(entity.sectionX, entity.sectionZ); -+ } -+ } -+ -+ synchronized (slices) { -+ slices.addEntity(entity, newSectionY); -+ -+ entity.sectionX = newSectionX; -+ entity.sectionY = newSectionY; -+ entity.sectionZ = newSectionZ; -+ } -+ } else { -+ final ChunkEntitySlices slices = this.getChunk(newSectionX, newSectionZ); -+ // same chunk -+ synchronized (slices) { -+ slices.removeEntity(entity, entity.sectionY); -+ slices.addEntity(entity, newSectionY); -+ } -+ entity.sectionY = newSectionY; -+ } -+ } -+ -+ } -+ -+ public void getEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; -+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; -+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; -+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; -+ -+ final int minRegionX = minChunkX >> REGION_SHIFT; -+ final int minRegionZ = minChunkZ >> REGION_SHIFT; -+ final int maxRegionX = maxChunkX >> REGION_SHIFT; -+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; -+ -+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { -+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; -+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; -+ -+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { -+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); -+ -+ if (region == null) { -+ continue; -+ } -+ -+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; -+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); -+ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { -+ continue; -+ } -+ -+ chunk.getEntities(except, box, into, predicate); -+ } -+ } -+ } -+ } -+ } -+ -+ public void getHardCollidingEntities(final Entity except, final AABB box, final List into, final Predicate predicate) { -+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; -+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; -+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; -+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; -+ -+ final int minRegionX = minChunkX >> REGION_SHIFT; -+ final int minRegionZ = minChunkZ >> REGION_SHIFT; -+ final int maxRegionX = maxChunkX >> REGION_SHIFT; -+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; -+ -+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { -+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; -+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; -+ -+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { -+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); -+ -+ if (region == null) { -+ continue; -+ } -+ -+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; -+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); -+ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { -+ continue; -+ } -+ -+ chunk.getHardCollidingEntities(except, box, into, predicate); -+ } -+ } -+ } -+ } -+ } -+ -+ public void getEntities(final EntityType type, final AABB box, final List into, -+ final Predicate predicate) { -+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; -+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; -+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; -+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; -+ -+ final int minRegionX = minChunkX >> REGION_SHIFT; -+ final int minRegionZ = minChunkZ >> REGION_SHIFT; -+ final int maxRegionX = maxChunkX >> REGION_SHIFT; -+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; -+ -+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { -+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; -+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; -+ -+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { -+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); -+ -+ if (region == null) { -+ continue; -+ } -+ -+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; -+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); -+ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { -+ continue; -+ } -+ -+ chunk.getEntities(type, box, (List)into, (Predicate)predicate); -+ } -+ } -+ } -+ } -+ } -+ -+ public void getEntities(final Class clazz, final Entity except, final AABB box, final List into, -+ final Predicate predicate) { -+ final int minChunkX = (Mth.floor(box.minX) - 2) >> 4; -+ final int minChunkZ = (Mth.floor(box.minZ) - 2) >> 4; -+ final int maxChunkX = (Mth.floor(box.maxX) + 2) >> 4; -+ final int maxChunkZ = (Mth.floor(box.maxZ) + 2) >> 4; -+ -+ final int minRegionX = minChunkX >> REGION_SHIFT; -+ final int minRegionZ = minChunkZ >> REGION_SHIFT; -+ final int maxRegionX = maxChunkX >> REGION_SHIFT; -+ final int maxRegionZ = maxChunkZ >> REGION_SHIFT; -+ -+ for (int currRegionZ = minRegionZ; currRegionZ <= maxRegionZ; ++currRegionZ) { -+ final int minZ = currRegionZ == minRegionZ ? minChunkZ & REGION_MASK : 0; -+ final int maxZ = currRegionZ == maxRegionZ ? maxChunkZ & REGION_MASK : REGION_MASK; -+ -+ for (int currRegionX = minRegionX; currRegionX <= maxRegionX; ++currRegionX) { -+ final ChunkSlicesRegion region = this.getRegion(currRegionX, currRegionZ); -+ -+ if (region == null) { -+ continue; -+ } -+ -+ final int minX = currRegionX == minRegionX ? minChunkX & REGION_MASK : 0; -+ final int maxX = currRegionX == maxRegionX ? maxChunkX & REGION_MASK : REGION_MASK; -+ -+ for (int currZ = minZ; currZ <= maxZ; ++currZ) { -+ for (int currX = minX; currX <= maxX; ++currX) { -+ final ChunkEntitySlices chunk = region.get(currX | (currZ << REGION_SHIFT)); -+ if (chunk == null || !chunk.status.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { -+ continue; -+ } -+ -+ chunk.getEntities(clazz, except, box, into, predicate); -+ } -+ } -+ } -+ } -+ } -+ -+ public ChunkEntitySlices getChunk(final int chunkX, final int chunkZ) { -+ final ChunkSlicesRegion region = this.getRegion(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); -+ if (region == null) { -+ return null; -+ } -+ -+ return region.get((chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT)); -+ } -+ -+ public ChunkEntitySlices getOrCreateChunk(final int chunkX, final int chunkZ) { -+ final ChunkSlicesRegion region = this.getRegion(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); -+ ChunkEntitySlices ret; -+ if (region == null || (ret = region.get((chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT))) == null) { -+ ret = new ChunkEntitySlices(this.world, chunkX, chunkZ, this.statusMap.get(CoordinateUtils.getChunkKey(chunkX, chunkZ)), -+ WorldUtil.getMinSection(this.world), WorldUtil.getMaxSection(this.world)); -+ -+ this.addChunk(chunkX, chunkZ, ret); -+ -+ return ret; -+ } -+ -+ return ret; -+ } -+ -+ public ChunkSlicesRegion getRegion(final int regionX, final int regionZ) { -+ final long key = CoordinateUtils.getChunkKey(regionX, regionZ); -+ final long attempt = this.stateLock.tryOptimisticRead(); -+ if (attempt != 0L) { -+ try { -+ final ChunkSlicesRegion ret = this.regions.get(key); -+ -+ if (this.stateLock.validate(attempt)) { -+ return ret; -+ } -+ } catch (final Error error) { -+ throw error; -+ } catch (final Throwable thr) { -+ // ignore -+ } -+ } -+ -+ this.stateLock.readLock(); -+ try { -+ return this.regions.get(key); -+ } finally { -+ this.stateLock.tryUnlockRead(); -+ } -+ } -+ -+ public synchronized void removeChunk(final int chunkX, final int chunkZ) { -+ final long key = CoordinateUtils.getChunkKey(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); -+ final int relIndex = (chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT); -+ -+ final ChunkSlicesRegion region = this.regions.get(key); -+ final int remaining = region.remove(relIndex); -+ -+ if (remaining == 0) { -+ this.stateLock.writeLock(); -+ try { -+ this.regions.remove(key); -+ } finally { -+ this.stateLock.tryUnlockWrite(); -+ } -+ } -+ } -+ -+ public synchronized void addChunk(final int chunkX, final int chunkZ, final ChunkEntitySlices slices) { -+ final long key = CoordinateUtils.getChunkKey(chunkX >> REGION_SHIFT, chunkZ >> REGION_SHIFT); -+ final int relIndex = (chunkX & REGION_MASK) | ((chunkZ & REGION_MASK) << REGION_SHIFT); -+ -+ ChunkSlicesRegion region = this.regions.get(key); -+ if (region != null) { -+ region.add(relIndex, slices); -+ } else { -+ region = new ChunkSlicesRegion(); -+ region.add(relIndex, slices); -+ this.stateLock.writeLock(); -+ try { -+ this.regions.put(key, region); -+ } finally { -+ this.stateLock.tryUnlockWrite(); -+ } -+ } -+ } -+ -+ public static final class ChunkSlicesRegion { -+ -+ protected final ChunkEntitySlices[] slices = new ChunkEntitySlices[REGION_SIZE * REGION_SIZE]; -+ protected int sliceCount; -+ -+ public ChunkEntitySlices get(final int index) { -+ return this.slices[index]; -+ } -+ -+ public int remove(final int index) { -+ final ChunkEntitySlices slices = this.slices[index]; -+ if (slices == null) { -+ throw new IllegalStateException(); -+ } -+ -+ this.slices[index] = null; -+ -+ return --this.sliceCount; -+ } -+ -+ public void add(final int index, final ChunkEntitySlices slices) { -+ final ChunkEntitySlices curr = this.slices[index]; -+ if (curr != null) { -+ throw new IllegalStateException(); -+ } -+ -+ this.slices[index] = slices; -+ -+ ++this.sliceCount; -+ } -+ } -+} -diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java -index b70aa66732fb5e957aed0901f4c76358b2c56f8e..b01d7da333bac7820e42b6f645634a15ef88ae4f 100644 ---- a/src/main/java/net/minecraft/core/BlockPos.java -+++ b/src/main/java/net/minecraft/core/BlockPos.java -@@ -478,9 +478,9 @@ public class BlockPos extends Vec3i { - } - - public BlockPos.MutableBlockPos set(int x, int y, int z) { -- this.setX(x); -- this.setY(y); -- this.setZ(z); -+ this.x = x; // Tuinity - force inline -+ this.y = y; // Tuinity - force inline -+ this.z = z; // Tuinity - force inline - return this; - } - -@@ -544,19 +544,19 @@ public class BlockPos extends Vec3i { - // Paper start - comment out useless overrides @Override - TODO figure out why this is suddenly important to keep - @Override - public BlockPos.MutableBlockPos setX(int i) { -- super.setX(i); -+ this.x = i; // Tuinity - return this; - } - - @Override - public BlockPos.MutableBlockPos setY(int i) { -- super.setY(i); -+ this.y = i; // Tuinity - return this; - } - - @Override - public BlockPos.MutableBlockPos setZ(int i) { -- super.setZ(i); -+ this.z = i; // Tuinity - return this; - } - // Paper end -diff --git a/src/main/java/net/minecraft/core/Vec3i.java b/src/main/java/net/minecraft/core/Vec3i.java -index 5e09890ba2fe326503a49b2dbec09845f5c8c5eb..3ad3652f8074de10222fb01c50548b4312103cc3 100644 ---- a/src/main/java/net/minecraft/core/Vec3i.java -+++ b/src/main/java/net/minecraft/core/Vec3i.java -@@ -17,9 +17,9 @@ public class Vec3i implements Comparable { - return IntStream.of(vec3i.getX(), vec3i.getY(), vec3i.getZ()); - }); - public static final Vec3i ZERO = new Vec3i(0, 0, 0); -- private int x; -- private int y; -- private int z; -+ protected int x; // Tuinity - protected -+ protected int y; // Tuinity - protected -+ protected int z; // Tuinity - protected - - // Paper start - public boolean isValidLocation(net.minecraft.world.level.LevelHeightAccessor levelHeightAccessor) { -@@ -84,17 +84,17 @@ public class Vec3i implements Comparable { - return this.z; - } - -- public Vec3i setX(int x) { -+ protected Vec3i setX(int x) { // Tuinity - not needed here - Also revert the decision to expose set on an _immutable_ type - this.x = x; - return this; - } - -- public Vec3i setY(int y) { -+ protected Vec3i setY(int y) { // Tuinity - not needed here - Also revert the decision to expose set on an _immutable_ type - this.y = y; - return this; - } - -- public Vec3i setZ(int z) { -+ protected Vec3i setZ(int z) { // Tuinity - not needed here - Also revert the decision to expose set on an _immutable_ type - this.z = z; - return this; - } -diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java -index c203a78a28e6457bd25b34b5c5ecaa35e3f9211e..0232fb8123c7dfa735802442f8575c6ce1566847 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -49,6 +49,8 @@ import org.apache.logging.log4j.Logger; - import org.apache.logging.log4j.Marker; - import org.apache.logging.log4j.MarkerManager; - -+ -+import io.netty.util.concurrent.AbstractEventExecutor; // Tuinity - public class Connection extends SimpleChannelInboundHandler> { - - private static final float AVERAGE_PACKETS_SMOOTHING = 0.75F; -@@ -93,6 +95,77 @@ public class Connection extends SimpleChannelInboundHandler> { - public boolean queueImmunity = false; - public ConnectionProtocol protocol; - // Paper end -+ // Tuinity start - add pending task queue -+ private final Queue pendingTasks = new java.util.concurrent.ConcurrentLinkedQueue<>(); -+ public void execute(final Runnable run) { -+ if (this.channel == null || !this.channel.isRegistered()) { -+ run.run(); -+ return; -+ } -+ final boolean queue = !this.queue.isEmpty(); -+ if (!queue) { -+ this.channel.eventLoop().execute(run); -+ } else { -+ this.pendingTasks.add(run); -+ if (this.queue.isEmpty()) { -+ // something flushed async, dump tasks now -+ Runnable r; -+ while ((r = this.pendingTasks.poll()) != null) { -+ this.channel.eventLoop().execute(r); -+ } -+ } -+ } -+ } -+ // Tuinity end - add pending task queue -+ -+ // Tuinity start - allow controlled flushing -+ volatile boolean canFlush = true; -+ private final java.util.concurrent.atomic.AtomicInteger packetWrites = new java.util.concurrent.atomic.AtomicInteger(); -+ private int flushPacketsStart; -+ private final Object flushLock = new Object(); -+ -+ public void disableAutomaticFlush() { -+ synchronized (this.flushLock) { -+ this.flushPacketsStart = this.packetWrites.get(); // must be volatile and before canFlush = false -+ this.canFlush = false; -+ } -+ } -+ -+ public void enableAutomaticFlush() { -+ synchronized (this.flushLock) { -+ this.canFlush = true; -+ if (this.packetWrites.get() != this.flushPacketsStart) { // must be after canFlush = true -+ this.flush(); // only make the flush call if we need to -+ } -+ } -+ } -+ -+ private final void flush() { -+ if (this.channel.eventLoop().inEventLoop()) { -+ this.channel.flush(); -+ } else { -+ this.channel.eventLoop().execute(() -> { -+ this.channel.flush(); -+ }); -+ } -+ } -+ // Tuinity end - allow controlled flushing -+ // Tuinity start - packet limiter -+ protected final Object PACKET_LIMIT_LOCK = new Object(); -+ protected final com.tuinity.tuinity.util.IntervalledCounter allPacketCounts = com.tuinity.tuinity.config.TuinityConfig.allPacketsLimit != null ? new com.tuinity.tuinity.util.IntervalledCounter( -+ (long)(com.tuinity.tuinity.config.TuinityConfig.allPacketsLimit.packetLimitInterval * 1.0e9) -+ ) : null; -+ protected final java.util.Map>, com.tuinity.tuinity.util.IntervalledCounter> packetSpecificLimits = new java.util.HashMap<>(); -+ -+ private boolean stopReadingPackets; -+ private void killForPacketSpam() { -+ this.sendPacket(new ClientboundDisconnectPacket(org.bukkit.craftbukkit.util.CraftChatMessage.fromString(com.tuinity.tuinity.config.TuinityConfig.kickMessage, true)[0]), (future) -> { -+ this.disconnect(org.bukkit.craftbukkit.util.CraftChatMessage.fromString(com.tuinity.tuinity.config.TuinityConfig.kickMessage, true)[0]); -+ }); -+ this.setReadOnly(); -+ this.stopReadingPackets = true; -+ } -+ // Tuinity end - packet limiter - - public Connection(PacketFlow side) { - this.receiving = side; -@@ -173,6 +246,45 @@ public class Connection extends SimpleChannelInboundHandler> { - - protected void channelRead0(ChannelHandlerContext channelhandlercontext, Packet packet) { - if (this.channel.isOpen()) { -+ // Tuinity start - packet limiter -+ if (this.stopReadingPackets) { -+ return; -+ } -+ if (this.allPacketCounts != null || -+ com.tuinity.tuinity.config.TuinityConfig.packetSpecificLimits.containsKey(packet.getClass())) { -+ long time = System.nanoTime(); -+ synchronized (PACKET_LIMIT_LOCK) { -+ if (this.allPacketCounts != null) { -+ this.allPacketCounts.updateAndAdd(1, time); -+ if (this.allPacketCounts.getRate() >= com.tuinity.tuinity.config.TuinityConfig.allPacketsLimit.maxPacketRate) { -+ this.killForPacketSpam(); -+ return; -+ } -+ } -+ -+ for (Class check = packet.getClass(); check != Object.class; check = check.getSuperclass()) { -+ com.tuinity.tuinity.config.TuinityConfig.PacketLimit packetSpecificLimit = -+ com.tuinity.tuinity.config.TuinityConfig.packetSpecificLimits.get(check); -+ if (packetSpecificLimit == null) { -+ continue; -+ } -+ com.tuinity.tuinity.util.IntervalledCounter counter = this.packetSpecificLimits.computeIfAbsent((Class)check, (clazz) -> { -+ return new com.tuinity.tuinity.util.IntervalledCounter((long)(packetSpecificLimit.packetLimitInterval * 1.0e9)); -+ }); -+ counter.updateAndAdd(1, time); -+ if (counter.getRate() >= packetSpecificLimit.maxPacketRate) { -+ switch (packetSpecificLimit.violateAction) { -+ case DROP: -+ return; -+ case KICK: -+ this.killForPacketSpam(); -+ return; -+ } -+ } -+ } -+ } -+ } -+ // Tuinity end - packet limiter - try { - Connection.genericsFtw(packet, this.packetListener); - } catch (RunningOnDifferentThreadException cancelledpackethandleexception) { -@@ -255,7 +367,7 @@ public class Connection extends SimpleChannelInboundHandler> { - net.minecraft.server.MCUtil.isMainThread() && packet.isReady() && this.queue.isEmpty() && - (packet.getExtraPackets() == null || packet.getExtraPackets().isEmpty()) - ))) { -- this.sendPacket(packet, callback); -+ this.writePacket(packet, callback, null); // Tuinity - return; - } - // write the packets to the queue, then flush - antixray hooks there already -@@ -279,6 +391,14 @@ public class Connection extends SimpleChannelInboundHandler> { - } - - private void sendPacket(Packet packet, @Nullable GenericFutureListener> callback) { -+ // Tuinity start - add flush parameter -+ this.writePacket(packet, callback, Boolean.TRUE); -+ } -+ private void writePacket(Packet packet, @Nullable GenericFutureListener> callback, Boolean flushConditional) { -+ this.packetWrites.getAndIncrement(); // must be befeore using canFlush -+ boolean effectiveFlush = flushConditional == null ? this.canFlush : flushConditional.booleanValue(); -+ final boolean flush = effectiveFlush || packet instanceof net.minecraft.network.protocol.game.ClientboundKeepAlivePacket || packet instanceof ClientboundDisconnectPacket; // no delay for certain packets -+ // Tuinity end - add flush parameter - ConnectionProtocol enumprotocol = ConnectionProtocol.getProtocolForPacket(packet); - ConnectionProtocol enumprotocol1 = this.getCurrentProtocol(); - -@@ -289,16 +409,31 @@ public class Connection extends SimpleChannelInboundHandler> { - } - - if (this.channel.eventLoop().inEventLoop()) { -- this.a(packet, callback, enumprotocol, enumprotocol1); -+ this.a(packet, callback, enumprotocol, enumprotocol1, flush); // Tuinity - add flush parameter - } else { -+ // Tuinity start - optimise packets that are not flushed -+ // note: since the type is not dynamic here, we need to actually copy the old executor code -+ // into two branches. On conflict, just re-copy - no changes were made inside the executor code. -+ if (!flush) { -+ AbstractEventExecutor.LazyRunnable run = () -> { -+ this.a(packet, callback, enumprotocol, enumprotocol1, flush); // Tuinity - add flush parameter -+ }; -+ this.channel.eventLoop().execute(run); -+ } else { // Tuinity end - optimise packets that are not flushed - this.channel.eventLoop().execute(() -> { -- this.a(packet, callback, enumprotocol, enumprotocol1); -+ this.a(packet, callback, enumprotocol, enumprotocol1, flush); // Tuinity - add flush parameter // Tuinity - diff on change - }); -+ } // Tuinity - } - - } - - private void a(Packet packet, @Nullable GenericFutureListener> genericfuturelistener, ConnectionProtocol enumprotocol, ConnectionProtocol enumprotocol1) { -+ // Tuinity start - add flush parameter -+ this.a(packet, genericfuturelistener, enumprotocol, enumprotocol1, true); -+ } -+ private void a(Packet packet, @Nullable GenericFutureListener> genericfuturelistener, ConnectionProtocol enumprotocol, ConnectionProtocol enumprotocol1, boolean flush) { -+ // Tuinity end - add flush parameter - if (enumprotocol != enumprotocol1) { - this.setProtocol(enumprotocol); - } -@@ -312,7 +447,7 @@ public class Connection extends SimpleChannelInboundHandler> { - - try { - // Paper end -- ChannelFuture channelfuture = this.channel.writeAndFlush(packet); -+ ChannelFuture channelfuture = flush ? this.channel.writeAndFlush(packet) : this.channel.write(packet); // Tuinity - add flush parameter - - if (genericfuturelistener != null) { - channelfuture.addListener(genericfuturelistener); -@@ -353,7 +488,12 @@ public class Connection extends SimpleChannelInboundHandler> { - return false; - } - private boolean processQueue() { -+ try { // Tuinity - add pending task queue - if (this.queue.isEmpty()) return true; -+ // Tuinity start - make only one flush call per sendPacketQueue() call -+ final boolean needsFlush = this.canFlush; -+ boolean hasWrotePacket = false; -+ // Tuinity end - make only one flush call per sendPacketQueue() call - // If we are on main, we are safe here in that nothing else should be processing queue off main anymore - // But if we are not on main due to login/status, the parent is synchronized on packetQueue - java.util.Iterator iterator = this.queue.iterator(); -@@ -361,19 +501,31 @@ public class Connection extends SimpleChannelInboundHandler> { - PacketHolder queued = iterator.next(); // poll -> peek - - // Fix NPE (Spigot bug caused by handleDisconnection()) -- if (queued == null) { -+ if (false && queued == null) { // Tuinity - diff on change, this logic is redundant: iterator guarantees ret of an element - on change, hook the flush logic here - return true; - } - - Packet packet = queued.packet; - if (!packet.isReady()) { -+ // Tuinity start - make only one flush call per sendPacketQueue() call -+ if (hasWrotePacket && (needsFlush || this.canFlush)) { -+ this.flush(); -+ } -+ // Tuinity end - make only one flush call per sendPacketQueue() call - return false; - } else { - iterator.remove(); -- this.sendPacket(packet, queued.listener); -+ this.writePacket(packet, queued.listener, (!iterator.hasNext() && (needsFlush || this.canFlush)) ? Boolean.TRUE : Boolean.FALSE); // Tuinity - make only one flush call per sendPacketQueue() call -+ hasWrotePacket = true; // Tuinity - make only one flush call per sendPacketQueue() call - } - } - return true; -+ } finally { // Tuinity start - add pending task queue -+ Runnable r; -+ while ((r = this.pendingTasks.poll()) != null) { -+ this.channel.eventLoop().execute(r); -+ } -+ } // Tuinity end - add pending task queue - } - // Paper end - -@@ -396,7 +548,14 @@ public class Connection extends SimpleChannelInboundHandler> { - } - - if (this.packetListener instanceof ServerGamePacketListenerImpl) { -+ // Tuinity start - detailed watchdog information -+ net.minecraft.network.protocol.PacketUtils.packetProcessing.push(this.packetListener); -+ try { -+ // Tuinity end - detailed watchdog information - ((ServerGamePacketListenerImpl) this.packetListener).tick(); -+ } finally { // Tuinity start - detailed watchdog information -+ net.minecraft.network.protocol.PacketUtils.packetProcessing.pop(); -+ } // Tuinity start - detailed watchdog information - } - - if (!this.isConnected() && !this.disconnectionHandled) { -diff --git a/src/main/java/net/minecraft/network/protocol/PacketUtils.java b/src/main/java/net/minecraft/network/protocol/PacketUtils.java -index bcf53ec07b8eeec7a88fb67e6fb908362e6f51b0..7265bee436d61d33645fa2d9ed4240529834dbf5 100644 ---- a/src/main/java/net/minecraft/network/protocol/PacketUtils.java -+++ b/src/main/java/net/minecraft/network/protocol/PacketUtils.java -@@ -20,6 +20,24 @@ public class PacketUtils { - - private static final Logger LOGGER = LogManager.getLogger(); - -+ // Tuinity start - detailed watchdog information -+ public static final java.util.concurrent.ConcurrentLinkedDeque packetProcessing = new java.util.concurrent.ConcurrentLinkedDeque<>(); -+ static final java.util.concurrent.atomic.AtomicLong totalMainThreadPacketsProcessed = new java.util.concurrent.atomic.AtomicLong(); -+ -+ public static long getTotalProcessedPackets() { -+ return totalMainThreadPacketsProcessed.get(); -+ } -+ -+ public static java.util.List getCurrentPacketProcessors() { -+ java.util.List ret = new java.util.ArrayList<>(4); -+ for (PacketListener listener : packetProcessing) { -+ ret.add(listener); -+ } -+ -+ return ret; -+ } -+ // Tuinity end - detailed watchdog information -+ - public PacketUtils() {} - - public static void ensureRunningOnSameThread(Packet packet, T listener, ServerLevel world) throws RunningOnDifferentThreadException { -@@ -30,6 +48,8 @@ public class PacketUtils { - if (!engine.isSameThread()) { - Timing timing = MinecraftTimings.getPacketTiming(packet); // Paper - timings - engine.execute(() -> { -+ packetProcessing.push(listener); // Tuinity - detailed watchdog information -+ try { // Tuinity - detailed watchdog information - if (MinecraftServer.getServer().hasStopped() || (listener instanceof ServerGamePacketListenerImpl && ((ServerGamePacketListenerImpl) listener).processedDisconnect)) return; // CraftBukkit, MC-142590 - if (listener.getConnection().isConnected()) { - try (Timing ignored = timing.startTiming()) { // Paper - timings -@@ -53,6 +73,12 @@ public class PacketUtils { - } else { - PacketUtils.LOGGER.debug("Ignoring packet due to disconnection: {}", packet); - } -+ // Tuinity start - detailed watchdog information -+ } finally { -+ totalMainThreadPacketsProcessed.getAndIncrement(); -+ packetProcessing.pop(); -+ } -+ // Tuinity end - detailed watchdog information - - }); - throw RunningOnDifferentThreadException.RUNNING_ON_DIFFERENT_THREAD; -diff --git a/src/main/java/net/minecraft/network/protocol/game/ClientboundLightUpdatePacket.java b/src/main/java/net/minecraft/network/protocol/game/ClientboundLightUpdatePacket.java -index d8be2ad889f46491e50404916fb4ae0de5f42098..5b9ea0af272c5e7a85d2a954a9214bf875bc7e9f 100644 ---- a/src/main/java/net/minecraft/network/protocol/game/ClientboundLightUpdatePacket.java -+++ b/src/main/java/net/minecraft/network/protocol/game/ClientboundLightUpdatePacket.java -@@ -32,25 +32,17 @@ public class ClientboundLightUpdatePacket implements Packet { -- if (remainingSends.get() == 0) { -- cleaner1.run(); -- cleaner2.run(); -- } -- }, "Light Packet Release"); -- } -+ // Tuinity - rewrite light engine - } - - @Override - public boolean hasFinishListener() { -- return true; -+ return false; // Tuinity - rewrite light engine - } - - // Paper end -@@ -63,8 +55,8 @@ public class ClientboundLightUpdatePacket implements Packet com.mojang.serialization.MapCodec fieldWithFallbacks(com.mojang.serialization.Codec codec, String name, String ...fallback) { -+ return com.mojang.serialization.MapCodec.of( -+ new com.mojang.serialization.codecs.FieldEncoder<>(name, codec), -+ new FieldFallbackDecoder<>(name, java.util.Arrays.asList(fallback), codec), -+ () -> "FieldFallback[" + name + ": " + codec.toString() + "]" -+ ); -+ } -+ -+ // This is likely a common occurrence, sadly -+ public static final class FieldFallbackDecoder extends com.mojang.serialization.MapDecoder.Implementation { -+ protected final String name; -+ protected final List fallback; -+ private final com.mojang.serialization.Decoder elementCodec; -+ -+ public FieldFallbackDecoder(final String name, final List fallback, final com.mojang.serialization.Decoder elementCodec) { -+ this.name = name; -+ this.fallback = fallback; -+ this.elementCodec = elementCodec; -+ } -+ -+ @Override -+ public com.mojang.serialization.DataResult decode(final com.mojang.serialization.DynamicOps ops, final com.mojang.serialization.MapLike input) { -+ T value = input.get(name); -+ if (value == null) { -+ for (String fall : fallback) { -+ value = input.get(fall); -+ if (value != null) { -+ break; -+ } -+ } -+ if (value == null) { -+ return com.mojang.serialization.DataResult.error("No key " + name + " in " + input); -+ } -+ } -+ return elementCodec.parse(ops, value); -+ } -+ -+ @Override -+ public java.util.stream.Stream keys(final com.mojang.serialization.DynamicOps ops) { -+ return java.util.stream.Stream.of(ops.createString(name)); -+ } -+ -+ @Override -+ public boolean equals(final Object o) { -+ if (this == o) { -+ return true; -+ } -+ if (o == null || getClass() != o.getClass()) { -+ return false; -+ } -+ final FieldFallbackDecoder that = (FieldFallbackDecoder)o; -+ return java.util.Objects.equals(name, that.name) && java.util.Objects.equals(elementCodec, that.elementCodec) -+ && java.util.Objects.equals(fallback, that.fallback); -+ } -+ -+ @Override -+ public int hashCode() { -+ return java.util.Objects.hash(name, fallback, elementCodec); -+ } -+ -+ @Override -+ public String toString() { -+ return "FieldDecoder[" + name + ": " + elementCodec + ']'; -+ } -+ } - } -diff --git a/src/main/java/net/minecraft/server/Main.java b/src/main/java/net/minecraft/server/Main.java -index cfd43069ee2b6f79afb12e10d223f6bf75100034..75c31ec2553c0959f1ac34b554a39a73144da8bd 100644 ---- a/src/main/java/net/minecraft/server/Main.java -+++ b/src/main/java/net/minecraft/server/Main.java -@@ -57,7 +57,7 @@ import org.apache.logging.log4j.Logger; - // CraftBukkit start - import net.minecraft.SharedConstants; - --public class Main { -+public class Main { // - - private static final Logger LOGGER = LogManager.getLogger(); - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 7682bd72c3932a9b20f14e552711d74f70b969b1..f3bf6270c7735869083559f907c65d95144dbe6f 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -297,6 +297,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); - public int autosavePeriod; - public boolean serverAutoSave = false; // Paper -@@ -331,6 +332,76 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop= MAX_CHUNK_EXEC_TIME) { -+ if (!moreTasks) { -+ lastMidTickExecuteFailure = currTime; -+ } -+ -+ // note: negative values reduce the time -+ long overuse = diff - MAX_CHUNK_EXEC_TIME; -+ if (overuse >= (10L * 1000L * 1000L)) { // 10ms -+ // make sure something like a GC or dumb plugin doesn't screw us over... -+ overuse = 10L * 1000L * 1000L; // 10ms -+ } -+ -+ double overuseCount = (double)overuse/(double)MAX_CHUNK_EXEC_TIME; -+ long extraSleep = (long)Math.round(overuseCount*CHUNK_TASK_QUEUE_BACKOFF_MIN_TIME); -+ -+ lastMidTickExecute = currTime + extraSleep; -+ return; -+ } -+ } -+ } finally { -+ co.aikar.timings.MinecraftTimings.midTickChunkTasks.stopTiming(); -+ } -+ } -+ // Tuinity end - execute chunk tasks mid tick -+ - public MinecraftServer(OptionSet options, DataPackConfig datapackconfiguration, Thread thread, RegistryAccess.RegistryHolder iregistrycustom_dimension, LevelStorageSource.LevelStorageAccess convertable_conversionsession, WorldData savedata, PackRepository resourcepackrepository, Proxy proxy, DataFixer datafixer, ServerResources datapackresources, @Nullable MinecraftSessionService minecraftsessionservice, @Nullable GameProfileRepository gameprofilerepository, @Nullable GameProfileCache usercache, ChunkProgressListenerFactory worldloadlistenerfactory) { - super("Server"); - SERVER = this; // Paper - better singleton -@@ -1133,6 +1204,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop { -- midTickLoadChunks(); // will only do loads since we are still considered !canSleepForTick -+ // Tuinity - replace logic - return !this.canOversleep(); - }); - isOversleep = false;MinecraftTimings.serverOversleep.stopTiming(); -@@ -1457,6 +1516,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop // Spigot - Spigot > // CraftBukkit - cb > vanilla! -+ return "Tuinity"; // Tuinity - Tuinity > //Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla! - } - - public SystemReport fillSystemReport(SystemReport systemreport) { -diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 6a1fd7a983724d9e16e8aef06052108ba7ed46f1..9bb0ff063da162f2b5c91d367d9555c1cf1a3ab1 100644 ---- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -+++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -222,6 +222,7 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface - io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // init PaperBrigadierProvider - io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.getClass(); // load mappings for stacktrace deobf - // Paper end -+ com.tuinity.tuinity.config.TuinityConfig.init((java.io.File) options.valueOf("tuinity-settings")); // Tuinity - Server Config - - this.setPvpAllowed(dedicatedserverproperties.pvp); - this.setFlightAllowed(dedicatedserverproperties.allowFlight); -diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java -index 9fe60d058ea1702930981dbd06093dc594e6bf8e..2dd5909a08a8d4bc250b36d297ef9f3f04aed8bf 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -41,6 +41,8 @@ import net.minecraft.world.level.lighting.LevelLightEngine; - import net.minecraft.server.MinecraftServer; - // CraftBukkit end - -+import it.unimi.dsi.fastutil.shorts.ShortOpenHashSet; // Tuinity -+ - public class ChunkHolder { - - public static final Either UNLOADED_CHUNK = Either.right(ChunkHolder.ChunkLoadingFailure.UNLOADED); -@@ -55,7 +57,7 @@ public class ChunkHolder { - private volatile CompletableFuture> fullChunkFuture; private int fullChunkCreateCount; private volatile boolean isFullChunkReady; // Paper - cache chunk ticking stage - private volatile CompletableFuture> tickingChunkFuture; private volatile boolean isTickingReady; // Paper - cache chunk ticking stage - private volatile CompletableFuture> entityTickingChunkFuture; private volatile boolean isEntityTickingReady; // Paper - cache chunk ticking stage -- private CompletableFuture chunkToSave; -+ public CompletableFuture chunkToSave; // Tuinity - public - @Nullable - private final DebugBuffer chunkToSaveHistory; - public int oldTicketLevel; -@@ -238,6 +240,12 @@ public class ChunkHolder { - long key = net.minecraft.server.MCUtil.getCoordinateKey(this.pos); - this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key); - this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key); -+ // Tuinity start - optimise checkDespawn -+ LevelChunk chunk = this.getFullChunkUnchecked(); -+ if (chunk != null) { -+ chunk.updateGeneralAreaCache(); -+ } -+ // Tuinity end - optimise checkDespawn - } - // Paper end - optimise isOutsideOfRange - long lastAutoSaveTime; // Paper - incremental autosave -@@ -388,7 +396,7 @@ public class ChunkHolder { - if (i < 0 || i >= this.changedBlocksPerSection.length) return; // CraftBukkit - SPIGOT-6086, SPIGOT-6296 - if (this.changedBlocksPerSection[i] == null) { - this.hasChangedSections = true; -- this.changedBlocksPerSection[i] = new ShortArraySet(); -+ this.changedBlocksPerSection[i] = new ShortOpenHashSet(); // Tuinity - use a set to make setting constant-time - } - - this.changedBlocksPerSection[i].add(SectionPos.sectionRelativePos(pos)); -@@ -489,7 +497,7 @@ public class ChunkHolder { - // Paper start - per player view distance - // there can be potential desync with player's last mapped section and the view distance map, so use the - // view distance map here. -- com.destroystokyo.paper.util.misc.PlayerAreaMap viewDistanceMap = this.chunkMap.playerViewDistanceBroadcastMap; -+ com.destroystokyo.paper.util.misc.PlayerAreaMap viewDistanceMap = this.chunkMap.playerChunkManager.broadcastMap; // Tuinity - replace old player chunk manager - com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = viewDistanceMap.getObjectsInRange(this.pos); - if (players == null) { - return; -@@ -506,6 +514,7 @@ public class ChunkHolder { - - int viewDistance = viewDistanceMap.getLastViewDistance(player); - long lastPosition = viewDistanceMap.getLastCoordinate(player); -+ if (!this.chunkMap.playerChunkManager.isChunkSent(player, this.pos.x, this.pos.z)) continue; // Tuinity - replace player chunk management - - int distX = Math.abs(net.minecraft.server.MCUtil.getCoordinateX(lastPosition) - this.pos.x); - int distZ = Math.abs(net.minecraft.server.MCUtil.getCoordinateZ(lastPosition) - this.pos.z); -@@ -522,6 +531,7 @@ public class ChunkHolder { - continue; - } - ServerPlayer player = (ServerPlayer)temp; -+ if (!this.chunkMap.playerChunkManager.isChunkSent(player, this.pos.x, this.pos.z)) continue; // Tuinity - replace player chunk management - player.connection.send(packet); - } - } -@@ -597,7 +607,13 @@ public class ChunkHolder { - CompletableFuture completablefuture1 = new CompletableFuture(); - - completablefuture1.thenRunAsync(() -> { -+ // Tuinity start - do not allow ticket level changes -+ boolean unloadingBefore = this.chunkMap.unloadingPlayerChunk; -+ this.chunkMap.unloadingPlayerChunk = true; -+ try { -+ // Tuinity end - do not allow ticket level changes - playerchunkmap.onFullChunkStatusChange(this.pos, playerchunk_state); -+ } finally { this.chunkMap.unloadingPlayerChunk = unloadingBefore; } // Tuinity - do not allow ticket level changes - }, executor); - this.pendingFullStateConfirmation = completablefuture1; - completablefuture.thenAccept((either) -> { -@@ -607,12 +623,23 @@ public class ChunkHolder { - }); - } - -+ private boolean loadCallbackScheduled = false; -+ private boolean unloadCallbackScheduled = false; -+ - private void demoteFullChunk(ChunkMap playerchunkmap, ChunkHolder.FullChunkStatus playerchunk_state) { - this.pendingFullStateConfirmation.cancel(false); -+ // Tuinity start - do not allow ticket level changes -+ boolean unloadingBefore = this.chunkMap.unloadingPlayerChunk; -+ this.chunkMap.unloadingPlayerChunk = true; -+ try { // Tuinity end - do not allow ticket level changes - playerchunkmap.onFullChunkStatusChange(this.pos, playerchunk_state); -+ } finally { this.chunkMap.unloadingPlayerChunk = unloadingBefore; } // Tuinity - do not allow ticket level changes - } - -- protected void updateFutures(ChunkMap chunkStorage, Executor executor) { -+ protected long updateCount; // Tuinity - correctly handle recursion -+ public void updateFutures(ChunkMap chunkStorage, Executor executor) { // Tuinity -+ com.tuinity.tuinity.util.TickThread.ensureTickThread("Async ticket level update"); // Tuinity -+ long updateCount = ++this.updateCount; // Tuinity - correctly handle recursion - ChunkStatus chunkstatus = ChunkHolder.getStatus(this.oldTicketLevel); - ChunkStatus chunkstatus1 = ChunkHolder.getStatus(this.ticketLevel); - boolean flag = this.oldTicketLevel <= ChunkMap.MAX_CHUNK_DISTANCE; -@@ -622,10 +649,23 @@ public class ChunkHolder { - // CraftBukkit start - // ChunkUnloadEvent: Called before the chunk is unloaded: isChunkLoaded is still true and chunk can still be modified by plugins. - if (playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && !playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { -- this.getStatusFutureUncheckedMain(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main -+ this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main // Tuinity - is always on main -+ com.tuinity.tuinity.util.TickThread.ensureTickThread("Async full status chunk future completion"); // Tuinity - LevelChunk chunk = (LevelChunk)either.left().orElse(null); -- if (chunk != null) { -+ if (chunk != null && chunk.wasLoadCallbackInvoked() && ChunkHolder.this.ticketLevel > 33) { // Tuinity - only invoke unload if load was called -+ // Tuinity start - only schedule once, now the future is no longer completed as RIGHT if unloaded... -+ if (ChunkHolder.this.unloadCallbackScheduled) { -+ return; -+ } -+ ChunkHolder.this.unloadCallbackScheduled = true; -+ // Tuinity end - only schedule once, now the future is no longer completed as RIGHT if unloaded... - chunkStorage.callbackExecutor.execute(() -> { -+ // Tuinity start - only schedule once, now the future is no longer completed as RIGHT if unloaded... -+ ChunkHolder.this.unloadCallbackScheduled = false; -+ if (ChunkHolder.this.ticketLevel <= 33) { -+ return; -+ } -+ // Tuinity end - only schedule once, now the future is no longer completed as RIGHT if unloaded... - // Minecraft will apply the chunks tick lists to the world once the chunk got loaded, and then store the tick - // lists again inside the chunk once the chunk becomes inaccessible and set the chunk's needsSaving flag. - // These actions may however happen deferred, so we manually set the needsSaving flag already here. -@@ -641,6 +681,12 @@ public class ChunkHolder { - - // Run callback right away if the future was already done - chunkStorage.callbackExecutor.run(); -+ // Tuinity start - correctly handle recursion -+ if (this.updateCount != updateCount) { -+ // something else updated ticket level for us. -+ return; -+ } -+ // Tuinity end - correctly handle recursion - } - // CraftBukkit end - CompletableFuture completablefuture; -@@ -681,7 +727,8 @@ public class ChunkHolder { - this.fullChunkFuture = chunkStorage.prepareAccessibleChunk(this); - this.scheduleFullChunkPromotion(chunkStorage, this.fullChunkFuture, executor, ChunkHolder.FullChunkStatus.BORDER); - // Paper start - cache ticking ready status -- ensureMain(this.fullChunkFuture).thenAccept(either -> { // Paper - ensure main -+ this.fullChunkFuture.thenAccept(either -> { // Paper - ensure main // Tuinity - always fired on main -+ com.tuinity.tuinity.util.TickThread.ensureTickThread("Async full chunk future completion"); // Tuinity - final Optional left = either.left(); - if (left.isPresent() && ChunkHolder.this.fullChunkCreateCount == expectCreateCount) { - // note: Here is a very good place to add callbacks to logic waiting on this. -@@ -712,7 +759,8 @@ public class ChunkHolder { - this.tickingChunkFuture = chunkStorage.prepareTickingChunk(this); - this.scheduleFullChunkPromotion(chunkStorage, this.tickingChunkFuture, executor, ChunkHolder.FullChunkStatus.TICKING); - // Paper start - cache ticking ready status -- ensureMain(this.tickingChunkFuture).thenAccept(either -> { // Paper - ensure main -+ this.tickingChunkFuture.thenAccept(either -> { // Paper - ensure main // Tuinity - always completed on main -+ com.tuinity.tuinity.util.TickThread.ensureTickThread("Async ticking chunk future completion"); // Tuinity - either.ifLeft(chunk -> { - // note: Here is a very good place to add callbacks to logic waiting on this. - ChunkHolder.this.isTickingReady = true; -@@ -720,6 +768,9 @@ public class ChunkHolder { - // Paper start - rewrite ticklistserver - ChunkHolder.this.chunkMap.level.onChunkSetTicking(ChunkHolder.this.pos.x, ChunkHolder.this.pos.z); - // Paper end - rewrite ticklistserver -+ // Tuinity start - ticking chunk set -+ ChunkHolder.this.chunkMap.level.getChunkSource().tickingChunks.add(chunk); -+ // Tuinity end - ticking chunk set - }); - }); - // Paper end -@@ -729,6 +780,12 @@ public class ChunkHolder { - if (flag4 && !flag5) { - this.tickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isTickingReady = false; // Paper - cache chunk ticking stage - this.tickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; -+ // Tuinity start - ticking chunk set -+ LevelChunk chunkIfCached = this.getFullChunkUnchecked(); -+ if (chunkIfCached != null) { -+ this.chunkMap.level.getChunkSource().tickingChunks.remove(chunkIfCached); -+ } -+ // Tuinity end - ticking chunk set - } - - boolean flag6 = playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.ENTITY_TICKING); -@@ -742,9 +799,13 @@ public class ChunkHolder { - this.entityTickingChunkFuture = chunkStorage.prepareEntityTickingChunk(this.pos); - this.scheduleFullChunkPromotion(chunkStorage, this.entityTickingChunkFuture, executor, ChunkHolder.FullChunkStatus.ENTITY_TICKING); - // Paper start - cache ticking ready status -- ensureMain(this.entityTickingChunkFuture).thenAccept(either -> { // Paper ensureMain -+ this.entityTickingChunkFuture.thenAccept(either -> { // Paper ensureMain // Tuinity - always completed on main -+ com.tuinity.tuinity.util.TickThread.ensureTickThread("Async entity ticking chunk future completion"); // Tuinity - either.ifLeft(chunk -> { - ChunkHolder.this.isEntityTickingReady = true; -+ // Tuinity start - entity ticking chunk set -+ ChunkHolder.this.chunkMap.level.getChunkSource().entityTickingChunks.add(chunk); -+ // Tuinity end - entity ticking chunk set - }); - }); - // Paper end -@@ -754,6 +815,12 @@ public class ChunkHolder { - if (flag6 && !flag7) { - this.entityTickingChunkFuture.complete(ChunkHolder.UNLOADED_LEVEL_CHUNK); this.isEntityTickingReady = false; // Paper - cache chunk ticking stage - this.entityTickingChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE; -+ // Tuinity start - entity ticking chunk set -+ LevelChunk chunkIfCached = this.getFullChunkUnchecked(); -+ if (chunkIfCached != null) { -+ this.chunkMap.level.getChunkSource().entityTickingChunks.remove(chunkIfCached); -+ } -+ // Tuinity end - entity ticking chunk set - } - - if (!playerchunk_state1.isOrAfter(playerchunk_state)) { -@@ -783,11 +850,19 @@ public class ChunkHolder { - // CraftBukkit start - // ChunkLoadEvent: Called after the chunk is loaded: isChunkLoaded returns true and chunk is ready to be modified by plugins. - if (!playerchunk_state.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && playerchunk_state1.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)) { -- this.getStatusFutureUncheckedMain(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main -+ this.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> { // Paper - ensure main // Tuinity - is always on main -+ com.tuinity.tuinity.util.TickThread.ensureTickThread("Async full status chunk future completion"); // Tuinity - LevelChunk chunk = (LevelChunk)either.left().orElse(null); -- if (chunk != null) { -+ if (chunk != null && ChunkHolder.this.oldTicketLevel <= 33 && !chunk.wasLoadCallbackInvoked()) { // Tuinity - ensure ticket level is set to loaded before calling, as now this can complete with ticket level > 33 -+ // Tuinity start - only schedule once, now the future is no longer completed as RIGHT if unloaded... -+ if (ChunkHolder.this.loadCallbackScheduled) { -+ return; -+ } -+ ChunkHolder.this.loadCallbackScheduled = true; -+ // Tuinity end - only schedule once, now the future is no longer completed as RIGHT if unloaded... - chunkStorage.callbackExecutor.execute(() -> { -- chunk.loadCallback(); -+ ChunkHolder.this.loadCallbackScheduled = false; // Tuinity - only schedule once, now the future is no longer completed as RIGHT if unloaded... -+ if (ChunkHolder.this.oldTicketLevel <= 33) chunk.loadCallback(); // Tuinity " - }); - } - }).exceptionally((throwable) -> { -diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java -index 6ed95a0cff3e9c874f14bc90283f750e15765c67..509a4239dbda8d8d7edebfdc92bed84a13def369 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -103,6 +103,7 @@ import org.apache.logging.log4j.LogManager; - import org.apache.logging.log4j.Logger; - - import org.bukkit.entity.Player; // CraftBukkit -+import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; // Tuinity - - public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider { - -@@ -115,8 +116,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - public static final int MAX_VIEW_DISTANCE = 33; - public static final int MAX_CHUNK_DISTANCE = 33 + ChunkStatus.maxDistance(); - // Paper start - faster copying -- public final Long2ObjectLinkedOpenHashMap updatingChunkMap = new com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy<>(); // Paper - faster copying -- public final Long2ObjectLinkedOpenHashMap visibleChunkMap = new ProtectedVisibleChunksMap(); // Paper - faster copying -+ // Tuinity start - Don't copy -+ public final com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object updatingChunks = new com.destroystokyo.paper.util.map.QueuedChangesMapLong2Object<>(); -+ // Tuinity end - Don't copy - - private class ProtectedVisibleChunksMap extends com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy { - @Override -@@ -139,8 +141,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - } - // Paper end -- public final com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy pendingVisibleChunks = new com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy(); // Paper - this is used if the visible chunks is updated while iterating only -- public transient com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy visibleChunksClone; // Paper - used for async access of visible chunks, clone and cache only when needed -+ // Tuinity - Don't copy - public static final int FORCED_TICKET_LEVEL = 31; - // public final Long2ObjectLinkedOpenHashMap updatingChunkMap = new Long2ObjectLinkedOpenHashMap(); // Paper - moved up - // public volatile Long2ObjectLinkedOpenHashMap visibleChunkMap; // Paper - moved up -@@ -148,7 +149,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - public final LongSet entitiesInLevel; - public final ServerLevel level; - private final ThreadedLevelLightEngine lightEngine; -- private final BlockableEventLoop mainThreadExecutor; -+ public final BlockableEventLoop mainThreadExecutor; // Tuinity - public - final java.util.concurrent.Executor mainInvokingExecutor; // Paper - public final ChunkGenerator generator; - public final Supplier overworldDataStorage; -@@ -181,32 +182,29 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - public final CallbackExecutor callbackExecutor = new CallbackExecutor(); - public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable { - -- // Paper start - replace impl with recursive safe multi entry queue -- // it's possible to schedule multiple tasks currently, so it's vital we change this impl -- // If we recurse into the executor again, we will append to another queue, ensuring task order consistency -- private java.util.Queue queue = new java.util.ArrayDeque<>(); // Paper - remove final -+ // Tuinity start - revert paper's change -+ private Runnable queued; - - @Override - public void execute(Runnable runnable) { - org.spigotmc.AsyncCatcher.catchOp("Callback Executor execute"); -- if (this.queue == null) { -- this.queue = new java.util.ArrayDeque<>(); -+ if (queued != null) { -+ MinecraftServer.LOGGER.fatal("Failed to schedule runnable", new IllegalStateException("Already queued")); // Paper - make sure this is printed -+ throw new IllegalStateException("Already queued"); - } -- this.queue.add(runnable); -+ queued = runnable; - } -+ // Tuinity end - revert paper's change - - @Override - public void run() { - org.spigotmc.AsyncCatcher.catchOp("Callback Executor run"); -- if (this.queue == null) { -- return; -- } -- java.util.Queue queue = this.queue; -- this.queue = null; -- // Paper end -- Runnable task; -- while ((task = queue.poll()) != null) { // Paper -+ // Tuinity start - revert paper's change -+ Runnable task = queued; -+ queued = null; -+ if (task != null) { - task.run(); -+ // Tuinity end - revert paper's change - } - } - }; -@@ -215,22 +213,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - final CallbackExecutor chunkLoadConversionCallbackExecutor = new CallbackExecutor(); // Paper - // Paper start - distance maps - private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>(); -- // Paper start - no-tick view distance -- int noTickViewDistance; -- public final int getRawNoTickViewDistance() { -- return this.noTickViewDistance; -- } -- public final int getEffectiveNoTickViewDistance() { -- return this.noTickViewDistance == -1 ? this.getEffectiveViewDistance() : this.noTickViewDistance; -- } -- public final int getLoadViewDistance() { -- return Math.max(this.getEffectiveViewDistance(), this.getEffectiveNoTickViewDistance()); -- } -- -- public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceBroadcastMap; -- public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceTickMap; -- public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerViewDistanceNoTickMap; -- // Paper end - no-tick view distance -+ public final com.tuinity.tuinity.chunk.PlayerChunkLoader playerChunkManager = new com.tuinity.tuinity.chunk.PlayerChunkLoader(this, this.pooledLinkedPlayerHashSets); // Tuinity - replace chunk loader - // Paper start - use distance map to optimise tracker - public static boolean isLegacyTrackingEntity(Entity entity) { - return entity.isLegacyTrackingEntity; -@@ -239,7 +222,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // inlined EnumMap, TrackingRange.TrackingRangeType - static final org.spigotmc.TrackingRange.TrackingRangeType[] TRACKING_RANGE_TYPES = org.spigotmc.TrackingRange.TrackingRangeType.values(); - public final com.destroystokyo.paper.util.misc.PlayerAreaMap[] playerEntityTrackerTrackMaps; -- final int[] entityTrackerTrackRanges; -+ final int[] entityTrackerTrackRanges; public int getEntityTrackerRange(final int ordinal) { return this.entityTrackerTrackRanges[ordinal]; } // Tuinity - public read - - private int convertSpigotRangeToVanilla(final int vanilla) { - return MinecraftServer.getServer().getScaledTrackingDistance(vanilla); -@@ -256,6 +239,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobSpawnMap; // this map is absent from updateMaps since it's controlled at the start of the chunkproviderserver tick - public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerChunkTickRangeMap; - // Paper end - optimise PlayerChunkMap#isOutsideRange -+ // Tuinity start - optimise checkDespawn -+ public static final int GENERAL_AREA_MAP_SQUARE_RADIUS = 40; -+ public static final double GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE = 16.0 * (GENERAL_AREA_MAP_SQUARE_RADIUS - 1); -+ public static final double GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE_SQUARED = GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE * GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE; -+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerGeneralAreaMap; -+ // Tuinity end - optimise checkDespawn - - void addPlayerToDistanceMaps(ServerPlayer player) { - int chunkX = MCUtil.getChunkCoordinate(player.getX()); -@@ -266,7 +255,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i]; - int trackRange = this.entityTrackerTrackRanges[i]; - -- trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, this.getEffectiveViewDistance())); -+ trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, player.getBukkitEntity().getViewDistance())); // Tuinity - per player view distances - } - // Paper end - use distance map to optimise entity tracker - // Paper start - optimise PlayerChunkMap#isOutsideRange -@@ -275,19 +264,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // Paper start - optimise PlayerChunkMap#isOutsideRange - this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); - // Paper end - optimise PlayerChunkMap#isOutsideRange -- // Paper start - no-tick view distance -- int effectiveTickViewDistance = this.getEffectiveViewDistance(); -- int effectiveNoTickViewDistance = Math.max(this.getEffectiveNoTickViewDistance(), effectiveTickViewDistance); -- -- if (!this.skipPlayer(player)) { -- this.playerViewDistanceTickMap.add(player, chunkX, chunkZ, effectiveTickViewDistance); -- this.playerViewDistanceNoTickMap.add(player, chunkX, chunkZ, effectiveNoTickViewDistance + 2); // clients need chunk 1 neighbour, and we need another 1 for sending those extra neighbours (as we require neighbours to send) -- } -- -- player.needsChunkCenterUpdate = true; -- this.playerViewDistanceBroadcastMap.add(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured -- player.needsChunkCenterUpdate = false; -- // Paper end - no-tick view distance -+ this.playerChunkManager.addPlayer(player); // Tuinity - replace chunk loader -+ // Tuinity start - optimise checkDespawn -+ this.playerGeneralAreaMap.add(player, chunkX, chunkZ, GENERAL_AREA_MAP_SQUARE_RADIUS); -+ // Tuinity end - optimise checkDespawn - } - - void removePlayerFromDistanceMaps(ServerPlayer player) { -@@ -300,11 +280,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.playerMobSpawnMap.remove(player); - this.playerChunkTickRangeMap.remove(player); - // Paper end - optimise PlayerChunkMap#isOutsideRange -- // Paper start - no-tick view distance -- this.playerViewDistanceBroadcastMap.remove(player); -- this.playerViewDistanceTickMap.remove(player); -- this.playerViewDistanceNoTickMap.remove(player); -- // Paper end - no-tick view distance -+ this.playerChunkManager.removePlayer(player); // Tuinity - replace chunk loader -+ // Tuinity start - optimise checkDespawn -+ this.playerGeneralAreaMap.remove(player); -+ // Tuinity end - optimise checkDespawn - } - - void updateMaps(ServerPlayer player) { -@@ -316,27 +295,125 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - com.destroystokyo.paper.util.misc.PlayerAreaMap trackMap = this.playerEntityTrackerTrackMaps[i]; - int trackRange = this.entityTrackerTrackRanges[i]; - -- trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, this.getEffectiveViewDistance())); -+ trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, player.getBukkitEntity().getViewDistance())); // Tuinity - per player view distances - } - // Paper end - use distance map to optimise entity tracker - // Paper start - optimise PlayerChunkMap#isOutsideRange - this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); - // Paper end - optimise PlayerChunkMap#isOutsideRange -- // Paper start - no-tick view distance -- int effectiveTickViewDistance = this.getEffectiveViewDistance(); -- int effectiveNoTickViewDistance = Math.max(this.getEffectiveNoTickViewDistance(), effectiveTickViewDistance); -+ this.playerChunkManager.updatePlayer(player); // Tuinity - replace chunk loader -+ // Tuinity start - optimise checkDespawn -+ this.playerGeneralAreaMap.update(player, chunkX, chunkZ, GENERAL_AREA_MAP_SQUARE_RADIUS); -+ // Tuinity end - optimise checkDespawn -+ } -+ // Paper end -+ // Tuinity start -+ public final List regionManagers = new java.util.ArrayList<>(); -+ public final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager dataRegionManager; - -- if (!this.skipPlayer(player)) { -- this.playerViewDistanceTickMap.update(player, chunkX, chunkZ, effectiveTickViewDistance); -- this.playerViewDistanceNoTickMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 2); // clients need chunk 1 neighbour, and we need another 1 for sending those extra neighbours (as we require neighbours to send) -+ public static final class DataRegionData implements com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionData { -+ // Tuinity start - optimise notify() -+ private com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet navigators; -+ -+ public com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet getNavigators() { -+ return this.navigators; -+ } -+ -+ public boolean addToNavigators(final Mob navigator) { -+ if (this.navigators == null) { -+ this.navigators = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(); -+ } -+ return this.navigators.add(navigator); - } - -- player.needsChunkCenterUpdate = true; -- this.playerViewDistanceBroadcastMap.update(player, chunkX, chunkZ, effectiveNoTickViewDistance + 1); // clients need an extra neighbour to render the full view distance configured -- player.needsChunkCenterUpdate = false; -- // Paper end - no-tick view distance -+ public boolean removeFromNavigators(final Mob navigator) { -+ if (this.navigators == null) { -+ return false; -+ } -+ return this.navigators.remove(navigator); -+ } -+ // Tuinity end - optimise notify() - } -- // Paper end -+ -+ public static final class DataRegionSectionData implements com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSectionData { -+ -+ // Tuinity start - optimise notify() -+ private com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet navigators; -+ -+ public com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet getNavigators() { -+ return this.navigators; -+ } -+ -+ public boolean addToNavigators(final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section, final Mob navigator) { -+ if (this.navigators == null) { -+ this.navigators = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(); -+ } -+ final boolean ret = this.navigators.add(navigator); -+ if (ret) { -+ final DataRegionData data = (DataRegionData)section.getRegion().regionData; -+ if (!data.addToNavigators(navigator)) { -+ throw new IllegalStateException(); -+ } -+ } -+ return ret; -+ } -+ -+ public boolean removeFromNavigators(final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section, final Mob navigator) { -+ if (this.navigators == null) { -+ return false; -+ } -+ final boolean ret = this.navigators.remove(navigator); -+ if (ret) { -+ final DataRegionData data = (DataRegionData)section.getRegion().regionData; -+ if (!data.removeFromNavigators(navigator)) { -+ throw new IllegalStateException(); -+ } -+ } -+ return ret; -+ } -+ // Tuinity end - optimise notify() -+ -+ @Override -+ public void removeFromRegion(final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section, -+ final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.Region from) { -+ final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData; -+ final DataRegionData fromData = (DataRegionData)from.regionData; -+ // Tuinity start - optimise notify() -+ if (sectionData.navigators != null) { -+ for (final Iterator iterator = sectionData.navigators.unsafeIterator(com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { -+ if (!fromData.removeFromNavigators(iterator.next())) { -+ throw new IllegalStateException(); -+ } -+ } -+ } -+ // Tuinity end - optimise notify() -+ } -+ -+ @Override -+ public void addToRegion(final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section, -+ final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.Region oldRegion, -+ final com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.Region newRegion) { -+ final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData; -+ final DataRegionData oldRegionData = oldRegion == null ? null : (DataRegionData)oldRegion.regionData; -+ final DataRegionData newRegionData = (DataRegionData)newRegion.regionData; -+ // Tuinity start - optimise notify() -+ if (sectionData.navigators != null) { -+ for (final Iterator iterator = sectionData.navigators.unsafeIterator(com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.ITERATOR_FLAG_SEE_ADDITIONS); iterator.hasNext();) { -+ if (!newRegionData.addToNavigators(iterator.next())) { -+ throw new IllegalStateException(); -+ } -+ } -+ } -+ // Tuinity end - optimise notify() -+ } -+ } -+ -+ public final ChunkHolder getUnloadingChunkHolder(int chunkX, int chunkZ) { -+ return this.pendingUnloads.get(com.tuinity.tuinity.util.CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } -+ // Tuiniy end -+ -+ boolean unloadingPlayerChunk = false; // Tuinity - do not allow ticket level changes while unloading chunks - - public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureManager structureManager, Executor executor, BlockableEventLoop mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory, int viewDistance, boolean dsync) { - super(new File(session.getDimensionPath(world.dimension()), "region"), dataFixer, dsync); -@@ -453,53 +530,28 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - }); - // Paper end - optimise PlayerChunkMap#isOutsideRange -- // Paper start - no-tick view distance -- this.setNoTickViewDistance(this.level.paperConfig.noTickViewDistance); -- this.playerViewDistanceTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, -+ this.setNoTickViewDistance(this.level.paperConfig.noTickViewDistance); // Tuinity - replace chunk loading system -+ // Tuinity start -+ this.dataRegionManager = new com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager(this.level, 2, (1.0 / 3.0), 1, 6, "Data", DataRegionData::new, DataRegionSectionData::new); -+ this.regionManagers.add(this.dataRegionManager); -+ // Tuinity end -+ // Tuinity start - optimise checkDespawn -+ this.playerGeneralAreaMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, - (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, - com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { -- checkHighPriorityChunks(player); -- if (newState.size() != 1) { -- return; -+ LevelChunk chunk = ChunkMap.this.level.getChunkSource().getChunkAtIfCachedImmediately(rangeX, rangeZ); -+ if (chunk != null) { -+ chunk.updateGeneralAreaCache(newState); - } -- LevelChunk chunk = ChunkMap.this.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(rangeX, rangeZ); -- if (chunk == null || !chunk.areNeighboursLoaded(2)) { -- return; -- } -- -- ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ); -- ChunkMap.this.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update - }, - (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, - com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { -- if (newState != null) { -- return; -+ LevelChunk chunk = ChunkMap.this.level.getChunkSource().getChunkAtIfCachedImmediately(rangeX, rangeZ); -+ if (chunk != null) { -+ chunk.updateGeneralAreaCache(newState); - } -- ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ); -- ChunkMap.this.level.getChunkSource().removeTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update -- // Paper start -- ChunkMap.this.level.getChunkSource().clearPriorityTickets(chunkPos); -- }, -- (player, prevPos, newPos) -> { -- player.lastHighPriorityChecked = -1; // reset and recheck -- checkHighPriorityChunks(player); -- }); -- // Paper end -- this.playerViewDistanceNoTickMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets); -- this.playerViewDistanceBroadcastMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets, -- (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, -- com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { -- if (player.needsChunkCenterUpdate) { -- player.needsChunkCenterUpdate = false; -- player.connection.send(new ClientboundSetChunkCacheCenterPacket(currPosX, currPosZ)); -- } -- ChunkMap.this.updateChunkTracking(player, new ChunkPos(rangeX, rangeZ), new Packet[2], false, true); // unloaded, loaded -- }, -- (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, -- com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { -- ChunkMap.this.updateChunkTracking(player, new ChunkPos(rangeX, rangeZ), null, true, false); // unloaded, loaded - }); -- // Paper end - no-tick view distance -+ // Tuinity end - optimise checkDespawn - } - - // Paper start - Chunk Prioritization -@@ -533,6 +585,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - public void checkHighPriorityChunks(ServerPlayer player) { -+ if (true) return; // Tuinity - replace player chunk loader - int currentTick = MinecraftServer.currentTick; - if (currentTick - player.lastHighPriorityChecked < 20 || !player.isRealPlayer) { // weed out fake players - return; -@@ -540,7 +593,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - player.lastHighPriorityChecked = currentTick; - it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap priorities = new it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap(); - -- int viewDistance = getEffectiveNoTickViewDistance(); -+ int viewDistance = 10;//int viewDistance = getEffectiveNoTickViewDistance(); // Tuinity - replace player chunk loader - net.minecraft.core.BlockPos.MutableBlockPos pos = new net.minecraft.core.BlockPos.MutableBlockPos(); - - // Prioritize circular near -@@ -606,7 +659,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - private boolean shouldSkipPrioritization(ChunkPos coord) { -- if (playerViewDistanceNoTickMap.getObjectsInRange(coord.toLong()) == null) return true; -+ if (true) return true; // Tuinity - replace player chunk loader - unused outside paper player loader logic - ChunkHolder chunk = getUpdatingChunkIfPresent(coord.toLong()); - return chunk != null && (chunk.isFullChunkReady()); - } -@@ -674,7 +727,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - @Nullable - public ChunkHolder getUpdatingChunkIfPresent(long pos) { -- return (ChunkHolder) this.updatingChunkMap.get(pos); -+ return this.updatingChunks.getUpdating(pos); // Tuinity - Don't copy - } - - // Paper start - remove cloning of visible chunks unless accessed as a collection async -@@ -682,47 +735,25 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - private boolean isIterating = false; - private boolean hasPendingVisibleUpdate = false; - public void forEachVisibleChunk(java.util.function.Consumer consumer) { -- org.spigotmc.AsyncCatcher.catchOp("forEachVisibleChunk"); -- boolean prev = isIterating; -- isIterating = true; -- try { -- for (ChunkHolder value : this.visibleChunkMap.values()) { -- consumer.accept(value); -- } -- } finally { -- this.isIterating = prev; -- if (!this.isIterating && this.hasPendingVisibleUpdate) { -- ((ProtectedVisibleChunksMap)this.visibleChunkMap).copyFrom(this.pendingVisibleChunks); -- this.pendingVisibleChunks.clear(); -- this.hasPendingVisibleUpdate = false; -- } -- } -+ throw new UnsupportedOperationException(); // Tuinity - Don't copy - } - public Long2ObjectLinkedOpenHashMap getVisibleChunks() { -- if (Thread.currentThread() == this.level.thread) { -- return this.visibleChunkMap; -- } else { -- synchronized (this.visibleChunkMap) { -- if (DEBUG_ASYNC_VISIBLE_CHUNKS) new Throwable("Async getVisibleChunks").printStackTrace(); -- if (this.visibleChunksClone == null) { -- this.visibleChunksClone = this.hasPendingVisibleUpdate ? this.pendingVisibleChunks.clone() : ((ProtectedVisibleChunksMap)this.visibleChunkMap).clone(); -- } -- return this.visibleChunksClone; -- } -+ // Tuinity start - Don't copy (except in rare cases) -+ synchronized (this.updatingChunks) { -+ return this.updatingChunks.getVisibleMap().clone(); - } -+ // Tuinity end - Don't copy (except in rare cases) - } - // Paper end - - @Nullable - public ChunkHolder getVisibleChunkIfPresent(long pos) { -- // Paper start - mt safe get -- if (Thread.currentThread() != this.level.thread) { -- synchronized (this.visibleChunkMap) { -- return (ChunkHolder) (this.hasPendingVisibleUpdate ? this.pendingVisibleChunks.get(pos) : ((ProtectedVisibleChunksMap)this.visibleChunkMap).safeGet(pos)); -- } -+ // Tuinity start - Don't copy -+ if (Thread.currentThread() == this.level.thread) { -+ return this.updatingChunks.getVisible(pos); - } -- return (ChunkHolder) (this.hasPendingVisibleUpdate ? this.pendingVisibleChunks.get(pos) : ((ProtectedVisibleChunksMap)this.visibleChunkMap).safeGet(pos)); -- // Paper end -+ return this.updatingChunks.getVisibleAsync(pos); -+ // Tuinity end - Don't copy - } - - protected IntSupplier getChunkQueueLevel(long pos) { -@@ -844,12 +875,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - - @Nullable - ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k) { -+ if (this.unloadingPlayerChunk) { LOGGER.fatal("Cannot tick distance manager while unloading playerchunks", new Throwable()); throw new IllegalStateException("Cannot tick distance manager while unloading playerchunks"); } // Tuinity - if (k > ChunkMap.MAX_CHUNK_DISTANCE && level > ChunkMap.MAX_CHUNK_DISTANCE) { - return holder; - } else { - if (holder != null) { - holder.setTicketLevel(level); -- holder.updateRanges(); // Paper - optimise isOutsideOfRange -+ // Tuinity - move to correct place - } - - if (holder != null) { -@@ -864,11 +896,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - holder = (ChunkHolder) this.pendingUnloads.remove(pos); - if (holder != null) { - holder.setTicketLevel(level); -+ holder.updateRanges(); // Paper - optimise isOutsideOfRange // Tuinity - move to correct place - } else { - holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this.queueSorter, this); -+ // Tuinity start -+ for (int index = 0, len = this.regionManagers.size(); index < len; ++index) { -+ this.regionManagers.get(index).addChunk(holder.pos.x, holder.pos.z); -+ } -+ // Tuinity end - } - -- this.updatingChunkMap.put(pos, holder); -+ this.updatingChunks.queueUpdate(pos, holder); // Tuinity - Don't copy - this.modified = true; - } - -@@ -1023,7 +1061,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - while (longiterator.hasNext()) { // Spigot - long j = longiterator.nextLong(); - longiterator.remove(); // Spigot -- ChunkHolder playerchunk = (ChunkHolder) this.updatingChunkMap.remove(j); -+ ChunkHolder playerchunk = this.updatingChunks.queueRemove(j); // Tuinity - Don't copy - - if (playerchunk != null) { - this.pendingUnloads.put(j, playerchunk); -@@ -1060,7 +1098,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.level, chunkPos.x, chunkPos.z, -- poiData, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.LOW_PRIORITY); -+ poiData, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY); // Tuinity - use normal priority - - if (!chunk.isUnsaved()) { - return; -@@ -1081,7 +1119,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - asyncSaveData = ChunkSerializer.getAsyncSaveData(this.level, chunk); - } - -- this.level.asyncChunkTaskManager.scheduleChunkSave(chunkPos.x, chunkPos.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.LOW_PRIORITY, -+ this.level.asyncChunkTaskManager.scheduleChunkSave(chunkPos.x, chunkPos.z, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY, // Tuinity - use normal priority - asyncSaveData, chunk); - - chunk.setUnsaved(false); -@@ -1097,7 +1135,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - if (completablefuture1 != completablefuture) { - this.scheduleUnload(pos, holder); - } else { -- if (this.pendingUnloads.remove(pos, holder) && ichunkaccess != null) { -+ // Tuinity start - do not allow ticket level changes while unloading chunks -+ org.spigotmc.AsyncCatcher.catchOp("playerchunk unload"); -+ boolean unloadingBefore = this.unloadingPlayerChunk; -+ this.unloadingPlayerChunk = true; -+ try { -+ // Tuinity end - do not allow ticket level changes while unloading chunks -+ // Tuinity start -+ boolean removed; -+ if ((removed = this.pendingUnloads.remove(pos, holder)) && ichunkaccess != null) { -+ for (int index = 0, len = this.regionManagers.size(); index < len; ++index) { -+ this.regionManagers.get(index).removeChunk(holder.pos.x, holder.pos.z); -+ } -+ // Tuinity end - if (ichunkaccess instanceof LevelChunk) { - ((LevelChunk) ichunkaccess).setLoaded(false); - } -@@ -1122,7 +1172,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.lightEngine.updateChunkStatus(ichunkaccess.getPos()); - this.lightEngine.tryScheduleUpdate(); - this.progressListener.onStatusChange(ichunkaccess.getPos(), (ChunkStatus) null); -+ } else if (removed) { // Tuinity start -+ for (int index = 0, len = this.regionManagers.size(); index < len; ++index) { -+ this.regionManagers.get(index).removeChunk(holder.pos.x, holder.pos.z); -+ } - } -+ // Tuinity end -+ } finally { this.unloadingPlayerChunk = unloadingBefore; } // Tuinity - do not allow ticket level changes while unloading chunks - - } - }; -@@ -1141,19 +1197,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - if (!this.modified) { - return false; - } else { -- // Paper start - stop cloning visibleChunks -- synchronized (this.visibleChunkMap) { -- if (isIterating) { -- hasPendingVisibleUpdate = true; -- this.pendingVisibleChunks.copyFrom((com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy)this.updatingChunkMap); -- } else { -- hasPendingVisibleUpdate = false; -- this.pendingVisibleChunks.clear(); -- ((ProtectedVisibleChunksMap)this.visibleChunkMap).copyFrom((com.destroystokyo.paper.util.map.Long2ObjectLinkedOpenHashMapFastCopy)this.updatingChunkMap); -- this.visibleChunksClone = null; -- } -+ // Tuinity start - Don't copy -+ synchronized (this.updatingChunks) { -+ this.updatingChunks.performUpdates(); - } -- // Paper end -+ // Tuinity end - Don't copy - - this.modified = false; - return true; -@@ -1166,11 +1214,20 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - if (requiredStatus == ChunkStatus.EMPTY) { - return this.scheduleChunkLoad(chunkcoordintpair); - } else { -+ // Tuinity start - revert 1.17 chunk system changes -+ CompletableFuture> future = holder.getOrScheduleFuture(requiredStatus.getParent(), this); -+ return future.thenComposeAsync((either) -> { -+ Optional optional = either.left(); -+ if (!optional.isPresent()) { -+ return CompletableFuture.completedFuture(either); -+ } -+ // Tuinity end - revert 1.17 chunk system changes -+ -+ // Tuinity start - revert 1.17 chunk system changes - if (requiredStatus == ChunkStatus.LIGHT) { - this.distanceManager.addTicket(TicketType.LIGHT, chunkcoordintpair, 33 + ChunkStatus.getDistance(ChunkStatus.FEATURES), chunkcoordintpair); - } -- -- Optional optional = ((Either) holder.getOrScheduleFuture(requiredStatus.getParent(), this).getNow(ChunkHolder.UNLOADED_CHUNK)).left(); -+ // Tuinity end - revert 1.17 chunk system changes - - if (optional.isPresent() && ((ChunkAccess) optional.get()).getStatus().isOrAfter(requiredStatus)) { - CompletableFuture> completablefuture = requiredStatus.load(this.level, this.structureManager, this.lightEngine, (ichunkaccess) -> { -@@ -1182,6 +1239,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } else { - return this.scheduleChunkGeneration(holder, requiredStatus); - } -+ }, this.mainThreadExecutor).thenComposeAsync(CompletableFuture::completedFuture, this.mainThreadExecutor); // Tuinity - revert 1.17 chunk system changes - } - } - -@@ -1282,7 +1340,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - }); - Executor executor = (runnable) -> { - // Paper start - optimize chunk status progression without jumping through thread pool -- if (holder.canAdvanceStatus()) { -+ if (false && holder.canAdvanceStatus()) { // Tuinity - these optimisations will be revisited later - this.mainInvokingExecutor.execute(runnable); - return; - } -@@ -1313,7 +1371,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - this.releaseLightTicket(chunkcoordintpair); - return CompletableFuture.completedFuture(Either.right(playerchunk_failure)); - }); -- }, executor); -+ }, executor).thenComposeAsync((either) -> { // Tuinity start - force competion on the main thread -+ return CompletableFuture.completedFuture(either); -+ }, this.mainThreadExecutor); // use the main executor, we want to ensure only one chunk callback can be completed per runnable execute -+ // Tuinity end - force competion on the main thread - } - - protected void releaseLightTicket(ChunkPos pos) { -@@ -1472,9 +1533,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - chunk.unpackTicks(); - return chunk; - }); -- }, (runnable) -> { -- this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(playerchunk, runnable)); -- }); -+ }, this.mainThreadExecutor); // Tuinity - queue to execute immediately so this doesn't delay chunk unloading - } - - public int getTickingGenerated() { -@@ -1559,7 +1618,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - int k = this.viewDistance; - - this.viewDistance = j; -- this.setNoTickViewDistance(this.getRawNoTickViewDistance()); //Paper - no-tick view distance - propagate changes to no-tick, which does the actual chunk loading/sending -+ this.playerChunkManager.setTickDistance(Mth.clamp(viewDistance, 2, 32)); // Tuinity - replace player loader system - } - - } -@@ -1567,26 +1626,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // Paper start - no-tick view distance - public final void setNoTickViewDistance(int viewDistance) { - viewDistance = viewDistance == -1 ? -1 : Mth.clamp(viewDistance, 2, 32); -- -- this.noTickViewDistance = viewDistance; -- int loadViewDistance = this.getLoadViewDistance(); -- this.distanceManager.setNoTickViewDistance(loadViewDistance + 2 + 2); // add 2 to account for the change to 31 -> 33 tickets // see notes in the distance map updating for the other + 2 -- -- if (this.level != null && this.level.players != null) { // this can be called from constructor, where these aren't set -- for (ServerPlayer player : this.level.players) { -- net.minecraft.server.network.ServerGamePacketListenerImpl connection = player.connection; -- if (connection != null) { -- // moved in from PlayerList -- connection.send(new net.minecraft.network.protocol.game.ClientboundSetChunkCacheRadiusPacket(loadViewDistance)); -- } -- this.updateMaps(player); -- // Paper end - no-tick view distance -- } -- } -+ this.playerChunkManager.setLoadDistance(viewDistance == -1 ? -1 : viewDistance + 1); // Tuinity - replace player loader system - add 1 here, we need an extra one to send to clients for chunks in this viewDistance to render - - } - -- protected void updateChunkTracking(ServerPlayer player, ChunkPos pos, Packet[] packets, boolean withinMaxWatchDistance, boolean withinViewDistance) { -+ public void updateChunkTracking(ServerPlayer player, ChunkPos pos, Packet[] packets, boolean withinMaxWatchDistance, boolean withinViewDistance) { // Tuinity - public - if (player.level == this.level) { - if (withinViewDistance && !withinMaxWatchDistance) { - ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.toLong()); -@@ -1610,7 +1654,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - } - - public int size() { -- return this.visibleChunkMap.size(); -+ return this.updatingChunks.getVisibleMap().size(); // Tuinity - Don't copy - } - - protected DistanceManager getDistanceManager() { -@@ -1915,6 +1959,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - */ // Paper end - replaced by distance map - - this.updateMaps(player); // Paper - distance maps -+ this.playerChunkManager.updatePlayer(player); // Tuinity - respond to movement immediately - - } - -@@ -1923,7 +1968,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - // Paper start - per player view distance - // there can be potential desync with player's last mapped section and the view distance map, so use the - // view distance map here. -- com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet inRange = this.playerViewDistanceBroadcastMap.getObjectsInRange(chunkPos); -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet inRange = this.playerChunkManager.broadcastMap.getObjectsInRange(chunkPos); // Tuinity - replace player chunk loader system - - if (inRange == null) { - return Stream.empty(); -@@ -1939,8 +1984,9 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - continue; - } - ServerPlayer player = (ServerPlayer)temp; -- int viewDistance = this.playerViewDistanceBroadcastMap.getLastViewDistance(player); -- long lastPosition = this.playerViewDistanceBroadcastMap.getLastCoordinate(player); -+ if (!this.playerChunkManager.isChunkSent(player, chunkPos.x, chunkPos.z)) continue; // Tuinity - replace player chunk management -+ int viewDistance = this.playerChunkManager.broadcastMap.getLastViewDistance(player); // Tuinity - replace player chunk loader system -+ long lastPosition = this.playerChunkManager.broadcastMap.getLastCoordinate(player); // Tuinity - replace player chunk loader system - - int distX = Math.abs(MCUtil.getCoordinateX(lastPosition) - chunkPos.x); - int distZ = Math.abs(MCUtil.getCoordinateZ(lastPosition) - chunkPos.z); -@@ -1955,6 +2001,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - continue; - } - ServerPlayer player = (ServerPlayer)temp; -+ if (!this.playerChunkManager.isChunkSent(player, chunkPos.x, chunkPos.z)) continue; // Tuinity - replace player chunk management - players.add(player); - } - } -@@ -2265,7 +2312,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially - final Entity entity; - private final int range; - SectionPos lastSectionPos; -- public final Set seenBy = Sets.newIdentityHashSet(); -+ public final Set seenBy = new ReferenceOpenHashSet<>(); // Tuinity - optimise map impl - - public TrackedEntity(Entity entity, int i, int j, boolean flag) { - this.serverEntity = new ServerEntity(ChunkMap.this.level, entity, j, flag, this::broadcast, this.seenBy); // CraftBukkit -@@ -2365,7 +2412,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially - double vec3d_dy = player.getY() - this.entity.getY(); - double vec3d_dz = player.getZ() - this.entity.getZ(); - // Paper end - remove allocation of Vec3D here -- int i = Math.min(this.getEffectiveRange(), (ChunkMap.this.viewDistance - 1) * 16); -+ int i = Math.min(this.getEffectiveRange(), player.getBukkitEntity().getViewDistance() * 16); // Tuinity - per player view distance - boolean flag = vec3d_dx >= (double) (-i) && vec3d_dx <= (double) i && vec3d_dz >= (double) (-i) && vec3d_dz <= (double) i && this.entity.broadcastToPlayer(player); // Paper - remove allocation of Vec3D here - - // CraftBukkit start - respect vanish API -@@ -2400,7 +2447,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially - int j = entity.getType().clientTrackingRange() * 16; - j = org.spigotmc.TrackingRange.getEntityTrackingRange(entity, j); // Paper - -- if (j < i) { // Paper - we need the lowest range thanks to the fact that our tracker doesn't account for passenger logic -+ if (j > i) { // Paper - we need the lowest range thanks to the fact that our tracker doesn't account for passenger logic // Tuinity - not anymore! - i = j; - } - } -diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java -index 1cc4e0a1f3d8235ef88b48e01ca8b78a263d2676..428d94c60b826ddf3797d6713661dff1ca835ac2 100644 ---- a/src/main/java/net/minecraft/server/level/DistanceManager.java -+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -36,6 +36,7 @@ import net.minecraft.world.level.chunk.LevelChunk; - import org.apache.logging.log4j.LogManager; - import org.apache.logging.log4j.Logger; - -+import it.unimi.dsi.fastutil.longs.Long2IntLinkedOpenHashMap; // Tuinity - public abstract class DistanceManager { - - static final Logger LOGGER = LogManager.getLogger(); -@@ -44,9 +45,9 @@ public abstract class DistanceManager { - private static final int INITIAL_TICKET_LIST_CAPACITY = 4; - final Long2ObjectMap> playersPerChunk = new Long2ObjectOpenHashMap(); - public final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap(); -- private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); -+ //private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker(); // Tuinity - replace ticket level propagator - public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used -- private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); -+ //private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); // Tuinity - no longer used - // Paper start use a queue, but still keep unique requirement - public final java.util.Queue pendingChunkUpdates = new java.util.ArrayDeque() { - @Override -@@ -77,6 +78,46 @@ public abstract class DistanceManager { - this.mainThreadExecutor = mainThreadExecutor; - } - -+ // Tuinity start - replace ticket level propagator -+ protected final Long2IntLinkedOpenHashMap ticketLevelUpdates = new Long2IntLinkedOpenHashMap() { -+ @Override -+ protected void rehash(int newN) { -+ // no downsizing allowed -+ if (newN < this.n) { -+ return; -+ } -+ super.rehash(newN); -+ } -+ }; -+ protected final com.tuinity.tuinity.util.misc.Delayed8WayDistancePropagator2D ticketLevelPropagator = new com.tuinity.tuinity.util.misc.Delayed8WayDistancePropagator2D( -+ (long coordinate, byte oldLevel, byte newLevel) -> { -+ DistanceManager.this.ticketLevelUpdates.putAndMoveToLast(coordinate, convertBetweenTicketLevels(newLevel)); -+ } -+ ); -+ // function for converting between ticket levels and propagator levels and vice versa -+ // the problem is the ticket level propagator will propagate from a set source down to zero, whereas mojang expects -+ // levels to propagate from a set value up to a maximum value. so we need to convert the levels we put into the propagator -+ // and the levels we get out of the propagator -+ -+ // this maps so that GOLDEN_TICKET + 1 will be 0 in the propagator, GOLDEN_TICKET will be 1, and so on -+ // we need GOLDEN_TICKET+1 as 0 because anything >= GOLDEN_TICKET+1 should be unloaded -+ public static int convertBetweenTicketLevels(final int level) { -+ return ChunkMap.MAX_CHUNK_DISTANCE - level + 1; -+ } -+ -+ protected final int getPropagatedTicketLevel(final long coordinate) { -+ return convertBetweenTicketLevels(this.ticketLevelPropagator.getLevel(coordinate)); -+ } -+ -+ protected final void updateTicketLevel(final long coordinate, final int ticketLevel) { -+ if (ticketLevel > ChunkMap.MAX_CHUNK_DISTANCE) { -+ this.ticketLevelPropagator.removeSource(coordinate); -+ } else { -+ this.ticketLevelPropagator.setSource(coordinate, convertBetweenTicketLevels(ticketLevel)); -+ } -+ } -+ // Tuinity end - replace ticket level propagator -+ - protected void purgeStaleTickets() { - ++this.ticketTickCounter; - ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator(); -@@ -87,7 +128,7 @@ public abstract class DistanceManager { - if ((entry.getValue()).removeIf((ticket) -> { // CraftBukkit - decompile error - return ticket.timedOut(this.ticketTickCounter); - })) { -- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false); -+ this.updateTicketLevel(entry.getLongKey(), getTicketLevelAt(entry.getValue())); // Tuinity - replace ticket level propagator - } - - if (((SortedArraySet) entry.getValue()).isEmpty()) { -@@ -110,60 +151,93 @@ public abstract class DistanceManager { - @Nullable - protected abstract ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k); - -+ protected long ticketLevelUpdateCount; // Tuinity - replace ticket level propagator - public boolean runAllUpdates(ChunkMap playerchunkmap) { - //this.f.a(); // Paper - no longer used - org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper -- this.playerTicketManager.runAllUpdates(); -- int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE); -- boolean flag = i != 0; -+ //this.playerTicketManager.runAllUpdates(); // Tuinity - no longer used -+ boolean flag = this.ticketLevelPropagator.propagateUpdates(); // Tuinity - replace ticket level propagator - - if (flag) { - ; - } - -- // Paper start -- if (!this.pendingChunkUpdates.isEmpty()) { -- this.pollingPendingChunkUpdates = true; try { // Paper - Chunk priority -- while(!this.pendingChunkUpdates.isEmpty()) { -- ChunkHolder remove = this.pendingChunkUpdates.remove(); -- remove.isUpdateQueued = false; -- remove.updateFutures(playerchunkmap, this.mainThreadExecutor); -- } -- } finally { this.pollingPendingChunkUpdates = false; } // Paper - Chunk priority -- // Paper end -- return true; -- } else { -- if (!this.ticketsToRelease.isEmpty()) { -- LongIterator longiterator = this.ticketsToRelease.iterator(); -+ // Tuinity start - replace level propagator -+ ticket_update_loop: -+ while (!this.ticketLevelUpdates.isEmpty()) { -+ flag = true; - -- while (longiterator.hasNext()) { -- long j = longiterator.nextLong(); -+ boolean oldPolling = this.pollingPendingChunkUpdates; -+ this.pollingPendingChunkUpdates = true; -+ try { -+ for (java.util.Iterator iterator = this.ticketLevelUpdates.long2IntEntrySet().fastIterator(); iterator.hasNext();) { -+ Long2IntMap.Entry entry = iterator.next(); -+ long key = entry.getLongKey(); -+ int newLevel = entry.getIntValue(); -+ ChunkHolder chunk = this.getChunk(key); -+ -+ if (chunk == null && newLevel > ChunkMap.MAX_CHUNK_DISTANCE) { -+ // not loaded and it shouldn't be loaded! -+ continue; -+ } -+ -+ int currentLevel = chunk == null ? ChunkMap.MAX_CHUNK_DISTANCE + 1 : chunk.getTicketLevel(); -+ -+ if (currentLevel == newLevel) { -+ // nothing to do -+ continue; -+ } - -- if (this.getTickets(j).stream().anyMatch((ticket) -> { -- return ticket.getType() == TicketType.PLAYER; -- })) { -- ChunkHolder playerchunk = playerchunkmap.getUpdatingChunkIfPresent(j); -+ this.updateChunkScheduling(key, newLevel, chunk, currentLevel); -+ } - -- if (playerchunk == null) { -- throw new IllegalStateException(); -+ long recursiveCheck = ++this.ticketLevelUpdateCount; -+ while (!this.ticketLevelUpdates.isEmpty()) { -+ long key = this.ticketLevelUpdates.firstLongKey(); -+ int newLevel = this.ticketLevelUpdates.removeFirstInt(); -+ ChunkHolder chunk = this.getChunk(key); -+ -+ if (chunk == null) { -+ if (newLevel <= ChunkMap.MAX_CHUNK_DISTANCE) { -+ throw new IllegalStateException("Expected chunk holder to be created"); - } -+ // not loaded and it shouldn't be loaded! -+ continue; -+ } - -- CompletableFuture> completablefuture = playerchunk.getEntityTickingChunkFuture(); -+ int currentLevel = chunk.oldTicketLevel; - -- completablefuture.thenAccept((either) -> { -- this.mainThreadExecutor.execute(() -> { -- this.ticketThrottlerReleaser.tell(ChunkTaskPriorityQueueSorter.release(() -> { -- }, j, false)); -- }); -- }); -+ if (currentLevel == newLevel) { -+ // nothing to do -+ continue; -+ } -+ -+ chunk.updateFutures(playerchunkmap, this.mainThreadExecutor); -+ if (recursiveCheck != this.ticketLevelUpdateCount) { -+ // back to the start, we must create player chunks and update the ticket level fields before -+ // processing the actual level updates -+ continue ticket_update_loop; - } - } - -- this.ticketsToRelease.clear(); -- } -+ for (;;) { -+ if (recursiveCheck != this.ticketLevelUpdateCount) { -+ continue ticket_update_loop; -+ } -+ ChunkHolder pendingUpdate = this.pendingChunkUpdates.poll(); -+ if (pendingUpdate == null) { -+ break; -+ } - -- return flag; -+ pendingUpdate.updateFutures(playerchunkmap, this.mainThreadExecutor); -+ } -+ } finally { -+ this.pollingPendingChunkUpdates = oldPolling; -+ } - } -+ -+ return flag; -+ // Tuinity end - replace level propagator - } - boolean pollingPendingChunkUpdates = false; // Paper - Chunk priority - -@@ -175,7 +249,7 @@ public abstract class DistanceManager { - - ticket1.setCreatedTick(this.ticketTickCounter); - if (ticket.getTicketLevel() < j) { -- this.ticketTracker.update(i, ticket.getTicketLevel(), true); -+ this.updateTicketLevel(i, ticket.getTicketLevel()); // Tuinity - replace ticket level propagator - } - - return ticket == ticket1; // CraftBukkit -@@ -219,7 +293,7 @@ public abstract class DistanceManager { - // Paper start - Chunk priority - int newLevel = getTicketLevelAt(arraysetsorted); - if (newLevel > oldLevel) { -- this.ticketTracker.update(i, newLevel, false); -+ this.updateTicketLevel(i, newLevel); // Paper // Tuinity - replace ticket level propagator - } - // Paper end - return removed; // CraftBukkit -@@ -313,7 +387,7 @@ public abstract class DistanceManager { - org.spigotmc.AsyncCatcher.catchOp("ChunkMapDistance::addPriorityTicket"); - long pair = coords.toLong(); - ChunkHolder chunk = chunkMap.getUpdatingChunkIfPresent(pair); -- boolean needsTicket = chunkMap.playerViewDistanceNoTickMap.getObjectsInRange(pair) != null && !hasPlayerTicket(coords, 33); -+ boolean needsTicket = false; // Tuinity - replace old loader system - - if (needsTicket) { - Ticket ticket = new Ticket<>(TicketType.PLAYER, 33, coords); -@@ -411,7 +485,7 @@ public abstract class DistanceManager { - return new ObjectOpenHashSet(); - })).add(player); - //this.f.update(i, 0, true); // Paper - no longer used -- this.playerTicketManager.update(i, 0, true); -+ //this.playerTicketManager.update(i, 0, true); // Tuinity - no longer used - } - - public void removePlayer(SectionPos pos, ServerPlayer player) { -@@ -423,7 +497,7 @@ public abstract class DistanceManager { - if (objectset == null || objectset.isEmpty()) { // Paper - this.playersPerChunk.remove(i); - //this.f.update(i, Integer.MAX_VALUE, false); // Paper - no longer used -- this.playerTicketManager.update(i, Integer.MAX_VALUE, false); -+ //this.playerTicketManager.update(i, Integer.MAX_VALUE, false); // Tuinity - no longer used - } - - } -@@ -442,7 +516,7 @@ public abstract class DistanceManager { - } - - protected void setNoTickViewDistance(int i) { // Paper - force abi breakage on usage change -- this.playerTicketManager.updateViewDistance(i); -+ throw new UnsupportedOperationException("use world api"); // Tuinity - no longer relevant - } - - public int getNaturalSpawnChunkCount() { -@@ -507,7 +581,7 @@ public abstract class DistanceManager { - SortedArraySet> tickets = entry.getValue(); - if (tickets.remove(target)) { - // copied from removeTicket -- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt(tickets), false); -+ this.updateTicketLevel(entry.getLongKey(), getTicketLevelAt(tickets)); // Tuinity - replace ticket level propagator - - // can't use entry after it's removed - if (tickets.isEmpty()) { -@@ -563,6 +637,7 @@ public abstract class DistanceManager { - } - } - -+ /* Tuinity - replace old loader system - private class FixedPlayerDistanceChunkTracker extends ChunkTracker { - - protected final Long2ByteMap chunks = new Long2ByteOpenHashMap(); -@@ -858,4 +933,5 @@ public abstract class DistanceManager { - } - // Paper end - } -+ */ // Tuinity - replace old loader system - } -diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -index 82d3cfb2d346a8b929e9469ae09369f6a639f81d..958b7044c196ebd66f60391c33c64ad2ff82d4e8 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -48,6 +48,7 @@ import net.minecraft.world.level.storage.LevelStorageSource; - import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; // Paper - - public class ServerChunkCache extends ChunkSource { -+ public static final org.apache.logging.log4j.Logger LOGGER = org.apache.logging.log4j.LogManager.getLogger(); // Tuinity - - public static final List CHUNK_STATUSES = ChunkStatus.getStatusList(); - private final DistanceManager distanceManager; -@@ -139,7 +140,7 @@ public class ServerChunkCache extends ChunkSource { - return (LevelChunk)this.getChunk(x, z, ChunkStatus.FULL, true); - } - -- private long chunkFutureAwaitCounter; -+ long chunkFutureAwaitCounter; // Tuinity - private -> package private - - public void getEntityTickingChunkAsync(int x, int z, java.util.function.Consumer onLoad) { - if (Thread.currentThread() != this.mainThread) { -@@ -201,9 +202,9 @@ public class ServerChunkCache extends ChunkSource { - - try { - if (onLoad != null) { -- chunkMap.callbackExecutor.execute(() -> { -+ // Tuinity - revert incorrect use of callback executor - onLoad.accept(either == null ? null : either.left().orElse(null)); // indicate failure to the callback. -- }); -+ // Tuinity - revert incorrect use of callback executor - } - } catch (Throwable thr) { - if (thr instanceof ThreadDeath) { -@@ -227,6 +228,166 @@ public class ServerChunkCache extends ChunkSource { - } - // Paper end - rewrite ticklistserver - -+ // Tuinity start -+ // this will try to avoid chunk neighbours for lighting -+ public final ChunkAccess getFullStatusChunkAt(int chunkX, int chunkZ) { -+ LevelChunk ifLoaded = this.getChunkAtIfLoadedImmediately(chunkX, chunkZ); -+ if (ifLoaded != null) { -+ return ifLoaded; -+ } -+ -+ ChunkAccess empty = this.getChunk(chunkX, chunkZ, ChunkStatus.EMPTY, true); -+ if (empty != null && empty.getStatus().isOrAfter(ChunkStatus.FULL)) { -+ return empty; -+ } -+ return this.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true); -+ } -+ -+ public final ChunkAccess getFullStatusChunkAtIfLoaded(int chunkX, int chunkZ) { -+ LevelChunk ifLoaded = this.getChunkAtIfLoadedImmediately(chunkX, chunkZ); -+ if (ifLoaded != null) { -+ return ifLoaded; -+ } -+ -+ ChunkAccess ret = this.getChunkAtImmediately(chunkX, chunkZ); -+ if (ret != null && ret.getStatus().isOrAfter(ChunkStatus.FULL)) { -+ return ret; -+ } else { -+ return null; -+ } -+ } -+ -+ void getChunkAtAsynchronously(int chunkX, int chunkZ, int ticketLevel, -+ java.util.function.Consumer consumer) { -+ this.getChunkAtAsynchronously(chunkX, chunkZ, ticketLevel, (ChunkHolder chunkHolder) -> { -+ if (ticketLevel <= 33) { -+ return (CompletableFuture)chunkHolder.getFullChunkFuture(); -+ } else { -+ return chunkHolder.getOrScheduleFuture(ChunkHolder.getStatus(ticketLevel), ServerChunkCache.this.chunkMap); -+ } -+ }, consumer); -+ } -+ -+ void getChunkAtAsynchronously(int chunkX, int chunkZ, int ticketLevel, -+ java.util.function.Function>> function, -+ java.util.function.Consumer consumer) { -+ if (Thread.currentThread() != this.mainThread) { -+ throw new IllegalStateException(); -+ } -+ -+ ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); -+ Long identifier = Long.valueOf(this.chunkFutureAwaitCounter++); -+ this.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier); -+ this.runDistanceManagerUpdates(); -+ -+ ChunkHolder chunk = this.chunkMap.getUpdatingChunkIfPresent(chunkPos.toLong()); -+ -+ if (chunk == null) { -+ throw new IllegalStateException("Expected playerchunk " + chunkPos + " in world '" + this.level.getWorld().getName() + "'"); -+ } -+ -+ CompletableFuture> future = function.apply(chunk); -+ -+ future.whenCompleteAsync((either, throwable) -> { -+ try { -+ if (throwable != null) { -+ if (throwable instanceof ThreadDeath) { -+ throw (ThreadDeath)throwable; -+ } -+ LOGGER.fatal("Failed to complete future await for chunk " + chunkPos.toString() + " in world '" + ServerChunkCache.this.level.getWorld().getName() + "'", throwable); -+ } else if (either.right().isPresent()) { -+ LOGGER.fatal("Failed to complete future await for chunk " + chunkPos.toString() + " in world '" + ServerChunkCache.this.level.getWorld().getName() + "': " + either.right().get().toString()); -+ } -+ -+ try { -+ if (consumer != null) { -+ consumer.accept(either == null ? null : either.left().orElse(null)); // indicate failure to the callback. -+ } -+ } catch (Throwable thr) { -+ if (thr instanceof ThreadDeath) { -+ throw (ThreadDeath)thr; -+ } -+ LOGGER.fatal("Load callback for future await failed " + chunkPos.toString() + " in world '" + ServerChunkCache.this.level.getWorld().getName() + "'", thr); -+ return; -+ } -+ } finally { -+ // due to odd behaviour with CB unload implementation we need to have these AFTER the load callback. -+ ServerChunkCache.this.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos); -+ ServerChunkCache.this.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, identifier); -+ } -+ }, this.mainThreadProcessor); -+ } -+ -+ void chunkLoadAccept(int chunkX, int chunkZ, ChunkAccess chunk, java.util.function.Consumer consumer) { -+ try { -+ consumer.accept(chunk); -+ } catch (Throwable throwable) { -+ if (throwable instanceof ThreadDeath) { -+ throw (ThreadDeath)throwable; -+ } -+ LOGGER.error("Load callback for chunk " + chunkX + "," + chunkZ + " in world '" + this.level.getWorld().getName() + "' threw an exception", throwable); -+ } -+ } -+ -+ public final void getChunkAtAsynchronously(int chunkX, int chunkZ, ChunkStatus status, boolean gen, boolean allowSubTicketLevel, java.util.function.Consumer onLoad) { -+ // try to fire sync -+ int chunkStatusTicketLevel = 33 + ChunkStatus.getDistance(status); -+ ChunkHolder playerChunk = this.chunkMap.getUpdatingChunkIfPresent(com.tuinity.tuinity.util.CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ if (playerChunk != null) { -+ ChunkStatus holderStatus = playerChunk.getChunkHolderStatus(); -+ ChunkAccess immediate = playerChunk.getAvailableChunkNow(); -+ if (immediate != null) { -+ if (allowSubTicketLevel ? immediate.getStatus().isOrAfter(status) : (playerChunk.getTicketLevel() <= chunkStatusTicketLevel && holderStatus != null && holderStatus.isOrAfter(status))) { -+ this.chunkLoadAccept(chunkX, chunkZ, immediate, onLoad); -+ return; -+ } else { -+ if (gen || (!allowSubTicketLevel && immediate.getStatus().isOrAfter(status))) { -+ this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad); -+ return; -+ } else { -+ this.chunkLoadAccept(chunkX, chunkZ, null, onLoad); -+ return; -+ } -+ } -+ } -+ } -+ -+ // need to fire async -+ -+ if (gen && !allowSubTicketLevel) { -+ this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad); -+ return; -+ } -+ -+ this.getChunkAtAsynchronously(chunkX, chunkZ, net.minecraft.server.MCUtil.getTicketLevelFor(ChunkStatus.EMPTY), (ChunkAccess chunk) -> { -+ if (chunk == null) { -+ throw new IllegalStateException("Chunk cannot be null"); -+ } -+ -+ if (!chunk.getStatus().isOrAfter(status)) { -+ if (gen) { -+ this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad); -+ return; -+ } else { -+ ServerChunkCache.this.chunkLoadAccept(chunkX, chunkZ, null, onLoad); -+ return; -+ } -+ } else { -+ if (allowSubTicketLevel) { -+ ServerChunkCache.this.chunkLoadAccept(chunkX, chunkZ, chunk, onLoad); -+ return; -+ } else { -+ this.getChunkAtAsynchronously(chunkX, chunkZ, chunkStatusTicketLevel, onLoad); -+ return; -+ } -+ } -+ }); -+ } -+ -+ final com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet tickingChunks = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true); -+ final com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet entityTickingChunks = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(4096, 0.75f, 4096, 0.15, true); -+ // Tuinity end -+ - public ServerChunkCache(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureManager structureManager, Executor workerExecutor, ChunkGenerator chunkGenerator, int viewDistance, boolean flag, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkstatusupdatelistener, Supplier supplier) { - this.level = world; - this.mainThreadProcessor = new ServerChunkCache.MainThreadExecutor(world); -@@ -570,6 +731,8 @@ public class ServerChunkCache extends ChunkSource { - return completablefuture; - } - -+ private long syncLoadCounter; // Tuinity - prevent plugin unloads from removing our ticket -+ - private CompletableFuture> getChunkFutureMainThread(int i, int j, ChunkStatus chunkstatus, boolean flag) { - // Paper start - add isUrgent - old sig left in place for dirty nms plugins - return getChunkFutureMainThread(i, j, chunkstatus, flag, false); -@@ -588,9 +751,12 @@ public class ServerChunkCache extends ChunkSource { - ChunkHolder.FullChunkStatus currentChunkState = ChunkHolder.getFullChunkStatus(playerchunk.getTicketLevel()); - currentlyUnloading = (oldChunkState.isOrAfter(ChunkHolder.FullChunkStatus.BORDER) && !currentChunkState.isOrAfter(ChunkHolder.FullChunkStatus.BORDER)); - } -+ final Long identifier; // Tuinity - prevent plugin unloads from removing our ticket - if (flag && !currentlyUnloading) { - // CraftBukkit end - this.distanceManager.addTicket(TicketType.UNKNOWN, chunkcoordintpair, l, chunkcoordintpair); -+ identifier = Long.valueOf(this.syncLoadCounter++); // Tuinity - prevent plugin unloads from removing our ticket -+ this.distanceManager.addTicketAtLevel(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier); // Tuinity - prevent plugin unloads from removing our ticket - if (isUrgent) this.distanceManager.markUrgent(chunkcoordintpair); // Paper - Chunk priority - if (this.chunkAbsent(playerchunk, l)) { - ProfilerFiller gameprofilerfiller = this.level.getProfiler(); -@@ -601,12 +767,20 @@ public class ServerChunkCache extends ChunkSource { - playerchunk = this.getVisibleChunkIfPresent(k); - gameprofilerfiller.pop(); - if (this.chunkAbsent(playerchunk, l)) { -+ this.distanceManager.removeTicketAtLevel(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier); // Tuinity - throw (IllegalStateException) Util.pauseInIde((Throwable) (new IllegalStateException("No chunk holder after ticket has been added"))); - } - } -- } -+ } else { identifier = null; } // Tuinity - prevent plugin unloads from removing our ticket - // Paper start - Chunk priority - CompletableFuture> future = this.chunkAbsent(playerchunk, l) ? ChunkHolder.UNLOADED_CHUNK_FUTURE : playerchunk.getOrScheduleFuture(chunkstatus, this.chunkMap); -+ // Tuinity start - prevent plugin unloads from removing our ticket -+ if (flag && !currentlyUnloading) { -+ future.thenAcceptAsync((either) -> { -+ ServerChunkCache.this.distanceManager.removeTicketAtLevel(TicketType.REQUIRED_LOAD, chunkcoordintpair, l, identifier); -+ }, ServerChunkCache.this.mainThreadProcessor); -+ } -+ // Tuinity end - prevent plugin unloads from removing our ticket - if (isUrgent) { - future.thenAccept(either -> this.distanceManager.clearUrgent(chunkcoordintpair)); - } -@@ -664,6 +838,8 @@ public class ServerChunkCache extends ChunkSource { - - public boolean runDistanceManagerUpdates() { - if (distanceManager.delayDistanceManagerTick) return false; // Paper - Chunk priority -+ if (this.chunkMap.unloadingPlayerChunk) { LOGGER.fatal("Cannot tick distance manager while unloading playerchunks", new Throwable()); throw new IllegalStateException("Cannot tick distance manager while unloading playerchunks"); } // Tuinity -+ co.aikar.timings.MinecraftTimings.distanceManagerTick.startTiming(); try { // Tuinity - add timings for distance manager - boolean flag = this.distanceManager.runAllUpdates(this.chunkMap); - boolean flag1 = this.chunkMap.promoteChunkMap(); - -@@ -673,6 +849,7 @@ public class ServerChunkCache extends ChunkSource { - this.clearCache(); - return true; - } -+ } finally { co.aikar.timings.MinecraftTimings.distanceManagerTick.stopTiming(); } // Tuinity - add timings for distance manager - } - - // Paper start - helper -@@ -730,6 +907,7 @@ public class ServerChunkCache extends ChunkSource { - - // CraftBukkit start - modelled on below - public void purgeUnload() { -+ if (true) return; // Tuinity - tickets will be removed later, this behavior isn't really well accounted for by the chunk system - this.level.getProfiler().push("purge"); - this.distanceManager.purgeStaleTickets(); - this.runDistanceManagerUpdates(); -@@ -745,17 +923,18 @@ public class ServerChunkCache extends ChunkSource { - this.level.getProfiler().push("purge"); - this.level.timings.doChunkMap.startTiming(); // Spigot - this.distanceManager.purgeStaleTickets(); -- this.level.getServer().midTickLoadChunks(); // Paper -+ // Tuinity - replace logic - this.runDistanceManagerUpdates(); - this.level.timings.doChunkMap.stopTiming(); // Spigot - this.level.getProfiler().popPush("chunks"); - this.level.timings.chunks.startTiming(); // Paper - timings -+ this.chunkMap.playerChunkManager.tick(); // Tuinity - this is mostly is to account for view distance changes - this.tickChunks(); - this.level.timings.chunks.stopTiming(); // Paper - timings - this.level.timings.doChunkUnload.startTiming(); // Spigot - this.level.getProfiler().popPush("unload"); - this.chunkMap.tick(booleansupplier); -- this.level.getServer().midTickLoadChunks(); // Paper -+ // Tuinity - replace logic - this.level.timings.doChunkUnload.stopTiming(); // Spigot - this.level.getProfiler().pop(); - this.clearCache(); -@@ -833,12 +1012,15 @@ public class ServerChunkCache extends ChunkSource { - //Collections.shuffle(list); // Paper - // Paper - moved up - this.level.timings.chunkTicks.startTiming(); // Paper -- final int[] chunksTicked = {0}; this.chunkMap.forEachVisibleChunk((playerchunk) -> { // Paper - safe iterator incase chunk loads, also no wrapping -- Optional optional = ((Either) playerchunk.getTickingChunkFuture().getNow(ChunkHolder.UNLOADED_LEVEL_CHUNK)).left(); -- -- if (optional.isPresent()) { -+ // Tuinity start -+ int chunksTicked = 0; -+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = this.entityTickingChunks.iterator(); -+ try { while (iterator.hasNext()) { -+ LevelChunk chunk = iterator.next(); -+ ChunkHolder playerchunk = chunk.playerChunk; -+ if (playerchunk != null) { - this.level.getProfiler().push("broadcast"); -- LevelChunk chunk = (LevelChunk) optional.get(); -+ // Tuinity end - - this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timings - playerchunk.broadcastChanges(chunk); -@@ -846,11 +1028,11 @@ public class ServerChunkCache extends ChunkSource { - this.level.getProfiler().pop(); - ChunkPos chunkcoordintpair = chunk.getPos(); - -- if (this.level.isPositionEntityTicking(chunkcoordintpair) && !this.chunkMap.isOutsideOfRange(playerchunk, chunkcoordintpair, false)) { // Paper - optimise isOutsideOfRange -+ if ((true || this.level.isPositionEntityTicking(chunkcoordintpair)) && !this.chunkMap.isOutsideOfRange(playerchunk, chunkcoordintpair, false)) { // Paper - optimise isOutsideOfRange // Tuinity - we only iterate entity ticking chunks - chunk.setInhabitedTime(chunk.getInhabitedTime() + j); - if (flag1 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunk.getPos()) && !this.chunkMap.isOutsideOfRange(playerchunk, chunkcoordintpair, true)) { // Spigot // Paper - optimise isOutsideOfRange - NaturalSpawner.spawnForChunk(this.level, chunk, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag2); -- if (chunksTicked[0]++ % 10 == 0) this.level.getServer().midTickLoadChunks(); // Paper -+ if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper // Tuinity - } - - // this.level.timings.doTickTiles.startTiming(); // Spigot // Paper -@@ -858,7 +1040,11 @@ public class ServerChunkCache extends ChunkSource { - // this.level.timings.doTickTiles.stopTiming(); // Spigot // Paper - } - } -- }); -+ } // Tuinity start - optimise chunk tick iteration -+ } finally { -+ iterator.finishedIterating(); -+ } -+ // Tuinity end - optimise chunk tick iteration - this.level.timings.chunkTicks.stopTiming(); // Paper - this.level.getProfiler().push("customSpawners"); - if (flag1) { -@@ -871,7 +1057,24 @@ public class ServerChunkCache extends ChunkSource { - this.level.getProfiler().pop(); - } - -+ // Tuinity start - controlled flush for entity tracker packets -+ List disabledFlushes = new java.util.ArrayList<>(this.level.players.size()); -+ for (ServerPlayer player : this.level.players) { -+ net.minecraft.server.network.ServerGamePacketListenerImpl connection = player.connection; -+ if (connection != null) { -+ connection.connection.disableAutomaticFlush(); -+ disabledFlushes.add(connection.connection); -+ } -+ } -+ try { // Tuinity end - controlled flush for entity tracker packets - this.chunkMap.tick(); -+ // Tuinity start - controlled flush for entity tracker packets -+ } finally { -+ for (net.minecraft.network.Connection networkManager : disabledFlushes) { -+ networkManager.enableAutomaticFlush(); -+ } -+ } -+ // Tuinity end - controlled flush for entity tracker packets - } - - private void getFullChunk(long pos, Consumer chunkConsumer) { -@@ -1018,46 +1221,14 @@ public class ServerChunkCache extends ChunkSource { - super.doRunTask(task); - } - -- // Paper start -- private long lastMidTickChunkTask = 0; -- public boolean pollChunkLoadTasks() { -- if (com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ServerChunkCache.this.level.asyncChunkTaskManager.pollNextChunkTask()) { -- try { -- ServerChunkCache.this.runDistanceManagerUpdates(); -- } finally { -- // from below: process pending Chunk loadCallback() and unloadCallback() after each run task -- chunkMap.callbackExecutor.run(); -- } -- return true; -- } -- return false; -- } -- public void midTickLoadChunks() { -- net.minecraft.server.MinecraftServer server = ServerChunkCache.this.level.getServer(); -- // always try to load chunks, restrain generation/other updates only. don't count these towards tick count -- //noinspection StatementWithEmptyBody -- while (pollChunkLoadTasks()) {} -- -- if (System.nanoTime() - lastMidTickChunkTask < 200000) { -- return; -- } -- -- for (;server.midTickChunksTasksRan < com.destroystokyo.paper.PaperConfig.midTickChunkTasks && server.haveTime();) { -- if (this.pollTask()) { -- server.midTickChunksTasksRan++; -- lastMidTickChunkTask = System.nanoTime(); -- } else { -- break; -- } -- } -- } -- // Paper end -+ // Tuinity - replace logic - - @Override - // CraftBukkit start - process pending Chunk loadCallback() and unloadCallback() after each run task - public boolean pollTask() { - try { - boolean execChunkTask = com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ServerChunkCache.this.level.asyncChunkTaskManager.pollNextChunkTask(); // Paper -+ ServerChunkCache.this.chunkMap.playerChunkManager.tickMidTick(); // Tuinity - if (ServerChunkCache.this.runDistanceManagerUpdates()) { - return true; - } else { -diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index 44aa0c4ec6f0e4df2541c74fa7de852dae59bda5..a00627e0fa38632449042f59c053b4dac13e58bf 100644 ---- a/src/main/java/net/minecraft/server/level/ServerEntity.java -+++ b/src/main/java/net/minecraft/server/level/ServerEntity.java -@@ -173,7 +173,7 @@ public class ServerEntity { - // Paper end - remove allocation of Vec3D here - boolean flag4 = k < -32768L || k > 32767L || l < -32768L || l > 32767L || i1 < -32768L || i1 > 32767L; - -- if (!flag4 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.isOnGround()) { -+ if (!flag4 && this.teleportDelay <= 400 && !this.wasRiding && this.wasOnGround == this.entity.isOnGround() && !(com.tuinity.tuinity.config.TuinityConfig.sendFullPosForHardCollidingEntities && this.entity.hardCollides())) { // Tuinity - send full pos for hard colliding entities to prevent collision problems due to desync - if ((!flag2 || !flag3) && !(this.entity instanceof AbstractArrow)) { - if (flag2) { - packet1 = new ClientboundMoveEntityPacket.Pos(this.entity.getId(), (short) ((int) k), (short) ((int) l), (short) ((int) i1), this.entity.isOnGround()); -diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 8154ca39ec7e2e8559cd125d73a59b8d2b00714c..57163e3cb883ded5861e57c3ca03663c02ee7492 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -115,6 +115,7 @@ import net.minecraft.world.level.block.EntityBlock; - import net.minecraft.world.level.block.entity.BlockEntity; - import net.minecraft.world.level.block.entity.TickingBlockEntity; - import net.minecraft.world.level.block.state.BlockState; -+import net.minecraft.world.level.chunk.ChunkAccess; - import net.minecraft.world.level.chunk.ChunkGenerator; - import net.minecraft.world.level.chunk.LevelChunk; - import net.minecraft.world.level.chunk.LevelChunkSection; -@@ -165,6 +166,7 @@ import org.bukkit.event.server.MapInitializeEvent; - import org.bukkit.event.weather.LightningStrikeEvent; - import org.bukkit.event.world.TimeSkipEvent; - // CraftBukkit end -+import it.unimi.dsi.fastutil.ints.IntArrayList; // Tuinity - - public class ServerLevel extends net.minecraft.world.level.Level implements WorldGenLevel { - -@@ -193,7 +195,9 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl - final Int2ObjectMap dragonParts; - private final StructureFeatureManager structureFeatureManager; - private final boolean tickTime; -- -+ // Tuinity start - execute chunk tasks mid tick -+ public long lastMidTickExecuteFailure; -+ // Tuinity end - execute chunk tasks mid tick - - // CraftBukkit start - private int tickPosition; -@@ -304,6 +308,172 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl - } - } - // Paper end - rewrite ticklistserver -+ // Tuinity start -+ public final boolean areChunksLoadedForMove(AABB axisalignedbb) { -+ // copied code from collision methods, so that we can guarantee that they wont load chunks (we don't override -+ // ICollisionAccess methods for VoxelShapes) -+ // be more strict too, add a block (dumb plugins in move events?) -+ int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3; -+ int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3; -+ -+ int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3; -+ int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3; -+ -+ int minChunkX = minBlockX >> 4; -+ int maxChunkX = maxBlockX >> 4; -+ -+ int minChunkZ = minBlockZ >> 4; -+ int maxChunkZ = maxBlockZ >> 4; -+ -+ ServerChunkCache chunkProvider = this.getChunkSource(); -+ -+ for (int cx = minChunkX; cx <= maxChunkX; ++cx) { -+ for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) { -+ if (chunkProvider.getChunkAtIfLoadedImmediately(cx, cz) == null) { -+ return false; -+ } -+ } -+ } -+ -+ return true; -+ } -+ -+ public final void loadChunksForMoveAsync(AABB axisalignedbb, double toX, double toZ, -+ java.util.function.Consumer> onLoad) { -+ if (Thread.currentThread() != this.thread) { -+ this.getChunkSource().mainThreadProcessor.execute(() -> { -+ this.loadChunksForMoveAsync(axisalignedbb, toX, toZ, onLoad); -+ }); -+ return; -+ } -+ List ret = new java.util.ArrayList<>(); -+ IntArrayList ticketLevels = new IntArrayList(); -+ -+ int minBlockX = Mth.floor(axisalignedbb.minX - 1.0E-7D) - 3; -+ int maxBlockX = Mth.floor(axisalignedbb.maxX + 1.0E-7D) + 3; -+ -+ int minBlockZ = Mth.floor(axisalignedbb.minZ - 1.0E-7D) - 3; -+ int maxBlockZ = Mth.floor(axisalignedbb.maxZ + 1.0E-7D) + 3; -+ -+ int minChunkX = minBlockX >> 4; -+ int maxChunkX = maxBlockX >> 4; -+ -+ int minChunkZ = minBlockZ >> 4; -+ int maxChunkZ = maxBlockZ >> 4; -+ -+ ServerChunkCache chunkProvider = this.getChunkSource(); -+ -+ int requiredChunks = (maxChunkX - minChunkX + 1) * (maxChunkZ - minChunkZ + 1); -+ int[] loadedChunks = new int[1]; -+ -+ Long holderIdentifier = Long.valueOf(chunkProvider.chunkFutureAwaitCounter++); -+ -+ java.util.function.Consumer consumer = (ChunkAccess chunk) -> { -+ if (chunk != null) { -+ int ticketLevel = Math.max(33, chunkProvider.chunkMap.getUpdatingChunkIfPresent(chunk.getPos().toLong()).getTicketLevel()); -+ ret.add(chunk); -+ ticketLevels.add(ticketLevel); -+ chunkProvider.addTicketAtLevel(TicketType.FUTURE_AWAIT, chunk.getPos(), ticketLevel, holderIdentifier); -+ } -+ if (++loadedChunks[0] == requiredChunks) { -+ try { -+ onLoad.accept(java.util.Collections.unmodifiableList(ret)); -+ } finally { -+ for (int i = 0, len = ret.size(); i < len; ++i) { -+ ChunkPos chunkPos = ret.get(i).getPos(); -+ int ticketLevel = ticketLevels.getInt(i); -+ -+ chunkProvider.addTicketAtLevel(TicketType.UNKNOWN, chunkPos, ticketLevel, chunkPos); -+ chunkProvider.removeTicketAtLevel(TicketType.FUTURE_AWAIT, chunkPos, ticketLevel, holderIdentifier); -+ } -+ } -+ } -+ }; -+ -+ for (int cx = minChunkX; cx <= maxChunkX; ++cx) { -+ for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) { -+ chunkProvider.getChunkAtAsynchronously(cx, cz, net.minecraft.world.level.chunk.ChunkStatus.FULL, true, false, consumer); -+ } -+ } -+ } -+ // Tuinity end -+ // Tuinity start - optimise checkDespawn -+ public final List playersAffectingSpawning = new java.util.ArrayList<>(); -+ // Tuinity end - optimise checkDespawn -+ // Tuinity start - optimise get nearest players for entity AI -+ @Override -+ public final ServerPlayer getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions condition, @Nullable LivingEntity source, -+ double centerX, double centerY, double centerZ) { -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearby; -+ nearby = this.getChunkSource().chunkMap.playerGeneralAreaMap.getObjectsInRange(Mth.floor(centerX) >> 4, Mth.floor(centerZ) >> 4); -+ -+ if (nearby == null) { -+ return null; -+ } -+ -+ Object[] backingSet = nearby.getBackingSet(); -+ -+ double closestDistanceSquared = Double.MAX_VALUE; -+ ServerPlayer closest = null; -+ -+ for (int i = 0, len = backingSet.length; i < len; ++i) { -+ Object _player = backingSet[i]; -+ if (!(_player instanceof ServerPlayer)) { -+ continue; -+ } -+ ServerPlayer player = (ServerPlayer)_player; -+ -+ double distanceSquared = player.distanceToSqr(centerX, centerY, centerZ); -+ if (distanceSquared < closestDistanceSquared && condition.test(source, player)) { -+ closest = player; -+ closestDistanceSquared = distanceSquared; -+ } -+ } -+ -+ return closest; -+ } -+ -+ @Override -+ public Player getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions pathfindertargetcondition, LivingEntity entityliving) { -+ return this.getNearestPlayer(pathfindertargetcondition, entityliving, entityliving.getX(), entityliving.getY(), entityliving.getZ()); -+ } -+ -+ @Override -+ public Player getNearestPlayer(net.minecraft.world.entity.ai.targeting.TargetingConditions pathfindertargetcondition, -+ double d0, double d1, double d2) { -+ return this.getNearestPlayer(pathfindertargetcondition, null, d0, d1, d2); -+ } -+ -+ @Override -+ public List getNearbyPlayers(net.minecraft.world.entity.ai.targeting.TargetingConditions condition, LivingEntity source, AABB axisalignedbb) { -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearby; -+ double centerX = (axisalignedbb.maxX + axisalignedbb.minX) * 0.5; -+ double centerZ = (axisalignedbb.maxZ + axisalignedbb.minZ) * 0.5; -+ nearby = this.getChunkSource().chunkMap.playerGeneralAreaMap.getObjectsInRange(Mth.floor(centerX) >> 4, Mth.floor(centerZ) >> 4); -+ -+ List ret = new java.util.ArrayList<>(); -+ -+ if (nearby == null) { -+ return ret; -+ } -+ -+ Object[] backingSet = nearby.getBackingSet(); -+ -+ for (int i = 0, len = backingSet.length; i < len; ++i) { -+ Object _player = backingSet[i]; -+ if (!(_player instanceof ServerPlayer)) { -+ continue; -+ } -+ ServerPlayer player = (ServerPlayer)_player; -+ -+ if (axisalignedbb.contains(player.getX(), player.getY(), player.getZ()) && condition.test(source, player)) { -+ ret.add(player); -+ } -+ } -+ -+ return ret; -+ } -+ // Tuinity end - optimise get nearest players for entity AI - - // Add env and gen to constructor, WorldData -> WorldDataServer - public ServerLevel(MinecraftServer minecraftserver, Executor executor, LevelStorageSource.LevelStorageAccess convertable_conversionsession, ServerLevelData iworlddataserver, ResourceKey resourcekey, DimensionType dimensionmanager, ChunkProgressListener worldloadlistener, ChunkGenerator chunkgenerator, boolean flag, long i, List list, boolean flag1, org.bukkit.World.Environment env, org.bukkit.generator.ChunkGenerator gen) { -@@ -351,7 +521,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl - DataFixer datafixer = minecraftserver.getFixerUpper(); - EntityPersistentStorage entitypersistentstorage = new EntityStorage(this, new File(convertable_conversionsession.getDimensionPath(resourcekey), "entities"), datafixer, flag2, minecraftserver); - -- this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage); -+ this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage, this.entitySliceManager); // Tuinity - StructureManager definedstructuremanager = minecraftserver.getStructureManager(); - int j = this.spigotConfig.viewDistance; // Spigot - PersistentEntitySectionManager persistententitysectionmanager = this.entityManager; -@@ -386,6 +556,10 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl - this.asyncChunkTaskManager = new com.destroystokyo.paper.io.chunk.ChunkTaskManager(this); // Paper - } - -+ // Tuinity start - optimise collision -+ -+ // Tuinity end - optimise collision -+ - // CraftBukkit start - @Override - public BlockEntity getTileEntity(BlockPos pos, boolean validate) { -@@ -439,6 +613,14 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl - } - - public void tick(BooleanSupplier shouldKeepTicking) { -+ // Tuinity start - optimise checkDespawn -+ this.playersAffectingSpawning.clear(); -+ for (ServerPlayer player : this.players) { -+ if (net.minecraft.world.entity.EntitySelector.affectsSpawning.test(player)) { -+ this.playersAffectingSpawning.add(player); -+ } -+ } -+ // Tuinity end - optimise checkDespawn - ProfilerFiller gameprofilerfiller = this.getProfiler(); - - this.handlingTick = true; -@@ -588,7 +770,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl - } - timings.scheduledBlocks.stopTiming(); // Paper - -- this.getServer().midTickLoadChunks(); // Paper -+ // Tuinity - replace logic - gameprofilerfiller.popPush("raid"); - this.timings.raids.startTiming(); // Paper - timings - this.raids.tick(); -@@ -597,7 +779,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl - timings.doSounds.startTiming(); // Spigot - this.runBlockEvents(); - timings.doSounds.stopTiming(); // Spigot -- this.getServer().midTickLoadChunks(); // Paper -+ // Tuinity - replace logic - this.handlingTick = false; - gameprofilerfiller.pop(); - boolean flag3 = true || !this.players.isEmpty() || !this.getForcedChunks().isEmpty(); // CraftBukkit - this prevents entity cleanup, other issues on servers with no players -@@ -644,12 +826,12 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl - timings.entityTick.stopTiming(); // Spigot - timings.tickEntities.stopTiming(); // Spigot - gameprofilerfiller.pop(); -- this.getServer().midTickLoadChunks(); // Paper -+ // Tuinity - replace logic - this.tickBlockEntities(); - } - - gameprofilerfiller.push("entityManagement"); -- this.getServer().midTickLoadChunks(); // Paper -+ // Tuinity - replace logic - this.entityManager.tick(); - gameprofilerfiller.pop(); - } -@@ -694,6 +876,10 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl - entityplayer.stopSleepInBed(false, false); - }); - } -+ // Paper start - optimise random block ticking -+ private final BlockPos.MutableBlockPos chunkTickMutablePosition = new BlockPos.MutableBlockPos(); -+ private final com.tuinity.tuinity.util.math.ThreadUnsafeRandom randomTickRandom = new com.tuinity.tuinity.util.math.ThreadUnsafeRandom(); -+ // Paper end - - public void tickChunk(LevelChunk chunk, int randomTickSpeed) { - ChunkPos chunkcoordintpair = chunk.getPos(); -@@ -703,10 +889,10 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl - ProfilerFiller gameprofilerfiller = this.getProfiler(); - - gameprofilerfiller.push("thunder"); -- BlockPos blockposition; -+ final BlockPos.MutableBlockPos blockposition = this.chunkTickMutablePosition; // Paper - use mutable to reduce allocation rate, final to force compile fail on change - - if (!this.paperConfig.disableThunder && flag && this.isThundering() && this.random.nextInt(100000) == 0) { // Paper - Disable thunder -- blockposition = this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15)); -+ blockposition.set(this.findLightningTargetAround(this.getBlockRandomPos(j, 0, k, 15))); // Paper - if (this.isRainingAt(blockposition)) { - DifficultyInstance difficultydamagescaler = this.getCurrentDifficultyAt(blockposition); - boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * paperConfig.skeleHorseSpawnChance && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); // Paper -@@ -729,64 +915,78 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl - } - - gameprofilerfiller.popPush("iceandsnow"); -- if (!this.paperConfig.disableIceAndSnow && this.random.nextInt(16) == 0) { // Paper - Disable ice and snow -- blockposition = this.getHeightmapPos(Heightmap.Types.MOTION_BLOCKING, this.getBlockRandomPos(j, 0, k, 15)); -- BlockPos blockposition1 = blockposition.below(); -+ if (!this.paperConfig.disableIceAndSnow && this.randomTickRandom.nextInt(16) == 0) { // Paper - Disable ice and snow // Paper - optimise random ticking -+ // Paper start - optimise chunk ticking -+ this.getRandomBlockPosition(j, 0, k, 15, blockposition); -+ int normalY = chunk.getHeight(Heightmap.Types.MOTION_BLOCKING, blockposition.getX() & 15, blockposition.getZ() & 15) + 1; -+ int downY = normalY - 1; -+ blockposition.setY(normalY); -+ // Paper end - Biome biomebase = this.getBiome(blockposition); - -- if (biomebase.shouldFreeze((LevelReader) this, blockposition1)) { -- org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition1, Blocks.ICE.defaultBlockState(), null); // CraftBukkit -+ // Paper start - optimise chunk ticking -+ blockposition.setY(downY); -+ if (biomebase.shouldFreeze(this, blockposition)) { -+ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition, Blocks.ICE.defaultBlockState(), null); // CraftBukkit -+ // Paper end - } - - if (flag) { -+ blockposition.setY(normalY); // Paper - if (biomebase.shouldSnow(this, blockposition)) { - org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockFormEvent(this, blockposition, Blocks.SNOW.defaultBlockState(), null); // CraftBukkit - } - -- BlockState iblockdata = this.getBlockState(blockposition1); -+ blockposition.setY(downY); // Paper -+ BlockState iblockdata = this.getBlockState(blockposition); // Paper -+ blockposition.setY(normalY); // Paper - Biome.Precipitation biomebase_precipitation = this.getBiome(blockposition).getPrecipitation(); - -- if (biomebase_precipitation == Biome.Precipitation.RAIN && biomebase.isColdEnoughToSnow(blockposition1)) { -+ blockposition.setY(downY); // Paper -+ if (biomebase_precipitation == Biome.Precipitation.RAIN && biomebase.isColdEnoughToSnow(blockposition)) { // Paper - biomebase_precipitation = Biome.Precipitation.SNOW; - } - -- iblockdata.getBlock().handlePrecipitation(iblockdata, (net.minecraft.world.level.Level) this, blockposition1, biomebase_precipitation); -+ iblockdata.getBlock().handlePrecipitation(iblockdata, (net.minecraft.world.level.Level) this, blockposition, biomebase_precipitation); // Paper - } - } - -- gameprofilerfiller.popPush("tickBlocks"); -+ // Paper start - optimise random block ticking -+ gameprofilerfiller.popPush("randomTick"); - timings.chunkTicksBlocks.startTiming(); // Paper - if (randomTickSpeed > 0) { -- LevelChunkSection[] achunksection = chunk.getSections(); -- int l = achunksection.length; -- -- for (int i1 = 0; i1 < l; ++i1) { -- LevelChunkSection chunksection = achunksection[i1]; -- -- if (chunksection != LevelChunk.EMPTY_SECTION && chunksection.isRandomlyTicking()) { -- int j1 = chunksection.bottomBlockY(); -- -- for (int k1 = 0; k1 < randomTickSpeed; ++k1) { -- BlockPos blockposition2 = this.getBlockRandomPos(j, j1, k, 15); -- -- gameprofilerfiller.push("randomTick"); -- BlockState iblockdata1 = chunksection.getBlockState(blockposition2.getX() - j, blockposition2.getY() - j1, blockposition2.getZ() - k); -+ LevelChunkSection[] sections = chunk.getSections(); -+ int minSection = com.tuinity.tuinity.util.WorldUtil.getMinSection(this); -+ for (int sectionIndex = 0; sectionIndex < sections.length; ++sectionIndex) { -+ LevelChunkSection section = sections[sectionIndex]; -+ if (section == null || section.tickingList.size() == 0) { -+ continue; -+ } - -- if (iblockdata1.isRandomlyTicking()) { -- iblockdata1.randomTick(this, blockposition2, this.random); -- } -+ int yPos = (sectionIndex + minSection) << 4; -+ for (int a = 0; a < randomTickSpeed; ++a) { -+ int tickingBlocks = section.tickingList.size(); -+ int index = this.randomTickRandom.nextInt(16 * 16 * 16); -+ if (index >= tickingBlocks) { -+ continue; -+ } - -- FluidState fluid = iblockdata1.getFluidState(); -+ long raw = section.tickingList.getRaw(index); -+ int location = com.destroystokyo.paper.util.maplist.IBlockDataList.getLocationFromRaw(raw); -+ int randomX = location & 15; -+ int randomY = ((location >>> (4 + 4)) & 255) | yPos; -+ int randomZ = (location >>> 4) & 15; - -- if (fluid.isRandomlyTicking()) { -- fluid.randomTick(this, blockposition2, this.random); -- } -+ BlockPos blockposition2 = blockposition.set(j + randomX, randomY, k + randomZ); -+ BlockState iblockdata = com.destroystokyo.paper.util.maplist.IBlockDataList.getBlockDataFromRaw(raw); - -- gameprofilerfiller.pop(); -- } -+ iblockdata.randomTick(this, blockposition2, this.randomTickRandom); -+ // We drop the fluid tick since LAVA is ALREADY TICKED by the above method (See LiquidBlock). -+ // TODO CHECK ON UPDATE - } - } - } -+ // Paper end - optimise random block ticking - timings.chunkTicksBlocks.stopTiming(); // Paper - gameprofilerfiller.pop(); - } -@@ -912,7 +1112,27 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl - - } - -+ // Tuinity start - log detailed entity tick information -+ // TODO replace with varhandle -+ static final java.util.concurrent.atomic.AtomicReference currentlyTickingEntity = new java.util.concurrent.atomic.AtomicReference<>(); -+ -+ public static List getCurrentlyTickingEntities() { -+ Entity ticking = currentlyTickingEntity.get(); -+ List ret = java.util.Arrays.asList(ticking == null ? new Entity[0] : new Entity[] { ticking }); -+ -+ return ret; -+ } -+ // Tuinity end - log detailed entity tick information -+ - public void tickNonPassenger(Entity entity) { -+ // Tuinity start - log detailed entity tick information -+ com.tuinity.tuinity.util.TickThread.ensureTickThread("Cannot tick an entity off-main"); -+ this.entityManager.updateNavigatorsInRegion(entity); // Tuinity - optimise notify -+ try { -+ if (currentlyTickingEntity.get() == null) { -+ currentlyTickingEntity.lazySet(entity); -+ } -+ // Tuinity end - log detailed entity tick information - ++TimingHistory.entityTicks; // Paper - timings - // Spigot start - co.aikar.timings.Timing timer; // Paper -@@ -953,7 +1173,13 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl - } - - // } finally { timer.stopTiming(); } // Paper - timings - move up -- -+ // Tuinity start - log detailed entity tick information -+ } finally { -+ if (currentlyTickingEntity.get() == entity) { -+ currentlyTickingEntity.lazySet(null); -+ } -+ } -+ // Tuinity end - log detailed entity tick information - } - - private void tickPassenger(Entity vehicle, Entity passenger) { -@@ -1245,9 +1471,13 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl - // Spigot Start - for (BlockEntity tileentity : chunk.getBlockEntities().values()) { - if (tileentity instanceof net.minecraft.world.Container) { -+ // Tuinity start - this area looks like it can load chunks, change the behavior -+ // chests for example can apply physics to the world -+ // so instead we just change the active container and call the event - for (org.bukkit.entity.HumanEntity h : Lists.newArrayList(((net.minecraft.world.Container) tileentity).getViewers())) { -- h.closeInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper -+ ((org.bukkit.craftbukkit.entity.CraftHumanEntity)h).getHandle().closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason.UNLOADED); // Paper - } -+ // Tuiniy end - } - } - // Spigot End -@@ -1344,9 +1574,19 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl - VoxelShape voxelshape1 = newState.getCollisionShape(this, pos); - - if (Shapes.joinIsNotEmpty(voxelshape, voxelshape1, BooleanOp.NOT_SAME)) { -- Iterator iterator = this.navigatingMobs.iterator(); -+ // Tuinity start - optimise notify() -+ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.Region region = this.getChunkSource().chunkMap.dataRegionManager.getRegion(pos.getX() >> 4, pos.getZ() >> 4); -+ if (region == null) { -+ return; -+ } -+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet navigatorsFromRegion = ((ChunkMap.DataRegionData)region.regionData).getNavigators(); -+ if (navigatorsFromRegion == null) { -+ return; -+ } -+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = navigatorsFromRegion.iterator(); - -- while (iterator.hasNext()) { -+ -+ try { while (iterator.hasNext()) { // Tuinity end - optimise notify() - // CraftBukkit start - fix SPIGOT-6362 - Mob entityinsentient; - try { -@@ -1365,6 +1605,11 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl - navigationabstract.recomputePath(pos); - } - } -+ // Tuinity start - optimise notify() -+ } finally { -+ iterator.finishedIterating(); -+ } -+ // Tuinity end - optimise notify() - - } - } // Paper -@@ -2146,10 +2391,12 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl - - public void onTickingStart(Entity entity) { - ServerLevel.this.entityTickList.add(entity); -+ ServerLevel.this.entityManager.addNavigatorsIfPathingToRegion(entity); // Tuinity - optimise notify - } - - public void onTickingEnd(Entity entity) { - ServerLevel.this.entityTickList.remove(entity); -+ ServerLevel.this.entityManager.removeNavigatorsFromData(entity); // Tuinity - optimise notify - } - - public void onTrackingStart(Entity entity) { -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index a0acaac510aa2206a5c58f2b7aafdbc2bdf7a3dd..0da2dbeba93d428a035872e05177ed3fc29acf9b 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -260,7 +260,7 @@ public class ServerPlayer extends Player { - - public double lastEntitySpawnRadiusSquared; // Paper - optimise isOutsideRange, this field is in blocks - public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet cachedSingleHashSet; // Paper -- boolean needsChunkCenterUpdate; // Paper - no-tick view distance -+ public boolean needsChunkCenterUpdate; // Paper - no-tick view distance // Tuinity - public - public org.bukkit.event.player.PlayerQuitEvent.QuitReason quitReason = null; // Paper - there are a lot of changes to do if we change all methods leading to the event - - public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile) { -@@ -423,7 +423,7 @@ public class ServerPlayer extends Player { - - if (blockposition1 != null) { - this.moveTo(blockposition1, 0.0F, 0.0F); -- if (world.noCollision(this)) { -+ if (world.noCollision(this, this.getBoundingBox(), null, true)) { // Tuinity - make sure this loads chunks, we default to NOT loading now - break; - } - } -@@ -431,7 +431,7 @@ public class ServerPlayer extends Player { - } else { - this.moveTo(blockposition, 0.0F, 0.0F); - -- while (!world.noCollision(this) && this.getY() < (double) (world.getMaxBuildHeight() - 1)) { -+ while (!world.noCollision(this, this.getBoundingBox(), null, true) && this.getY() < (double) (world.getMaxBuildHeight() - 1)) { // Tuinity - make sure this loads chunks, we default to NOT loading now - this.setPos(this.getX(), this.getY() + 1.0D, this.getZ()); - } - } -@@ -1562,6 +1562,18 @@ public class ServerPlayer extends Player { - this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); - this.doCloseContainer(); - } -+ // Tuinity start - special close for unloaded inventory -+ @Override -+ public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { -+ // copied from above -+ CraftEventFactory.handleInventoryCloseEvent(this, reason); // CraftBukkit -+ // Paper end -+ // copied from below -+ this.connection.send(new ClientboundContainerClosePacket(this.containerMenu.containerId)); -+ this.containerMenu = this.inventoryMenu; -+ // do not run close logic -+ } -+ // Tuinity end - special close for unloaded inventory - - public void doCloseContainer() { - this.containerMenu.removed((Player) this); -diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index e572088cad8b9e09b1d64f7971bacac2f10c5b17..b2c8cae1a777cd63a35ed1340caf205b1b3bb0ad 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -56,14 +56,28 @@ public class ServerPlayerGameMode { - @Nullable - private GameType previousGameModeForPlayer; - private boolean isDestroyingBlock; -- private int destroyProgressStart; -+ private int destroyProgressStart; private long lastDigTime; // Tuinity - lag compensate block breaking - private BlockPos destroyPos; - private int gameTicks; - private boolean hasDelayedDestroy; - private BlockPos delayedDestroyPos; -- private int delayedTickStart; -+ private int delayedTickStart; private long hasDestroyedTooFastStartTime; // Tuinity - lag compensate block breaking - private int lastSentState; - -+ // Tuinity start - lag compensate block breaking -+ private int getTimeDiggingLagCompensate() { -+ int lagCompensated = (int)((System.nanoTime() - this.lastDigTime) / (50L * 1000L * 1000L)); -+ int tickDiff = this.gameTicks - this.destroyProgressStart; -+ return (com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking && lagCompensated > (tickDiff + 1)) ? lagCompensated : tickDiff; // add one to ensure we don't lag compensate unless we need to -+ } -+ -+ private int getTimeDiggingTooFastLagCompensate() { -+ int lagCompensated = (int)((System.nanoTime() - this.hasDestroyedTooFastStartTime) / (50L * 1000L * 1000L)); -+ int tickDiff = this.gameTicks - this.delayedTickStart; -+ return (com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking && lagCompensated > (tickDiff + 1)) ? lagCompensated : tickDiff; // add one to ensure we don't lag compensate unless we need to -+ } -+ // Tuinity end -+ - public ServerPlayerGameMode(ServerPlayer player) { - this.gameModeForPlayer = GameType.DEFAULT_MODE; - this.destroyPos = BlockPos.ZERO; -@@ -130,7 +144,7 @@ public class ServerPlayerGameMode { - if (iblockdata == null || iblockdata.isAir()) { // Paper - this.hasDelayedDestroy = false; - } else { -- float f = this.incrementDestroyProgress(iblockdata, this.delayedDestroyPos, this.delayedTickStart); -+ float f = this.updateBlockBreakAnimation(iblockdata, this.delayedDestroyPos, this.getTimeDiggingTooFastLagCompensate()); // Tuinity - lag compensate destroying blocks - - if (f >= 1.0F) { - this.hasDelayedDestroy = false; -@@ -150,7 +164,7 @@ public class ServerPlayerGameMode { - this.lastSentState = -1; - this.isDestroyingBlock = false; - } else { -- this.incrementDestroyProgress(iblockdata, this.destroyPos, this.destroyProgressStart); -+ this.updateBlockBreakAnimation(iblockdata, this.destroyPos, this.getTimeDiggingLagCompensate()); // Tuinity - lag compensate destroying - } - } - -@@ -158,6 +172,12 @@ public class ServerPlayerGameMode { - - private float incrementDestroyProgress(BlockState state, BlockPos pos, int i) { - int j = this.gameTicks - i; -+ // Tuinity start - change i (startTime) to totalTime -+ return this.updateBlockBreakAnimation(state, pos, j); -+ } -+ private float updateBlockBreakAnimation(BlockState state, BlockPos pos, int totalTime) { -+ int j = totalTime; -+ // Tuinity end - float f = state.getDestroyProgress(this.player, this.player.level, pos) * (float) (j + 1); - int k = (int) (f * 10.0F); - -@@ -225,7 +245,7 @@ public class ServerPlayerGameMode { - return; - } - -- this.destroyProgressStart = this.gameTicks; -+ this.destroyProgressStart = this.gameTicks; this.lastDigTime = System.nanoTime(); // Tuinity - lag compensate block breaking - float f = 1.0F; - - iblockdata = this.level.getBlockState(pos); -@@ -278,12 +298,12 @@ public class ServerPlayerGameMode { - int j = (int) (f * 10.0F); - - this.level.destroyBlockProgress(this.player.getId(), pos, j); -- this.player.connection.send(new ClientboundBlockBreakAckPacket(pos, this.level.getBlockState(pos), action, true, "actual start of destroying")); -+ if (!com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking) this.player.connection.send(new ClientboundBlockBreakAckPacket(pos, this.level.getBlockState(pos), action, true, "actual start of destroying")); - this.lastSentState = j; - } - } else if (action == ServerboundPlayerActionPacket.Action.STOP_DESTROY_BLOCK) { - if (pos.equals(this.destroyPos)) { -- int k = this.gameTicks - this.destroyProgressStart; -+ int k = this.getTimeDiggingLagCompensate(); // Tuinity - lag compensate block breaking - - iblockdata = this.level.getBlockState(pos); - if (!iblockdata.isAir()) { -@@ -300,12 +320,18 @@ public class ServerPlayerGameMode { - this.isDestroyingBlock = false; - this.hasDelayedDestroy = true; - this.delayedDestroyPos = pos; -- this.delayedTickStart = this.destroyProgressStart; -+ this.delayedTickStart = this.destroyProgressStart; this.hasDestroyedTooFastStartTime = this.lastDigTime; // Tuinity - lag compensate block breaking - } - } - } - -+ // Tuinity start - this can cause clients on a lagging server to think they're not currently destroying a block -+ if (com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking) { -+ this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); -+ } else { - this.player.connection.send(new ClientboundBlockBreakAckPacket(pos, this.level.getBlockState(pos), action, true, "stopped destroying")); -+ } -+ // Tuinity end - this can cause clients on a lagging server to think they're not currently destroying a block - } else if (action == ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK) { - this.isDestroyingBlock = false; - if (!Objects.equals(this.destroyPos, pos) && !BlockPos.ZERO.equals(this.destroyPos)) { -@@ -317,7 +343,7 @@ public class ServerPlayerGameMode { - } - - this.level.destroyBlockProgress(this.player.getId(), pos, -1); -- this.player.connection.send(new ClientboundBlockBreakAckPacket(pos, this.level.getBlockState(pos), action, true, "aborted destroying")); -+ if (!com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking) this.player.connection.send(new ClientboundBlockBreakAckPacket(pos, this.level.getBlockState(pos), action, true, "aborted destroying")); // Tuinity - this can cause clients on a lagging server to think they stopped destroying a block they're currently destroying - } - - } -@@ -327,7 +353,13 @@ public class ServerPlayerGameMode { - - public void destroyAndAck(BlockPos pos, ServerboundPlayerActionPacket.Action action, String reason) { - if (this.destroyBlock(pos)) { -+ // Tuinity start - this can cause clients on a lagging server to think they're not currently destroying a block -+ if (com.tuinity.tuinity.config.TuinityConfig.lagCompensateBlockBreaking) { -+ this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); -+ } else { - this.player.connection.send(new ClientboundBlockBreakAckPacket(pos, this.level.getBlockState(pos), action, true, reason)); -+ } -+ // Tuinity end - this can cause clients on a lagging server to think they're not currently destroying a block - } else { - this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos)); // CraftBukkit - SPIGOT-5196 - } -diff --git a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -index de228b677810ce49c4e953ca0b4e590413b20e45..580165d0a728a4558031dac11f5edaf2923b5ad0 100644 ---- a/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -+++ b/src/main/java/net/minecraft/server/level/ThreadedLevelLightEngine.java -@@ -23,6 +23,17 @@ import net.minecraft.world.level.lighting.LevelLightEngine; - import org.apache.logging.log4j.LogManager; - import org.apache.logging.log4j.Logger; - -+// Tuinity start -+import ca.spottedleaf.starlight.light.StarLightEngine; -+import com.tuinity.tuinity.util.CoordinateUtils; -+import java.util.function.Supplier; -+import net.minecraft.world.level.lighting.LayerLightEventListener; -+import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap; -+import it.unimi.dsi.fastutil.longs.LongArrayList; -+import it.unimi.dsi.fastutil.longs.LongIterator; -+import net.minecraft.world.level.chunk.ChunkStatus; -+// Tuinity end -+ - public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCloseable { - private static final Logger LOGGER = LogManager.getLogger(); - private final ProcessorMailbox taskMailbox; -@@ -32,13 +43,166 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - private volatile int taskPerBatch = 5; - private final AtomicBoolean scheduled = new AtomicBoolean(); - -+ // Tuinity start - replace light engine impl -+ protected final ca.spottedleaf.starlight.light.StarLightInterface theLightEngine; -+ public final boolean hasBlockLight; -+ public final boolean hasSkyLight; -+ // Tuinity end - replace light engine impl -+ - public ThreadedLevelLightEngine(LightChunkGetter chunkProvider, ChunkMap chunkStorage, boolean hasBlockLight, ProcessorMailbox processor, ProcessorHandle> executor) { -- super(chunkProvider, true, hasBlockLight); -+ super(chunkProvider, false, false); // Tuinity - destroy vanilla light engine state - this.chunkMap = chunkStorage; - this.sorterMailbox = executor; - this.taskMailbox = processor; -+ // Tuinity start - replace light engine impl -+ this.hasBlockLight = true; -+ this.hasSkyLight = hasBlockLight; // Nice variable name. -+ this.theLightEngine = new ca.spottedleaf.starlight.light.StarLightInterface(chunkProvider, this.hasSkyLight, this.hasBlockLight, this); -+ // Tuinity end - replace light engine impl -+ } -+ -+ // Tuinity start - replace light engine impl -+ protected final ChunkAccess getChunk(final int chunkX, final int chunkZ) { -+ return ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().getChunkAtImmediately(chunkX, chunkZ); -+ } -+ -+ protected long relightCounter; -+ -+ public int relight(java.util.Set chunks_param, -+ java.util.function.Consumer chunkLightCallback, -+ java.util.function.IntConsumer onComplete) { -+ if (!org.bukkit.Bukkit.isPrimaryThread()) { -+ throw new IllegalStateException("Must only be called on the main thread"); -+ } -+ -+ java.util.Set chunks = new java.util.LinkedHashSet<>(chunks_param); -+ // add tickets -+ java.util.Map ticketIds = new java.util.HashMap<>(); -+ int totalChunks = 0; -+ for (java.util.Iterator iterator = chunks.iterator(); iterator.hasNext();) { -+ final ChunkPos chunkPos = iterator.next(); -+ -+ final ChunkAccess chunk = ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().getChunkAtImmediately(chunkPos.x, chunkPos.z); -+ if (chunk == null || !chunk.isLightCorrect() || !chunk.getStatus().isOrAfter(ChunkStatus.LIGHT)) { -+ // cannot relight this chunk -+ iterator.remove(); -+ continue; -+ } -+ -+ final Long id = Long.valueOf(this.relightCounter++); -+ -+ ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().addTicketAtLevel(TicketType.CHUNK_RELIGHT, chunkPos, net.minecraft.server.MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), id); -+ ticketIds.put(chunkPos, id); -+ -+ ++totalChunks; -+ } -+ -+ this.taskMailbox.tell(() -> { -+ this.theLightEngine.relightChunks(chunks, (ChunkPos chunkPos) -> { -+ chunkLightCallback.accept(chunkPos); -+ ((java.util.concurrent.Executor)((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().mainThreadProcessor).execute(() -> { -+ ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().chunkMap.getUpdatingChunkIfPresent(chunkPos.toLong()).broadcast(new net.minecraft.network.protocol.game.ClientboundLightUpdatePacket(chunkPos, ThreadedLevelLightEngine.this, null, null, true), false); -+ ((ServerLevel)this.theLightEngine.getWorld()).getChunkSource().removeTicketAtLevel(TicketType.CHUNK_RELIGHT, chunkPos, net.minecraft.server.MCUtil.getTicketLevelFor(ChunkStatus.LIGHT), ticketIds.get(chunkPos)); -+ }); -+ }, onComplete); -+ }); -+ this.tryScheduleUpdate(); -+ -+ return totalChunks; -+ } -+ -+ private final Long2IntOpenHashMap chunksBeingWorkedOn = new Long2IntOpenHashMap(); -+ -+ private void queueTaskForSection(final int chunkX, final int chunkY, final int chunkZ, final Supplier> runnable) { -+ final ServerLevel world = (ServerLevel)this.theLightEngine.getWorld(); -+ -+ final ChunkAccess center = this.theLightEngine.getAnyChunkNow(chunkX, chunkZ); -+ if (center == null || !center.getStatus().isOrAfter(ChunkStatus.LIGHT)) { -+ // do not accept updates in unlit chunks, unless we might be generating a chunk. thanks to the amazing -+ // chunk scheduling, we could be lighting and generating a chunk at the same time -+ return; -+ } -+ -+ if (center.getStatus() != ChunkStatus.FULL) { -+ // do not keep chunk loaded, we are probably in a gen thread -+ // if we proceed to add a ticket the chunk will be loaded, which is not what we want (avoid cascading gen) -+ runnable.get(); -+ return; -+ } -+ -+ if (!world.getChunkSource().chunkMap.mainThreadExecutor.isSameThread()) { -+ // ticket logic is not safe to run off-main, re-schedule -+ world.getChunkSource().chunkMap.mainThreadExecutor.execute(() -> { -+ this.queueTaskForSection(chunkX, chunkY, chunkZ, runnable); -+ }); -+ return; -+ } -+ -+ final long key = CoordinateUtils.getChunkKey(chunkX, chunkZ); -+ -+ final CompletableFuture updateFuture = runnable.get(); -+ -+ if (updateFuture == null) { -+ // not scheduled -+ return; -+ } -+ -+ final int references = this.chunksBeingWorkedOn.addTo(key, 1); -+ if (references == 0) { -+ final ChunkPos pos = new ChunkPos(chunkX, chunkZ); -+ world.getChunkSource().addRegionTicket(ca.spottedleaf.starlight.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); -+ } -+ -+ // append future to this chunk and 1 radius neighbours chunk save futures -+ // this prevents us from saving the world without first waiting for the light engine -+ -+ for (int dx = -1; dx <= 1; ++dx) { -+ for (int dz = -1; dz <= 1; ++dz) { -+ ChunkHolder neighbour = world.getChunkSource().chunkMap.getUpdatingChunkIfPresent(CoordinateUtils.getChunkKey(dx + chunkX, dz + chunkZ)); -+ if (neighbour != null) { -+ neighbour.chunkToSave = neighbour.chunkToSave.thenCombine(updateFuture, (final ChunkAccess curr, final Void ignore) -> { -+ return curr; -+ }); -+ } -+ } -+ } -+ -+ updateFuture.thenAcceptAsync((final Void ignore) -> { -+ final int newReferences = this.chunksBeingWorkedOn.get(key); -+ if (newReferences == 1) { -+ this.chunksBeingWorkedOn.remove(key); -+ final ChunkPos pos = new ChunkPos(chunkX, chunkZ); -+ world.getChunkSource().removeRegionTicket(ca.spottedleaf.starlight.light.StarLightInterface.CHUNK_WORK_TICKET, pos, 0, pos); -+ } else { -+ this.chunksBeingWorkedOn.put(key, newReferences - 1); -+ } -+ }, world.getChunkSource().chunkMap.mainThreadExecutor).whenComplete((final Void ignore, final Throwable thr) -> { -+ if (thr != null) { -+ LOGGER.fatal("Failed to remove ticket level for post chunk task " + new ChunkPos(chunkX, chunkZ), thr); -+ } -+ }); -+ } -+ -+ @Override -+ public boolean hasLightWork() { -+ // route to new light engine -+ return this.theLightEngine.hasUpdates() || !this.lightTasks.isEmpty(); - } - -+ @Override -+ public LayerLightEventListener getLayerListener(final LightLayer lightType) { -+ return lightType == LightLayer.BLOCK ? this.theLightEngine.getBlockReader() : this.theLightEngine.getSkyReader(); -+ } -+ -+ @Override -+ public int getRawBrightness(final BlockPos pos, final int ambientDarkness) { -+ // need to use new light hooks for this -+ final int sky = this.theLightEngine.getSkyReader().getLightValue(pos) - ambientDarkness; -+ final int block = this.theLightEngine.getBlockReader().getLightValue(pos); -+ return Math.max(sky, block); -+ } -+ // Tuinity end - replace light engine impl -+ - @Override - public void close() { - } -@@ -55,15 +219,16 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - - @Override - public void checkBlock(BlockPos pos) { -- BlockPos blockPos = pos.immutable(); -- this.addTask(SectionPos.blockToSectionCoord(pos.getX()), SectionPos.blockToSectionCoord(pos.getZ()), ThreadedLevelLightEngine.TaskType.POST_UPDATE, Util.name(() -> { -- super.checkBlock(blockPos); -- }, () -> { -- return "checkBlock " + blockPos; -- })); -+ // Tuinity start - replace light engine impl -+ final BlockPos posCopy = pos.immutable(); -+ this.queueTaskForSection(posCopy.getX() >> 4, posCopy.getY() >> 4, posCopy.getZ() >> 4, () -> { -+ return this.theLightEngine.blockChange(posCopy); -+ }); -+ // Tuinity end - replace light engine impl - } - - protected void updateChunkStatus(ChunkPos pos) { -+ if (true) return; // Tuinity - replace light engine impl - this.addTask(pos.x, pos.z, () -> { - return 0; - }, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { -@@ -86,17 +251,16 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - - @Override - public void updateSectionStatus(SectionPos pos, boolean notReady) { -- this.addTask(pos.x(), pos.z(), () -> { -- return 0; -- }, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { -- super.updateSectionStatus(pos, notReady); -- }, () -> { -- return "updateSectionStatus " + pos + " " + notReady; -- })); -+ // Tuinity start - replace light engine impl -+ this.queueTaskForSection(pos.getX(), pos.getY(), pos.getZ(), () -> { -+ return this.theLightEngine.sectionChange(pos, notReady); -+ }); -+ // Tuinity end - replace light engine impl - } - - @Override - public void enableLightSources(ChunkPos chunkPos, boolean bl) { -+ if (true) return; // Tuinity - replace light engine impl - this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { - super.enableLightSources(chunkPos, bl); - }, () -> { -@@ -106,6 +270,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - - @Override - public void queueSectionData(LightLayer lightType, SectionPos pos, @Nullable DataLayer nibbles, boolean bl) { -+ if (true) return; // Tuinity - replace light engine impl - this.addTask(pos.x(), pos.z(), () -> { - return 0; - }, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { -@@ -131,6 +296,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - - @Override - public void retainData(ChunkPos pos, boolean retainData) { -+ if (true) return; // Tuinity - replace light engine impl - this.addTask(pos.x, pos.z, () -> { - return 0; - }, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { -@@ -141,6 +307,37 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - } - - public CompletableFuture lightChunk(ChunkAccess chunk, boolean excludeBlocks) { -+ // Tuinity start - replace light engine impl -+ if (true) { -+ boolean lit = excludeBlocks; -+ final ChunkPos chunkPos = chunk.getPos(); -+ -+ return CompletableFuture.supplyAsync(() -> { -+ final Boolean[] emptySections = StarLightEngine.getEmptySectionsForChunk(chunk); -+ if (!lit) { -+ chunk.setLightCorrect(false); -+ this.theLightEngine.lightChunk(chunk, emptySections); -+ chunk.setLightCorrect(true); -+ } else { -+ this.theLightEngine.forceLoadInChunk(chunk, emptySections); -+ // can't really force the chunk to be edged checked, as we need neighbouring chunks - but we don't have -+ // them, so if it's not loaded then i guess we can't do edge checks. later loads of the chunk should -+ // catch what we miss here. -+ this.theLightEngine.checkChunkEdges(chunkPos.x, chunkPos.z); -+ } -+ -+ this.chunkMap.releaseLightTicket(chunkPos); -+ return chunk; -+ }, (runnable) -> { -+ this.theLightEngine.scheduleChunkLight(chunkPos, runnable); -+ this.tryScheduleUpdate(); -+ }).whenComplete((final ChunkAccess c, final Throwable throwable) -> { -+ if (throwable != null) { -+ LOGGER.fatal("Failed to light chunk " + chunkPos, throwable); -+ } -+ }); -+ } -+ // Tuinity end - replace light engine impl - ChunkPos chunkPos = chunk.getPos(); - chunk.setLightCorrect(false); - this.addTask(chunkPos.x, chunkPos.z, ThreadedLevelLightEngine.TaskType.PRE_UPDATE, Util.name(() -> { -@@ -175,7 +372,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - } - - public void tryScheduleUpdate() { -- if ((!this.lightTasks.isEmpty() || super.hasLightWork()) && this.scheduled.compareAndSet(false, true)) { -+ if (this.hasLightWork() && this.scheduled.compareAndSet(false, true)) { // Tuinity - rewrite light engine - this.taskMailbox.tell(() -> { - this.runUpdate(); - this.scheduled.set(false); -@@ -197,7 +394,7 @@ public class ThreadedLevelLightEngine extends LevelLightEngine implements AutoCl - } - - objectListIterator.back(j); -- super.runUpdates(Integer.MAX_VALUE, true, true); -+ this.theLightEngine.propagateChanges(); // Tuinity - rewrite light engine - - for(int var5 = 0; objectListIterator.hasNext() && var5 < i; ++var5) { - Pair pair2 = objectListIterator.next(); -diff --git a/src/main/java/net/minecraft/server/level/TicketType.java b/src/main/java/net/minecraft/server/level/TicketType.java -index 3c1698ba0d3bc412ab957777d9b5211dbc555208..c438cbfa1f964a5bea98bca85e688d8019e1c4fb 100644 ---- a/src/main/java/net/minecraft/server/level/TicketType.java -+++ b/src/main/java/net/minecraft/server/level/TicketType.java -@@ -31,6 +31,8 @@ public class TicketType { - public static final TicketType PLUGIN = TicketType.create("plugin", (a, b) -> 0); // CraftBukkit - public static final TicketType PLUGIN_TICKET = TicketType.create("plugin_ticket", (plugin1, plugin2) -> plugin1.getClass().getName().compareTo(plugin2.getClass().getName())); // CraftBukkit - public static final TicketType DELAY_UNLOAD = create("delay_unload", Long::compareTo, 300); // Paper -+ public static final TicketType CHUNK_RELIGHT = create("light_update", Long::compareTo); // Tuinity - ensure chunks stay loaded for lighting -+ public static final TicketType REQUIRED_LOAD = create("required_load", Long::compareTo); // Tuinity - make sure getChunkAt does not fail - - public static TicketType create(String name, Comparator argumentComparator) { - return new TicketType<>(name, argumentComparator, 0L); -diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -index 0f6b534a4c789a2f09f6c4624e5d58b99c7ed0e6..fea852674098fe411841d8e5ebeace7d11d94e4f 100644 ---- a/src/main/java/net/minecraft/server/level/WorldGenRegion.java -+++ b/src/main/java/net/minecraft/server/level/WorldGenRegion.java -@@ -77,6 +77,23 @@ public class WorldGenRegion implements WorldGenLevel { - @Nullable - private Supplier currentlyGenerating; - -+ // Tuinity start -+ // No-op, this class doesn't provide entity access -+ @Override -+ public List getHardCollidingEntities(Entity except, AABB box, Predicate predicate) { -+ return Collections.emptyList(); -+ } -+ -+ @Override -+ public void getEntities(Entity except, AABB box, Predicate predicate, List into) {} -+ -+ @Override -+ public void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into) {} -+ -+ @Override -+ public void getEntitiesByClass(Class clazz, Entity except, AABB box, List into, Predicate predicate) {} -+ // Tuinity end -+ - public WorldGenRegion(ServerLevel world, List list, ChunkStatus chunkstatus, int i) { - this.generatingStatus = chunkstatus; - this.writeRadiusCutoff = i; -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 35fa416a8ce332e823ed5077a8fd3492683d7ad0..f78119970da27ef66a9d9093e2e42ce129d4cf31 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -537,6 +537,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser - double d10 = Math.max(d6 * d6 + d7 * d7 + d8 * d8, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); - // Paper end - fix large move vectors killing the server - -+ // Tuinity start - fix large move vectors killing the server -+ double otherFieldX = d3 - this.vehicleLastGoodX; -+ double otherFieldY = d4 - this.vehicleLastGoodY - 1.0E-6D; -+ double otherFieldZ = d5 - this.vehicleLastGoodZ; -+ d10 = Math.max(d10, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1); -+ // Tuinity end - fix large move vectors killing the server - - // CraftBukkit start - handle custom speeds and skipped ticks - this.allowedPlayerTicks += (System.currentTimeMillis() / 50) - this.lastTick; -@@ -577,12 +583,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser - return; - } - -- boolean flag = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); -+ AABB oldBox = entity.getBoundingBox(); // Tuinity - copy from player movement packet - -- d6 = d3 - this.vehicleLastGoodX; -- d7 = d4 - this.vehicleLastGoodY - 1.0E-6D; -- d8 = d5 - this.vehicleLastGoodZ; -+ d6 = d3 - this.vehicleLastGoodX; // Tuinity - diff on change, used for checking large move vectors above -+ d7 = d4 - this.vehicleLastGoodY - 1.0E-6D; // Tuinity - diff on change, used for checking large move vectors above -+ d8 = d5 - this.vehicleLastGoodZ; // Tuinity - diff on change, used for checking large move vectors above - entity.move(MoverType.PLAYER, new Vec3(d6, d7, d8)); -+ boolean didCollide = toX != entity.getX() || toY != entity.getY() || toZ != entity.getZ(); // Tuinity - needed here as the difference in Y can be reset - also note: this is only a guess at whether collisions took place, floating point errors can make this true when it shouldn't be... - double d11 = d7; - - d6 = d3 - entity.getX(); -@@ -596,16 +603,23 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser - boolean flag1 = false; - - if (d10 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot -- flag1 = true; -+ flag1 = true; // Tuinity - diff on change, this should be moved wrongly - ServerGamePacketListenerImpl.LOGGER.warn("{} (vehicle of {}) moved wrongly! {}", entity.getName().getString(), this.player.getName().getString(), Math.sqrt(d10)); - } - Location curPos = this.getCraftPlayer().getLocation(); // Spigot - - entity.absMoveTo(d3, d4, d5, f, f1); - this.player.absMoveTo(d3, d4, d5, this.player.getYRot(), this.player.getXRot()); // CraftBukkit -- boolean flag2 = worldserver.noCollision(entity, entity.getBoundingBox().deflate(0.0625D)); -- -- if (flag && (flag1 || !flag2)) { -+ // Tuinity start - optimise out extra getCubes -+ boolean teleportBack = flag1; // violating this is always a fail -+ if (!teleportBack) { -+ // note: only call after setLocation, or else getBoundingBox is wrong -+ AABB newBox = entity.getBoundingBox(); -+ if (didCollide || !oldBox.equals(newBox)) { -+ teleportBack = this.hasNewCollision(worldserver, entity, oldBox, newBox); -+ } // else: no collision at all detected, why do we care? -+ } -+ if (teleportBack) { // Tuinity end - optimise out extra getCubes - entity.absMoveTo(d0, d1, d2, f, f1); - this.player.absMoveTo(d0, d1, d2, this.player.getYRot(), this.player.getXRot()); // CraftBukkit - this.connection.send(new ClientboundMoveVehiclePacket(entity)); -@@ -691,7 +705,32 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser - } - - private boolean noBlocksAround(Entity entity) { -- return entity.level.getBlockStates(entity.getBoundingBox().inflate(0.0625D).expandTowards(0.0D, -0.55D, 0.0D)).allMatch(BlockBehaviour.BlockStateBase::isAir); -+ // Tuinity start - stop using streams, this is already a known fixed problem in Entity#move -+ AABB box = entity.getBoundingBox().inflate(0.0625D).expandTowards(0.0D, -0.55D, 0.0D); -+ int minX = Mth.floor(box.minX); -+ int minY = Mth.floor(box.minY); -+ int minZ = Mth.floor(box.minZ); -+ int maxX = Mth.floor(box.maxX); -+ int maxY = Mth.floor(box.maxY); -+ int maxZ = Mth.floor(box.maxZ); -+ -+ Level world = entity.level; -+ BlockPos.MutableBlockPos pos = new BlockPos.MutableBlockPos(); -+ -+ for (int y = minY; y <= maxY; ++y) { -+ for (int z = minZ; z <= maxZ; ++z) { -+ for (int x = minX; x <= maxX; ++x) { -+ pos.set(x, y, z); -+ BlockState type = world.getTypeIfLoaded(pos); -+ if (type != null && !type.isAir()) { -+ return false; -+ } -+ } -+ } -+ } -+ -+ return true; -+ // Tuinity end - stop using streams, this is already a known fixed problem in Entity#move - } - - @Override -@@ -1247,7 +1286,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser - } - - if (this.awaitingPositionFromClient != null) { -- if (this.tickCount - this.awaitingTeleportTime > 20) { -+ if (false && this.tickCount - this.awaitingTeleportTime > 20) { // Tuinity - this will greatly screw with clients with > 1000ms RTT - this.awaitingTeleportTime = this.tickCount; - this.teleport(this.awaitingPositionFromClient.x, this.awaitingPositionFromClient.y, this.awaitingPositionFromClient.z, this.player.getYRot(), this.player.getXRot()); - } -@@ -1286,6 +1325,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser - double currDeltaZ = toZ - prevZ; - double d11 = Math.max(d7 * d7 + d8 * d8 + d9 * d9, (currDeltaX * currDeltaX + currDeltaY * currDeltaY + currDeltaZ * currDeltaZ) - 1); - // Paper end - fix large move vectors killing the server -+ // Tuinity start - fix large move vectors killing the server -+ double otherFieldX = d0 - this.lastGoodX; -+ double otherFieldY = d1 - this.lastGoodY; -+ double otherFieldZ = d2 - this.lastGoodZ; -+ d11 = Math.max(d11, (otherFieldX * otherFieldX + otherFieldY * otherFieldY + otherFieldZ * otherFieldZ) - 1); -+ // Tuinity end - fix large move vectors killing the server - - if (this.player.isSleeping()) { - if (d11 > 1.0D) { -@@ -1335,11 +1380,11 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser - } - } - -- AABB axisalignedbb = this.player.getBoundingBox(); -+ AABB axisalignedbb = this.player.getBoundingBox(); // Tuinity - diff on change, should be old AABB - -- d7 = d0 - this.lastGoodX; -- d8 = d1 - this.lastGoodY; -- d9 = d2 - this.lastGoodZ; -+ d7 = d0 - this.lastGoodX; // Tuinity - diff on change, used for checking large move vectors above -+ d8 = d1 - this.lastGoodY; // Tuinity - diff on change, used for checking large move vectors above -+ d9 = d2 - this.lastGoodZ; // Tuinity - diff on change, used for checking large move vectors above - boolean flag = d8 > 0.0D; - - if (this.player.isOnGround() && !packet.isOnGround() && flag) { -@@ -1374,6 +1419,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser - } - - this.player.move(MoverType.PLAYER, new Vec3(d7, d8, d9)); -+ boolean didCollide = toX != this.player.getX() || toY != this.player.getY() || toZ != this.player.getZ(); // Tuinity - needed here as the difference in Y can be reset - also note: this is only a guess at whether collisions took place, floating point errors can make this true when it shouldn't be... - this.player.setOnGround(packet.isOnGround()); // CraftBukkit - SPIGOT-5810, SPIGOT-5835: reset by this.player.move - // Paper start - prevent position desync - if (this.awaitingPositionFromClient != null) { -@@ -1393,12 +1439,23 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser - boolean flag1 = false; - - if (!this.player.isChangingDimension() && d11 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !this.player.isSleeping() && !this.player.gameMode.isCreative() && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR) { // Spigot -- flag1 = true; -+ flag1 = true; // Tuinity - diff on change, this should be moved wrongly - ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!", this.player.getName().getString()); - } - - this.player.absMoveTo(d0, d1, d2, f, f1); -- if (!this.player.noPhysics && !this.player.isSleeping() && (flag1 && worldserver.noCollision(this.player, axisalignedbb) || this.isPlayerCollidingWithAnythingNew((LevelReader) worldserver, axisalignedbb))) { -+ // Tuinity start - optimise out extra getCubes -+ // Original for reference: -+ // boolean teleportBack = flag1 && worldserver.getCubes(this.player, axisalignedbb) || (didCollide && this.a((IWorldReader) worldserver, axisalignedbb)); -+ boolean teleportBack = flag1; // violating this is always a fail -+ if (!this.player.noPhysics && !this.player.isSleeping() && !teleportBack) { -+ AABB newBox = this.player.getBoundingBox(); -+ if (didCollide || !axisalignedbb.equals(newBox)) { -+ // note: only call after setLocation, or else getBoundingBox is wrong -+ teleportBack = this.hasNewCollision(worldserver, this.player, axisalignedbb, newBox); -+ } // else: no collision at all detected, why do we care? -+ } -+ if (!this.player.noPhysics && !this.player.isSleeping() && teleportBack) { // Tuinity end - optimise out extra getCubes - this.teleport(d3, d4, d5, f, f1); - } else { - // CraftBukkit start - fire PlayerMoveEvent -@@ -1485,6 +1542,27 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser - } - } - -+ // Tuinity start - optimise out extra getCubes -+ private boolean hasNewCollision(final ServerLevel world, final Entity entity, final AABB oldBox, final AABB newBox) { -+ final List collisions = com.tuinity.tuinity.util.CachedLists.getTempCollisionList(); -+ try { -+ com.tuinity.tuinity.util.CollisionUtil.getCollisions(world, entity, newBox, collisions, true, false, -+ true, false, null, null); -+ -+ for (int i = 0, len = collisions.size(); i < len; ++i) { -+ final AABB box = collisions.get(i); -+ if (!com.tuinity.tuinity.util.CollisionUtil.voxelShapeIntersect(box, oldBox)) { -+ return true; -+ } -+ } -+ -+ return false; -+ } finally { -+ com.tuinity.tuinity.util.CachedLists.returnTempCollisionList(collisions); -+ } -+ } -+ // Tuinity end - optimise out extra getCubes -+ - private boolean isPlayerCollidingWithAnythingNew(LevelReader world, AABB box) { - Stream stream = world.getCollisions(this.player, this.player.getBoundingBox().deflate(9.999999747378752E-6D), (entity) -> { - return true; -diff --git a/src/main/java/net/minecraft/server/players/GameProfileCache.java b/src/main/java/net/minecraft/server/players/GameProfileCache.java -index 9a428e166561b4bc028732ec563d3b2e99f81a8e..771c575ffe46db94d9c91f3fd0440d4deb460de7 100644 ---- a/src/main/java/net/minecraft/server/players/GameProfileCache.java -+++ b/src/main/java/net/minecraft/server/players/GameProfileCache.java -@@ -60,6 +60,11 @@ public class GameProfileCache { - @Nullable - private Executor executor; - -+ // Tuinity start -+ protected final java.util.concurrent.locks.ReentrantLock stateLock = new java.util.concurrent.locks.ReentrantLock(); -+ protected final java.util.concurrent.locks.ReentrantLock lookupLock = new java.util.concurrent.locks.ReentrantLock(); -+ // Tuinity end -+ - public GameProfileCache(GameProfileRepository profileRepository, File cacheFile) { - this.profileRepository = profileRepository; - this.file = cacheFile; -@@ -67,6 +72,7 @@ public class GameProfileCache { - } - - private void safeAdd(GameProfileCache.GameProfileInfo entry) { -+ try { this.stateLock.lock(); // Tuinity - allow better concurrency - GameProfile gameprofile = entry.getProfile(); - - entry.setLastAccess(this.getNextOperation()); -@@ -81,6 +87,7 @@ public class GameProfileCache { - if (uuid != null) { - this.profilesByUUID.put(uuid, entry); - } -+ } finally { this.stateLock.unlock(); } // Tuinity - allow better concurrency - - } - -@@ -118,7 +125,7 @@ public class GameProfileCache { - return com.destroystokyo.paper.PaperConfig.isProxyOnlineMode(); // Paper - } - -- public synchronized void add(GameProfile profile) { // Paper - synchronize -+ public void add(GameProfile profile) { // Paper - synchronize // Tuinity - allow better concurrency - Calendar calendar = Calendar.getInstance(); - - calendar.setTime(new Date()); -@@ -135,8 +142,9 @@ public class GameProfileCache { - } - - @Nullable -- public synchronized GameProfile get(String name) { // Paper - synchronize -+ public GameProfile get(String name) { // Paper - synchronize // Tuinity start - allow better concurrency - String s1 = name.toLowerCase(Locale.ROOT); -+ boolean stateLocked = true; try { this.stateLock.lock(); // Tuinity - allow better concurrency - GameProfileCache.GameProfileInfo usercache_usercacheentry = (GameProfileCache.GameProfileInfo) this.profilesByName.get(s1); - boolean flag = false; - -@@ -150,10 +158,14 @@ public class GameProfileCache { - GameProfile gameprofile; - - if (usercache_usercacheentry != null) { -+ stateLocked = false; this.stateLock.unlock(); // Tuinity - allow better concurrency - usercache_usercacheentry.setLastAccess(this.getNextOperation()); - gameprofile = usercache_usercacheentry.getProfile(); - } else { -+ stateLocked = false; this.stateLock.unlock(); // Tuinity - allow better concurrency -+ try { this.lookupLock.lock(); // Tuinity - allow better concurrency - gameprofile = GameProfileCache.lookupGameProfile(this.profileRepository, name); // Spigot - use correct case for offline players -+ } finally { this.lookupLock.unlock(); } // Tuinity - allow better concurrency - if (gameprofile != null) { - this.add(gameprofile); - flag = false; -@@ -165,6 +177,7 @@ public class GameProfileCache { - } - - return gameprofile; -+ } finally { if (stateLocked) { this.stateLock.unlock(); } } // Tuinity - allow better concurrency - } - - public void getAsync(String s, Consumer consumer) { -@@ -326,7 +339,9 @@ public class GameProfileCache { - } - - private Stream getTopMRUProfiles(int limit) { -+ try { this.stateLock.lock(); // Tuinity - allow better concurrency - return ImmutableList.copyOf(this.profilesByUUID.values()).stream().sorted(Comparator.comparing(GameProfileCache.GameProfileInfo::getLastAccess).reversed()).limit((long) limit); -+ } finally { this.stateLock.unlock(); } // Tuinity - allow better concurrency - } - - private static JsonElement writeGameProfile(GameProfileCache.GameProfileInfo entry, DateFormat dateFormat) { -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index b46e64a386d256d30eac330c463c71396452563d..570cea8ee6a442b2dc3c6ef849294ef0c02027ca 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -175,6 +175,7 @@ public abstract class PlayerList { - abstract public void loadAndSaveFiles(); // Paper - moved from DedicatedPlayerList constructor - - public void placeNewPlayer(Connection connection, ServerPlayer player) { -+ player.isRealPlayer = true; // Paper // Tuinity - this is a better place to write this that works and isn't overriden by plugins - ServerPlayer prev = pendingPlayers.put(player.getUUID(), player);// Paper - if (prev != null) { - disconnectPendingPlayer(prev); -@@ -264,7 +265,7 @@ public abstract class PlayerList { - boolean flag1 = gamerules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO); - - // Spigot - view distance -- playerconnection.send(new ClientboundLoginPacket(player.getId(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), worlddata.isHardcore(), this.server.levelKeys(), this.registryHolder, worldserver1.dimensionType(), worldserver1.dimension(), this.getMaxPlayers(), worldserver1.getChunkSource().chunkMap.getLoadViewDistance(), flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat())); // Paper - no-tick view distance -+ playerconnection.send(new ClientboundLoginPacket(player.getId(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), worlddata.isHardcore(), this.server.levelKeys(), this.registryHolder, worldserver1.dimensionType(), worldserver1.dimension(), this.getMaxPlayers(), worldserver1.getChunkSource().chunkMap.playerChunkManager.getLoadDistance(), flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat())); // Paper - no-tick view distance // Tuinity - replace old player chunk management - player.getBukkitEntity().sendSupportedChannels(); // CraftBukkit - playerconnection.send(new ClientboundCustomPayloadPacket(ClientboundCustomPayloadPacket.BRAND, (new FriendlyByteBuf(Unpooled.buffer())).writeUtf(this.getServer().getServerModName()))); - playerconnection.send(new ClientboundChangeDifficultyPacket(worlddata.getDifficulty(), worlddata.isDifficultyLocked())); -@@ -723,7 +724,7 @@ public abstract class PlayerList { - SocketAddress socketaddress = loginlistener.connection.getRemoteAddress(); - - ServerPlayer entity = new ServerPlayer(this.server, this.server.getLevel(Level.OVERWORLD), gameprofile); -- entity.isRealPlayer = true; // Paper - Chunk priority -+ // Tuinity - some plugins (namely protocolsupport) bypass this logic completely! So this needs to be moved. - Player player = entity.getBukkitEntity(); - PlayerLoginEvent event = new PlayerLoginEvent(player, hostname, ((java.net.InetSocketAddress) socketaddress).getAddress(), ((java.net.InetSocketAddress) loginlistener.connection.getRawAddress()).getAddress()); - -@@ -927,13 +928,13 @@ public abstract class PlayerList { - - worldserver1.getChunkSource().addRegionTicket(net.minecraft.server.level.TicketType.POST_TELEPORT, new net.minecraft.world.level.ChunkPos(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper - entityplayer1.forceCheckHighPriority(); // Player - Chunk priority -- while (avoidSuffocation && !worldserver1.noCollision(entityplayer1) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) { -+ while (avoidSuffocation && !worldserver1.noCollision(entityplayer1, entityplayer1.getBoundingBox(), null, true) && entityplayer1.getY() < (double) worldserver1.getMaxBuildHeight()) { // Tuinity - make sure this loads chunks, we default to NOT loading now - entityplayer1.setPos(entityplayer1.getX(), entityplayer1.getY() + 1.0D, entityplayer1.getZ()); - } - // CraftBukkit start - LevelData worlddata = worldserver1.getLevelData(); - entityplayer1.connection.send(new ClientboundRespawnPacket(worldserver1.dimensionType(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), entityplayer1.gameMode.getGameModeForPlayer(), entityplayer1.gameMode.getPreviousGameModeForPlayer(), worldserver1.isDebug(), worldserver1.isFlat(), flag)); -- entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.getChunkSource().chunkMap.getLoadViewDistance())); // Spigot // Paper - no-tick view distance -+ entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.getChunkSource().chunkMap.playerChunkManager.getLoadDistance())); // Spigot // Paper - no-tick view distance// Tuinity - replace old player chunk management - entityplayer1.setLevel(worldserver1); - entityplayer1.unsetRemoved(); - entityplayer1.connection.teleport(new Location(worldserver1.getWorld(), entityplayer1.getX(), entityplayer1.getY(), entityplayer1.getZ(), entityplayer1.getYRot(), entityplayer1.getXRot())); -@@ -1208,7 +1209,7 @@ public abstract class PlayerList { - // Really shouldn't happen... - backingSet = world != null ? world.players.toArray() : players.toArray(); - } else { -- com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearbyPlayers = chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(MCUtil.fastFloor(x) >> 4, MCUtil.fastFloor(z) >> 4); -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearbyPlayers = chunkMap.playerChunkManager.broadcastMap.getObjectsInRange(MCUtil.fastFloor(x) >> 4, MCUtil.fastFloor(z) >> 4); // Tuinity - replace old player chunk management - if (nearbyPlayers == null) { - return; - } -diff --git a/src/main/java/net/minecraft/util/BitStorage.java b/src/main/java/net/minecraft/util/BitStorage.java -index 07e1374ac3430662edd9f585e59b785e329f0820..9f9c0b56f0891e9c423d79f8ae4c3643a2b91048 100644 ---- a/src/main/java/net/minecraft/util/BitStorage.java -+++ b/src/main/java/net/minecraft/util/BitStorage.java -@@ -104,4 +104,32 @@ public class BitStorage { - } - - } -+ -+ // Paper start -+ public final void forEach(DataBitConsumer consumer) { -+ int i = 0; -+ long[] along = this.data; -+ int j = along.length; -+ -+ for (int k = 0; k < j; ++k) { -+ long l = along[k]; -+ -+ for (int i1 = 0; i1 < this.valuesPerLong; ++i1) { -+ consumer.accept(i, (int) (l & this.mask)); -+ l >>= this.bits; -+ ++i; -+ if (i >= this.size) { -+ return; -+ } -+ } -+ } -+ } -+ -+ @FunctionalInterface -+ public static interface DataBitConsumer { -+ -+ void accept(int location, int data); -+ -+ } -+ // Paper end - } -diff --git a/src/main/java/net/minecraft/util/valueproviders/IntProvider.java b/src/main/java/net/minecraft/util/valueproviders/IntProvider.java -index 020a19cd683dd3779c5116d12b3cdcd3b3ca69b4..17d209c347b07acef451180c97835f41b8bf8433 100644 ---- a/src/main/java/net/minecraft/util/valueproviders/IntProvider.java -+++ b/src/main/java/net/minecraft/util/valueproviders/IntProvider.java -@@ -9,13 +9,44 @@ import net.minecraft.core.Registry; - - public abstract class IntProvider { - private static final Codec> CONSTANT_OR_DISPATCH_CODEC = Codec.either(Codec.INT, Registry.INT_PROVIDER_TYPES.dispatch(IntProvider::getType, IntProviderType::codec)); -- public static final Codec CODEC = CONSTANT_OR_DISPATCH_CODEC.xmap((either) -> { -+ public static final Codec CODEC_REAL = CONSTANT_OR_DISPATCH_CODEC.xmap((either) -> { // Paper - used by CODEC below - return either.map(ConstantInt::of, (intProvider) -> { - return intProvider; - }); - }, (intProvider) -> { - return intProvider.getType() == IntProviderType.CONSTANT ? Either.left(((ConstantInt)intProvider).getValue()) : Either.right(intProvider); - }); -+ // Tuinity start -+ public static final Codec CODEC = new Codec<>() { -+ @Override -+ public DataResult> decode(com.mojang.serialization.DynamicOps ops, T input) { -+ /* -+ UniformInt: -+ count -> { (old format) -+ base, spread -+ } -> {UniformInt} { (new format & type) -+ base, base + spread -+ } */ -+ -+ -+ if (ops.get(input, "base").result().isPresent() && ops.get(input, "spread").result().isPresent()) { -+ // detected old format -+ int base = ops.getNumberValue(ops.get(input, "base").result().get()).result().get().intValue(); -+ int spread = ops.getNumberValue(ops.get(input, "spread").result().get()).result().get().intValue(); -+ return DataResult.success(new com.mojang.datafixers.util.Pair<>(UniformInt.of(base, base + spread), input)); -+ } -+ -+ // not old format, forward to real codec -+ return CODEC_REAL.decode(ops, input); -+ } -+ -+ @Override -+ public DataResult encode(IntProvider input, com.mojang.serialization.DynamicOps ops, T prefix) { -+ // forward to real codec -+ return CODEC_REAL.encode(input, ops, prefix); -+ } -+ }; -+ // Tuinity end - public static final Codec NON_NEGATIVE_CODEC = codec(0, Integer.MAX_VALUE); - public static final Codec POSITIVE_CODEC = codec(1, Integer.MAX_VALUE); - -diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 4fd030ef9537d9b31c6167d73349f4c4a6b33a15..ca7718053a6a2eb715ea3671bd4bc15304ede420 100644 ---- a/src/main/java/net/minecraft/world/entity/Entity.java -+++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -356,8 +356,27 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n - } - - public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getPlayersInTrackRange() { -- return ((ServerLevel)this.level).getChunkSource().chunkMap.playerEntityTrackerTrackMaps[this.trackingRangeType.ordinal()] -- .getObjectsInRange(MCUtil.getCoordinateKey(this)); -+ // Tuinity start - determine highest range of passengers -+ if (this.passengers.isEmpty()) { -+ return ((ServerLevel)this.level).getChunkSource().chunkMap.playerEntityTrackerTrackMaps[this.trackingRangeType.ordinal()] -+ .getObjectsInRange(MCUtil.getCoordinateKey(this)); -+ } -+ Iterable passengers = this.getIndirectPassengers(); -+ net.minecraft.server.level.ChunkMap chunkMap = ((ServerLevel)this.level).getChunkSource().chunkMap; -+ org.spigotmc.TrackingRange.TrackingRangeType type = this.trackingRangeType; -+ int range = chunkMap.getEntityTrackerRange(type.ordinal()); -+ -+ for (Entity passenger : passengers) { -+ org.spigotmc.TrackingRange.TrackingRangeType passengerType = passenger.trackingRangeType; -+ int passengerRange = chunkMap.getEntityTrackerRange(passengerType.ordinal()); -+ if (passengerRange > range) { -+ type = passengerType; -+ range = passengerRange; -+ } -+ } -+ -+ return chunkMap.playerEntityTrackerTrackMaps[type.ordinal()].getObjectsInRange(MCUtil.getCoordinateKey(this)); -+ // Tuinity end - determine highest range of passengers - } - // Paper end - optimise entity tracking - -@@ -392,6 +411,56 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n - } - // Paper end - make end portalling safe - -+ // Tuinity start -+ public final AABB getBoundingBoxAt(double x, double y, double z) { -+ return this.dimensions.makeBoundingBox(x, y, z); -+ } -+ // Tuinity end -+ -+ // Tuinity start -+ /** -+ * Overriding this field will cause memory leaks. -+ */ -+ private final boolean hardCollides; -+ -+ private static final java.util.Map, Boolean> cachedOverrides = java.util.Collections.synchronizedMap(new java.util.WeakHashMap<>()); -+ { -+ /* // Goodbye, broken on reobf... -+ Boolean hardCollides = cachedOverrides.get(this.getClass()); -+ if (hardCollides == null) { -+ try { -+ java.lang.reflect.Method getHardCollisionBoxEntityMethod = Entity.class.getMethod("canCollideWith", Entity.class); -+ java.lang.reflect.Method hasHardCollisionBoxMethod = Entity.class.getMethod("canBeCollidedWith"); -+ if (!this.getClass().getMethod(hasHardCollisionBoxMethod.getName(), hasHardCollisionBoxMethod.getParameterTypes()).equals(hasHardCollisionBoxMethod) -+ || !this.getClass().getMethod(getHardCollisionBoxEntityMethod.getName(), getHardCollisionBoxEntityMethod.getParameterTypes()).equals(getHardCollisionBoxEntityMethod)) { -+ hardCollides = Boolean.TRUE; -+ } else { -+ hardCollides = Boolean.FALSE; -+ } -+ cachedOverrides.put(this.getClass(), hardCollides); -+ } -+ catch (ThreadDeath thr) { throw thr; } -+ catch (Throwable thr) { -+ // shouldn't happen, just explode -+ throw new RuntimeException(thr); -+ } -+ } */ -+ this.hardCollides = this instanceof Boat -+ || this instanceof net.minecraft.world.entity.monster.Shulker -+ || this instanceof net.minecraft.world.entity.vehicle.AbstractMinecart; -+ } -+ -+ public final boolean hardCollides() { -+ return this.hardCollides; -+ } -+ -+ public net.minecraft.server.level.ChunkHolder.FullChunkStatus chunkStatus; -+ -+ public int sectionX = Integer.MIN_VALUE; -+ public int sectionY = Integer.MIN_VALUE; -+ public int sectionZ = Integer.MIN_VALUE; -+ // Tuinity end -+ - public Entity(EntityType type, Level world) { - this.id = Entity.ENTITY_COUNTER.incrementAndGet(); - this.passengers = ImmutableList.of(); -@@ -813,7 +882,42 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n - return this.onGround; - } - -+ // Tuinity start - detailed watchdog information -+ public final Object posLock = new Object(); // Tuinity - log detailed entity tick information -+ -+ private Vec3 moveVector; -+ private double moveStartX; -+ private double moveStartY; -+ private double moveStartZ; -+ -+ public final Vec3 getMoveVector() { -+ return this.moveVector; -+ } -+ -+ public final double getMoveStartX() { -+ return this.moveStartX; -+ } -+ -+ public final double getMoveStartY() { -+ return this.moveStartY; -+ } -+ -+ public final double getMoveStartZ() { -+ return this.moveStartZ; -+ } -+ // Tuinity end - detailed watchdog information -+ - public void move(MoverType movementType, Vec3 movement) { -+ // Tuinity start - detailed watchdog information -+ com.tuinity.tuinity.util.TickThread.ensureTickThread("Cannot move an entity off-main"); -+ synchronized (this.posLock) { -+ this.moveStartX = this.getX(); -+ this.moveStartY = this.getY(); -+ this.moveStartZ = this.getZ(); -+ this.moveVector = movement; -+ } -+ try { -+ // Tuinity end - detailed watchdog information - if (this.noPhysics) { - this.setPos(this.getX() + movement.x, this.getY() + movement.y, this.getZ() + movement.z); - } else { -@@ -949,9 +1053,44 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n - float f2 = this.getBlockSpeedFactor(); - - this.setDeltaMovement(this.getDeltaMovement().multiply((double) f2, 1.0D, (double) f2)); -- if (this.level.getBlockStatesIfLoaded(this.getBoundingBox().deflate(1.0E-6D)).noneMatch((iblockdata1) -> { -- return iblockdata1.is((Tag) BlockTags.FIRE) || iblockdata1.is(Blocks.LAVA); -- })) { -+ // Tuinity start - remove expensive streams from here -+ boolean noneMatch = true; -+ AABB fireSearchBox = this.getBoundingBox().deflate(1.0E-6D); -+ { -+ int minX = Mth.floor(fireSearchBox.minX); -+ int minY = Mth.floor(fireSearchBox.minY); -+ int minZ = Mth.floor(fireSearchBox.minZ); -+ int maxX = Mth.floor(fireSearchBox.maxX); -+ int maxY = Mth.floor(fireSearchBox.maxY); -+ int maxZ = Mth.floor(fireSearchBox.maxZ); -+ fire_search_loop: -+ for (int fz = minZ; fz <= maxZ; ++fz) { -+ for (int fx = minX; fx <= maxX; ++fx) { -+ for (int fy = minY; fy <= maxY; ++fy) { -+ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)this.level.getChunkIfLoadedImmediately(fx >> 4, fz >> 4); -+ if (chunk == null) { -+ // Vanilla rets an empty stream if all the chunks are not loaded, so noneMatch will be true -+ // even if we're in lava/fire -+ noneMatch = true; -+ break fire_search_loop; -+ } -+ if (!noneMatch) { -+ // don't do get type, we already know we're in fire - we just need to check the chunks -+ // loaded state -+ continue; -+ } -+ -+ BlockState type = chunk.getType(fx, fy, fz); -+ if (type.is((Tag) BlockTags.FIRE) || type.is(Blocks.LAVA)) { -+ noneMatch = false; -+ // can't break, we need to retain vanilla behavior by ensuring ALL chunks are loaded -+ } -+ } -+ } -+ } -+ } -+ if (noneMatch) { -+ // Tuinity end - remove expensive streams from here - if (this.remainingFireTicks <= 0) { - this.setRemainingFireTicks(-this.getFireImmuneTicks()); - } -@@ -968,6 +1107,13 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n - this.level.getProfiler().pop(); - } - } -+ // Tuinity start - detailed watchdog information -+ } finally { -+ synchronized (this.posLock) { // Tuinity -+ this.moveVector = null; -+ } // Tuinity -+ } -+ // Tuinity end - detailed watchdog information - } - - protected void tryCheckInsideBlocks() { -@@ -1073,39 +1219,79 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n - return offsetFactor; - } - -- private Vec3 collide(Vec3 movement) { -- AABB axisalignedbb = this.getBoundingBox(); -- CollisionContext voxelshapecollision = CollisionContext.of(this); -- VoxelShape voxelshape = this.level.getWorldBorder().getCollisionShape(); -- Stream stream = !this.level.getWorldBorder().isWithinBounds(axisalignedbb) ? Stream.empty() : Stream.of(voxelshape); // Paper -- Stream stream1 = this.level.getEntityCollisions(this, axisalignedbb.expandTowards(movement), (entity) -> { -- return true; -- }); -- RewindableStream streamaccumulator = new RewindableStream<>(Stream.concat(stream1, stream)); -- Vec3 vec3d1 = movement.lengthSqr() == 0.0D ? movement : Entity.collideBoundingBoxHeuristically(this, movement, axisalignedbb, this.level, voxelshapecollision, streamaccumulator); -- boolean flag = movement.x != vec3d1.x; -- boolean flag1 = movement.y != vec3d1.y; -- boolean flag2 = movement.z != vec3d1.z; -- boolean flag3 = this.onGround || flag1 && movement.y < 0.0D; -- -- if (this.maxUpStep > 0.0F && flag3 && (flag || flag2)) { -- Vec3 vec3d2 = Entity.collideBoundingBoxHeuristically(this, new Vec3(movement.x, (double) this.maxUpStep, movement.z), axisalignedbb, this.level, voxelshapecollision, streamaccumulator); -- Vec3 vec3d3 = Entity.collideBoundingBoxHeuristically(this, new Vec3(0.0D, (double) this.maxUpStep, 0.0D), axisalignedbb.expandTowards(movement.x, 0.0D, movement.z), this.level, voxelshapecollision, streamaccumulator); -- -- if (vec3d3.y < (double) this.maxUpStep) { -- Vec3 vec3d4 = Entity.collideBoundingBoxHeuristically(this, new Vec3(movement.x, 0.0D, movement.z), axisalignedbb.move(vec3d3), this.level, voxelshapecollision, streamaccumulator).add(vec3d3); -- -- if (vec3d4.horizontalDistanceSqr() > vec3d2.horizontalDistanceSqr()) { -- vec3d2 = vec3d4; -+ private Vec3 collide(Vec3 moveVector) { -+ // Tuinity start - optimise collisions -+ // This is a copy of vanilla's except that it uses strictly AABB math -+ if (moveVector.x == 0.0 && moveVector.y == 0.0 && moveVector.z == 0.0) { -+ return moveVector; -+ } -+ -+ final Level world = this.level; -+ final AABB currBoundingBox = this.getBoundingBox(); -+ -+ if (com.tuinity.tuinity.util.CollisionUtil.isEmpty(currBoundingBox)) { -+ return moveVector; -+ } -+ -+ final List potentialCollisions = com.tuinity.tuinity.util.CachedLists.getTempCollisionList(); -+ try { -+ final double stepHeight = (double)this.maxUpStep; -+ final AABB collisionBox; -+ -+ if (moveVector.x == 0.0 && moveVector.z == 0.0 && moveVector.y != 0.0) { -+ if (moveVector.y > 0.0) { -+ collisionBox = com.tuinity.tuinity.util.CollisionUtil.cutUpwards(currBoundingBox, moveVector.y); -+ } else { -+ collisionBox = com.tuinity.tuinity.util.CollisionUtil.cutDownwards(currBoundingBox, moveVector.y); -+ } -+ } else { -+ if (stepHeight > 0.0 && (this.onGround || (moveVector.y < 0.0)) && (moveVector.x != 0.0 || moveVector.z != 0.0)) { -+ // don't bother getting the collisions if we don't need them. -+ if (moveVector.y <= 0.0) { -+ collisionBox = com.tuinity.tuinity.util.CollisionUtil.expandUpwards(currBoundingBox.expandTowards(moveVector.x, moveVector.y, moveVector.z), stepHeight); -+ } else { -+ collisionBox = currBoundingBox.expandTowards(moveVector.x, Math.max(stepHeight, moveVector.y), moveVector.z); -+ } -+ } else { -+ collisionBox = currBoundingBox.expandTowards(moveVector.x, moveVector.y, moveVector.z); - } - } - -- if (vec3d2.horizontalDistanceSqr() > vec3d1.horizontalDistanceSqr()) { -- return vec3d2.add(Entity.collideBoundingBoxHeuristically(this, new Vec3(0.0D, -vec3d2.y + movement.y, 0.0D), axisalignedbb.move(vec3d2), this.level, voxelshapecollision, streamaccumulator)); -+ com.tuinity.tuinity.util.CollisionUtil.getCollisions(world, this, collisionBox, potentialCollisions, false, true, -+ false, false, null, null); -+ -+ if (com.tuinity.tuinity.util.CollisionUtil.isCollidingWithBorderEdge(world.getWorldBorder(), collisionBox)) { -+ com.tuinity.tuinity.util.CollisionUtil.addBoxesToIfIntersects(world.getWorldBorder().getCollisionShape(), collisionBox, potentialCollisions); - } -- } - -- return vec3d1; -+ final Vec3 limitedMoveVector = com.tuinity.tuinity.util.CollisionUtil.performCollisions(moveVector, currBoundingBox, potentialCollisions); -+ -+ if (stepHeight > 0.0 -+ && (this.onGround || (limitedMoveVector.y != moveVector.y && moveVector.y < 0.0)) -+ && (limitedMoveVector.x != moveVector.x || limitedMoveVector.z != moveVector.z)) { -+ Vec3 vec3d2 = com.tuinity.tuinity.util.CollisionUtil.performCollisions(new Vec3(moveVector.x, stepHeight, moveVector.z), currBoundingBox, potentialCollisions); -+ final Vec3 vec3d3 = com.tuinity.tuinity.util.CollisionUtil.performCollisions(new Vec3(0.0, stepHeight, 0.0), currBoundingBox.expandTowards(moveVector.x, 0.0, moveVector.z), potentialCollisions); -+ -+ if (vec3d3.y < stepHeight) { -+ final Vec3 vec3d4 = com.tuinity.tuinity.util.CollisionUtil.performCollisions(new Vec3(moveVector.x, 0.0D, moveVector.z), currBoundingBox.move(vec3d3), potentialCollisions).add(vec3d3); -+ -+ if (vec3d4.horizontalDistanceSqr() > vec3d2.horizontalDistanceSqr()) { -+ vec3d2 = vec3d4; -+ } -+ } -+ -+ if (vec3d2.horizontalDistanceSqr() > limitedMoveVector.horizontalDistanceSqr()) { -+ return vec3d2.add(com.tuinity.tuinity.util.CollisionUtil.performCollisions(new Vec3(0.0D, -vec3d2.y + moveVector.y, 0.0D), currBoundingBox.move(vec3d2), potentialCollisions)); -+ } -+ -+ return limitedMoveVector; -+ } else { -+ return limitedMoveVector; -+ } -+ } finally { -+ com.tuinity.tuinity.util.CachedLists.returnTempCollisionList(potentialCollisions); -+ } -+ // Tuinity end - optimise collisions - } - - public static Vec3 collideBoundingBoxHeuristically(@Nullable Entity entity, Vec3 movement, AABB entityBoundingBox, Level world, CollisionContext context, RewindableStream collisions) { -@@ -2244,9 +2430,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n - float f = this.dimensions.width * 0.8F; - AABB axisalignedbb = AABB.ofSize(this.getEyePosition(), (double) f, 1.0E-6D, (double) f); - -- return this.level.getBlockCollisions(this, axisalignedbb, (iblockdata, blockposition) -> { -- return iblockdata.isSuffocating(this.level, blockposition); -- }).findAny().isPresent(); -+ // Tuinity start -+ return com.tuinity.tuinity.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this.level, this, axisalignedbb, null, -+ false, false, false, true, (iblockdata, blockposition) -> { -+ return iblockdata.isSuffocating(this.level, blockposition); -+ }); -+ // Tuinity end - } - } - -@@ -2254,11 +2443,11 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n - return InteractionResult.PASS; - } - -- public boolean canCollideWith(Entity other) { -+ public boolean canCollideWith(Entity other) { // Tuinity - diff on change, hard colliding entities override this - TODO CHECK ON UPDATE - AbstractMinecart/Boat override - return other.canBeCollidedWith() && !this.isPassengerOfSameVehicle(other); - } - -- public boolean canBeCollidedWith() { -+ public boolean canBeCollidedWith() { // Tuinity - diff on change, hard colliding entities override this TODO CHECK ON UPDATE - Boat/Shulker override - return false; - } - -@@ -3727,7 +3916,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n - } - - public void setDeltaMovement(Vec3 velocity) { -+ synchronized (this.posLock) { // Tuinity - this.deltaMovement = velocity; -+ } // Tuinity - } - - public void setDeltaMovement(double x, double y, double z) { -@@ -3789,7 +3980,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n - public final void setPosRaw(double x, double y, double z) { - // Paper start - fix MC-4 - if (this instanceof ItemEntity) { -- if (com.destroystokyo.paper.PaperConfig.fixEntityPositionDesync) { -+ if (false && com.destroystokyo.paper.PaperConfig.fixEntityPositionDesync) { // Tuinity - revert - // encode/decode from PacketPlayOutEntity - x = Mth.lfloor(x * 4096.0D) * (1 / 4096.0D); - y = Mth.lfloor(y * 4096.0D) * (1 / 4096.0D); -@@ -3804,7 +3995,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n - } - // Paper end - if (this.position.x != x || this.position.y != y || this.position.z != z) { -+ synchronized (this.posLock) { // Tuinity - this.position = new Vec3(x, y, z); -+ } // Tuinity - int i = Mth.floor(x); - int j = Mth.floor(y); - int k = Mth.floor(z); -diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index c4c5c35e37b793f3b74349ff03c0829f4913b91c..154b3c767d079f72643c826b962892c1029b0a1b 100644 ---- a/src/main/java/net/minecraft/world/entity/Mob.java -+++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -784,7 +784,12 @@ public abstract class Mob extends LivingEntity { - if (this.level.getDifficulty() == Difficulty.PEACEFUL && this.shouldDespawnInPeaceful()) { - this.discard(); - } else if (!this.isPersistenceRequired() && !this.requiresCustomPersistence()) { -- Player entityhuman = this.level.findNearbyPlayer(this, -1.0D, EntitySelector.affectsSpawning); // Paper -+ // Tuinity start - optimise checkDespawn -+ Player entityhuman = this.level.findNearbyPlayer(this, level.paperConfig.hardDespawnDistance + 1, EntitySelector.affectsSpawning); // Paper -+ if (entityhuman == null) { -+ entityhuman = ((ServerLevel)this.level).playersAffectingSpawning.isEmpty() ? null : ((ServerLevel)this.level).playersAffectingSpawning.get(0); -+ } -+ // Tuinity end - optimise checkDespawn - - if (entityhuman != null) { - double d0 = entityhuman.distanceToSqr((Entity) this); // CraftBukkit - decompile error -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java -index 84a0ee595bebcc1947c602c4c06e7437706ce37c..efe66264ad5717bf3aac0fbda07275fb5571acc1 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java -@@ -83,7 +83,11 @@ public class AcquirePoi extends Behavior { - return true; - } - }; -- Set set = poiManager.findAllClosestFirst(this.poiType.getPredicate(), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.HAS_SPACE).limit(5L).collect(Collectors.toSet()); -+ // Tuinity start - optimise POI access -+ java.util.List poiposes = new java.util.ArrayList<>(); -+ com.tuinity.tuinity.util.PoiAccess.findNearestPoiPositions(poiManager, this.poiType.getPredicate(), predicate, entity.blockPosition(), 48, 48*48, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes); -+ Set set = new java.util.HashSet<>(poiposes); -+ // Tuinity end - optimise POI access - Path path = entity.getNavigation().createPath(set, this.poiType.getValidRange()); - if (path != null && path.canReach()) { - BlockPos blockPos = path.getTarget(); -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java -index 09998d160a6d79fdb5a5041a5d572649a1532e6a..3fe1f9bd4bb670d9a1ddabf2475f4d8f44d7e6fe 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/GateBehavior.java -@@ -30,11 +30,19 @@ public class GateBehavior extends Behavior { - - @Override - protected boolean canStillUse(ServerLevel world, E entity, long time) { -- return this.behaviors.stream().filter((behavior) -> { -- return behavior.getStatus() == Behavior.Status.RUNNING; -- }).anyMatch((behavior) -> { -- return behavior.canStillUse(world, entity, time); -- }); -+ // Tuinity start - remove streams -+ List>> entries = this.behaviors.entries; -+ for (int i = 0; i < entries.size(); i++) { -+ ShufflingList.WeightedEntry> entry = entries.get(i); -+ Behavior behavior = entry.getData(); -+ if (behavior.getStatus() == Status.RUNNING) { -+ if (behavior.canStillUse(world, entity, time)) { -+ return true; -+ } -+ } -+ } -+ return false; -+ // Tuinity end - remove streams - } - - @Override -@@ -45,25 +53,35 @@ public class GateBehavior extends Behavior { - @Override - protected void start(ServerLevel world, E entity, long time) { - this.orderPolicy.apply(this.behaviors); -- this.runningPolicy.apply(this.behaviors.stream(), world, entity, time); -+ this.runningPolicy.apply(this.behaviors.entries, world, entity, time); // Tuinity - remove streams - } - - @Override - protected void tick(ServerLevel world, E entity, long time) { -- this.behaviors.stream().filter((behavior) -> { -- return behavior.getStatus() == Behavior.Status.RUNNING; -- }).forEach((behavior) -> { -- behavior.tickOrStop(world, entity, time); -- }); -+ // Tuinity start - remove streams -+ List>> entries = this.behaviors.entries; -+ for (int i = 0; i < entries.size(); i++) { -+ ShufflingList.WeightedEntry> entry = entries.get(i); -+ Behavior behavior = entry.getData(); -+ if (behavior.getStatus() == Status.RUNNING) { -+ behavior.tickOrStop(world, entity, time); -+ } -+ } -+ // Tuinity end - remove streams - } - - @Override - protected void stop(ServerLevel world, E entity, long time) { -- this.behaviors.stream().filter((behavior) -> { -- return behavior.getStatus() == Behavior.Status.RUNNING; -- }).forEach((behavior) -> { -- behavior.doStop(world, entity, time); -- }); -+ // Tuinity start - remove streams -+ List>> entries = this.behaviors.entries; -+ for (int i = 0; i < entries.size(); i++) { -+ ShufflingList.WeightedEntry> entry = entries.get(i); -+ Behavior behavior = entry.getData(); -+ if (behavior.getStatus() == Status.RUNNING) { -+ behavior.doStop(world, entity, time); -+ } -+ } -+ // Tuinity end - remove streams - this.exitErasedMemories.forEach(entity.getBrain()::eraseMemory); - } - -@@ -94,25 +112,33 @@ public class GateBehavior extends Behavior { - public static enum RunningPolicy { - RUN_ONE { - @Override -- public void apply(Stream> tasks, ServerLevel world, E entity, long time) { -- tasks.filter((behavior) -> { -- return behavior.getStatus() == Behavior.Status.STOPPED; -- }).filter((behavior) -> { -- return behavior.tryStart(world, entity, time); -- }).findFirst(); -+ // Tuinity start - remove streams -+ public void apply(List>> tasks, ServerLevel world, E entity, long time) { -+ for (int i = 0; i < tasks.size(); i++) { -+ ShufflingList.WeightedEntry> task = tasks.get(i); -+ Behavior behavior = task.getData(); -+ if (behavior.getStatus() == Status.STOPPED && behavior.tryStart(world, entity, time)) { -+ break; -+ } -+ } -+ // Tuinity end - remove streams - } - }, - TRY_ALL { - @Override -- public void apply(Stream> tasks, ServerLevel world, E entity, long time) { -- tasks.filter((behavior) -> { -- return behavior.getStatus() == Behavior.Status.STOPPED; -- }).forEach((behavior) -> { -- behavior.tryStart(world, entity, time); -- }); -+ // Tuinity start - remove streams -+ public void apply(List>> tasks, ServerLevel world, E entity, long time) { -+ for (int i = 0; i < tasks.size(); i++) { -+ ShufflingList.WeightedEntry> task = tasks.get(i); -+ Behavior behavior = task.getData(); -+ if (behavior.getStatus() == Status.STOPPED) { -+ behavior.tryStart(world, entity, time); -+ } -+ } -+ // Tuinity end - remove streams - } - }; - -- public abstract void apply(Stream> tasks, ServerLevel world, E entity, long time); -+ public abstract void apply(List>> tasks, ServerLevel world, E entity, long time); // Tuinity - remove streams - } - } -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/SetLookAndInteract.java b/src/main/java/net/minecraft/world/entity/ai/behavior/SetLookAndInteract.java -index 1f59e790d62f0be8e505e339a6699ca3964aea0d..bb43e47d4b3989610a52c1941598865aee93ac04 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/SetLookAndInteract.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/SetLookAndInteract.java -@@ -34,21 +34,42 @@ public class SetLookAndInteract extends Behavior { - - @Override - public boolean checkExtraStartConditions(ServerLevel world, LivingEntity entity) { -- return this.selfFilter.test(entity) && this.getVisibleEntities(entity).stream().anyMatch(this::isMatchingTarget); -+ // Tuinity start - remove streams -+ if (!this.selfFilter.test(entity)) { -+ return false; -+ } -+ -+ List visibleEntities = this.getVisibleEntities(entity); -+ for (int i = 0; i < visibleEntities.size(); i++) { -+ LivingEntity livingEntity = visibleEntities.get(i); -+ if (this.isMatchingTarget(livingEntity)) { -+ return true; -+ } -+ } -+ return false; -+ // Tuinity end - remove streams - } - - @Override - public void start(ServerLevel world, LivingEntity entity, long time) { - super.start(world, entity, time); - Brain brain = entity.getBrain(); -- brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES).ifPresent((list) -> { -- list.stream().filter((livingEntity2) -> { -- return livingEntity2.distanceToSqr(entity) <= (double)this.interactionRangeSqr; -- }).filter(this::isMatchingTarget).findFirst().ifPresent((livingEntity) -> { -- brain.setMemory(MemoryModuleType.INTERACTION_TARGET, livingEntity); -- brain.setMemory(MemoryModuleType.LOOK_TARGET, new EntityTracker(livingEntity, true)); -- }); -- }); -+ // Tuinity start - remove streams -+ List list = brain.getMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES).orElse(null); -+ if (list != null) { -+ double maxRangeSquared = (double)this.interactionRangeSqr; -+ for (int i = 0; i < list.size(); i++) { -+ LivingEntity livingEntity2 = list.get(i); -+ if (livingEntity2.distanceToSqr(entity) <= maxRangeSquared) { -+ if (this.isMatchingTarget(livingEntity2)) { -+ brain.setMemory(MemoryModuleType.INTERACTION_TARGET, livingEntity2); -+ brain.setMemory(MemoryModuleType.LOOK_TARGET, new EntityTracker(livingEntity2, true)); -+ break; -+ } -+ } -+ } -+ } -+ // Tuinity end - remove streams - } - - private boolean isMatchingTarget(LivingEntity entity) { -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java b/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java -index 4fa64b1e2004810906bb0b174436c8e687a75ada..d5a3c6d239abbb31c52ec2dfb9b18b1b705cbc88 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/ShufflingList.java -@@ -12,7 +12,7 @@ import java.util.Random; - import java.util.stream.Stream; - - public class ShufflingList { -- protected final List> entries; -+ public final List> entries; // Tuinity - public - private final Random random = new Random(); - private final boolean isUnsafe; // Paper - -diff --git a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java -index e605daac0c90f5d0b9315d1499938feb0e478d0e..570316cf7831de70086fae35676006ee052851e0 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java -+++ b/src/main/java/net/minecraft/world/entity/ai/navigation/PathNavigation.java -@@ -27,7 +27,7 @@ import net.minecraft.world.phys.Vec3; - - public abstract class PathNavigation { - private static final int MAX_TIME_RECOMPUTE = 20; -- protected final Mob mob; -+ protected final Mob mob; public final Mob getEntity() { return this.mob; } // Tuinity - public accessor - protected final Level level; - @Nullable - protected Path path; -@@ -40,7 +40,7 @@ public abstract class PathNavigation { - protected long lastTimeoutCheck; - protected double timeoutLimit; - protected float maxDistanceToWaypoint = 0.5F; -- protected boolean hasDelayedRecomputation; -+ protected boolean hasDelayedRecomputation; protected final boolean needsPathRecalculation() { return this.hasDelayedRecomputation; } // Tuinity - public accessor - protected long timeLastRecompute; - protected NodeEvaluator nodeEvaluator; - private BlockPos targetPos; -@@ -49,6 +49,13 @@ public abstract class PathNavigation { - public final PathFinder pathFinder; - private boolean isStuck; - -+ // Tuinity start -+ public boolean isViableForPathRecalculationChecking() { -+ return !this.needsPathRecalculation() && -+ (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0); -+ } -+ // Tuinity end -+ - public PathNavigation(Mob mob, Level world) { - this.mob = mob; - this.level = world; -@@ -404,7 +411,7 @@ public abstract class PathNavigation { - } - - public void recomputePath(BlockPos pos) { -- if (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0) { -+ if (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0) { // Tuinity - diff on change - needed for isViableForPathRecalculationChecking() - Node node = this.path.getEndNode(); - Vec3 vec3 = new Vec3(((double)node.x + this.mob.getX()) / 2.0D, ((double)node.y + this.mob.getY()) / 2.0D, ((double)node.z + this.mob.getZ()) / 2.0D); - if (pos.closerThan(vec3, (double)(this.path.getNodeCount() - this.path.getNextNodeIndex()))) { -diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java -index e41b2fa1db6fb77a26cdb498904021b430e35be0..f0ba454eea673bf02d1f6d7fe30c4f672643fe0c 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java -+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java -@@ -49,8 +49,12 @@ public class NearestBedSensor extends Sensor { - return true; - } - }; -- Stream stream = poiManager.findAll(PoiType.HOME.getPredicate(), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY); -- Path path = entity.getNavigation().createPath(stream, PoiType.HOME.getValidRange()); -+ // Tuinity start - optimise POI access -+ java.util.List poiposes = new java.util.ArrayList<>(); -+ // don't ask me why it's unbounded. ask mojang. -+ com.tuinity.tuinity.util.PoiAccess.findAnyPoiPositions(poiManager, PoiType.HOME.getPredicate(), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes); -+ Path path = entity.getNavigation().createPath(new java.util.HashSet<>(poiposes), PoiType.HOME.getValidRange()); -+ // Tuinity end - optimise POI access - if (path != null && path.canReach()) { - BlockPos blockPos = path.getTarget(); - Optional optional = poiManager.getType(blockPos); -diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java -index 49f3b25d28072b61f5cc97260df61df892a58714..de6b591eb865c6f5c23aaa4b9374bb9bbaaa85f6 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java -+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestItemSensor.java -@@ -25,17 +25,20 @@ public class NearestItemSensor extends Sensor { - protected void doTick(ServerLevel world, Mob entity) { - Brain brain = entity.getBrain(); - List list = world.getEntitiesOfClass(ItemEntity.class, entity.getBoundingBox().inflate(8.0D, 4.0D, 8.0D), (itemEntity) -> { -- return true; -+ return itemEntity.closerThan(entity, 9.0D) && entity.wantsToPickUp(itemEntity.getItem()); // Tuinity - move predicate into getEntities - }); -- list.sort(Comparator.comparingDouble(entity::distanceToSqr)); -+ list.sort((e1, e2) -> Double.compare(entity.distanceToSqr(e1), entity.distanceToSqr(e2))); // better to take the sort perf hit than using line of sight more than we need to. -+ // Tuinity start - remove streams - // Paper start - remove streams in favour of lists - ItemEntity nearest = null; -- for (ItemEntity entityItem : list) { -- if (entity.wantsToPickUp(entityItem.getItem()) && entityItem.closerThan(entity, 9.0D) && entity.hasLineOfSight(entityItem)) { -+ for (int i = 0; i < list.size(); i++) { -+ ItemEntity entityItem = list.get(i); -+ if (entity.hasLineOfSight(entityItem)) { - nearest = entityItem; - break; - } - } -+ // Tuinity end - remove streams - brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_WANTED_ITEM, Optional.ofNullable(nearest)); - // Paper end - } -diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java -index ffd83db0a419ab589e89feeddd3fb038d6ed5839..31ef567cf4f331d3329dd176392686db56aead66 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java -+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestLivingEntitySensor.java -@@ -18,12 +18,19 @@ public class NearestLivingEntitySensor extends Sensor { - List list = world.getEntitiesOfClass(LivingEntity.class, aABB, (livingEntity2) -> { - return livingEntity2 != entity && livingEntity2.isAlive(); - }); -- list.sort(Comparator.comparingDouble(entity::distanceToSqr)); -+ // Tuinity start - remove streams -+ list.sort((e1, e2) -> Double.compare(entity.distanceToSqr(e1), entity.distanceToSqr(e2))); - Brain brain = entity.getBrain(); - brain.setMemory(MemoryModuleType.NEAREST_LIVING_ENTITIES, list); - // Paper start - remove streams in favour of lists -- List visibleMobs = new java.util.ArrayList<>(list); -- visibleMobs.removeIf(otherEntityLiving -> !Sensor.isEntityTargetable(entity, otherEntityLiving)); -+ List visibleMobs = new java.util.ArrayList<>(); -+ for (int i = 0, len = list.size(); i < len; i++) { -+ LivingEntity nearby = list.get(i); -+ if (Sensor.isEntityTargetable(entity, nearby)) { -+ visibleMobs.add(nearby); -+ } -+ } -+ // Tuinity end - remove streams - brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_LIVING_ENTITIES, visibleMobs); - // Paper end - } -diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java -index 457ea75137b8b02dc32bf1769ae8d57c470da470..3392a8d425d9f5e1417a665fb1514d013bf89337 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java -+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/PlayerSensor.java -@@ -21,25 +21,31 @@ public class PlayerSensor extends Sensor { - - @Override - protected void doTick(ServerLevel world, LivingEntity entity) { -- // Paper start - remove streams in favour of lists -- List players = new java.util.ArrayList<>(world.players()); -- players.removeIf(player -> !EntitySelector.NO_SPECTATORS.test(player) || !entity.closerThan(player, 16.0D)); // Paper - removeIf only re-allocates once compared to iterator -+ // Tuinity start - remove streams -+ List players = (List)world.getNearbyPlayers(entity, entity.getX(), entity.getY(), entity.getZ(), 16.0D, EntitySelector.NO_SPECTATORS); -+ players.sort((e1, e2) -> Double.compare(entity.distanceToSqr(e1), entity.distanceToSqr(e2))); - Brain brain = entity.getBrain(); - - brain.setMemory(MemoryModuleType.NEAREST_PLAYERS, players); - -- Player nearest = null, nearestTargetable = null; -- for (Player player : players) { -- if (Sensor.isEntityTargetable(entity, player)) { -- if (nearest == null) nearest = player; -- if (Sensor.isEntityAttackable(entity, player)) { -- nearestTargetable = player; -- break; // Both variables are assigned, no reason to loop further -- } -+ Player firstTargetable = null; -+ Player firstAttackable = null; -+ for (int index = 0, len = players.size(); index < len; ++index) { -+ Player player = players.get(index); -+ if (firstTargetable == null && isEntityTargetable(entity, player)) { -+ firstTargetable = player; -+ } -+ if (firstAttackable == null && isEntityAttackable(entity, player)) { -+ firstAttackable = player; -+ } -+ -+ if (firstAttackable != null && firstTargetable != null) { -+ break; - } - } -- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, nearest); -- brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, nearestTargetable); -- // Paper end -+ -+ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_PLAYER, firstTargetable); -+ brain.setMemory(MemoryModuleType.NEAREST_VISIBLE_ATTACKABLE_PLAYER, Optional.ofNullable(firstAttackable)); -+ // Tuinity end - remove streams - } - } -diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java -index 478010bc291fa3276aab0f66ce6283403af710ec..a198538fe4ae560adc66fad5a2f6b80bbd894e4b 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java -+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/VillagerBabiesSensor.java -@@ -22,7 +22,17 @@ public class VillagerBabiesSensor extends Sensor { - } - - private List getNearestVillagerBabies(LivingEntity entities) { -- return this.getVisibleEntities(entities).stream().filter(this::isVillagerBaby).collect(Collectors.toList()); -+ // Tuinity start - remove streams -+ List list = new java.util.ArrayList<>(); -+ List visibleEntities = this.getVisibleEntities(entities); -+ for (int i = 0; i < visibleEntities.size(); i++) { -+ LivingEntity livingEntity = visibleEntities.get(i); -+ if (this.isVillagerBaby(livingEntity)) { -+ list.add(livingEntity); -+ } -+ } -+ return list; -+ // Tuinity end - remove streams - } - - private boolean isVillagerBaby(LivingEntity entity) { -diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -index 6c3455823f996e0421975b7f4a00f4e333e9f514..3ba30a2e6f1e3eb82b2b6e8968fd2babbf220ded 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -@@ -37,7 +37,7 @@ public class PoiManager extends SectionStorage { - public static final int VILLAGE_SECTION_SIZE = 1; - private final PoiManager.DistanceTracker distanceTracker; - private final LongSet loadedChunks = new LongOpenHashSet(); -- private final net.minecraft.server.level.ServerLevel world; // Paper -+ public final net.minecraft.server.level.ServerLevel world; // Paper // Tuinity public - - public PoiManager(File directory, DataFixer dataFixer, boolean dsync, LevelHeightAccessor world) { - super(directory, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, world); -@@ -100,36 +100,55 @@ public class PoiManager extends SectionStorage { - } - - public Optional find(Predicate typePredicate, Predicate posPredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) { -- return this.findAll(typePredicate, posPredicate, pos, radius, occupationStatus).findFirst(); -+ // Tuinity start - re-route to faster logic -+ BlockPos ret = com.tuinity.tuinity.util.PoiAccess.findAnyPoiPosition(this, typePredicate, posPredicate, pos, radius, occupationStatus, false); -+ return Optional.ofNullable(ret); -+ // Tuinity end - re-route to faster logic - } - - public Optional findClosest(Predicate typePredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) { -- return this.getInRange(typePredicate, pos, radius, occupationStatus).map(PoiRecord::getPos).min(Comparator.comparingDouble((blockPos2) -> { -- return blockPos2.distSqr(pos); -- })); -+ // Tuinity start - re-route to faster logic -+ BlockPos ret = com.tuinity.tuinity.util.PoiAccess.findClosestPoiDataPosition(this, typePredicate, null, pos, radius, radius*radius, occupationStatus, false); -+ return Optional.ofNullable(ret); -+ // Tuinity end - re-route to faster logic - } - - public Optional findClosest(Predicate predicate, Predicate predicate2, BlockPos blockPos, int i, PoiManager.Occupancy occupancy) { -- return this.getInRange(predicate, blockPos, i, occupancy).map(PoiRecord::getPos).filter(predicate2).min(Comparator.comparingDouble((blockPos2) -> { -- return blockPos2.distSqr(blockPos); -- })); -+ // Tuinity start - re-route to faster logic -+ BlockPos ret = com.tuinity.tuinity.util.PoiAccess.findClosestPoiDataPosition(this, predicate, predicate2, blockPos, i, i*i, occupancy, false); -+ return Optional.ofNullable(ret); -+ // Tuinity end - re-route to faster logic - } - - public Optional take(Predicate typePredicate, Predicate positionPredicate, BlockPos pos, int radius) { -- return this.getInRange(typePredicate, pos, radius, PoiManager.Occupancy.HAS_SPACE).filter((poi) -> { -- return positionPredicate.test(poi.getPos()); -- }).findFirst().map((poi) -> { -- poi.acquireTicket(); -- return poi.getPos(); -- }); -+ // Tuinity start - re-route to faster logic -+ PoiRecord ret = com.tuinity.tuinity.util.PoiAccess.findAnyPoiRecord( -+ this, typePredicate, positionPredicate, pos, radius, PoiManager.Occupancy.HAS_SPACE, false -+ ); -+ if (ret == null) { -+ return Optional.empty(); -+ } -+ ret.acquireTicket(); -+ return Optional.of(ret.getPos()); -+ // Tuinity end - re-route to faster logic - } - - public Optional getRandom(Predicate typePredicate, Predicate positionPredicate, PoiManager.Occupancy occupationStatus, BlockPos pos, int radius, Random random) { -- List list = this.getInRange(typePredicate, pos, radius, occupationStatus).collect(Collectors.toList()); -- Collections.shuffle(list, random); -- return list.stream().filter((poiRecord) -> { -- return positionPredicate.test(poiRecord.getPos()); -- }).findFirst().map(PoiRecord::getPos); -+ // Tuinity start - re-route to faster logic -+ List list = new java.util.ArrayList<>(); -+ com.tuinity.tuinity.util.PoiAccess.findAnyPoiRecords( -+ this, typePredicate, positionPredicate, pos, radius, occupationStatus, false, Integer.MAX_VALUE, list -+ ); -+ -+ // the old method shuffled the list and then tried to find the first element in it that -+ // matched positionPredicate, however we moved positionPredicate into the poi search. This means we can avoid a -+ // shuffle entirely, and just pick a random element from list -+ if (list.isEmpty()) { -+ return Optional.empty(); -+ } -+ -+ return Optional.of(list.get(random.nextInt(list.size())).getPos()); -+ // Tuinity end - re-route to faster logic - } - - public boolean release(BlockPos pos) { -@@ -183,7 +202,7 @@ public class PoiManager extends SectionStorage { - data = this.getData(chunkcoordintpair); - } - com.destroystokyo.paper.io.PaperFileIOThread.Holder.INSTANCE.scheduleSave(this.world, -- chunkcoordintpair.x, chunkcoordintpair.z, data, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.LOW_PRIORITY); -+ chunkcoordintpair.x, chunkcoordintpair.z, data, null, com.destroystokyo.paper.io.PrioritizedTaskQueue.NORMAL_PRIORITY); // Tuinity - use normal priority - } - // Paper end - this.distanceTracker.runAllUpdates(); -diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java -index 75c1c4671fedb425dea20dc4fb0c6cb2304dee83..fc7b364adc2d0e5db22aa25e029c2e13c84d6096 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java -+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java -@@ -25,12 +25,12 @@ import org.apache.logging.log4j.Logger; - public class PoiSection { - private static final Logger LOGGER = LogManager.getLogger(); - private final Short2ObjectMap records = new Short2ObjectOpenHashMap<>(); -- private final Map> byType = Maps.newHashMap(); -+ private final Map> byType = Maps.newHashMap(); public final Map> getData() { return this.byType; } // Tuinity - public accessor - private final Runnable setDirty; - private boolean isValid; - - public static Codec codec(Runnable updateListener) { -- return RecordCodecBuilder.create((instance) -> { -+ return RecordCodecBuilder.create((instance) -> { // Tuinity - decompile fix - return instance.group(RecordCodecBuilder.point(updateListener), Codec.BOOL.optionalFieldOf("Valid", Boolean.valueOf(false)).forGetter((poiSet) -> { - return poiSet.isValid; - }), PoiRecord.codec(updateListener).listOf().fieldOf("Records").forGetter((poiSet) -> { -diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -index 925f16d5eb092518ef774f69a8d99689feb0f5d7..01d8af06f19427354cac95d691e65d31253fef94 100644 ---- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java -+++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -@@ -91,7 +91,7 @@ public class Turtle extends Animal { - } - - public void setHomePos(BlockPos pos) { -- this.entityData.set(Turtle.HOME_POS, pos); -+ this.entityData.set(Turtle.HOME_POS, pos.immutable()); // Paper - called with mutablepos... - } - - public BlockPos getHomePos() { // Paper - public -diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 1bccd932851045c374e3092d33dc77fab680d0db..069f658003d96a05aac0b30af1d89f15ea554475 100644 ---- a/src/main/java/net/minecraft/world/entity/player/Player.java -+++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -498,6 +498,11 @@ public abstract class Player extends LivingEntity { - this.containerMenu = this.inventoryMenu; - } - // Paper end -+ // Tuinity start - special close for unloaded inventory -+ public void closeUnloadedInventory(org.bukkit.event.inventory.InventoryCloseEvent.Reason reason) { -+ this.containerMenu = this.inventoryMenu; -+ } -+ // Tuinity end - special close for unloaded inventory - - public void closeContainer() { - this.containerMenu = this.inventoryMenu; -diff --git a/src/main/java/net/minecraft/world/item/EnderEyeItem.java b/src/main/java/net/minecraft/world/item/EnderEyeItem.java -index 7ccfe737fdf7f07b731ea0ff82e897564350705c..abcc3dac7c7369a3f37e85ddeecbe272833298c9 100644 ---- a/src/main/java/net/minecraft/world/item/EnderEyeItem.java -+++ b/src/main/java/net/minecraft/world/item/EnderEyeItem.java -@@ -60,9 +60,10 @@ public class EnderEyeItem extends Item { - - // CraftBukkit start - Use relative location for far away sounds - // world.b(1038, blockposition1.c(1, 0, 1), 0); -- int viewDistance = world.getCraftServer().getViewDistance() * 16; -+ //int viewDistance = world.getCraftServer().getViewDistance() * 16; // Tuinity - apply view distance patch - BlockPos soundPos = blockposition1.offset(1, 0, 1); - for (ServerPlayer player : world.getServer().getPlayerList().players) { -+ final int viewDistance = player.getBukkitEntity().getViewDistance(); // Tuinity - apply view distance patch - double deltaX = soundPos.getX() - player.getX(); - double deltaZ = soundPos.getZ() - player.getZ(); - double distanceSquared = deltaX * deltaX + deltaZ * deltaZ; -diff --git a/src/main/java/net/minecraft/world/level/BlockGetter.java b/src/main/java/net/minecraft/world/level/BlockGetter.java -index fe4dba491b586757a16aa36e62682f364daa2602..ec781ab232d12cedb5f0236860377c4917c576d7 100644 ---- a/src/main/java/net/minecraft/world/level/BlockGetter.java -+++ b/src/main/java/net/minecraft/world/level/BlockGetter.java -@@ -84,7 +84,8 @@ public interface BlockGetter extends LevelHeightAccessor { - return BlockHitResult.miss(raytrace1.getTo(), Direction.getNearest(vec3d.x, vec3d.y, vec3d.z), new BlockPos(raytrace1.getTo())); - } - // Paper end -- FluidState fluid = this.getFluidState(blockposition); -+ if (iblockdata.isAir()) return null; // Tuinity - optimise air cases -+ FluidState fluid = iblockdata.getFluidState(); // Tuinity - don't need to go to world state again - Vec3 vec3d = raytrace1.getFrom(); - Vec3 vec3d1 = raytrace1.getTo(); - VoxelShape voxelshape = raytrace1.getBlockShape(iblockdata, this, blockposition); -diff --git a/src/main/java/net/minecraft/world/level/CollisionGetter.java b/src/main/java/net/minecraft/world/level/CollisionGetter.java -index 2a784a8342e708e0813c7076a2ca8e429446ffd3..b909bd7bf10adc9165df49a210df0d73912cd626 100644 ---- a/src/main/java/net/minecraft/world/level/CollisionGetter.java -+++ b/src/main/java/net/minecraft/world/level/CollisionGetter.java -@@ -36,28 +36,40 @@ public interface CollisionGetter extends BlockGetter { - return this.isUnobstructed(entity, Shapes.create(entity.getBoundingBox())); - } - -+ // Tuinity start - optimise collisions -+ default boolean noCollision(Entity entity, AABB box, Predicate filter, boolean loadChunks) { -+ return !com.tuinity.tuinity.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, loadChunks, false, entity != null, true, null) -+ && !com.tuinity.tuinity.util.CollisionUtil.getEntityHardCollisions(this, entity, box, null, true, filter); -+ } -+ // Tuinity end - optimise collisions -+ - default boolean noCollision(AABB box) { -- return this.noCollision((Entity)null, box, (e) -> { -- return true; -- }); -+ // Tuinity start - optimise collisions -+ return !com.tuinity.tuinity.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, null, box, null, false, false, false, true, null) -+ && !com.tuinity.tuinity.util.CollisionUtil.getEntityHardCollisions(this, null, box, null, true, null); -+ // Tuinity end - optimise collisions - } - - default boolean noCollision(Entity entity) { -- return this.noCollision(entity, entity.getBoundingBox(), (e) -> { -- return true; -- }); -+ // Tuinity start - optimise collisions -+ AABB box = entity.getBoundingBox(); -+ return !com.tuinity.tuinity.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, false, false, entity != null, true, null) -+ && !com.tuinity.tuinity.util.CollisionUtil.getEntityHardCollisions(this, entity, box, null, true, null); -+ // Tuinity end - optimise collisions - } - - default boolean noCollision(Entity entity, AABB box) { -- return this.noCollision(entity, box, (e) -> { -- return true; -- }); -+ // Tuinity start - optimise collisions -+ return !com.tuinity.tuinity.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, false, false, entity != null, true, null) -+ && !com.tuinity.tuinity.util.CollisionUtil.getEntityHardCollisions(this, entity, box, null, true, null); -+ // Tuinity end - optimise collisions - } - - default boolean noCollision(@Nullable Entity entity, AABB box, Predicate filter) { -- try { if (entity != null) entity.collisionLoadChunks = true; // Paper -- return this.getCollisions(entity, box, filter).allMatch(VoxelShape::isEmpty); -- } finally { if (entity != null) entity.collisionLoadChunks = false; } // Paper -+ // Tuinity start - optimise collisions -+ return !com.tuinity.tuinity.util.CollisionUtil.getCollisionsForBlocksOrWorldBorder(this, entity, box, null, false, false, entity != null, true, null) -+ && !com.tuinity.tuinity.util.CollisionUtil.getEntityHardCollisions(this, entity, box, null, true, filter); -+ // Tuinity end - optimise collisions - } - - Stream getEntityCollisions(@Nullable Entity entity, AABB box, Predicate predicate); -diff --git a/src/main/java/net/minecraft/world/level/CollisionSpliterator.java b/src/main/java/net/minecraft/world/level/CollisionSpliterator.java -index e420c98d9ccc45d570984dc30fdb928883edec9f..ac83704692cf60c34b579ed11689863ef191cad3 100644 ---- a/src/main/java/net/minecraft/world/level/CollisionSpliterator.java -+++ b/src/main/java/net/minecraft/world/level/CollisionSpliterator.java -@@ -99,7 +99,7 @@ public class CollisionSpliterator extends AbstractSpliterator { - - VoxelShape voxelShape = blockState.getCollisionShape(this.collisionGetter, this.pos, this.context); - if (voxelShape == Shapes.block()) { -- if (!this.box.intersects((double)i, (double)j, (double)k, (double)i + 1.0D, (double)j + 1.0D, (double)k + 1.0D)) { -+ if (!com.tuinity.tuinity.util.CollisionUtil.voxelShapeIntersect(this.box, (double)i, (double)j, (double)k, (double)i + 1.0D, (double)j + 1.0D, (double)k + 1.0D)) { // Tuinity - keep vanilla behavior for voxelshape intersection - See comment in CollisionUtil - continue; - } - -diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java -index 325e244c46ec208a2e7e18d71ccbbfcc25fc1bce..6a4e44dd8935018d1b5283761dfb8e855be62987 100644 ---- a/src/main/java/net/minecraft/world/level/EntityGetter.java -+++ b/src/main/java/net/minecraft/world/level/EntityGetter.java -@@ -18,6 +18,18 @@ import net.minecraft.world.phys.shapes.Shapes; - import net.minecraft.world.phys.shapes.VoxelShape; - - public interface EntityGetter { -+ -+ // Tuinity start -+ List getHardCollidingEntities(Entity except, AABB box, Predicate predicate); -+ -+ void getEntities(Entity except, AABB box, Predicate predicate, List into); -+ -+ void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into); -+ -+ void getEntitiesByClass(Class clazz, Entity except, final AABB box, List into, -+ Predicate predicate); -+ // Tuinity end -+ - List getEntities(@Nullable Entity except, AABB box, Predicate predicate); - - List getEntities(EntityTypeTest filter, AABB box, Predicate predicate); -@@ -37,7 +49,7 @@ public interface EntityGetter { - return true; - } else { - for(Entity entity2 : this.getEntities(entity, shape.bounds())) { -- if (!entity2.isRemoved() && entity2.blocksBuilding && (entity == null || !entity2.isPassengerOfSameVehicle(entity)) && Shapes.joinIsNotEmpty(shape, Shapes.create(entity2.getBoundingBox()), BooleanOp.AND)) { -+ if (!entity2.isRemoved() && entity2.blocksBuilding && (entity == null || !entity2.isPassengerOfSameVehicle(entity)) && shape.intersects(entity2.getBoundingBox())) { // Tuinity - return false; - } - } -@@ -54,9 +66,9 @@ public interface EntityGetter { - if (box.getSize() < 1.0E-7D) { - return Stream.empty(); - } else { -- AABB aABB = box.inflate(1.0E-7D); -- return this.getEntities(entity, aABB, predicate.and((entityx) -> { -- if (entityx.getBoundingBox().intersects(aABB)) { -+ AABB aABB = box.inflate(-1.0E-7D); // Tuinity - needs to be negated, or else we get things we don't collide with -+ Predicate hardCollides = (entityx) -> { // Tuinity - optimise entity hard collisions -+ if (true || entityx.getBoundingBox().intersects(aABB)) { // Tuinity - always true - if (entity == null) { - if (entityx.canBeCollidedWith()) { - return true; -@@ -67,7 +79,11 @@ public interface EntityGetter { - } - - return false; -- })).stream().map(Entity::getBoundingBox).map(Shapes::create); -+ }; // Tuinity start - optimise entity hard collisions -+ predicate = predicate == null ? hardCollides : hardCollides.and(predicate); -+ return (entity != null && entity.hardCollides() ? this.getEntities(entity, aABB, predicate) : this.getHardCollidingEntities(entity, aABB, predicate)) -+ .stream().map(Entity::getBoundingBox).map(Shapes::create); -+ // Tuinity end - optimise entity hard collisions - } - } - -diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 17281575ff83bbf1e720335619a78a6d0a0e5077..38753e10b1597a2f3bd2cde208c6e30b26a03b43 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -166,6 +166,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - public final com.destroystokyo.paper.PaperWorldConfig paperConfig; // Paper - public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray - -+ public final com.tuinity.tuinity.config.TuinityConfig.WorldConfig tuinityConfig; // Tuinity - Server Config -+ - public final co.aikar.timings.WorldTimingsHandler timings; // Paper - public static BlockPos lastPhysicsProblem; // Spigot - private org.spigotmc.TickLimiter entityLimiter; -@@ -202,9 +204,117 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - return this.typeKey; - } - -+ // Tuinity start -+ protected final com.tuinity.tuinity.world.EntitySliceManager entitySliceManager; -+ -+ // Tuinity start - optimise CraftChunk#getEntities -+ public org.bukkit.entity.Entity[] getChunkEntities(int chunkX, int chunkZ) { -+ com.tuinity.tuinity.world.ChunkEntitySlices slices = this.entitySliceManager.getChunk(chunkX, chunkZ); -+ if (slices == null) { -+ return new org.bukkit.entity.Entity[0]; -+ } -+ return slices.getChunkEntities(); -+ } -+ // Tuinity end - optimise CraftChunk#getEntities -+ -+ @Override -+ public List getHardCollidingEntities(Entity except, AABB box, Predicate predicate) { -+ List ret = new java.util.ArrayList<>(); -+ this.entitySliceManager.getEntities(except, box, ret, predicate); -+ return ret; -+ } -+ -+ @Override -+ public void getEntities(Entity except, AABB box, Predicate predicate, List into) { -+ this.entitySliceManager.getEntities(except, box, into, predicate); -+ } -+ -+ @Override -+ public void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into) { -+ this.entitySliceManager.getHardCollidingEntities(except, box, into, predicate); -+ } -+ -+ @Override -+ public void getEntitiesByClass(Class clazz, Entity except, final AABB box, List into, -+ Predicate predicate) { -+ this.entitySliceManager.getEntities((Class)clazz, except, box, (List)into, (Predicate)predicate); -+ } -+ -+ @Override -+ public List getEntitiesOfClass(Class entityClass, AABB box, Predicate predicate) { -+ List ret = new java.util.ArrayList<>(); -+ this.entitySliceManager.getEntities(entityClass, null, box, ret, predicate); -+ return ret; -+ } -+ // Tuinity end -+ // Tuinity start - optimise checkDespawn -+ public final List getNearbyPlayers(@Nullable Entity source, double sourceX, double sourceY, -+ double sourceZ, double maxRange, @Nullable Predicate predicate) { -+ LevelChunk chunk; -+ if (maxRange < 0.0 || maxRange >= net.minecraft.server.level.ChunkMap.GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE || -+ (chunk = (LevelChunk)this.getChunkIfLoadedImmediately(Mth.floor(sourceX) >> 4, Mth.floor(sourceZ) >> 4)) == null) { -+ return this.getNearbyPlayersSlow(source, sourceX, sourceY, sourceZ, maxRange, predicate); -+ } -+ -+ List ret = new java.util.ArrayList<>(); -+ chunk.getNearestPlayers(sourceX, sourceY, sourceZ, predicate, maxRange, ret); -+ return ret; -+ } -+ -+ private List getNearbyPlayersSlow(@Nullable Entity source, double sourceX, double sourceY, -+ double sourceZ, double maxRange, @Nullable Predicate predicate) { -+ List ret = new java.util.ArrayList<>(); -+ double maxRangeSquared = maxRange * maxRange; -+ -+ for (net.minecraft.server.level.ServerPlayer player : (List)this.players()) { -+ if ((maxRange < 0.0 || player.distanceToSqr(sourceX, sourceY, sourceZ) < maxRangeSquared)) { -+ if (predicate == null || predicate.test(player)) { -+ ret.add(player); -+ } -+ } -+ } -+ -+ return ret; -+ } -+ -+ private net.minecraft.server.level.ServerPlayer getNearestPlayerSlow(@Nullable Entity source, double sourceX, double sourceY, -+ double sourceZ, double maxRange, @Nullable Predicate predicate) { -+ net.minecraft.server.level.ServerPlayer closest = null; -+ double closestRangeSquared = maxRange < 0.0 ? Double.MAX_VALUE : maxRange * maxRange; -+ -+ for (net.minecraft.server.level.ServerPlayer player : (List)this.players()) { -+ double distanceSquared = player.distanceToSqr(sourceX, sourceY, sourceZ); -+ if (distanceSquared < closestRangeSquared && (predicate == null || predicate.test(player))) { -+ closest = player; -+ closestRangeSquared = distanceSquared; -+ } -+ } -+ -+ return closest; -+ } -+ -+ -+ public final net.minecraft.server.level.ServerPlayer getNearestPlayer(@Nullable Entity source, double sourceX, double sourceY, -+ double sourceZ, double maxRange, @Nullable Predicate predicate) { -+ LevelChunk chunk; -+ if (maxRange < 0.0 || maxRange >= net.minecraft.server.level.ChunkMap.GENERAL_AREA_MAP_ACCEPTABLE_SEARCH_RANGE || -+ (chunk = (LevelChunk)this.getChunkIfLoadedImmediately(Mth.floor(sourceX) >> 4, Mth.floor(sourceZ) >> 4)) == null) { -+ return this.getNearestPlayerSlow(source, sourceX, sourceY, sourceZ, maxRange, predicate); -+ } -+ -+ return chunk.findNearestPlayer(sourceX, sourceY, sourceZ, maxRange, predicate); -+ } -+ -+ @Override -+ public @Nullable Player getNearestPlayer(double d0, double d1, double d2, double d3, @Nullable Predicate predicate) { -+ return this.getNearestPlayer(null, d0, d1, d2, d3, predicate); -+ } -+ // Tuinity end - optimise checkDespawn -+ - protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, final DimensionType dimensionmanager, Supplier supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env, java.util.concurrent.Executor executor) { // Paper - Anti-Xray - Pass executor - this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot - this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), this.spigotConfig); // Paper -+ this.tuinityConfig = new com.tuinity.tuinity.config.TuinityConfig.WorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData)worlddatamutable).getLevelName()); // Tuinity - Server Config - this.generator = gen; - this.world = new CraftWorld((ServerLevel) this, gen, env); - this.ticksPerAnimalSpawns = this.getCraftServer().getTicksPerAnimalSpawns(); // CraftBukkit -@@ -278,6 +388,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - this.chunkPacketBlockController = this.paperConfig.antiXray ? - new com.destroystokyo.paper.antixray.ChunkPacketBlockControllerAntiXray(this, executor) - : com.destroystokyo.paper.antixray.ChunkPacketBlockController.NO_OPERATION_INSTANCE; // Paper - Anti-Xray -+ this.entitySliceManager = new com.tuinity.tuinity.world.EntitySliceManager((ServerLevel)this); // Tuinity - } - - // Paper start -@@ -363,6 +474,15 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - - @Override - public final LevelChunk getChunk(int chunkX, int chunkZ) { // Paper - final to help inline -+ // Tuinity start - make sure loaded chunks get the inlined variant of this function -+ net.minecraft.server.level.ServerChunkCache cps = ((ServerLevel)this).getChunkSource(); -+ if (cps.mainThread == Thread.currentThread()) { -+ LevelChunk ifLoaded = cps.getChunkAtIfLoadedMainThread(chunkX, chunkZ); -+ if (ifLoaded != null) { -+ return ifLoaded; -+ } -+ } -+ // Tuinity end - make sure loaded chunks get the inlined variant of this function - return (LevelChunk) this.getChunk(chunkX, chunkZ, ChunkStatus.FULL, true); // Paper - avoid a method jump - } - -@@ -551,7 +671,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - this.sendBlockUpdated(blockposition, iblockdata1, iblockdata, i); - // Paper start - per player view distance - allow block updates for non-ticking chunks in player view distance - // if copied from above -- } else if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || ((ServerLevel)this).getChunkSource().chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(MCUtil.getCoordinateKey(blockposition)) != null)) { -+ } else if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || ((ServerLevel)this).getChunkSource().chunkMap.playerChunkManager.broadcastMap.getObjectsInRange(MCUtil.getCoordinateKey(blockposition)) != null)) { // Tuinity - replace old player chunk management - ((ServerLevel)this).getChunkSource().blockChanged(blockposition); - // Paper end - per player view distance - } -@@ -862,6 +982,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - public void guardEntityTick(Consumer tickConsumer, T entity) { - try { - tickConsumer.accept(entity); -+ MinecraftServer.getServer().executeMidTickTasks(); // Tuinity - execute chunk tasks mid tick - } catch (Throwable throwable) { - if (throwable instanceof ThreadDeath) throw throwable; // Paper - // Paper start - Prevent tile entity and entity crashes -@@ -991,26 +1112,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - public List getEntities(@Nullable Entity except, AABB box, Predicate predicate) { - this.getProfiler().incrementCounter("getEntities"); - List list = Lists.newArrayList(); -- -- this.getEntities().get(box, (entity1) -> { -- if (entity1 != except && predicate.test(entity1)) { -- list.add(entity1); -- } -- -- if (entity1 instanceof EnderDragon) { -- EnderDragonPart[] aentitycomplexpart = ((EnderDragon) entity1).getSubEntities(); -- int i = aentitycomplexpart.length; -- -- for (int j = 0; j < i; ++j) { -- EnderDragonPart entitycomplexpart = aentitycomplexpart[j]; -- -- if (entity1 != except && predicate.test(entitycomplexpart)) { -- list.add(entitycomplexpart); -- } -- } -- } -- -- }, predicate == net.minecraft.world.entity.EntitySelector.CONTAINER_ENTITY_SELECTOR); // Paper -+ this.entitySliceManager.getEntities(except, box, list, predicate); // Tuinity - optimise this call - return list; - } - -@@ -1019,26 +1121,22 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - this.getProfiler().incrementCounter("getEntities"); - List list = Lists.newArrayList(); - -- this.getEntities().get(filter, box, (entity) -> { -- if (predicate.test(entity)) { -- list.add(entity); -- } -- -- if (entity instanceof EnderDragon) { -- EnderDragonPart[] aentitycomplexpart = ((EnderDragon) entity).getSubEntities(); -- int i = aentitycomplexpart.length; -- -- for (int j = 0; j < i; ++j) { -- EnderDragonPart entitycomplexpart = aentitycomplexpart[j]; -- T t0 = filter.tryCast(entitycomplexpart); -- -- if (t0 != null && predicate.test(t0)) { -- list.add(t0); -- } -- } -+ // Tuinity start - optimise this call -+ if (filter instanceof net.minecraft.world.entity.EntityType) { -+ this.entitySliceManager.getEntities((net.minecraft.world.entity.EntityType)filter, box, list, predicate); -+ } else { -+ Predicate test = (obj) -> { -+ return filter.tryCast(obj) != null; -+ }; -+ predicate = predicate == null ? test : test.and((Predicate)predicate); -+ Class base; -+ if (filter == null || (base = filter.getBaseClass()) == null || base == Entity.class) { -+ this.entitySliceManager.getEntities((Entity) null, box, (List)list, (Predicate)predicate); -+ } else { -+ this.entitySliceManager.getEntities(base, null, box, (List)list, (Predicate)predicate); // Tuinity - optimise this call - } -- -- }); -+ } -+ // Tuinity end - optimise this call - return list; - } - -@@ -1326,10 +1424,18 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - public abstract TagContainer getTagManager(); - - public BlockPos getBlockRandomPos(int x, int y, int z, int l) { -+ // Paper start - allow use of mutable pos -+ BlockPos.MutableBlockPos ret = new BlockPos.MutableBlockPos(); -+ this.getRandomBlockPosition(x, y, z, l, ret); -+ return ret.immutable(); -+ } -+ public final BlockPos.MutableBlockPos getRandomBlockPosition(int x, int y, int z, int l, BlockPos.MutableBlockPos out) { -+ // Paper end - this.randValue = this.randValue * 3 + 1013904223; - int i1 = this.randValue >> 2; - -- return new BlockPos(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15)); -+ out.set(x + (i1 & 15), y + (i1 >> 16 & l), z + (i1 >> 8 & 15)); // Paper - change to setValues call -+ return out; // Paper - } - - public boolean noSave() { -diff --git a/src/main/java/net/minecraft/world/level/NaturalSpawner.java b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -index 31fbcf6a35b902ce80c0a5a23dabb8ec3d8cbdfc..0059f0488acc22ebddc2faf4c5879f9f0c24fd14 100644 ---- a/src/main/java/net/minecraft/world/level/NaturalSpawner.java -+++ b/src/main/java/net/minecraft/world/level/NaturalSpawner.java -@@ -262,7 +262,7 @@ public final class NaturalSpawner { - blockposition_mutableblockposition.set(l, i, i1); - double d0 = (double) l + 0.5D; - double d1 = (double) i1 + 0.5D; -- Player entityhuman = world.getNearestPlayer(d0, (double) i, d1, -1.0D, false); -+ Player entityhuman = (chunk instanceof LevelChunk) ? ((LevelChunk)chunk).findNearestPlayer(d0, i, d1, 576.0D, net.minecraft.world.entity.EntitySelector.NO_SPECTATORS) : world.getNearestPlayer(d0, (double) i, d1, -1.0D, false); // Tuinity - use chunk's player cache to optimize search in range - - if (entityhuman != null) { - double d2 = entityhuman.distanceToSqr(d0, (double) i, d1); -@@ -335,7 +335,7 @@ public final class NaturalSpawner { - } - - private static boolean isRightDistanceToPlayerAndSpawnPoint(ServerLevel world, ChunkAccess chunk, BlockPos.MutableBlockPos pos, double squaredDistance) { -- return squaredDistance <= 576.0D ? false : (world.getSharedSpawnPos().closerThan((Position) (new Vec3((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D)), 24.0D) ? false : Objects.equals(new ChunkPos(pos), chunk.getPos()) || world.isPositionEntityTicking((BlockPos) pos)); -+ return squaredDistance <= 576.0D ? false : (world.getSharedSpawnPos().closerThan((Position) (new Vec3((double) pos.getX() + 0.5D, (double) pos.getY(), (double) pos.getZ() + 0.5D)), 24.0D) ? false : Objects.equals(new ChunkPos(pos), chunk.getPos()) || world.isPositionEntityTicking((BlockPos) pos)); // Tuinity - diff on change, copy into caller - } - - private static Boolean isValidSpawnPostitionForType(ServerLevel world, MobCategory group, StructureFeatureManager structureAccessor, ChunkGenerator chunkGenerator, MobSpawnSettings.SpawnerData spawnEntry, BlockPos.MutableBlockPos pos, double squaredDistance) { // Paper -diff --git a/src/main/java/net/minecraft/world/level/block/FarmBlock.java b/src/main/java/net/minecraft/world/level/block/FarmBlock.java -index aa1ba8b74ab70b6cede99e4853ac0203f388ab06..a242a80b16c7d074d52a52728646224b1a0091d4 100644 ---- a/src/main/java/net/minecraft/world/level/block/FarmBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/FarmBlock.java -@@ -139,19 +139,28 @@ public class FarmBlock extends Block { - } - - private static boolean isNearWater(LevelReader world, BlockPos pos) { -- Iterator iterator = BlockPos.betweenClosed(pos.offset(-4, 0, -4), pos.offset(4, 1, 4)).iterator(); -- -- BlockPos blockposition1; -- -- do { -- if (!iterator.hasNext()) { -- return false; -+ // Tuinity start - remove abstract block iteration -+ int xOff = pos.getX(); -+ int yOff = pos.getY(); -+ int zOff = pos.getZ(); -+ -+ for (int dz = -4; dz <= 4; ++dz) { -+ int z = dz + zOff; -+ for (int dx = -4; dx <= 4; ++dx) { -+ int x = xOff + dx; -+ for (int dy = 0; dy <= 1; ++dy) { -+ int y = dy + yOff; -+ net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk)world.getChunk(x >> 4, z >> 4); -+ net.minecraft.world.level.material.FluidState fluid = chunk.getBlockData(x, y, z).getFluidState(); -+ if (fluid.is(FluidTags.WATER)) { -+ return true; -+ } -+ } - } -+ } - -- blockposition1 = (BlockPos) iterator.next(); -- } while (!world.getFluidState(blockposition1).is((Tag) FluidTags.WATER)); -- -- return true; -+ return false; -+ // Tuinity end - remove abstract block iteration - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -index 1179c62695da4dcf02590c97d8da3c6fcdbee9ef..04d5ef90cd4171f9360017ac0c01ce48ae6ec983 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -+++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -610,14 +610,14 @@ public abstract class BlockBehaviour { - - public abstract static class BlockStateBase extends StateHolder { - -- private final int lightEmission; -- private final boolean useShapeForLightOcclusion; -+ private final int lightEmission; public final int getEmittedLight() { return this.lightEmission; } // Tuinity - OBFHELPER -+ private final boolean useShapeForLightOcclusion; public final boolean isTransparentOnSomeFaces() { return this.useShapeForLightOcclusion; } // Tuinity - OBFHELPER - private final boolean isAir; - private final Material material; - private final MaterialColor materialColor; - public final float destroySpeed; - private final boolean requiresCorrectToolForDrops; -- private final boolean canOcclude; -+ private final boolean canOcclude; public final boolean isOpaque() { return this.canOcclude; } // Tuinity - OBFHELPER - private final BlockBehaviour.StatePredicate isRedstoneConductor; - private final BlockBehaviour.StatePredicate isSuffocating; - private final BlockBehaviour.StatePredicate isViewBlocking; -@@ -643,6 +643,7 @@ public abstract class BlockBehaviour { - this.isViewBlocking = blockbase_info.isViewBlocking; - this.hasPostProcess = blockbase_info.hasPostProcess; - this.emissiveRendering = blockbase_info.emissiveRendering; -+ this.conditionallyFullOpaque = this.isOpaque() & this.isTransparentOnSomeFaces(); // Tuinity - } - // Paper start - impl cached craft block data, lazy load to fix issue with loading at the wrong time - private org.bukkit.craftbukkit.block.data.CraftBlockData cachedCraftBlockData; -@@ -658,13 +659,34 @@ public abstract class BlockBehaviour { - protected FluidState fluid; - // Paper end - -+ // Tuinity start -+ protected boolean shapeExceedsCube = true; -+ public final boolean shapeExceedsCube() { -+ return this.shapeExceedsCube; -+ } -+ // Tuinity end -+ // Tuinity start -+ protected int opacityIfCached = -1; -+ // ret -1 if opacity is dynamic, or -1 if the block is conditionally full opaque, else return opacity in [0, 15] -+ public final int getOpacityIfCached() { -+ return this.opacityIfCached; -+ } -+ -+ protected final boolean conditionallyFullOpaque; -+ public final boolean isConditionallyFullOpaque() { -+ return this.conditionallyFullOpaque; -+ } -+ // Tuinity end -+ - public void initCache() { - this.fluid = this.getBlock().getFluidState(this.asState()); // Paper - moved from getFluid() - this.isTicking = this.getBlock().isRandomlyTicking(this.asState()); // Paper - moved from isTicking() - if (!this.getBlock().hasDynamicShape()) { - this.cache = new BlockBehaviour.BlockStateBase.Cache(this.asState()); - } -- -+ this.shapeExceedsCube = this.cache == null || this.cache.largeCollisionShape; // Tuinity - moved from actual method to here -+ this.opacityIfCached = this.cache == null || this.isConditionallyFullOpaque() ? -1 : this.cache.lightBlock; // Tuinity - cache opacity for light -+ // TODO optimise light - } - - public Block getBlock() { -@@ -700,7 +722,7 @@ public abstract class BlockBehaviour { - } - - public final boolean hasLargeCollisionShape() { // Paper -- return this.cache == null || this.cache.largeCollisionShape; -+ return this.shapeExceedsCube; // Tuinity - moved into shape cache init - } - - public final boolean useShapeForLightOcclusion() { // Paper -diff --git a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java -index baf1cb77eb170a44d821eae572d059f18ea46d7e..5d25223cb2f31e78b1608bd2846effba5b4301a4 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/StateHolder.java -+++ b/src/main/java/net/minecraft/world/level/block/state/StateHolder.java -@@ -40,11 +40,13 @@ public abstract class StateHolder { - private final ImmutableMap, Comparable> values; - private Table, Comparable, S> neighbours; - protected final MapCodec propertiesCodec; -+ protected final com.tuinity.tuinity.util.table.ZeroCollidingReferenceStateTable optimisedTable; // Tuinity - optimise state lookup - - protected StateHolder(O owner, ImmutableMap, Comparable> entries, MapCodec codec) { - this.owner = owner; - this.values = entries; - this.propertiesCodec = codec; -+ this.optimisedTable = new com.tuinity.tuinity.util.table.ZeroCollidingReferenceStateTable(this, entries); // Tuinity - optimise state lookup - } - - public > S cycle(Property property) { -@@ -85,11 +87,11 @@ public abstract class StateHolder { - } - - public > boolean hasProperty(Property property) { -- return this.values.containsKey(property); -+ return this.optimisedTable.get(property) != null; // Tuinity - optimise state lookup - } - - public > T getValue(Property property) { -- Comparable comparable = this.values.get(property); -+ Comparable comparable = this.optimisedTable.get(property); // Tuinity - optimise state lookup - if (comparable == null) { - throw new IllegalArgumentException("Cannot get property " + property + " as it does not exist in " + this.owner); - } else { -@@ -98,24 +100,18 @@ public abstract class StateHolder { - } - - public > Optional getOptionalValue(Property property) { -- Comparable comparable = this.values.get(property); -+ Comparable comparable = this.optimisedTable.get(property); // Tuinity - optimise state lookup - return comparable == null ? Optional.empty() : Optional.of(property.getValueClass().cast(comparable)); - } - - public , V extends T> S setValue(Property property, V value) { -- Comparable comparable = this.values.get(property); -- if (comparable == null) { -- throw new IllegalArgumentException("Cannot set property " + property + " as it does not exist in " + this.owner); -- } else if (comparable == value) { -- return (S)this; -- } else { -- S object = this.neighbours.get(property, value); -- if (object == null) { -- throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on " + this.owner + ", it is not an allowed value"); -- } else { -- return object; -- } -+ // Tuinity start - optimise state lookup -+ final S ret = (S)this.optimisedTable.get(property, value); -+ if (ret == null) { -+ throw new IllegalArgumentException("Cannot set property " + property + " to " + value + " on " + this.owner + ", it is not an allowed value"); - } -+ return ret; -+ // Tuinity end - optimise state lookup - } - - public void populateNeighbours(Map, Comparable>, S> states) { -@@ -134,7 +130,7 @@ public abstract class StateHolder { - } - } - -- this.neighbours = (Table, Comparable, S>)(table.isEmpty() ? table : ArrayTable.create(table)); -+ this.neighbours = (Table, Comparable, S>)(table.isEmpty() ? table : ArrayTable.create(table)); this.optimisedTable.loadInTable((Table)this.neighbours, this.values); // Tuinity - optimise state lookup - } - } - -diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java -index ff1a0d125edd2ea10c870cbb62ae9aa23644b6dc..90c5d20d92dd0dba3503c0f8bc16ed533ca59869 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java -+++ b/src/main/java/net/minecraft/world/level/block/state/properties/BooleanProperty.java -@@ -7,6 +7,13 @@ import java.util.Optional; - public class BooleanProperty extends Property { - private final ImmutableSet values = ImmutableSet.of(true, false); - -+ // Tuinity start - optimise iblockdata state lookup -+ @Override -+ public final int getIdFor(final Boolean value) { -+ return value.booleanValue() ? 1 : 0; -+ } -+ // Tuinity end - optimise iblockdata state lookup -+ - protected BooleanProperty(String name) { - super(name, Boolean.class); - } -diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java -index bcf8b24e9f9e9870c1a1d27c721a6a433305d55a..32aa07141682ebdd99c2fce9b64c9f283a5d5707 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java -+++ b/src/main/java/net/minecraft/world/level/block/state/properties/EnumProperty.java -@@ -17,6 +17,15 @@ public class EnumProperty & StringRepresentable> extends Prope - private final ImmutableSet values; - private final Map names = Maps.newHashMap(); - -+ // Tuinity start - optimise iblockdata state lookup -+ private int[] idLookupTable; -+ -+ @Override -+ public final int getIdFor(final T value) { -+ return this.idLookupTable[value.ordinal()]; -+ } -+ // Tuinity end - optimise iblockdata state lookup -+ - protected EnumProperty(String name, Class type, Collection values) { - super(name, type); - this.values = ImmutableSet.copyOf(values); -@@ -31,6 +40,14 @@ public class EnumProperty & StringRepresentable> extends Prope - this.names.put(string, enum_); - } - -+ // Tuinity start - optimise iblockdata state lookup -+ int id = 0; -+ this.idLookupTable = new int[type.getEnumConstants().length]; -+ java.util.Arrays.fill(this.idLookupTable, -1); -+ for (final T value : this.getPossibleValues()) { -+ this.idLookupTable[value.ordinal()] = id++; -+ } -+ // Tuinity end - optimise iblockdata state lookup - } - - @Override -diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java -index 72f508321ebffcca31240fbdd068b4d185454cbc..346ae8ff58afd1c1f439c150c3d21143b41c3295 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java -+++ b/src/main/java/net/minecraft/world/level/block/state/properties/IntegerProperty.java -@@ -13,6 +13,16 @@ public class IntegerProperty extends Property { - public final int min; - public final int max; - -+ // Tuinity start - optimise iblockdata state lookup -+ @Override -+ public final int getIdFor(final Integer value) { -+ final int val = value.intValue(); -+ final int ret = val - this.min; -+ -+ return ret | ((this.max - ret) >> 31); -+ } -+ // Tuinity end - optimise iblockdata state lookup -+ - protected IntegerProperty(String name, int min, int max) { - super(name, Integer.class); - this.min = min; -diff --git a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java -index 81b43e0b0146729a8a1c6ade82634c86cde67857..9d5e76877bc06b3318c817c40821a453ac4c4a97 100644 ---- a/src/main/java/net/minecraft/world/level/block/state/properties/Property.java -+++ b/src/main/java/net/minecraft/world/level/block/state/properties/Property.java -@@ -20,6 +20,17 @@ public abstract class Property> { - }, this::getName); - private final Codec> valueCodec = this.codec.xmap(this::value, Property.Value::value); - -+ // Tuinity start - optimise iblockdata state lookup -+ private static final java.util.concurrent.atomic.AtomicInteger ID_GENERATOR = new java.util.concurrent.atomic.AtomicInteger(); -+ private final int id = ID_GENERATOR.getAndIncrement(); -+ -+ public final int getId() { -+ return this.id; -+ } -+ -+ public abstract int getIdFor(final T value); -+ // Tuinity end - optimise state lookup -+ - protected Property(String name, Class type) { - this.clazz = type; - this.name = name; -diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -index 63203172a127d812fd59cea0546b67e855ce3ad5..498988b70617f086f047d8d293e525377971e66e 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkAccess.java -@@ -1,5 +1,6 @@ - package net.minecraft.world.level.chunk; - -+import ca.spottedleaf.starlight.light.SWMRNibbleArray; - import it.unimi.dsi.fastutil.shorts.ShortArrayList; - import it.unimi.dsi.fastutil.shorts.ShortList; - import java.util.Collection; -@@ -42,6 +43,36 @@ public interface ChunkAccess extends BlockGetter, FeatureAccess { - } - // Paper end - -+ // Tuinity start -+ default SWMRNibbleArray[] getBlockNibbles() { -+ throw new UnsupportedOperationException(this.getClass().getName()); -+ } -+ default void setBlockNibbles(SWMRNibbleArray[] nibbles) { -+ throw new UnsupportedOperationException(this.getClass().getName()); -+ } -+ -+ default SWMRNibbleArray[] getSkyNibbles() { -+ throw new UnsupportedOperationException(this.getClass().getName()); -+ } -+ default void setSkyNibbles(SWMRNibbleArray[] nibbles) { -+ throw new UnsupportedOperationException(this.getClass().getName()); -+ } -+ public default boolean[] getSkyEmptinessMap() { -+ throw new UnsupportedOperationException(this.getClass().getName()); -+ } -+ public default void setSkyEmptinessMap(final boolean[] emptinessMap) { -+ throw new UnsupportedOperationException(this.getClass().getName()); -+ } -+ -+ public default boolean[] getBlockEmptinessMap() { -+ throw new UnsupportedOperationException(this.getClass().getName()); -+ } -+ -+ public default void setBlockEmptinessMap(final boolean[] emptinessMap) { -+ throw new UnsupportedOperationException(this.getClass().getName()); -+ } -+ // Tuinity end -+ - BlockState getType(final int x, final int y, final int z); // Paper - @Nullable - BlockState setBlockState(BlockPos pos, BlockState state, boolean moved); -diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -index c2b0b1adcff5baf169901710d492317d44b93846..c7636191fa2ba92db95a7f779d0e5a1bd45198aa 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -@@ -196,7 +196,7 @@ public abstract class ChunkGenerator { - // Get origin location (re)defined by event call. - center = new BlockPos(event.getOrigin().getBlockX(), event.getOrigin().getBlockY(), event.getOrigin().getBlockZ()); - // Get world (re)defined by event call. -- world = ((org.bukkit.craftbukkit.CraftWorld) event.getOrigin().getWorld()).getHandle(); -+ //world = ((org.bukkit.craftbukkit.CraftWorld) event.getOrigin().getWorld()).getHandle(); // Tuinity - callers and this function don't expect this to change - // Get radius and whether to find unexplored structures (re)defined by event call. - radius = event.getRadius(); - skipExistingChunks = event.shouldFindUnexplored(); -diff --git a/src/main/java/net/minecraft/world/level/chunk/DataLayer.java b/src/main/java/net/minecraft/world/level/chunk/DataLayer.java -index 25570730f376665ca6477263d3b3f94d725ecd21..21d85b2f70e5ffe46220905b27715579d7fcdc59 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/DataLayer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/DataLayer.java -@@ -11,7 +11,7 @@ public class DataLayer { - public static final int LAYER_SIZE = 128; - private static final int NIBBLE_SIZE = 4; - @Nullable -- protected byte[] data; -+ protected byte[] data; public final byte[] getDataRaw() { return this.data; } // Tuinity - provide accessor - // Paper start - public static byte[] EMPTY_NIBBLE = new byte[2048]; - private static final int nibbleBucketSizeMultiplier = Integer.getInteger("Paper.nibbleBucketSize", 3072); -@@ -54,6 +54,7 @@ public class DataLayer { - boolean poolSafe = false; - public java.lang.Runnable cleaner; - private void registerCleaner() { -+ if (true) return; // Tuinity - purge cleaner usage - if (!poolSafe) { - cleaner = net.minecraft.server.MCUtil.registerCleaner(this, this.data, DataLayer::releaseBytes); - } else { -@@ -68,7 +69,7 @@ public class DataLayer { - } - public DataLayer(byte[] bytes, boolean isSafe) { - this.data = bytes; -- if (!isSafe) this.data = getCloneIfSet(); // Paper - clone for safety -+ // Tuinity - purge cleaner usage - registerCleaner(); - // Paper end - if (bytes.length != 2048) { -@@ -153,7 +154,7 @@ public class DataLayer { - } - // Paper end - public DataLayer copy() { -- return this.data == null ? new DataLayer() : new DataLayer(this.data); // Paper - clone in ctor -+ return this.data == null ? new DataLayer() : new DataLayer(this.data.clone()); // Paper - clone in ctor // Tuinity - no longer clone in constructor - } - - public String toString() { -diff --git a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java -index 8245c5834ec69beb8e3b95fb3900601009a9273f..88f30cd8e57ccb69da633daac49f8bc9e44111da 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ImposterProtoChunk.java -@@ -1,5 +1,6 @@ - package net.minecraft.world.level.chunk; - -+import ca.spottedleaf.starlight.light.SWMRNibbleArray; - import it.unimi.dsi.fastutil.longs.LongSet; - import java.util.BitSet; - import java.util.Map; -@@ -29,6 +30,48 @@ public class ImposterProtoChunk extends ProtoChunk { - this.wrapped = wrapped; - } - -+ // Tuinity start - rewrite light engine -+ @Override -+ public SWMRNibbleArray[] getBlockNibbles() { -+ return this.getWrapped().getBlockNibbles(); -+ } -+ -+ @Override -+ public void setBlockNibbles(SWMRNibbleArray[] nibbles) { -+ this.getWrapped().setBlockNibbles(nibbles); -+ } -+ -+ @Override -+ public SWMRNibbleArray[] getSkyNibbles() { -+ return this.getWrapped().getSkyNibbles(); -+ } -+ -+ @Override -+ public void setSkyNibbles(SWMRNibbleArray[] nibbles) { -+ this.getWrapped().setSkyNibbles(nibbles); -+ } -+ -+ @Override -+ public boolean[] getSkyEmptinessMap() { -+ return this.getWrapped().getSkyEmptinessMap(); -+ } -+ -+ @Override -+ public void setSkyEmptinessMap(boolean[] emptinessMap) { -+ this.getWrapped().setSkyEmptinessMap(emptinessMap); -+ } -+ -+ @Override -+ public boolean[] getBlockEmptinessMap() { -+ return this.getWrapped().getBlockEmptinessMap(); -+ } -+ -+ @Override -+ public void setBlockEmptinessMap(boolean[] emptinessMap) { -+ this.getWrapped().setBlockEmptinessMap(emptinessMap); -+ } -+ // Tuinity end - rewrite light engine -+ - @Nullable - @Override - public BlockEntity getBlockEntity(BlockPos pos) { -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -index cc02b577453fa251f0f1b508281ddea2513138a1..54e23d303aad286ab46c3e5f9b17a5f9922e2942 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -1,5 +1,7 @@ - package net.minecraft.world.level.chunk; - -+import ca.spottedleaf.starlight.light.SWMRNibbleArray; -+import ca.spottedleaf.starlight.light.StarLightEngine; - import com.google.common.collect.ImmutableList; - import com.destroystokyo.paper.exception.ServerInternalException; - import com.google.common.collect.Maps; -@@ -17,7 +19,6 @@ import java.util.Collections; - import java.util.Iterator; - import java.util.Map; - import java.util.Map.Entry; --import java.util.Objects; - import java.util.Set; - import java.util.function.Consumer; - import java.util.function.Supplier; -@@ -28,7 +29,6 @@ import net.minecraft.CrashReport; - import net.minecraft.CrashReportCategory; - import net.minecraft.ReportedException; - import net.minecraft.core.BlockPos; --import net.minecraft.core.DefaultedRegistry; - import net.minecraft.core.Registry; - import net.minecraft.core.SectionPos; - import net.minecraft.nbt.CompoundTag; -@@ -125,11 +125,62 @@ public class LevelChunk implements ChunkAccess { - private volatile boolean isLightCorrect; - private final Int2ObjectMap gameEventDispatcherSections; - -+ // Tuinity start - rewrite light engine -+ protected volatile SWMRNibbleArray[] blockNibbles; -+ protected volatile SWMRNibbleArray[] skyNibbles; -+ protected volatile boolean[] skyEmptinessMap; -+ protected volatile boolean[] blockEmptinessMap; -+ -+ @Override -+ public SWMRNibbleArray[] getBlockNibbles() { -+ return this.blockNibbles; -+ } -+ -+ @Override -+ public void setBlockNibbles(SWMRNibbleArray[] nibbles) { -+ this.blockNibbles = nibbles; -+ } -+ -+ @Override -+ public SWMRNibbleArray[] getSkyNibbles() { -+ return this.skyNibbles; -+ } -+ -+ @Override -+ public void setSkyNibbles(SWMRNibbleArray[] nibbles) { -+ this.skyNibbles = nibbles; -+ } -+ -+ @Override -+ public boolean[] getSkyEmptinessMap() { -+ return this.skyEmptinessMap; -+ } -+ -+ @Override -+ public void setSkyEmptinessMap(boolean[] emptinessMap) { -+ this.skyEmptinessMap = emptinessMap; -+ } -+ -+ @Override -+ public boolean[] getBlockEmptinessMap() { -+ return this.blockEmptinessMap; -+ } -+ -+ @Override -+ public void setBlockEmptinessMap(boolean[] emptinessMap) { -+ this.blockEmptinessMap = emptinessMap; -+ } -+ // Tuinity end - rewrite light engine -+ - public LevelChunk(Level world, ChunkPos pos, ChunkBiomeContainer biomes) { - this(world, pos, biomes, UpgradeData.EMPTY, EmptyTickList.empty(), EmptyTickList.empty(), 0L, (LevelChunkSection[]) null, (Consumer) null); - } - - public LevelChunk(Level world, ChunkPos pos, ChunkBiomeContainer biomes, UpgradeData upgradeData, TickList blockTickScheduler, TickList fluidTickScheduler, long inhabitedTime, @Nullable LevelChunkSection[] sections, @Nullable Consumer loadToWorldConsumer) { -+ // Tuinity start -+ this.blockNibbles = StarLightEngine.getFilledEmptyLight(world); -+ this.skyNibbles = StarLightEngine.getFilledEmptyLight(world); -+ // Tuinity end - this.pendingBlockEntities = Maps.newHashMap(); - this.tickersInLevel = Maps.newHashMap(); - this.heightmaps = Maps.newEnumMap(Heightmap.Types.class); -@@ -192,7 +243,7 @@ public class LevelChunk implements ChunkAccess { - return NEIGHBOUR_CACHE_RADIUS; - } - -- boolean loadedTicketLevel; -+ boolean loadedTicketLevel; public final boolean wasLoadCallbackInvoked() { return this.loadedTicketLevel; } // Tuinity - public accessor - private long neighbourChunksLoadedBitset; - private final LevelChunk[] loadedNeighbourChunks = new LevelChunk[(NEIGHBOUR_CACHE_RADIUS * 2 + 1) * (NEIGHBOUR_CACHE_RADIUS * 2 + 1)]; - -@@ -242,11 +293,12 @@ public class LevelChunk implements ChunkAccess { - ChunkMap chunkMap = chunkProviderServer.chunkMap; - // this code handles the addition of ticking tickets - the distance map handles the removal - if (!areNeighboursLoaded(bitsetBefore, 2) && areNeighboursLoaded(bitsetAfter, 2)) { -- if (chunkMap.playerViewDistanceTickMap.getObjectsInRange(this.coordinateKey) != null) { -+ if (chunkMap.playerChunkManager.tickMap.getObjectsInRange(this.coordinateKey) != null) { // Tuinity - replace old player chunk loading system - // now we're ready for entity ticking - chunkProviderServer.mainThreadProcessor.execute(() -> { - // double check that this condition still holds. -- if (LevelChunk.this.areNeighboursLoaded(2) && chunkMap.playerViewDistanceTickMap.getObjectsInRange(LevelChunk.this.coordinateKey) != null) { -+ if (LevelChunk.this.areNeighboursLoaded(2) && chunkMap.playerChunkManager.tickMap.getObjectsInRange(LevelChunk.this.coordinateKey) != null) { // Tuinity - replace old player chunk loading system -+ chunkMap.playerChunkManager.onChunkPlayerTickReady(this.chunkPos.x, this.chunkPos.z); // Tuinity - replace old player chunk - chunkProviderServer.addTicketAtLevel(net.minecraft.server.level.TicketType.PLAYER, LevelChunk.this.chunkPos, 31, LevelChunk.this.chunkPos); // 31 -> entity ticking, TODO check on update - } - }); -@@ -255,31 +307,18 @@ public class LevelChunk implements ChunkAccess { - - // this code handles the chunk sending - if (!areNeighboursLoaded(bitsetBefore, 1) && areNeighboursLoaded(bitsetAfter, 1)) { -- if (chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(this.coordinateKey) != null) { -- // now we're ready to send -- chunkMap.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(chunkMap.getUpdatingChunkIfPresent(this.coordinateKey), (() -> { // Copied frm PlayerChunkMap -- // double check that this condition still holds. -- if (!LevelChunk.this.areNeighboursLoaded(1)) { -- return; -- } -- com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet inRange = chunkMap.playerViewDistanceBroadcastMap.getObjectsInRange(LevelChunk.this.coordinateKey); -- if (inRange == null) { -- return; -- } -- -- // broadcast -- Object[] backingSet = inRange.getBackingSet(); -- Packet[] chunkPackets = new Packet[2]; -- for (int index = 0, len = backingSet.length; index < len; ++index) { -- Object temp = backingSet[index]; -- if (!(temp instanceof net.minecraft.server.level.ServerPlayer)) { -- continue; -- } -- net.minecraft.server.level.ServerPlayer player = (net.minecraft.server.level.ServerPlayer)temp; -- chunkMap.playerLoadedChunk(player, chunkPackets, LevelChunk.this); -- } -- }))); -- } -+ // Tuinity start - replace old player chunk loading system -+ chunkProviderServer.mainThreadProcessor.execute(() -> { -+ if (!LevelChunk.this.areNeighboursLoaded(1)) { -+ return; -+ } -+ LevelChunk.this.postProcessGeneration(); -+ if (!LevelChunk.this.areNeighboursLoaded(1)) { -+ return; -+ } -+ chunkMap.playerChunkManager.onChunkSendReady(this.chunkPos.x, this.chunkPos.z); -+ }); -+ // Tuinity end - replace old player chunk loading system - } - // Paper end - no-tick view distance - } -@@ -330,9 +369,102 @@ public class LevelChunk implements ChunkAccess { - } - } - // Paper end -+ // Tuinity start - optimise checkDespawn -+ private boolean playerGeneralAreaCacheSet; -+ private com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playerGeneralAreaCache; -+ -+ public com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet getPlayerGeneralAreaCache() { -+ if (!this.playerGeneralAreaCacheSet) { -+ this.updateGeneralAreaCache(); -+ } -+ return this.playerGeneralAreaCache; -+ } -+ -+ public void updateGeneralAreaCache() { -+ this.updateGeneralAreaCache(((ServerLevel)this.level).getChunkSource().chunkMap.playerGeneralAreaMap.getObjectsInRange(this.coordinateKey)); -+ } -+ -+ public void updateGeneralAreaCache(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet value) { -+ this.playerGeneralAreaCacheSet = true; -+ this.playerGeneralAreaCache = value; -+ } -+ -+ public net.minecraft.server.level.ServerPlayer findNearestPlayer(double sourceX, double sourceY, double sourceZ, -+ double maxRange, java.util.function.Predicate predicate) { -+ if (!this.playerGeneralAreaCacheSet) { -+ this.updateGeneralAreaCache(); -+ } -+ -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearby = this.playerGeneralAreaCache; -+ -+ if (nearby == null) { -+ return null; -+ } -+ -+ Object[] backingSet = nearby.getBackingSet(); -+ double closestDistance = maxRange < 0.0 ? Double.MAX_VALUE : maxRange * maxRange; -+ net.minecraft.server.level.ServerPlayer closest = null; -+ for (int i = 0, len = backingSet.length; i < len; ++i) { -+ Object _player = backingSet[i]; -+ if (!(_player instanceof net.minecraft.server.level.ServerPlayer)) { -+ continue; -+ } -+ net.minecraft.server.level.ServerPlayer player = (net.minecraft.server.level.ServerPlayer)_player; -+ -+ double distance = player.distanceToSqr(sourceX, sourceY, sourceZ); -+ if (distance < closestDistance && predicate.test(player)) { -+ closest = player; -+ closestDistance = distance; -+ } -+ } -+ -+ return closest; -+ } -+ -+ public void getNearestPlayers(double sourceX, double sourceY, double sourceZ, java.util.function.Predicate predicate, -+ double range, java.util.List ret) { -+ if (!this.playerGeneralAreaCacheSet) { -+ this.updateGeneralAreaCache(); -+ } -+ -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet nearby = this.playerGeneralAreaCache; -+ -+ if (nearby == null) { -+ return; -+ } -+ -+ double rangeSquared = range * range; -+ -+ Object[] backingSet = nearby.getBackingSet(); -+ for (int i = 0, len = backingSet.length; i < len; ++i) { -+ Object _player = backingSet[i]; -+ if (!(_player instanceof net.minecraft.server.level.ServerPlayer)) { -+ continue; -+ } -+ net.minecraft.server.level.ServerPlayer player = (net.minecraft.server.level.ServerPlayer)_player; -+ -+ if (range >= 0.0) { -+ double distanceSquared = player.distanceToSqr(sourceX, sourceY, sourceZ); -+ if (distanceSquared > rangeSquared) { -+ continue; -+ } -+ } -+ -+ if (predicate == null || predicate.test(player)) { -+ ret.add(player); -+ } -+ } -+ } -+ // Tuinity end - optimise checkDespawn - - public LevelChunk(ServerLevel worldserver, ProtoChunk protoChunk, @Nullable Consumer consumer) { - this(worldserver, protoChunk.getPos(), protoChunk.getBiomes(), protoChunk.getUpgradeData(), protoChunk.getBlockTicks(), protoChunk.getLiquidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), consumer); -+ // Tuinity start - copy over protochunk light -+ this.setBlockNibbles(protoChunk.getBlockNibbles()); -+ this.setSkyNibbles(protoChunk.getSkyNibbles()); -+ this.setSkyEmptinessMap(protoChunk.getSkyEmptinessMap()); -+ this.setBlockEmptinessMap(protoChunk.getBlockEmptinessMap()); -+ // Tuinity end - copy over protochunk light - Iterator iterator = protoChunk.getBlockEntities().values().iterator(); - - while (iterator.hasNext()) { -@@ -788,6 +920,7 @@ public class LevelChunk implements ChunkAccess { - - // CraftBukkit start - public void loadCallback() { -+ if (this.loadedTicketLevel) { LOGGER.error("Double calling chunk load!", new Throwable()); } // Tuinity - // Paper start - neighbour cache - int chunkX = this.chunkPos.x; - int chunkZ = this.chunkPos.z; -@@ -807,6 +940,7 @@ public class LevelChunk implements ChunkAccess { - // Paper end - neighbour cache - org.bukkit.Server server = this.level.getCraftServer(); - this.level.getChunkSource().addLoadedChunk(this); // Paper -+ ((ServerLevel)this.level).getChunkSource().chunkMap.playerChunkManager.onChunkLoad(this.chunkPos.x, this.chunkPos.z); // Tuinity - rewrite player chunk management - if (server != null) { - /* - * If it's a new world, the first few chunks are generated inside -@@ -842,6 +976,7 @@ public class LevelChunk implements ChunkAccess { - } - - public void unloadCallback() { -+ if (!this.loadedTicketLevel) { LOGGER.error("Double calling chunk unload!", new Throwable()); } // Tuinity - org.bukkit.Server server = this.level.getCraftServer(); - org.bukkit.event.world.ChunkUnloadEvent unloadEvent = new org.bukkit.event.world.ChunkUnloadEvent(this.bukkitChunk, this.isUnsaved()); - server.getPluginManager().callEvent(unloadEvent); -diff --git a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -index cdac1f7b30e4c043dcb12ac9e29af926df8170bd..5d2f76eeb4aef0a5ee8c202c1c682171d4d5b2ea 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java -@@ -18,7 +18,8 @@ public class LevelChunkSection { - short nonEmptyBlockCount; // Paper - package-private - private short tickingBlockCount; - private short tickingFluidCount; -- final PalettedContainer states; // Paper - package-private -+ public final PalettedContainer states; // Paper - package-private // Tuinity - public -+ public final com.destroystokyo.paper.util.maplist.IBlockDataList tickingList = new com.destroystokyo.paper.util.maplist.IBlockDataList(); // Paper - - // Paper start - Anti-Xray - Add parameters - @Deprecated public LevelChunkSection(int yOffset) { this(yOffset, null, null, true); } // Notice for updates: Please make sure this constructor isn't used anywhere -@@ -79,6 +80,9 @@ public class LevelChunkSection { - --this.nonEmptyBlockCount; - if (blockState.isRandomlyTicking()) { - --this.tickingBlockCount; -+ // Paper start -+ this.tickingList.remove(x, y, z); -+ // Paper end - } - } - -@@ -90,6 +94,9 @@ public class LevelChunkSection { - ++this.nonEmptyBlockCount; - if (state.isRandomlyTicking()) { - ++this.tickingBlockCount; -+ // Paper start -+ this.tickingList.add(x, y, z, state); -+ // Paper end - } - } - -@@ -125,22 +132,28 @@ public class LevelChunkSection { - } - - public void recalcBlockCounts() { -+ // Paper start -+ this.tickingList.clear(); -+ // Paper end - this.nonEmptyBlockCount = 0; - this.tickingBlockCount = 0; - this.tickingFluidCount = 0; -- this.states.count((state, count) -> { -+ this.states.forEachLocation((state, location) -> { // Paper - FluidState fluidState = state.getFluidState(); - if (!state.isAir()) { -- this.nonEmptyBlockCount = (short)(this.nonEmptyBlockCount + count); -+ this.nonEmptyBlockCount = (short)(this.nonEmptyBlockCount + 1); // Paper - if (state.isRandomlyTicking()) { -- this.tickingBlockCount = (short)(this.tickingBlockCount + count); -+ // Paper start -+ this.tickingBlockCount = (short)(this.tickingBlockCount + 1); -+ this.tickingList.add(location, state); -+ // Paper end - } - } - - if (!fluidState.isEmpty()) { -- this.nonEmptyBlockCount = (short)(this.nonEmptyBlockCount + count); -+ this.nonEmptyBlockCount = (short)(this.nonEmptyBlockCount + 1); // Paper - if (fluidState.isRandomlyTicking()) { -- this.tickingFluidCount = (short)(this.tickingFluidCount + count); -+ this.tickingFluidCount = (short)(this.tickingFluidCount + 1); // Paper - } - } - -diff --git a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -index 554474d4b2e57d8a005b3c3b9b23f32a62243058..ebeb3e3b0619b034a9681da999e9ac33cc241718 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/PalettedContainer.java -@@ -174,7 +174,7 @@ public class PalettedContainer implements PaletteResize { - return this.get(y << 8 | z << 4 | x); // Paper - inline - } - -- protected T get(int index) { -+ public T get(int index) { // Tuinity - public - T object = this.palette.valueFor(this.storage.get(index)); - return (T)(object == null ? this.defaultValue : object); - } -@@ -320,4 +320,12 @@ public class PalettedContainer implements PaletteResize { - public interface CountConsumer { - void accept(T object, int count); - } -+ -+ // Paper start -+ public void forEachLocation(PalettedContainer.CountConsumer datapaletteblock_a) { -+ this.storage.forEach((int location, int data) -> { -+ datapaletteblock_a.accept(this.palette.valueFor(data), location); -+ }); -+ } -+ // Paper end - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java -index 78bd3274866fed3d627a3eda7b96b92716507d38..ccdadf5d7c07d74f5bea94fc21784114b6d520da 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java -@@ -1,5 +1,7 @@ - package net.minecraft.world.level.chunk; - -+import ca.spottedleaf.starlight.light.SWMRNibbleArray; -+import ca.spottedleaf.starlight.light.StarLightEngine; - import com.google.common.collect.Lists; - import com.google.common.collect.Maps; - import com.google.common.collect.Sets; -@@ -65,6 +67,53 @@ public class ProtoChunk implements ChunkAccess { - private volatile boolean isLightCorrect; - final net.minecraft.world.level.Level level; // Paper - Add level - -+ // Tuinity start - rewrite light engine -+ protected volatile SWMRNibbleArray[] blockNibbles; -+ protected volatile SWMRNibbleArray[] skyNibbles; -+ protected volatile boolean[] skyEmptinessMap; -+ protected volatile boolean[] blockEmptinessMap; -+ -+ @Override -+ public SWMRNibbleArray[] getBlockNibbles() { -+ return this.blockNibbles; -+ } -+ -+ @Override -+ public void setBlockNibbles(SWMRNibbleArray[] nibbles) { -+ this.blockNibbles = nibbles; -+ } -+ -+ @Override -+ public SWMRNibbleArray[] getSkyNibbles() { -+ return this.skyNibbles; -+ } -+ -+ @Override -+ public void setSkyNibbles(SWMRNibbleArray[] nibbles) { -+ this.skyNibbles = nibbles; -+ } -+ -+ @Override -+ public boolean[] getSkyEmptinessMap() { -+ return this.skyEmptinessMap; -+ } -+ -+ @Override -+ public void setSkyEmptinessMap(boolean[] emptinessMap) { -+ this.skyEmptinessMap = emptinessMap; -+ } -+ -+ @Override -+ public boolean[] getBlockEmptinessMap() { -+ return this.blockEmptinessMap; -+ } -+ -+ @Override -+ public void setBlockEmptinessMap(boolean[] emptinessMap) { -+ this.blockEmptinessMap = emptinessMap; -+ } -+ // Tuinity end - rewrite light engine -+ - // Paper start - add level - @Deprecated public ProtoChunk(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor world) { this(pos, upgradeData, world, null); } - public ProtoChunk(ChunkPos pos, UpgradeData upgradeData, LevelHeightAccessor world, net.minecraft.server.level.ServerLevel level) { -@@ -79,6 +128,10 @@ public class ProtoChunk implements ChunkAccess { - // Paper start - add level - @Deprecated public ProtoChunk(ChunkPos pos, UpgradeData upgradeData, @Nullable LevelChunkSection[] levelChunkSections, ProtoTickList blockTickScheduler, ProtoTickList fluidTickScheduler, LevelHeightAccessor world) { this(pos, upgradeData, levelChunkSections, blockTickScheduler, fluidTickScheduler, world, null); } - public ProtoChunk(ChunkPos pos, UpgradeData upgradeData, @Nullable LevelChunkSection[] levelChunkSections, ProtoTickList blockTickScheduler, ProtoTickList fluidTickScheduler, LevelHeightAccessor world, net.minecraft.server.level.ServerLevel level) { -+ // Tuinity start -+ this.blockNibbles = StarLightEngine.getFilledEmptyLight(world); -+ this.skyNibbles = StarLightEngine.getFilledEmptyLight(world); -+ // Tuinity end - this.level = level; - // Paper end - this.chunkPos = pos; -@@ -176,7 +229,7 @@ public class ProtoChunk implements ChunkAccess { - - LevelChunkSection levelChunkSection = this.getOrCreateSection(l); - BlockState blockState = levelChunkSection.setBlockState(i & 15, j & 15, k & 15, state); -- if (this.status.isOrAfter(ChunkStatus.FEATURES) && state != blockState && (state.getLightBlock(this, pos) != blockState.getLightBlock(this, pos) || state.getLightEmission() != blockState.getLightEmission() || state.useShapeForLightOcclusion() || blockState.useShapeForLightOcclusion())) { -+ if (this.status.isOrAfter(ChunkStatus.LIGHT) && state != blockState && (state.getLightBlock(this, pos) != blockState.getLightBlock(this, pos) || state.getLightEmission() != blockState.getLightEmission() || state.useShapeForLightOcclusion() || blockState.useShapeForLightOcclusion())) { // Tuinity - move block updates to only happen after lighting occurs (or during, thanks chunk system) - this.lightEngine.checkBlock(pos); - } - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -index 18432dc1e5067d65cda709b7b3bcc2dd37b77d02..917fa5a3106259c01d6a01acf770890dbdf50f1a 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkSerializer.java -@@ -65,6 +65,21 @@ import org.apache.logging.log4j.Logger; - - public class ChunkSerializer { - -+ // Tuinity start - replace light engine impl -+ private static final int STARLIGHT_LIGHT_VERSION = 5; -+ -+ private static final String BLOCKLIGHT_STATE_TAG = "starlight.blocklight_state"; -+ private static final String SKYLIGHT_STATE_TAG = "starlight.skylight_state"; -+ private static final String STARLIGHT_VERSION_TAG = "starlight.light_version"; -+ // Tuinity end - replace light engine impl -+ // Tuinity start -+ // TODO: Check on update -+ public static long getLastWorldSaveTime(CompoundTag chunkData) { -+ CompoundTag levelData = chunkData.getCompound("Level"); -+ return levelData.getLong("LastUpdate"); -+ } -+ // Tuinity end -+ - private static final Logger LOGGER = LogManager.getLogger(); - public static final String TAG_UPGRADE_DATA = "UpgradeData"; - -@@ -118,7 +133,7 @@ public class ChunkSerializer { - } - // Paper end - BiomeSource worldchunkmanager = chunkgenerator.getBiomeSource(); -- CompoundTag nbttagcompound1 = nbt.getCompound("Level"); // Paper - diff on change, see ChunkSerializer#getChunkCoordinate -+ CompoundTag nbttagcompound1 = nbt.getCompound("Level"); // Paper - diff on change, see ChunkSerializer#getChunkCoordinate // Tuinity - diff on change - ChunkPos chunkcoordintpair1 = new ChunkPos(nbttagcompound1.getInt("xPos"), nbttagcompound1.getInt("zPos")); // Paper - diff on change, see ChunkSerializer#getChunkCoordinate - - if (!Objects.equals(pos, chunkcoordintpair1)) { -@@ -133,13 +148,20 @@ public class ChunkSerializer { - ProtoTickList protochunkticklist1 = new ProtoTickList<>((fluidtype) -> { - return fluidtype == null || fluidtype == Fluids.EMPTY; - }, pos, nbttagcompound1.getList("LiquidsToBeTicked", 9), world); -- boolean flag = nbttagcompound1.getBoolean("isLightOn"); -+ boolean flag = getStatus(nbt).isOrAfter(ChunkStatus.LIGHT) && nbttagcompound1.get("isLightOn") != null && nbttagcompound1.getInt(STARLIGHT_VERSION_TAG) == STARLIGHT_LIGHT_VERSION; // Tuinity - ListTag nbttaglist = nbttagcompound1.getList("Sections", 10); - int i = world.getSectionsCount(); - LevelChunkSection[] achunksection = new LevelChunkSection[i]; - boolean flag1 = world.dimensionType().hasSkyLight(); - ServerChunkCache chunkproviderserver = world.getChunkSource(); - LevelLightEngine lightengine = chunkproviderserver.getLightEngine(); -+ // Tuinity start -+ ca.spottedleaf.starlight.light.SWMRNibbleArray[] blockNibbles = ca.spottedleaf.starlight.light.StarLightEngine.getFilledEmptyLight(world); // Tuinity - replace light impl -+ ca.spottedleaf.starlight.light.SWMRNibbleArray[] skyNibbles = ca.spottedleaf.starlight.light.StarLightEngine.getFilledEmptyLight(world); // Tuinity - replace light impl -+ final int minSection = com.tuinity.tuinity.util.WorldUtil.getMinLightSection(world); -+ final int maxSection = com.tuinity.tuinity.util.WorldUtil.getMaxLightSection(world); -+ boolean canReadSky = world.dimensionType().hasSkyLight(); -+ // Tuinity end - - if (flag) { - tasksToExecuteOnMain.add(() -> { // Paper - delay this task since we're executing off-main -@@ -148,7 +170,7 @@ public class ChunkSerializer { - } - - for (int j = 0; j < nbttaglist.size(); ++j) { -- CompoundTag nbttagcompound2 = nbttaglist.getCompound(j); -+ CompoundTag nbttagcompound2 = nbttaglist.getCompound(j); CompoundTag sectionData = nbttagcompound2; // Tuinity - byte b0 = nbttagcompound2.getByte("Y"); - - if (nbttagcompound2.contains("Palette", 9) && nbttagcompound2.contains("BlockStates", 12)) { -@@ -166,23 +188,29 @@ public class ChunkSerializer { - } - - if (flag) { -- if (nbttagcompound2.contains("BlockLight", 7)) { -- // Paper start - delay this task since we're executing off-main -- DataLayer blockLight = new DataLayer(nbttagcompound2.getByteArray("BlockLight")); -- tasksToExecuteOnMain.add(() -> { -- lightengine.queueSectionData(LightLayer.BLOCK, SectionPos.of(chunkcoordintpair1, b0), blockLight, true); -- }); -- // Paper end - delay this task since we're executing off-main -+ // Tuinity start - rewrite light engine -+ int y = sectionData.getByte("Y"); -+ -+ if (sectionData.contains("BlockLight", 7)) { -+ // this is where our diff is -+ blockNibbles[y - minSection] = new ca.spottedleaf.starlight.light.SWMRNibbleArray(sectionData.getByteArray("BlockLight").clone(), sectionData.getInt(BLOCKLIGHT_STATE_TAG)); // clone for data safety -+ } else { -+ blockNibbles[y - minSection] = new ca.spottedleaf.starlight.light.SWMRNibbleArray(null, sectionData.getInt(BLOCKLIGHT_STATE_TAG)); - } - -- if (flag1 && nbttagcompound2.contains("SkyLight", 7)) { -- // Paper start - delay this task since we're executing off-main -- DataLayer skyLight = new DataLayer(nbttagcompound2.getByteArray("SkyLight")); -- tasksToExecuteOnMain.add(() -> { -- lightengine.queueSectionData(LightLayer.SKY, SectionPos.of(chunkcoordintpair1, b0), skyLight, true); -- }); -- // Paper end - delay this task since we're executing off-main -+ if (canReadSky) { -+ if (sectionData.contains("SkyLight", 7)) { -+ // we store under the same key so mod programs editing nbt -+ // can still read the data, hopefully. -+ // however, for compatibility we store chunks as unlit so vanilla -+ // is forced to re-light them if it encounters our data. It's too much of a burden -+ // to try and maintain compatibility with a broken and inferior skylight management system. -+ skyNibbles[y - minSection] = new ca.spottedleaf.starlight.light.SWMRNibbleArray(sectionData.getByteArray("SkyLight").clone(), sectionData.getInt(SKYLIGHT_STATE_TAG)); // clone for data safety -+ } else { -+ skyNibbles[y - minSection] = new ca.spottedleaf.starlight.light.SWMRNibbleArray(null, sectionData.getInt(SKYLIGHT_STATE_TAG)); -+ } - } -+ // Tuinity end - rewrite light engine - } - } - -@@ -226,8 +254,12 @@ public class ChunkSerializer { - object = new LevelChunk(world.getLevel(), pos, biomestorage, chunkconverter, (TickList) object1, (TickList) object2, k, achunksection, // Paper start - fix massive nbt memory leak due to lambda. move lambda into a container method to not leak scope. Only clone needed NBT keys. - createLoadEntitiesConsumer(new SafeNBTCopy(nbttagcompound1, "TileEntities", "Entities", "ChunkBukkitValues")) // Paper - move CB Chunk PDC into here - );// Paper end -+ ((LevelChunk)object).setBlockNibbles(blockNibbles); // Tuinity - replace light impl -+ ((LevelChunk)object).setSkyNibbles(skyNibbles); // Tuinity - replace light impl - } else { - ProtoChunk protochunk = new ProtoChunk(pos, chunkconverter, achunksection, protochunkticklist, protochunkticklist1, world, world); // Paper - add level -+ protochunk.setBlockNibbles(blockNibbles); // Tuinity - replace light impl -+ protochunk.setSkyNibbles(skyNibbles); // Tuinity - replace light impl - - protochunk.setBiomes(biomestorage); - object = protochunk; -@@ -408,7 +440,7 @@ public class ChunkSerializer { - DataLayer[] blockLight = new DataLayer[lightenginethreaded.getMaxLightSection() - lightenginethreaded.getMinLightSection()]; - DataLayer[] skyLight = new DataLayer[lightenginethreaded.getMaxLightSection() - lightenginethreaded.getMinLightSection()]; - -- for (int i = lightenginethreaded.getMinLightSection(); i < lightenginethreaded.getMaxLightSection(); ++i) { -+ for (int i = lightenginethreaded.getMinLightSection(); false && i < lightenginethreaded.getMaxLightSection(); ++i) { // Tuinity - don't run loop, we don't need to - light data is per chunk now - DataLayer blockArray = lightenginethreaded.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunkPos, i)); - DataLayer skyArray = lightenginethreaded.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunkPos, i)); - -@@ -457,6 +489,12 @@ public class ChunkSerializer { - return saveChunk(world, chunk, null); - } - public static CompoundTag saveChunk(ServerLevel world, ChunkAccess chunk, AsyncSaveData asyncsavedata) { -+ // Tuinity start - rewrite light impl -+ final int minSection = com.tuinity.tuinity.util.WorldUtil.getMinLightSection(world); -+ final int maxSection = com.tuinity.tuinity.util.WorldUtil.getMaxLightSection(world); -+ ca.spottedleaf.starlight.light.SWMRNibbleArray[] blockNibbles = chunk.getBlockNibbles(); -+ ca.spottedleaf.starlight.light.SWMRNibbleArray[] skyNibbles = chunk.getSkyNibbles(); -+ // Tuinity end - rewrite light impl - // Paper end - ChunkPos chunkcoordintpair = chunk.getPos(); - CompoundTag nbttagcompound = new CompoundTag(); -@@ -466,7 +504,7 @@ public class ChunkSerializer { - nbttagcompound.put("Level", nbttagcompound1); - nbttagcompound1.putInt("xPos", chunkcoordintpair.x); - nbttagcompound1.putInt("zPos", chunkcoordintpair.z); -- nbttagcompound1.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading -+ nbttagcompound1.putLong("LastUpdate", asyncsavedata != null ? asyncsavedata.worldTime : world.getGameTime()); // Paper - async chunk unloading // Tuinity - diff on change - nbttagcompound1.putLong("InhabitedTime", chunk.getInhabitedTime()); - nbttagcompound1.putString("Status", chunk.getStatus().getName()); - UpgradeData chunkconverter = chunk.getUpgradeData(); -@@ -485,32 +523,33 @@ public class ChunkSerializer { - LevelChunkSection chunksection = (LevelChunkSection) Arrays.stream(achunksection).filter((chunksection1) -> { - return chunksection1 != null && SectionPos.blockToSectionCoord(chunksection1.bottomBlockY()) == finalI; // CraftBukkit - decompile errors - }).findFirst().orElse(LevelChunk.EMPTY_SECTION); -- // Paper start - async chunk save for unload -- DataLayer nibblearray; // block light -- DataLayer nibblearray1; // sky light -- if (asyncsavedata == null) { -- nibblearray = lightenginethreaded.getLayerListener(LightLayer.BLOCK).getDataLayerData(SectionPos.of(chunkcoordintpair, i)); /// Paper - diff on method change (see getAsyncSaveData) -- nibblearray1 = lightenginethreaded.getLayerListener(LightLayer.SKY).getDataLayerData(SectionPos.of(chunkcoordintpair, i)); // Paper - diff on method change (see getAsyncSaveData) -- } else { -- nibblearray = asyncsavedata.blockLight[i - lightenginethreaded.getMinLightSection()]; -- nibblearray1 = asyncsavedata.skyLight[i - lightenginethreaded.getMinLightSection()]; -- } -- // Paper end -- if (chunksection != LevelChunk.EMPTY_SECTION || nibblearray != null || nibblearray1 != null) { -- CompoundTag nbttagcompound2 = new CompoundTag(); -+ // Tuinity start - replace light engine -+ ca.spottedleaf.starlight.light.SWMRNibbleArray.SaveState blockNibble = blockNibbles[i - minSection].getSaveState(); -+ ca.spottedleaf.starlight.light.SWMRNibbleArray.SaveState skyNibble = skyNibbles[i - minSection].getSaveState(); -+ if (chunksection != LevelChunk.EMPTY_SECTION || blockNibble != null || skyNibble != null) { -+ // Tuinity end - replace light engine -+ CompoundTag nbttagcompound2 = new CompoundTag(); CompoundTag section = nbttagcompound2; // Tuinity - - nbttagcompound2.putByte("Y", (byte) (i & 255)); - if (chunksection != LevelChunk.EMPTY_SECTION) { - chunksection.getStates().write(nbttagcompound2, "Palette", "BlockStates"); - } - -- if (nibblearray != null && !nibblearray.isEmpty()) { -- nbttagcompound2.putByteArray("BlockLight", nibblearray.asBytesPoolSafe().clone()); // Paper -+ // Tuinity start - replace light engine -+ if (blockNibble != null) { -+ if (blockNibble.data != null) { -+ section.putByteArray("BlockLight", blockNibble.data); -+ } -+ section.putInt(BLOCKLIGHT_STATE_TAG, blockNibble.state); - } - -- if (nibblearray1 != null && !nibblearray1.isEmpty()) { -- nbttagcompound2.putByteArray("SkyLight", nibblearray1.asBytesPoolSafe().clone()); // Paper -+ if (skyNibble != null) { -+ if (skyNibble.data != null) { -+ section.putByteArray("SkyLight", skyNibble.data); -+ } -+ section.putInt(SKYLIGHT_STATE_TAG, skyNibble.state); - } -+ // Tuinity end - replace light engine - - nbttaglist.add(nbttagcompound2); - } -@@ -518,7 +557,8 @@ public class ChunkSerializer { - - nbttagcompound1.put("Sections", nbttaglist); - if (flag) { -- nbttagcompound1.putBoolean("isLightOn", true); -+ nbttagcompound1.putInt(STARLIGHT_VERSION_TAG, STARLIGHT_LIGHT_VERSION); // Tuinity -+ nbttagcompound1.putBoolean("isLightOn", false); // Tuinity - set to false but still store, this allows us to detect --eraseCache (as eraseCache _removes_) - } - - ChunkBiomeContainer biomestorage = chunk.getBiomes(); -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -index 176610b31f66b890afe61f4de46c412382bb8d22..70ec2feef1553afca2c8cca3a7f19498637b41d5 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/ChunkStorage.java -@@ -34,12 +34,13 @@ public class ChunkStorage implements AutoCloseable { - this.fixerUpper = dataFixer; - // Paper start - async chunk io - // remove IO worker -- this.regionFileCache = new RegionFileStorage(directory, dsync); // Paper - nuke IOWorker -+ this.regionFileCache = new RegionFileStorage(directory, dsync, true); // Paper - nuke IOWorker // Tuinity - // Paper end - async chunk io - } - - // CraftBukkit start - private boolean check(ServerChunkCache cps, int x, int z) throws IOException { -+ if (true) return true; // Tuinity - this isn't even needed anymore, light is purged updating to 1.14+, why are we holding up the conversion process reading chunk data off disk - return true, we need to set light populated to true so the converter recognizes the chunk as being "full" - ChunkPos pos = new ChunkPos(x, z); - if (cps != null) { - //com.google.common.base.Preconditions.checkState(org.bukkit.Bukkit.isPrimaryThread(), "primary thread"); // Paper - this function is now MT-Safe -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java -index c8298a597818227de33a4afce4698ec0666cf758..b49b0c4cac8aec09ffe970c92e5a75047c0e1f1d 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionBitmap.java -@@ -9,6 +9,27 @@ import java.util.BitSet; - public class RegionBitmap { - private final BitSet used = new BitSet(); - -+ // Tuinity start -+ public final void copyFrom(RegionBitmap other) { -+ BitSet thisBitset = this.used; -+ BitSet otherBitset = other.used; -+ -+ for (int i = 0; i < Math.max(thisBitset.size(), otherBitset.size()); ++i) { -+ thisBitset.set(i, otherBitset.get(i)); -+ } -+ } -+ -+ public final boolean tryAllocate(int from, int length) { -+ BitSet bitset = this.used; -+ int firstSet = bitset.nextSetBit(from); -+ if (firstSet > 0 && firstSet < (from + length)) { -+ return false; -+ } -+ bitset.set(from, from + length); -+ return true; -+ } -+ // Tuinity end -+ - public void force(int start, int size) { - this.used.set(start, start + size); - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java -index c22391a0d4b7db49bd3994b0887939a7d8019391..118adb6fbdc56ca03652f114c1b7ced0ef26a628 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFile.java -@@ -55,6 +55,341 @@ public class RegionFile implements AutoCloseable { - public final java.util.concurrent.locks.ReentrantLock fileLock = new java.util.concurrent.locks.ReentrantLock(true); // Paper - public final File regionFile; // Paper - -+ // Tuinity start - try to recover from RegionFile header corruption -+ private static long roundToSectors(long bytes) { -+ long sectors = bytes >>> 12; // 4096 = 2^12 -+ long remainingBytes = bytes & 4095; -+ long sign = -remainingBytes; // sign is 1 if nonzero -+ return sectors + (sign >>> 63); -+ } -+ -+ private static final CompoundTag OVERSIZED_COMPOUND = new CompoundTag(); -+ -+ private CompoundTag attemptRead(long sector, int chunkDataLength, long fileLength) throws IOException { -+ try { -+ if (chunkDataLength < 0) { -+ return null; -+ } -+ -+ long offset = sector * 4096L + 4L; // offset for chunk data -+ -+ if ((offset + chunkDataLength) > fileLength) { -+ return null; -+ } -+ -+ ByteBuffer chunkData = ByteBuffer.allocate(chunkDataLength); -+ if (chunkDataLength != this.file.read(chunkData, offset)) { -+ return null; -+ } -+ -+ ((java.nio.Buffer)chunkData).flip(); -+ -+ byte compressionType = chunkData.get(); -+ if (compressionType < 0) { // compressionType & 128 != 0 -+ // oversized chunk -+ return OVERSIZED_COMPOUND; -+ } -+ -+ RegionFileVersion compression = RegionFileVersion.fromId(compressionType); -+ if (compression == null) { -+ return null; -+ } -+ -+ InputStream input = compression.wrap(new ByteArrayInputStream(chunkData.array(), chunkData.position(), chunkDataLength - chunkData.position())); -+ -+ return NbtIo.read((java.io.DataInput)new DataInputStream(new BufferedInputStream(input))); -+ } catch (Exception ex) { -+ return null; -+ } -+ } -+ -+ private int getLength(long sector) throws IOException { -+ ByteBuffer length = ByteBuffer.allocate(4); -+ if (4 != this.file.read(length, sector * 4096L)) { -+ return -1; -+ } -+ -+ return length.getInt(0); -+ } -+ -+ private void backupRegionFile() { -+ File backup = new File(this.regionFile.getParent(), this.regionFile.getName() + "." + new java.util.Random().nextLong() + ".backup"); -+ this.backupRegionFile(backup); -+ } -+ -+ private void backupRegionFile(File to) { -+ try { -+ this.file.force(true); -+ LOGGER.warn("Backing up regionfile \"" + this.regionFile.getAbsolutePath() + "\" to " + to.getAbsolutePath()); -+ java.nio.file.Files.copy(this.regionFile.toPath(), to.toPath()); -+ LOGGER.warn("Backed up the regionfile to " + to.getAbsolutePath()); -+ } catch (IOException ex) { -+ LOGGER.error("Failed to backup to " + to.getAbsolutePath(), ex); -+ } -+ } -+ -+ // note: only call for CHUNK regionfiles -+ void recalculateHeader() throws IOException { -+ if (!this.canRecalcHeader) { -+ return; -+ } -+ synchronized (this) { -+ LOGGER.warn("Corrupt regionfile header detected! Attempting to re-calculate header offsets for regionfile " + this.regionFile.getAbsolutePath(), new Throwable()); -+ -+ // try to backup file so maybe it could be sent to us for further investigation -+ -+ this.backupRegionFile(); -+ CompoundTag[] compounds = new CompoundTag[32 * 32]; // only in the regionfile (i.e exclude mojang/aikar oversized data) -+ int[] rawLengths = new int[32 * 32]; // length of chunk data including 4 byte length field, bytes -+ int[] sectorOffsets = new int[32 * 32]; // in sectors -+ boolean[] hasAikarOversized = new boolean[32 * 32]; -+ -+ long fileLength = this.file.size(); -+ long totalSectors = roundToSectors(fileLength); -+ -+ // search the regionfile from start to finish for the most up-to-date chunk data -+ -+ for (long i = 2, maxSector = Math.min((long)(Integer.MAX_VALUE >>> 8), totalSectors); i < maxSector; ++i) { // first two sectors are header, skip -+ int chunkDataLength = this.getLength(i); -+ CompoundTag compound = this.attemptRead(i, chunkDataLength, fileLength); -+ if (compound == null || compound == OVERSIZED_COMPOUND) { -+ continue; -+ } -+ -+ ChunkPos chunkPos = ChunkSerializer.getChunkCoordinate(compound); -+ int location = (chunkPos.x & 31) | ((chunkPos.z & 31) << 5); -+ -+ CompoundTag otherCompound = compounds[location]; -+ -+ if (otherCompound != null && ChunkSerializer.getLastWorldSaveTime(otherCompound) > ChunkSerializer.getLastWorldSaveTime(compound)) { -+ continue; // don't overwrite newer data. -+ } -+ -+ // aikar oversized? -+ File aikarOversizedFile = this.getOversizedFile(chunkPos.x, chunkPos.z); -+ boolean isAikarOversized = false; -+ if (aikarOversizedFile.exists()) { -+ try { -+ CompoundTag aikarOversizedCompound = this.getOversizedData(chunkPos.x, chunkPos.z); -+ if (ChunkSerializer.getLastWorldSaveTime(compound) == ChunkSerializer.getLastWorldSaveTime(aikarOversizedCompound)) { -+ // best we got for an id. hope it's good enough -+ isAikarOversized = true; -+ } -+ } catch (Exception ex) { -+ LOGGER.error("Failed to read aikar oversized data for absolute chunk (" + chunkPos.x + "," + chunkPos.z + ") in regionfile " + this.regionFile.getAbsolutePath() + ", oversized data for this chunk will be lost", ex); -+ // fall through, if we can't read aikar oversized we can't risk corrupting chunk data -+ } -+ } -+ -+ hasAikarOversized[location] = isAikarOversized; -+ compounds[location] = compound; -+ rawLengths[location] = chunkDataLength + 4; -+ sectorOffsets[location] = (int)i; -+ -+ int chunkSectorLength = (int)roundToSectors(rawLengths[location]); -+ i += chunkSectorLength; -+ --i; // gets incremented next iteration -+ } -+ -+ // forge style oversized data is already handled by the local search, and aikar data we just hope -+ // we get it right as aikar data has no identifiers we could use to try and find its corresponding -+ // local data compound -+ -+ java.nio.file.Path containingFolder = this.externalFileDir; -+ File[] regionFiles = containingFolder.toFile().listFiles(); -+ boolean[] oversized = new boolean[32 * 32]; -+ RegionFileVersion[] oversizedCompressionTypes = new RegionFileVersion[32 * 32]; -+ -+ if (regionFiles != null) { -+ ChunkPos ourLowerLeftPosition = RegionFileStorage.getRegionFileCoordinates(this.regionFile); -+ -+ if (ourLowerLeftPosition == null) { -+ LOGGER.fatal("Unable to get chunk location of regionfile " + this.regionFile.getAbsolutePath() + ", cannot recover oversized chunks"); -+ } else { -+ int lowerXBound = ourLowerLeftPosition.x; // inclusive -+ int lowerZBound = ourLowerLeftPosition.z; // inclusive -+ int upperXBound = lowerXBound + 32 - 1; // inclusive -+ int upperZBound = lowerZBound + 32 - 1; // inclusive -+ -+ // read mojang oversized data -+ for (File regionFile : regionFiles) { -+ ChunkPos oversizedCoords = getOversizedChunkPair(regionFile); -+ if (oversizedCoords == null) { -+ continue; -+ } -+ -+ if ((oversizedCoords.x < lowerXBound || oversizedCoords.x > upperXBound) || (oversizedCoords.z < lowerZBound || oversizedCoords.z > upperZBound)) { -+ continue; // not in our regionfile -+ } -+ -+ // ensure oversized data is valid & is newer than data in the regionfile -+ -+ int location = (oversizedCoords.x & 31) | ((oversizedCoords.z & 31) << 5); -+ -+ byte[] chunkData; -+ try { -+ chunkData = Files.readAllBytes(regionFile.toPath()); -+ } catch (Exception ex) { -+ LOGGER.error("Failed to read oversized chunk data in file " + regionFile.getAbsolutePath() + ", data will be lost", ex); -+ continue; -+ } -+ -+ CompoundTag compound = null; -+ -+ // We do not know the compression type, as it's stored in the regionfile. So we need to try all of them -+ RegionFileVersion compression = null; -+ for (RegionFileVersion compressionType : RegionFileVersion.VERSIONS.values()) { -+ try { -+ DataInputStream in = new DataInputStream(new BufferedInputStream(compressionType.wrap(new ByteArrayInputStream(chunkData)))); // typical java -+ compound = NbtIo.read((java.io.DataInput)in); -+ compression = compressionType; -+ break; // reaches here iff readNBT does not throw -+ } catch (Exception ex) { -+ continue; -+ } -+ } -+ -+ if (compound == null) { -+ LOGGER.error("Failed to read oversized chunk data in file " + regionFile.getAbsolutePath() + ", it's corrupt. Its data will be lost"); -+ continue; -+ } -+ -+ if (compounds[location] == null || ChunkSerializer.getLastWorldSaveTime(compound) > ChunkSerializer.getLastWorldSaveTime(compounds[location])) { -+ oversized[location] = true; -+ oversizedCompressionTypes[location] = compression; -+ } -+ } -+ } -+ } -+ -+ // now we need to calculate a new offset header -+ -+ int[] calculatedOffsets = new int[32 * 32]; -+ RegionBitmap newSectorAllocations = new RegionBitmap(); -+ newSectorAllocations.force(0, 2); // make space for header -+ -+ // allocate sectors for normal chunks -+ -+ for (int chunkX = 0; chunkX < 32; ++chunkX) { -+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { -+ int location = chunkX | (chunkZ << 5); -+ -+ if (oversized[location]) { -+ continue; -+ } -+ -+ int rawLength = rawLengths[location]; // bytes -+ int sectorOffset = sectorOffsets[location]; // sectors -+ int sectorLength = (int)roundToSectors(rawLength); -+ -+ if (newSectorAllocations.tryAllocate(sectorOffset, sectorLength)) { -+ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized -+ } else { -+ LOGGER.error("Failed to allocate space for local chunk (overlapping data??) at (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.getAbsolutePath() + ", chunk will be regenerated"); -+ } -+ } -+ } -+ -+ // allocate sectors for oversized chunks -+ -+ for (int chunkX = 0; chunkX < 32; ++chunkX) { -+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { -+ int location = chunkX | (chunkZ << 5); -+ -+ if (!oversized[location]) { -+ continue; -+ } -+ -+ int sectorOffset = newSectorAllocations.allocate(1); -+ int sectorLength = 1; -+ -+ try { -+ this.file.write(this.createExternalStub(oversizedCompressionTypes[location]), sectorOffset * 4096); -+ // only allocate in the new offsets if the write succeeds -+ calculatedOffsets[location] = sectorOffset << 8 | (sectorLength > 255 ? 255 : sectorLength); // support forge style oversized -+ } catch (IOException ex) { -+ newSectorAllocations.free(sectorOffset, sectorLength); -+ LOGGER.error("Failed to write new oversized chunk data holder, local chunk at (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.getAbsolutePath() + " will be regenerated"); -+ } -+ } -+ } -+ -+ // rewrite aikar oversized data -+ -+ this.oversizedCount = 0; -+ for (int chunkX = 0; chunkX < 32; ++chunkX) { -+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { -+ int location = chunkX | (chunkZ << 5); -+ int isAikarOversized = hasAikarOversized[location] ? 1 : 0; -+ -+ this.oversizedCount += isAikarOversized; -+ this.oversized[location] = (byte)isAikarOversized; -+ } -+ } -+ -+ if (this.oversizedCount > 0) { -+ try { -+ this.writeOversizedMeta(); -+ } catch (Exception ex) { -+ LOGGER.error("Failed to write aikar oversized chunk meta, all aikar style oversized chunk data will be lost for regionfile " + this.regionFile.getAbsolutePath(), ex); -+ this.getOversizedMetaFile().delete(); -+ } -+ } else { -+ this.getOversizedMetaFile().delete(); -+ } -+ -+ this.usedSectors.copyFrom(newSectorAllocations); -+ -+ // before we overwrite the old sectors, print a summary of the chunks that got changed. -+ -+ LOGGER.info("Starting summary of changes for regionfile " + this.regionFile.getAbsolutePath()); -+ -+ for (int chunkX = 0; chunkX < 32; ++chunkX) { -+ for (int chunkZ = 0; chunkZ < 32; ++chunkZ) { -+ int location = chunkX | (chunkZ << 5); -+ -+ int oldOffset = this.offsets.get(location); -+ int newOffset = calculatedOffsets[location]; -+ -+ if (oldOffset == newOffset) { -+ continue; -+ } -+ -+ this.offsets.put(location, newOffset); // overwrite incorrect offset -+ -+ if (oldOffset == 0) { -+ // found lost data -+ LOGGER.info("Found missing data for local chunk (" + chunkX + "," + chunkZ + ") in regionfile " + this.regionFile.getAbsolutePath()); -+ } else if (newOffset == 0) { -+ LOGGER.warn("Data for local chunk (" + chunkX + "," + chunkZ + ") could not be recovered in regionfile " + this.regionFile.getAbsolutePath() + ", it will be regenerated"); -+ } else { -+ LOGGER.info("Local chunk (" + chunkX + "," + chunkZ + ") changed to point to newer data or correct chunk in regionfile " + this.regionFile.getAbsolutePath()); -+ } -+ } -+ } -+ -+ LOGGER.info("End of change summary for regionfile " + this.regionFile.getAbsolutePath()); -+ -+ // simply destroy the timestamp header, it's not used -+ -+ for (int i = 0; i < 32 * 32; ++i) { -+ this.timestamps.put(i, calculatedOffsets[i] != 0 ? (int)System.currentTimeMillis() : 0); // write a valid timestamp for valid chunks, I do not want to find out whatever dumb program actually checks this -+ } -+ -+ // write new header -+ try { -+ this.flush(); -+ this.file.force(true); // try to ensure it goes through... -+ LOGGER.info("Successfully wrote new header to disk for regionfile " + this.regionFile.getAbsolutePath()); -+ } catch (IOException ex) { -+ LOGGER.fatal("Failed to write new header to disk for regionfile " + this.regionFile.getAbsolutePath(), ex); -+ } -+ } -+ } -+ -+ final boolean canRecalcHeader; // final forces compile fail on new constructor -+ // Tuinity end -+ - // Paper start - Cache chunk status - private final ChunkStatus[] statuses = new ChunkStatus[32 * 32]; - -@@ -82,8 +417,19 @@ public class RegionFile implements AutoCloseable { - public RegionFile(File file, File directory, boolean dsync) throws IOException { - this(file.toPath(), directory.toPath(), RegionFileVersion.VERSION_DEFLATE, dsync); - } -+ // Tuinity start - add can recalc flag -+ public RegionFile(File file, File directory, boolean dsync, boolean canRecalcHeader) throws IOException { -+ this(file.toPath(), directory.toPath(), RegionFileVersion.VERSION_DEFLATE, dsync, canRecalcHeader); -+ } -+ // Tuinity end - add can recalc flag - - public RegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync) throws IOException { -+ // Tuinity start - add can recalc flag -+ this(file, directory, outputChunkStreamVersion, dsync, false); -+ } -+ public RegionFile(Path file, Path directory, RegionFileVersion outputChunkStreamVersion, boolean dsync, boolean canRecalcHeader) throws IOException { -+ this.canRecalcHeader = canRecalcHeader; -+ // Tuinity end - add can recalc flag - this.header = ByteBuffer.allocateDirect(8192); - this.regionFile = file.toFile(); // Paper - initOversizedState(); // Paper -@@ -112,14 +458,16 @@ public class RegionFile implements AutoCloseable { - RegionFile.LOGGER.warn("Region file {} has truncated header: {}", file, i); - } - -- long j = Files.size(file); -+ final long j = Files.size(file); final long regionFileSize = j; // Tuinity - recalculate header on header corruption - -+ boolean needsHeaderRecalc = false; // Tuinity - recalculate header on header corruption -+ boolean hasBackedUp = false; // Tuinity - recalculate header on header corruption - for (int k = 0; k < 1024; ++k) { -- int l = this.offsets.get(k); -+ final int l = this.offsets.get(k); final int headerLocation = l; // Tuinity - we expect this to be the header location - - if (l != 0) { -- int i1 = RegionFile.getSectorNumber(l); -- int j1 = RegionFile.getNumSectors(l); -+ final int i1 = RegionFile.getSectorNumber(l); final int offset = i1; // Tuinity - we expect this to be offset in file in sectors -+ int j1 = RegionFile.getNumSectors(l); final int sectorLength; // Tuinity - diff on change, we expect this to be sector length of region - watch out for reassignments - // Spigot start - if (j1 == 255) { - // We're maxed out, so we need to read the proper length from the section -@@ -128,32 +476,102 @@ public class RegionFile implements AutoCloseable { - j1 = (realLen.getInt(0) + 4) / 4096 + 1; - } - // Spigot end -+ sectorLength = j1; // Tuinity - diff on change, we expect this to be sector length of region - - if (i1 < 2) { - RegionFile.LOGGER.warn("Region file {} has invalid sector at index: {}; sector {} overlaps with header", file, k, i1); -- this.offsets.put(k, 0); -+ //this.offsets.put(k, 0); // Tuinity - we catch this, but need it in the header for the summary change - } else if (j1 == 0) { - RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; size has to be > 0", file, k); -- this.offsets.put(k, 0); -+ //this.offsets.put(k, 0); // Tuinity - we catch this, but need it in the header for the summary change - } else if ((long) i1 * 4096L > j) { - RegionFile.LOGGER.warn("Region file {} has an invalid sector at index: {}; sector {} is out of bounds", file, k, i1); -- this.offsets.put(k, 0); -+ //this.offsets.put(k, 0); // Tuinity - we catch this, but need it in the header for the summary change - } else { -- this.usedSectors.force(i1, j1); -+ //this.usedSectors.force(i1, j1); // Tuinity - move this down so we can check if it fails to allocate -+ } -+ // Tuinity start - recalculate header on header corruption -+ if (offset < 2 || sectorLength <= 0 || ((long)offset * 4096L) > regionFileSize) { -+ if (canRecalcHeader) { -+ LOGGER.error("Detected invalid header for regionfile " + this.regionFile.getAbsolutePath() + "! Recalculating header..."); -+ needsHeaderRecalc = true; -+ break; -+ } else { -+ // location = chunkX | (chunkZ << 5); -+ LOGGER.fatal("Detected invalid header for regionfile " + this.regionFile.getAbsolutePath() + -+ "! Cannot recalculate, removing local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") from header"); -+ if (!hasBackedUp) { -+ hasBackedUp = true; -+ this.backupRegionFile(); -+ } -+ this.timestamps.put(headerLocation, 0); // be consistent, delete the timestamp too -+ this.offsets.put(headerLocation, 0); // delete the entry from header -+ continue; -+ } - } -+ boolean failedToAllocate = !this.usedSectors.tryAllocate(offset, sectorLength); -+ if (failedToAllocate) { -+ LOGGER.error("Overlapping allocation by local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") in regionfile " + this.regionFile.getAbsolutePath()); -+ } -+ if (failedToAllocate & !canRecalcHeader) { -+ // location = chunkX | (chunkZ << 5); -+ LOGGER.fatal("Detected invalid header for regionfile " + this.regionFile.getAbsolutePath() + -+ "! Cannot recalculate, removing local chunk (" + (headerLocation & 31) + "," + (headerLocation >>> 5) + ") from header"); -+ if (!hasBackedUp) { -+ hasBackedUp = true; -+ this.backupRegionFile(); -+ } -+ this.timestamps.put(headerLocation, 0); // be consistent, delete the timestamp too -+ this.offsets.put(headerLocation, 0); // delete the entry from header -+ continue; -+ } -+ needsHeaderRecalc |= failedToAllocate; -+ // Tuinity end - recalculate header on header corruption - } - } -+ // Tuinity start - recalculate header on header corruption -+ // we move the recalc here so comparison to old header is correct when logging to console -+ if (needsHeaderRecalc) { // true if header gave us overlapping allocations or had other issues -+ LOGGER.error("Recalculating regionfile " + this.regionFile.getAbsolutePath() + ", header gave erroneous offsets & locations"); -+ this.recalculateHeader(); -+ } -+ // Tuinity end - } - - } - } - - private Path getExternalChunkPath(ChunkPos chunkPos) { -- String s = "c." + chunkPos.x + "." + chunkPos.z + ".mcc"; -+ String s = "c." + chunkPos.x + "." + chunkPos.z + ".mcc"; // Tuinity - diff on change - - return this.externalFileDir.resolve(s); - } - -+ // Tuinity start -+ private static ChunkPos getOversizedChunkPair(File file) { -+ String fileName = file.getName(); -+ -+ if (!fileName.startsWith("c.") || !fileName.endsWith(".mcc")) { -+ return null; -+ } -+ -+ String[] split = fileName.split("\\."); -+ -+ if (split.length != 4) { -+ return null; -+ } -+ -+ try { -+ int x = Integer.parseInt(split[1]); -+ int z = Integer.parseInt(split[2]); -+ -+ return new ChunkPos(x, z); -+ } catch (NumberFormatException ex) { -+ return null; -+ } -+ } -+ // Tuinity end -+ - @Nullable - public synchronized DataInputStream getChunkDataInputStream(ChunkPos pos) throws IOException { - int i = this.getOffset(pos); -@@ -177,6 +595,12 @@ public class RegionFile implements AutoCloseable { - ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error - if (bytebuffer.remaining() < 5) { - RegionFile.LOGGER.error("Chunk {} header is truncated: expected {} but read {}", pos, l, bytebuffer.remaining()); -+ // Tuinity start - recalculate header on regionfile corruption -+ if (this.canRecalcHeader) { -+ this.recalculateHeader(); -+ return this.getChunkDataInputStream(pos); -+ } -+ // Tuinity end - recalculate header on regionfile corruption - return null; - } else { - int i1 = bytebuffer.getInt(); -@@ -184,6 +608,12 @@ public class RegionFile implements AutoCloseable { - - if (i1 == 0) { - RegionFile.LOGGER.warn("Chunk {} is allocated, but stream is missing", pos); -+ // Tuinity start - recalculate header on regionfile corruption -+ if (this.canRecalcHeader) { -+ this.recalculateHeader(); -+ return this.getChunkDataInputStream(pos); -+ } -+ // Tuinity end - recalculate header on regionfile corruption - return null; - } else { - int j1 = i1 - 1; -@@ -191,17 +621,49 @@ public class RegionFile implements AutoCloseable { - if (RegionFile.isExternalStreamChunk(b0)) { - if (j1 != 0) { - RegionFile.LOGGER.warn("Chunk has both internal and external streams"); -+ // Tuinity start - recalculate header on regionfile corruption -+ if (this.canRecalcHeader) { -+ this.recalculateHeader(); -+ return this.getChunkDataInputStream(pos); -+ } -+ // Tuinity end - recalculate header on regionfile corruption - } - -- return this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(b0)); -+ // Tuinity start - recalculate header on regionfile corruption -+ final DataInputStream ret = this.createExternalChunkInputStream(pos, RegionFile.getExternalChunkVersion(b0)); -+ if (ret == null && this.canRecalcHeader) { -+ this.recalculateHeader(); -+ return this.getChunkDataInputStream(pos); -+ } -+ return ret; -+ // Tuinity end - recalculate header on regionfile corruption - } else if (j1 > bytebuffer.remaining()) { - RegionFile.LOGGER.error("Chunk {} stream is truncated: expected {} but read {}", pos, j1, bytebuffer.remaining()); -+ // Tuinity start - recalculate header on regionfile corruption -+ if (this.canRecalcHeader) { -+ this.recalculateHeader(); -+ return this.getChunkDataInputStream(pos); -+ } -+ // Tuinity end - recalculate header on regionfile corruption - return null; - } else if (j1 < 0) { - RegionFile.LOGGER.error("Declared size {} of chunk {} is negative", i1, pos); -+ // Tuinity start - recalculate header on regionfile corruption -+ if (this.canRecalcHeader) { -+ this.recalculateHeader(); -+ return this.getChunkDataInputStream(pos); -+ } -+ // Tuinity end - recalculate header on regionfile corruption - return null; - } else { -- return this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1)); -+ // Tuinity start - recalculate header on regionfile corruption -+ final DataInputStream ret = this.createChunkInputStream(pos, b0, RegionFile.createStream(bytebuffer, j1)); -+ if (ret == null && this.canRecalcHeader) { -+ this.recalculateHeader(); -+ return this.getChunkDataInputStream(pos); -+ } -+ return ret; -+ // Tuinity end - recalculate header on regionfile corruption - } - } - } -@@ -376,10 +838,15 @@ public class RegionFile implements AutoCloseable { - } - - private ByteBuffer createExternalStub() { -+ // Tuinity start - add version param -+ return this.createExternalStub(this.version); -+ } -+ private ByteBuffer createExternalStub(RegionFileVersion version) { -+ // Tuinity end - add version param - ByteBuffer bytebuffer = ByteBuffer.allocate(5); - - bytebuffer.putInt(1); -- bytebuffer.put((byte) (this.version.getId() | 128)); -+ bytebuffer.put((byte) (version.getId() | 128)); // Tuinity - replace with version param - ((java.nio.Buffer) bytebuffer).flip(); // CraftBukkit - decompile error - return bytebuffer; - } -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -index 6496108953effae82391b5c1ea6fdec8482731cd..a6f831fea2245e2d1f44ffa60f96b6f1243b888b 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileStorage.java -@@ -25,7 +25,15 @@ public class RegionFileStorage implements AutoCloseable { - private final File folder; - private final boolean sync; - -+ private final boolean isChunkData; // Tuinity -+ - RegionFileStorage(File directory, boolean dsync) { -+ // Tuinity start - add isChunkData param -+ this(directory, dsync, false); -+ } -+ RegionFileStorage(File directory, boolean dsync, boolean isChunkData) { -+ this.isChunkData = isChunkData; -+ // Tuinity end - add isChunkData param - this.folder = directory; - this.sync = dsync; - } -@@ -90,9 +98,9 @@ public class RegionFileStorage implements AutoCloseable { - - File file = this.folder; - int j = chunkcoordintpair.getRegionX(); -- File file1 = new File(file, "r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); -+ File file1 = new File(file, "r." + j + "." + chunkcoordintpair.getRegionZ() + ".mca"); // Tuinity - diff on change - if (existingOnly && !file1.exists()) return null; // CraftBukkit -- RegionFile regionfile1 = new RegionFile(file1, this.folder, this.sync); -+ RegionFile regionfile1 = new RegionFile(file1, this.folder, this.sync, this.isChunkData); // Tuinity - allow for chunk regionfiles to regen header - - this.regionCache.putAndMoveToFirst(i, regionfile1); - // Paper start -@@ -180,6 +188,13 @@ public class RegionFileStorage implements AutoCloseable { - if (regionfile == null) { - return null; - } -+ // Tuinity start - Add regionfile parameter -+ return this.read(pos, regionfile); -+ } -+ public CompoundTag read(ChunkPos pos, RegionFile regionfile) throws IOException { -+ // We add the regionfile parameter to avoid the potential deadlock (on fileLock) if we went back to obtain a regionfile -+ // if we decide to re-read -+ // Tuinity end - // CraftBukkit end - try { // Paper - DataInputStream datainputstream = regionfile.getChunkDataInputStream(pos); -@@ -196,6 +211,17 @@ public class RegionFileStorage implements AutoCloseable { - try { - if (datainputstream != null) { - nbttagcompound = NbtIo.read((DataInput) datainputstream); -+ // Tuinity start - recover from corrupt regionfile header -+ if (this.isChunkData) { -+ ChunkPos chunkPos = ChunkSerializer.getChunkCoordinate(nbttagcompound); -+ if (!chunkPos.equals(pos)) { -+ MinecraftServer.LOGGER.error("Attempting to read chunk data at " + pos.toString() + " but got chunk data for " + chunkPos.toString() + " instead! Attempting regionfile recalculation for regionfile " + regionfile.regionFile.getAbsolutePath()); -+ regionfile.recalculateHeader(); -+ regionfile.fileLock.lock(); // otherwise we will unlock twice and only lock once. -+ return this.read(pos, regionfile); -+ } -+ } -+ // Tuinity end - recover from corrupt regionfile header - break label43; - } - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java -index b7835b9b904e7d4bff64f7189049e334f5ab4d6f..ae638ac0a0557de204471fef4b03bdb0ad310b2b 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/RegionFileVersion.java -@@ -12,7 +12,7 @@ import java.util.zip.InflaterInputStream; - import javax.annotation.Nullable; - - public class RegionFileVersion { -- private static final Int2ObjectMap VERSIONS = new Int2ObjectOpenHashMap<>(); -+ public static final Int2ObjectMap VERSIONS = new Int2ObjectOpenHashMap<>(); // Tuinity - public - public static final RegionFileVersion VERSION_GZIP = register(new RegionFileVersion(1, GZIPInputStream::new, GZIPOutputStream::new)); - public static final RegionFileVersion VERSION_DEFLATE = register(new RegionFileVersion(2, InflaterInputStream::new, DeflaterOutputStream::new)); - public static final RegionFileVersion VERSION_NONE = register(new RegionFileVersion(3, (inputStream) -> { -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -index 90f7b06bd2c558be35c4577044fa033e1fb5cc22..8f244db7e46ac1a3d2c8358f001d488900a76926 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -@@ -61,11 +61,11 @@ public class SectionStorage extends RegionFileStorage implements AutoCloseabl - } - - @Nullable -- protected Optional get(long pos) { -+ public Optional get(long pos) { // Tuinity - public - return this.storage.get(pos); - } - -- protected Optional getOrLoad(long pos) { -+ public Optional getOrLoad(long pos) { // Tuinity - public - if (this.outsideStoredRange(pos)) { - return Optional.empty(); - } else { -diff --git a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java -index f01182a0ac8a14bcd5b1deb778306e7bf1bf70ed..2cfc54a577d0a63a504e24bc54fd763fe51083e5 100644 ---- a/src/main/java/net/minecraft/world/level/entity/EntityTickList.java -+++ b/src/main/java/net/minecraft/world/level/entity/EntityTickList.java -@@ -9,54 +9,40 @@ import javax.annotation.Nullable; - import net.minecraft.world.entity.Entity; - - public class EntityTickList { -- private Int2ObjectMap active = new Int2ObjectLinkedOpenHashMap<>(); -- private Int2ObjectMap passive = new Int2ObjectLinkedOpenHashMap<>(); -- @Nullable -- private Int2ObjectMap iterated; -+ private final com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet entities = new com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet<>(true); // Tuinity - rewrite this, always keep this updated - why would we EVER tick an entity that's not ticking? - - private void ensureActiveIsNotIterated() { -- if (this.iterated == this.active) { -- this.passive.clear(); -- -- for(Entry entry : Int2ObjectMaps.fastIterable(this.active)) { -- this.passive.put(entry.getIntKey(), entry.getValue()); -- } -- -- Int2ObjectMap int2ObjectMap = this.active; -- this.active = this.passive; -- this.passive = int2ObjectMap; -- } -+ // Tuinity - replace with better logic, do not delay removals - - } - - public void add(Entity entity) { - this.ensureActiveIsNotIterated(); -- this.active.put(entity.getId(), entity); -+ this.entities.add(entity); // Tuinity - replace with better logic, do not delay removals/additions - } - - public void remove(Entity entity) { - this.ensureActiveIsNotIterated(); -- this.active.remove(entity.getId()); -+ this.entities.remove(entity); // Tuinity - replace with better logic, do not delay removals/additions - } - - public boolean contains(Entity entity) { -- return this.active.containsKey(entity.getId()); -+ return this.entities.contains(entity); // Tuinity - replace with better logic, do not delay removals/additions - } - - public void forEach(Consumer action) { -- if (this.iterated != null) { -- throw new UnsupportedOperationException("Only one concurrent iteration supported"); -- } else { -- this.iterated = this.active; -- -- try { -- for(Entity entity : this.active.values()) { -- action.accept(entity); -- } -- } finally { -- this.iterated = null; -+ // Tuinity start - replace with better logic, do not delay removals/additions -+ // To ensure nothing weird happens with dimension travelling, do not iterate over new entries... -+ // (by dfl iterator() is configured to not iterate over new entries) -+ com.tuinity.tuinity.util.maplist.IteratorSafeOrderedReferenceSet.Iterator iterator = this.entities.iterator(); -+ try { -+ while (iterator.hasNext()) { -+ action.accept(iterator.next()); - } -- -+ } finally { -+ iterator.finishedIterating(); - } -+ -+ // Tuinity end - replace with better logic, do not delay removals/additions - } - } -diff --git a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -index 79e733b3ea2e6589d60f3b322244479d2b3b9f86..0465235016d00a9db3694b920206b1cd3ae0824c 100644 ---- a/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -+++ b/src/main/java/net/minecraft/world/level/entity/PersistentEntitySectionManager.java -@@ -41,8 +41,10 @@ public class PersistentEntitySectionManager implements A - private final Long2ObjectMap chunkLoadStatuses = new Long2ObjectOpenHashMap<>(); - private final LongSet chunksToUnload = new LongOpenHashSet(); - private final Queue> loadingInbox = Queues.newConcurrentLinkedQueue(); -+ public final com.tuinity.tuinity.world.EntitySliceManager entitySliceManager; // Tuinity - -- public PersistentEntitySectionManager(Class entityClass, LevelCallback handler, EntityPersistentStorage dataAccess) { -+ public PersistentEntitySectionManager(Class entityClass, LevelCallback handler, EntityPersistentStorage dataAccess, com.tuinity.tuinity.world.EntitySliceManager entitySliceManager) { // Tuinity -+ this.entitySliceManager = entitySliceManager; // Tuinity - this.visibleEntityStorage = new EntityLookup<>(); - this.sectionStorage = new EntitySectionStorage<>(entityClass, this.chunkVisibility); - this.chunkVisibility.defaultReturnValue(Visibility.HIDDEN); -@@ -52,6 +54,65 @@ public class PersistentEntitySectionManager implements A - this.entityGetter = new LevelEntityGetterAdapter<>(this.visibleEntityStorage, this.sectionStorage); - } - -+ // Tuinity start - optimise notify() -+ public final void removeNavigatorsFromData(Entity entity, final int chunkX, final int chunkZ) { -+ if (!(entity instanceof net.minecraft.world.entity.Mob)) { -+ return; -+ } -+ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section = -+ this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(chunkX, chunkZ); -+ if (section != null) { -+ net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData; -+ sectionData.removeFromNavigators(section, ((net.minecraft.world.entity.Mob)entity)); -+ } -+ } -+ -+ public final void removeNavigatorsFromData(Entity entity) { -+ if (!(entity instanceof net.minecraft.world.entity.Mob)) { -+ return; -+ } -+ BlockPos entityPos = entity.blockPosition(); -+ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section = -+ this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(entityPos.getX() >> 4, entityPos.getZ() >> 4); -+ if (section != null) { -+ net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData; -+ sectionData.removeFromNavigators(section, ((net.minecraft.world.entity.Mob)entity)); -+ } -+ } -+ -+ public final void addNavigatorsIfPathingToRegion(Entity entity) { -+ if (!(entity instanceof net.minecraft.world.entity.Mob)) { -+ return; -+ } -+ BlockPos entityPos = entity.blockPosition(); -+ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section = -+ this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(entityPos.getX() >> 4, entityPos.getZ() >> 4); -+ if (section != null) { -+ net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData; -+ if (((net.minecraft.world.entity.Mob)entity).getNavigation().isViableForPathRecalculationChecking()) { -+ sectionData.addToNavigators(section, ((net.minecraft.world.entity.Mob)entity)); -+ } -+ } -+ } -+ -+ public final void updateNavigatorsInRegion(Entity entity) { -+ if (!(entity instanceof net.minecraft.world.entity.Mob)) { -+ return; -+ } -+ BlockPos entityPos = entity.blockPosition(); -+ com.tuinity.tuinity.chunk.SingleThreadChunkRegionManager.RegionSection section = -+ this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.getRegionSection(entityPos.getX() >> 4, entityPos.getZ() >> 4); -+ if (section != null) { -+ net.minecraft.server.level.ChunkMap.DataRegionSectionData sectionData = (net.minecraft.server.level.ChunkMap.DataRegionSectionData)section.sectionData; -+ if (((net.minecraft.world.entity.Mob)entity).getNavigation().isViableForPathRecalculationChecking()) { -+ sectionData.addToNavigators(section, ((net.minecraft.world.entity.Mob)entity)); -+ } else { -+ sectionData.removeFromNavigators(section, ((net.minecraft.world.entity.Mob)entity)); -+ } -+ } -+ } -+ // Tuinity end - optimise notify() -+ - void removeSectionIfEmpty(long sectionPos, EntitySection section) { - if (section.isEmpty()) { - this.sectionStorage.remove(sectionPos); -@@ -93,6 +154,7 @@ public class PersistentEntitySectionManager implements A - long l = SectionPos.asLong(entity.blockPosition()); - EntitySection entitySection = this.sectionStorage.getOrCreateSection(l); - entitySection.add(entity); -+ this.entitySliceManager.addEntity((Entity)entity); // Tuinity - entity.setLevelCallback(new PersistentEntitySectionManager.Callback(entity, l, entitySection)); - if (!existing) { - this.callbacks.onCreated(entity); -@@ -147,6 +209,7 @@ public class PersistentEntitySectionManager implements A - - public void updateChunkStatus(ChunkPos chunkPos, ChunkHolder.FullChunkStatus levelType) { - Visibility visibility = Visibility.fromFullChunkStatus(levelType); -+ this.entitySliceManager.chunkStatusChange(chunkPos.x, chunkPos.z, levelType); // Tuinity - this.updateChunkStatus(chunkPos, visibility); - } - -@@ -381,18 +444,38 @@ public class PersistentEntitySectionManager implements A - @Override - public void onMove() { - BlockPos blockPos = this.entity.blockPosition(); -- long l = SectionPos.asLong(blockPos); -+ long l = SectionPos.asLong(blockPos); // Tuinity - diff on change, new position section - if (l != this.currentSectionKey) { -- Visibility visibility = this.currentSection.getStatus(); -+ PersistentEntitySectionManager.this.entitySliceManager.moveEntity((Entity)this.entity); // Tuinity -+ // Tuinity start -+ int shift = PersistentEntitySectionManager.this.entitySliceManager.world.getChunkSource().chunkMap.dataRegionManager.regionChunkShift; -+ int oldChunkX = com.tuinity.tuinity.util.CoordinateUtils.getChunkSectionX(this.currentSectionKey); -+ int oldChunkZ = com.tuinity.tuinity.util.CoordinateUtils.getChunkSectionZ(this.currentSectionKey); -+ int oldRegionX = oldChunkX >> shift; -+ int oldRegionZ = oldChunkZ >> shift; -+ -+ int newRegionX = com.tuinity.tuinity.util.CoordinateUtils.getChunkSectionX(l) >> shift; -+ int newRegionZ = com.tuinity.tuinity.util.CoordinateUtils.getChunkSectionZ(l) >> shift; -+ -+ if (oldRegionX != newRegionX || oldRegionZ != newRegionZ) { -+ PersistentEntitySectionManager.this.removeNavigatorsFromData((Entity)this.entity, oldChunkX, oldChunkZ); -+ } -+ // Tuinity end -+ Visibility visibility = this.currentSection.getStatus(); // Tuinity - diff on change - this should be OLD section visibility - if (!this.currentSection.remove(this.entity)) { - PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (moving to {})", this.entity, SectionPos.of(this.currentSectionKey), l); - } - - PersistentEntitySectionManager.this.removeSectionIfEmpty(this.currentSectionKey, this.currentSection); -- EntitySection entitySection = PersistentEntitySectionManager.this.sectionStorage.getOrCreateSection(l); -+ EntitySection entitySection = PersistentEntitySectionManager.this.sectionStorage.getOrCreateSection(l); // Tuinity - diff on change, this should be NEW section - entitySection.add(this.entity); - this.currentSection = entitySection; - this.currentSectionKey = l; -+ // Tuinity start -+ if ((oldRegionX != newRegionX || oldRegionZ != newRegionZ) && visibility.isTicking() && entitySection.getStatus().isTicking()) { -+ PersistentEntitySectionManager.this.addNavigatorsIfPathingToRegion((Entity)this.entity); -+ } -+ // Tuinity end - this.updateStatus(visibility, entitySection.getStatus()); - } - -@@ -426,6 +509,7 @@ public class PersistentEntitySectionManager implements A - if (!this.currentSection.remove(this.entity)) { - PersistentEntitySectionManager.LOGGER.warn("Entity {} wasn't found in section {} (destroying due to {})", this.entity, SectionPos.of(this.currentSectionKey), reason); - } -+ PersistentEntitySectionManager.this.entitySliceManager.removeEntity((Entity)this.entity); // Tuinity - - Visibility visibility = PersistentEntitySectionManager.getEffectiveStatus(this.entity, this.currentSection.getStatus()); - if (visibility.isTicking()) { -diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/blockplacers/ColumnPlacer.java b/src/main/java/net/minecraft/world/level/levelgen/feature/blockplacers/ColumnPlacer.java -index 05bba5410fbd9f8e333584ccbd65a909f3040322..de3122f450edacaf2eed6f60b0680ebe64f7d214 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/feature/blockplacers/ColumnPlacer.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/feature/blockplacers/ColumnPlacer.java -@@ -10,11 +10,28 @@ import net.minecraft.world.level.LevelAccessor; - import net.minecraft.world.level.block.state.BlockState; - - public class ColumnPlacer extends BlockPlacer { -+ // Tuinity start - public static final Codec CODEC = RecordCodecBuilder.create((instance) -> { -- return instance.group(IntProvider.NON_NEGATIVE_CODEC.fieldOf("size").forGetter((columnPlacer) -> { -- return columnPlacer.size; -- })).apply(instance, ColumnPlacer::new); -+ return instance.group( -+ IntProvider.NON_NEGATIVE_CODEC.optionalFieldOf("size").forGetter((columnPlacer) -> { -+ return java.util.Optional.of(columnPlacer.size); -+ }), -+ Codec.INT.optionalFieldOf("min_size").forGetter((columnPlacer) -> { -+ return java.util.Optional.empty(); -+ }), -+ Codec.INT.optionalFieldOf("extra_size").forGetter((columnPlacer) -> { -+ return java.util.Optional.empty(); -+ }) -+ ).apply(instance, ColumnPlacer::new); - }); -+ public ColumnPlacer(java.util.Optional size, java.util.Optional minSize, java.util.Optional extraSize) { -+ if (size.isPresent()) { -+ this.size = size.get(); -+ } else { -+ this.size = net.minecraft.util.valueproviders.BiasedToBottomInt.of(minSize.get().intValue(), minSize.get().intValue() + extraSize.get().intValue()); -+ } -+ } -+ // Tuinity end - private final IntProvider size; - - public ColumnPlacer(IntProvider size) { -diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/configurations/TreeConfiguration.java b/src/main/java/net/minecraft/world/level/levelgen/feature/configurations/TreeConfiguration.java -index 5da68897148192905c2747676c1ee2ee649f923f..b990099cf274f8cb0d96c139345cf0bf328affd6 100644 ---- a/src/main/java/net/minecraft/world/level/levelgen/feature/configurations/TreeConfiguration.java -+++ b/src/main/java/net/minecraft/world/level/levelgen/feature/configurations/TreeConfiguration.java -@@ -18,13 +18,13 @@ public class TreeConfiguration implements FeatureConfiguration { - return treeConfiguration.trunkProvider; - }), TrunkPlacer.CODEC.fieldOf("trunk_placer").forGetter((treeConfiguration) -> { - return treeConfiguration.trunkPlacer; -- }), BlockStateProvider.CODEC.fieldOf("foliage_provider").forGetter((treeConfiguration) -> { -+ }), net.minecraft.server.MCUtil.fieldWithFallbacks(BlockStateProvider.CODEC, "foliage_provider", "leaves_provider").forGetter((treeConfiguration) -> { // Paper - provide fallback for rename - return treeConfiguration.foliageProvider; -- }), BlockStateProvider.CODEC.fieldOf("sapling_provider").forGetter((treeConfiguration) -> { -+ }), BlockStateProvider.CODEC.optionalFieldOf("sapling_provider", new SimpleStateProvider(Blocks.OAK_SAPLING.defaultBlockState())).forGetter((treeConfiguration) -> { // Paper - provide default - it looks like for now this is OK because it's just used to check canSurvive. Same check happens in 1.16.5 for the default we provide - so it should retain behavior... - return treeConfiguration.saplingProvider; - }), FoliagePlacer.CODEC.fieldOf("foliage_placer").forGetter((treeConfiguration) -> { - return treeConfiguration.foliagePlacer; -- }), BlockStateProvider.CODEC.fieldOf("dirt_provider").forGetter((treeConfiguration) -> { -+ }), BlockStateProvider.CODEC.optionalFieldOf("dirt_provider", new SimpleStateProvider(Blocks.DIRT.defaultBlockState())).forGetter((treeConfiguration) -> { // Paper - provide defaults, old data DOES NOT have this key (thankfully ALL OLD DATA used DIRT) - return treeConfiguration.dirtProvider; - }), FeatureSize.CODEC.fieldOf("minimum_size").forGetter((treeConfiguration) -> { - return treeConfiguration.minimumSize; -diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -index d5ba2e679ed1858ea18e18feffce50544ae036c2..78da12a3feb05a5504daf8379be3d568c389a458 100644 ---- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -+++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -@@ -52,16 +52,37 @@ public class PortalForcer { - // int i = flag ? 16 : 128; - // CraftBukkit end - -- villageplace.ensureLoadedAndValid(this.level, blockposition, i); -- Optional optional = villageplace.getInSquare((villageplacetype) -> { -- return villageplacetype == PoiType.NETHER_PORTAL; -- }, blockposition, i, PoiManager.Occupancy.ANY).sorted(Comparator.comparingDouble((PoiRecord villageplacerecord) -> { // CraftBukkit - decompile error -- return villageplacerecord.getPos().distSqr(blockposition); -- }).thenComparingInt((villageplacerecord) -> { -- return villageplacerecord.getPos().getY(); -- })).filter((villageplacerecord) -> { -- return this.level.getBlockState(villageplacerecord.getPos()).hasProperty(BlockStateProperties.HORIZONTAL_AXIS); -- }).findFirst(); -+ // Tuinity start - optimise portals -+ Optional optional; -+ java.util.List records = new java.util.ArrayList<>(); -+ com.tuinity.tuinity.util.PoiAccess.findClosestPoiDataRecords( -+ villageplace, -+ (PoiType type) -> { -+ return type == PoiType.NETHER_PORTAL; -+ }, -+ (BlockPos pos) -> { -+ net.minecraft.world.level.chunk.ChunkAccess lowest = this.level.getChunk(pos.getX() >> 4, pos.getZ() >> 4, net.minecraft.world.level.chunk.ChunkStatus.EMPTY); -+ if (!lowest.getStatus().isOrAfter(net.minecraft.world.level.chunk.ChunkStatus.FULL)) { -+ // why would we generate the chunk? -+ return false; -+ } -+ return lowest.getBlockState(pos).hasProperty(BlockStateProperties.HORIZONTAL_AXIS); -+ }, -+ blockposition, i, Double.MAX_VALUE, PoiManager.Occupancy.ANY, true, records -+ ); -+ -+ // this gets us most of the way there, but we bias towards lower y values. -+ PoiRecord lowestYRecord = null; -+ for (PoiRecord record : records) { -+ if (lowestYRecord == null) { -+ lowestYRecord = record; -+ } else if (lowestYRecord.getPos().getY() > record.getPos().getY()) { -+ lowestYRecord = record; -+ } -+ } -+ // now we're done -+ optional = Optional.ofNullable(lowestYRecord); -+ // Tuinity end - optimise portals - - return optional.map((villageplacerecord) -> { - BlockPos blockposition1 = villageplacerecord.getPos(); -diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java -index 120498a39b7ca7aee9763084507508d4a1c425aa..6f7e6429c35eea346517cbf08cf223fc6d838a8c 100644 ---- a/src/main/java/net/minecraft/world/phys/AABB.java -+++ b/src/main/java/net/minecraft/world/phys/AABB.java -@@ -25,6 +25,17 @@ public class AABB { - this.maxZ = Math.max(z1, z2); - } - -+ // Tuinity start -+ public AABB(double minX, double minY, double minZ, double maxX, double maxY, double maxZ, boolean dummy) { -+ this.minX = minX; -+ this.minY = minY; -+ this.minZ = minZ; -+ this.maxX = maxX; -+ this.maxY = maxY; -+ this.maxZ = maxZ; -+ } -+ // Tuinity end -+ - public AABB(BlockPos pos) { - this((double)pos.getX(), (double)pos.getY(), (double)pos.getZ(), (double)(pos.getX() + 1), (double)(pos.getY() + 1), (double)(pos.getZ() + 1)); - } -diff --git a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java -index 99427b6130895ddecee8bcf77db72d809c24c375..af1ef430e81cb9bdd749aa235577c63fa381f4c5 100644 ---- a/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java -+++ b/src/main/java/net/minecraft/world/phys/shapes/ArrayVoxelShape.java -@@ -6,6 +6,9 @@ import java.util.Arrays; - import net.minecraft.Util; - import net.minecraft.core.Direction; - -+// Tuinity start -+import it.unimi.dsi.fastutil.doubles.AbstractDoubleList; -+// Tuinity end - public class ArrayVoxelShape extends VoxelShape { - private final DoubleList xs; - private final DoubleList ys; -@@ -16,6 +19,11 @@ public class ArrayVoxelShape extends VoxelShape { - } - - ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xPoints, DoubleList yPoints, DoubleList zPoints) { -+ // Tuinity start - optimise multi-aabb shapes -+ this(shape, xPoints, yPoints, zPoints, null, 0.0, 0.0, 0.0); -+ } -+ ArrayVoxelShape(DiscreteVoxelShape shape, DoubleList xPoints, DoubleList yPoints, DoubleList zPoints, net.minecraft.world.phys.AABB[] boundingBoxesRepresentation, double offsetX, double offsetY, double offsetZ) { -+ // Tuinity end - optimise multi-aabb shapes - super(shape); - int i = shape.getXSize() + 1; - int j = shape.getYSize() + 1; -@@ -27,6 +35,12 @@ public class ArrayVoxelShape extends VoxelShape { - } else { - throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException("Lengths of point arrays must be consistent with the size of the VoxelShape.")); - } -+ // Tuinity start - optimise multi-aabb shapes -+ this.boundingBoxesRepresentation = boundingBoxesRepresentation == null ? this.toAabbs().toArray(EMPTY) : boundingBoxesRepresentation; -+ this.offsetX = offsetX; -+ this.offsetY = offsetY; -+ this.offsetZ = offsetZ; -+ // Tuinity end - optimise multi-aabb shapes - } - - @Override -@@ -42,4 +56,152 @@ public class ArrayVoxelShape extends VoxelShape { - throw new IllegalArgumentException(); - } - } -+ -+ // Tuinity start -+ public static final class DoubleListOffsetExposed extends AbstractDoubleList { -+ -+ public final DoubleArrayList list; -+ public final double offset; -+ -+ public DoubleListOffsetExposed(final DoubleArrayList list, final double offset) { -+ this.list = list; -+ this.offset = offset; -+ } -+ -+ @Override -+ public double getDouble(final int index) { -+ return this.list.getDouble(index) + this.offset; -+ } -+ -+ @Override -+ public int size() { -+ return this.list.size(); -+ } -+ } -+ -+ static final net.minecraft.world.phys.AABB[] EMPTY = new net.minecraft.world.phys.AABB[0]; -+ final net.minecraft.world.phys.AABB[] boundingBoxesRepresentation; -+ -+ final double offsetX; -+ final double offsetY; -+ final double offsetZ; -+ -+ public final net.minecraft.world.phys.AABB[] getBoundingBoxesRepresentation() { -+ return this.boundingBoxesRepresentation; -+ } -+ -+ public final double getOffsetX() { -+ return this.offsetX; -+ } -+ -+ public final double getOffsetY() { -+ return this.offsetY; -+ } -+ -+ public final double getOffsetZ() { -+ return this.offsetZ; -+ } -+ -+ @Override -+ public java.util.List toAabbs() { -+ if (this.boundingBoxesRepresentation == null) { -+ return super.toAabbs(); -+ } -+ java.util.List ret = new java.util.ArrayList<>(this.boundingBoxesRepresentation.length); -+ -+ double offX = this.offsetX; -+ double offY = this.offsetY; -+ double offZ = this.offsetZ; -+ -+ for (net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) { -+ ret.add(boundingBox.move(offX, offY, offZ)); -+ } -+ -+ return ret; -+ } -+ -+ protected static DoubleArrayList getList(DoubleList from) { -+ if (from instanceof DoubleArrayList) { -+ return (DoubleArrayList)from; -+ } else { -+ return DoubleArrayList.wrap(from.toDoubleArray()); -+ } -+ } -+ -+ @Override -+ public VoxelShape move(double x, double y, double z) { -+ if (x == 0.0 && y == 0.0 && z == 0.0) { -+ return this; -+ } -+ DoubleListOffsetExposed xPoints, yPoints, zPoints; -+ double offsetX, offsetY, offsetZ; -+ -+ if (this.xs instanceof DoubleListOffsetExposed) { -+ xPoints = new DoubleListOffsetExposed(((DoubleListOffsetExposed)this.xs).list, offsetX = this.offsetX + x); -+ yPoints = new DoubleListOffsetExposed(((DoubleListOffsetExposed)this.ys).list, offsetY = this.offsetY + y); -+ zPoints = new DoubleListOffsetExposed(((DoubleListOffsetExposed)this.zs).list, offsetZ = this.offsetZ + z); -+ } else { -+ xPoints = new DoubleListOffsetExposed(getList(this.xs), offsetX = x); -+ yPoints = new DoubleListOffsetExposed(getList(this.ys), offsetY = y); -+ zPoints = new DoubleListOffsetExposed(getList(this.zs), offsetZ = z); -+ } -+ -+ return new ArrayVoxelShape(this.shape, xPoints, yPoints, zPoints, this.boundingBoxesRepresentation, offsetX, offsetY, offsetZ); -+ } -+ -+ @Override -+ public final boolean intersects(net.minecraft.world.phys.AABB axisalingedbb) { -+ // this can be optimised by checking an "overall shape" first, but not needed -+ double offX = this.offsetX; -+ double offY = this.offsetY; -+ double offZ = this.offsetZ; -+ -+ for (net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) { -+ if (com.tuinity.tuinity.util.CollisionUtil.voxelShapeIntersect(axisalingedbb, boundingBox.minX + offX, boundingBox.minY + offY, boundingBox.minZ + offZ, -+ boundingBox.maxX + offX, boundingBox.maxY + offY, boundingBox.maxZ + offZ)) { -+ return true; -+ } -+ } -+ -+ return false; -+ } -+ -+ @Override -+ public void forAllBoxes(Shapes.DoubleLineConsumer doubleLineConsumer) { -+ if (this.boundingBoxesRepresentation == null) { -+ super.forAllBoxes(doubleLineConsumer); -+ return; -+ } -+ for (final net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) { -+ doubleLineConsumer.consume(boundingBox.minX + this.offsetX, boundingBox.minY + this.offsetY, boundingBox.minZ + this.offsetZ, -+ boundingBox.maxX + this.offsetX, boundingBox.maxY + this.offsetY, boundingBox.maxZ + this.offsetZ); -+ } -+ } -+ -+ @Override -+ public VoxelShape optimize() { -+ if (this == Shapes.empty() || this.boundingBoxesRepresentation.length == 0) { -+ return this; -+ } -+ -+ VoxelShape simplified = Shapes.empty(); -+ for (final net.minecraft.world.phys.AABB boundingBox : this.boundingBoxesRepresentation) { -+ simplified = Shapes.joinUnoptimized(simplified, Shapes.box(boundingBox.minX + this.offsetX, boundingBox.minY + this.offsetY, boundingBox.minZ + this.offsetZ, -+ boundingBox.maxX + this.offsetX, boundingBox.maxY + this.offsetY, boundingBox.maxZ + this.offsetZ), BooleanOp.OR); -+ } -+ -+ if (!(simplified instanceof ArrayVoxelShape)) { -+ return simplified; -+ } -+ -+ final net.minecraft.world.phys.AABB[] boundingBoxesRepresentation = ((ArrayVoxelShape)simplified).getBoundingBoxesRepresentation(); -+ -+ if (boundingBoxesRepresentation.length == 1) { -+ return new com.tuinity.tuinity.voxel.AABBVoxelShape(boundingBoxesRepresentation[0]).optimize(); -+ } -+ -+ return simplified; -+ } -+ // Tuinity end -+ - } -diff --git a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java -index 16bc18cacbf7a23fb744c8a12e7fd8da699b2fea..472c47a585da7d95b3f4774d3caef1d864b6337a 100644 ---- a/src/main/java/net/minecraft/world/phys/shapes/Shapes.java -+++ b/src/main/java/net/minecraft/world/phys/shapes/Shapes.java -@@ -26,16 +26,17 @@ public final class Shapes { - DiscreteVoxelShape discreteVoxelShape = new BitSetDiscreteVoxelShape(1, 1, 1); - discreteVoxelShape.fill(0, 0, 0); - return new CubeVoxelShape(discreteVoxelShape); -- }); -+ }); public static VoxelShape getFullUnoptimisedCube() { return BLOCK; } // Tuinity - OBFHELPER - public static final VoxelShape INFINITY = box(Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY); - private static final VoxelShape EMPTY = new ArrayVoxelShape(new BitSetDiscreteVoxelShape(0, 0, 0), (DoubleList)(new DoubleArrayList(new double[]{0.0D})), (DoubleList)(new DoubleArrayList(new double[]{0.0D})), (DoubleList)(new DoubleArrayList(new double[]{0.0D}))); -+ public static final com.tuinity.tuinity.voxel.AABBVoxelShape BLOCK_OPTIMISED = new com.tuinity.tuinity.voxel.AABBVoxelShape(new AABB(0.0, 0.0, 0.0, 1.0, 1.0, 1.0)); // Tuinity - - public static VoxelShape empty() { - return EMPTY; - } - - public static VoxelShape block() { -- return BLOCK; -+ return BLOCK_OPTIMISED; // Tuinity - } - - public static VoxelShape box(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { -@@ -47,30 +48,11 @@ public final class Shapes { - } - - public static VoxelShape create(double minX, double minY, double minZ, double maxX, double maxY, double maxZ) { -- if (!(maxX - minX < 1.0E-7D) && !(maxY - minY < 1.0E-7D) && !(maxZ - minZ < 1.0E-7D)) { -- int i = findBits(minX, maxX); -- int j = findBits(minY, maxY); -- int k = findBits(minZ, maxZ); -- if (i >= 0 && j >= 0 && k >= 0) { -- if (i == 0 && j == 0 && k == 0) { -- return block(); -- } else { -- int l = 1 << i; -- int m = 1 << j; -- int n = 1 << k; -- BitSetDiscreteVoxelShape bitSetDiscreteVoxelShape = BitSetDiscreteVoxelShape.withFilledBounds(l, m, n, (int)Math.round(minX * (double)l), (int)Math.round(minY * (double)m), (int)Math.round(minZ * (double)n), (int)Math.round(maxX * (double)l), (int)Math.round(maxY * (double)m), (int)Math.round(maxZ * (double)n)); -- return new CubeVoxelShape(bitSetDiscreteVoxelShape); -- } -- } else { -- return new ArrayVoxelShape(BLOCK.shape, (DoubleList)DoubleArrayList.wrap(new double[]{minX, maxX}), (DoubleList)DoubleArrayList.wrap(new double[]{minY, maxY}), (DoubleList)DoubleArrayList.wrap(new double[]{minZ, maxZ})); -- } -- } else { -- return empty(); -- } -+ return new com.tuinity.tuinity.voxel.AABBVoxelShape(new AABB(minX, minY, minZ, maxX, maxY, maxZ)); // Tuinity - } - - public static VoxelShape create(AABB box) { -- return create(box.minX, box.minY, box.minZ, box.maxX, box.maxY, box.maxZ); -+ return new com.tuinity.tuinity.voxel.AABBVoxelShape(box); // Tuinity - } - - @VisibleForTesting -@@ -132,6 +114,20 @@ public final class Shapes { - } - - public static boolean joinIsNotEmpty(VoxelShape shape1, VoxelShape shape2, BooleanOp predicate) { -+ // Tuinity start - optimise voxelshape -+ if (predicate == BooleanOp.AND) { -+ if (shape1 instanceof com.tuinity.tuinity.voxel.AABBVoxelShape && shape2 instanceof com.tuinity.tuinity.voxel.AABBVoxelShape) { -+ return com.tuinity.tuinity.util.CollisionUtil.voxelShapeIntersect(((com.tuinity.tuinity.voxel.AABBVoxelShape)shape1).aabb, ((com.tuinity.tuinity.voxel.AABBVoxelShape)shape2).aabb); -+ } else if (shape1 instanceof com.tuinity.tuinity.voxel.AABBVoxelShape && shape2 instanceof ArrayVoxelShape) { -+ return ((ArrayVoxelShape)shape2).intersects(((com.tuinity.tuinity.voxel.AABBVoxelShape)shape1).aabb); -+ } else if (shape2 instanceof com.tuinity.tuinity.voxel.AABBVoxelShape && shape1 instanceof ArrayVoxelShape) { -+ return ((ArrayVoxelShape)shape1).intersects(((com.tuinity.tuinity.voxel.AABBVoxelShape)shape2).aabb); -+ } -+ } -+ return joinIsNotEmptyVanilla(shape1, shape2, predicate); -+ } -+ public static boolean joinIsNotEmptyVanilla(VoxelShape shape1, VoxelShape shape2, BooleanOp predicate) { -+ // Tuinity end - optimise voxelshape - if (predicate.apply(false, false)) { - throw (IllegalArgumentException)Util.pauseInIde(new IllegalArgumentException()); - } else { -@@ -285,6 +281,43 @@ public final class Shapes { - } - - public static VoxelShape getFaceShape(VoxelShape shape, Direction direction) { -+ // Tuinity start - optimise shape creation here for lighting, as this shape is going to be used -+ // for transparency checks -+ if (shape == BLOCK || shape == BLOCK_OPTIMISED) { -+ return BLOCK_OPTIMISED; -+ } else if (shape == empty()) { -+ return empty(); -+ } -+ -+ if (shape instanceof com.tuinity.tuinity.voxel.AABBVoxelShape) { -+ final AABB box = ((com.tuinity.tuinity.voxel.AABBVoxelShape)shape).aabb; -+ switch (direction) { -+ case WEST: // -X -+ case EAST: { // +X -+ final boolean useEmpty = direction == Direction.EAST ? !DoubleMath.fuzzyEquals(box.maxX, 1.0, com.tuinity.tuinity.util.CollisionUtil.COLLISION_EPSILON) : -+ !DoubleMath.fuzzyEquals(box.minX, 0.0, com.tuinity.tuinity.util.CollisionUtil.COLLISION_EPSILON); -+ return useEmpty ? empty() : new com.tuinity.tuinity.voxel.AABBVoxelShape(new AABB(0.0, box.minY, box.minZ, 1.0, box.maxY, box.maxZ)).optimize(); -+ } -+ case DOWN: // -Y -+ case UP: { // +Y -+ final boolean useEmpty = direction == Direction.UP ? !DoubleMath.fuzzyEquals(box.maxY, 1.0, com.tuinity.tuinity.util.CollisionUtil.COLLISION_EPSILON) : -+ !DoubleMath.fuzzyEquals(box.minY, 0.0, com.tuinity.tuinity.util.CollisionUtil.COLLISION_EPSILON); -+ return useEmpty ? empty() : new com.tuinity.tuinity.voxel.AABBVoxelShape(new AABB(box.minX, 0.0, box.minZ, box.maxX, 1.0, box.maxZ)).optimize(); -+ } -+ case NORTH: // -Z -+ case SOUTH: { // +Z -+ final boolean useEmpty = direction == Direction.SOUTH ? !DoubleMath.fuzzyEquals(box.maxZ, 1.0, com.tuinity.tuinity.util.CollisionUtil.COLLISION_EPSILON) : -+ !DoubleMath.fuzzyEquals(box.minZ,0.0, com.tuinity.tuinity.util.CollisionUtil.COLLISION_EPSILON); -+ return useEmpty ? empty() : new com.tuinity.tuinity.voxel.AABBVoxelShape(new AABB(box.minX, box.minY, 0.0, box.maxX, box.maxY, 1.0)).optimize(); -+ } -+ } -+ } -+ -+ // fall back to vanilla -+ return getFaceShapeVanilla(shape, direction); -+ } -+ public static VoxelShape getFaceShapeVanilla(VoxelShape shape, Direction direction) { -+ // Tuinity end - if (shape == block()) { - return block(); - } else { -@@ -299,7 +332,7 @@ public final class Shapes { - i = 0; - } - -- return (VoxelShape)(!bl ? empty() : new SliceShape(shape, axis, i)); -+ return (VoxelShape)(!bl ? empty() : new SliceShape(shape, axis, i).optimize().optimize()); // Tuinity - first optimize converts to ArrayVoxelShape, second optimize could convert to AABBVoxelShape - } - } - -@@ -324,6 +357,53 @@ public final class Shapes { - } - - public static boolean faceShapeOccludes(VoxelShape one, VoxelShape two) { -+ // Tuinity start - try to optimise for the case where the shapes do _not_ occlude -+ // which is _most_ of the time in lighting -+ if (one == getFullUnoptimisedCube() || one == BLOCK_OPTIMISED -+ || two == getFullUnoptimisedCube() || two == BLOCK_OPTIMISED) { -+ return true; -+ } -+ boolean v1Empty = one == empty(); -+ boolean v2Empty = two == empty(); -+ if (v1Empty && v2Empty) { -+ return false; -+ } -+ if ((one instanceof com.tuinity.tuinity.voxel.AABBVoxelShape || v1Empty) -+ && (two instanceof com.tuinity.tuinity.voxel.AABBVoxelShape || v2Empty)) { -+ if (!v1Empty && !v2Empty && (one != two)) { -+ AABB boundingBox1 = ((com.tuinity.tuinity.voxel.AABBVoxelShape)one).aabb; -+ AABB boundingBox2 = ((com.tuinity.tuinity.voxel.AABBVoxelShape)two).aabb; -+ // can call it here in some cases -+ -+ // check overall bounding box -+ double minY = Math.min(boundingBox1.minY, boundingBox2.minY); -+ double maxY = Math.max(boundingBox1.maxY, boundingBox2.maxY); -+ if (minY > com.tuinity.tuinity.util.CollisionUtil.COLLISION_EPSILON || maxY < (1 - com.tuinity.tuinity.util.CollisionUtil.COLLISION_EPSILON)) { -+ return false; -+ } -+ double minX = Math.min(boundingBox1.minX, boundingBox2.minX); -+ double maxX = Math.max(boundingBox1.maxX, boundingBox2.maxX); -+ if (minX > com.tuinity.tuinity.util.CollisionUtil.COLLISION_EPSILON || maxX < (1 - com.tuinity.tuinity.util.CollisionUtil.COLLISION_EPSILON)) { -+ return false; -+ } -+ double minZ = Math.min(boundingBox1.minZ, boundingBox2.minZ); -+ double maxZ = Math.max(boundingBox1.maxZ, boundingBox2.maxZ); -+ if (minZ > com.tuinity.tuinity.util.CollisionUtil.COLLISION_EPSILON || maxZ < (1 - com.tuinity.tuinity.util.CollisionUtil.COLLISION_EPSILON)) { -+ return false; -+ } -+ // fall through to full merge check -+ } else { -+ AABB boundingBox = v1Empty ? ((com.tuinity.tuinity.voxel.AABBVoxelShape)two).aabb : ((com.tuinity.tuinity.voxel.AABBVoxelShape)one).aabb; -+ // check if the bounding box encloses the full cube -+ return (boundingBox.minY <= com.tuinity.tuinity.util.CollisionUtil.COLLISION_EPSILON && boundingBox.maxY >= (1 - com.tuinity.tuinity.util.CollisionUtil.COLLISION_EPSILON)) && -+ (boundingBox.minX <= com.tuinity.tuinity.util.CollisionUtil.COLLISION_EPSILON && boundingBox.maxX >= (1 - com.tuinity.tuinity.util.CollisionUtil.COLLISION_EPSILON)) && -+ (boundingBox.minZ <= com.tuinity.tuinity.util.CollisionUtil.COLLISION_EPSILON && boundingBox.maxZ >= (1 - com.tuinity.tuinity.util.CollisionUtil.COLLISION_EPSILON)); -+ } -+ } -+ return faceShapeOccludesVanilla(one, two); -+ } -+ public static boolean faceShapeOccludesVanilla(VoxelShape one, VoxelShape two) { -+ // Tuinity end - if (one != block() && two != block()) { - if (one.isEmpty() && two.isEmpty()) { - return false; -diff --git a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java -index f325d76c79d63629200262a77eab7cdcc9beedfa..ad23eafd6d9e7901f726977ad8404fa34dc0874e 100644 ---- a/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java -+++ b/src/main/java/net/minecraft/world/phys/shapes/VoxelShape.java -@@ -16,11 +16,17 @@ import net.minecraft.world.phys.BlockHitResult; - import net.minecraft.world.phys.Vec3; - - public abstract class VoxelShape { -- protected final DiscreteVoxelShape shape; -+ public final DiscreteVoxelShape shape; // Tuinity - public - @Nullable - private VoxelShape[] faces; - -- VoxelShape(DiscreteVoxelShape voxels) { -+ // Tuinity start -+ public boolean intersects(AABB shape) { -+ return Shapes.joinIsNotEmpty(this, new com.tuinity.tuinity.voxel.AABBVoxelShape(shape), BooleanOp.AND); -+ } -+ // Tuinity end -+ -+ protected VoxelShape(DiscreteVoxelShape voxels) { // Tuinity - protected - this.shape = voxels; - } - -@@ -163,7 +169,7 @@ public abstract class VoxelShape { - } - } - -- private VoxelShape calculateFace(Direction direction) { -+ protected VoxelShape calculateFace(Direction direction) { // Tuinity - Direction.Axis axis = direction.getAxis(); - DoubleList doubleList = this.getCoords(axis); - if (doubleList.size() == 2 && DoubleMath.fuzzyEquals(doubleList.getDouble(0), 0.0D, 1.0E-7D) && DoubleMath.fuzzyEquals(doubleList.getDouble(1), 1.0D, 1.0E-7D)) { -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -index 40d6dfe30e8f388fb2014ba81f9ea4a986354b88..9de4b1c9402e78c661b4d2dc7d70439e75768bc8 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftChunk.java -@@ -110,13 +110,7 @@ public class CraftChunk implements Chunk { - this.getWorld().getChunkAt(x, z); // Transient load for this tick - } - -- // Paper start - improve CraftChunk#getEntities -- return this.worldServer.entityManager.sectionStorage.getExistingSectionsInChunk(ChunkPos.asLong(this.x, this.z)) -- .flatMap(net.minecraft.world.level.entity.EntitySection::getEntities) -- .map(net.minecraft.world.entity.Entity::getBukkitEntity) -- .filter(entity -> entity != null && entity.isValid()) -- .toArray(Entity[]::new); -- // Paper end -+ return ((CraftWorld)this.getWorld()).getHandle().getChunkEntities(this.x, this.z); // Tuinity - optimise this better than paper :) - } - - @Override -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 2ba3b01092cef23bcc958244992ef44103bc7e74..130a088e694b85f7d56620352f044161ea56caf3 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -230,7 +230,7 @@ import javax.annotation.Nullable; // Paper - import javax.annotation.Nonnull; // Paper - - public final class CraftServer implements Server { -- private final String serverName = "Paper"; // Paper -+ private final String serverName = "Tuinity"; // Tuinity // Paper - private final String serverVersion; - private final String bukkitVersion = Versioning.getBukkitVersion(); - private final Logger logger = Logger.getLogger("Minecraft"); -@@ -875,6 +875,7 @@ public final class CraftServer implements Server { - - org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot - com.destroystokyo.paper.PaperConfig.init((File) console.options.valueOf("paper-settings")); // Paper -+ com.tuinity.tuinity.config.TuinityConfig.init((File) console.options.valueOf("tuinity-settings")); // Tuinity - Server Config - for (ServerLevel world : this.console.getAllLevels()) { - world.serverLevelData.setDifficulty(config.difficulty); - world.setSpawnSettings(config.spawnMonsters, config.spawnAnimals); -@@ -909,6 +910,7 @@ public final class CraftServer implements Server { - } - world.spigotConfig.init(); // Spigot - world.paperConfig.init(); // Paper -+ world.tuinityConfig.init(); // Tuinity - Server Config - } - - Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper -@@ -2374,6 +2376,14 @@ public final class CraftServer implements Server { - return com.destroystokyo.paper.PaperConfig.config; - } - -+ // Tuinity start - add config to timings report -+ @Override -+ public YamlConfiguration getTuinityConfig() -+ { -+ return com.tuinity.tuinity.config.TuinityConfig.config; -+ } -+ // Tuinity end - add config to timings report -+ - @Override - public void restart() { - org.spigotmc.RestartCommand.restart(); -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 3403b75c8311f1e52a0533363c5f0307442f8a15..92cb1fd2419eb3a3e64ebc0c5e699a79483f8c44 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -289,7 +289,7 @@ public class CraftWorld implements World { - public int getTileEntityCount() { - return net.minecraft.server.MCUtil.ensureMain(() -> { - // We don't use the full world tile entity list, so we must iterate chunks -- Long2ObjectLinkedOpenHashMap chunks = world.getChunkSource().chunkMap.visibleChunkMap; -+ Long2ObjectLinkedOpenHashMap chunks = world.getChunkSource().chunkMap.updatingChunks.getVisibleMap(); // Tuinity - change updating chunks map - int size = 0; - for (ChunkHolder playerchunk : chunks.values()) { - net.minecraft.world.level.chunk.LevelChunk chunk = playerchunk.getTickingChunk(); -@@ -312,7 +312,7 @@ public class CraftWorld implements World { - return net.minecraft.server.MCUtil.ensureMain(() -> { - int ret = 0; - -- for (ChunkHolder chunkHolder : world.getChunkSource().chunkMap.visibleChunkMap.values()) { -+ for (ChunkHolder chunkHolder : world.getChunkSource().chunkMap.updatingChunks.getVisibleMap().values()) { // Tuinity - change updating chunks map - if (chunkHolder.getTickingChunk() != null) { - ++ret; - } -@@ -351,13 +351,20 @@ public class CraftWorld implements World { - this.generator = gen; - - this.environment = env; -+ // Tuinity start - per world spawn limits -+ this.monsterSpawn = world.tuinityConfig.spawnLimitMonsters; -+ this.animalSpawn = world.tuinityConfig.spawnLimitAnimals; -+ this.waterAmbientSpawn = world.tuinityConfig.spawnLimitWaterAmbient; -+ this.waterAnimalSpawn = world.tuinityConfig.spawnLimitWaterAnimals; -+ this.ambientSpawn = world.tuinityConfig.spawnLimitAmbient; - // Paper start - per world spawn limits -- this.monsterSpawn = this.world.paperConfig.spawnLimitMonsters; -- this.animalSpawn = this.world.paperConfig.spawnLimitAnimals; -- this.waterAnimalSpawn = this.world.paperConfig.spawnLimitWaterAnimals; -- this.waterAmbientSpawn = this.world.paperConfig.spawnLimitWaterAmbient; -- this.ambientSpawn = this.world.paperConfig.spawnLimitAmbient; -+ if (this.monsterSpawn == -1) this.monsterSpawn = this.world.paperConfig.spawnLimitMonsters; -+ if (this.animalSpawn == -1) this.animalSpawn = this.world.paperConfig.spawnLimitAnimals; -+ if (this.waterAnimalSpawn == -1) this.waterAnimalSpawn = this.world.paperConfig.spawnLimitWaterAnimals; -+ if (this.waterAmbientSpawn == -1) this.waterAmbientSpawn = this.world.paperConfig.spawnLimitWaterAmbient; -+ if (this.ambientSpawn == -1) this.ambientSpawn = this.world.paperConfig.spawnLimitAmbient; - // Paper end -+ // Tuinity end - per world spawn limits - } - - @Override -@@ -431,14 +438,7 @@ public class CraftWorld implements World { - - @Override - public Chunk getChunkAt(int x, int z) { -- // Paper start - add ticket to hold chunk for a little while longer if plugin accesses it -- net.minecraft.world.level.chunk.LevelChunk chunk = world.getChunkSource().getChunkAtIfLoadedImmediately(x, z); -- if (chunk == null) { -- addTicket(x, z); -- chunk = this.world.getChunkSource().getChunk(x, z, true); -- } -- return chunk.bukkitChunk; -- // Paper end -+ return this.world.getChunkSource().getChunk(x, z, true).bukkitChunk; // Tuinity - revert paper diff - } - - // Paper start -@@ -486,13 +486,16 @@ public class CraftWorld implements World { - public Chunk[] getLoadedChunks() { - // Paper start - if (Thread.currentThread() != world.getLevel().thread) { -- synchronized (world.getChunkSource().chunkMap.visibleChunkMap) { -- Long2ObjectLinkedOpenHashMap chunks = world.getChunkSource().chunkMap.visibleChunkMap; -- return chunks.values().stream().map(ChunkHolder::getFullChunk).filter(Objects::nonNull).map(net.minecraft.world.level.chunk.LevelChunk::getBukkitChunk).toArray(Chunk[]::new); -+ // Tuinity start - change updating chunks map -+ Long2ObjectLinkedOpenHashMap chunks; -+ synchronized (world.getChunkSource().chunkMap.updatingChunks) { -+ chunks = world.getChunkSource().chunkMap.updatingChunks.getVisibleMap().clone(); - } -+ return chunks.values().stream().map(ChunkHolder::getFullChunk).filter(Objects::nonNull).map(net.minecraft.world.level.chunk.LevelChunk::getBukkitChunk).toArray(Chunk[]::new); -+ // Tuinity end - change updating chunks map - } - // Paper end -- Long2ObjectLinkedOpenHashMap chunks = this.world.getChunkSource().chunkMap.visibleChunkMap; -+ Long2ObjectLinkedOpenHashMap chunks = world.getChunkSource().chunkMap.updatingChunks.getVisibleMap(); // Tuinity - change updating chunks map - return chunks.values().stream().map(ChunkHolder::getFullChunk).filter(Objects::nonNull).map(net.minecraft.world.level.chunk.LevelChunk::getBukkitChunk).toArray(Chunk[]::new); - } - -@@ -2671,7 +2674,7 @@ public class CraftWorld implements World { - // Paper end - return this.world.getChunkSource().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> { - net.minecraft.world.level.chunk.LevelChunk chunk = (net.minecraft.world.level.chunk.LevelChunk) either.left().orElse(null); -- if (chunk != null) addTicket(x, z); // Paper -+ if (false && chunk != null) addTicket(x, z); // Paper // Tuinity - revert - return java.util.concurrent.CompletableFuture.completedFuture(chunk == null ? null : chunk.getBukkitChunk()); - }, net.minecraft.server.MinecraftServer.getServer()); - } -@@ -2696,14 +2699,14 @@ public class CraftWorld implements World { - throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]"); - } - net.minecraft.server.level.ChunkMap chunkMap = getHandle().getChunkSource().chunkMap; -- if (viewDistance != chunkMap.getEffectiveViewDistance()) { -+ if (true) { // Tuinity - replace old player chunk management - chunkMap.setViewDistance(viewDistance); - } - } - - @Override - public int getNoTickViewDistance() { -- return getHandle().getChunkSource().chunkMap.getEffectiveNoTickViewDistance(); -+ return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance(); // Tuinity - replace old player chunk management - } - - @Override -@@ -2712,11 +2715,22 @@ public class CraftWorld implements World { - throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]"); - } - net.minecraft.server.level.ChunkMap chunkMap = getHandle().getChunkSource().chunkMap; -- if (viewDistance != chunkMap.getRawNoTickViewDistance()) { -+ if (true) { // Tuinity - replace old player chunk management - chunkMap.setNoTickViewDistance(viewDistance); - } - } - // Paper end - per player view distance -+ // Tuinity start - add view distances -+ @Override -+ public int getSendViewDistance() { -+ return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance(); -+ } -+ -+ @Override -+ public void setSendViewDistance(int viewDistance) { -+ getHandle().getChunkSource().chunkMap.playerChunkManager.setTargetSendDistance(viewDistance); -+ } -+ // Tuinity end - add view distances - - // Spigot start - private final org.bukkit.World.Spigot spigot = new org.bukkit.World.Spigot() -diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index c3c7b34ceb1b8f0ed042b29924c633fa7519dc30..c59deadcfbfd5afbf951a167979a4eceb0c63579 100644 ---- a/src/main/java/org/bukkit/craftbukkit/Main.java -+++ b/src/main/java/org/bukkit/craftbukkit/Main.java -@@ -146,6 +146,13 @@ public class Main { - .defaultsTo(new File("paper.yml")) - .describedAs("Yml file"); - // Paper end -+ // Tuinity start - Server Config -+ acceptsAll(asList("tuinity", "tuinity-settings"), "File for tuinity settings") -+ .withRequiredArg() -+ .ofType(File.class) -+ .defaultsTo(new File("tuinity.yml")) -+ .describedAs("Yml file"); -+ // Tuinity end - Server Config - - // Paper start - acceptsAll(asList("server-name"), "Name of the server") -@@ -269,7 +276,7 @@ public class Main { - if (buildDate.before(deadline.getTime())) { - // Paper start - This is some stupid bullshit - System.err.println("*** Warning, you've not updated in a while! ***"); -- System.err.println("*** Please download a new build as per instructions from https://papermc.io/downloads ***"); // Paper -+ System.err.println("*** Please download a new build ***"); // Paper // Tuinity - //System.err.println("*** Server will start in 20 seconds ***"); - //Thread.sleep(TimeUnit.SECONDS.toMillis(20)); - // Paper End -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 8246ad7ebecdfc0b7519fe4412fef7b07407e850..c0a508295d2e68d92ec8d24e14f9b7626911f548 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -517,27 +517,36 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { - this.entity.setYHeadRot(yaw); - } - -- @Override// Paper start -- public java.util.concurrent.CompletableFuture teleportAsync(Location loc, @javax.annotation.Nonnull org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause) { -- net.minecraft.server.level.ChunkMap playerChunkMap = ((CraftWorld) loc.getWorld()).getHandle().getChunkSource().chunkMap; -- java.util.concurrent.CompletableFuture future = new java.util.concurrent.CompletableFuture<>(); -- -- loc.getWorld().getChunkAtAsyncUrgently(loc).thenCompose(chunk -> { -- net.minecraft.world.level.ChunkPos pair = new net.minecraft.world.level.ChunkPos(chunk.getX(), chunk.getZ()); -- ((CraftWorld) loc.getWorld()).getHandle().getChunkSource().addTicketAtLevel(TicketType.POST_TELEPORT, pair, 31, 0); -- net.minecraft.server.level.ChunkHolder updatingChunk = playerChunkMap.getUpdatingChunkIfPresent(pair.toLong()); -- if (updatingChunk != null) { -- return updatingChunk.getEntityTickingChunkFuture(); -- } else { -- return java.util.concurrent.CompletableFuture.completedFuture(com.mojang.datafixers.util.Either.left(((org.bukkit.craftbukkit.CraftChunk)chunk).getHandle())); -+ // Tuinity start - implement teleportAsync better -+ @Override -+ public java.util.concurrent.CompletableFuture teleportAsync(Location location, TeleportCause cause) { -+ Preconditions.checkArgument(location != null, "location"); -+ location.checkFinite(); -+ Location locationClone = location.clone(); // clone so we don't need to worry about mutations after this call. -+ -+ net.minecraft.server.level.ServerLevel world = ((CraftWorld)locationClone.getWorld()).getHandle(); -+ java.util.concurrent.CompletableFuture ret = new java.util.concurrent.CompletableFuture<>(); -+ -+ world.loadChunksForMoveAsync(getHandle().getBoundingBoxAt(locationClone.getX(), locationClone.getY(), locationClone.getZ()), location.getX(), location.getZ(), (list) -> { -+ net.minecraft.server.level.ServerChunkCache chunkProviderServer = world.getChunkSource(); -+ for (net.minecraft.world.level.chunk.ChunkAccess chunk : list) { -+ chunkProviderServer.addTicketAtLevel(net.minecraft.server.level.TicketType.POST_TELEPORT, chunk.getPos(), 33, CraftEntity.this.getEntityId()); - } -- }).thenAccept((chunk) -> future.complete(teleport(loc, cause))).exceptionally(ex -> { -- future.completeExceptionally(ex); -- return null; -+ net.minecraft.server.MinecraftServer.getServer().scheduleOnMain(() -> { -+ try { -+ ret.complete(CraftEntity.this.teleport(locationClone, cause) ? Boolean.TRUE : Boolean.FALSE); -+ } catch (Throwable throwable) { -+ if (throwable instanceof ThreadDeath) { -+ throw (ThreadDeath)throwable; -+ } -+ ret.completeExceptionally(throwable); -+ } -+ }); - }); -- return future; -+ -+ return ret; - } -- // Paper end -+ // Tuinity end - implement teleportAsync better - - @Override - public boolean teleport(Location location) { -diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 4e95bf2eb6434d8ca44d478262329c56b0b0a079..1da5b6f73e78a697031f7662e68c546543fb9d1a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -516,15 +516,70 @@ public class CraftPlayer extends CraftHumanEntity implements Player { - } - } - -+ // Tuinity start - implement view distances -+ @Override -+ public int getSendViewDistance() { -+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; -+ com.tuinity.tuinity.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); -+ if (data == null) { -+ return chunkMap.playerChunkManager.getTargetSendDistance(); -+ } -+ return data.getTargetSendViewDistance(); -+ } -+ -+ @Override -+ public void setSendViewDistance(int viewDistance) { -+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; -+ com.tuinity.tuinity.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); -+ if (data == null) { -+ throw new IllegalStateException("Player is not attached to world"); -+ } -+ -+ data.setTargetSendViewDistance(viewDistance); -+ } -+ -+ @Override -+ public int getNoTickViewDistance() { -+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; -+ com.tuinity.tuinity.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); -+ if (data == null) { -+ return chunkMap.playerChunkManager.getTargetNoTickViewDistance(); -+ } -+ return data.getTargetNoTickViewDistance(); -+ } -+ -+ @Override -+ public void setNoTickViewDistance(int viewDistance) { -+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; -+ com.tuinity.tuinity.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); -+ if (data == null) { -+ throw new IllegalStateException("Player is not attached to world"); -+ } -+ -+ data.setTargetNoTickViewDistance(viewDistance); -+ } -+ - @Override - public int getViewDistance() { -- throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO -+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; -+ com.tuinity.tuinity.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); -+ if (data == null) { -+ return chunkMap.playerChunkManager.getTargetViewDistance(); -+ } -+ return data.getTargetTickViewDistance(); - } - - @Override - public void setViewDistance(int viewDistance) { -- throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO -+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; -+ com.tuinity.tuinity.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); -+ if (data == null) { -+ throw new IllegalStateException("Player is not attached to world"); -+ } -+ -+ data.setTargetTickViewDistance(viewDistance); - } -+ // Tuinity end - implement view distances - - @Override - public T getClientOption(com.destroystokyo.paper.ClientOption type) { -diff --git a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java -index 2f3e2a404f55f09ae4db8261e495275e31228034..eb6cde923012d34a53a31f72b86870837e5f0824 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java -+++ b/src/main/java/org/bukkit/craftbukkit/scheduler/CraftAsyncTask.java -@@ -25,7 +25,10 @@ class CraftAsyncTask extends CraftTask { - @Override - public void run() { - final Thread thread = Thread.currentThread(); -- synchronized (this.workers) { -+ // Tuinity start - name threads according to running plugin -+ final String nameBefore = thread.getName(); -+ thread.setName(nameBefore + " - " + this.getOwner().getName()); -+ try { synchronized (this.workers) { // Tuinity end - name threads according to running plugin - if (getPeriod() == CraftTask.CANCEL) { - // Never continue running after cancelled. - // Checking this with the lock is important! -@@ -92,6 +95,7 @@ class CraftAsyncTask extends CraftTask { - } - } - } -+ } finally { thread.setName(nameBefore); } // Tuinity - name worker thread according - } - - LinkedList getWorkers() { -diff --git a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java -index 8ccfe9488db44d7d2cf4040a5b4cead33da1d5f4..d8c572b686c332eca722922c8a96d4629232856a 100644 ---- a/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java -+++ b/src/main/java/org/bukkit/craftbukkit/scoreboard/CraftScoreboardManager.java -@@ -113,9 +113,18 @@ public final class CraftScoreboardManager implements ScoreboardManager { - - // CraftBukkit method - public void getScoreboardScores(ObjectiveCriteria criteria, String name, Consumer consumer) { -+ // Tuinity start - add timings for scoreboard search -+ // plugins leaking scoreboards will make this very expensive, let server owners debug it easily -+ co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.startTimingIfSync(); -+ try { -+ // Tuinity end - add timings for scoreboard search - for (CraftScoreboard scoreboard : this.scoreboards) { - Scoreboard board = scoreboard.board; - board.forAllObjectives(criteria, name, (score) -> consumer.accept(score)); - } -+ } finally { // Tuinity start - add timings for scoreboard search -+ co.aikar.timings.MinecraftTimings.scoreboardScoreSearch.stopTimingIfSync(); -+ } -+ // Tuinity end - add timings for scoreboard search - } - } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -index a430506c31d9ce7a5c90d726a68f097498629545..e8c5109c36d437287e3eec23a5d1031f197a6162 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/DummyGeneratorAccess.java -@@ -1,5 +1,6 @@ - package org.bukkit.craftbukkit.util; - -+import java.util.Collections; - import java.util.List; - import java.util.Random; - import java.util.function.Predicate; -@@ -234,4 +235,20 @@ public class DummyGeneratorAccess implements LevelAccessor { - public boolean destroyBlock(BlockPos pos, boolean drop, Entity breakingEntity, int maxUpdateDepth) { - return false; // SPIGOT-6515 - } -+ -+ // Tuinity start -+ @Override -+ public List getHardCollidingEntities(Entity except, AABB box, Predicate predicate) { -+ return Collections.emptyList(); -+ } -+ -+ @Override -+ public void getEntities(Entity except, AABB box, Predicate predicate, List into) {} -+ -+ @Override -+ public void getHardCollidingEntities(Entity except, AABB box, Predicate predicate, List into) {} -+ -+ @Override -+ public void getEntitiesByClass(Class clazz, Entity except, AABB box, List into, Predicate predicate) {} -+ // Tuinity end - } -diff --git a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java -index d40c0d8be1b0153d62021b8bcb6e8b37fd0acb4e..025540a62e805816cb93307c472bf0de64e2b01f 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/UnsafeList.java -@@ -119,6 +119,32 @@ public class UnsafeList extends AbstractList implements List, RandomAcc - return this.indexOf(o) >= 0; - } - -+ // Tuinity start -+ protected transient int maxSize; -+ public void setSize(int size) { -+ if (this.maxSize < this.size) { -+ this.maxSize = this.size; -+ } -+ this.size = size; -+ } -+ -+ public void completeReset() { -+ if (this.data != null) { -+ Arrays.fill(this.data, 0, Math.max(this.size, this.maxSize), null); -+ } -+ this.size = 0; -+ this.maxSize = 0; -+ if (this.iterPool != null) { -+ for (Iterator temp : this.iterPool) { -+ if (temp == null) { -+ continue; -+ } -+ ((Itr)temp).valid = false; -+ } -+ } -+ } -+ // Tuinity end -+ - @Override - public void clear() { - // Create new array to reset memory usage to initial capacity -diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java -index 774556a62eb240da42e84db4502e2ed43495be17..001b1e5197eaa51bfff9031aa6c69876c9a47960 100644 ---- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java -+++ b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java -@@ -11,7 +11,7 @@ public final class Versioning { - public static String getBukkitVersion() { - String result = "Unknown-Version"; - -- InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/io.papermc.paper/paper-api/pom.properties"); -+ InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/com.tuinity/tuinity-api/pom.properties"); // Tuinity - Properties properties = new Properties(); - - if (stream != null) { -diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java -index a08583863f9fa08016bdfc7949a273eaa4429927..7361bf04de16d0526dc4cdbd0f564713df041d90 100644 ---- a/src/main/java/org/spigotmc/ActivationRange.java -+++ b/src/main/java/org/spigotmc/ActivationRange.java -@@ -205,7 +205,7 @@ public class ActivationRange - ActivationType.VILLAGER.boundingBox = player.getBoundingBox().inflate( villagerActivationRange, 256, waterActivationRange ); - // Paper end - -- world.getEntities().get(maxBB, ActivationRange::activateEntity); -+ world.getEntities(null, maxBB).forEach(ActivationRange::activateEntity); // Tuinity - } - MinecraftTimings.entityActivationCheckTimer.stopTiming(); - } -diff --git a/src/main/java/org/spigotmc/AsyncCatcher.java b/src/main/java/org/spigotmc/AsyncCatcher.java -index 7a23b56752f6733ee626a8b1e4c3b78591855c4e..17151d9dc8c7030b54f1ba0956424d9d704c81ab 100644 ---- a/src/main/java/org/spigotmc/AsyncCatcher.java -+++ b/src/main/java/org/spigotmc/AsyncCatcher.java -@@ -10,8 +10,9 @@ public class AsyncCatcher - - public static void catchOp(String reason) - { -- if ( AsyncCatcher.enabled && Thread.currentThread() != MinecraftServer.getServer().serverThread ) -+ if ( (AsyncCatcher.enabled || com.tuinity.tuinity.util.TickThread.STRICT_THREAD_CHECKS) && Thread.currentThread() != MinecraftServer.getServer().serverThread ) // Tuinity - { -+ MinecraftServer.LOGGER.fatal("Thread " + Thread.currentThread().getName() + " failed thread check for reason: Asynchronous " + reason, new Throwable()); // Tuinity - not all exceptions are printed - throw new IllegalStateException( "Asynchronous " + reason + "!" ); - } - } -diff --git a/src/main/java/org/spigotmc/WatchdogThread.java b/src/main/java/org/spigotmc/WatchdogThread.java -index dcfbe77bdb25d9c58ffb7b75c48bdb580bc0de47..fcd3917e0af080ae4726e7f511abf586df4dc7de 100644 ---- a/src/main/java/org/spigotmc/WatchdogThread.java -+++ b/src/main/java/org/spigotmc/WatchdogThread.java -@@ -23,6 +23,78 @@ public class WatchdogThread extends Thread - private volatile long lastTick; - private volatile boolean stopping; - -+ // Tuinity start - log detailed tick information -+ private void dumpEntity(net.minecraft.world.entity.Entity entity) { -+ Logger log = Bukkit.getServer().getLogger(); -+ double posX, posY, posZ; -+ net.minecraft.world.phys.Vec3 mot; -+ double moveStartX, moveStartY, moveStartZ; -+ net.minecraft.world.phys.Vec3 moveVec; -+ synchronized (entity.posLock) { -+ posX = entity.getX(); -+ posY = entity.getY(); -+ posZ = entity.getZ(); -+ mot = entity.getDeltaMovement(); -+ moveStartX = entity.getMoveStartX(); -+ moveStartY = entity.getMoveStartY(); -+ moveStartZ = entity.getMoveStartZ(); -+ moveVec = entity.getMoveVector(); -+ } -+ -+ String entityType = entity.getMinecraftKey().toString(); -+ java.util.UUID entityUUID = entity.getUUID(); -+ net.minecraft.world.level.Level world = entity.level; -+ -+ log.log(Level.SEVERE, "Ticking entity: " + entityType + ", entity class: " + entity.getClass().getName()); -+ log.log(Level.SEVERE, "Entity status: removed: " + entity.isRemoved() + ", valid: " + entity.valid + ", alive: " + entity.isAlive() + ", is passenger: " + entity.isPassenger()); -+ log.log(Level.SEVERE, "Entity UUID: " + entityUUID); -+ log.log(Level.SEVERE, "Position: world: '" + (world == null ? "unknown world?" : world.getWorld().getName()) + "' at location (" + posX + ", " + posY + ", " + posZ + ")"); -+ log.log(Level.SEVERE, "Velocity: " + (mot == null ? "unknown velocity" : mot.toString()) + " (in blocks per tick)"); -+ log.log(Level.SEVERE, "Entity AABB: " + entity.getBoundingBox()); -+ if (moveVec != null) { -+ log.log(Level.SEVERE, "Move call information: "); -+ log.log(Level.SEVERE, "Start position: (" + moveStartX + ", " + moveStartY + ", " + moveStartZ + ")"); -+ log.log(Level.SEVERE, "Move vector: " + moveVec.toString()); -+ } -+ } -+ -+ private void dumpTickingInfo() { -+ Logger log = Bukkit.getServer().getLogger(); -+ -+ // ticking entities -+ for (net.minecraft.world.entity.Entity entity : net.minecraft.server.level.ServerLevel.getCurrentlyTickingEntities()) { -+ this.dumpEntity(entity); -+ net.minecraft.world.entity.Entity vehicle = entity.getVehicle(); -+ if (vehicle != null) { -+ log.log(Level.SEVERE, "Detailing vehicle for above entity:"); -+ this.dumpEntity(vehicle); -+ } -+ } -+ -+ // packet processors -+ for (net.minecraft.network.PacketListener packetListener : net.minecraft.network.protocol.PacketUtils.getCurrentPacketProcessors()) { -+ if (packetListener instanceof net.minecraft.server.network.ServerGamePacketListenerImpl) { -+ net.minecraft.server.level.ServerPlayer player = ((net.minecraft.server.network.ServerGamePacketListenerImpl)packetListener).player; -+ long totalPackets = net.minecraft.network.protocol.PacketUtils.getTotalProcessedPackets(); -+ if (player == null) { -+ log.log(Level.SEVERE, "Handling packet for player connection or ticking player connection (null player): " + packetListener); -+ log.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets); -+ } else { -+ this.dumpEntity(player); -+ net.minecraft.world.entity.Entity vehicle = player.getVehicle(); -+ if (vehicle != null) { -+ log.log(Level.SEVERE, "Detailing vehicle for above entity:"); -+ this.dumpEntity(vehicle); -+ } -+ log.log(Level.SEVERE, "Total packets processed on the main thread for all players: " + totalPackets); -+ } -+ } else { -+ log.log(Level.SEVERE, "Handling packet for connection: " + packetListener); -+ } -+ } -+ } -+ // Tuinity end - log detailed tick information -+ - private WatchdogThread(long timeoutTime, boolean restart) - { - super( "Paper Watchdog Thread" ); -@@ -121,6 +193,7 @@ public class WatchdogThread extends Thread - log.log( Level.SEVERE, "------------------------------" ); - log.log( Level.SEVERE, "Server thread dump (Look for plugins here before reporting to Paper!):" ); // Paper - com.destroystokyo.paper.io.chunk.ChunkTaskManager.dumpAllChunkLoadInfo(); // Paper -+ this.dumpTickingInfo(); // Tuinity - log detailed tick information - WatchdogThread.dumpThread( ManagementFactory.getThreadMXBean().getThreadInfo( server.serverThread.getId(), Integer.MAX_VALUE ), log ); - log.log( Level.SEVERE, "------------------------------" ); - // diff --git a/src/main/resources/logo.png b/src/main/resources/logo.png index a7d785f60c884ee4ee487cc364402d66c3dc2ecc..1e6ee83b1a207eca59d82b25c06895ce894e8173 100644 GIT binary patch diff --git a/patches/server/0002-Rebrand.patch b/patches/server/0002-Rebrand.patch index 97fe1b66d..cabb882a1 100644 --- a/patches/server/0002-Rebrand.patch +++ b/patches/server/0002-Rebrand.patch @@ -50,33 +50,33 @@ index e0b1f0671d16ddddcb6725acd25a1d1d69e42701..8c3c68465197fafc14849dc38a572e30 .completer(new ConsoleCommandCompleter(this.server)) .option(LineReader.Option.COMPLETE_IN_WORD, true); diff --git a/src/main/java/net/minecraft/server/Eula.java b/src/main/java/net/minecraft/server/Eula.java -index 3789df8ef9c0b4150c3baccf84dbaff57c0fb65a..43a88bc58716eef4040584944f52893c5a47699f 100644 +index a1d5c0f8fe2adb2ee56f3217e089211ec7c61eb0..43a88bc58716eef4040584944f52893c5a47699f 100644 --- a/src/main/java/net/minecraft/server/Eula.java +++ b/src/main/java/net/minecraft/server/Eula.java @@ -64,7 +64,7 @@ public class Eula { try { Properties properties = new Properties(); properties.setProperty("eula", "false"); -- properties.store(outputStream, "By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/minecraft_eula)."); // Paper - fix lag; // Tuinity - Tacos are disgusting +- properties.store(outputStream, "By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/minecraft_eula).\nYou also agree that tacos are tasty, and the best food in the world."); // Paper - fix lag; + properties.store(outputStream, "By changing the setting below to TRUE you are indicating your agreement to our EULA (https://account.mojang.com/documents/minecraft_eula).\nYou also agree that tacos are tasty, and the best food in the world."); // Paper - fix lag; // Tuinity - Tacos are disgusting // Purpur - no they're not } catch (Throwable var5) { if (outputStream != null) { try { diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index f3bf6270c7735869083559f907c65d95144dbe6f..4cc099b469a7554e381a6f7aceb686a94f7d6605 100644 +index 3dded5c491ace6b073a7bc3178976bd70f0b9393..1164e6a63e895818b3645b31588e27ae221e4859 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1714,7 +1714,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop //Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla! +- return "Paper"; //Paper - Paper > // Spigot - Spigot > // CraftBukkit - cb > vanilla! + return "Purpur"; // Purpur // Tuinity // Paper // Spigot // CraftBukkit } - public SystemReport fillSystemReport(SystemReport systemreport) { + public SystemReport fillSystemReport(SystemReport details) { diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 9bb0ff063da162f2b5c91d367d9555c1cf1a3ab1..0ed5e15d3175ee5d145730ff8f506ffb959e5e6c 100644 +index 6d7eef79de7a899ccdbc3194d925bb4caa0a4b03..2c6b0018727409f7d56081cb207174f5dc37a6a7 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java @@ -281,11 +281,12 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface @@ -220,20 +220,20 @@ index 0000000000000000000000000000000000000000..cabfcebf9f944f7a2a2a1cffc7401435 + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 130a088e694b85f7d56620352f044161ea56caf3..818dde63df240c7e72526ef0aef87c9f8739f78a 100644 +index c79b193ad822b8c246f24a87cd418892bc18ff5a..977d4c6d3b770caef9a619978063b2e7a8ecac82 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -230,7 +230,7 @@ import javax.annotation.Nullable; // Paper import javax.annotation.Nonnull; // Paper public final class CraftServer implements Server { -- private final String serverName = "Tuinity"; // Tuinity // Paper +- private final String serverName = "Paper"; // Paper + private final String serverName = "Purpur"; // Paper // Tuinity // Purpur private final String serverVersion; private final String bukkitVersion = Versioning.getBukkitVersion(); private final Logger logger = Logger.getLogger("Minecraft"); diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index edf7e8d65d7f67f2f34490fdb766eca7d3b17aee..9239c4c948ddfc6f7ad8c5f277e355581f8f37ec 100644 +index 7a8db8d481e9487ea83a640af208242f4987ad28..82d56d8c4616f1012e70697dae8d1d31d7d53f2f 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java @@ -395,7 +395,7 @@ public final class CraftMagicNumbers implements UnsafeValues { @@ -246,14 +246,14 @@ index edf7e8d65d7f67f2f34490fdb766eca7d3b17aee..9239c4c948ddfc6f7ad8c5f277e35558 @Override diff --git a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java -index 001b1e5197eaa51bfff9031aa6c69876c9a47960..13b98439320ac1401a920c01d7cf5a4b3a23deff 100644 +index 774556a62eb240da42e84db4502e2ed43495be17..13b98439320ac1401a920c01d7cf5a4b3a23deff 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/Versioning.java +++ b/src/main/java/org/bukkit/craftbukkit/util/Versioning.java @@ -11,7 +11,7 @@ public final class Versioning { public static String getBukkitVersion() { String result = "Unknown-Version"; -- InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/com.tuinity/tuinity-api/pom.properties"); // Tuinity +- InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/io.papermc.paper/paper-api/pom.properties"); + InputStream stream = Bukkit.class.getClassLoader().getResourceAsStream("META-INF/maven/net.pl3x.purpur/purpur-api/pom.properties"); // Tuinity // Purpur Properties properties = new Properties(); diff --git a/patches/server/0003-Purpur-config-files.patch b/patches/server/0003-Purpur-config-files.patch index a7237dbac..c629397b5 100644 --- a/patches/server/0003-Purpur-config-files.patch +++ b/patches/server/0003-Purpur-config-files.patch @@ -5,14 +5,14 @@ Subject: [PATCH] Purpur config files diff --git a/src/main/java/com/destroystokyo/paper/Metrics.java b/src/main/java/com/destroystokyo/paper/Metrics.java -index 3918b24c98faa5232c7ffd733ba8000562132785..4d8740678049aa749b42618470e9cc838555528d 100644 +index 218f5bafeed8551b55b91c7fccaf6935c8b631ca..4d8740678049aa749b42618470e9cc838555528d 100644 --- a/src/main/java/com/destroystokyo/paper/Metrics.java +++ b/src/main/java/com/destroystokyo/paper/Metrics.java @@ -593,7 +593,7 @@ public class Metrics { boolean logFailedRequests = config.getBoolean("logFailedRequests", false); // Only start Metrics, if it's enabled in the config if (config.getBoolean("enabled", true)) { -- Metrics metrics = new Metrics("Tuinity", serverUUID, logFailedRequests, Bukkit.getLogger()); // Tuinity - we have our own bstats page +- Metrics metrics = new Metrics("Paper", serverUUID, logFailedRequests, Bukkit.getLogger()); + Metrics metrics = new Metrics("Purpur", serverUUID, logFailedRequests, Bukkit.getLogger()); // Purpur metrics.addCustomChart(new Metrics.SimplePie("minecraft_version", () -> { @@ -22,14 +22,14 @@ index 3918b24c98faa5232c7ffd733ba8000562132785..4d8740678049aa749b42618470e9cc83 metrics.addCustomChart(new Metrics.SingleLineChart("players", () -> Bukkit.getOnlinePlayers().size())); - metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : "offline")); -- metrics.addCustomChart(new Metrics.SimplePie("tuinity_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown")); // Tuinity - we have our own bstats page +- metrics.addCustomChart(new Metrics.SimplePie("paper_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown")); + metrics.addCustomChart(new Metrics.SimplePie("online_mode", () -> Bukkit.getOnlineMode() ? "online" : (PaperConfig.isProxyOnlineMode() ? "bungee" : "offline"))); // Purpur + metrics.addCustomChart(new Metrics.SimplePie("purpur_version", () -> (Metrics.class.getPackage().getImplementationVersion() != null) ? Metrics.class.getPackage().getImplementationVersion() : "unknown")); // Purpur metrics.addCustomChart(new Metrics.DrilldownPie("java_version", () -> { Map> map = new HashMap<>(); diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java -index 73652e403eaa419fb5b4b54bd506f246c0aa4e99..90c8ec385f972ffea4ddb7a1b80e41db3be7c248 100644 +index 98b71384508447adc80c2175f8e35e5d86b0c378..4f49a0b2bb22b95503c3f6a0b304f287ee9048ae 100644 --- a/src/main/java/com/destroystokyo/paper/PaperConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java @@ -118,6 +118,11 @@ public class PaperConfig { @@ -45,7 +45,7 @@ index 73652e403eaa419fb5b4b54bd506f246c0aa4e99..90c8ec385f972ffea4ddb7a1b80e41db config.save(CONFIG_FILE); } catch (IOException ex) { diff --git a/src/main/java/net/minecraft/commands/CommandSourceStack.java b/src/main/java/net/minecraft/commands/CommandSourceStack.java -index 6a330170ec1ea9d06593a1bbd1bdb8d98c0904fb..f1ebfd810fd06e62d796110345aa36ce4a94e2fd 100644 +index 134bb2a4826419110c10a483834747b942576e58..d9e868b6c70da18b4ce23c80e2aaf347f2dc6d50 100644 --- a/src/main/java/net/minecraft/commands/CommandSourceStack.java +++ b/src/main/java/net/minecraft/commands/CommandSourceStack.java @@ -234,6 +234,30 @@ public class CommandSourceStack implements SharedSuggestionProvider, com.destroy @@ -80,10 +80,10 @@ index 6a330170ec1ea9d06593a1bbd1bdb8d98c0904fb..f1ebfd810fd06e62d796110345aa36ce if (this.source.acceptsSuccess() && !this.silent) { this.source.sendMessage(message, Util.NIL_UUID); diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 0ed5e15d3175ee5d145730ff8f506ffb959e5e6c..6e67ea018cd6f362bba104f542c52438b455ccab 100644 +index 2c6b0018727409f7d56081cb207174f5dc37a6a7..1f9cc47bac2cd8344ecc355c22101fc48b13fb62 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -218,6 +218,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface +@@ -219,6 +219,15 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface return false; } com.destroystokyo.paper.PaperConfig.registerCommands(); @@ -100,22 +100,22 @@ index 0ed5e15d3175ee5d145730ff8f506ffb959e5e6c..6e67ea018cd6f362bba104f542c52438 io.papermc.paper.brigadier.PaperBrigadierProviderImpl.INSTANCE.getClass(); // init PaperBrigadierProvider io.papermc.paper.util.StacktraceDeobfuscator.INSTANCE.getClass(); // load mappings for stacktrace deobf diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 38753e10b1597a2f3bd2cde208c6e30b26a03b43..70f2b5f41550cc73a520ae1637fac5797ea135f9 100644 +index 474078b68f1bf22037495f42bae59b790fd8cb46..bda7cf2b8431f8541cba98e2a641dad03e736fa8 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -168,6 +168,8 @@ public abstract class Level implements LevelAccessor, AutoCloseable { - - public final com.tuinity.tuinity.config.TuinityConfig.WorldConfig tuinityConfig; // Tuinity - Server Config +@@ -166,7 +166,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + public final com.destroystokyo.paper.PaperWorldConfig paperConfig; // Paper + public final com.destroystokyo.paper.antixray.ChunkPacketBlockController chunkPacketBlockController; // Paper - Anti-Xray +- + public final net.pl3x.purpur.PurpurWorldConfig purpurConfig; // Purpur -+ public final co.aikar.timings.WorldTimingsHandler timings; // Paper public static BlockPos lastPhysicsProblem; // Spigot private org.spigotmc.TickLimiter entityLimiter; -@@ -315,6 +317,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -206,6 +206,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + protected Level(WritableLevelData worlddatamutable, ResourceKey resourcekey, final DimensionType dimensionmanager, Supplier supplier, boolean flag, boolean flag1, long i, org.bukkit.generator.ChunkGenerator gen, org.bukkit.World.Environment env, java.util.concurrent.Executor executor) { // Paper - Anti-Xray - Pass executor this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), this.spigotConfig); // Paper - this.tuinityConfig = new com.tuinity.tuinity.config.TuinityConfig.WorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData)worlddatamutable).getLevelName()); // Tuinity - Server Config + this.purpurConfig = new net.pl3x.purpur.PurpurWorldConfig((ServerLevel) this, ((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), env); // Purpur this.generator = gen; this.world = new CraftWorld((ServerLevel) this, gen, env); @@ -285,13 +285,12 @@ index 0000000000000000000000000000000000000000..fcf5f32c32eb17f7580b3c8c9190e367 +} diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java new file mode 100644 -index 0000000000000000000000000000000000000000..dd8b29f3aa58c5c7302ec5e93bef4190da913e3a +index 0000000000000000000000000000000000000000..15472bf9cfd279b1873f7a20c250a2c089debad1 --- /dev/null +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -0,0 +1,95 @@ +@@ -0,0 +1,94 @@ +package net.pl3x.purpur; + -+import com.tuinity.tuinity.config.TuinityConfig; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerLevel; @@ -456,26 +455,26 @@ index 0000000000000000000000000000000000000000..6e7f56fe2b78d7a09d5d130f2c88338f + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -index 818dde63df240c7e72526ef0aef87c9f8739f78a..6329a85dbed1de21dd7dcde8b62d51f09da47e78 100644 +index 977d4c6d3b770caef9a619978063b2e7a8ecac82..9123ae3a99efb80bc938e013a4eff4259cec1084 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -876,6 +876,7 @@ public final class CraftServer implements Server { +@@ -875,6 +875,7 @@ public final class CraftServer implements Server { + org.spigotmc.SpigotConfig.init((File) console.options.valueOf("spigot-settings")); // Spigot com.destroystokyo.paper.PaperConfig.init((File) console.options.valueOf("paper-settings")); // Paper - com.tuinity.tuinity.config.TuinityConfig.init((File) console.options.valueOf("tuinity-settings")); // Tuinity - Server Config + net.pl3x.purpur.PurpurConfig.init((File) console.options.valueOf("purpur-settings")); // Purpur for (ServerLevel world : this.console.getAllLevels()) { world.serverLevelData.setDifficulty(config.difficulty); world.setSpawnSettings(config.spawnMonsters, config.spawnAnimals); -@@ -911,6 +912,7 @@ public final class CraftServer implements Server { +@@ -909,6 +910,7 @@ public final class CraftServer implements Server { + } world.spigotConfig.init(); // Spigot world.paperConfig.init(); // Paper - world.tuinityConfig.init(); // Tuinity - Server Config + world.purpurConfig.init(); // Purpur } Plugin[] pluginClone = pluginManager.getPlugins().clone(); // Paper -@@ -926,6 +928,7 @@ public final class CraftServer implements Server { +@@ -924,6 +926,7 @@ public final class CraftServer implements Server { this.reloadData(); org.spigotmc.SpigotConfig.registerCommands(); // Spigot com.destroystokyo.paper.PaperConfig.registerCommands(); // Paper @@ -483,9 +482,9 @@ index 818dde63df240c7e72526ef0aef87c9f8739f78a..6329a85dbed1de21dd7dcde8b62d51f0 this.overrideAllCommandBlockCommands = this.commandsConfiguration.getStringList("command-block-overrides").contains("*"); this.ignoreVanillaPermissions = this.commandsConfiguration.getBoolean("ignore-vanilla-permissions"); -@@ -2384,6 +2387,18 @@ public final class CraftServer implements Server { +@@ -2374,6 +2377,18 @@ public final class CraftServer implements Server { + return com.destroystokyo.paper.PaperConfig.config; } - // Tuinity end - add config to timings report + // Purpur start + @Override @@ -503,12 +502,12 @@ index 818dde63df240c7e72526ef0aef87c9f8739f78a..6329a85dbed1de21dd7dcde8b62d51f0 public void restart() { org.spigotmc.RestartCommand.restart(); diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index c59deadcfbfd5afbf951a167979a4eceb0c63579..a335d1689ebf01e0e96a45c640188dc024610e2c 100644 +index c3c7b34ceb1b8f0ed042b29924c633fa7519dc30..910ffb563d66ce3d5a3ea562fd1b9d6d2030cccb 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java -@@ -154,6 +154,14 @@ public class Main { +@@ -147,6 +147,14 @@ public class Main { .describedAs("Yml file"); - // Tuinity end - Server Config + // Paper end + // Purpur Start + acceptsAll(asList("purpur", "purpur-settings"), "File for purpur settings") diff --git a/patches/server/0004-Component-related-conveniences.patch b/patches/server/0004-Component-related-conveniences.patch index 810819314..7aead1d89 100644 --- a/patches/server/0004-Component-related-conveniences.patch +++ b/patches/server/0004-Component-related-conveniences.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Component related conveniences diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 0da2dbeba93d428a035872e05177ed3fc29acf9b..f3d23ced47e7dd7bc4005379962a9efcb8edf084 100644 +index 8e2bccc3a9ddb17a4978596056189eb776976338..cf06e4f51984a64c294f402c62a03214eea0e5c3 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1677,6 +1677,26 @@ public class ServerPlayer extends Player { +@@ -1661,6 +1661,26 @@ public class ServerPlayer extends Player { } // CraftBukkit end @@ -36,10 +36,10 @@ index 0da2dbeba93d428a035872e05177ed3fc29acf9b..f3d23ced47e7dd7bc4005379962a9efc public void displayClientMessage(Component message, boolean actionBar) { this.sendMessage(message, actionBar ? ChatType.GAME_INFO : ChatType.CHAT, Util.NIL_UUID); diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 570cea8ee6a442b2dc3c6ef849294ef0c02027ca..ec98fb6d4a407d5be8faf64db0d73e935e16623d 100644 +index 65657c009f6d5a5d5740e80f912a5893333c7085..c647a768a10076969f81fed33cefb7a4aa3b8597 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -1389,6 +1389,62 @@ public abstract class PlayerList { +@@ -1390,6 +1390,62 @@ public abstract class PlayerList { } // CraftBukkit end @@ -103,10 +103,10 @@ index 570cea8ee6a442b2dc3c6ef849294ef0c02027ca..ec98fb6d4a407d5be8faf64db0d73e93 this.server.sendMessage(message, sender); Iterator iterator = this.players.iterator(); diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index ca7718053a6a2eb715ea3671bd4bc15304ede420..19d716a7350c6ed5b912064aa1e63a1fbbe4183f 100644 +index 5ffc9d02ee10e9efe653e124b1eb9cbd0adc3df9..042da7707f7fd01a6195be15f97557d049ded22a 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3565,6 +3565,34 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n +@@ -3376,6 +3376,34 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n return SlotAccess.NULL; } diff --git a/patches/server/0005-Ridables.patch b/patches/server/0005-Ridables.patch index fda76ff21..03a2bb9bc 100644 --- a/patches/server/0005-Ridables.patch +++ b/patches/server/0005-Ridables.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Ridables diff --git a/src/main/java/net/minecraft/core/BlockPos.java b/src/main/java/net/minecraft/core/BlockPos.java -index b01d7da333bac7820e42b6f645634a15ef88ae4f..30a4b80eacf76ad7f0a48b79bcf01b752c0af48a 100644 +index b70aa66732fb5e957aed0901f4c76358b2c56f8e..c281018818e5f27e23a155565eb2130fcd16a295 100644 --- a/src/main/java/net/minecraft/core/BlockPos.java +++ b/src/main/java/net/minecraft/core/BlockPos.java @@ -41,6 +41,12 @@ public class BlockPos extends Vec3i { @@ -22,10 +22,10 @@ index b01d7da333bac7820e42b6f645634a15ef88ae4f..30a4b80eacf76ad7f0a48b79bcf01b75 super(x, y, z); } diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 4cc099b469a7554e381a6f7aceb686a94f7d6605..06177ec182d79cd101da9cd43a841656d27d6d8b 100644 +index 1164e6a63e895818b3645b31588e27ae221e4859..745df03f3b425a247d63d7d896c3dcf2c8baa0e0 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1590,6 +1590,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper worldserver.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper @@ -34,10 +34,10 @@ index 4cc099b469a7554e381a6f7aceb686a94f7d6605..06177ec182d79cd101da9cd43a841656 this.profiler.push(() -> { diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 57163e3cb883ded5861e57c3ca03663c02ee7492..67cdf31f3ce9f85743ce10bd2dee6854be1d69f9 100644 +index 6ecf60c69a27f8db1c245db15449bba581c3dbf5..a02b0d819392db96a370c57818a378dd5fa27ef4 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -205,6 +205,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl +@@ -201,6 +201,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl public final UUID uuid; public boolean hasPhysicsEvent = true; // Paper public boolean hasEntityMoveEvent = false; // Paper @@ -46,10 +46,10 @@ index 57163e3cb883ded5861e57c3ca03663c02ee7492..67cdf31f3ce9f85743ce10bd2dee6854 return new Throwable(entity + " Added to world at " + new java.util.Date()); } diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index f3d23ced47e7dd7bc4005379962a9efcb8edf084..fde98f9c4a3e151dbc277541fbd2cc0a8d957069 100644 +index cf06e4f51984a64c294f402c62a03214eea0e5c3..d9df08cdf065b0316f43ca53988ccbe119e5106f 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -668,6 +668,15 @@ public class ServerPlayer extends Player { +@@ -664,6 +664,15 @@ public class ServerPlayer extends Player { } this.advancements.flushDirty(this); @@ -65,7 +65,7 @@ index f3d23ced47e7dd7bc4005379962a9efcb8edf084..fde98f9c4a3e151dbc277541fbd2cc0a } public void doTick() { -@@ -2404,4 +2413,6 @@ public class ServerPlayer extends Player { +@@ -2398,4 +2407,6 @@ public class ServerPlayer extends Player { return (CraftPlayer) super.getBukkitEntity(); } // CraftBukkit end @@ -73,10 +73,10 @@ index f3d23ced47e7dd7bc4005379962a9efcb8edf084..fde98f9c4a3e151dbc277541fbd2cc0a + } diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index f78119970da27ef66a9d9093e2e42ce129d4cf31..17a6857f5ee70120d664e46426f739ef2dae0a0a 100644 +index 6ff04fc7202a7eb1f2b5978a2e2a573945d9dde1..dbb5f0bd721b2490f727a0b2462cf215e729947d 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -2427,6 +2427,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser +@@ -2329,6 +2329,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser ServerGamePacketListenerImpl.this.cserver.getPluginManager().callEvent(event); @@ -86,7 +86,7 @@ index f78119970da27ef66a9d9093e2e42ce129d4cf31..17a6857f5ee70120d664e46426f739ef if ((entity instanceof AbstractFish && origItem != null && origItem.asItem() == Items.WATER_BUCKET) && (event.isCancelled() || ServerGamePacketListenerImpl.this.player.getInventory().getSelected() == null || ServerGamePacketListenerImpl.this.player.getInventory().getSelected().getItem() != origItem)) { ServerGamePacketListenerImpl.this.send(new ClientboundAddMobPacket((AbstractFish) entity)); diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 19d716a7350c6ed5b912064aa1e63a1fbbe4183f..37db4d89996cde1bdcdc513757be75612d444f97 100644 +index 042da7707f7fd01a6195be15f97557d049ded22a..081506649de3929becd448664e2c5b0d7b4859fc 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -230,7 +230,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n @@ -107,7 +107,7 @@ index 19d716a7350c6ed5b912064aa1e63a1fbbe4183f..37db4d89996cde1bdcdc513757be7561 private float eyeHeight; public boolean isInPowderSnow; public boolean wasInPowderSnow; -@@ -2601,6 +2601,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n +@@ -2412,6 +2412,12 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n this.passengers = ImmutableList.copyOf(list); } @@ -120,7 +120,7 @@ index 19d716a7350c6ed5b912064aa1e63a1fbbe4183f..37db4d89996cde1bdcdc513757be7561 } return true; // CraftBukkit } -@@ -2641,6 +2647,14 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n +@@ -2452,6 +2458,14 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n return false; } // Spigot end @@ -135,7 +135,7 @@ index 19d716a7350c6ed5b912064aa1e63a1fbbe4183f..37db4d89996cde1bdcdc513757be7561 if (this.passengers.size() == 1 && this.passengers.get(0) == entity) { this.passengers = ImmutableList.of(); } else { -@@ -4208,4 +4222,41 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n +@@ -4015,4 +4029,41 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n return ((ServerChunkCache) level.getChunkSource()).isPositionTicking(this); } // Paper end @@ -201,7 +201,7 @@ index d28cecd9bea7c82fa675d333810e2e63a91c615e..8f8bc29d847801938e251904b8334b4b protected ParticleOptions getInkParticle() { return ParticleTypes.GLOW_SQUID_INK; diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 57c448ee93df76fc2a17c75fafc78408d720ced3..e918f7cc7fdfdeb1b7b69488eadf69f0d4a3cb05 100644 +index ed745776316c5346ee1b44c8f022c40359b7e642..911a6f63e8ffeacc95fa49cdf99140c1ce3d06b1 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -220,9 +220,9 @@ public abstract class LivingEntity extends Entity { @@ -217,16 +217,16 @@ index 57c448ee93df76fc2a17c75fafc78408d720ced3..e918f7cc7fdfdeb1b7b69488eadf69f0 protected int lerpSteps; protected double lerpX; protected double lerpY; -@@ -2559,7 +2559,7 @@ public abstract class LivingEntity extends Entity { - return 0.42F * this.getBlockJumpFactor(); +@@ -2567,7 +2567,7 @@ public abstract class LivingEntity extends Entity { + return this.hasEffect(MobEffects.JUMP) ? (double) (0.1F * (float) (this.getEffect(MobEffects.JUMP).getAmplifier() + 1)) : 0.0D; } - protected void jumpFromGround() { + public void jumpFromGround() { // Purpur - protected -> public - float f = this.getJumpPower(); + double d0 = (double) this.getJumpPower() + this.getJumpBoostPower(); + Vec3 vec3d = this.getDeltaMovement(); - if (this.hasEffect(MobEffects.JUMP)) { -@@ -3218,8 +3218,10 @@ public abstract class LivingEntity extends Entity { +@@ -3221,8 +3221,10 @@ public abstract class LivingEntity extends Entity { this.pushEntities(); this.level.getProfiler().pop(); // Paper start @@ -239,7 +239,7 @@ index 57c448ee93df76fc2a17c75fafc78408d720ced3..e918f7cc7fdfdeb1b7b69488eadf69f0 Location from = new Location(this.level.getWorld(), this.xo, this.yo, this.zo, this.yRotO, this.xRotO); Location to = new Location (this.level.getWorld(), this.getX(), this.getY(), this.getZ(), this.getYRot(), this.getXRot()); io.papermc.paper.event.entity.EntityMoveEvent event = new io.papermc.paper.event.entity.EntityMoveEvent(this.getBukkitLivingEntity(), from, to.clone()); -@@ -3229,6 +3231,21 @@ public abstract class LivingEntity extends Entity { +@@ -3232,6 +3234,21 @@ public abstract class LivingEntity extends Entity { absMoveTo(event.getTo().getX(), event.getTo().getY(), event.getTo().getZ(), event.getTo().getYaw(), event.getTo().getPitch()); } } @@ -262,10 +262,10 @@ index 57c448ee93df76fc2a17c75fafc78408d720ced3..e918f7cc7fdfdeb1b7b69488eadf69f0 // Paper end if (!this.level.isClientSide && this.isSensitiveToWater() && this.isInWaterRainOrBubble()) { diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 154b3c767d079f72643c826b962892c1029b0a1b..c35c5d58523be370732a19c52db4f8e94c88b12f 100644 +index 0ce0e7a923da812a02d9ab83607d3cc9c87047df..a597a09b428cb2b3dc7fef5503ef705cf7b2a1fb 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -139,6 +139,8 @@ public abstract class Mob extends LivingEntity { +@@ -140,6 +140,8 @@ public abstract class Mob extends LivingEntity { this.targetSelector = new GoalSelector(world.getProfilerSupplier()); this.lookControl = new LookControl(this); this.moveControl = new MoveControl(this); @@ -274,7 +274,7 @@ index 154b3c767d079f72643c826b962892c1029b0a1b..c35c5d58523be370732a19c52db4f8e9 this.jumpControl = new JumpControl(this); this.bodyRotationControl = this.createBodyControl(); this.navigation = this.createNavigation(world); -@@ -1278,7 +1280,7 @@ public abstract class Mob extends LivingEntity { +@@ -1274,7 +1276,7 @@ public abstract class Mob extends LivingEntity { protected void onOffspringSpawnedFromEgg(Player player, Mob child) {} protected InteractionResult mobInteract(Player player, InteractionHand hand) { @@ -283,7 +283,7 @@ index 154b3c767d079f72643c826b962892c1029b0a1b..c35c5d58523be370732a19c52db4f8e9 } public boolean isWithinRestriction() { -@@ -1637,4 +1639,52 @@ public abstract class Mob extends LivingEntity { +@@ -1633,4 +1635,52 @@ public abstract class Mob extends LivingEntity { return itemmonsteregg == null ? null : new ItemStack(itemmonsteregg); } @@ -1872,7 +1872,7 @@ index 56838c9f214c0f75041e75c45ad1a0c72fcacc66..151bb142c553d733367a92b39b430b53 if (i > 100) { diff --git a/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java b/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java -index 77f56978449ece43bdca28a50a0681cda9f89eba..4d5ceb4ce913248ad404393e4f0c75cad24029bb 100644 +index 84015b5bf4b9a11670ad4d984844a431876efb63..5f61fcffebf4d853711a38d1f315f3def25e31a7 100644 --- a/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java +++ b/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java @@ -43,6 +43,18 @@ public class TropicalFish extends AbstractSchoolingFish { @@ -1895,7 +1895,7 @@ index 77f56978449ece43bdca28a50a0681cda9f89eba..4d5ceb4ce913248ad404393e4f0c75ca return "entity.minecraft.tropical_fish.predefined." + variant; } diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -index 01d8af06f19427354cac95d691e65d31253fef94..631539a752a038926355c23aeb160af64f363a61 100644 +index 925f16d5eb092518ef774f69a8d99689feb0f5d7..c86f13d190b41cb18dd833af39c7b4916068fd69 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java +++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java @@ -90,6 +90,18 @@ public class Turtle extends Animal { @@ -1915,7 +1915,7 @@ index 01d8af06f19427354cac95d691e65d31253fef94..631539a752a038926355c23aeb160af6 + // Purpur end + public void setHomePos(BlockPos pos) { - this.entityData.set(Turtle.HOME_POS, pos.immutable()); // Paper - called with mutablepos... + this.entityData.set(Turtle.HOME_POS, pos); } @@ -192,6 +204,7 @@ public class Turtle extends Animal { @@ -2012,7 +2012,7 @@ index 80caabee4d2100208f117a1c3e35247b65e318ad..bca3300e06d6eb0c6acdfb11d715a1e8 this.targetSelector.addGoal(2, new OwnerHurtTargetGoal(this)); this.targetSelector.addGoal(3, (new HurtByTargetGoal(this, new Class[0])).setAlertOthers(new Class[0])); // CraftBukkit - decompile error diff --git a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java -index 25ebcb30cf5165675f26802983b6f68404b93b06..e076d03025690492c2226f03d777eba714819300 100644 +index c72819b03f9339dadd691155f196f6744f14da75..06c831f1a4b5159afadecff4a25248397ecbd2c7 100644 --- a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java +++ b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java @@ -90,6 +90,23 @@ public class Axolotl extends Animal implements LerpingModel, Bucketable { @@ -2039,7 +2039,7 @@ index 25ebcb30cf5165675f26802983b6f68404b93b06..e076d03025690492c2226f03d777eba7 @Override public Map getModelRotationValues() { return this.modelRotationValues; -@@ -513,14 +530,22 @@ public class Axolotl extends Animal implements LerpingModel, Bucketable { +@@ -509,14 +526,22 @@ public class Axolotl extends Animal implements LerpingModel, Bucketable { private static class AxolotlMoveControl extends SmoothSwimmingMoveControl { private final Axolotl axolotl; @@ -2062,7 +2062,7 @@ index 25ebcb30cf5165675f26802983b6f68404b93b06..e076d03025690492c2226f03d777eba7 if (!this.axolotl.isPlayingDead()) { super.tick(); } -@@ -535,9 +560,9 @@ public class Axolotl extends Animal implements LerpingModel, Bucketable { +@@ -531,9 +556,9 @@ public class Axolotl extends Animal implements LerpingModel, Bucketable { } @Override @@ -2075,10 +2075,10 @@ index 25ebcb30cf5165675f26802983b6f68404b93b06..e076d03025690492c2226f03d777eba7 } diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -index 580f3e8de2e10ddc01430e84fc42e243736c4810..c0d85eb70dbe1d85d07b47a41a43d19506586399 100644 +index 15787afad42f9299638a1c9e57d26678805f18ee..69a8578f86c42bde4c1df0c5ead033dae248d3ab 100644 --- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -@@ -70,6 +70,18 @@ public class Goat extends Animal { +@@ -72,6 +72,18 @@ public class Goat extends Animal { this.getNavigation().setCanFloat(true); } @@ -2097,7 +2097,7 @@ index 580f3e8de2e10ddc01430e84fc42e243736c4810..c0d85eb70dbe1d85d07b47a41a43d195 @Override protected Brain.Provider brainProvider() { return Brain.provider((Collection) Goat.MEMORY_TYPES, (Collection) Goat.SENSOR_TYPES); -@@ -133,6 +145,7 @@ public class Goat extends Animal { +@@ -147,6 +159,7 @@ public class Goat extends Animal { @Override protected void customServerAiStep() { this.level.getProfiler().push("goatBrain"); @@ -2106,10 +2106,10 @@ index 580f3e8de2e10ddc01430e84fc42e243736c4810..c0d85eb70dbe1d85d07b47a41a43d195 this.level.getProfiler().pop(); this.level.getProfiler().push("goatActivityUpdate"); diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -index ba58e066cca533dfed7610a730c4dd7423fe124d..d91b6951dc25097e6f84296406634e48e1f13404 100644 +index 5c104bf219e5e4acb8acfb160bd92f0a0621d864..fbd96e516ac34f874b0cca2da9076120e29254fb 100644 --- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java +++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -@@ -114,12 +114,22 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, +@@ -113,12 +113,22 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, protected AbstractHorse(EntityType type, Level world) { super(type, world); @@ -2132,7 +2132,7 @@ index ba58e066cca533dfed7610a730c4dd7423fe124d..d91b6951dc25097e6f84296406634e48 this.goalSelector.addGoal(1, new PanicGoal(this, 1.2D)); this.goalSelector.addGoal(1, new RunAroundLikeCrazyGoal(this, 1.2D)); this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D, AbstractHorse.class)); -@@ -127,6 +137,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, +@@ -126,6 +136,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, this.goalSelector.addGoal(6, new WaterAvoidingRandomStrollGoal(this, 0.7D)); this.goalSelector.addGoal(7, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(8, new RandomLookAroundGoal(this)); @@ -2373,7 +2373,7 @@ index 305a891e4b51d1031d9e9238ff00e2ea7de8d954..84625d09df800fcfd477fc493fb5f824 protected void defineSynchedData() { } diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -index c98202092752a9015aaf95bd1471135b88e84425..00e74d4fb1c719475bed27db13774bc7cc7447a7 100644 +index 29aa428e019681af8d6b0020c12b18660ff6af6c..5df112f87fba042f13f615a22a5c6f850b779bf7 100644 --- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java @@ -102,6 +102,7 @@ public class EnderDragon extends Mob implements Enemy { @@ -2517,7 +2517,7 @@ index c98202092752a9015aaf95bd1471135b88e84425..00e74d4fb1c719475bed27db13774bc7 this.dragonFight.updateDragon(this); } diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -index 1c8f6863b976cfcb559de9b3e3cf9292831166ee..68c5609845617bf5aeb82ea4e3a88bdccb8273cc 100644 +index 6e3bd5146c687922e7b4680d59a1ae2d1480ad40..9d879d85d6ca7d1163b05f15bb13baeeb59eea5b 100644 --- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java @@ -83,6 +83,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob @@ -3710,10 +3710,10 @@ index 2459ae800a5f6b234a4f4bb1cd3738e4e9cac67d..e66cc79dc61721b31ffb743f68f4388c this.targetSelector.addGoal(2, new NearestAttackableTargetGoal<>(this, Player.class, true)); } diff --git a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java -index c87d1f8a057e98f7f4ad7e11d89bfa791a7bae90..dca18730731407bb68ad32852c7994062b0b4ba6 100644 +index fcec4120113de6c403892b38f077593b3b90d10c..55c09358766b2e7e871d9922b42e6e6b4157dab6 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java +++ b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java -@@ -25,6 +25,18 @@ public class Skeleton extends AbstractSkeleton { +@@ -23,6 +23,18 @@ public class Skeleton extends AbstractSkeleton { super(type, world); } @@ -3913,7 +3913,7 @@ index 0cfcd9c69213d75b52dc93392da727208c13150d..6a8a0cd09e0bf17c7ecb6e55342b645f BlockPos blockPos = pos; diff --git a/src/main/java/net/minecraft/world/entity/monster/Strider.java b/src/main/java/net/minecraft/world/entity/monster/Strider.java -index 1e81e115fe28fe20c18d25372892ba714699303f..a4b14b7eaaef3f70cedf1b71afdcef8a0702dc94 100644 +index fc772387713d4313ec8f90a8abcaf7b703fd9f21..6c0af80f47c53e4573efb0c50412d289c0bb5540 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Strider.java +++ b/src/main/java/net/minecraft/world/entity/monster/Strider.java @@ -97,6 +97,18 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { @@ -3943,7 +3943,7 @@ index 1e81e115fe28fe20c18d25372892ba714699303f..a4b14b7eaaef3f70cedf1b71afdcef8a this.goalSelector.addGoal(1, this.panicGoal); this.goalSelector.addGoal(2, new BreedGoal(this, 1.0D)); this.temptGoal = new TemptGoal(this, 1.4D, Strider.TEMPT_ITEMS, false); -@@ -437,7 +450,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { +@@ -438,7 +451,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { if (!enuminteractionresult.consumesAction()) { ItemStack itemstack = player.getItemInHand(hand); @@ -4205,7 +4205,7 @@ index 056c0e66d2f90850906c78a25d759f22c20e4d35..8fd4e26ebe0527fd8a69b15095dd4091 this.level.getProfiler().pop(); this.updateActivity(); diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index bb3b932c57fd1e5b1517940c7602c7f4aeeaf17e..4880a8f9576339ed87dbeeebc76d3332ab14d27c 100644 +index ddda5d1e85864030db0cfecbf7a5fe134d7013a1..abcdfc77ccef27db11a9e0fe8cb29f84f5e82391 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java +++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java @@ -107,11 +107,25 @@ public class Zombie extends Monster { @@ -4258,7 +4258,7 @@ index a2fbab27980d7f52033fd542220d534cefcc4747..5744d181b91bcf7f8202c801bce42c96 protected void defineSynchedData() { super.defineSynchedData(); diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java -index 233b390541acddcf815db4a8f299496eaea4f758..b86d29a16f4c4ad0b166506fe31e64b902ecc06b 100644 +index 025d53ab0787d596f4c486b15d286b9547838e16..708ba2c64a0736a30e477017b46dc711948cfc3d 100644 --- a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java +++ b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java @@ -58,6 +58,18 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { @@ -4472,7 +4472,7 @@ index c4f7c94255e4631a3c0355f9260132ba28296f50..d6c31596e21041a124a263054ccb6447 this.setTradingPlayer(player); this.openTradingScreen(player, this.getDisplayName(), 1); diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 069f658003d96a05aac0b30af1d89f15ea554475..5af2c68a328b5465ce487f312f0c50df3cb44aac 100644 +index 19980b2d627eb3cacf8d0c3e6785ad2206910fbc..dd7e76f9e5fd05a38507f32b3e021efe43315049 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java +++ b/src/main/java/net/minecraft/world/entity/player/Player.java @@ -193,6 +193,19 @@ public abstract class Player extends LivingEntity { @@ -4513,7 +4513,7 @@ index 85a509e4fc0e4b9f182585e17b7deab2fea7e6c0..f1a12b147d55e34d4f8374593640a311 public void tick() { super.tick(); diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -index 69f439851fe1ff07d827eaed274940a5783d5f6c..07853aff3d42ce50799406ee1c14389b5f715b51 100644 +index 37356b36f0ae12d55150f399318581fa77c30cee..4e667be589fd95eb61e57a99448939a945c59e5e 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java @@ -35,7 +35,7 @@ public abstract class Projectile extends Entity { @@ -4540,10 +4540,10 @@ index fcf5f32c32eb17f7580b3c8c9190e36725f793ad..d92812cb334b590b728afb828dae61da + } } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index dd8b29f3aa58c5c7302ec5e93bef4190da913e3a..9012e278f23466cb052391d60c55179fccaa14ea 100644 +index 15472bf9cfd279b1873f7a20c250a2c089debad1..59c424426e0eddd0b62db769ec289f5903583a84 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -92,4 +92,532 @@ public class PurpurWorldConfig { +@@ -91,4 +91,532 @@ public class PurpurWorldConfig { final Map value = PurpurConfig.getMap("world-settings." + worldName + "." + path, null); return value.isEmpty() ? fallback : value; } @@ -5718,10 +5718,10 @@ index 0000000000000000000000000000000000000000..8eefb7b7eb33aecf48ac206d3f0139e0 + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index c0a508295d2e68d92ec8d24e14f9b7626911f548..edc08af4ec2ce6e90c30da286c0ba5ac16efd3fc 100644 +index 8246ad7ebecdfc0b7519fe4412fef7b07407e850..6b85ba7d9bad9f648b4a6cb5a3938509b3e73cca 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1216,4 +1216,27 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -1207,4 +1207,27 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { return getHandle().isTicking(); } // Paper end @@ -5750,7 +5750,7 @@ index c0a508295d2e68d92ec8d24e14f9b7626911f548..edc08af4ec2ce6e90c30da286c0ba5ac + // Purpur end } diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index a59a449c0a7b76527f009031aee2d11d6b43cadf..d055b5548848c87d9ce8372b6c64df8d081eb779 100644 +index 9e08668ecf224dae91405da994cc7d346499a969..f9196241a0d6f9a70c674f7dfca5e013f9839398 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -537,6 +537,15 @@ public class CraftEventFactory { diff --git a/patches/server/0006-Configurable-entity-base-attributes.patch b/patches/server/0006-Configurable-entity-base-attributes.patch index 3a9936ba5..ad5e23706 100644 --- a/patches/server/0006-Configurable-entity-base-attributes.patch +++ b/patches/server/0006-Configurable-entity-base-attributes.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Configurable entity base attributes diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 37db4d89996cde1bdcdc513757be75612d444f97..f3c5d5906a8ce64c93f4fb1f63474800a24ef012 100644 +index 081506649de3929becd448664e2c5b0d7b4859fc..a43c95abc8f6d0226ca097495ed9aeab0649d02b 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java @@ -150,7 +150,7 @@ import org.bukkit.plugin.PluginManager; @@ -34,7 +34,7 @@ index 8f8bc29d847801938e251904b8334b4b31bd21c5..87d01bebbb179eec53323e9e23db011a @Override diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index e918f7cc7fdfdeb1b7b69488eadf69f0d4a3cb05..b3921a084d20a25a6ea775faae43493030aafd77 100644 +index 911a6f63e8ffeacc95fa49cdf99140c1ce3d06b1..ecc06c084625c24b9ac8045b1d53e75dfffbdea5 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -288,6 +288,7 @@ public abstract class LivingEntity extends Entity { @@ -411,7 +411,7 @@ index 151bb142c553d733367a92b39b430b536e569480..f96def2ebdf114823c322c2d4318d039 @Override diff --git a/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java b/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java -index 4d5ceb4ce913248ad404393e4f0c75cad24029bb..5ab75a110d45dd9a2c341820b53838a55cac6bb3 100644 +index 5f61fcffebf4d853711a38d1f315f3def25e31a7..fcd3166a7c818ef089ccf2a687596bef51ad14a6 100644 --- a/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java +++ b/src/main/java/net/minecraft/world/entity/animal/TropicalFish.java @@ -53,6 +53,11 @@ public class TropicalFish extends AbstractSchoolingFish { @@ -427,7 +427,7 @@ index 4d5ceb4ce913248ad404393e4f0c75cad24029bb..5ab75a110d45dd9a2c341820b53838a5 public static String getPredefinedName(int variant) { diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -index 631539a752a038926355c23aeb160af64f363a61..66c01b8300bc09ace27e4d1a30ee9274c69fcc9a 100644 +index c86f13d190b41cb18dd833af39c7b4916068fd69..31568adcf4a89b11e61f455a15326c7f72bf487e 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java +++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java @@ -100,6 +100,11 @@ public class Turtle extends Animal { @@ -459,7 +459,7 @@ index bca3300e06d6eb0c6acdfb11d715a1e8447c9198..ae416b70109c959980b3115da6e97df1 @Override diff --git a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java -index e076d03025690492c2226f03d777eba714819300..15a0aeb1131618ea27620c5893a7448af624b6dd 100644 +index 06c831f1a4b5159afadecff4a25248397ecbd2c7..cdc8e548ea48efd481ad6495cab6d8e9f77cfb15 100644 --- a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java +++ b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java @@ -105,6 +105,11 @@ public class Axolotl extends Animal implements LerpingModel, Bucketable { @@ -475,10 +475,10 @@ index e076d03025690492c2226f03d777eba714819300..15a0aeb1131618ea27620c5893a7448a @Override diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -index c0d85eb70dbe1d85d07b47a41a43d19506586399..9092eac3e7e15845d14175cad8030f4ea60d43ad 100644 +index 69a8578f86c42bde4c1df0c5ead033dae248d3ab..26d8b59dd385cc89af64fbbf3f8e09c2e9b41b8c 100644 --- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -@@ -80,6 +80,11 @@ public class Goat extends Animal { +@@ -82,6 +82,11 @@ public class Goat extends Animal { public boolean rideableUnderWater() { return level.purpurConfig.goatRidableInWater; } @@ -491,10 +491,10 @@ index c0d85eb70dbe1d85d07b47a41a43d19506586399..9092eac3e7e15845d14175cad8030f4e @Override diff --git a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -index d91b6951dc25097e6f84296406634e48e1f13404..a580afaa886c04df3caf9037b9b9cf17b92cee7b 100644 +index fbd96e516ac34f874b0cca2da9076120e29254fb..54401524a2be27e18d5e616e9bb8481007c7af88 100644 --- a/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java +++ b/src/main/java/net/minecraft/world/entity/animal/horse/AbstractHorse.java -@@ -125,6 +125,32 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, +@@ -124,6 +124,32 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, public boolean isRidable() { return false; // vanilla handles } @@ -527,7 +527,7 @@ index d91b6951dc25097e6f84296406634e48e1f13404..a580afaa886c04df3caf9037b9b9cf17 // Purpur end @Override -@@ -1142,7 +1168,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, +@@ -1134,7 +1160,7 @@ public abstract class AbstractHorse extends Animal implements ContainerListener, entityData = new AgeableMob.AgeableMobGroupData(0.2F); } @@ -719,7 +719,7 @@ index d328e36015b6b7d6a9e093fbe232eb5ecda46d96..6ca7b168a1ea26102922d9377e52662f public static AttributeSupplier.Builder createAttributes() { diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -index 00e74d4fb1c719475bed27db13774bc7cc7447a7..177c5b385613ac08a75500013326874ff31a1992 100644 +index 5df112f87fba042f13f615a22a5c6f850b779bf7..d3caa18f5de2d96eae691655dd13e83f82c61e0c 100644 --- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java @@ -154,6 +154,11 @@ public class EnderDragon extends Mob implements Enemy { @@ -735,7 +735,7 @@ index 00e74d4fb1c719475bed27db13774bc7cc7447a7..177c5b385613ac08a75500013326874f public static AttributeSupplier.Builder createAttributes() { diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -index 68c5609845617bf5aeb82ea4e3a88bdccb8273cc..96631591db018545120ba1c9980a03eb596eb6e5 100644 +index 9d879d85d6ca7d1163b05f15bb13baeeb59eea5b..2f33897a74ff2bd629b4ffacc4a1e1e0cd6b7987 100644 --- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java @@ -196,6 +196,11 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob @@ -1083,10 +1083,10 @@ index e66cc79dc61721b31ffb743f68f4388cc499a92d..0d60d6352294fadc1a26579a712b01cf @Override diff --git a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java -index dca18730731407bb68ad32852c7994062b0b4ba6..070e1ef31d4dabb943fc57ea4f46bb8dfffed5c0 100644 +index 55c09358766b2e7e871d9922b42e6e6b4157dab6..a36c78054cb70d310d9bb21c849e05b588aca8de 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Skeleton.java +++ b/src/main/java/net/minecraft/world/entity/monster/Skeleton.java -@@ -35,6 +35,11 @@ public class Skeleton extends AbstractSkeleton { +@@ -33,6 +33,11 @@ public class Skeleton extends AbstractSkeleton { public boolean rideableUnderWater() { return level.purpurConfig.skeletonRidableInWater; } @@ -1175,7 +1175,7 @@ index 6a8a0cd09e0bf17c7ecb6e55342b645f111dac22..d2eb323c8957ac4ccdf2ab73d83dbbde public static boolean checkStraySpawnRules(EntityType type, ServerLevelAccessor world, MobSpawnType spawnReason, BlockPos pos, Random random) { diff --git a/src/main/java/net/minecraft/world/entity/monster/Strider.java b/src/main/java/net/minecraft/world/entity/monster/Strider.java -index a4b14b7eaaef3f70cedf1b71afdcef8a0702dc94..6b9316536e09c52be2cd1e9cdc71529f4ddb69c6 100644 +index 6c0af80f47c53e4573efb0c50412d289c0bb5540..2d7b83ce2e0e26ef3976514ec8921a718ccc28bd 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Strider.java +++ b/src/main/java/net/minecraft/world/entity/monster/Strider.java @@ -107,6 +107,11 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { @@ -1271,7 +1271,7 @@ index 8fd4e26ebe0527fd8a69b15095dd4091fcdde206..7afc9b83e385d711096db5ea9c6c259f @Override diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index 4880a8f9576339ed87dbeeebc76d3332ab14d27c..6eef158223d361f58f49810335895478430dda2d 100644 +index abcdfc77ccef27db11a9e0fe8cb29f84f5e82391..3b7e261eae87445387f5a34d6fbb420c4cd0cf0f 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java +++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java @@ -117,6 +117,11 @@ public class Zombie extends Monster { @@ -1286,7 +1286,7 @@ index 4880a8f9576339ed87dbeeebc76d3332ab14d27c..6eef158223d361f58f49810335895478 // Purpur end @Override -@@ -584,7 +589,7 @@ public class Zombie extends Monster { +@@ -589,7 +594,7 @@ public class Zombie extends Monster { } protected void randomizeReinforcementsChance() { @@ -1317,7 +1317,7 @@ index 5744d181b91bcf7f8202c801bce42c96acbdb524..ce15ebc6248eaa849ccb1de4319b51e8 @Override diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java -index b86d29a16f4c4ad0b166506fe31e64b902ecc06b..04d09123f70a192f1283c0d7e8a8254f3d30889a 100644 +index 708ba2c64a0736a30e477017b46dc711948cfc3d..8464026df1b46ad30301fed4944aa1d3cd39bd9e 100644 --- a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java +++ b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java @@ -68,6 +68,11 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { @@ -1422,10 +1422,10 @@ index d6c31596e21041a124a263054ccb6447829eccdd..d2588a91b55aebdecf8e1644498111cc @Override diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 9012e278f23466cb052391d60c55179fccaa14ea..a8cdd158a8207c875ed94f550dc8f5d7e05f472b 100644 +index 59c424426e0eddd0b62db769ec289f5903583a84..332754f541165590ecd96d650f16f40924b4263a 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -103,257 +103,573 @@ public class PurpurWorldConfig { +@@ -102,257 +102,573 @@ public class PurpurWorldConfig { } public boolean axolotlRidable = false; @@ -1999,7 +1999,7 @@ index 9012e278f23466cb052391d60c55179fccaa14ea..a8cdd158a8207c875ed94f550dc8f5d7 } public boolean phantomRidable = false; -@@ -362,6 +678,7 @@ public class PurpurWorldConfig { +@@ -361,6 +677,7 @@ public class PurpurWorldConfig { public float phantomFlameDamage = 1.0F; public int phantomFlameFireTime = 8; public boolean phantomAllowGriefing = false; @@ -2007,7 +2007,7 @@ index 9012e278f23466cb052391d60c55179fccaa14ea..a8cdd158a8207c875ed94f550dc8f5d7 private void phantomSettings() { phantomRidable = getBoolean("mobs.phantom.ridable", phantomRidable); phantomRidableInWater = getBoolean("mobs.phantom.ridable-in-water", phantomRidableInWater); -@@ -369,255 +686,559 @@ public class PurpurWorldConfig { +@@ -368,255 +685,559 @@ public class PurpurWorldConfig { phantomFlameDamage = (float) getDouble("mobs.phantom.flames.damage", phantomFlameDamage); phantomFlameFireTime = getInt("mobs.phantom.flames.fire-time", phantomFlameFireTime); phantomAllowGriefing = getBoolean("mobs.phantom.allow-griefing", phantomAllowGriefing); diff --git a/patches/server/0007-Timings-stuff.patch b/patches/server/0007-Timings-stuff.patch index cf3e2611c..f540946f0 100644 --- a/patches/server/0007-Timings-stuff.patch +++ b/patches/server/0007-Timings-stuff.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Timings stuff diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java -index 9d920565ff65a84b1b9a2a4777fd8bc8f07e0153..33263af33b4355e5079fadfcfae77d038aebd2c4 100644 +index 2ff4d4921e2076abf415bd3c8f5173ecd6222168..dd9efe48006d060397ef3932afa3665f89ea99b3 100644 --- a/src/main/java/co/aikar/timings/TimingsExport.java +++ b/src/main/java/co/aikar/timings/TimingsExport.java -@@ -226,10 +226,15 @@ public class TimingsExport extends Thread { +@@ -226,9 +226,14 @@ public class TimingsExport extends Thread { // Information on the users Config parent.put("config", createObject( @@ -17,15 +17,14 @@ index 9d920565ff65a84b1b9a2a4777fd8bc8f07e0153..33263af33b4355e5079fadfcfae77d03 + pair("bukkit", mapAsJSON(Bukkit.spigot().getBukkitConfig(), null)), pair("spigot", mapAsJSON(Bukkit.spigot().getSpigotConfig(), null)), pair("bukkit", mapAsJSON(Bukkit.spigot().getBukkitConfig(), null)), - pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null)), // Tuinity - add config to timings report -- pair("tuinity", mapAsJSON(Bukkit.spigot().getTuinityConfig(), null)) // Tuinity - add config to timings report -+ pair("tuinity", mapAsJSON(Bukkit.spigot().getTuinityConfig(), null)), // Tuinity - add config to timings report +- pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null)) ++ pair("paper", mapAsJSON(Bukkit.spigot().getPaperConfig(), null)), // Tuinity - add config to timings report + pair("purpur", mapAsJSON(Bukkit.spigot().getPurpurConfig(), null)) + // Purpur end )); new TimingsExport(listeners, parent, history).start(); -@@ -270,6 +275,19 @@ public class TimingsExport extends Thread { +@@ -269,6 +274,19 @@ public class TimingsExport extends Thread { return timingsCost; } @@ -45,7 +44,7 @@ index 9d920565ff65a84b1b9a2a4777fd8bc8f07e0153..33263af33b4355e5079fadfcfae77d03 private static JSONObject mapAsJSON(ConfigurationSection config, String parentKey) { JSONObject object = new JSONObject(); -@@ -306,7 +324,7 @@ public class TimingsExport extends Thread { +@@ -305,7 +323,7 @@ public class TimingsExport extends Thread { String response = null; String timingsURL = null; try { @@ -55,7 +54,7 @@ index 9d920565ff65a84b1b9a2a4777fd8bc8f07e0153..33263af33b4355e5079fadfcfae77d03 String hostName = "BrokenHost"; try { diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java -index f7c46b8173d30192853c875675a1ed0f59170b34..d2d602eda6f2899f809c31aa1082540e1c7855b9 100644 +index d92812cb334b590b728afb828dae61da483d8d56..e381ff1767b1ef2275ed112c27c491a3671b5039 100644 --- a/src/main/java/net/pl3x/purpur/PurpurConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java @@ -159,4 +159,10 @@ public class PurpurConfig { diff --git a/patches/server/0011-AFK-API.patch b/patches/server/0011-AFK-API.patch index c6e6a215a..baf55d17b 100644 --- a/patches/server/0011-AFK-API.patch +++ b/patches/server/0011-AFK-API.patch @@ -5,10 +5,10 @@ Subject: [PATCH] AFK API diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index fde98f9c4a3e151dbc277541fbd2cc0a8d957069..011ebba656174cb415d43244c50db250fa8f0a30 100644 +index d9df08cdf065b0316f43ca53988ccbe119e5106f..687f1bfa07246e2297be7124edf5bf7568a7ce09 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1952,8 +1952,58 @@ public class ServerPlayer extends Player { +@@ -1936,8 +1936,58 @@ public class ServerPlayer extends Player { public void resetLastActionTime() { this.lastActionTime = Util.getMillis(); @@ -68,10 +68,10 @@ index fde98f9c4a3e151dbc277541fbd2cc0a8d957069..011ebba656174cb415d43244c50db250 return this.stats; } diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 17a6857f5ee70120d664e46426f739ef2dae0a0a..210b774eae5d76823e171016e9e6b706e8da4f07 100644 +index dbb5f0bd721b2490f727a0b2462cf215e729947d..c5863036c0fdeee755494dfc55c527f4696c92c1 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -391,6 +391,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser +@@ -390,6 +390,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser } if (this.player.getLastActionTime() > 0L && this.server.getPlayerIdleTimeout() > 0 && Util.getMillis() - this.player.getLastActionTime() > (long) (this.server.getPlayerIdleTimeout() * 1000 * 60)) { @@ -84,7 +84,7 @@ index 17a6857f5ee70120d664e46426f739ef2dae0a0a..210b774eae5d76823e171016e9e6b706 this.player.resetLastActionTime(); // CraftBukkit - SPIGOT-854 this.disconnect(new TranslatableComponent("multiplayer.disconnect.idling"), org.bukkit.event.player.PlayerKickEvent.Cause.IDLING); // Paper - kick event cause } -@@ -663,6 +669,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser +@@ -648,6 +654,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser this.lastYaw = to.getYaw(); this.lastPitch = to.getPitch(); @@ -93,16 +93,18 @@ index 17a6857f5ee70120d664e46426f739ef2dae0a0a..210b774eae5d76823e171016e9e6b706 // Skip the first time we do this if (true) { // Spigot - don't skip any move events Location oldTo = to.clone(); -@@ -1440,7 +1448,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser +@@ -1373,8 +1381,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser + boolean flag1 = false; if (!this.player.isChangingDimension() && d11 > org.spigotmc.SpigotConfig.movedWronglyThreshold && !this.player.isSleeping() && !this.player.gameMode.isCreative() && this.player.gameMode.getGameModeForPlayer() != GameType.SPECTATOR) { // Spigot - flag1 = true; // Tuinity - diff on change, this should be moved wrongly +- flag1 = true; - ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!", this.player.getName().getString()); ++ flag1 = true; // Tuinity - diff on change, this should be moved wrongly + ServerGamePacketListenerImpl.LOGGER.warn("{} moved wrongly!, ({})", this.player.getName().getString(), d11); // Purpur } this.player.absMoveTo(d0, d1, d2, f, f1); -@@ -1490,6 +1498,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser +@@ -1413,6 +1421,8 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser this.lastYaw = to.getYaw(); this.lastPitch = to.getPitch(); @@ -146,7 +148,7 @@ index a060cca08631fb42041e3a79a9abc422fe7757af..e7b11d1ba984ea14f0cdf8e84f9eaab4 private EntitySelector() {} // Paper start diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 5af2c68a328b5465ce487f312f0c50df3cb44aac..e0544699714fd4c6ef2deb17f49b8246a7021463 100644 +index dd7e76f9e5fd05a38507f32b3e021efe43315049..995098c7e7eaa15fa1cda40fdb763cf4607f3381 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java +++ b/src/main/java/net/minecraft/world/entity/player/Player.java @@ -196,6 +196,13 @@ public abstract class Player extends LivingEntity { @@ -164,10 +166,10 @@ index 5af2c68a328b5465ce487f312f0c50df3cb44aac..e0544699714fd4c6ef2deb17f49b8246 public boolean processClick(InteractionHand hand) { Entity vehicle = getRootVehicle(); diff --git a/src/main/java/net/minecraft/world/level/EntityGetter.java b/src/main/java/net/minecraft/world/level/EntityGetter.java -index 6a4e44dd8935018d1b5283761dfb8e855be62987..afe70b0d5bd98d05bbb7afc756108f09d35a5848 100644 +index 325e244c46ec208a2e7e18d71ccbbfcc25fc1bce..3645ebf52ad1461937ce6cc0cf38a92176627227 100644 --- a/src/main/java/net/minecraft/world/level/EntityGetter.java +++ b/src/main/java/net/minecraft/world/level/EntityGetter.java -@@ -161,7 +161,7 @@ public interface EntityGetter { +@@ -145,7 +145,7 @@ public interface EntityGetter { default boolean hasNearbyAlivePlayer(double x, double y, double z, double range) { for(Player player : this.players()) { @@ -198,10 +200,10 @@ index 0373f3fb256d796c090a3eddcf6e2be0ce25ec16..d35285b58b7141f2e9f438331faf566d public static String timingsUrl = "https://timings.pl3x.net"; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index a8cdd158a8207c875ed94f550dc8f5d7e05f472b..ec0cef53349bc20e936adbbe6138145bcaca945d 100644 +index 332754f541165590ecd96d650f16f40924b4263a..bfc1077dc7d5cfb48f5cc98b289b98d44474280e 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -93,6 +93,17 @@ public class PurpurWorldConfig { +@@ -92,6 +92,17 @@ public class PurpurWorldConfig { return value.isEmpty() ? fallback : value; } @@ -220,7 +222,7 @@ index a8cdd158a8207c875ed94f550dc8f5d7e05f472b..ec0cef53349bc20e936adbbe6138145b public boolean untamedTamablesAreRidable = true; public boolean useNightVisionWhenRiding = false; diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 1da5b6f73e78a697031f7662e68c546543fb9d1a..3eeb138d060f86e0f928fc59234563528d83bf65 100644 +index 2e41a1bc20f257f4f461a74623a3ffe2d3112fdd..d858590b7ffea28dcfbc48973df6e3c916886b65 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -423,10 +423,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @@ -240,7 +242,7 @@ index 1da5b6f73e78a697031f7662e68c546543fb9d1a..3eeb138d060f86e0f928fc5923456352 for (ServerPlayer player : (List) server.getHandle().players) { if (player.getBukkitEntity().canSee(this)) { player.connection.send(new ClientboundPlayerInfoPacket(ClientboundPlayerInfoPacket.Action.UPDATE_DISPLAY_NAME, this.getHandle())); -@@ -2529,4 +2534,21 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -2475,4 +2480,21 @@ public class CraftPlayer extends CraftHumanEntity implements Player { return this.spigot; } // Spigot end @@ -263,7 +265,7 @@ index 1da5b6f73e78a697031f7662e68c546543fb9d1a..3eeb138d060f86e0f928fc5923456352 + // Purpur end } diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java -index 7361bf04de16d0526dc4cdbd0f564713df041d90..48ce9975b872c259dae1348148203fdbcb25e2ee 100644 +index a08583863f9fa08016bdfc7949a273eaa4429927..f36c97529edbd3642d0ba37887a232226f766a35 100644 --- a/src/main/java/org/spigotmc/ActivationRange.java +++ b/src/main/java/org/spigotmc/ActivationRange.java @@ -194,6 +194,7 @@ public class ActivationRange diff --git a/patches/server/0012-Bring-back-server-name.patch b/patches/server/0012-Bring-back-server-name.patch index 35e183edb..03892b169 100644 --- a/patches/server/0012-Bring-back-server-name.patch +++ b/patches/server/0012-Bring-back-server-name.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Bring back server name diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java -index c9d80a5430cc66d6189bf337770af43121a5bfd5..329336d413317388455fbbf03036cee13186b00d 100644 +index 0544ac93513d3a274bfb53bb6120bd598f4d603b..9ce5984fbeba4839290c9d213d441957d38fe835 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServerProperties.java @@ -18,6 +18,7 @@ public class DedicatedServerProperties extends Settings defaultType; diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -index d1ca61f394c0efa1688069e5d1fb529348d5985f..6eb42189021647887848748e7070d171ddb17bd9 100644 +index 1a8ff7339c58a4fffb051a090a7b8c34cb346a61..ae9919b5df569c0e6e4a42af57542c6ffb30960c 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java -@@ -404,4 +404,18 @@ public final class CraftItemFactory implements ItemFactory { +@@ -407,4 +407,18 @@ public final class CraftItemFactory implements ItemFactory { new net.md_5.bungee.api.chat.TextComponent(customName)); } // Paper end diff --git a/patches/server/0019-Player-invulnerabilities.patch b/patches/server/0019-Player-invulnerabilities.patch index a91225df0..c426b73e4 100644 --- a/patches/server/0019-Player-invulnerabilities.patch +++ b/patches/server/0019-Player-invulnerabilities.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Player invulnerabilities diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 011ebba656174cb415d43244c50db250fa8f0a30..1653928faba2422bfa5fff93be5b4d8f9c9447ed 100644 +index 687f1bfa07246e2297be7124edf5bf7568a7ce09..335893049b3625a80cef78526eff3761cd8d5fbf 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -338,6 +338,7 @@ public class ServerPlayer extends Player { +@@ -334,6 +334,7 @@ public class ServerPlayer extends Player { this.bukkitPickUpLoot = true; this.maxHealthCache = this.getMaxHealth(); this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper @@ -16,7 +16,7 @@ index 011ebba656174cb415d43244c50db250fa8f0a30..1653928faba2422bfa5fff93be5b4d8f } // Paper start - Chunk priority public BlockPos getPointInFront(double inFront) { -@@ -979,6 +980,12 @@ public class ServerPlayer extends Player { +@@ -975,6 +976,12 @@ public class ServerPlayer extends Player { } @@ -29,7 +29,7 @@ index 011ebba656174cb415d43244c50db250fa8f0a30..1653928faba2422bfa5fff93be5b4d8f @Override public boolean hurt(DamageSource source, float amount) { if (this.isInvulnerableTo(source)) { -@@ -986,7 +993,7 @@ public class ServerPlayer extends Player { +@@ -982,7 +989,7 @@ public class ServerPlayer extends Player { } else { boolean flag = this.server.isDedicatedServer() && this.isPvpAllowed() && "fall".equals(source.msgId); @@ -38,7 +38,7 @@ index 011ebba656174cb415d43244c50db250fa8f0a30..1653928faba2422bfa5fff93be5b4d8f return false; } else { if (source instanceof EntityDamageSource) { -@@ -1161,6 +1168,7 @@ public class ServerPlayer extends Player { +@@ -1157,6 +1164,7 @@ public class ServerPlayer extends Player { } // Paper end @@ -46,7 +46,7 @@ index 011ebba656174cb415d43244c50db250fa8f0a30..1653928faba2422bfa5fff93be5b4d8f return this; } } -@@ -2415,9 +2423,17 @@ public class ServerPlayer extends Player { +@@ -2409,9 +2417,17 @@ public class ServerPlayer extends Player { @Override public boolean isImmobile() { @@ -66,10 +66,10 @@ index 011ebba656174cb415d43244c50db250fa8f0a30..1653928faba2422bfa5fff93be5b4d8f public Scoreboard getScoreboard() { return this.getBukkitEntity().getScoreboard().getHandle(); diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 210b774eae5d76823e171016e9e6b706e8da4f07..550ff06264aa4fffdd9b9291efec121ac034d963 100644 +index c5863036c0fdeee755494dfc55c527f4696c92c1..74eee0b4865582ea88396ad69fc9278535777dae 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1938,6 +1938,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser +@@ -1840,6 +1840,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser } // Paper start PlayerResourcePackStatusEvent.Status packStatus = PlayerResourcePackStatusEvent.Status.values()[packet.action.ordinal()]; @@ -78,10 +78,10 @@ index 210b774eae5d76823e171016e9e6b706e8da4f07..550ff06264aa4fffdd9b9291efec121a this.cserver.getPluginManager().callEvent(new PlayerResourcePackStatusEvent(this.getCraftPlayer(), packStatus)); // CraftBukkit // Paper end diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index ec98fb6d4a407d5be8faf64db0d73e935e16623d..3961f1908876206b86126a672b691bfc571ea2f0 100644 +index c647a768a10076969f81fed33cefb7a4aa3b8597..f2c328c52c127eb48b6c4ab4e1348a1bfd75c299 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -991,6 +991,8 @@ public abstract class PlayerList { +@@ -992,6 +992,8 @@ public abstract class PlayerList { } // Paper end @@ -91,10 +91,10 @@ index ec98fb6d4a407d5be8faf64db0d73e935e16623d..3961f1908876206b86126a672b691bfc return entityplayer1; } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index ec0cef53349bc20e936adbbe6138145bcaca945d..578e1685e6b5e2febae463d836071aa2b8144200 100644 +index bfc1077dc7d5cfb48f5cc98b289b98d44474280e..90a312246e5ecdd4bbe4bc7e360ea7aa6a64953f 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -97,11 +97,15 @@ public class PurpurWorldConfig { +@@ -96,11 +96,15 @@ public class PurpurWorldConfig { public boolean idleTimeoutTickNearbyEntities = true; public boolean idleTimeoutCountAsSleeping = false; public boolean idleTimeoutUpdateTabList = false; @@ -111,10 +111,10 @@ index ec0cef53349bc20e936adbbe6138145bcaca945d..578e1685e6b5e2febae463d836071aa2 public boolean babiesAreRidable = true; diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 3eeb138d060f86e0f928fc59234563528d83bf65..2e42abb396b84b9f6d42809086f5a513dbc9ef40 100644 +index d858590b7ffea28dcfbc48973df6e3c916886b65..3306decd607ae190d6eaf674b3a59393c93dae34 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2550,5 +2550,20 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -2496,5 +2496,20 @@ public class CraftPlayer extends CraftHumanEntity implements Player { public void resetIdleTimer() { getHandle().resetLastActionTime(); } diff --git a/patches/server/0020-Anvil-API.patch b/patches/server/0020-Anvil-API.patch index c2efe2786..13b7153e7 100644 --- a/patches/server/0020-Anvil-API.patch +++ b/patches/server/0020-Anvil-API.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Anvil API diff --git a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -index 56d3ed1cdafd7904c35be5db568b9975a97418a7..c81af461fa01dac0b7b26becc1a5e7ae31bb5f95 100644 +index 3070ad3a4bbe83485c6b4a68aa634045c70cafdd..ddfb89d62d2ec316683e9f0f5550e298ab26d137 100644 --- a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java +++ b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java @@ -2,8 +2,12 @@ package net.minecraft.world.inventory; @@ -46,7 +46,7 @@ index 56d3ed1cdafd7904c35be5db568b9975a97418a7..c81af461fa01dac0b7b26becc1a5e7ae player.giveExperienceLevels(-this.cost.get()); } -@@ -117,6 +125,12 @@ public class AnvilMenu extends ItemCombinerMenu { +@@ -116,6 +124,12 @@ public class AnvilMenu extends ItemCombinerMenu { @Override public void createResult() { @@ -59,7 +59,7 @@ index 56d3ed1cdafd7904c35be5db568b9975a97418a7..c81af461fa01dac0b7b26becc1a5e7ae ItemStack itemstack = this.inputSlots.getItem(0); this.cost.set(1); -@@ -193,7 +207,7 @@ public class AnvilMenu extends ItemCombinerMenu { +@@ -192,7 +206,7 @@ public class AnvilMenu extends ItemCombinerMenu { int i2 = (Integer) map1.get(enchantment); i2 = l1 == i2 ? i2 + 1 : Math.max(i2, l1); @@ -68,7 +68,7 @@ index 56d3ed1cdafd7904c35be5db568b9975a97418a7..c81af461fa01dac0b7b26becc1a5e7ae if (this.player.getAbilities().instabuild || itemstack.is(Items.ENCHANTED_BOOK)) { flag3 = true; -@@ -205,7 +219,7 @@ public class AnvilMenu extends ItemCombinerMenu { +@@ -204,7 +218,7 @@ public class AnvilMenu extends ItemCombinerMenu { Enchantment enchantment1 = (Enchantment) iterator1.next(); if (enchantment1 != enchantment && !enchantment.isCompatibleWith(enchantment1)) { @@ -77,7 +77,7 @@ index 56d3ed1cdafd7904c35be5db568b9975a97418a7..c81af461fa01dac0b7b26becc1a5e7ae ++i; } } -@@ -276,6 +290,13 @@ public class AnvilMenu extends ItemCombinerMenu { +@@ -275,6 +289,13 @@ public class AnvilMenu extends ItemCombinerMenu { this.cost.set(this.maximumRepairCost - 1); // CraftBukkit } @@ -91,13 +91,13 @@ index 56d3ed1cdafd7904c35be5db568b9975a97418a7..c81af461fa01dac0b7b26becc1a5e7ae if (this.cost.get() >= this.maximumRepairCost && !this.player.getAbilities().instabuild) { // CraftBukkit itemstack1 = ItemStack.EMPTY; } -@@ -297,6 +318,12 @@ public class AnvilMenu extends ItemCombinerMenu { +@@ -296,6 +317,12 @@ public class AnvilMenu extends ItemCombinerMenu { org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), itemstack1); // CraftBukkit this.broadcastChanges(); + // Purpur start + if (canDoUnsafeEnchants && itemstack1 != ItemStack.EMPTY) { -+ ((ServerPlayer) player).connection.send(new ClientboundContainerSetSlotPacket(containerId, 2, itemstack1)); ++ ((ServerPlayer) player).connection.send(new ClientboundContainerSetSlotPacket(containerId, incrementStateId(), 2, itemstack1)); + ((ServerPlayer) player).connection.send(new ClientboundContainerSetDataPacket(containerId, 0, cost.get())); + } + // Purpur end diff --git a/patches/server/0021-Configurable-villager-brain-ticks.patch b/patches/server/0021-Configurable-villager-brain-ticks.patch index 5f33df4ca..4721749f1 100644 --- a/patches/server/0021-Configurable-villager-brain-ticks.patch +++ b/patches/server/0021-Configurable-villager-brain-ticks.patch @@ -36,10 +36,10 @@ index b7b25813aa00a49fd83edfbade27fa03d7fcd7c5..ed96780ededea9ae98bc0fb60d250587 this.level.getProfiler().pop(); if (this.assignProfessionWhenSpawned) { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 578e1685e6b5e2febae463d836071aa2b8144200..6c1fb8ac40e8d90714e397d1985498ad650e24b0 100644 +index 90a312246e5ecdd4bbe4bc7e360ea7aa6a64953f..add5f7a9929835e7f2383ba13e50b4e8fc1b52e9 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1069,6 +1069,8 @@ public class PurpurWorldConfig { +@@ -1068,6 +1068,8 @@ public class PurpurWorldConfig { public boolean villagerRidable = false; public boolean villagerRidableInWater = false; public double villagerMaxHealth = 20.0D; @@ -48,7 +48,7 @@ index 578e1685e6b5e2febae463d836071aa2b8144200..6c1fb8ac40e8d90714e397d1985498ad private void villagerSettings() { villagerRidable = getBoolean("mobs.villager.ridable", villagerRidable); villagerRidableInWater = getBoolean("mobs.villager.ridable-in-water", villagerRidableInWater); -@@ -1078,6 +1080,8 @@ public class PurpurWorldConfig { +@@ -1077,6 +1079,8 @@ public class PurpurWorldConfig { set("mobs.villager.attributes.max_health", oldValue); } villagerMaxHealth = getDouble("mobs.villager.attributes.max_health", villagerMaxHealth); diff --git a/patches/server/0022-Alternative-Keepalive-Handling.patch b/patches/server/0022-Alternative-Keepalive-Handling.patch index 59af0598f..535f6f9d8 100644 --- a/patches/server/0022-Alternative-Keepalive-Handling.patch +++ b/patches/server/0022-Alternative-Keepalive-Handling.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Alternative Keepalive Handling diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 71030d9e156ce28f1ef9e3ed6845f022a62dfdaf..db8a8e81054e91161d43515fb19ab0106e9da1d0 100644 +index 74eee0b4865582ea88396ad69fc9278535777dae..fa38157d4c422557b8ee05f47c833f840556b93d 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -229,6 +229,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser +@@ -228,6 +228,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser private long keepAliveTime = Util.getMillis(); private boolean keepAlivePending; private long keepAliveChallenge; @@ -16,7 +16,7 @@ index 71030d9e156ce28f1ef9e3ed6845f022a62dfdaf..db8a8e81054e91161d43515fb19ab010 // CraftBukkit start - multithreaded fields private AtomicInteger chatSpamTickCount = new AtomicInteger(); private final java.util.concurrent.atomic.AtomicInteger tabSpamLimiter = new java.util.concurrent.atomic.AtomicInteger(); // Paper - configurable tab spam limits -@@ -359,6 +360,21 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser +@@ -358,6 +359,21 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser long currentTime = Util.getMillis(); long elapsedTime = currentTime - this.keepAliveTime; @@ -38,7 +38,7 @@ index 71030d9e156ce28f1ef9e3ed6845f022a62dfdaf..db8a8e81054e91161d43515fb19ab010 if (this.keepAlivePending) { if (!this.processedDisconnect && elapsedTime >= KEEPALIVE_LIMIT) { // check keepalive limit, don't fire if already disconnected ServerGamePacketListenerImpl.LOGGER.warn("{} was kicked due to keepalive timeout!", this.player.getScoreboardName()); // more info -@@ -3088,6 +3104,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser +@@ -2991,6 +3007,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser @Override public void handleKeepAlive(ServerboundKeepAlivePacket packet) { @@ -56,7 +56,7 @@ index 71030d9e156ce28f1ef9e3ed6845f022a62dfdaf..db8a8e81054e91161d43515fb19ab010 if (this.keepAlivePending && packet.getId() == this.keepAliveChallenge) { int i = (int) (Util.getMillis() - this.keepAliveTime); diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java -index 2744735f6a15f6fd20c2b93b9c5c0422923bec01..772fe2509cf2e421b2e9279e6b159e95cf8fc4ae 100644 +index 2697188c7e18cf5ae11e6e96a3dab95192470ac1..32520a38274224361a24c3b2fdc92af65a0164da 100644 --- a/src/main/java/net/pl3x/purpur/PurpurConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java @@ -184,6 +184,11 @@ public class PurpurConfig { diff --git a/patches/server/0023-Silk-touch-spawners.patch b/patches/server/0023-Silk-touch-spawners.patch index 2fbfea269..860924318 100644 --- a/patches/server/0023-Silk-touch-spawners.patch +++ b/patches/server/0023-Silk-touch-spawners.patch @@ -17,7 +17,7 @@ index d24c569f00786b2bde953429aad57025abee72d6..44d837f624e2a23b0412cca4c0646f48 public static final GsonComponentSerializer GSON = GsonComponentSerializer.builder() .legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.INSTANCE) diff --git a/src/main/java/net/minecraft/world/item/Items.java b/src/main/java/net/minecraft/world/item/Items.java -index bd52d7d19060b0922c5165a071a5d12123028f79..6322251336a4300649f207efdb4d404d25023c9a 100644 +index f68639508d7ff9a0e743b5282301e62435d53656..89d4b7e4cd4222b61b49833fceda56ffa39710fa 100644 --- a/src/main/java/net/minecraft/world/item/Items.java +++ b/src/main/java/net/minecraft/world/item/Items.java @@ -258,7 +258,7 @@ public class Items { @@ -138,10 +138,10 @@ index b1e04d41de80971a7a1616beb0860226ecc25045..295ae3877b955978105b756055c21e63 return i; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 6c1fb8ac40e8d90714e397d1985498ad650e24b0..a96d7cad39076b08ee43849d3014e71d1c3d4a82 100644 +index add5f7a9929835e7f2383ba13e50b4e8fc1b52e9..60787c43d992674a94b4229e27504cb5cd518cff 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -108,6 +108,31 @@ public class PurpurWorldConfig { +@@ -107,6 +107,31 @@ public class PurpurWorldConfig { playerInvulnerableWhileAcceptingResourcePack = getBoolean("gameplay-mechanics.player.invulnerable-while-accepting-resource-pack", playerInvulnerableWhileAcceptingResourcePack); } diff --git a/patches/server/0024-MC-168772-Fix-Add-turtle-egg-block-options.patch b/patches/server/0024-MC-168772-Fix-Add-turtle-egg-block-options.patch index 961914283..fc5169fc4 100644 --- a/patches/server/0024-MC-168772-Fix-Add-turtle-egg-block-options.patch +++ b/patches/server/0024-MC-168772-Fix-Add-turtle-egg-block-options.patch @@ -50,10 +50,10 @@ index fdb3ab919a78221605257ae82bfd026346ce2ffb..e98fc3c235f9160f1928a8afb0d7991a } } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index a96d7cad39076b08ee43849d3014e71d1c3d4a82..2f5e6325398b2933c751027382593e3e3be17a70 100644 +index 60787c43d992674a94b4229e27504cb5cd518cff..125a9e31a46cb093e6c63a6fcb63aba45060888a 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1064,7 +1064,10 @@ public class PurpurWorldConfig { +@@ -1063,7 +1063,10 @@ public class PurpurWorldConfig { public boolean turtleRidable = false; public boolean turtleRidableInWater = false; public double turtleMaxHealth = 30.0D; @@ -65,7 +65,7 @@ index a96d7cad39076b08ee43849d3014e71d1c3d4a82..2f5e6325398b2933c751027382593e3e turtleRidable = getBoolean("mobs.turtle.ridable", turtleRidable); turtleRidableInWater = getBoolean("mobs.turtle.ridable-in-water", turtleRidableInWater); if (PurpurConfig.version < 10) { -@@ -1073,6 +1076,9 @@ public class PurpurWorldConfig { +@@ -1072,6 +1075,9 @@ public class PurpurWorldConfig { set("mobs.turtle.attributes.max_health", oldValue); } turtleMaxHealth = getDouble("mobs.turtle.attributes.max_health", turtleMaxHealth); diff --git a/patches/server/0027-Disable-outdated-build-check.patch b/patches/server/0027-Disable-outdated-build-check.patch index d5a4a49c8..e7395e604 100644 --- a/patches/server/0027-Disable-outdated-build-check.patch +++ b/patches/server/0027-Disable-outdated-build-check.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Disable outdated build check diff --git a/src/main/java/org/bukkit/craftbukkit/Main.java b/src/main/java/org/bukkit/craftbukkit/Main.java -index a335d1689ebf01e0e96a45c640188dc024610e2c..01ca38ee35f2c1e5031ea4b8aca09a2a59c4a475 100644 +index 910ffb563d66ce3d5a3ea562fd1b9d6d2030cccb..0d201ea0f65d2e9972d39091bc0960ae0221b174 100644 --- a/src/main/java/org/bukkit/craftbukkit/Main.java +++ b/src/main/java/org/bukkit/craftbukkit/Main.java -@@ -276,7 +276,7 @@ public class Main { +@@ -269,7 +269,7 @@ public class Main { System.setProperty(TerminalConsoleAppender.JLINE_OVERRIDE_PROPERTY, "false"); // Paper } diff --git a/patches/server/0028-Giants-AI-settings.patch b/patches/server/0028-Giants-AI-settings.patch index 00c3a4689..c9929fd3a 100644 --- a/patches/server/0028-Giants-AI-settings.patch +++ b/patches/server/0028-Giants-AI-settings.patch @@ -120,10 +120,10 @@ index a8ffdc8810152d77668aad7bad15a00c4d194d4c..91e28c414545d2bbb4e2f22c516d0f68 } } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 2f5e6325398b2933c751027382593e3e3be17a70..074f5233fe8eac7c1cf6219a4271562a7b45d94a 100644 +index 125a9e31a46cb093e6c63a6fcb63aba45060888a..ce9aff038bf3f2451923a456c47998876fd3a8c8 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -453,6 +453,10 @@ public class PurpurWorldConfig { +@@ -452,6 +452,10 @@ public class PurpurWorldConfig { public double giantMovementSpeed = 0.5D; public double giantAttackDamage = 50.0D; public double giantMaxHealth = 100.0D; @@ -134,7 +134,7 @@ index 2f5e6325398b2933c751027382593e3e3be17a70..074f5233fe8eac7c1cf6219a4271562a private void giantSettings() { giantRidable = getBoolean("mobs.giant.ridable", giantRidable); giantRidableInWater = getBoolean("mobs.giant.ridable-in-water", giantRidableInWater); -@@ -468,6 +472,10 @@ public class PurpurWorldConfig { +@@ -467,6 +471,10 @@ public class PurpurWorldConfig { set("mobs.giant.attributes.max_health", oldValue); } giantMaxHealth = getDouble("mobs.giant.attributes.max_health", giantMaxHealth); diff --git a/patches/server/0029-Zombie-horse-naturally-spawn.patch b/patches/server/0029-Zombie-horse-naturally-spawn.patch index eed110eab..a19389d55 100644 --- a/patches/server/0029-Zombie-horse-naturally-spawn.patch +++ b/patches/server/0029-Zombie-horse-naturally-spawn.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Zombie horse naturally spawn diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 67cdf31f3ce9f85743ce10bd2dee6854be1d69f9..0b22974ffe3ad2dd858f571980107eba9b56882e 100644 +index a02b0d819392db96a370c57818a378dd5fa27ef4..e4ac7b3a8a10056e3e24eef5263cafa65f29145f 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -87,6 +87,7 @@ import net.minecraft.world.entity.ai.village.poi.PoiManager; @@ -16,7 +16,7 @@ index 67cdf31f3ce9f85743ce10bd2dee6854be1d69f9..0b22974ffe3ad2dd858f571980107eba import net.minecraft.world.entity.animal.horse.SkeletonHorse; import net.minecraft.world.entity.boss.EnderDragonPart; import net.minecraft.world.entity.boss.enderdragon.EnderDragon; -@@ -899,12 +900,18 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl +@@ -713,12 +714,18 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl boolean flag1 = this.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && this.random.nextDouble() < (double) difficultydamagescaler.getEffectiveDifficulty() * paperConfig.skeleHorseSpawnChance && !this.getBlockState(blockposition.below()).is(Blocks.LIGHTNING_ROD); // Paper if (flag1) { @@ -42,10 +42,10 @@ index 67cdf31f3ce9f85743ce10bd2dee6854be1d69f9..0b22974ffe3ad2dd858f571980107eba LightningBolt entitylightning = (LightningBolt) EntityType.LIGHTNING_BOLT.create((net.minecraft.world.level.Level) this); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 074f5233fe8eac7c1cf6219a4271562a7b45d94a..d145fff0ecd3d898e13dd8d74224f53a4512649c 100644 +index ce9aff038bf3f2451923a456c47998876fd3a8c8..4c45a692a62fd451718c72ce031552bcd0261048 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1251,6 +1251,7 @@ public class PurpurWorldConfig { +@@ -1250,6 +1250,7 @@ public class PurpurWorldConfig { public double zombieHorseJumpStrengthMax = 1.0D; public double zombieHorseMovementSpeedMin = 0.2D; public double zombieHorseMovementSpeedMax = 0.2D; @@ -53,7 +53,7 @@ index 074f5233fe8eac7c1cf6219a4271562a7b45d94a..d145fff0ecd3d898e13dd8d74224f53a private void zombieHorseSettings() { zombieHorseRidableInWater = getBoolean("mobs.zombie_horse.ridable-in-water", zombieHorseRidableInWater); zombieHorseCanSwim = getBoolean("mobs.zombie_horse.can-swim", zombieHorseCanSwim); -@@ -1266,6 +1267,7 @@ public class PurpurWorldConfig { +@@ -1265,6 +1266,7 @@ public class PurpurWorldConfig { zombieHorseJumpStrengthMax = getDouble("mobs.zombie_horse.attributes.jump_strength.max", zombieHorseJumpStrengthMax); zombieHorseMovementSpeedMin = getDouble("mobs.zombie_horse.attributes.movement_speed.min", zombieHorseMovementSpeedMin); zombieHorseMovementSpeedMax = getDouble("mobs.zombie_horse.attributes.movement_speed.max", zombieHorseMovementSpeedMax); diff --git a/patches/server/0030-Charged-creeper-naturally-spawn.patch b/patches/server/0030-Charged-creeper-naturally-spawn.patch index 1e0362766..7d682a836 100644 --- a/patches/server/0030-Charged-creeper-naturally-spawn.patch +++ b/patches/server/0030-Charged-creeper-naturally-spawn.patch @@ -24,10 +24,10 @@ index b71a6af3c2d1ca2a946b23efb11b0e794840cf25..b2b3630a90c685b0a692efb9bf8bb111 @Override diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index d145fff0ecd3d898e13dd8d74224f53a4512649c..a32dc6f6b9d2f1036e4d8393f772c70ef9178177 100644 +index 4c45a692a62fd451718c72ce031552bcd0261048..b8dcd087a87e031aeca1e3c4c68aaaefd18164b7 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -275,6 +275,7 @@ public class PurpurWorldConfig { +@@ -274,6 +274,7 @@ public class PurpurWorldConfig { public boolean creeperRidable = false; public boolean creeperRidableInWater = false; public double creeperMaxHealth = 20.0D; @@ -35,7 +35,7 @@ index d145fff0ecd3d898e13dd8d74224f53a4512649c..a32dc6f6b9d2f1036e4d8393f772c70e private void creeperSettings() { creeperRidable = getBoolean("mobs.creeper.ridable", creeperRidable); creeperRidableInWater = getBoolean("mobs.creeper.ridable-in-water", creeperRidableInWater); -@@ -284,6 +285,7 @@ public class PurpurWorldConfig { +@@ -283,6 +284,7 @@ public class PurpurWorldConfig { set("mobs.creeper.attributes.max_health", oldValue); } creeperMaxHealth = getDouble("mobs.creeper.attributes.max_health", creeperMaxHealth); diff --git a/patches/server/0031-Rabbit-naturally-spawn-toast-and-killer.patch b/patches/server/0031-Rabbit-naturally-spawn-toast-and-killer.patch index fb96562e0..d22acd5d0 100644 --- a/patches/server/0031-Rabbit-naturally-spawn-toast-and-killer.patch +++ b/patches/server/0031-Rabbit-naturally-spawn-toast-and-killer.patch @@ -38,10 +38,10 @@ index 587feda351efae19407cb9f23c6c1d42d5ed0cc9..e0ba1ef404c9f8ba1eae563b733d10d9 int i = this.random.nextInt(100); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index a32dc6f6b9d2f1036e4d8393f772c70ef9178177..4578060c276d479a8a89e42064c10ee724fa8386 100644 +index b8dcd087a87e031aeca1e3c4c68aaaefd18164b7..8a502e31350e6e2aa829f3601ceaffded6c621ce 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -829,6 +829,8 @@ public class PurpurWorldConfig { +@@ -828,6 +828,8 @@ public class PurpurWorldConfig { public boolean rabbitRidable = false; public boolean rabbitRidableInWater = false; public double rabbitMaxHealth = 3.0D; @@ -50,7 +50,7 @@ index a32dc6f6b9d2f1036e4d8393f772c70ef9178177..4578060c276d479a8a89e42064c10ee7 private void rabbitSettings() { rabbitRidable = getBoolean("mobs.rabbit.ridable", rabbitRidable); rabbitRidableInWater = getBoolean("mobs.rabbit.ridable-in-water", rabbitRidableInWater); -@@ -838,6 +840,8 @@ public class PurpurWorldConfig { +@@ -837,6 +839,8 @@ public class PurpurWorldConfig { set("mobs.rabbit.attributes.max_health", oldValue); } rabbitMaxHealth = getDouble("mobs.rabbit.attributes.max_health", rabbitMaxHealth); diff --git a/patches/server/0033-Dont-send-useless-entity-packets.patch b/patches/server/0033-Dont-send-useless-entity-packets.patch index 3c074a0cd..1c855e87e 100644 --- a/patches/server/0033-Dont-send-useless-entity-packets.patch +++ b/patches/server/0033-Dont-send-useless-entity-packets.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Dont send useless entity packets diff --git a/src/main/java/net/minecraft/server/level/ServerEntity.java b/src/main/java/net/minecraft/server/level/ServerEntity.java -index a00627e0fa38632449042f59c053b4dac13e58bf..8218a074cdbe9df514f4de5aefd3c2669ec65250 100644 +index 2f3e69ad809199ffc2661d524bb627ec8dbc2e80..dc821e0565be38171922546208423613b7682de8 100644 --- a/src/main/java/net/minecraft/server/level/ServerEntity.java +++ b/src/main/java/net/minecraft/server/level/ServerEntity.java @@ -188,6 +188,7 @@ public class ServerEntity { @@ -38,9 +38,9 @@ index a00627e0fa38632449042f59c053b4dac13e58bf..8218a074cdbe9df514f4de5aefd3c266 + public void removePairing(ServerPlayer player) { this.entity.stopSeenByPlayer(player); - player.connection.send(new ClientboundRemoveEntityPacket(this.entity.getId())); + player.connection.send(new ClientboundRemoveEntitiesPacket(new int[]{this.entity.getId()})); diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java -index e425f660e93db4186a9d0ef3f43137667d5ecd4f..4fd18a3d1d8d72d42e14de2b59c2564a18721709 100644 +index be5f68ac35d364b07eddb549bc43a14137258d40..297c3d4427cfff2355f7ec5b276b4273584a56ac 100644 --- a/src/main/java/net/pl3x/purpur/PurpurConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java @@ -208,6 +208,11 @@ public class PurpurConfig { diff --git a/patches/server/0034-Tulips-change-fox-type.patch b/patches/server/0034-Tulips-change-fox-type.patch index 17b6e140a..6ce3789c7 100644 --- a/patches/server/0034-Tulips-change-fox-type.patch +++ b/patches/server/0034-Tulips-change-fox-type.patch @@ -75,10 +75,10 @@ index 80749df5d00df415a1b9e7c0e8586625b6453ffb..406b6ed8084c18da1062d8e8049bdfff // Paper start - Cancellable death event protected org.bukkit.event.entity.EntityDeathEvent dropAllDeathLoot(DamageSource source) { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 4578060c276d479a8a89e42064c10ee724fa8386..8e456185b5207dd63a0e6f18608f43fd341e71c1 100644 +index 8a502e31350e6e2aa829f3601ceaffded6c621ce..8fff2beaaf58e683abb8251c351035bcf48509de 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -423,6 +423,7 @@ public class PurpurWorldConfig { +@@ -422,6 +422,7 @@ public class PurpurWorldConfig { public boolean foxRidable = false; public boolean foxRidableInWater = false; public double foxMaxHealth = 10.0D; @@ -86,7 +86,7 @@ index 4578060c276d479a8a89e42064c10ee724fa8386..8e456185b5207dd63a0e6f18608f43fd private void foxSettings() { foxRidable = getBoolean("mobs.fox.ridable", foxRidable); foxRidableInWater = getBoolean("mobs.fox.ridable-in-water", foxRidableInWater); -@@ -432,6 +433,7 @@ public class PurpurWorldConfig { +@@ -431,6 +432,7 @@ public class PurpurWorldConfig { set("mobs.fox.attributes.max_health", oldValue); } foxMaxHealth = getDouble("mobs.fox.attributes.max_health", foxMaxHealth); diff --git a/patches/server/0035-Breedable-Polar-Bears.patch b/patches/server/0035-Breedable-Polar-Bears.patch index e97a9ce61..41b8a5007 100644 --- a/patches/server/0035-Breedable-Polar-Bears.patch +++ b/patches/server/0035-Breedable-Polar-Bears.patch @@ -59,10 +59,10 @@ index 18780fb268cabb47bb0deb84c44520831c1a762b..d39c88af3882a09ff1a06f9052d7b9b8 this.goalSelector.addGoal(5, new RandomStrollGoal(this, 1.0D)); this.goalSelector.addGoal(6, new LookAtPlayerGoal(this, Player.class, 6.0F)); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 8e456185b5207dd63a0e6f18608f43fd341e71c1..4a2c8e7fbdae48d1a4a654cce558538f5ac8d4ce 100644 +index 8fff2beaaf58e683abb8251c351035bcf48509de..949c301a97939e92d7a64ac8473e0ebe8ee27c30 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -805,6 +805,8 @@ public class PurpurWorldConfig { +@@ -804,6 +804,8 @@ public class PurpurWorldConfig { public boolean polarBearRidable = false; public boolean polarBearRidableInWater = false; public double polarBearMaxHealth = 30.0D; @@ -71,7 +71,7 @@ index 8e456185b5207dd63a0e6f18608f43fd341e71c1..4a2c8e7fbdae48d1a4a654cce558538f private void polarBearSettings() { polarBearRidable = getBoolean("mobs.polar_bear.ridable", polarBearRidable); polarBearRidableInWater = getBoolean("mobs.polar_bear.ridable-in-water", polarBearRidableInWater); -@@ -814,6 +816,9 @@ public class PurpurWorldConfig { +@@ -813,6 +815,9 @@ public class PurpurWorldConfig { set("mobs.polar_bear.attributes.max_health", oldValue); } polarBearMaxHealth = getDouble("mobs.polar_bear.attributes.max_health", polarBearMaxHealth); diff --git a/patches/server/0036-Chickens-can-retaliate.patch b/patches/server/0036-Chickens-can-retaliate.patch index 3a79f104c..9500479c5 100644 --- a/patches/server/0036-Chickens-can-retaliate.patch +++ b/patches/server/0036-Chickens-can-retaliate.patch @@ -51,10 +51,10 @@ index 55dccf338f9fba17fbcb88672d36b83e7bbec88b..eaec6eba96db3b8ce046208a4a1e5fc4 @Override diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 4a2c8e7fbdae48d1a4a654cce558538f5ac8d4ce..7ae28310a29fd808f48212d30e722ac09de40eca 100644 +index 949c301a97939e92d7a64ac8473e0ebe8ee27c30..fdbfd3ffde05f2787ad319801e845e51cd516671 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -235,6 +235,7 @@ public class PurpurWorldConfig { +@@ -234,6 +234,7 @@ public class PurpurWorldConfig { public boolean chickenRidable = false; public boolean chickenRidableInWater = false; public double chickenMaxHealth = 4.0D; @@ -62,7 +62,7 @@ index 4a2c8e7fbdae48d1a4a654cce558538f5ac8d4ce..7ae28310a29fd808f48212d30e722ac0 private void chickenSettings() { chickenRidable = getBoolean("mobs.chicken.ridable", chickenRidable); chickenRidableInWater = getBoolean("mobs.chicken.ridable-in-water", chickenRidableInWater); -@@ -244,6 +245,7 @@ public class PurpurWorldConfig { +@@ -243,6 +244,7 @@ public class PurpurWorldConfig { set("mobs.chicken.attributes.max_health", oldValue); } chickenMaxHealth = getDouble("mobs.chicken.attributes.max_health", chickenMaxHealth); diff --git a/patches/server/0037-Add-option-to-set-armorstand-step-height.patch b/patches/server/0037-Add-option-to-set-armorstand-step-height.patch index 057e00737..dfc6e7842 100644 --- a/patches/server/0037-Add-option-to-set-armorstand-step-height.patch +++ b/patches/server/0037-Add-option-to-set-armorstand-step-height.patch @@ -17,10 +17,10 @@ index 5fc66d7096afcfe63eba774e1dc330ac3263e4b0..7a3a364f5e3b025cc0a5694401cb9298 if (!this.canTick) { if (this.noTickPoseDirty) { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 7ae28310a29fd808f48212d30e722ac09de40eca..776615a91c63bc47239211c303d3424bcdf89746 100644 +index fdbfd3ffde05f2787ad319801e845e51cd516671..0edf2a2bbeabba2f90f3611de937f287aee5d194 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -93,6 +93,11 @@ public class PurpurWorldConfig { +@@ -92,6 +92,11 @@ public class PurpurWorldConfig { return value.isEmpty() ? fallback : value; } diff --git a/patches/server/0038-Cat-spawning-options.patch b/patches/server/0038-Cat-spawning-options.patch index f5232ae63..a63edf847 100644 --- a/patches/server/0038-Cat-spawning-options.patch +++ b/patches/server/0038-Cat-spawning-options.patch @@ -49,10 +49,10 @@ index 4cab98b5e441a174482893d3d289bbafa1f7a5fc..fa3cdff99a16b67ed86c8f7940ffa139 } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 776615a91c63bc47239211c303d3424bcdf89746..e3f9e6d7efab3c2b5249b7e0f3933c1a7112f995 100644 +index 0edf2a2bbeabba2f90f3611de937f287aee5d194..84520a367cf4f7a859076a24456f18d006e4247d 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -212,6 +212,9 @@ public class PurpurWorldConfig { +@@ -211,6 +211,9 @@ public class PurpurWorldConfig { public boolean catRidable = false; public boolean catRidableInWater = false; public double catMaxHealth = 10.0D; @@ -62,7 +62,7 @@ index 776615a91c63bc47239211c303d3424bcdf89746..e3f9e6d7efab3c2b5249b7e0f3933c1a private void catSettings() { catRidable = getBoolean("mobs.cat.ridable", catRidable); catRidableInWater = getBoolean("mobs.cat.ridable-in-water", catRidableInWater); -@@ -221,6 +224,9 @@ public class PurpurWorldConfig { +@@ -220,6 +223,9 @@ public class PurpurWorldConfig { set("mobs.cat.attributes.max_health", oldValue); } catMaxHealth = getDouble("mobs.cat.attributes.max_health", catMaxHealth); diff --git a/patches/server/0040-Cows-eat-mushrooms.patch b/patches/server/0040-Cows-eat-mushrooms.patch index 5e0d41ef3..fe322c12e 100644 --- a/patches/server/0040-Cows-eat-mushrooms.patch +++ b/patches/server/0040-Cows-eat-mushrooms.patch @@ -114,10 +114,10 @@ index bee82d5bf600cbeacfcede600e5606529af1435e..9da76357da891a70e20ad80f50873b3b public Cow getBreedOffspring(ServerLevel world, AgeableMob entity) { return (Cow) EntityType.COW.create((Level) world); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index e3f9e6d7efab3c2b5249b7e0f3933c1a7112f995..06d8ecd2feb6a13d1bcffb3f02025bcc8299e25f 100644 +index 84520a367cf4f7a859076a24456f18d006e4247d..2b938123386f9d9e3907159c2be6b6bc48b5447c 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -274,6 +274,7 @@ public class PurpurWorldConfig { +@@ -273,6 +273,7 @@ public class PurpurWorldConfig { public boolean cowRidable = false; public boolean cowRidableInWater = false; public double cowMaxHealth = 10.0D; @@ -125,7 +125,7 @@ index e3f9e6d7efab3c2b5249b7e0f3933c1a7112f995..06d8ecd2feb6a13d1bcffb3f02025bcc private void cowSettings() { cowRidable = getBoolean("mobs.cow.ridable", cowRidable); cowRidableInWater = getBoolean("mobs.cow.ridable-in-water", cowRidableInWater); -@@ -283,6 +284,7 @@ public class PurpurWorldConfig { +@@ -282,6 +283,7 @@ public class PurpurWorldConfig { set("mobs.cow.attributes.max_health", oldValue); } cowMaxHealth = getDouble("mobs.cow.attributes.max_health", cowMaxHealth); diff --git a/patches/server/0042-Pigs-give-saddle-back.patch b/patches/server/0042-Pigs-give-saddle-back.patch index 2c6467921..b30764fac 100644 --- a/patches/server/0042-Pigs-give-saddle-back.patch +++ b/patches/server/0042-Pigs-give-saddle-back.patch @@ -28,10 +28,10 @@ index f4a099e691dce3c57069e76d67859161b459098e..518d28dc0b5b8c04263c93a4347e4c97 } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 06d8ecd2feb6a13d1bcffb3f02025bcc8299e25f..3e0c36363efd2e30f7b14a1db1dceb554c0c4497 100644 +index 2b938123386f9d9e3907159c2be6b6bc48b5447c..2ed4b811d05d53fd99be70c37ebacc867fb33da9 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -764,6 +764,7 @@ public class PurpurWorldConfig { +@@ -763,6 +763,7 @@ public class PurpurWorldConfig { public boolean pigRidable = false; public boolean pigRidableInWater = false; public double pigMaxHealth = 10.0D; @@ -39,7 +39,7 @@ index 06d8ecd2feb6a13d1bcffb3f02025bcc8299e25f..3e0c36363efd2e30f7b14a1db1dceb55 private void pigSettings() { pigRidable = getBoolean("mobs.pig.ridable", pigRidable); pigRidableInWater = getBoolean("mobs.pig.ridable-in-water", pigRidableInWater); -@@ -773,6 +774,7 @@ public class PurpurWorldConfig { +@@ -772,6 +773,7 @@ public class PurpurWorldConfig { set("mobs.pig.attributes.max_health", oldValue); } pigMaxHealth = getDouble("mobs.pig.attributes.max_health", pigMaxHealth); diff --git a/patches/server/0043-Snowman-drop-and-put-back-pumpkin.patch b/patches/server/0043-Snowman-drop-and-put-back-pumpkin.patch index 419b2a62d..137a8620b 100644 --- a/patches/server/0043-Snowman-drop-and-put-back-pumpkin.patch +++ b/patches/server/0043-Snowman-drop-and-put-back-pumpkin.patch @@ -32,10 +32,10 @@ index 7fbe1a62e9c67a8bdaf13aaa9fae1d8742d75148..0733f9c057fef17fd79a4769f19b78f4 } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 3e0c36363efd2e30f7b14a1db1dceb554c0c4497..b3ef2d8617580e183e60dff81a767cf3e8f29900 100644 +index 2ed4b811d05d53fd99be70c37ebacc867fb33da9..469784fc896f1cc739bd4b943c170b53f74735f2 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -995,6 +995,8 @@ public class PurpurWorldConfig { +@@ -994,6 +994,8 @@ public class PurpurWorldConfig { public boolean snowGolemRidableInWater = false; public boolean snowGolemLeaveTrailWhenRidden = false; public double snowGolemMaxHealth = 4.0D; @@ -44,7 +44,7 @@ index 3e0c36363efd2e30f7b14a1db1dceb554c0c4497..b3ef2d8617580e183e60dff81a767cf3 private void snowGolemSettings() { snowGolemRidable = getBoolean("mobs.snow_golem.ridable", snowGolemRidable); snowGolemRidableInWater = getBoolean("mobs.snow_golem.ridable-in-water", snowGolemRidableInWater); -@@ -1005,6 +1007,8 @@ public class PurpurWorldConfig { +@@ -1004,6 +1006,8 @@ public class PurpurWorldConfig { set("mobs.snow_golem.attributes.max_health", oldValue); } snowGolemMaxHealth = getDouble("mobs.snow_golem.attributes.max_health", snowGolemMaxHealth); diff --git a/patches/server/0044-Ender-dragon-always-drop-full-exp.patch b/patches/server/0044-Ender-dragon-always-drop-full-exp.patch index 38732f027..d79b11f4a 100644 --- a/patches/server/0044-Ender-dragon-always-drop-full-exp.patch +++ b/patches/server/0044-Ender-dragon-always-drop-full-exp.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Ender dragon always drop full exp diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -index 177c5b385613ac08a75500013326874ff31a1992..a70c631691512a633c7fe4df9e9f2881f7397298 100644 +index d3caa18f5de2d96eae691655dd13e83f82c61e0c..f637cd740ec3801ce1c387473b5c4ff6080e76ec 100644 --- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java @@ -709,7 +709,7 @@ public class EnderDragon extends Mob implements Enemy { @@ -18,10 +18,10 @@ index 177c5b385613ac08a75500013326874ff31a1992..a70c631691512a633c7fe4df9e9f2881 } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index b3ef2d8617580e183e60dff81a767cf3e8f29900..2640d71e77b5c8ebcad118c11f2424cca5331bc7 100644 +index 469784fc896f1cc739bd4b943c170b53f74735f2..0c026a779905d3e0947ac4a91397f82ac5d2921b 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -377,6 +377,7 @@ public class PurpurWorldConfig { +@@ -376,6 +376,7 @@ public class PurpurWorldConfig { public boolean enderDragonRidableInWater = false; public double enderDragonMaxY = 256D; public double enderDragonMaxHealth = 200.0D; @@ -29,7 +29,7 @@ index b3ef2d8617580e183e60dff81a767cf3e8f29900..2640d71e77b5c8ebcad118c11f2424cc private void enderDragonSettings() { enderDragonRidable = getBoolean("mobs.ender_dragon.ridable", enderDragonRidable); enderDragonRidableInWater = getBoolean("mobs.ender_dragon.ridable-in-water", enderDragonRidableInWater); -@@ -391,6 +392,7 @@ public class PurpurWorldConfig { +@@ -390,6 +391,7 @@ public class PurpurWorldConfig { set("mobs.ender_dragon.attributes.max_health", oldValue); } enderDragonMaxHealth = getDouble("mobs.ender_dragon.attributes.max_health", enderDragonMaxHealth); diff --git a/patches/server/0045-Signs-editable-on-right-click.patch b/patches/server/0045-Signs-editable-on-right-click.patch index b95190a4a..699ff8d71 100644 --- a/patches/server/0045-Signs-editable-on-right-click.patch +++ b/patches/server/0045-Signs-editable-on-right-click.patch @@ -35,10 +35,10 @@ index 1844ea93f8cea420f01937f85ed17c0ec1bb8bf0..9e80ba8970cad91ea22ac701f76e1413 } else { return InteractionResult.PASS; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 2640d71e77b5c8ebcad118c11f2424cca5331bc7..410282f97889e808d18b0374ad7d89fc050f3df8 100644 +index 0c026a779905d3e0947ac4a91397f82ac5d2921b..82e79d28a75a0610e37044cf3df2e23e9fa088ff 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -138,6 +138,11 @@ public class PurpurWorldConfig { +@@ -137,6 +137,11 @@ public class PurpurWorldConfig { }); } diff --git a/patches/server/0046-Signs-allow-color-codes.patch b/patches/server/0046-Signs-allow-color-codes.patch index 00fe2171f..c79625ef6 100644 --- a/patches/server/0046-Signs-allow-color-codes.patch +++ b/patches/server/0046-Signs-allow-color-codes.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Signs allow color codes diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 1653928faba2422bfa5fff93be5b4d8f9c9447ed..7722a0e82dda2342feed5d636583e41e1d90b8df 100644 +index 335893049b3625a80cef78526eff3761cd8d5fbf..f641c27b9ab9e2ed5cd4f8122998df58577225ea 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1464,6 +1464,7 @@ public class ServerPlayer extends Player { +@@ -1460,6 +1460,7 @@ public class ServerPlayer extends Player { @Override public void openTextEdit(SignBlockEntity sign) { @@ -17,10 +17,10 @@ index 1653928faba2422bfa5fff93be5b4d8f9c9447ed..7722a0e82dda2342feed5d636583e41e this.connection.send(new ClientboundBlockUpdatePacket(this.level, sign.getBlockPos())); this.connection.send(new ClientboundOpenSignEditorPacket(sign.getBlockPos())); diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 8bcc80f2dabbc43b2f0970f53002f19e565d9b9e..56c8432a7c9f189f8afcd1001eb029c00d06e366 100644 +index fa38157d4c422557b8ee05f47c833f840556b93d..0252c916dd1ac44265401696e34b1c8e71b2f3c1 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -3078,11 +3078,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser +@@ -2981,11 +2981,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser } } // Paper end @@ -70,10 +70,10 @@ index 9b5d11ece006d7aa893360a84ba652c666517ac1..bdd6d8201ac078635d637081a37f353c @Override public ClientboundBlockEntityDataPacket getUpdatePacket() { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 410282f97889e808d18b0374ad7d89fc050f3df8..7548312d059e1e7745d1265f3c783580a62fcd46 100644 +index 82e79d28a75a0610e37044cf3df2e23e9fa088ff..1849f7970c5356f84a2b50a2a1f1b9742d822c67 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -138,8 +138,10 @@ public class PurpurWorldConfig { +@@ -137,8 +137,10 @@ public class PurpurWorldConfig { }); } diff --git a/patches/server/0047-Allow-soil-to-moisten-from-water-directly-under-it.patch b/patches/server/0047-Allow-soil-to-moisten-from-water-directly-under-it.patch index 320b52efd..7955e55f9 100644 --- a/patches/server/0047-Allow-soil-to-moisten-from-water-directly-under-it.patch +++ b/patches/server/0047-Allow-soil-to-moisten-from-water-directly-under-it.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Allow soil to moisten from water directly under it diff --git a/src/main/java/net/minecraft/world/level/block/FarmBlock.java b/src/main/java/net/minecraft/world/level/block/FarmBlock.java -index a242a80b16c7d074d52a52728646224b1a0091d4..5d9d77cb382c8075af2713a0ce26c28a35a0aaa8 100644 +index aa1ba8b74ab70b6cede99e4853ac0203f388ab06..e59861e0feb20b66735a76c19fd4e48bf13443e2 100644 --- a/src/main/java/net/minecraft/world/level/block/FarmBlock.java +++ b/src/main/java/net/minecraft/world/level/block/FarmBlock.java @@ -50,23 +50,6 @@ public class FarmBlock extends Block { @@ -32,20 +32,20 @@ index a242a80b16c7d074d52a52728646224b1a0091d4..5d9d77cb382c8075af2713a0ce26c28a @Override public VoxelShape getShape(BlockState state, BlockGetter world, BlockPos pos, CollisionContext context) { return FarmBlock.SHAPE; -@@ -159,7 +142,7 @@ public class FarmBlock extends Block { - } - } +@@ -151,7 +134,7 @@ public class FarmBlock extends Block { + blockposition1 = (BlockPos) iterator.next(); + } while (!world.getFluidState(blockposition1).is((Tag) FluidTags.WATER)); -- return false; +- return true; + return ((ServerLevel) world).purpurConfig.farmlandGetsMoistFromBelow && world.getFluidState(pos.relative(Direction.DOWN)).is(FluidTags.WATER); // Purpur - // Tuinity end - remove abstract block iteration } + @Override diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 7548312d059e1e7745d1265f3c783580a62fcd46..ba3e9d855b4715a427172a66c3be0b4968904308 100644 +index 1849f7970c5356f84a2b50a2a1f1b9742d822c67..8164b35f2c2d80a6b75ac10bd4fe293e47da3eba 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -138,6 +138,11 @@ public class PurpurWorldConfig { +@@ -137,6 +137,11 @@ public class PurpurWorldConfig { }); } diff --git a/patches/server/0048-Minecart-settings-and-WASD-controls.patch b/patches/server/0048-Minecart-settings-and-WASD-controls.patch index fb7b717b1..52cfaeff3 100644 --- a/patches/server/0048-Minecart-settings-and-WASD-controls.patch +++ b/patches/server/0048-Minecart-settings-and-WASD-controls.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Minecart settings and WASD controls diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 7722a0e82dda2342feed5d636583e41e1d90b8df..ccc9aacebb93e567b5b882431e5de6b1d2aba2f6 100644 +index f641c27b9ab9e2ed5cd4f8122998df58577225ea..fb7470a4f2c28d1010947b4adabb344adcf9802d 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -991,6 +991,7 @@ public class ServerPlayer extends Player { +@@ -987,6 +987,7 @@ public class ServerPlayer extends Player { if (this.isInvulnerableTo(source)) { return false; } else { @@ -135,7 +135,7 @@ index 9ea3837acc315e5c57f28c63c356efd633f1e6cf..e8b76b67f972c2f44e7611434246a822 } } diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -index 04d5ef90cd4171f9360017ac0c01ce48ae6ec983..7538262e14c86e4da9cd4cb887b76f649bfef2e6 100644 +index 1179c62695da4dcf02590c97d8da3c6fcdbee9ef..a107304351381d68fdaa35a4d7ff214e6c1546a6 100644 --- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java @@ -70,7 +70,7 @@ import net.minecraft.world.phys.shapes.VoxelShape; @@ -148,10 +148,10 @@ index 04d5ef90cd4171f9360017ac0c01ce48ae6ec983..7538262e14c86e4da9cd4cb887b76f64 protected final float explosionResistance; protected final boolean isRandomlyTicking; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index ba3e9d855b4715a427172a66c3be0b4968904308..5ff1f15dc01fe9e9a9c52ff09165efb13dfe9c5d 100644 +index 8164b35f2c2d80a6b75ac10bd4fe293e47da3eba..47dc4ea4779ff72d0fd204e5c360cba415544325 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -98,6 +98,68 @@ public class PurpurWorldConfig { +@@ -97,6 +97,68 @@ public class PurpurWorldConfig { armorstandStepHeight = (float) getDouble("gameplay-mechanics.armorstand.step-height", armorstandStepHeight); } diff --git a/patches/server/0049-Disable-loot-drops-on-death-by-cramming.patch b/patches/server/0049-Disable-loot-drops-on-death-by-cramming.patch index 1a019a79f..50b71d9aa 100644 --- a/patches/server/0049-Disable-loot-drops-on-death-by-cramming.patch +++ b/patches/server/0049-Disable-loot-drops-on-death-by-cramming.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Disable loot drops on death by cramming diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 28d5fc868498a4a6746b9defa1c06da27a4aa495..5b25fc620161053e1f3811dbdd7fac01491024fd 100644 +index 39dfda78320bc04ffb9b4be7b34abef84d4c8eeb..16a507780822e0118f40acae1dff5606136e4806 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1700,8 +1700,10 @@ public abstract class LivingEntity extends Entity { +@@ -1704,8 +1704,10 @@ public abstract class LivingEntity extends Entity { this.dropEquipment(); // CraftBukkit - from below if (this.shouldDropLoot() && this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBLOOT)) { @@ -20,10 +20,10 @@ index 28d5fc868498a4a6746b9defa1c06da27a4aa495..5b25fc620161053e1f3811dbdd7fac01 // CraftBukkit start - Call death event org.bukkit.event.entity.EntityDeathEvent deathEvent = CraftEventFactory.callEntityDeathEvent(this, this.drops); // Paper diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 5ff1f15dc01fe9e9a9c52ff09165efb13dfe9c5d..4a28a3769b220ae203da9b72e5330cbb813eae87 100644 +index 47dc4ea4779ff72d0fd204e5c360cba415544325..1b305531e884eba8511944b518b69bcac199b631 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -200,6 +200,11 @@ public class PurpurWorldConfig { +@@ -199,6 +199,11 @@ public class PurpurWorldConfig { }); } diff --git a/patches/server/0050-Players-should-not-cram-to-death.patch b/patches/server/0050-Players-should-not-cram-to-death.patch index 4b89fb659..7650b8da4 100644 --- a/patches/server/0050-Players-should-not-cram-to-death.patch +++ b/patches/server/0050-Players-should-not-cram-to-death.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Players should not cram to death diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index ccc9aacebb93e567b5b882431e5de6b1d2aba2f6..135f13864a85934c0127aa059bfe6c0e25140dfc 100644 +index fb7470a4f2c28d1010947b4adabb344adcf9802d..d4e12961d00c44b3d62bcc5ad710e1379c5b297a 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1441,7 +1441,7 @@ public class ServerPlayer extends Player { +@@ -1437,7 +1437,7 @@ public class ServerPlayer extends Player { @Override public boolean isInvulnerableTo(DamageSource damageSource) { @@ -18,10 +18,10 @@ index ccc9aacebb93e567b5b882431e5de6b1d2aba2f6..135f13864a85934c0127aa059bfe6c0e @Override diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 4a28a3769b220ae203da9b72e5330cbb813eae87..aeb3f1286de48393a3a352c32633d1ba87b68c8f 100644 +index 1b305531e884eba8511944b518b69bcac199b631..9e105df03e783bd2f29786230a2a66a46ab572e3 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -166,6 +166,7 @@ public class PurpurWorldConfig { +@@ -165,6 +165,7 @@ public class PurpurWorldConfig { public boolean idleTimeoutUpdateTabList = false; public int playerSpawnInvulnerableTicks = 60; public boolean playerInvulnerableWhileAcceptingResourcePack = false; @@ -29,7 +29,7 @@ index 4a28a3769b220ae203da9b72e5330cbb813eae87..aeb3f1286de48393a3a352c32633d1ba private void playerSettings() { idleTimeoutKick = getBoolean("gameplay-mechanics.player.idle-timeout.kick-if-idle", idleTimeoutKick); idleTimeoutTickNearbyEntities = getBoolean("gameplay-mechanics.player.idle-timeout.tick-nearby-entities", idleTimeoutTickNearbyEntities); -@@ -173,6 +174,7 @@ public class PurpurWorldConfig { +@@ -172,6 +173,7 @@ public class PurpurWorldConfig { idleTimeoutUpdateTabList = getBoolean("gameplay-mechanics.player.idle-timeout.update-tab-list", idleTimeoutUpdateTabList); playerSpawnInvulnerableTicks = getInt("gameplay-mechanics.player.spawn-invulnerable-ticks", playerSpawnInvulnerableTicks); playerInvulnerableWhileAcceptingResourcePack = getBoolean("gameplay-mechanics.player.invulnerable-while-accepting-resource-pack", playerInvulnerableWhileAcceptingResourcePack); diff --git a/patches/server/0051-Option-to-toggle-milk-curing-bad-omen.patch b/patches/server/0051-Option-to-toggle-milk-curing-bad-omen.patch index 4b009ed14..c3f31836b 100644 --- a/patches/server/0051-Option-to-toggle-milk-curing-bad-omen.patch +++ b/patches/server/0051-Option-to-toggle-milk-curing-bad-omen.patch @@ -28,10 +28,10 @@ index f33977d95b6db473be4f95075ba99caf90ad0220..56dc04d8875971ee9a5d077a695509af return stack.isEmpty() ? new ItemStack(Items.BUCKET) : stack; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index aeb3f1286de48393a3a352c32633d1ba87b68c8f..ceac72f608a9023480464d293d9582792ab9d2fa 100644 +index 9e105df03e783bd2f29786230a2a66a46ab572e3..c612eaaef7072e6d737b52bcb9dc425e5af6154c 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -203,8 +203,10 @@ public class PurpurWorldConfig { +@@ -202,8 +202,10 @@ public class PurpurWorldConfig { } public boolean disableDropsOnCrammingDeath = false; diff --git a/patches/server/0053-Fix-the-dead-lagging-the-server.patch b/patches/server/0053-Fix-the-dead-lagging-the-server.patch index 04bbfde58..fe5578131 100644 --- a/patches/server/0053-Fix-the-dead-lagging-the-server.patch +++ b/patches/server/0053-Fix-the-dead-lagging-the-server.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Fix the dead lagging the server diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index f3c5d5906a8ce64c93f4fb1f63474800a24ef012..d5df366dcbbd92523b9dd3a4afd4186a4d7ce5a1 100644 +index a43c95abc8f6d0226ca097495ed9aeab0649d02b..f3e842a7ca7c8acba63796a5e74bb89545c829fb 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -1735,6 +1735,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n +@@ -1549,6 +1549,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n this.setXRot(Mth.clamp(pitch, -90.0F, 90.0F) % 360.0F); this.yRotO = this.getYRot(); this.xRotO = this.getXRot(); @@ -17,10 +17,10 @@ index f3c5d5906a8ce64c93f4fb1f63474800a24ef012..d5df366dcbbd92523b9dd3a4afd4186a public void absMoveTo(double x, double y, double z) { diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 5b25fc620161053e1f3811dbdd7fac01491024fd..54bc16b6971259f6083ca530d4109909c86a3e1e 100644 +index 16a507780822e0118f40acae1dff5606136e4806..6dd8ee52c29e0df1c6d0abd547618ccedd9f4bcc 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -2877,7 +2877,7 @@ public abstract class LivingEntity extends Entity { +@@ -2880,7 +2880,7 @@ public abstract class LivingEntity extends Entity { } } diff --git a/patches/server/0055-Add-permission-for-F3-N-debug.patch b/patches/server/0055-Add-permission-for-F3-N-debug.patch index 28bedf294..37f655b2b 100644 --- a/patches/server/0055-Add-permission-for-F3-N-debug.patch +++ b/patches/server/0055-Add-permission-for-F3-N-debug.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add permission for F3+N debug diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 62ba8ce35dc5827607c99163c6005ea0c4b7ee16..c92b57e7e5d1b31db8b7e75a9013df41eb2806c4 100644 +index f2c328c52c127eb48b6c4ab4e1348a1bfd75c299..826fe9c183521e57466ba73d3e819c5368ff46c6 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -1156,6 +1156,7 @@ public abstract class PlayerList { +@@ -1157,6 +1157,7 @@ public abstract class PlayerList { } else { b0 = (byte) (24 + i); } diff --git a/patches/server/0056-Add-wither-skeleton-takes-wither-damage-option.patch b/patches/server/0056-Add-wither-skeleton-takes-wither-damage-option.patch index b866420a0..5b126253d 100644 --- a/patches/server/0056-Add-wither-skeleton-takes-wither-damage-option.patch +++ b/patches/server/0056-Add-wither-skeleton-takes-wither-damage-option.patch @@ -17,10 +17,10 @@ index 8212ee2cc1242c0a3626f3643c455f3be0de18c2..aaf6f43c0f37a51a7b2db57b8da43365 } } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index ceac72f608a9023480464d293d9582792ab9d2fa..5ef65a767b1a052cfeadcb612c2d7578eee38bf0 100644 +index c612eaaef7072e6d737b52bcb9dc425e5af6154c..b72963171098778793b4f12fe37deb8b271db4b7 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1307,6 +1307,7 @@ public class PurpurWorldConfig { +@@ -1306,6 +1306,7 @@ public class PurpurWorldConfig { public boolean witherSkeletonRidable = false; public boolean witherSkeletonRidableInWater = false; public double witherSkeletonMaxHealth = 20.0D; @@ -28,7 +28,7 @@ index ceac72f608a9023480464d293d9582792ab9d2fa..5ef65a767b1a052cfeadcb612c2d7578 private void witherSkeletonSettings() { witherSkeletonRidable = getBoolean("mobs.wither_skeleton.ridable", witherSkeletonRidable); witherSkeletonRidableInWater = getBoolean("mobs.wither_skeleton.ridable-in-water", witherSkeletonRidableInWater); -@@ -1316,6 +1317,7 @@ public class PurpurWorldConfig { +@@ -1315,6 +1316,7 @@ public class PurpurWorldConfig { set("mobs.wither_skeleton.attributes.max_health", oldValue); } witherSkeletonMaxHealth = getDouble("mobs.wither_skeleton.attributes.max_health", witherSkeletonMaxHealth); diff --git a/patches/server/0057-Configurable-TPS-Catchup.patch b/patches/server/0057-Configurable-TPS-Catchup.patch index b84dfd74f..d26e136ff 100644 --- a/patches/server/0057-Configurable-TPS-Catchup.patch +++ b/patches/server/0057-Configurable-TPS-Catchup.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Configurable TPS Catchup diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 34e364afff8cfe060244e5dd7cc0c34033dc6945..c5f1141d46acca284f5489d68d3b628e669950cb 100644 +index ac78bea2e411324c10a0183b1c73ce542bdbd13d..9fe12f0be064940e195b33559752349c6ca8680e 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1255,7 +1255,13 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop= this.level.paperConfig.netherVoidTopDamageHeight)) { // Paper end diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 54bc16b6971259f6083ca530d4109909c86a3e1e..b4640aa8fe8a37a6f27a626862b68b630bd370cd 100644 +index 6dd8ee52c29e0df1c6d0abd547618ccedd9f4bcc..ddc469e3c431c417820461ea9cb6e9c8bbad17cd 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -2381,7 +2381,7 @@ public abstract class LivingEntity extends Entity { +@@ -2385,7 +2385,7 @@ public abstract class LivingEntity extends Entity { @Override protected void outOfWorld() { @@ -31,10 +31,10 @@ index 54bc16b6971259f6083ca530d4109909c86a3e1e..b4640aa8fe8a37a6f27a626862b68b63 protected void updateSwingTime() { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 92e0e53a93f63b7f1301d0cc14f7a1178f639ced..905f75f78eabc61b1ab2a4157cbd806c53062990 100644 +index de7efcad4d0db0d14f60ce76f0ee2d1cdbb77e11..622a73529972ff9906e0fdea43c43ee3d123f418 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -209,10 +209,14 @@ public class PurpurWorldConfig { +@@ -208,10 +208,14 @@ public class PurpurWorldConfig { public boolean disableDropsOnCrammingDeath = false; public boolean milkCuresBadOmen = true; public double tridentLoyaltyVoidReturnHeight = 0.0D; diff --git a/patches/server/0067-Add-canSaveToDisk-to-Entity.patch b/patches/server/0067-Add-canSaveToDisk-to-Entity.patch index 9c5492296..12d5c37ac 100644 --- a/patches/server/0067-Add-canSaveToDisk-to-Entity.patch +++ b/patches/server/0067-Add-canSaveToDisk-to-Entity.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add canSaveToDisk to Entity diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index b9896dd54fe1cd6084f55f697e0f36a3ea242545..d2828fabedd353fb8a854fbd0beb978f5a93b504 100644 +index 513efac159e0285be3ea4353dce26067f18898bd..e64d4a3dd193bf7481884616034c5916114c649c 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -4259,5 +4259,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n +@@ -4066,5 +4066,9 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n public boolean processClick(InteractionHand hand) { return false; } @@ -19,7 +19,7 @@ index b9896dd54fe1cd6084f55f697e0f36a3ea242545..d2828fabedd353fb8a854fbd0beb978f // Purpur end } diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -index 96631591db018545120ba1c9980a03eb596eb6e5..22dd455b3b9c148f38a63f72901009f864e63a4b 100644 +index 2f33897a74ff2bd629b4ffacc4a1e1e0cd6b7987..4527fdd2c2001767c225574e1610ddfaa7bb4f34 100644 --- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java @@ -192,6 +192,11 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob @@ -35,10 +35,10 @@ index 96631591db018545120ba1c9980a03eb596eb6e5..22dd455b3b9c148f38a63f72901009f8 skull.setPosRaw(headX, headY, headZ); level.addFreshEntity(skull); diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java -index 572a013445bd83d366cce27d0e015ba8271df7e0..bba780934f4bb05bdb5ff8e69dc615a6b555b2f0 100644 +index 69ac9ed94b1890f9bd5ba21cdfe31e42529274e1..7ee8272b40d5c1dcd45a948e1a72063c8106ea5f 100644 --- a/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java +++ b/src/main/java/net/minecraft/world/level/chunk/storage/EntityStorage.java -@@ -91,6 +91,7 @@ public class EntityStorage implements EntityPersistentStorage { +@@ -92,6 +92,7 @@ public class EntityStorage implements EntityPersistentStorage { ListTag listTag = new ListTag(); final java.util.Map, Integer> savedEntityCounts = new java.util.HashMap<>(); // Paper dataList.getEntities().forEach((entity) -> { diff --git a/patches/server/0068-Dispenser-curse-of-binding-protection.patch b/patches/server/0068-Dispenser-curse-of-binding-protection.patch index a2e879f25..5ce76fb94 100644 --- a/patches/server/0068-Dispenser-curse-of-binding-protection.patch +++ b/patches/server/0068-Dispenser-curse-of-binding-protection.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Dispenser curse of binding protection diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index e07fbf6ba955a6c80ebf64db4d0f73f24f1aee9a..de3f7b0798d803e6d13a3c95aaaef3045ea22fed 100644 +index ae1beda7d2eba8afcf313de164e123fb7c8b3937..f7c71db9e33b7127f13f8d9b802322deda375c49 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java @@ -62,6 +62,7 @@ import net.minecraft.world.item.ProjectileWeaponItem; @@ -16,7 +16,7 @@ index e07fbf6ba955a6c80ebf64db4d0f73f24f1aee9a..de3f7b0798d803e6d13a3c95aaaef304 import net.minecraft.world.level.BlockGetter; import net.minecraft.world.level.GameRules; import net.minecraft.world.level.ItemLike; -@@ -1070,6 +1071,12 @@ public abstract class Mob extends LivingEntity { +@@ -1066,6 +1067,12 @@ public abstract class Mob extends LivingEntity { } @@ -43,10 +43,10 @@ index aab4a63e61aced51b1b6c885fd8b8426a4f14408..ad095c6bc7e00d286c83e37f84d8ed5d // CraftBukkit start Level world = pointer.getLevel(); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 905f75f78eabc61b1ab2a4157cbd806c53062990..a7604a133913100485deef79de96190eb851fbe1 100644 +index 622a73529972ff9906e0fdea43c43ee3d123f418..af4bf306c118fa109b8745e7ac8a0bcaa38a0fbc 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -219,6 +219,11 @@ public class PurpurWorldConfig { +@@ -218,6 +218,11 @@ public class PurpurWorldConfig { voidDamageDealt = getDouble("gameplay-mechanics.void-damage-dealt", voidDamageDealt); } diff --git a/patches/server/0069-Add-option-for-boats-to-eject-players-on-land.patch b/patches/server/0069-Add-option-for-boats-to-eject-players-on-land.patch index c0dd58384..8782b3321 100644 --- a/patches/server/0069-Add-option-for-boats-to-eject-players-on-land.patch +++ b/patches/server/0069-Add-option-for-boats-to-eject-players-on-land.patch @@ -17,10 +17,10 @@ index aa7c022c4faade23bd9061311d4152cf845d3331..d4a19fc38027717e43353bc62ef23e56 } else { return Boat.Status.IN_AIR; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index a7604a133913100485deef79de96190eb851fbe1..bd17ffc80d855011ac389d00ff34fcdab35289b7 100644 +index af4bf306c118fa109b8745e7ac8a0bcaa38a0fbc..be285a9c50893a8bb0a305eb7dde4953b81be768 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -206,12 +206,14 @@ public class PurpurWorldConfig { +@@ -205,12 +205,14 @@ public class PurpurWorldConfig { }); } diff --git a/patches/server/0070-Mending-mends-most-damages-equipment-first.patch b/patches/server/0070-Mending-mends-most-damages-equipment-first.patch index 79abf9f3f..80428dfe2 100644 --- a/patches/server/0070-Mending-mends-most-damages-equipment-first.patch +++ b/patches/server/0070-Mending-mends-most-damages-equipment-first.patch @@ -18,7 +18,7 @@ index 1caf10ecf949e0f465ffe573f3bed1a3c5733a7f..8b6614bde99b17db2e161f3fe4ab2491 if (entry != null) { ItemStack itemstack = (ItemStack) entry.getValue(); diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index aa0680291d042b9b4510008965d80cfd075f277c..7c1852c7e32fe1945fde0ec3f3b9b96dfa0c1043 100644 +index 86aa07827ce0e02939ebb594956b1023ffd83ad4..e2cc529405d80f4e8f22117da98ee6474351c218 100644 --- a/src/main/java/net/minecraft/world/item/ItemStack.java +++ b/src/main/java/net/minecraft/world/item/ItemStack.java @@ -511,6 +511,16 @@ public final class ItemStack { @@ -39,10 +39,10 @@ index aa0680291d042b9b4510008965d80cfd075f277c..7c1852c7e32fe1945fde0ec3f3b9b96d return this.tag == null ? 0 : this.tag.getInt("Damage"); } diff --git a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java -index e246bf034a86deba5a15e7c639f5e08213fe7eee..585b29a214d0306ac2d6a170125270c1bbd7c58d 100644 +index d439e8ce87bf7da03683a336941c7673b8b166e4..955e482e4bc83f2b777cd2e4254c40beef08853b 100644 --- a/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java +++ b/src/main/java/net/minecraft/world/item/enchantment/EnchantmentHelper.java -@@ -246,6 +246,29 @@ public class EnchantmentHelper { +@@ -270,6 +270,29 @@ public class EnchantmentHelper { return getItemEnchantmentLevel(Enchantments.CHANNELING, stack) > 0; } @@ -73,10 +73,10 @@ index e246bf034a86deba5a15e7c639f5e08213fe7eee..585b29a214d0306ac2d6a170125270c1 Entry entry = getRandomItemWith(enchantment, entityliving); return entry != null ? entry.getValue() : ItemStack.EMPTY; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index bd17ffc80d855011ac389d00ff34fcdab35289b7..437997c9e221e2fa8cdda52ad0028a87e41ed081 100644 +index be285a9c50893a8bb0a305eb7dde4953b81be768..580871c876a4b115968856c9d99b0442778f8063 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -206,6 +206,7 @@ public class PurpurWorldConfig { +@@ -205,6 +205,7 @@ public class PurpurWorldConfig { }); } @@ -84,7 +84,7 @@ index bd17ffc80d855011ac389d00ff34fcdab35289b7..437997c9e221e2fa8cdda52ad0028a87 public boolean boatEjectPlayersOnLand = false; public boolean disableDropsOnCrammingDeath = false; public boolean milkCuresBadOmen = true; -@@ -213,6 +214,7 @@ public class PurpurWorldConfig { +@@ -212,6 +213,7 @@ public class PurpurWorldConfig { public double voidDamageHeight = -64.0D; public double voidDamageDealt = 4.0D; private void miscGameplayMechanicsSettings() { diff --git a/patches/server/0071-Add-5-second-tps-average-in-tps.patch b/patches/server/0071-Add-5-second-tps-average-in-tps.patch index 522721966..a073d3d73 100644 --- a/patches/server/0071-Add-5-second-tps-average-in-tps.patch +++ b/patches/server/0071-Add-5-second-tps-average-in-tps.patch @@ -27,10 +27,10 @@ index fa56cd09102a89692b42f1d14257990508c5c720..f9251183df72ddc56662fd3f02acf216 setListData(vector); } diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index c5f1141d46acca284f5489d68d3b628e669950cb..9017d3626f4982a41d34d65758de4844e5502a9c 100644 +index 9fe12f0be064940e195b33559752349c6ca8680e..afe22502613d131bbca9af28b1716768e9a46f9c 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -308,7 +308,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop type, ServerLevelAccessor world, MobSpawnType spawnReason, BlockPos pos, Random random) { diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index 6eef158223d361f58f49810335895478430dda2d..77002a4c3b03e0397515fd922f14e56b8e81074a 100644 +index 3b7e261eae87445387f5a34d6fbb420c4cd0cf0f..f6e33ca1933792a3a417f54d20fea8dc4a41c1b5 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java +++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java @@ -122,6 +122,18 @@ public class Zombie extends Monster { @@ -79,7 +79,7 @@ index 6eef158223d361f58f49810335895478430dda2d..77002a4c3b03e0397515fd922f14e56b // Purpur end @Override -@@ -523,19 +535,20 @@ public class Zombie extends Monster { +@@ -528,19 +540,20 @@ public class Zombie extends Monster { if (object instanceof Zombie.ZombieGroupData) { Zombie.ZombieGroupData entityzombie_groupdatazombie = (Zombie.ZombieGroupData) object; @@ -106,7 +106,7 @@ index 6eef158223d361f58f49810335895478430dda2d..77002a4c3b03e0397515fd922f14e56b Chicken entitychicken1 = (Chicken) EntityType.CHICKEN.create(this.level); entitychicken1.moveTo(this.getX(), this.getY(), this.getZ(), this.getYRot(), 0.0F); -@@ -543,6 +556,7 @@ public class Zombie extends Monster { +@@ -548,6 +561,7 @@ public class Zombie extends Monster { entitychicken1.setChickenJockey(true); this.startRiding(entitychicken1); world.addEntity(entitychicken1, CreatureSpawnEvent.SpawnReason.MOUNT); // CraftBukkit @@ -141,7 +141,7 @@ index ce15ebc6248eaa849ccb1de4319b51e8a12f2e3e..8c71bf52cebf4b7825c770b120e6ac7b @Override diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java -index 04d09123f70a192f1283c0d7e8a8254f3d30889a..43ef93d2c0c59e0d7021ee9aa2b44345192cc0a9 100644 +index 8464026df1b46ad30301fed4944aa1d3cd39bd9e..5b5958e37918b97fa994500fe94cd0e57faa1948 100644 --- a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java +++ b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java @@ -73,6 +73,21 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { @@ -167,10 +167,10 @@ index 04d09123f70a192f1283c0d7e8a8254f3d30889a..43ef93d2c0c59e0d7021ee9aa2b44345 @Override diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index fa3e4e8fdfd5220aab2d84158f72d358feb75623..0b995e10a38f7f418fa7ad1752d647637fb9d37f 100644 +index 180d1c9a552b6d96dd7a30f991bb375c299e4ddb..2dd3b0701cacd4948690a5cc7f79cb0ddbc89d99 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -520,6 +520,9 @@ public class PurpurWorldConfig { +@@ -519,6 +519,9 @@ public class PurpurWorldConfig { public boolean drownedRidableInWater = false; public double drownedMaxHealth = 20.0D; public double drownedSpawnReinforcements = 0.1D; @@ -180,7 +180,7 @@ index fa3e4e8fdfd5220aab2d84158f72d358feb75623..0b995e10a38f7f418fa7ad1752d64763 private void drownedSettings() { drownedRidable = getBoolean("mobs.drowned.ridable", drownedRidable); drownedRidableInWater = getBoolean("mobs.drowned.ridable-in-water", drownedRidableInWater); -@@ -530,6 +533,9 @@ public class PurpurWorldConfig { +@@ -529,6 +532,9 @@ public class PurpurWorldConfig { } drownedMaxHealth = getDouble("mobs.drowned.attributes.max_health", drownedMaxHealth); drownedSpawnReinforcements = getDouble("mobs.drowned.attributes.spawn_reinforcements", drownedSpawnReinforcements); @@ -190,7 +190,7 @@ index fa3e4e8fdfd5220aab2d84158f72d358feb75623..0b995e10a38f7f418fa7ad1752d64763 } public boolean elderGuardianRidable = false; -@@ -742,6 +748,9 @@ public class PurpurWorldConfig { +@@ -741,6 +747,9 @@ public class PurpurWorldConfig { public boolean huskRidableInWater = false; public double huskMaxHealth = 20.0D; public double huskSpawnReinforcements = 0.1D; @@ -200,7 +200,7 @@ index fa3e4e8fdfd5220aab2d84158f72d358feb75623..0b995e10a38f7f418fa7ad1752d64763 private void huskSettings() { huskRidable = getBoolean("mobs.husk.ridable", huskRidable); huskRidableInWater = getBoolean("mobs.husk.ridable-in-water", huskRidableInWater); -@@ -752,6 +761,9 @@ public class PurpurWorldConfig { +@@ -751,6 +760,9 @@ public class PurpurWorldConfig { } huskMaxHealth = getDouble("mobs.husk.attributes.max_health", huskMaxHealth); huskSpawnReinforcements = getDouble("mobs.husk.attributes.spawn_reinforcements", huskSpawnReinforcements); @@ -210,7 +210,7 @@ index fa3e4e8fdfd5220aab2d84158f72d358feb75623..0b995e10a38f7f418fa7ad1752d64763 } public boolean illusionerRidable = false; -@@ -1454,6 +1466,9 @@ public class PurpurWorldConfig { +@@ -1453,6 +1465,9 @@ public class PurpurWorldConfig { public boolean zombieRidableInWater = false; public double zombieMaxHealth = 20.0D; public double zombieSpawnReinforcements = 0.1D; @@ -220,7 +220,7 @@ index fa3e4e8fdfd5220aab2d84158f72d358feb75623..0b995e10a38f7f418fa7ad1752d64763 private void zombieSettings() { zombieRidable = getBoolean("mobs.zombie.ridable", zombieRidable); zombieRidableInWater = getBoolean("mobs.zombie.ridable-in-water", zombieRidableInWater); -@@ -1464,6 +1479,9 @@ public class PurpurWorldConfig { +@@ -1463,6 +1478,9 @@ public class PurpurWorldConfig { } zombieMaxHealth = getDouble("mobs.zombie.attributes.max_health", zombieMaxHealth); zombieSpawnReinforcements = getDouble("mobs.zombie.attributes.spawn_reinforcements", zombieSpawnReinforcements); @@ -230,7 +230,7 @@ index fa3e4e8fdfd5220aab2d84158f72d358feb75623..0b995e10a38f7f418fa7ad1752d64763 } public boolean zombieHorseRidableInWater = false; -@@ -1497,6 +1515,9 @@ public class PurpurWorldConfig { +@@ -1496,6 +1514,9 @@ public class PurpurWorldConfig { public boolean zombieVillagerRidableInWater = false; public double zombieVillagerMaxHealth = 20.0D; public double zombieVillagerSpawnReinforcements = 0.1D; @@ -240,7 +240,7 @@ index fa3e4e8fdfd5220aab2d84158f72d358feb75623..0b995e10a38f7f418fa7ad1752d64763 private void zombieVillagerSettings() { zombieVillagerRidable = getBoolean("mobs.zombie_villager.ridable", zombieVillagerRidable); zombieVillagerRidableInWater = getBoolean("mobs.zombie_villager.ridable-in-water", zombieVillagerRidableInWater); -@@ -1507,12 +1528,18 @@ public class PurpurWorldConfig { +@@ -1506,12 +1527,18 @@ public class PurpurWorldConfig { } zombieVillagerMaxHealth = getDouble("mobs.zombie_villager.attributes.max_health", zombieVillagerMaxHealth); zombieVillagerSpawnReinforcements = getDouble("mobs.zombie_villager.attributes.spawn_reinforcements", zombieVillagerSpawnReinforcements); @@ -259,7 +259,7 @@ index fa3e4e8fdfd5220aab2d84158f72d358feb75623..0b995e10a38f7f418fa7ad1752d64763 private void zombifiedPiglinSettings() { zombifiedPiglinRidable = getBoolean("mobs.zombified_piglin.ridable", zombifiedPiglinRidable); zombifiedPiglinRidableInWater = getBoolean("mobs.zombified_piglin.ridable-in-water", zombifiedPiglinRidableInWater); -@@ -1523,5 +1550,8 @@ public class PurpurWorldConfig { +@@ -1522,5 +1549,8 @@ public class PurpurWorldConfig { } zombifiedPiglinMaxHealth = getDouble("mobs.zombified_piglin.attributes.max_health", zombifiedPiglinMaxHealth); zombifiedPiglinSpawnReinforcements = getDouble("mobs.zombified_piglin.attributes.spawn_reinforcements", zombifiedPiglinSpawnReinforcements); diff --git a/patches/server/0078-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch b/patches/server/0078-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch index e7b8e5d2e..e63451293 100644 --- a/patches/server/0078-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch +++ b/patches/server/0078-Phantoms-attracted-to-crystals-and-crystals-shoot-ph.patch @@ -258,10 +258,10 @@ index 5e069ac0c57d2a3a23f6e4483d12ce298d172691..916c29d08fbcf245ad6f50f8e8cc1736 private float speed = 0.1F; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 0b995e10a38f7f418fa7ad1752d647637fb9d37f..a989d32a6f9f69b739e726bfa90328134bc3d30f 100644 +index 2dd3b0701cacd4948690a5cc7f79cb0ddbc89d99..99a7d9e598678a7deeff694b5e050b72a40a9fe8 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -933,6 +933,9 @@ public class PurpurWorldConfig { +@@ -932,6 +932,9 @@ public class PurpurWorldConfig { public int phantomFlameFireTime = 8; public boolean phantomAllowGriefing = false; public double phantomMaxHealth = 20.0D; @@ -271,7 +271,7 @@ index 0b995e10a38f7f418fa7ad1752d647637fb9d37f..a989d32a6f9f69b739e726bfa9032813 private void phantomSettings() { phantomRidable = getBoolean("mobs.phantom.ridable", phantomRidable); phantomRidableInWater = getBoolean("mobs.phantom.ridable-in-water", phantomRidableInWater); -@@ -946,6 +949,9 @@ public class PurpurWorldConfig { +@@ -945,6 +948,9 @@ public class PurpurWorldConfig { set("mobs.phantom.attributes.max_health", oldValue); } phantomMaxHealth = getDouble("mobs.phantom.attributes.max_health", phantomMaxHealth); diff --git a/patches/server/0079-Add-phantom-spawning-options.patch b/patches/server/0079-Add-phantom-spawning-options.patch index 50086e20a..38900bb2f 100644 --- a/patches/server/0079-Add-phantom-spawning-options.patch +++ b/patches/server/0079-Add-phantom-spawning-options.patch @@ -48,10 +48,10 @@ index 79504dc3448402e73b09c4232b1fd0488872cf68..300c9f136edace2babea4a574090b184 for (int l = 0; l < k; ++l) { // Paper start diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index a989d32a6f9f69b739e726bfa90328134bc3d30f..42df00cdb3d4878b9d73983e97a1778157a9c3b2 100644 +index 99a7d9e598678a7deeff694b5e050b72a40a9fe8..05a24c0fc9330359aaff8052f0066cc4ddd14191 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -936,6 +936,18 @@ public class PurpurWorldConfig { +@@ -935,6 +935,18 @@ public class PurpurWorldConfig { public double phantomAttackedByCrystalRadius = 0.0D; public float phantomAttackedByCrystalDamage = 1.0F; public double phantomOrbitCrystalRadius = 0.0D; @@ -70,7 +70,7 @@ index a989d32a6f9f69b739e726bfa90328134bc3d30f..42df00cdb3d4878b9d73983e97a17781 private void phantomSettings() { phantomRidable = getBoolean("mobs.phantom.ridable", phantomRidable); phantomRidableInWater = getBoolean("mobs.phantom.ridable-in-water", phantomRidableInWater); -@@ -952,6 +964,18 @@ public class PurpurWorldConfig { +@@ -951,6 +963,18 @@ public class PurpurWorldConfig { phantomAttackedByCrystalRadius = getDouble("mobs.phantom.attacked-by-crystal-range", phantomAttackedByCrystalRadius); phantomAttackedByCrystalDamage = (float) getDouble("mobs.phantom.attacked-by-crystal-damage", phantomAttackedByCrystalDamage); phantomOrbitCrystalRadius = getDouble("mobs.phantom.orbit-crystal-radius", phantomOrbitCrystalRadius); diff --git a/patches/server/0080-Implement-bed-explosion-options.patch b/patches/server/0080-Implement-bed-explosion-options.patch index f9a3620cf..9575f25ca 100644 --- a/patches/server/0080-Implement-bed-explosion-options.patch +++ b/patches/server/0080-Implement-bed-explosion-options.patch @@ -27,10 +27,10 @@ index 163a7861f987c3832aac51cc6df950c768546731..bf5765b6af9c7807d50f7daaacb5d524 } } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 42df00cdb3d4878b9d73983e97a1778157a9c3b2..deca4f1f4597893cff4eba16c5266b61b43fc075 100644 +index 05a24c0fc9330359aaff8052f0066cc4ddd14191..2bd7dea57c0e0005be2b4fbe98de10fd167186a6 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -279,6 +279,22 @@ public class PurpurWorldConfig { +@@ -278,6 +278,22 @@ public class PurpurWorldConfig { voidDamageDealt = getDouble("gameplay-mechanics.void-damage-dealt", voidDamageDealt); } diff --git a/patches/server/0081-Implement-respawn-anchor-explosion-options.patch b/patches/server/0081-Implement-respawn-anchor-explosion-options.patch index 06308d0ba..4c10d9821 100644 --- a/patches/server/0081-Implement-respawn-anchor-explosion-options.patch +++ b/patches/server/0081-Implement-respawn-anchor-explosion-options.patch @@ -18,10 +18,10 @@ index af4eb4a8814491afef449a2874521636957d7557..365c28300ecfbe0161716972adf22a8a public static boolean canSetSpawn(Level world) { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index deca4f1f4597893cff4eba16c5266b61b43fc075..783b0106d7320892250073c2cda5df05bafdf424 100644 +index 2bd7dea57c0e0005be2b4fbe98de10fd167186a6..e7c816cbb0817239a1575a2328af23e32352e5a5 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -316,6 +316,22 @@ public class PurpurWorldConfig { +@@ -315,6 +315,22 @@ public class PurpurWorldConfig { lavaSpeedNotNether = getInt("blocks.lava.speed.not-nether", lavaSpeedNotNether); } diff --git a/patches/server/0082-Add-allow-water-in-end-world-option.patch b/patches/server/0082-Add-allow-water-in-end-world-option.patch index eb4637c03..7d3aaedfb 100644 --- a/patches/server/0082-Add-allow-water-in-end-world-option.patch +++ b/patches/server/0082-Add-allow-water-in-end-world-option.patch @@ -27,10 +27,10 @@ index 4fdb99240e6ebda946fd2e0a847654d92b7c56a1..e6dbe6e2d65aa4432f469910fd060649 return true; diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index beb845128b0ef8eef254afa7141af280f391ade6..8a9d103a579bc2a1874b99b3f8543d875cd9119f 100644 +index bda7cf2b8431f8541cba98e2a641dad03e736fa8..2571738bd83e21207e5463be5e2ba7d7b7f94a87 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -1493,4 +1493,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -1390,4 +1390,14 @@ public abstract class Level implements LevelAccessor, AutoCloseable { } } @@ -68,7 +68,7 @@ index df4f2c729f09d5229553308e4876f29de648543f..f2b0278679fa649bbc2904660e0dc9ab } else { world.setBlockAndUpdate(pos, Blocks.WATER.defaultBlockState()); diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java -index fa58d301da334cf901b7c2d4a747ef6da96db69d..40559def56630d31bbe2627b66dff31e8a989c6c 100644 +index d7f43f4be2e38febb9ff7b67a319316c40929416..3836bde09be67c0ee8678937bed99fbb881773e9 100644 --- a/src/main/java/net/pl3x/purpur/PurpurConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java @@ -219,6 +219,11 @@ public class PurpurConfig { diff --git a/patches/server/0083-Allow-color-codes-in-books.patch b/patches/server/0083-Allow-color-codes-in-books.patch index c03ba0103..7ba6f0705 100644 --- a/patches/server/0083-Allow-color-codes-in-books.patch +++ b/patches/server/0083-Allow-color-codes-in-books.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Allow color codes in books diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 857ec4fe7f0bc721cd8649062a14fa58331db3c0..be953d756c6263221a7fa06ca0835bbfd63bd14a 100644 +index 0252c916dd1ac44265401696e34b1c8e71b2f3c1..684cbd2cdce9472f4b263a44ec6f29dbeda7d6db 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1220,13 +1220,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser +@@ -1161,13 +1161,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser itemstack1.setTag(nbttagcompound.copy()); } @@ -28,7 +28,7 @@ index 857ec4fe7f0bc721cd8649062a14fa58331db3c0..be953d756c6263221a7fa06ca0835bbf this.a(pages, (s) -> { return Component.Serializer.toJson((Component) (new TextComponent(s))); -@@ -1238,10 +1241,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser +@@ -1179,10 +1182,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser private void a(List list, UnaryOperator unaryoperator, ItemStack itemstack, int slot, ItemStack handItem) { // CraftBukkit ListTag nbttaglist = new ListTag(); @@ -44,7 +44,7 @@ index 857ec4fe7f0bc721cd8649062a14fa58331db3c0..be953d756c6263221a7fa06ca0835bbf Objects.requireNonNull(nbttaglist); stream.forEach(nbttaglist::add); -@@ -1251,10 +1257,10 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser +@@ -1192,10 +1198,10 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser for (int j = list.size(); i < j; ++i) { TextFilter.FilteredText itextfilter_a = (TextFilter.FilteredText) list.get(i); @@ -57,7 +57,7 @@ index 857ec4fe7f0bc721cd8649062a14fa58331db3c0..be953d756c6263221a7fa06ca0835bbf if (!s.equals(s1)) { nbttagcompound.putString(String.valueOf(i), (String) unaryoperator.apply(s1)); -@@ -1270,6 +1276,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser +@@ -1211,6 +1217,16 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser this.player.getInventory().setItem(slot, CraftEventFactory.handleEditBookEvent(player, slot, handItem, itemstack)); // CraftBukkit // Paper - Don't ignore result (see other callsite for handleEditBookEvent) } diff --git a/patches/server/0084-Entity-lifespan.patch b/patches/server/0084-Entity-lifespan.patch index 4c581c151..81174e1d4 100644 --- a/patches/server/0084-Entity-lifespan.patch +++ b/patches/server/0084-Entity-lifespan.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Entity lifespan diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index de3f7b0798d803e6d13a3c95aaaef3045ea22fed..9b8b82bf5bb276be51b8ba5c023879b3a45212cb 100644 +index f7c71db9e33b7127f13f8d9b802322deda375c49..ff40d97479cd3aa8c7a164cdb1dbef92d4ab77ee 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -125,6 +125,7 @@ public abstract class Mob extends LivingEntity { +@@ -126,6 +126,7 @@ public abstract class Mob extends LivingEntity { private BlockPos restrictCenter; private float restrictRadius; @@ -16,7 +16,7 @@ index de3f7b0798d803e6d13a3c95aaaef3045ea22fed..9b8b82bf5bb276be51b8ba5c023879b3 public boolean aware = true; // CraftBukkit protected Mob(EntityType type, Level world) { -@@ -278,6 +279,7 @@ public abstract class Mob extends LivingEntity { +@@ -279,6 +280,7 @@ public abstract class Mob extends LivingEntity { entityliving = null; } } @@ -24,7 +24,7 @@ index de3f7b0798d803e6d13a3c95aaaef3045ea22fed..9b8b82bf5bb276be51b8ba5c023879b3 this.target = entityliving; return true; // CraftBukkit end -@@ -322,9 +324,35 @@ public abstract class Mob extends LivingEntity { +@@ -323,9 +325,35 @@ public abstract class Mob extends LivingEntity { this.playAmbientSound(); } @@ -60,7 +60,7 @@ index de3f7b0798d803e6d13a3c95aaaef3045ea22fed..9b8b82bf5bb276be51b8ba5c023879b3 @Override protected void playHurtSound(DamageSource source) { this.resetAmbientSoundTime(); -@@ -508,6 +536,7 @@ public abstract class Mob extends LivingEntity { +@@ -509,6 +537,7 @@ public abstract class Mob extends LivingEntity { } nbt.putBoolean("Bukkit.Aware", this.aware); // CraftBukkit @@ -68,7 +68,7 @@ index de3f7b0798d803e6d13a3c95aaaef3045ea22fed..9b8b82bf5bb276be51b8ba5c023879b3 } @Override -@@ -578,6 +607,11 @@ public abstract class Mob extends LivingEntity { +@@ -579,6 +608,11 @@ public abstract class Mob extends LivingEntity { this.aware = nbt.getBoolean("Bukkit.Aware"); } // CraftBukkit end @@ -80,7 +80,7 @@ index de3f7b0798d803e6d13a3c95aaaef3045ea22fed..9b8b82bf5bb276be51b8ba5c023879b3 } @Override -@@ -1583,6 +1617,7 @@ public abstract class Mob extends LivingEntity { +@@ -1579,6 +1613,7 @@ public abstract class Mob extends LivingEntity { this.setLastHurtMob(target); } @@ -89,10 +89,10 @@ index de3f7b0798d803e6d13a3c95aaaef3045ea22fed..9b8b82bf5bb276be51b8ba5c023879b3 } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 783b0106d7320892250073c2cda5df05bafdf424..eefc96db78ea9dfe4207357ce8e528c099856502 100644 +index e7c816cbb0817239a1575a2328af23e32352e5a5..5040f2c1ac73888fe3334742e096a237d1ae1ebb 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -111,6 +111,11 @@ public class PurpurWorldConfig { +@@ -110,6 +110,11 @@ public class PurpurWorldConfig { elytraDamagePerTridentBoost = getInt("gameplay-mechanics.elytra.damage-per-boost.trident", elytraDamagePerTridentBoost); } diff --git a/patches/server/0085-Add-option-to-teleport-to-spawn-if-outside-world-bor.patch b/patches/server/0085-Add-option-to-teleport-to-spawn-if-outside-world-bor.patch index 94e1e568d..8b4ef57a2 100644 --- a/patches/server/0085-Add-option-to-teleport-to-spawn-if-outside-world-bor.patch +++ b/patches/server/0085-Add-option-to-teleport-to-spawn-if-outside-world-bor.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add option to teleport to spawn if outside world border diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 135f13864a85934c0127aa059bfe6c0e25140dfc..2cfd9895b9a371e731b416ec8745aae839e0054b 100644 +index d4e12961d00c44b3d62bcc5ad710e1379c5b297a..4b74790581b0f629507e5d00c0882bad0f0e168e 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -2482,5 +2482,25 @@ public class ServerPlayer extends Player { +@@ -2476,5 +2476,25 @@ public class ServerPlayer extends Player { } // CraftBukkit end @@ -35,7 +35,7 @@ index 135f13864a85934c0127aa059bfe6c0e25140dfc..2cfd9895b9a371e731b416ec8745aae8 + // Purpur end } diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 7192cc7e5a78af2a611bfd853972ac92a5490f23..d96c4d52282e69180273322cb9a83f7365667fe3 100644 +index cb1ed54aa49a93fff15a0d72d459fd7adc037f6c..a9767676a2e84984374cc7d7d012477c60be5a7f 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -45,6 +45,7 @@ import net.minecraft.network.syncher.EntityDataAccessor; @@ -55,10 +55,10 @@ index 7192cc7e5a78af2a611bfd853972ac92a5490f23..d96c4d52282e69180273322cb9a83f73 } } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index eefc96db78ea9dfe4207357ce8e528c099856502..b3a3e2a88021fc0d6e28e8869bf367207295ce3e 100644 +index 5040f2c1ac73888fe3334742e096a237d1ae1ebb..b9e6082a7f15b83a6121684177516a4f0478d5a6 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -230,6 +230,7 @@ public class PurpurWorldConfig { +@@ -229,6 +229,7 @@ public class PurpurWorldConfig { public boolean playersShouldCramToDeath = true; public String playerDeathExpDropEquation = "expLevel * 7"; public int playerDeathExpDropMax = 100; @@ -66,7 +66,7 @@ index eefc96db78ea9dfe4207357ce8e528c099856502..b3a3e2a88021fc0d6e28e8869bf36720 private void playerSettings() { idleTimeoutKick = getBoolean("gameplay-mechanics.player.idle-timeout.kick-if-idle", idleTimeoutKick); idleTimeoutTickNearbyEntities = getBoolean("gameplay-mechanics.player.idle-timeout.tick-nearby-entities", idleTimeoutTickNearbyEntities); -@@ -240,6 +241,7 @@ public class PurpurWorldConfig { +@@ -239,6 +240,7 @@ public class PurpurWorldConfig { playersShouldCramToDeath = getBoolean("gameplay-mechanics.player.should-cram-to-death", playersShouldCramToDeath); playerDeathExpDropEquation = getString("gameplay-mechanics.player.exp-dropped-on-death.equation", playerDeathExpDropEquation); playerDeathExpDropMax = getInt("gameplay-mechanics.player.exp-dropped-on-death.maximum", playerDeathExpDropMax); diff --git a/patches/server/0086-Squid-EAR-immunity.patch b/patches/server/0086-Squid-EAR-immunity.patch index 7be661a9f..eef94584e 100644 --- a/patches/server/0086-Squid-EAR-immunity.patch +++ b/patches/server/0086-Squid-EAR-immunity.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Squid EAR immunity diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index b3a3e2a88021fc0d6e28e8869bf367207295ce3e..953ca17a43241e980197bad3084b9344f189de41 100644 +index b9e6082a7f15b83a6121684177516a4f0478d5a6..50de6f8b6a18ddd330ae1bb6ba8f58b73e17d7da 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1269,6 +1269,7 @@ public class PurpurWorldConfig { +@@ -1268,6 +1268,7 @@ public class PurpurWorldConfig { public boolean squidRidable = false; public double squidMaxHealth = 10.0D; @@ -16,7 +16,7 @@ index b3a3e2a88021fc0d6e28e8869bf367207295ce3e..953ca17a43241e980197bad3084b9344 private void squidSettings() { squidRidable = getBoolean("mobs.squid.ridable", squidRidable); if (PurpurConfig.version < 10) { -@@ -1277,6 +1278,7 @@ public class PurpurWorldConfig { +@@ -1276,6 +1277,7 @@ public class PurpurWorldConfig { set("mobs.squid.attributes.max_health", oldValue); } squidMaxHealth = getDouble("mobs.squid.attributes.max_health", squidMaxHealth); @@ -25,7 +25,7 @@ index b3a3e2a88021fc0d6e28e8869bf367207295ce3e..953ca17a43241e980197bad3084b9344 public boolean spiderRidable = false; diff --git a/src/main/java/org/spigotmc/ActivationRange.java b/src/main/java/org/spigotmc/ActivationRange.java -index 48ce9975b872c259dae1348148203fdbcb25e2ee..073b0e6dbd4be7cb7edd8864b1be1c4b6a56e641 100644 +index f36c97529edbd3642d0ba37887a232226f766a35..6b6750f20a70bd7dd74db431321d57e306b1e2cd 100644 --- a/src/main/java/org/spigotmc/ActivationRange.java +++ b/src/main/java/org/spigotmc/ActivationRange.java @@ -14,6 +14,7 @@ import net.minecraft.world.entity.ambient.AmbientCreature; diff --git a/patches/server/0087-Configurable-feature-seed-settings.patch b/patches/server/0087-Configurable-feature-seed-settings.patch index d45510665..9475224f5 100644 --- a/patches/server/0087-Configurable-feature-seed-settings.patch +++ b/patches/server/0087-Configurable-feature-seed-settings.patch @@ -313,10 +313,10 @@ index 66f97b70985ed728eadcf837f25e81a256c2033b..6120409ab412c1237b44862ed1c402c8 this.configuredCodec = configCodec.fieldOf("config").xmap((config) -> { return new ConfiguredFeature<>(this, config); diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/FossilFeature.java b/src/main/java/net/minecraft/world/level/levelgen/feature/FossilFeature.java -index 5edee64e2b446f8055cae77058992e54a8b28660..fbc1af1dfe1f48be1383980b416a543e784da821 100644 +index 82ea6a21d5fe0853c8fe1ef245609f01e9dbadb7..5032d5d190835aa941dfe5a2203f5b22966f6f2f 100644 --- a/src/main/java/net/minecraft/world/level/levelgen/feature/FossilFeature.java +++ b/src/main/java/net/minecraft/world/level/levelgen/feature/FossilFeature.java -@@ -25,7 +25,7 @@ public class FossilFeature extends Feature { +@@ -24,7 +24,7 @@ public class FossilFeature extends Feature { @Override public boolean place(FeaturePlaceContext context) { @@ -668,7 +668,7 @@ index fc5a9fba1ecfa8850d31ba68e92c537e65dbed78..f913486542f0ca35cd36eb19f2591850 BlockPos blockPos = context.origin(); ProbabilityFeatureConfiguration probabilityFeatureConfiguration = context.config(); diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/SimpleBlockFeature.java b/src/main/java/net/minecraft/world/level/levelgen/feature/SimpleBlockFeature.java -index bc47bba59d4edb092ccf7e012d0ce38a9741e043..fefa21b1c967a1891d6076d30be45d4a0231b777 100644 +index 1aaa0d7b73e7b89202b3cedd284c3b739d1e3091..7bcde212bc61d6c883b443f97b4a5ae4d3990f39 100644 --- a/src/main/java/net/minecraft/world/level/levelgen/feature/SimpleBlockFeature.java +++ b/src/main/java/net/minecraft/world/level/levelgen/feature/SimpleBlockFeature.java @@ -17,8 +17,9 @@ public class SimpleBlockFeature extends Feature { @@ -835,7 +835,7 @@ index ed3944a60d09495eb424dd11d00e8c3585177d51..fcf25cc905ece0213a0eb6365a0a5756 return false; } else { diff --git a/src/main/java/net/pl3x/purpur/PurpurConfig.java b/src/main/java/net/pl3x/purpur/PurpurConfig.java -index 40559def56630d31bbe2627b66dff31e8a989c6c..44286c661943cb147372b7864709485e747545c1 100644 +index 3836bde09be67c0ee8678937bed99fbb881773e9..4e928dd1efdd7e1f71a5c5adb3a287eb0463e077 100644 --- a/src/main/java/net/pl3x/purpur/PurpurConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurConfig.java @@ -174,6 +174,128 @@ public class PurpurConfig { diff --git a/patches/server/0088-Phantoms-burn-in-light.patch b/patches/server/0088-Phantoms-burn-in-light.patch index 91befa505..f26b096c5 100644 --- a/patches/server/0088-Phantoms-burn-in-light.patch +++ b/patches/server/0088-Phantoms-burn-in-light.patch @@ -47,10 +47,10 @@ index 916c29d08fbcf245ad6f50f8e8cc173677b01081..c55aba456aa144e58fc35877c61eff30 list.sort(Comparator.comparing(Entity::getY).reversed()); // Paper - remap fix Iterator iterator = list.iterator(); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 953ca17a43241e980197bad3084b9344f189de41..f34926d9da4103b4b36fd023e05d7aa4285bcabf 100644 +index 50de6f8b6a18ddd330ae1bb6ba8f58b73e17d7da..58b0b17167a5248acc9f0ebfac2d357f49688d79 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -987,6 +987,9 @@ public class PurpurWorldConfig { +@@ -986,6 +986,9 @@ public class PurpurWorldConfig { public int phantomSpawnOverheadRadius = 10; public int phantomSpawnMinPerAttempt = 1; public int phantomSpawnMaxPerAttempt = -1; @@ -60,7 +60,7 @@ index 953ca17a43241e980197bad3084b9344f189de41..f34926d9da4103b4b36fd023e05d7aa4 private void phantomSettings() { phantomRidable = getBoolean("mobs.phantom.ridable", phantomRidable); phantomRidableInWater = getBoolean("mobs.phantom.ridable-in-water", phantomRidableInWater); -@@ -1015,6 +1018,9 @@ public class PurpurWorldConfig { +@@ -1014,6 +1017,9 @@ public class PurpurWorldConfig { phantomSpawnOverheadRadius = getInt("mobs.phantom.spawn.overhead.radius", phantomSpawnOverheadRadius); phantomSpawnMinPerAttempt = getInt("mobs.phantom.spawn.per-attempt.min", phantomSpawnMinPerAttempt); phantomSpawnMaxPerAttempt = getInt("mobs.phantom.spawn.per-attempt.max", phantomSpawnMaxPerAttempt); diff --git a/patches/server/0089-Configurable-villager-breeding.patch b/patches/server/0089-Configurable-villager-breeding.patch index ba5880d56..8c03a7961 100644 --- a/patches/server/0089-Configurable-villager-breeding.patch +++ b/patches/server/0089-Configurable-villager-breeding.patch @@ -18,10 +18,10 @@ index 3865f16e027307f5b8b2a5617b66ffd8b9f85c60..bb3572370a86519a92b7b3dab0482cd1 private boolean hungry() { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index f34926d9da4103b4b36fd023e05d7aa4285bcabf..f8c2b2174a02be8756994c473c5676f6d5a41243 100644 +index 58b0b17167a5248acc9f0ebfac2d357f49688d79..2a8435b39afc16d3ca862d17bb40fe3cc9d5e494 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1412,6 +1412,7 @@ public class PurpurWorldConfig { +@@ -1411,6 +1411,7 @@ public class PurpurWorldConfig { public boolean villagerCanBeLeashed = false; public int villagerSpawnIronGolemRadius = 0; public int villagerSpawnIronGolemLimit = 0; @@ -29,7 +29,7 @@ index f34926d9da4103b4b36fd023e05d7aa4285bcabf..f8c2b2174a02be8756994c473c5676f6 private void villagerSettings() { villagerRidable = getBoolean("mobs.villager.ridable", villagerRidable); villagerRidableInWater = getBoolean("mobs.villager.ridable-in-water", villagerRidableInWater); -@@ -1427,6 +1428,7 @@ public class PurpurWorldConfig { +@@ -1426,6 +1427,7 @@ public class PurpurWorldConfig { villagerCanBeLeashed = getBoolean("mobs.villager.can-be-leashed", villagerCanBeLeashed); villagerSpawnIronGolemRadius = getInt("mobs.villager.spawn-iron-golem.radius", villagerSpawnIronGolemRadius); villagerSpawnIronGolemLimit = getInt("mobs.villager.spawn-iron-golem.limit", villagerSpawnIronGolemLimit); diff --git a/patches/server/0090-Redstone-deactivates-spawners.patch b/patches/server/0090-Redstone-deactivates-spawners.patch index f43cb6a2f..49ff6f3c1 100644 --- a/patches/server/0090-Redstone-deactivates-spawners.patch +++ b/patches/server/0090-Redstone-deactivates-spawners.patch @@ -17,10 +17,10 @@ index 9228c0bc797fb95c8ac949bdc568eadafee84a80..f2c9f841d397f445cd3d0420f19e765c } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index f8c2b2174a02be8756994c473c5676f6d5a41243..1283e183c66629630726e48e6983afecb38e1204 100644 +index 2a8435b39afc16d3ca862d17bb40fe3cc9d5e494..584757245d2703e69659746c3d382af456cb3f89 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -346,6 +346,11 @@ public class PurpurWorldConfig { +@@ -345,6 +345,11 @@ public class PurpurWorldConfig { signRightClickEdit = getBoolean("blocks.sign.right-click-edit", signRightClickEdit); } diff --git a/patches/server/0091-Totems-work-in-inventory.patch b/patches/server/0091-Totems-work-in-inventory.patch index 437d4c044..30013e287 100644 --- a/patches/server/0091-Totems-work-in-inventory.patch +++ b/patches/server/0091-Totems-work-in-inventory.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Totems work in inventory diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index d96c4d52282e69180273322cb9a83f7365667fe3..0fa7a658af3dc1766e06d90d396a426c558917ab 100644 +index a9767676a2e84984374cc7d7d012477c60be5a7f..3bda2f895612cb5bfd6205a675c03edc6a2270d6 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -1516,6 +1516,19 @@ public abstract class LivingEntity extends Entity { @@ -29,10 +29,10 @@ index d96c4d52282e69180273322cb9a83f7365667fe3..0fa7a658af3dc1766e06d90d396a426c event.setCancelled(itemstack == null); this.level.getCraftServer().getPluginManager().callEvent(event); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 1283e183c66629630726e48e6983afecb38e1204..a0fbd24dfb21c3ab0e345feee174536a8cda905f 100644 +index 584757245d2703e69659746c3d382af456cb3f89..31ccb922343d971f8669147abffa97547df54e4d 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -231,6 +231,7 @@ public class PurpurWorldConfig { +@@ -230,6 +230,7 @@ public class PurpurWorldConfig { public String playerDeathExpDropEquation = "expLevel * 7"; public int playerDeathExpDropMax = 100; public boolean teleportIfOutsideBorder = false; @@ -40,7 +40,7 @@ index 1283e183c66629630726e48e6983afecb38e1204..a0fbd24dfb21c3ab0e345feee174536a private void playerSettings() { idleTimeoutKick = getBoolean("gameplay-mechanics.player.idle-timeout.kick-if-idle", idleTimeoutKick); idleTimeoutTickNearbyEntities = getBoolean("gameplay-mechanics.player.idle-timeout.tick-nearby-entities", idleTimeoutTickNearbyEntities); -@@ -242,6 +243,7 @@ public class PurpurWorldConfig { +@@ -241,6 +242,7 @@ public class PurpurWorldConfig { playerDeathExpDropEquation = getString("gameplay-mechanics.player.exp-dropped-on-death.equation", playerDeathExpDropEquation); playerDeathExpDropMax = getInt("gameplay-mechanics.player.exp-dropped-on-death.maximum", playerDeathExpDropMax); teleportIfOutsideBorder = getBoolean("gameplay-mechanics.player.teleport-if-outside-border", teleportIfOutsideBorder); diff --git a/patches/server/0092-Add-vindicator-johnny-spawn-chance.patch b/patches/server/0092-Add-vindicator-johnny-spawn-chance.patch index 447cb2b4a..16a3d9938 100644 --- a/patches/server/0092-Add-vindicator-johnny-spawn-chance.patch +++ b/patches/server/0092-Add-vindicator-johnny-spawn-chance.patch @@ -30,10 +30,10 @@ index 76e6ea34db3942e9dd7646ad7ca1259f4387a4d8..9096c40ad5784d9097e889f0f43b6cf1 } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index a0fbd24dfb21c3ab0e345feee174536a8cda905f..75c159b8251aaee446a8ec44490be2335bd6d822 100644 +index 31ccb922343d971f8669147abffa97547df54e4d..e28c5973ec4642fc729bcfa47aabade4afed4765 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1441,6 +1441,7 @@ public class PurpurWorldConfig { +@@ -1440,6 +1440,7 @@ public class PurpurWorldConfig { public boolean vindicatorRidable = false; public boolean vindicatorRidableInWater = false; public double vindicatorMaxHealth = 24.0D; @@ -41,7 +41,7 @@ index a0fbd24dfb21c3ab0e345feee174536a8cda905f..75c159b8251aaee446a8ec44490be233 private void vindicatorSettings() { vindicatorRidable = getBoolean("mobs.vindicator.ridable", vindicatorRidable); vindicatorRidableInWater = getBoolean("mobs.vindicator.ridable-in-water", vindicatorRidableInWater); -@@ -1450,6 +1451,7 @@ public class PurpurWorldConfig { +@@ -1449,6 +1450,7 @@ public class PurpurWorldConfig { set("mobs.vindicator.attributes.max_health", oldValue); } vindicatorMaxHealth = getDouble("mobs.vindicator.attributes.max_health", vindicatorMaxHealth); diff --git a/patches/server/0094-Dispensers-place-anvils-option.patch b/patches/server/0094-Dispensers-place-anvils-option.patch index d8fb6b053..6949b274f 100644 --- a/patches/server/0094-Dispensers-place-anvils-option.patch +++ b/patches/server/0094-Dispensers-place-anvils-option.patch @@ -41,10 +41,10 @@ index 92623ae25249d63efb92be8bd6c95228f9155ad2..20bf6d01046488eff53a109f5239351a static void setEntityPokingOutOfBlock(BlockSource pointer, Entity entity, Direction direction) { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 75c159b8251aaee446a8ec44490be2335bd6d822..f34dfe5d2b235e482623214cd233e83475807491 100644 +index e28c5973ec4642fc729bcfa47aabade4afed4765..cce72e2574486fb178caa3bc1a89cbf39d66e4d1 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -305,8 +305,10 @@ public class PurpurWorldConfig { +@@ -304,8 +304,10 @@ public class PurpurWorldConfig { } public boolean dispenserApplyCursedArmor = true; diff --git a/patches/server/0095-Allow-anvil-colors.patch b/patches/server/0095-Allow-anvil-colors.patch index cfd7f013c..1299d0bf7 100644 --- a/patches/server/0095-Allow-anvil-colors.patch +++ b/patches/server/0095-Allow-anvil-colors.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Allow anvil colors diff --git a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -index c81af461fa01dac0b7b26becc1a5e7ae31bb5f95..2dbc71a3d76cc87e2683b8f351bd8db04481855e 100644 +index ddfb89d62d2ec316683e9f0f5550e298ab26d137..410ac71efff92dfa1f1e11895d0f5bf3fca1be17 100644 --- a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java +++ b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java @@ -2,6 +2,9 @@ package net.minecraft.world.inventory; @@ -18,7 +18,7 @@ index c81af461fa01dac0b7b26becc1a5e7ae31bb5f95..2dbc71a3d76cc87e2683b8f351bd8db0 import net.minecraft.nbt.IntTag; import net.minecraft.network.chat.Component; import net.minecraft.network.chat.TextComponent; -@@ -278,6 +281,17 @@ public class AnvilMenu extends ItemCombinerMenu { +@@ -277,6 +280,17 @@ public class AnvilMenu extends ItemCombinerMenu { } else if (!this.itemName.equals(itemstack.getHoverName().getString())) { b1 = 1; i += b1; @@ -37,10 +37,10 @@ index c81af461fa01dac0b7b26becc1a5e7ae31bb5f95..2dbc71a3d76cc87e2683b8f351bd8db0 } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index f34dfe5d2b235e482623214cd233e83475807491..40103cf35d15b2fbc88ea737eb74f7d76bbbbc7f 100644 +index cce72e2574486fb178caa3bc1a89cbf39d66e4d1..f2e4cd773a3a3145d7a67245e23534fc77fadb72 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -288,6 +288,11 @@ public class PurpurWorldConfig { +@@ -287,6 +287,11 @@ public class PurpurWorldConfig { voidDamageDealt = getDouble("gameplay-mechanics.void-damage-dealt", voidDamageDealt); } diff --git a/patches/server/0096-Add-no-random-tick-block-list.patch b/patches/server/0096-Add-no-random-tick-block-list.patch index 2bf374d50..b4b951e9a 100644 --- a/patches/server/0096-Add-no-random-tick-block-list.patch +++ b/patches/server/0096-Add-no-random-tick-block-list.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add no-random-tick block list diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 0b22974ffe3ad2dd858f571980107eba9b56882e..f3a1eeb389e790d7c482626f5aee872ab298af66 100644 +index e4ac7b3a8a10056e3e24eef5263cafa65f29145f..f3aa58b31c4e3384c3315e53de4e9e4cc6181adc 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -488,7 +488,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl +@@ -318,7 +318,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl this.players = Lists.newArrayList(); this.entityTickList = new EntityTickList(); Predicate predicate = (block) -> { // CraftBukkit - decompile eror @@ -18,10 +18,10 @@ index 0b22974ffe3ad2dd858f571980107eba9b56882e..f3a1eeb389e790d7c482626f5aee872a DefaultedRegistry registryblocks = Registry.BLOCK; diff --git a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -index 7538262e14c86e4da9cd4cb887b76f649bfef2e6..f34973be478de4f088a0593b45bd89e558a13609 100644 +index a107304351381d68fdaa35a4d7ff214e6c1546a6..6e5dcf3c8a537729a18c085ebb5ab46b59c1d24c 100644 --- a/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java +++ b/src/main/java/net/minecraft/world/level/block/state/BlockBehaviour.java -@@ -915,10 +915,12 @@ public abstract class BlockBehaviour { +@@ -893,10 +893,12 @@ public abstract class BlockBehaviour { } public void tick(ServerLevel world, BlockPos pos, Random random) { @@ -35,10 +35,10 @@ index 7538262e14c86e4da9cd4cb887b76f649bfef2e6..f34973be478de4f088a0593b45bd89e5 } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 40103cf35d15b2fbc88ea737eb74f7d76bbbbc7f..d1a239672c16cfac3872f39ecd8c42e56cf2923b 100644 +index f2e4cd773a3a3145d7a67245e23534fc77fadb72..b2c28f97ceb6c8c18332a56f9d2042dc9301ce3e 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -288,6 +288,28 @@ public class PurpurWorldConfig { +@@ -287,6 +287,28 @@ public class PurpurWorldConfig { voidDamageDealt = getDouble("gameplay-mechanics.void-damage-dealt", voidDamageDealt); } diff --git a/patches/server/0097-Add-option-to-disable-dolphin-treasure-searching.patch b/patches/server/0097-Add-option-to-disable-dolphin-treasure-searching.patch index ecb97ac72..768d67299 100644 --- a/patches/server/0097-Add-option-to-disable-dolphin-treasure-searching.patch +++ b/patches/server/0097-Add-option-to-disable-dolphin-treasure-searching.patch @@ -17,10 +17,10 @@ index c372d47a929e06c8cfb0df86cf4e9bfee4d4b300..2aead13e8f879b614445715fb1912a20 } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index d1a239672c16cfac3872f39ecd8c42e56cf2923b..580f826622bb197da9e4439a556c840eb41087e8 100644 +index b2c28f97ceb6c8c18332a56f9d2042dc9301ce3e..d46484ca99fac9751db3ab8cae430f7167733a2e 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -554,6 +554,7 @@ public class PurpurWorldConfig { +@@ -553,6 +553,7 @@ public class PurpurWorldConfig { public float dolphinSpitSpeed = 1.0F; public float dolphinSpitDamage = 2.0F; public double dolphinMaxHealth = 10.0D; @@ -28,7 +28,7 @@ index d1a239672c16cfac3872f39ecd8c42e56cf2923b..580f826622bb197da9e4439a556c840e private void dolphinSettings() { dolphinRidable = getBoolean("mobs.dolphin.ridable", dolphinRidable); dolphinSpitCooldown = getInt("mobs.dolphin.spit.cooldown", dolphinSpitCooldown); -@@ -565,6 +566,7 @@ public class PurpurWorldConfig { +@@ -564,6 +565,7 @@ public class PurpurWorldConfig { set("mobs.dolphin.attributes.max_health", oldValue); } dolphinMaxHealth = getDouble("mobs.dolphin.attributes.max_health", dolphinMaxHealth); diff --git a/patches/server/0099-Stop-squids-floating-on-top-of-water.patch b/patches/server/0099-Stop-squids-floating-on-top-of-water.patch index 598e76911..b12f8d1ff 100644 --- a/patches/server/0099-Stop-squids-floating-on-top-of-water.patch +++ b/patches/server/0099-Stop-squids-floating-on-top-of-water.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Stop squids floating on top of water diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index d2828fabedd353fb8a854fbd0beb978f5a93b504..e43b31f07d6f0cbb35f925dca952b5947fe1fd4f 100644 +index e64d4a3dd193bf7481884616034c5916114c649c..e5e93f6e2f1b17b165029bc1163c465920cc3fb9 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -3829,11 +3829,17 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n +@@ -3640,11 +3640,17 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n this.yRotO = this.getYRot(); } @@ -45,10 +45,10 @@ index f96def2ebdf114823c322c2d4318d039e20eab97..2affff346a7fe81480e86cb61996039d @Override diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java -index 6f7e6429c35eea346517cbf08cf223fc6d838a8c..6a77112180556675af38cb1b3ce0b38a42ce9525 100644 +index 120498a39b7ca7aee9763084507508d4a1c425aa..0dfc639043998cd3bd32afaaf8153459172cc9f9 100644 --- a/src/main/java/net/minecraft/world/phys/AABB.java +++ b/src/main/java/net/minecraft/world/phys/AABB.java -@@ -367,4 +367,10 @@ public class AABB { +@@ -356,4 +356,10 @@ public class AABB { public static AABB ofSize(Vec3 center, double dx, double dy, double dz) { return new AABB(center.x - dx / 2.0D, center.y - dy / 2.0D, center.z - dz / 2.0D, center.x + dx / 2.0D, center.y + dy / 2.0D, center.z + dz / 2.0D); } @@ -60,10 +60,10 @@ index 6f7e6429c35eea346517cbf08cf223fc6d838a8c..6a77112180556675af38cb1b3ce0b38a + // Purpur } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 580f826622bb197da9e4439a556c840eb41087e8..2e924d6cf915b93b3c3f18e0d3dfddb45cdc2463 100644 +index d46484ca99fac9751db3ab8cae430f7167733a2e..298b246a383100a9860afad156ed9e9219d61bf6 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1314,6 +1314,7 @@ public class PurpurWorldConfig { +@@ -1313,6 +1313,7 @@ public class PurpurWorldConfig { public boolean squidRidable = false; public double squidMaxHealth = 10.0D; public boolean squidImmuneToEAR = true; @@ -71,7 +71,7 @@ index 580f826622bb197da9e4439a556c840eb41087e8..2e924d6cf915b93b3c3f18e0d3dfddb4 private void squidSettings() { squidRidable = getBoolean("mobs.squid.ridable", squidRidable); if (PurpurConfig.version < 10) { -@@ -1323,6 +1324,7 @@ public class PurpurWorldConfig { +@@ -1322,6 +1323,7 @@ public class PurpurWorldConfig { } squidMaxHealth = getDouble("mobs.squid.attributes.max_health", squidMaxHealth); squidImmuneToEAR = getBoolean("mobs.squid.immune-to-EAR", squidImmuneToEAR); diff --git a/patches/server/0102-Entities-can-use-portals-configuration.patch b/patches/server/0102-Entities-can-use-portals-configuration.patch index a6349e7ba..67ba13920 100644 --- a/patches/server/0102-Entities-can-use-portals-configuration.patch +++ b/patches/server/0102-Entities-can-use-portals-configuration.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Entities can use portals configuration diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index e43b31f07d6f0cbb35f925dca952b5947fe1fd4f..cc5854281d976c2faa453c2a6b07b5747eed6100 100644 +index e5e93f6e2f1b17b165029bc1163c465920cc3fb9..de75f6317e661c79d3957180f6d5b20d23af42e4 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2701,7 +2701,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n +@@ -2512,7 +2512,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n public void handleInsidePortal(BlockPos pos) { if (this.isOnPortalCooldown()) { this.setPortalCooldown(); @@ -17,7 +17,7 @@ index e43b31f07d6f0cbb35f925dca952b5947fe1fd4f..cc5854281d976c2faa453c2a6b07b574 if (!this.level.isClientSide && !pos.equals(this.portalEntrancePos)) { this.portalEntrancePos = pos.immutable(); } -@@ -3333,7 +3333,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n +@@ -3144,7 +3144,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n } public boolean canChangeDimensions() { @@ -27,10 +27,10 @@ index e43b31f07d6f0cbb35f925dca952b5947fe1fd4f..cc5854281d976c2faa453c2a6b07b574 public float getBlockExplosionResistance(Explosion explosion, BlockGetter world, BlockPos pos, BlockState blockState, FluidState fluidState, float max) { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 2e924d6cf915b93b3c3f18e0d3dfddb45cdc2463..5e1876ceed7ed597012f85df6e8e5f6d99868ee9 100644 +index 298b246a383100a9860afad156ed9e9219d61bf6..8d5090df3fbd40a514751c72897689d2023310a2 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -274,6 +274,7 @@ public class PurpurWorldConfig { +@@ -273,6 +273,7 @@ public class PurpurWorldConfig { public boolean useBetterMending = false; public boolean boatEjectPlayersOnLand = false; public boolean disableDropsOnCrammingDeath = false; @@ -38,7 +38,7 @@ index 2e924d6cf915b93b3c3f18e0d3dfddb45cdc2463..5e1876ceed7ed597012f85df6e8e5f6d public boolean milkCuresBadOmen = true; public double tridentLoyaltyVoidReturnHeight = 0.0D; public double voidDamageHeight = -64.0D; -@@ -282,6 +283,7 @@ public class PurpurWorldConfig { +@@ -281,6 +282,7 @@ public class PurpurWorldConfig { useBetterMending = getBoolean("gameplay-mechanics.use-better-mending", useBetterMending); boatEjectPlayersOnLand = getBoolean("gameplay-mechanics.boat.eject-players-on-land", boatEjectPlayersOnLand); disableDropsOnCrammingDeath = getBoolean("gameplay-mechanics.disable-drops-on-cramming-death", disableDropsOnCrammingDeath); diff --git a/patches/server/0104-Customizable-wither-health-and-healing.patch b/patches/server/0104-Customizable-wither-health-and-healing.patch index 4368d3051..a0a73feb7 100644 --- a/patches/server/0104-Customizable-wither-health-and-healing.patch +++ b/patches/server/0104-Customizable-wither-health-and-healing.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Customizable wither health and healing Adds the ability to customize the health of the wither, as well as the amount that it heals, and how often. diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -index 22dd455b3b9c148f38a63f72901009f864e63a4b..689df66281ef84f0cc31d89c6f2e654c9f3a5150 100644 +index 4527fdd2c2001767c225574e1610ddfaa7bb4f34..5f24fb3fc4ffb2e032cf755d984716d1c3a3e965 100644 --- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java @@ -504,8 +504,10 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob @@ -23,10 +23,10 @@ index 22dd455b3b9c148f38a63f72901009f864e63a4b..689df66281ef84f0cc31d89c6f2e654c this.bossEvent.setProgress(this.getHealth() / this.getMaxHealth()); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 5e1876ceed7ed597012f85df6e8e5f6d99868ee9..7ef7877013068fd76545b30d1afea899ff9298a6 100644 +index 8d5090df3fbd40a514751c72897689d2023310a2..ae8fb1abb64b2f2d5a6580ad5e708072576da1a5 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1525,6 +1525,8 @@ public class PurpurWorldConfig { +@@ -1524,6 +1524,8 @@ public class PurpurWorldConfig { public boolean witherRidableInWater = false; public double witherMaxY = 256D; public double witherMaxHealth = 300.0D; @@ -35,7 +35,7 @@ index 5e1876ceed7ed597012f85df6e8e5f6d99868ee9..7ef7877013068fd76545b30d1afea899 private void witherSettings() { witherRidable = getBoolean("mobs.wither.ridable", witherRidable); witherRidableInWater = getBoolean("mobs.wither.ridable-in-water", witherRidableInWater); -@@ -1539,6 +1541,8 @@ public class PurpurWorldConfig { +@@ -1538,6 +1540,8 @@ public class PurpurWorldConfig { set("mobs.wither.attributes.max_health", oldValue); } witherMaxHealth = getDouble("mobs.wither.attributes.max_health", witherMaxHealth); diff --git a/patches/server/0105-Allow-toggling-special-MobSpawners-per-world.patch b/patches/server/0105-Allow-toggling-special-MobSpawners-per-world.patch index 48d7bfa08..1570e60c7 100644 --- a/patches/server/0105-Allow-toggling-special-MobSpawners-per-world.patch +++ b/patches/server/0105-Allow-toggling-special-MobSpawners-per-world.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Allow toggling special MobSpawners per world In vanilla, these are all hardcoded on for world type 0 (overworld) and hardcoded off for every other world type. Default config behaviour matches this. diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index f3a1eeb389e790d7c482626f5aee872ab298af66..7620030f795604c49596ce972c0b39438341e4df 100644 +index f3aa58b31c4e3384c3315e53de4e9e4cc6181adc..dd8248b89ee8694a18fffc4d880c92038d725cf7 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -83,6 +83,7 @@ import net.minecraft.world.entity.MobCategory; @@ -27,7 +27,7 @@ index f3a1eeb389e790d7c482626f5aee872ab298af66..7620030f795604c49596ce972c0b3943 import net.minecraft.world.entity.player.Player; import net.minecraft.world.entity.raid.Raid; import net.minecraft.world.entity.raid.Raids; -@@ -133,6 +136,8 @@ import net.minecraft.world.level.gameevent.GameEvent; +@@ -132,6 +135,8 @@ import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.level.gameevent.GameEventListenerRegistrar; import net.minecraft.world.level.gameevent.vibrations.VibrationPath; import net.minecraft.world.level.levelgen.Heightmap; @@ -36,7 +36,7 @@ index f3a1eeb389e790d7c482626f5aee872ab298af66..7620030f795604c49596ce972c0b3943 import net.minecraft.world.level.levelgen.feature.StructureFeature; import net.minecraft.world.level.levelgen.structure.BoundingBox; import net.minecraft.world.level.levelgen.structure.StructureStart; -@@ -511,7 +516,24 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl +@@ -341,7 +346,24 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl this.dragonParts = new Int2ObjectOpenHashMap(); this.tickTime = flag1; this.server = minecraftserver; @@ -94,10 +94,10 @@ index f8ede3588bfda9a7d4d5807311a3e9c2651fd0a3..56967cef0f184def046935e20148574f if (NaturalSpawner.isSpawnPositionOk(SpawnPlacements.Type.ON_GROUND, world, blockposition2, EntityType.WANDERING_TRADER)) { blockposition1 = blockposition2; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 7ef7877013068fd76545b30d1afea899ff9298a6..8ae9ca467ea1d8803b8a97a2495b1edb8ef8dc40 100644 +index ae8fb1abb64b2f2d5a6580ad5e708072576da1a5..db422ed3d1691c48ddcf06cec0cdb9be7ae91d75 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -72,6 +72,12 @@ public class PurpurWorldConfig { +@@ -71,6 +71,12 @@ public class PurpurWorldConfig { return PurpurConfig.config.getBoolean("world-settings." + worldName + "." + path, PurpurConfig.config.getBoolean("world-settings.default." + path)); } @@ -110,7 +110,7 @@ index 7ef7877013068fd76545b30d1afea899ff9298a6..8ae9ca467ea1d8803b8a97a2495b1edb private double getDouble(String path, double def) { PurpurConfig.config.addDefault("world-settings.default." + path, def); return PurpurConfig.config.getDouble("world-settings." + worldName + "." + path, PurpurConfig.config.getDouble("world-settings.default." + path)); -@@ -221,6 +227,21 @@ public class PurpurWorldConfig { +@@ -220,6 +226,21 @@ public class PurpurWorldConfig { } } diff --git a/patches/server/0106-Raid-cooldown-setting.patch b/patches/server/0106-Raid-cooldown-setting.patch index 7f1c81281..e0cacb4e0 100644 --- a/patches/server/0106-Raid-cooldown-setting.patch +++ b/patches/server/0106-Raid-cooldown-setting.patch @@ -52,10 +52,10 @@ index 45e369aa69a6b78def42b619b1b1b8259d4b30ea..de7443e7a27e51eabaed2d6d348ec8ea if (!this.raidMap.containsKey(raid.getId())) { this.raidMap.put(raid.getId(), raid); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 8ae9ca467ea1d8803b8a97a2495b1edb8ef8dc40..23e38d663bf5ab8d592d64e54128fe17dfe8f025 100644 +index db422ed3d1691c48ddcf06cec0cdb9be7ae91d75..5c8e25590e99e7db547ef415f4cab34c200021e9 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -300,6 +300,7 @@ public class PurpurWorldConfig { +@@ -299,6 +299,7 @@ public class PurpurWorldConfig { public double tridentLoyaltyVoidReturnHeight = 0.0D; public double voidDamageHeight = -64.0D; public double voidDamageDealt = 4.0D; @@ -63,7 +63,7 @@ index 8ae9ca467ea1d8803b8a97a2495b1edb8ef8dc40..23e38d663bf5ab8d592d64e54128fe17 private void miscGameplayMechanicsSettings() { useBetterMending = getBoolean("gameplay-mechanics.use-better-mending", useBetterMending); boatEjectPlayersOnLand = getBoolean("gameplay-mechanics.boat.eject-players-on-land", boatEjectPlayersOnLand); -@@ -309,6 +310,7 @@ public class PurpurWorldConfig { +@@ -308,6 +309,7 @@ public class PurpurWorldConfig { tridentLoyaltyVoidReturnHeight = getDouble("gameplay-mechanics.trident-loyalty-void-return-height", tridentLoyaltyVoidReturnHeight); voidDamageHeight = getDouble("gameplay-mechanics.void-damage-height", voidDamageHeight); voidDamageDealt = getDouble("gameplay-mechanics.void-damage-dealt", voidDamageDealt); diff --git a/patches/server/0107-Despawn-rate-config-options-per-projectile-type.patch b/patches/server/0107-Despawn-rate-config-options-per-projectile-type.patch index 4d72d287d..72e70dc88 100644 --- a/patches/server/0107-Despawn-rate-config-options-per-projectile-type.patch +++ b/patches/server/0107-Despawn-rate-config-options-per-projectile-type.patch @@ -127,7 +127,7 @@ index f1a12b147d55e34d4f8374593640a311598cf1a6..c4bdebd4310035a5cce5a5790f538eb0 + // Purpur end } diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -index 07853aff3d42ce50799406ee1c14389b5f715b51..9a5da1eec68c7c435406809fda9695cbd54ba6c5 100644 +index 4e667be589fd95eb61e57a99448939a945c59e5e..cecdafb496669419911ac7ea6db5a69f71f9be13 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java @@ -33,6 +33,7 @@ public abstract class Projectile extends Entity { @@ -289,10 +289,10 @@ index 4a11f7417b438ee5711a720aca3321c88e970b2a..46b74271ce5f614f07754db14d2a552c + // Purpur end } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 23e38d663bf5ab8d592d64e54128fe17dfe8f025..30789fb3579896ac404eaa3941bc4c41a8979282 100644 +index 5c8e25590e99e7db547ef415f4cab34c200021e9..d207aa7ae9ece4959831f9e7c606b16a55e6b76f 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -267,6 +267,35 @@ public class PurpurWorldConfig { +@@ -266,6 +266,35 @@ public class PurpurWorldConfig { totemOfUndyingWorksInInventory = getBoolean("gameplay-mechanics.player.totem-of-undying-works-in-inventory", totemOfUndyingWorksInInventory); } diff --git a/patches/server/0108-Add-option-to-disable-zombie-aggressiveness-towards-.patch b/patches/server/0108-Add-option-to-disable-zombie-aggressiveness-towards-.patch index 24c849794..1b95cf2a0 100644 --- a/patches/server/0108-Add-option-to-disable-zombie-aggressiveness-towards-.patch +++ b/patches/server/0108-Add-option-to-disable-zombie-aggressiveness-towards-.patch @@ -46,7 +46,7 @@ index 7d28a410ae18594b5db4559640b4eb30762f5a69..8d3ce6c97a8734c0d13844cafca251a3 this.targetSelector.addGoal(3, new NearestAttackableTargetGoal<>(this, Axolotl.class, true, false)); this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR)); diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index 77002a4c3b03e0397515fd922f14e56b8e81074a..b80f7c71cbf7b10bda6fac3cfe673ac7fe129923 100644 +index f6e33ca1933792a3a417f54d20fea8dc4a41c1b5..7eed6c176345c766a99d4304d61d28354291960f 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java +++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java @@ -152,7 +152,19 @@ public class Zombie extends Monster { @@ -71,10 +71,10 @@ index 77002a4c3b03e0397515fd922f14e56b8e81074a..b80f7c71cbf7b10bda6fac3cfe673ac7 this.targetSelector.addGoal(5, new NearestAttackableTargetGoal<>(this, Turtle.class, 10, true, false, Turtle.BABY_ON_LAND_SELECTOR)); } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 30789fb3579896ac404eaa3941bc4c41a8979282..fe946308f944b41d4839ed8654f8f0c721ec2759 100644 +index d207aa7ae9ece4959831f9e7c606b16a55e6b76f..050f2d8325d8b5a18e534027662fcbfff06a4c68 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1648,6 +1648,7 @@ public class PurpurWorldConfig { +@@ -1647,6 +1647,7 @@ public class PurpurWorldConfig { public boolean zombieJockeyOnlyBaby = true; public double zombieJockeyChance = 0.05D; public boolean zombieJockeyTryExistingChickens = true; @@ -82,7 +82,7 @@ index 30789fb3579896ac404eaa3941bc4c41a8979282..fe946308f944b41d4839ed8654f8f0c7 private void zombieSettings() { zombieRidable = getBoolean("mobs.zombie.ridable", zombieRidable); zombieRidableInWater = getBoolean("mobs.zombie.ridable-in-water", zombieRidableInWater); -@@ -1661,6 +1662,7 @@ public class PurpurWorldConfig { +@@ -1660,6 +1661,7 @@ public class PurpurWorldConfig { zombieJockeyOnlyBaby = getBoolean("mobs.zombie.jockey.only-babies", zombieJockeyOnlyBaby); zombieJockeyChance = getDouble("mobs.zombie.jockey.chance", zombieJockeyChance); zombieJockeyTryExistingChickens = getBoolean("mobs.zombie.jockey.try-existing-chickens", zombieJockeyTryExistingChickens); diff --git a/patches/server/0109-Persistent-TileEntity-Lore-and-DisplayName.patch b/patches/server/0109-Persistent-TileEntity-Lore-and-DisplayName.patch index 741e9ae2a..446fe3d80 100644 --- a/patches/server/0109-Persistent-TileEntity-Lore-and-DisplayName.patch +++ b/patches/server/0109-Persistent-TileEntity-Lore-and-DisplayName.patch @@ -37,7 +37,7 @@ index 44b28773fe8e79931e738d493bd9405e0ee3dca9..d9114ee2ef4f1dee7ae018f932a7804a @Nullable diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java -index 878cdfc49253e7916d038495f79fec7cce75aa50..99d05dbe7d7d9bcc1c5f5b3dd02e413e3a398a10 100644 +index d6a3f3a2edae806b0ebf5bf5ac445116c0d64535..0f295a496d4d94811199fa952ed68325ee1df0f6 100644 --- a/src/main/java/net/minecraft/world/level/block/Block.java +++ b/src/main/java/net/minecraft/world/level/block/Block.java @@ -20,6 +20,9 @@ import net.minecraft.core.IdMapper; @@ -189,10 +189,10 @@ index c3a07ccccd5cc38552363c82398f432c8d624288..132c9e6a643995d9fde535a78d9edc9e + // Purpur end } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index fe946308f944b41d4839ed8654f8f0c721ec2759..039b20dc90b37522563885fd9a35b089b2771e98 100644 +index 050f2d8325d8b5a18e534027662fcbfff06a4c68..2086377305f800980d436b606c72ddba447694f1 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -326,6 +326,7 @@ public class PurpurWorldConfig { +@@ -325,6 +325,7 @@ public class PurpurWorldConfig { public boolean disableDropsOnCrammingDeath = false; public boolean entitiesCanUsePortals = true; public boolean milkCuresBadOmen = true; @@ -200,7 +200,7 @@ index fe946308f944b41d4839ed8654f8f0c721ec2759..039b20dc90b37522563885fd9a35b089 public double tridentLoyaltyVoidReturnHeight = 0.0D; public double voidDamageHeight = -64.0D; public double voidDamageDealt = 4.0D; -@@ -336,6 +337,7 @@ public class PurpurWorldConfig { +@@ -335,6 +336,7 @@ public class PurpurWorldConfig { disableDropsOnCrammingDeath = getBoolean("gameplay-mechanics.disable-drops-on-cramming-death", disableDropsOnCrammingDeath); entitiesCanUsePortals = getBoolean("gameplay-mechanics.entities-can-use-portals", entitiesCanUsePortals); milkCuresBadOmen = getBoolean("gameplay-mechanics.milk-cures-bad-omen", milkCuresBadOmen); diff --git a/patches/server/0111-Flying-squids-Oh-my.patch b/patches/server/0111-Flying-squids-Oh-my.patch index 6dd0e9947..587b377d8 100644 --- a/patches/server/0111-Flying-squids-Oh-my.patch +++ b/patches/server/0111-Flying-squids-Oh-my.patch @@ -58,10 +58,10 @@ index 2affff346a7fe81480e86cb61996039df0569853..12be4a2f25a7def8341acda47d10a256 float f1 = Mth.cos(f) * 0.2F; float f2 = -0.1F + this.squid.getRandom().nextFloat() * 0.2F; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 039b20dc90b37522563885fd9a35b089b2771e98..7308e5bdd235f2459b1debdaa123615c283539cb 100644 +index 2086377305f800980d436b606c72ddba447694f1..f9da36aa9aead8f8ec7ec33469a1d95009e3a297 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -813,9 +813,11 @@ public class PurpurWorldConfig { +@@ -812,9 +812,11 @@ public class PurpurWorldConfig { public boolean glowSquidRidable = false; public double glowSquidMaxHealth = 10.0D; @@ -73,7 +73,7 @@ index 039b20dc90b37522563885fd9a35b089b2771e98..7308e5bdd235f2459b1debdaa123615c } public boolean goatRidable = false; -@@ -1371,6 +1373,7 @@ public class PurpurWorldConfig { +@@ -1370,6 +1372,7 @@ public class PurpurWorldConfig { public double squidMaxHealth = 10.0D; public boolean squidImmuneToEAR = true; public double squidOffsetWaterCheck = 0.0D; @@ -81,7 +81,7 @@ index 039b20dc90b37522563885fd9a35b089b2771e98..7308e5bdd235f2459b1debdaa123615c private void squidSettings() { squidRidable = getBoolean("mobs.squid.ridable", squidRidable); if (PurpurConfig.version < 10) { -@@ -1381,6 +1384,7 @@ public class PurpurWorldConfig { +@@ -1380,6 +1383,7 @@ public class PurpurWorldConfig { squidMaxHealth = getDouble("mobs.squid.attributes.max_health", squidMaxHealth); squidImmuneToEAR = getBoolean("mobs.squid.immune-to-EAR", squidImmuneToEAR); squidOffsetWaterCheck = getDouble("mobs.squid.water-offset-check", squidOffsetWaterCheck); diff --git a/patches/server/0112-Infinity-bow-settings.patch b/patches/server/0112-Infinity-bow-settings.patch index 94eb7dcec..50a195fa5 100644 --- a/patches/server/0112-Infinity-bow-settings.patch +++ b/patches/server/0112-Infinity-bow-settings.patch @@ -27,10 +27,10 @@ index afe33f20578177cb517e1c116e6319481642e66c..fe4695adbb506733b4029ecfabcfda3d } else { user.startUsingItem(hand); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 7308e5bdd235f2459b1debdaa123615c283539cb..ab1a8250c90bc7b555f678ff0a119a90102a1356 100644 +index f9da36aa9aead8f8ec7ec33469a1d95009e3a297..d5799c1084095eda3d3fd2d5bd2ae307ddad996b 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -122,6 +122,17 @@ public class PurpurWorldConfig { +@@ -121,6 +121,17 @@ public class PurpurWorldConfig { entityLifeSpan = getInt("gameplay-mechanics.entity-lifespan", entityLifeSpan); } diff --git a/patches/server/0113-Stonecutter-damage.patch b/patches/server/0113-Stonecutter-damage.patch index e89fb2ea4..f3727d314 100644 --- a/patches/server/0113-Stonecutter-damage.patch +++ b/patches/server/0113-Stonecutter-damage.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Stonecutter damage diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index cc5854281d976c2faa453c2a6b07b5747eed6100..57a80aed6106ec55aeb636c5b5f3abc7fb4fff14 100644 +index de75f6317e661c79d3957180f6d5b20d23af42e4..1e21e75558dc69cb9c32dce99644ddd022165b58 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -1001,7 +1001,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n +@@ -897,7 +897,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n } // CraftBukkit end @@ -61,10 +61,10 @@ index 2ad5ff9a1d7de54e75436e99da8a73db9dc91bde..60605a8a021cc56f9c3ba22bc43c43c3 } else if (blockState.is(Blocks.HONEY_BLOCK)) { return BlockPathTypes.STICKY_HONEY; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index ab1a8250c90bc7b555f678ff0a119a90102a1356..cddccb3329be99ce11ea30b651184fb9eea243b3 100644 +index d5799c1084095eda3d3fd2d5bd2ae307ddad996b..21d3acb8a69fffd372af6cd1fc667feb91a3a4dc 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -449,6 +449,11 @@ public class PurpurWorldConfig { +@@ -448,6 +448,11 @@ public class PurpurWorldConfig { spawnerDeactivateByRedstone = getBoolean("blocks.spawner.deactivate-by-redstone", spawnerDeactivateByRedstone); } diff --git a/patches/server/0114-Configurable-daylight-cycle.patch b/patches/server/0114-Configurable-daylight-cycle.patch index 6abd7f198..100dd79b2 100644 --- a/patches/server/0114-Configurable-daylight-cycle.patch +++ b/patches/server/0114-Configurable-daylight-cycle.patch @@ -18,26 +18,26 @@ index 689ad22925b2561f7c8db961743eb1f821dbb25f..fa3c960992cc240161817e54659d83fe public ClientboundSetTimePacket(long time, long timeOfDay, boolean doDaylightCycle) { this.gameTime = time % 192000; // Paper - fix guardian beam diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 7620030f795604c49596ce972c0b39438341e4df..276fb2b9fc9d33dc2157903e799178168c69bc88 100644 +index dd8248b89ee8694a18fffc4d880c92038d725cf7..9abc5063efe95a48a70045d8cedc15ab245ee65c 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -201,6 +201,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl - final Int2ObjectMap dragonParts; +@@ -200,6 +200,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl private final StructureFeatureManager structureFeatureManager; private final boolean tickTime; + + private double fakeTime; // Purpur - // Tuinity start - execute chunk tasks mid tick - public long lastMidTickExecuteFailure; - // Tuinity end - execute chunk tasks mid tick -@@ -578,6 +579,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + + // CraftBukkit start + private int tickPosition; +@@ -408,6 +409,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl this.getCraftServer().addWorld(this.getWorld()); // CraftBukkit this.asyncChunkTaskManager = new com.destroystokyo.paper.io.chunk.ChunkTaskManager(this); // Paper + this.fakeTime = this.serverLevelData.getDayTime(); // Purpur } - // Tuinity start - optimise collision -@@ -869,6 +871,18 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl + // CraftBukkit start +@@ -687,6 +689,18 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl this.liquidTicks.nextTick(); // Paper this.serverLevelData.getScheduledEvents().tick(this.server, i); if (this.levelData.getGameRules().getBoolean(GameRules.RULE_DAYLIGHT)) { @@ -56,7 +56,7 @@ index 7620030f795604c49596ce972c0b39438341e4df..276fb2b9fc9d33dc2157903e79917816 this.setDayTime(this.levelData.getDayTime() + 1L); } -@@ -877,6 +891,12 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl +@@ -695,6 +709,12 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl public void setDayTime(long timeOfDay) { this.serverLevelData.setDayTime(timeOfDay); @@ -70,10 +70,10 @@ index 7620030f795604c49596ce972c0b39438341e4df..276fb2b9fc9d33dc2157903e79917816 public void tickCustomSpawners(boolean spawnMonsters, boolean spawnAnimals) { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index cddccb3329be99ce11ea30b651184fb9eea243b3..b91e2bda59fa82ab0d86f70a6d0185ae76126772 100644 +index 21d3acb8a69fffd372af6cd1fc667feb91a3a4dc..a2e9ed180930e3f20b4973f2082d4b336e2719a1 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -104,6 +104,13 @@ public class PurpurWorldConfig { +@@ -103,6 +103,13 @@ public class PurpurWorldConfig { armorstandStepHeight = (float) getDouble("gameplay-mechanics.armorstand.step-height", armorstandStepHeight); } diff --git a/patches/server/0116-Furnace-uses-lava-from-underneath.patch b/patches/server/0116-Furnace-uses-lava-from-underneath.patch index cc51ff16d..baa1b558c 100644 --- a/patches/server/0116-Furnace-uses-lava-from-underneath.patch +++ b/patches/server/0116-Furnace-uses-lava-from-underneath.patch @@ -48,10 +48,10 @@ index 348e485897c34cca19113cc35f055a58778ca38b..fb3f7e821224889b08c0f424d79e122c private static boolean canBurn(@Nullable Recipe recipe, NonNullList slots, int count) { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index b91e2bda59fa82ab0d86f70a6d0185ae76126772..173cd963b74715943ba8d6ef49ffa9bcd0e4a09b 100644 +index a2e9ed180930e3f20b4973f2082d4b336e2719a1..e2e7dafa46f16c8b3fcc68ff8a56b4183ca51e09 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -417,6 +417,17 @@ public class PurpurWorldConfig { +@@ -416,6 +416,17 @@ public class PurpurWorldConfig { farmlandGetsMoistFromBelow = getBoolean("blocks.farmland.gets-moist-from-below", farmlandGetsMoistFromBelow); } diff --git a/patches/server/0118-Ability-to-re-add-farmland-mechanics-from-Alpha.patch b/patches/server/0118-Ability-to-re-add-farmland-mechanics-from-Alpha.patch index e6df655e4..5f4eaa5cc 100644 --- a/patches/server/0118-Ability-to-re-add-farmland-mechanics-from-Alpha.patch +++ b/patches/server/0118-Ability-to-re-add-farmland-mechanics-from-Alpha.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Ability to re-add farmland mechanics from Alpha diff --git a/src/main/java/net/minecraft/world/level/block/FarmBlock.java b/src/main/java/net/minecraft/world/level/block/FarmBlock.java -index 5d9d77cb382c8075af2713a0ce26c28a35a0aaa8..2d4d59eb5a534e4c283933b734c44776b674b2eb 100644 +index e59861e0feb20b66735a76c19fd4e48bf13443e2..9fb0464f74bcbb757171290ae44f4a4c7ea4d499 100644 --- a/src/main/java/net/minecraft/world/level/block/FarmBlock.java +++ b/src/main/java/net/minecraft/world/level/block/FarmBlock.java @@ -96,6 +96,14 @@ public class FarmBlock extends Block { @@ -24,10 +24,10 @@ index 5d9d77cb382c8075af2713a0ce26c28a35a0aaa8..2d4d59eb5a534e4c283933b734c44776 return; } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 173cd963b74715943ba8d6ef49ffa9bcd0e4a09b..92651fe2b2fc2837d3c74a7a71192e8502bf013e 100644 +index e2e7dafa46f16c8b3fcc68ff8a56b4183ca51e09..5ba0878c35606d7b980105a3c4f12c7d885203d3 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -413,8 +413,10 @@ public class PurpurWorldConfig { +@@ -412,8 +412,10 @@ public class PurpurWorldConfig { } public boolean farmlandGetsMoistFromBelow = false; diff --git a/patches/server/0119-Add-adjustable-breeding-cooldown-to-config.patch b/patches/server/0119-Add-adjustable-breeding-cooldown-to-config.patch index 32d3dddb3..a6ea216fd 100644 --- a/patches/server/0119-Add-adjustable-breeding-cooldown-to-config.patch +++ b/patches/server/0119-Add-adjustable-breeding-cooldown-to-config.patch @@ -33,10 +33,10 @@ index 5a503a255b4e7e684a8f42d8190430397ca81683..7a90c6a628571730eee382e1efcfe1b9 entityageable.setBaby(true); entityageable.moveTo(this.getX(), this.getY(), this.getZ(), 0.0F, 0.0F); diff --git a/src/main/java/net/minecraft/world/level/Level.java b/src/main/java/net/minecraft/world/level/Level.java -index 0b673d8854f3afc79226bf10c00cd8098438eed0..132f6e7de8bd9072c41a14caba3167500f1e16e7 100644 +index 2571738bd83e21207e5463be5e2ba7d7b7f94a87..9427f43db158d2e813e5b8584079df027df91635 100644 --- a/src/main/java/net/minecraft/world/level/Level.java +++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -187,6 +187,49 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -184,6 +184,49 @@ public abstract class Level implements LevelAccessor, AutoCloseable { } // Paper end - fix and optimise world upgrading @@ -86,19 +86,19 @@ index 0b673d8854f3afc79226bf10c00cd8098438eed0..132f6e7de8bd9072c41a14caba316750 public CraftWorld getWorld() { return this.world; } -@@ -318,6 +361,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { +@@ -207,6 +250,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable { + this.spigotConfig = new org.spigotmc.SpigotWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName()); // Spigot this.paperConfig = new com.destroystokyo.paper.PaperWorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), this.spigotConfig); // Paper - this.tuinityConfig = new com.tuinity.tuinity.config.TuinityConfig.WorldConfig(((net.minecraft.world.level.storage.PrimaryLevelData)worlddatamutable).getLevelName()); // Tuinity - Server Config this.purpurConfig = new net.pl3x.purpur.PurpurWorldConfig((ServerLevel) this, ((net.minecraft.world.level.storage.PrimaryLevelData) worlddatamutable).getLevelName(), env); // Purpur + this.playerBreedingCooldowns = this.getNewBreedingCooldownCache(); // Purpur this.generator = gen; this.world = new CraftWorld((ServerLevel) this, gen, env); this.ticksPerAnimalSpawns = this.getCraftServer().getTicksPerAnimalSpawns(); // CraftBukkit diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 92651fe2b2fc2837d3c74a7a71192e8502bf013e..965ab0b3d77fa8a46fa64a149aed4308d38ee1dd 100644 +index 5ba0878c35606d7b980105a3c4f12c7d885203d3..f7a73173585239c90ae55f49eed3cd40e0f8e1e8 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -349,6 +349,7 @@ public class PurpurWorldConfig { +@@ -348,6 +348,7 @@ public class PurpurWorldConfig { public double voidDamageHeight = -64.0D; public double voidDamageDealt = 4.0D; public int raidCooldownSeconds = 0; @@ -106,7 +106,7 @@ index 92651fe2b2fc2837d3c74a7a71192e8502bf013e..965ab0b3d77fa8a46fa64a149aed4308 private void miscGameplayMechanicsSettings() { useBetterMending = getBoolean("gameplay-mechanics.use-better-mending", useBetterMending); boatEjectPlayersOnLand = getBoolean("gameplay-mechanics.boat.eject-players-on-land", boatEjectPlayersOnLand); -@@ -360,6 +361,7 @@ public class PurpurWorldConfig { +@@ -359,6 +360,7 @@ public class PurpurWorldConfig { voidDamageHeight = getDouble("gameplay-mechanics.void-damage-height", voidDamageHeight); voidDamageDealt = getDouble("gameplay-mechanics.void-damage-dealt", voidDamageDealt); raidCooldownSeconds = getInt("gameplay-mechanics.raid-cooldown-seconds", raidCooldownSeconds); diff --git a/patches/server/0120-Make-entity-breeding-times-configurable.patch b/patches/server/0120-Make-entity-breeding-times-configurable.patch index ca7f6d7d7..8c18da363 100644 --- a/patches/server/0120-Make-entity-breeding-times-configurable.patch +++ b/patches/server/0120-Make-entity-breeding-times-configurable.patch @@ -266,7 +266,7 @@ index 497be3182c72b5a0f3bc42088c4168702119b527..607823661ba942ec03b1f61dac5a786b @Override diff --git a/src/main/java/net/minecraft/world/entity/animal/Turtle.java b/src/main/java/net/minecraft/world/entity/animal/Turtle.java -index 66c01b8300bc09ace27e4d1a30ee9274c69fcc9a..3e8c90c2d44d906fae3dfc068a30c81a494a3268 100644 +index 31568adcf4a89b11e61f455a15326c7f72bf487e..c1202540383ef991d7b8c3767132c4fc54d4c570 100644 --- a/src/main/java/net/minecraft/world/entity/animal/Turtle.java +++ b/src/main/java/net/minecraft/world/entity/animal/Turtle.java @@ -105,6 +105,11 @@ public class Turtle extends Animal { @@ -298,7 +298,7 @@ index ae416b70109c959980b3115da6e97df1610996ca..ef4abaf68de01b0879f7d0b330d2d57c @Override diff --git a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java -index 15a0aeb1131618ea27620c5893a7448af624b6dd..a75d7da8278b5a0671d85708898731d9bd3344e2 100644 +index cdc8e548ea48efd481ad6495cab6d8e9f77cfb15..c9056e16015f6e0ed9e5da03ad036162dbab4d09 100644 --- a/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java +++ b/src/main/java/net/minecraft/world/entity/animal/axolotl/Axolotl.java @@ -110,6 +110,11 @@ public class Axolotl extends Animal implements LerpingModel, Bucketable { @@ -314,10 +314,10 @@ index 15a0aeb1131618ea27620c5893a7448af624b6dd..a75d7da8278b5a0671d85708898731d9 @Override diff --git a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -index 9092eac3e7e15845d14175cad8030f4ea60d43ad..93439e518b071cc19eeabe4c8db7676e5a31cfff 100644 +index 26d8b59dd385cc89af64fbbf3f8e09c2e9b41b8c..ca62d4adafc3195541665192888feac91312d71a 100644 --- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java -@@ -85,6 +85,11 @@ public class Goat extends Animal { +@@ -87,6 +87,11 @@ public class Goat extends Animal { public void initAttributes() { this.getAttribute(Attributes.MAX_HEALTH).setBaseValue(this.level.purpurConfig.goatMaxHealth); } @@ -442,7 +442,7 @@ index 6ca7b168a1ea26102922d9377e52662f16c1e725..74321ed51b25617bac25e3497ba908cd public static AttributeSupplier.Builder createAttributes() { diff --git a/src/main/java/net/minecraft/world/entity/monster/Strider.java b/src/main/java/net/minecraft/world/entity/monster/Strider.java -index 6b9316536e09c52be2cd1e9cdc71529f4ddb69c6..294f276fa8d2d754abde11ebc3d39e5e68996b05 100644 +index 2d7b83ce2e0e26ef3976514ec8921a718ccc28bd..9d314470361b2e17afdadc355c084254e5b03aff 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Strider.java +++ b/src/main/java/net/minecraft/world/entity/monster/Strider.java @@ -112,6 +112,11 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { @@ -474,10 +474,10 @@ index 5d289be8f0ef003abbce992e7662f6ddce4f4a99..5e3d7321a73144c3e4c43c18c5b748b2 @Override diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527d46d65b1 100644 +index f7a73173585239c90ae55f49eed3cd40e0f8e1e8..a1e135e7813c31dbb0508ebb0bee1a2c453e5b4e 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -487,9 +487,11 @@ public class PurpurWorldConfig { +@@ -486,9 +486,11 @@ public class PurpurWorldConfig { public boolean axolotlRidable = false; public double axolotlMaxHealth = 14.0D; @@ -489,7 +489,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 } public boolean batRidable = false; -@@ -519,6 +521,7 @@ public class PurpurWorldConfig { +@@ -518,6 +520,7 @@ public class PurpurWorldConfig { public boolean beeRidableInWater = false; public double beeMaxY = 256D; public double beeMaxHealth = 10.0D; @@ -497,7 +497,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 private void beeSettings() { beeRidable = getBoolean("mobs.bee.ridable", beeRidable); beeRidableInWater = getBoolean("mobs.bee.ridable-in-water", beeRidableInWater); -@@ -529,6 +532,7 @@ public class PurpurWorldConfig { +@@ -528,6 +531,7 @@ public class PurpurWorldConfig { set("mobs.bee.attributes.max_health", oldValue); } beeMaxHealth = getDouble("mobs.bee.attributes.max_health", beeMaxHealth); @@ -505,7 +505,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 } public boolean blazeRidable = false; -@@ -553,6 +557,7 @@ public class PurpurWorldConfig { +@@ -552,6 +556,7 @@ public class PurpurWorldConfig { public int catSpawnDelay = 1200; public int catSpawnSwampHutScanRange = 16; public int catSpawnVillageScanRange = 48; @@ -513,7 +513,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 private void catSettings() { catRidable = getBoolean("mobs.cat.ridable", catRidable); catRidableInWater = getBoolean("mobs.cat.ridable-in-water", catRidableInWater); -@@ -565,6 +570,7 @@ public class PurpurWorldConfig { +@@ -564,6 +569,7 @@ public class PurpurWorldConfig { catSpawnDelay = getInt("mobs.cat.spawn-delay", catSpawnDelay); catSpawnSwampHutScanRange = getInt("mobs.cat.scan-range-for-other-cats.swamp-hut", catSpawnSwampHutScanRange); catSpawnVillageScanRange = getInt("mobs.cat.scan-range-for-other-cats.village", catSpawnVillageScanRange); @@ -521,7 +521,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 } public boolean caveSpiderRidable = false; -@@ -585,6 +591,7 @@ public class PurpurWorldConfig { +@@ -584,6 +590,7 @@ public class PurpurWorldConfig { public boolean chickenRidableInWater = false; public double chickenMaxHealth = 4.0D; public boolean chickenRetaliate = false; @@ -529,7 +529,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 private void chickenSettings() { chickenRidable = getBoolean("mobs.chicken.ridable", chickenRidable); chickenRidableInWater = getBoolean("mobs.chicken.ridable-in-water", chickenRidableInWater); -@@ -595,6 +602,7 @@ public class PurpurWorldConfig { +@@ -594,6 +601,7 @@ public class PurpurWorldConfig { } chickenMaxHealth = getDouble("mobs.chicken.attributes.max_health", chickenMaxHealth); chickenRetaliate = getBoolean("mobs.chicken.retaliate", chickenRetaliate); @@ -537,7 +537,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 } public boolean codRidable = false; -@@ -613,6 +621,7 @@ public class PurpurWorldConfig { +@@ -612,6 +620,7 @@ public class PurpurWorldConfig { public boolean cowRidableInWater = false; public double cowMaxHealth = 10.0D; public int cowFeedMushrooms = 0; @@ -545,7 +545,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 private void cowSettings() { cowRidable = getBoolean("mobs.cow.ridable", cowRidable); cowRidableInWater = getBoolean("mobs.cow.ridable-in-water", cowRidableInWater); -@@ -623,6 +632,7 @@ public class PurpurWorldConfig { +@@ -622,6 +631,7 @@ public class PurpurWorldConfig { } cowMaxHealth = getDouble("mobs.cow.attributes.max_health", cowMaxHealth); cowFeedMushrooms = getInt("mobs.cow.feed-mushrooms-for-mooshroom", cowFeedMushrooms); @@ -553,7 +553,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 } public boolean creeperRidable = false; -@@ -670,6 +680,7 @@ public class PurpurWorldConfig { +@@ -669,6 +679,7 @@ public class PurpurWorldConfig { public double donkeyJumpStrengthMax = 0.5D; public double donkeyMovementSpeedMin = 0.175D; public double donkeyMovementSpeedMax = 0.175D; @@ -561,7 +561,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 private void donkeySettings() { donkeyRidableInWater = getBoolean("mobs.donkey.ridable-in-water", donkeyRidableInWater); if (PurpurConfig.version < 10) { -@@ -685,6 +696,7 @@ public class PurpurWorldConfig { +@@ -684,6 +695,7 @@ public class PurpurWorldConfig { donkeyJumpStrengthMax = getDouble("mobs.donkey.attributes.jump_strength.max", donkeyJumpStrengthMax); donkeyMovementSpeedMin = getDouble("mobs.donkey.attributes.movement_speed.min", donkeyMovementSpeedMin); donkeyMovementSpeedMax = getDouble("mobs.donkey.attributes.movement_speed.max", donkeyMovementSpeedMax); @@ -569,7 +569,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 } public boolean drownedRidable = false; -@@ -791,6 +803,7 @@ public class PurpurWorldConfig { +@@ -790,6 +802,7 @@ public class PurpurWorldConfig { public boolean foxRidableInWater = false; public double foxMaxHealth = 10.0D; public boolean foxTypeChangesWithTulips = false; @@ -577,7 +577,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 private void foxSettings() { foxRidable = getBoolean("mobs.fox.ridable", foxRidable); foxRidableInWater = getBoolean("mobs.fox.ridable-in-water", foxRidableInWater); -@@ -801,6 +814,7 @@ public class PurpurWorldConfig { +@@ -800,6 +813,7 @@ public class PurpurWorldConfig { } foxMaxHealth = getDouble("mobs.fox.attributes.max_health", foxMaxHealth); foxTypeChangesWithTulips = getBoolean("mobs.fox.tulips-change-type", foxTypeChangesWithTulips); @@ -585,7 +585,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 } public boolean ghastRidable = false; -@@ -861,10 +875,12 @@ public class PurpurWorldConfig { +@@ -860,10 +874,12 @@ public class PurpurWorldConfig { public boolean goatRidable = false; public boolean goatRidableInWater = false; public double goatMaxHealth = 10.0D; @@ -598,7 +598,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 } public boolean guardianRidable = false; -@@ -882,6 +898,7 @@ public class PurpurWorldConfig { +@@ -881,6 +897,7 @@ public class PurpurWorldConfig { public boolean hoglinRidable = false; public boolean hoglinRidableInWater = false; public double hoglinMaxHealth = 40.0D; @@ -606,7 +606,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 private void hoglinSettings() { hoglinRidable = getBoolean("mobs.hoglin.ridable", hoglinRidable); hoglinRidableInWater = getBoolean("mobs.hoglin.ridable-in-water", hoglinRidableInWater); -@@ -891,6 +908,7 @@ public class PurpurWorldConfig { +@@ -890,6 +907,7 @@ public class PurpurWorldConfig { set("mobs.hoglin.attributes.max_health", oldValue); } hoglinMaxHealth = getDouble("mobs.hoglin.attributes.max_health", hoglinMaxHealth); @@ -614,7 +614,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 } public boolean horseRidableInWater = false; -@@ -900,6 +918,7 @@ public class PurpurWorldConfig { +@@ -899,6 +917,7 @@ public class PurpurWorldConfig { public double horseJumpStrengthMax = 1.0D; public double horseMovementSpeedMin = 0.1125D; public double horseMovementSpeedMax = 0.3375D; @@ -622,7 +622,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 private void horseSettings() { horseRidableInWater = getBoolean("mobs.horse.ridable-in-water", horseRidableInWater); if (PurpurConfig.version < 10) { -@@ -915,6 +934,7 @@ public class PurpurWorldConfig { +@@ -914,6 +933,7 @@ public class PurpurWorldConfig { horseJumpStrengthMax = getDouble("mobs.horse.attributes.jump_strength.max", horseJumpStrengthMax); horseMovementSpeedMin = getDouble("mobs.horse.attributes.movement_speed.min", horseMovementSpeedMin); horseMovementSpeedMax = getDouble("mobs.horse.attributes.movement_speed.max", horseMovementSpeedMax); @@ -630,7 +630,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 } public boolean huskRidable = false; -@@ -985,6 +1005,7 @@ public class PurpurWorldConfig { +@@ -984,6 +1004,7 @@ public class PurpurWorldConfig { public double llamaJumpStrengthMax = 0.5D; public double llamaMovementSpeedMin = 0.175D; public double llamaMovementSpeedMax = 0.175D; @@ -638,7 +638,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 private void llamaSettings() { llamaRidable = getBoolean("mobs.llama.ridable", llamaRidable); llamaRidableInWater = getBoolean("mobs.llama.ridable-in-water", llamaRidableInWater); -@@ -1001,6 +1022,7 @@ public class PurpurWorldConfig { +@@ -1000,6 +1021,7 @@ public class PurpurWorldConfig { llamaJumpStrengthMax = getDouble("mobs.llama.attributes.jump_strength.max", llamaJumpStrengthMax); llamaMovementSpeedMin = getDouble("mobs.llama.attributes.movement_speed.min", llamaMovementSpeedMin); llamaMovementSpeedMax = getDouble("mobs.llama.attributes.movement_speed.max", llamaMovementSpeedMax); @@ -646,7 +646,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 } public boolean magmaCubeRidable = false; -@@ -1020,6 +1042,7 @@ public class PurpurWorldConfig { +@@ -1019,6 +1041,7 @@ public class PurpurWorldConfig { public boolean mooshroomRidable = false; public boolean mooshroomRidableInWater = false; public double mooshroomMaxHealth = 10.0D; @@ -654,7 +654,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 private void mooshroomSettings() { mooshroomRidable = getBoolean("mobs.mooshroom.ridable", mooshroomRidable); mooshroomRidableInWater = getBoolean("mobs.mooshroom.ridable-in-water", mooshroomRidableInWater); -@@ -1029,6 +1052,7 @@ public class PurpurWorldConfig { +@@ -1028,6 +1051,7 @@ public class PurpurWorldConfig { set("mobs.mooshroom.attributes.max_health", oldValue); } mooshroomMaxHealth = getDouble("mobs.mooshroom.attributes.max_health", mooshroomMaxHealth); @@ -662,7 +662,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 } public boolean muleRidableInWater = false; -@@ -1038,6 +1062,7 @@ public class PurpurWorldConfig { +@@ -1037,6 +1061,7 @@ public class PurpurWorldConfig { public double muleJumpStrengthMax = 0.5D; public double muleMovementSpeedMin = 0.175D; public double muleMovementSpeedMax = 0.175D; @@ -670,7 +670,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 private void muleSettings() { muleRidableInWater = getBoolean("mobs.mule.ridable-in-water", muleRidableInWater); if (PurpurConfig.version < 10) { -@@ -1053,11 +1078,13 @@ public class PurpurWorldConfig { +@@ -1052,11 +1077,13 @@ public class PurpurWorldConfig { muleJumpStrengthMax = getDouble("mobs.mule.attributes.jump_strength.max", muleJumpStrengthMax); muleMovementSpeedMin = getDouble("mobs.mule.attributes.movement_speed.min", muleMovementSpeedMin); muleMovementSpeedMax = getDouble("mobs.mule.attributes.movement_speed.max", muleMovementSpeedMax); @@ -684,7 +684,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 private void ocelotSettings() { ocelotRidable = getBoolean("mobs.ocelot.ridable", ocelotRidable); ocelotRidableInWater = getBoolean("mobs.ocelot.ridable-in-water", ocelotRidableInWater); -@@ -1067,11 +1094,13 @@ public class PurpurWorldConfig { +@@ -1066,11 +1093,13 @@ public class PurpurWorldConfig { set("mobs.ocelot.attributes.max_health", oldValue); } ocelotMaxHealth = getDouble("mobs.ocelot.attributes.max_health", ocelotMaxHealth); @@ -698,7 +698,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 private void pandaSettings() { pandaRidable = getBoolean("mobs.panda.ridable", pandaRidable); pandaRidableInWater = getBoolean("mobs.panda.ridable-in-water", pandaRidableInWater); -@@ -1081,6 +1110,7 @@ public class PurpurWorldConfig { +@@ -1080,6 +1109,7 @@ public class PurpurWorldConfig { set("mobs.panda.attributes.max_health", oldValue); } pandaMaxHealth = getDouble("mobs.panda.attributes.max_health", pandaMaxHealth); @@ -706,7 +706,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 } public boolean parrotRidable = false; -@@ -1161,6 +1191,7 @@ public class PurpurWorldConfig { +@@ -1160,6 +1190,7 @@ public class PurpurWorldConfig { public boolean pigRidableInWater = false; public double pigMaxHealth = 10.0D; public boolean pigGiveSaddleBack = false; @@ -714,7 +714,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 private void pigSettings() { pigRidable = getBoolean("mobs.pig.ridable", pigRidable); pigRidableInWater = getBoolean("mobs.pig.ridable-in-water", pigRidableInWater); -@@ -1171,6 +1202,7 @@ public class PurpurWorldConfig { +@@ -1170,6 +1201,7 @@ public class PurpurWorldConfig { } pigMaxHealth = getDouble("mobs.pig.attributes.max_health", pigMaxHealth); pigGiveSaddleBack = getBoolean("mobs.pig.give-saddle-back", pigGiveSaddleBack); @@ -722,7 +722,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 } public boolean piglinRidable = false; -@@ -1220,6 +1252,7 @@ public class PurpurWorldConfig { +@@ -1219,6 +1251,7 @@ public class PurpurWorldConfig { public double polarBearMaxHealth = 30.0D; public String polarBearBreedableItemString = ""; public Item polarBearBreedableItem = null; @@ -730,7 +730,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 private void polarBearSettings() { polarBearRidable = getBoolean("mobs.polar_bear.ridable", polarBearRidable); polarBearRidableInWater = getBoolean("mobs.polar_bear.ridable-in-water", polarBearRidableInWater); -@@ -1232,6 +1265,7 @@ public class PurpurWorldConfig { +@@ -1231,6 +1264,7 @@ public class PurpurWorldConfig { polarBearBreedableItemString = getString("mobs.polar_bear.breedable-item", polarBearBreedableItemString); Item item = Registry.ITEM.get(new ResourceLocation(polarBearBreedableItemString)); if (item != Items.AIR) polarBearBreedableItem = item; @@ -738,7 +738,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 } public boolean pufferfishRidable = false; -@@ -1251,6 +1285,7 @@ public class PurpurWorldConfig { +@@ -1250,6 +1284,7 @@ public class PurpurWorldConfig { public double rabbitMaxHealth = 3.0D; public double rabbitNaturalToast = 0.0D; public double rabbitNaturalKiller = 0.0D; @@ -746,7 +746,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 private void rabbitSettings() { rabbitRidable = getBoolean("mobs.rabbit.ridable", rabbitRidable); rabbitRidableInWater = getBoolean("mobs.rabbit.ridable-in-water", rabbitRidableInWater); -@@ -1262,6 +1297,7 @@ public class PurpurWorldConfig { +@@ -1261,6 +1296,7 @@ public class PurpurWorldConfig { rabbitMaxHealth = getDouble("mobs.rabbit.attributes.max_health", rabbitMaxHealth); rabbitNaturalToast = getDouble("mobs.rabbit.spawn-toast-chance", rabbitNaturalToast); rabbitNaturalKiller = getDouble("mobs.rabbit.spawn-killer-rabbit-chance", rabbitNaturalKiller); @@ -754,7 +754,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 } public boolean ravagerRidable = false; -@@ -1293,6 +1329,7 @@ public class PurpurWorldConfig { +@@ -1292,6 +1328,7 @@ public class PurpurWorldConfig { public boolean sheepRidable = false; public boolean sheepRidableInWater = false; public double sheepMaxHealth = 8.0D; @@ -762,7 +762,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 private void sheepSettings() { sheepRidable = getBoolean("mobs.sheep.ridable", sheepRidable); sheepRidableInWater = getBoolean("mobs.sheep.ridable-in-water", sheepRidableInWater); -@@ -1302,6 +1339,7 @@ public class PurpurWorldConfig { +@@ -1301,6 +1338,7 @@ public class PurpurWorldConfig { set("mobs.sheep.attributes.max_health", oldValue); } sheepMaxHealth = getDouble("mobs.sheep.attributes.max_health", sheepMaxHealth); @@ -770,7 +770,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 } public boolean shulkerRidable = false; -@@ -1456,6 +1494,7 @@ public class PurpurWorldConfig { +@@ -1455,6 +1493,7 @@ public class PurpurWorldConfig { public boolean striderRidable = false; public boolean striderRidableInWater = false; public double striderMaxHealth = 20.0D; @@ -778,7 +778,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 private void striderSettings() { striderRidable = getBoolean("mobs.strider.ridable", striderRidable); striderRidableInWater = getBoolean("mobs.strider.ridable-in-water", striderRidableInWater); -@@ -1465,6 +1504,7 @@ public class PurpurWorldConfig { +@@ -1464,6 +1503,7 @@ public class PurpurWorldConfig { set("mobs.strider.attributes.max_health", oldValue); } striderMaxHealth = getDouble("mobs.strider.attributes.max_health", striderMaxHealth); @@ -786,7 +786,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 } public boolean traderLlamaRidable = false; -@@ -1475,6 +1515,7 @@ public class PurpurWorldConfig { +@@ -1474,6 +1514,7 @@ public class PurpurWorldConfig { public double traderLlamaJumpStrengthMax = 0.5D; public double traderLlamaMovementSpeedMin = 0.175D; public double traderLlamaMovementSpeedMax = 0.175D; @@ -794,7 +794,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 private void traderLlamaSettings() { traderLlamaRidable = getBoolean("mobs.trader_llama.ridable", traderLlamaRidable); traderLlamaRidableInWater = getBoolean("mobs.trader_llama.ridable-in-water", traderLlamaRidableInWater); -@@ -1491,6 +1532,7 @@ public class PurpurWorldConfig { +@@ -1490,6 +1531,7 @@ public class PurpurWorldConfig { traderLlamaJumpStrengthMax = getDouble("mobs.trader_llama.attributes.jump_strength.max", traderLlamaJumpStrengthMax); traderLlamaMovementSpeedMin = getDouble("mobs.trader_llama.attributes.movement_speed.min", traderLlamaMovementSpeedMin); traderLlamaMovementSpeedMax = getDouble("mobs.trader_llama.attributes.movement_speed.max", traderLlamaMovementSpeedMax); @@ -802,7 +802,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 } public boolean tropicalFishRidable = false; -@@ -1511,6 +1553,7 @@ public class PurpurWorldConfig { +@@ -1510,6 +1552,7 @@ public class PurpurWorldConfig { public boolean turtleEggsBreakFromExpOrbs = true; public boolean turtleEggsBreakFromItems = true; public boolean turtleEggsBreakFromMinecarts = true; @@ -810,7 +810,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 private void turtleEggSettings() { turtleRidable = getBoolean("mobs.turtle.ridable", turtleRidable); turtleRidableInWater = getBoolean("mobs.turtle.ridable-in-water", turtleRidableInWater); -@@ -1523,6 +1566,7 @@ public class PurpurWorldConfig { +@@ -1522,6 +1565,7 @@ public class PurpurWorldConfig { turtleEggsBreakFromExpOrbs = getBoolean("blocks.turtle_egg.break-from-exp-orbs", turtleEggsBreakFromExpOrbs); turtleEggsBreakFromItems = getBoolean("blocks.turtle_egg.break-from-items", turtleEggsBreakFromItems); turtleEggsBreakFromMinecarts = getBoolean("blocks.turtle_egg.break-from-minecarts", turtleEggsBreakFromMinecarts); @@ -818,7 +818,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 } public boolean vexRidable = false; -@@ -1551,6 +1595,7 @@ public class PurpurWorldConfig { +@@ -1550,6 +1594,7 @@ public class PurpurWorldConfig { public int villagerSpawnIronGolemRadius = 0; public int villagerSpawnIronGolemLimit = 0; public boolean villagerCanBreed = true; @@ -826,7 +826,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 private void villagerSettings() { villagerRidable = getBoolean("mobs.villager.ridable", villagerRidable); villagerRidableInWater = getBoolean("mobs.villager.ridable-in-water", villagerRidableInWater); -@@ -1567,6 +1612,7 @@ public class PurpurWorldConfig { +@@ -1566,6 +1611,7 @@ public class PurpurWorldConfig { villagerSpawnIronGolemRadius = getInt("mobs.villager.spawn-iron-golem.radius", villagerSpawnIronGolemRadius); villagerSpawnIronGolemLimit = getInt("mobs.villager.spawn-iron-golem.limit", villagerSpawnIronGolemLimit); villagerCanBreed = getBoolean("mobs.villager.can-breed", villagerCanBreed); @@ -834,7 +834,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 } public boolean vindicatorRidable = false; -@@ -1660,6 +1706,7 @@ public class PurpurWorldConfig { +@@ -1659,6 +1705,7 @@ public class PurpurWorldConfig { public boolean wolfRidable = false; public boolean wolfRidableInWater = false; public double wolfMaxHealth = 8.0D; @@ -842,7 +842,7 @@ index 965ab0b3d77fa8a46fa64a149aed4308d38ee1dd..4c03174509c6638ab3cf0f796b27a527 private void wolfSettings() { wolfRidable = getBoolean("mobs.wolf.ridable", wolfRidable); wolfRidableInWater = getBoolean("mobs.wolf.ridable-in-water", wolfRidableInWater); -@@ -1669,6 +1716,7 @@ public class PurpurWorldConfig { +@@ -1668,6 +1715,7 @@ public class PurpurWorldConfig { set("mobs.wolf.attributes.max_health", oldValue); } wolfMaxHealth = getDouble("mobs.wolf.attributes.max_health", wolfMaxHealth); diff --git a/patches/server/0121-Apply-display-names-from-item-forms-of-entities-to-e.patch b/patches/server/0121-Apply-display-names-from-item-forms-of-entities-to-e.patch index 1543ae4d1..bd31aed7f 100644 --- a/patches/server/0121-Apply-display-names-from-item-forms-of-entities-to-e.patch +++ b/patches/server/0121-Apply-display-names-from-item-forms-of-entities-to-e.patch @@ -142,10 +142,10 @@ index 282bfe4904637aaff1bd29e30ed18ba843c07cab..ddd50db8bb92c147d7c1eef4d1df3ac5 if (((HangingEntity) object).survives()) { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 4c03174509c6638ab3cf0f796b27a527d46d65b1..12d554d7b05560795ee2064efac31cea976bb538 100644 +index a1e135e7813c31dbb0508ebb0bee1a2c453e5b4e..396091bd1a72cac5cc07815f7ca1c68b5cec87c2 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -100,8 +100,10 @@ public class PurpurWorldConfig { +@@ -99,8 +99,10 @@ public class PurpurWorldConfig { } public float armorstandStepHeight = 0.0F; @@ -156,7 +156,7 @@ index 4c03174509c6638ab3cf0f796b27a527d46d65b1..12d554d7b05560795ee2064efac31cea } public int daytimeTicks = 12000; -@@ -345,6 +347,7 @@ public class PurpurWorldConfig { +@@ -344,6 +346,7 @@ public class PurpurWorldConfig { public boolean entitiesCanUsePortals = true; public boolean milkCuresBadOmen = true; public boolean persistentTileEntityDisplayNames = false; @@ -164,7 +164,7 @@ index 4c03174509c6638ab3cf0f796b27a527d46d65b1..12d554d7b05560795ee2064efac31cea public double tridentLoyaltyVoidReturnHeight = 0.0D; public double voidDamageHeight = -64.0D; public double voidDamageDealt = 4.0D; -@@ -357,6 +360,7 @@ public class PurpurWorldConfig { +@@ -356,6 +359,7 @@ public class PurpurWorldConfig { entitiesCanUsePortals = getBoolean("gameplay-mechanics.entities-can-use-portals", entitiesCanUsePortals); milkCuresBadOmen = getBoolean("gameplay-mechanics.milk-cures-bad-omen", milkCuresBadOmen); persistentTileEntityDisplayNames = getBoolean("gameplay-mechanics.persistent-tileentity-display-names-and-lore", persistentTileEntityDisplayNames); diff --git a/patches/server/0122-Set-name-visible-when-using-a-Name-Tag-on-an-Armor-S.patch b/patches/server/0122-Set-name-visible-when-using-a-Name-Tag-on-an-Armor-S.patch index 6b433ae61..076a80d46 100644 --- a/patches/server/0122-Set-name-visible-when-using-a-Name-Tag-on-an-Armor-S.patch +++ b/patches/server/0122-Set-name-visible-when-using-a-Name-Tag-on-an-Armor-S.patch @@ -17,10 +17,10 @@ index 623f78c078fb3aa2665d7e8a37672438227bce6b..500c69e555c7247e20ef8cc59d834155 ((Mob) newEntityLiving).setPersistenceRequired(); // Paper end diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 12d554d7b05560795ee2064efac31cea976bb538..3df2529c8b606d44679c2cc32c34748db28762e3 100644 +index 396091bd1a72cac5cc07815f7ca1c68b5cec87c2..53d0ec1e89b05d798ebfc620ada0d95bcf61cb21 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -101,9 +101,11 @@ public class PurpurWorldConfig { +@@ -100,9 +100,11 @@ public class PurpurWorldConfig { public float armorstandStepHeight = 0.0F; public boolean armorstandSetNameVisible = false; diff --git a/patches/server/0123-Add-twisting-and-weeping-vines-growth-rates.patch b/patches/server/0123-Add-twisting-and-weeping-vines-growth-rates.patch index 03d6da007..69484df77 100644 --- a/patches/server/0123-Add-twisting-and-weeping-vines-growth-rates.patch +++ b/patches/server/0123-Add-twisting-and-weeping-vines-growth-rates.patch @@ -82,10 +82,10 @@ index 35b2bad76c45b5a94ba7f2e9c7a8cfeb8c3f498b..d2cb1a7e7ea364cb8e2af4c4e756d8e4 + // Purpur end } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 3df2529c8b606d44679c2cc32c34748db28762e3..72653a7aebd68bc754488b5ebb9c5ebc9b77e973 100644 +index 53d0ec1e89b05d798ebfc620ada0d95bcf61cb21..646fceecfe7c626d0a2cec671c1f12be743c7805 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -413,6 +413,11 @@ public class PurpurWorldConfig { +@@ -412,6 +412,11 @@ public class PurpurWorldConfig { } } @@ -97,7 +97,7 @@ index 3df2529c8b606d44679c2cc32c34748db28762e3..72653a7aebd68bc754488b5ebb9c5ebc public boolean dispenserApplyCursedArmor = true; public boolean dispenserPlaceAnvils = false; private void dispenserSettings() { -@@ -482,6 +487,16 @@ public class PurpurWorldConfig { +@@ -481,6 +486,16 @@ public class PurpurWorldConfig { stonecutterDamage = (float) getDouble("blocks.stonecutter.damage", stonecutterDamage); } diff --git a/patches/server/0124-Kelp-weeping-and-twisting-vines-configurable-max-gro.patch b/patches/server/0124-Kelp-weeping-and-twisting-vines-configurable-max-gro.patch index b8bef4909..cd84f9d14 100644 --- a/patches/server/0124-Kelp-weeping-and-twisting-vines-configurable-max-gro.patch +++ b/patches/server/0124-Kelp-weeping-and-twisting-vines-configurable-max-gro.patch @@ -103,10 +103,10 @@ index d2cb1a7e7ea364cb8e2af4c4e756d8e45bc0ca10..bb99dda3c5167f23b2500a1f37cbc1ca // Purpur end } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 72653a7aebd68bc754488b5ebb9c5ebc9b77e973..d13e90639894a9707cbb47d3f681b2f800186405 100644 +index 646fceecfe7c626d0a2cec671c1f12be743c7805..3895f0f0f096adbefd806f60a86cd2ea40c288b7 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -414,8 +414,10 @@ public class PurpurWorldConfig { +@@ -413,8 +413,10 @@ public class PurpurWorldConfig { } public double caveVinesGrowthModifier = 0.10D; @@ -117,7 +117,7 @@ index 72653a7aebd68bc754488b5ebb9c5ebc9b77e973..d13e90639894a9707cbb47d3f681b2f8 } public boolean dispenserApplyCursedArmor = true; -@@ -454,6 +456,11 @@ public class PurpurWorldConfig { +@@ -453,6 +455,11 @@ public class PurpurWorldConfig { lavaSpeedNotNether = getInt("blocks.lava.speed.not-nether", lavaSpeedNotNether); } @@ -129,7 +129,7 @@ index 72653a7aebd68bc754488b5ebb9c5ebc9b77e973..d13e90639894a9707cbb47d3f681b2f8 public boolean respawnAnchorExplode = true; public double respawnAnchorExplosionPower = 5.0D; public boolean respawnAnchorExplosionFire = true; -@@ -488,13 +495,17 @@ public class PurpurWorldConfig { +@@ -487,13 +494,17 @@ public class PurpurWorldConfig { } public double twistingVinesGrowthModifier = 0.10D; diff --git a/patches/server/0125-Add-config-for-allowing-Endermen-to-despawn-even-whi.patch b/patches/server/0125-Add-config-for-allowing-Endermen-to-despawn-even-whi.patch index 20b91ba49..7c8447aa1 100644 --- a/patches/server/0125-Add-config-for-allowing-Endermen-to-despawn-even-whi.patch +++ b/patches/server/0125-Add-config-for-allowing-Endermen-to-despawn-even-whi.patch @@ -21,10 +21,10 @@ index 5ebedd6a156b06e98aded57c817f63429a1ae380..c99d295b999a28dd1eb504179250445d private static class EndermanFreezeWhenLookedAt extends Goal { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index d13e90639894a9707cbb47d3f681b2f800186405..69f4a0978e84be0d8c2abe4ed11385616797f3fd 100644 +index 3895f0f0f096adbefd806f60a86cd2ea40c288b7..38cb6bb3f0f45a257614ce26c40ca6b259af626d 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -791,6 +791,7 @@ public class PurpurWorldConfig { +@@ -790,6 +790,7 @@ public class PurpurWorldConfig { public boolean endermanRidableInWater = false; public double endermanMaxHealth = 40.0D; public boolean endermanAllowGriefing = true; @@ -32,7 +32,7 @@ index d13e90639894a9707cbb47d3f681b2f800186405..69f4a0978e84be0d8c2abe4ed1138561 private void endermanSettings() { endermanRidable = getBoolean("mobs.enderman.ridable", endermanRidable); endermanRidableInWater = getBoolean("mobs.enderman.ridable-in-water", endermanRidableInWater); -@@ -801,6 +802,7 @@ public class PurpurWorldConfig { +@@ -800,6 +801,7 @@ public class PurpurWorldConfig { } endermanMaxHealth = getDouble("mobs.enderman.attributes.max_health", endermanMaxHealth); endermanAllowGriefing = getBoolean("mobs.enderman.allow-griefing", endermanAllowGriefing); diff --git a/patches/server/0126-Add-critical-hit-check-to-EntityDamagedByEntityEvent.patch b/patches/server/0126-Add-critical-hit-check-to-EntityDamagedByEntityEvent.patch index 0a21511c0..0eb8dfb5f 100644 --- a/patches/server/0126-Add-critical-hit-check-to-EntityDamagedByEntityEvent.patch +++ b/patches/server/0126-Add-critical-hit-check-to-EntityDamagedByEntityEvent.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Add critical hit check to EntityDamagedByEntityEvent diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 831a62e56c745884f34fb5d8f84f037543d77d1d..d4e6c16b08c5aaf9c50467941498b566f11cc5d2 100644 +index fbdf7d211fa7b3acaddc035ec6e04ab599c32c78..fab14cd3148cb81a343b7b1fb4f94da977dba763 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java +++ b/src/main/java/net/minecraft/world/entity/player/Player.java @@ -182,6 +182,7 @@ public abstract class Player extends LivingEntity { @@ -16,7 +16,7 @@ index 831a62e56c745884f34fb5d8f84f037543d77d1d..d4e6c16b08c5aaf9c50467941498b566 // CraftBukkit start public boolean fauxSleeping; -@@ -1245,6 +1246,7 @@ public abstract class Player extends LivingEntity { +@@ -1236,6 +1237,7 @@ public abstract class Player extends LivingEntity { flag2 = flag2 && !level.paperConfig.disablePlayerCrits; // Paper flag2 = flag2 && !this.isSprinting(); if (flag2) { @@ -24,7 +24,7 @@ index 831a62e56c745884f34fb5d8f84f037543d77d1d..d4e6c16b08c5aaf9c50467941498b566 f *= 1.5F; } -@@ -1281,6 +1283,7 @@ public abstract class Player extends LivingEntity { +@@ -1272,6 +1274,7 @@ public abstract class Player extends LivingEntity { Vec3 vec3d = target.getDeltaMovement(); boolean flag5 = target.hurt(DamageSource.playerAttack(this), f); @@ -33,7 +33,7 @@ index 831a62e56c745884f34fb5d8f84f037543d77d1d..d4e6c16b08c5aaf9c50467941498b566 if (flag5) { if (i > 0) { diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java -index d055b5548848c87d9ce8372b6c64df8d081eb779..f8ae2647237027e409e7e111cddce1883dec7bfd 100644 +index f9196241a0d6f9a70c674f7dfca5e013f9839398..f60ef0605163ecf50d9f61a73a84146d214aaf19 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -952,7 +952,7 @@ public class CraftEventFactory { diff --git a/patches/server/0127-Add-configurable-snowball-damage.patch b/patches/server/0127-Add-configurable-snowball-damage.patch index 8c01c887c..08b4a9a1b 100644 --- a/patches/server/0127-Add-configurable-snowball-damage.patch +++ b/patches/server/0127-Add-configurable-snowball-damage.patch @@ -18,10 +18,10 @@ index ed2f039c4042861bcfa2e41d8281eefd37daa9fa..d5d84893c77b4e60a19032d765d76bfd } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 69f4a0978e84be0d8c2abe4ed11385616797f3fd..ee88a19c7c2314aa284732a8e824bbf2edeb10a8 100644 +index 38cb6bb3f0f45a257614ce26c40ca6b259af626d..0e30b578748c2c6f2231ec1a526cbfef9edabf9f 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -289,6 +289,11 @@ public class PurpurWorldConfig { +@@ -288,6 +288,11 @@ public class PurpurWorldConfig { totemOfUndyingWorksInInventory = getBoolean("gameplay-mechanics.player.totem-of-undying-works-in-inventory", totemOfUndyingWorksInInventory); } diff --git a/patches/server/0128-Zombie-break-door-minimum-difficulty-option.patch b/patches/server/0128-Zombie-break-door-minimum-difficulty-option.patch index a88102090..3aa2ad44a 100644 --- a/patches/server/0128-Zombie-break-door-minimum-difficulty-option.patch +++ b/patches/server/0128-Zombie-break-door-minimum-difficulty-option.patch @@ -44,10 +44,10 @@ index fe045f8e35fe2aac51032a67ce52b27a53a8eff0..03bc86c776596ca5964c22adb757115d + // Purpur end } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index ee88a19c7c2314aa284732a8e824bbf2edeb10a8..d84f60a3d1148d9755246628744b31ceb0c86b4d 100644 +index 0e30b578748c2c6f2231ec1a526cbfef9edabf9f..1000177563e5681a8caa745f1cd40af37723e8bc 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1780,6 +1780,7 @@ public class PurpurWorldConfig { +@@ -1779,6 +1779,7 @@ public class PurpurWorldConfig { public double zombieJockeyChance = 0.05D; public boolean zombieJockeyTryExistingChickens = true; public boolean zombieAggressiveTowardsVillagerWhenLagging = true; @@ -55,7 +55,7 @@ index ee88a19c7c2314aa284732a8e824bbf2edeb10a8..d84f60a3d1148d9755246628744b31ce private void zombieSettings() { zombieRidable = getBoolean("mobs.zombie.ridable", zombieRidable); zombieRidableInWater = getBoolean("mobs.zombie.ridable-in-water", zombieRidableInWater); -@@ -1794,6 +1795,11 @@ public class PurpurWorldConfig { +@@ -1793,6 +1794,11 @@ public class PurpurWorldConfig { zombieJockeyChance = getDouble("mobs.zombie.jockey.chance", zombieJockeyChance); zombieJockeyTryExistingChickens = getBoolean("mobs.zombie.jockey.try-existing-chickens", zombieJockeyTryExistingChickens); zombieAggressiveTowardsVillagerWhenLagging = getBoolean("mobs.zombie.aggressive-towards-villager-when-lagging", zombieAggressiveTowardsVillagerWhenLagging); diff --git a/patches/server/0130-Changeable-Mob-Left-Handed-Chance.patch b/patches/server/0130-Changeable-Mob-Left-Handed-Chance.patch index 2dedab6cf..014c8bf2f 100644 --- a/patches/server/0130-Changeable-Mob-Left-Handed-Chance.patch +++ b/patches/server/0130-Changeable-Mob-Left-Handed-Chance.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Changeable Mob Left Handed Chance diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 9b8b82bf5bb276be51b8ba5c023879b3a45212cb..13b256c7d9723e38d1a1f5766fb2a28bc26f8943 100644 +index ff40d97479cd3aa8c7a164cdb1dbef92d4ab77ee..20af97629b614da792bdee2f8e450433570102f2 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -1203,7 +1203,7 @@ public abstract class Mob extends LivingEntity { +@@ -1199,7 +1199,7 @@ public abstract class Mob extends LivingEntity { @Nullable public SpawnGroupData finalizeSpawn(ServerLevelAccessor world, DifficultyInstance difficulty, MobSpawnType spawnReason, @Nullable SpawnGroupData entityData, @Nullable CompoundTag entityNbt) { this.getAttribute(Attributes.FOLLOW_RANGE).addPermanentModifier(new AttributeModifier("Random spawn bonus", this.random.nextGaussian() * 0.05D, AttributeModifier.Operation.MULTIPLY_BASE)); @@ -18,10 +18,10 @@ index 9b8b82bf5bb276be51b8ba5c023879b3a45212cb..13b256c7d9723e38d1a1f5766fb2a28b } else { this.setLeftHanded(false); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index d84f60a3d1148d9755246628744b31ceb0c86b4d..3ba0e248e3da3d80c4b696929c131e3bbb2ebe6a 100644 +index 1000177563e5681a8caa745f1cd40af37723e8bc..27f9e9e65b7773190af12513085136709cf18d44 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -129,8 +129,10 @@ public class PurpurWorldConfig { +@@ -128,8 +128,10 @@ public class PurpurWorldConfig { } public int entityLifeSpan = 0; diff --git a/patches/server/0131-Add-boat-fall-damage-config.patch b/patches/server/0131-Add-boat-fall-damage-config.patch index c48b62eff..ccaee732e 100644 --- a/patches/server/0131-Add-boat-fall-damage-config.patch +++ b/patches/server/0131-Add-boat-fall-damage-config.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add boat fall damage config diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 2cfd9895b9a371e731b416ec8745aae839e0054b..93b8260c59c659ddd2e80660be23b9a825339a72 100644 +index 4b74790581b0f629507e5d00c0882bad0f0e168e..85d2fda86638075130def6a47912682637186d84 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -991,7 +991,16 @@ public class ServerPlayer extends Player { +@@ -987,7 +987,16 @@ public class ServerPlayer extends Player { if (this.isInvulnerableTo(source)) { return false; } else { @@ -27,10 +27,10 @@ index 2cfd9895b9a371e731b416ec8745aae839e0054b..93b8260c59c659ddd2e80660be23b9a8 if (!flag && isSpawnInvulnerable() && source != DamageSource.OUT_OF_WORLD) { // Purpur diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 3ba0e248e3da3d80c4b696929c131e3bbb2ebe6a..97d282f69bb48d4300bae4f2d03c8724c7383b2a 100644 +index 27f9e9e65b7773190af12513085136709cf18d44..89bb1405ca28456bc0bd0ad6b3828d39b33c0264 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -352,6 +352,7 @@ public class PurpurWorldConfig { +@@ -351,6 +351,7 @@ public class PurpurWorldConfig { public boolean useBetterMending = false; public boolean boatEjectPlayersOnLand = false; @@ -38,7 +38,7 @@ index 3ba0e248e3da3d80c4b696929c131e3bbb2ebe6a..97d282f69bb48d4300bae4f2d03c8724 public boolean disableDropsOnCrammingDeath = false; public boolean entitiesCanUsePortals = true; public boolean milkCuresBadOmen = true; -@@ -365,6 +366,7 @@ public class PurpurWorldConfig { +@@ -364,6 +365,7 @@ public class PurpurWorldConfig { private void miscGameplayMechanicsSettings() { useBetterMending = getBoolean("gameplay-mechanics.use-better-mending", useBetterMending); boatEjectPlayersOnLand = getBoolean("gameplay-mechanics.boat.eject-players-on-land", boatEjectPlayersOnLand); diff --git a/patches/server/0132-Snow-Golem-rate-of-fire-config.patch b/patches/server/0132-Snow-Golem-rate-of-fire-config.patch index acaaedb5a..92038737c 100644 --- a/patches/server/0132-Snow-Golem-rate-of-fire-config.patch +++ b/patches/server/0132-Snow-Golem-rate-of-fire-config.patch @@ -23,10 +23,10 @@ index 0733f9c057fef17fd79a4769f19b78f4c83a7784..1697b573ffd0c5d17d2d538c40f5ce4b this.goalSelector.addGoal(3, new LookAtPlayerGoal(this, Player.class, 6.0F)); this.goalSelector.addGoal(4, new RandomLookAroundGoal(this)); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 97d282f69bb48d4300bae4f2d03c8724c7383b2a..b6d5f5c96b02c957d058e2b7c8616fb211f832d9 100644 +index 89bb1405ca28456bc0bd0ad6b3828d39b33c0264..09e9d82db853b865192d0c420b80b4dc784e6f46 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1474,6 +1474,10 @@ public class PurpurWorldConfig { +@@ -1473,6 +1473,10 @@ public class PurpurWorldConfig { public double snowGolemMaxHealth = 4.0D; public boolean snowGolemDropsPumpkin = true; public boolean snowGolemPutPumpkinBack = false; @@ -37,7 +37,7 @@ index 97d282f69bb48d4300bae4f2d03c8724c7383b2a..b6d5f5c96b02c957d058e2b7c8616fb2 private void snowGolemSettings() { snowGolemRidable = getBoolean("mobs.snow_golem.ridable", snowGolemRidable); snowGolemRidableInWater = getBoolean("mobs.snow_golem.ridable-in-water", snowGolemRidableInWater); -@@ -1486,6 +1490,10 @@ public class PurpurWorldConfig { +@@ -1485,6 +1489,10 @@ public class PurpurWorldConfig { snowGolemMaxHealth = getDouble("mobs.snow_golem.attributes.max_health", snowGolemMaxHealth); snowGolemDropsPumpkin = getBoolean("mobs.snow_golem.drop-pumpkin-when-sheared", snowGolemDropsPumpkin); snowGolemPutPumpkinBack = getBoolean("mobs.snow_golem.pumpkin-can-be-added-back", snowGolemPutPumpkinBack); diff --git a/patches/server/0135-Lobotomize-stuck-villagers.patch b/patches/server/0135-Lobotomize-stuck-villagers.patch index 563eabda8..6d3440694 100644 --- a/patches/server/0135-Lobotomize-stuck-villagers.patch +++ b/patches/server/0135-Lobotomize-stuck-villagers.patch @@ -53,10 +53,10 @@ index bb3572370a86519a92b7b3dab0482cd1527de19d..9a460eeb48c14590d28d071cfa5a9251 if (this.assignProfessionWhenSpawned) { this.assignProfessionWhenSpawned = false; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index b6d5f5c96b02c957d058e2b7c8616fb211f832d9..4c06f35032f02ac65fd4d13b9fb58f62cf1894a3 100644 +index 09e9d82db853b865192d0c420b80b4dc784e6f46..58924239d28b1c0e5ae7d2ed719f617cb1eee184 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1647,6 +1647,8 @@ public class PurpurWorldConfig { +@@ -1646,6 +1646,8 @@ public class PurpurWorldConfig { public int villagerSpawnIronGolemLimit = 0; public boolean villagerCanBreed = true; public int villagerBreedingTicks = 6000; @@ -65,7 +65,7 @@ index b6d5f5c96b02c957d058e2b7c8616fb211f832d9..4c06f35032f02ac65fd4d13b9fb58f62 private void villagerSettings() { villagerRidable = getBoolean("mobs.villager.ridable", villagerRidable); villagerRidableInWater = getBoolean("mobs.villager.ridable-in-water", villagerRidableInWater); -@@ -1664,6 +1666,13 @@ public class PurpurWorldConfig { +@@ -1663,6 +1665,13 @@ public class PurpurWorldConfig { villagerSpawnIronGolemLimit = getInt("mobs.villager.spawn-iron-golem.limit", villagerSpawnIronGolemLimit); villagerCanBreed = getBoolean("mobs.villager.can-breed", villagerCanBreed); villagerBreedingTicks = getInt("mobs.villager.breeding-delay-ticks", villagerBreedingTicks); diff --git a/patches/server/0136-Option-for-Villager-Clerics-to-farm-Nether-Wart.patch b/patches/server/0136-Option-for-Villager-Clerics-to-farm-Nether-Wart.patch index dd29e9ee8..2a437b12d 100644 --- a/patches/server/0136-Option-for-Villager-Clerics-to-farm-Nether-Wart.patch +++ b/patches/server/0136-Option-for-Villager-Clerics-to-farm-Nether-Wart.patch @@ -185,10 +185,10 @@ index 901fc6520d58a5fa5f2cf1b4fa78fec6008aa409..9050cd25663c71197c597aac0ab2e612 public static final VillagerProfession FISHERMAN = register("fisherman", PoiType.FISHERMAN, SoundEvents.VILLAGER_WORK_FISHERMAN); public static final VillagerProfession FLETCHER = register("fletcher", PoiType.FLETCHER, SoundEvents.VILLAGER_WORK_FLETCHER); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 4c06f35032f02ac65fd4d13b9fb58f62cf1894a3..009150862f6b9b86efc315031381b1c2ce7ad4dd 100644 +index 58924239d28b1c0e5ae7d2ed719f617cb1eee184..92d76fa843c846906d8f54114d7d24df3e4050d9 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1649,6 +1649,8 @@ public class PurpurWorldConfig { +@@ -1648,6 +1648,8 @@ public class PurpurWorldConfig { public int villagerBreedingTicks = 6000; public boolean villagerLobotomizeEnabled = false; public int villagerLobotomizeCheck = 60; @@ -197,7 +197,7 @@ index 4c06f35032f02ac65fd4d13b9fb58f62cf1894a3..009150862f6b9b86efc315031381b1c2 private void villagerSettings() { villagerRidable = getBoolean("mobs.villager.ridable", villagerRidable); villagerRidableInWater = getBoolean("mobs.villager.ridable-in-water", villagerRidableInWater); -@@ -1673,6 +1675,8 @@ public class PurpurWorldConfig { +@@ -1672,6 +1674,8 @@ public class PurpurWorldConfig { } villagerLobotomizeEnabled = getBoolean("mobs.villager.lobotomize.enabled", villagerLobotomizeEnabled); villagerLobotomizeCheck = getInt("mobs.villager.lobotomize.check-interval", villagerLobotomizeCheck); diff --git a/patches/server/0137-Toggle-for-Zombified-Piglin-death-always-counting-as.patch b/patches/server/0137-Toggle-for-Zombified-Piglin-death-always-counting-as.patch index 03e9ae99b..8ba788e8f 100644 --- a/patches/server/0137-Toggle-for-Zombified-Piglin-death-always-counting-as.patch +++ b/patches/server/0137-Toggle-for-Zombified-Piglin-death-always-counting-as.patch @@ -13,7 +13,7 @@ to the Piglin being angry, even though the player never hit them. This patch adds a toggle to disable this behavior. diff --git a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java -index 43ef93d2c0c59e0d7021ee9aa2b44345192cc0a9..ce780a2ba7dcbd9c1c9dc24c0738786190545382 100644 +index 5b5958e37918b97fa994500fe94cd0e57faa1948..64216c9d76ce5ec6a845915ca078b0c0fb0199d4 100644 --- a/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java +++ b/src/main/java/net/minecraft/world/entity/monster/ZombifiedPiglin.java @@ -137,7 +137,7 @@ public class ZombifiedPiglin extends Zombie implements NeutralMob { @@ -35,10 +35,10 @@ index 43ef93d2c0c59e0d7021ee9aa2b44345192cc0a9..ce780a2ba7dcbd9c1c9dc24c07387861 } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 009150862f6b9b86efc315031381b1c2ce7ad4dd..d65ac43df8a03acd57fdbb46691fe4c62fcfd1e0 100644 +index 92d76fa843c846906d8f54114d7d24df3e4050d9..cc12e27ebf60f92ce46c86055ee8559d0027ab3b 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1883,6 +1883,7 @@ public class PurpurWorldConfig { +@@ -1882,6 +1882,7 @@ public class PurpurWorldConfig { public boolean zombifiedPiglinJockeyOnlyBaby = true; public double zombifiedPiglinJockeyChance = 0.05D; public boolean zombifiedPiglinJockeyTryExistingChickens = true; @@ -46,7 +46,7 @@ index 009150862f6b9b86efc315031381b1c2ce7ad4dd..d65ac43df8a03acd57fdbb46691fe4c6 private void zombifiedPiglinSettings() { zombifiedPiglinRidable = getBoolean("mobs.zombified_piglin.ridable", zombifiedPiglinRidable); zombifiedPiglinRidableInWater = getBoolean("mobs.zombified_piglin.ridable-in-water", zombifiedPiglinRidableInWater); -@@ -1896,5 +1897,6 @@ public class PurpurWorldConfig { +@@ -1895,5 +1896,6 @@ public class PurpurWorldConfig { zombifiedPiglinJockeyOnlyBaby = getBoolean("mobs.zombified_piglin.jockey.only-babies", zombifiedPiglinJockeyOnlyBaby); zombifiedPiglinJockeyChance = getDouble("mobs.zombified_piglin.jockey.chance", zombifiedPiglinJockeyChance); zombifiedPiglinJockeyTryExistingChickens = getBoolean("mobs.zombified_piglin.jockey.try-existing-chickens", zombifiedPiglinJockeyTryExistingChickens); diff --git a/patches/server/0138-Spread-out-and-optimise-player-list-ticksSpread-out-.patch b/patches/server/0138-Spread-out-and-optimise-player-list-ticksSpread-out-.patch index 6af61631f..93dd38e21 100644 --- a/patches/server/0138-Spread-out-and-optimise-player-list-ticksSpread-out-.patch +++ b/patches/server/0138-Spread-out-and-optimise-player-list-ticksSpread-out-.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Spread out and optimise player list ticksSpread out and diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index c92b57e7e5d1b31db8b7e75a9013df41eb2806c4..7d7e5b7508470c0570e7f0becd607a2c35d17d6b 100644 +index 826fe9c183521e57466ba73d3e819c5368ff46c6..1dae795321bc35ceaedd6f9218b61e6231066e7b 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -1005,22 +1005,22 @@ public abstract class PlayerList { +@@ -1006,22 +1006,22 @@ public abstract class PlayerList { } public void tick() { @@ -46,10 +46,10 @@ index c92b57e7e5d1b31db8b7e75a9013df41eb2806c4..7d7e5b7508470c0570e7f0becd607a2c public void broadcastAll(Packet packet) { diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 2e42abb396b84b9f6d42809086f5a513dbc9ef40..1ff393d62ae98de65b1cd963705c4e3897a34b5a 100644 +index 3306decd607ae190d6eaf674b3a59393c93dae34..98a3b692d17a7aa8166bf53a1d707c43590ed7f7 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1563,7 +1563,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -1508,7 +1508,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player { @Override public boolean canSee(Player player) { diff --git a/patches/server/0139-Configurable-chance-for-wolves-to-spawn-rabid.patch b/patches/server/0139-Configurable-chance-for-wolves-to-spawn-rabid.patch index e00e3d9b2..1929f894f 100644 --- a/patches/server/0139-Configurable-chance-for-wolves-to-spawn-rabid.patch +++ b/patches/server/0139-Configurable-chance-for-wolves-to-spawn-rabid.patch @@ -201,10 +201,10 @@ index ef4abaf68de01b0879f7d0b330d2d57cc6bd10f9..3e7409ebf1f94b9cf55f2d0b0fe17ca8 return super.mobInteract(player, hand); } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index d65ac43df8a03acd57fdbb46691fe4c62fcfd1e0..b24ce184ee49f473c65c514662491a0960edd442 100644 +index cc12e27ebf60f92ce46c86055ee8559d0027ab3b..5c189d221dddd9e550770418361e7a5965d7ddf7 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1770,6 +1770,8 @@ public class PurpurWorldConfig { +@@ -1769,6 +1769,8 @@ public class PurpurWorldConfig { public boolean wolfRidable = false; public boolean wolfRidableInWater = false; public double wolfMaxHealth = 8.0D; @@ -213,7 +213,7 @@ index d65ac43df8a03acd57fdbb46691fe4c62fcfd1e0..b24ce184ee49f473c65c514662491a09 public int wolfBreedingTicks = 6000; private void wolfSettings() { wolfRidable = getBoolean("mobs.wolf.ridable", wolfRidable); -@@ -1780,6 +1782,8 @@ public class PurpurWorldConfig { +@@ -1779,6 +1781,8 @@ public class PurpurWorldConfig { set("mobs.wolf.attributes.max_health", oldValue); } wolfMaxHealth = getDouble("mobs.wolf.attributes.max_health", wolfMaxHealth); diff --git a/patches/server/0140-Configurable-default-wolf-collar-color.patch b/patches/server/0140-Configurable-default-wolf-collar-color.patch index de3fa19a5..25d7bc513 100644 --- a/patches/server/0140-Configurable-default-wolf-collar-color.patch +++ b/patches/server/0140-Configurable-default-wolf-collar-color.patch @@ -24,10 +24,10 @@ index 3e7409ebf1f94b9cf55f2d0b0fe17ca8ec44659f..518dd0e6b4889c049e438b393baa795a @Override diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index b24ce184ee49f473c65c514662491a0960edd442..b05073fdcaff86df2418f240c0aaf0ac1c120a10 100644 +index 5c189d221dddd9e550770418361e7a5965d7ddf7..ca7596a6531dbc9ac0e1769764518e7e9d7eeeec 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1770,6 +1770,7 @@ public class PurpurWorldConfig { +@@ -1769,6 +1769,7 @@ public class PurpurWorldConfig { public boolean wolfRidable = false; public boolean wolfRidableInWater = false; public double wolfMaxHealth = 8.0D; @@ -35,7 +35,7 @@ index b24ce184ee49f473c65c514662491a0960edd442..b05073fdcaff86df2418f240c0aaf0ac public boolean wolfMilkCuresRabies = true; public double wolfNaturalRabid = 0.0D; public int wolfBreedingTicks = 6000; -@@ -1782,6 +1783,11 @@ public class PurpurWorldConfig { +@@ -1781,6 +1782,11 @@ public class PurpurWorldConfig { set("mobs.wolf.attributes.max_health", oldValue); } wolfMaxHealth = getDouble("mobs.wolf.attributes.max_health", wolfMaxHealth); diff --git a/patches/server/0141-Phantom-flames-on-swoop.patch b/patches/server/0141-Phantom-flames-on-swoop.patch index 00cb70553..07556a5b2 100644 --- a/patches/server/0141-Phantom-flames-on-swoop.patch +++ b/patches/server/0141-Phantom-flames-on-swoop.patch @@ -17,10 +17,10 @@ index c55aba456aa144e58fc35877c61eff309eaa391f..c39e2d05fa81279a684532ee796880b1 @Override diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index b05073fdcaff86df2418f240c0aaf0ac1c120a10..0d4406b661eed8e9f86f9ae85c81614f102d9636 100644 +index ca7596a6531dbc9ac0e1769764518e7e9d7eeeec..e44328d3ac04512e79f1b1363e72bf5e82426782 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1178,6 +1178,7 @@ public class PurpurWorldConfig { +@@ -1177,6 +1177,7 @@ public class PurpurWorldConfig { public float phantomFlameDamage = 1.0F; public int phantomFlameFireTime = 8; public boolean phantomAllowGriefing = false; @@ -28,7 +28,7 @@ index b05073fdcaff86df2418f240c0aaf0ac1c120a10..0d4406b661eed8e9f86f9ae85c81614f public double phantomMaxHealth = 20.0D; public double phantomAttackedByCrystalRadius = 0.0D; public float phantomAttackedByCrystalDamage = 1.0F; -@@ -1204,6 +1205,7 @@ public class PurpurWorldConfig { +@@ -1203,6 +1204,7 @@ public class PurpurWorldConfig { phantomFlameDamage = (float) getDouble("mobs.phantom.flames.damage", phantomFlameDamage); phantomFlameFireTime = getInt("mobs.phantom.flames.fire-time", phantomFlameFireTime); phantomAllowGriefing = getBoolean("mobs.phantom.allow-griefing", phantomAllowGriefing); diff --git a/patches/server/0142-Option-for-chests-to-open-even-with-a-solid-block-on.patch b/patches/server/0142-Option-for-chests-to-open-even-with-a-solid-block-on.patch index fd86702a6..ddf27856b 100644 --- a/patches/server/0142-Option-for-chests-to-open-even-with-a-solid-block-on.patch +++ b/patches/server/0142-Option-for-chests-to-open-even-with-a-solid-block-on.patch @@ -17,10 +17,10 @@ index d980a556785b52fe827310b83638139df0816b11..3c8c02fc92374def12254f7ffad604b2 return world.getBlockState(blockposition1).isRedstoneConductor(world, blockposition1); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 0d4406b661eed8e9f86f9ae85c81614f102d9636..d98de312e685e0db82f5591c6aac63890213d5ff 100644 +index e44328d3ac04512e79f1b1363e72bf5e82426782..aec89025473b9de9c5460cd4dc0bceeb24dcf345 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -429,6 +429,11 @@ public class PurpurWorldConfig { +@@ -428,6 +428,11 @@ public class PurpurWorldConfig { caveVinesMaxGrowthAge = getInt("blocks.cave_vines.max-growth-age", caveVinesMaxGrowthAge); } diff --git a/patches/server/0143-Implement-TPSBar.patch b/patches/server/0143-Implement-TPSBar.patch index fb8bd7849..fa84b2a0d 100644 --- a/patches/server/0143-Implement-TPSBar.patch +++ b/patches/server/0143-Implement-TPSBar.patch @@ -17,10 +17,10 @@ index f164106a957c24bdb71bde85d82948a64613fd91..bef1f6ca54fc6c1741b54b8e071a90eb if (environment.includeIntegrated) { diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 9017d3626f4982a41d34d65758de4844e5502a9c..108dadb24607ea42cf857879386c34c9bb72dbee 100644 +index afe22502613d131bbca9af28b1716768e9a46f9c..4410ead33e18790f8674765affeba736a89c8ddd 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -1111,6 +1111,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 100) { - ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send a book with too many pages"); -+ net.pl3x.purpur.event.player.PlayerBookTooLargeEvent event = new net.pl3x.purpur.event.player.PlayerBookTooLargeEvent(player.getBukkitEntity(), testStack.asBukkitCopy()); if (event.shouldKickPlayer()) // Purpur - server.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause - return; - } -@@ -1134,6 +1135,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser +@@ -1083,10 +1083,12 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser + int maxBookPageSize = com.destroystokyo.paper.PaperConfig.maxBookPageSize; + double multiplier = Math.max(0.3D, Math.min(1D, com.destroystokyo.paper.PaperConfig.maxBookTotalSizeMultiplier)); + long byteAllowed = maxBookPageSize; ++ ItemStack itemstack = this.player.getInventory().getItem(packet.getSlot()); // Purpur + for (String testString : pageList) { int byteLength = testString.getBytes(java.nio.charset.StandardCharsets.UTF_8).length; if (byteLength > 256 * 4) { ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send a book with with a page too large!"); -+ net.pl3x.purpur.event.player.PlayerBookTooLargeEvent event = new net.pl3x.purpur.event.player.PlayerBookTooLargeEvent(player.getBukkitEntity(), testStack.asBukkitCopy()); if (event.shouldKickPlayer()) // Purpur ++ net.pl3x.purpur.event.player.PlayerBookTooLargeEvent event = new net.pl3x.purpur.event.player.PlayerBookTooLargeEvent(player.getBukkitEntity(), itemstack.asBukkitCopy()); if (event.shouldKickPlayer()) // Purpur server.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause return; } -@@ -1157,6 +1159,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser +@@ -1110,6 +1112,7 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser if (byteTotal > byteAllowed) { ServerGamePacketListenerImpl.LOGGER.warn(this.player.getScoreboardName() + " tried to send too large of a book. Book Size: " + byteTotal + " - Allowed: "+ byteAllowed + " - Pages: " + pageList.size()); -+ net.pl3x.purpur.event.player.PlayerBookTooLargeEvent event = new net.pl3x.purpur.event.player.PlayerBookTooLargeEvent(player.getBukkitEntity(), testStack.asBukkitCopy()); if (event.shouldKickPlayer()) // Purpur ++ net.pl3x.purpur.event.player.PlayerBookTooLargeEvent event = new net.pl3x.purpur.event.player.PlayerBookTooLargeEvent(player.getBukkitEntity(), itemstack.asBukkitCopy()); if (event.shouldKickPlayer()) // Purpur server.scheduleOnMain(() -> this.disconnect("Book too large!", org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_ACTION)); // Paper - kick event cause return; } diff --git a/patches/server/0146-Full-netherite-armor-grants-fire-resistance.patch b/patches/server/0146-Full-netherite-armor-grants-fire-resistance.patch index 8469659c4..1369425a6 100644 --- a/patches/server/0146-Full-netherite-armor-grants-fire-resistance.patch +++ b/patches/server/0146-Full-netherite-armor-grants-fire-resistance.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Full netherite armor grants fire resistance diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index d4e6c16b08c5aaf9c50467941498b566f11cc5d2..f4f49b87b615a3c7ef56247896392de93eb1bb0d 100644 +index fab14cd3148cb81a343b7b1fb4f94da977dba763..6301f71df9573f91040934c85a8530f2cf2bfdad 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java +++ b/src/main/java/net/minecraft/world/entity/player/Player.java @@ -360,6 +360,16 @@ public abstract class Player extends LivingEntity { @@ -26,10 +26,10 @@ index d4e6c16b08c5aaf9c50467941498b566f11cc5d2..f4f49b87b615a3c7ef56247896392de9 protected ItemCooldowns createItemCooldowns() { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index eefa02c1c6b1ec9219bd3d5dc6b28b1851164a6e..b61ae4082a76056b9b8e115eb604e860603da875 100644 +index 4d978502c426c177a75b92a38b0ee03f38eeb697..5aa1ecf7f9ae3b3ef6157e925bf8e0c750fedea4 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -266,6 +266,19 @@ public class PurpurWorldConfig { +@@ -265,6 +265,19 @@ public class PurpurWorldConfig { villageSiegeSpawning = getBoolean("gameplay-mechanics.mob-spawning.village-sieges", predicate); } diff --git a/patches/server/0148-Add-MC-4-fix-back.patch b/patches/server/0148-Add-MC-4-fix-back.patch index d21f62748..5d1a3ffb5 100644 --- a/patches/server/0148-Add-MC-4-fix-back.patch +++ b/patches/server/0148-Add-MC-4-fix-back.patch @@ -5,49 +5,15 @@ Subject: [PATCH] Add MC-4 fix back diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 57a80aed6106ec55aeb636c5b5f3abc7fb4fff14..7ebbe6588256471b6e5903ed6aaa20878340332e 100644 +index 1e21e75558dc69cb9c32dce99644ddd022165b58..9c0bf033f15d344944fefcfb0696f83197a5704d 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -4026,17 +4026,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n - return this.getZ((2.0D * this.random.nextDouble() - 1.0D) * widthScale); - } - -- public final void setPosRaw(double x, double y, double z) { -- // Paper start - fix MC-4 -- if (this instanceof ItemEntity) { -- if (false && com.destroystokyo.paper.PaperConfig.fixEntityPositionDesync) { // Tuinity - revert -- // encode/decode from PacketPlayOutEntity -- x = Mth.lfloor(x * 4096.0D) * (1 / 4096.0D); -- y = Mth.lfloor(y * 4096.0D) * (1 / 4096.0D); -- z = Mth.lfloor(z * 4096.0D) * (1 / 4096.0D); -- } -- } -- // Paper end - fix MC-4 -+ public void setPosRaw(double x, double y, double z) { // Purpur - remove final for ItemEntity - // Paper start - never allow AABB to become desynced from position - // hanging has its own special logic - if (!(this instanceof net.minecraft.world.entity.decoration.HangingEntity) && (this.position.x != x || this.position.y != y || this.position.z != z)) { -diff --git a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -index 5201e59c7ce9e92790c185279ba69d7fbbfccf90..8845c7b55ac1e6284c4fcd69b395dfb1f5523f90 100644 ---- a/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -+++ b/src/main/java/net/minecraft/world/entity/item/ItemEntity.java -@@ -87,6 +87,19 @@ public class ItemEntity extends Entity { - this.bobOffs = entity.bobOffs; - } - -+ // Purpur start - fix MC-4 -+ @Override -+ public void setPosRaw(double x, double y, double z) { -+ if (com.destroystokyo.paper.PaperConfig.fixEntityPositionDesync) { // Tuinity - revert // Purpur - No. -+ // encode/decode from PacketPlayOutEntity -+ x = Mth.lfloor(x * 4096.0D) * (1 / 4096.0D); -+ y = Mth.lfloor(y * 4096.0D) * (1 / 4096.0D); -+ z = Mth.lfloor(z * 4096.0D) * (1 / 4096.0D); -+ } -+ super.setPosRaw(x, y, z); -+ } -+ // Purpur end - fix MC-4 -+ - @Override - public boolean occludesVibrations() { - return ItemTags.OCCLUDES_VIBRATION_SIGNALS.contains(this.getItem().getItem()); +@@ -3838,7 +3838,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n + public final void setPosRaw(double x, double y, double z) { + // Paper start - fix MC-4 + if (this instanceof ItemEntity) { +- if (com.destroystokyo.paper.PaperConfig.fixEntityPositionDesync) { ++ if (com.destroystokyo.paper.PaperConfig.fixEntityPositionDesync) { // Purpur + // encode/decode from PacketPlayOutEntity + x = Mth.lfloor(x * 4096.0D) * (1 / 4096.0D); + y = Mth.lfloor(y * 4096.0D) * (1 / 4096.0D); diff --git a/patches/server/0149-Add-mobGriefing-bypass-to-everything-affected.patch b/patches/server/0149-Add-mobGriefing-bypass-to-everything-affected.patch index 29cbd0f43..3f5c42029 100644 --- a/patches/server/0149-Add-mobGriefing-bypass-to-everything-affected.patch +++ b/patches/server/0149-Add-mobGriefing-bypass-to-everything-affected.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add mobGriefing bypass to everything affected diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 0fa7a658af3dc1766e06d90d396a426c558917ab..5c82dd279fc3a5847e2e0ed6c9cf9e70acfb3bff 100644 +index 3bda2f895612cb5bfd6205a675c03edc6a2270d6..fb031a1da8f1df142155ffb368ccc082da776e97 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1674,7 +1674,7 @@ public abstract class LivingEntity extends Entity { +@@ -1678,7 +1678,7 @@ public abstract class LivingEntity extends Entity { boolean flag = false; if (this.dead && adversary instanceof WitherBoss) { // Paper @@ -18,10 +18,10 @@ index 0fa7a658af3dc1766e06d90d396a426c558917ab..5c82dd279fc3a5847e2e0ed6c9cf9e70 BlockState iblockdata = Blocks.WITHER_ROSE.defaultBlockState(); diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index 13b256c7d9723e38d1a1f5766fb2a28bc26f8943..a397a4bd2deb682d1465a305731cee0d911e64af 100644 +index 20af97629b614da792bdee2f8e450433570102f2..713b80dde1f6cf3ba516513873c949a57047a566 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -656,7 +656,7 @@ public abstract class Mob extends LivingEntity { +@@ -657,7 +657,7 @@ public abstract class Mob extends LivingEntity { public void aiStep() { super.aiStep(); this.level.getProfiler().push("looting"); @@ -131,7 +131,7 @@ index 1697b573ffd0c5d17d2d538c40f5ce4b709f261a..69bb27e8223b4be0e410ab9dd10e4db2 } diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -index a70c631691512a633c7fe4df9e9f2881f7397298..0324f69918de432e6e726fee3bd8e86c7d180523 100644 +index f637cd740ec3801ce1c387473b5c4ff6080e76ec..3247f3f3c9d53953f8268c8f9575275126cf33a3 100644 --- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java @@ -560,7 +560,7 @@ public class EnderDragon extends Mob implements Enemy { @@ -144,7 +144,7 @@ index a70c631691512a633c7fe4df9e9f2881f7397298..0324f69918de432e6e726fee3bd8e86c // flag1 = this.level.a(blockposition, false) || flag1; flag1 = true; diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -index 689df66281ef84f0cc31d89c6f2e654c9f3a5150..15d35d325b66e1a417eb7ba699597d627bd4eb54 100644 +index 5f24fb3fc4ffb2e032cf755d984716d1c3a3e965..232fe770b9befacc4d436a01139fe5fe46f92879 100644 --- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java @@ -375,7 +375,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob @@ -290,7 +290,7 @@ index e69213b43c8aa5a7c04add7a87482d531fbf52d2..f51ea103238b4a50439f5162a248cd9a // CraftBukkit start - fire ExplosionPrimeEvent ExplosionPrimeEvent event = new ExplosionPrimeEvent((org.bukkit.entity.Explosive) this.getBukkitEntity()); diff --git a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java -index 9a5da1eec68c7c435406809fda9695cbd54ba6c5..24a95bbee394dae1a3eac937990cae10a287a40b 100644 +index cecdafb496669419911ac7ea6db5a69f71f9be13..facd45496e966fe973de2e4cde93f01297932669 100644 --- a/src/main/java/net/minecraft/world/entity/projectile/Projectile.java +++ b/src/main/java/net/minecraft/world/entity/projectile/Projectile.java @@ -290,6 +290,6 @@ public abstract class Projectile extends Entity { @@ -354,7 +354,7 @@ index 6dda5eeca4e310eceb2598322803bfafc184e9c7..3c51e6d419a244b9270119590aa29952 } diff --git a/src/main/java/net/minecraft/world/level/block/FarmBlock.java b/src/main/java/net/minecraft/world/level/block/FarmBlock.java -index 2d4d59eb5a534e4c283933b734c44776b674b2eb..aad204181c4f54ee533bfe3fc04a8705b847e371 100644 +index 9fb0464f74bcbb757171290ae44f4a4c7ea4d499..668724cfaf9f34fe057171b69213782a22f8013b 100644 --- a/src/main/java/net/minecraft/world/level/block/FarmBlock.java +++ b/src/main/java/net/minecraft/world/level/block/FarmBlock.java @@ -82,7 +82,7 @@ public class FarmBlock extends Block { @@ -393,10 +393,10 @@ index e98fc3c235f9160f1928a8afb0d7991a6d3430cb..db35f756b7adb6b113659ae13b08ab89 return true; // Purpur end diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac884adee0f 100644 +index 5aa1ecf7f9ae3b3ef6157e925bf8e0c750fedea4..315e24bc0ef9950f68260e2f5aefbe0b7d421a1b 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -368,9 +368,12 @@ public class PurpurWorldConfig { +@@ -367,9 +367,12 @@ public class PurpurWorldConfig { public boolean boatsDoFallDamage = true; public boolean disableDropsOnCrammingDeath = false; public boolean entitiesCanUsePortals = true; @@ -409,7 +409,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 public double tridentLoyaltyVoidReturnHeight = 0.0D; public double voidDamageHeight = -64.0D; public double voidDamageDealt = 4.0D; -@@ -382,9 +385,12 @@ public class PurpurWorldConfig { +@@ -381,9 +384,12 @@ public class PurpurWorldConfig { boatsDoFallDamage = getBoolean("gameplay-mechanics.boat.do-fall-damage", boatsDoFallDamage); disableDropsOnCrammingDeath = getBoolean("gameplay-mechanics.disable-drops-on-cramming-death", disableDropsOnCrammingDeath); entitiesCanUsePortals = getBoolean("gameplay-mechanics.entities-can-use-portals", entitiesCanUsePortals); @@ -422,7 +422,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 tridentLoyaltyVoidReturnHeight = getDouble("gameplay-mechanics.trident-loyalty-void-return-height", tridentLoyaltyVoidReturnHeight); voidDamageHeight = getDouble("gameplay-mechanics.void-damage-height", voidDamageHeight); voidDamageDealt = getDouble("gameplay-mechanics.void-damage-dealt", voidDamageDealt); -@@ -454,9 +460,11 @@ public class PurpurWorldConfig { +@@ -453,9 +459,11 @@ public class PurpurWorldConfig { dispenserPlaceAnvils = getBoolean("blocks.dispenser.place-anvils", dispenserPlaceAnvils); } @@ -434,7 +434,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 farmlandGetsMoistFromBelow = getBoolean("blocks.farmland.gets-moist-from-below", farmlandGetsMoistFromBelow); farmlandAlpha = getBoolean("blocks.farmland.use-alpha-farmland", farmlandAlpha); } -@@ -488,6 +496,11 @@ public class PurpurWorldConfig { +@@ -487,6 +495,11 @@ public class PurpurWorldConfig { kelpMaxGrowthAge = getInt("blocks.kelp.max-growth-age", kelpMaxGrowthAge); } @@ -446,7 +446,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 public boolean respawnAnchorExplode = true; public double respawnAnchorExplosionPower = 5.0D; public boolean respawnAnchorExplosionFire = true; -@@ -521,6 +534,11 @@ public class PurpurWorldConfig { +@@ -520,6 +533,11 @@ public class PurpurWorldConfig { stonecutterDamage = (float) getDouble("blocks.stonecutter.damage", stonecutterDamage); } @@ -458,7 +458,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 public double twistingVinesGrowthModifier = 0.10D; public int twistingVinesMaxGrowthAge = 25; private void twistingVinesSettings() { -@@ -699,6 +717,7 @@ public class PurpurWorldConfig { +@@ -698,6 +716,7 @@ public class PurpurWorldConfig { public double creeperMaxHealth = 20.0D; public double creeperChargedChance = 0.0D; public boolean creeperAllowGriefing = true; @@ -466,7 +466,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 private void creeperSettings() { creeperRidable = getBoolean("mobs.creeper.ridable", creeperRidable); creeperRidableInWater = getBoolean("mobs.creeper.ridable-in-water", creeperRidableInWater); -@@ -710,6 +729,7 @@ public class PurpurWorldConfig { +@@ -709,6 +728,7 @@ public class PurpurWorldConfig { creeperMaxHealth = getDouble("mobs.creeper.attributes.max_health", creeperMaxHealth); creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance); creeperAllowGriefing = getBoolean("mobs.creeper.allow-griefing", creeperAllowGriefing); @@ -474,7 +474,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 } public boolean dolphinRidable = false; -@@ -797,6 +817,7 @@ public class PurpurWorldConfig { +@@ -796,6 +816,7 @@ public class PurpurWorldConfig { public double enderDragonMaxY = 256D; public double enderDragonMaxHealth = 200.0D; public boolean enderDragonAlwaysDropsFullExp = false; @@ -482,7 +482,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 private void enderDragonSettings() { enderDragonRidable = getBoolean("mobs.ender_dragon.ridable", enderDragonRidable); enderDragonRidableInWater = getBoolean("mobs.ender_dragon.ridable-in-water", enderDragonRidableInWater); -@@ -812,6 +833,7 @@ public class PurpurWorldConfig { +@@ -811,6 +832,7 @@ public class PurpurWorldConfig { } enderDragonMaxHealth = getDouble("mobs.ender_dragon.attributes.max_health", enderDragonMaxHealth); enderDragonAlwaysDropsFullExp = getBoolean("mobs.ender_dragon.always-drop-full-exp", enderDragonAlwaysDropsFullExp); @@ -490,7 +490,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 } public boolean endermanRidable = false; -@@ -819,6 +841,7 @@ public class PurpurWorldConfig { +@@ -818,6 +840,7 @@ public class PurpurWorldConfig { public double endermanMaxHealth = 40.0D; public boolean endermanAllowGriefing = true; public boolean endermanDespawnEvenWithBlock = false; @@ -498,7 +498,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 private void endermanSettings() { endermanRidable = getBoolean("mobs.enderman.ridable", endermanRidable); endermanRidableInWater = getBoolean("mobs.enderman.ridable-in-water", endermanRidableInWater); -@@ -830,6 +853,7 @@ public class PurpurWorldConfig { +@@ -829,6 +852,7 @@ public class PurpurWorldConfig { endermanMaxHealth = getDouble("mobs.enderman.attributes.max_health", endermanMaxHealth); endermanAllowGriefing = getBoolean("mobs.enderman.allow-griefing", endermanAllowGriefing); endermanDespawnEvenWithBlock = getBoolean("mobs.enderman.can-despawn-with-held-block", endermanDespawnEvenWithBlock); @@ -506,7 +506,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 } public boolean endermiteRidable = false; -@@ -849,6 +873,7 @@ public class PurpurWorldConfig { +@@ -848,6 +872,7 @@ public class PurpurWorldConfig { public boolean evokerRidable = false; public boolean evokerRidableInWater = false; public double evokerMaxHealth = 24.0D; @@ -514,7 +514,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 private void evokerSettings() { evokerRidable = getBoolean("mobs.evoker.ridable", evokerRidable); evokerRidableInWater = getBoolean("mobs.evoker.ridable-in-water", evokerRidableInWater); -@@ -858,6 +883,7 @@ public class PurpurWorldConfig { +@@ -857,6 +882,7 @@ public class PurpurWorldConfig { set("mobs.evoker.attributes.max_health", oldValue); } evokerMaxHealth = getDouble("mobs.evoker.attributes.max_health", evokerMaxHealth); @@ -522,7 +522,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 } public boolean foxRidable = false; -@@ -865,6 +891,7 @@ public class PurpurWorldConfig { +@@ -864,6 +890,7 @@ public class PurpurWorldConfig { public double foxMaxHealth = 10.0D; public boolean foxTypeChangesWithTulips = false; public int foxBreedingTicks = 6000; @@ -530,7 +530,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 private void foxSettings() { foxRidable = getBoolean("mobs.fox.ridable", foxRidable); foxRidableInWater = getBoolean("mobs.fox.ridable-in-water", foxRidableInWater); -@@ -876,6 +903,7 @@ public class PurpurWorldConfig { +@@ -875,6 +902,7 @@ public class PurpurWorldConfig { foxMaxHealth = getDouble("mobs.fox.attributes.max_health", foxMaxHealth); foxTypeChangesWithTulips = getBoolean("mobs.fox.tulips-change-type", foxTypeChangesWithTulips); foxBreedingTicks = getInt("mobs.fox.breeding-delay-ticks", foxBreedingTicks); @@ -538,7 +538,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 } public boolean ghastRidable = false; -@@ -1271,6 +1299,7 @@ public class PurpurWorldConfig { +@@ -1270,6 +1298,7 @@ public class PurpurWorldConfig { public boolean piglinRidable = false; public boolean piglinRidableInWater = false; public double piglinMaxHealth = 16.0D; @@ -546,7 +546,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 private void piglinSettings() { piglinRidable = getBoolean("mobs.piglin.ridable", piglinRidable); piglinRidableInWater = getBoolean("mobs.piglin.ridable-in-water", piglinRidableInWater); -@@ -1280,6 +1309,7 @@ public class PurpurWorldConfig { +@@ -1279,6 +1308,7 @@ public class PurpurWorldConfig { set("mobs.piglin.attributes.max_health", oldValue); } piglinMaxHealth = getDouble("mobs.piglin.attributes.max_health", piglinMaxHealth); @@ -554,7 +554,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 } public boolean piglinBruteRidable = false; -@@ -1299,6 +1329,7 @@ public class PurpurWorldConfig { +@@ -1298,6 +1328,7 @@ public class PurpurWorldConfig { public boolean pillagerRidable = false; public boolean pillagerRidableInWater = false; public double pillagerMaxHealth = 24.0D; @@ -562,7 +562,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 private void pillagerSettings() { pillagerRidable = getBoolean("mobs.pillager.ridable", pillagerRidable); pillagerRidableInWater = getBoolean("mobs.pillager.ridable-in-water", pillagerRidableInWater); -@@ -1308,6 +1339,7 @@ public class PurpurWorldConfig { +@@ -1307,6 +1338,7 @@ public class PurpurWorldConfig { set("mobs.pillager.attributes.max_health", oldValue); } pillagerMaxHealth = getDouble("mobs.pillager.attributes.max_health", pillagerMaxHealth); @@ -570,7 +570,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 } public boolean polarBearRidable = false; -@@ -1349,6 +1381,7 @@ public class PurpurWorldConfig { +@@ -1348,6 +1380,7 @@ public class PurpurWorldConfig { public double rabbitNaturalToast = 0.0D; public double rabbitNaturalKiller = 0.0D; public int rabbitBreedingTicks = 6000; @@ -578,7 +578,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 private void rabbitSettings() { rabbitRidable = getBoolean("mobs.rabbit.ridable", rabbitRidable); rabbitRidableInWater = getBoolean("mobs.rabbit.ridable-in-water", rabbitRidableInWater); -@@ -1361,11 +1394,13 @@ public class PurpurWorldConfig { +@@ -1360,11 +1393,13 @@ public class PurpurWorldConfig { rabbitNaturalToast = getDouble("mobs.rabbit.spawn-toast-chance", rabbitNaturalToast); rabbitNaturalKiller = getDouble("mobs.rabbit.spawn-killer-rabbit-chance", rabbitNaturalKiller); rabbitBreedingTicks = getInt("mobs.rabbit.breeding-delay-ticks", rabbitBreedingTicks); @@ -592,7 +592,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 private void ravagerSettings() { ravagerRidable = getBoolean("mobs.ravager.ridable", ravagerRidable); ravagerRidableInWater = getBoolean("mobs.ravager.ridable-in-water", ravagerRidableInWater); -@@ -1375,6 +1410,7 @@ public class PurpurWorldConfig { +@@ -1374,6 +1409,7 @@ public class PurpurWorldConfig { set("mobs.ravager.attributes.max_health", oldValue); } ravagerMaxHealth = getDouble("mobs.ravager.attributes.max_health", ravagerMaxHealth); @@ -600,7 +600,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 } public boolean salmonRidable = false; -@@ -1393,6 +1429,7 @@ public class PurpurWorldConfig { +@@ -1392,6 +1428,7 @@ public class PurpurWorldConfig { public boolean sheepRidableInWater = false; public double sheepMaxHealth = 8.0D; public int sheepBreedingTicks = 6000; @@ -608,7 +608,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 private void sheepSettings() { sheepRidable = getBoolean("mobs.sheep.ridable", sheepRidable); sheepRidableInWater = getBoolean("mobs.sheep.ridable-in-water", sheepRidableInWater); -@@ -1403,6 +1440,7 @@ public class PurpurWorldConfig { +@@ -1402,6 +1439,7 @@ public class PurpurWorldConfig { } sheepMaxHealth = getDouble("mobs.sheep.attributes.max_health", sheepMaxHealth); sheepBreedingTicks = getInt("mobs.sheep.breeding-delay-ticks", sheepBreedingTicks); @@ -616,7 +616,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 } public boolean shulkerRidable = false; -@@ -1422,6 +1460,7 @@ public class PurpurWorldConfig { +@@ -1421,6 +1459,7 @@ public class PurpurWorldConfig { public boolean silverfishRidable = false; public boolean silverfishRidableInWater = false; public double silverfishMaxHealth = 8.0D; @@ -624,7 +624,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 private void silverfishSettings() { silverfishRidable = getBoolean("mobs.silverfish.ridable", silverfishRidable); silverfishRidableInWater = getBoolean("mobs.silverfish.ridable-in-water", silverfishRidableInWater); -@@ -1431,6 +1470,7 @@ public class PurpurWorldConfig { +@@ -1430,6 +1469,7 @@ public class PurpurWorldConfig { set("mobs.silverfish.attributes.max_health", oldValue); } silverfishMaxHealth = getDouble("mobs.silverfish.attributes.max_health", silverfishMaxHealth); @@ -632,7 +632,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 } public boolean skeletonRidable = false; -@@ -1498,6 +1538,7 @@ public class PurpurWorldConfig { +@@ -1497,6 +1537,7 @@ public class PurpurWorldConfig { public int snowGolemSnowBallMax = 20; public float snowGolemSnowBallModifier = 10.0F; public double snowGolemAttackDistance = 1.25D; @@ -640,7 +640,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 private void snowGolemSettings() { snowGolemRidable = getBoolean("mobs.snow_golem.ridable", snowGolemRidable); snowGolemRidableInWater = getBoolean("mobs.snow_golem.ridable-in-water", snowGolemRidableInWater); -@@ -1514,6 +1555,7 @@ public class PurpurWorldConfig { +@@ -1513,6 +1554,7 @@ public class PurpurWorldConfig { snowGolemSnowBallMax = getInt("mobs.snow_golem.max-shoot-interval-ticks", snowGolemSnowBallMax); snowGolemSnowBallModifier = (float) getDouble("mobs.snow_golem.snow-ball-modifier", snowGolemSnowBallModifier); snowGolemAttackDistance = getDouble("mobs.snow_golem.attack-distance", snowGolemAttackDistance); @@ -648,7 +648,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 } public boolean squidRidable = false; -@@ -1673,6 +1715,7 @@ public class PurpurWorldConfig { +@@ -1672,6 +1714,7 @@ public class PurpurWorldConfig { public int villagerLobotomizeCheck = 60; public boolean villagerClericsFarmWarts = false; public boolean villagerClericFarmersThrowWarts = true; @@ -656,7 +656,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 private void villagerSettings() { villagerRidable = getBoolean("mobs.villager.ridable", villagerRidable); villagerRidableInWater = getBoolean("mobs.villager.ridable-in-water", villagerRidableInWater); -@@ -1699,6 +1742,7 @@ public class PurpurWorldConfig { +@@ -1698,6 +1741,7 @@ public class PurpurWorldConfig { villagerLobotomizeCheck = getInt("mobs.villager.lobotomize.check-interval", villagerLobotomizeCheck); villagerClericsFarmWarts = getBoolean("mobs.villager.clerics-farm-warts", villagerClericsFarmWarts); villagerClericFarmersThrowWarts = getBoolean("mobs.villager.cleric-wart-farmers-throw-warts-at-villagers", villagerClericFarmersThrowWarts); @@ -664,7 +664,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 } public boolean vindicatorRidable = false; -@@ -1755,6 +1799,7 @@ public class PurpurWorldConfig { +@@ -1754,6 +1798,7 @@ public class PurpurWorldConfig { public double witherMaxHealth = 300.0D; public float witherHealthRegenAmount = 1.0f; public int witherHealthRegenDelay = 20; @@ -672,7 +672,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 private void witherSettings() { witherRidable = getBoolean("mobs.wither.ridable", witherRidable); witherRidableInWater = getBoolean("mobs.wither.ridable-in-water", witherRidableInWater); -@@ -1771,6 +1816,7 @@ public class PurpurWorldConfig { +@@ -1770,6 +1815,7 @@ public class PurpurWorldConfig { witherMaxHealth = getDouble("mobs.wither.attributes.max_health", witherMaxHealth); witherHealthRegenAmount = (float) getDouble("mobs.wither.health-regen-amount", witherHealthRegenAmount); witherHealthRegenDelay = getInt("mobs.wither.health-regen-delay", witherHealthRegenDelay); @@ -680,7 +680,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 } public boolean witherSkeletonRidable = false; -@@ -1838,6 +1884,7 @@ public class PurpurWorldConfig { +@@ -1837,6 +1883,7 @@ public class PurpurWorldConfig { public boolean zombieJockeyTryExistingChickens = true; public boolean zombieAggressiveTowardsVillagerWhenLagging = true; public Difficulty zombieBreakDoorMinDifficulty = Difficulty.HARD; @@ -688,7 +688,7 @@ index b61ae4082a76056b9b8e115eb604e860603da875..a9cda879921260f350277cf49d11eac8 private void zombieSettings() { zombieRidable = getBoolean("mobs.zombie.ridable", zombieRidable); zombieRidableInWater = getBoolean("mobs.zombie.ridable-in-water", zombieRidableInWater); -@@ -1857,6 +1904,7 @@ public class PurpurWorldConfig { +@@ -1856,6 +1903,7 @@ public class PurpurWorldConfig { } catch (IllegalArgumentException ignore) { zombieBreakDoorMinDifficulty = Difficulty.HARD; } diff --git a/patches/server/0150-Config-to-allow-Note-Block-sounds-when-blocked.patch b/patches/server/0150-Config-to-allow-Note-Block-sounds-when-blocked.patch index 793b39e78..a187bde0d 100644 --- a/patches/server/0150-Config-to-allow-Note-Block-sounds-when-blocked.patch +++ b/patches/server/0150-Config-to-allow-Note-Block-sounds-when-blocked.patch @@ -22,10 +22,10 @@ index d3c8fd8399629efb8bcbaf7d9a0c43340fcdfeda..c74df3b5c2a25469ad3fb6a853438bbc org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, blockposition, data.getValue(NoteBlock.INSTRUMENT), data.getValue(NoteBlock.NOTE)); if (!event.isCancelled()) { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index a9cda879921260f350277cf49d11eac884adee0f..d9eee62b0b9bce501a6db08c27925325d9ba49e4 100644 +index 315e24bc0ef9950f68260e2f5aefbe0b7d421a1b..393d3ce76b833044daa3f18670780b848b95e5c9 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -371,6 +371,7 @@ public class PurpurWorldConfig { +@@ -370,6 +370,7 @@ public class PurpurWorldConfig { public boolean entitiesPickUpLootBypassMobGriefing = false; public boolean fireballsBypassMobGriefing = false; public boolean milkCuresBadOmen = true; @@ -33,7 +33,7 @@ index a9cda879921260f350277cf49d11eac884adee0f..d9eee62b0b9bce501a6db08c27925325 public boolean persistentTileEntityDisplayNames = false; public boolean persistentDroppableEntityDisplayNames = false; public boolean projectilesBypassMobGriefing = false; -@@ -388,6 +389,7 @@ public class PurpurWorldConfig { +@@ -387,6 +388,7 @@ public class PurpurWorldConfig { entitiesPickUpLootBypassMobGriefing = getBoolean("gameplay-mechanics.entities-pick-up-loot-bypass-mob-griefing", entitiesPickUpLootBypassMobGriefing); fireballsBypassMobGriefing = getBoolean("gameplay-mechanics.fireballs-bypass-mob-griefing", fireballsBypassMobGriefing); milkCuresBadOmen = getBoolean("gameplay-mechanics.milk-cures-bad-omen", milkCuresBadOmen); diff --git a/patches/server/0151-Add-EntityTeleportHinderedEvent.patch b/patches/server/0151-Add-EntityTeleportHinderedEvent.patch index 7996eaeb1..7744a3420 100644 --- a/patches/server/0151-Add-EntityTeleportHinderedEvent.patch +++ b/patches/server/0151-Add-EntityTeleportHinderedEvent.patch @@ -78,10 +78,10 @@ index 09cbce5aec6eabfa220f7de81b492a180cb8ca1e..265770975ad1190283103b04cdd52a07 blockEntity.teleportCooldown = 100; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index d9eee62b0b9bce501a6db08c27925325d9ba49e4..6aa1e58038304f8eb2fe31bc9f1e41f5cfc27f3e 100644 +index 393d3ce76b833044daa3f18670780b848b95e5c9..b967dfd7d675ed53a76fa743df0d3fbac1f414e8 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -370,6 +370,7 @@ public class PurpurWorldConfig { +@@ -369,6 +369,7 @@ public class PurpurWorldConfig { public boolean entitiesCanUsePortals = true; public boolean entitiesPickUpLootBypassMobGriefing = false; public boolean fireballsBypassMobGriefing = false; @@ -89,7 +89,7 @@ index d9eee62b0b9bce501a6db08c27925325d9ba49e4..6aa1e58038304f8eb2fe31bc9f1e41f5 public boolean milkCuresBadOmen = true; public boolean noteBlockIgnoreAbove = false; public boolean persistentTileEntityDisplayNames = false; -@@ -388,6 +389,7 @@ public class PurpurWorldConfig { +@@ -387,6 +388,7 @@ public class PurpurWorldConfig { entitiesCanUsePortals = getBoolean("gameplay-mechanics.entities-can-use-portals", entitiesCanUsePortals); entitiesPickUpLootBypassMobGriefing = getBoolean("gameplay-mechanics.entities-pick-up-loot-bypass-mob-griefing", entitiesPickUpLootBypassMobGriefing); fireballsBypassMobGriefing = getBoolean("gameplay-mechanics.fireballs-bypass-mob-griefing", fireballsBypassMobGriefing); @@ -98,10 +98,10 @@ index d9eee62b0b9bce501a6db08c27925325d9ba49e4..6aa1e58038304f8eb2fe31bc9f1e41f5 noteBlockIgnoreAbove = getBoolean("gameplay-mechanics.note-block-ignore-above", noteBlockIgnoreAbove); persistentTileEntityDisplayNames = getBoolean("gameplay-mechanics.persistent-tileentity-display-names-and-lore", persistentTileEntityDisplayNames); diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index edc08af4ec2ce6e90c30da286c0ba5ac16efd3fc..3a92c0112befe51e795f81b1fce52e1f083f6373 100644 +index 6b85ba7d9bad9f648b4a6cb5a3938509b3e73cca..d2c776b8c189dc01dadb7399d3892399ebcb6276 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -559,6 +559,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -550,6 +550,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { location.checkFinite(); if (this.entity.isVehicle() || this.entity.isRemoved()) { @@ -113,10 +113,10 @@ index edc08af4ec2ce6e90c30da286c0ba5ac16efd3fc..3a92c0112befe51e795f81b1fce52e1f } diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index 1ff393d62ae98de65b1cd963705c4e3897a34b5a..d7150eb5b7c51c3346ce9b5d143c744156a72117 100644 +index 98a3b692d17a7aa8166bf53a1d707c43590ed7f7..a3b72845e6a7323f6b7e73d0494bf2c58de62c7b 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -1002,6 +1002,10 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -947,6 +947,10 @@ public class CraftPlayer extends CraftHumanEntity implements Player { } if (entity.isVehicle()) { diff --git a/patches/server/0153-Farmland-trampling-changes.patch b/patches/server/0153-Farmland-trampling-changes.patch index 82d27d55a..1925ee554 100644 --- a/patches/server/0153-Farmland-trampling-changes.patch +++ b/patches/server/0153-Farmland-trampling-changes.patch @@ -12,7 +12,7 @@ necessary to trample in the first place. Feather Falling 1 requires you to fall over 3+ blocks to trample. FF 2 requires 4+, etc. diff --git a/src/main/java/net/minecraft/world/level/block/FarmBlock.java b/src/main/java/net/minecraft/world/level/block/FarmBlock.java -index aad204181c4f54ee533bfe3fc04a8705b847e371..16eb80e7926cf4fe421c17d7d851d457a2bc0f5d 100644 +index 668724cfaf9f34fe057171b69213782a22f8013b..1d662162fbf8cf7afa5fac167f8bfee41a0060c0 100644 --- a/src/main/java/net/minecraft/world/level/block/FarmBlock.java +++ b/src/main/java/net/minecraft/world/level/block/FarmBlock.java @@ -97,12 +97,20 @@ public class FarmBlock extends Block { @@ -37,10 +37,10 @@ index aad204181c4f54ee533bfe3fc04a8705b847e371..16eb80e7926cf4fe421c17d7d851d457 if (CraftEventFactory.callEntityChangeBlockEvent(entity, pos, Blocks.DIRT.defaultBlockState()).isCancelled()) { return; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 6aa1e58038304f8eb2fe31bc9f1e41f5cfc27f3e..2fd081c6b29638faf310189cec6f2fc87d8151c2 100644 +index b967dfd7d675ed53a76fa743df0d3fbac1f414e8..0e8af1daaffd7deaceb58d2298e5c0d027959822 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -467,10 +467,16 @@ public class PurpurWorldConfig { +@@ -466,10 +466,16 @@ public class PurpurWorldConfig { public boolean farmlandBypassMobGriefing = false; public boolean farmlandGetsMoistFromBelow = false; public boolean farmlandAlpha = false; diff --git a/patches/server/0154-Movement-options-for-armor-stands.patch b/patches/server/0154-Movement-options-for-armor-stands.patch index 990469600..fa3bcc4c6 100644 --- a/patches/server/0154-Movement-options-for-armor-stands.patch +++ b/patches/server/0154-Movement-options-for-armor-stands.patch @@ -17,10 +17,10 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 7ebbe6588256471b6e5903ed6aaa20878340332e..1029b004b526acbeecc1f2e82ddae00ff3de1196 100644 +index 9c0bf033f15d344944fefcfb0696f83197a5704d..c220dbe126f754b3acb35559855df5f577ca4801 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -1588,7 +1588,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n +@@ -1402,7 +1402,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n return this.isInWater() || flag; } @@ -66,10 +66,10 @@ index 796ab61f4513c02b0d55d34044d2f7084c447796..d119f8ab447bc17deabc494463de4961 + // Purpur end } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 2fd081c6b29638faf310189cec6f2fc87d8151c2..c0f24f9c8737a32a5314dfd9131d0133bd56f255 100644 +index 0e8af1daaffd7deaceb58d2298e5c0d027959822..b9a3df20b454699c3643b717ccc07de08366cba5 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -102,10 +102,16 @@ public class PurpurWorldConfig { +@@ -101,10 +101,16 @@ public class PurpurWorldConfig { public float armorstandStepHeight = 0.0F; public boolean armorstandSetNameVisible = false; public boolean armorstandFixNametags = false; diff --git a/patches/server/0155-Fix-stuck-in-portals.patch b/patches/server/0155-Fix-stuck-in-portals.patch index 858d10983..ace6f2de6 100644 --- a/patches/server/0155-Fix-stuck-in-portals.patch +++ b/patches/server/0155-Fix-stuck-in-portals.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Fix stuck in portals diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 93b8260c59c659ddd2e80660be23b9a825339a72..0c652c509c94d82a784acc8c6d4c2c6d037532db 100644 +index 85d2fda86638075130def6a47912682637186d84..6e9d011dd855a5fcb9fb603751a1dff434db7fe0 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1141,6 +1141,7 @@ public class ServerPlayer extends Player { +@@ -1137,6 +1137,7 @@ public class ServerPlayer extends Player { playerlist.sendPlayerPermissionLevel(this); worldserver1.removePlayerImmediately(this, Entity.RemovalReason.CHANGED_DIMENSION); this.unsetRemoved(); @@ -17,10 +17,10 @@ index 93b8260c59c659ddd2e80660be23b9a825339a72..0c652c509c94d82a784acc8c6d4c2c6d // CraftBukkit end this.setLevel(worldserver); diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 1029b004b526acbeecc1f2e82ddae00ff3de1196..1f06a8b4102d8736755e052f03d3bfb0bbf867a1 100644 +index c220dbe126f754b3acb35559855df5f577ca4801..e87e30d6ff41ca1097b61729a8f3f678119d6007 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2698,12 +2698,15 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n +@@ -2509,12 +2509,15 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n return Vec3.directionFromRotation(this.getRotationVector()); } @@ -37,10 +37,10 @@ index 1029b004b526acbeecc1f2e82ddae00ff3de1196..1f06a8b4102d8736755e052f03d3bfb0 this.isInsidePortal = true; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index c0f24f9c8737a32a5314dfd9131d0133bd56f255..6bd19d153fcdb59b144ed40e8493e4a9f4c0e523 100644 +index b9a3df20b454699c3643b717ccc07de08366cba5..26f6b0f31cbbedb20ac7660726c5244c33685954 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -296,6 +296,7 @@ public class PurpurWorldConfig { +@@ -295,6 +295,7 @@ public class PurpurWorldConfig { public int playerDeathExpDropMax = 100; public boolean teleportIfOutsideBorder = false; public boolean totemOfUndyingWorksInInventory = false; @@ -48,7 +48,7 @@ index c0f24f9c8737a32a5314dfd9131d0133bd56f255..6bd19d153fcdb59b144ed40e8493e4a9 private void playerSettings() { idleTimeoutKick = getBoolean("gameplay-mechanics.player.idle-timeout.kick-if-idle", idleTimeoutKick); idleTimeoutTickNearbyEntities = getBoolean("gameplay-mechanics.player.idle-timeout.tick-nearby-entities", idleTimeoutTickNearbyEntities); -@@ -308,6 +309,7 @@ public class PurpurWorldConfig { +@@ -307,6 +308,7 @@ public class PurpurWorldConfig { playerDeathExpDropMax = getInt("gameplay-mechanics.player.exp-dropped-on-death.maximum", playerDeathExpDropMax); teleportIfOutsideBorder = getBoolean("gameplay-mechanics.player.teleport-if-outside-border", teleportIfOutsideBorder); totemOfUndyingWorksInInventory = getBoolean("gameplay-mechanics.player.totem-of-undying-works-in-inventory", totemOfUndyingWorksInInventory); diff --git a/patches/server/0156-Toggle-for-water-sensitive-mob-damage.patch b/patches/server/0156-Toggle-for-water-sensitive-mob-damage.patch index 193a48131..e9f9c1165 100644 --- a/patches/server/0156-Toggle-for-water-sensitive-mob-damage.patch +++ b/patches/server/0156-Toggle-for-water-sensitive-mob-damage.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Toggle for water sensitive mob damage diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index a397a4bd2deb682d1465a305731cee0d911e64af..b057284fea2fc32e300183ee1db29173efa6d490 100644 +index 713b80dde1f6cf3ba516513873c949a57047a566..9e144b0e3838b371d9fe6d146bc171f6e70e25b2 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -860,7 +860,7 @@ public abstract class Mob extends LivingEntity { +@@ -856,7 +856,7 @@ public abstract class Mob extends LivingEntity { if (goalFloat.canUse()) goalFloat.tick(); this.getJumpControl().tick(); } @@ -57,10 +57,10 @@ index 96ad40de21a4f00df15ec5c0c8c130566ac27cd1..3907b7cb559dabdd3cc347678d420712 @Override diff --git a/src/main/java/net/minecraft/world/entity/monster/Strider.java b/src/main/java/net/minecraft/world/entity/monster/Strider.java -index b3928617f732b49cfc124e9bdb879110413defd7..1c6d29a30df66e9971cd50e264bb4455cefe0e8a 100644 +index 66dab9b4e5ae05deeae11c8588a0b855d8847bdc..19753a1855b11f8cdc9fb77e8d9079bbeb3c519b 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Strider.java +++ b/src/main/java/net/minecraft/world/entity/monster/Strider.java -@@ -407,7 +407,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { +@@ -408,7 +408,7 @@ public class Strider extends Animal implements ItemSteerable, Saddleable { @Override public boolean isSensitiveToWater() { @@ -70,10 +70,10 @@ index b3928617f732b49cfc124e9bdb879110413defd7..1c6d29a30df66e9971cd50e264bb4455 @Override diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 6bd19d153fcdb59b144ed40e8493e4a9f4c0e523..20298204893a4fab0e3b1be36eca7611eac7399e 100644 +index 26f6b0f31cbbedb20ac7660726c5244c33685954..157ebb6cb3bcaed9303dd1919d2335cca3df3b48 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -634,6 +634,7 @@ public class PurpurWorldConfig { +@@ -633,6 +633,7 @@ public class PurpurWorldConfig { public boolean blazeRidableInWater = false; public double blazeMaxY = 256D; public double blazeMaxHealth = 20.0D; @@ -81,7 +81,7 @@ index 6bd19d153fcdb59b144ed40e8493e4a9f4c0e523..20298204893a4fab0e3b1be36eca7611 private void blazeSettings() { blazeRidable = getBoolean("mobs.blaze.ridable", blazeRidable); blazeRidableInWater = getBoolean("mobs.blaze.ridable-in-water", blazeRidableInWater); -@@ -644,6 +645,7 @@ public class PurpurWorldConfig { +@@ -643,6 +644,7 @@ public class PurpurWorldConfig { set("mobs.blaze.attributes.max_health", oldValue); } blazeMaxHealth = getDouble("mobs.blaze.attributes.max_health", blazeMaxHealth); @@ -89,7 +89,7 @@ index 6bd19d153fcdb59b144ed40e8493e4a9f4c0e523..20298204893a4fab0e3b1be36eca7611 } public boolean catRidable = false; -@@ -860,6 +862,7 @@ public class PurpurWorldConfig { +@@ -859,6 +861,7 @@ public class PurpurWorldConfig { public boolean endermanAllowGriefing = true; public boolean endermanDespawnEvenWithBlock = false; public boolean endermanBypassMobGriefing = false; @@ -97,7 +97,7 @@ index 6bd19d153fcdb59b144ed40e8493e4a9f4c0e523..20298204893a4fab0e3b1be36eca7611 private void endermanSettings() { endermanRidable = getBoolean("mobs.enderman.ridable", endermanRidable); endermanRidableInWater = getBoolean("mobs.enderman.ridable-in-water", endermanRidableInWater); -@@ -872,6 +875,7 @@ public class PurpurWorldConfig { +@@ -871,6 +874,7 @@ public class PurpurWorldConfig { endermanAllowGriefing = getBoolean("mobs.enderman.allow-griefing", endermanAllowGriefing); endermanDespawnEvenWithBlock = getBoolean("mobs.enderman.can-despawn-with-held-block", endermanDespawnEvenWithBlock); endermanBypassMobGriefing = getBoolean("mobs.enderman.bypass-mob-griefing", endermanBypassMobGriefing); @@ -105,7 +105,7 @@ index 6bd19d153fcdb59b144ed40e8493e4a9f4c0e523..20298204893a4fab0e3b1be36eca7611 } public boolean endermiteRidable = false; -@@ -1557,6 +1561,7 @@ public class PurpurWorldConfig { +@@ -1556,6 +1560,7 @@ public class PurpurWorldConfig { public float snowGolemSnowBallModifier = 10.0F; public double snowGolemAttackDistance = 1.25D; public boolean snowGolemBypassMobGriefing = false; @@ -113,7 +113,7 @@ index 6bd19d153fcdb59b144ed40e8493e4a9f4c0e523..20298204893a4fab0e3b1be36eca7611 private void snowGolemSettings() { snowGolemRidable = getBoolean("mobs.snow_golem.ridable", snowGolemRidable); snowGolemRidableInWater = getBoolean("mobs.snow_golem.ridable-in-water", snowGolemRidableInWater); -@@ -1574,6 +1579,7 @@ public class PurpurWorldConfig { +@@ -1573,6 +1578,7 @@ public class PurpurWorldConfig { snowGolemSnowBallModifier = (float) getDouble("mobs.snow_golem.snow-ball-modifier", snowGolemSnowBallModifier); snowGolemAttackDistance = getDouble("mobs.snow_golem.attack-distance", snowGolemAttackDistance); snowGolemBypassMobGriefing = getBoolean("mobs.snow_golem.bypass-mob-griefing", snowGolemBypassMobGriefing); @@ -121,7 +121,7 @@ index 6bd19d153fcdb59b144ed40e8493e4a9f4c0e523..20298204893a4fab0e3b1be36eca7611 } public boolean squidRidable = false; -@@ -1627,6 +1633,7 @@ public class PurpurWorldConfig { +@@ -1626,6 +1632,7 @@ public class PurpurWorldConfig { public double striderMaxHealth = 20.0D; public int striderBreedingTicks = 6000; public boolean striderGiveSaddleBack = false; @@ -129,7 +129,7 @@ index 6bd19d153fcdb59b144ed40e8493e4a9f4c0e523..20298204893a4fab0e3b1be36eca7611 private void striderSettings() { striderRidable = getBoolean("mobs.strider.ridable", striderRidable); striderRidableInWater = getBoolean("mobs.strider.ridable-in-water", striderRidableInWater); -@@ -1638,6 +1645,7 @@ public class PurpurWorldConfig { +@@ -1637,6 +1644,7 @@ public class PurpurWorldConfig { striderMaxHealth = getDouble("mobs.strider.attributes.max_health", striderMaxHealth); striderBreedingTicks = getInt("mobs.strider.breeding-delay-ticks", striderBreedingTicks); striderGiveSaddleBack = getBoolean("mobs.strider.give-saddle-back", striderGiveSaddleBack); diff --git a/patches/server/0157-Config-to-always-tame-in-Creative.patch b/patches/server/0157-Config-to-always-tame-in-Creative.patch index d881dd8d4..3f9eae99e 100644 --- a/patches/server/0157-Config-to-always-tame-in-Creative.patch +++ b/patches/server/0157-Config-to-always-tame-in-Creative.patch @@ -59,10 +59,10 @@ index 518dd0e6b4889c049e438b393baa795a5eac3e7d..21e154c4e7fe261a41c891b481072fbd this.navigation.stop(); this.setTarget((LivingEntity) null); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 20298204893a4fab0e3b1be36eca7611eac7399e..5bfa8b91be88790263ad0b2a25ba5c7bfd338dbf 100644 +index 157ebb6cb3bcaed9303dd1919d2335cca3df3b48..7efc52e632fb578b0f8f6fa57d45ad7619033e6e 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -372,6 +372,7 @@ public class PurpurWorldConfig { +@@ -371,6 +371,7 @@ public class PurpurWorldConfig { } public boolean useBetterMending = false; @@ -70,7 +70,7 @@ index 20298204893a4fab0e3b1be36eca7611eac7399e..5bfa8b91be88790263ad0b2a25ba5c7b public boolean boatEjectPlayersOnLand = false; public boolean boatsDoFallDamage = true; public boolean disableDropsOnCrammingDeath = false; -@@ -391,6 +392,7 @@ public class PurpurWorldConfig { +@@ -390,6 +391,7 @@ public class PurpurWorldConfig { public int animalBreedingCooldownSeconds = 0; private void miscGameplayMechanicsSettings() { useBetterMending = getBoolean("gameplay-mechanics.use-better-mending", useBetterMending); diff --git a/patches/server/0158-End-crystal-explosion-options.patch b/patches/server/0158-End-crystal-explosion-options.patch index aba2d5530..4624c8903 100644 --- a/patches/server/0158-End-crystal-explosion-options.patch +++ b/patches/server/0158-End-crystal-explosion-options.patch @@ -52,10 +52,10 @@ index 92e65f3fbc8f5d77bb8cc31e7a7780c2589f4227..0c46507ab0b904fb1f79bc5421c88c03 this.onDestroyedBy(source); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 5bfa8b91be88790263ad0b2a25ba5c7bfd338dbf..230522b2ff5113786ddd0c04918f05a97ba1f152 100644 +index 7efc52e632fb578b0f8f6fa57d45ad7619033e6e..e750f39abaed75443004208a9c48b7d4a08901c1 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -474,6 +474,35 @@ public class PurpurWorldConfig { +@@ -473,6 +473,35 @@ public class PurpurWorldConfig { dispenserPlaceAnvils = getBoolean("blocks.dispenser.place-anvils", dispenserPlaceAnvils); } diff --git a/patches/server/0159-Add-unsafe-Entity-serialization-API.patch b/patches/server/0159-Add-unsafe-Entity-serialization-API.patch index b6d20fa14..001d7ecf8 100644 --- a/patches/server/0159-Add-unsafe-Entity-serialization-API.patch +++ b/patches/server/0159-Add-unsafe-Entity-serialization-API.patch @@ -17,10 +17,10 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 3a92c0112befe51e795f81b1fce52e1f083f6373..1035e023003521574a09fdea3fd08e5fca66d8fc 100644 +index d2c776b8c189dc01dadb7399d3892399ebcb6276..e2b1574af471699f93956130b50268647f24e0b9 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1242,5 +1242,12 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -1233,5 +1233,12 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { public boolean isRidableInWater() { return getHandle().rideableUnderWater(); } @@ -34,7 +34,7 @@ index 3a92c0112befe51e795f81b1fce52e1f083f6373..1035e023003521574a09fdea3fd08e5f // Purpur end } diff --git a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java -index 9239c4c948ddfc6f7ad8c5f277e355581f8f37ec..75c5cb1a0b95f319eb32da618df7fa75ccefccbd 100644 +index 82d56d8c4616f1012e70697dae8d1d31d7d53f2f..a84d3f474711a87897fdc29a2ec1569306562941 100644 --- a/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java +++ b/src/main/java/org/bukkit/craftbukkit/util/CraftMagicNumbers.java @@ -408,9 +408,14 @@ public final class CraftMagicNumbers implements UnsafeValues { diff --git a/patches/server/0160-Configs-for-if-Wither-Ender-Dragon-can-ride-vehicles.patch b/patches/server/0160-Configs-for-if-Wither-Ender-Dragon-can-ride-vehicles.patch index 0e57e6826..331eb0828 100644 --- a/patches/server/0160-Configs-for-if-Wither-Ender-Dragon-can-ride-vehicles.patch +++ b/patches/server/0160-Configs-for-if-Wither-Ender-Dragon-can-ride-vehicles.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Configs for if Wither/Ender Dragon can ride vehicles diff --git a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java -index 0324f69918de432e6e726fee3bd8e86c7d180523..ecee28a40f4c3545797133caf94db89bb51b936a 100644 +index 3247f3f3c9d53953f8268c8f9575275126cf33a3..dd9655f29c397d3df15685f2901e1f232f17fec1 100644 --- a/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java +++ b/src/main/java/net/minecraft/world/entity/boss/enderdragon/EnderDragon.java @@ -1109,6 +1109,7 @@ public class EnderDragon extends Mob implements Enemy { @@ -17,7 +17,7 @@ index 0324f69918de432e6e726fee3bd8e86c7d180523..ecee28a40f4c3545797133caf94db89b } diff --git a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java -index 15d35d325b66e1a417eb7ba699597d627bd4eb54..36745f845f33c877595d572e46bd8e966c03d11a 100644 +index 232fe770b9befacc4d436a01139fe5fe46f92879..e8c9e5dc96658f2c3028da58eb3982e98cb1f28c 100644 --- a/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java +++ b/src/main/java/net/minecraft/world/entity/boss/wither/WitherBoss.java @@ -717,6 +717,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob @@ -29,10 +29,10 @@ index 15d35d325b66e1a417eb7ba699597d627bd4eb54..36745f845f33c877595d572e46bd8e96 } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 230522b2ff5113786ddd0c04918f05a97ba1f152..56bed5e58d49196f1f94660dbd88a9fae99a9be7 100644 +index e750f39abaed75443004208a9c48b7d4a08901c1..0b1f0a4530ffda4fd4e2b7f7b4487f7c0e466476 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -869,6 +869,7 @@ public class PurpurWorldConfig { +@@ -868,6 +868,7 @@ public class PurpurWorldConfig { public double enderDragonMaxHealth = 200.0D; public boolean enderDragonAlwaysDropsFullExp = false; public boolean enderDragonBypassMobGriefing = false; @@ -40,7 +40,7 @@ index 230522b2ff5113786ddd0c04918f05a97ba1f152..56bed5e58d49196f1f94660dbd88a9fa private void enderDragonSettings() { enderDragonRidable = getBoolean("mobs.ender_dragon.ridable", enderDragonRidable); enderDragonRidableInWater = getBoolean("mobs.ender_dragon.ridable-in-water", enderDragonRidableInWater); -@@ -885,6 +886,7 @@ public class PurpurWorldConfig { +@@ -884,6 +885,7 @@ public class PurpurWorldConfig { enderDragonMaxHealth = getDouble("mobs.ender_dragon.attributes.max_health", enderDragonMaxHealth); enderDragonAlwaysDropsFullExp = getBoolean("mobs.ender_dragon.always-drop-full-exp", enderDragonAlwaysDropsFullExp); enderDragonBypassMobGriefing = getBoolean("mobs.ender_dragon.bypass-mob-griefing", enderDragonBypassMobGriefing); @@ -48,7 +48,7 @@ index 230522b2ff5113786ddd0c04918f05a97ba1f152..56bed5e58d49196f1f94660dbd88a9fa } public boolean endermanRidable = false; -@@ -1857,6 +1859,7 @@ public class PurpurWorldConfig { +@@ -1856,6 +1858,7 @@ public class PurpurWorldConfig { public float witherHealthRegenAmount = 1.0f; public int witherHealthRegenDelay = 20; public boolean witherBypassMobGriefing = false; @@ -56,7 +56,7 @@ index 230522b2ff5113786ddd0c04918f05a97ba1f152..56bed5e58d49196f1f94660dbd88a9fa private void witherSettings() { witherRidable = getBoolean("mobs.wither.ridable", witherRidable); witherRidableInWater = getBoolean("mobs.wither.ridable-in-water", witherRidableInWater); -@@ -1874,6 +1877,7 @@ public class PurpurWorldConfig { +@@ -1873,6 +1876,7 @@ public class PurpurWorldConfig { witherHealthRegenAmount = (float) getDouble("mobs.wither.health-regen-amount", witherHealthRegenAmount); witherHealthRegenDelay = getInt("mobs.wither.health-regen-delay", witherHealthRegenDelay); witherBypassMobGriefing = getBoolean("mobs.wither.bypass-mob-griefing", witherBypassMobGriefing); diff --git a/patches/server/0161-Dont-run-with-scissors.patch b/patches/server/0161-Dont-run-with-scissors.patch index 6168c5bd6..0a1d4ae5a 100644 --- a/patches/server/0161-Dont-run-with-scissors.patch +++ b/patches/server/0161-Dont-run-with-scissors.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Dont run with scissors! diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 25c5d4c7c2db31c7a8c04a862928c6743ab5b130..9a2da610017064c914f84701f2b83d70f887acdb 100644 +index 4279bb45a75a9ae0510fa96bdee4efd331433e3e..0d3e73240e5d363427379f50ecb163482c6f1bfc 100644 --- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -1575,6 +1575,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser +@@ -1498,6 +1498,13 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser this.player.fallDistance = 0.0F; } @@ -43,10 +43,10 @@ index 00a5c65439a30499156a10b1822760fe48ee7776..3b68981645eb70df8828d437411564e8 public static int seedFeatureBamboo = -1; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 56bed5e58d49196f1f94660dbd88a9fae99a9be7..0cb4b3d9084646b493418dea60323752db3f13a0 100644 +index 0b1f0a4530ffda4fd4e2b7f7b4487f7c0e466476..eb536735310a3d2fe8adb65b0aed543a5d6f6add 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -156,6 +156,8 @@ public class PurpurWorldConfig { +@@ -155,6 +155,8 @@ public class PurpurWorldConfig { public List itemImmuneToExplosion = new ArrayList<>(); public List itemImmuneToFire = new ArrayList<>(); public List itemImmuneToLightning = new ArrayList<>(); @@ -55,7 +55,7 @@ index 56bed5e58d49196f1f94660dbd88a9fae99a9be7..0cb4b3d9084646b493418dea60323752 private void itemSettings() { itemImmuneToCactus.clear(); getList("gameplay-mechanics.item.immune.cactus", new ArrayList<>()).forEach(key -> { -@@ -193,6 +195,8 @@ public class PurpurWorldConfig { +@@ -192,6 +194,8 @@ public class PurpurWorldConfig { Item item = Registry.ITEM.get(new ResourceLocation(key.toString())); if (item != Items.AIR) itemImmuneToLightning.add(item); }); diff --git a/patches/server/0162-One-Punch-Man.patch b/patches/server/0162-One-Punch-Man.patch index e4ffd7487..2b1df315f 100644 --- a/patches/server/0162-One-Punch-Man.patch +++ b/patches/server/0162-One-Punch-Man.patch @@ -5,10 +5,10 @@ Subject: [PATCH] One Punch Man! diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 5c82dd279fc3a5847e2e0ed6c9cf9e70acfb3bff..c123e723d4fc3202eb7a4c74a356ffcde19e2ba5 100644 +index fb031a1da8f1df142155ffb368ccc082da776e97..4dd6b5745ca42a067f00d2fc24056e97b242a5f7 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -2116,6 +2116,20 @@ public abstract class LivingEntity extends Entity { +@@ -2120,6 +2120,20 @@ public abstract class LivingEntity extends Entity { ((ServerPlayer) damagesource.getEntity()).awardStat(Stats.DAMAGE_DEALT_ABSORBED, Math.round(f2 * 10.0F)); } @@ -30,10 +30,10 @@ index 5c82dd279fc3a5847e2e0ed6c9cf9e70acfb3bff..c123e723d4fc3202eb7a4c74a356ffcd if (human) { // PAIL: Be sure to drag all this code from the EntityHuman subclass each update. diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 0cb4b3d9084646b493418dea60323752db3f13a0..568be31ce53d8f037e49a592c0cb548d9ca644d1 100644 +index eb536735310a3d2fe8adb65b0aed543a5d6f6add..32c1fc2af8aeccf9683daf776e33b09fa8fe1a28 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -301,6 +301,7 @@ public class PurpurWorldConfig { +@@ -300,6 +300,7 @@ public class PurpurWorldConfig { public boolean teleportIfOutsideBorder = false; public boolean totemOfUndyingWorksInInventory = false; public boolean playerFixStuckPortal = false; @@ -41,7 +41,7 @@ index 0cb4b3d9084646b493418dea60323752db3f13a0..568be31ce53d8f037e49a592c0cb548d private void playerSettings() { idleTimeoutKick = getBoolean("gameplay-mechanics.player.idle-timeout.kick-if-idle", idleTimeoutKick); idleTimeoutTickNearbyEntities = getBoolean("gameplay-mechanics.player.idle-timeout.tick-nearby-entities", idleTimeoutTickNearbyEntities); -@@ -314,6 +315,7 @@ public class PurpurWorldConfig { +@@ -313,6 +314,7 @@ public class PurpurWorldConfig { teleportIfOutsideBorder = getBoolean("gameplay-mechanics.player.teleport-if-outside-border", teleportIfOutsideBorder); totemOfUndyingWorksInInventory = getBoolean("gameplay-mechanics.player.totem-of-undying-works-in-inventory", totemOfUndyingWorksInInventory); playerFixStuckPortal = getBoolean("gameplay-mechanics.player.fix-stuck-in-portal", playerFixStuckPortal); diff --git a/patches/server/0163-Add-config-for-snow-on-blue-ice.patch b/patches/server/0163-Add-config-for-snow-on-blue-ice.patch index 51ff3e6b7..83405583b 100644 --- a/patches/server/0163-Add-config-for-snow-on-blue-ice.patch +++ b/patches/server/0163-Add-config-for-snow-on-blue-ice.patch @@ -29,10 +29,10 @@ index 0169d874247a96c2e10a65ecb9c0c093f5a6ecfb..b760e2d014b3ae70671878082bb853b7 @Override diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 568be31ce53d8f037e49a592c0cb548d9ca644d1..f5c6f603165a0afa9432b8ef86dd72a4ce5cc24f 100644 +index 32c1fc2af8aeccf9683daf776e33b09fa8fe1a28..ec3fe1fabf1a6b07f3bfe95628702b7fb12185ad 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -535,6 +535,11 @@ public class PurpurWorldConfig { +@@ -534,6 +534,11 @@ public class PurpurWorldConfig { furnaceUseLavaFromUnderneath = getBoolean("blocks.furnace.use-lava-from-underneath", furnaceUseLavaFromUnderneath); } diff --git a/patches/server/0164-Configurable-Ender-Pearl-cooldown-damage-and-Endermi.patch b/patches/server/0164-Configurable-Ender-Pearl-cooldown-damage-and-Endermi.patch index 64ff5c8a4..2e18316f1 100644 --- a/patches/server/0164-Configurable-Ender-Pearl-cooldown-damage-and-Endermi.patch +++ b/patches/server/0164-Configurable-Ender-Pearl-cooldown-damage-and-Endermi.patch @@ -43,10 +43,10 @@ index 749ab72edc0d2e9c6f1161415ab8d59d3d6ca976..897c202c0905040072a06fdfa2032a7f // Paper end if (user instanceof net.minecraft.server.level.ServerPlayer) { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index f5c6f603165a0afa9432b8ef86dd72a4ce5cc24f..1a6c40ce6f3ee5e48280827952db51d00bae3908 100644 +index ec3fe1fabf1a6b07f3bfe95628702b7fb12185ad..53a9ee123387c0ce33d49c3ebcc366088a61fb2f 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -158,6 +158,10 @@ public class PurpurWorldConfig { +@@ -157,6 +157,10 @@ public class PurpurWorldConfig { public List itemImmuneToLightning = new ArrayList<>(); public boolean dontRunWithScissors = false; public double scissorsRunningDamage = 1D; @@ -57,7 +57,7 @@ index f5c6f603165a0afa9432b8ef86dd72a4ce5cc24f..1a6c40ce6f3ee5e48280827952db51d0 private void itemSettings() { itemImmuneToCactus.clear(); getList("gameplay-mechanics.item.immune.cactus", new ArrayList<>()).forEach(key -> { -@@ -197,6 +201,10 @@ public class PurpurWorldConfig { +@@ -196,6 +200,10 @@ public class PurpurWorldConfig { }); dontRunWithScissors = getBoolean("gameplay-mechanics.item.shears.damage-if-sprinting", dontRunWithScissors); scissorsRunningDamage = getDouble("gameplay-mechanics.item.shears.sprinting-damage", scissorsRunningDamage); diff --git a/patches/server/0165-Config-to-ignore-nearby-mobs-when-sleeping.patch b/patches/server/0165-Config-to-ignore-nearby-mobs-when-sleeping.patch index 13bdc5ddc..6c8a05c55 100644 --- a/patches/server/0165-Config-to-ignore-nearby-mobs-when-sleeping.patch +++ b/patches/server/0165-Config-to-ignore-nearby-mobs-when-sleeping.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Config to ignore nearby mobs when sleeping diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index 0c652c509c94d82a784acc8c6d4c2c6d037532db..d4be590e81a966eddfdf8c7d9c8849435f29bdcf 100644 +index 6e9d011dd855a5fcb9fb603751a1dff434db7fe0..9ce5a27dabb59104d35e7dffb9950b2dfd98611e 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -1304,7 +1304,7 @@ public class ServerPlayer extends Player { +@@ -1300,7 +1300,7 @@ public class ServerPlayer extends Player { return entitymonster.isPreventingPlayerRest((Player) this); }); @@ -18,10 +18,10 @@ index 0c652c509c94d82a784acc8c6d4c2c6d037532db..d4be590e81a966eddfdf8c7d9c884943 } } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 1a6c40ce6f3ee5e48280827952db51d00bae3908..469f61aa99f73cdae0fd9fd58c788c4e6c981e0b 100644 +index 53a9ee123387c0ce33d49c3ebcc366088a61fb2f..5d3b592ea596717101f791222ca2520e0dd4cf06 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -310,6 +310,7 @@ public class PurpurWorldConfig { +@@ -309,6 +309,7 @@ public class PurpurWorldConfig { public boolean totemOfUndyingWorksInInventory = false; public boolean playerFixStuckPortal = false; public boolean creativeOnePunch = false; @@ -29,7 +29,7 @@ index 1a6c40ce6f3ee5e48280827952db51d00bae3908..469f61aa99f73cdae0fd9fd58c788c4e private void playerSettings() { idleTimeoutKick = getBoolean("gameplay-mechanics.player.idle-timeout.kick-if-idle", idleTimeoutKick); idleTimeoutTickNearbyEntities = getBoolean("gameplay-mechanics.player.idle-timeout.tick-nearby-entities", idleTimeoutTickNearbyEntities); -@@ -324,6 +325,7 @@ public class PurpurWorldConfig { +@@ -323,6 +324,7 @@ public class PurpurWorldConfig { totemOfUndyingWorksInInventory = getBoolean("gameplay-mechanics.player.totem-of-undying-works-in-inventory", totemOfUndyingWorksInInventory); playerFixStuckPortal = getBoolean("gameplay-mechanics.player.fix-stuck-in-portal", playerFixStuckPortal); creativeOnePunch = getBoolean("gameplay-mechanics.player.one-punch-in-creative", creativeOnePunch); diff --git a/patches/server/0167-Config-Enderman-aggressiveness-towards-Endermites.patch b/patches/server/0167-Config-Enderman-aggressiveness-towards-Endermites.patch index 62e82b0c0..8615efb43 100644 --- a/patches/server/0167-Config-Enderman-aggressiveness-towards-Endermites.patch +++ b/patches/server/0167-Config-Enderman-aggressiveness-towards-Endermites.patch @@ -18,10 +18,10 @@ index 3907b7cb559dabdd3cc347678d42071215c66a6c..e8779b23b5e1a399dde19fc66d820101 } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 469f61aa99f73cdae0fd9fd58c788c4e6c981e0b..b3914c0665a11f85aa7e072a46ba2cab923d1665 100644 +index 5d3b592ea596717101f791222ca2520e0dd4cf06..64024cfdd153dd0aa2cd095dd3c30172609f9bce 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -917,6 +917,8 @@ public class PurpurWorldConfig { +@@ -916,6 +916,8 @@ public class PurpurWorldConfig { public boolean endermanDespawnEvenWithBlock = false; public boolean endermanBypassMobGriefing = false; public boolean endermanTakeDamageFromWater = true; @@ -30,7 +30,7 @@ index 469f61aa99f73cdae0fd9fd58c788c4e6c981e0b..b3914c0665a11f85aa7e072a46ba2cab private void endermanSettings() { endermanRidable = getBoolean("mobs.enderman.ridable", endermanRidable); endermanRidableInWater = getBoolean("mobs.enderman.ridable-in-water", endermanRidableInWater); -@@ -925,11 +927,17 @@ public class PurpurWorldConfig { +@@ -924,11 +926,17 @@ public class PurpurWorldConfig { set("mobs.enderman.attributes.max-health", null); set("mobs.enderman.attributes.max_health", oldValue); } diff --git a/patches/server/0168-Config-to-ignore-Dragon-Head-wearers-and-stare-aggro.patch b/patches/server/0168-Config-to-ignore-Dragon-Head-wearers-and-stare-aggro.patch index 8d2e4a31d..49598e7de 100644 --- a/patches/server/0168-Config-to-ignore-Dragon-Head-wearers-and-stare-aggro.patch +++ b/patches/server/0168-Config-to-ignore-Dragon-Head-wearers-and-stare-aggro.patch @@ -20,10 +20,10 @@ index e8779b23b5e1a399dde19fc66d820101d61f36bc..ba61f78874d8578b862f317fe00a3162 } else { Vec3 vec3d = player.getViewVector(1.0F).normalize(); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index b3914c0665a11f85aa7e072a46ba2cab923d1665..7c8cc0b04c6704a691b8ecf110454f64680921e3 100644 +index 64024cfdd153dd0aa2cd095dd3c30172609f9bce..4f4b0900b89662b2424b55b0b00354f451dcd19f 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -919,6 +919,8 @@ public class PurpurWorldConfig { +@@ -918,6 +918,8 @@ public class PurpurWorldConfig { public boolean endermanTakeDamageFromWater = true; public boolean endermanAggroEndermites = true; public boolean endermanAggroEndermitesOnlyIfPlayerSpawned = false; @@ -32,7 +32,7 @@ index b3914c0665a11f85aa7e072a46ba2cab923d1665..7c8cc0b04c6704a691b8ecf110454f64 private void endermanSettings() { endermanRidable = getBoolean("mobs.enderman.ridable", endermanRidable); endermanRidableInWater = getBoolean("mobs.enderman.ridable-in-water", endermanRidableInWater); -@@ -938,6 +940,8 @@ public class PurpurWorldConfig { +@@ -937,6 +939,8 @@ public class PurpurWorldConfig { endermanTakeDamageFromWater = getBoolean("mobs.enderman.takes-damage-from-water", endermanTakeDamageFromWater); endermanAggroEndermites = getBoolean("mobs.enderman.aggressive-towards-endermites", endermanAggroEndermites); endermanAggroEndermitesOnlyIfPlayerSpawned = getBoolean("mobs.enderman.aggressive-towards-endermites-only-spawned-by-player-thrown-ender-pearls", endermanAggroEndermitesOnlyIfPlayerSpawned); diff --git a/patches/server/0169-Tick-fluids-config.patch b/patches/server/0169-Tick-fluids-config.patch index 90f7f501b..8debcc9fd 100644 --- a/patches/server/0169-Tick-fluids-config.patch +++ b/patches/server/0169-Tick-fluids-config.patch @@ -36,10 +36,10 @@ index 087601ffdeea97ec4cbb9959607bdcbcbae7c6fa..ad24daa0c727df15dbe0549036290a6c } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 7c8cc0b04c6704a691b8ecf110454f64680921e3..ff0512c6d5735ba2c42b021efd075bf724d6996b 100644 +index 4f4b0900b89662b2424b55b0b00354f451dcd19f..97e52c23492755044cc14e40f149f5bbab5e2b93 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -401,6 +401,7 @@ public class PurpurWorldConfig { +@@ -400,6 +400,7 @@ public class PurpurWorldConfig { public boolean persistentTileEntityDisplayNames = false; public boolean persistentDroppableEntityDisplayNames = false; public boolean projectilesBypassMobGriefing = false; @@ -47,7 +47,7 @@ index 7c8cc0b04c6704a691b8ecf110454f64680921e3..ff0512c6d5735ba2c42b021efd075bf7 public double tridentLoyaltyVoidReturnHeight = 0.0D; public double voidDamageHeight = -64.0D; public double voidDamageDealt = 4.0D; -@@ -421,6 +422,7 @@ public class PurpurWorldConfig { +@@ -420,6 +421,7 @@ public class PurpurWorldConfig { persistentTileEntityDisplayNames = getBoolean("gameplay-mechanics.persistent-tileentity-display-names-and-lore", persistentTileEntityDisplayNames); persistentDroppableEntityDisplayNames = getBoolean("gameplay-mechanics.persistent-droppable-entity-display-names", persistentDroppableEntityDisplayNames); projectilesBypassMobGriefing = getBoolean("gameplay-mechanics.projectiles-bypass-mob-griefing", projectilesBypassMobGriefing); diff --git a/patches/server/0170-Config-to-disable-Llama-caravans.patch b/patches/server/0170-Config-to-disable-Llama-caravans.patch index 0033bff4e..3ceb27561 100644 --- a/patches/server/0170-Config-to-disable-Llama-caravans.patch +++ b/patches/server/0170-Config-to-disable-Llama-caravans.patch @@ -32,10 +32,10 @@ index 93a05b945ac248df0ea7a0b9d7264a9c129c3bcf..8f12851f220bb23102f52f523a4c5d98 this.caravanHead.caravanTail = this; } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index ff0512c6d5735ba2c42b021efd075bf724d6996b..5e9e84bed13ee76859ed10d9c40780ff6e171db7 100644 +index 97e52c23492755044cc14e40f149f5bbab5e2b93..491bd562ba8eb55634047857df98b7aa73f3e743 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1185,6 +1185,7 @@ public class PurpurWorldConfig { +@@ -1184,6 +1184,7 @@ public class PurpurWorldConfig { public double llamaMovementSpeedMin = 0.175D; public double llamaMovementSpeedMax = 0.175D; public int llamaBreedingTicks = 6000; @@ -43,7 +43,7 @@ index ff0512c6d5735ba2c42b021efd075bf724d6996b..5e9e84bed13ee76859ed10d9c40780ff private void llamaSettings() { llamaRidable = getBoolean("mobs.llama.ridable", llamaRidable); llamaRidableInWater = getBoolean("mobs.llama.ridable-in-water", llamaRidableInWater); -@@ -1202,6 +1203,7 @@ public class PurpurWorldConfig { +@@ -1201,6 +1202,7 @@ public class PurpurWorldConfig { llamaMovementSpeedMin = getDouble("mobs.llama.attributes.movement_speed.min", llamaMovementSpeedMin); llamaMovementSpeedMax = getDouble("mobs.llama.attributes.movement_speed.max", llamaMovementSpeedMax); llamaBreedingTicks = getInt("mobs.llama.breeding-delay-ticks", llamaBreedingTicks); diff --git a/patches/server/0171-Config-to-make-Creepers-explode-on-death.patch b/patches/server/0171-Config-to-make-Creepers-explode-on-death.patch index b72e3428a..19779248e 100644 --- a/patches/server/0171-Config-to-make-Creepers-explode-on-death.patch +++ b/patches/server/0171-Config-to-make-Creepers-explode-on-death.patch @@ -50,10 +50,10 @@ index c550b8c19837ed9bf730a3eb777bc00de4e7ceb2..02494dcc8a342f65b2855612aebeb019 private void spawnLingeringCloud() { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 5e9e84bed13ee76859ed10d9c40780ff6e171db7..29b4a9648347ab354554d1526f38d6959af48e87 100644 +index 491bd562ba8eb55634047857df98b7aa73f3e743..6c0159cb30850967b0dadf948ad1cdebcca3cfb3 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -792,6 +792,7 @@ public class PurpurWorldConfig { +@@ -791,6 +791,7 @@ public class PurpurWorldConfig { public double creeperChargedChance = 0.0D; public boolean creeperAllowGriefing = true; public boolean creeperBypassMobGriefing = false; @@ -61,7 +61,7 @@ index 5e9e84bed13ee76859ed10d9c40780ff6e171db7..29b4a9648347ab354554d1526f38d695 private void creeperSettings() { creeperRidable = getBoolean("mobs.creeper.ridable", creeperRidable); creeperRidableInWater = getBoolean("mobs.creeper.ridable-in-water", creeperRidableInWater); -@@ -804,6 +805,7 @@ public class PurpurWorldConfig { +@@ -803,6 +804,7 @@ public class PurpurWorldConfig { creeperChargedChance = getDouble("mobs.creeper.naturally-charged-chance", creeperChargedChance); creeperAllowGriefing = getBoolean("mobs.creeper.allow-griefing", creeperAllowGriefing); creeperBypassMobGriefing = getBoolean("mobs.creeper.bypass-mob-griefing", creeperBypassMobGriefing); diff --git a/patches/server/0172-Configurable-ravager-griefable-blocks-list.patch b/patches/server/0172-Configurable-ravager-griefable-blocks-list.patch index c3a57a3f1..25a0bd805 100644 --- a/patches/server/0172-Configurable-ravager-griefable-blocks-list.patch +++ b/patches/server/0172-Configurable-ravager-griefable-blocks-list.patch @@ -31,10 +31,10 @@ index 3c51e6d419a244b9270119590aa299527163c331..b466b0430dd94777975a1e7ab9792166 } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 29b4a9648347ab354554d1526f38d6959af48e87..1146a4de8dbfffec741b52c103563f67f7ed50b2 100644 +index 6c0159cb30850967b0dadf948ad1cdebcca3cfb3..5d88c7b57ddc05ef7a84c3f0075cca87c61bb14b 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1495,6 +1495,7 @@ public class PurpurWorldConfig { +@@ -1494,6 +1494,7 @@ public class PurpurWorldConfig { public boolean ravagerRidableInWater = false; public double ravagerMaxHealth = 100.0D; public boolean ravagerBypassMobGriefing = false; @@ -42,7 +42,7 @@ index 29b4a9648347ab354554d1526f38d6959af48e87..1146a4de8dbfffec741b52c103563f67 private void ravagerSettings() { ravagerRidable = getBoolean("mobs.ravager.ridable", ravagerRidable); ravagerRidableInWater = getBoolean("mobs.ravager.ridable-in-water", ravagerRidableInWater); -@@ -1505,6 +1506,23 @@ public class PurpurWorldConfig { +@@ -1504,6 +1505,23 @@ public class PurpurWorldConfig { } ravagerMaxHealth = getDouble("mobs.ravager.attributes.max_health", ravagerMaxHealth); ravagerBypassMobGriefing = getBoolean("mobs.ravager.bypass-mob-griefing", ravagerBypassMobGriefing); diff --git a/patches/server/0173-Sneak-to-bulk-process-composter.patch b/patches/server/0173-Sneak-to-bulk-process-composter.patch index d6f331815..5dad32b11 100644 --- a/patches/server/0173-Sneak-to-bulk-process-composter.patch +++ b/patches/server/0173-Sneak-to-bulk-process-composter.patch @@ -62,10 +62,10 @@ index 4c9ae6bdb2f0358798f84928271a2d783dcba7b4..47bf769a031ae39cc72d2191195d1378 int i = (Integer) iblockdata.getValue(ComposterBlock.LEVEL); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 1146a4de8dbfffec741b52c103563f67f7ed50b2..c05851072c293e47fc349c2137b41e969ad01be3 100644 +index 5d88c7b57ddc05ef7a84c3f0075cca87c61bb14b..6c3f0f2e53d32959bef2f41571d6171760abb2e7 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -485,6 +485,11 @@ public class PurpurWorldConfig { +@@ -484,6 +484,11 @@ public class PurpurWorldConfig { chestOpenWithBlockOnTop = getBoolean("blocks.chest.open-with-solid-block-on-top", chestOpenWithBlockOnTop); } diff --git a/patches/server/0174-Config-for-skipping-night.patch b/patches/server/0174-Config-for-skipping-night.patch index 0c359bd07..d0517fee9 100644 --- a/patches/server/0174-Config-for-skipping-night.patch +++ b/patches/server/0174-Config-for-skipping-night.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Config for skipping night diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 276fb2b9fc9d33dc2157903e799178168c69bc88..68255224e72ae5e7e5eec88c2c29be0dfda64147 100644 +index 9abc5063efe95a48a70045d8cedc15ab245ee65c..57a29f286815ee20abc87c0dffa0c6d682adaf35 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -761,7 +761,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl +@@ -579,7 +579,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl // CraftBukkit end i = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE); @@ -18,10 +18,10 @@ index 276fb2b9fc9d33dc2157903e799178168c69bc88..68255224e72ae5e7e5eec88c2c29be0d long l = this.levelData.getDayTime() + 24000L; TimeSkipEvent event = new TimeSkipEvent(this.getWorld(), TimeSkipEvent.SkipReason.NIGHT_SKIP, (l - l % 24000L) - this.getDayTime()); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index c05851072c293e47fc349c2137b41e969ad01be3..45880d44c2f274d1f253872a324838ada0cc82ec 100644 +index 6c3f0f2e53d32959bef2f41571d6171760abb2e7..a89dca8d44d9d005caaab060aa9e0e04ba51462e 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -311,6 +311,7 @@ public class PurpurWorldConfig { +@@ -310,6 +310,7 @@ public class PurpurWorldConfig { public boolean playerFixStuckPortal = false; public boolean creativeOnePunch = false; public boolean playerSleepNearMonsters = false; @@ -29,7 +29,7 @@ index c05851072c293e47fc349c2137b41e969ad01be3..45880d44c2f274d1f253872a324838ad private void playerSettings() { idleTimeoutKick = getBoolean("gameplay-mechanics.player.idle-timeout.kick-if-idle", idleTimeoutKick); idleTimeoutTickNearbyEntities = getBoolean("gameplay-mechanics.player.idle-timeout.tick-nearby-entities", idleTimeoutTickNearbyEntities); -@@ -326,6 +327,7 @@ public class PurpurWorldConfig { +@@ -325,6 +326,7 @@ public class PurpurWorldConfig { playerFixStuckPortal = getBoolean("gameplay-mechanics.player.fix-stuck-in-portal", playerFixStuckPortal); creativeOnePunch = getBoolean("gameplay-mechanics.player.one-punch-in-creative", creativeOnePunch); playerSleepNearMonsters = getBoolean("gameplay-mechanics.player.sleep-ignore-nearby-mobs", playerSleepNearMonsters); diff --git a/patches/server/0175-Add-config-for-villager-trading.patch b/patches/server/0175-Add-config-for-villager-trading.patch index acabdb347..0020c0d4e 100644 --- a/patches/server/0175-Add-config-for-villager-trading.patch +++ b/patches/server/0175-Add-config-for-villager-trading.patch @@ -31,10 +31,10 @@ index c48935d35a6141c41db22e3ec172d5994fd317a2..fa4644c11cbb252734a6f5dc21c861d2 this.openTradingScreen(player, this.getDisplayName(), 1); } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 45880d44c2f274d1f253872a324838ada0cc82ec..09e2d114105e177cc2cb291b064718333cd13f6a 100644 +index a89dca8d44d9d005caaab060aa9e0e04ba51462e..7c796c99076f510533462d46122b41a3b4128945 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1839,6 +1839,7 @@ public class PurpurWorldConfig { +@@ -1838,6 +1838,7 @@ public class PurpurWorldConfig { public boolean villagerClericsFarmWarts = false; public boolean villagerClericFarmersThrowWarts = true; public boolean villagerBypassMobGriefing = false; @@ -42,7 +42,7 @@ index 45880d44c2f274d1f253872a324838ada0cc82ec..09e2d114105e177cc2cb291b06471833 private void villagerSettings() { villagerRidable = getBoolean("mobs.villager.ridable", villagerRidable); villagerRidableInWater = getBoolean("mobs.villager.ridable-in-water", villagerRidableInWater); -@@ -1866,6 +1867,7 @@ public class PurpurWorldConfig { +@@ -1865,6 +1866,7 @@ public class PurpurWorldConfig { villagerClericsFarmWarts = getBoolean("mobs.villager.clerics-farm-warts", villagerClericsFarmWarts); villagerClericFarmersThrowWarts = getBoolean("mobs.villager.cleric-wart-farmers-throw-warts-at-villagers", villagerClericFarmersThrowWarts); villagerBypassMobGriefing = getBoolean("mobs.villager.bypass-mob-griefing", villagerBypassMobGriefing); @@ -50,7 +50,7 @@ index 45880d44c2f274d1f253872a324838ada0cc82ec..09e2d114105e177cc2cb291b06471833 } public boolean vindicatorRidable = false; -@@ -1889,6 +1891,7 @@ public class PurpurWorldConfig { +@@ -1888,6 +1890,7 @@ public class PurpurWorldConfig { public double wanderingTraderMaxHealth = 20.0D; public boolean wanderingTraderFollowEmeraldBlock = false; public boolean wanderingTraderCanBeLeashed = false; @@ -58,7 +58,7 @@ index 45880d44c2f274d1f253872a324838ada0cc82ec..09e2d114105e177cc2cb291b06471833 private void wanderingTraderSettings() { wanderingTraderRidable = getBoolean("mobs.wandering_trader.ridable", wanderingTraderRidable); wanderingTraderRidableInWater = getBoolean("mobs.wandering_trader.ridable-in-water", wanderingTraderRidableInWater); -@@ -1900,6 +1903,7 @@ public class PurpurWorldConfig { +@@ -1899,6 +1902,7 @@ public class PurpurWorldConfig { wanderingTraderMaxHealth = getDouble("mobs.wandering_trader.attributes.max_health", wanderingTraderMaxHealth); wanderingTraderFollowEmeraldBlock = getBoolean("mobs.wandering_trader.follow-emerald-blocks", wanderingTraderFollowEmeraldBlock); wanderingTraderCanBeLeashed = getBoolean("mobs.wandering_trader.can-be-leashed", wanderingTraderCanBeLeashed); diff --git a/patches/server/0177-Drowning-Settings.patch b/patches/server/0177-Drowning-Settings.patch index 8f0c1b74f..82d8ad8c5 100644 --- a/patches/server/0177-Drowning-Settings.patch +++ b/patches/server/0177-Drowning-Settings.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Drowning Settings diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 1f06a8b4102d8736755e052f03d3bfb0bbf867a1..51dcd696413efe9c2e4fbf7536b5f65fd7806f0f 100644 +index e87e30d6ff41ca1097b61729a8f3f678119d6007..fe8fbb95489b2f79dff73c5fce064a90708e208b 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2932,7 +2932,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n +@@ -2743,7 +2743,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n } public int getMaxAirSupply() { @@ -18,7 +18,7 @@ index 1f06a8b4102d8736755e052f03d3bfb0bbf867a1..51dcd696413efe9c2e4fbf7536b5f65f public int getAirSupply() { diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index c123e723d4fc3202eb7a4c74a356ffcde19e2ba5..0a50c0d3450430de9b6251aa65a8bfc8633a8502 100644 +index 4dd6b5745ca42a067f00d2fc24056e97b242a5f7..784124b31d273f0eecb2798b6758bb4eda880544 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -425,7 +425,7 @@ public abstract class LivingEntity extends Entity { @@ -40,10 +40,10 @@ index c123e723d4fc3202eb7a4c74a356ffcde19e2ba5..0a50c0d3450430de9b6251aa65a8bfc8 } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 09e2d114105e177cc2cb291b064718333cd13f6a..ed5d3690aff4583b9d1cdc2e7fe3d18330cd06ca 100644 +index 7c796c99076f510533462d46122b41a3b4128945..ab723a14cc1f0db42a4f5d34674e27cee6f5702f 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -121,6 +121,15 @@ public class PurpurWorldConfig { +@@ -120,6 +120,15 @@ public class PurpurWorldConfig { nighttimeTicks = getInt("gameplay-mechanics.daylight-cycle-ticks.nighttime", nighttimeTicks); } diff --git a/patches/server/0178-Break-individual-slabs-when-sneaking.patch b/patches/server/0178-Break-individual-slabs-when-sneaking.patch index a26176d31..56a570c8f 100644 --- a/patches/server/0178-Break-individual-slabs-when-sneaking.patch +++ b/patches/server/0178-Break-individual-slabs-when-sneaking.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Break individual slabs when sneaking diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index b2c8cae1a777cd63a35ed1340caf205b1b3bb0ad..11270c763dc5e260074260e10a6dd9a9b7a09c8f 100644 +index e572088cad8b9e09b1d64f7971bacac2f10c5b17..50c0b4cfe9dab9bd1c1e1f3f9a2b0aa970931cc1 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -419,6 +419,7 @@ public class ServerPlayerGameMode { +@@ -387,6 +387,7 @@ public class ServerPlayerGameMode { } return false; } @@ -47,10 +47,10 @@ index eb7f8907bb362c0461194bbaf62917ce71c669f3..89f5e0d26500f1806dff9f91390546cd + // Purpur end } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index ed5d3690aff4583b9d1cdc2e7fe3d18330cd06ca..f1864d45312b69f2089c6e2043efb674606952b0 100644 +index ab723a14cc1f0db42a4f5d34674e27cee6f5702f..34728bb981d39067537c7eb73024e72c2274bb6c 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -612,6 +612,11 @@ public class PurpurWorldConfig { +@@ -611,6 +611,11 @@ public class PurpurWorldConfig { signRightClickEdit = getBoolean("blocks.sign.right-click-edit", signRightClickEdit); } diff --git a/patches/server/0179-Config-to-disable-hostile-mob-spawn-on-ice.patch b/patches/server/0179-Config-to-disable-hostile-mob-spawn-on-ice.patch index 278c775ea..41054e9f7 100644 --- a/patches/server/0179-Config-to-disable-hostile-mob-spawn-on-ice.patch +++ b/patches/server/0179-Config-to-disable-hostile-mob-spawn-on-ice.patch @@ -22,10 +22,10 @@ index fc34cfa8bfb3b82a8e1b28d261f0e901d837467e..35d47bb0d8c4a2b8374564133f040899 return false; } else { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index f1864d45312b69f2089c6e2043efb674606952b0..1c1f536a09b11c98c20a3875d7cee225963cd110 100644 +index 34728bb981d39067537c7eb73024e72c2274bb6c..4ae7478bc2525f4d0867abbda1de7b0e56b2edf9 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -564,8 +564,12 @@ public class PurpurWorldConfig { +@@ -563,8 +563,12 @@ public class PurpurWorldConfig { } public boolean snowOnBlueIce = true; diff --git a/patches/server/0180-Config-to-show-Armor-Stand-arms-on-spawn.patch b/patches/server/0180-Config-to-show-Armor-Stand-arms-on-spawn.patch index ded2eb6cb..a4c9c629b 100644 --- a/patches/server/0180-Config-to-show-Armor-Stand-arms-on-spawn.patch +++ b/patches/server/0180-Config-to-show-Armor-Stand-arms-on-spawn.patch @@ -17,10 +17,10 @@ index d119f8ab447bc17deabc494463de496161c9b126..c9a44a4765f43b9c0247ed1005f4c134 public ArmorStand(Level world, double x, double y, double z) { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 1c1f536a09b11c98c20a3875d7cee225963cd110..37a163e436548c6ca58bdc017bf311abff1146f3 100644 +index 4ae7478bc2525f4d0867abbda1de7b0e56b2edf9..d06a85856381f1290d53d1c74bc4c8af45b80662 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -105,6 +105,7 @@ public class PurpurWorldConfig { +@@ -104,6 +104,7 @@ public class PurpurWorldConfig { public boolean armorstandMovement = true; public boolean armorstandWaterMovement = true; public boolean armorstandWaterFence = true; @@ -28,7 +28,7 @@ index 1c1f536a09b11c98c20a3875d7cee225963cd110..37a163e436548c6ca58bdc017bf311ab private void armorstandSettings() { armorstandStepHeight = (float) getDouble("gameplay-mechanics.armorstand.step-height", armorstandStepHeight); armorstandSetNameVisible = getBoolean("gameplay-mechanics.armorstand.set-name-visible-when-placing-with-custom-name", armorstandSetNameVisible); -@@ -112,6 +113,7 @@ public class PurpurWorldConfig { +@@ -111,6 +112,7 @@ public class PurpurWorldConfig { armorstandMovement = getBoolean("gameplay-mechanics.armorstand.can-movement-tick", armorstandMovement); armorstandWaterMovement = getBoolean("gameplay-mechanics.armorstand.can-move-in-water", armorstandWaterMovement); armorstandWaterFence = getBoolean("gameplay-mechanics.armorstand.can-move-in-water-over-fence", armorstandWaterFence); diff --git a/patches/server/0181-Option-to-make-doors-require-redstone.patch b/patches/server/0181-Option-to-make-doors-require-redstone.patch index c077302e9..89c008d81 100644 --- a/patches/server/0181-Option-to-make-doors-require-redstone.patch +++ b/patches/server/0181-Option-to-make-doors-require-redstone.patch @@ -67,10 +67,10 @@ index c903a1a8d2234bb0fa354d1c44ff3ab2275b04c7..d01e4064a772710c1383927e0848b9b3 + // Purpur end } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 37a163e436548c6ca58bdc017bf311abff1146f3..91a4c067df251a67f858acc7b56ffc45b5c20b36 100644 +index d06a85856381f1290d53d1c74bc4c8af45b80662..30188b683f1ed9f71180930b7920b83dea1584c8 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -510,6 +510,16 @@ public class PurpurWorldConfig { +@@ -509,6 +509,16 @@ public class PurpurWorldConfig { dispenserPlaceAnvils = getBoolean("blocks.dispenser.place-anvils", dispenserPlaceAnvils); } diff --git a/patches/server/0182-Config-to-allow-for-unsafe-enchants.patch b/patches/server/0182-Config-to-allow-for-unsafe-enchants.patch index d43fd7cdb..6107397bd 100644 --- a/patches/server/0182-Config-to-allow-for-unsafe-enchants.patch +++ b/patches/server/0182-Config-to-allow-for-unsafe-enchants.patch @@ -27,10 +27,10 @@ index 514cc0e8805045549eacde6c280859aa2dc4a91d..a3ac6bebcef7b1e1f9c3ebe525656a15 ++i; } else if (targets.size() == 1) { diff --git a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -index 2dbc71a3d76cc87e2683b8f351bd8db04481855e..168f16f3117b6b18279cbe597a32221bc04a0648 100644 +index 410ac71efff92dfa1f1e11895d0f5bf3fca1be17..f380659b261253e327f018ce9b54b15195ad65d7 100644 --- a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java +++ b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -@@ -210,7 +210,7 @@ public class AnvilMenu extends ItemCombinerMenu { +@@ -209,7 +209,7 @@ public class AnvilMenu extends ItemCombinerMenu { int i2 = (Integer) map1.get(enchantment); i2 = l1 == i2 ? i2 + 1 : Math.max(i2, l1); @@ -39,7 +39,7 @@ index 2dbc71a3d76cc87e2683b8f351bd8db04481855e..168f16f3117b6b18279cbe597a32221b if (this.player.getAbilities().instabuild || itemstack.is(Items.ENCHANTED_BOOK)) { flag3 = true; -@@ -222,7 +222,7 @@ public class AnvilMenu extends ItemCombinerMenu { +@@ -221,7 +221,7 @@ public class AnvilMenu extends ItemCombinerMenu { Enchantment enchantment1 = (Enchantment) iterator1.next(); if (enchantment1 != enchantment && !enchantment.isCompatibleWith(enchantment1)) { @@ -48,20 +48,20 @@ index 2dbc71a3d76cc87e2683b8f351bd8db04481855e..168f16f3117b6b18279cbe597a32221b ++i; } } -@@ -333,7 +333,7 @@ public class AnvilMenu extends ItemCombinerMenu { +@@ -332,7 +332,7 @@ public class AnvilMenu extends ItemCombinerMenu { org.bukkit.craftbukkit.event.CraftEventFactory.callPrepareAnvilEvent(this.getBukkitView(), itemstack1); // CraftBukkit this.broadcastChanges(); // Purpur start - if (canDoUnsafeEnchants && itemstack1 != ItemStack.EMPTY) { + if ((canDoUnsafeEnchants || net.pl3x.purpur.PurpurConfig.allowUnsafeEnchants) && itemstack1 != ItemStack.EMPTY) { - ((ServerPlayer) player).connection.send(new ClientboundContainerSetSlotPacket(containerId, 2, itemstack1)); + ((ServerPlayer) player).connection.send(new ClientboundContainerSetSlotPacket(containerId, incrementStateId(), 2, itemstack1)); ((ServerPlayer) player).connection.send(new ClientboundContainerSetDataPacket(containerId, 0, cost.get())); } diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 5bd25f8b8a554b965665b3f5686c14189b51f28e..4be9924556e0f447dbe6a53c2d4cb7fb89dac455 100644 +index 18da9a82fb2ca4b9b8d80e37df29c76c8214c37f..e61be8f9a9b26a9daa3f70ec028ed689dfd43c93 100644 --- a/src/main/java/net/minecraft/world/item/ItemStack.java +++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -1140,6 +1140,12 @@ public final class ItemStack { +@@ -1137,6 +1137,12 @@ public final class ItemStack { return this.tag != null && this.tag.contains("Enchantments", 9) ? !this.tag.getList("Enchantments", 10).isEmpty() : false; } diff --git a/patches/server/0183-Configurable-sponge-absorption.patch b/patches/server/0183-Configurable-sponge-absorption.patch index 2abf374da..2abcde6e8 100644 --- a/patches/server/0183-Configurable-sponge-absorption.patch +++ b/patches/server/0183-Configurable-sponge-absorption.patch @@ -43,10 +43,10 @@ index 1ef8eadd4e59f2e5d2bbd84f6f9bcf37b59db5bd..5b10e1110f938745c8f9ed0b55960566 } } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 91a4c067df251a67f858acc7b56ffc45b5c20b36..67dff8cf85086a2123fa6076885b4d6123dedeb8 100644 +index 30188b683f1ed9f71180930b7920b83dea1584c8..fd356e3431a3be4992442d9cb71a467d5a521413 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -638,6 +638,13 @@ public class PurpurWorldConfig { +@@ -637,6 +637,13 @@ public class PurpurWorldConfig { spawnerDeactivateByRedstone = getBoolean("blocks.spawner.deactivate-by-redstone", spawnerDeactivateByRedstone); } diff --git a/patches/server/0184-Projectile-offset-config.patch b/patches/server/0184-Projectile-offset-config.patch index de940aa17..3d5edeb2b 100644 --- a/patches/server/0184-Projectile-offset-config.patch +++ b/patches/server/0184-Projectile-offset-config.patch @@ -96,10 +96,10 @@ index 3f53dc8f250ad3f7616ce7ef0a2353caa0ab1879..29130aa9673a3956030f3e43b784ac46 entitythrowntrident.pickup = AbstractArrow.Pickup.CREATIVE_ONLY; } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 67dff8cf85086a2123fa6076885b4d6123dedeb8..90023fad0e5fd2a209041676bfb70cfcd7b73ef7 100644 +index fd356e3431a3be4992442d9cb71a467d5a521413..1d3fedefc7a6dfbb0182d8cad5341f8682b65cf8 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -375,6 +375,23 @@ public class PurpurWorldConfig { +@@ -374,6 +374,23 @@ public class PurpurWorldConfig { witherSkullDespawnRate = getInt("gameplay-mechanics.projectile-despawn-rates.wither_skull", witherSkullDespawnRate); } diff --git a/patches/server/0185-Config-for-powered-rail-activation-distance.patch b/patches/server/0185-Config-for-powered-rail-activation-distance.patch index cb7f3a807..0478cc2e3 100644 --- a/patches/server/0185-Config-for-powered-rail-activation-distance.patch +++ b/patches/server/0185-Config-for-powered-rail-activation-distance.patch @@ -18,10 +18,10 @@ index 7fddb6fa8fd30ef88346a59f7867aae792f13772..40893e71fe8447b695350273bef9623b } else { int j = pos.getX(); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 90023fad0e5fd2a209041676bfb70cfcd7b73ef7..7de5f110939ef9a4b3ae0629c47d38fee741de0a 100644 +index 1d3fedefc7a6dfbb0182d8cad5341f8682b65cf8..9df8de15eb37c248ad5240564ad4213ae9445a9e 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -650,6 +650,11 @@ public class PurpurWorldConfig { +@@ -649,6 +649,11 @@ public class PurpurWorldConfig { slabHalfBreak = getBoolean("blocks.slab.break-individual-slabs-when-sneaking", slabHalfBreak); } diff --git a/patches/server/0186-Piglin-portal-spawn-modifier.patch b/patches/server/0186-Piglin-portal-spawn-modifier.patch index 48768b30c..1a5dc48cc 100644 --- a/patches/server/0186-Piglin-portal-spawn-modifier.patch +++ b/patches/server/0186-Piglin-portal-spawn-modifier.patch @@ -31,10 +31,10 @@ index fef1027829c44957e23c0a121033bfb7640d06f0..c42349d0f6b0025525278295b36f4030 pos = pos.below(); } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 7de5f110939ef9a4b3ae0629c47d38fee741de0a..c145e68a7ecc9d1c2077f46c1cb6007921a56e4d 100644 +index 9df8de15eb37c248ad5240564ad4213ae9445a9e..b6964ab54d20fb6d5579db2e95cf4aaa5f563439 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1460,6 +1460,7 @@ public class PurpurWorldConfig { +@@ -1459,6 +1459,7 @@ public class PurpurWorldConfig { public boolean piglinRidableInWater = false; public double piglinMaxHealth = 16.0D; public boolean piglinBypassMobGriefing = false; @@ -42,7 +42,7 @@ index 7de5f110939ef9a4b3ae0629c47d38fee741de0a..c145e68a7ecc9d1c2077f46c1cb60079 private void piglinSettings() { piglinRidable = getBoolean("mobs.piglin.ridable", piglinRidable); piglinRidableInWater = getBoolean("mobs.piglin.ridable-in-water", piglinRidableInWater); -@@ -1470,6 +1471,7 @@ public class PurpurWorldConfig { +@@ -1469,6 +1470,7 @@ public class PurpurWorldConfig { } piglinMaxHealth = getDouble("mobs.piglin.attributes.max_health", piglinMaxHealth); piglinBypassMobGriefing = getBoolean("mobs.piglin.bypass-mob-griefing", piglinBypassMobGriefing); diff --git a/patches/server/0188-Configurable-damage-settings-for-magma-blocks.patch b/patches/server/0188-Configurable-damage-settings-for-magma-blocks.patch index 134edf738..e8a63ebfa 100644 --- a/patches/server/0188-Configurable-damage-settings-for-magma-blocks.patch +++ b/patches/server/0188-Configurable-damage-settings-for-magma-blocks.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Configurable damage settings for magma blocks diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 51dcd696413efe9c2e4fbf7536b5f65fd7806f0f..aba462649a21ea68c83bd80c61ab9bf63eeb1a93 100644 +index fe8fbb95489b2f79dff73c5fce064a90708e208b..7fae7966027d19dd9757b4761401c21d81f051ac 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -1001,7 +1001,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n +@@ -897,7 +897,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n } // CraftBukkit end @@ -31,10 +31,10 @@ index 5d844ed98b916298a657d5e9766ab7f383a304e0..0129460ce1ca199a47b6657f824c75fd entity.hurt(DamageSource.HOT_FLOOR, 1.0F); org.bukkit.craftbukkit.event.CraftEventFactory.blockDamage = null; // CraftBukkit diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index c145e68a7ecc9d1c2077f46c1cb6007921a56e4d..9032f55686979f29707670670ad65c82341ae524 100644 +index b6964ab54d20fb6d5579db2e95cf4aaa5f563439..8f0f033900531b8dfd625da9ad83c27bc0ac6d44 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -617,6 +617,13 @@ public class PurpurWorldConfig { +@@ -616,6 +616,13 @@ public class PurpurWorldConfig { kelpMaxGrowthAge = getInt("blocks.kelp.max-growth-age", kelpMaxGrowthAge); } diff --git a/patches/server/0189-Config-for-wither-explosion-radius.patch b/patches/server/0189-Config-for-wither-explosion-radius.patch index b730a4189..7f5134b35 100644 --- a/patches/server/0189-Config-for-wither-explosion-radius.patch +++ b/patches/server/0189-Config-for-wither-explosion-radius.patch @@ -18,10 +18,10 @@ index 430aa10101d9f21561155941ff24441fd0c4103a..de91f0dd7f9a62e5a96b4cc3e4f505ec if (!event.isCancelled()) { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 9032f55686979f29707670670ad65c82341ae524..d13e44d0e600a423f6b420c02bb435a703e336c9 100644 +index 8f0f033900531b8dfd625da9ad83c27bc0ac6d44..2f7476a72575485836a154b81333cc092d22ab70 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1996,6 +1996,7 @@ public class PurpurWorldConfig { +@@ -1995,6 +1995,7 @@ public class PurpurWorldConfig { public int witherHealthRegenDelay = 20; public boolean witherBypassMobGriefing = false; public boolean witherCanRideVehicles = false; @@ -29,7 +29,7 @@ index 9032f55686979f29707670670ad65c82341ae524..d13e44d0e600a423f6b420c02bb435a7 private void witherSettings() { witherRidable = getBoolean("mobs.wither.ridable", witherRidable); witherRidableInWater = getBoolean("mobs.wither.ridable-in-water", witherRidableInWater); -@@ -2014,6 +2015,7 @@ public class PurpurWorldConfig { +@@ -2013,6 +2014,7 @@ public class PurpurWorldConfig { witherHealthRegenDelay = getInt("mobs.wither.health-regen-delay", witherHealthRegenDelay); witherBypassMobGriefing = getBoolean("mobs.wither.bypass-mob-griefing", witherBypassMobGriefing); witherCanRideVehicles = getBoolean("mobs.wither.can-ride-vehicles", witherCanRideVehicles); diff --git a/patches/server/0191-Config-for-changing-the-blocks-that-turn-into-dirt-p.patch b/patches/server/0191-Config-for-changing-the-blocks-that-turn-into-dirt-p.patch index 8f4536add..710a7f8bc 100644 --- a/patches/server/0191-Config-for-changing-the-blocks-that-turn-into-dirt-p.patch +++ b/patches/server/0191-Config-for-changing-the-blocks-that-turn-into-dirt-p.patch @@ -18,10 +18,10 @@ index e5562b407bba35ab93bf8bc3c22ac9d45e8353cb..c688bb73cd062f36524cfc231cb691f2 if (blockState2 != null && level.getBlockState(blockPos.above()).isAir()) { level.playSound(player, blockPos, SoundEvents.SHOVEL_FLATTEN, SoundSource.BLOCKS, 1.0F, 1.0F); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index d13e44d0e600a423f6b420c02bb435a703e336c9..78aefc7bf6be3063f7e8f6fc233ac4d2f2b964ef 100644 +index 2f7476a72575485836a154b81333cc092d22ab70..86daa97595ea38e805dff72edc1b1af7b5289bcc 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -392,6 +392,21 @@ public class PurpurWorldConfig { +@@ -391,6 +391,21 @@ public class PurpurWorldConfig { snowballProjectileOffset = getDouble("gameplay-mechanics.projectile-offset.snowball", snowballProjectileOffset); } diff --git a/patches/server/0192-Configurable-piston-push-limit.patch b/patches/server/0192-Configurable-piston-push-limit.patch index d6dd395c1..e10835704 100644 --- a/patches/server/0192-Configurable-piston-push-limit.patch +++ b/patches/server/0192-Configurable-piston-push-limit.patch @@ -36,10 +36,10 @@ index 744d91546d1a810f60a43c15ed74b4158f341a4a..354538daefa603f6df5a139b6bff87db } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 78aefc7bf6be3063f7e8f6fc233ac4d2f2b964ef..b92bc1df5c9848671563a49cfdcc177636ff6b0a 100644 +index 86daa97595ea38e805dff72edc1b1af7b5289bcc..e40f396a176520d0fe1c6919ebb1fd319bc30dce 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -639,6 +639,11 @@ public class PurpurWorldConfig { +@@ -638,6 +638,11 @@ public class PurpurWorldConfig { magmaBlockDamageWithFrostWalker = getBoolean("blocks.magma-block.damage-with-frost-walker", magmaBlockDamageWithFrostWalker); } diff --git a/patches/server/0193-Configurable-broadcast-settings.patch b/patches/server/0193-Configurable-broadcast-settings.patch index 391191017..d6df80f9d 100644 --- a/patches/server/0193-Configurable-broadcast-settings.patch +++ b/patches/server/0193-Configurable-broadcast-settings.patch @@ -17,10 +17,10 @@ index c46df052a5a39d92688f51377ee1f7b5b5b36faa..d7d2a975386cecb0d50b4f7ed37de8ad // Paper end } diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java -index d4be590e81a966eddfdf8c7d9c8849435f29bdcf..745fddc22edefc012d0897a0d79fe2563646fc27 100644 +index 9ce5a27dabb59104d35e7dffb9950b2dfd98611e..33f41de3aabaee37dbd92ad17d8dc65a60fef427 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayer.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -879,6 +879,7 @@ public class ServerPlayer extends Player { +@@ -875,6 +875,7 @@ public class ServerPlayer extends Player { }); Team scoreboardteambase = this.getTeam(); diff --git a/patches/server/0195-Configurable-mob-blindness.patch b/patches/server/0195-Configurable-mob-blindness.patch index d893979ae..fa028c261 100644 --- a/patches/server/0195-Configurable-mob-blindness.patch +++ b/patches/server/0195-Configurable-mob-blindness.patch @@ -6,7 +6,7 @@ Subject: [PATCH] Configurable mob blindness Ported from https://github.com/raltsmc/mobblindness diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 0a50c0d3450430de9b6251aa65a8bfc8633a8502..521a353a1de6573b7e4fcebc673494cdb93d7815 100644 +index 784124b31d273f0eecb2798b6758bb4eda880544..2ece26f762f9764db27bea60b63f2121d1fd4211 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -992,6 +992,17 @@ public abstract class LivingEntity extends Entity { @@ -28,10 +28,10 @@ index 0a50c0d3450430de9b6251aa65a8bfc8633a8502..521a353a1de6573b7e4fcebc673494cd return d0; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index b92bc1df5c9848671563a49cfdcc177636ff6b0a..d52d94699f392494081da802d5efece7831c42d8 100644 +index e40f396a176520d0fe1c6919ebb1fd319bc30dce..8824e8fe1abcecd48baf2fd9792eeba37086df94 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -447,6 +447,7 @@ public class PurpurWorldConfig { +@@ -446,6 +446,7 @@ public class PurpurWorldConfig { public boolean persistentDroppableEntityDisplayNames = false; public boolean projectilesBypassMobGriefing = false; public boolean tickFluids = true; @@ -39,7 +39,7 @@ index b92bc1df5c9848671563a49cfdcc177636ff6b0a..d52d94699f392494081da802d5efece7 public double tridentLoyaltyVoidReturnHeight = 0.0D; public double voidDamageHeight = -64.0D; public double voidDamageDealt = 4.0D; -@@ -468,6 +469,7 @@ public class PurpurWorldConfig { +@@ -467,6 +468,7 @@ public class PurpurWorldConfig { persistentDroppableEntityDisplayNames = getBoolean("gameplay-mechanics.persistent-droppable-entity-display-names", persistentDroppableEntityDisplayNames); projectilesBypassMobGriefing = getBoolean("gameplay-mechanics.projectiles-bypass-mob-griefing", projectilesBypassMobGriefing); tickFluids = getBoolean("gameplay-mechanics.tick-fluids", tickFluids); diff --git a/patches/server/0197-Config-for-health-to-impact-Creeper-explosion-radius.patch b/patches/server/0197-Config-for-health-to-impact-Creeper-explosion-radius.patch index ad7f00366..26756bdd1 100644 --- a/patches/server/0197-Config-for-health-to-impact-Creeper-explosion-radius.patch +++ b/patches/server/0197-Config-for-health-to-impact-Creeper-explosion-radius.patch @@ -21,10 +21,10 @@ index 02494dcc8a342f65b2855612aebeb019095abf65..d66c8866f3c6b1412a6c1876c62e3b29 if (!event.isCancelled()) { this.dead = true; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index d52d94699f392494081da802d5efece7831c42d8..f240da91fc203aa7c99ff0cdfa9f977886527bcd 100644 +index 8824e8fe1abcecd48baf2fd9792eeba37086df94..db42b5d941b047bb5d30923ead77ee7584774d06 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -888,6 +888,7 @@ public class PurpurWorldConfig { +@@ -887,6 +887,7 @@ public class PurpurWorldConfig { public boolean creeperAllowGriefing = true; public boolean creeperBypassMobGriefing = false; public boolean creeperExplodeWhenKilled = false; @@ -32,7 +32,7 @@ index d52d94699f392494081da802d5efece7831c42d8..f240da91fc203aa7c99ff0cdfa9f9778 private void creeperSettings() { creeperRidable = getBoolean("mobs.creeper.ridable", creeperRidable); creeperRidableInWater = getBoolean("mobs.creeper.ridable-in-water", creeperRidableInWater); -@@ -901,6 +902,7 @@ public class PurpurWorldConfig { +@@ -900,6 +901,7 @@ public class PurpurWorldConfig { creeperAllowGriefing = getBoolean("mobs.creeper.allow-griefing", creeperAllowGriefing); creeperBypassMobGriefing = getBoolean("mobs.creeper.bypass-mob-griefing", creeperBypassMobGriefing); creeperExplodeWhenKilled = getBoolean("mobs.creeper.explode-when-killed", creeperExplodeWhenKilled); diff --git a/patches/server/0198-Iron-golem-poppy-calms-anger.patch b/patches/server/0198-Iron-golem-poppy-calms-anger.patch index 1ebebd722..ebea632ee 100644 --- a/patches/server/0198-Iron-golem-poppy-calms-anger.patch +++ b/patches/server/0198-Iron-golem-poppy-calms-anger.patch @@ -17,10 +17,10 @@ index f4e983da7206923ee0b0f984e65a6c2b3a6a8aeb..cea3725009af1bc746a593c4db63ed63 this.goalSelector.addGoal(2, new MoveTowardsTargetGoal(this, 0.9D, 32.0F)); this.goalSelector.addGoal(2, new MoveBackToVillageGoal(this, 0.6D, false)); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index f240da91fc203aa7c99ff0cdfa9f977886527bcd..8fd2a780131cda871c8a9150460a0b45adfa2997 100644 +index db42b5d941b047bb5d30923ead77ee7584774d06..1574291ad301fe27161740d72bbce87d8470217b 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1263,6 +1263,7 @@ public class PurpurWorldConfig { +@@ -1262,6 +1262,7 @@ public class PurpurWorldConfig { public boolean ironGolemRidableInWater = false; public boolean ironGolemCanSwim = false; public double ironGolemMaxHealth = 100.0D; @@ -28,7 +28,7 @@ index f240da91fc203aa7c99ff0cdfa9f977886527bcd..8fd2a780131cda871c8a9150460a0b45 private void ironGolemSettings() { ironGolemRidable = getBoolean("mobs.iron_golem.ridable", ironGolemRidable); ironGolemRidableInWater = getBoolean("mobs.iron_golem.ridable-in-water", ironGolemRidableInWater); -@@ -1273,6 +1274,7 @@ public class PurpurWorldConfig { +@@ -1272,6 +1273,7 @@ public class PurpurWorldConfig { set("mobs.iron_golem.attributes.max_health", oldValue); } ironGolemMaxHealth = getDouble("mobs.iron_golem.attributes.max_health", ironGolemMaxHealth); diff --git a/patches/server/0199-Breedable-parrots.patch b/patches/server/0199-Breedable-parrots.patch index a39588b50..20c571bfb 100644 --- a/patches/server/0199-Breedable-parrots.patch +++ b/patches/server/0199-Breedable-parrots.patch @@ -50,10 +50,10 @@ index 553b0aff0ccc5baf41d5faae1a2fd88249dd5a74..3da2b68fb03a80676d7a5eed271499f6 @Override diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 8fd2a780131cda871c8a9150460a0b45adfa2997..96dc85aac58aa65c7811bd10caaeddb9c94146ba 100644 +index 1574291ad301fe27161740d72bbce87d8470217b..76800bb7a317d63a77532dcb3fc01d19b22ce655 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1399,6 +1399,7 @@ public class PurpurWorldConfig { +@@ -1398,6 +1398,7 @@ public class PurpurWorldConfig { public boolean parrotRidableInWater = false; public double parrotMaxY = 256D; public double parrotMaxHealth = 6.0D; @@ -61,7 +61,7 @@ index 8fd2a780131cda871c8a9150460a0b45adfa2997..96dc85aac58aa65c7811bd10caaeddb9 private void parrotSettings() { parrotRidable = getBoolean("mobs.parrot.ridable", parrotRidable); parrotRidableInWater = getBoolean("mobs.parrot.ridable-in-water", parrotRidableInWater); -@@ -1409,6 +1410,7 @@ public class PurpurWorldConfig { +@@ -1408,6 +1409,7 @@ public class PurpurWorldConfig { set("mobs.parrot.attributes.max_health", oldValue); } parrotMaxHealth = getDouble("mobs.parrot.attributes.max_health", parrotMaxHealth); diff --git a/patches/server/0200-Configurable-powered-rail-boost-modifier.patch b/patches/server/0200-Configurable-powered-rail-boost-modifier.patch index 65c2a4346..27c27d361 100644 --- a/patches/server/0200-Configurable-powered-rail-boost-modifier.patch +++ b/patches/server/0200-Configurable-powered-rail-boost-modifier.patch @@ -18,10 +18,10 @@ index 1e3077a22d9d3d26356b865001dcce81c9c1d7e5..cc57ff699d159a0cc748e91b61d53965 Vec3 vec3d5 = this.getDeltaMovement(); double d21 = vec3d5.x; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 96dc85aac58aa65c7811bd10caaeddb9c94146ba..ac21c3f7924a7e143d92a0987307799c63dbd1b6 100644 +index 76800bb7a317d63a77532dcb3fc01d19b22ce655..e2e2b7e7e6a734f3838021388f7d117b95df2625 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -226,6 +226,7 @@ public class PurpurWorldConfig { +@@ -225,6 +225,7 @@ public class PurpurWorldConfig { public boolean minecartControllableFallDamage = true; public double minecartControllableBaseSpeed = 0.1D; public Map minecartControllableBlockSpeeds = new HashMap<>(); @@ -29,7 +29,7 @@ index 96dc85aac58aa65c7811bd10caaeddb9c94146ba..ac21c3f7924a7e143d92a0987307799c private void minecartSettings() { if (PurpurConfig.version < 12) { boolean oldBool = getBoolean("gameplay-mechanics.controllable-minecarts.place-anywhere", minecartPlaceAnywhere); -@@ -278,6 +279,7 @@ public class PurpurWorldConfig { +@@ -277,6 +278,7 @@ public class PurpurWorldConfig { set("gameplay-mechanics.minecart.controllable.block-speed.grass_block", 0.3D); set("gameplay-mechanics.minecart.controllable.block-speed.stone", 0.5D); } diff --git a/patches/server/0201-Add-config-change-multiplier-critical-damage-value.patch b/patches/server/0201-Add-config-change-multiplier-critical-damage-value.patch index 6c42003b6..fd6b69e72 100644 --- a/patches/server/0201-Add-config-change-multiplier-critical-damage-value.patch +++ b/patches/server/0201-Add-config-change-multiplier-critical-damage-value.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add config change multiplier critical damage value diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index f4f49b87b615a3c7ef56247896392de93eb1bb0d..dfe78217add616c761ba53fb4999cc6593863d2d 100644 +index 6301f71df9573f91040934c85a8530f2cf2bfdad..6fa69683727f2111571468261ef16f7c0ff5c238 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java +++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1257,7 +1257,7 @@ public abstract class Player extends LivingEntity { +@@ -1248,7 +1248,7 @@ public abstract class Player extends LivingEntity { flag2 = flag2 && !this.isSprinting(); if (flag2) { this.isCritical = true; // Purpur @@ -18,10 +18,10 @@ index f4f49b87b615a3c7ef56247896392de93eb1bb0d..dfe78217add616c761ba53fb4999cc65 f += f1; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index ac21c3f7924a7e143d92a0987307799c63dbd1b6..00887732afbbc616ed6191fcce64565564eed0e1 100644 +index e2e2b7e7e6a734f3838021388f7d117b95df2625..cea35e4b795c660c010a54834c26d4c73115d54d 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -325,6 +325,7 @@ public class PurpurWorldConfig { +@@ -324,6 +324,7 @@ public class PurpurWorldConfig { public boolean creativeOnePunch = false; public boolean playerSleepNearMonsters = false; public boolean playersSkipNight = true; @@ -29,7 +29,7 @@ index ac21c3f7924a7e143d92a0987307799c63dbd1b6..00887732afbbc616ed6191fcce645655 private void playerSettings() { idleTimeoutKick = getBoolean("gameplay-mechanics.player.idle-timeout.kick-if-idle", idleTimeoutKick); idleTimeoutTickNearbyEntities = getBoolean("gameplay-mechanics.player.idle-timeout.tick-nearby-entities", idleTimeoutTickNearbyEntities); -@@ -341,6 +342,7 @@ public class PurpurWorldConfig { +@@ -340,6 +341,7 @@ public class PurpurWorldConfig { creativeOnePunch = getBoolean("gameplay-mechanics.player.one-punch-in-creative", creativeOnePunch); playerSleepNearMonsters = getBoolean("gameplay-mechanics.player.sleep-ignore-nearby-mobs", playerSleepNearMonsters); playersSkipNight = getBoolean("gameplay-mechanics.player.can-skip-night", playersSkipNight); diff --git a/patches/server/0202-Option-to-disable-dragon-egg-teleporting.patch b/patches/server/0202-Option-to-disable-dragon-egg-teleporting.patch index 14347ad4f..55f19e970 100644 --- a/patches/server/0202-Option-to-disable-dragon-egg-teleporting.patch +++ b/patches/server/0202-Option-to-disable-dragon-egg-teleporting.patch @@ -17,10 +17,10 @@ index 78f51f3dd0e7249af69228479da932e9aea982d6..d9d4421f7f316281487828739168cfd6 BlockPos blockposition1 = pos.offset(world.random.nextInt(16) - world.random.nextInt(16), world.random.nextInt(8) - world.random.nextInt(8), world.random.nextInt(16) - world.random.nextInt(16)); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 00887732afbbc616ed6191fcce64565564eed0e1..a8e02eca9fc4aed1bcf9b43bb8f0483f2bdc6dce 100644 +index cea35e4b795c660c010a54834c26d4c73115d54d..8955d05d414b232a9737b02d2330b93509241396 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -558,6 +558,11 @@ public class PurpurWorldConfig { +@@ -557,6 +557,11 @@ public class PurpurWorldConfig { }); } diff --git a/patches/server/0204-Make-anvil-cumulative-cost-configurable.patch b/patches/server/0204-Make-anvil-cumulative-cost-configurable.patch index c5a86ec77..e8d8f46c2 100644 --- a/patches/server/0204-Make-anvil-cumulative-cost-configurable.patch +++ b/patches/server/0204-Make-anvil-cumulative-cost-configurable.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Make anvil cumulative cost configurable diff --git a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -index 168f16f3117b6b18279cbe597a32221bc04a0648..5c1a72a402895f2166a3f36691253d806a78261f 100644 +index f380659b261253e327f018ce9b54b15195ad65d7..080449cf3aa0394bd179e26fda8d7248f488ea8d 100644 --- a/src/main/java/net/minecraft/world/inventory/AnvilMenu.java +++ b/src/main/java/net/minecraft/world/inventory/AnvilMenu.java -@@ -342,7 +342,7 @@ public class AnvilMenu extends ItemCombinerMenu { +@@ -341,7 +341,7 @@ public class AnvilMenu extends ItemCombinerMenu { } public static int calculateIncreasedRepairCost(int cost) { diff --git a/patches/server/0205-ShulkerBox-allow-oversized-stacks.patch b/patches/server/0205-ShulkerBox-allow-oversized-stacks.patch index 05fb9e2d6..fbc1c5542 100644 --- a/patches/server/0205-ShulkerBox-allow-oversized-stacks.patch +++ b/patches/server/0205-ShulkerBox-allow-oversized-stacks.patch @@ -9,10 +9,10 @@ creating an itemstack using the TileEntity's NBT data (how it handles it for creative players) instead of routing it through the LootableBuilder. diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index 11270c763dc5e260074260e10a6dd9a9b7a09c8f..a25e4eeb1ce4e18c7cb14e269ad3852715209cb9 100644 +index 50c0b4cfe9dab9bd1c1e1f3f9a2b0aa970931cc1..5048a7f109d704ccc5bffdf220f97154ec0dd288 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -448,7 +448,7 @@ public class ServerPlayerGameMode { +@@ -416,7 +416,7 @@ public class ServerPlayerGameMode { block.destroy(this.level, pos, iblockdata); } @@ -35,10 +35,10 @@ index b9c558060024d380e89116489c7fc12ad88db8ad..0a0a4be15bed899812fcd4af0e311f5f CompoundTag compoundTag = shulkerBoxBlockEntity.saveToTag(new CompoundTag()); if (!compoundTag.isEmpty()) { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index a8e02eca9fc4aed1bcf9b43bb8f0483f2bdc6dce..90e14108ebe8d2cabead62c39b02d619e126f1a7 100644 +index 8955d05d414b232a9737b02d2330b93509241396..9b7e5299a67fd4f58d107e8e96c76e49117b75fd 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -676,6 +676,11 @@ public class PurpurWorldConfig { +@@ -675,6 +675,11 @@ public class PurpurWorldConfig { } } diff --git a/patches/server/0206-Bee-can-work-when-raining-or-at-night.patch b/patches/server/0206-Bee-can-work-when-raining-or-at-night.patch index 062a000b0..c03645886 100644 --- a/patches/server/0206-Bee-can-work-when-raining-or-at-night.patch +++ b/patches/server/0206-Bee-can-work-when-raining-or-at-night.patch @@ -31,10 +31,10 @@ index ffacc4b8cc3ab8285c4131aec58e48ffa9e1952e..e0e039e2f614f2df48d8d1b6e8bbbe7a return false; } else { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 90e14108ebe8d2cabead62c39b02d619e126f1a7..73b18882a242ea2239a5fb747805c0faf2c0b3c4 100644 +index 9b7e5299a67fd4f58d107e8e96c76e49117b75fd..bbd22135683df633524a989f6e9388e1a8de2b69 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -780,6 +780,8 @@ public class PurpurWorldConfig { +@@ -779,6 +779,8 @@ public class PurpurWorldConfig { public double beeMaxY = 256D; public double beeMaxHealth = 10.0D; public int beeBreedingTicks = 6000; @@ -43,7 +43,7 @@ index 90e14108ebe8d2cabead62c39b02d619e126f1a7..73b18882a242ea2239a5fb747805c0fa private void beeSettings() { beeRidable = getBoolean("mobs.bee.ridable", beeRidable); beeRidableInWater = getBoolean("mobs.bee.ridable-in-water", beeRidableInWater); -@@ -791,6 +793,8 @@ public class PurpurWorldConfig { +@@ -790,6 +792,8 @@ public class PurpurWorldConfig { } beeMaxHealth = getDouble("mobs.bee.attributes.max_health", beeMaxHealth); beeBreedingTicks = getInt("mobs.bee.breeding-delay-ticks", beeBreedingTicks); diff --git a/patches/server/0207-API-for-any-mob-to-burn-daylight.patch b/patches/server/0207-API-for-any-mob-to-burn-daylight.patch index 9b95ff6a6..87eb56165 100644 --- a/patches/server/0207-API-for-any-mob-to-burn-daylight.patch +++ b/patches/server/0207-API-for-any-mob-to-burn-daylight.patch @@ -6,10 +6,10 @@ Subject: [PATCH] API for any mob to burn daylight Co-authored by: Encode42 diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index aba462649a21ea68c83bd80c61ab9bf63eeb1a93..55ab331c8de0aa7c5c47e42ee92ae0d05c0443e7 100644 +index 7fae7966027d19dd9757b4761401c21d81f051ac..936213c774400c5fc6f7e28cbaafb79c67ddf3e7 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -4262,5 +4262,18 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n +@@ -4079,5 +4079,18 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n public boolean canSaveToDisk() { return true; } @@ -29,7 +29,7 @@ index aba462649a21ea68c83bd80c61ab9bf63eeb1a93..55ab331c8de0aa7c5c47e42ee92ae0d0 // Purpur end } diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index 521a353a1de6573b7e4fcebc673494cdb93d7815..c4865a08e40205bd17697b039769fd8615b24744 100644 +index 2ece26f762f9764db27bea60b63f2121d1fd4211..85d9e1d485699c375e888503b71910b39afc6988 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java @@ -265,6 +265,7 @@ public abstract class LivingEntity extends Entity { @@ -60,7 +60,7 @@ index 521a353a1de6573b7e4fcebc673494cdb93d7815..c4865a08e40205bd17697b039769fd86 } // CraftBukkit start -@@ -3298,6 +3305,27 @@ public abstract class LivingEntity extends Entity { +@@ -3301,6 +3308,27 @@ public abstract class LivingEntity extends Entity { this.hurt(DamageSource.DROWN, 1.0F); } @@ -89,10 +89,10 @@ index 521a353a1de6573b7e4fcebc673494cdb93d7815..c4865a08e40205bd17697b039769fd86 public boolean isSensitiveToWater() { diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index b057284fea2fc32e300183ee1db29173efa6d490..dcec19d6323697f8469a7f595e989954c5e3f224 100644 +index 9e144b0e3838b371d9fe6d146bc171f6e70e25b2..9fdc2fb0144db68868e09cf0b1dd19305fc91a50 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -1634,17 +1634,7 @@ public abstract class Mob extends LivingEntity { +@@ -1630,17 +1630,7 @@ public abstract class Mob extends LivingEntity { } public boolean isSunBurnTick() { @@ -257,7 +257,7 @@ index c39e2d05fa81279a684532ee796880b1345e8c1c..c44ca111cf8601256bbfb8b6fc959956 public void setShouldBurnInDay(boolean shouldBurnInDay) { this.shouldBurnInDay = shouldBurnInDay; } // Paper end diff --git a/src/main/java/net/minecraft/world/entity/monster/Zombie.java b/src/main/java/net/minecraft/world/entity/monster/Zombie.java -index b80f7c71cbf7b10bda6fac3cfe673ac7fe129923..5adef472e37b422c9b83ac00221be3fb8558d225 100644 +index 7eed6c176345c766a99d4304d61d28354291960f..40f2fd62b1d36843c5539932d2fb2496009fee21 100644 --- a/src/main/java/net/minecraft/world/entity/monster/Zombie.java +++ b/src/main/java/net/minecraft/world/entity/monster/Zombie.java @@ -96,11 +96,12 @@ public class Zombie extends Monster { @@ -333,10 +333,10 @@ index b80f7c71cbf7b10bda6fac3cfe673ac7fe129923..5adef472e37b422c9b83ac00221be3fb // Paper end diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -index 1035e023003521574a09fdea3fd08e5fca66d8fc..7484f9c13e41f9be305134595b7052dfff4d72c3 100644 +index e2b1574af471699f93956130b50268647f24e0b9..c5733b0d9d4de696421a30b5d7266334af004c3b 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftEntity.java -@@ -1249,5 +1249,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { +@@ -1240,5 +1240,10 @@ public abstract class CraftEntity implements org.bukkit.entity.Entity { entity.absMoveTo(location.getX(), location.getY(), location.getZ(), location.getYaw(), location.getPitch()); return !entity.valid && entity.level.addEntity(entity, spawnReason); } diff --git a/patches/server/0208-Fix-advancement-triggers-on-entity-death.patch b/patches/server/0208-Fix-advancement-triggers-on-entity-death.patch index 973e7de52..2d399b31c 100644 --- a/patches/server/0208-Fix-advancement-triggers-on-entity-death.patch +++ b/patches/server/0208-Fix-advancement-triggers-on-entity-death.patch @@ -16,10 +16,10 @@ restoring it back to the entity just before the criterion triggers run and then finally clearing the equipment again right after the criterion is done. diff --git a/src/main/java/net/minecraft/world/entity/LivingEntity.java b/src/main/java/net/minecraft/world/entity/LivingEntity.java -index c4865a08e40205bd17697b039769fd8615b24744..0b8393a3d4202c8c57c5a63142c3ede8a487c595 100644 +index 85d9e1d485699c375e888503b71910b39afc6988..b7ed8f7fec01003a5a006b8c46400e79d1aff1ae 100644 --- a/src/main/java/net/minecraft/world/entity/LivingEntity.java +++ b/src/main/java/net/minecraft/world/entity/LivingEntity.java -@@ -1658,10 +1658,13 @@ public abstract class LivingEntity extends Entity { +@@ -1662,10 +1662,13 @@ public abstract class LivingEntity extends Entity { } // Paper start @@ -33,7 +33,7 @@ index c4865a08e40205bd17697b039769fd8615b24744..0b8393a3d4202c8c57c5a63142c3ede8 } if (this.isSleeping()) { -@@ -2527,6 +2530,12 @@ public abstract class LivingEntity extends Entity { +@@ -2531,6 +2534,12 @@ public abstract class LivingEntity extends Entity { @Override public abstract void setItemSlot(EquipmentSlot slot, ItemStack stack); @@ -47,10 +47,10 @@ index c4865a08e40205bd17697b039769fd8615b24744..0b8393a3d4202c8c57c5a63142c3ede8 CompoundTag nbttagcompound = stack.getTag(); diff --git a/src/main/java/net/minecraft/world/entity/Mob.java b/src/main/java/net/minecraft/world/entity/Mob.java -index dcec19d6323697f8469a7f595e989954c5e3f224..4601df22a3ced8752b6aff9f052e564a24466ce4 100644 +index 9fdc2fb0144db68868e09cf0b1dd19305fc91a50..79956cae6a6fc2a30f52ebadf5f86ee1f13c1e91 100644 --- a/src/main/java/net/minecraft/world/entity/Mob.java +++ b/src/main/java/net/minecraft/world/entity/Mob.java -@@ -1019,6 +1019,41 @@ public abstract class Mob extends LivingEntity { +@@ -1015,6 +1015,41 @@ public abstract class Mob extends LivingEntity { } @@ -139,10 +139,10 @@ index c9a44a4765f43b9c0247ed1005f4c13469bdee95..6d08c8c31a32ea38f06410fbaddf19b9 public boolean canTakeItem(ItemStack stack) { net.minecraft.world.entity.EquipmentSlot enumitemslot = Mob.getEquipmentSlotForItem(stack); diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index dfe78217add616c761ba53fb4999cc6593863d2d..b9ce1021f12f14ba45c49890d8d529b733bae532 100644 +index 6fa69683727f2111571468261ef16f7c0ff5c238..747f493d6206635787271e8329e56caa89252f35 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java +++ b/src/main/java/net/minecraft/world/entity/player/Player.java -@@ -1993,6 +1993,52 @@ public abstract class Player extends LivingEntity { +@@ -1984,6 +1984,52 @@ public abstract class Player extends LivingEntity { } diff --git a/patches/server/0209-Config-MobEffect-by-world.patch b/patches/server/0209-Config-MobEffect-by-world.patch index 2ff60a3d1..9c175dee3 100644 --- a/patches/server/0209-Config-MobEffect-by-world.patch +++ b/patches/server/0209-Config-MobEffect-by-world.patch @@ -40,10 +40,10 @@ index 79e036d79dec2ec4404baf02c23ba5ccad20cdce..6706d8e6d43cc5f3058f08fdfde77bed ((ServerPlayer) entityhuman).connection.send(new ClientboundSetHealthPacket(((ServerPlayer) entityhuman).getBukkitEntity().getScaledHealth(), entityhuman.getFoodData().foodLevel, entityhuman.getFoodData().saturationLevel)); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 73b18882a242ea2239a5fb747805c0faf2c0b3c4..afa0f3921302fe6aa2f07aa43d7f8da7221babda 100644 +index bbd22135683df633524a989f6e9388e1a8de2b69..72855a2a9747b20e6b55d74d702fd1b13a74df5c 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -282,6 +282,21 @@ public class PurpurWorldConfig { +@@ -281,6 +281,21 @@ public class PurpurWorldConfig { poweredRailBoostModifier = getDouble("gameplay-mechanics.minecart.powered-rail.boost-modifier", poweredRailBoostModifier); } diff --git a/patches/server/0210-Beacon-Activation-Range-Configurable.patch b/patches/server/0210-Beacon-Activation-Range-Configurable.patch index 525337eb4..d139f2cf3 100644 --- a/patches/server/0210-Beacon-Activation-Range-Configurable.patch +++ b/patches/server/0210-Beacon-Activation-Range-Configurable.patch @@ -26,10 +26,10 @@ index 3281448bf37da8a1b4b7b44f10f4b2438b4a4f29..418c2ddf8ff50a5071b2a31585b77e9f } else { return effectRange; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index afa0f3921302fe6aa2f07aa43d7f8da7221babda..b712f6290e9f33ca51d43f7d514611efa62f420e 100644 +index 72855a2a9747b20e6b55d74d702fd1b13a74df5c..b28d05569629b3826bd71c5e5f16ae86388d9171 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -523,6 +523,17 @@ public class PurpurWorldConfig { +@@ -522,6 +522,17 @@ public class PurpurWorldConfig { anvilAllowColors = getBoolean("blocks.anvil.allow-colors", anvilAllowColors); } diff --git a/patches/server/0211-Add-toggle-for-sand-duping-fix.patch b/patches/server/0211-Add-toggle-for-sand-duping-fix.patch index c1df3cf5f..baeca30c4 100644 --- a/patches/server/0211-Add-toggle-for-sand-duping-fix.patch +++ b/patches/server/0211-Add-toggle-for-sand-duping-fix.patch @@ -27,10 +27,10 @@ index 8336ea928faa92c6f58f8cdfb9faf1d8e26c9ccf..c765c182081fe83eb0f30dcbf97d8126 } // Paper end - fix sand duping diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index b712f6290e9f33ca51d43f7d514611efa62f420e..f2238699e9bdb601b218bfab765a1d4009bd5bb3 100644 +index b28d05569629b3826bd71c5e5f16ae86388d9171..a001af5b903016a68cf2eb0aa4a31efd2ad7cdfd 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -702,6 +702,11 @@ public class PurpurWorldConfig { +@@ -701,6 +701,11 @@ public class PurpurWorldConfig { } } diff --git a/patches/server/0212-Add-toggle-for-end-portal-safe-teleporting.patch b/patches/server/0212-Add-toggle-for-end-portal-safe-teleporting.patch index 079fe247c..693788b9e 100644 --- a/patches/server/0212-Add-toggle-for-end-portal-safe-teleporting.patch +++ b/patches/server/0212-Add-toggle-for-end-portal-safe-teleporting.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Add toggle for end portal safe teleporting diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java -index 55ab331c8de0aa7c5c47e42ee92ae0d05c0443e7..63cfc111ccdaa725975234eb3f58c743c57177f3 100644 +index 936213c774400c5fc6f7e28cbaafb79c67ddf3e7..a75b0a7d8bbb6ed0871865c70b5a5256a4c61cbe 100644 --- a/src/main/java/net/minecraft/world/entity/Entity.java +++ b/src/main/java/net/minecraft/world/entity/Entity.java -@@ -2749,7 +2749,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n +@@ -2560,7 +2560,7 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, n } this.processPortalCooldown(); @@ -45,10 +45,10 @@ index 197482e1ace23c3de002242097a68c6cc297cd3f..428875a6a99a619d337e2a7bbd2cb182 entity.portalWorld = ((ServerLevel)world); entity.portalBlock = pos.immutable(); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index f2238699e9bdb601b218bfab765a1d4009bd5bb3..85acb6af06b9e3401cd9b2ac873cc6f583c51b09 100644 +index a001af5b903016a68cf2eb0aa4a31efd2ad7cdfd..b6dec1172dbe0d49c0901fece931743959989d9d 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -644,6 +644,11 @@ public class PurpurWorldConfig { +@@ -643,6 +643,11 @@ public class PurpurWorldConfig { furnaceUseLavaFromUnderneath = getBoolean("blocks.furnace.use-lava-from-underneath", furnaceUseLavaFromUnderneath); } diff --git a/patches/server/0213-Flying-Fall-Damage-API.patch b/patches/server/0213-Flying-Fall-Damage-API.patch index 7b7dea663..3aae8a79d 100644 --- a/patches/server/0213-Flying-Fall-Damage-API.patch +++ b/patches/server/0213-Flying-Fall-Damage-API.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Flying Fall Damage API diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index b9ce1021f12f14ba45c49890d8d529b733bae532..48e208c332a06c56fef604920a1cb95cbe743efc 100644 +index 747f493d6206635787271e8329e56caa89252f35..cfb12af54b9593e9b642c8d87ab349f93853f4a6 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java +++ b/src/main/java/net/minecraft/world/entity/player/Player.java @@ -183,6 +183,7 @@ public abstract class Player extends LivingEntity { @@ -16,7 +16,7 @@ index b9ce1021f12f14ba45c49890d8d529b733bae532..48e208c332a06c56fef604920a1cb95c // CraftBukkit start public boolean fauxSleeping; -@@ -1732,7 +1733,7 @@ public abstract class Player extends LivingEntity { +@@ -1723,7 +1724,7 @@ public abstract class Player extends LivingEntity { @Override public boolean causeFallDamage(float fallDistance, float damageMultiplier, DamageSource damageSource) { @@ -26,10 +26,10 @@ index b9ce1021f12f14ba45c49890d8d529b733bae532..48e208c332a06c56fef604920a1cb95c } else { if (fallDistance >= 2.0F) { diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -index d7150eb5b7c51c3346ce9b5d143c744156a72117..667feff00111d7bc6b271c3692b8708c68aba811 100644 +index a3b72845e6a7323f6b7e73d0494bf2c58de62c7b..26f0b436e2a9c8cf239f38e91f98fb580262b272 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java -@@ -2575,5 +2575,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { +@@ -2521,5 +2521,15 @@ public class CraftPlayer extends CraftHumanEntity implements Player { public void setSpawnInvulnerableTicks(int spawnInvulnerableTime) { getHandle().spawnInvulnerableTime = spawnInvulnerableTime; } diff --git a/patches/server/0214-Make-lightning-rod-range-configurable.patch b/patches/server/0214-Make-lightning-rod-range-configurable.patch index bfd9c11d5..ec781230c 100644 --- a/patches/server/0214-Make-lightning-rod-range-configurable.patch +++ b/patches/server/0214-Make-lightning-rod-range-configurable.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Make lightning rod range configurable diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 68255224e72ae5e7e5eec88c2c29be0dfda64147..7f81ac2cd975b17fc2f3d4bc21111515658663ae 100644 +index 57a29f286815ee20abc87c0dffa0c6d682adaf35..8d56327ee9ff88baba34af2931c2f074cdb8f078 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -1046,7 +1046,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl +@@ -846,7 +846,7 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl return villageplacetype == PoiType.LIGHTNING_ROD; }, (blockposition1) -> { return blockposition1.getY() == this.getLevel().getHeight(Heightmap.Types.WORLD_SURFACE, blockposition1.getX(), blockposition1.getZ()) - 1; diff --git a/patches/server/0215-Burp-after-eating-food-fills-hunger-bar-completely.patch b/patches/server/0215-Burp-after-eating-food-fills-hunger-bar-completely.patch index 9e1f15f21..e220a6b09 100644 --- a/patches/server/0215-Burp-after-eating-food-fills-hunger-bar-completely.patch +++ b/patches/server/0215-Burp-after-eating-food-fills-hunger-bar-completely.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Burp after eating food fills hunger bar completely diff --git a/src/main/java/net/minecraft/world/entity/player/Player.java b/src/main/java/net/minecraft/world/entity/player/Player.java -index 48e208c332a06c56fef604920a1cb95cbe743efc..34e6be36d1304e072bd742a61105757f8e6b0a62 100644 +index cfb12af54b9593e9b642c8d87ab349f93853f4a6..894e5adaed05e6e83d53f7c04a9a059f6c6abb29 100644 --- a/src/main/java/net/minecraft/world/entity/player/Player.java +++ b/src/main/java/net/minecraft/world/entity/player/Player.java @@ -196,6 +196,8 @@ public abstract class Player extends LivingEntity { @@ -30,7 +30,7 @@ index 48e208c332a06c56fef604920a1cb95cbe743efc..34e6be36d1304e072bd742a61105757f this.noPhysics = this.isSpectator(); if (this.isSpectator()) { this.onGround = false; -@@ -2347,7 +2355,7 @@ public abstract class Player extends LivingEntity { +@@ -2338,7 +2346,7 @@ public abstract class Player extends LivingEntity { public ItemStack eat(Level world, ItemStack stack) { this.getFoodData().eat(stack.getItem(), stack); this.awardStat(Stats.ITEM_USED.get(stack.getItem())); @@ -55,10 +55,10 @@ index 97133bd4af30d0ba92cbf884b83140f3399f92e2..c1130952e3fa22abaa27fcc1c4761c83 public void eat(Item item, ItemStack stack) { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 85acb6af06b9e3401cd9b2ac873cc6f583c51b09..fdca927b5111a49a27ceef89989e1b1a5b0a4377 100644 +index b6dec1172dbe0d49c0901fece931743959989d9d..e2880d47c06002b2baabf6029f44b13462b89768 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -341,6 +341,7 @@ public class PurpurWorldConfig { +@@ -340,6 +340,7 @@ public class PurpurWorldConfig { public boolean playerSleepNearMonsters = false; public boolean playersSkipNight = true; public double playerCriticalDamageMultiplier = 1.5D; @@ -66,7 +66,7 @@ index 85acb6af06b9e3401cd9b2ac873cc6f583c51b09..fdca927b5111a49a27ceef89989e1b1a private void playerSettings() { idleTimeoutKick = getBoolean("gameplay-mechanics.player.idle-timeout.kick-if-idle", idleTimeoutKick); idleTimeoutTickNearbyEntities = getBoolean("gameplay-mechanics.player.idle-timeout.tick-nearby-entities", idleTimeoutTickNearbyEntities); -@@ -358,6 +359,7 @@ public class PurpurWorldConfig { +@@ -357,6 +358,7 @@ public class PurpurWorldConfig { playerSleepNearMonsters = getBoolean("gameplay-mechanics.player.sleep-ignore-nearby-mobs", playerSleepNearMonsters); playersSkipNight = getBoolean("gameplay-mechanics.player.can-skip-night", playersSkipNight); playerCriticalDamageMultiplier = getDouble("gameplay-mechanics.player.critical-damage-multiplier", playerCriticalDamageMultiplier); diff --git a/patches/server/0216-Allow-player-join-full-server-by-permission.patch b/patches/server/0216-Allow-player-join-full-server-by-permission.patch index 104413be9..519fa5fee 100644 --- a/patches/server/0216-Allow-player-join-full-server-by-permission.patch +++ b/patches/server/0216-Allow-player-join-full-server-by-permission.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Allow player join full server by permission diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 3f5002c106ef251ed0c30e61a8726c1a35c51c21..1eddf43eae9a5302040129cbb1d1a3b2f84c2e98 100644 +index c93a5bbf546b8b501306a98e84f995ec1a8c8c9e..f4b33bf57851ee179b64c20de83ca26d01b2f453 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -757,7 +757,7 @@ public abstract class PlayerList { +@@ -758,7 +758,7 @@ public abstract class PlayerList { event.disallow(PlayerLoginEvent.Result.KICK_BANNED, PaperAdventure.asAdventure(chatmessage)); // Paper - Adventure } else { // return this.players.size() >= this.maxPlayers && !this.d(gameprofile) ? new ChatMessage("multiplayer.disconnect.server_full") : null; diff --git a/patches/server/0217-Populator-seed-controls.patch b/patches/server/0217-Populator-seed-controls.patch index f1411f4f2..68d615163 100644 --- a/patches/server/0217-Populator-seed-controls.patch +++ b/patches/server/0217-Populator-seed-controls.patch @@ -39,27 +39,16 @@ index a7a7e6cd87270e64a92448f03f8b0b0c7e375ec7..9fb19162c0e436122087d03d37b502a1 try { region.setCurrentlyGenerating(supplier3); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index fdca927b5111a49a27ceef89989e1b1a5b0a4377..e24249b64432eb41e86972cdf76ee32e59e5dc3d 100644 +index e2880d47c06002b2baabf6029f44b13462b89768..3a1a328e74242a70fdfde2c774969955e2f573a4 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -2262,4 +2262,20 @@ public class PurpurWorldConfig { +@@ -2261,4 +2261,9 @@ public class PurpurWorldConfig { zombifiedPiglinJockeyTryExistingChickens = getBoolean("mobs.zombified_piglin.jockey.try-existing-chickens", zombifiedPiglinJockeyTryExistingChickens); zombifiedPiglinCountAsPlayerKillWhenAngry = getBoolean("mobs.zombified_piglin.count-as-player-kill-when-angry", zombifiedPiglinCountAsPlayerKillWhenAngry); } + + public boolean randomPopulatorSeed = false; + private void seedSettings() { -+ if (PurpurConfig.version < 16) { -+ try { -+ java.lang.reflect.Method method = TuinityConfig.WorldConfig.class.getDeclaredMethod("getString", String.class, String.class); -+ method.setAccessible(true); -+ String def = (String) method.invoke(level.tuinityConfig, "worldgen.seeds.populator", "default"); -+ if (def.equalsIgnoreCase("random")) { -+ set("seed.random-populator-seed", true); -+ } -+ } catch (NoSuchMethodException | java.lang.reflect.InvocationTargetException | IllegalAccessException ignore) { -+ } -+ } + randomPopulatorSeed = getBoolean("seed.random-populator-seed", randomPopulatorSeed); + } } diff --git a/patches/server/0219-Shulker-spawn-from-bullet-options.patch b/patches/server/0219-Shulker-spawn-from-bullet-options.patch index 2571eb7e0..dd5c5f6e5 100644 --- a/patches/server/0219-Shulker-spawn-from-bullet-options.patch +++ b/patches/server/0219-Shulker-spawn-from-bullet-options.patch @@ -61,10 +61,10 @@ index f812a75985d26785639491c9a980387a3f261f2d..b11fb39b69f5225ca7da72ca1a2200c7 + // Purpur end } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index e24249b64432eb41e86972cdf76ee32e59e5dc3d..938a1e41db57ae57a33863a1366213b5ecc18dcc 100644 +index 3a1a328e74242a70fdfde2c774969955e2f573a4..68d2feac1cc64e8f4e0e3274a0212d1113475862 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1717,6 +1717,11 @@ public class PurpurWorldConfig { +@@ -1716,6 +1716,11 @@ public class PurpurWorldConfig { public boolean shulkerRidable = false; public boolean shulkerRidableInWater = false; public double shulkerMaxHealth = 30.0D; @@ -76,7 +76,7 @@ index e24249b64432eb41e86972cdf76ee32e59e5dc3d..938a1e41db57ae57a33863a1366213b5 private void shulkerSettings() { shulkerRidable = getBoolean("mobs.shulker.ridable", shulkerRidable); shulkerRidableInWater = getBoolean("mobs.shulker.ridable-in-water", shulkerRidableInWater); -@@ -1726,6 +1731,11 @@ public class PurpurWorldConfig { +@@ -1725,6 +1730,11 @@ public class PurpurWorldConfig { set("mobs.shulker.attributes.max_health", oldValue); } shulkerMaxHealth = getDouble("mobs.shulker.attributes.max_health", shulkerMaxHealth); diff --git a/patches/server/0220-Eating-glow-berries-adds-glow-effect.patch b/patches/server/0220-Eating-glow-berries-adds-glow-effect.patch index 0a5f86d87..8bdf373db 100644 --- a/patches/server/0220-Eating-glow-berries-adds-glow-effect.patch +++ b/patches/server/0220-Eating-glow-berries-adds-glow-effect.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Eating glow berries adds glow effect diff --git a/src/main/java/net/minecraft/world/item/Items.java b/src/main/java/net/minecraft/world/item/Items.java -index 6322251336a4300649f207efdb4d404d25023c9a..35abd27226d953f42ca52476042c0d417fd35778 100644 +index 89d4b7e4cd4222b61b49833fceda56ffa39710fa..1eb50f0bc41db79f091f900861ba71d702a5bd2a 100644 --- a/src/main/java/net/minecraft/world/item/Items.java +++ b/src/main/java/net/minecraft/world/item/Items.java @@ -1069,7 +1069,7 @@ public class Items { @@ -18,10 +18,10 @@ index 6322251336a4300649f207efdb4d404d25023c9a..35abd27226d953f42ca52476042c0d41 public static final Item SOUL_CAMPFIRE = registerBlock(Blocks.SOUL_CAMPFIRE, CreativeModeTab.TAB_DECORATIONS); public static final Item SHROOMLIGHT = registerBlock(Blocks.SHROOMLIGHT, CreativeModeTab.TAB_DECORATIONS); diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 938a1e41db57ae57a33863a1366213b5ecc18dcc..4aeb8e0944152efe66e8fc74d92104d3359bc0ae 100644 +index 68d2feac1cc64e8f4e0e3274a0212d1113475862..fc6f817266a17efaa8c27f3e6bc01099db45be86 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -173,6 +173,7 @@ public class PurpurWorldConfig { +@@ -172,6 +172,7 @@ public class PurpurWorldConfig { public int enderPearlCooldown = 20; public int enderPearlCooldownCreative = 20; public float enderPearlEndermiteChance = 0.05F; @@ -29,7 +29,7 @@ index 938a1e41db57ae57a33863a1366213b5ecc18dcc..4aeb8e0944152efe66e8fc74d92104d3 private void itemSettings() { itemImmuneToCactus.clear(); getList("gameplay-mechanics.item.immune.cactus", new ArrayList<>()).forEach(key -> { -@@ -216,6 +217,7 @@ public class PurpurWorldConfig { +@@ -215,6 +216,7 @@ public class PurpurWorldConfig { enderPearlCooldown = getInt("gameplay-mechanics.item.ender-pearl.cooldown", enderPearlCooldown); enderPearlCooldownCreative = getInt("gameplay-mechanics.item.ender-pearl.creative-cooldown", enderPearlCooldownCreative); enderPearlEndermiteChance = (float) getDouble("gameplay-mechanics.item.ender-pearl.endermite-spawn-chance", enderPearlEndermiteChance); diff --git a/patches/server/0221-Option-to-make-drowned-break-doors.patch b/patches/server/0221-Option-to-make-drowned-break-doors.patch index 236893b1d..395256878 100644 --- a/patches/server/0221-Option-to-make-drowned-break-doors.patch +++ b/patches/server/0221-Option-to-make-drowned-break-doors.patch @@ -34,10 +34,10 @@ index 8d3ce6c97a8734c0d13844cafca251a3f4dce8a4..a004d59483dc3ffb404ae28daf76c807 @Override diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 4aeb8e0944152efe66e8fc74d92104d3359bc0ae..29d72fe1e0332e528b15f16bd647778a4167d7ee 100644 +index fc6f817266a17efaa8c27f3e6bc01099db45be86..62aaf7033bc2426d109389f113d21b67b7f50894 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -1016,6 +1016,7 @@ public class PurpurWorldConfig { +@@ -1015,6 +1015,7 @@ public class PurpurWorldConfig { public boolean drownedJockeyOnlyBaby = true; public double drownedJockeyChance = 0.05D; public boolean drownedJockeyTryExistingChickens = true; @@ -45,7 +45,7 @@ index 4aeb8e0944152efe66e8fc74d92104d3359bc0ae..29d72fe1e0332e528b15f16bd647778a private void drownedSettings() { drownedRidable = getBoolean("mobs.drowned.ridable", drownedRidable); drownedRidableInWater = getBoolean("mobs.drowned.ridable-in-water", drownedRidableInWater); -@@ -1029,6 +1030,7 @@ public class PurpurWorldConfig { +@@ -1028,6 +1029,7 @@ public class PurpurWorldConfig { drownedJockeyOnlyBaby = getBoolean("mobs.drowned.jockey.only-babies", drownedJockeyOnlyBaby); drownedJockeyChance = getDouble("mobs.drowned.jockey.chance", drownedJockeyChance); drownedJockeyTryExistingChickens = getBoolean("mobs.drowned.jockey.try-existing-chickens", drownedJockeyTryExistingChickens); diff --git a/patches/server/0222-Configurable-hunger-starvation-damage.patch b/patches/server/0222-Configurable-hunger-starvation-damage.patch index 9e57713cf..06c7fb26e 100644 --- a/patches/server/0222-Configurable-hunger-starvation-damage.patch +++ b/patches/server/0222-Configurable-hunger-starvation-damage.patch @@ -18,11 +18,11 @@ index c1130952e3fa22abaa27fcc1c4761c831dc56cc3..1ac08eca469739cb52abd38483c431b6 this.tickTimer = 0; diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 29d72fe1e0332e528b15f16bd647778a4167d7ee..8fd5d9800bd94372474002eaeca2e83120541c9f 100644 +index 62aaf7033bc2426d109389f113d21b67b7f50894..ff6c5d7a9054935178d0236153ddc66e4914b5d5 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -2292,4 +2292,9 @@ public class PurpurWorldConfig { - } +@@ -2280,4 +2280,9 @@ public class PurpurWorldConfig { + private void seedSettings() { randomPopulatorSeed = getBoolean("seed.random-populator-seed", randomPopulatorSeed); } + diff --git a/patches/server/0223-Redirect-System.out-calls-to-plugin-loggers.patch b/patches/server/0223-Redirect-System.out-calls-to-plugin-loggers.patch index 4a0b5410e..8debfa73c 100644 --- a/patches/server/0223-Redirect-System.out-calls-to-plugin-loggers.patch +++ b/patches/server/0223-Redirect-System.out-calls-to-plugin-loggers.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Redirect System.out calls to plugin loggers diff --git a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -index 66d2559e5bd8630c6cbca0fe8d0eb1f756c388df..76ff4b0c1ebf4f7b3cf7b752ee8ec4f418c622b9 100644 +index 5d9d76f1b90e991bb01b059da86dcdd483d93c80..2ff5bf7f5dc66ad493f219b709809115fd4d18f7 100644 --- a/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java +++ b/src/main/java/net/minecraft/server/dedicated/DedicatedServer.java -@@ -177,8 +177,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface +@@ -178,8 +178,8 @@ public class DedicatedServer extends MinecraftServer implements ServerInterface */ // Paper end diff --git a/patches/server/0224-Armor-click-equip-options.patch b/patches/server/0224-Armor-click-equip-options.patch index 144978570..7434b0044 100644 --- a/patches/server/0224-Armor-click-equip-options.patch +++ b/patches/server/0224-Armor-click-equip-options.patch @@ -5,10 +5,10 @@ Subject: [PATCH] Armor click equip options diff --git a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -index a25e4eeb1ce4e18c7cb14e269ad3852715209cb9..111779c0a614b29bea5ad7a8301d704efaf62bf1 100644 +index 5048a7f109d704ccc5bffdf220f97154ec0dd288..e8c3b14601ce1d352cfd4c031c029f5541d6cfe0 100644 --- a/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java +++ b/src/main/java/net/minecraft/server/level/ServerPlayerGameMode.java -@@ -498,7 +498,7 @@ public class ServerPlayerGameMode { +@@ -466,7 +466,7 @@ public class ServerPlayerGameMode { return interactionresultwrapper.getResult(); } else { player.setItemInHand(hand, itemstack1); @@ -58,10 +58,10 @@ index 42f79d418ec4e2dbeac9a217d9dc144cda2ef714..250c0e31825f772d3fee7a523f150cb2 return InteractionResultHolder.fail(itemStack); } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 8fd5d9800bd94372474002eaeca2e83120541c9f..dc52aa05c402e72e44a8a3d783ccd3091baa7845 100644 +index ff6c5d7a9054935178d0236153ddc66e4914b5d5..2c6579e5c77c0f409a9c92a80574c8a3961b1ad1 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -344,6 +344,8 @@ public class PurpurWorldConfig { +@@ -343,6 +343,8 @@ public class PurpurWorldConfig { public boolean playersSkipNight = true; public double playerCriticalDamageMultiplier = 1.5D; public boolean playerBurpWhenFull = false; @@ -70,7 +70,7 @@ index 8fd5d9800bd94372474002eaeca2e83120541c9f..dc52aa05c402e72e44a8a3d783ccd309 private void playerSettings() { idleTimeoutKick = getBoolean("gameplay-mechanics.player.idle-timeout.kick-if-idle", idleTimeoutKick); idleTimeoutTickNearbyEntities = getBoolean("gameplay-mechanics.player.idle-timeout.tick-nearby-entities", idleTimeoutTickNearbyEntities); -@@ -362,6 +364,8 @@ public class PurpurWorldConfig { +@@ -361,6 +363,8 @@ public class PurpurWorldConfig { playersSkipNight = getBoolean("gameplay-mechanics.player.can-skip-night", playersSkipNight); playerCriticalDamageMultiplier = getDouble("gameplay-mechanics.player.critical-damage-multiplier", playerCriticalDamageMultiplier); playerBurpWhenFull = getBoolean("gameplay-mechanics.player.burp-when-full", playerBurpWhenFull); diff --git a/patches/server/0225-Add-uptime-command.patch b/patches/server/0225-Add-uptime-command.patch index 4dbce576c..160ef919c 100644 --- a/patches/server/0225-Add-uptime-command.patch +++ b/patches/server/0225-Add-uptime-command.patch @@ -17,13 +17,13 @@ index bef1f6ca54fc6c1741b54b8e071a90eb5e3a36f3..84c970ed188509127cd5b4d268a5fa1f } diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 108dadb24607ea42cf857879386c34c9bb72dbee..a87be8fe6fbed952bfee76bab49e313b2fbfa62d 100644 +index 4410ead33e18790f8674765affeba736a89c8ddd..2e8ea3883cbf0dd5ce06af7595e0f3b070ec729a 100644 --- a/src/main/java/net/minecraft/server/MinecraftServer.java +++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -298,6 +298,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop processQueue = new java.util.concurrent.ConcurrentLinkedQueue(); public int autosavePeriod; diff --git a/patches/server/0227-Tool-actionable-options.patch b/patches/server/0227-Tool-actionable-options.patch index 4610a88da..a2022fbfe 100644 --- a/patches/server/0227-Tool-actionable-options.patch +++ b/patches/server/0227-Tool-actionable-options.patch @@ -109,10 +109,10 @@ index 009dd6b59e27a9413b3ef115468a6d1dd0746d56..fdbc1378bc148f5a16b414a3e5867cdc return InteractionResult.PASS; } diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index dc52aa05c402e72e44a8a3d783ccd3091baa7845..456a9d3144b0b1c7d955b03245ef9ae0445d72a9 100644 +index 2c6579e5c77c0f409a9c92a80574c8a3961b1ad1..2c3f734ae06a8d1c282e714b28377f3c4691350f 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -459,6 +459,153 @@ public class PurpurWorldConfig { +@@ -458,6 +458,153 @@ public class PurpurWorldConfig { }); } diff --git a/patches/server/0228-SPIGOT-5988-Fix-bed-respawn-location-not-resetting.patch b/patches/server/0228-SPIGOT-5988-Fix-bed-respawn-location-not-resetting.patch index fdd96804f..1416a7b13 100644 --- a/patches/server/0228-SPIGOT-5988-Fix-bed-respawn-location-not-resetting.patch +++ b/patches/server/0228-SPIGOT-5988-Fix-bed-respawn-location-not-resetting.patch @@ -5,10 +5,10 @@ Subject: [PATCH] SPIGOT-5988 Fix bed respawn location not resetting diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index a4af10623511bfbbf9ef799d5727c6e1752a2fa5..75ea35574ad999fd7f6cce4b794017ebc9d0f218 100644 +index f4b33bf57851ee179b64c20de83ca26d01b2f453..c36040edb47f40cfb5740639c8c761e311a6fae4 100644 --- a/src/main/java/net/minecraft/server/players/PlayerList.java +++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -895,6 +895,7 @@ public abstract class PlayerList { +@@ -896,6 +896,7 @@ public abstract class PlayerList { location = new Location(worldserver1.getWorld(), vec3d.x, vec3d.y, vec3d.z, f1, 0.0F); } else if (blockposition != null) { entityplayer1.connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.NO_RESPAWN_BLOCK_AVAILABLE, 0.0F)); diff --git a/patches/server/0229-Store-placer-on-Block-when-placed.patch b/patches/server/0229-Store-placer-on-Block-when-placed.patch index 793ddeb1a..6bf0e2ea3 100644 --- a/patches/server/0229-Store-placer-on-Block-when-placed.patch +++ b/patches/server/0229-Store-placer-on-Block-when-placed.patch @@ -5,17 +5,17 @@ Subject: [PATCH] Store placer on Block when placed diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java -index 4be9924556e0f447dbe6a53c2d4cb7fb89dac455..0554261342308573551f05413a45a7592d32ca64 100644 +index e61be8f9a9b26a9daa3f70ec028ed689dfd43c93..52ed8a1629ab95e52c8c391902069e2fd6f0738f 100644 --- a/src/main/java/net/minecraft/world/item/ItemStack.java +++ b/src/main/java/net/minecraft/world/item/ItemStack.java -@@ -382,6 +382,7 @@ public final class ItemStack { - // revert back all captured blocks +@@ -381,6 +381,7 @@ public final class ItemStack { + world.preventPoiUpdated = true; // CraftBukkit - SPIGOT-5710 for (BlockState blockstate : blocks) { blockstate.update(true, false); + ((CraftBlock) blockstate.getBlock()).getNMS().getBlock().forgetPlacer(); // Purpur } + world.preventPoiUpdated = false; - // Brute force all possible updates @@ -409,6 +410,7 @@ public final class ItemStack { if (!(block.getBlock() instanceof BaseEntityBlock)) { // Containers get placed automatically block.getBlock().onPlace(block, world, newblockposition, oldBlock, true, itemactioncontext); // Paper - pass itemactioncontext @@ -25,7 +25,7 @@ index 4be9924556e0f447dbe6a53c2d4cb7fb89dac455..0554261342308573551f05413a45a759 world.notifyAndUpdatePhysics(newblockposition, null, oldBlock, block, world.getBlockState(newblockposition), updateFlag, 512); // send null chunk as chunk.k() returns false by this point } diff --git a/src/main/java/net/minecraft/world/level/block/Block.java b/src/main/java/net/minecraft/world/level/block/Block.java -index 99d05dbe7d7d9bcc1c5f5b3dd02e413e3a398a10..8189dbf510702e81e86ff0070d4819fac1bfef03 100644 +index 0f295a496d4d94811199fa952ed68325ee1df0f6..5aab59cba33bcf4b615977dcd9261b0cf2b56aa9 100644 --- a/src/main/java/net/minecraft/world/level/block/Block.java +++ b/src/main/java/net/minecraft/world/level/block/Block.java @@ -461,7 +461,17 @@ public class Block extends BlockBehaviour implements ItemLike { diff --git a/patches/server/0231-Customizable-sleeping-actionbar-messages.patch b/patches/server/0231-Customizable-sleeping-actionbar-messages.patch index e64bba371..2072dc446 100644 --- a/patches/server/0231-Customizable-sleeping-actionbar-messages.patch +++ b/patches/server/0231-Customizable-sleeping-actionbar-messages.patch @@ -5,7 +5,7 @@ Subject: [PATCH] Customizable sleeping actionbar messages diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java -index 7f81ac2cd975b17fc2f3d4bc21111515658663ae..05635818b4ae597b6b2053dbf9c8351f6ea9ea42 100644 +index 8d56327ee9ff88baba34af2931c2f074cdb8f078..fd68db19b041df90b3c4ff15dc0e5b2f4f9bb860 100644 --- a/src/main/java/net/minecraft/server/level/ServerLevel.java +++ b/src/main/java/net/minecraft/server/level/ServerLevel.java @@ -4,6 +4,7 @@ import com.google.common.annotations.VisibleForTesting; @@ -26,7 +26,7 @@ index 7f81ac2cd975b17fc2f3d4bc21111515658663ae..05635818b4ae597b6b2053dbf9c8351f import net.minecraft.CrashReport; import net.minecraft.core.BlockPos; import net.minecraft.core.DefaultedRegistry; -@@ -158,6 +162,7 @@ import net.minecraft.world.phys.Vec3; +@@ -157,6 +161,7 @@ import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.BooleanOp; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; @@ -34,7 +34,7 @@ index 7f81ac2cd975b17fc2f3d4bc21111515658663ae..05635818b4ae597b6b2053dbf9c8351f import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -@@ -1089,11 +1094,29 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl +@@ -889,11 +894,29 @@ public class ServerLevel extends net.minecraft.world.level.Level implements Worl if (this.canSleepThroughNights()) { if (!this.getServer().isSingleplayer() || this.getServer().isPublished()) { int i = this.getGameRules().getInt(GameRules.RULE_PLAYERS_SLEEPING_PERCENTAGE); diff --git a/patches/server/0232-option-to-disable-shulker-box-items-from-dropping-co.patch b/patches/server/0232-option-to-disable-shulker-box-items-from-dropping-co.patch index 77629e973..61b5c287f 100644 --- a/patches/server/0232-option-to-disable-shulker-box-items-from-dropping-co.patch +++ b/patches/server/0232-option-to-disable-shulker-box-items-from-dropping-co.patch @@ -19,10 +19,10 @@ index d9114ee2ef4f1dee7ae018f932a7804a61a90ef6..8b521ee48480d3eb5cc77051c425ff45 if (nbttagcompound != null) { diff --git a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -index 456a9d3144b0b1c7d955b03245ef9ae0445d72a9..44a45ed22f8d8e9b58b7ba6ce0d5833f1a937c5d 100644 +index 2c3f734ae06a8d1c282e714b28377f3c4691350f..0a2b847f77d0546c7e28925b259a5c3167a0e012 100644 --- a/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java +++ b/src/main/java/net/pl3x/purpur/PurpurWorldConfig.java -@@ -174,6 +174,7 @@ public class PurpurWorldConfig { +@@ -173,6 +173,7 @@ public class PurpurWorldConfig { public int enderPearlCooldownCreative = 20; public float enderPearlEndermiteChance = 0.05F; public int glowBerriesEatGlowDuration = 0; @@ -30,7 +30,7 @@ index 456a9d3144b0b1c7d955b03245ef9ae0445d72a9..44a45ed22f8d8e9b58b7ba6ce0d5833f private void itemSettings() { itemImmuneToCactus.clear(); getList("gameplay-mechanics.item.immune.cactus", new ArrayList<>()).forEach(key -> { -@@ -218,6 +219,7 @@ public class PurpurWorldConfig { +@@ -217,6 +218,7 @@ public class PurpurWorldConfig { enderPearlCooldownCreative = getInt("gameplay-mechanics.item.ender-pearl.creative-cooldown", enderPearlCooldownCreative); enderPearlEndermiteChance = (float) getDouble("gameplay-mechanics.item.ender-pearl.endermite-spawn-chance", enderPearlEndermiteChance); glowBerriesEatGlowDuration = getInt("gameplay-mechanics.item.glow_berries.eat-glow-duration", glowBerriesEatGlowDuration);