diff --git a/patches/unapplied/server/API-for-creating-command-sender-which-forwards-feedb.patch b/patches/server/API-for-creating-command-sender-which-forwards-feedb.patch similarity index 100% rename from patches/unapplied/server/API-for-creating-command-sender-which-forwards-feedb.patch rename to patches/server/API-for-creating-command-sender-which-forwards-feedb.patch diff --git a/patches/unapplied/server/Add-GameEvent-tags.patch b/patches/server/Add-GameEvent-tags.patch similarity index 88% rename from patches/unapplied/server/Add-GameEvent-tags.patch rename to patches/server/Add-GameEvent-tags.patch index ed828ce0f4..bb121045d7 100644 --- a/patches/unapplied/server/Add-GameEvent-tags.patch +++ b/patches/server/Add-GameEvent-tags.patch @@ -48,14 +48,6 @@ diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/ja index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java -@@ -0,0 +0,0 @@ import net.minecraft.world.level.biome.BiomeSource; - import net.minecraft.world.level.block.Block; - import net.minecraft.world.level.dimension.DimensionType; - import net.minecraft.world.level.dimension.LevelStem; -+import net.minecraft.world.level.gameevent.GameEvent; - import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator; - import net.minecraft.world.level.levelgen.PatrolSpawner; - import net.minecraft.world.level.levelgen.PhantomSpawner; @@ -0,0 +0,0 @@ public final class CraftServer implements Server { return (org.bukkit.Tag) new CraftEntityTag(Registry.ENTITY_TYPE, entityTagKey); } @@ -79,7 +71,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + // Paper start + case org.bukkit.Tag.REGISTRY_GAME_EVENTS -> { + Preconditions.checkArgument(clazz == org.bukkit.GameEvent.class); -+ Registry gameEvents = Registry.GAME_EVENT; ++ Registry gameEvents = Registry.GAME_EVENT; + return gameEvents.getTags().map(pair -> (org.bukkit.Tag) new io.papermc.paper.CraftGameEventTag(gameEvents, pair.getFirst())).collect(ImmutableList.toImmutableList()); + // Paper end + } diff --git a/patches/unapplied/server/Add-config-for-stronghold-seed.patch b/patches/server/Add-config-for-stronghold-seed.patch similarity index 84% rename from patches/unapplied/server/Add-config-for-stronghold-seed.patch rename to patches/server/Add-config-for-stronghold-seed.patch index 2fb9bf4722..fe245a5ff3 100644 --- a/patches/unapplied/server/Add-config-for-stronghold-seed.patch +++ b/patches/server/Add-config-for-stronghold-seed.patch @@ -8,18 +8,18 @@ diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java +++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java -@@ -0,0 +0,0 @@ public abstract class ChunkGenerator implements BiomeManager.NoiseBiomeSource { - int k = concentricringsstructureplacement.spread(); - Random random = new Random(); +@@ -0,0 +0,0 @@ public abstract class ChunkGenerator { + HolderSet holderset = concentricringsstructureplacement.preferredBiomes(); + RandomSource randomsource = RandomSource.create(); + // Paper start + if (this.conf.strongholdSeed != null && this.structureSets.getResourceKey(holder).orElse(null) == net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.STRONGHOLDS) { -+ random.setSeed(this.conf.strongholdSeed); ++ randomsource.setSeed(this.conf.strongholdSeed); + } else { + randomsource.setSeed(this instanceof FlatLevelSource ? 0L : randomstate.legacyLevelSeed()); ++ } + // Paper end - random.setSeed(this.ringPlacementSeed); -+ } // Paper - double d0 = random.nextDouble() * 3.141592653589793D * 2.0D; + double d0 = randomsource.nextDouble() * 3.141592653589793D * 2.0D; int l = 0; int i1 = 0; diff --git a/src/main/java/org/spigotmc/SpigotWorldConfig.java b/src/main/java/org/spigotmc/SpigotWorldConfig.java diff --git a/patches/unapplied/server/Add-configurable-height-for-slime-spawn.patch b/patches/server/Add-configurable-height-for-slime-spawn.patch similarity index 91% rename from patches/unapplied/server/Add-configurable-height-for-slime-spawn.patch rename to patches/server/Add-configurable-height-for-slime-spawn.patch index 61d50af414..2b7e396d81 100644 --- a/patches/unapplied/server/Add-configurable-height-for-slime-spawn.patch +++ b/patches/server/Add-configurable-height-for-slime-spawn.patch @@ -31,14 +31,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +++ b/src/main/java/net/minecraft/world/entity/monster/Slime.java @@ -0,0 +0,0 @@ public class Slime extends Mob implements Enemy { - public static boolean checkSlimeSpawnRules(EntityType type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, Random random) { + public static boolean checkSlimeSpawnRules(EntityType type, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) { if (world.getDifficulty() != Difficulty.PEACEFUL) { -- if (world.getBiome(pos).is(Biomes.SWAMP) && pos.getY() > 50 && pos.getY() < 70 && random.nextFloat() < 0.5F && random.nextFloat() < world.getMoonBrightness() && world.getMaxLocalRawBrightness(pos) <= random.nextInt(8)) { +- if (world.getBiome(pos).is(BiomeTags.ALLOWS_SURFACE_SLIME_SPAWNS) && pos.getY() > 50 && pos.getY() < 70 && random.nextFloat() < 0.5F && random.nextFloat() < world.getMoonBrightness() && world.getMaxLocalRawBrightness(pos) <= random.nextInt(8)) { + // Paper start - Replace rules for Height in Swamp Biome + final double maxHeightSwamp = world.getMinecraftWorld().paperConfig.slimeMaxSpawnHeightInSwamp; + final double minHeightSwamp = world.getMinecraftWorld().paperConfig.slimeMinSpawnHeightInSwamp; + if (world.getBiome(pos).is(Biomes.SWAMP) && pos.getY() > minHeightSwamp && pos.getY() < maxHeightSwamp && random.nextFloat() < 0.5F && random.nextFloat() < world.getMoonBrightness() && world.getMaxLocalRawBrightness(pos) <= random.nextInt(8)) { -+ // Paper end ++ // Paper end return checkMobSpawnRules(type, world, spawnReason, pos, random); } diff --git a/patches/unapplied/server/Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch b/patches/server/Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch similarity index 100% rename from patches/unapplied/server/Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch rename to patches/server/Add-missing-Validate-calls-to-CraftServer-getSpawnLi.patch diff --git a/patches/unapplied/server/Add-missing-block-data-mins-and-maxes.patch b/patches/server/Add-missing-block-data-mins-and-maxes.patch similarity index 75% rename from patches/unapplied/server/Add-missing-block-data-mins-and-maxes.patch rename to patches/server/Add-missing-block-data-mins-and-maxes.patch index 28e398b24b..4e2a3cf92d 100644 --- a/patches/unapplied/server/Add-missing-block-data-mins-and-maxes.patch +++ b/patches/server/Add-missing-block-data-mins-and-maxes.patch @@ -4,19 +4,6 @@ Date: Sat, 16 Oct 2021 22:57:31 -0700 Subject: [PATCH] Add missing block data mins and maxes -diff --git a/src/main/java/org/bukkit/craftbukkit/block/data/type/CraftLeaves.java b/src/main/java/org/bukkit/craftbukkit/block/data/type/CraftLeaves.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/data/type/CraftLeaves.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/data/type/CraftLeaves.java -@@ -0,0 +0,0 @@ package org.bukkit.craftbukkit.block.data.type; - import org.bukkit.block.data.type.Leaves; - import org.bukkit.craftbukkit.block.data.CraftBlockData; - --public class CraftLeaves extends CraftBlockData implements Leaves { -+public abstract class CraftLeaves extends CraftBlockData implements Leaves { // Paper - make abstract (not used anyways) - - private static final net.minecraft.world.level.block.state.properties.IntegerProperty DISTANCE = getInteger("distance"); - private static final net.minecraft.world.level.block.state.properties.BooleanProperty PERSISTENT = getBoolean("persistent"); diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftCandle.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftCandle.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftCandle.java @@ -34,22 +21,6 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 // org.bukkit.craftbukkit.block.data.CraftLightable -diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftCauldron.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftCauldron.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftCauldron.java -+++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftCauldron.java -@@ -0,0 +0,0 @@ public final class CraftCauldron extends org.bukkit.craftbukkit.block.data.Craft - public int getMaximumLevel() { - return getMax(CraftCauldron.LEVEL); - } -+ -+ // Paper start -+ @Override -+ public int getMinimumLevel() { -+ return getMin(CraftCauldron.LEVEL); -+ } -+ // Paper end - } diff --git a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftComposter.java b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftComposter.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftComposter.java @@ -103,8 +74,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLeaves.java +++ b/src/main/java/org/bukkit/craftbukkit/block/impl/CraftLeaves.java @@ -0,0 +0,0 @@ public final class CraftLeaves extends org.bukkit.craftbukkit.block.data.CraftBl - public void setDistance(int distance) { - set(CraftLeaves.DISTANCE, distance); + public void setWaterlogged(boolean waterlogged) { + set(CraftLeaves.WATERLOGGED, waterlogged); } + + // Paper start diff --git a/patches/unapplied/server/Add-new-overload-to-PersistentDataContainer-has.patch b/patches/server/Add-new-overload-to-PersistentDataContainer-has.patch similarity index 100% rename from patches/unapplied/server/Add-new-overload-to-PersistentDataContainer-has.patch rename to patches/server/Add-new-overload-to-PersistentDataContainer-has.patch diff --git a/patches/unapplied/server/Added-getHostname-to-AsyncPlayerPreLoginEvent.patch b/patches/server/Added-getHostname-to-AsyncPlayerPreLoginEvent.patch similarity index 100% rename from patches/unapplied/server/Added-getHostname-to-AsyncPlayerPreLoginEvent.patch rename to patches/server/Added-getHostname-to-AsyncPlayerPreLoginEvent.patch diff --git a/patches/unapplied/server/Configurable-sculk-sensor-listener-range.patch b/patches/server/Configurable-sculk-sensor-listener-range.patch similarity index 87% rename from patches/unapplied/server/Configurable-sculk-sensor-listener-range.patch rename to patches/server/Configurable-sculk-sensor-listener-range.patch index ff82505000..0afebe8875 100644 --- a/patches/unapplied/server/Configurable-sculk-sensor-listener-range.patch +++ b/patches/server/Configurable-sculk-sensor-listener-range.patch @@ -9,17 +9,21 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java +++ b/src/main/java/net/minecraft/world/level/block/entity/SculkSensorBlockEntity.java @@ -0,0 +0,0 @@ public class SculkSensorBlockEntity extends BlockEntity implements VibrationList - public void load(CompoundTag nbt) { - super.load(nbt); - this.lastVibrationFrequency = nbt.getInt("last_vibration_frequency"); + this.listener = listener; + }); + } + if (nbt.contains(PAPER_LISTENER_RANGE_NBT_KEY)) this.listener.listenerRange = nbt.getInt(PAPER_LISTENER_RANGE_NBT_KEY); // Paper + } + private static final String PAPER_LISTENER_RANGE_NBT_KEY = "Paper.ListenerRange"; // Paper @Override protected void saveAdditional(CompoundTag nbt) { super.saveAdditional(nbt); - nbt.putInt("last_vibration_frequency", this.lastVibrationFrequency); +@@ -0,0 +0,0 @@ public class SculkSensorBlockEntity extends BlockEntity implements VibrationList + VibrationListener.codec(this).encodeStart(NbtOps.INSTANCE, this.listener).resultOrPartial(LOGGER::error).ifPresent((listenerNbt) -> { + nbt.put("listener", listenerNbt); + }); + if (this.listener.listenerRange != ((SculkSensorBlock) net.minecraft.world.level.block.Blocks.SCULK_SENSOR).getListenerRange()) nbt.putInt(PAPER_LISTENER_RANGE_NBT_KEY, this.listener.listenerRange); // Paper - only save if it's different from the default } diff --git a/patches/unapplied/server/Custom-Potion-Mixes.patch b/patches/server/Custom-Potion-Mixes.patch similarity index 100% rename from patches/unapplied/server/Custom-Potion-Mixes.patch rename to patches/server/Custom-Potion-Mixes.patch diff --git a/patches/unapplied/server/Dolphin-API.patch b/patches/server/Dolphin-API.patch similarity index 100% rename from patches/unapplied/server/Dolphin-API.patch rename to patches/server/Dolphin-API.patch diff --git a/patches/unapplied/server/Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch b/patches/server/Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch similarity index 100% rename from patches/unapplied/server/Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch rename to patches/server/Execute-chunk-tasks-fairly-for-worlds-while-waiting-.patch diff --git a/patches/unapplied/server/Fix-Entity-Position-Desync.patch b/patches/server/Fix-Entity-Position-Desync.patch similarity index 83% rename from patches/unapplied/server/Fix-Entity-Position-Desync.patch rename to patches/server/Fix-Entity-Position-Desync.patch index 822e43c9ed..51fcf14133 100644 --- a/patches/unapplied/server/Fix-Entity-Position-Desync.patch +++ b/patches/server/Fix-Entity-Position-Desync.patch @@ -17,16 +17,14 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/net/minecraft/server/level/ServerEntity.java +++ b/src/main/java/net/minecraft/server/level/ServerEntity.java @@ -0,0 +0,0 @@ public class ServerEntity { - } - // CraftBukkit end + boolean flag2 = flag1 || this.tickCount % 60 == 0; + boolean flag3 = Math.abs(i - this.yRotp) >= 1 || Math.abs(j - this.xRotp) >= 1; - if (this.tickCount > 0 || this.entity instanceof AbstractArrow) { + if (!(this.entity instanceof net.minecraft.world.entity.decoration.HangingEntity) || this.tickCount > 0 || this.entity instanceof AbstractArrow) { // Paper - Always update position - // Paper start - remove allocation of Vec3D here - long k = ClientboundMoveEntityPacket.entityToPacket(vec3d_dx); - long l = ClientboundMoveEntityPacket.entityToPacket(vec3d_dy); - long i1 = ClientboundMoveEntityPacket.entityToPacket(vec3d_dz); - // Paper end - remove allocation of Vec3D here + long k = this.positionCodec.encodeX(vec3d); + long l = this.positionCodec.encodeY(vec3d); + long i1 = this.positionCodec.encodeZ(vec3d); boolean flag4 = k < -32768L || k > 32767L || l < -32768L || l > 32767L || i1 < -32768L || i1 > 32767L; + if (this.entity instanceof net.minecraft.world.entity.decoration.Painting) {flag4 = true;} // Paper - Always send exact position for paintings diff --git a/patches/unapplied/server/Fix-Fluid-tags-isTagged-method.patch b/patches/server/Fix-Fluid-tags-isTagged-method.patch similarity index 100% rename from patches/unapplied/server/Fix-Fluid-tags-isTagged-method.patch rename to patches/server/Fix-Fluid-tags-isTagged-method.patch diff --git a/patches/unapplied/server/Fix-cancelled-powdered-snow-bucket-placement.patch b/patches/server/Fix-cancelled-powdered-snow-bucket-placement.patch similarity index 100% rename from patches/unapplied/server/Fix-cancelled-powdered-snow-bucket-placement.patch rename to patches/server/Fix-cancelled-powdered-snow-bucket-placement.patch diff --git a/patches/unapplied/server/Fix-falling-block-spawn-methods.patch b/patches/server/Fix-falling-block-spawn-methods.patch similarity index 100% rename from patches/unapplied/server/Fix-falling-block-spawn-methods.patch rename to patches/server/Fix-falling-block-spawn-methods.patch diff --git a/patches/unapplied/server/Fix-xp-reward-for-baby-zombies.patch b/patches/server/Fix-xp-reward-for-baby-zombies.patch similarity index 88% rename from patches/unapplied/server/Fix-xp-reward-for-baby-zombies.patch rename to patches/server/Fix-xp-reward-for-baby-zombies.patch index db6af156d1..b8afe33928 100644 --- a/patches/unapplied/server/Fix-xp-reward-for-baby-zombies.patch +++ b/patches/server/Fix-xp-reward-for-baby-zombies.patch @@ -15,15 +15,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 @@ -0,0 +0,0 @@ public class Zombie extends Monster { @Override - protected int getExperienceReward(Player player) { + public int getExperienceReward() { + final int previousReward = this.xpReward; // Paper - store previous value to reset after calculating XP reward if (this.isBaby()) { this.xpReward = (int) ((double) this.xpReward * 2.5D); } -- return super.getExperienceReward(player); +- return super.getExperienceReward(); + // Paper start - only change the XP reward for the calculations in the super method -+ int reward = super.getExperienceReward(player); ++ int reward = super.getExperienceReward(); + this.xpReward = previousReward; + return reward; + // Paper end diff --git a/patches/unapplied/server/Force-close-world-loading-screen.patch b/patches/server/Force-close-world-loading-screen.patch similarity index 100% rename from patches/unapplied/server/Force-close-world-loading-screen.patch rename to patches/server/Force-close-world-loading-screen.patch diff --git a/patches/unapplied/server/Freeze-Tick-Lock-API.patch b/patches/server/Freeze-Tick-Lock-API.patch similarity index 100% rename from patches/unapplied/server/Freeze-Tick-Lock-API.patch rename to patches/server/Freeze-Tick-Lock-API.patch diff --git a/patches/unapplied/server/Furnace-RecipesUsed-API.patch b/patches/server/Furnace-RecipesUsed-API.patch similarity index 100% rename from patches/unapplied/server/Furnace-RecipesUsed-API.patch rename to patches/server/Furnace-RecipesUsed-API.patch diff --git a/patches/unapplied/server/Implement-regenerateChunk.patch b/patches/server/Implement-regenerateChunk.patch similarity index 100% rename from patches/unapplied/server/Implement-regenerateChunk.patch rename to patches/server/Implement-regenerateChunk.patch diff --git a/patches/server/Kick-on-main-for-illegal-chars.patch b/patches/server/Kick-on-main-for-illegal-chars.patch new file mode 100644 index 0000000000..36d24b9d37 --- /dev/null +++ b/patches/server/Kick-on-main-for-illegal-chars.patch @@ -0,0 +1,30 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nassim Jahnke +Date: Mon, 17 Jan 2022 19:47:19 +0100 +Subject: [PATCH] Kick on main for illegal chars + + +diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 +--- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java ++++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java +@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser + } + // CraftBukkit end + if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.getMessage())) { ++ this.server.scheduleOnMain(() -> { // Paper - push to main for event firing + this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - add cause ++ }); // Paper - push to main for event firing + } else { + if (this.tryHandleChat(packet.getMessage(), packet.getTimeStamp())) { + // CraftBukkit start +@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser + @Override + public void handleChatCommand(ServerboundChatCommandPacket packet) { + if (ServerGamePacketListenerImpl.isChatMessageIllegal(packet.command())) { ++ this.server.scheduleOnMain(() -> { // Paper - push to main for event firing + this.disconnect(Component.translatable("multiplayer.disconnect.illegal_characters")); ++ }); // Paper - push to main for event firing + } else { + PacketUtils.ensureRunningOnSameThread(packet, this, this.player.getLevel()); + if (this.tryHandleChat(packet.command(), packet.timeStamp())) { diff --git a/patches/unapplied/server/Log-exceptions-thrown-during-chat-processing.patch b/patches/server/Log-exceptions-thrown-during-chat-processing.patch similarity index 100% rename from patches/unapplied/server/Log-exceptions-thrown-during-chat-processing.patch rename to patches/server/Log-exceptions-thrown-during-chat-processing.patch diff --git a/patches/unapplied/server/More-PotionEffectType-API.patch b/patches/server/More-PotionEffectType-API.patch similarity index 100% rename from patches/unapplied/server/More-PotionEffectType-API.patch rename to patches/server/More-PotionEffectType-API.patch diff --git a/patches/unapplied/server/Multi-Block-Change-API-Implementation.patch b/patches/server/Multi-Block-Change-API-Implementation.patch similarity index 96% rename from patches/unapplied/server/Multi-Block-Change-API-Implementation.patch rename to patches/server/Multi-Block-Change-API-Implementation.patch index 4cc6556b72..ced5dc7cd9 100644 --- a/patches/unapplied/server/Multi-Block-Change-API-Implementation.patch +++ b/patches/server/Multi-Block-Change-API-Implementation.patch @@ -28,14 +28,14 @@ diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/src/ 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 @@ import javax.annotation.Nullable; - import net.minecraft.Util; +@@ -0,0 +0,0 @@ import java.util.logging.Logger; + import javax.annotation.Nullable; import net.minecraft.advancements.AdvancementProgress; import net.minecraft.core.BlockPos; +import net.minecraft.core.SectionPos; // Paper import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; - import net.minecraft.network.chat.ChatType; + import net.minecraft.network.chat.ChatSender; @@ -0,0 +0,0 @@ public class CraftPlayer extends CraftHumanEntity implements Player { this.getHandle().connection.send(packet); } diff --git a/patches/unapplied/server/Multiple-Entries-with-Scoreboards.patch b/patches/server/Multiple-Entries-with-Scoreboards.patch similarity index 100% rename from patches/unapplied/server/Multiple-Entries-with-Scoreboards.patch rename to patches/server/Multiple-Entries-with-Scoreboards.patch diff --git a/patches/unapplied/server/Option-to-have-default-CustomSpawners-in-custom-worl.patch b/patches/server/Option-to-have-default-CustomSpawners-in-custom-worl.patch similarity index 78% rename from patches/unapplied/server/Option-to-have-default-CustomSpawners-in-custom-worl.patch rename to patches/server/Option-to-have-default-CustomSpawners-in-custom-worl.patch index 3f607945ff..ce6eb3bf3e 100644 --- a/patches/unapplied/server/Option-to-have-default-CustomSpawners-in-custom-worl.patch +++ b/patches/server/Option-to-have-default-CustomSpawners-in-custom-worl.patch @@ -14,8 +14,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/com/destroystokyo/paper/PaperConfig.java +++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java @@ -0,0 +0,0 @@ public class PaperConfig { - globalMaxConcurrentChunkLoads = getDouble("settings.chunk-loading.global-max-concurrent-loads", 500.0); - playerMaxChunkLoadRate = getDouble("settings.chunk-loading.player-max-chunk-load-rate", -1.0); + private static void timeCommandAffectsAllWorlds() { + timeCommandAffectsAllWorlds = getBoolean("settings.time-command-affects-all-worlds", timeCommandAffectsAllWorlds); } + + public static boolean useDimensionTypeForCustomSpawners; @@ -31,15 +31,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 this.commandStorage = new CommandStorage(worldpersistentdata); } else { ChunkProgressListener worldloadlistener = this.progressListenerFactory.create(11); -- world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, holder, worldloadlistener, chunkgenerator, flag, j, ImmutableList.of(), true, org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider); +- world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, ImmutableList.of(), true, org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider); + // Paper start - option to use the dimension_type to check if spawners should be added. I imagine mojang will add some datapack-y way of managing this in the future. + final List spawners; -+ if (com.destroystokyo.paper.PaperConfig.useDimensionTypeForCustomSpawners && this.registryHolder.registryOrThrow(Registry.DIMENSION_TYPE_REGISTRY).getResourceKey(holder.value()).orElseThrow() == DimensionType.OVERWORLD_LOCATION) { ++ if (com.destroystokyo.paper.PaperConfig.useDimensionTypeForCustomSpawners && this.registryHolder.registryOrThrow(Registry.DIMENSION_TYPE_REGISTRY).getResourceKey(worlddimension.typeHolder().value()).orElseThrow() == net.minecraft.world.level.dimension.BuiltinDimensionTypes.OVERWORLD) { + spawners = list; + } else { + spawners = Collections.emptyList(); + } -+ world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, holder, worldloadlistener, chunkgenerator, flag, j, spawners, true, org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider); ++ world = new ServerLevel(this, this.executor, worldSession, iworlddataserver, worldKey, worlddimension, worldloadlistener, flag, j, spawners, true, org.bukkit.World.Environment.getEnvironment(dimension), gen, biomeProvider); + // Paper end } diff --git a/patches/unapplied/server/Put-world-into-worldlist-before-initing-the-world.patch b/patches/server/Put-world-into-worldlist-before-initing-the-world.patch similarity index 100% rename from patches/unapplied/server/Put-world-into-worldlist-before-initing-the-world.patch rename to patches/server/Put-world-into-worldlist-before-initing-the-world.patch diff --git a/patches/unapplied/server/Replace-ticket-level-propagator.patch b/patches/server/Replace-ticket-level-propagator.patch similarity index 96% rename from patches/unapplied/server/Replace-ticket-level-propagator.patch rename to patches/server/Replace-ticket-level-propagator.patch index 74b7b898dd..64859ac881 100644 --- a/patches/unapplied/server/Replace-ticket-level-propagator.patch +++ b/patches/server/Replace-ticket-level-propagator.patch @@ -33,8 +33,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - 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 + private final TickingTracker tickingTicketsTracker = new TickingTracker(); + private final DistanceManager.PlayerTicketTracker playerTicketManager = new DistanceManager.PlayerTicketTracker(33); @@ -0,0 +0,0 @@ public abstract class DistanceManager { this.chunkMap = chunkMap; // Paper } @@ -98,9 +98,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + protected long ticketLevelUpdateCount; // Paper - replace ticket level propagator public boolean runAllUpdates(ChunkMap chunkStorage) { //this.f.a(); // Paper - no longer used - //this.tickingTicketsTracker.runAllUpdates(); // Paper - no longer used + this.tickingTicketsTracker.runAllUpdates(); org.spigotmc.AsyncCatcher.catchOp("DistanceManagerTick"); // Paper - //this.playerTicketManager.runAllUpdates(); // Paper - no longer used + this.playerTicketManager.runAllUpdates(); - int i = Integer.MAX_VALUE - this.ticketTracker.runDistanceUpdates(Integer.MAX_VALUE); - boolean flag = i != 0; + boolean flag = this.ticketLevelPropagator.propagateUpdates(); // Paper - replace ticket level propagator diff --git a/patches/unapplied/server/Reset-placed-block-on-exception.patch b/patches/server/Reset-placed-block-on-exception.patch similarity index 96% rename from patches/unapplied/server/Reset-placed-block-on-exception.patch rename to patches/server/Reset-placed-block-on-exception.patch index c47ff07b4e..8cd0cfedc3 100644 --- a/patches/unapplied/server/Reset-placed-block-on-exception.patch +++ b/patches/server/Reset-placed-block-on-exception.patch @@ -9,7 +9,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/net/minecraft/world/item/BlockItem.java +++ b/src/main/java/net/minecraft/world/item/BlockItem.java @@ -0,0 +0,0 @@ public class BlockItem extends Item { - if (this instanceof WaterLilyBlockItem || this instanceof SolidBucketItem) { + if (this instanceof PlaceOnWaterBlockItem || this instanceof SolidBucketItem) { blockstate = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockactioncontext1.getLevel(), blockactioncontext1.getClickedPos()); } + final org.bukkit.block.BlockState oldBlockstate = blockstate != null ? blockstate : org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockactioncontext1.getLevel(), blockactioncontext1.getClickedPos()); // Paper diff --git a/patches/unapplied/server/Use-a-CHM-for-StructureTemplate.Pallete-cache.patch b/patches/server/Use-a-CHM-for-StructureTemplate.Pallete-cache.patch similarity index 100% rename from patches/unapplied/server/Use-a-CHM-for-StructureTemplate.Pallete-cache.patch rename to patches/server/Use-a-CHM-for-StructureTemplate.Pallete-cache.patch diff --git a/patches/unapplied/server/Fix-NotePlayEvent.patch b/patches/unapplied/server/Fix-NotePlayEvent.patch deleted file mode 100644 index 76fa0453ab..0000000000 --- a/patches/unapplied/server/Fix-NotePlayEvent.patch +++ /dev/null @@ -1,40 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Kieran Wallbanks -Date: Mon, 21 Jun 2021 14:23:50 +0100 -Subject: [PATCH] Fix NotePlayEvent - - -diff --git a/src/main/java/net/minecraft/world/level/block/NoteBlock.java b/src/main/java/net/minecraft/world/level/block/NoteBlock.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/level/block/NoteBlock.java -+++ b/src/main/java/net/minecraft/world/level/block/NoteBlock.java -@@ -0,0 +0,0 @@ public class NoteBlock extends Block { - private void playNote(Level world, BlockPos blockposition, BlockState data) { // CraftBukkit - if (world.getBlockState(blockposition.above()).isAir()) { - // CraftBukkit start -- org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, blockposition, data.getValue(NoteBlock.INSTRUMENT), data.getValue(NoteBlock.NOTE)); -- if (!event.isCancelled()) { -+ // Paper start - move NotePlayEvent call to fix instrument/note changes - world.blockEvent(blockposition, this, 0, 0); -- } -+ // Paper end - // CraftBukkit end - } - -@@ -0,0 +0,0 @@ public class NoteBlock extends Block { - - @Override - public boolean triggerEvent(BlockState state, Level world, BlockPos pos, int type, int data) { -- int k = (Integer) state.getValue(NoteBlock.NOTE); -+ // Paper start - move NotePlayEvent call to fix instrument/note changes -+ org.bukkit.event.block.NotePlayEvent event = org.bukkit.craftbukkit.event.CraftEventFactory.callNotePlayEvent(world, pos, state.getValue(INSTRUMENT), state.getValue(NOTE)); -+ if (event.isCancelled()) return false; -+ int k = event.getNote().getId(); - float f = (float) Math.pow(2.0D, (double) (k - 12) / 12.0D); - -- world.playSound((Player) null, pos, ((NoteBlockInstrument) state.getValue(NoteBlock.INSTRUMENT)).getSoundEvent(), SoundSource.RECORDS, 3.0F, f); -+ world.playSound(null, pos, org.bukkit.craftbukkit.block.data.CraftBlockData.toNMS(event.getInstrument(), NoteBlockInstrument.class).getSoundEvent(), SoundSource.RECORDS, 3.0F, f); -+ // Paper end - world.addParticle(ParticleTypes.NOTE, (double) pos.getX() + 0.5D, (double) pos.getY() + 1.2D, (double) pos.getZ() + 0.5D, (double) k / 24.0D, 0.0D, 0.0D); - return true; - } diff --git a/patches/unapplied/server/Fix-World-locateNearestStructure.patch b/patches/unapplied/server/Fix-World-locateNearestStructure.patch deleted file mode 100644 index ac782a1883..0000000000 --- a/patches/unapplied/server/Fix-World-locateNearestStructure.patch +++ /dev/null @@ -1,74 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Jake Potrebic -Date: Tue, 1 Mar 2022 14:12:17 -0800 -Subject: [PATCH] Fix World#locateNearestStructure - -1.18.2 switched to TagKeys to reference tags of objects, and this method - impl needs to be changed to reflect that - -diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/MinecraftServer.java -+++ b/src/main/java/net/minecraft/server/MinecraftServer.java -@@ -0,0 +0,0 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop>> pair = this.getChunkSource().getGenerator().findNearestMapFeature(this, (HolderSet) optional.get(), pos, radius, skipExistingChunks); -+ // Paper start -+ return this.findNearestMapFeature(optional.get(), pos, radius, skipExistingChunks); -+ } -+ } -+ } -+ public @Nullable BlockPos findNearestMapFeature(HolderSet> holderSet, BlockPos pos, int radius, boolean skipExistingChunks) { -+ { -+ { -+ Pair>> pair = this.getChunkSource().getGenerator().findNearestMapFeature(this, holderSet, pos, radius, skipExistingChunks); -+ // Paper end - - return pair != null ? (BlockPos) pair.getFirst() : null; - } -diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World { - - } - -+ public final Map>>> structureCache = new java.util.HashMap<>(); // Paper - @Override - public Location locateNearestStructure(Location origin, StructureType structureType, int radius, boolean findUnexplored) { - BlockPos originPos = new BlockPos(origin.getX(), origin.getY(), origin.getZ()); -- BlockPos nearest = this.getHandle().findNearestMapFeature(TagKey.create(Registry.CONFIGURED_STRUCTURE_FEATURE_REGISTRY, CraftNamespacedKey.toMinecraft(structureType.getKey())), originPos, radius, findUnexplored); -+ // Paper start - fix because you can't just create random TagKeys -+ if (!this.getHandle().serverLevelData.worldGenSettings().generateFeatures()) { // from ServerLevel#findNearestMapFeature -+ return null; -+ } -+ final List>> features = this.structureCache.computeIfAbsent(structureType, (type) -> { -+ final Registry> structureFeatureRegistry = this.getHandle().registryAccess().registryOrThrow(Registry.STRUCTURE_FEATURE_REGISTRY); -+ return this.getHandle().registryAccess().registryOrThrow(Registry.CONFIGURED_STRUCTURE_FEATURE_REGISTRY).holders().filter(holder -> { -+ return structureType.getKey().equals(org.bukkit.craftbukkit.util.CraftNamespacedKey.fromMinecraft(Objects.requireNonNull(structureFeatureRegistry.getKey(holder.value().feature)))); -+ }).toList(); -+ }); -+ BlockPos nearest = this.getHandle().findNearestMapFeature(net.minecraft.core.HolderSet.direct(features), originPos, radius, findUnexplored); -+ // Paper end - return (nearest == null) ? null : new Location(this, nearest.getX(), nearest.getY(), nearest.getZ()); - } - diff --git a/patches/unapplied/server/Kick-on-main-for-illegal-chars.patch b/patches/unapplied/server/Kick-on-main-for-illegal-chars.patch deleted file mode 100644 index 7a8057f332..0000000000 --- a/patches/unapplied/server/Kick-on-main-for-illegal-chars.patch +++ /dev/null @@ -1,20 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Nassim Jahnke -Date: Mon, 17 Jan 2022 19:47:19 +0100 -Subject: [PATCH] Kick on main for illegal chars - - -diff --git a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -+++ b/src/main/java/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -0,0 +0,0 @@ public class ServerGamePacketListenerImpl implements ServerPlayerConnection, Ser - - for (int i = 0; i < s.length(); ++i) { - if (!SharedConstants.isAllowedChatCharacter(s.charAt(i))) { -+ this.server.scheduleOnMain(() -> { // Paper - push to main for event firing - this.disconnect(new TranslatableComponent("multiplayer.disconnect.illegal_characters"), org.bukkit.event.player.PlayerKickEvent.Cause.ILLEGAL_CHARACTERS); // Paper - add cause -+ }); // Paper - push to main for event firing - return; - } - } diff --git a/patches/unapplied/server/Replace-player-chunk-loader-system.patch b/patches/unapplied/server/Replace-player-chunk-loader-system.patch deleted file mode 100644 index 8e47816821..0000000000 --- a/patches/unapplied/server/Replace-player-chunk-loader-system.patch +++ /dev/null @@ -1,2283 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 24 Jan 2021 20:27:32 -0800 -Subject: [PATCH] Replace player chunk loader system - -The old one has undebuggable problems. Rewriting seems -the most sensible option. - -This new player chunk manager will also strictly rate limit -chunk sends so that netty threads do not get overloaded, whether -it be from the anti-xray logic or the compression itself. - -Chunk loading is also rate limited in the same manner, so this -will result in a maximum responsiveness for change. - -Config: -``` -chunk-loading: - min-load-radius: 2 - max-concurrent-sends: 2 - autoconfig-send-distance: true - target-player-chunk-send-rate: 100.0 - global-max-chunk-send-rate: -1 - enable-frustum-priority: false - global-max-chunk-load-rate: -1.0 - player-max-concurrent-loads: 25.0 - global-max-concurrent-loads: 500.0 -``` - -min-load-radius - The radius of chunks around a player that -are not throttled for loading. The number of chunks -affected is actually the configured value plus one as this -config controls the chunks the client will be able to render. - -max-concurrent-sends - The maximum number of chunks that -can be queued to send at any given time. Low values -are generally going to solve server-sided networking -bottlenecks like anti-xray and chunk compression. Client -side networking is unlikely to be helped (i.e this wont help -people running off McDonald's wifi). - -autoconfig-send-distance - Whether to try to use the client's -view distance for the send view distance in the server. In the -case that no plugin has explicitly set the send distance and -the client view distance is less-than the server's send distance, -the client's view distance will be used. This will not affect -tick view distance or no-tick view distance. - -target-player-chunk-send-rate - The maximum chunk send rate -an individual player will have. -1 means no limit - -global-max-chunk-send-rate - The maximum chunk send rate for -the whole server. -1 means no limit - -enable-frustum-priority - Whether chunks in front of a player -are prioritised to load/send first. Disabled by default -because the client can bug out due to the out of order -chunk sending. - -global-max-chunk-load-rate - The maximum chunk load rate -for the whole server. -1 means no limit - -player-max-concurrent-loads and global-max-concurrent-loads -The maximum number of concurrent loads for the server is -determined by the number of players on the server multiplied by the -`player-max-concurrent-loads`. It is then limited to -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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/co/aikar/timings/TimingsExport.java -+++ b/src/main/java/co/aikar/timings/TimingsExport.java -@@ -0,0 +0,0 @@ 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()) -+ // 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/com/destroystokyo/paper/PaperConfig.java -+++ b/src/main/java/com/destroystokyo/paper/PaperConfig.java -@@ -0,0 +0,0 @@ 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; -+ public static double playerTargetChunkSendRate; -+ public static double globalMaxChunkSendRate; -+ public static boolean playerFrustumPrioritisation; -+ public static double globalMaxChunkLoadRate; -+ public static double playerMaxConcurrentChunkLoads; -+ public static double globalMaxConcurrentChunkLoads; -+ public static double playerMaxChunkLoadRate; -+ -+ private static void newPlayerChunkManagement() { -+ playerMinChunkLoadRadius = getInt("settings.chunk-loading.min-load-radius", 2); -+ playerMaxConcurrentChunkSends = getInt("settings.chunk-loading.max-concurrent-sends", 2); -+ playerAutoConfigureSendViewDistance = getBoolean("settings.chunk-loading.autoconfig-send-distance", true); -+ playerTargetChunkSendRate = getDouble("settings.chunk-loading.target-player-chunk-send-rate", 100.0); -+ globalMaxChunkSendRate = getDouble("settings.chunk-loading.global-max-chunk-send-rate", -1.0); -+ 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", 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); -+ } -+ globalMaxConcurrentChunkLoads = getDouble("settings.chunk-loading.global-max-concurrent-loads", 500.0); -+ playerMaxChunkLoadRate = getDouble("settings.chunk-loading.player-max-chunk-load-rate", -1.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..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/chunk/PlayerChunkLoader.java -@@ -0,0 +0,0 @@ -+package io.papermc.paper.chunk; -+ -+import com.destroystokyo.paper.PaperConfig; -+import com.destroystokyo.paper.util.misc.PlayerAreaMap; -+import com.destroystokyo.paper.util.misc.PooledLinkedHashSets; -+import io.papermc.paper.util.CoordinateUtils; -+import io.papermc.paper.util.IntervalledCounter; -+import io.papermc.paper.util.TickThread; -+import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap; -+import it.unimi.dsi.fastutil.objects.Reference2ObjectLinkedOpenHashMap; -+import it.unimi.dsi.fastutil.objects.ReferenceLinkedOpenHashSet; -+import net.minecraft.network.protocol.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.*; -+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; -+import java.util.TreeSet; -+import java.util.concurrent.atomic.AtomicInteger; -+ -+public final class PlayerChunkLoader { -+ -+ public static final int MIN_VIEW_DISTANCE = 2; -+ public static final int MAX_VIEW_DISTANCE = 32; -+ -+ public static final int TICK_TICKET_LEVEL = 31; -+ public static final int LOADED_TICKET_LEVEL = 33; -+ -+ 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); -+ -+ protected final TreeSet chunkLoadQueue = new TreeSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> { -+ if (p1 == p2) { -+ return 0; -+ } -+ -+ final ChunkPriorityHolder holder1 = p1.loadQueue.peekFirst(); -+ final ChunkPriorityHolder holder2 = p2.loadQueue.peekFirst(); -+ -+ final int priorityCompare = Double.compare(holder1 == null ? Double.MAX_VALUE : holder1.priority, holder2 == null ? Double.MAX_VALUE : holder2.priority); -+ -+ final int lastLoadTimeCompare = Long.compare(p1.lastChunkLoad, p2.lastChunkLoad); -+ -+ if ((holder1 == null || holder2 == null || lastLoadTimeCompare == 0 || holder1.priority < 0.0 || holder2.priority < 0.0) && priorityCompare != 0) { -+ return priorityCompare; -+ } -+ -+ if (lastLoadTimeCompare != 0) { -+ return lastLoadTimeCompare; -+ } -+ -+ final int idCompare = Integer.compare(p1.player.getId(), p2.player.getId()); -+ -+ if (idCompare != 0) { -+ return idCompare; -+ } -+ -+ // last resort -+ return Integer.compare(System.identityHashCode(p1), System.identityHashCode(p2)); -+ }); -+ -+ protected final TreeSet chunkSendWaitQueue = new TreeSet<>((final PlayerLoaderData p1, final PlayerLoaderData p2) -> { -+ if (p1 == p2) { -+ return 0; -+ } -+ -+ final int timeCompare = Long.compare(p1.nextChunkSendTarget, p2.nextChunkSendTarget); -+ if (timeCompare != 0) { -+ return timeCompare; -+ } -+ -+ final int idCompare = Integer.compare(p1.player.getId(), p2.player.getId()); -+ -+ if (idCompare != 0) { -+ return idCompare; -+ } -+ -+ // last resort -+ return Integer.compare(System.identityHashCode(p1), System.identityHashCode(p2)); -+ }); -+ -+ -+ // no throttling is applied below this VD for loading -+ -+ /** -+ * The chunks to be sent to players, provided they're send-ready. Send-ready means the chunk and its 1 radius neighbours are loaded. -+ */ -+ public final PlayerAreaMap broadcastMap; -+ -+ /** -+ * The chunks to be brought up to send-ready status. Send-ready means the chunk and its 1 radius neighbours are loaded. -+ */ -+ public final PlayerAreaMap loadMap; -+ -+ /** -+ * Areamap used only to remove tickets for send-ready chunks. View distance is always + 1 of load view distance. Thus, -+ * this map is always representing the chunks we are actually going to load. -+ */ -+ public final PlayerAreaMap loadTicketCleanup; -+ -+ /** -+ * The chunks to brought to ticking level. Each chunk must have 2 radius neighbours loaded before this can happen. -+ */ -+ public final PlayerAreaMap tickMap; -+ -+ /** -+ * -1 if defaulting to [load distance], else always in [2, load distance] -+ */ -+ protected int rawSendDistance = -1; -+ -+ /** -+ * -1 if defaulting to [tick view distance + 1], else always in [tick view distance + 1, 32 + 1] -+ */ -+ protected int rawLoadDistance = -1; -+ -+ /** -+ * Never -1, always in [2, 32] -+ */ -+ protected int rawTickDistance = -1; -+ -+ // methods to bridge for API -+ -+ public int getTargetTickViewDistance() { -+ return this.getTickDistance(); -+ } -+ -+ public void setTargetTickViewDistance(final int distance) { -+ this.setTickDistance(distance); -+ } -+ -+ public int getTargetNoTickViewDistance() { -+ return this.getLoadDistance() - 1; -+ } -+ -+ public void setTargetNoTickViewDistance(final int distance) { -+ this.setLoadDistance(distance == -1 ? -1 : distance + 1); -+ } -+ -+ public int getTargetSendDistance() { -+ return this.rawSendDistance == -1 ? this.getLoadDistance() : this.rawSendDistance; -+ } -+ -+ public void setTargetSendDistance(final int distance) { -+ this.setSendDistance(distance); -+ } -+ -+ // internal methods -+ -+ public int getSendDistance() { -+ final int loadDistance = this.getLoadDistance(); -+ return this.rawSendDistance == -1 ? loadDistance : Math.min(this.rawSendDistance, loadDistance); -+ } -+ -+ public void setSendDistance(final int distance) { -+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE + 1)) { -+ throw new IllegalArgumentException("Send distance must be a number between " + MIN_VIEW_DISTANCE + " and " + (MAX_VIEW_DISTANCE + 1) + ", or -1, got: " + distance); -+ } -+ this.rawSendDistance = distance; -+ } -+ -+ public int getLoadDistance() { -+ final int tickDistance = this.getTickDistance(); -+ return this.rawLoadDistance == -1 ? tickDistance + 1 : Math.max(tickDistance + 1, this.rawLoadDistance); -+ } -+ -+ public void setLoadDistance(final int distance) { -+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE + 1)) { -+ throw new IllegalArgumentException("Load distance must be a number between " + MIN_VIEW_DISTANCE + " and " + (MAX_VIEW_DISTANCE + 1) + ", or -1, got: " + distance); -+ } -+ this.rawLoadDistance = distance; -+ } -+ -+ public int getTickDistance() { -+ return this.rawTickDistance; -+ } -+ -+ public void setTickDistance(final int distance) { -+ if (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE) { -+ throw new IllegalArgumentException("View distance must be a number between " + MIN_VIEW_DISTANCE + " and " + MAX_VIEW_DISTANCE + ", got: " + distance); -+ } -+ this.rawTickDistance = distance; -+ } -+ -+ /* -+ Players have 3 different types of view distance: -+ 1. Sending view distance -+ 2. Loading view distance -+ 3. Ticking view distance -+ -+ But for configuration purposes (and API) there are: -+ 1. No-tick view distance -+ 2. Tick view distance -+ 3. Broadcast view distance -+ -+ These aren't always the same as the types we represent internally. -+ -+ Loading view distance is always max(no-tick + 1, tick + 1) -+ - no-tick has 1 added because clients need an extra radius to render chunks -+ - tick has 1 added because it needs an extra radius of chunks to load before they can be marked ticking -+ -+ Loading view distance is defined as the radius of chunks that will be brought to send-ready status, which means -+ it loads chunks in radius load-view-distance + 1. -+ -+ The maximum value for send view distance is the load view distance. API can set it lower. -+ */ -+ -+ public PlayerChunkLoader(final ChunkMap chunkMap, final PooledLinkedHashSets pooledHashSets) { -+ this.chunkMap = chunkMap; -+ this.broadcastMap = new PlayerAreaMap(pooledHashSets, -+ 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); -+ }); -+ this.loadMap = new PlayerAreaMap(pooledHashSets, -+ null, -+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { -+ if (newState != null) { -+ return; -+ } -+ PlayerChunkLoader.this.isTargetedForPlayerLoad.remove(CoordinateUtils.getChunkKey(rangeX, rangeZ)); -+ }); -+ this.loadTicketCleanup = new PlayerAreaMap(pooledHashSets, -+ null, -+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { -+ if (newState != null) { -+ return; -+ } -+ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ); -+ PlayerChunkLoader.this.chunkMap.level.getChunkSource().removeTicketAtLevel(TicketType.PLAYER, chunkPos, LOADED_TICKET_LEVEL, chunkPos); -+ if (PlayerChunkLoader.this.chunkTicketTracker.remove(chunkPos.toLong())) { -+ --PlayerChunkLoader.this.concurrentChunkLoads; -+ } -+ }); -+ this.tickMap = new PlayerAreaMap(pooledHashSets, -+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { -+ if (newState.size() != 1) { -+ return; -+ } -+ LevelChunk chunk = PlayerChunkLoader.this.chunkMap.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(rangeX, rangeZ); -+ if (chunk == null || !chunk.areNeighboursLoaded(2)) { -+ return; -+ } -+ -+ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ); -+ PlayerChunkLoader.this.chunkMap.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, TICK_TICKET_LEVEL, chunkPos); -+ }, -+ (ServerPlayer player, int rangeX, int rangeZ, int currPosX, int currPosZ, int prevPosX, int prevPosZ, -+ com.destroystokyo.paper.util.misc.PooledLinkedHashSets.PooledObjectLinkedOpenHashSet newState) -> { -+ if (newState != null) { -+ return; -+ } -+ ChunkPos chunkPos = new ChunkPos(rangeX, rangeZ); -+ PlayerChunkLoader.this.chunkMap.level.getChunkSource().removeTicketAtLevel(TicketType.PLAYER, chunkPos, TICK_TICKET_LEVEL, chunkPos); -+ }); -+ } -+ -+ protected final LongOpenHashSet isTargetedForPlayerLoad = new LongOpenHashSet(); -+ protected final LongOpenHashSet chunkTicketTracker = new LongOpenHashSet(); -+ -+ 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); -+ final ChunkHolder chunk = this.chunkMap.getVisibleChunkIfPresent(key); -+ -+ if (chunk == null) { -+ return false; -+ } -+ -+ 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) { -+ final PlayerLoaderData data = this.playerMap.get(player); -+ if (data == null) { -+ return false; -+ } -+ -+ 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; -+ } -+ -+ protected int getMaxChunkLoads() { -+ double config = PaperConfig.playerMaxConcurrentChunkLoads; -+ double max = PaperConfig.globalMaxConcurrentChunkLoads; -+ return (int)Math.ceil(Math.min(config * MinecraftServer.getServer().getPlayerCount(), max <= 1.0 ? Double.MAX_VALUE : max)); -+ } -+ -+ protected long getTargetSendPerPlayerAddend() { -+ return PaperConfig.playerTargetChunkSendRate <= 1.0 ? 0L : (long)Math.round(1.0e9 / PaperConfig.playerTargetChunkSendRate); -+ } -+ -+ protected long getMaxSendAddend() { -+ return PaperConfig.globalMaxChunkSendRate <= 1.0 ? 0L : (long)Math.round(1.0e9 / PaperConfig.globalMaxChunkSendRate); -+ } -+ -+ public void onChunkPlayerTickReady(final int chunkX, final int chunkZ) { -+ final ChunkPos chunkPos = new ChunkPos(chunkX, chunkZ); -+ this.chunkMap.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, TICK_TICKET_LEVEL, chunkPos); -+ } -+ -+ public void onChunkSendReady(final int chunkX, final int chunkZ) { -+ final PooledLinkedHashSets.PooledObjectLinkedOpenHashSet playersInSendRange = this.broadcastMap.getObjectsInRange(chunkX, chunkZ); -+ -+ if (playersInSendRange == null) { -+ return; -+ } -+ -+ final Object[] rawData = playersInSendRange.getBackingSet(); -+ for (int i = 0, len = rawData.length; i < len; ++i) { -+ final Object raw = rawData[i]; -+ -+ if (!(raw instanceof ServerPlayer)) { -+ continue; -+ } -+ this.onChunkSendReady((ServerPlayer)raw, chunkX, chunkZ); -+ } -+ } -+ -+ public void onChunkSendReady(final ServerPlayer player, final int chunkX, final int chunkZ) { -+ final PlayerLoaderData data = this.playerMap.get(player); -+ -+ if (data == null) { -+ return; -+ } -+ -+ if (data.hasSentChunk(chunkX, chunkZ) || !this.isChunkPlayerLoaded(chunkX, chunkZ)) { -+ // if we don't have player tickets, then the load logic will pick this up and queue to send -+ return; -+ } -+ -+ 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); -+ final int manhattanDistance = Math.abs(playerChunkX - chunkX) + Math.abs(playerChunkZ - chunkZ); -+ -+ final ChunkPriorityHolder holder = new ChunkPriorityHolder(chunkX, chunkZ, manhattanDistance, 0.0); -+ data.sendQueue.add(holder); -+ } -+ -+ public void onChunkLoad(final int chunkX, final int chunkZ) { -+ if (this.chunkTicketTracker.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { -+ --this.concurrentChunkLoads; -+ } -+ } -+ -+ public void onChunkLeave(final ServerPlayer player, final int chunkX, final int chunkZ) { -+ final PlayerLoaderData data = this.playerMap.get(player); -+ -+ if (data == null) { -+ return; -+ } -+ -+ data.unloadChunk(chunkX, chunkZ); -+ } -+ -+ public void addPlayer(final ServerPlayer player) { -+ TickThread.ensureTickThread("Cannot add player async"); -+ if (!player.isRealPlayer) { -+ return; -+ } -+ final PlayerLoaderData data = new PlayerLoaderData(player, this); -+ if (this.playerMap.putIfAbsent(player, data) == null) { -+ data.update(); -+ } -+ } -+ -+ public void removePlayer(final ServerPlayer player) { -+ TickThread.ensureTickThread("Cannot remove player async"); -+ if (!player.isRealPlayer) { -+ return; -+ } -+ -+ final PlayerLoaderData loaderData = this.playerMap.remove(player); -+ if (loaderData == null) { -+ return; -+ } -+ loaderData.remove(); -+ this.chunkLoadQueue.remove(loaderData); -+ this.chunkSendQueue.remove(loaderData); -+ this.chunkSendWaitQueue.remove(loaderData); -+ synchronized (this.sendingChunkCounts) { -+ final int count = this.sendingChunkCounts.removeInt(loaderData); -+ if (count != 0) { -+ concurrentChunkSends.getAndAdd(-count); -+ } -+ } -+ } -+ -+ public void updatePlayer(final ServerPlayer player) { -+ TickThread.ensureTickThread("Cannot update player async"); -+ if (!player.isRealPlayer) { -+ return; -+ } -+ final PlayerLoaderData loaderData = this.playerMap.get(player); -+ if (loaderData != null) { -+ loaderData.update(); -+ } -+ } -+ -+ public PlayerLoaderData getData(final ServerPlayer player) { -+ return this.playerMap.get(player); -+ } -+ -+ public void tick() { -+ TickThread.ensureTickThread("Cannot tick async"); -+ for (final PlayerLoaderData data : this.playerMap.values()) { -+ data.update(); -+ } -+ this.tickMidTick(); -+ } -+ -+ protected static final AtomicInteger concurrentChunkSends = new AtomicInteger(); -+ protected final Reference2IntOpenHashMap sendingChunkCounts = new Reference2IntOpenHashMap<>(); -+ private static long nextChunkSend; -+ private void trySendChunks() { -+ final long time = System.nanoTime(); -+ if (time < nextChunkSend) { -+ return; -+ } -+ // drain entries from wait queue -+ while (!this.chunkSendWaitQueue.isEmpty()) { -+ final PlayerLoaderData data = this.chunkSendWaitQueue.first(); -+ -+ if (data.nextChunkSendTarget > time) { -+ break; -+ } -+ -+ this.chunkSendWaitQueue.pollFirst(); -+ -+ this.chunkSendQueue.add(data); -+ } -+ -+ if (this.chunkSendQueue.isEmpty()) { -+ return; -+ } -+ -+ final int maxSends = this.getMaxConcurrentChunkSends(); -+ final long nextPlayerDeadline = this.getTargetSendPerPlayerAddend() + time; -+ for (;;) { -+ if (this.chunkSendQueue.isEmpty()) { -+ break; -+ } -+ final int currSends = concurrentChunkSends.get(); -+ if (currSends >= maxSends) { -+ break; -+ } -+ -+ if (!concurrentChunkSends.compareAndSet(currSends, currSends + 1)) { -+ continue; -+ } -+ -+ // send chunk -+ -+ final PlayerLoaderData data = this.chunkSendQueue.removeFirst(); -+ -+ final ChunkPriorityHolder queuedSend = data.sendQueue.pollFirst(); -+ if (queuedSend == null) { -+ concurrentChunkSends.getAndDecrement(); // we never sent, so decrease -+ // stop iterating over players who have nothing to send -+ if (this.chunkSendQueue.isEmpty()) { -+ // nothing left -+ break; -+ } -+ continue; -+ } -+ -+ if (!this.isChunkPlayerLoaded(queuedSend.chunkX, queuedSend.chunkZ)) { -+ throw new IllegalStateException(); -+ } -+ -+ data.nextChunkSendTarget = nextPlayerDeadline; -+ this.chunkSendWaitQueue.add(data); -+ -+ synchronized (this.sendingChunkCounts) { -+ this.sendingChunkCounts.addTo(data, 1); -+ } -+ -+ data.sendChunk(queuedSend.chunkX, queuedSend.chunkZ, () -> { -+ synchronized (this.sendingChunkCounts) { -+ final int count = this.sendingChunkCounts.getInt(data); -+ if (count == 0) { -+ // disconnected, so we don't need to decrement: it will be decremented for us -+ return; -+ } -+ if (count == 1) { -+ this.sendingChunkCounts.removeInt(data); -+ } else { -+ this.sendingChunkCounts.put(data, count - 1); -+ } -+ } -+ -+ concurrentChunkSends.getAndDecrement(); -+ }); -+ -+ nextChunkSend = this.getMaxSendAddend() + time; -+ if (time < nextChunkSend) { -+ break; -+ } -+ } -+ } -+ -+ protected int concurrentChunkLoads; -+ // this interval prevents bursting a lot of chunk loads -+ protected static final IntervalledCounter TICKET_ADDITION_COUNTER_SHORT = new IntervalledCounter((long)(1.0e6 * 50.0)); // 50ms -+ // this interval ensures the rate is kept between ticks correctly -+ protected static final IntervalledCounter TICKET_ADDITION_COUNTER_LONG = new IntervalledCounter((long)(1.0e6 * 1000.0)); // 1000ms -+ private void tryLoadChunks() { -+ if (this.chunkLoadQueue.isEmpty()) { -+ return; -+ } -+ -+ final int maxLoads = this.getMaxChunkLoads(); -+ final long time = System.nanoTime(); -+ boolean updatedCounters = false; -+ for (;;) { -+ final PlayerLoaderData data = this.chunkLoadQueue.pollFirst(); -+ -+ data.lastChunkLoad = time; -+ -+ final ChunkPriorityHolder queuedLoad = data.loadQueue.peekFirst(); -+ if (queuedLoad == null) { -+ if (this.chunkLoadQueue.isEmpty()) { -+ break; -+ } -+ continue; -+ } -+ -+ if (!updatedCounters) { -+ updatedCounters = true; -+ TICKET_ADDITION_COUNTER_SHORT.updateCurrentTime(time); -+ TICKET_ADDITION_COUNTER_LONG.updateCurrentTime(time); -+ data.ticketAdditionCounterShort.updateCurrentTime(time); -+ data.ticketAdditionCounterLong.updateCurrentTime(time); -+ } -+ -+ if (this.isChunkPlayerLoaded(queuedLoad.chunkX, queuedLoad.chunkZ)) { -+ // already loaded! -+ data.loadQueue.pollFirst(); // already loaded so we just skip -+ this.chunkLoadQueue.add(data); -+ -+ // ensure the chunk is queued to send -+ this.onChunkSendReady(queuedLoad.chunkX, queuedLoad.chunkZ); -+ continue; -+ } -+ -+ final long chunkKey = CoordinateUtils.getChunkKey(queuedLoad.chunkX, queuedLoad.chunkZ); -+ -+ final double priority = queuedLoad.priority; -+ // while we do need to rate limit chunk loads, the logic for sending chunks requires that tickets are present. -+ // when chunks are loaded (i.e spawn) but do not have this player's tickets, they have to wait behind the -+ // load queue. To avoid this problem, we check early here if tickets are required to load the chunk - if they -+ // aren't required, it bypasses the limiter system. -+ boolean unloadedTargetChunk = false; -+ unloaded_check: -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ final int offX = queuedLoad.chunkX + dx; -+ final int offZ = queuedLoad.chunkZ + dz; -+ if (this.chunkMap.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(offX, offZ) == null) { -+ unloadedTargetChunk = true; -+ break unloaded_check; -+ } -+ } -+ } -+ if (unloadedTargetChunk && priority >= 0.0) { -+ // priority >= 0.0 implies rate limited chunks -+ -+ final int currentChunkLoads = this.concurrentChunkLoads; -+ if (currentChunkLoads >= maxLoads || (PaperConfig.globalMaxChunkLoadRate > 0 && (TICKET_ADDITION_COUNTER_SHORT.getRate() >= PaperConfig.globalMaxChunkLoadRate || TICKET_ADDITION_COUNTER_LONG.getRate() >= PaperConfig.globalMaxChunkLoadRate)) -+ || (PaperConfig.playerMaxChunkLoadRate > 0.0 && (data.ticketAdditionCounterShort.getRate() >= PaperConfig.playerMaxChunkLoadRate || data.ticketAdditionCounterLong.getRate() >= PaperConfig.playerMaxChunkLoadRate))) { -+ // don't poll, we didn't load it -+ this.chunkLoadQueue.add(data); -+ break; -+ } -+ } -+ -+ // can only poll after we decide to load -+ data.loadQueue.pollFirst(); -+ -+ // now that we've polled we can re-add to load queue -+ this.chunkLoadQueue.add(data); -+ -+ // add necessary tickets to load chunk up to send-ready -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ final int offX = queuedLoad.chunkX + dx; -+ final int offZ = queuedLoad.chunkZ + dz; -+ final ChunkPos chunkPos = new ChunkPos(offX, offZ); -+ -+ this.chunkMap.level.getChunkSource().addTicketAtLevel(TicketType.PLAYER, chunkPos, LOADED_TICKET_LEVEL, chunkPos); -+ if (this.chunkMap.level.getChunkSource().getChunkAtIfLoadedMainThreadNoCache(offX, offZ) != null) { -+ continue; -+ } -+ -+ if (priority > 0.0 && this.chunkTicketTracker.add(CoordinateUtils.getChunkKey(offX, offZ))) { -+ // won't reach here if unloadedTargetChunk is false -+ ++this.concurrentChunkLoads; -+ TICKET_ADDITION_COUNTER_SHORT.addTime(time); -+ TICKET_ADDITION_COUNTER_LONG.addTime(time); -+ data.ticketAdditionCounterShort.addTime(time); -+ data.ticketAdditionCounterLong.addTime(time); -+ } -+ } -+ } -+ -+ // mark that we've added tickets here -+ this.isTargetedForPlayerLoad.add(chunkKey); -+ -+ // it's possible all we needed was the player tickets to queue up the send. -+ if (this.isChunkPlayerLoaded(queuedLoad.chunkX, queuedLoad.chunkZ)) { -+ // yup, all we needed. -+ this.onChunkSendReady(queuedLoad.chunkX, queuedLoad.chunkZ); -+ } 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(); -+ } -+ }); -+ } -+ } -+ } -+ -+ public void tickMidTick() { -+ // try to send more chunks -+ this.trySendChunks(); -+ -+ // try to queue more chunks to load -+ this.tryLoadChunks(); -+ } -+ -+ static final class ChunkPriorityHolder { -+ public final int chunkX; -+ public final int chunkZ; -+ public final int manhattanDistanceToPlayer; -+ public final double priority; -+ -+ public ChunkPriorityHolder(final int chunkX, final int chunkZ, final int manhattanDistanceToPlayer, final double priority) { -+ this.chunkX = chunkX; -+ this.chunkZ = chunkZ; -+ this.manhattanDistanceToPlayer = manhattanDistanceToPlayer; -+ this.priority = priority; -+ } -+ } -+ -+ public static final class PlayerLoaderData { -+ -+ protected static final float FOV = 110.0f; -+ protected static final double PRIORITISED_DISTANCE = 12.0 * 16.0; -+ -+ // Player max sprint speed is approximately 8m/s -+ protected static final double LOOK_PRIORITY_SPEED_THRESHOLD = (10.0/20.0) * (10.0/20.0); -+ protected static final double LOOK_PRIORITY_YAW_DELTA_RECALC_THRESHOLD = 3.0f; -+ -+ protected double lastLocX = Double.NEGATIVE_INFINITY; -+ protected double lastLocZ = Double.NEGATIVE_INFINITY; -+ -+ protected int lastChunkX = 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; -+ -+ protected int lastSendDistance = Integer.MIN_VALUE; -+ protected int lastLoadDistance = Integer.MIN_VALUE; -+ protected int lastTickDistance = Integer.MIN_VALUE; -+ protected boolean usingLookingPriority; -+ -+ protected final ServerPlayer player; -+ protected final PlayerChunkLoader loader; -+ -+ // warning: modifications of this field must be aware that the loadQueue inside PlayerChunkLoader uses this field -+ // in a comparator! -+ protected final ArrayDeque loadQueue = new ArrayDeque<>(); -+ protected final LongOpenHashSet sentChunks = new LongOpenHashSet(); -+ protected final 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); -+ if (distanceCompare != 0) { -+ return distanceCompare; -+ } -+ -+ final int coordinateXCompare = Integer.compare(p1.chunkX, p2.chunkX); -+ if (coordinateXCompare != 0) { -+ return coordinateXCompare; -+ } -+ -+ return Integer.compare(p1.chunkZ, p2.chunkZ); -+ }); -+ -+ protected int sendViewDistance = -1; -+ protected int loadViewDistance = -1; -+ protected int tickViewDistance = -1; -+ -+ protected long nextChunkSendTarget; -+ -+ // this interval prevents bursting a lot of chunk loads -+ protected final IntervalledCounter ticketAdditionCounterShort = new IntervalledCounter((long)(1.0e6 * 50.0)); // 50ms -+ // this ensures the rate is kept between ticks correctly -+ protected final IntervalledCounter ticketAdditionCounterLong = new IntervalledCounter((long)(1.0e6 * 1000.0)); // 1000ms -+ -+ public long lastChunkLoad; -+ -+ public PlayerLoaderData(final ServerPlayer player, final PlayerChunkLoader loader) { -+ this.player = player; -+ this.loader = loader; -+ } -+ -+ // these view distance methods are for api -+ public int getTargetSendViewDistance() { -+ final int tickViewDistance = this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance; -+ final int loadViewDistance = Math.max(tickViewDistance + 1, this.loadViewDistance == -1 ? this.loader.getLoadDistance() : this.loadViewDistance); -+ final int clientViewDistance = this.getClientViewDistance(); -+ final int sendViewDistance = Math.min(loadViewDistance, this.sendViewDistance == -1 ? (!PaperConfig.playerAutoConfigureSendViewDistance || clientViewDistance == -1 ? this.loader.getSendDistance() : clientViewDistance + 1) : this.sendViewDistance); -+ return sendViewDistance; -+ } -+ -+ public void setTargetSendViewDistance(final int distance) { -+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE + 1)) { -+ throw new IllegalArgumentException("Send view distance must be a number between " + MIN_VIEW_DISTANCE + " and " + (MAX_VIEW_DISTANCE + 1) + " or -1, got: " + distance); -+ } -+ this.sendViewDistance = distance; -+ } -+ -+ public int getTargetNoTickViewDistance() { -+ return (this.loadViewDistance == -1 ? this.getLoadDistance() : this.loadViewDistance) - 1; -+ } -+ -+ public void setTargetNoTickViewDistance(final int distance) { -+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE)) { -+ throw new IllegalArgumentException("Simulation distance must be a number between " + MIN_VIEW_DISTANCE + " and " + MAX_VIEW_DISTANCE + " or -1, got: " + distance); -+ } -+ this.loadViewDistance = distance == -1 ? -1 : distance + 1; -+ } -+ -+ public int getTargetTickViewDistance() { -+ return this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance; -+ } -+ -+ public void setTargetTickViewDistance(final int distance) { -+ if (distance != -1 && (distance < MIN_VIEW_DISTANCE || distance > MAX_VIEW_DISTANCE)) { -+ throw new IllegalArgumentException("View distance must be a number between " + MIN_VIEW_DISTANCE + " and " + MAX_VIEW_DISTANCE + " or -1, got: " + distance); -+ } -+ this.tickViewDistance = distance; -+ } -+ -+ protected int getLoadDistance() { -+ final int tickViewDistance = this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance; -+ -+ return Math.max(tickViewDistance + 1, this.loadViewDistance == -1 ? this.loader.getLoadDistance() : this.loadViewDistance); -+ } -+ -+ public boolean hasSentChunk(final int chunkX, final int chunkZ) { -+ return this.sentChunks.contains(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } -+ -+ public void sendChunk(final int chunkX, final int chunkZ, final Runnable onChunkSend) { -+ if (this.sentChunks.add(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { -+ this.player.getLevel().getChunkSource().chunkMap.updateChunkTracking(this.player, -+ new ChunkPos(chunkX, chunkZ), new MutableObject<>(), false, true); // unloaded, loaded -+ this.player.connection.connection.execute(onChunkSend); -+ } else { -+ throw new IllegalStateException(); -+ } -+ } -+ -+ public void unloadChunk(final int chunkX, final int chunkZ) { -+ if (this.sentChunks.remove(CoordinateUtils.getChunkKey(chunkX, chunkZ))) { -+ this.player.getLevel().getChunkSource().chunkMap.updateChunkTracking(this.player, -+ new ChunkPos(chunkX, chunkZ), null, true, false); // unloaded, loaded -+ } -+ } -+ -+ protected static boolean 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 -+ -+ final double targetX, final double targetZ) { // point -+ // from barycentric coordinates: -+ // targetX = a*p1x + b*p2x + c*p3x -+ // targetZ = a*p1z + b*p2z + c*p3z -+ // 1.0 = a*1.0 + b*1.0 + c*1.0 -+ // where a, b, c >= 0.0 -+ // so, if any of a, b, c are less-than zero then there is no intersection. -+ -+ // d = ((p2z - p3z)(p1x - p3x) + (p3x - p2x)(p1z - p3z)) -+ // a = ((p2z - p3z)(targetX - p3x) + (p3x - p2x)(targetZ - p3z)) / d -+ // b = ((p3z - p1z)(targetX - p3x) + (p1x - p3x)(targetZ - p3z)) / d -+ // c = 1.0 - a - b -+ -+ final double d = (p2z - p3z)*(p1x - p3x) + (p3x - p2x)*(p1z - p3z); -+ final double a = ((p2z - p3z)*(targetX - p3x) + (p3x - p2x)*(targetZ - p3z)) / d; -+ -+ if (a < 0.0 || a > 1.0) { -+ return false; -+ } -+ -+ final double b = ((p3z - p1z)*(targetX - p3x) + (p1x - p3x)*(targetZ - p3z)) / d; -+ if (b < 0.0 || b > 1.0) { -+ return false; -+ } -+ -+ final double c = 1.0 - a - b; -+ -+ return c >= 0.0 && c <= 1.0; -+ } -+ -+ public void remove() { -+ this.loader.broadcastMap.remove(this.player); -+ this.loader.loadMap.remove(this.player); -+ this.loader.loadTicketCleanup.remove(this.player); -+ this.loader.tickMap.remove(this.player); -+ } -+ -+ protected int getClientViewDistance() { -+ return this.player.clientViewDistance == null ? -1 : Math.max(0, this.player.clientViewDistance.intValue()); -+ } -+ -+ public void update() { -+ final int tickViewDistance = this.tickViewDistance == -1 ? this.loader.getTickDistance() : this.tickViewDistance; -+ // load view cannot be less-than tick view + 1 -+ final int loadViewDistance = Math.max(tickViewDistance + 1, this.loadViewDistance == -1 ? this.loader.getLoadDistance() : this.loadViewDistance); -+ // send view cannot be greater-than load view -+ final int clientViewDistance = this.getClientViewDistance(); -+ final int sendViewDistance = Math.min(loadViewDistance, this.sendViewDistance == -1 ? (!PaperConfig.playerAutoConfigureSendViewDistance || clientViewDistance == -1 ? this.loader.getSendDistance() : clientViewDistance + 1) : this.sendViewDistance); -+ -+ final double posX = this.player.getX(); -+ final double posZ = this.player.getZ(); -+ final float yaw = MCUtil.normalizeYaw(this.player.yRot + 90.0f); // mc yaw 0 is along the positive z axis, but obviously this is really dumb - offset so we are at positive x-axis -+ -+ // in general, we really only want to prioritise chunks in front if we know we're moving pretty fast into them. -+ final boolean useLookPriority = PaperConfig.playerFrustumPrioritisation && (this.player.getDeltaMovement().horizontalDistanceSqr() > LOOK_PRIORITY_SPEED_THRESHOLD || -+ this.player.getAbilities().flying); -+ -+ // make sure we're in the send queue -+ this.loader.chunkSendWaitQueue.add(this); -+ -+ if ( -+ // has view distance stayed the same? -+ sendViewDistance == this.lastSendDistance -+ && loadViewDistance == this.lastLoadDistance -+ && tickViewDistance == this.lastTickDistance -+ -+ && (this.usingLookingPriority ? ( -+ // has our block stayed the same (this also accounts for chunk change)? -+ Mth.floor(this.lastLocX) == Mth.floor(posX) -+ && Mth.floor(this.lastLocZ) == Mth.floor(posZ) -+ ) : ( -+ // has our chunk stayed the same -+ (Mth.floor(this.lastLocX) >> 4) == (Mth.floor(posX) >> 4) -+ && (Mth.floor(this.lastLocZ) >> 4) == (Mth.floor(posZ) >> 4) -+ )) -+ -+ // has our decision about look priority changed? -+ && this.usingLookingPriority == useLookPriority -+ -+ // if we are currently using look priority, has our yaw stayed within recalc threshold? -+ && (!this.usingLookingPriority || Math.abs(yaw - this.lastYaw) <= LOOK_PRIORITY_YAW_DELTA_RECALC_THRESHOLD) -+ ) { -+ // nothing we care about changed, so we're not re-calculating -+ return; -+ } -+ -+ final int centerChunkX = Mth.floor(posX) >> 4; -+ final int centerChunkZ = Mth.floor(posZ) >> 4; -+ -+ final boolean needsChunkCenterUpdate = (centerChunkX != this.lastChunkX) || (centerChunkZ != this.lastChunkZ); -+ this.loader.broadcastMap.addOrUpdate(this.player, centerChunkX, centerChunkZ, sendViewDistance); -+ this.loader.loadMap.addOrUpdate(this.player, centerChunkX, centerChunkZ, loadViewDistance); -+ this.loader.loadTicketCleanup.addOrUpdate(this.player, centerChunkX, centerChunkZ, loadViewDistance + 1); -+ this.loader.tickMap.addOrUpdate(this.player, centerChunkX, centerChunkZ, tickViewDistance); -+ -+ if (sendViewDistance != this.lastSendDistance) { -+ // update the view radius for client -+ // note that this should be after the map calls because the client wont expect unload calls not in its VD -+ // and it's possible we decreased VD here -+ this.player.connection.send(new ClientboundSetChunkCacheRadiusPacket(sendViewDistance)); -+ } -+ if (tickViewDistance != this.lastTickDistance) { -+ this.player.connection.send(new ClientboundSetSimulationDistancePacket(tickViewDistance)); -+ } -+ -+ this.lastLocX = posX; -+ this.lastLocZ = posZ; -+ this.lastYaw = yaw; -+ this.lastSendDistance = sendViewDistance; -+ this.lastLoadDistance = loadViewDistance; -+ this.lastTickDistance = tickViewDistance; -+ this.usingLookingPriority = useLookPriority; -+ -+ this.lastChunkX = centerChunkX; -+ this.lastChunkZ = centerChunkZ; -+ -+ // points for player "view" triangle: -+ -+ // obviously, the player pos is a vertex -+ final double p1x = posX; -+ final double p1z = posZ; -+ -+ // to the left of the looking direction -+ final double p2x = PRIORITISED_DISTANCE * Math.cos(Math.toRadians(yaw + (double)(FOV / 2.0))) // calculate rotated vector -+ + p1x; // offset vector -+ final double p2z = PRIORITISED_DISTANCE * Math.sin(Math.toRadians(yaw + (double)(FOV / 2.0))) // calculate rotated vector -+ + p1z; // offset vector -+ -+ // to the right of the looking direction -+ final double p3x = PRIORITISED_DISTANCE * Math.cos(Math.toRadians(yaw - (double)(FOV / 2.0))) // calculate rotated vector -+ + p1x; // offset vector -+ final double p3z = PRIORITISED_DISTANCE * Math.sin(Math.toRadians(yaw - (double)(FOV / 2.0))) // calculate rotated vector -+ + p1z; // offset vector -+ -+ // now that we have all of our points, we can recalculate the load queue -+ -+ final List loadQueue = new ArrayList<>(); -+ -+ // clear send queue, we are re-sorting -+ this.sendQueue.clear(); -+ // clear chunk want set, vd/position might have changed -+ this.chunksToBeSent.clear(); -+ -+ final int searchViewDistance = Math.max(loadViewDistance, sendViewDistance); -+ -+ for (int dx = -searchViewDistance; dx <= searchViewDistance; ++dx) { -+ for (int dz = -searchViewDistance; dz <= searchViewDistance; ++dz) { -+ final int chunkX = dx + centerChunkX; -+ final int chunkZ = dz + centerChunkZ; -+ final int squareDistance = Math.max(Math.abs(dx), Math.abs(dz)); -+ 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 prioritised = useLookPriority && triangleIntersects( -+ // prioritisation triangle -+ p1x, p1z, p2x, p2z, p3x, p3z, -+ -+ // center of chunk -+ (double)((chunkX << 4) | 8), (double)((chunkZ << 4) | 8) -+ ); -+ -+ final int manhattanDistance = Math.abs(dx) + Math.abs(dz); -+ -+ final double priority; -+ -+ if (squareDistance <= 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) - manhattanDistance); -+ } else { -+ if (prioritised) { -+ // we don't prioritise these chunks above others because we also want to make sure some chunks -+ // will be loaded if the player changes direction -+ priority = (double)manhattanDistance / 6.0; -+ } else { -+ priority = (double)manhattanDistance; -+ } -+ } -+ -+ final ChunkPriorityHolder holder = new ChunkPriorityHolder(chunkX, chunkZ, manhattanDistance, priority); -+ -+ if (!this.loader.isChunkPlayerLoaded(chunkX, chunkZ)) { -+ if (loadChunk) { -+ loadQueue.add(holder); -+ if (sendChunk) { -+ this.chunksToBeSent.add(CoordinateUtils.getChunkKey(chunkX, chunkZ)); -+ } -+ } -+ } else { -+ // loaded but not sent: so queue it! -+ if (sendChunk) { -+ this.sendQueue.add(holder); -+ } -+ } -+ } -+ } -+ -+ loadQueue.sort((final ChunkPriorityHolder p1, final ChunkPriorityHolder p2) -> { -+ return Double.compare(p1.priority, p2.priority); -+ }); -+ -+ // we're modifying loadQueue, must remove -+ this.loader.chunkLoadQueue.remove(this); -+ -+ this.loadQueue.clear(); -+ this.loadQueue.addAll(loadQueue); -+ -+ // must re-add -+ this.loader.chunkLoadQueue.add(this); -+ -+ // 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/network/Connection.java -+++ b/src/main/java/net/minecraft/network/Connection.java -@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler> { - public boolean queueImmunity = false; - public ConnectionProtocol protocol; - // Paper end -+ // Paper start - add pending task queue -+ private final Queue pendingTasks = new java.util.concurrent.ConcurrentLinkedQueue<>(); -+ public void execute(final Runnable run) { -+ if (this.channel == null || !this.channel.isRegistered()) { -+ run.run(); -+ return; -+ } -+ final boolean queue = !this.queue.isEmpty(); -+ if (!queue) { -+ this.channel.eventLoop().execute(run); -+ } else { -+ this.pendingTasks.add(run); -+ if (this.queue.isEmpty()) { -+ // something flushed async, dump tasks now -+ Runnable r; -+ while ((r = this.pendingTasks.poll()) != null) { -+ this.channel.eventLoop().execute(r); -+ } -+ } -+ } -+ } -+ // Paper end - add pending task queue - - // Paper start - allow controlled flushing - volatile boolean canFlush = true; -@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler> { - return false; - } - private boolean processQueue() { -+ try { // Paper - add pending task queue - if (this.queue.isEmpty()) return true; - // Paper start - make only one flush call per sendPacketQueue() call - final boolean needsFlush = this.canFlush; -@@ -0,0 +0,0 @@ public class Connection extends SimpleChannelInboundHandler> { - } - } - return true; -+ } finally { // Paper start - add pending task queue -+ Runnable r; -+ while ((r = this.pendingTasks.poll()) != null) { -+ this.channel.eventLoop().execute(r); -+ } -+ } // Paper end - add pending task queue - } - // Paper end - -diff --git a/src/main/java/net/minecraft/server/MCUtil.java b/src/main/java/net/minecraft/server/MCUtil.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/MCUtil.java -+++ b/src/main/java/net/minecraft/server/MCUtil.java -@@ -0,0 +0,0 @@ public final class MCUtil { - }); - - worldData.addProperty("name", world.getWorld().getName()); -- 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkHolder.java -+++ b/src/main/java/net/minecraft/server/level/ChunkHolder.java -@@ -0,0 +0,0 @@ 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 -@@ -0,0 +0,0 @@ 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()); -@@ -0,0 +0,0 @@ public class ChunkHolder { - } - - public void sectionLightChanged(LightLayer lightType, int y) { -- LevelChunk chunk = this.getFullChunk(); -+ ChunkAccess chunk = this.getAvailableChunkNow(); // Paper - no-tick view distance - - if (chunk != null) { - chunk.setUnsaved(true); -- LevelChunk chunk1 = this.getTickingChunk(); -+ LevelChunk chunk1 = this.getSendingChunk(); // Paper - no-tick view distance - - if (chunk1 != null) { - int j = this.lightEngine.getMinLightSection(); -@@ -0,0 +0,0 @@ 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; -+ } -+ -+ 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 - } - - 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ChunkMap.java -+++ b/src/main/java/net/minecraft/server/level/ChunkMap.java -@@ -0,0 +0,0 @@ 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; -@@ -0,0 +0,0 @@ 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 -@@ -0,0 +0,0 @@ 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, io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player))); // Paper - per player view distances - } - // Paper end - use distance map to optimise entity tracker - // Note: players need to be explicitly added to distance maps before they can be updated -@@ -0,0 +0,0 @@ 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) { -@@ -0,0 +0,0 @@ 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, io.papermc.paper.chunk.PlayerChunkLoader.getSendViewDistance(player))); // Paper - per player view distances - } - // Paper end - use distance map to optimise entity tracker - this.playerChunkTickRangeMap.update(player, chunkX, chunkZ, DistanceManager.MOB_SPAWN_RANGE); // Paper - optimise ChunkMap#anyPlayerCloseEnoughForSpawning -@@ -0,0 +0,0 @@ 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 -@@ -0,0 +0,0 @@ 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 -- -- 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)); -@@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider - int k = this.viewDistance; - - this.viewDistance = j; -- 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(this.viewDistance); // Paper - replace player loader system - } - - } - -- 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); -@@ -0,0 +0,0 @@ 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()) { -@@ -0,0 +0,0 @@ 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(); -@@ -0,0 +0,0 @@ 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.updateChunkTracking(player, chunkcoordintpair, new MutableObject(), !added, added); -- } -- } -- } -+ // Paper - handled by player chunk loader - - } - -@@ -0,0 +0,0 @@ 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; - } - -@@ -0,0 +0,0 @@ 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 - - } - - @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); - -- 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; -+ } - -- 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); - } - -- return builder.build(); -+ return ret; -+ // Paper end - per player view distance - } - - public void addEntity(Entity entity) { -@@ -0,0 +0,0 @@ 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 -- 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/DistanceManager.java -+++ b/src/main/java/net/minecraft/server/level/DistanceManager.java -@@ -0,0 +0,0 @@ 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 -@@ -0,0 +0,0 @@ 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; - }; -@@ -0,0 +0,0 @@ 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 - } - } - -@@ -0,0 +0,0 @@ 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; - -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - long j = chunkcoordintpair.toLong(); - - boolean added = this.addTicket(j, ticket); // CraftBukkit -- this.tickingTicketsTracker.addTicket(j, ticket); -+ //this.tickingTicketsTracker.addTicket(j, ticket); // Paper - no longer used - return added; // CraftBukkit - } - -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - long j = chunkcoordintpair.toLong(); - - boolean removed = this.removeTicket(j, ticket); // CraftBukkit -- this.tickingTicketsTracker.removeTicket(j, ticket); -+ //this.tickingTicketsTracker.removeTicket(j, ticket); // Paper - no longer used - return removed; // CraftBukkit - } - -@@ -0,0 +0,0 @@ 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 - } - - } -@@ -0,0 +0,0 @@ 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) { -@@ -0,0 +0,0 @@ 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 - } - - } -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - } - - 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 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) { -@@ -0,0 +0,0 @@ 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 - -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - - } - -- @VisibleForTesting -- TickingTracker tickingTracker() { -- return this.tickingTicketsTracker; -- } -+ // Paper - replace player chunk loader - - public void removeTicketsOnClosing() { - ImmutableSet> immutableset = ImmutableSet.of(TicketType.UNKNOWN, TicketType.POST_TELEPORT, TicketType.LIGHT, TicketType.FUTURE_AWAIT, TicketType.ASYNC_LOAD, TicketType.REQUIRED_LOAD, TicketType.CHUNK_RELIGHT, ca.spottedleaf.starlight.common.light.StarLightInterface.CHUNK_WORK_TICKET); // Paper - add additional tickets to preserve -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - if (!immutableset.contains(ticket.getType())) { - iterator.remove(); - flag = true; -- this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket); -+ // this.tickingTicketsTracker.removeTicket(entry.getLongKey(), ticket); // Paper - no longer used - } - } - - 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 -+ // this.ticketTracker.update(entry.getLongKey(), DistanceManager.getTicketLevelAt((SortedArraySet) entry.getValue()), false); // Paper - no longer used - } - - if (((SortedArraySet) entry.getValue()).isEmpty()) { -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - } - // CraftBukkit end - -+ /* Paper - replace old loader system - private class ChunkTicketTracker extends ChunkTracker { - - public ChunkTicketTracker() { -@@ -0,0 +0,0 @@ public abstract class DistanceManager { - return distance <= this.viewDistance - 2; - } - } -+ */ // 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ServerChunkCache.java -+++ b/src/main/java/net/minecraft/server/level/ServerChunkCache.java -@@ -0,0 +0,0 @@ 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) { -@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { - this.level.getProfiler().popPush("chunks"); - if (tickChunks) { - this.level.timings.chunks.startTiming(); // Paper - timings -+ this.chunkMap.playerChunkManager.tick(); // Paper - this is mostly is to account for view distance changes - this.tickChunks(); - this.level.timings.chunks.stopTiming(); // Paper - timings - } -@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { - // Paper end - optimise chunk tick iteration - ChunkPos chunkcoordintpair = chunk1.getPos(); - -- if (this.level.isNaturalSpawningAllowed(chunkcoordintpair) && this.chunkMap.anyPlayerCloseEnoughForSpawning(holder, chunkcoordintpair, false)) { // Paper - optimise anyPlayerCloseEnoughForSpawning -+ if ((true || this.level.isNaturalSpawningAllowed(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 - } -@@ -0,0 +0,0 @@ public class ServerChunkCache extends ChunkSource { - public boolean pollTask() { - try { - boolean execChunkTask = com.destroystokyo.paper.io.chunk.ChunkTaskManager.pollChunkWaitQueue() || ServerChunkCache.this.level.asyncChunkTaskManager.pollNextChunkTask(); // Paper -+ ServerChunkCache.this.chunkMap.playerChunkManager.tickMidTick(); // Paper - 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ServerLevel.java -+++ b/src/main/java/net/minecraft/server/level/ServerLevel.java -@@ -0,0 +0,0 @@ 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) { -@@ -0,0 +0,0 @@ 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() { -@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/level/ServerPlayer.java -+++ b/src/main/java/net/minecraft/server/level/ServerPlayer.java -@@ -0,0 +0,0 @@ public class ServerPlayer extends Player { - } - // CraftBukkit end - -- public final int getViewDistance() { return this.getLevel().getChunkSource().chunkMap.viewDistance - 1; } // Paper - placeholder -+ public final int getViewDistance() { throw new UnsupportedOperationException("Use PlayerChunkLoader"); } // Paper - placeholder - } -diff --git a/src/main/java/net/minecraft/server/players/PlayerList.java b/src/main/java/net/minecraft/server/players/PlayerList.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/server/players/PlayerList.java -+++ b/src/main/java/net/minecraft/server/players/PlayerList.java -@@ -0,0 +0,0 @@ public abstract class PlayerList { - boolean flag1 = gamerules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO); - - // Spigot - view distance -- playerconnection.send(new ClientboundLoginPacket(player.getId(), worlddata.isHardcore(), player.gameMode.getGameModeForPlayer(), player.gameMode.getPreviousGameModeForPlayer(), this.server.levelKeys(), this.registryHolder, worldserver1.dimensionTypeRegistration(), 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.dimensionTypeRegistration(), 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())); -@@ -0,0 +0,0 @@ public abstract class PlayerList { - // CraftBukkit start - LevelData worlddata = worldserver1.getLevelData(); - entityplayer1.connection.send(new ClientboundRespawnPacket(worldserver1.dimensionTypeRegistration(), worldserver1.dimension(), BiomeManager.obfuscateSeed(worldserver1.getSeed()), entityplayer1.gameMode.getGameModeForPlayer(), entityplayer1.gameMode.getPreviousGameModeForPlayer(), worldserver1.isDebug(), worldserver1.isFlat(), flag)); -- 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())); -@@ -0,0 +0,0 @@ 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()) { -@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 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 -@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 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 -@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/item/EnderEyeItem.java -+++ b/src/main/java/net/minecraft/world/item/EnderEyeItem.java -@@ -0,0 +0,0 @@ public class EnderEyeItem extends Item { - - // CraftBukkit start - Use relative location for far away sounds - // world.b(1038, blockposition1.c(1, 0, 1), 0); -- int viewDistance = world.getCraftServer().getViewDistance() * 16; -+ //int viewDistance = world.getCraftServer().getViewDistance() * 16; // Paper - apply view distance patch - BlockPos soundPos = blockposition1.offset(1, 0, 1); - for (ServerPlayer player : world.getServer().getPlayerList().players) { -+ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/level/Level.java -+++ b/src/main/java/net/minecraft/world/level/Level.java -@@ -0,0 +0,0 @@ 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.playerChunkManager.broadcastMap.getObjectsInRange(MCUtil.getCoordinateKey(blockposition)) != null)) { // Paper - replace old player chunk management -+ ((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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -+++ b/src/main/java/net/minecraft/world/level/chunk/LevelChunk.java -@@ -0,0 +0,0 @@ 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.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 -+ } -+ }); -+ } -+ } -+ -+ // this code handles the chunk sending -+ if (!areNeighboursLoaded(bitsetBefore, 1) && areNeighboursLoaded(bitsetAfter, 1)) { -+ // Paper start - replace old player chunk loading system -+ 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 - } - - public final boolean isAnyNeighborsLoaded() { -@@ -0,0 +0,0 @@ public class LevelChunk extends ChunkAccess { - // Paper end - neighbour cache - org.bukkit.Server server = this.level.getCraftServer(); - this.level.getChunkSource().addLoadedChunk(this); // Paper -+ ((ServerLevel)this.level).getChunkSource().chunkMap.playerChunkManager.onChunkLoad(this.chunkPos.x, this.chunkPos.z); // Paper - rewrite player chunk management - if (server != null) { - /* - * If it's a new world, the first few chunks are generated inside -@@ -0,0 +0,0 @@ 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) { -@@ -0,0 +0,0 @@ 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 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -+++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java -@@ -0,0 +0,0 @@ public class CraftWorld extends CraftRegionAccessor implements World { - // Spigot start - @Override - public int getViewDistance() { -- return world.spigotConfig.viewDistance; -+ return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetNoTickViewDistance(); // Paper - replace old player chunk management - } - - @Override - public int getSimulationDistance() { -- return world.spigotConfig.simulationDistance; -+ return getHandle().getChunkSource().chunkMap.playerChunkManager.getTargetTickViewDistance(); // Paper - replace old player chunk management - } - // Spigot end - // Paper start - view distance api - @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 setSimulationDistance(int simulationDistance) { -- throw new UnsupportedOperationException(); //TODO -+ // 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 - } - // Paper end - view distance api - -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 { - } - } - -+ // Paper start - implement view distances - @Override - public int getViewDistance() { -- throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO -+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; -+ 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 setViewDistance(int viewDistance) { -- throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO -+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; -+ 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.setTargetNoTickViewDistance(viewDistance); - } - - @Override - public int getSimulationDistance() { -- throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO -+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; -+ io.papermc.paper.chunk.PlayerChunkLoader.PlayerLoaderData data = chunkMap.playerChunkManager.getData(this.getHandle()); -+ if (data == null) { -+ return chunkMap.playerChunkManager.getTargetTickViewDistance(); -+ } -+ return data.getTargetTickViewDistance(); - } - - @Override - public void setSimulationDistance(int simulationDistance) { -- throw new NotImplementedException("Per-Player View Distance APIs need further understanding to properly implement (There are per world view distances though!)"); // TODO -+ net.minecraft.server.level.ChunkMap chunkMap = this.getHandle().getLevel().getChunkSource().chunkMap; -+ 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(simulationDistance); - } - - @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 -+ 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.getTargetSendDistance(); -+ } -+ return data.getTargetSendViewDistance(); - } - - @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 -+ 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.setTargetSendViewDistance(viewDistance); - } -+ // Paper end - implement view distances - - @Override - public T getClientOption(com.destroystokyo.paper.ClientOption type) {