aboutsummaryrefslogtreecommitdiffhomepage
path: root/patches/server/0416-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch
diff options
context:
space:
mode:
Diffstat (limited to 'patches/server/0416-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch')
-rw-r--r--patches/server/0416-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch346
1 files changed, 346 insertions, 0 deletions
diff --git a/patches/server/0416-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch b/patches/server/0416-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch
new file mode 100644
index 0000000000..90f095a707
--- /dev/null
+++ b/patches/server/0416-Optimize-anyPlayerCloseEnoughForSpawning-to-use-dist.patch
@@ -0,0 +1,346 @@
+From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
+From: Spottedleaf <[email protected]>
+Date: Tue, 5 May 2020 20:40:53 -0700
+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 b550f302b1bb6ef92987c8c3431b94c3ba3a091d..2add24517d38708a84e7f8ec25fbe9c62309375e 100644
+--- a/src/main/java/net/minecraft/server/level/ChunkHolder.java
++++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java
+@@ -83,16 +83,29 @@ public class ChunkHolder {
+
+ // Paper start
+ public void onChunkAdd() {
+-
++ // Paper start - optimise anyPlayerCloseEnoughForSpawning
++ long key = net.minecraft.server.MCUtil.getCoordinateKey(this.pos);
++ this.playersInMobSpawnRange = this.chunkMap.playerMobSpawnMap.getObjectsInRange(key);
++ this.playersInChunkTickRange = this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(key);
++ // Paper end - optimise anyPlayerCloseEnoughForSpawning
+ }
+
+ public void onChunkRemove() {
+-
++ // Paper start - optimise anyPlayerCloseEnoughForSpawning
++ this.playersInMobSpawnRange = null;
++ this.playersInChunkTickRange = null;
++ // Paper end - optimise anyPlayerCloseEnoughForSpawning
+ }
+ // Paper end
+
+ public final io.papermc.paper.chunk.system.scheduling.NewChunkHolder newChunkHolder; // Paper - rewrite chunk system
+
++ // Paper start - optimise anyPlayerCloseEnoughForSpawning
++ // cached here to avoid a map lookup
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInMobSpawnRange;
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInChunkTickRange;
++ // Paper end - optimise anyPlayerCloseEnoughForSpawning
++
+ public ChunkHolder(ChunkPos pos, LevelHeightAccessor world, LevelLightEngine lightingProvider, ChunkHolder.PlayerProvider playersWatchingChunkProvider, io.papermc.paper.chunk.system.scheduling.NewChunkHolder newChunkHolder) { // Paper - rewrite chunk system
+ this.newChunkHolder = newChunkHolder; // Paper - rewrite chunk system
+ this.chunkToSaveHistory = null;
+diff --git a/src/main/java/net/minecraft/server/level/ChunkMap.java b/src/main/java/net/minecraft/server/level/ChunkMap.java
+index 8dce24ae70057115feff193a1307eb2437e16773..50e9d29b5a0ea6d11a44c41c49d6f52bc464e6e2 100644
+--- a/src/main/java/net/minecraft/server/level/ChunkMap.java
++++ b/src/main/java/net/minecraft/server/level/ChunkMap.java
+@@ -152,12 +152,24 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ // Paper start - distance maps
+ private final com.destroystokyo.paper.util.misc.PooledLinkedHashSets<ServerPlayer> pooledLinkedPlayerHashSets = new com.destroystokyo.paper.util.misc.PooledLinkedHashSets<>();
+ public final io.papermc.paper.chunk.PlayerChunkLoader playerChunkManager = new io.papermc.paper.chunk.PlayerChunkLoader(this, this.pooledLinkedPlayerHashSets); // Paper - replace chunk loader
++ // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
++ // A note about the naming used here:
++ // Previously, mojang used a "spawn range" of 8 for controlling both ticking and
++ // mob spawn range. However, spigot makes the spawn range configurable by
++ // checking if the chunk is in the tick range (8) and the spawn range
++ // obviously this means a spawn range > 8 cannot be implemented
++
++ // these maps are named after spigot's uses
++ 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
+
+ void addPlayerToDistanceMaps(ServerPlayer player) {
+ this.playerChunkManager.addPlayer(player); // Paper - replace chunk loader
+ int chunkX = MCUtil.getChunkCoordinate(player.getX());
+ int chunkZ = MCUtil.getChunkCoordinate(player.getZ());
+ // 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
+ // Paper start - per player mob spawning
+ if (this.playerMobDistanceMap != null) {
+ this.playerMobDistanceMap.add(player, chunkX, chunkZ, net.minecraft.server.ChunkSystem.getTickViewDistance(player));
+@@ -168,6 +180,10 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ void removePlayerFromDistanceMaps(ServerPlayer player) {
+ this.playerChunkManager.removePlayer(player); // Paper - replace chunk loader
+
++ // Paper start - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
++ this.playerMobSpawnMap.remove(player);
++ this.playerChunkTickRangeMap.remove(player);
++ // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
+ // Paper start - per player mob spawning
+ if (this.playerMobDistanceMap != null) {
+ this.playerMobDistanceMap.remove(player);
+@@ -180,6 +196,7 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ int chunkZ = MCUtil.getChunkCoordinate(player.getZ());
+ // Note: players need to be explicitly added to distance maps before they can be updated
+ this.playerChunkManager.updatePlayer(player); // Paper - replace chunk loader
++ this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
+ // Paper start - per player mob spawning
+ if (this.playerMobDistanceMap != null) {
+ this.playerMobDistanceMap.update(player, chunkX, chunkZ, net.minecraft.server.ChunkSystem.getTickViewDistance(player));
+@@ -267,6 +284,38 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ this.regionManagers.add(this.dataRegionManager);
+ // Paper end
+ this.playerMobDistanceMap = this.level.paperConfig().entities.spawning.perPlayerMobSpawns ? new com.destroystokyo.paper.util.misc.PlayerAreaMap(this.pooledLinkedPlayerHashSets) : null; // Paper
++ // 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,
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ));
++ if (playerChunk != null) {
++ playerChunk.playersInChunkTickRange = newState;
++ }
++ },
++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ));
++ if (playerChunk != null) {
++ playerChunk.playersInChunkTickRange = newState;
++ }
++ });
++ this.playerMobSpawnMap = 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<ServerPlayer> newState) -> {
++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ));
++ if (playerChunk != null) {
++ playerChunk.playersInMobSpawnRange = newState;
++ }
++ },
++ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ,
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> newState) -> {
++ ChunkHolder playerChunk = ChunkMap.this.getUpdatingChunkIfPresent(MCUtil.getCoordinateKey(rangeX, rangeZ));
++ if (playerChunk != null) {
++ playerChunk.playersInMobSpawnRange = newState;
++ }
++ });
++ // Paper end - optimise ChunkMap#anyPlayerCloseEnoughForSpawning
+ }
+
+ protected ChunkGenerator generator() {
+@@ -822,43 +871,48 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider
+ return this.anyPlayerCloseEnoughForSpawning(pos, false);
+ }
+
+- boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) {
+- int chunkRange = level.spigotConfig.mobSpawnRange;
+- chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange;
+- chunkRange = (chunkRange > 8) ? 8 : chunkRange;
+-
+- final int finalChunkRange = chunkRange; // Paper for lambda below
+- //double blockRange = (reducedRange) ? Math.pow(chunkRange << 4, 2) : 16384.0D; // Paper - use from event
+- double blockRange = 16384.0D; // Paper
+- // Spigot end
+- long i = chunkcoordintpair.toLong();
++ // Paper start - optimise anyPlayerCloseEnoughForSpawning
++ final boolean anyPlayerCloseEnoughForSpawning(ChunkPos chunkcoordintpair, boolean reducedRange) {
++ return this.anyPlayerCloseEnoughForSpawning(this.getUpdatingChunkIfPresent(chunkcoordintpair.toLong()), chunkcoordintpair, reducedRange);
++ }
+
+- if (!this.distanceManager.hasPlayersNearby(i)) {
++ final boolean anyPlayerCloseEnoughForSpawning(ChunkHolder playerchunk, ChunkPos chunkcoordintpair, boolean reducedRange) {
++ // this function is so hot that removing the map lookup call can have an order of magnitude impact on its performance
++ // tested and confirmed via System.nanoTime()
++ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> playersInRange = reducedRange ? playerchunk.playersInMobSpawnRange : playerchunk.playersInChunkTickRange;
++ if (playersInRange == null) {
+ return false;
+- } else {
+- Iterator iterator = this.playerMap.getPlayers(i).iterator();
+-
+- ServerPlayer entityplayer;
++ }
++ Object[] backingSet = playersInRange.getBackingSet();
+
+- do {
+- if (!iterator.hasNext()) {
+- return false;
++ if (reducedRange) {
++ for (int i = 0, len = backingSet.length; i < len; ++i) {
++ Object raw = backingSet[i];
++ if (!(raw instanceof ServerPlayer player)) {
++ continue;
+ }
+-
+- entityplayer = (ServerPlayer) iterator.next();
+- // Paper start - add PlayerNaturallySpawnCreaturesEvent
+- com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event;
+- blockRange = 16384.0D;
+- if (reducedRange) {
+- event = entityplayer.playerNaturallySpawnedEvent;
+- if (event == null || event.isCancelled()) return false;
+- blockRange = (double) ((event.getSpawnRadius() << 4) * (event.getSpawnRadius() << 4));
++ // don't check spectator and whatnot, already handled by mob spawn map update
++ if (euclideanDistanceSquared(chunkcoordintpair, player) < player.lastEntitySpawnRadiusSquared) {
++ return true; // in range
+ }
+- // Paper end
+- } while (!this.playerIsCloseEnoughForSpawning(entityplayer, chunkcoordintpair, blockRange)); // Spigot
+-
+- return true;
++ }
++ } else {
++ final double range = (DistanceManager.MOB_SPAWN_RANGE * 16) * (DistanceManager.MOB_SPAWN_RANGE * 16);
++ // before spigot, mob spawn range was actually mob spawn range + tick range, but it was split
++ for (int i = 0, len = backingSet.length; i < len; ++i) {
++ Object raw = backingSet[i];
++ if (!(raw instanceof ServerPlayer player)) {
++ continue;
++ }
++ // don't check spectator and whatnot, already handled by mob spawn map update
++ if (euclideanDistanceSquared(chunkcoordintpair, player) < range) {
++ return true; // in range
++ }
++ }
+ }
++ // no players in range
++ return false;
++ // Paper end - optimise anyPlayerCloseEnoughForSpawning
+ }
+
+ public List<ServerPlayer> 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 d3c3db919e9b0507e8543313d9028394e5163673..52cba8f68d274cce106304aef1249a95474d3238 100644
+--- a/src/main/java/net/minecraft/server/level/DistanceManager.java
++++ b/src/main/java/net/minecraft/server/level/DistanceManager.java
+@@ -54,7 +54,7 @@ public abstract class DistanceManager {
+ private static final int BLOCK_TICKING_LEVEL_THRESHOLD = 33;
+ final Long2ObjectMap<ObjectSet<ServerPlayer>> playersPerChunk = new Long2ObjectOpenHashMap();
+ // Paper - rewrite chunk system
+- private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(8);
++ public static final int MOB_SPAWN_RANGE = 8; // private final DistanceManager.FixedPlayerDistanceChunkTracker naturalSpawnChunkCounter = new DistanceManager.FixedPlayerDistanceChunkTracker(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 - rewrite chunk system
+@@ -142,7 +142,7 @@ public abstract class DistanceManager {
+ long i = chunkcoordintpair.toLong();
+
+ // Paper - no longer used
+- this.naturalSpawnChunkCounter.update(i, 0, true);
++ //this.naturalSpawnChunkCounter.update(i, 0, true); // Paper - no longer used
+ //this.playerTicketManager.update(i, 0, true); // Paper - no longer used
+ //this.tickingTicketsTracker.addTicket(TicketType.PLAYER, chunkcoordintpair, this.getPlayerTicketLevel(), chunkcoordintpair); // Paper - no longer used
+ }
+@@ -156,7 +156,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);
+- this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false);
++ // this.naturalSpawnChunkCounter.update(i, Integer.MAX_VALUE, false); // Paper - no longer used
+ //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
+ }
+@@ -198,13 +198,17 @@ public abstract class DistanceManager {
+ }
+
+ public int getNaturalSpawnChunkCount() {
+- this.naturalSpawnChunkCounter.runAllUpdates();
+- return this.naturalSpawnChunkCounter.chunks.size();
++ // Paper start - use distance map to implement
++ // note: this is the spawn chunk count
++ return this.chunkMap.playerChunkTickRangeMap.size();
++ // Paper end - use distance map to implement
+ }
+
+ public boolean hasPlayersNearby(long chunkPos) {
+- this.naturalSpawnChunkCounter.runAllUpdates();
+- return this.naturalSpawnChunkCounter.chunks.containsKey(chunkPos);
++ // Paper start - use distance map to implement
++ // note: this is the is spawn chunk method
++ return this.chunkMap.playerChunkTickRangeMap.getObjectsInRange(chunkPos) != null;
++ // Paper end - use distance map to implement
+ }
+
+ 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 60264feb656861d5a9474fe4285ac69d8d12269e..44766ea7e5dd8f8411b52cf259187d7557cc0c23 100644
+--- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java
++++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java
+@@ -657,6 +657,37 @@ public class ServerChunkCache extends ChunkSource {
+ if (flag) {
+ this.chunkMap.tick();
+ } else {
++ // Paper start - optimize isOutisdeRange
++ ChunkMap playerChunkMap = this.chunkMap;
++ for (ServerPlayer player : this.level.players) {
++ if (!player.affectsSpawning || player.isSpectator()) {
++ playerChunkMap.playerMobSpawnMap.remove(player);
++ continue;
++ }
++
++ int viewDistance = this.chunkMap.getEffectiveViewDistance();
++
++ // copied and modified from isOutisdeRange
++ int chunkRange = level.spigotConfig.mobSpawnRange;
++ chunkRange = (chunkRange > viewDistance) ? (byte)viewDistance : chunkRange;
++ chunkRange = (chunkRange > DistanceManager.MOB_SPAWN_RANGE) ? DistanceManager.MOB_SPAWN_RANGE : chunkRange;
++
++ com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent event = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(player.getBukkitEntity(), (byte)chunkRange);
++ event.callEvent();
++ if (event.isCancelled() || event.getSpawnRadius() < 0 || playerChunkMap.playerChunkTickRangeMap.getLastViewDistance(player) == -1) {
++ playerChunkMap.playerMobSpawnMap.remove(player);
++ continue;
++ }
++
++ int range = Math.min(event.getSpawnRadius(), 32); // limit to max view distance
++ int chunkX = net.minecraft.server.MCUtil.getChunkCoordinate(player.getX());
++ int chunkZ = net.minecraft.server.MCUtil.getChunkCoordinate(player.getZ());
++
++ playerChunkMap.playerMobSpawnMap.addOrUpdate(player, chunkX, chunkZ, range);
++ player.lastEntitySpawnRadiusSquared = (double)((range << 4) * (range << 4)); // used in anyPlayerCloseEnoughForSpawning
++ player.playerNaturallySpawnedEvent = event;
++ }
++ // Paper end - optimize isOutisdeRange
+ LevelData worlddata = this.level.getLevelData();
+ ProfilerFiller gameprofilerfiller = this.level.getProfiler();
+
+@@ -700,15 +731,7 @@ public class ServerChunkCache extends ChunkSource {
+ boolean flag2 = this.level.getGameRules().getBoolean(GameRules.RULE_DOMOBSPAWNING) && !this.level.players().isEmpty(); // CraftBukkit
+
+ Collections.shuffle(list);
+- // Paper start - call player naturally spawn event
+- int chunkRange = level.spigotConfig.mobSpawnRange;
+- chunkRange = (chunkRange > level.spigotConfig.viewDistance) ? (byte) level.spigotConfig.viewDistance : chunkRange;
+- chunkRange = Math.min(chunkRange, 8);
+- for (ServerPlayer entityPlayer : this.level.players()) {
+- entityPlayer.playerNaturallySpawnedEvent = new com.destroystokyo.paper.event.entity.PlayerNaturallySpawnCreaturesEvent(entityPlayer.getBukkitEntity(), (byte) chunkRange);
+- entityPlayer.playerNaturallySpawnedEvent.callEvent();
+- };
+- // Paper end
++ // Paper - moved natural spawn event up
+ Iterator iterator1 = list.iterator();
+
+ while (iterator1.hasNext()) {
+@@ -716,9 +739,9 @@ public class ServerChunkCache extends ChunkSource {
+ LevelChunk chunk1 = chunkproviderserver_a.chunk;
+ ChunkPos chunkcoordintpair = chunk1.getPos();
+
+- if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair)) {
++ if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning
+ chunk1.incrementInhabitedTime(j);
+- if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkcoordintpair, true)) { // Spigot
++ if (flag2 && (this.spawnEnemies || this.spawnFriendlies) && this.level.getWorldBorder().isWithinBounds(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(chunkproviderserver_a.holder, chunkcoordintpair, true)) { // Spigot // Paper - optimise anyPlayerCloseEnoughForSpawning
+ NaturalSpawner.spawnForChunk(this.level, chunk1, spawnercreature_d, this.spawnFriendlies, this.spawnEnemies, flag1);
+ }
+
+diff --git a/src/main/java/net/minecraft/server/level/ServerPlayer.java b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+index fdc1d1d5840ee7f12e4a72656698924c51fea05c..0faa0d73907e6ef1285eba4cf7d692a5f8c3a0e3 100644
+--- a/src/main/java/net/minecraft/server/level/ServerPlayer.java
++++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java
+@@ -263,6 +263,7 @@ public class ServerPlayer extends Player {
+ public PlayerNaturallySpawnCreaturesEvent playerNaturallySpawnedEvent; // Paper
+
+ public boolean isRealPlayer; // Paper
++ public double lastEntitySpawnRadiusSquared; // Paper - optimise isOutsideRange, this field is in blocks
+ public final com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<ServerPlayer> cachedSingleHashSet; // Paper
+
+ public ServerPlayer(MinecraftServer server, ServerLevel world, GameProfile profile, @Nullable ProfilePublicKey publicKey) {