diff --git a/patches/api/0014-Add-view-distance-API.patch b/patches/api/0014-Add-view-distance-API.patch
index e953593216..151e3ef4c1 100644
--- a/patches/api/0014-Add-view-distance-API.patch
+++ b/patches/api/0014-Add-view-distance-API.patch
@@ -8,10 +8,10 @@ Add per player no-tick, tick, and send view distances.
Also add send/no-tick view distance to World.
diff --git a/src/main/java/org/bukkit/World.java b/src/main/java/org/bukkit/World.java
-index cf6fe1b5a1531e8d30c0386e36c023d003458b7e..bf23ef001fb5177b7aab0b3ed8752f58641bb840 100644
+index cf6fe1b5a1531e8d30c0386e36c023d003458b7e..ad342ecd8b86903276c62644624cff55cf190026 100644
--- a/src/main/java/org/bukkit/World.java
+++ b/src/main/java/org/bukkit/World.java
-@@ -2597,6 +2597,52 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
+@@ -2597,6 +2597,62 @@ public interface World extends RegionAccessor, WorldInfo, PluginMessageRecipient
int getSimulationDistance();
// Spigot end
@@ -23,13 +23,21 @@ index cf6fe1b5a1531e8d30c0386e36c023d003458b7e..bf23ef001fb5177b7aab0b3ed8752f58
+ void setViewDistance(int viewDistance);
+
+ /**
++ * Sets the simulation distance for this world.
++ * @param simulationDistance simulation distance in [2, 32]
++ */
++ void setSimulationDistance(int simulationDistance);
++
++ /**
+ * Returns the no-tick view distance for this world.
+ *
+ * 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 world.
++ * @deprecated Use {@link #getViewDistance()}
+ */
++ @Deprecated
+ int getNoTickViewDistance();
+
+ /**
@@ -39,7 +47,9 @@ index cf6fe1b5a1531e8d30c0386e36c023d003458b7e..bf23ef001fb5177b7aab0b3ed8752f58
+ * be set to tick.
+ *
+ * @param viewDistance view distance in [2, 32]
++ * @deprecated Use {@link #setViewDistance(int)}
+ */
++ @Deprecated
+ void setNoTickViewDistance(int viewDistance);
+
+ /**
@@ -49,7 +59,7 @@ index cf6fe1b5a1531e8d30c0386e36c023d003458b7e..bf23ef001fb5177b7aab0b3ed8752f58
+ *
+ * @return The sending view distance for this world.
+ */
-+ public int getSendViewDistance();
++ int getSendViewDistance();
+
+ /**
+ * Sets the sending view distance for this world.
@@ -58,17 +68,17 @@ index cf6fe1b5a1531e8d30c0386e36c023d003458b7e..bf23ef001fb5177b7aab0b3ed8752f58
+ *
+ * @param viewDistance view distance in [2, 32] or -1
+ */
-+ public void setSendViewDistance(int viewDistance);
++ void setSendViewDistance(int viewDistance);
+ // Paper end - view distance api
+
// Spigot start
public class Spigot {
diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
-index 43d91f578b0aefd18f7f5ecd120a366af4ee98e6..2d93f5ad7f9c0df08bcd099a813c1d8e9b8c16eb 100644
+index 43d91f578b0aefd18f7f5ecd120a366af4ee98e6..9f861fcc350b803e63b08631f7a6c30d13b4cb7c 100644
--- a/src/main/java/org/bukkit/entity/Player.java
+++ b/src/main/java/org/bukkit/entity/Player.java
-@@ -1793,6 +1793,62 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+@@ -1793,6 +1793,78 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
* @param affects Whether the player can affect mob spawning
*/
public void setAffectsSpawning(boolean affects);
@@ -78,7 +88,6 @@ index 43d91f578b0aefd18f7f5ecd120a366af4ee98e6..2d93f5ad7f9c0df08bcd099a813c1d8e
+ *
+ * @return the player's view distance
+ * @see org.bukkit.World#getViewDistance()
-+ * @see org.bukkit.World#getNoTickViewDistance()
+ */
+ public int getViewDistance();
+
@@ -87,9 +96,22 @@ index 43d91f578b0aefd18f7f5ecd120a366af4ee98e6..2d93f5ad7f9c0df08bcd099a813c1d8e
+ *
+ * @param viewDistance the player's view distance
+ * @see org.bukkit.World#setViewDistance(int)
-+ * @see org.bukkit.World#setNoTickViewDistance(int)
+ */
+ public void setViewDistance(int viewDistance);
++
++ /**
++ * Gets the simulation distance for this player
++ *
++ * @return the player's simulation distance
++ */
++ public int getSimulationDistance();
++
++ /**
++ * Sets the simulation distance for this player
++ *
++ * @param simulationDistance the player's new simulation distance
++ */
++ public void setSimulationDistance(int simulationDistance);
+
+ /**
+ * Gets the no-ticking view distance for this player.
@@ -98,7 +120,9 @@ index 43d91f578b0aefd18f7f5ecd120a366af4ee98e6..2d93f5ad7f9c0df08bcd099a813c1d8e
+ * be set to tick.
+ *
+ * @return The no-tick view distance for this player.
++ * @deprecated Use {@link #getViewDistance()}
+ */
++ @Deprecated
+ public int getNoTickViewDistance();
+
+ /**
@@ -108,7 +132,9 @@ index 43d91f578b0aefd18f7f5ecd120a366af4ee98e6..2d93f5ad7f9c0df08bcd099a813c1d8e
+ * be set to tick.
+ *
+ * @param viewDistance view distance in [2, 32] or -1
++ * @deprecated Use {@link #setViewDistance(int)}
+ */
++ @Deprecated
+ public void setNoTickViewDistance(int viewDistance);
+
+ /**
diff --git a/patches/api/0025-Complete-resource-pack-API.patch b/patches/api/0025-Complete-resource-pack-API.patch
index fddaadcafd..6c3c368d90 100644
--- a/patches/api/0025-Complete-resource-pack-API.patch
+++ b/patches/api/0025-Complete-resource-pack-API.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Complete resource pack API
diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
-index 5f5cde854dd7928e0b640ab1713f42194eab0833..09b5941bcd412420ffc6edfe62bc95cff426ba06 100644
+index 29bd571bd270b9c6ed05d95122d42d9bc0c797f6..fd8a40bacaaa39c20428bbebe8611ba8cdd33e0b 100644
--- a/src/main/java/org/bukkit/entity/Player.java
+++ b/src/main/java/org/bukkit/entity/Player.java
@@ -1284,7 +1284,9 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
@@ -18,7 +18,7 @@ index 5f5cde854dd7928e0b640ab1713f42194eab0833..09b5941bcd412420ffc6edfe62bc95cf
public void setResourcePack(@NotNull String url);
/**
-@@ -2058,6 +2060,124 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+@@ -2074,6 +2076,124 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
default net.kyori.adventure.text.event.HoverEvent asHoverEvent(final @NotNull java.util.function.UnaryOperator op) {
return net.kyori.adventure.text.event.HoverEvent.showEntity(op.apply(net.kyori.adventure.text.event.HoverEvent.ShowEntity.of(this.getType().getKey(), this.getUniqueId(), this.displayName())));
}
diff --git a/patches/api/0045-Add-String-based-Action-Bar-API.patch b/patches/api/0045-Add-String-based-Action-Bar-API.patch
index a69f6b828a..4c7c14bcc0 100644
--- a/patches/api/0045-Add-String-based-Action-Bar-API.patch
+++ b/patches/api/0045-Add-String-based-Action-Bar-API.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] Add String based Action Bar API
diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
-index 09b5941bcd412420ffc6edfe62bc95cff426ba06..a0777f9dc7cb6d4274635d794cf66de714535cde 100644
+index fd8a40bacaaa39c20428bbebe8611ba8cdd33e0b..d6a2e66e07590013bf1596012a96ec1018493e06 100644
--- a/src/main/java/org/bukkit/entity/Player.java
+++ b/src/main/java/org/bukkit/entity/Player.java
@@ -3,6 +3,7 @@ package org.bukkit.entity;
@@ -68,7 +68,7 @@ index 09b5941bcd412420ffc6edfe62bc95cff426ba06..a0777f9dc7cb6d4274635d794cf66de7
public default void sendMessage(net.md_5.bungee.api.ChatMessageType position, net.md_5.bungee.api.chat.BaseComponent... components) {
spigot().sendMessage(position, components);
}
-@@ -2249,6 +2285,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+@@ -2265,6 +2301,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
/**
* Sends the component to the specified screen position of this player
*
@@ -76,7 +76,7 @@ index 09b5941bcd412420ffc6edfe62bc95cff426ba06..a0777f9dc7cb6d4274635d794cf66de7
* @param position the screen position
* @param component the components to send
* @deprecated use {@code sendMessage} methods that accept {@link net.kyori.adventure.text.Component}
-@@ -2261,6 +2298,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+@@ -2277,6 +2314,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
/**
* Sends an array of components as a single message to the specified screen position of this player
*
diff --git a/patches/api/0091-Player.setPlayerProfile-API.patch b/patches/api/0091-Player.setPlayerProfile-API.patch
index b16ea0742c..970243cc3b 100644
--- a/patches/api/0091-Player.setPlayerProfile-API.patch
+++ b/patches/api/0091-Player.setPlayerProfile-API.patch
@@ -62,10 +62,10 @@ index 51c96a0b6645cf31f4ca051f6a8c75b5f188484c..80d474a979add473c99692ccde93439d
/**
diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
-index b68f2774e4f88e905cc195df6d1592d96103df7a..23df52c7ac4ec5e687e763c13a0937c3fbc5dd47 100644
+index c16716e77427d0968c004a5b30600a40a36d5137..888a60fe60e6242680c153fd8edd95f6e37e26d5 100644
--- a/src/main/java/org/bukkit/entity/Player.java
+++ b/src/main/java/org/bukkit/entity/Player.java
-@@ -2235,6 +2235,20 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+@@ -2251,6 +2251,20 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
* was {@link org.bukkit.event.player.PlayerResourcePackStatusEvent.Status#SUCCESSFULLY_LOADED}
*/
boolean hasResourcePack();
diff --git a/patches/api/0094-Add-openSign-method-to-HumanEntity.patch b/patches/api/0094-Add-openSign-method-to-HumanEntity.patch
index 01e4c7c908..275a48f7ce 100644
--- a/patches/api/0094-Add-openSign-method-to-HumanEntity.patch
+++ b/patches/api/0094-Add-openSign-method-to-HumanEntity.patch
@@ -24,10 +24,10 @@ index 8a479c7dfd3825fab8bb057d8afa5ae0cb01b071..6ef0d7f3dcb779fb7dc5786e74332620
/**
* Make the entity drop the item in their hand.
diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
-index 23df52c7ac4ec5e687e763c13a0937c3fbc5dd47..fd4613f702ebbd32ec22a81f993a1ea9d8dd896f 100644
+index 888a60fe60e6242680c153fd8edd95f6e37e26d5..87f62e65dbf8128d684abaf4fde88cfa566f7a1f 100644
--- a/src/main/java/org/bukkit/entity/Player.java
+++ b/src/main/java/org/bukkit/entity/Player.java
-@@ -2090,7 +2090,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+@@ -2106,7 +2106,7 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
/**
* Open a Sign for editing by the Player.
*
diff --git a/patches/api/0148-Expose-attack-cooldown-methods-for-Player.patch b/patches/api/0148-Expose-attack-cooldown-methods-for-Player.patch
index 4c59ac3481..adc4f4e170 100644
--- a/patches/api/0148-Expose-attack-cooldown-methods-for-Player.patch
+++ b/patches/api/0148-Expose-attack-cooldown-methods-for-Player.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] Expose attack cooldown methods for Player
diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
-index 61e75620b205cfeda0aee433651c45235bf21181..11e85e664fdd875c2a6e84b158b78d4784999932 100644
+index 81b3d2da2bb92c874845688a8b8a2b15b2a4bd59..ffbc26048e359044be6b0c96f3bd8cd1048fb316 100644
--- a/src/main/java/org/bukkit/entity/Player.java
+++ b/src/main/java/org/bukkit/entity/Player.java
-@@ -2405,6 +2405,26 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+@@ -2421,6 +2421,26 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
* @param profile The new profile to use
*/
void setPlayerProfile(@NotNull com.destroystokyo.paper.profile.PlayerProfile profile);
diff --git a/patches/api/0194-Add-Player-Client-Options-API.patch b/patches/api/0194-Add-Player-Client-Options-API.patch
index 64f5711eac..e1c25f935c 100644
--- a/patches/api/0194-Add-Player-Client-Options-API.patch
+++ b/patches/api/0194-Add-Player-Client-Options-API.patch
@@ -193,7 +193,7 @@ index 0000000000000000000000000000000000000000..f7f171c4ee0b8339b2f8fbe82442d65f
+ }
+}
diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
-index 11e85e664fdd875c2a6e84b158b78d4784999932..385846a2011ec07d4f37c98f38d3369199780418 100644
+index ffbc26048e359044be6b0c96f3bd8cd1048fb316..db12ce34a7fa4f309e7cc52c953bb409e9b6ab0c 100644
--- a/src/main/java/org/bukkit/entity/Player.java
+++ b/src/main/java/org/bukkit/entity/Player.java
@@ -2,6 +2,7 @@ package org.bukkit.entity;
@@ -204,7 +204,7 @@ index 11e85e664fdd875c2a6e84b158b78d4784999932..385846a2011ec07d4f37c98f38d33691
import com.destroystokyo.paper.Title; // Paper
import net.kyori.adventure.text.Component;
import org.bukkit.DyeColor;
-@@ -2425,6 +2426,12 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+@@ -2441,6 +2442,12 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
* Reset the cooldown counter to 0, effectively starting the cooldown period.
*/
void resetCooldown();
diff --git a/patches/api/0218-Brand-support.patch b/patches/api/0218-Brand-support.patch
index efa72be48a..0a783da287 100644
--- a/patches/api/0218-Brand-support.patch
+++ b/patches/api/0218-Brand-support.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] Brand support
diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
-index 385846a2011ec07d4f37c98f38d3369199780418..e61d0ff51674213e4711d5bbe9e8aaed31ed00df 100644
+index db12ce34a7fa4f309e7cc52c953bb409e9b6ab0c..6983b3d6d5402ea1ed3646bffdd4d8bc613a6121 100644
--- a/src/main/java/org/bukkit/entity/Player.java
+++ b/src/main/java/org/bukkit/entity/Player.java
-@@ -2560,6 +2560,16 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+@@ -2576,6 +2576,16 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
// Paper end
}
diff --git a/patches/api/0227-Player-elytra-boost-API.patch b/patches/api/0227-Player-elytra-boost-API.patch
index 4af3333673..ce6b1c1ab1 100644
--- a/patches/api/0227-Player-elytra-boost-API.patch
+++ b/patches/api/0227-Player-elytra-boost-API.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] Player elytra boost API
diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
-index e61d0ff51674213e4711d5bbe9e8aaed31ed00df..45284317f195f08e88a4977a32a1757afb6c4b17 100644
+index 6983b3d6d5402ea1ed3646bffdd4d8bc613a6121..167ea1fe8d5d16098559745a67e5128e4ef78082 100644
--- a/src/main/java/org/bukkit/entity/Player.java
+++ b/src/main/java/org/bukkit/entity/Player.java
-@@ -2432,6 +2432,19 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+@@ -2448,6 +2448,19 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
*/
@NotNull
T getClientOption(@NotNull ClientOption option);
diff --git a/patches/api/0255-Add-sendOpLevel-API.patch b/patches/api/0255-Add-sendOpLevel-API.patch
index bf2832e09f..0d543ba31d 100644
--- a/patches/api/0255-Add-sendOpLevel-API.patch
+++ b/patches/api/0255-Add-sendOpLevel-API.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] Add sendOpLevel API
diff --git a/src/main/java/org/bukkit/entity/Player.java b/src/main/java/org/bukkit/entity/Player.java
-index 45284317f195f08e88a4977a32a1757afb6c4b17..71de9f4c7f07c4c0b1155df14794de3ba8e28d69 100644
+index 167ea1fe8d5d16098559745a67e5128e4ef78082..6efcd4c5b89af422c630a566e8a851062e05b69a 100644
--- a/src/main/java/org/bukkit/entity/Player.java
+++ b/src/main/java/org/bukkit/entity/Player.java
-@@ -2445,6 +2445,17 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
+@@ -2461,6 +2461,17 @@ public interface Player extends HumanEntity, Conversable, OfflinePlayer, PluginM
*/
@Nullable
Firework boostElytra(@NotNull ItemStack firework);
diff --git a/patches/server/0004-Paper-config-files.patch b/patches/server/0004-Paper-config-files.patch
index dbcf12511f..acf5a01323 100644
--- a/patches/server/0004-Paper-config-files.patch
+++ b/patches/server/0004-Paper-config-files.patch
@@ -298,7 +298,7 @@ index 0000000000000000000000000000000000000000..bee2fa2bfbb61209381f24ed6508d3d1
+}
diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
new file mode 100644
-index 0000000000000000000000000000000000000000..e4368db074da7b5e48b47d41875c1e63b9745c2a
+index 0000000000000000000000000000000000000000..5c7b2850d311e4d54236ba9acce148bca05672fd
--- /dev/null
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -0,0 +1,188 @@
@@ -364,8 +364,8 @@ index 0000000000000000000000000000000000000000..e4368db074da7b5e48b47d41875c1e63
+ commands = new HashMap();
+ commands.put("paper", new PaperCommand("paper"));
+
-+ version = getInt("config-version", 24);
-+ set("config-version", 24);
++ version = getInt("config-version", 25);
++ set("config-version", 25);
+ readConfig(PaperConfig.class, null);
+ }
+
diff --git a/patches/server/0381-Add-tick-times-API-and-mspt-command.patch b/patches/server/0381-Add-tick-times-API-and-mspt-command.patch
index 8efbea0258..aed1d11b7a 100644
--- a/patches/server/0381-Add-tick-times-API-and-mspt-command.patch
+++ b/patches/server/0381-Add-tick-times-API-and-mspt-command.patch
@@ -75,7 +75,7 @@ index 0000000000000000000000000000000000000000..d0211d4f39f9d6af1d751ac66342b42c
+ }
+}
diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
-index e683e5bf47abe7bd3d2f7e9811a377549308ded4..c20fe23174d8a12bfc5acb4b0e947c6fca5ab897 100644
+index a3c3656ba4e8bb85bfb3186d6284f02f09975b13..6cfaef1886b517c56ed9a96347be6e51efa84d9f 100644
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -69,6 +69,7 @@ public class PaperConfig {
@@ -84,8 +84,8 @@ index e683e5bf47abe7bd3d2f7e9811a377549308ded4..c20fe23174d8a12bfc5acb4b0e947c6f
commands.put("paper", new PaperCommand("paper"));
+ commands.put("mspt", new MSPTCommand("mspt"));
- version = getInt("config-version", 24);
- set("config-version", 24);
+ version = getInt("config-version", 25);
+ set("config-version", 25);
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 76da16590f27702883c07200a02db823d9720c61..3c2af39f7bd62448a3075d327132ebc19af6bd77 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
diff --git a/patches/server/0398-Optimise-ArraySetSorted-removeIf.patch b/patches/server/0398-Optimise-ArraySetSorted-removeIf.patch
index 1f9f1e2e4e..cc09beb287 100644
--- a/patches/server/0398-Optimise-ArraySetSorted-removeIf.patch
+++ b/patches/server/0398-Optimise-ArraySetSorted-removeIf.patch
@@ -5,6 +5,41 @@ Subject: [PATCH] Optimise ArraySetSorted#removeIf
Remove iterator allocation and ensure the call is always O(n)
+diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
+index 07b616d9d7cde77c001f5c627daef0731d883e61..e8b4de332cc655a55621e40360fa46d893d903f4 100644
+--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
+@@ -85,13 +85,27 @@ public abstract class DistanceManager {
+ protected void purgeStaleTickets() {
+ ++this.ticketTickCounter;
+ ObjectIterator objectiterator = this.tickets.long2ObjectEntrySet().fastIterator();
++ // Paper start - use optimised removeIf
++ long[] currChunk = new long[1];
++ long ticketCounter = DistanceManager.this.ticketTickCounter;
++ java.util.function.Predicate> removeIf = (ticket) -> {
++ final boolean ret = ticket.timedOut(ticketCounter);
++ if (ret) {
++ this.tickingTicketsTracker.removeTicket(currChunk[0], ticket);
++ }
++ return ret;
++ };
++ // Paper end - use optimised removeIf
+
+ while (objectiterator.hasNext()) {
+ Entry>> entry = (Entry) objectiterator.next();
+- Iterator> iterator = ((SortedArraySet) entry.getValue()).iterator();
+- boolean flag = false;
++ // Paper start - use optimised removeIf
++ Iterator> iterator = null;
++ currChunk[0] = entry.getLongKey();
++ boolean flag = entry.getValue().removeIf(removeIf);
+
+- while (iterator.hasNext()) {
++ while (false && iterator.hasNext()) {
++ // Paper end - use optimised removeIf
+ Ticket> ticket = (Ticket) iterator.next();
+
+ if (ticket.timedOut(this.ticketTickCounter)) {
diff --git a/src/main/java/net/minecraft/util/SortedArraySet.java b/src/main/java/net/minecraft/util/SortedArraySet.java
index d1b2ba24ef54e01c6249c3b2ca16e80f03c001a6..5f1c4c6b9e36f2d6ec43b82cc0e2cae24b800dc4 100644
--- a/src/main/java/net/minecraft/util/SortedArraySet.java
diff --git a/patches/server/0401-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch b/patches/server/0401-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch
index cd2777bf53..2b327db275 100644
--- a/patches/server/0401-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch
+++ b/patches/server/0401-Don-t-crash-if-player-is-attempted-to-be-removed-fro.patch
@@ -7,10 +7,10 @@ Subject: [PATCH] Don't crash if player is attempted to be removed from
I suspect it deals with teleporting as it uses players current x/y/z
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
-index f0dac1f596911eb2109192ef16a619f8ae71d1f7..8868ffcda194e8c2300181a2cdda9337dbde6284 100644
+index e8b4de332cc655a55621e40360fa46d893d903f4..75f11490737018096e7645ce0b991fd210c8f596 100644
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
-@@ -283,8 +283,8 @@ public abstract class DistanceManager {
+@@ -297,8 +297,8 @@ public abstract class DistanceManager {
ObjectSet objectset = (ObjectSet) this.playersPerChunk.get(i);
if (objectset == null) return; // CraftBukkit - SPIGOT-6208
diff --git a/patches/server/0432-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch b/patches/server/0432-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch
index 4f97ceeb73..8bfa554361 100644
--- a/patches/server/0432-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch
+++ b/patches/server/0432-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch
@@ -6,10 +6,10 @@ Subject: [PATCH] Optimize anyPlayerCloseEnoughForSpawning to use distance maps
Use a distance map to find the players in range quickly
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-index 74d674b2684b0db4aa6c183edc6091d53e9ee882..626bcbc6dd013260c3f8b38a1d14e7ba35dc1e01 100644
+index 74d674b2684b0db4aa6c183edc6091d53e9ee882..560cc04ee7a8fd0c3c7217e08d53d1da682054e2 100644
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-@@ -73,6 +73,18 @@ public class ChunkHolder {
+@@ -73,6 +73,23 @@ public class ChunkHolder {
boolean isUpdateQueued = false; // Paper
private final ChunkMap chunkMap; // Paper
@@ -18,26 +18,31 @@ index 74d674b2684b0db4aa6c183edc6091d53e9ee882..626bcbc6dd013260c3f8b38a1d14e7ba
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInMobSpawnRange;
+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInChunkTickRange;
+
-+ void updateRanges() {
++ void onChunkAdd() {
+ long key = net.minecraft.server.MCUtil.getCoordinateKey(this.pos);
+ this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key);
+ this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key);
+ }
++
++ void onChunkRemove() {
++ this.playersInMobSpawnRange = null;
++ this.playersInChunkTickRange = null;
++ }
+ // Paper end - optimise anyPlayerCloseEnoughForSpawning
+
public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) {
this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size());
this.fullChunkFuture = ChunkHolder.UNLOADED_LEVEL_CHUNK_FUTURE;
-@@ -94,6 +106,7 @@ public class ChunkHolder {
+@@ -94,6 +111,7 @@ public class ChunkHolder {
this.setTicketLevel(level);
this.changedBlocksPerSection = new ShortSet[world.getSectionsCount()];
this.chunkMap = (ChunkMap)playersWatchingChunkProvider; // Paper
-+ this.updateRanges(); // Paper - optimise anyPlayerCloseEnoughForSpawning
++ this.onChunkAdd(); // Paper - optimise anyPlayerCloseEnoughForSpawning
}
// CraftBukkit start
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 7f8c82f2f50934bcc2eacd40a5fbd93d5dae68f6..745b8c16600ba106907302daa9630abdce6807b0 100644
+index 00eeaa04c98a658bac63f72da6b6d157131e9120..cd58121bfa059366919e098005299dde0c55f46f 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -177,11 +177,23 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
@@ -122,15 +127,23 @@ index 7f8c82f2f50934bcc2eacd40a5fbd93d5dae68f6..745b8c16600ba106907302daa9630abd
}
protected ChunkGenerator generator() {
-@@ -487,6 +536,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- } else {
- if (holder != null) {
- holder.setTicketLevel(level);
-+ holder.updateRanges(); // Paper - optimise anyPlayerCloseEnoughForSpawning
- }
+@@ -501,6 +550,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ holder = (ChunkHolder) this.pendingUnloads.remove(pos);
+ if (holder != null) {
+ holder.setTicketLevel(level);
++ holder.onChunkAdd(); // Paper - optimise anyPlayerCloseEnoughForSpawning - PUT HERE AFTER RE-ADDING ONLY
+ } else {
+ holder = new ChunkHolder(new ChunkPos(pos), level, this.level, this.lightEngine, this.queueSorter, this);
+ // Paper start
+@@ -600,6 +650,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ ChunkHolder playerchunk = (ChunkHolder) this.updatingChunkMap.remove(j);
- if (holder != null) {
-@@ -1331,43 +1381,48 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ if (playerchunk != null) {
++ playerchunk.onChunkRemove(); // Paper
+ this.pendingUnloads.put(j, playerchunk);
+ this.modified = true;
+ this.scheduleUnload(j, playerchunk); // Paper - Move up - don't leak chunks
+@@ -1331,43 +1382,48 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
return this.anyPlayerCloseEnoughForSpawning(pos, false);
}
@@ -211,7 +224,7 @@ index 7f8c82f2f50934bcc2eacd40a5fbd93d5dae68f6..745b8c16600ba106907302daa9630abd
public List getPlayersCloseForSpawning(ChunkPos pos) {
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
-index f11e9421d393ef5a04f24c78a1214359710cc2f8..3865146697051e01a778414064425ea0e5162b8d 100644
+index 75f11490737018096e7645ce0b991fd210c8f596..14774d51dd35a98471d29230a64fadee74651d19 100644
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
@@ -48,7 +48,7 @@ public abstract class DistanceManager {
@@ -223,7 +236,7 @@ index f11e9421d393ef5a04f24c78a1214359710cc2f8..3865146697051e01a778414064425ea0
private final TickingTracker tickingTicketsTracker = new TickingTracker();
private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33);
// Paper start use a queue, but still keep unique requirement
-@@ -125,7 +125,7 @@ public abstract class DistanceManager {
+@@ -139,7 +139,7 @@ public abstract class DistanceManager {
protected abstract ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k);
public boolean runAllUpdates(ChunkMap chunkStorage) {
@@ -232,7 +245,7 @@ index f11e9421d393ef5a04f24c78a1214359710cc2f8..3865146697051e01a778414064425ea0
this.tickingTicketsTracker.runAllUpdates();
this.playerTicketManager.runAllUpdates();
int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE);
-@@ -272,7 +272,7 @@ public abstract class DistanceManager {
+@@ -286,7 +286,7 @@ public abstract class DistanceManager {
((ObjectSet) this.playersPerChunk.computeIfAbsent(i, (j) -> {
return new ObjectOpenHashSet();
})).add(player);
@@ -241,7 +254,7 @@ index f11e9421d393ef5a04f24c78a1214359710cc2f8..3865146697051e01a778414064425ea0
this.playerTicketManager.update(i, 0, true);
this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair);
}
-@@ -286,7 +286,7 @@ public abstract class DistanceManager {
+@@ -300,7 +300,7 @@ public abstract class DistanceManager {
if (objectset != null) objectset.remove(player); // Paper - some state corruption happens here, don't crash, clean up gracefully.
if (objectset == null || objectset.isEmpty()) { // Paper
this.playersPerChunk.remove(i);
@@ -250,7 +263,7 @@ index f11e9421d393ef5a04f24c78a1214359710cc2f8..3865146697051e01a778414064425ea0
this.playerTicketManager.update(i, Integer.MAX_VALUE, false);
this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair);
}
-@@ -330,13 +330,17 @@ public abstract class DistanceManager {
+@@ -344,13 +344,17 @@ public abstract class DistanceManager {
// Paper end
public int getNaturalSpawnChunkCount() {
@@ -273,7 +286,7 @@ index f11e9421d393ef5a04f24c78a1214359710cc2f8..3865146697051e01a778414064425ea0
public String getDebugStatus() {
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-index 02516d4a6ee08908765f5bc84b14560754a67680..b327bd2f48166a4a0c831b0209695fe5935f6a68 100644
+index 8cfd38617b49ea6be318abc819f2ed5b7dabd2e1..59cd2b8ecdd2c962a027526fe45032559aa2ee8c 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -881,6 +881,37 @@ public class ServerChunkCache extends ChunkSource {
diff --git a/patches/server/0433-Use-distance-map-to-optimise-entity-tracker.patch b/patches/server/0433-Use-distance-map-to-optimise-entity-tracker.patch
index 0f096223c3..bb03397ee4 100644
--- a/patches/server/0433-Use-distance-map-to-optimise-entity-tracker.patch
+++ b/patches/server/0433-Use-distance-map-to-optimise-entity-tracker.patch
@@ -6,7 +6,7 @@ Subject: [PATCH] Use distance map to optimise entity tracker
Use the distance map to find candidate players for tracking.
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index d924dfe452244633bcf62e7ccefe3a5a1a3d7f37..e35a47e0c8eebbad7154a1357a6b868887bc4a0c 100644
+index cd58121bfa059366919e098005299dde0c55f46f..25c71ca93c54976cc319abb0d932beb6f95deb79 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -67,6 +67,7 @@ import net.minecraft.network.protocol.game.ClientboundSetEntityLinkPacket;
@@ -126,7 +126,7 @@ index d924dfe452244633bcf62e7ccefe3a5a1a3d7f37..e35a47e0c8eebbad7154a1357a6b8688
// Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
this.playerChunkTickRangeMap = new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets,
(ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
-@@ -1504,17 +1582,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1505,17 +1583,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
public void move(ServerPlayer player) {
@@ -145,7 +145,7 @@ index d924dfe452244633bcf62e7ccefe3a5a1a3d7f37..e35a47e0c8eebbad7154a1357a6b8688
int i = SectionPos.blockToSectionCoord(player.getBlockX());
int j = SectionPos.blockToSectionCoord(player.getBlockZ());
-@@ -1641,7 +1709,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1642,7 +1710,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
entity.tracker = playerchunkmap_entitytracker; // Paper - Fast access to tracker
this.entityMap.put(entity.getId(), playerchunkmap_entitytracker);
@@ -154,7 +154,7 @@ index d924dfe452244633bcf62e7ccefe3a5a1a3d7f37..e35a47e0c8eebbad7154a1357a6b8688
if (entity instanceof ServerPlayer) {
ServerPlayer entityplayer = (ServerPlayer) entity;
-@@ -1685,7 +1753,37 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1686,7 +1754,37 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
entity.tracker = null; // Paper - We're no longer tracked
}
@@ -192,7 +192,7 @@ index d924dfe452244633bcf62e7ccefe3a5a1a3d7f37..e35a47e0c8eebbad7154a1357a6b8688
List list = Lists.newArrayList();
List list1 = this.level.players();
ObjectIterator objectiterator = this.entityMap.values().iterator();
-@@ -1761,23 +1859,31 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1762,23 +1860,31 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
DebugPackets.sendPoiPacketsForChunk(this.level, chunk.getPos());
List list = Lists.newArrayList();
List list1 = Lists.newArrayList();
@@ -236,7 +236,7 @@ index d924dfe452244633bcf62e7ccefe3a5a1a3d7f37..e35a47e0c8eebbad7154a1357a6b8688
Iterator iterator;
Entity entity1;
-@@ -1853,6 +1959,42 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1854,6 +1960,42 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.lastSectionPos = SectionPos.of(entity);
}
@@ -280,7 +280,7 @@ index d924dfe452244633bcf62e7ccefe3a5a1a3d7f37..e35a47e0c8eebbad7154a1357a6b8688
return object instanceof ChunkMap.TrackedEntity ? ((ChunkMap.TrackedEntity) object).entity.getId() == this.entity.getId() : false;
}
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
-index a02eb37845e0609ddf14a4214395e00443534b08..8e59a63e8c161e90ccff951ecb2b6530fadf3b80 100644
+index 7bf62752b6604abe0bda6f5d0024f0e93efb3a9a..9a4454746452f64d7a8caa52eef2dca86e9edd94 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -51,6 +51,7 @@ import net.minecraft.network.syncher.EntityDataSerializers;
diff --git a/patches/server/0435-Delay-Chunk-Unloads-based-on-Player-Movement.patch b/patches/server/0435-Delay-Chunk-Unloads-based-on-Player-Movement.patch
index 1eed6fd1c2..d626ac1f6c 100644
--- a/patches/server/0435-Delay-Chunk-Unloads-based-on-Player-Movement.patch
+++ b/patches/server/0435-Delay-Chunk-Unloads-based-on-Player-Movement.patch
@@ -35,10 +35,10 @@ index bc880a78381b9c30e4ec92bdf17c9eca1ded41f2..eae27a641ee69f3383f1200f8c8ab8dd
+ }
}
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
-index 3865146697051e01a778414064425ea0e5162b8d..cd81e7844c985d7d0f0930faa96478af6a7f6cd4 100644
+index 14774d51dd35a98471d29230a64fadee74651d19..93a642cd6cf21a268074a73bff22249d32992ad3 100644
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
-@@ -197,6 +197,27 @@ public abstract class DistanceManager {
+@@ -211,6 +211,27 @@ public abstract class DistanceManager {
boolean removed = false; // CraftBukkit
if (arraysetsorted.remove(ticket)) {
removed = true; // CraftBukkit
diff --git a/patches/server/0456-incremental-chunk-and-player-saving.patch b/patches/server/0456-incremental-chunk-and-player-saving.patch
index 2507e27291..6f948b6f95 100644
--- a/patches/server/0456-incremental-chunk-and-player-saving.patch
+++ b/patches/server/0456-incremental-chunk-and-player-saving.patch
@@ -5,7 +5,7 @@ Subject: [PATCH] incremental chunk and player saving
diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
-index a545a882335094daa787ec5c7005939080195263..933d3ace21f5a313f1d5e4dfd86777f6fa235f3f 100644
+index df1337c89c943a80a31d127c844f061c1e56eb91..cb967380155d34ff511346d54b61e1c2109780f3 100644
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
@@ -459,4 +459,14 @@ public class PaperConfig {
@@ -24,7 +24,7 @@ index a545a882335094daa787ec5c7005939080195263..933d3ace21f5a313f1d5e4dfd86777f6
+ }
}
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
-index d6d549df6e8920c936dd0d1b7ba828dbebc60b32..8b4b521a84c8623665d21d0340bca7665953d20b 100644
+index eae27a641ee69f3383f1200f8c8ab8dd7a4105a4..12a6f12ffa8a45b054b01a7edc73ca8a5bcb59b5 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -64,6 +64,21 @@ public class PaperWorldConfig {
@@ -50,7 +50,7 @@ index d6d549df6e8920c936dd0d1b7ba828dbebc60b32..8b4b521a84c8623665d21d0340bca766
config.addDefault("world-settings.default." + path, def);
return config.getBoolean("world-settings." + worldName + "." + path, config.getBoolean("world-settings.default." + path));
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
-index 918fdf14080338983b8725bf2619088fd23c332a..95842327aa08d4717f86e9dcc0519ab24c41ca14 100644
+index 9a926114aa550ca5a6329e3ce993bf1f686cd10e..ab7ca991e5d7940a386a820e1953df1ac2428107 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -902,7 +902,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop> completablefuture = (CompletableFuture) this.futures.get(i);
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index e35a47e0c8eebbad7154a1357a6b868887bc4a0c..cac9c9ede6024653f4ae83cbbdd9939f75ccad48 100644
+index 25c71ca93c54976cc319abb0d932beb6f95deb79..e66e4d1656e5f6f042c5c63715e13a6b5e7d81c5 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -101,6 +101,7 @@ import net.minecraft.world.level.levelgen.structure.templatesystem.StructureMana
@@ -241,7 +241,7 @@ index e35a47e0c8eebbad7154a1357a6b868887bc4a0c..cac9c9ede6024653f4ae83cbbdd9939f
protected void saveAllChunks(boolean flush) {
if (flush) {
List list = (List) this.visibleChunkMap.values().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).collect(Collectors.toList());
-@@ -751,13 +810,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -752,13 +811,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
int l = 0;
@@ -256,7 +256,7 @@ index e35a47e0c8eebbad7154a1357a6b868887bc4a0c..cac9c9ede6024653f4ae83cbbdd9939f
}
-@@ -795,6 +848,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -796,6 +849,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.level.unload(chunk);
}
@@ -264,7 +264,7 @@ index e35a47e0c8eebbad7154a1357a6b868887bc4a0c..cac9c9ede6024653f4ae83cbbdd9939f
this.lightEngine.updateChunkStatus(ichunkaccess.getPos());
this.lightEngine.tryScheduleUpdate();
-@@ -1192,6 +1246,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1193,6 +1247,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
asyncSaveData, chunk);
chunk.setUnsaved(false);
@@ -272,7 +272,7 @@ index e35a47e0c8eebbad7154a1357a6b868887bc4a0c..cac9c9ede6024653f4ae83cbbdd9939f
}
// Paper end
-@@ -1201,6 +1256,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1202,6 +1257,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
if (!chunk.isUnsaved()) {
return false;
} else {
@@ -281,7 +281,7 @@ index e35a47e0c8eebbad7154a1357a6b868887bc4a0c..cac9c9ede6024653f4ae83cbbdd9939f
ChunkPos chunkcoordintpair = chunk.getPos();
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-index b327bd2f48166a4a0c831b0209695fe5935f6a68..73b1018b0aca377ed0ff188e74040cef57036b0b 100644
+index 59cd2b8ecdd2c962a027526fe45032559aa2ee8c..fdc027bc5a7e9d691ac90c5fee0cdfebd959bc5f 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -825,6 +825,15 @@ public class ServerChunkCache extends ChunkSource {
@@ -301,7 +301,7 @@ index b327bd2f48166a4a0c831b0209695fe5935f6a68..73b1018b0aca377ed0ff188e74040cef
public void close() throws IOException {
// CraftBukkit start
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 0d70647bdd08371beaf476bd4eb5a45d985e498d..b27fb07aacb66259f640de5c5aa6849eb7e8cc9c 100644
+index 04aae918f1951291459ce17b25986e5cac9d2378..3b99a6dda3638f3a7212d7c9fab5470409de4e6f 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1058,6 +1058,37 @@ public class ServerLevel extends Level implements WorldGenLevel {
diff --git a/patches/server/0470-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch b/patches/server/0470-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch
index febb8e646c..d9c530e0a6 100644
--- a/patches/server/0470-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch
+++ b/patches/server/0470-Implement-Chunk-Priority-Urgency-System-for-Chunks.patch
@@ -80,7 +80,7 @@ index 89e0181af99cba2368f875fc192342efc972f2ef..b3516862d796c2d9fcc1c67a60734454
chunkData.addProperty("queued-for-unload", chunkMap.toDrop.contains(playerChunk.pos.longKey));
chunkData.addProperty("status", status == null ? "unloaded" : status.toString());
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-index 9e96b0465717bfa761289c255fd8d2f1df1be3d8..87271552aa85626f22f7f8569c8fb48fe4b30bf3 100644
+index 5479deb3f80a95c58cb6f0f532b2f56b71a68a3a..b62d7a85db012265dd4fae02cbed5363a41d536b 100644
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
@@ -57,7 +57,7 @@ public class ChunkHolder {
@@ -100,7 +100,7 @@ index 9e96b0465717bfa761289c255fd8d2f1df1be3d8..87271552aa85626f22f7f8569c8fb48f
boolean isUpdateQueued = false; // Paper
private final ChunkMap chunkMap; // Paper
-@@ -419,12 +420,18 @@ public class ChunkHolder {
+@@ -424,12 +425,18 @@ public class ChunkHolder {
});
}
@@ -119,7 +119,7 @@ index 9e96b0465717bfa761289c255fd8d2f1df1be3d8..87271552aa85626f22f7f8569c8fb48f
ChunkStatus chunkstatus = ChunkHolder.getStatus(this.oldTicketLevel);
ChunkStatus chunkstatus1 = ChunkHolder.getStatus(this.ticketLevel);
boolean flag = this.oldTicketLevel <= ChunkMap.MAX_CHUNK_DISTANCE;
-@@ -435,9 +442,22 @@ public class ChunkHolder {
+@@ -440,9 +447,22 @@ public class ChunkHolder {
// 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.getFutureIfPresentUnchecked(ChunkStatus.FULL).thenAccept((either) -> {
@@ -143,7 +143,7 @@ index 9e96b0465717bfa761289c255fd8d2f1df1be3d8..87271552aa85626f22f7f8569c8fb48f
// 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.
-@@ -494,12 +514,14 @@ public class ChunkHolder {
+@@ -499,12 +519,14 @@ public class ChunkHolder {
this.scheduleFullChunkPromotion(chunkStorage, this.fullChunkFuture, executor, ChunkHolder.FullChunkStatus.BORDER);
// Paper start - cache ticking ready status
this.fullChunkFuture.thenAccept(either -> {
@@ -158,7 +158,7 @@ index 9e96b0465717bfa761289c255fd8d2f1df1be3d8..87271552aa85626f22f7f8569c8fb48f
}
});
this.updateChunkToSave(this.fullChunkFuture, "full");
-@@ -520,6 +542,7 @@ public class ChunkHolder {
+@@ -525,6 +547,7 @@ public class ChunkHolder {
this.scheduleFullChunkPromotion(chunkStorage, this.tickingChunkFuture, executor, ChunkHolder.FullChunkStatus.TICKING);
// Paper start - cache ticking ready status
this.tickingChunkFuture.thenAccept(either -> {
@@ -166,7 +166,7 @@ index 9e96b0465717bfa761289c255fd8d2f1df1be3d8..87271552aa85626f22f7f8569c8fb48f
either.ifLeft(chunk -> {
// note: Here is a very good place to add callbacks to logic waiting on this.
ChunkHolder.this.isTickingReady = true;
-@@ -555,6 +578,7 @@ public class ChunkHolder {
+@@ -560,6 +583,7 @@ public class ChunkHolder {
this.scheduleFullChunkPromotion(chunkStorage, this.entityTickingChunkFuture, executor, ChunkHolder.FullChunkStatus.ENTITY_TICKING);
// Paper start - cache ticking ready status
this.entityTickingChunkFuture.thenAccept(either -> {
@@ -174,7 +174,7 @@ index 9e96b0465717bfa761289c255fd8d2f1df1be3d8..87271552aa85626f22f7f8569c8fb48f
either.ifLeft(chunk -> {
ChunkHolder.this.isEntityTickingReady = true;
// Paper start - entity ticking chunk set
-@@ -581,16 +605,45 @@ public class ChunkHolder {
+@@ -586,16 +610,45 @@ public class ChunkHolder {
this.demoteFullChunk(chunkStorage, playerchunk_state1);
}
@@ -223,7 +223,7 @@ index 9e96b0465717bfa761289c255fd8d2f1df1be3d8..87271552aa85626f22f7f8569c8fb48f
});
}
}).exceptionally((throwable) -> {
-@@ -705,7 +758,134 @@ public class ChunkHolder {
+@@ -710,7 +763,134 @@ public class ChunkHolder {
};
}
@@ -360,7 +360,7 @@ index 9e96b0465717bfa761289c255fd8d2f1df1be3d8..87271552aa85626f22f7f8569c8fb48f
return this.isEntityTickingReady;
}
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 92cb20466d2747e6013bf433d87c5bb30a89ba4f..11b3a3105cac879d8a52290967b9d3b638fb3721 100644
+index e66e4d1656e5f6f042c5c63715e13a6b5e7d81c5..aef2974ecec878a014ada9619d814ae333f4923a 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -128,6 +128,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
@@ -448,7 +448,7 @@ index 92cb20466d2747e6013bf433d87c5bb30a89ba4f..11b3a3105cac879d8a52290967b9d3b6
list1.add(playerchunk);
list.add(completablefuture);
-@@ -888,11 +938,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -889,11 +939,19 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
if (requiredStatus == ChunkStatus.EMPTY) {
return this.scheduleChunkLoad(chunkcoordintpair);
} else {
@@ -469,7 +469,7 @@ index 92cb20466d2747e6013bf433d87c5bb30a89ba4f..11b3a3105cac879d8a52290967b9d3b6
if (optional.isPresent() && ((ChunkAccess) optional.get()).getStatus().isOrAfter(requiredStatus)) {
CompletableFuture> completablefuture = requiredStatus.load(this.level, this.structureManager, this.lightEngine, (ichunkaccess) -> {
-@@ -904,6 +962,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -905,6 +963,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
} else {
return this.scheduleChunkGeneration(holder, requiredStatus);
}
@@ -477,7 +477,7 @@ index 92cb20466d2747e6013bf433d87c5bb30a89ba4f..11b3a3105cac879d8a52290967b9d3b6
}
}
-@@ -960,14 +1019,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -961,14 +1020,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
};
CompletableFuture chunkSaveFuture = this.level.asyncChunkTaskManager.getChunkSaveFuture(pos.x, pos.z);
@@ -507,7 +507,7 @@ index 92cb20466d2747e6013bf433d87c5bb30a89ba4f..11b3a3105cac879d8a52290967b9d3b6
return ret;
// Paper end
}
-@@ -1019,7 +1088,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1020,7 +1089,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.releaseLightTicket(chunkcoordintpair);
return CompletableFuture.completedFuture(Either.right(playerchunk_failure));
});
@@ -519,7 +519,7 @@ index 92cb20466d2747e6013bf433d87c5bb30a89ba4f..11b3a3105cac879d8a52290967b9d3b6
}
protected void releaseLightTicket(ChunkPos pos) {
-@@ -1103,7 +1175,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1104,7 +1176,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
long i = chunkHolder.getPos().toLong();
Objects.requireNonNull(chunkHolder);
@@ -529,10 +529,10 @@ index 92cb20466d2747e6013bf433d87c5bb30a89ba4f..11b3a3105cac879d8a52290967b9d3b6
}
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
-index cd81e7844c985d7d0f0930faa96478af6a7f6cd4..1744f4983b24a87f3861ebd5d68120cfce904934 100644
+index 93a642cd6cf21a268074a73bff22249d32992ad3..7c66e4ed02c80521196b4ca797477dd9573752d8 100644
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
-@@ -113,6 +113,7 @@ public abstract class DistanceManager {
+@@ -127,6 +127,7 @@ public abstract class DistanceManager {
}
private static int getTicketLevelAt(SortedArraySet> tickets) {
@@ -540,7 +540,7 @@ index cd81e7844c985d7d0f0930faa96478af6a7f6cd4..1744f4983b24a87f3861ebd5d68120cf
return !tickets.isEmpty() ? ((Ticket) tickets.first()).getTicketLevel() : ChunkMap.MAX_CHUNK_DISTANCE + 1;
}
-@@ -127,6 +128,7 @@ public abstract class DistanceManager {
+@@ -141,6 +142,7 @@ public abstract class DistanceManager {
public boolean runAllUpdates(ChunkMap chunkStorage) {
//this.f.a(); // Paper - no longer used
this.tickingTicketsTracker.runAllUpdates();
@@ -548,7 +548,7 @@ index cd81e7844c985d7d0f0930faa96478af6a7f6cd4..1744f4983b24a87f3861ebd5d68120cf
this.playerTicketManager.runAllUpdates();
int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE);
boolean flag = i != 0;
-@@ -137,11 +139,13 @@ public abstract class DistanceManager {
+@@ -151,11 +153,13 @@ public abstract class DistanceManager {
// Paper start
if (!this.pendingChunkUpdates.isEmpty()) {
@@ -562,7 +562,7 @@ index cd81e7844c985d7d0f0930faa96478af6a7f6cd4..1744f4983b24a87f3861ebd5d68120cf
// Paper end
return true;
} else {
-@@ -177,8 +181,10 @@ public abstract class DistanceManager {
+@@ -191,8 +195,10 @@ public abstract class DistanceManager {
return flag;
}
}
@@ -573,7 +573,7 @@ index cd81e7844c985d7d0f0930faa96478af6a7f6cd4..1744f4983b24a87f3861ebd5d68120cf
SortedArraySet> arraysetsorted = this.getTickets(i);
int j = DistanceManager.getTicketLevelAt(arraysetsorted);
Ticket> ticket1 = (Ticket) arraysetsorted.addOrGet(ticket);
-@@ -192,7 +198,9 @@ public abstract class DistanceManager {
+@@ -206,7 +212,9 @@ public abstract class DistanceManager {
}
boolean removeTicket(long i, Ticket> ticket) { // CraftBukkit - void -> boolean
@@ -583,7 +583,7 @@ index cd81e7844c985d7d0f0930faa96478af6a7f6cd4..1744f4983b24a87f3861ebd5d68120cf
boolean removed = false; // CraftBukkit
if (arraysetsorted.remove(ticket)) {
-@@ -224,7 +232,12 @@ public abstract class DistanceManager {
+@@ -238,7 +246,12 @@ public abstract class DistanceManager {
this.tickets.remove(i);
}
@@ -597,7 +597,7 @@ index cd81e7844c985d7d0f0930faa96478af6a7f6cd4..1744f4983b24a87f3861ebd5d68120cf
return removed; // CraftBukkit
}
-@@ -272,6 +285,112 @@ public abstract class DistanceManager {
+@@ -286,6 +299,112 @@ public abstract class DistanceManager {
});
}
diff --git a/patches/server/0490-Improve-Chunk-Status-Transition-Speed.patch b/patches/server/0490-Improve-Chunk-Status-Transition-Speed.patch
index 97c098d49a..d5c6e82268 100644
--- a/patches/server/0490-Improve-Chunk-Status-Transition-Speed.patch
+++ b/patches/server/0490-Improve-Chunk-Status-Transition-Speed.patch
@@ -36,10 +36,10 @@ scenario / path:
Previously would have hopped to SERVER around 12+ times there extra.
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-index 87271552aa85626f22f7f8569c8fb48fe4b30bf3..80aae4303e011dad13ce818136f0383e12ab5c41 100644
+index b62d7a85db012265dd4fae02cbed5363a41d536b..9810810467a9052f3362056e9372dd3d0f56b417 100644
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-@@ -87,6 +87,13 @@ public class ChunkHolder {
+@@ -92,6 +92,13 @@ public class ChunkHolder {
// Paper end - optimise anyPlayerCloseEnoughForSpawning
long lastAutoSaveTime; // Paper - incremental autosave
long inactiveTimeStart; // Paper - incremental autosave
@@ -54,7 +54,7 @@ index 87271552aa85626f22f7f8569c8fb48fe4b30bf3..80aae4303e011dad13ce818136f0383e
public ChunkHolder(ChunkPos pos, int level, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.LevelChangeListener levelUpdateListener, ChunkHolder.PlayerProvider playersWatchingChunkProvider) {
this.futures = new AtomicReferenceArray(ChunkHolder.CHUNK_STATUSES.size());
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 3e6cc16ff6f6c7993b15bd807257c0554afb6c77..a737fb56fef791f53ed3b0252e5a0369c22bcc7d 100644
+index aef2974ecec878a014ada9619d814ae333f4923a..f4a10bd3bbd421999b5ab2d5896b1aec57eb6a82 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -655,7 +655,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
@@ -66,7 +66,7 @@ index 3e6cc16ff6f6c7993b15bd807257c0554afb6c77..a737fb56fef791f53ed3b0252e5a0369
}
@Nullable
-@@ -1059,6 +1059,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1060,6 +1060,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
return "chunkGenerate " + requiredStatus.getName();
});
Executor executor = (runnable) -> {
diff --git a/patches/server/0507-Don-t-mark-dirty-in-invalid-locations-SPIGOT-6086.patch b/patches/server/0507-Don-t-mark-dirty-in-invalid-locations-SPIGOT-6086.patch
index 77d27fca91..d97a6bdd6f 100644
--- a/patches/server/0507-Don-t-mark-dirty-in-invalid-locations-SPIGOT-6086.patch
+++ b/patches/server/0507-Don-t-mark-dirty-in-invalid-locations-SPIGOT-6086.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] Don't mark dirty in invalid locations (SPIGOT-6086)
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-index 80aae4303e011dad13ce818136f0383e12ab5c41..17a71e2fce455552c0e8af4073c516c21bc3e208 100644
+index 9810810467a9052f3362056e9372dd3d0f56b417..c2a040f735db091b5fb6017aa28a95f91ebcd9d3 100644
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-@@ -229,6 +229,7 @@ public class ChunkHolder {
+@@ -234,6 +234,7 @@ public class ChunkHolder {
}
public void blockChanged(BlockPos pos) {
diff --git a/patches/server/0734-Correctly-handle-recursion-for-chunkholder-updates.patch b/patches/server/0734-Correctly-handle-recursion-for-chunkholder-updates.patch
index d4a47032bf..531c87e6ea 100644
--- a/patches/server/0734-Correctly-handle-recursion-for-chunkholder-updates.patch
+++ b/patches/server/0734-Correctly-handle-recursion-for-chunkholder-updates.patch
@@ -8,10 +8,10 @@ cause a recursive call which would handle the increase but then
the caller would think the chunk would be unloaded.
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-index 17a71e2fce455552c0e8af4073c516c21bc3e208..6bfc705cbf3d8efdb0926df4e1bc6948f9052440 100644
+index c2a040f735db091b5fb6017aa28a95f91ebcd9d3..49247d12e743f987113524121e60a3e0ba4a825a 100644
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-@@ -438,8 +438,10 @@ public class ChunkHolder {
+@@ -443,8 +443,10 @@ public class ChunkHolder {
playerchunkmap.onFullChunkStatusChange(this.pos, playerchunk_state);
}
@@ -22,7 +22,7 @@ index 17a71e2fce455552c0e8af4073c516c21bc3e208..6bfc705cbf3d8efdb0926df4e1bc6948
ChunkStatus chunkstatus = ChunkHolder.getStatus(this.oldTicketLevel);
ChunkStatus chunkstatus1 = ChunkHolder.getStatus(this.ticketLevel);
boolean flag = this.oldTicketLevel <= ChunkMap.MAX_CHUNK_DISTANCE;
-@@ -481,6 +483,12 @@ public class ChunkHolder {
+@@ -486,6 +488,12 @@ public class ChunkHolder {
// Run callback right away if the future was already done
chunkStorage.callbackExecutor.run();
diff --git a/patches/server/0736-Fix-chunks-refusing-to-unload-at-low-TPS.patch b/patches/server/0736-Fix-chunks-refusing-to-unload-at-low-TPS.patch
index fa0fe11e0b..72e1040974 100644
--- a/patches/server/0736-Fix-chunks-refusing-to-unload-at-low-TPS.patch
+++ b/patches/server/0736-Fix-chunks-refusing-to-unload-at-low-TPS.patch
@@ -10,10 +10,10 @@ chunk future to complete. We can simply schedule to the immediate
executor to get this effect, rather than the main mailbox.
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index ea0e86c3eb124a3ea10276776bfc9f32f4079792..01f95016dc8974db6d6fc09f54294673d83c2297 100644
+index 73cd4d12a4991a69adf3f19665bfb5672b7f2ecf..a7bc1d6ce7eebfe0b10f8c179b6a1441169d8fc1 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -1272,9 +1272,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1273,9 +1273,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
return chunk;
});
diff --git a/patches/server/0737-Do-not-allow-ticket-level-changes-while-unloading-pl.patch b/patches/server/0737-Do-not-allow-ticket-level-changes-while-unloading-pl.patch
index 9ddac76d98..2a955f5ece 100644
--- a/patches/server/0737-Do-not-allow-ticket-level-changes-while-unloading-pl.patch
+++ b/patches/server/0737-Do-not-allow-ticket-level-changes-while-unloading-pl.patch
@@ -8,7 +8,7 @@ Sync loading the chunk at this stage would cause it to load
older data, as well as screwing our region state.
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 382532963a856478aa663d9b66e2169eaa36aefc..ba5dab2d3bc8325e5678ef58fedac5b2bc7e38d3 100644
+index a7bc1d6ce7eebfe0b10f8c179b6a1441169d8fc1..c773d6ca536389aac5efda6112440e1b0b85594d 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -309,6 +309,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
@@ -27,7 +27,7 @@ index 382532963a856478aa663d9b66e2169eaa36aefc..ba5dab2d3bc8325e5678ef58fedac5b2
if (k > ChunkMap.MAX_CHUNK_DISTANCE && level > ChunkMap.MAX_CHUNK_DISTANCE) {
return holder;
} else {
-@@ -883,6 +885,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -884,6 +886,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
if (completablefuture1 != completablefuture) {
this.scheduleUnload(pos, holder);
} else {
@@ -40,7 +40,7 @@ index 382532963a856478aa663d9b66e2169eaa36aefc..ba5dab2d3bc8325e5678ef58fedac5b2
// Paper start
boolean removed;
if ((removed = this.pendingUnloads.remove(pos, holder)) && ichunkaccess != null) {
-@@ -919,6 +927,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -920,6 +928,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.regionManagers.get(index).removeChunk(holder.pos.x, holder.pos.z);
}
} // Paper end
@@ -49,7 +49,7 @@ index 382532963a856478aa663d9b66e2169eaa36aefc..ba5dab2d3bc8325e5678ef58fedac5b2
}
};
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-index 55cc46fb065246ef9fc8332ecc1db75a20986c81..b3b10baad56bf14a31a11a3375000195ce84d4d3 100644
+index 9cb9bd439be0595e8c94bf128fecc2bd6c1647fc..fc404951b8a910594ffa42aee5ebb8bc3d9bbca4 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
@@ -820,6 +820,7 @@ public class ServerChunkCache extends ChunkSource {
diff --git a/patches/server/0738-Do-not-allow-ticket-level-changes-when-updating-chun.patch b/patches/server/0738-Do-not-allow-ticket-level-changes-when-updating-chun.patch
index 538eaa84b7..0d4632a0ef 100644
--- a/patches/server/0738-Do-not-allow-ticket-level-changes-when-updating-chun.patch
+++ b/patches/server/0738-Do-not-allow-ticket-level-changes-when-updating-chun.patch
@@ -8,10 +8,10 @@ This WILL cause state corruption if it happens. So, don't
allow it.
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-index 6bfc705cbf3d8efdb0926df4e1bc6948f9052440..1f602d50f3212078490c0092ceefd3b17e0b1532 100644
+index 49247d12e743f987113524121e60a3e0ba4a825a..b473a0e3235405dde46d9d6cb300e88d6b1d121b 100644
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-@@ -418,7 +418,13 @@ public class ChunkHolder {
+@@ -423,7 +423,13 @@ public class ChunkHolder {
CompletableFuture completablefuture1 = new CompletableFuture();
completablefuture1.thenRunAsync(() -> {
@@ -25,7 +25,7 @@ index 6bfc705cbf3d8efdb0926df4e1bc6948f9052440..1f602d50f3212078490c0092ceefd3b1
}, executor);
this.pendingFullStateConfirmation = completablefuture1;
completablefuture.thenAccept((either) -> {
-@@ -435,7 +441,12 @@ public class ChunkHolder {
+@@ -440,7 +446,12 @@ public class ChunkHolder {
private void demoteFullChunk(ChunkMap playerchunkmap, ChunkHolder.FullChunkStatus playerchunk_state) {
this.pendingFullStateConfirmation.cancel(false);
diff --git a/patches/server/0748-Optimise-chunk-tick-iteration.patch b/patches/server/0748-Optimise-chunk-tick-iteration.patch
index f640eb1f10..c3263b021e 100644
--- a/patches/server/0748-Optimise-chunk-tick-iteration.patch
+++ b/patches/server/0748-Optimise-chunk-tick-iteration.patch
@@ -5,11 +5,95 @@ Subject: [PATCH] Optimise chunk tick iteration
Use a dedicated list of entity ticking chunks to reduce the cost
+diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
+index b473a0e3235405dde46d9d6cb300e88d6b1d121b..43f97571f0b837cbba5c3e889edbc5737a6e7b54 100644
+--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
+@@ -83,11 +83,21 @@ 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);
++ // Paper start - optimise chunk tick iteration
++ if (this.needsBroadcastChanges()) {
++ this.chunkMap.needsChangeBroadcasting.add(this);
++ }
++ // Paper end - optimise chunk tick iteration
+ }
+
+ void onChunkRemove() {
+ this.playersInMobSpawnRange = null;
+ this.playersInChunkTickRange = null;
++ // Paper start - optimise chunk tick iteration
++ if (this.needsBroadcastChanges()) {
++ this.chunkMap.needsChangeBroadcasting.remove(this);
++ }
++ // Paper end - optimise chunk tick iteration
+ }
+ // Paper end - optimise anyPlayerCloseEnoughForSpawning
+ long lastAutoSaveTime; // Paper - incremental autosave
+@@ -242,7 +252,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.hasChangedSections = true; this.addToBroadcastMap(); // Paper - optimise chunk tick iteration
+ this.changedBlocksPerSection[i] = new ShortOpenHashSet();
+ }
+
+@@ -259,6 +269,7 @@ public class ChunkHolder {
+ int k = this.lightEngine.getMaxLightSection();
+
+ if (y >= j && y <= k) {
++ this.addToBroadcastMap(); // Paper - optimise chunk tick iteration
+ int l = y - j;
+
+ if (lightType == LightLayer.SKY) {
+@@ -271,8 +282,19 @@ public class ChunkHolder {
+ }
+ }
+
++ // Paper start - optimise chunk tick iteration
++ public final boolean needsBroadcastChanges() {
++ return this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty();
++ }
++
++ private void addToBroadcastMap() {
++ org.spigotmc.AsyncCatcher.catchOp("ChunkHolder update");
++ this.chunkMap.needsChangeBroadcasting.add(this);
++ }
++ // Paper end - optimise chunk tick iteration
++
+ public void broadcastChanges(LevelChunk chunk) {
+- if (this.hasChangedSections || !this.skyChangedLightSectionFilter.isEmpty() || !this.blockChangedLightSectionFilter.isEmpty()) {
++ if (this.needsBroadcastChanges()) { // Paper - moved into above, other logic needs to call
+ Level world = chunk.getLevel();
+ int i = 0;
+
+diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
+index c773d6ca536389aac5efda6112440e1b0b85594d..a7fcf3042e6d26739051ed37efbef4a722561c5a 100644
+--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
+@@ -155,6 +155,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ private final Queue unloadQueue;
+ int viewDistance;
+ public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobDistanceMap; // Paper
++ public final ReferenceOpenHashSet needsChangeBroadcasting = new ReferenceOpenHashSet<>();
+
+ // CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback()
+ public final CallbackExecutor callbackExecutor = new CallbackExecutor();
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-index 7c3a1622f161409e92c4ccfa13e8c1fc1d66c947..f7b2fa30eac4da6847d77fd3b2bdd73349b41822 100644
+index 59ebce5b9f4db5e48b4b46d68e7325f2d2820bd6..ab1b24820a5ec576f33095ad5688aaecd44910f4 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-@@ -995,34 +995,46 @@ public class ServerChunkCache extends ChunkSource {
+@@ -50,6 +50,7 @@ import org.apache.logging.log4j.LogManager;
+ import org.apache.logging.log4j.Logger;
+ import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; // Paper
+ import java.util.function.Function; // Paper
++import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; // Paper
+
+ public class ServerChunkCache extends ChunkSource {
+
+@@ -995,34 +996,42 @@ public class ServerChunkCache extends ChunkSource {
this.lastSpawnState = spawnercreature_d;
gameprofilerfiller.popPush("filteringLoadedChunks");
@@ -56,23 +140,19 @@ index 7c3a1622f161409e92c4ccfa13e8c1fc1d66c947..f7b2fa30eac4da6847d77fd3b2bdd733
+ LevelChunk chunk1 = iterator1.next();
+ ChunkHolder holder = chunk1.playerChunk;
+ if (holder != null) {
-+ gameprofilerfiller.popPush("broadcast");
-+ this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing
-+ holder.broadcastChanges(chunk1);
-+ this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing
-+ gameprofilerfiller.pop();
++ // Paper - move down
+ // Paper end - optimise chunk tick iteration
ChunkPos chunkcoordintpair = chunk1.getPos();
- if (this.level.isPositionEntityTicking(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning
-+ if ((true || this.level.isPositionEntityTicking(chunkcoordintpair)) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning & optimise chunk tick iteration
++ if (this.level.isPositionEntityTicking(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning
chunk1.incrementInhabitedTime(j);
- if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning
+ if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning & optimise chunk tick iteration
NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
}
-@@ -1030,7 +1042,16 @@ public class ServerChunkCache extends ChunkSource {
+@@ -1030,7 +1039,16 @@ public class ServerChunkCache extends ChunkSource {
this.level.tickChunk(chunk1, k);
}
}
@@ -89,18 +169,35 @@ index 7c3a1622f161409e92c4ccfa13e8c1fc1d66c947..f7b2fa30eac4da6847d77fd3b2bdd733
this.level.timings.chunkTicks.stopTiming(); // Paper
gameprofilerfiller.popPush("customSpawners");
if (flag2) {
-@@ -1039,13 +1060,7 @@ public class ServerChunkCache extends ChunkSource {
+@@ -1038,15 +1056,24 @@ public class ServerChunkCache extends ChunkSource {
+ this.level.tickCustomSpawners(this.spawnEnemies, this.spawnFriendlies);
} // Paper - timings
}
-
+-
- gameprofilerfiller.popPush("broadcast");
- list.forEach((chunkproviderserver_a1) -> {
- this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing
- chunkproviderserver_a1.holder.broadcastChanges(chunkproviderserver_a1.chunk);
- this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing
- });
-- gameprofilerfiller.pop();
-+ // Paper - no, iterating just ONCE is expensive enough! Don't do it TWICE! Code moved up
gameprofilerfiller.pop();
++ // Paper start - use set of chunks requiring updates, rather than iterating every single one loaded
++ gameprofilerfiller.popPush("broadcast");
++ this.level.timings.broadcastChunkUpdates.startTiming(); // Paper - timing
++ if (!this.chunkMap.needsChangeBroadcasting.isEmpty()) {
++ ReferenceOpenHashSet copy = this.chunkMap.needsChangeBroadcasting.clone();
++ this.chunkMap.needsChangeBroadcasting.clear();
++ for (ChunkHolder holder : copy) {
++ holder.broadcastChanges(holder.getFullChunkUnchecked()); // LevelChunks are NEVER unloaded
++ if (holder.needsBroadcastChanges()) {
++ // I DON'T want to KNOW what DUMB plugins might be doing.
++ this.chunkMap.needsChangeBroadcasting.add(holder);
++ }
++ }
++ }
++ this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing
+ gameprofilerfiller.pop();
++ // Paper end - use set of chunks requiring updates, rather than iterating every single one loaded
this.chunkMap.tick();
}
+ }
diff --git a/patches/server/0749-Execute-chunk-tasks-mid-tick.patch b/patches/server/0749-Execute-chunk-tasks-mid-tick.patch
index 0ee692aaa5..6f9ccac575 100644
--- a/patches/server/0749-Execute-chunk-tasks-mid-tick.patch
+++ b/patches/server/0749-Execute-chunk-tasks-mid-tick.patch
@@ -108,10 +108,10 @@ index 79e5b8a05828bbc07468d2deeb0f4dad51ca12a5..7be369cf60e45a551fb2d274b9514856
} else {
if (this.haveTime()) {
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-index 579b2ac31034082456de5ee91e3c6906c7199e32..4e7027d739f7d3c8586804fc053931b2dca1de59 100644
+index ab1b24820a5ec576f33095ad5688aaecd44910f4..264105bb8b806d64d1a108bb438a8623c502c990 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-@@ -1019,6 +1019,7 @@ public class ServerChunkCache extends ChunkSource {
+@@ -1020,6 +1020,7 @@ public class ServerChunkCache extends ChunkSource {
iterator1 = shuffled.iterator();
}
@@ -119,7 +119,7 @@ index 579b2ac31034082456de5ee91e3c6906c7199e32..4e7027d739f7d3c8586804fc053931b2
try {
while (iterator1.hasNext()) {
LevelChunk chunk1 = iterator1.next();
-@@ -1040,6 +1041,7 @@ public class ServerChunkCache extends ChunkSource {
+@@ -1037,6 +1038,7 @@ public class ServerChunkCache extends ChunkSource {
if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) {
this.level.tickChunk(chunk1, k);
@@ -128,7 +128,7 @@ index 579b2ac31034082456de5ee91e3c6906c7199e32..4e7027d739f7d3c8586804fc053931b2
}
// Paper start - optimise chunk tick iteration
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 9362b39f4cc321bf0d4fc272bc3fb0463c47d4ce..cb327920cfa8d4eec626af1fe42ec1cc5e8953c7 100644
+index 3c2c326c974531eda95757893981a28fb52b4d16..b5e60bd6c023cfc9d48fe8c567feff24b00b3a0e 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -201,7 +201,9 @@ public class ServerLevel extends Level implements WorldGenLevel {
diff --git a/patches/server/0750-Do-not-copy-visible-chunks.patch b/patches/server/0750-Do-not-copy-visible-chunks.patch
index f8f6dd21bb..d9e08e0b9d 100644
--- a/patches/server/0750-Do-not-copy-visible-chunks.patch
+++ b/patches/server/0750-Do-not-copy-visible-chunks.patch
@@ -35,7 +35,7 @@ index b3516862d796c2d9fcc1c67a6073445403d73088..b61abf227a04b4565c2525e5f469db30
List allChunks = new ArrayList<>(visibleChunks.values());
List players = world.players;
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index f9f1afb49c8dba14d8d9134e84c73fa2e1d13f02..2e11bedafe383242996aeb545d6612f245070e72 100644
+index a7fcf3042e6d26739051ed37efbef4a722561c5a..58dc590cf659cba84d7cb4f7205fdb0bdefded79 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -120,9 +120,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
@@ -52,7 +52,7 @@ index f9f1afb49c8dba14d8d9134e84c73fa2e1d13f02..2e11bedafe383242996aeb545d6612f2
private final Long2ObjectLinkedOpenHashMap pendingUnloads;
public final LongSet entitiesInLevel;
public final ServerLevel level;
-@@ -312,7 +314,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -313,7 +315,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
boolean unloadingPlayerChunk = false; // Paper - 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(session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync);
@@ -61,7 +61,7 @@ index f9f1afb49c8dba14d8d9134e84c73fa2e1d13f02..2e11bedafe383242996aeb545d6612f2
this.pendingUnloads = new Long2ObjectLinkedOpenHashMap();
this.entitiesInLevel = new LongOpenHashSet();
this.toDrop = new LongOpenHashSet();
-@@ -535,12 +537,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -536,12 +538,17 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
@Nullable
public ChunkHolder getUpdatingChunkIfPresent(long pos) {
@@ -81,7 +81,7 @@ index f9f1afb49c8dba14d8d9134e84c73fa2e1d13f02..2e11bedafe383242996aeb545d6612f2
}
protected IntSupplier getChunkQueueLevel(long pos) {
-@@ -702,7 +709,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -703,7 +710,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// Paper end
}
@@ -90,7 +90,7 @@ index f9f1afb49c8dba14d8d9134e84c73fa2e1d13f02..2e11bedafe383242996aeb545d6612f2
this.modified = true;
}
-@@ -782,7 +789,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -783,7 +790,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
protected void saveAllChunks(boolean flush) {
if (flush) {
@@ -99,7 +99,7 @@ index f9f1afb49c8dba14d8d9134e84c73fa2e1d13f02..2e11bedafe383242996aeb545d6612f2
MutableBoolean mutableboolean = new MutableBoolean();
do {
-@@ -813,7 +820,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -814,7 +821,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
//this.flushWorker(); // Paper - nuke IOWorker
this.level.asyncChunkTaskManager.flush(); // Paper - flush to preserve behavior compat with pre-async behaviour
} else {
@@ -108,7 +108,7 @@ index f9f1afb49c8dba14d8d9134e84c73fa2e1d13f02..2e11bedafe383242996aeb545d6612f2
}
}
-@@ -847,7 +854,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -848,7 +855,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
while (longiterator.hasNext()) { // Spigot
long j = longiterator.nextLong();
longiterator.remove(); // Spigot
@@ -116,8 +116,8 @@ index f9f1afb49c8dba14d8d9134e84c73fa2e1d13f02..2e11bedafe383242996aeb545d6612f2
+ ChunkHolder playerchunk = this.updatingChunks.queueRemove(j); // Paper - Don't copy
if (playerchunk != null) {
- this.pendingUnloads.put(j, playerchunk);
-@@ -946,7 +953,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ playerchunk.onChunkRemove(); // Paper
+@@ -948,7 +955,12 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
if (!this.modified) {
return false;
} else {
@@ -131,7 +131,7 @@ index f9f1afb49c8dba14d8d9134e84c73fa2e1d13f02..2e11bedafe383242996aeb545d6612f2
this.modified = false;
return true;
}
-@@ -1424,7 +1436,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1426,7 +1438,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.viewDistance = j;
this.distanceManager.updatePlayerTickets(this.viewDistance + 1);
@@ -140,7 +140,7 @@ index f9f1afb49c8dba14d8d9134e84c73fa2e1d13f02..2e11bedafe383242996aeb545d6612f2
while (objectiterator.hasNext()) {
ChunkHolder playerchunk = (ChunkHolder) objectiterator.next();
-@@ -1467,7 +1479,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1469,7 +1481,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
public int size() {
@@ -149,7 +149,7 @@ index f9f1afb49c8dba14d8d9134e84c73fa2e1d13f02..2e11bedafe383242996aeb545d6612f2
}
public DistanceManager getDistanceManager() {
-@@ -1475,13 +1487,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1477,13 +1489,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
protected Iterable getChunks() {
diff --git a/patches/server/0755-Distance-manager-tick-timings.patch b/patches/server/0755-Distance-manager-tick-timings.patch
index 46878c6c13..d4a9a9561c 100644
--- a/patches/server/0755-Distance-manager-tick-timings.patch
+++ b/patches/server/0755-Distance-manager-tick-timings.patch
@@ -19,10 +19,10 @@ index eada966d7f108a6081be7a848f5c1dfcb1eed676..a977f7483f37df473096b2234dc1308b
public static final Timing midTickChunkTasks = Timings.ofSafe("Mid Tick Chunk Tasks");
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-index 05f2fad85da8192d097c2349e630349f6c61754c..631eb52d646309cfbd5b4c7c0621c865967c08e7 100644
+index 264105bb8b806d64d1a108bb438a8623c502c990..5bd358983f4b501adb0433e10df320d06816e6e7 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-@@ -834,6 +834,7 @@ public class ServerChunkCache extends ChunkSource {
+@@ -835,6 +835,7 @@ public class ServerChunkCache extends ChunkSource {
public boolean runDistanceManagerUpdates() {
if (distanceManager.delayDistanceManagerTick) return false; // Paper - Chunk priority
if (this.chunkMap.unloadingPlayerChunk) { net.minecraft.server.MinecraftServer.LOGGER.fatal("Cannot tick distance manager while unloading playerchunks", new Throwable()); throw new IllegalStateException("Cannot tick distance manager while unloading playerchunks"); } // Paper
@@ -30,7 +30,7 @@ index 05f2fad85da8192d097c2349e630349f6c61754c..631eb52d646309cfbd5b4c7c0621c865
boolean flag = this.distanceManager.runAllUpdates(this.chunkMap);
boolean flag1 = this.chunkMap.promoteChunkMap();
-@@ -843,6 +844,7 @@ public class ServerChunkCache extends ChunkSource {
+@@ -844,6 +845,7 @@ public class ServerChunkCache extends ChunkSource {
this.clearCache();
return true;
}
diff --git a/patches/server/0762-Consolidate-flush-calls-for-entity-tracker-packets.patch b/patches/server/0762-Consolidate-flush-calls-for-entity-tracker-packets.patch
index 2a471a6bb2..5fb9275062 100644
--- a/patches/server/0762-Consolidate-flush-calls-for-entity-tracker-packets.patch
+++ b/patches/server/0762-Consolidate-flush-calls-for-entity-tracker-packets.patch
@@ -22,13 +22,13 @@ With this change I could get all 200 on at 0ms ping.
So in general this patch should reduce Netty I/O thread load.
diff --git a/src/main/java/net/minecraft/server/level/ServerChunkCache.java b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-index 631eb52d646309cfbd5b4c7c0621c865967c08e7..9a07ccbd12675e501a9aebf89ab85adf6fb658ba 100644
+index 5bd358983f4b501adb0433e10df320d06816e6e7..98be0ea366732695f76bc5b1a78e0a36060515bd 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-@@ -1066,7 +1066,24 @@ public class ServerChunkCache extends ChunkSource {
-
- // Paper - no, iterating just ONCE is expensive enough! Don't do it TWICE! Code moved up
+@@ -1078,7 +1078,24 @@ public class ServerChunkCache extends ChunkSource {
+ this.level.timings.broadcastChunkUpdates.stopTiming(); // Paper - timing
gameprofilerfiller.pop();
+ // Paper end - use set of chunks requiring updates, rather than iterating every single one loaded
+ // Paper start - controlled flush for entity tracker packets
+ List disabledFlushes = new java.util.ArrayList<>(this.level.players.size());
+ for (ServerPlayer player : this.level.players) {
diff --git a/patches/server/0767-Oprimise-map-impl-for-tracked-players.patch b/patches/server/0767-Oprimise-map-impl-for-tracked-players.patch
index cdfa81b622..4e52c8b1cd 100644
--- a/patches/server/0767-Oprimise-map-impl-for-tracked-players.patch
+++ b/patches/server/0767-Oprimise-map-impl-for-tracked-players.patch
@@ -7,7 +7,7 @@ Reference2BooleanOpenHashMap is going to have
better lookups than HashMap.
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 7353b4e43503b371973c2de6a61e5ca91d613680..31a1a13887bda174181d926d83273a89b59ec789 100644
+index 58dc590cf659cba84d7cb4f7205fdb0bdefded79..72443ab38931a3774641bdc52999e7dcd2586de0 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -108,6 +108,7 @@ import org.apache.logging.log4j.LogManager;
@@ -18,7 +18,7 @@ index 7353b4e43503b371973c2de6a61e5ca91d613680..31a1a13887bda174181d926d83273a89
public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider {
-@@ -2114,7 +2115,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -2116,7 +2117,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
final Entity entity;
private final int range;
SectionPos lastSectionPos;
diff --git a/patches/server/0772-Optimise-nearby-player-lookups.patch b/patches/server/0772-Optimise-nearby-player-lookups.patch
index 968f27a807..1dede6fa65 100644
--- a/patches/server/0772-Optimise-nearby-player-lookups.patch
+++ b/patches/server/0772-Optimise-nearby-player-lookups.patch
@@ -9,29 +9,42 @@ since the penalty of a map lookup could outweigh the benefits of
searching less players (as it basically did in the outside range patch).
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-index 1f602d50f3212078490c0092ceefd3b17e0b1532..825fdb0336b0388dbbc54c8da99781900612031c 100644
+index 43f97571f0b837cbba5c3e889edbc5737a6e7b54..f5b6d0f69e727b8f5e425340f8c1381a82a24ce3 100644
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-@@ -83,6 +83,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);
+@@ -88,6 +88,12 @@ public class ChunkHolder {
+ this.chunkMap.needsChangeBroadcasting.add(this);
+ }
+ // Paper end - optimise chunk tick iteration
+ // Paper start - optimise checkDespawn
+ LevelChunk chunk = this.getFullChunkUnchecked();
+ if (chunk != null) {
+ chunk.updateGeneralAreaCache();
+ }
++ // Paper end - optimise checkDespawn
+ }
+
+ void onChunkRemove() {
+@@ -98,6 +104,12 @@ public class ChunkHolder {
+ this.chunkMap.needsChangeBroadcasting.remove(this);
+ }
+ // Paper end - optimise chunk tick iteration
++ // Paper start - optimise checkDespawn
++ LevelChunk chunk = this.getFullChunkUnchecked();
++ if (chunk != null) {
++ chunk.removeGeneralAreaCache();
++ }
+ // Paper end - optimise checkDespawn
}
// Paper end - optimise anyPlayerCloseEnoughForSpawning
long lastAutoSaveTime; // Paper - incremental autosave
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index dd23e7110259574859a074c423312c452d533668..c56ccb8a6faba0be36b53038e723da5a4461ded1 100644
+index 72443ab38931a3774641bdc52999e7dcd2586de0..69533a1239f12c41a255bf9deeb5640695edfa13 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -159,6 +159,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- int viewDistance;
+@@ -160,6 +160,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
public final com.destroystokyo.paper.util.misc.PlayerAreaMap playerMobDistanceMap; // Paper
+ public final ReferenceOpenHashSet needsChangeBroadcasting = new ReferenceOpenHashSet<>();
+ // Paper start - optimise checkDespawn
+ public static final int GENERAL_AREA_MAP_SQUARE_RADIUS = 40;
@@ -43,7 +56,7 @@ index dd23e7110259574859a074c423312c452d533668..c56ccb8a6faba0be36b53038e723da5a
// CraftBukkit start - recursion-safe executor for Chunk loadCallback() and unloadCallback()
public final CallbackExecutor callbackExecutor = new CallbackExecutor();
public static final class CallbackExecutor implements java.util.concurrent.Executor, Runnable {
-@@ -236,6 +243,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -237,6 +244,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// Paper end - use distance map to optimise entity tracker
// Note: players need to be explicitly added to distance maps before they can be updated
this.playerChunkTickRangeMap.add(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
@@ -51,7 +64,7 @@ index dd23e7110259574859a074c423312c452d533668..c56ccb8a6faba0be36b53038e723da5a
// Paper start - per player mob spawning
if (this.playerMobDistanceMap != null) {
this.playerMobDistanceMap.add(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance());
-@@ -254,6 +262,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -255,6 +263,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.playerMobSpawnMap.remove(player);
this.playerChunkTickRangeMap.remove(player);
// Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
@@ -59,7 +72,7 @@ index dd23e7110259574859a074c423312c452d533668..c56ccb8a6faba0be36b53038e723da5a
// Paper start - per player mob spawning
if (this.playerMobDistanceMap != null) {
this.playerMobDistanceMap.remove(player);
-@@ -274,6 +283,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -275,6 +284,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
// Paper end - use distance map to optimise entity tracker
this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
@@ -67,7 +80,7 @@ index dd23e7110259574859a074c423312c452d533668..c56ccb8a6faba0be36b53038e723da5a
// Paper start - per player mob spawning
if (this.playerMobDistanceMap != null) {
this.playerMobDistanceMap.update(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance());
-@@ -434,6 +444,23 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -435,6 +445,23 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
});
// Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
@@ -92,7 +105,7 @@ index dd23e7110259574859a074c423312c452d533668..c56ccb8a6faba0be36b53038e723da5a
protected ChunkGenerator generator() {
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index fe2e5eb72307f8f07987b12a1af9100348b86a82..94798e2715a16b43f11493b08fe342aeecf11cef 100644
+index 4b18dfbf559214dc511003bb3396acbcfb80fdda..9c73cb316522302bf88b671398ba53c0eb2d29d5 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -399,6 +399,83 @@ public class ServerLevel extends Level implements WorldGenLevel {
@@ -309,10 +322,10 @@ index 6dba117b4f5dc6c4e078a32037a4026b45bf2176..515e58e3db223fbdc01ca87607aca234
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/chunk/LevelChunk.java b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-index 8e03e63a00dd242791ba0d5a8a17922227b16165..4a9a1fef5603b073e6d2d12e3e8e5dca73a7bd1b 100644
+index 8e03e63a00dd242791ba0d5a8a17922227b16165..0746300e8c0a29bedb9ec02803c194c2d03b78fb 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -235,6 +235,93 @@ public class LevelChunk extends ChunkAccess {
+@@ -235,6 +235,98 @@ public class LevelChunk extends ChunkAccess {
}
}
// Paper end
@@ -331,6 +344,11 @@ index 8e03e63a00dd242791ba0d5a8a17922227b16165..4a9a1fef5603b073e6d2d12e3e8e5dca
+ this.updateGeneralAreaCache(((ServerLevel)this.level).getChunkSource().chunkMap.playerGeneralAreaMap.getObjectsInRange(this.coordinateKey));
+ }
+
++ public void removeGeneralAreaCache() {
++ this.playerGeneralAreaCacheSet = false;
++ this.playerGeneralAreaCache = null;
++ }
++
+ public void updateGeneralAreaCache(com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet value) {
+ this.playerGeneralAreaCacheSet = true;
+ this.playerGeneralAreaCache = value;
diff --git a/patches/server/0773-Optimise-WorldServer-notify.patch b/patches/server/0773-Optimise-WorldServer-notify.patch
index 3382201094..9c6cdc5b4d 100644
--- a/patches/server/0773-Optimise-WorldServer-notify.patch
+++ b/patches/server/0773-Optimise-WorldServer-notify.patch
@@ -8,10 +8,10 @@ Instead, only iterate over navigators in the current region that are
eligible for repathing.
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 60ae567fe76a643f6a363644bfc99eac2ef64835..634f8690ccce7cdcc524258a6f2844c60b9563b5 100644
+index 69533a1239f12c41a255bf9deeb5640695edfa13..3c90c6514dc856490da7fca5c8a42023493836d7 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -296,15 +296,81 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -297,15 +297,81 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
public final io.papermc.paper.chunk.SingleThreadChunkRegionManager dataRegionManager;
public static final class DataRegionData implements io.papermc.paper.chunk.SingleThreadChunkRegionManager.RegionData {
@@ -93,7 +93,7 @@ index 60ae567fe76a643f6a363644bfc99eac2ef64835..634f8690ccce7cdcc524258a6f2844c6
}
@Override
-@@ -314,6 +380,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -315,6 +381,15 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
final DataRegionSectionData sectionData = (DataRegionSectionData)section.sectionData;
final DataRegionData oldRegionData = oldRegion == null ? null : (DataRegionData)oldRegion.regionData;
final DataRegionData newRegionData = (DataRegionData)newRegion.regionData;
@@ -110,7 +110,7 @@ index 60ae567fe76a643f6a363644bfc99eac2ef64835..634f8690ccce7cdcc524258a6f2844c6
}
diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
-index 0918bb28fd058e6b79f45993a46738a50b05b60a..f17c0f501c89c07651a40673ad5ecfe6c7168fce 100644
+index 9c73cb316522302bf88b671398ba53c0eb2d29d5..9f1f55d457b1a99c12f7fc94aaf9005fb8df50e7 100644
--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
@@ -1093,6 +1093,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
diff --git a/patches/server/0791-Do-not-overload-I-O-threads-with-chunk-data-while-fl.patch b/patches/server/0791-Do-not-overload-I-O-threads-with-chunk-data-while-fl.patch
index b8c570f4c7..55761fb502 100644
--- a/patches/server/0791-Do-not-overload-I-O-threads-with-chunk-data-while-fl.patch
+++ b/patches/server/0791-Do-not-overload-I-O-threads-with-chunk-data-while-fl.patch
@@ -12,10 +12,10 @@ time to save, as flush saving performs a full flush at
the end anyways.
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 634f8690ccce7cdcc524258a6f2844c60b9563b5..b7a215c94e1ef021360dde5c4099ddd5b1f676b8 100644
+index 3c90c6514dc856490da7fca5c8a42023493836d7..8678640da7724283664e3f115230d6b2687967b1 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -891,6 +891,16 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -892,6 +892,16 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
// Paper end
protected void saveAllChunks(boolean flush) {
@@ -32,7 +32,7 @@ index 634f8690ccce7cdcc524258a6f2844c60b9563b5..b7a215c94e1ef021360dde5c4099ddd5
if (flush) {
List list = (List) this.updatingChunks.getVisibleValuesCopy().stream().filter(ChunkHolder::wasAccessibleSinceLastSave).peek(ChunkHolder::refreshAccessibility).collect(Collectors.toList()); // Paper
MutableBoolean mutableboolean = new MutableBoolean();
-@@ -913,6 +923,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -914,6 +924,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}).filter((ichunkaccess) -> {
return ichunkaccess instanceof ImposterProtoChunk || ichunkaccess instanceof LevelChunk;
}).filter(this::save).forEach((ichunkaccess) -> {
diff --git a/patches/server/0807-Rewrite-the-light-engine.patch b/patches/server/0807-Rewrite-the-light-engine.patch
index 7b4f8c6b70..bec98e6517 100644
--- a/patches/server/0807-Rewrite-the-light-engine.patch
+++ b/patches/server/0807-Rewrite-the-light-engine.patch
@@ -4345,7 +4345,7 @@ index 0000000000000000000000000000000000000000..dd995e25ae620ae36cd5eecb2fe10ad0
+
+}
diff --git a/src/main/java/com/destroystokyo/paper/PaperCommand.java b/src/main/java/com/destroystokyo/paper/PaperCommand.java
-index 315bd2408e4a45993c9b2572e0ab5260a70522ec..c0d123bff1825366c30aadd3ad8a7fde68ef74e4 100644
+index c57999061a7a9adb7b5207a13af3d693529a43cd..76d27205b2bd08ba2b3530b46f0d9b4e2315d82e 100644
--- a/src/main/java/com/destroystokyo/paper/PaperCommand.java
+++ b/src/main/java/com/destroystokyo/paper/PaperCommand.java
@@ -700,6 +700,46 @@ public class PaperCommand extends Command {
@@ -4419,7 +4419,7 @@ index 315bd2408e4a45993c9b2572e0ab5260a70522ec..c0d123bff1825366c30aadd3ad8a7fde
Deque queue = new ArrayDeque<>(MCUtil.getSpiralOutChunks(center, radius));
updateLight(sender, world, lightengine, queue);
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-index 825fdb0336b0388dbbc54c8da99781900612031c..d271871563fa883efb77b35ec3b1dfbba87f0b62 100644
+index f5b6d0f69e727b8f5e425340f8c1381a82a24ce3..9c4e4ab16441555d7940863b6736a03ee4af545c 100644
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
@@ -52,7 +52,7 @@ public class ChunkHolder {
@@ -4432,7 +4432,7 @@ index 825fdb0336b0388dbbc54c8da99781900612031c..d271871563fa883efb77b35ec3b1dfbb
private final DebugBuffer chunkToSaveHistory;
public int oldTicketLevel;
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index b7a215c94e1ef021360dde5c4099ddd5b1f676b8..02bcffcdde33088e43a7771924b2d2bf47badd09 100644
+index 8678640da7724283664e3f115230d6b2687967b1..29fdc1b991c67006a13231abbbe50e201744b5c2 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
@@ -130,7 +130,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
@@ -4961,7 +4961,7 @@ index e15263a152c88371ebc65b47f0be938f7c19a8f2..59c053deb52c9307f1b4c1515384a7c6
super(wrapped.getPos(), UpgradeData.EMPTY, wrapped.levelHeightAccessor, wrapped.getLevel().registryAccess().registryOrThrow(Registry.BIOME_REGISTRY), wrapped.getBlendingData());
this.wrapped = wrapped;
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 4a9a1fef5603b073e6d2d12e3e8e5dca73a7bd1b..7567a8bf848c82b27383f084056cb43c41df6d0c 100644
+index 0746300e8c0a29bedb9ec02803c194c2d03b78fb..55c0e9655ded14e25b0f284ad0c1f99eb5d0b192 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
@@ -100,6 +100,10 @@ public class LevelChunk extends ChunkAccess {
@@ -4975,7 +4975,7 @@ index 4a9a1fef5603b073e6d2d12e3e8e5dca73a7bd1b..7567a8bf848c82b27383f084056cb43c
this.tickersInLevel = Maps.newHashMap();
this.clientLightReady = false;
this.level = (ServerLevel) world; // CraftBukkit - type
-@@ -325,6 +329,12 @@ public class LevelChunk extends ChunkAccess {
+@@ -330,6 +334,12 @@ public class LevelChunk extends ChunkAccess {
public LevelChunk(ServerLevel world, ProtoChunk protoChunk, @Nullable LevelChunk.PostLoadProcessor entityLoader) {
this(world, protoChunk.getPos(), protoChunk.getUpgradeData(), protoChunk.unpackBlockTicks(), protoChunk.unpackFluidTicks(), protoChunk.getInhabitedTime(), protoChunk.getSections(), entityLoader, protoChunk.getBlendingData());
@@ -5002,7 +5002,7 @@ index d850cae1ec024a557e62cd561fbca137dc2be96c..eef1b58cfaf3cfa90f3786785dd94d05
return data.palette.valueFor(data.storage.get(index));
}
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 0dfa51c8826b9e984586a3e4e050a50a4fbb1bd3..e947a47dd8c6906bc36eca757c4b9f9f2ab3cedc 100644
+index acfd46c7035b4009d61bda8a7c8dd6953e4836e6..9e293afe94e85ecbc2a236b1b34df1a4926b83cb 100644
--- a/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/ProtoChunk.java
@@ -54,6 +54,12 @@ public class ProtoChunk extends ChunkAccess {
diff --git a/patches/server/0818-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch b/patches/server/0818-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch
index 18434acc86..7a657d63bd 100644
--- a/patches/server/0818-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch
+++ b/patches/server/0818-Highly-optimise-single-and-multi-AABB-VoxelShapes-an.patch
@@ -70,10 +70,10 @@ index be668387f65a633c6ac497fca632a4767a1bf3a2..e08f4e39db4ee3fed62e37364d17dcc5
}
diff --git a/src/main/java/io/papermc/paper/util/CollisionUtil.java b/src/main/java/io/papermc/paper/util/CollisionUtil.java
new file mode 100644
-index 0000000000000000000000000000000000000000..58629451977c89db2fa895bde946135784a0d8bc
+index 0000000000000000000000000000000000000000..b25018ab6cff59e43652c953421f01b6d0bb0a85
--- /dev/null
+++ b/src/main/java/io/papermc/paper/util/CollisionUtil.java
-@@ -0,0 +1,639 @@
+@@ -0,0 +1,899 @@
+package io.papermc.paper.util;
+
+import io.papermc.paper.voxel.AABBVoxelShape;
@@ -91,6 +91,7 @@ index 0000000000000000000000000000000000000000..58629451977c89db2fa895bde9461357
+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.level.chunk.PalettedContainer;
+import net.minecraft.world.level.material.FlowingFluid;
+import net.minecraft.world.level.material.FluidState;
+import net.minecraft.world.phys.AABB;
@@ -101,7 +102,6 @@ index 0000000000000000000000000000000000000000..58629451977c89db2fa895bde9461357
+import net.minecraft.world.phys.shapes.Shapes;
+import net.minecraft.world.phys.shapes.VoxelShape;
+import java.util.List;
-+import java.util.Optional;
+import java.util.function.BiPredicate;
+import java.util.function.Predicate;
+
@@ -109,6 +109,15 @@ index 0000000000000000000000000000000000000000..58629451977c89db2fa895bde9461357
+
+ public static final double COLLISION_EPSILON = 1.0E-7;
+
++ public static final long KNOWN_EMPTY_BLOCK = 0b00; // known to always have voxelshape of empty
++ public static final long KNOWN_FULL_BLOCK = 0b01; // known to always have voxelshape of full cube
++ public static final long KNOWN_UNKNOWN_BLOCK = 0b10; // must read the actual block state for info
++ public static final long KNOWN_SPECIAL_BLOCK = 0b11; // caller must check this block for special collisions
++
++ public static boolean isSpecialCollidingBlock(final net.minecraft.world.level.block.state.BlockBehaviour.BlockStateBase block) {
++ return block.shapeExceedsCube() || block.getBlock() == Blocks.MOVING_PISTON;
++ }
++
+ 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;
+ }
@@ -471,8 +480,8 @@ index 0000000000000000000000000000000000000000..58629451977c89db2fa895bde9461357
+ }
+
+ 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) {
++ final List into, final boolean loadChunks, final boolean collidesWithUnloaded,
++ final boolean checkBorder, final boolean checkOnly, final BiPredicate predicate) {
+ boolean ret = false;
+
+ if (checkBorder) {
@@ -486,21 +495,21 @@ index 0000000000000000000000000000000000000000..58629451977c89db2fa895bde9461357
+ }
+ }
+
-+ int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1;
-+ int maxBlockX = Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1;
++ final int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1;
++ final 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;
++ final int minBlockY = Mth.floor(aabb.minY - COLLISION_EPSILON) - 1;
++ final 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 minBlockZ = Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1;
++ final 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();
++ final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
+ CollisionContext collisionShape = null;
+
+ // special cases:
@@ -509,16 +518,22 @@ index 0000000000000000000000000000000000000000..58629451977c89db2fa895bde9461357
+ return ret;
+ }
+
-+ int minYIterate = Math.max(minBlock, minBlockY);
-+ int maxYIterate = Math.min(maxBlock, maxBlockY);
++ final int minYIterate = Math.max(minBlock, minBlockY);
++ final int maxYIterate = Math.min(maxBlock, maxBlockY);
+
-+ int minChunkX = minBlockX >> 4;
-+ int maxChunkX = maxBlockX >> 4;
++ final int minChunkX = minBlockX >> 4;
++ final int maxChunkX = maxBlockX >> 4;
+
-+ int minChunkZ = minBlockZ >> 4;
-+ int maxChunkZ = maxBlockZ >> 4;
++ final int minChunkY = minBlockY >> 4;
++ final int maxChunkY = maxBlockY >> 4;
+
-+ ServerChunkCache chunkProvider;
++ final int minChunkYIterate = minYIterate >> 4;
++ final int maxChunkYIterate = maxYIterate >> 4;
++
++ final int minChunkZ = minBlockZ >> 4;
++ final int maxChunkZ = maxBlockZ >> 4;
++
++ final ServerChunkCache chunkProvider;
+ if (getter instanceof WorldGenRegion) {
+ chunkProvider = null;
+ } else if (getter instanceof ServerLevel) {
@@ -526,26 +541,24 @@ index 0000000000000000000000000000000000000000..58629451977c89db2fa895bde9461357
+ } 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
++ final int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk
++ final 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
++ final int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk
++ final int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk
+
-+ int chunkXGlobalPos = currChunkX << 4;
-+ int chunkZGlobalPos = currChunkZ << 4;
-+ ChunkAccess chunk;
++ final int chunkXGlobalPos = currChunkX << 4;
++ final int chunkZGlobalPos = currChunkZ << 4;
++ final 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) {
@@ -558,59 +571,306 @@ index 0000000000000000000000000000000000000000..58629451977c89db2fa895bde9461357
+ continue;
+ }
+
-+ LevelChunkSection[] sections = chunk.getSections();
++ final LevelChunkSection[] sections = chunk.getSections();
+
+ // bound y
+
-+ for (int currY = minYIterate; currY <= maxYIterate; ++currY) {
-+ LevelChunkSection section = sections[(currY >> 4) - minSection];
-+ if (section.hasOnlyAir()) {
++ for (int currChunkY = minChunkYIterate; currChunkY <= maxChunkYIterate; ++currChunkY) {
++ final LevelChunkSection section = sections[currChunkY - minSection];
++ if (section == null || section.hasOnlyAir()) {
+ // empty
-+ // skip to next section
-+ currY = (currY & ~(15)) + 15; // increment by 15: iterator loop increments by the extra one
+ continue;
+ }
++ final PalettedContainer blocks = section.states;
+
-+ net.minecraft.world.level.chunk.PalettedContainer blocks = section.states;
++ final int minY = currChunkY == minChunkYIterate ? minYIterate & 15 : 0; // coordinate in chunk
++ final int maxY = currChunkY == maxChunkYIterate ? maxYIterate & 15 : 15; // coordinate in chunk
++ final int chunkYGlobalPos = currChunkY << 4;
+
-+ 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;
++ final boolean sectionHasSpecial = section.hasSpecialCollidingBlocks();
+
-+ int edgeCount = ((blockX == minBlockX || blockX == maxBlockX) ? 1 : 0) +
-+ ((blockY == minBlockY || blockY == maxBlockY) ? 1 : 0) +
-+ ((blockZ == minBlockZ || blockZ == maxBlockZ) ? 1 : 0);
-+ if (edgeCount == 3) {
++ final int minXIterate;
++ final int maxXIterate;
++ final int minZIterate;
++ final int maxZIterate;
++ final int minYIterateLocal;
++ final int maxYIterateLocal;
++
++ if (!sectionHasSpecial) {
++ minXIterate = currChunkX == minChunkX ? minX + 1 : minX;
++ maxXIterate = currChunkX == maxChunkX ? maxX - 1 : maxX;
++ minZIterate = currChunkZ == minChunkZ ? minZ + 1 : minZ;
++ maxZIterate = currChunkZ == maxChunkZ ? maxZ - 1 : maxZ;
++ minYIterateLocal = currChunkY == minChunkY ? minY + 1 : minY;
++ maxYIterateLocal = currChunkY == maxChunkY ? maxY - 1 : maxY;
++ if (minXIterate > maxXIterate || minZIterate > maxZIterate) {
++ continue;
++ }
++ } else {
++ minXIterate = minX;
++ maxXIterate = maxX;
++ minZIterate = minZ;
++ maxZIterate = maxZ;
++ minYIterateLocal = minY;
++ maxYIterateLocal = maxY;
++ }
++
++ for (int currY = minYIterateLocal; currY <= maxYIterateLocal; ++currY) {
++ long collisionForHorizontal = section.getKnownBlockInfoHorizontalRaw(currY, minZIterate & 15);
++ for (int currZ = minZIterate; currZ <= maxZIterate; ++currZ,
++ collisionForHorizontal = (currZ & 1) == 0 ? section.getKnownBlockInfoHorizontalRaw(currY, currZ & 15) : collisionForHorizontal) {
++ // From getKnownBlockInfoHorizontalRaw:
++ // important detail: this returns 32 values, one for localZ = localZ & (~1) and one for localZ = localZ | 1
++ // the even localZ is the lower 32 bits, the odd is the upper 32 bits
++ // We want to use a bitset to only iterate over non-empty blocks.
++ // We need to build a bitset mask to and out the other collisions we just don't care at all about
++ // First, we need to build a bitset from 0..n*2 where n is the number of blocks on the x axis
++ // It's important to note that the iterate values can be outside [0, 15], but if they are,
++ // then none of the x or z loops would meet their conditions. So we can assume they are never
++ // out of bounds here
++ final int xAxisBits = (maxXIterate - minXIterate + 1) << 1; // << 1 -> * 2 // Never > 32
++ long bitset = (1L << xAxisBits) - 1;
++ // Now we need to offset it by 32 bits if current Z is odd (lower 32 bits is 16 block infos for even z, upper is for odd)
++ int shift = (currZ & 1) << 5; // this will be a LEFT shift
++ // Now we need to offset shift so that the bitset first position is at minXIterate
++ shift += (minXIterate << 1); // 0th pos -> 0th bit, 1st pos -> 2nd bit, ...
++
++ // all done
++ bitset = bitset << shift;
++ if ((collisionForHorizontal & bitset) == 0L) {
++ // All empty
+ continue;
+ }
++ for (int currX = minXIterate; currX <= maxXIterate; ++currX) {
++ final int localBlockIndex = (currX) | (currZ << 4) | (currY << 8);
+
-+ BlockState blockData = blocks.get(localBlockIndex);
-+ if (blockData.isAir()) {
-+ continue;
-+ }
++ final int blockInfo = (int) LevelChunkSection.getKnownBlockInfo(localBlockIndex, collisionForHorizontal);
+
-+ if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) {
-+ mutablePos.set(blockX, blockY, blockZ);
-+ if (collisionShape == null) {
-+ collisionShape = new LazyEntityCollisionContext(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)) {
++ switch (blockInfo) {
++ case (int) CollisionUtil.KNOWN_EMPTY_BLOCK: {
+ continue;
+ }
-+
-+ if (checkOnly) {
-+ if (voxelshape3.intersects(aabb)) {
-+ return true;
++ case (int) CollisionUtil.KNOWN_FULL_BLOCK: {
++ double blockX = (double)(currX | chunkXGlobalPos);
++ double blockY = (double)(currY | chunkYGlobalPos);
++ double blockZ = (double)(currZ | chunkZGlobalPos);
++ final AABB blockBox = new AABB(
++ blockX, blockY, blockZ,
++ blockX + 1.0, blockY + 1.0, blockZ + 1.0,
++ true
++ );
++ if (predicate != null) {
++ if (!voxelShapeIntersect(aabb, blockBox)) {
++ continue;
++ }
++ // fall through to get the block for the predicate
++ } else {
++ if (voxelShapeIntersect(aabb, blockBox)) {
++ if (checkOnly) {
++ return true;
++ } else {
++ into.add(blockBox);
++ ret = true;
++ }
++ }
++ continue;
++ }
++ }
++ // default: fall through to standard logic
++ }
++
++ int blockX = currX | chunkXGlobalPos;
++ int blockY = currY | chunkYGlobalPos;
++ 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 ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) {
++ mutablePos.set(blockX, blockY, blockZ);
++ if (collisionShape == null) {
++ collisionShape = new LazyEntityCollisionContext(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 getCollisionsForBlocksOrWorldBorderReference(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;
++ }
++ }
++ }
++
++ final int minBlockX = Mth.floor(aabb.minX - COLLISION_EPSILON) - 1;
++ final int maxBlockX = Mth.floor(aabb.maxX + COLLISION_EPSILON) + 1;
++
++ final int minBlockY = Mth.floor(aabb.minY - COLLISION_EPSILON) - 1;
++ final int maxBlockY = Mth.floor(aabb.maxY + COLLISION_EPSILON) + 1;
++
++ final int minBlockZ = Mth.floor(aabb.minZ - COLLISION_EPSILON) - 1;
++ final 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;
++
++ final BlockPos.MutableBlockPos mutablePos = new BlockPos.MutableBlockPos();
++ CollisionContext collisionShape = null;
++
++ // special cases:
++ if (minBlockY > maxBlock || maxBlockY < minBlock) {
++ // no point in checking
++ return ret;
++ }
++
++ final int minYIterate = Math.max(minBlock, minBlockY);
++ final int maxYIterate = Math.min(maxBlock, maxBlockY);
++
++ final int minChunkX = minBlockX >> 4;
++ final int maxChunkX = maxBlockX >> 4;
++
++ final int minChunkY = minBlockY >> 4;
++ final int maxChunkY = maxBlockY >> 4;
++
++ final int minChunkYIterate = minYIterate >> 4;
++ final int maxChunkYIterate = maxYIterate >> 4;
++
++ final int minChunkZ = minBlockZ >> 4;
++ final int maxChunkZ = maxBlockZ >> 4;
++
++ final ServerChunkCache chunkProvider;
++ if (getter instanceof WorldGenRegion) {
++ chunkProvider = null;
++ } else if (getter instanceof ServerLevel) {
++ chunkProvider = ((ServerLevel)getter).getChunkSource();
++ } else {
++ chunkProvider = null;
++ }
++
++ for (int currChunkZ = minChunkZ; currChunkZ <= maxChunkZ; ++currChunkZ) {
++ final int minZ = currChunkZ == minChunkZ ? minBlockZ & 15 : 0; // coordinate in chunk
++ final int maxZ = currChunkZ == maxChunkZ ? maxBlockZ & 15 : 15; // coordinate in chunk
++
++ for (int currChunkX = minChunkX; currChunkX <= maxChunkX; ++currChunkX) {
++ final int minX = currChunkX == minChunkX ? minBlockX & 15 : 0; // coordinate in chunk
++ final int maxX = currChunkX == maxChunkX ? maxBlockX & 15 : 15; // coordinate in chunk
++
++ final int chunkXGlobalPos = currChunkX << 4;
++ final int chunkZGlobalPos = currChunkZ << 4;
++ final 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;
++ }
++
++ final LevelChunkSection[] sections = chunk.getSections();
++
++ // bound y
++ for (int currChunkY = minChunkYIterate; currChunkY <= maxChunkYIterate; ++currChunkY) {
++ final LevelChunkSection section = sections[currChunkY - minSection];
++ if (section == null || section.hasOnlyAir()) {
++ // empty
++ continue;
++ }
++ final PalettedContainer blocks = section.states;
++
++ final int minY = currChunkY == minChunkYIterate ? minYIterate & 15 : 0; // coordinate in chunk
++ final int maxY = currChunkY == maxChunkYIterate ? maxYIterate & 15 : 15; // coordinate in chunk
++ final int chunkYGlobalPos = currChunkY << 4;
++
++ for (int currY = minY; currY <= maxY; ++currY) {
++ for (int currZ = minZ; currZ <= maxZ; ++currZ) {
++ for (int currX = minX; currX <= maxX; ++currX) {
++ int localBlockIndex = (currX) | (currZ << 4) | ((currY) << 8);
++ int blockX = currX | chunkXGlobalPos;
++ int blockY = currY | chunkYGlobalPos;
++ 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.getBlockCollisionBehavior() == CollisionUtil.KNOWN_EMPTY_BLOCK) {
++ continue;
++ }
++
++ if ((edgeCount != 1 || blockData.shapeExceedsCube()) && (edgeCount != 2 || blockData.getBlock() == Blocks.MOVING_PISTON)) {
++ mutablePos.set(blockX, blockY, blockZ);
++ if (collisionShape == null) {
++ collisionShape = new LazyEntityCollisionContext(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);
+ }
-+ } else {
-+ ret |= addBoxesToIfIntersects(voxelshape3, aabb, into);
+ }
+ }
+ }
@@ -955,7 +1215,7 @@ index 80888d5adf7d4377e17e6f530f35053cfcc9eed4..fe9810c3b908339ca050ed832c2e67d6
}
// CraftBukkit start
diff --git a/src/main/java/net/minecraft/world/entity/Entity.java b/src/main/java/net/minecraft/world/entity/Entity.java
-index 26546c030710685cdc1c75b8018462cd424b9ed6..2e61243b86ea1f21ec799a63badeae329e1ac3fa 100644
+index bf3bf6f359bfbef0fa2c6f872ec3ff35739a15fc..fe6f2ec41c521c8f1ac17aa3af8bb7f7375e5a02 100644
--- a/src/main/java/net/minecraft/world/entity/Entity.java
+++ b/src/main/java/net/minecraft/world/entity/Entity.java
@@ -1082,9 +1082,44 @@ public abstract class Entity implements Nameable, EntityAccess, CommandSource, i
@@ -1216,18 +1476,202 @@ index f9527d1d867f93b4e0e2758485cfa1f6efa0bf8b..1f4b72a0aca200b2e0860449c718e6e6
return List.of();
} else {
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 1831588b275f11aff37573fead835f6ddabfece1..05c46f3b3bce5225b819d86e6e06729a5093e092 100644
+index 1831588b275f11aff37573fead835f6ddabfece1..eac017fc521bfd1391e75db8628f42b28329d681 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
-@@ -726,7 +726,7 @@ public abstract class BlockBehaviour {
+@@ -717,6 +717,13 @@ public abstract class BlockBehaviour {
+ return this.conditionallyFullOpaque;
+ }
+ // Paper end
++ // Paper start
++ private long blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_SPECIAL_BLOCK;
++
++ public final long getBlockCollisionBehavior() {
++ return this.blockCollisionBehavior;
++ }
++ // Paper end
+
+ public void initCache() {
+ this.fluid = this.getBlock().getFluidState(this.asState()); // Paper - moved from getFluid()
+@@ -726,7 +733,35 @@ public abstract class BlockBehaviour {
}
this.shapeExceedsCube = this.cache == null || this.cache.largeCollisionShape; // Paper - moved from actual method to here
this.opacityIfCached = this.cache == null || this.isConditionallyFullOpaque() ? -1 : this.cache.lightBlock; // Paper - cache opacity for light
-
-+ // TODO optimise light
++ // Paper start
++ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(this)) {
++ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_SPECIAL_BLOCK;
++ } else {
++ try {
++ // There is NOTHING HACKY ABOUT THIS AT ALLLLLLLLLLLLLLL
++ VoxelShape constantShape = this.getCollisionShape(null, null, null);
++ if (constantShape == null) {
++ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_UNKNOWN_BLOCK;
++ } else {
++ constantShape = constantShape.optimize();
++ if (constantShape.isEmpty()) {
++ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_EMPTY_BLOCK;
++ } else {
++ final List boxes = constantShape.toAabbs();
++ if (constantShape == net.minecraft.world.phys.shapes.Shapes.getFullUnoptimisedCube() || (boxes.size() == 1 && boxes.get(0).equals(net.minecraft.world.phys.shapes.Shapes.BLOCK_OPTIMISED.aabb))) {
++ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_FULL_BLOCK;
++ } else {
++ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_UNKNOWN_BLOCK;
++ }
++ }
++ }
++ } catch (final Error error) {
++ throw error;
++ } catch (final Throwable throwable) {
++ this.blockCollisionBehavior = io.papermc.paper.util.CollisionUtil.KNOWN_UNKNOWN_BLOCK;
++ }
++ }
++ // Paper end
}
public Block getBlock() {
+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 d5ceebee36885c6470917bc1d0952733e983f030..e962db693510dc261d6456706a459929369f2510 100644
+--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
++++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunkSection.java
+@@ -43,6 +43,110 @@ public class LevelChunkSection {
+ this.biomes = new PalettedContainer<>(biomeRegistry, (Biome) biomeRegistry.getOrThrow(Biomes.PLAINS), PalettedContainer.Strategy.SECTION_BIOMES, null); // Paper - Anti-Xray - Add preset biomes
+ }
+
++ // Paper start
++ protected int specialCollidingBlocks;
++ // blockIndex = x | (z << 4) | (y << 8)
++ private long[] knownBlockCollisionData;
++
++ private long[] initKnownDataField() {
++ return this.knownBlockCollisionData = new long[16 * 16 * 16 * 2 / Long.SIZE];
++ }
++
++ public final boolean hasSpecialCollidingBlocks() {
++ return this.specialCollidingBlocks != 0;
++ }
++
++ public static long getKnownBlockInfo(final int blockIndex, final long value) {
++ final int valueShift = (blockIndex & (Long.SIZE / 2 - 1));
++
++ return (value >>> (valueShift << 1)) & 0b11L;
++ }
++
++ public final long getKnownBlockInfo(final int blockIndex) {
++ if (this.knownBlockCollisionData == null) {
++ return 0L;
++ }
++
++ final int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2)
++ final int valueShift = (blockIndex & (Long.SIZE / 2 - 1));
++
++ final long value = this.knownBlockCollisionData[arrayIndex];
++
++ return (value >>> (valueShift << 1)) & 0b11L;
++ }
++
++ // important detail: this returns 32 values, one for localZ = localZ & (~1) and one for localZ = localZ | 1
++ // the even localZ is the lower 32 bits, the odd is the upper 32 bits
++ public final long getKnownBlockInfoHorizontalRaw(final int localY, final int localZ) {
++ if (this.knownBlockCollisionData == null) {
++ return 0L;
++ }
++
++ final int horizontalIndex = (localZ << 4) | (localY << 8);
++ return this.knownBlockCollisionData[horizontalIndex >>> (6 - 1)];
++ }
++
++ private void initBlockCollisionData() {
++ this.specialCollidingBlocks = 0;
++ // In 1.18 all sections will be initialised, whether or not they have blocks (fucking stupid btw)
++ // This means we can't aggressively initialise the backing long[], or else memory usage will just skyrocket.
++ // So only init if we contain non-empty blocks.
++ if (this.nonEmptyBlockCount == 0) {
++ this.knownBlockCollisionData = null;
++ return;
++ }
++ this.initKnownDataField();
++ for (int index = 0; index < (16 * 16 * 16); ++index) {
++ final BlockState state = this.states.get(index);
++ this.setKnownBlockInfo(index, state);
++ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(state)) {
++ ++this.specialCollidingBlocks;
++ }
++ }
++ }
++
++ // only use for initBlockCollisionData
++ private void setKnownBlockInfo(final int blockIndex, final BlockState blockState) {
++ final int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2)
++ final int valueShift = (blockIndex & (Long.SIZE / 2 - 1)) << 1;
++
++ long value = this.knownBlockCollisionData[arrayIndex];
++
++ value &= ~(0b11L << valueShift);
++ value |= blockState.getBlockCollisionBehavior() << valueShift;
++
++ this.knownBlockCollisionData[arrayIndex] = value;
++ }
++
++ public void updateKnownBlockInfo(final int blockIndex, final BlockState from, final BlockState to) {
++ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(from)) {
++ --this.specialCollidingBlocks;
++ }
++ if (io.papermc.paper.util.CollisionUtil.isSpecialCollidingBlock(to)) {
++ ++this.specialCollidingBlocks;
++ }
++
++ if (this.nonEmptyBlockCount == 0) {
++ this.knownBlockCollisionData = null;
++ return;
++ }
++
++ if (this.knownBlockCollisionData == null) {
++ this.initKnownDataField();
++ }
++
++ final int arrayIndex = (blockIndex >>> (6 - 1)); // blockIndex / (64/2)
++ final int valueShift = (blockIndex & (Long.SIZE / 2 - 1)) << 1;
++
++ long value = this.knownBlockCollisionData[arrayIndex];
++
++ value &= ~(0b11L << valueShift);
++ value |= to.getBlockCollisionBehavior() << valueShift;
++
++ this.knownBlockCollisionData[arrayIndex] = value;
++ }
++ // Paper end
++
+ public static int getBottomBlockY(int chunkPos) {
+ return chunkPos << 4;
+ }
+@@ -67,8 +171,8 @@ public class LevelChunkSection {
+ return this.setBlockState(x, y, z, state, true);
+ }
+
+- public BlockState setBlockState(int x, int y, int z, BlockState state, boolean lock) {
+- BlockState iblockdata1;
++ public BlockState setBlockState(int x, int y, int z, BlockState state, boolean lock) { // Paper - state -> new state
++ BlockState iblockdata1; // Paper - iblockdata1 -> oldState
+
+ if (lock) {
+ iblockdata1 = (BlockState) this.states.getAndSet(x, y, z, state);
+@@ -107,6 +211,7 @@ public class LevelChunkSection {
+ ++this.tickingFluidCount;
+ }
+
++ this.updateKnownBlockInfo(x | (z << 4) | (y << 8), iblockdata1, state); // Paper
+ return iblockdata1;
+ }
+
+@@ -158,6 +263,7 @@ public class LevelChunkSection {
+ }
+
+ });
++ this.initBlockCollisionData(); // Paper
+ }
+
+ public PalettedContainer getStates() {
diff --git a/src/main/java/net/minecraft/world/phys/AABB.java b/src/main/java/net/minecraft/world/phys/AABB.java
index 120498a39b7ca7aee9763084507508d4a1c425aa..68cc6f2a78a06293a29317fda72ab3ee79b3533a 100644
--- a/src/main/java/net/minecraft/world/phys/AABB.java
diff --git a/patches/server/0820-Actually-unload-POI-data.patch b/patches/server/0820-Actually-unload-POI-data.patch
index 0a61e261e9..21268f4bd3 100644
--- a/patches/server/0820-Actually-unload-POI-data.patch
+++ b/patches/server/0820-Actually-unload-POI-data.patch
@@ -10,10 +10,10 @@ This patch also prevents the saving/unloading of POI data when
world saving is disabled.
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 02bcffcdde33088e43a7771924b2d2bf47badd09..e97e0dbc094963f0976590c2700686647f6dcc8a 100644
+index 29fdc1b991c67006a13231abbbe50e201744b5c2..79dbffe899dd7e31af648302c557e2993f170623 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -811,6 +811,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -812,6 +812,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
// Paper end
}
@@ -21,7 +21,7 @@ index 02bcffcdde33088e43a7771924b2d2bf47badd09..e97e0dbc094963f0976590c270068664
this.updatingChunks.queueUpdate(pos, holder); // Paper - Don't copy
this.modified = true;
-@@ -956,7 +957,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -957,7 +958,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
gameprofilerfiller.pop();
}
@@ -30,7 +30,7 @@ index 02bcffcdde33088e43a7771924b2d2bf47badd09..e97e0dbc094963f0976590c270068664
private void processUnloads(BooleanSupplier shouldKeepTicking) {
LongIterator longiterator = this.toDrop.iterator();
-@@ -1019,6 +1020,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1021,6 +1022,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
this.regionManagers.get(index).removeChunk(holder.pos.x, holder.pos.z);
}
// Paper end
@@ -38,7 +38,7 @@ index 02bcffcdde33088e43a7771924b2d2bf47badd09..e97e0dbc094963f0976590c270068664
if (ichunkaccess instanceof LevelChunk) {
((LevelChunk) ichunkaccess).setLoaded(false);
}
-@@ -1047,6 +1049,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1049,6 +1051,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
for (int index = 0, len = this.regionManagers.size(); index < len; ++index) {
this.regionManagers.get(index).removeChunk(holder.pos.x, holder.pos.z);
}
@@ -46,7 +46,7 @@ index 02bcffcdde33088e43a7771924b2d2bf47badd09..e97e0dbc094963f0976590c270068664
} // Paper end
} finally { this.unloadingPlayerChunk = unloadingBefore; } // Paper - do not allow ticket level changes while unloading chunks
-@@ -1123,6 +1126,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -1125,6 +1128,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
}
this.poiManager.loadInData(pos, chunkHolder.poiData);
chunkHolder.tasks.forEach(Runnable::run);
diff --git a/patches/server/0850-Fix-int-overflow-in-chunk-range-check.patch b/patches/server/0850-Fix-int-overflow-in-chunk-range-check.patch
index 669ff14f4a..f997da5ac5 100644
--- a/patches/server/0850-Fix-int-overflow-in-chunk-range-check.patch
+++ b/patches/server/0850-Fix-int-overflow-in-chunk-range-check.patch
@@ -5,10 +5,10 @@ Subject: [PATCH] Fix int overflow in chunk range check
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 387b0e9b862621e7d0c1179f348e07c25f1ee9c0..2e6e86439173ebdb13b9cebd1e266e91335c1e2d 100644
+index 79dbffe899dd7e31af648302c557e2993f170623..6a035b173cf0d288b2912f568078fede45d138f2 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -621,9 +621,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -622,9 +622,11 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
public static boolean isChunkInRange(int x1, int z1, int x2, int z2, int distance) {
int j1 = Math.max(0, Math.abs(x1 - x2) - 1);
int k1 = Math.max(0, Math.abs(z1 - z2) - 1);
diff --git a/patches/removed/1.18/0769-Replace-player-chunk-loader-system.patch b/patches/server/0868-Replace-player-chunk-loader-system.patch
similarity index 57%
rename from patches/removed/1.18/0769-Replace-player-chunk-loader-system.patch
rename to patches/server/0868-Replace-player-chunk-loader-system.patch
index ef546632f7..ef9e099624 100644
--- a/patches/removed/1.18/0769-Replace-player-chunk-loader-system.patch
+++ b/patches/server/0868-Replace-player-chunk-loader-system.patch
@@ -23,7 +23,7 @@ chunk-loading:
global-max-chunk-send-rate: -1
enable-frustum-priority: false
global-max-chunk-load-rate: -1.0
- player-max-concurrent-loads: 4.0
+ player-max-concurrent-loads: 25.0
global-max-concurrent-loads: 500.0
```
@@ -67,27 +67,32 @@ determined by the number of players on the server multiplied by the
whatever `global-max-concurrent-loads` is configured to.
diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java
-index cfe293881f68c8db337c3a48948362bb7b3e3522..7d44abcb4fff9717a1af55879deb7eb9c2d9e7e9 100644
+index 5e3b7fb2e0b7608610555cd23e7ad25a05883181..1cb0aae3e0c619a715766e0fa604dfd9a8caefcc 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 {
+@@ -152,7 +152,11 @@ public class TimingsExport extends Thread {
+ pair("gamerules", toObjectMapper(world.getWorld().getGameRules(), rule -> {
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()) // Paper - replace old player chunk management
+- pair("ticking-distance", world.getChunkSource().chunkMap.getEffectiveViewDistance())
++ // Paper start - replace chunk loader system
++ pair("ticking-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance()),
++ pair("no-ticking-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance()),
++ pair("sending-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance())
++ // Paper end - replace chunk loader system
));
}));
diff --git a/src/main/java/com/destroystokyo/paper/PaperConfig.java b/src/main/java/com/destroystokyo/paper/PaperConfig.java
-index 939744ef6850d4e59e5c94bc4781d045041b977d..a72f15c10410508ff344caf3ca376fd3d7317518 100644
+index 66501c3b0eb92d946ef77bbd3f36ebcc0d3023af..153f07bac06093b43a1f5b0f8e1a46ffbe6407e5 100644
--- a/src/main/java/com/destroystokyo/paper/PaperConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java
-@@ -525,4 +525,29 @@ public class PaperConfig {
- itemValidationBookAuthorLength = getInt("settings.item-validation.book.author", itemValidationBookAuthorLength);
- itemValidationBookPageLength = getInt("settings.item-validation.book.page", itemValidationBookPageLength);
+@@ -650,4 +650,33 @@ public class PaperConfig {
+ private static void timeCommandAffectsAllWorlds() {
+ timeCommandAffectsAllWorlds = getBoolean("settings.time-command-affects-all-worlds", timeCommandAffectsAllWorlds);
}
+
++
+ public static int playerMinChunkLoadRadius;
+ public static boolean playerAutoConfigureSendViewDistance;
+ public static int playerMaxConcurrentChunkSends;
@@ -107,18 +112,21 @@ index 939744ef6850d4e59e5c94bc4781d045041b977d..a72f15c10410508ff344caf3ca376fd3
+ playerFrustumPrioritisation = getBoolean("settings.chunk-loading.enable-frustum-priority", false);
+ globalMaxChunkLoadRate = getDouble("settings.chunk-loading.global-max-chunk-load-rate", -1.0);
+ if (version < 23 && globalMaxChunkLoadRate == 300.0) {
-+ set("settings.chunk-loading.global-max-chunk-load-rate", -1.0);
++ set("settings.chunk-loading.global-max-chunk-load-rate", globalMaxChunkLoadRate = -1.0);
++ }
++ playerMaxConcurrentChunkLoads = getDouble("settings.chunk-loading.player-max-concurrent-loads", 20.0);
++ if (version < 25 && playerMaxConcurrentChunkLoads == 4.0) {
++ set("settings.chunk-loading.player-max-concurrent-loads", playerMaxConcurrentChunkLoads = 20.0);
+ }
-+ playerMaxConcurrentChunkLoads = getDouble("settings.chunk-loading.player-max-concurrent-loads", 4.0);
+ globalMaxConcurrentChunkLoads = getDouble("settings.chunk-loading.global-max-concurrent-loads", 500.0);
+ }
}
diff --git a/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java
new file mode 100644
-index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a649a46ed9
+index 0000000000000000000000000000000000000000..9ad84dc6f0463dbbbdd53723edcc2a3ebc63de24
--- /dev/null
+++ b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java
-@@ -0,0 +1,989 @@
+@@ -0,0 +1,1108 @@
+package io.papermc.paper.chunk;
+
+import com.destroystokyo.paper.PaperConfig;
@@ -131,18 +139,18 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+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.network.protocol.game.ClientboundSetSimulationDistancePacket;
+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.server.level.*;
+import net.minecraft.util.Mth;
+import net.minecraft.world.level.ChunkPos;
+import net.minecraft.world.level.chunk.LevelChunk;
++import org.apache.commons.lang3.mutable.MutableObject;
++import org.bukkit.craftbukkit.entity.CraftPlayer;
++import org.bukkit.entity.Player;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.List;
@@ -157,6 +165,45 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+ public static final int TICK_TICKET_LEVEL = 31;
+ public static final int LOADED_TICKET_LEVEL = 33;
+
++ public static int getTickViewDistance(final Player player) {
++ return getTickViewDistance(((CraftPlayer)player).getHandle());
++ }
++
++ public static int getTickViewDistance(final ServerPlayer player) {
++ final ServerLevel level = (ServerLevel)player.level;
++ final PlayerLoaderData data = level.chunkSource.chunkMap.playerChunkManager.getData(player);
++ if (data == null) {
++ return level.chunkSource.chunkMap.playerChunkManager.getTargetTickViewDistance();
++ }
++ return data.getTargetTickViewDistance();
++ }
++
++ public static int getLoadViewDistance(final Player player) {
++ return getLoadViewDistance(((CraftPlayer)player).getHandle());
++ }
++
++ public static int getLoadViewDistance(final ServerPlayer player) {
++ final ServerLevel level = (ServerLevel)player.level;
++ final PlayerLoaderData data = level.chunkSource.chunkMap.playerChunkManager.getData(player);
++ if (data == null) {
++ return level.chunkSource.chunkMap.playerChunkManager.getLoadDistance();
++ }
++ return data.getLoadDistance();
++ }
++
++ public static int getSendViewDistance(final Player player) {
++ return getSendViewDistance(((CraftPlayer)player).getHandle());
++ }
++
++ public static int getSendViewDistance(final ServerPlayer player) {
++ final ServerLevel level = (ServerLevel)player.level;
++ final PlayerLoaderData data = level.chunkSource.chunkMap.playerChunkManager.getData(player);
++ if (data == null) {
++ return level.chunkSource.chunkMap.playerChunkManager.getTargetSendDistance();
++ }
++ return data.getTargetSendViewDistance();
++ }
++
+ protected final ChunkMap chunkMap;
+ protected final Reference2ObjectLinkedOpenHashMap playerMap = new Reference2ObjectLinkedOpenHashMap<>(512, 0.7f);
+ protected final ReferenceLinkedOpenHashSet chunkSendQueue = new ReferenceLinkedOpenHashSet<>(512, 0.7f);
@@ -246,11 +293,11 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+
+ // methods to bridge for API
+
-+ public int getTargetViewDistance() {
++ public int getTargetTickViewDistance() {
+ return this.getTickDistance();
+ }
+
-+ public void setTargetViewDistance(final int distance) {
++ public void setTargetTickViewDistance(final int distance) {
+ this.setTickDistance(distance);
+ }
+
@@ -333,14 +380,7 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+ 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);
-+ },
++ null,
+ (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);
@@ -394,6 +434,29 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+ protected final LongOpenHashSet isTargetedForPlayerLoad = new LongOpenHashSet();
+ protected final LongOpenHashSet chunkTicketTracker = new LongOpenHashSet();
+
++ public boolean isChunkNearPlayers(final int chunkX, final int chunkZ) {
++ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInSendRange = this.broadcastMap.getObjectsInRange(chunkX, chunkZ);
++
++ return playersInSendRange != null;
++ }
++
++ public void onChunkPostProcessing(final int chunkX, final int chunkZ) {
++ this.onChunkSendReady(chunkX, chunkZ);
++ }
++
++ private boolean chunkNeedsPostProcessing(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;
++ }
++
++ final LevelChunk levelChunk = chunk.getSendingChunk();
++
++ return levelChunk != null && !levelChunk.isPostProcessingDone;
++ }
++
+ // 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);
@@ -403,7 +466,13 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+ return false;
+ }
+
-+ return chunk.getSendingChunk() != null && this.isTargetedForPlayerLoad.contains(key);
++ final LevelChunk levelChunk = chunk.getSendingChunk();
++
++ return levelChunk != null && levelChunk.isPostProcessingDone && this.isTargetedForPlayerLoad.contains(key);
++ }
++
++ public boolean isChunkSent(final ServerPlayer player, final int chunkX, final int chunkZ, final boolean borderOnly) {
++ return borderOnly ? this.isChunkSentBorderOnly(player, chunkX, chunkZ) : this.isChunkSent(player, chunkX, chunkZ);
+ }
+
+ public boolean isChunkSent(final ServerPlayer player, final int chunkX, final int chunkZ) {
@@ -415,6 +484,21 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+ return data.hasSentChunk(chunkX, chunkZ);
+ }
+
++ public boolean isChunkSentBorderOnly(final ServerPlayer player, final int chunkX, final int chunkZ) {
++ final PlayerLoaderData data = this.playerMap.get(player);
++ if (data == null) {
++ return false;
++ }
++
++ final boolean center = data.hasSentChunk(chunkX, chunkZ);
++ if (!center) {
++ return false;
++ }
++
++ return !(data.hasSentChunk(chunkX - 1, chunkZ) && data.hasSentChunk(chunkX + 1, chunkZ) &&
++ data.hasSentChunk(chunkX, chunkZ - 1) && data.hasSentChunk(chunkX, chunkZ + 1));
++ }
++
+ protected int getMaxConcurrentChunkSends() {
+ return PaperConfig.playerMaxConcurrentChunkSends;
+ }
@@ -452,13 +536,11 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+ if (!(raw instanceof ServerPlayer)) {
+ continue;
+ }
-+ this.onChunkEnter((ServerPlayer)raw, chunkX, chunkZ);
++ this.onChunkSendReady((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) {
++ public void onChunkSendReady(final ServerPlayer player, final int chunkX, final int chunkZ) {
+ final PlayerLoaderData data = this.playerMap.get(player);
+
+ if (data == null) {
@@ -470,6 +552,11 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+ return;
+ }
+
++ if (!data.chunksToBeSent.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) {
++ // don't queue to send, we don't want the chunk
++ return;
++ }
++
+ final long playerPos = this.broadcastMap.getLastCoordinate(player);
+ final int playerChunkX = CoordinateUtils.getChunkX(playerPos);
+ final int playerChunkZ = CoordinateUtils.getChunkZ(playerPos);
@@ -700,8 +787,8 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+ }
+ }
+ }
-+ if (unloadedTargetChunk && priority > 0.0) {
-+ // priority > 0.0 implies rate limited chunks
++ if (unloadedTargetChunk && priority >= 0.0) {
++ // priority >= 0.0 implies rate limited chunks
+
+ final int currentChunkLoads = this.concurrentChunkLoads;
+ if (currentChunkLoads >= maxLoads || (PaperConfig.globalMaxChunkLoadRate > 0 && (TICKET_ADDITION_COUNTER_SHORT.getRate() >= PaperConfig.globalMaxChunkLoadRate || TICKET_ADDITION_COUNTER_LONG.getRate() >= PaperConfig.globalMaxChunkLoadRate))) {
@@ -745,6 +832,22 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+ if (this.isChunkPlayerLoaded(queuedLoad.chunkX, queuedLoad.chunkZ)) {
+ // yup, all we needed.
+ this.onChunkSendReady(queuedLoad.chunkX, queuedLoad.chunkZ);
++ } else if (this.chunkNeedsPostProcessing(queuedLoad.chunkX, queuedLoad.chunkZ)) {
++ // requires post processing
++ this.chunkMap.mainThreadExecutor.execute(() -> {
++ final long key = CoordinateUtils.getChunkKey(queuedLoad.chunkX, queuedLoad.chunkZ);
++ final ChunkHolder holder = PlayerChunkLoader.this.chunkMap.getVisibleChunkIfPresent(key);
++
++ if (holder == null) {
++ return;
++ }
++
++ final LevelChunk chunk = holder.getSendingChunk();
++
++ if (chunk != null && !chunk.isPostProcessingDone) {
++ chunk.postProcessGeneration();
++ }
++ });
+ }
+ }
+ }
@@ -783,8 +886,8 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+ protected double lastLocX = Double.NEGATIVE_INFINITY;
+ protected double lastLocZ = Double.NEGATIVE_INFINITY;
+
-+ protected int lastChunkX;
-+ protected int lastChunkZ;
++ protected int lastChunkX = Integer.MIN_VALUE;
++ protected int lastChunkZ = Integer.MIN_VALUE;
+
+ // this is corrected so that 0 is along the positive x-axis
+ protected float lastYaw = Float.NEGATIVE_INFINITY;
@@ -801,6 +904,7 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+ // in a comparator!
+ protected final ArrayDeque loadQueue = new ArrayDeque<>();
+ protected final LongOpenHashSet sentChunks = new LongOpenHashSet();
++ protected final LongOpenHashSet chunksToBeSent = new LongOpenHashSet();
+
+ protected final TreeSet sendQueue = new TreeSet<>((final ChunkPriorityHolder p1, final ChunkPriorityHolder p2) -> {
+ final int distanceCompare = Integer.compare(p1.manhattanDistanceToPlayer, p2.manhattanDistanceToPlayer);
@@ -859,7 +963,7 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+ }
+
+ public void setTargetTickViewDistance(final int distance) {
-+ if (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE) {
++ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE)) {
+ throw new IllegalArgumentException(Integer.toString(distance));
+ }
+ this.tickViewDistance = distance;
@@ -878,7 +982,7 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+ 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
++ new ChunkPos(chunkX, chunkZ), new MutableObject<>(), false, true); // unloaded, loaded
+ this.player.connection.connection.execute(onChunkSend);
+ } else {
+ throw new IllegalStateException();
@@ -892,6 +996,12 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+ }
+ }
+
++ protected static boolean wantChunkLoaded(final int centerX, final int centerZ, final int chunkX, final int chunkZ,
++ final int sendRadius) {
++ // expect sendRadius to be = 1 + target viewable radius
++ return ChunkMap.isChunkInRange(chunkX, chunkZ, centerX, centerZ, sendRadius);
++ }
++
+ 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
@@ -985,9 +1095,8 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+ final int centerChunkX = Mth.floor(posX) >> 4;
+ final int centerChunkZ = Mth.floor(posZ) >> 4;
+
-+ this.player.needsChunkCenterUpdate = true;
++ final boolean needsChunkCenterUpdate = (centerChunkX != this.lastChunkX) || (centerChunkZ != this.lastChunkZ);
+ 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);
@@ -996,7 +1105,10 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+ // 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.player.connection.send(new ClientboundSetChunkCacheRadiusPacket(sendViewDistance));
++ }
++ if (tickViewDistance != this.lastTickDistance) {
++ this.player.connection.send(new ClientboundSetSimulationDistancePacket(tickViewDistance));
+ }
+
+ this.lastLocX = posX;
@@ -1034,6 +1146,8 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+
+ // clear send queue, we are re-sorting
+ this.sendQueue.clear();
++ // clear chunk want set, vd/position might have changed
++ this.chunksToBeSent.clear();
+
+ final int searchViewDistance = Math.max(loadViewDistance, sendViewDistance);
+
@@ -1042,14 +1156,19 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+ final int chunkX = dx + centerChunkX;
+ final int chunkZ = dz + centerChunkZ;
+ final int squareDistance = Math.max(Math.abs(dx), Math.abs(dz));
++ final boolean sendChunk = squareDistance <= sendViewDistance && wantChunkLoaded(centerChunkX, centerChunkZ, chunkX, chunkZ, sendViewDistance);
+
+ if (this.hasSentChunk(chunkX, chunkZ)) {
+ // already sent (which means it is also loaded)
++ if (!sendChunk) {
++ // have sent the chunk, but don't want it anymore
++ // unload it now
++ this.unloadChunk(chunkX, chunkZ);
++ }
+ continue;
+ }
+
+ final boolean loadChunk = squareDistance <= loadViewDistance;
-+ final boolean sendChunk = squareDistance <= sendViewDistance;
+
+ final boolean prioritised = useLookPriority && triangleIntersects(
+ // prioritisation triangle
@@ -1059,15 +1178,14 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+ (double)((chunkX << 4) | 8), (double)((chunkZ << 4) | 8)
+ );
+
-+
-+ final int manhattanDistance = (Math.abs(dx) + Math.abs(dz));
++ final int manhattanDistance = Math.abs(dx) + Math.abs(dz);
+
+ final double priority;
+
+ if (squareDistance <= PaperConfig.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 * PaperConfig.playerMinChunkLoadRadius + 1) - (dx + dz));
++ priority = -((2 * PaperConfig.playerMinChunkLoadRadius + 1) - manhattanDistance);
+ } else {
+ if (prioritised) {
+ // we don't prioritise these chunks above others because we also want to make sure some chunks
@@ -1083,6 +1201,9 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+ if (!this.loader.isChunkPlayerLoaded(chunkX, chunkZ)) {
+ if (loadChunk) {
+ loadQueue.add(holder);
++ if (sendChunk) {
++ this.chunksToBeSent.add(CoordinateUtils.getChunkKey(chunkX, chunkZ));
++ }
+ }
+ } else {
+ // loaded but not sent: so queue it!
@@ -1105,14 +1226,20 @@ index 0000000000000000000000000000000000000000..4eadc15f747528b59349f095171dd5a6
+
+ // must re-add
+ this.loader.chunkLoadQueue.add(this);
++
++ // update the chunk center
++ // this must be done last so that the client does not ignore any of our unload chunk packets
++ if (needsChunkCenterUpdate) {
++ this.player.connection.send(new ClientboundSetChunkCacheCenterPacket(centerChunkX, centerChunkZ));
++ }
+ }
+ }
+}
diff --git a/src/main/java/net/minecraft/network/Connection.java b/src/main/java/net/minecraft/network/Connection.java
-index 032d65a489d65e9b5b5066dff80c65d2e1b28c82..580bdaa99129c8edb82b835edfa822892f1cd243 100644
+index 81dde0efc1a06420c0791520b9e40b24dd1f0318..7b8f9cf06833860d0fc02399822e6aea214883ed 100644
--- a/src/main/java/net/minecraft/network/Connection.java
+++ b/src/main/java/net/minecraft/network/Connection.java
-@@ -93,6 +93,28 @@ public class Connection extends SimpleChannelInboundHandler> {
+@@ -95,6 +95,28 @@ public class Connection extends SimpleChannelInboundHandler> {
public boolean queueImmunity = false;
public ConnectionProtocol protocol;
// Paper end
@@ -1141,7 +1268,7 @@ index 032d65a489d65e9b5b5066dff80c65d2e1b28c82..580bdaa99129c8edb82b835edfa82289
// Paper start - allow controlled flushing
volatile boolean canFlush = true;
-@@ -399,6 +421,7 @@ public class Connection extends SimpleChannelInboundHandler> {
+@@ -466,6 +488,7 @@ public class Connection extends SimpleChannelInboundHandler> {
return false;
}
private boolean processQueue() {
@@ -1149,7 +1276,7 @@ index 032d65a489d65e9b5b5066dff80c65d2e1b28c82..580bdaa99129c8edb82b835edfa82289
if (this.queue.isEmpty()) return true;
// Paper start - make only one flush call per sendPacketQueue() call
final boolean needsFlush = this.canFlush;
-@@ -430,6 +453,12 @@ public class Connection extends SimpleChannelInboundHandler> {
+@@ -497,6 +520,12 @@ public class Connection extends SimpleChannelInboundHandler> {
}
}
return true;
@@ -1163,360 +1290,497 @@ index 032d65a489d65e9b5b5066dff80c65d2e1b28c82..580bdaa99129c8edb82b835edfa82289
// Paper end
diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java
-index 82a233b413791eff4bc6b9140b5bbf99354ed671..15fb4ee2066df1c8ce341913a64f350fb8b9718c 100644
+index b61abf227a04b4565c2525e5f469db30c3a545a5..7bddc7517356cc74104dcc5c7c55522a53f2596f 100644
--- a/src/main/java/net/minecraft/server/MCUtil.java
+++ b/src/main/java/net/minecraft/server/MCUtil.java
-@@ -642,7 +642,7 @@ public final class MCUtil {
+@@ -647,7 +647,8 @@ public final class MCUtil {
+ });
worldData.addProperty("name", world.getWorld().getName());
- worldData.addProperty("view-distance", world.getChunkSource().chunkMap.getEffectiveViewDistance());
-- worldData.addProperty("no-view-distance", world.getChunkSource().chunkMap.getRawNoTickViewDistance());
-+ worldData.addProperty("no-view-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance()); // Paper - replace old player chunk management
+- worldData.addProperty("view-distance", world.spigotConfig.viewDistance);
++ worldData.addProperty("view-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance()); // Paper - replace chunk loader system
++ worldData.addProperty("tick-view-distance", world.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance()); // Paper - replace chunk loader system
worldData.addProperty("keep-spawn-loaded", world.keepSpawnInMemory);
worldData.addProperty("keep-spawn-loaded-range", world.paperConfig.keepLoadedRange);
worldData.addProperty("visible-chunk-count", visibleChunks.size());
diff --git a/src/main/java/net/minecraft/server/level/ChunkHolder.java b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-index a9267e64e54f451c896e35693f469b8563f578f9..326aecc38a7f93fe0d25fb9b772d06f78f99781d 100644
+index 9c4e4ab16441555d7940863b6736a03ee4af545c..1501759a44bd17a623a852b887b660cbd2780627 100644
--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
-@@ -491,7 +491,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;
+@@ -73,6 +73,17 @@ public class ChunkHolder {
+ public ServerLevel getWorld() { return chunkMap.level; } // Paper
+ boolean isUpdateQueued = false; // Paper
+ private final ChunkMap chunkMap; // Paper
++ // Paper start - no-tick view distance
++ public final LevelChunk getSendingChunk() {
++ // it's important that we use getChunkAtIfLoadedImmediately to mirror the chunk sending logic used
++ // in Chunk's neighbour callback
++ LevelChunk ret = this.chunkMap.level.getChunkSource().getChunkAtIfLoadedImmediately(this.pos.x, this.pos.z);
++ if (ret != null && ret.areNeighboursLoaded(1)) {
++ return ret;
++ }
++ return null;
++ }
++ // Paper end - no-tick view distance
+
+ // Paper start - optimise anyPlayerCloseEnoughForSpawning
+ // cached here to avoid a map lookup
+@@ -257,7 +268,7 @@ public class ChunkHolder {
+
+ public void blockChanged(BlockPos pos) {
+ if (!pos.isInsideBuildHeightAndWorldBoundsHorizontal(levelHeightAccessor)) return; // Paper - SPIGOT-6086 for all invalid locations; avoid acquiring locks
+- LevelChunk chunk = this.getTickingChunk();
++ LevelChunk chunk = this.getSendingChunk(); // Paper - no-tick view distance
+
+ if (chunk != null) {
+ int i = this.levelHeightAccessor.getSectionIndex(pos.getY());
+@@ -273,7 +284,7 @@ public class ChunkHolder {
+ }
+
+ public void sectionLightChanged(LightLayer lightType, int y) {
+- LevelChunk chunk = this.getTickingChunk();
++ LevelChunk chunk = this.getSendingChunk(); // Paper - no-tick view distance
+
+ if (chunk != null) {
+ chunk.setUnsaved(true);
+@@ -375,9 +386,28 @@ public class ChunkHolder {
+ }
+
+ public void broadcast(Packet> packet, boolean onlyOnWatchDistanceEdge) {
+- this.playerProvider.getPlayers(this.pos, onlyOnWatchDistanceEdge).forEach((entityplayer) -> {
+- entityplayer.connection.send(packet);
+- });
++ // 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.playerChunkManager.broadcastMap; // Paper - replace old player chunk manager
- com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = viewDistanceMap.getObjectsInRange(this.pos);
- if (players == null) {
- return;
-@@ -508,6 +508,7 @@ public class ChunkHolder {
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = viewDistanceMap.getObjectsInRange(this.pos);
++ if (players == null) {
++ return;
++ }
++
++ Object[] backingSet = players.getBackingSet();
++ for (int i = 0, len = backingSet.length; i < len; ++i) {
++ Object temp = backingSet[i];
++ if (!(temp instanceof ServerPlayer)) {
++ continue;
++ }
++ ServerPlayer player = (ServerPlayer)temp;
++ if (!this.chunkMap.playerChunkManager.isChunkSent(player, this.pos.x, this.pos.z, onlyOnWatchDistanceEdge)) {
++ continue;
++ }
++ player.connection.send(packet);
++ }
++ // Paper end - per player view distance
+ }
- int viewDistance = viewDistanceMap.getLastViewDistance(player);
- long lastPosition = viewDistanceMap.getLastCoordinate(player);
-+ if (!this.chunkMap.playerChunkManager.isChunkSent(player, this.pos.x, this.pos.z)) continue; // Paper - 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);
-@@ -524,6 +525,7 @@ public class ChunkHolder {
- continue;
- }
- ServerPlayer player = (ServerPlayer)temp;
-+ if (!this.chunkMap.playerChunkManager.isChunkSent(player, this.pos.x, this.pos.z)) continue; // Paper - replace player chunk management
- player.connection.send(packet);
- }
- }
+ public CompletableFuture> getOrScheduleFuture(ChunkStatus targetStatus, ChunkMap chunkStorage) {
diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
-index 85c97767cdaf45b24f5764a6a1ef3c56535bb37f..8e0762bc1d705b7df664b6270c4d536f77572b87 100644
+index 6a035b173cf0d288b2912f568078fede45d138f2..afef3ea6d1ae5f145261eaae3da720fdf9e923a8 100644
--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
-@@ -187,22 +187,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
+@@ -213,6 +213,7 @@ 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 ChunkMap#anyPlayerCloseEnoughForSpawning
+ public final io.papermc.paper.chunk.PlayerChunkLoader playerChunkManager = new io.papermc.paper.chunk.PlayerChunkLoader(this, this.pooledLinkedPlayerHashSets); // Paper - replace chunk loader
// Paper start - use distance map to optimise tracker
public static boolean isLegacyTrackingEntity(Entity entity) {
return entity.isLegacyTrackingEntity;
-@@ -241,7 +226,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -232,6 +233,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ // Paper end - use distance map to optimise tracker
+
+ void addPlayerToDistanceMaps(ServerPlayer player) {
++ this.playerChunkManager.addPlayer(player); // Paper - replace chunk loader
+ int chunkX = MCUtil.getChunkCoordinate(player.getX());
+ int chunkZ = MCUtil.getChunkCoordinate(player.getZ());
+ // Paper start - use distance map to optimise entity tracker
+@@ -239,7 +241,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())); // Paper - per player view distances
++ trackMap.add(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player))); // Paper - per player view distances
}
// Paper end - use distance map to optimise entity tracker
- // Paper start - optimise PlayerChunkMap#isOutsideRange
-@@ -250,19 +235,7 @@ 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); // Paper - replace chunk loader
- }
-
- void removePlayerFromDistanceMaps(ServerPlayer player) {
-@@ -275,11 +248,7 @@ 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
+ // Note: players need to be explicitly added to distance maps before they can be updated
+@@ -269,6 +271,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ this.playerMobDistanceMap.remove(player);
+ }
+ // Paper end - per player mob spawning
+ this.playerChunkManager.removePlayer(player); // Paper - replace chunk loader
}
void updateMaps(ServerPlayer player) {
-@@ -291,25 +260,13 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+@@ -280,7 +283,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.update(player, chunkX, chunkZ, Math.min(trackRange, this.getEffectiveViewDistance()));
-+ trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, player.getBukkitEntity().getViewDistance())); // Paper - per player view distances
++ trackMap.update(player, chunkX, chunkZ, Math.min(trackRange, io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player))); // Paper - 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);
--
-- 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)
-- }
--
-- 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
+ this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
+@@ -290,6 +293,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ this.playerMobDistanceMap.update(player, chunkX, chunkZ, this.distanceManager.getSimulationDistance());
+ }
+ // Paper end - per player mob spawning
+ this.playerChunkManager.updatePlayer(player); // Paper - replace chunk loader
}
// Paper end
// Paper start
-@@ -394,43 +351,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- this.regionManagers.add(this.dataRegionManager);
- // Paper end
- // Paper start - no-tick view distance
-- this.setNoTickViewDistance(this.level.paperConfig.noTickViewDistance);
-- this.playerViewDistanceTickMap = 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 (newState.size() != 1) {
-- return;
-- }
-- LevelChunk chunk = ChunkMap.this.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(rangeX, rangeZ);
-- if (chunk == null || !chunk.areNeighboursLoaded(2)) {
-- return;
-- }
+@@ -1396,11 +1400,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ completablefuture1.thenAcceptAsync((either) -> {
+ either.ifLeft((chunk) -> {
+ this.tickingGenerated.getAndIncrement();
+- MutableObject> mutableobject = new MutableObject<>(); // Paper - Anti-Xray - Bypass
-
-- 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;
-- }
-- ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ);
-- ChunkMap.this.level.getChunkSource().removeTicketAtLevel(TicketType.PLAYER, chunkPos, 31, chunkPos); // entity ticking level, TODO check on update
-- });
-- 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
-- });
-+ this.setNoTickViewDistance(this.level.paperConfig.noTickViewDistance); // Paper - replace chunk loading system
- // Paper end - no-tick view distance
- this.playerMobDistanceMap = this.level.paperConfig.perPlayerMobSpawns ? new com.destroystokyo.paper.util.PlayerMobDistanceMap() : null; // Paper
- // Paper start - use distance map to optimise entity tracker
-@@ -537,6 +458,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- }
-
- public void checkHighPriorityChunks(ServerPlayer player) {
-+ if (true) return; // Paper - replace player chunk loader
- int currentTick = MinecraftServer.currentTick;
- if (currentTick - player.lastHighPriorityChecked < 20 || !player.isRealPlayer) { // weed out fake players
- return;
-@@ -544,7 +466,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(); // Paper - replace player chunk loader
- net.minecraft.core.BlockPos.MutableBlockPos pos = new net.minecraft.core.BlockPos.MutableBlockPos();
-
- // Prioritize circular near
-@@ -610,7 +532,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; // Paper - replace player chunk loader - unused outside paper player loader logic
- ChunkHolder chunk = getUpdatingChunkIfPresent(coord.toLong());
- return chunk != null && (chunk.isFullChunkReady());
- }
-@@ -1548,7 +1470,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+- this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> {
+- this.playerLoadedChunk(entityplayer, mutableobject, chunk);
+- });
++ // Paper - no-tick view distance - moved to Chunk neighbour update
+ });
+ }, (runnable) -> {
+ this.mainThreadMailbox.tell(ChunkTaskPriorityQueueSorter.message(holder, runnable));
+@@ -1557,33 +1557,24 @@ 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(watchDistance, 2, 32)); // Paper - replace player loader system
+- this.distanceManager.updatePlayerTickets(this.viewDistance + 1);
+- Iterator objectiterator = this.updatingChunks.getVisibleValuesCopy().iterator(); // Paper
+-
+- while (objectiterator.hasNext()) {
+- ChunkHolder playerchunk = (ChunkHolder) objectiterator.next();
+- ChunkPos chunkcoordintpair = playerchunk.getPos();
+- MutableObject> mutableobject = new MutableObject<>(); // Paper - Anti-Xray - Bypass
+-
+- this.getPlayers(chunkcoordintpair, false).forEach((entityplayer) -> {
+- SectionPos sectionposition = entityplayer.getLastSectionPos();
+- boolean flag = ChunkMap.isChunkInRange(chunkcoordintpair.x, chunkcoordintpair.z, sectionposition.x(), sectionposition.z(), k);
+- boolean flag1 = ChunkMap.isChunkInRange(chunkcoordintpair.x, chunkcoordintpair.z, sectionposition.x(), sectionposition.z(), this.viewDistance);
+-
+- this.updateChunkTracking(entityplayer, chunkcoordintpair, mutableobject, flag, flag1);
+- });
+- }
++ this.playerChunkManager.setLoadDistance(Mth.clamp(this.viewDistance, 2, 32)); // Paper - replace player loader system
}
}
-@@ -1556,26 +1478,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);
+
+- protected void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject> packet, boolean oldWithinViewDistance, boolean newWithinViewDistance) { // Paper - Anti-Xray - Bypass
++ // Paper start - replace player loader system
++ public void setTickViewDistance(int distance) {
++ this.playerChunkManager.setTickDistance(distance);
++ }
++ // Paper end - replace player loader system
++
++ public void updateChunkTracking(ServerPlayer player, ChunkPos pos, MutableObject> packet, boolean oldWithinViewDistance, boolean newWithinViewDistance) { // Paper - Anti-Xray - Bypass // Paper - public
+ if (player.level == this.level) {
+ if (newWithinViewDistance && !oldWithinViewDistance) {
+ ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.toLong());
+
+ if (playerchunk != null) {
+- LevelChunk chunk = playerchunk.getTickingChunk();
++ LevelChunk chunk = playerchunk.getSendingChunk(); // Paper - replace chunk loader system
+
+ if (chunk != null) {
+ this.playerLoadedChunk(player, packet, chunk);
+@@ -1614,7 +1605,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+
+ void dumpChunks(Writer writer) throws IOException {
+ CsvOutput csvwriter = CsvOutput.builder().addColumn("x").addColumn("z").addColumn("level").addColumn("in_memory").addColumn("status").addColumn("full_status").addColumn("accessible_ready").addColumn("ticking_ready").addColumn("entity_ticking_ready").addColumn("ticket").addColumn("spawning").addColumn("block_entity_count").addColumn("ticking_ticket").addColumn("ticking_level").addColumn("block_ticks").addColumn("fluid_ticks").build(writer);
+- TickingTracker tickingtracker = this.distanceManager.tickingTracker();
++ // Paper - replace loader system
+ ObjectBidirectionalIterator objectbidirectionaliterator = this.updatingChunks.getVisibleMap().clone().long2ObjectEntrySet().fastIterator(); // Paper
+
+ while (objectbidirectionaliterator.hasNext()) {
+@@ -1630,7 +1621,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ // CraftBukkit - decompile error
+ csvwriter.writeRow(chunkcoordintpair.x, chunkcoordintpair.z, playerchunk.getTicketLevel(), optional.isPresent(), optional.map(ChunkAccess::getStatus).orElse(null), optional1.map(LevelChunk::getFullStatus).orElse(null), ChunkMap.printFuture(playerchunk.getFullChunkFuture()), ChunkMap.printFuture(playerchunk.getTickingChunkFuture()), ChunkMap.printFuture(playerchunk.getEntityTickingChunkFuture()), this.distanceManager.getTicketDebugString(i), this.anyPlayerCloseEnoughForSpawning(chunkcoordintpair), optional1.map((chunk) -> {
+ return chunk.getBlockEntities().size();
+- }).orElse(0), tickingtracker.getTicketDebugString(i), tickingtracker.getLevel(i), optional1.map((chunk) -> {
++ }).orElse(0), "Use ticket level", -1000, optional1.map((chunk) -> { // Paper - replace loader system
+ return chunk.getBlockTicks().count();
+ }).orElse(0), optional1.map((chunk) -> {
+ return chunk.getFluidTicks().count();
+@@ -1847,15 +1838,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ this.removePlayerFromDistanceMaps(player); // Paper - distance maps
+ }
+
+- for (int k = i - this.viewDistance - 1; k <= i + this.viewDistance + 1; ++k) {
+- for (int l = j - this.viewDistance - 1; l <= j + this.viewDistance + 1; ++l) {
+- if (ChunkMap.isChunkInRange(k, l, i, j, this.viewDistance)) {
+- ChunkPos chunkcoordintpair = new ChunkPos(k, l);
-
-- 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.updateChunkTracking(player, chunkcoordintpair, new MutableObject(), !added, added);
- }
-- this.updateMaps(player);
-- // Paper end - no-tick view distance
- }
- }
-+ this.playerChunkManager.setLoadDistance(viewDistance == -1 ? -1 : viewDistance + 1); // Paper - replace player loader system - add 1 here, we need an extra one to send to clients for chunks in this viewDistance to render
++ // Paper - handled by player chunk loader
}
-- 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) { // Paper - public
- if (player.level == this.level) {
- if (withinViewDistance && !withinMaxWatchDistance) {
- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos.toLong());
-@@ -1904,6 +1811,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- */ // Paper end - replaced by distance map
+@@ -1863,7 +1846,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ SectionPos sectionposition = SectionPos.of((Entity) player);
+
+ player.setLastSectionPos(sectionposition);
+- player.connection.send(new ClientboundSetChunkCacheCenterPacket(sectionposition.x(), sectionposition.z()));
++ //player.connection.send(new ClientboundSetChunkCacheCenterPacket(sectionposition.x(), sectionposition.z())); // Paper - handled by player chunk loader
+ return sectionposition;
+ }
+
+@@ -1908,65 +1891,40 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ int k1;
+ int l1;
+
+- if (Math.abs(i1 - i) <= this.viewDistance * 2 && Math.abs(j1 - j) <= this.viewDistance * 2) {
+- k1 = Math.min(i, i1) - this.viewDistance - 1;
+- l1 = Math.min(j, j1) - this.viewDistance - 1;
+- int i2 = Math.max(i, i1) + this.viewDistance + 1;
+- int j2 = Math.max(j, j1) + this.viewDistance + 1;
+-
+- for (int k2 = k1; k2 <= i2; ++k2) {
+- for (int l2 = l1; l2 <= j2; ++l2) {
+- boolean flag3 = ChunkMap.isChunkInRange(k2, l2, i1, j1, this.viewDistance);
+- boolean flag4 = ChunkMap.isChunkInRange(k2, l2, i, j, this.viewDistance);
+-
+- this.updateChunkTracking(player, new ChunkPos(k2, l2), new MutableObject(), flag3, flag4);
+- }
+- }
+- } else {
+- boolean flag5;
+- boolean flag6;
+-
+- for (k1 = i1 - this.viewDistance - 1; k1 <= i1 + this.viewDistance + 1; ++k1) {
+- for (l1 = j1 - this.viewDistance - 1; l1 <= j1 + this.viewDistance + 1; ++l1) {
+- if (ChunkMap.isChunkInRange(k1, l1, i1, j1, this.viewDistance)) {
+- flag5 = true;
+- flag6 = false;
+- this.updateChunkTracking(player, new ChunkPos(k1, l1), new MutableObject(), true, false);
+- }
+- }
+- }
+-
+- for (k1 = i - this.viewDistance - 1; k1 <= i + this.viewDistance + 1; ++k1) {
+- for (l1 = j - this.viewDistance - 1; l1 <= j + this.viewDistance + 1; ++l1) {
+- if (ChunkMap.isChunkInRange(k1, l1, i, j, this.viewDistance)) {
+- flag5 = false;
+- flag6 = true;
+- this.updateChunkTracking(player, new ChunkPos(k1, l1), new MutableObject(), false, true);
+- }
+- }
+- }
+- }
++ // Paper - replaced by PlayerChunkLoader
this.updateMaps(player); // Paper - distance maps
+ this.playerChunkManager.updatePlayer(player); // Paper - respond to movement immediately
}
-@@ -1912,7 +1820,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); // Paper - replace player chunk loader system
+ @Override
+ public List getPlayers(ChunkPos chunkPos, boolean onlyOnWatchDistanceEdge) {
+- Set set = this.playerMap.getPlayers(chunkPos.toLong());
+- Builder builder = ImmutableList.builder();
+- Iterator iterator = set.iterator();
++ // 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.
++ List ret = new java.util.ArrayList<>(4);
- if (inRange == null) {
- return Stream.empty();
-@@ -1928,8 +1836,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; // Paper - replace player chunk management
-+ int viewDistance = this.playerChunkManager.broadcastMap.getLastViewDistance(player); // Paper - replace player chunk loader system
-+ long lastPosition = this.playerChunkManager.broadcastMap.getLastCoordinate(player); // Paper - replace player chunk loader system
+- while (iterator.hasNext()) {
+- ServerPlayer entityplayer = (ServerPlayer) iterator.next();
+- SectionPos sectionposition = entityplayer.getLastSectionPos();
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet players = this.playerChunkManager.broadcastMap.getObjectsInRange(chunkPos);
++ if (players == null) {
++ return ret;
++ }
- int distX = Math.abs(MCUtil.getCoordinateX(lastPosition) - chunkPos.x);
- int distZ = Math.abs(MCUtil.getCoordinateZ(lastPosition) - chunkPos.z);
-@@ -1944,6 +1853,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
- continue;
- }
- ServerPlayer player = (ServerPlayer)temp;
-+ if (!this.playerChunkManager.isChunkSent(player, chunkPos.x, chunkPos.z)) continue; // Paper - replace player chunk management
- players.add(player);
+- if (onlyOnWatchDistanceEdge && ChunkMap.isChunkOnRangeBorder(chunkPos.x, chunkPos.z, sectionposition.x(), sectionposition.z(), this.viewDistance) || !onlyOnWatchDistanceEdge && ChunkMap.isChunkInRange(chunkPos.x, chunkPos.z, sectionposition.x(), sectionposition.z(), this.viewDistance)) {
+- builder.add(entityplayer);
++ Object[] backingSet = players.getBackingSet();
++ for (int i = 0, len = backingSet.length; i < len; ++i) {
++ Object temp = backingSet[i];
++ if (!(temp instanceof ServerPlayer)) {
++ continue;
++ }
++ ServerPlayer player = (ServerPlayer)temp;
++ if (!this.playerChunkManager.isChunkSent(player, chunkPos.x, chunkPos.z, onlyOnWatchDistanceEdge)) {
++ continue;
}
++ ret.add(player);
}
-@@ -2357,7 +2267,7 @@ Sections go from 0..16. Now whenever a section is not empty, it can potentially
- double vec3d_dy = player.getY() - this.entity.getY();
+
+- return builder.build();
++ return ret;
++ // Paper end - per player view distance
+ }
+
+ public void addEntity(Entity entity) {
+@@ -2335,7 +2293,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ double vec3d_dx = player.getX() - this.entity.getX();
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); // Paper - 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
+- double d0 = (double) Math.min(this.getEffectiveRange(), (ChunkMap.this.viewDistance - 1) * 16);
++ double d0 = (double) Math.min(this.getEffectiveRange(), io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player) * 16); // Paper - per player view distance
+ double d1 = vec3d_dx * vec3d_dx + vec3d_dz * vec3d_dz; // Paper
+ double d2 = d0 * d0;
+ boolean flag = d1 <= d2 && this.entity.broadcastToPlayer(player);
diff --git a/src/main/java/net/minecraft/server/level/DistanceManager.java b/src/main/java/net/minecraft/server/level/DistanceManager.java
-index 1cc4e0a1f3d8235ef88b48e01ca8b78a263d2676..0b34536cdffab31a717b613042d7098109846586 100644
+index 7c66e4ed02c80521196b4ca797477dd9573752d8..fd379155a67794288f7cdae3250767bc3615d421 100644
--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
-@@ -46,7 +46,7 @@ public abstract class DistanceManager {
+@@ -49,8 +49,8 @@ public abstract class DistanceManager {
public final Long2ObjectOpenHashMap>> tickets = new Long2ObjectOpenHashMap();
private final DistanceManager.ChunkTicketTracker ticketTracker = new DistanceManager.ChunkTicketTracker();
public static final int MOB_SPAWN_RANGE = 8; // private final ChunkMapDistance.b f = new ChunkMapDistance.b(8); // Paper - no longer used
+- private final TickingTracker tickingTicketsTracker = new TickingTracker();
- private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33);
++ //private final TickingTracker tickingTicketsTracker = new TickingTracker(); // Paper - no longer used
+ //private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); // Paper - no longer used
// Paper start use a queue, but still keep unique requirement
public final java.util.Queue pendingChunkUpdates = new java.util.ArrayDeque() {
@Override
-@@ -113,7 +113,7 @@ public abstract class DistanceManager {
- public boolean runAllUpdates(ChunkMap playerchunkmap) {
+@@ -91,7 +91,7 @@ public abstract class DistanceManager {
+ java.util.function.Predicate> removeIf = (ticket) -> {
+ final boolean ret = ticket.timedOut(ticketCounter);
+ if (ret) {
+- this.tickingTicketsTracker.removeTicket(currChunk[0], ticket);
++ //this.tickingTicketsTracker.removeTicket(currChunk[0], ticket); // Paper - no longer used
+ }
+ return ret;
+ };
+@@ -111,7 +111,7 @@ public abstract class DistanceManager {
+ if (ticket.timedOut(this.ticketTickCounter)) {
+ iterator.remove();
+ flag = true;
+- this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket);
++ //this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket); // Paper - no longer used
+ }
+ }
+
+@@ -141,9 +141,9 @@ public abstract class DistanceManager {
+
+ public boolean runAllUpdates(ChunkMap chunkStorage) {
//this.f.a(); // Paper - no longer used
+- this.tickingTicketsTracker.runAllUpdates();
++ //this.tickingTicketsTracker.runAllUpdates(); // Paper - no longer used
org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper
- this.playerTicketManager.runAllUpdates();
+ //this.playerTicketManager.runAllUpdates(); // Paper - no longer used
int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE);
boolean flag = i != 0;
-@@ -313,7 +313,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; // Paper - replace old loader system
+@@ -282,7 +282,7 @@ public abstract class DistanceManager {
+ long j = pos.toLong();
- if (needsTicket) {
- Ticket> ticket = new Ticket<>(TicketType.PLAYER, 33, coords);
-@@ -411,7 +411,7 @@ public abstract class DistanceManager {
+ this.addTicket(j, ticket);
+- this.tickingTicketsTracker.addTicket(j, ticket);
++ //this.tickingTicketsTracker.addTicket(j, ticket); // Paper - no longer used
+ }
+
+ public void removeRegionTicket(TicketType type, ChunkPos pos, int radius, T argument) {
+@@ -290,7 +290,7 @@ public abstract class DistanceManager {
+ long j = pos.toLong();
+
+ this.removeTicket(j, ticket);
+- this.tickingTicketsTracker.removeTicket(j, ticket);
++ //this.tickingTicketsTracker.removeTicket(j, ticket); // Paper - no longer used
+ }
+
+ private SortedArraySet> getTickets(long position) {
+@@ -411,10 +411,10 @@ public abstract class DistanceManager {
+
+ if (forced) {
+ this.addTicket(i, ticket);
+- this.tickingTicketsTracker.addTicket(i, ticket);
++ //this.tickingTicketsTracker.addTicket(i, ticket); // Paper - no longer used
+ } else {
+ this.removeTicket(i, ticket);
+- this.tickingTicketsTracker.removeTicket(i, ticket);
++ //this.tickingTicketsTracker.removeTicket(i, ticket); // Paper - no longer used
+ }
+
+ }
+@@ -427,8 +427,8 @@ 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.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair);
+ //this.playerTicketManager.update(i, 0, true); // Paper - no longer used
++ //this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used
}
public void removePlayer(SectionPos pos, ServerPlayer player) {
-@@ -423,7 +423,7 @@ public abstract class DistanceManager {
+@@ -441,8 +441,8 @@ 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.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair);
+ //this.playerTicketManager.update(i, Integer.MAX_VALUE, false); // Paper - no longer used
++ //this.tickingTicketsTracker.removeTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used
}
}
-@@ -442,7 +442,7 @@ public abstract class DistanceManager {
+@@ -452,11 +452,17 @@ 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"); // Paper - no longer relevant
+ public boolean inEntityTickingRange(long chunkPos) {
+- return this.tickingTicketsTracker.getLevel(chunkPos) < 32;
++ // Paper start - replace player chunk loader system
++ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(chunkPos);
++ return holder != null && holder.isEntityTickingReady();
++ // Paper end - replace player chunk loader system
}
- public int getNaturalSpawnChunkCount() {
-@@ -563,6 +563,7 @@ public abstract class DistanceManager {
+ public boolean inBlockTickingRange(long chunkPos) {
+- return this.tickingTicketsTracker.getLevel(chunkPos) < 33;
++ // Paper start - replace player chunk loader system
++ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(chunkPos);
++ return holder != null && holder.isTickingReady();
++ // Paper end - replace player chunk loader system
+ }
+
+ protected String getTicketDebugString(long pos) {
+@@ -466,20 +472,16 @@ public abstract class DistanceManager {
+ }
+
+ protected void updatePlayerTickets(int viewDistance) {
+- this.playerTicketManager.updateViewDistance(viewDistance);
++ this.chunkMap.playerChunkManager.setTargetNoTickViewDistance(viewDistance); // Paper - route to player chunk manager
+ }
+
+ public void updateSimulationDistance(int simulationDistance) {
+- if (simulationDistance != this.simulationDistance) {
+- this.simulationDistance = simulationDistance;
+- this.tickingTicketsTracker.replacePlayerTicketsLevel(this.getPlayerTicketLevel());
+- }
+-
++ this.chunkMap.playerChunkManager.setTargetTickViewDistance(simulationDistance); // Paper - route to player chunk manager
+ }
+
+ // Paper start
+ public int getSimulationDistance() {
+- return this.simulationDistance;
++ return this.chunkMap.playerChunkManager.getTargetTickViewDistance(); // Paper - route to player chunk manager
+ }
+ // Paper end
+
+@@ -536,10 +538,7 @@ public abstract class DistanceManager {
+
+ }
+
+- @VisibleForTesting
+- TickingTracker tickingTracker() {
+- return this.tickingTicketsTracker;
+- }
++ // Paper - replace player chunk loader
+
+ // CraftBukkit start
+ public void removeAllTicketsFor(TicketType ticketType, int ticketLevel, T ticketIdentifier) {
+@@ -606,6 +605,7 @@ public abstract class DistanceManager {
}
}
@@ -1524,17 +1788,39 @@ index 1cc4e0a1f3d8235ef88b48e01ca8b78a263d2676..0b34536cdffab31a717b613042d70981
private class FixedPlayerDistanceChunkTracker extends ChunkTracker {
protected final Long2ByteMap chunks = new Long2ByteOpenHashMap();
-@@ -858,4 +859,5 @@ public abstract class DistanceManager {
+@@ -779,4 +779,5 @@ public abstract class DistanceManager {
+ return distance <= this.viewDistance - 2;
}
- // Paper end
}
+ */ // Paper - 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 39840403da99252c5d634e99e1da19f6066dee7c..6db8c95693772296d947fbc051b97937fd184685 100644
+index 98be0ea366732695f76bc5b1a78e0a36060515bd..e20fc528b85a8278bffab32845daac2208836748 100644
--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
-@@ -929,6 +929,7 @@ public class ServerChunkCache extends ChunkSource {
+@@ -855,17 +855,10 @@ public class ServerChunkCache extends ChunkSource {
+ // Paper end
+
+ public boolean isPositionTicking(long pos) {
+- ChunkHolder playerchunk = this.getVisibleChunkIfPresent(pos);
+-
+- if (playerchunk == null) {
+- return false;
+- } else if (!this.level.shouldTickBlocksAt(pos)) {
+- return false;
+- } else {
+- Either either = (Either) playerchunk.getTickingChunkFuture().getNow(null); // CraftBukkit - decompile error
+-
+- return either != null && either.left().isPresent();
+- }
++ // Paper start - replace player chunk loader system
++ ChunkHolder holder = this.chunkMap.getVisibleChunkIfPresent(pos);
++ return holder != null && holder.isTickingReady();
++ // Paper end - replace player chunk loader system
+ }
+
+ public void save(boolean flush) {
+@@ -921,6 +914,7 @@ public class ServerChunkCache extends ChunkSource {
this.level.timings.doChunkMap.stopTiming(); // Spigot
this.level.getProfiler().popPush("chunks");
this.level.timings.chunks.startTiming(); // Paper - timings
@@ -1542,7 +1828,23 @@ index 39840403da99252c5d634e99e1da19f6066dee7c..6db8c95693772296d947fbc051b97937
this.tickChunks();
this.level.timings.chunks.stopTiming(); // Paper - timings
this.level.timings.doChunkUnload.startTiming(); // Spigot
-@@ -1219,6 +1220,7 @@ public class ServerChunkCache extends ChunkSource {
+@@ -1032,13 +1026,13 @@ public class ServerChunkCache extends ChunkSource {
+ // Paper end - optimise chunk tick iteration
+ ChunkPos chunkcoordintpair = chunk1.getPos();
+
+- if (this.level.isPositionEntityTicking(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning
++ if ((true || this.level.isPositionEntityTicking(chunkcoordintpair)) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning // Paper - replace player chunk loader system
+ chunk1.incrementInhabitedTime(j);
+ if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning & optimise chunk tick iteration
+ NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
+ }
+
+- if (this.level.shouldTickBlocksAt(chunkcoordintpair.toLong())) {
++ if ((true || this.level.shouldTickBlocksAt(chunkcoordintpair.toLong()))) { // Paper - replace player chunk loader system
+ this.level.tickChunk(chunk1, k);
+ if ((chunksTicked++ & 1) == 0) net.minecraft.server.MinecraftServer.getServer().executeMidTickTasks(); // Paper
+ }
+@@ -1261,6 +1255,7 @@ public class ServerChunkCache extends ChunkSource {
public boolean pollTask() {
try {
boolean execChunkTask = com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ServerChunkCache.this.level.asyncChunkTaskManager.pollNextChunkTask(); // Paper
@@ -1550,52 +1852,123 @@ index 39840403da99252c5d634e99e1da19f6066dee7c..6db8c95693772296d947fbc051b97937
if (ServerChunkCache.this.runDistanceManagerUpdates()) {
return true;
} else {
+diff --git a/src/main/java/net/minecraft/server/level/ServerLevel.java b/src/main/java/net/minecraft/server/level/ServerLevel.java
+index e9de8f8ac62dab370c28b4ef6a4b61c8d3ebbb15..4ebdeca14b4b4378bb433d68d5f60da99ca95b82 100644
+--- a/src/main/java/net/minecraft/server/level/ServerLevel.java
++++ b/src/main/java/net/minecraft/server/level/ServerLevel.java
+@@ -655,7 +655,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
+ gameprofilerfiller.push("checkDespawn");
+ entity.checkDespawn();
+ gameprofilerfiller.pop();
+- if (this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) {
++ if (true || this.chunkSource.chunkMap.getDistanceManager().inEntityTickingRange(entity.chunkPosition().toLong())) { // Paper - now always true if in the ticking list
+ Entity entity1 = entity.getVehicle();
+
+ if (entity1 != null) {
+@@ -686,7 +686,10 @@ public class ServerLevel extends Level implements WorldGenLevel {
+
+ @Override
+ public boolean shouldTickBlocksAt(long chunkPos) {
+- return this.chunkSource.chunkMap.getDistanceManager().inBlockTickingRange(chunkPos);
++ // Paper start - replace player chunk loader system
++ ChunkHolder holder = this.chunkSource.chunkMap.getVisibleChunkIfPresent(chunkPos);
++ return holder != null && holder.isTickingReady();
++ // Paper end - replace player chunk loader system
+ }
+
+ protected void tickTime() {
+@@ -2367,7 +2370,7 @@ public class ServerLevel extends Level implements WorldGenLevel {
+ private boolean isPositionTickingWithEntitiesLoaded(long chunkPos) {
+ // Paper start - optimize is ticking ready type functions
+ ChunkHolder chunkHolder = this.chunkSource.chunkMap.getVisibleChunkIfPresent(chunkPos);
+- return chunkHolder != null && this.chunkSource.isPositionTicking(chunkPos) && chunkHolder.isTickingReady() && this.areEntitiesLoaded(chunkPos);
++ return chunkHolder != null && chunkHolder.isTickingReady() && this.areEntitiesLoaded(chunkPos); // Paper - no longer need to check with chunk source
+ // 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 d0a6da33bb9152b6fafe6b3a788817d523c1fe49..6614954dd30efdd6a53d561fcb3cbfba8b168805 100644
+index 7b23535a680d2a8534dcb8dd87770f66fb982c13..470dbed47830e2f580c090bc762a7be5cf389ce5 100644
--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
-@@ -261,7 +261,7 @@ public class ServerPlayer extends Player {
+@@ -168,7 +168,7 @@ import org.bukkit.inventory.MainHand;
- 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 // Paper - 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 class ServerPlayer extends Player {
- public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile) {
+- public final int getViewDistance() { return this.getLevel().getChunkSource().chunkMap.viewDistance - 1; } // Paper - placeholder
++ public final int getViewDistance() { throw new UnsupportedOperationException("Use PlayerChunkLoader"); } // Paper - placeholder
+
+ private static final Logger LOGGER = LogManager.getLogger();
+ public long lastSave = MinecraftServer.currentTick; // Paper
diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java
-index 9b6770a41d272eea4b0a1d2076c936af3eaf5c2c..99aa8f2ba2f10578f37de621d1a5a8e222cd70b1 100644
+index cd08f9b16c065be8f0eacaeba51d3e72d332daf9..7a0c1ba40156df69bbbf36d96bed0950130d2351 100644
--- a/src/main/java/net/minecraft/server/players/PlayerList.java
+++ b/src/main/java/net/minecraft/server/players/PlayerList.java
-@@ -267,7 +267,7 @@ public abstract class PlayerList {
+@@ -271,7 +271,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 // Paper - replace old player chunk management
+- playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), this.server.levelKeys(), this.registryHolder, worldserver1.dimensionType(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), this.getMaxPlayers(), worldserver1.spigotConfig.viewDistance, worldserver1.spigotConfig.simulationDistance, flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat()));
++ playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), this.server.levelKeys(), this.registryHolder, worldserver1.dimensionType(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), this.getMaxPlayers(), worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance(), worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance(), flag1, !flag, worldserver1.isDebug(), worldserver1.isFlat())); // Paper - 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()));
-@@ -941,7 +941,7 @@ public abstract class PlayerList {
+@@ -942,8 +942,8 @@ public abstract class PlayerList {
// 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// Paper - replace old player chunk management
- entityplayer1.setLevel(worldserver1);
+- entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.spigotConfig.viewDistance)); // Spigot
+- entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.spigotConfig.simulationDistance)); // Spigot
++ entityplayer1.connection.send(new ClientboundSetChunkCacheRadiusPacket(worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance())); // Spigot // Paper - replace old player chunk management
++ entityplayer1.connection.send(new ClientboundSetSimulationDistancePacket(worldserver1.getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance())); // Spigot // Paper - replace old player chunk management
+ entityplayer1.spawnIn(worldserver1);
entityplayer1.unsetRemoved();
entityplayer1.connection.teleport(new Location(worldserver1.getWorld(), entityplayer1.getX(), entityplayer1.getY(), entityplayer1.getZ(), entityplayer1.getYRot(), entityplayer1.getXRot()));
-@@ -1216,7 +1216,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); // Paper - replace old player chunk management
- if (nearbyPlayers == null) {
- return;
- }
+@@ -1453,7 +1453,7 @@ public abstract class PlayerList {
+
+ public void setViewDistance(int viewDistance) {
+ this.viewDistance = viewDistance;
+- this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance));
++ //this.broadcastAll(new ClientboundSetChunkCacheRadiusPacket(viewDistance)); // Paper - move into setViewDistance
+ Iterator iterator = this.server.getAllLevels().iterator();
+
+ while (iterator.hasNext()) {
+@@ -1468,7 +1468,7 @@ public abstract class PlayerList {
+
+ public void setSimulationDistance(int simulationDistance) {
+ this.simulationDistance = simulationDistance;
+- this.broadcastAll(new ClientboundSetSimulationDistancePacket(simulationDistance));
++ //this.broadcastAll(new ClientboundSetSimulationDistancePacket(simulationDistance)); // Paper - handled by playerchunkloader
+ Iterator iterator = this.server.getAllLevels().iterator();
+
+ while (iterator.hasNext()) {
+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 e17a3afa41fd628d2c4a3637ae19418e258a99b8..b74cdaecb361851b0662002c8ec8f196ab1275bd 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
+@@ -647,7 +647,7 @@ public class EnderDragon extends Mob implements Enemy {
+ // this.world.b(1028, this.getChunkCoordinates(), 0);
+ //int viewDistance = ((WorldServer) this.world).getServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API
+ for (net.minecraft.server.level.ServerPlayer player : (List) ((ServerLevel)level).players()) {
+- final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch
++ final int viewDistance = io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player); // Paper - route to player chunk loader
+ double deltaX = this.getX() - player.getX();
+ double deltaZ = this.getZ() - player.getZ();
+ double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
+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 ede0ced64d74d71547d1b8bb6853c5aacc1b486a..c0ee9915c971482e765d91b4f85d65c3a1f526eb 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
+@@ -279,7 +279,7 @@ public class WitherBoss extends Monster implements PowerableMob, RangedAttackMob
+ // this.world.globalLevelEvent(1023, new BlockPosition(this), 0);
+ //int viewDistance = ((ServerLevel) this.level).getCraftServer().getViewDistance() * 16; // Paper - updated to use worlds actual view distance incase we have to uncomment this due to removal of player view distance API
+ for (ServerPlayer player : (List)this.level.players()) { // Paper
+- final int viewDistance = player.getViewDistance(); // TODO apply view distance api patch
++ final int viewDistance = io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player); // Paper - route to player chunk loader
+ double deltaX = this.getX() - player.getX();
+ double deltaZ = this.getZ() - player.getZ();
+ double distanceSquared = deltaX * deltaX + deltaZ * deltaZ;
diff --git a/src/main/java/net/minecraft/world/item/EnderEyeItem.java b/src/main/java/net/minecraft/world/item/EnderEyeItem.java
-index 7ccfe737fdf7f07b731ea0ff82e897564350705c..fe709bee0e657ff9bfc6deb6d4bd8ce4eccfcf06 100644
+index 46ca1a11930c57813fbcbab7de7dd2fd47241f64..2429bdc5fc5150dcadbedf6c33810889c2444f54 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 {
@@ -1606,87 +1979,75 @@ index 7ccfe737fdf7f07b731ea0ff82e897564350705c..fe709bee0e657ff9bfc6deb6d4bd8ce4
+ //int viewDistance = world.getCraftServer().getViewDistance() * 16; // Paper - apply view distance patch
BlockPos soundPos = blockposition1.offset(1, 0, 1);
for (ServerPlayer player : world.getServer().getPlayerList().players) {
-+ final int viewDistance = player.getBukkitEntity().getViewDistance(); // Paper - apply view distance patch
++ final int viewDistance = io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player); // Paper - 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/Level.java b/src/main/java/net/minecraft/world/level/Level.java
-index 3a6f79233cec7aee87be20787b6deae4b313f0ac..41be326b8ea5b7419dc09578e48c7c7f378e22cc 100644
+index 7dda99a5464816f1488fb110da587f12d751b3fb..87c8c59b9d47b6c292a92e97471c558c03453cfb 100644
--- a/src/main/java/net/minecraft/world/level/Level.java
+++ b/src/main/java/net/minecraft/world/level/Level.java
-@@ -588,7 +588,7 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+@@ -655,6 +655,11 @@ public abstract class Level implements LevelAccessor, AutoCloseable {
+
+ if ((i & 2) != 0 && (!this.isClientSide || (i & 4) == 0) && (this.isClientSide || chunk == null || (chunk.getFullStatus() != null && chunk.getFullStatus().isOrAfter(ChunkHolder.FullChunkStatus.TICKING)))) { // allow chunk to be null here as chunk.isReady() is false when we send our notification during block placement
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)) {
++ // 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.playerChunkManager.broadcastMap.getObjectsInRange(MCUtil.getCoordinateKey(blockposition)) != null)) { // Paper - replace old player chunk management
- ((ServerLevel)this).getChunkSource().blockChanged(blockposition);
- // Paper end - per player view distance
++ ((ServerLevel)this).getChunkSource().blockChanged(blockposition);
++ // Paper end - per player view distance
}
+
+ if ((i & 1) != 0) {
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 f0c43f9f636a5dd1f0dfbae604dfa1f4ff9ebd4e..9e75ee7f43722f05dd5df4ef00f7d3e271f4c6e5 100644
+index 55c0e9655ded14e25b0f284ad0c1f99eb5d0b192..b47c4c9e9b82030cd82d72fe90d7c8bf558d6b28 100644
--- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java
-@@ -293,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) {
+@@ -191,6 +191,43 @@ public class LevelChunk extends ChunkAccess {
+
+ protected void onNeighbourChange(final long bitsetBefore, final long bitsetAfter) {
+
++ // Paper start - no-tick view distance
++ ServerChunkCache chunkProviderServer = ((ServerLevel)this.level).getChunkSource();
++ net.minecraft.server.level.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.playerChunkManager.tickMap.getObjectsInRange(this.coordinateKey) != null) { // Paper - 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) {
++ // now we're ready for entity ticking
++ chunkProviderServer.mainThreadProcessor.execute(() -> {
++ // double check that this condition still holds.
+ if (LevelChunk.this.areNeighboursLoaded(2) && chunkMap.playerChunkManager.tickMap.getObjectsInRange(LevelChunk.this.coordinateKey) != null) { // Paper - replace old player chunk loading system
+ chunkMap.playerChunkManager.onChunkPlayerTickReady(this.chunkPos.x, this.chunkPos.z); // Paper - 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
- }
- });
-@@ -306,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);
-- }
-- })));
-- }
++ chunkProviderServer.addTicketAtLevel(net.minecraft.server.level.TicketType.PLAYER, LevelChunk.this.chunkPos, 31, LevelChunk.this.chunkPos); // 31 -> entity ticking, TODO check on update
++ }
++ });
++ }
++ }
++
++ // this code handles the chunk sending
++ if (!areNeighboursLoaded(bitsetBefore, 1) && areNeighboursLoaded(bitsetAfter, 1)) {
+ // Paper 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);
-+ });
++ if (chunkMap.playerChunkManager.isChunkNearPlayers(this.chunkPos.x, this.chunkPos.z)) {
++ // the post processing is expensive, so we don't want to run it unless we're actually near
++ // a player.
++ 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);
++ });
++ }
+ // Paper end - replace old player chunk loading system
- }
- // Paper end - no-tick view distance
++ }
++ // Paper end - no-tick view distance
}
-@@ -871,6 +859,7 @@ public class LevelChunk implements ChunkAccess {
+
+ public final boolean isAnyNeighborsLoaded() {
+@@ -805,6 +842,7 @@ public class LevelChunk extends ChunkAccess {
// Paper end - neighbour cache
org.bukkit.Server server = this.level.getCraftServer();
this.level.getChunkSource().addLoadedChunk(this); // Paper
@@ -1694,61 +2055,108 @@ index f0c43f9f636a5dd1f0dfbae604dfa1f4ff9ebd4e..9e75ee7f43722f05dd5df4ef00f7d3e2
if (server != null) {
/*
* If it's a new world, the first few chunks are generated inside
-diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-index b4cf0d44bda13625cfa8264043a49c1a0daf1054..e5e23c907d49ee64218f3302e2a2323d6937a8a1 100644
---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
-@@ -2067,14 +2067,14 @@ public class CraftWorld extends CraftRegionAccessor 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) { // Paper - replace old player chunk management
- chunkMap.setViewDistance(viewDistance);
- }
+@@ -929,7 +967,10 @@ public class LevelChunk extends ChunkAccess {
+ });
}
++ public boolean isPostProcessingDone; // Paper - replace chunk loader system
++
+ public void postProcessGeneration() {
++ try { // Paper - replace chunk loader system
+ ChunkPos chunkcoordintpair = this.getPos();
+
+ for (int i = 0; i < this.postProcessing.length; ++i) {
+@@ -967,6 +1008,11 @@ public class LevelChunk extends ChunkAccess {
+
+ this.pendingBlockEntities.clear();
+ this.upgradeData.upgrade(this);
++ } finally { // Paper start - replace chunk loader system
++ this.isPostProcessingDone = true;
++ this.level.getChunkSource().chunkMap.playerChunkManager.onChunkPostProcessing(this.chunkPos.x, this.chunkPos.z);
++ }
++ // Paper end - replace chunk loader system
+ }
+
+ @Nullable
+diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+index 23bd74836e8396720747540829c7b8e27cfb00bd..2c3ce2065812de227c34506edddb439da9a07ba1 100644
+--- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
++++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java
+@@ -2215,37 +2215,55 @@ public class CraftWorld extends CraftRegionAccessor implements World {
+ // Spigot start
@Override
- public int getNoTickViewDistance() {
-- return getHandle().getChunkSource().chunkMap.getEffectiveNoTickViewDistance();
+ public int getViewDistance() {
+- return world.spigotConfig.viewDistance;
+ return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance(); // Paper - replace old player chunk management
}
@Override
-@@ -2083,11 +2083,22 @@ public class CraftWorld extends CraftRegionAccessor 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) { // Paper - replace old player chunk management
- chunkMap.setNoTickViewDistance(viewDistance);
- }
+ public int getSimulationDistance() {
+- return world.spigotConfig.simulationDistance;
++ return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance(); // Paper - replace old player chunk management
}
- // Paper end - per player view distance
-+ // Paper start - add view distances
-+ @Override
-+ public int getSendViewDistance() {
-+ return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance();
+
+ @Override
+ public void setViewDistance(int viewDistance) {
+- throw new UnsupportedOperationException(); //TODO
++ // Paper start - replace old player chunk management
++ if (viewDistance < 2 || viewDistance > 32) {
++ throw new IllegalArgumentException("View distance " + viewDistance + " is out of range of [2, 32]");
++ }
++ net.minecraft.server.level.ChunkMap chunkMap = getHandle().getChunkSource().chunkMap;
++ chunkMap.setViewDistance(viewDistance);
++ // Paper end - replace old player chunk management
+ }
+
++ // Paper start - replace old player chunk management
+ @Override
-+ public void setSendViewDistance(int viewDistance) {
-+ getHandle().getChunkSource().chunkMap.playerChunkManager.setTargetSendDistance(viewDistance);
-+ }
-+ // Paper end - add view distances
++ public void setSimulationDistance(int simulationDistance) {
++ // Paper start - replace old player chunk management
++ if (simulationDistance < 2 || simulationDistance > 32) {
++ throw new IllegalArgumentException("Simulation distance " + simulationDistance + " is out of range of [2, 32]");
++ }
++ net.minecraft.server.level.ChunkMap chunkMap = getHandle().getChunkSource().chunkMap;
++ chunkMap.setTickViewDistance(simulationDistance);
+ }
++ // Paper end - replace old player chunk management
+
+ @Override
+ public int getNoTickViewDistance() {
+- throw new UnsupportedOperationException(); //TODO
++ return this.getViewDistance(); // Paper - replace old player chunk management
+ }
+
+ @Override
+ public void setNoTickViewDistance(int viewDistance) {
+- throw new UnsupportedOperationException(); //TODO
++ this.setViewDistance(viewDistance); // Paper - replace old player chunk management
+ }
+
+ @Override
+ public int getSendViewDistance() {
+- throw new UnsupportedOperationException(); //TODO
++ return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetSendDistance(); // Paper - replace old player chunk management
+ }
+
+ @Override
+ public void setSendViewDistance(int viewDistance) {
+- throw new UnsupportedOperationException(); //TODO
++ getHandle().getChunkSource().chunkMap.playerChunkManager.setSendDistance(viewDistance); // Paper - replace old player chunk management
+ }
+ // Spigot end
- // Spigot start
- private final org.bukkit.World.Spigot spigot = new org.bukkit.World.Spigot()
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-index b3ffda6f177e4a45f25772483e935a36b3f5f72a..4ff4143f3a7cd89ef92f4b8882fa3e5addfe0f06 100644
+index d309d9f0b54e2a966ccfbb780359bb8e68d15ee3..ef841a5ea1f634e87e5437faf83dc00efd590106 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java
-@@ -517,15 +517,70 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
+@@ -528,35 +528,80 @@ public class CraftPlayer extends CraftHumanEntity implements Player {
}
}
+ // Paper start - implement view distances
-+ @Override
+ @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
+ public int getSendViewDistance() {
+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap;
+ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
@@ -1756,9 +2164,11 @@ index b3ffda6f177e4a45f25772483e935a36b3f5f72a..4ff4143f3a7cd89ef92f4b8882fa3e5a
+ return chunkMap.playerChunkManager.getTargetSendDistance();
+ }
+ return data.getTargetSendViewDistance();
-+ }
-+
-+ @Override
+ }
+
+ @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
+ public void setSendViewDistance(int viewDistance) {
+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap;
+ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
@@ -1767,20 +2177,36 @@ index b3ffda6f177e4a45f25772483e935a36b3f5f72a..4ff4143f3a7cd89ef92f4b8882fa3e5a
+ }
+
+ data.setTargetSendViewDistance(viewDistance);
-+ }
-+
-+ @Override
-+ public int getNoTickViewDistance() {
+ }
+
+ @Override
+ public int getNoTickViewDistance() {
+- throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
++ return this.getViewDistance();
+ }
+
+ @Override
+ public void setNoTickViewDistance(int viewDistance) {
+- throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
++ this.setViewDistance(viewDistance);
+ }
+
+ @Override
+- public int getSendViewDistance() {
+- throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
++ public int getViewDistance() {
+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap;
+ io.papermc.paper.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) {
+ }
+
+ @Override
+- public void setSendViewDistance(int viewDistance) {
+- throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO
++ public void setViewDistance(int viewDistance) {
+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap;
+ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
+ if (data == null) {
@@ -1790,27 +2216,25 @@ index b3ffda6f177e4a45f25772483e935a36b3f5f72a..4ff4143f3a7cd89ef92f4b8882fa3e5a
+ 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
++ @Override
++ public int getSimulationDistance() {
+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap;
+ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
+ if (data == null) {
-+ return chunkMap.playerChunkManager.getTargetViewDistance();
++ return chunkMap.playerChunkManager.getTargetTickViewDistance();
+ }
+ 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
++ }
++
++ @Override
++ public void setSimulationDistance(int simulationDistance) {
+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap;
+ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle());
+ if (data == null) {
+ throw new IllegalStateException("Player is not attached to world");
+ }
+
-+ data.setTargetTickViewDistance(viewDistance);
++ data.setTargetTickViewDistance(simulationDistance);
}
+ // Paper end - implement view distances
diff --git a/patches/server/0869-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch b/patches/server/0869-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch
new file mode 100644
index 0000000000..051913ad4e
--- /dev/null
+++ b/patches/server/0869-Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch
@@ -0,0 +1,37 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf
+Date: Tue, 28 Dec 2021 07:19:01 -0800
+Subject: [PATCH] Execute chunk tasks fairly for worlds while waiting for next
+ tick
+
+Currently, only the first world would have had tasks executed.
+This might result in chunks loading far slower in the nether,
+for example.
+
+diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
+index eab93e1e3712c0a01cac187bf5944818c813d665..1674deebbeab0995ed7acacf8052e1daf4d2a7bc 100644
+--- a/src/main/java/net/minecraft/server/MinecraftServer.java
++++ b/src/main/java/net/minecraft/server/MinecraftServer.java
+@@ -1392,6 +1392,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop> 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(); // Paper - 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 TickingTracker tickingTicketsTracker = new TickingTracker(); // Paper - no longer used
//private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); // Paper - no longer used
- // Paper start use a queue, but still keep unique requirement
-@@ -77,6 +78,46 @@ public abstract class DistanceManager {
- this.mainThreadExecutor = mainThreadExecutor;
+@@ -82,6 +83,46 @@ public abstract class DistanceManager {
+ this.chunkMap = chunkMap; // Paper
}
+ // Paper start - replace ticket level propagator
@@ -82,22 +82,23 @@ index 0b34536cdffab31a717b613042d7098109846586..24fa3d847f7c0aec52133ffc658cd90a
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);
- })) {
+@@ -116,7 +157,7 @@ public abstract class DistanceManager {
+ }
+
+ if (flag) {
- this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false);
+ this.updateTicketLevel(entry.getLongKey(), getTicketLevelAt(entry.getValue())); // Paper - replace ticket level propagator
}
if (((SortedArraySet) entry.getValue()).isEmpty()) {
-@@ -110,60 +151,93 @@ public abstract class DistanceManager {
+@@ -139,61 +180,94 @@ public abstract class DistanceManager {
@Nullable
protected abstract ChunkHolder updateChunkScheduling(long pos, int level, @Nullable ChunkHolder holder, int k);
+ protected long ticketLevelUpdateCount; // Paper - replace ticket level propagator
- public boolean runAllUpdates(ChunkMap playerchunkmap) {
+ public boolean runAllUpdates(ChunkMap chunkStorage) {
//this.f.a(); // Paper - no longer used
+ //this.tickingTicketsTracker.runAllUpdates(); // Paper - no longer used
org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper
//this.playerTicketManager.runAllUpdates(); // Paper - no longer used
- int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE);
@@ -114,7 +115,7 @@ index 0b34536cdffab31a717b613042d7098109846586..24fa3d847f7c0aec52133ffc658cd90a
- while(!this.pendingChunkUpdates.isEmpty()) {
- ChunkHolder remove = this.pendingChunkUpdates.remove();
- remove.isUpdateQueued = false;
-- remove.updateFutures(playerchunkmap, this.mainThreadExecutor);
+- remove.updateFutures(chunkStorage, this.mainThreadExecutor);
- }
- } finally { this.pollingPendingChunkUpdates = false; } // Paper - Chunk priority
- // Paper end
@@ -142,23 +143,23 @@ index 0b34536cdffab31a717b613042d7098109846586..24fa3d847f7c0aec52133ffc658cd90a
+ // 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);
-+ }
+- ChunkHolder playerchunk = chunkStorage.getUpdatingChunkIfPresent(j);
++ int currentLevel = chunk == null ? ChunkMap.MAX_CHUNK_DISTANCE + 1 : chunk.getTicketLevel();
- if (playerchunk == null) {
- throw new IllegalStateException();
++ if (currentLevel == newLevel) {
++ // nothing to do
++ continue;
++ }
++
++ this.updateChunkScheduling(key, newLevel, chunk, currentLevel);
++ }
++
+ long recursiveCheck = ++this.ticketLevelUpdateCount;
+ while (!this.ticketLevelUpdates.isEmpty()) {
+ long key = this.ticketLevelUpdates.firstLongKey();
@@ -187,7 +188,7 @@ index 0b34536cdffab31a717b613042d7098109846586..24fa3d847f7c0aec52133ffc658cd90a
+ continue;
+ }
+
-+ chunk.updateFutures(playerchunkmap, this.mainThreadExecutor);
++ chunk.updateFutures(chunkStorage, 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
@@ -207,7 +208,7 @@ index 0b34536cdffab31a717b613042d7098109846586..24fa3d847f7c0aec52133ffc658cd90a
+ }
- return flag;
-+ pendingUpdate.updateFutures(playerchunkmap, this.mainThreadExecutor);
++ pendingUpdate.updateFutures(chunkStorage, this.mainThreadExecutor);
+ }
+ } finally {
+ this.pollingPendingChunkUpdates = oldPolling;
@@ -219,7 +220,7 @@ index 0b34536cdffab31a717b613042d7098109846586..24fa3d847f7c0aec52133ffc658cd90a
}
boolean pollingPendingChunkUpdates = false; // Paper - Chunk priority
-@@ -175,7 +249,7 @@ public abstract class DistanceManager {
+@@ -205,7 +279,7 @@ public abstract class DistanceManager {
ticket1.setCreatedTick(this.ticketTickCounter);
if (ticket.getTicketLevel() < j) {
@@ -228,7 +229,7 @@ index 0b34536cdffab31a717b613042d7098109846586..24fa3d847f7c0aec52133ffc658cd90a
}
return ticket == ticket1; // CraftBukkit
-@@ -219,7 +293,7 @@ public abstract class DistanceManager {
+@@ -249,7 +323,7 @@ public abstract class DistanceManager {
// Paper start - Chunk priority
int newLevel = getTicketLevelAt(arraysetsorted);
if (newLevel > oldLevel) {
@@ -237,7 +238,7 @@ index 0b34536cdffab31a717b613042d7098109846586..24fa3d847f7c0aec52133ffc658cd90a
}
// Paper end
return removed; // CraftBukkit
-@@ -507,7 +581,7 @@ public abstract class DistanceManager {
+@@ -549,7 +623,7 @@ public abstract class DistanceManager {
SortedArraySet> tickets = entry.getValue();
if (tickets.remove(target)) {
// copied from removeTicket