diff --git a/Spigot-Server-Patches/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch b/Spigot-Server-Patches/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch index a10b773bce..377c3cc13d 100644 --- a/Spigot-Server-Patches/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch +++ b/Spigot-Server-Patches/Implement-Chunk-Priority-Urgency-System-for-Chunks.patch @@ -170,6 +170,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // Paper start + public static final int PRIORITY_TICKET_LEVEL = PlayerChunkMap.GOLDEN_TICKET; + public static final int URGENT_PRIORITY = 29; ++ public boolean delayDistanceManagerTick = false; + public boolean markUrgent(ChunkCoordIntPair coords) { + return addPriorityTicket(coords, TicketType.URGENT, URGENT_PRIORITY); + } @@ -178,10 +179,34 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return addPriorityTicket(coords, TicketType.PRIORITY, priority); + } + ++ public void markAreaHighPriority(ChunkCoordIntPair center, int priority, int radius) { ++ delayDistanceManagerTick = true; ++ MCUtil.getSpiralOutChunks(center.asPosition(), radius).forEach(coords -> { ++ addPriorityTicket(coords, TicketType.PRIORITY, priority); ++ }); ++ delayDistanceManagerTick = false; ++ chunkMap.world.getChunkProvider().tickDistanceManager(); ++ } ++ ++ public void clearAreaPriorityTickets(ChunkCoordIntPair center, int radius) { ++ delayDistanceManagerTick = true; ++ MCUtil.getSpiralOutChunks(center.asPosition(), radius).forEach(coords -> { ++ this.removeTicket(coords.pair(), new Ticket<ChunkCoordIntPair>(TicketType.PRIORITY, PRIORITY_TICKET_LEVEL, coords)); ++ }); ++ delayDistanceManagerTick = false; ++ chunkMap.world.getChunkProvider().tickDistanceManager(); ++ } ++ + private boolean addPriorityTicket(ChunkCoordIntPair coords, TicketType<ChunkCoordIntPair> ticketType, int priority) { + AsyncCatcher.catchOp("ChunkMapDistance::addPriorityTicket"); + long pair = coords.pair(); -+ PlayerChunk updatingChunk = chunkMap.getUpdatingChunk(pair); ++ PlayerChunk chunk = chunkMap.getUpdatingChunk(pair); ++ if (chunk != null && chunk.isFullChunkReady()) { ++ return false; ++ } ++ if (getChunkPriority(coords) >= priority) { ++ return false; ++ } + + boolean success; + if (!(success = updatePriorityTicket(coords, ticketType, priority))) { @@ -189,11 +214,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + ticket.priority = priority; + success = this.addTicket(pair, ticket); + } else { -+ if (updatingChunk == null) { -+ updatingChunk = chunkMap.getUpdatingChunk(pair); ++ if (chunk == null) { ++ chunk = chunkMap.getUpdatingChunk(pair); + } -+ chunkMap.queueHolderUpdate(updatingChunk); ++ chunkMap.queueHolderUpdate(chunk); + } ++ ++ //chunkMap.world.getWorld().spawnParticle(priority <= 15 ? org.bukkit.Particle.EXPLOSION_HUGE : org.bukkit.Particle.EXPLOSION_NORMAL, chunkMap.world.getWorld().getPlayers(), null, coords.x << 4, 70, coords.z << 4, 2, 0, 0, 0, 1, null, true); ++ + chunkMap.world.getChunkProvider().tickDistanceManager(); + + return success; @@ -256,7 +284,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - ChunkMapDistance.this.m.execute(() -> { - if (this.c(this.c(i))) { + // Paper start - smarter ticket delay based on frustum and distance -+ scheduleChunkLoad(i, MinecraftServer.currentTick, (priority) -> { ++ scheduleChunkLoad(i, MinecraftServer.currentTick, j, (priority) -> { + ChunkMapDistance.this.j.a(ChunkTaskQueueSorter.a(() -> { // CraftBukkit - decompile error + if (chunkMap.playerViewDistanceNoTickMap.getObjectsInRange(i) != null && this.c(this.c(i))) { // Copy c(c()) stuff below + // Paper end @@ -271,8 +299,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 }, i, () -> { - return j; - })); -+ return priority; // Paper -+ })); }); ++ return Math.min(PlayerChunkMap.GOLDEN_TICKET, (priority <= 6 ? 20 : 30) + priority); // Paper - delay new ticket adds to avoid spamming the queue ++ })); }); // Paper } else { ChunkMapDistance.this.k.a(ChunkTaskQueueSorter.a(() -> { // CraftBukkit - decompile error ChunkMapDistance.this.m.execute(() -> { @@ -286,12 +314,12 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 } + // Paper start - smart scheduling of player tickets -+ public void scheduleChunkLoad(long i, long startTick, java.util.function.Consumer<Integer> task) { ++ public void scheduleChunkLoad(long i, long startTick, int initialDistance, java.util.function.Consumer<Integer> task) { + long elapsed = MinecraftServer.currentTick - startTick; + PlayerChunk updatingChunk = chunkMap.getUpdatingChunk(i); + if ((updatingChunk != null && updatingChunk.isFullChunkReady()) || !this.c(this.c(i))) { // Copied from above + // no longer needed -+ task.accept(1); ++ task.accept(initialDistance); + return; + } + @@ -299,26 +327,35 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + double minDist = Double.MAX_VALUE; + com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet<EntityPlayer> players = chunkMap.playerViewDistanceNoTickMap.getObjectsInRange(i); + ChunkCoordIntPair chunkPos = new ChunkCoordIntPair(i); -+ if (players != null) { -+ BlockPosition.PooledBlockPosition pos = BlockPosition.PooledBlockPosition.acquire(); ++ if (elapsed == 0 && initialDistance <= 4) { ++ // Aim for no delay on initial 6 chunk radius tickets save on performance of the below code to only > 6 ++ minDist = initialDistance; ++ } else if (players != null) { + Object[] backingSet = players.getBackingSet(); + + BlockPosition blockPos = chunkPos.asPosition(); + + boolean isFront = false; ++ BlockPosition.PooledBlockPosition pos = BlockPosition.PooledBlockPosition.acquire(); + for (int index = 0, len = backingSet.length; index < len; ++index) { + if (!(backingSet[index] instanceof EntityPlayer)) { + continue; + } + EntityPlayer player = (EntityPlayer) backingSet[index]; -+ BlockPosition pointInFront = player.getPointInFront(3 * 16).add(0, (int) -player.locY(), 0); -+ pos.setValues(((int) player.locX() >> 4) << 4, 0, ((int) player.locZ() >> 4) << 4); -+ double frontDist = MCUtil.distanceSq(pointInFront, blockPos); ++ ++ ChunkCoordIntPair pointInFront = player.getChunkInFront(5); ++ pos.setValues(pointInFront.x << 4, 0, pointInFront.z << 4); ++ double frontDist = MCUtil.distanceSq(pos, blockPos); ++ ++ pos.setValues(player.locX(), 0, player.locZ()); + double center = MCUtil.distanceSq(pos, blockPos); ++ + double dist = Math.min(frontDist, center); + if (!isFront) { -+ BlockPosition pointInBack = player.getPointInFront(3 * 16 * -1).add(0, (int) -player.locY(), 0); -+ double backDist = MCUtil.distanceSq(pointInBack, blockPos); ++ ++ ChunkCoordIntPair pointInBack = player.getChunkInFront(-5); ++ pos.setValues(pointInBack.x << 4, 0, pointInBack.z << 4); ++ double backDist = MCUtil.distanceSq(pos, blockPos); + if (frontDist < backDist) { + isFront = true; + } @@ -328,13 +365,19 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + pos.close(); -+ if (minDist < Double.MAX_VALUE) { ++ if (minDist == Double.MAX_VALUE) { ++ minDist = 15; ++ } else { + minDist = Math.sqrt(minDist) / 16; -+ if (minDist > 5) { -+ desireDelay += ((isFront ? 15 : 30) * 20) * (minDist / 32); -+ } ++ } ++ if (minDist > 4) { ++ int desiredTimeDelayMax = isFront ? ++ (minDist < 10 ? 10 : 15) : // Front ++ (minDist < 10 ? 15 : 30); // Back ++ desireDelay += (desiredTimeDelayMax * 20) * (minDist / 32); + } + } else { ++ minDist = initialDistance; + desireDelay = 1; + } + long delay = desireDelay - elapsed; @@ -345,7 +388,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + if (x == 0 && z == 0) continue; + long pair = new ChunkCoordIntPair(chunkPos.x + x, chunkPos.z + z).pair(); + PlayerChunk neighbor = chunkMap.getUpdatingChunk(pair); -+ if (neighbor != null && neighbor.isFullChunkReady()) { ++ ChunkStatus current = neighbor != null ? neighbor.getChunkHolderStatus() : null; ++ if (current != null && current.isAtLeastStatus(ChunkStatus.LIGHT)) { + hasAnyNeighbor = true; + } + } @@ -355,9 +399,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + if (delay <= 0) { -+ task.accept(Math.min(PlayerChunkMap.GOLDEN_TICKET, minDist < Double.MAX_VALUE ? (int) minDist : 15)); ++ task.accept((int) minDist); + } else { -+ MCUtil.scheduleTask((int) Math.min(delay, 20), () -> scheduleChunkLoad(i, startTick, task), "Player Ticket Delayer"); ++ MCUtil.scheduleTask((int) Math.min(delay, minDist >= 8 ? 60 : 20), () -> scheduleChunkLoad(i, startTick, initialDistance, task), "Player Ticket Delayer"); + } + } + // Paper end @@ -382,6 +426,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return this.chunkMapDistance.markHighPriority(coords, priority); + } + ++ public void markAreaHighPriority(ChunkCoordIntPair center, int priority, int radius) { ++ this.chunkMapDistance.markAreaHighPriority(center, priority, radius); ++ } ++ ++ public void clearAreaPriorityTickets(ChunkCoordIntPair center, int radius) { ++ this.chunkMapDistance.clearAreaPriorityTickets(center, radius); ++ } ++ + public void clearPriorityTickets(ChunkCoordIntPair coords) { + this.chunkMapDistance.clearPriorityTickets(coords); + } @@ -440,6 +492,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - private boolean tickDistanceManager() { + public boolean tickDistanceManager() { // Paper - public ++ if (chunkMapDistance.delayDistanceManagerTick) return false; // Paper boolean flag = this.chunkMapDistance.a(this.playerChunkMap); boolean flag1 = this.playerChunkMap.b(); @@ -447,18 +500,32 @@ diff --git a/src/main/java/net/minecraft/server/EntityPlayer.java b/src/main/jav index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/EntityPlayer.java +++ b/src/main/java/net/minecraft/server/EntityPlayer.java +@@ -0,0 +0,0 @@ public class EntityPlayer extends EntityHuman implements ICrafting { + private int lastArmorScored = Integer.MIN_VALUE; + private int lastExpLevelScored = Integer.MIN_VALUE; + private int lastExpTotalScored = Integer.MIN_VALUE; ++ public long lastHighPriorityChecked; // Paper + private float lastHealthSent = -1.0E8F; + private int lastFoodSent = -99999999; + private boolean lastSentSaturationZero = true; @@ -0,0 +0,0 @@ public class EntityPlayer extends EntityHuman implements ICrafting { this.maxHealthCache = this.getMaxHealth(); this.cachedSingleMobDistanceMap = new com.destroystokyo.paper.util.PooledHashSets.PooledObjectLinkedOpenHashSet<>(this); // Paper } + // Paper start + public BlockPosition getPointInFront(double inFront) { -+ final float yaw = MCUtil.normalizeYaw(this.yaw); -+ double rads = Math.toRadians(yaw); ++ double rads = Math.toRadians(MCUtil.normalizeYaw(this.yaw+90)); // MC rotates yaw 90 for some odd reason + final double x = locX() + inFront * Math.cos(rads); + final double z = locZ() + inFront * Math.sin(rads); + return new BlockPosition(x, locY(), z); + } ++ ++ public ChunkCoordIntPair getChunkInFront(double inFront) { ++ double rads = Math.toRadians(MCUtil.normalizeYaw(this.yaw+90)); // MC rotates yaw 90 for some odd reason ++ final double x = locX() + (inFront * 16) * Math.cos(rads); ++ final double z = locZ() + (inFront * 16) * Math.sin(rads); ++ return new ChunkCoordIntPair(MathHelper.floor(x) >> 4, MathHelper.floor(z) >> 4); ++ } + // Paper end // Yes, this doesn't match Vanilla, but it's the best we can do for now. @@ -467,7 +534,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 if (valid && (!this.isSpectator() || this.world.isLoaded(new BlockPosition(this)))) { // Paper - don't tick dead players that are not in the world currently (pending respawn) super.tick(); } -+ if (valid && isAlive() && this.ticksLived % 20 == 0) ((WorldServer)world).getChunkProvider().playerChunkMap.checkHighPriorityChunks(this); // Paper ++ if (valid && isAlive()) ((WorldServer)world).getChunkProvider().playerChunkMap.checkHighPriorityChunks(this); // Paper for (int i = 0; i < this.inventory.getSize(); ++i) { ItemStack itemstack = this.inventory.getItem(i); @@ -812,76 +879,75 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + public void checkHighPriorityChunks(EntityPlayer player) { -+ BlockPosition front2 = player.getPointInFront(16*2); -+ BlockPosition front4 = player.getPointInFront(16*4); -+ BlockPosition front6 = player.getPointInFront(16*6); -+ int viewDistance = getLoadViewDistance(); -+ int maxDistSq = (viewDistance * 16) * (viewDistance * 16); -+ -+ // Prioritize Frustum near 2 -+ int dist3Sq = 3 * 3; -+ MCUtil.getSpiralOutChunks(front2, Math.min(5, viewDistance)).forEach(coord -> { -+ PlayerChunk chunk = getUpdatingChunk(coord.pair()); -+ if (shouldSkipPrioritization(coord, chunk, player, maxDistSq)) return; -+ -+ double dist = chunk.getDistanceFrom(front2); -+ if (dist <= dist3Sq) { -+ chunkDistanceManager.markHighPriority(coord, 26); -+ } else { -+ chunkDistanceManager.markHighPriority(coord, 24); -+ } -+ }); -+ // Prioritize Frustum near 4 -+ if (viewDistance > 4) { -+ MCUtil.getSpiralOutChunks(front4, Math.min(4, viewDistance)).forEach(coord -> { -+ PlayerChunk chunk = getUpdatingChunk(coord.pair()); -+ if (shouldSkipPrioritization(coord, chunk, player, maxDistSq)) return; -+ -+ double dist = chunk.getDistanceFrom(front4); -+ if (dist <= dist3Sq) { -+ chunkDistanceManager.markHighPriority(coord, 22); -+ } else { -+ chunkDistanceManager.markHighPriority(coord, 20); -+ } -+ -+ }); ++ int currentTick = MinecraftServer.currentTick; ++ if (currentTick - player.lastHighPriorityChecked < 20) { ++ return; + } ++ player.lastHighPriorityChecked = currentTick; + -+ // Prioritize Frustum far 6 -+ if (viewDistance > 6) { -+ MCUtil.getSpiralOutChunks(front6, Math.min(4, viewDistance)).forEach(coord -> { -+ PlayerChunk chunk = getUpdatingChunk(coord.pair()); -+ if (shouldSkipPrioritization(coord, chunk, player, maxDistSq)) return; -+ -+ double dist = chunk.getDistanceFrom(front6); -+ if (dist <= dist3Sq) { -+ chunkDistanceManager.markHighPriority(coord, 15); -+ } -+ }); -+ } ++ int viewDistance = getEffectiveNoTickViewDistance(); ++ chunkDistanceManager.delayDistanceManagerTick = true; ++ BlockPosition.PooledBlockPosition pos = BlockPosition.PooledBlockPosition.acquire(); + + // Prioritize circular near -+ MCUtil.getSpiralOutChunks(new BlockPosition(player), Math.min(5, viewDistance)).forEach(coord -> { -+ PlayerChunk chunk = getUpdatingChunk(coord.pair()); -+ if (shouldSkipPrioritization(coord, chunk, player, maxDistSq)) return; -+ double dist = chunk.getDistance(player); ++ double playerChunkX = MathHelper.floor(player.locX()) >> 4; ++ double playerChunkZ = MathHelper.floor(player.locZ()) >> 4; ++ pos.setValues(player.locX(), 0, player.locZ()); ++ MCUtil.getSpiralOutChunks(pos, Math.min(6, viewDistance)).forEach(coord -> { ++ if (shouldSkipPrioritization(coord)) return; + ++ double dist = MCUtil.distance(playerChunkX, 0, playerChunkZ, coord.x, 0, coord.z); + // Prioritize immediate -+ if (dist <= dist3Sq) { -+ chunkDistanceManager.markHighPriority(coord, (int) (27 - dist)); ++ if (dist <= 4 * 4) { ++ chunkDistanceManager.markHighPriority(coord, (int) (27 - Math.sqrt(dist))); + return; + } + + // Prioritize nearby chunks -+ if (dist <= (5*5)) { -+ chunkDistanceManager.markHighPriority(coord, (int) (16 - Math.sqrt(dist*(4D/5D)))); -+ } ++ chunkDistanceManager.markHighPriority(coord, (int) (16 - Math.sqrt(dist*(2D/3D)))); + }); ++ ++ // Prioritize Frustum near 3 ++ ChunkCoordIntPair front3 = player.getChunkInFront(3); ++ pos.setValues(front3.x << 4, 0, front3.z << 4); ++ MCUtil.getSpiralOutChunks(pos, Math.min(5, viewDistance)).forEach(coord -> { ++ if (shouldSkipPrioritization(coord)) return; ++ ++ chunkDistanceManager.markHighPriority(coord, 26); ++ }); ++ ++ // Prioritize Frustum near 5 ++ if (viewDistance > 4) { ++ ChunkCoordIntPair front5 = player.getChunkInFront(5); ++ pos.setValues(front5.x << 4, 0, front5.z << 4); ++ MCUtil.getSpiralOutChunks(pos, 4).forEach(coord -> { ++ if (shouldSkipPrioritization(coord)) return; ++ ++ chunkDistanceManager.markHighPriority(coord, 20); ++ }); ++ } ++ ++ // Prioritize Frustum far 7 ++ if (viewDistance > 6) { ++ ChunkCoordIntPair front7 = player.getChunkInFront(7); ++ pos.setValues(front7.x << 4, 0, front7.z << 4); ++ MCUtil.getSpiralOutChunks(pos, 3).forEach(coord -> { ++ if (shouldSkipPrioritization(coord)) { ++ return; ++ } ++ chunkDistanceManager.markHighPriority(coord, 15); ++ }); ++ } ++ ++ pos.close(); ++ chunkDistanceManager.delayDistanceManagerTick = false; ++ world.getChunkProvider().tickDistanceManager(); + } + -+ private boolean shouldSkipPrioritization(ChunkCoordIntPair coord, PlayerChunk chunk, EntityPlayer player, int viewDistance) { -+ return chunk == null || chunk.isFullChunkReady() || !world.getWorldBorder().isInBounds(coord) -+ || isUnloading(chunk) || chunk.getDistance(player) > viewDistance; ++ private boolean shouldSkipPrioritization(ChunkCoordIntPair coord) { ++ if (playerViewDistanceNoTickMap.getObjectsInRange(coord.pair()) == null) return true; ++ PlayerChunk chunk = getUpdatingChunk(coord.pair()); ++ return chunk != null && (chunk.isFullChunkReady()); + } + // Paper end @@ -947,6 +1013,49 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 }); } +diff --git a/src/main/java/net/minecraft/server/PlayerConnection.java b/src/main/java/net/minecraft/server/PlayerConnection.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/PlayerConnection.java ++++ b/src/main/java/net/minecraft/server/PlayerConnection.java +@@ -0,0 +0,0 @@ public class PlayerConnection implements PacketListenerPlayIn { + // CraftBukkit end + + this.A = this.e; ++ this.player.getWorldServer().getChunkProvider().markAreaHighPriority(new ChunkCoordIntPair(MathHelper.floor(d1) >> 4, MathHelper.floor(d3) >> 4), 28, 3); // Paper - load area high priority + this.player.setLocation(d0, d1, d2, f, f1); + this.syncPosition(); // Paper + this.player.playerConnection.sendPacket(new PacketPlayOutPosition(d0 - d3, d1 - d4, d2 - d5, f - f2, f1 - f3, set, this.teleportAwait)); +diff --git a/src/main/java/net/minecraft/server/PlayerList.java b/src/main/java/net/minecraft/server/PlayerList.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/PlayerList.java ++++ b/src/main/java/net/minecraft/server/PlayerList.java +@@ -0,0 +0,0 @@ public abstract class PlayerList { + final ChunkCoordIntPair pos = new ChunkCoordIntPair(chunkX, chunkZ); + PlayerChunkMap playerChunkMap = finalWorldserver.getChunkProvider().playerChunkMap; + playerChunkMap.chunkDistanceManager.addTicketAtLevel(TicketType.LOGIN, pos, 31, pos.pair()); +- worldserver.getChunkProvider().tickDistanceManager(); +- worldserver.getChunkProvider().getChunkAtAsynchronously(chunkX, chunkZ, true, true).thenApply(chunk -> { ++ worldserver.getChunkProvider().markAreaHighPriority(pos, 28, 3); ++ worldserver.getChunkProvider().getChunkAtAsynchronously(chunkX, chunkZ, true, false).thenApply(chunk -> { + PlayerChunk updatingChunk = playerChunkMap.getUpdatingChunk(pos.pair()); + if (updatingChunk != null) { + return updatingChunk.getEntityTickingFuture(); +@@ -0,0 +0,0 @@ public abstract class PlayerList { + entityplayer, finalWorldserver, networkmanager, playerconnection, + nbttagcompound, networkmanager.getSocketAddress().toString(), lastKnownName + ); +- //playerChunkMap.chunkDistanceManager.removeTicketAtLevel(TicketType.LOGIN, pos, 31, pos.pair()); + }; + }); + } +@@ -0,0 +0,0 @@ public abstract class PlayerList { + // CraftBukkit end + + worldserver.getChunkProvider().addTicket(TicketType.POST_TELEPORT, new ChunkCoordIntPair(location.getBlockX() >> 4, location.getBlockZ() >> 4), 1, entityplayer.getId()); // Paper ++ worldserver.getChunkProvider().markAreaHighPriority(new ChunkCoordIntPair(location.getBlockX() >> 4, location.getBlockZ() >> 4), 28, 3); // Paper - load area at high priority + while (avoidSuffocation && !worldserver.getCubes(entityplayer1) && entityplayer1.locY() < 256.0D) { + entityplayer1.setPosition(entityplayer1.locX(), entityplayer1.locY() + 1.0D, entityplayer1.locZ()); + } diff --git a/src/main/java/net/minecraft/server/Ticket.java b/src/main/java/net/minecraft/server/Ticket.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/server/Ticket.java @@ -987,3 +1096,22 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 return this.world.getChunkProvider().getChunkAtAsynchronously(x, z, gen, urgent).thenComposeAsync((either) -> { net.minecraft.server.Chunk chunk = (net.minecraft.server.Chunk) either.left().orElse(null); return CompletableFuture.completedFuture(chunk == null ? null : chunk.getBukkitChunk()); +diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java ++++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +@@ -0,0 +0,0 @@ public class CraftPlayer extends CraftHumanEntity implements Player { + throw new UnsupportedOperationException("Cannot set rotation of players. Consider teleporting instead."); + } + ++ // Paper start ++ @Override ++ public java.util.concurrent.CompletableFuture<Boolean> teleportAsync(Location loc, PlayerTeleportEvent.TeleportCause cause) { ++ getHandle().getWorldServer().getChunkProvider().markAreaHighPriority(new net.minecraft.server.ChunkCoordIntPair(net.minecraft.server.MathHelper.floor(loc.getX()) >> 4, net.minecraft.server.MathHelper.floor(loc.getZ()) >> 4), 28, 3); // Paper - load area high priority ++ return super.teleportAsync(loc, cause); ++ } ++ // Paper end ++ + @Override + public boolean teleport(Location location, PlayerTeleportEvent.TeleportCause cause) { + Preconditions.checkArgument(location != null, "location");