From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Nassim Jahnke <jahnke.nassim@gmail.com>
Date: Tue, 31 Aug 2021 17:05:27 +0200
Subject: [PATCH] Configurable feature seeds

Co-authored-by: Thonk <30448663+ExcessiveAmountsOfZombies@users.noreply.github.com>

diff --git a/src/main/java/co/aikar/timings/TimingsExport.java b/src/main/java/co/aikar/timings/TimingsExport.java
index 7d44abcb4fff9717a1af55879deb7eb9c2d9e7e9..e29b0a90019b12bd6586ad0f7b5314f307e527ba 100644
--- a/src/main/java/co/aikar/timings/TimingsExport.java
+++ b/src/main/java/co/aikar/timings/TimingsExport.java
@@ -274,7 +274,7 @@ public class TimingsExport extends Thread {
         JSONObject object = new JSONObject();
         for (String key : config.getKeys(false)) {
             String fullKey = (parentKey != null ? parentKey + "." + key : key);
-            if (fullKey.equals("database") || fullKey.equals("settings.bungeecord-addresses") || TimingsManager.hiddenConfigs.contains(fullKey) || key.startsWith("seed-") || key.equals("worldeditregentempworld")) {
+            if (fullKey.equals("database") || fullKey.equals("settings.bungeecord-addresses") || TimingsManager.hiddenConfigs.contains(fullKey) || key.startsWith("seed-") || key.equals("worldeditregentempworld") || key.equals("feature-seeds")) {
                 continue;
             }
             final Object val = config.get(key);
diff --git a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
index 410e1e67ca625fc01553d61c2b0eaeec10317e5e..0a611df010c9c5e621f10ae55a0ce66fd0cae0e2 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -972,6 +972,55 @@ public class PaperWorldConfig {
         return table;
     }
 
+    public it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<net.minecraft.resources.ResourceLocation> featureSeeds = new it.unimi.dsi.fastutil.objects.Object2LongOpenHashMap<>();
+    private void featureSeeds() {
+        featureSeeds.defaultReturnValue(-1);
+        final boolean randomise = getBoolean("feature-seeds.generate-random-seeds-for-all", false);
+        final ConfigurationSection defaultSection = config.getConfigurationSection("world-settings.default.feature-seeds");
+        final ConfigurationSection section = config.getConfigurationSection("world-settings." + worldName + ".feature-seeds");
+        final net.minecraft.core.Registry<net.minecraft.world.level.levelgen.feature.ConfiguredFeature<?, ?>> registry
+            = net.minecraft.server.MinecraftServer.getServer().registryAccess().registryOrThrow(net.minecraft.core.Registry.CONFIGURED_FEATURE_REGISTRY);
+        if (section != null) {
+            loadFeatureSeeds(section, registry);
+        }
+
+        // Also use default set seeds if not already set per world
+        loadFeatureSeeds(defaultSection, registry);
+
+        if (randomise) {
+            final Map<String, Object> randomisedSeeds = new HashMap<>();
+            final java.util.Random random = new java.security.SecureRandom();
+            for (final net.minecraft.resources.ResourceLocation resourceLocation : registry.keySet()) {
+                if (featureSeeds.containsKey(resourceLocation)) {
+                    continue;
+                }
+
+                final long seed = random.nextLong();
+                randomisedSeeds.put("world-settings." + worldName + ".feature-seeds." + resourceLocation.getPath(), seed);
+                featureSeeds.put(resourceLocation, seed);
+            }
+            if (!randomisedSeeds.isEmpty()) {
+                config.addDefaults(randomisedSeeds);
+            }
+        }
+    }
+
+    private void loadFeatureSeeds(final ConfigurationSection section, final net.minecraft.core.Registry<net.minecraft.world.level.levelgen.feature.ConfiguredFeature<?, ?>> registry) {
+        for (final String key : section.getKeys(false)) {
+            if (!(section.get(key) instanceof Number)) {
+                continue;
+            }
+
+            final net.minecraft.resources.ResourceLocation location = new net.minecraft.resources.ResourceLocation(key);
+            if (!registry.containsKey(location)) {
+                logError("Invalid feature resource location: " + location);
+                continue;
+            }
+
+            featureSeeds.putIfAbsent(location, section.getLong(key));
+        }
+    }
+
     public int getBehaviorTickRate(String typeName, String entityType, int def) {
         return getIntOrDefault(behaviorTickRates, typeName, entityType, def);
     }
diff --git a/src/main/java/net/minecraft/world/level/biome/Biome.java b/src/main/java/net/minecraft/world/level/biome/Biome.java
index a7a7e6cd87270e64a92448f03f8b0b0c7e375ec7..2b814006fa30dd233dcb345d1d20ce3bf6469053 100644
--- a/src/main/java/net/minecraft/world/level/biome/Biome.java
+++ b/src/main/java/net/minecraft/world/level/biome/Biome.java
@@ -221,7 +221,7 @@ public final class Biome {
 
     public void generate(StructureFeatureManager structureAccessor, ChunkGenerator chunkGenerator, WorldGenRegion region, long populationSeed, WorldgenRandom random, BlockPos origin) {
         List<List<Supplier<ConfiguredFeature<?, ?>>>> list = this.generationSettings.features();
-        Registry<ConfiguredFeature<?, ?>> registry = region.registryAccess().registryOrThrow(Registry.CONFIGURED_FEATURE_REGISTRY);
+        Registry<ConfiguredFeature<?, ?>> registry = region.registryAccess().registryOrThrow(Registry.CONFIGURED_FEATURE_REGISTRY); // Paper - diff on change
         Registry<StructureFeature<?>> registry2 = region.registryAccess().registryOrThrow(Registry.STRUCTURE_FEATURE_REGISTRY);
         int i = GenerationStep.Decoration.values().length;
 
@@ -263,7 +263,16 @@ public final class Biome {
                     Supplier<String> supplier3 = () -> {
                         return registry.getResourceKey(configuredFeature).map(Object::toString).orElseGet(configuredFeature::toString);
                     };
-                    random.setFeatureSeed(populationSeed, k, j);
+                    // Paper start - change populationSeed used in random
+                    long featurePopulationSeed = populationSeed;
+                    final ResourceLocation location = registry.getKey(configuredFeature);
+                    final long configFeatureSeed = region.getMinecraftWorld().paperConfig.featureSeeds.getLong(location);
+                    if (configFeatureSeed != -1) {
+                        final ChunkPos center = region.getCenter();
+                        featurePopulationSeed = random.setDecorationSeed(configFeatureSeed, center.getMinBlockX(), center.getMinBlockZ()); // See ChunkGenerator#addVanillaDecorations
+                    }
+                    random.setFeatureSeed(featurePopulationSeed, k, j);
+                    // Paper end
 
                     try {
                         region.setCurrentlyGenerating(supplier3);