--- a/net/minecraft/world/level/chunk/ChunkGenerator.java
+++ b/net/minecraft/world/level/chunk/ChunkGenerator.java
@@ -104,8 +_,8 @@
 
     protected abstract MapCodec<? extends ChunkGenerator> codec();
 
-    public ChunkGeneratorStructureState createState(HolderLookup<StructureSet> structureSetLookup, RandomState randomState, long seed) {
-        return ChunkGeneratorStructureState.createForNormal(randomState, seed, this.biomeSource, structureSetLookup);
+    public ChunkGeneratorStructureState createState(HolderLookup<StructureSet> structureSetLookup, RandomState randomState, long seed, org.spigotmc.SpigotWorldConfig conf) { // Spigot
+        return ChunkGeneratorStructureState.createForNormal(randomState, seed, this.biomeSource, structureSetLookup, conf); // Spigot
     }
 
     public Optional<ResourceKey<MapCodec<? extends ChunkGenerator>>> getTypeNameForDataFixer() {
@@ -127,6 +_,24 @@
     public Pair<BlockPos, Holder<Structure>> findNearestMapStructure(
         ServerLevel level, HolderSet<Structure> structure, BlockPos pos, int searchRadius, boolean skipKnownStructures
     ) {
+        // Paper start - StructuresLocateEvent
+        final org.bukkit.World bukkitWorld = level.getWorld();
+        final org.bukkit.Location origin = org.bukkit.craftbukkit.util.CraftLocation.toBukkit(pos, level);
+        final List<org.bukkit.generator.structure.Structure> apiStructures = structure.stream().map(Holder::value).map(nms -> org.bukkit.craftbukkit.generator.structure.CraftStructure.minecraftToBukkit(nms)).toList();
+        if (!apiStructures.isEmpty()) {
+            final io.papermc.paper.event.world.StructuresLocateEvent event = new io.papermc.paper.event.world.StructuresLocateEvent(bukkitWorld, origin, apiStructures, searchRadius, skipKnownStructures);
+            if (!event.callEvent()) {
+                return null;
+            }
+            if (event.getResult() != null) {
+                return Pair.of(io.papermc.paper.util.MCUtil.toBlockPos(event.getResult().pos()), level.registryAccess().lookupOrThrow(Registries.STRUCTURE).wrapAsHolder(org.bukkit.craftbukkit.generator.structure.CraftStructure.bukkitToMinecraft(event.getResult().structure())));
+            }
+            pos = org.bukkit.craftbukkit.util.CraftLocation.toBlockPosition(event.getOrigin());
+            searchRadius = event.getRadius();
+            skipKnownStructures = event.shouldFindUnexplored();
+            structure = HolderSet.direct(api -> level.registryAccess().lookupOrThrow(Registries.STRUCTURE).wrapAsHolder(org.bukkit.craftbukkit.generator.structure.CraftStructure.bukkitToMinecraft(api)), event.getStructures());
+        }
+        // Paper end
         ChunkGeneratorStructureState generatorState = level.getChunkSource().getGeneratorState();
         Map<StructurePlacement, Set<Holder<Structure>>> map = new Object2ObjectArrayMap<>();
 
@@ -221,6 +_,7 @@
             BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos();
 
             for (ChunkPos chunkPos : ringPositionsFor) {
+                if (!level.paperConfig().environment.locateStructuresOutsideWorldBorder && !level.getWorldBorder().isChunkInBounds(chunkPos.x, chunkPos.z)) { continue; } // Paper - Bound treasure maps to world border
                 mutableBlockPos.set(SectionPos.sectionToBlockCoord(chunkPos.x, 8), 32, SectionPos.sectionToBlockCoord(chunkPos.z, 8));
                 double d1 = mutableBlockPos.distSqr(pos);
                 boolean flag = pair == null || d1 < d;
@@ -254,11 +_,15 @@
         int spacing = spreadPlacement.spacing();
 
         for (int i = -z; i <= z; i++) {
-            boolean flag = i == -z || i == z;
+            // Paper start - Perf: iterate over border chunks instead of entire square chunk area
+            final int radius = z;
+            boolean flag = i == -z || i == z; final boolean onBorderAlongZAxis = flag; // Paper - OBFHELPER
 
-            for (int i1 = -z; i1 <= z; i1++) {
-                boolean flag1 = i1 == -z || i1 == z;
-                if (flag || flag1) {
+            for (int i1 = -radius; i1 <= radius; i1 += onBorderAlongZAxis ? 1 : radius * 2) {
+                // boolean flag1 = i1 == -z || i1 == z;
+                // if (flag || flag1) {
+                if (true) {
+            // Paper end - Perf: iterate over border chunks instead of entire square chunk area
                     int i2 = x + spacing * i;
                     int i3 = y + spacing * i1;
                     ChunkPos potentialStructureChunk = spreadPlacement.getPotentialStructureChunk(seed, i2, i3);
@@ -311,7 +_,7 @@
         }
     }
 
-    public void applyBiomeDecoration(WorldGenLevel level, ChunkAccess chunk, StructureManager structureManager) {
+    public void addVanillaDecorations(WorldGenLevel level, ChunkAccess chunk, StructureManager structureManager) { // CraftBukkit - rename
         ChunkPos pos = chunk.getPos();
         if (!SharedConstants.debugVoidTerrain(pos)) {
             SectionPos sectionPos = SectionPos.of(pos, level.getMinSectionY());
@@ -382,7 +_,14 @@
                             int i3 = ints[i2];
                             PlacedFeature placedFeature = stepFeatureData1.features().get(i3);
                             Supplier<String> supplier1 = () -> registry1.getResourceKey(placedFeature).map(Object::toString).orElseGet(placedFeature::toString);
-                            worldgenRandom.setFeatureSeed(l, i3, i);
+                            // Paper start - Configurable feature seeds; change populationSeed used in random
+                            long featurePopulationSeed = l;
+                            final long configFeatureSeed = level.getMinecraftWorld().paperConfig().featureSeeds.features.getLong(placedFeature.feature());
+                            if (configFeatureSeed != -1) {
+                                featurePopulationSeed = worldgenRandom.setDecorationSeed(configFeatureSeed, blockPos.getX(), blockPos.getZ()); // See WorldgenRandom.setDecorationSeed from above
+                            }
+                            worldgenRandom.setFeatureSeed(featurePopulationSeed, i3, i);
+                            // Paper end - Configurable feature seeds
 
                             try {
                                 level.setCurrentlyGenerating(supplier1);
@@ -404,6 +_,32 @@
             }
         }
     }
+   // CraftBukkit start
+    public void applyBiomeDecoration(WorldGenLevel level, ChunkAccess chunk, StructureManager structureManager) {
+        this.applyBiomeDecoration(level, chunk, structureManager, true);
+    }
+
+    public void applyBiomeDecoration(WorldGenLevel level, ChunkAccess chunk, StructureManager structureManager, boolean addVanillaDecorations) {
+        if (addVanillaDecorations) {
+            this.addVanillaDecorations(level, chunk, structureManager);
+        }
+
+        org.bukkit.World world = level.getMinecraftWorld().getWorld();
+        // only call when a populator is present (prevents unnecessary entity conversion)
+        if (!world.getPopulators().isEmpty()) {
+            org.bukkit.craftbukkit.generator.CraftLimitedRegion limitedRegion = new org.bukkit.craftbukkit.generator.CraftLimitedRegion(level, chunk.getPos());
+            int x = chunk.getPos().x;
+            int z = chunk.getPos().z;
+            for (org.bukkit.generator.BlockPopulator populator : world.getPopulators()) {
+                WorldgenRandom seededrandom = new WorldgenRandom(new net.minecraft.world.level.levelgen.LegacyRandomSource(level.getSeed()));
+                seededrandom.setDecorationSeed(level.getSeed(), x, z);
+                populator.populate(world, new org.bukkit.craftbukkit.util.RandomSourceWrapper.RandomWrapper(seededrandom), x, z, limitedRegion);
+            }
+            limitedRegion.saveEntities();
+            limitedRegion.breakLink();
+        }
+    }
+    // CraftBukkit end
 
     private static BoundingBox getWritableArea(ChunkAccess chunk) {
         ChunkPos pos = chunk.getPos();
@@ -478,7 +_,7 @@
                         }
                     }
 
-                    if (structurePlacement.isStructureChunk(structureState, pos.x, pos.z)) {
+                    if (structurePlacement.isStructureChunk(structureState, pos.x, pos.z, structurePlacement instanceof net.minecraft.world.level.chunk.ChunkGeneratorStructureState.KeyedRandomSpreadStructurePlacement keyed ? keyed.key : null)) { // Paper - Add missing structure set seed configs
                         if (list.size() == 1) {
                             this.tryGenerateStructure(
                                 list.get(0),
@@ -572,6 +_,14 @@
             predicate
         );
         if (structureStart.isValid()) {
+            // CraftBukkit start
+            BoundingBox box = structureStart.getBoundingBox();
+            org.bukkit.event.world.AsyncStructureSpawnEvent event = new org.bukkit.event.world.AsyncStructureSpawnEvent(structureManager.level.getMinecraftWorld().getWorld(), org.bukkit.craftbukkit.generator.structure.CraftStructure.minecraftToBukkit(structure), new org.bukkit.util.BoundingBox(box.minX(), box.minY(), box.minZ(), box.maxX(), box.maxY(), box.maxZ()), chunkPos.x, chunkPos.z);
+            org.bukkit.Bukkit.getPluginManager().callEvent(event);
+            if (event.isCancelled()) {
+                return true;
+            }
+            // CraftBukkit end
             structureManager.setStartForStructure(sectionPos, structure, structureStart, chunk);
             return true;
         } else {
