From 83b4d889b7460938dcca2d2b9488f347ec982a92 Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Thu, 13 Jan 2022 23:05:53 -0800 Subject: [PATCH] Add missing structure set seed configs The 4 missing structure set seed configs are strongholds, mineshafts, buried treasure, and ancient cities. Strongholds use a ring placement scheme which isn't random so they utilize the world seed by default, this adds a config to override it for just generating the ring positions. Mineshafts and Buried Treasure structure sets are special cases where the "salt" that can be defined for them via datapacks has 0 effect because the difference between the spacing and separation is 1 which is used as the upper bound in the random with salt. So the random always returns the same int (0) so the salt has no effect. This adds seeds/salts to the frequency reducer which has a similar effect. Co-authored-by: William Blake Galbreath --- .../level/chunk/ChunkGenerator.java.patch | 30 +++--- .../ChunkGeneratorStructureState.java.patch | 78 ++++++++++++++-- .../structure/StructureCheck.java.patch | 30 ++++++ .../placement/StructurePlacement.java.patch | 93 +++++++++++++++++++ .../java/org/spigotmc/SpigotWorldConfig.java | 20 ++++ .../structure/StructureSeedConfigTest.java | 77 +++++++++++++++ 6 files changed, 311 insertions(+), 17 deletions(-) create mode 100644 paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java.patch create mode 100644 paper-server/src/test/java/io/papermc/paper/world/structure/StructureSeedConfigTest.java diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch index 74688a9d42..bdbd7b1b26 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGenerator.java.patch @@ -134,7 +134,7 @@ } catch (Exception exception1) { CrashReport crashreport1 = CrashReport.forThrowable(exception1, "Feature placement"); -@@ -435,15 +461,42 @@ +@@ -435,7 +461,7 @@ } } @@ -143,12 +143,10 @@ } catch (Exception exception2) { CrashReport crashreport2 = CrashReport.forThrowable(exception2, "Biome decoration"); - crashreport2.addCategory("Generation").setDetail("CenterX", (Object) chunkcoordintpair.x).setDetail("CenterZ", (Object) chunkcoordintpair.z).setDetail("Decoration Seed", (Object) i); - throw new ReportedException(crashreport2); -+ } -+ } -+ } -+ +@@ -445,6 +471,33 @@ + } + } + + // CraftBukkit start + public void applyBiomeDecoration(WorldGenLevel world, ChunkAccess chunk, StructureManager structureAccessor) { + this.applyBiomeDecoration(world, chunk, structureAccessor, true); @@ -169,15 +167,25 @@ + WorldgenRandom seededrandom = new WorldgenRandom(new net.minecraft.world.level.levelgen.LegacyRandomSource(generatoraccessseed.getSeed())); + seededrandom.setDecorationSeed(generatoraccessseed.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 chunkcoordintpair = chunk.getPos(); + int i = chunkcoordintpair.getMinBlockX(); +@@ -521,7 +574,7 @@ + } + } + +- if (structureplacement.isStructureChunk(placementCalculator, chunkcoordintpair.x, chunkcoordintpair.z)) { ++ if (structureplacement.isStructureChunk(placementCalculator, chunkcoordintpair.x, chunkcoordintpair.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((StructureSet.StructureSelectionEntry) list.get(0), structureAccessor, registryManager, randomstate, structureTemplateManager, placementCalculator.getLevelSeed(), chunk, chunkcoordintpair, sectionposition, dimension); + } else { @@ -582,6 +635,14 @@ StructureStart structurestart = structure.generate(weightedEntry.structure(), dimension, dynamicRegistryManager, this, this.biomeSource, noiseConfig, structureManager, seed, pos, j, chunk, predicate); diff --git a/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch index f023cd3e0d..f9ebf0e3b1 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/chunk/ChunkGeneratorStructureState.java.patch @@ -17,9 +17,11 @@ public class ChunkGeneratorStructureState { private static final Logger LOGGER = LogUtils.getLogger(); -@@ -45,21 +51,81 @@ +@@ -44,22 +50,109 @@ + private final Map>> ringPositions = new Object2ObjectArrayMap(); private boolean hasGeneratedPositions; private final List> possibleStructureSets; ++ public final SpigotWorldConfig conf; // Paper - Add missing structure set seed configs - public static ChunkGeneratorStructureState createForFlat(RandomState noiseConfig, long seed, BiomeSource biomeSource, Stream> structureSets) { - List> list = structureSets.filter((holder) -> { @@ -30,7 +32,7 @@ }).toList(); - return new ChunkGeneratorStructureState(noiseConfig, biomeSource, seed, 0L, list); -+ return new ChunkGeneratorStructureState(randomstate, worldchunkmanager, i, 0L, ChunkGeneratorStructureState.injectSpigot(list, conf)); // Spigot ++ return new ChunkGeneratorStructureState(randomstate, worldchunkmanager, i, 0L, ChunkGeneratorStructureState.injectSpigot(list, conf), conf); // Spigot } - public static ChunkGeneratorStructureState createForNormal(RandomState noiseConfig, long seed, BiomeSource biomeSource, HolderLookup structureSetRegistry) { @@ -42,14 +44,24 @@ }).collect(Collectors.toUnmodifiableList()); - return new ChunkGeneratorStructureState(noiseConfig, biomeSource, seed, seed, list); -+ return new ChunkGeneratorStructureState(randomstate, worldchunkmanager, i, i, ChunkGeneratorStructureState.injectSpigot(list, conf)); // Spigot ++ return new ChunkGeneratorStructureState(randomstate, worldchunkmanager, i, i, ChunkGeneratorStructureState.injectSpigot(list, conf), conf); // Spigot + } ++ // Paper start - Add missing structure set seed configs; horrible hack because spigot creates a ton of direct Holders which lose track of the identifying key ++ public static final class KeyedRandomSpreadStructurePlacement extends RandomSpreadStructurePlacement { ++ public final net.minecraft.resources.ResourceKey key; ++ public KeyedRandomSpreadStructurePlacement(net.minecraft.resources.ResourceKey key, net.minecraft.core.Vec3i locateOffset, FrequencyReductionMethod frequencyReductionMethod, float frequency, int salt, java.util.Optional exclusionZone, int spacing, int separation, net.minecraft.world.level.levelgen.structure.placement.RandomSpreadType spreadType) { ++ super(locateOffset, frequencyReductionMethod, frequency, salt, exclusionZone, spacing, separation, spreadType); ++ this.key = key; ++ } ++ } ++ // Paper end - Add missing structure set seed configs + + // Spigot start + private static List> injectSpigot(List> list, SpigotWorldConfig conf) { + return list.stream().map((holder) -> { + StructureSet structureset = holder.value(); -+ if (structureset.placement() instanceof RandomSpreadStructurePlacement randomConfig) { ++ final Holder newHolder; // Paper - Add missing structure set seed configs ++ if (structureset.placement() instanceof RandomSpreadStructurePlacement randomConfig && holder.unwrapKey().orElseThrow().location().getNamespace().equals(net.minecraft.resources.ResourceLocation.DEFAULT_NAMESPACE)) { // Paper - Add missing structure set seed configs; check namespace cause datapacks could add structure sets with the same path + String name = holder.unwrapKey().orElseThrow().location().getPath(); + int seed = randomConfig.salt; + @@ -96,14 +108,68 @@ + case "villages": + seed = conf.villageSeed; + break; ++ // Paper start - Add missing structure set seed configs ++ case "ancient_cities": ++ seed = conf.ancientCitySeed; ++ break; ++ case "trail_ruins": ++ seed = conf.trailRuinsSeed; ++ break; ++ case "trial_chambers": ++ seed = conf.trialChambersSeed; ++ break; ++ // Paper end - Add missing structure set seed configs + } + -+ structureset = new StructureSet(structureset.structures(), new RandomSpreadStructurePlacement(randomConfig.locateOffset, randomConfig.frequencyReductionMethod, randomConfig.frequency, seed, randomConfig.exclusionZone, randomConfig.spacing(), randomConfig.separation(), randomConfig.spreadType())); ++ // Paper start - Add missing structure set seed configs ++ structureset = new StructureSet(structureset.structures(), new KeyedRandomSpreadStructurePlacement(holder.unwrapKey().orElseThrow(), randomConfig.locateOffset, randomConfig.frequencyReductionMethod, randomConfig.frequency, seed, randomConfig.exclusionZone, randomConfig.spacing(), randomConfig.separation(), randomConfig.spreadType())); ++ newHolder = Holder.direct(structureset); // I really wish we didn't have to do this here ++ } else { ++ newHolder = holder; + } -+ return Holder.direct(structureset); ++ return newHolder; ++ // Paper end - Add missing structure set seed configs + }).collect(Collectors.toUnmodifiableList()); } + // Spigot end private static boolean hasBiomesForStructureSet(StructureSet structureSet, BiomeSource biomeSource) { Stream> stream = structureSet.structures().stream().flatMap((structureset_a) -> { +@@ -73,12 +166,13 @@ + return stream.anyMatch(set::contains); + } + +- private ChunkGeneratorStructureState(RandomState noiseConfig, BiomeSource biomeSource, long structureSeed, long concentricRingSeed, List> structureSets) { ++ private ChunkGeneratorStructureState(RandomState noiseConfig, BiomeSource biomeSource, long structureSeed, long concentricRingSeed, List> structureSets, SpigotWorldConfig conf) { // Paper - Add missing structure set seed configs + this.randomState = noiseConfig; + this.levelSeed = structureSeed; + this.biomeSource = biomeSource; + this.concentricRingsSeed = concentricRingSeed; + this.possibleStructureSets = structureSets; ++ this.conf = conf; // Paper - Add missing structure set seed configs + } + + public List> possibleStructureSets() { +@@ -132,7 +226,13 @@ + HolderSet holderset = placement.preferredBiomes(); + RandomSource randomsource = RandomSource.create(); + ++ // Paper start - Add missing structure set seed configs ++ if (this.conf.strongholdSeed != null && structureSetEntry.is(net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.STRONGHOLDS)) { ++ randomsource.setSeed(this.conf.strongholdSeed); ++ } else { ++ // Paper end - Add missing structure set seed configs + randomsource.setSeed(this.concentricRingsSeed); ++ } // Paper - Add missing structure set seed configs + double d0 = randomsource.nextDouble() * Math.PI * 2.0D; + int l = 0; + int i1 = 0; +@@ -209,7 +309,7 @@ + + for (int l = centerChunkX - chunkCount; l <= centerChunkX + chunkCount; ++l) { + for (int i1 = centerChunkZ - chunkCount; i1 <= centerChunkZ + chunkCount; ++i1) { +- if (structureplacement.isStructureChunk(this, l, i1)) { ++ if (structureplacement.isStructureChunk(this, l, i1, structureplacement instanceof KeyedRandomSpreadStructurePlacement keyed ? keyed.key : null)) { // Paper - Add missing structure set seed configs + return true; + } + } diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructureCheck.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructureCheck.java.patch index c9a15f37a5..9e6cb9ad46 100644 --- a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructureCheck.java.patch +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/StructureCheck.java.patch @@ -18,3 +18,33 @@ ChunkGenerator chunkGenerator, RandomState noiseConfig, LevelHeightAccessor world, +@@ -74,6 +74,20 @@ + this.fixerUpper = dataFixer; + } + ++ // Paper start - add missing structure salt configs ++ @Nullable ++ private Integer getSaltOverride(Structure type) { ++ if (this.heightAccessor instanceof net.minecraft.server.level.ServerLevel serverLevel) { ++ if (type instanceof net.minecraft.world.level.levelgen.structure.structures.MineshaftStructure) { ++ return serverLevel.spigotConfig.mineshaftSeed; ++ } else if (type instanceof net.minecraft.world.level.levelgen.structure.structures.BuriedTreasureStructure) { ++ return serverLevel.spigotConfig.buriedTreasureSeed; ++ } ++ } ++ return null; ++ } ++ // Paper end - add missing structure seed configs ++ + public StructureCheckResult checkStart(ChunkPos pos, Structure type, StructurePlacement placement, boolean skipReferencedStructures) { + long l = pos.toLong(); + Object2IntMap object2IntMap = this.loadedChunks.get(l); +@@ -83,7 +97,7 @@ + StructureCheckResult structureCheckResult = this.tryLoadFromStorage(pos, type, skipReferencedStructures, l); + if (structureCheckResult != null) { + return structureCheckResult; +- } else if (!placement.applyAdditionalChunkRestrictions(pos.x, pos.z, this.seed)) { ++ } else if (!placement.applyAdditionalChunkRestrictions(pos.x, pos.z, this.seed, this.getSaltOverride(type))) { // Paper - add missing structure seed configs + return StructureCheckResult.START_NOT_PRESENT; + } else { + boolean bl = this.featureChecks diff --git a/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java.patch b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java.patch new file mode 100644 index 0000000000..8abda3baff --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java.patch @@ -0,0 +1,93 @@ +--- a/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java ++++ b/net/minecraft/world/level/levelgen/structure/placement/StructurePlacement.java +@@ -79,14 +79,30 @@ + return this.exclusionZone; + } + ++ @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - Add missing structure set seed configs + public boolean isStructureChunk(ChunkGeneratorStructureState calculator, int chunkX, int chunkZ) { ++ // Paper start - Add missing structure set seed configs ++ return this.isStructureChunk(calculator, chunkX, chunkZ, null); ++ } ++ public boolean isStructureChunk(ChunkGeneratorStructureState calculator, int chunkX, int chunkZ, @org.jetbrains.annotations.Nullable net.minecraft.resources.ResourceKey structureSetKey) { ++ Integer saltOverride = null; ++ if (structureSetKey != null) { ++ if (structureSetKey == net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.MINESHAFTS) { ++ saltOverride = calculator.conf.mineshaftSeed; ++ } else if (structureSetKey == net.minecraft.world.level.levelgen.structure.BuiltinStructureSets.BURIED_TREASURES) { ++ saltOverride = calculator.conf.buriedTreasureSeed; ++ } ++ } ++ // Paper end - Add missing structure set seed configs + return this.isPlacementChunk(calculator, chunkX, chunkZ) +- && this.applyAdditionalChunkRestrictions(chunkX, chunkZ, calculator.getLevelSeed()) ++ && this.applyAdditionalChunkRestrictions(chunkX, chunkZ, calculator.getLevelSeed(), saltOverride) // Paper - Add missing structure set seed configs + && this.applyInteractionsWithOtherStructures(calculator, chunkX, chunkZ); + } + +- public boolean applyAdditionalChunkRestrictions(int chunkX, int chunkZ, long seed) { +- return !(this.frequency < 1.0F) || this.frequencyReductionMethod.shouldGenerate(seed, this.salt, chunkX, chunkZ, this.frequency); ++ // Paper start - Add missing structure set seed configs ++ public boolean applyAdditionalChunkRestrictions(int chunkX, int chunkZ, long seed, @org.jetbrains.annotations.Nullable Integer saltOverride) { ++ return !(this.frequency < 1.0F) || this.frequencyReductionMethod.shouldGenerate(seed, this.salt, chunkX, chunkZ, this.frequency, saltOverride); ++ // Paper end - Add missing structure set seed configs + } + + public boolean applyInteractionsWithOtherStructures(ChunkGeneratorStructureState calculator, int centerChunkX, int centerChunkZ) { +@@ -101,25 +117,31 @@ + + public abstract StructurePlacementType type(); + +- private static boolean probabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency) { ++ private static boolean probabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs; ignore here + WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); + worldgenRandom.setLargeFeatureWithSalt(seed, salt, chunkX, chunkZ); + return worldgenRandom.nextFloat() < frequency; + } + +- private static boolean legacyProbabilityReducerWithDouble(long seed, int salt, int chunkX, int chunkZ, float frequency) { ++ private static boolean legacyProbabilityReducerWithDouble(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs + WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); ++ if (saltOverride == null) { // Paper - Add missing structure set seed configs + worldgenRandom.setLargeFeatureSeed(seed, chunkX, chunkZ); ++ // Paper start - Add missing structure set seed configs ++ } else { ++ worldgenRandom.setLargeFeatureWithSalt(seed, chunkX, chunkZ, saltOverride); ++ } ++ // Paper end - Add missing structure set seed configs + return worldgenRandom.nextDouble() < (double)frequency; + } + +- private static boolean legacyArbitrarySaltProbabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency) { ++ private static boolean legacyArbitrarySaltProbabilityReducer(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs + WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); +- worldgenRandom.setLargeFeatureWithSalt(seed, chunkX, chunkZ, 10387320); ++ worldgenRandom.setLargeFeatureWithSalt(seed, chunkX, chunkZ, saltOverride != null ? saltOverride : HIGHLY_ARBITRARY_RANDOM_SALT); // Paper - Add missing structure set seed configs + return worldgenRandom.nextFloat() < frequency; + } + +- private static boolean legacyPillagerOutpostReducer(long seed, int salt, int chunkX, int chunkZ, float frequency) { ++ private static boolean legacyPillagerOutpostReducer(long seed, int salt, int chunkX, int chunkZ, float frequency, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs; ignore here + int i = chunkX >> 4; + int j = chunkZ >> 4; + WorldgenRandom worldgenRandom = new WorldgenRandom(new LegacyRandomSource(0L)); +@@ -147,7 +169,7 @@ + + @FunctionalInterface + public interface FrequencyReducer { +- boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance); ++ boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance, @org.jetbrains.annotations.Nullable Integer saltOverride); // Paper - Add missing structure set seed configs + } + + public static enum FrequencyReductionMethod implements StringRepresentable { +@@ -167,8 +189,8 @@ + this.reducer = generationPredicate; + } + +- public boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance) { +- return this.reducer.shouldGenerate(seed, salt, chunkX, chunkZ, chance); ++ public boolean shouldGenerate(long seed, int salt, int chunkX, int chunkZ, float chance, @org.jetbrains.annotations.Nullable Integer saltOverride) { // Paper - Add missing structure set seed configs ++ return this.reducer.shouldGenerate(seed, salt, chunkX, chunkZ, chance, saltOverride); // Paper - Add missing structure set seed configs + } + + @Override diff --git a/paper-server/src/main/java/org/spigotmc/SpigotWorldConfig.java b/paper-server/src/main/java/org/spigotmc/SpigotWorldConfig.java index e76f96a5c4..2b26324613 100644 --- a/paper-server/src/main/java/org/spigotmc/SpigotWorldConfig.java +++ b/paper-server/src/main/java/org/spigotmc/SpigotWorldConfig.java @@ -322,6 +322,18 @@ public class SpigotWorldConfig public int mansionSeed; public int fossilSeed; public int portalSeed; + // Paper start - add missing structure set configs + public int ancientCitySeed; + public int trailRuinsSeed; + public int trialChambersSeed; + public int buriedTreasureSeed; + public Integer mineshaftSeed; + public Long strongholdSeed; + private N getSeed(String path, java.util.function.Function toNumberFunc) { + final String value = this.getString(path, "default"); + return org.apache.commons.lang3.math.NumberUtils.isParsable(value) ? toNumberFunc.apply(value) : null; + } + // Paper end private void initWorldGenSeeds() { this.villageSeed = this.getInt( "seed-village", 10387312 ); @@ -339,6 +351,14 @@ public class SpigotWorldConfig this.mansionSeed = this.getInt( "seed-mansion", 10387319 ); this.fossilSeed = this.getInt( "seed-fossil", 14357921 ); this.portalSeed = this.getInt( "seed-portal", 34222645 ); + // Paper start - add missing structure set configs + this.ancientCitySeed = this.getInt("seed-ancientcity", 20083232); + this.trailRuinsSeed = this.getInt("seed-trailruins", 83469867); + this.trialChambersSeed = this.getInt("seed-trialchambers", 94251327); + this.buriedTreasureSeed = this.getInt("seed-buriedtreasure", 10387320); // StructurePlacement#HIGHLY_ARBITRARY_RANDOM_SALT + this.mineshaftSeed = this.getSeed("seed-mineshaft", Integer::parseInt); + this.strongholdSeed = this.getSeed("seed-stronghold", Long::parseLong); + // Paper end this.log( "Custom Map Seeds: Village: " + this.villageSeed + " Desert: " + this.desertSeed + " Igloo: " + this.iglooSeed + " Jungle: " + this.jungleSeed + " Swamp: " + this.swampSeed + " Monument: " + this.monumentSeed + " Ocean: " + this.oceanSeed + " Shipwreck: " + this.shipwreckSeed + " End City: " + this.endCitySeed + " Slime: " + this.slimeSeed + " Nether: " + this.netherSeed + " Mansion: " + this.mansionSeed + " Fossil: " + this.fossilSeed + " Portal: " + this.portalSeed ); } diff --git a/paper-server/src/test/java/io/papermc/paper/world/structure/StructureSeedConfigTest.java b/paper-server/src/test/java/io/papermc/paper/world/structure/StructureSeedConfigTest.java new file mode 100644 index 0000000000..cc1fb5ae9e --- /dev/null +++ b/paper-server/src/test/java/io/papermc/paper/world/structure/StructureSeedConfigTest.java @@ -0,0 +1,77 @@ +package io.papermc.paper.world.structure; + +import io.papermc.paper.configuration.PaperConfigurations; +import java.io.File; +import java.lang.reflect.Field; +import net.minecraft.core.Registry; +import net.minecraft.core.registries.Registries; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.level.levelgen.structure.BuiltinStructureSets; +import net.minecraft.world.level.levelgen.structure.StructureSet; +import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.support.RegistryHelper; +import org.bukkit.support.environment.AllFeatures; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Test; +import org.spigotmc.SpigotConfig; +import org.spigotmc.SpigotWorldConfig; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +@AllFeatures +public class StructureSeedConfigTest { + + @Test + public void checkStructureSeedDefaults() throws ReflectiveOperationException { + SpigotConfig.config = new YamlConfiguration() { + @Override + public void save(final @NotNull File file) { + // no-op + } + }; + final SpigotWorldConfig config = PaperConfigurations.SPIGOT_WORLD_DEFAULTS.get(); + + + final Registry structureSets = RegistryHelper.getRegistry().lookupOrThrow(Registries.STRUCTURE_SET); + for (final ResourceKey setKey : structureSets.registryKeySet()) { + assertEquals(ResourceLocation.DEFAULT_NAMESPACE, setKey.location().getNamespace()); + final StructureSet set = structureSets.getValueOrThrow(setKey); + if (setKey == BuiltinStructureSets.STRONGHOLDS) { // special case due to seed matching world seed + assertEquals(0, set.placement().salt); + continue; + } + int salt = switch (setKey.location().getPath()) { + case "villages" -> config.villageSeed; + case "desert_pyramids" -> config.desertSeed; + case "igloos" -> config.iglooSeed; + case "jungle_temples" -> config.jungleSeed; + case "swamp_huts" -> config.swampSeed; + case "pillager_outposts" -> config.outpostSeed; + case "ocean_monuments" -> config.monumentSeed; + case "woodland_mansions" -> config.mansionSeed; + case "buried_treasures" -> config.buriedTreasureSeed; + case "mineshafts" -> config.mineshaftSeed == null ? 0 : config.mineshaftSeed; // mineshaft seed is set differently + case "ruined_portals" -> config.portalSeed; + case "shipwrecks" -> config.shipwreckSeed; + case "ocean_ruins" -> config.oceanSeed; + case "nether_complexes" -> config.netherSeed; + case "nether_fossils" -> config.fossilSeed; + case "end_cities" -> config.endCitySeed; + case "ancient_cities" -> config.ancientCitySeed; + case "trail_ruins" -> config.trailRuinsSeed; + case "trial_chambers" -> config.trialChambersSeed; + default -> throw new AssertionError("Missing structure set seed in SpigotWorldConfig for " + setKey); + }; + if (setKey == BuiltinStructureSets.BURIED_TREASURES) { + final Field field = StructurePlacement.class.getDeclaredField("HIGHLY_ARBITRARY_RANDOM_SALT"); + field.trySetAccessible(); + assertEquals(0, set.placement().salt); + assertEquals(field.get(null), salt, "Mismatched default seed for " + setKey + ". Should be " + field.get(null)); + continue; + } + assertEquals(set.placement().salt, salt, "Mismatched default seed for " + setKey + ". Should be " + set.placement().salt); + } + } +}