From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Phoenix616 <mail@moep.tv>
Date: Mon, 13 Jan 2020 15:40:32 +0100
Subject: [PATCH] Seed based feature search

This tries to work around the issue where the server will load
surrounding chunks up to a radius of 100 chunks in order to search for
features e.g. when running the /locate command or for treasure maps
(issue #2312).
This is done by backporting Mojang's change in 1.17 which makes it so
that the biome (generated by the seed) is checked first if the feature
can be generated before actually to load the chunk.

Additionally to that the center location of the target chunk is simply
returned if the chunk is not loaded to avoid the sync chunk load.
As this can lead to less precise locations a toggle is provided to
enable the sync loading of the target chunk again.

The main downside of this is that it breaks once the seed or generator
changes but this should usually not happen. A config option to disable
this completely is added though in case that should ever be necessary.

diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index d985472c7982130cfa3a7d85694fd692717fa295..52c2373e1277e3f98bdfeafae0ef79158622b753 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -400,6 +400,14 @@ public class PaperWorldConfig {
         }
     }
 
+    public boolean seedBasedFeatureSearch = true;
+    public boolean seedBasedFeatureSearchLoadsChunks = true;
+    private void seedBasedFeatureSearch() {
+        seedBasedFeatureSearch = getBoolean("seed-based-feature-search", seedBasedFeatureSearch);
+        seedBasedFeatureSearchLoadsChunks = getBoolean("seed-based-feature-search-loads-chunks", seedBasedFeatureSearchLoadsChunks);
+        log("Feature search is based on seed: " + seedBasedFeatureSearch + ", loads chunks:" + seedBasedFeatureSearchLoadsChunks);
+    }
+
     public int maxCollisionsPerEntity;
     private void maxEntityCollision() {
         maxCollisionsPerEntity = getInt( "max-entity-collisions", this.spigotConfig.getInt("max-entity-collisions", 8) );
diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/StructureFeature.java b/src/main/java/net/minecraft/world/level/levelgen/feature/StructureFeature.java
index 0670c1f72d9d0c4f8ea32ed314f75a106b2e2b09..6389e94abf3bae3e3cbeac11e0c99b1e50964563 100644
--- a/src/main/java/net/minecraft/world/level/levelgen/feature/StructureFeature.java
+++ b/src/main/java/net/minecraft/world/level/levelgen/feature/StructureFeature.java
@@ -175,7 +175,24 @@ public class StructureFeature<C extends FeatureConfiguration> {
                                 return this.getLocatePos(chunkPos);
                             }
 
-                            ChunkAccess chunkAccess = levelReader.getChunk(chunkPos.x, chunkPos.z, ChunkStatus.STRUCTURE_STARTS);
+                            // Paper start - seed based feature search
+                            ChunkAccess chunkAccess = null;
+                            if (structureAccessor.getWorld().paperConfig.seedBasedFeatureSearch) {
+                                Biome biomeBase = structureAccessor.getWorld().getBiomeManager().getBiome(new BlockPos(chunkPos.getMinBlockX() + 9, 0, chunkPos.getMinBlockZ() + 9));
+                                if (!biomeBase.getGenerationSettings().isValidStart(this)) {
+                                    continue;
+                                }
+                                if (!structureAccessor.getWorld().paperConfig.seedBasedFeatureSearchLoadsChunks) {
+                                    chunkAccess = structureAccessor.getWorld().getChunkIfLoaded(chunkPos.x, chunkPos.z);
+                                    if (chunkAccess == null) {
+                                        return chunkPos.getWorldPosition().offset(8, searchStartPos.getY(), 8);
+                                    }
+                                }
+                            }
+                            if (chunkAccess == null) {
+                                chunkAccess = levelReader.getChunk(chunkPos.x, chunkPos.z, ChunkStatus.STRUCTURE_STARTS);
+                            }
+                            // Paper end
                             StructureStart<?> structureStart = structureAccessor.getStartForFeature(SectionPos.bottomOf(chunkAccess), this, chunkAccess);
                             if (structureStart != null && structureStart.isValid()) {
                                 if (skipExistingChunks && structureStart.canBeReferenced()) {
