From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: dfsek <dfsek@protonmail.com>
Date: Wed, 16 Sep 2020 01:12:29 -0700
Subject: [PATCH] Add StructuresLocateEvent

Co-authored-by: Jake Potrebic <jake.m.potrebic@gmail.com>

diff --git a/src/main/java/io/papermc/paper/registry/RegistryKey.java b/src/main/java/io/papermc/paper/registry/RegistryKey.java
index 6f39e343147803e15e7681c993b8797a629702e7..cbff75f19e54b37c762b209b04f6d4799152cf5b 100644
--- a/src/main/java/io/papermc/paper/registry/RegistryKey.java
+++ b/src/main/java/io/papermc/paper/registry/RegistryKey.java
@@ -1,8 +1,13 @@
 package io.papermc.paper.registry;
 
+import io.papermc.paper.world.structure.ConfiguredStructure;
 import net.minecraft.core.Registry;
 import net.minecraft.resources.ResourceKey;
+import net.minecraft.world.level.levelgen.feature.ConfiguredStructureFeature;
 import org.bukkit.Keyed;
 
 public record RegistryKey<API extends Keyed, MINECRAFT>(Class<API> apiClass, ResourceKey<? extends Registry<MINECRAFT>> resourceKey) {
+
+    public static final RegistryKey<ConfiguredStructure, ConfiguredStructureFeature<?, ?>> CONFIGURED_STRUCTURE_REGISTRY = new RegistryKey<>(ConfiguredStructure.class, Registry.CONFIGURED_STRUCTURE_FEATURE_REGISTRY);
+
 }
diff --git a/src/main/java/io/papermc/paper/world/structure/PaperConfiguredStructure.java b/src/main/java/io/papermc/paper/world/structure/PaperConfiguredStructure.java
new file mode 100644
index 0000000000000000000000000000000000000000..41f6c2e1e60fc32e6393097711412ca2ad643e57
--- /dev/null
+++ b/src/main/java/io/papermc/paper/world/structure/PaperConfiguredStructure.java
@@ -0,0 +1,42 @@
+package io.papermc.paper.world.structure;
+
+import io.papermc.paper.registry.PaperRegistry;
+import io.papermc.paper.registry.RegistryKey;
+import net.minecraft.core.Registry;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.world.level.levelgen.feature.ConfiguredStructureFeature;
+import net.minecraft.world.level.levelgen.feature.StructureFeature;
+import org.bukkit.NamespacedKey;
+import org.bukkit.StructureType;
+import org.checkerframework.checker.nullness.qual.NonNull;
+import org.checkerframework.framework.qual.DefaultQualifier;
+
+import java.util.Objects;
+import java.util.function.Supplier;
+
+@DefaultQualifier(NonNull.class)
+public final class PaperConfiguredStructure {
+
+    private PaperConfiguredStructure() {
+    }
+
+    public static void init() {
+        new ConfiguredStructureRegistry().register();
+    }
+
+    static final class ConfiguredStructureRegistry extends PaperRegistry<ConfiguredStructure, ConfiguredStructureFeature<?, ?>> {
+
+        private static final Supplier<Registry<StructureFeature<?>>> STRUCTURE_FEATURE_REGISTRY = registryFor(Registry.STRUCTURE_FEATURE_REGISTRY);
+
+        public ConfiguredStructureRegistry() {
+            super(RegistryKey.CONFIGURED_STRUCTURE_REGISTRY);
+        }
+
+        @Override
+        public ConfiguredStructure convertToApi(NamespacedKey key, ConfiguredStructureFeature<?, ?> nms) {
+            final ResourceLocation structureFeatureLoc = Objects.requireNonNull(STRUCTURE_FEATURE_REGISTRY.get().getKey(nms.feature));
+            final StructureType structureType = Objects.requireNonNull(StructureType.getStructureTypes().get(structureFeatureLoc.getPath()), structureFeatureLoc + " could not be converted to an API type");
+            return new ConfiguredStructure(key, structureType);
+        }
+    }
+}
diff --git a/src/main/java/net/minecraft/server/MinecraftServer.java b/src/main/java/net/minecraft/server/MinecraftServer.java
index 696d382283f94335f6e23eb12c6fa209c51cff8c..e74885c51c2970af13d847ec34a415e5aa18b01c 100644
--- a/src/main/java/net/minecraft/server/MinecraftServer.java
+++ b/src/main/java/net/minecraft/server/MinecraftServer.java
@@ -2032,7 +2032,7 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop<TickTa
             this.packRepository.setSelected(dataPacks);
             this.worldData.setDataPackConfig(MinecraftServer.getSelectedPacks(this.packRepository));
             this.resources.managers.updateRegistryTags(this.registryAccess());
