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 ee53453440177537fc653ea156785d7591498614..5e3b7fb2e0b7608610555cd23e7ad25a05883181 100644
--- a/src/main/java/co/aikar/timings/TimingsExport.java
+++ b/src/main/java/co/aikar/timings/TimingsExport.java
@@ -273,7 +273,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 e5eeab49d167a9a151301ca910e1421550e14245..0a70dfd7880f5fcf1292dd2fdae2964bc2f51d61 100644
--- a/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
+++ b/src/main/java/com/destroystokyo/paper/PaperWorldConfig.java
@@ -946,6 +946,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/chunk/ChunkGenerator.java b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
index 3461e62af6de8e4c5c72ff27937948d314bebf6a..3f0d83a90e1319baa0622b708b3ba940d3cee64a 100644
--- a/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
+++ b/src/main/java/net/minecraft/world/level/chunk/ChunkGenerator.java
@@ -277,7 +277,7 @@ public abstract class ChunkGenerator implements BiomeManager.NoiseBiomeSource {
             int j = list.size();
 
             try {
-                Registry<PlacedFeature> iregistry = generatoraccessseed.registryAccess().registryOrThrow(Registry.PLACED_FEATURE_REGISTRY);
+                Registry<PlacedFeature> iregistry = generatoraccessseed.registryAccess().registryOrThrow(Registry.PLACED_FEATURE_REGISTRY); // Paper - diff on change
                 Registry<StructureFeature<?>> iregistry1 = generatoraccessseed.registryAccess().registryOrThrow(Registry.STRUCTURE_FEATURE_REGISTRY);
                 int k = Math.max(GenerationStep.Decoration.values().length, j);
 
@@ -351,7 +351,15 @@ public abstract class ChunkGenerator implements BiomeManager.NoiseBiomeSource {
                                 return (String) optional.orElseGet(placedfeature::toString);
                             };
 
-                            seededrandom.setFeatureSeed(i, l1, l);
+                            // Paper start - change populationSeed used in random
+                            long featurePopulationSeed = i;
+                            final net.minecraft.resources.ResourceLocation location = iregistry.getKey(placedfeature);
+                            final long configFeatureSeed = generatoraccessseed.getMinecraftWorld().paperConfig.featureSeeds.getLong(location);
+                            if (configFeatureSeed != -1) {
+                                featurePopulationSeed = seededrandom.setDecorationSeed(configFeatureSeed, blockposition.getX(), blockposition.getZ()); // See seededrandom.setDecorationSeed from above
+                            }
+                            seededrandom.setFeatureSeed(featurePopulationSeed, l1, l);
+                            // Paper end
 
                             try {
                                 generatoraccessseed.setCurrentlyGenerating(supplier1);
