From 428127a055eef56d66491756832ec9300f2712fe Mon Sep 17 00:00:00 2001 From: Nassim Jahnke Date: Wed, 8 Jun 2022 12:20:57 +0200 Subject: [PATCH] More more more more more more more patches --- ...Add-API-for-resetting-a-single-score.patch | 0 .../server/Add-BlockBreakBlockEvent.patch | 2 +- .../Add-ItemFactory-getMonsterEgg-API.patch | 0 .../Add-Raw-Byte-Entity-Serialization.patch | 0 .../server/Add-advancement-display-API.patch | 0 .../Add-back-EntityPortalExitEvent.patch | 0 .../server/Add-critical-damage-API.patch | 0 ...Collidable-methods-to-various-places.patch | 0 ...o-find-targets-for-lightning-strikes.patch | 0 ...d-missing-team-sidebar-display-slots.patch | 0 .../server/Add-more-async-catchers.patch | 0 ...aper-mobcaps-and-paper-playermobcaps.patch | 2 +- .../server/Added-EntityDamageItemEvent.patch | 4 +- ...trolled-flushing-for-network-manager.patch | 0 ...target-without-changing-other-things.patch | 0 .../Clear-bucket-NBT-after-dispense.patch | 0 ...tem-frame-map-cursor-update-interval.patch | 12 +- ...le-recursion-for-chunkholder-updates.patch | 0 ...-server-to-unload-chunks-at-request-.patch | 0 ...ket-level-changes-when-updating-chun.patch | 0 ...ket-level-changes-while-unloading-pl.patch | 2 +- ...-logic-for-inventories-on-chunk-unlo.patch | 0 ...-profile-lookups-to-worldgen-threads.patch | 0 .../Fix-GameProfileCache-concurrency.patch | 0 ...position-losing-precision-millions-o.patch | 0 ...chunks-refusing-to-unload-at-low-TPS.patch | 0 .../Fix-issues-with-mob-conversion.patch | 0 .../Get-entity-default-attributes.patch | 0 .../{unapplied => }/server/Goat-ram-API.patch | 2 +- .../server/Left-handed-API.patch | 0 ...og-when-the-async-catcher-is-tripped.patch | 0 .../Make-CallbackExecutor-strict-again.patch | 0 .../Make-EntityUnleashEvent-cancellable.patch | 0 .../server/More-CommandBlock-API.patch | 2 +- ...ptimize-indirect-passenger-iteration.patch | 0 ...prevent-NBT-copy-in-smithing-recipes.patch | 0 ...alls-removing-tickets-for-sync-loads.patch | 0 ...ite-entity-bounding-box-lookup-calls.patch | 2 +- ...itize-ResourceLocation-error-logging.patch | 0 .../Vanilla-command-permission-fixes.patch | 9 +- .../server/Optimise-general-POI-access.patch | 998 ------------------ 41 files changed, 19 insertions(+), 1016 deletions(-) rename patches/{unapplied => }/server/Add-API-for-resetting-a-single-score.patch (100%) rename patches/{unapplied => }/server/Add-BlockBreakBlockEvent.patch (98%) rename patches/{unapplied => }/server/Add-ItemFactory-getMonsterEgg-API.patch (100%) rename patches/{unapplied => }/server/Add-Raw-Byte-Entity-Serialization.patch (100%) rename patches/{unapplied => }/server/Add-advancement-display-API.patch (100%) rename patches/{unapplied => }/server/Add-back-EntityPortalExitEvent.patch (100%) rename patches/{unapplied => }/server/Add-critical-damage-API.patch (100%) rename patches/{unapplied => }/server/Add-isCollidable-methods-to-various-places.patch (100%) rename patches/{unapplied => }/server/Add-methods-to-find-targets-for-lightning-strikes.patch (100%) rename patches/{unapplied => }/server/Add-missing-team-sidebar-display-slots.patch (100%) rename patches/{unapplied => }/server/Add-more-async-catchers.patch (100%) rename patches/{unapplied => }/server/Add-paper-mobcaps-and-paper-playermobcaps.patch (99%) rename patches/{unapplied => }/server/Added-EntityDamageItemEvent.patch (93%) rename patches/{unapplied => }/server/Allow-controlled-flushing-for-network-manager.patch (100%) rename patches/{unapplied => }/server/Change-EnderEye-target-without-changing-other-things.patch (100%) rename patches/{unapplied => }/server/Clear-bucket-NBT-after-dispense.patch (100%) rename patches/{unapplied => }/server/Configurable-item-frame-map-cursor-update-interval.patch (64%) rename patches/{unapplied => }/server/Correctly-handle-recursion-for-chunkholder-updates.patch (100%) rename patches/{unapplied => }/server/Do-not-allow-the-server-to-unload-chunks-at-request-.patch (100%) rename patches/{unapplied => }/server/Do-not-allow-ticket-level-changes-when-updating-chun.patch (100%) rename patches/{unapplied => }/server/Do-not-allow-ticket-level-changes-while-unloading-pl.patch (90%) rename patches/{unapplied => }/server/Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch (100%) rename patches/{unapplied => }/server/Do-not-submit-profile-lookups-to-worldgen-threads.patch (100%) rename patches/{unapplied => }/server/Fix-GameProfileCache-concurrency.patch (100%) rename patches/{unapplied => }/server/Fix-block-drops-position-losing-precision-millions-o.patch (100%) rename patches/{unapplied => }/server/Fix-chunks-refusing-to-unload-at-low-TPS.patch (100%) rename patches/{unapplied => }/server/Fix-issues-with-mob-conversion.patch (100%) rename patches/{unapplied => }/server/Get-entity-default-attributes.patch (100%) rename patches/{unapplied => }/server/Goat-ram-API.patch (98%) rename patches/{unapplied => }/server/Left-handed-API.patch (100%) rename patches/{unapplied => }/server/Log-when-the-async-catcher-is-tripped.patch (100%) rename patches/{unapplied => }/server/Make-CallbackExecutor-strict-again.patch (100%) rename patches/{unapplied => }/server/Make-EntityUnleashEvent-cancellable.patch (100%) rename patches/{unapplied => }/server/More-CommandBlock-API.patch (98%) rename patches/{unapplied => }/server/Optimize-indirect-passenger-iteration.patch (100%) rename patches/{unapplied => }/server/Option-to-prevent-NBT-copy-in-smithing-recipes.patch (100%) rename patches/{unapplied => }/server/Prevent-unload-calls-removing-tickets-for-sync-loads.patch (100%) rename patches/{unapplied => }/server/Rewrite-entity-bounding-box-lookup-calls.patch (99%) rename patches/{unapplied => }/server/Sanitize-ResourceLocation-error-logging.patch (100%) rename patches/{unapplied => }/server/Vanilla-command-permission-fixes.patch (92%) delete mode 100644 patches/unapplied/server/Optimise-general-POI-access.patch diff --git a/patches/unapplied/server/Add-API-for-resetting-a-single-score.patch b/patches/server/Add-API-for-resetting-a-single-score.patch similarity index 100% rename from patches/unapplied/server/Add-API-for-resetting-a-single-score.patch rename to patches/server/Add-API-for-resetting-a-single-score.patch diff --git a/patches/unapplied/server/Add-BlockBreakBlockEvent.patch b/patches/server/Add-BlockBreakBlockEvent.patch similarity index 98% rename from patches/unapplied/server/Add-BlockBreakBlockEvent.patch rename to patches/server/Add-BlockBreakBlockEvent.patch index 34d7d82a98..c09def6a4e 100644 --- a/patches/unapplied/server/Add-BlockBreakBlockEvent.patch +++ b/patches/server/Add-BlockBreakBlockEvent.patch @@ -43,8 +43,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - dropResources(iblockdata1, world, blockposition3, tileentity); + dropResources(iblockdata1, world, blockposition3, tileentity, pos); // Paper world.setBlock(blockposition3, Blocks.AIR.defaultBlockState(), 18); + world.gameEvent(GameEvent.BLOCK_DESTROY, blockposition3, GameEvent.Context.of(iblockdata1)); if (!iblockdata1.is(BlockTags.FIRE)) { - world.addDestroyBlockEffect(blockposition3, iblockdata1); diff --git a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java b/src/main/java/net/minecraft/world/level/material/FlowingFluid.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/net/minecraft/world/level/material/FlowingFluid.java diff --git a/patches/unapplied/server/Add-ItemFactory-getMonsterEgg-API.patch b/patches/server/Add-ItemFactory-getMonsterEgg-API.patch similarity index 100% rename from patches/unapplied/server/Add-ItemFactory-getMonsterEgg-API.patch rename to patches/server/Add-ItemFactory-getMonsterEgg-API.patch diff --git a/patches/unapplied/server/Add-Raw-Byte-Entity-Serialization.patch b/patches/server/Add-Raw-Byte-Entity-Serialization.patch similarity index 100% rename from patches/unapplied/server/Add-Raw-Byte-Entity-Serialization.patch rename to patches/server/Add-Raw-Byte-Entity-Serialization.patch diff --git a/patches/unapplied/server/Add-advancement-display-API.patch b/patches/server/Add-advancement-display-API.patch similarity index 100% rename from patches/unapplied/server/Add-advancement-display-API.patch rename to patches/server/Add-advancement-display-API.patch diff --git a/patches/unapplied/server/Add-back-EntityPortalExitEvent.patch b/patches/server/Add-back-EntityPortalExitEvent.patch similarity index 100% rename from patches/unapplied/server/Add-back-EntityPortalExitEvent.patch rename to patches/server/Add-back-EntityPortalExitEvent.patch diff --git a/patches/unapplied/server/Add-critical-damage-API.patch b/patches/server/Add-critical-damage-API.patch similarity index 100% rename from patches/unapplied/server/Add-critical-damage-API.patch rename to patches/server/Add-critical-damage-API.patch diff --git a/patches/unapplied/server/Add-isCollidable-methods-to-various-places.patch b/patches/server/Add-isCollidable-methods-to-various-places.patch similarity index 100% rename from patches/unapplied/server/Add-isCollidable-methods-to-various-places.patch rename to patches/server/Add-isCollidable-methods-to-various-places.patch diff --git a/patches/unapplied/server/Add-methods-to-find-targets-for-lightning-strikes.patch b/patches/server/Add-methods-to-find-targets-for-lightning-strikes.patch similarity index 100% rename from patches/unapplied/server/Add-methods-to-find-targets-for-lightning-strikes.patch rename to patches/server/Add-methods-to-find-targets-for-lightning-strikes.patch diff --git a/patches/unapplied/server/Add-missing-team-sidebar-display-slots.patch b/patches/server/Add-missing-team-sidebar-display-slots.patch similarity index 100% rename from patches/unapplied/server/Add-missing-team-sidebar-display-slots.patch rename to patches/server/Add-missing-team-sidebar-display-slots.patch diff --git a/patches/unapplied/server/Add-more-async-catchers.patch b/patches/server/Add-more-async-catchers.patch similarity index 100% rename from patches/unapplied/server/Add-more-async-catchers.patch rename to patches/server/Add-more-async-catchers.patch diff --git a/patches/unapplied/server/Add-paper-mobcaps-and-paper-playermobcaps.patch b/patches/server/Add-paper-mobcaps-and-paper-playermobcaps.patch similarity index 99% rename from patches/unapplied/server/Add-paper-mobcaps-and-paper-playermobcaps.patch rename to patches/server/Add-paper-mobcaps-and-paper-playermobcaps.patch index b3788c40ee..1a1df8f325 100644 --- a/patches/unapplied/server/Add-paper-mobcaps-and-paper-playermobcaps.patch +++ b/patches/server/Add-paper-mobcaps-and-paper-playermobcaps.patch @@ -292,8 +292,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + // Paper end + - // Paper start - add parameters and int ret type public static void spawnCategoryForChunk(MobCategory group, ServerLevel world, LevelChunk chunk, NaturalSpawner.SpawnPredicate checker, NaturalSpawner.AfterSpawnCallback runner) { + // Paper start - add parameters and int ret type spawnCategoryForChunk(group, world, chunk, checker, runner, Integer.MAX_VALUE, null); diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 diff --git a/patches/unapplied/server/Added-EntityDamageItemEvent.patch b/patches/server/Added-EntityDamageItemEvent.patch similarity index 93% rename from patches/unapplied/server/Added-EntityDamageItemEvent.patch rename to patches/server/Added-EntityDamageItemEvent.patch index f8a6214de1..08ad50844c 100644 --- a/patches/unapplied/server/Added-EntityDamageItemEvent.patch +++ b/patches/server/Added-EntityDamageItemEvent.patch @@ -12,8 +12,8 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 return this.getItem().getMaxDamage(); } -- public boolean hurt(int amount, Random random, @Nullable ServerPlayer player) { -+ public boolean hurt(int amount, Random random, @Nullable LivingEntity player) { // Paper - allow any living entity instead of only ServerPlayers +- public boolean hurt(int amount, RandomSource random, @Nullable ServerPlayer player) { ++ public boolean hurt(int amount, RandomSource random, @Nullable LivingEntity player) { // Paper - allow any living entity instead of only ServerPlayers if (!this.isDamageableItem()) { return false; } else { diff --git a/patches/unapplied/server/Allow-controlled-flushing-for-network-manager.patch b/patches/server/Allow-controlled-flushing-for-network-manager.patch similarity index 100% rename from patches/unapplied/server/Allow-controlled-flushing-for-network-manager.patch rename to patches/server/Allow-controlled-flushing-for-network-manager.patch diff --git a/patches/unapplied/server/Change-EnderEye-target-without-changing-other-things.patch b/patches/server/Change-EnderEye-target-without-changing-other-things.patch similarity index 100% rename from patches/unapplied/server/Change-EnderEye-target-without-changing-other-things.patch rename to patches/server/Change-EnderEye-target-without-changing-other-things.patch diff --git a/patches/unapplied/server/Clear-bucket-NBT-after-dispense.patch b/patches/server/Clear-bucket-NBT-after-dispense.patch similarity index 100% rename from patches/unapplied/server/Clear-bucket-NBT-after-dispense.patch rename to patches/server/Clear-bucket-NBT-after-dispense.patch diff --git a/patches/unapplied/server/Configurable-item-frame-map-cursor-update-interval.patch b/patches/server/Configurable-item-frame-map-cursor-update-interval.patch similarity index 64% rename from patches/unapplied/server/Configurable-item-frame-map-cursor-update-interval.patch rename to patches/server/Configurable-item-frame-map-cursor-update-interval.patch index e9563f46a6..f8e205780b 100644 --- a/patches/unapplied/server/Configurable-item-frame-map-cursor-update-interval.patch +++ b/patches/server/Configurable-item-frame-map-cursor-update-interval.patch @@ -25,11 +25,11 @@ 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 { - ItemFrame entityitemframe = (ItemFrame) this.entity; - ItemStack itemstack = entityitemframe.getItem(); + if (true || this.tickCount % 10 == 0) { // CraftBukkit - Moved below, should always enter this block + ItemStack itemstack = entityitemframe.getItem(); -- if (this.tickCount % 10 == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks -+ if (this.level.paperConfig.mapItemFrameCursorUpdateInterval > 0 && this.tickCount % this.level.paperConfig.mapItemFrameCursorUpdateInterval == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks // Paper - Make item frame map cursor update interval configurable - Integer integer = MapItem.getMapId(itemstack); - MapItemSavedData worldmap = MapItem.getSavedData(integer, this.level); +- if (this.tickCount % 10 == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks ++ if (this.level.paperConfig.mapItemFrameCursorUpdateInterval > 0 && this.tickCount % this.level.paperConfig.mapItemFrameCursorUpdateInterval == 0 && itemstack.getItem() instanceof MapItem) { // CraftBukkit - Moved this.tickCounter % 10 logic here so item frames do not enter the other blocks // Paper - Make item frame map cursor update interval configurable + Integer integer = MapItem.getMapId(itemstack); + MapItemSavedData worldmap = MapItem.getSavedData(integer, this.level); diff --git a/patches/unapplied/server/Correctly-handle-recursion-for-chunkholder-updates.patch b/patches/server/Correctly-handle-recursion-for-chunkholder-updates.patch similarity index 100% rename from patches/unapplied/server/Correctly-handle-recursion-for-chunkholder-updates.patch rename to patches/server/Correctly-handle-recursion-for-chunkholder-updates.patch diff --git a/patches/unapplied/server/Do-not-allow-the-server-to-unload-chunks-at-request-.patch b/patches/server/Do-not-allow-the-server-to-unload-chunks-at-request-.patch similarity index 100% rename from patches/unapplied/server/Do-not-allow-the-server-to-unload-chunks-at-request-.patch rename to patches/server/Do-not-allow-the-server-to-unload-chunks-at-request-.patch diff --git a/patches/unapplied/server/Do-not-allow-ticket-level-changes-when-updating-chun.patch b/patches/server/Do-not-allow-ticket-level-changes-when-updating-chun.patch similarity index 100% rename from patches/unapplied/server/Do-not-allow-ticket-level-changes-when-updating-chun.patch rename to patches/server/Do-not-allow-ticket-level-changes-when-updating-chun.patch diff --git a/patches/unapplied/server/Do-not-allow-ticket-level-changes-while-unloading-pl.patch b/patches/server/Do-not-allow-ticket-level-changes-while-unloading-pl.patch similarity index 90% rename from patches/unapplied/server/Do-not-allow-ticket-level-changes-while-unloading-pl.patch rename to patches/server/Do-not-allow-ticket-level-changes-while-unloading-pl.patch index b7cf1719cd..ed009f0a5c 100644 --- a/patches/unapplied/server/Do-not-allow-ticket-level-changes-while-unloading-pl.patch +++ b/patches/server/Do-not-allow-ticket-level-changes-while-unloading-pl.patch @@ -16,7 +16,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 // Paper end + boolean unloadingPlayerChunk = false; // Paper - do not allow ticket level changes while unloading chunks - public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureManager structureManager, Executor executor, BlockableEventLoop mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory, int viewDistance, boolean dsync) { + public ChunkMap(ServerLevel world, LevelStorageSource.LevelStorageAccess session, DataFixer dataFixer, StructureTemplateManager structureTemplateManager, Executor executor, BlockableEventLoop mainThreadExecutor, LightChunkGetter chunkProvider, ChunkGenerator chunkGenerator, ChunkProgressListener worldGenerationProgressListener, ChunkStatusUpdateListener chunkStatusChangeListener, Supplier persistentStateManagerFactory, int viewDistance, boolean dsync) { super(session.getDimensionPath(world.dimension()).resolve("region"), dataFixer, dsync); this.visibleChunkMap = this.updatingChunkMap.clone(); @@ -0,0 +0,0 @@ public class ChunkMap extends ChunkStorage implements ChunkHolder.PlayerProvider diff --git a/patches/unapplied/server/Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch b/patches/server/Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch similarity index 100% rename from patches/unapplied/server/Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch rename to patches/server/Do-not-run-close-logic-for-inventories-on-chunk-unlo.patch diff --git a/patches/unapplied/server/Do-not-submit-profile-lookups-to-worldgen-threads.patch b/patches/server/Do-not-submit-profile-lookups-to-worldgen-threads.patch similarity index 100% rename from patches/unapplied/server/Do-not-submit-profile-lookups-to-worldgen-threads.patch rename to patches/server/Do-not-submit-profile-lookups-to-worldgen-threads.patch diff --git a/patches/unapplied/server/Fix-GameProfileCache-concurrency.patch b/patches/server/Fix-GameProfileCache-concurrency.patch similarity index 100% rename from patches/unapplied/server/Fix-GameProfileCache-concurrency.patch rename to patches/server/Fix-GameProfileCache-concurrency.patch diff --git a/patches/unapplied/server/Fix-block-drops-position-losing-precision-millions-o.patch b/patches/server/Fix-block-drops-position-losing-precision-millions-o.patch similarity index 100% rename from patches/unapplied/server/Fix-block-drops-position-losing-precision-millions-o.patch rename to patches/server/Fix-block-drops-position-losing-precision-millions-o.patch diff --git a/patches/unapplied/server/Fix-chunks-refusing-to-unload-at-low-TPS.patch b/patches/server/Fix-chunks-refusing-to-unload-at-low-TPS.patch similarity index 100% rename from patches/unapplied/server/Fix-chunks-refusing-to-unload-at-low-TPS.patch rename to patches/server/Fix-chunks-refusing-to-unload-at-low-TPS.patch diff --git a/patches/unapplied/server/Fix-issues-with-mob-conversion.patch b/patches/server/Fix-issues-with-mob-conversion.patch similarity index 100% rename from patches/unapplied/server/Fix-issues-with-mob-conversion.patch rename to patches/server/Fix-issues-with-mob-conversion.patch diff --git a/patches/unapplied/server/Get-entity-default-attributes.patch b/patches/server/Get-entity-default-attributes.patch similarity index 100% rename from patches/unapplied/server/Get-entity-default-attributes.patch rename to patches/server/Get-entity-default-attributes.patch diff --git a/patches/unapplied/server/Goat-ram-API.patch b/patches/server/Goat-ram-API.patch similarity index 98% rename from patches/unapplied/server/Goat-ram-API.patch rename to patches/server/Goat-ram-API.patch index 6e3fed2f1b..a69d01fd63 100644 --- a/patches/unapplied/server/Goat-ram-API.patch +++ b/patches/server/Goat-ram-API.patch @@ -9,7 +9,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java +++ b/src/main/java/net/minecraft/world/entity/animal/goat/Goat.java @@ -0,0 +0,0 @@ public class Goat extends Animal { - public static boolean checkGoatSpawnRules(EntityType entityType, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, Random random) { + public static boolean checkGoatSpawnRules(EntityType entityType, LevelAccessor world, MobSpawnType spawnReason, BlockPos pos, RandomSource random) { return world.getBlockState(pos.below()).is(BlockTags.GOATS_SPAWNABLE_ON) && isBrightEnoughToSpawn(world, pos); } + diff --git a/patches/unapplied/server/Left-handed-API.patch b/patches/server/Left-handed-API.patch similarity index 100% rename from patches/unapplied/server/Left-handed-API.patch rename to patches/server/Left-handed-API.patch diff --git a/patches/unapplied/server/Log-when-the-async-catcher-is-tripped.patch b/patches/server/Log-when-the-async-catcher-is-tripped.patch similarity index 100% rename from patches/unapplied/server/Log-when-the-async-catcher-is-tripped.patch rename to patches/server/Log-when-the-async-catcher-is-tripped.patch diff --git a/patches/unapplied/server/Make-CallbackExecutor-strict-again.patch b/patches/server/Make-CallbackExecutor-strict-again.patch similarity index 100% rename from patches/unapplied/server/Make-CallbackExecutor-strict-again.patch rename to patches/server/Make-CallbackExecutor-strict-again.patch diff --git a/patches/unapplied/server/Make-EntityUnleashEvent-cancellable.patch b/patches/server/Make-EntityUnleashEvent-cancellable.patch similarity index 100% rename from patches/unapplied/server/Make-EntityUnleashEvent-cancellable.patch rename to patches/server/Make-EntityUnleashEvent-cancellable.patch diff --git a/patches/unapplied/server/More-CommandBlock-API.patch b/patches/server/More-CommandBlock-API.patch similarity index 98% rename from patches/unapplied/server/More-CommandBlock-API.patch rename to patches/server/More-CommandBlock-API.patch index 3c862c9b0d..5645763550 100644 --- a/patches/unapplied/server/More-CommandBlock-API.patch +++ b/patches/server/More-CommandBlock-API.patch @@ -58,7 +58,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 super(world, tileEntity); @@ -0,0 +0,0 @@ public class CraftCommandBlock extends CraftBlockEntityState public void name(net.kyori.adventure.text.Component name) { - getSnapshot().getCommandBlock().setName(name == null ? new net.minecraft.network.chat.TextComponent("@") : io.papermc.paper.adventure.PaperAdventure.asVanilla(name)); + getSnapshot().getCommandBlock().setName(name == null ? net.minecraft.network.chat.Component.literal("@") : io.papermc.paper.adventure.PaperAdventure.asVanilla(name)); } + + @Override diff --git a/patches/unapplied/server/Optimize-indirect-passenger-iteration.patch b/patches/server/Optimize-indirect-passenger-iteration.patch similarity index 100% rename from patches/unapplied/server/Optimize-indirect-passenger-iteration.patch rename to patches/server/Optimize-indirect-passenger-iteration.patch diff --git a/patches/unapplied/server/Option-to-prevent-NBT-copy-in-smithing-recipes.patch b/patches/server/Option-to-prevent-NBT-copy-in-smithing-recipes.patch similarity index 100% rename from patches/unapplied/server/Option-to-prevent-NBT-copy-in-smithing-recipes.patch rename to patches/server/Option-to-prevent-NBT-copy-in-smithing-recipes.patch diff --git a/patches/unapplied/server/Prevent-unload-calls-removing-tickets-for-sync-loads.patch b/patches/server/Prevent-unload-calls-removing-tickets-for-sync-loads.patch similarity index 100% rename from patches/unapplied/server/Prevent-unload-calls-removing-tickets-for-sync-loads.patch rename to patches/server/Prevent-unload-calls-removing-tickets-for-sync-loads.patch diff --git a/patches/unapplied/server/Rewrite-entity-bounding-box-lookup-calls.patch b/patches/server/Rewrite-entity-bounding-box-lookup-calls.patch similarity index 99% rename from patches/unapplied/server/Rewrite-entity-bounding-box-lookup-calls.patch rename to patches/server/Rewrite-entity-bounding-box-lookup-calls.patch index 16f2325b51..d6a62365a5 100644 --- a/patches/unapplied/server/Rewrite-entity-bounding-box-lookup-calls.patch +++ b/patches/server/Rewrite-entity-bounding-box-lookup-calls.patch @@ -923,7 +923,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 - this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage); + this.entityManager = new PersistentEntitySectionManager<>(Entity.class, new ServerLevel.EntityCallbacks(), entitypersistentstorage, this.entitySliceManager); // Paper - StructureManager definedstructuremanager = minecraftserver.getStructureManager(); + StructureTemplateManager structuretemplatemanager = minecraftserver.getStructureManager(); int j = this.spigotConfig.viewDistance; // Spigot int k = this.spigotConfig.simulationDistance; // Spigot diff --git a/src/main/java/net/minecraft/server/level/WorldGenRegion.java b/src/main/java/net/minecraft/server/level/WorldGenRegion.java diff --git a/patches/unapplied/server/Sanitize-ResourceLocation-error-logging.patch b/patches/server/Sanitize-ResourceLocation-error-logging.patch similarity index 100% rename from patches/unapplied/server/Sanitize-ResourceLocation-error-logging.patch rename to patches/server/Sanitize-ResourceLocation-error-logging.patch diff --git a/patches/unapplied/server/Vanilla-command-permission-fixes.patch b/patches/server/Vanilla-command-permission-fixes.patch similarity index 92% rename from patches/unapplied/server/Vanilla-command-permission-fixes.patch rename to patches/server/Vanilla-command-permission-fixes.patch index f719d7de15..47d1487a01 100644 --- a/patches/unapplied/server/Vanilla-command-permission-fixes.patch +++ b/patches/server/Vanilla-command-permission-fixes.patch @@ -34,9 +34,10 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 --- a/src/main/java/net/minecraft/commands/Commands.java +++ b/src/main/java/net/minecraft/commands/Commands.java @@ -0,0 +0,0 @@ public class Commands { + if (environment.includeIntegrated) { PublishCommand.register(this.dispatcher); } - +- + // Paper start + for (final CommandNode node : this.dispatcher.getRoot().getChildren()) { + if (node.getRequirement() == com.mojang.brigadier.builder.ArgumentBuilder.defaultRequirement()) { @@ -44,9 +45,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } + // Paper end - this.dispatcher.findAmbiguities((commandnode, commandnode1, commandnode2, collection) -> { - // CommandDispatcher.LOGGER.warn("Ambiguity between arguments {} and {} with inputs: {}", new Object[]{this.dispatcher.getPath(commandnode1), this.dispatcher.getPath(commandnode2), collection}); // CraftBukkit - }); + // CraftBukkit start + } + diff --git a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java b/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 --- a/src/main/java/org/bukkit/craftbukkit/command/VanillaCommandWrapper.java diff --git a/patches/unapplied/server/Optimise-general-POI-access.patch b/patches/unapplied/server/Optimise-general-POI-access.patch deleted file mode 100644 index 5128cc1938..0000000000 --- a/patches/unapplied/server/Optimise-general-POI-access.patch +++ /dev/null @@ -1,998 +0,0 @@ -From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 -From: Spottedleaf -Date: Sun, 31 Jan 2021 02:29:24 -0800 -Subject: [PATCH] Optimise general POI access - -There are a couple of problems with mojang's POI code. -Firstly, it's all streams. Unsurprisingly, stacking -streams on top of each other is horrible for performance -and ultimately took up half of a villager's tick! - -Secondly, sometime's the search radius is large and there are -a significant number of poi entries per chunk section. Even -removing streams at this point doesn't help much. The only solution -is to start at the search point and iterate outwards. This -type of approach shows massive gains for portals, simply because -we can avoid sync loading a large area of chunks. I also tested -a massive farm I found in JellySquid's discord, which showed -to benefit significantly simply because the farm had so many -portal blocks that searching through them all was very slow. - -Great care has been taken so that behavior remains identical to -vanilla, however I cannot account for oddball Stream API -implementations, if they even exist (streams can technically -be loose with iteration order in a sorted stream given its -source stream is not tagged with ordered, and mojang does not -tag the source stream as ordered). However in my testing on openjdk -there showed no difference, as expected. - -This patch also specifically optimises other areas of code to -use PoiAccess. For example, some villager AI and portaling code -had to be specifically modified. - -diff --git a/src/main/java/io/papermc/paper/util/PoiAccess.java b/src/main/java/io/papermc/paper/util/PoiAccess.java -new file mode 100644 -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 ---- /dev/null -+++ b/src/main/java/io/papermc/paper/util/PoiAccess.java -@@ -0,0 +0,0 @@ -+package io.papermc.paper.util; -+ -+import it.unimi.dsi.fastutil.doubles.Double2ObjectMap; -+import it.unimi.dsi.fastutil.doubles.Double2ObjectRBTreeMap; -+import it.unimi.dsi.fastutil.longs.LongArrayFIFOQueue; -+import it.unimi.dsi.fastutil.longs.LongOpenHashSet; -+import net.minecraft.core.BlockPos; -+import net.minecraft.util.Mth; -+import net.minecraft.world.entity.ai.village.poi.PoiManager; -+import net.minecraft.world.entity.ai.village.poi.PoiRecord; -+import net.minecraft.world.entity.ai.village.poi.PoiSection; -+import net.minecraft.world.entity.ai.village.poi.PoiType; -+import java.util.ArrayList; -+import java.util.HashSet; -+import java.util.Iterator; -+import java.util.List; -+import java.util.Map; -+import java.util.Optional; -+import java.util.Set; -+import java.util.function.Predicate; -+ -+/** -+ * Provides optimised access to POI data. All returned values will be identical to vanilla. -+ */ -+public final class PoiAccess { -+ -+ protected static double clamp(final double val, final double min, final double max) { -+ return (val < min ? min : (val > max ? max : val)); -+ } -+ -+ protected static double getSmallestDistanceSquared(final double boxMinX, final double boxMinY, final double boxMinZ, -+ final double boxMaxX, final double boxMaxY, final double boxMaxZ, -+ -+ final double circleX, final double circleY, final double circleZ) { -+ // is the circle center inside the box? -+ if (circleX >= boxMinX && circleX <= boxMaxX && circleY >= boxMinY && circleY <= boxMaxY && circleZ >= boxMinZ && circleZ <= boxMaxZ) { -+ return 0.0; -+ } -+ -+ final double boxWidthX = (boxMaxX - boxMinX) / 2.0; -+ final double boxWidthY = (boxMaxY - boxMinY) / 2.0; -+ final double boxWidthZ = (boxMaxZ - boxMinZ) / 2.0; -+ -+ final double boxCenterX = (boxMinX + boxMaxX) / 2.0; -+ final double boxCenterY = (boxMinY + boxMaxY) / 2.0; -+ final double boxCenterZ = (boxMinZ + boxMaxZ) / 2.0; -+ -+ double centerDiffX = circleX - boxCenterX; -+ double centerDiffY = circleY - boxCenterY; -+ double centerDiffZ = circleZ - boxCenterZ; -+ -+ centerDiffX = circleX - (clamp(centerDiffX, -boxWidthX, boxWidthX) + boxCenterX); -+ centerDiffY = circleY - (clamp(centerDiffY, -boxWidthY, boxWidthY) + boxCenterY); -+ centerDiffZ = circleZ - (clamp(centerDiffZ, -boxWidthZ, boxWidthZ) + boxCenterZ); -+ -+ return (centerDiffX * centerDiffX) + (centerDiffY * centerDiffY) + (centerDiffZ * centerDiffZ); -+ } -+ -+ -+ // key is: -+ // upper 32 bits: -+ // upper 16 bits: max y section -+ // lower 16 bits: min y section -+ // lower 32 bits: -+ // upper 16 bits: section -+ // lower 16 bits: radius -+ protected static long getKey(final int minSection, final int maxSection, final int section, final int radius) { -+ return ( -+ (maxSection & 0xFFFFL) << (64 - 16) -+ | (minSection & 0xFFFFL) << (64 - 32) -+ | (section & 0xFFFFL) << (64 - 48) -+ | (radius & 0xFFFFL) << (64 - 64) -+ ); -+ } -+ -+ // only includes x/z axis -+ // finds the closest poi data by distance. -+ public static BlockPos findClosestPoiDataPosition(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load) { -+ final PoiRecord ret = findClosestPoiDataRecord( -+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load -+ ); -+ -+ return ret == null ? null : ret.getPos(); -+ } -+ -+ // only includes x/z axis -+ // finds the closest poi data by distance. if multiple match the same distance, then they all are returned. -+ public static void findClosestPoiDataPositions(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load, -+ final Set ret) { -+ final Set positions = new HashSet<>(); -+ // pos predicate is last thing that runs before adding to ret. -+ final Predicate newPredicate = (final BlockPos pos) -> { -+ if (positionPredicate != null && !positionPredicate.test(pos)) { -+ return false; -+ } -+ return positions.add(pos.immutable()); -+ }; -+ -+ final List toConvert = new ArrayList<>(); -+ findClosestPoiDataRecords( -+ poiStorage, villagePlaceType, newPredicate, sourcePosition, range, maxDistance, occupancy, load, toConvert -+ ); -+ -+ for (final PoiRecord record : toConvert) { -+ ret.add(record.getPos()); -+ } -+ } -+ -+ // only includes x/z axis -+ // finds the closest poi data by distance. -+ public static PoiRecord findClosestPoiDataRecord(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load) { -+ final List ret = new ArrayList<>(); -+ findClosestPoiDataRecords( -+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load, ret -+ ); -+ return ret.isEmpty() ? null : ret.get(0); -+ } -+ -+ // only includes x/z axis -+ // finds the closest poi data by distance. if multiple match the same distance, then they all are returned. -+ public static void findClosestPoiDataRecords(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load, -+ final List ret) { -+ final Predicate occupancyFilter = occupancy.getTest(); -+ -+ final List closestRecords = new ArrayList<>(); -+ double closestDistanceSquared = maxDistance * maxDistance; -+ -+ final int lowerX = Mth.floor(sourcePosition.getX() - range) >> 4; -+ final int lowerY = WorldUtil.getMinSection(poiStorage.world); -+ final int lowerZ = Mth.floor(sourcePosition.getZ() - range) >> 4; -+ final int upperX = Mth.floor(sourcePosition.getX() + range) >> 4; -+ final int upperY = WorldUtil.getMaxSection(poiStorage.world); -+ final int upperZ = Mth.floor(sourcePosition.getZ() + range) >> 4; -+ -+ final int centerX = sourcePosition.getX() >> 4; -+ final int centerY = Mth.clamp(sourcePosition.getY() >> 4, lowerY, upperY); -+ final int centerZ = sourcePosition.getZ() >> 4; -+ -+ final LongArrayFIFOQueue queue = new LongArrayFIFOQueue(); -+ queue.enqueue(CoordinateUtils.getChunkSectionKey(centerX, centerY, centerZ)); -+ final LongOpenHashSet seen = new LongOpenHashSet(); -+ -+ while (!queue.isEmpty()) { -+ final long key = queue.dequeueLong(); -+ final int sectionX = CoordinateUtils.getChunkSectionX(key); -+ final int sectionY = CoordinateUtils.getChunkSectionY(key); -+ final int sectionZ = CoordinateUtils.getChunkSectionZ(key); -+ -+ if (sectionX < lowerX || sectionX > upperX || sectionY < lowerY || sectionY > upperY || sectionZ < lowerZ || sectionZ > upperZ) { -+ // out of bound chunk -+ continue; -+ } -+ -+ final double sectionDistanceSquared = getSmallestDistanceSquared( -+ (sectionX << 4) + 0.5, -+ (sectionY << 4) + 0.5, -+ (sectionZ << 4) + 0.5, -+ (sectionX << 4) + 15.5, -+ (sectionY << 4) + 15.5, -+ (sectionZ << 4) + 15.5, -+ (double)sourcePosition.getX(), (double)sourcePosition.getY(), (double)sourcePosition.getZ() -+ ); -+ if (sectionDistanceSquared > closestDistanceSquared) { -+ continue; -+ } -+ -+ // queue all neighbours -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ for (int dy = -1; dy <= 1; ++dy) { -+ // -1 and 1 have the 1st bit set. so just add up the first bits, and it will tell us how many -+ // values are set. we only care about cardinal neighbours, so, we only care if one value is set -+ if ((dx & 1) + (dy & 1) + (dz & 1) != 1) { -+ continue; -+ } -+ -+ final int neighbourX = sectionX + dx; -+ final int neighbourY = sectionY + dy; -+ final int neighbourZ = sectionZ + dz; -+ -+ final long neighbourKey = CoordinateUtils.getChunkSectionKey(neighbourX, neighbourY, neighbourZ); -+ if (seen.add(neighbourKey)) { -+ queue.enqueue(neighbourKey); -+ } -+ } -+ } -+ } -+ -+ final Optional poiSectionOptional = load ? poiStorage.getOrLoad(key) : poiStorage.get(key); -+ -+ if (poiSectionOptional == null || !poiSectionOptional.isPresent()) { -+ continue; -+ } -+ -+ final PoiSection poiSection = poiSectionOptional.orElse(null); -+ -+ final Map> sectionData = poiSection.getData(); -+ if (sectionData.isEmpty()) { -+ continue; -+ } -+ -+ // now we search the section data -+ for (final Map.Entry> entry : sectionData.entrySet()) { -+ if (!villagePlaceType.test(entry.getKey())) { -+ // filter out by poi type -+ continue; -+ } -+ -+ // now we can look at the poi data -+ for (final PoiRecord poiData : entry.getValue()) { -+ if (!occupancyFilter.test(poiData)) { -+ // filter by occupancy -+ continue; -+ } -+ -+ final BlockPos poiPosition = poiData.getPos(); -+ -+ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range -+ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) { -+ // out of range for square radius -+ continue; -+ } -+ -+ // it's important that it's poiPosition.distSqr(source) : the value actually is different IF the values are swapped! -+ final double dataRange = poiPosition.distSqr(sourcePosition); -+ -+ if (dataRange > closestDistanceSquared) { -+ // out of range for distance check -+ continue; -+ } -+ -+ if (positionPredicate != null && !positionPredicate.test(poiPosition)) { -+ // filter by position -+ continue; -+ } -+ -+ if (dataRange < closestDistanceSquared) { -+ closestRecords.clear(); -+ closestDistanceSquared = dataRange; -+ } -+ closestRecords.add(poiData); -+ } -+ } -+ } -+ -+ // uh oh! we might have multiple records that match the distance sorting! -+ // we need to re-order our results by the way vanilla would have iterated over them. -+ closestRecords.sort((record1, record2) -> { -+ // vanilla iterates the same way we do for data inside sections, so we know the ordering inside a section -+ // is fine and should be preserved (this sort is stable so we're good there) -+ // but they iterate sections by x then by z (like the following) -+ // for (int x = -dx; x <= dx; ++x) -+ // for (int z = -dz; z <= dz; ++z) -+ // .... -+ // so we need to reorder such that records with lower chunk z, then lower chunk x come first -+ final BlockPos pos1 = record1.getPos(); -+ final BlockPos pos2 = record2.getPos(); -+ -+ final int cx1 = pos1.getX() >> 4; -+ final int cz1 = pos1.getZ() >> 4; -+ -+ final int cx2 = pos2.getX() >> 4; -+ final int cz2 = pos2.getZ() >> 4; -+ -+ if (cz2 != cz1) { -+ // want smaller z -+ return Integer.compare(cz1, cz2); -+ } -+ -+ if (cx2 != cx1) { -+ // want smaller x -+ return Integer.compare(cx1, cx2); -+ } -+ -+ // same chunk -+ // once vanilla has the chunk, it will iterate from all of the chunk sections starting from smaller y -+ // so now we just compare section y, wanting smaller y -+ -+ return Integer.compare(pos1.getY() >> 4, pos2.getY() >> 4); -+ }); -+ -+ // now we match perfectly what vanilla would have outputted, without having to search the whole radius (hopefully). -+ ret.addAll(closestRecords); -+ } -+ -+ // finds the closest poi entry pos. -+ public static BlockPos findNearestPoiPosition(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load) { -+ final PoiRecord ret = findNearestPoiRecord( -+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load -+ ); -+ return ret == null ? null : ret.getPos(); -+ } -+ -+ // finds the closest `max` poi entry positions. -+ public static void findNearestPoiPositions(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load, -+ final int max, -+ final List ret) { -+ final Set positions = new HashSet<>(); -+ // pos predicate is last thing that runs before adding to ret. -+ final Predicate newPredicate = (final BlockPos pos) -> { -+ if (positionPredicate != null && !positionPredicate.test(pos)) { -+ return false; -+ } -+ return positions.add(pos.immutable()); -+ }; -+ -+ final List toConvert = new ArrayList<>(); -+ findNearestPoiRecords( -+ poiStorage, villagePlaceType, newPredicate, sourcePosition, range, maxDistance, occupancy, load, max, toConvert -+ ); -+ -+ for (final PoiRecord record : toConvert) { -+ ret.add(record.getPos()); -+ } -+ } -+ -+ // finds the closest poi entry. -+ public static PoiRecord findNearestPoiRecord(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load) { -+ final List ret = new ArrayList<>(); -+ findNearestPoiRecords( -+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, maxDistance, occupancy, load, -+ 1, ret -+ ); -+ return ret.isEmpty() ? null : ret.get(0); -+ } -+ -+ // finds the closest `max` poi entries. -+ public static void findNearestPoiRecords(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ // position predicate must not modify chunk POI -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final double maxDistance, -+ final PoiManager.Occupancy occupancy, -+ final boolean load, -+ final int max, -+ final List ret) { -+ final Predicate occupancyFilter = occupancy.getTest(); -+ -+ final double maxDistanceSquared = maxDistance * maxDistance; -+ final Double2ObjectRBTreeMap> closestRecords = new Double2ObjectRBTreeMap<>(); -+ int totalRecords = 0; -+ double furthestDistanceSquared = maxDistanceSquared; -+ -+ final int lowerX = Mth.floor(sourcePosition.getX() - range) >> 4; -+ final int lowerY = WorldUtil.getMinSection(poiStorage.world); -+ final int lowerZ = Mth.floor(sourcePosition.getZ() - range) >> 4; -+ final int upperX = Mth.floor(sourcePosition.getX() + range) >> 4; -+ final int upperY = WorldUtil.getMaxSection(poiStorage.world); -+ final int upperZ = Mth.floor(sourcePosition.getZ() + range) >> 4; -+ -+ final int centerX = sourcePosition.getX() >> 4; -+ final int centerY = Mth.clamp(sourcePosition.getY() >> 4, lowerY, upperY); -+ final int centerZ = sourcePosition.getZ() >> 4; -+ -+ final LongArrayFIFOQueue queue = new LongArrayFIFOQueue(); -+ queue.enqueue(CoordinateUtils.getChunkSectionKey(centerX, centerY, centerZ)); -+ final LongOpenHashSet seen = new LongOpenHashSet(); -+ -+ while (!queue.isEmpty()) { -+ final long key = queue.dequeueLong(); -+ final int sectionX = CoordinateUtils.getChunkSectionX(key); -+ final int sectionY = CoordinateUtils.getChunkSectionY(key); -+ final int sectionZ = CoordinateUtils.getChunkSectionZ(key); -+ -+ if (sectionX < lowerX || sectionX > upperX || sectionY < lowerY || sectionY > upperY || sectionZ < lowerZ || sectionZ > upperZ) { -+ // out of bound chunk -+ continue; -+ } -+ -+ final double sectionDistanceSquared = getSmallestDistanceSquared( -+ (sectionX << 4) + 0.5, -+ (sectionY << 4) + 0.5, -+ (sectionZ << 4) + 0.5, -+ (sectionX << 4) + 15.5, -+ (sectionY << 4) + 15.5, -+ (sectionZ << 4) + 15.5, -+ (double) sourcePosition.getX(), (double) sourcePosition.getY(), (double) sourcePosition.getZ() -+ ); -+ -+ if (sectionDistanceSquared > (totalRecords >= max ? furthestDistanceSquared : maxDistanceSquared)) { -+ continue; -+ } -+ -+ // queue all neighbours -+ for (int dz = -1; dz <= 1; ++dz) { -+ for (int dx = -1; dx <= 1; ++dx) { -+ for (int dy = -1; dy <= 1; ++dy) { -+ // -1 and 1 have the 1st bit set. so just add up the first bits, and it will tell us how many -+ // values are set. we only care about cardinal neighbours, so, we only care if one value is set -+ if ((dx & 1) + (dy & 1) + (dz & 1) != 1) { -+ continue; -+ } -+ -+ final int neighbourX = sectionX + dx; -+ final int neighbourY = sectionY + dy; -+ final int neighbourZ = sectionZ + dz; -+ -+ final long neighbourKey = CoordinateUtils.getChunkSectionKey(neighbourX, neighbourY, neighbourZ); -+ if (seen.add(neighbourKey)) { -+ queue.enqueue(neighbourKey); -+ } -+ } -+ } -+ } -+ -+ final Optional poiSectionOptional = load ? poiStorage.getOrLoad(key) : poiStorage.get(key); -+ -+ if (poiSectionOptional == null || !poiSectionOptional.isPresent()) { -+ continue; -+ } -+ -+ final PoiSection poiSection = poiSectionOptional.orElse(null); -+ -+ final Map> sectionData = poiSection.getData(); -+ if (sectionData.isEmpty()) { -+ continue; -+ } -+ -+ // now we search the section data -+ for (final Map.Entry> entry : sectionData.entrySet()) { -+ if (!villagePlaceType.test(entry.getKey())) { -+ // filter out by poi type -+ continue; -+ } -+ -+ // now we can look at the poi data -+ for (final PoiRecord poiData : entry.getValue()) { -+ if (!occupancyFilter.test(poiData)) { -+ // filter by occupancy -+ continue; -+ } -+ -+ final BlockPos poiPosition = poiData.getPos(); -+ -+ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range -+ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) { -+ // out of range for square radius -+ continue; -+ } -+ -+ // it's important that it's poiPosition.distSqr(source) : the value actually is different IF the values are swapped! -+ final double dataRange = poiPosition.distSqr(sourcePosition); -+ -+ if (dataRange > maxDistanceSquared) { -+ // out of range for distance check -+ continue; -+ } -+ -+ if (dataRange > furthestDistanceSquared && totalRecords >= max) { -+ // out of range for distance check -+ continue; -+ } -+ -+ if (positionPredicate != null && !positionPredicate.test(poiPosition)) { -+ // filter by position -+ continue; -+ } -+ -+ if (dataRange > furthestDistanceSquared) { -+ // we know totalRecords < max, so this entry is now our furthest -+ furthestDistanceSquared = dataRange; -+ } -+ -+ closestRecords.computeIfAbsent(dataRange, (final double unused) -> { -+ return new ArrayList<>(); -+ }).add(poiData); -+ -+ if (++totalRecords >= max) { -+ if (closestRecords.size() >= 2) { -+ int entriesInClosest = 0; -+ final Iterator>> iterator = closestRecords.double2ObjectEntrySet().iterator(); -+ double nextFurthestDistanceSquared = 0.0; -+ -+ for (int i = 0, len = closestRecords.size() - 1; i < len; ++i) { -+ final Double2ObjectMap.Entry> recordEntry = iterator.next(); -+ entriesInClosest += recordEntry.getValue().size(); -+ nextFurthestDistanceSquared = recordEntry.getDoubleKey(); -+ } -+ -+ if (entriesInClosest >= max) { -+ // the last set of entries at range wont even be considered for sure... nuke em -+ final Double2ObjectMap.Entry> recordEntry = iterator.next(); -+ totalRecords -= recordEntry.getValue().size(); -+ iterator.remove(); -+ -+ furthestDistanceSquared = nextFurthestDistanceSquared; -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ final List closestRecordsUnsorted = new ArrayList<>(); -+ -+ // we're done here, so now just flatten the map and sort it. -+ -+ for (final List records : closestRecords.values()) { -+ closestRecordsUnsorted.addAll(records); -+ } -+ -+ // uh oh! we might have multiple records that match the distance sorting! -+ // we need to re-order our results by the way vanilla would have iterated over them. -+ closestRecordsUnsorted.sort((record1, record2) -> { -+ // vanilla iterates the same way we do for data inside sections, so we know the ordering inside a section -+ // is fine and should be preserved (this sort is stable so we're good there) -+ // but they iterate sections by x then by z (like the following) -+ // for (int x = -dx; x <= dx; ++x) -+ // for (int z = -dz; z <= dz; ++z) -+ // .... -+ // so we need to reorder such that records with lower chunk z, then lower chunk x come first -+ final BlockPos pos1 = record1.getPos(); -+ final BlockPos pos2 = record2.getPos(); -+ -+ final int cx1 = pos1.getX() >> 4; -+ final int cz1 = pos1.getZ() >> 4; -+ -+ final int cx2 = pos2.getX() >> 4; -+ final int cz2 = pos2.getZ() >> 4; -+ -+ if (cz2 != cz1) { -+ // want smaller z -+ return Integer.compare(cz1, cz2); -+ } -+ -+ if (cx2 != cx1) { -+ // want smaller x -+ return Integer.compare(cx1, cx2); -+ } -+ -+ // same chunk -+ // once vanilla has the chunk, it will iterate from all of the chunk sections starting from smaller y -+ // so now we just compare section y, wanting smaller section y -+ -+ return Integer.compare(pos1.getY() >> 4, pos2.getY() >> 4); -+ }); -+ -+ // trim out any entries exceeding our maximum -+ for (int i = closestRecordsUnsorted.size() - 1; i >= max; --i) { -+ closestRecordsUnsorted.remove(i); -+ } -+ -+ // now we match perfectly what vanilla would have outputted, without having to search the whole radius (hopefully). -+ ret.addAll(closestRecordsUnsorted); -+ } -+ -+ public static BlockPos findAnyPoiPosition(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final PoiManager.Occupancy occupancy, -+ final boolean load) { -+ final PoiRecord ret = findAnyPoiRecord( -+ poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, occupancy, load -+ ); -+ -+ return ret == null ? null : ret.getPos(); -+ } -+ -+ public static void findAnyPoiPositions(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final PoiManager.Occupancy occupancy, -+ final boolean load, -+ final int max, -+ final List ret) { -+ final Set positions = new HashSet<>(); -+ // pos predicate is last thing that runs before adding to ret. -+ final Predicate newPredicate = (final BlockPos pos) -> { -+ if (positionPredicate != null && !positionPredicate.test(pos)) { -+ return false; -+ } -+ return positions.add(pos.immutable()); -+ }; -+ -+ final List toConvert = new ArrayList<>(); -+ findAnyPoiRecords( -+ poiStorage, villagePlaceType, newPredicate, sourcePosition, range, occupancy, load, max, toConvert -+ ); -+ -+ for (final PoiRecord record : toConvert) { -+ ret.add(record.getPos()); -+ } -+ } -+ -+ public static PoiRecord findAnyPoiRecord(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final PoiManager.Occupancy occupancy, -+ final boolean load) { -+ final List ret = new ArrayList<>(); -+ findAnyPoiRecords(poiStorage, villagePlaceType, positionPredicate, sourcePosition, range, occupancy, load, 1, ret); -+ return ret.isEmpty() ? null : ret.get(0); -+ } -+ -+ public static void findAnyPoiRecords(final PoiManager poiStorage, -+ final Predicate villagePlaceType, -+ final Predicate positionPredicate, -+ final BlockPos sourcePosition, -+ final int range, // distance on x y z axis -+ final PoiManager.Occupancy occupancy, -+ final boolean load, -+ final int max, -+ final List ret) { -+ // the biggest issue with the original mojang implementation is that they chain so many streams together -+ // the amount of streams chained just rolls performance, even if nothing is iterated over -+ final Predicate occupancyFilter = occupancy.getTest(); -+ final double rangeSquared = range * range; -+ -+ int added = 0; -+ -+ // First up, we need to iterate the chunks -+ // all the values here are in chunk sections -+ final int lowerX = Mth.floor(sourcePosition.getX() - range) >> 4; -+ final int lowerY = Math.max(WorldUtil.getMinSection(poiStorage.world), Mth.floor(sourcePosition.getY() - range) >> 4); -+ final int lowerZ = Mth.floor(sourcePosition.getZ() - range) >> 4; -+ final int upperX = Mth.floor(sourcePosition.getX() + range) >> 4; -+ final int upperY = Math.min(WorldUtil.getMaxSection(poiStorage.world), Mth.floor(sourcePosition.getY() + range) >> 4); -+ final int upperZ = Mth.floor(sourcePosition.getZ() + range) >> 4; -+ -+ // Vanilla iterates by x until max is reached then increases z -+ // vanilla also searches by increasing Y section value -+ for (int currZ = lowerZ; currZ <= upperZ; ++currZ) { -+ for (int currX = lowerX; currX <= upperX; ++currX) { -+ for (int currY = lowerY; currY <= upperY; ++currY) { // vanilla searches the entire chunk because they're actually stupid. just search the sections we need -+ final Optional poiSectionOptional = load ? poiStorage.getOrLoad(CoordinateUtils.getChunkSectionKey(currX, currY, currZ)) : -+ poiStorage.get(CoordinateUtils.getChunkSectionKey(currX, currY, currZ)); -+ final PoiSection poiSection = poiSectionOptional == null ? null : poiSectionOptional.orElse(null); -+ if (poiSection == null) { -+ continue; -+ } -+ -+ final Map> sectionData = poiSection.getData(); -+ if (sectionData.isEmpty()) { -+ continue; -+ } -+ -+ // now we search the section data -+ for (final Map.Entry> entry : sectionData.entrySet()) { -+ if (!villagePlaceType.test(entry.getKey())) { -+ // filter out by poi type -+ continue; -+ } -+ -+ // now we can look at the poi data -+ for (final PoiRecord poiData : entry.getValue()) { -+ if (!occupancyFilter.test(poiData)) { -+ // filter by occupancy -+ continue; -+ } -+ -+ final BlockPos poiPosition = poiData.getPos(); -+ -+ if (Math.abs(poiPosition.getX() - sourcePosition.getX()) > range -+ || Math.abs(poiPosition.getZ() - sourcePosition.getZ()) > range) { -+ // out of range for square radius -+ continue; -+ } -+ -+ if (poiPosition.distSqr(sourcePosition) > rangeSquared) { -+ // out of range for distance check -+ continue; -+ } -+ -+ if (positionPredicate != null && !positionPredicate.test(poiPosition)) { -+ // filter by position -+ continue; -+ } -+ -+ // found one! -+ ret.add(poiData); -+ if (++added >= max) { -+ return; -+ } -+ } -+ } -+ } -+ } -+ } -+ } -+ -+ private PoiAccess() { -+ throw new RuntimeException(); -+ } -+} -diff --git a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java -+++ b/src/main/java/net/minecraft/world/entity/ai/behavior/AcquirePoi.java -@@ -0,0 +0,0 @@ public class AcquirePoi extends Behavior { - return true; - } - }; -- Set set = poiManager.findAllClosestFirst(this.poiType.getPredicate(), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.HAS_SPACE).limit(5L).collect(Collectors.toSet()); -+ // Paper start - optimise POI access -+ java.util.List poiposes = new java.util.ArrayList<>(); -+ io.papermc.paper.util.PoiAccess.findNearestPoiPositions(poiManager, this.poiType.getPredicate(), predicate, entity.blockPosition(), 48, 48*48, PoiManager.Occupancy.HAS_SPACE, false, 5, poiposes); -+ Set set = new java.util.HashSet<>(poiposes); -+ // Paper end - optimise POI access - Path path = entity.getNavigation().createPath(set, this.poiType.getValidRange()); - if (path != null && path.canReach()) { - BlockPos blockPos = path.getTarget(); -diff --git a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java -+++ b/src/main/java/net/minecraft/world/entity/ai/sensing/NearestBedSensor.java -@@ -0,0 +0,0 @@ public class NearestBedSensor extends Sensor { - return true; - } - }; -- Stream stream = poiManager.findAll(PoiType.HOME.getPredicate(), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY); -- Path path = entity.getNavigation().createPath(stream, PoiType.HOME.getValidRange()); -+ // Paper start - optimise POI access -+ java.util.List poiposes = new java.util.ArrayList<>(); -+ // don't ask me why it's unbounded. ask mojang. -+ io.papermc.paper.util.PoiAccess.findAnyPoiPositions(poiManager, PoiType.HOME.getPredicate(), predicate, entity.blockPosition(), 48, PoiManager.Occupancy.ANY, false, Integer.MAX_VALUE, poiposes); -+ Path path = entity.getNavigation().createPath(new java.util.HashSet<>(poiposes), PoiType.HOME.getValidRange()); -+ // Paper end - optimise POI access - if (path != null && path.canReach()) { - BlockPos blockPos = path.getTarget(); - Optional optional = poiManager.getType(blockPos); -diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiManager.java -@@ -0,0 +0,0 @@ public class PoiManager extends SectionStorage { - public static final int VILLAGE_SECTION_SIZE = 1; - private final PoiManager.DistanceTracker distanceTracker; - private final LongSet loadedChunks = new LongOpenHashSet(); -- private final net.minecraft.server.level.ServerLevel world; // Paper -+ public final net.minecraft.server.level.ServerLevel world; // Paper // Paper public - - public PoiManager(Path path, DataFixer dataFixer, boolean dsync, LevelHeightAccessor world) { - super(path, PoiSection::codec, PoiSection::new, dataFixer, DataFixTypes.POI_CHUNK, dsync, world); -@@ -0,0 +0,0 @@ public class PoiManager extends SectionStorage { - } - - public Optional find(Predicate typePredicate, Predicate posPredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) { -- return this.findAll(typePredicate, posPredicate, pos, radius, occupationStatus).findFirst(); -+ // Paper start - re-route to faster logic -+ BlockPos ret = io.papermc.paper.util.PoiAccess.findAnyPoiPosition(this, typePredicate, posPredicate, pos, radius, occupationStatus, false); -+ return Optional.ofNullable(ret); -+ // Paper end - re-route to faster logic - } - - public Optional findClosest(Predicate typePredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) { -- return this.getInRange(typePredicate, pos, radius, occupationStatus).map(PoiRecord::getPos).min(Comparator.comparingDouble((blockPos2) -> { -- return blockPos2.distSqr(pos); -- })); -+ // Paper start - re-route to faster logic -+ BlockPos ret = io.papermc.paper.util.PoiAccess.findClosestPoiDataPosition(this, typePredicate, null, pos, radius, radius*radius, occupationStatus, false); -+ return Optional.ofNullable(ret); -+ // Paper end - re-route to faster logic - } - - public Optional findClosest(Predicate typePredicate, Predicate posPredicate, BlockPos pos, int radius, PoiManager.Occupancy occupationStatus) { -- return this.getInRange(typePredicate, pos, radius, occupationStatus).map(PoiRecord::getPos).filter(posPredicate).min(Comparator.comparingDouble((blockPos2) -> { -- return blockPos2.distSqr(pos); -- })); -+ // Paper start - re-route to faster logic -+ BlockPos ret = io.papermc.paper.util.PoiAccess.findClosestPoiDataPosition(this, typePredicate, posPredicate, pos, radius, radius * radius, occupationStatus, false); -+ return Optional.ofNullable(ret); -+ // Paper end - re-route to faster logic - } - - public Optional take(Predicate typePredicate, Predicate positionPredicate, BlockPos pos, int radius) { -- return this.getInRange(typePredicate, pos, radius, PoiManager.Occupancy.HAS_SPACE).filter((poi) -> { -- return positionPredicate.test(poi.getPos()); -- }).findFirst().map((poi) -> { -- poi.acquireTicket(); -- return poi.getPos(); -- }); -+ // Paper start - re-route to faster logic -+ PoiRecord ret = io.papermc.paper.util.PoiAccess.findAnyPoiRecord( -+ this, typePredicate, positionPredicate, pos, radius, PoiManager.Occupancy.HAS_SPACE, false -+ ); -+ if (ret == null) { -+ return Optional.empty(); -+ } -+ ret.acquireTicket(); -+ return Optional.of(ret.getPos()); -+ // Paper end - re-route to faster logic - } - - public Optional getRandom(Predicate typePredicate, Predicate positionPredicate, PoiManager.Occupancy occupationStatus, BlockPos pos, int radius, Random random) { -- List list = this.getInRange(typePredicate, pos, radius, occupationStatus).collect(Collectors.toList()); -- Collections.shuffle(list, random); -- return list.stream().filter((poi) -> { -- return positionPredicate.test(poi.getPos()); -- }).findFirst().map(PoiRecord::getPos); -+ // Paper start - re-route to faster logic -+ List list = new java.util.ArrayList<>(); -+ io.papermc.paper.util.PoiAccess.findAnyPoiRecords( -+ this, typePredicate, positionPredicate, pos, radius, occupationStatus, false, Integer.MAX_VALUE, list -+ ); -+ -+ // the old method shuffled the list and then tried to find the first element in it that -+ // matched positionPredicate, however we moved positionPredicate into the poi search. This means we can avoid a -+ // shuffle entirely, and just pick a random element from list -+ if (list.isEmpty()) { -+ return Optional.empty(); -+ } -+ -+ return Optional.of(list.get(random.nextInt(list.size())).getPos()); -+ // Paper end - re-route to faster logic - } - - public boolean release(BlockPos pos) { -diff --git a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java -+++ b/src/main/java/net/minecraft/world/entity/ai/village/poi/PoiSection.java -@@ -0,0 +0,0 @@ import org.slf4j.Logger; - public class PoiSection { - private static final Logger LOGGER = LogUtils.getLogger(); - private final Short2ObjectMap records = new Short2ObjectOpenHashMap<>(); -- private final Map> byType = Maps.newHashMap(); -+ private final Map> byType = Maps.newHashMap(); public final Map> getData() { return this.byType; } // Paper - public accessor - private final Runnable setDirty; - private boolean isValid; - -diff --git a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -+++ b/src/main/java/net/minecraft/world/level/chunk/storage/SectionStorage.java -@@ -0,0 +0,0 @@ public class SectionStorage extends RegionFileStorage implements AutoCloseabl - } - - @Nullable -- protected Optional get(long pos) { -+ public Optional get(long pos) { // Paper - public - return this.storage.get(pos); - } - -- protected Optional getOrLoad(long pos) { -+ public Optional getOrLoad(long pos) { // Paper - public - if (this.outsideStoredRange(pos)) { - return Optional.empty(); - } else { -diff --git a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 100644 ---- a/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -+++ b/src/main/java/net/minecraft/world/level/portal/PortalForcer.java -@@ -0,0 +0,0 @@ public class PortalForcer { - // int i = flag ? 16 : 128; - // CraftBukkit end - -- villageplace.ensureLoadedAndValid(this.level, blockposition, i); -- Optional optional = villageplace.getInSquare((villageplacetype) -> { -- return villageplacetype == PoiType.NETHER_PORTAL; -- }, blockposition, i, PoiManager.Occupancy.ANY).filter((villageplacerecord) -> { -- return worldborder.isWithinBounds(villageplacerecord.getPos()); -- }).sorted(Comparator.comparingDouble((PoiRecord villageplacerecord) -> { // CraftBukkit - decompile error -- return villageplacerecord.getPos().distSqr(blockposition); -- }).thenComparingInt((villageplacerecord) -> { -- return villageplacerecord.getPos().getY(); -- })).filter((villageplacerecord) -> { -- return this.level.getBlockState(villageplacerecord.getPos()).hasProperty(BlockStateProperties.HORIZONTAL_AXIS); -- }).findFirst(); -+ // Paper start - optimise portals -+ Optional optional; -+ java.util.List records = new java.util.ArrayList<>(); -+ io.papermc.paper.util.PoiAccess.findClosestPoiDataRecords( -+ villageplace, -+ (PoiType type) -> { -+ return type == PoiType.NETHER_PORTAL; -+ }, -+ (BlockPos pos) -> { -+ net.minecraft.world.level.chunk.ChunkAccess lowest = this.level.getChunk(pos.getX() >> 4, pos.getZ() >> 4, net.minecraft.world.level.chunk.ChunkStatus.EMPTY); -+ if (!lowest.getStatus().isOrAfter(net.minecraft.world.level.chunk.ChunkStatus.FULL) -+ && (lowest.getBelowZeroRetrogen() == null || !lowest.getBelowZeroRetrogen().targetStatus().isOrAfter(net.minecraft.world.level.chunk.ChunkStatus.HEIGHTMAPS))) { -+ // why would we generate the chunk? -+ return false; -+ } -+ if (!worldborder.isWithinBounds(pos)) { -+ return false; -+ } -+ return lowest.getBlockState(pos).hasProperty(BlockStateProperties.HORIZONTAL_AXIS); -+ }, -+ blockposition, i, Double.MAX_VALUE, PoiManager.Occupancy.ANY, true, records -+ ); -+ -+ // this gets us most of the way there, but we bias towards lower y values. -+ PoiRecord lowestYRecord = null; -+ for (PoiRecord record : records) { -+ if (lowestYRecord == null) { -+ lowestYRecord = record; -+ } else if (lowestYRecord.getPos().getY() > record.getPos().getY()) { -+ lowestYRecord = record; -+ } -+ } -+ // now we're done -+ optional = Optional.ofNullable(lowestYRecord); -+ // Paper end - optimise portals - - return optional.map((villageplacerecord) -> { - BlockPos blockposition1 = villageplacerecord.getPos();