-            io.papermc.paper.PaperRegistry.clearCaches(); // Paper
+            io.papermc.paper.registry.PaperRegistry.clearCaches(); // Paper
             new io.papermc.paper.event.server.ServerResourcesReloadedEvent(cause).callEvent(); // Paper
             if (Thread.currentThread() != this.serverThread) return; // Paper
             //this.getPlayerList().saveAll(); // Paper - we don't need to do this
diff --git a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
index f9fc2fc63080a60fe61ebb08ddd93c4f189df84d..97b29671a8df6d102be3982443f6f784e2e1d6e1 100644
--- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
@@ -294,6 +294,26 @@ public abstract class ChunkGenerator implements BiomeManager.NoiseBiomeSource {
 
     @Nullable
     public Pair<BlockPos, Holder<ConfiguredStructureFeature<?, ?>>> findNearestMapFeature(ServerLevel worldserver, HolderSet<ConfiguredStructureFeature<?, ?>> holderset, BlockPos center, int radius, boolean skipExistingChunks) {
+        // Paper start - StructureLocateEvent
+        final org.bukkit.World world = worldserver.getWorld();
+        final org.bukkit.Location origin = net.minecraft.server.MCUtil.toLocation(worldserver, center);
+        final var paperRegistry = io.papermc.paper.registry.PaperRegistry.getRegistry(io.papermc.paper.registry.RegistryKey.CONFIGURED_STRUCTURE_REGISTRY);
+        final List<io.papermc.paper.world.structure.ConfiguredStructure> configuredStructures = new ArrayList<>();
+        for (Holder<net.minecraft.world.level.levelgen.feature.ConfiguredStructureFeature<?, ?>> holder : holderset) {
+            configuredStructures.add(paperRegistry.convertToApi(holder));
+        }
+        final io.papermc.paper.event.world.StructuresLocateEvent event = new io.papermc.paper.event.world.StructuresLocateEvent(world, origin, configuredStructures, radius, skipExistingChunks);
+        if (!event.callEvent()) {
+            return null;
+        }
+        if (event.getResult() != null) {
+            return Pair.of(net.minecraft.server.MCUtil.toBlockPosition(event.getResult().position()), paperRegistry.getMinecraftHolder(event.getResult().configuredStructure()));
+        }
+        center = net.minecraft.server.MCUtil.toBlockPosition(event.getOrigin());
+        radius = event.getRadius();
+        skipExistingChunks = event.shouldFindUnexplored();
+        holderset = HolderSet.direct(paperRegistry::getMinecraftHolder, event.getConfiguredStructures());
+        // Paper end
         Set<Holder<Biome>> set = (Set) holderset.stream().flatMap((holder) -> {
             return ((ConfiguredStructureFeature) holder.value()).biomes().stream();
         }).collect(Collectors.toSet());
diff --git a/src/main/java/net/minecraft/world/level/levelgen/feature/ConfiguredStructureFeature.java b/src/main/java/net/minecraft/world/level/levelgen/feature/ConfiguredStructureFeature.java
index af9ef29f5c5a600e4544ba735c833699cc93f93a..473a54963fbe08beeff26a828827f9f72d8a29b8 100644
--- a/src/main/java/net/minecraft/world/level/levelgen/feature/ConfiguredStructureFeature.java
+++ b/src/main/java/net/minecraft/world/level/levelgen/feature/ConfiguredStructureFeature.java
@@ -38,6 +38,7 @@ public class ConfiguredStructureFeature<FC extends FeatureConfiguration, F exten
     public final HolderSet<Biome> biomes;
     public final Map<MobCategory, StructureSpawnOverride> spawnOverrides;
     public final boolean adaptNoise;
+    static { io.papermc.paper.world.structure.PaperConfiguredStructure.init(); } // Paper
 
     public ConfiguredStructureFeature(F feature, FC config, HolderSet<Biome> biomes, boolean bl, Map<MobCategory, StructureSpawnOverride> map) {
         this.feature = feature;
diff --git a/src/test/java/io/papermc/paper/world/structure/ConfiguredStructureTest.java b/src/test/java/io/papermc/paper/world/structure/ConfiguredStructureTest.java
new file mode 100644
index 0000000000000000000000000000000000000000..29c0209327374d5a4dad4e3bacdba7fa56d80749
--- /dev/null
+++ b/src/test/java/io/papermc/paper/world/structure/ConfiguredStructureTest.java
@@ -0,0 +1,89 @@
+package io.papermc.paper.world.structure;
+
+import io.papermc.paper.registry.Reference;
+import net.minecraft.data.BuiltinRegistries;
+import net.minecraft.resources.ResourceKey;
+import net.minecraft.resources.ResourceLocation;
+import net.minecraft.server.Bootstrap;
+import net.minecraft.world.level.levelgen.feature.ConfiguredStructureFeature;
+import net.minecraft.world.level.levelgen.structure.BuiltinStructures;
+import org.bukkit.NamespacedKey;
+import org.bukkit.craftbukkit.util.CraftNamespacedKey;
+import org.bukkit.support.AbstractTestingBase;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.PrintStream;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.StringJoiner;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+public class ConfiguredStructureTest extends AbstractTestingBase {
+
+    private static final Map<ResourceLocation, String> BUILT_IN_STRUCTURES = new LinkedHashMap<>();
+    private static final Map<NamespacedKey, Reference<?>> DEFAULT_CONFIGURED_STRUCTURES = new LinkedHashMap<>();
+
+    private static PrintStream out;
+
+    @BeforeClass
+    public static void collectStructures() throws ReflectiveOperationException {
+        out = System.out;
+        System.setOut(Bootstrap.STDOUT);
+        for (Field field : BuiltinStructures.class.getDeclaredFields()) {
+            if (field.getType().equals(ResourceKey.class) && Modifier.isStatic(field.getModifiers())) {
+                BUILT_IN_STRUCTURES.put(((ResourceKey<?>) field.get(null)).location(), field.getName());
+            }
+        }
+        for (Field field : ConfiguredStructure.class.getDeclaredFields()) {
+            if (field.getType().equals(Reference.class) && Modifier.isStatic(field.getModifiers())) {
+                final Reference<?> ref = (Reference<?>) field.get(null);
+                DEFAULT_CONFIGURED_STRUCTURES.put(ref.getKey(), ref);
+            }
+        }
+    }
+
+    @Test
+    public void testMinecraftToApi() {
+        assertEquals("configured structure maps should be the same size", BUILT_IN_STRUCTURES.size(), BuiltinRegistries.CONFIGURED_STRUCTURE_FEATURE.size());
+
+        Map<ResourceLocation, ConfiguredStructureFeature<?, ?>> missing = new LinkedHashMap<>();
+        for (ConfiguredStructureFeature<?, ?> feature : BuiltinRegistries.CONFIGURED_STRUCTURE_FEATURE) {
+            final ResourceLocation key = BuiltinRegistries.CONFIGURED_STRUCTURE_FEATURE.getKey(feature);
+            assertNotNull("Missing built-in registry key", key);
+            if (DEFAULT_CONFIGURED_STRUCTURES.get(CraftNamespacedKey.fromMinecraft(key)) == null) {
+                missing.put(key, feature);
+            }
+        }
+
+        assertTrue(printMissing(missing), missing.isEmpty());
+    }
+
+    @Test
+    public void testApiToMinecraft() {
+        for (NamespacedKey apiKey : DEFAULT_CONFIGURED_STRUCTURES.keySet()) {
+            assertTrue(apiKey + " does not have a minecraft counterpart", BuiltinRegistries.CONFIGURED_STRUCTURE_FEATURE.containsKey(CraftNamespacedKey.toMinecraft(apiKey)));
+        }
+    }
+
+    private static String printMissing(Map<ResourceLocation, ConfiguredStructureFeature<?, ?>> missing) {
+        final StringJoiner joiner = new StringJoiner("\n", "Missing: \n", "");
+
+        missing.forEach((key, configuredFeature) -> {
+            joiner.add("public static final Reference<ConfiguredStructure> " + BUILT_IN_STRUCTURES.get(key) + " = create(\"" + key.getPath() + "\");");
+        });
+
+        return joiner.toString();
+    }
+
+    @AfterClass
+    public static void after() {
+        System.setOut(out);
+    }
+}
