diff --git a/src/main/java/net/mcreator/customoregen/worldgen/BiomeBand.java b/src/main/java/net/mcreator/customoregen/worldgen/BiomeBand.java index 293fe6e6..63be6e17 100644 --- a/src/main/java/net/mcreator/customoregen/worldgen/BiomeBand.java +++ b/src/main/java/net/mcreator/customoregen/worldgen/BiomeBand.java @@ -115,23 +115,6 @@ public enum BiomeBand { } } - public List> underground() { - switch (this) { - case FROZEN: - return List.of(Biomes.DRIPSTONE_CAVES, Biomes.DEEP_DARK); - case COLD: - return List.of(Biomes.DRIPSTONE_CAVES); - case TEMPERATE: - return List.of(Biomes.LUSH_CAVES, Biomes.DRIPSTONE_CAVES); - case WARM: - return List.of(Biomes.LUSH_CAVES); - case HOT: - return List.of(Biomes.LUSH_CAVES, Biomes.DEEP_DARK); - default: - return List.of(Biomes.LUSH_CAVES); - } - } - /** Resolve a list of (possibly absent) biome keys into actual holders, skipping missing ones. */ public static List> resolve(HolderGetter getter, List> keys) { List> holders = new ArrayList<>(); diff --git a/src/main/java/net/mcreator/customoregen/worldgen/LatitudeBiomeSource.java b/src/main/java/net/mcreator/customoregen/worldgen/LatitudeBiomeSource.java index 224b2d3e..9f947aa4 100644 --- a/src/main/java/net/mcreator/customoregen/worldgen/LatitudeBiomeSource.java +++ b/src/main/java/net/mcreator/customoregen/worldgen/LatitudeBiomeSource.java @@ -36,10 +36,16 @@ import java.util.stream.Stream; * selector noise picks among the band's biomes, producing extremely large biomes that * encourage exploration and long-distance travel (railways, roads, nether hubs).

* + *

Underground philosophy: the surface latitude biome extends downward by default, so + * mining under a desert feels like being in the desert. Cave biomes (lush / dripstone) only + * appear as rare climatic pockets in the mid underground, and the Deep Dark is a legendary + * (~1%) feature of the deep zone. This keeps the latitude logic coherent at every depth + * instead of overriding it with a uniform cave-biome slab.

+ * *

Surface biomes come from dedicated climate tags (see {@code data/custom_ore_gen/tags/ * worldgen/biome/latitude_*_surface.json}), which support optional Biomes O' Plenty biomes - * via {@code "required": false} without ever creating unbound holders. Ocean and underground - * (cave) pools are vanilla-only.

+ * via {@code "required": false} without ever creating unbound holders. Ocean pools are + * vanilla-only.

*/ public class LatitudeBiomeSource extends BiomeSource { @@ -49,7 +55,7 @@ public class LatitudeBiomeSource extends BiomeSource { ).apply(instance, instance.stable(LatitudeBiomeSource::new))); // ------------------------------------------------------------------ - // Tunable constants + // Surface / climate tunables // ------------------------------------------------------------------ /** Number of blocks for the temperature to go from 0 (equator) to +-1 (pole). */ @@ -76,8 +82,34 @@ public class LatitudeBiomeSource extends BiomeSource { /** Above this moisture value a wet biome (swamp/mangrove) overrides the surface. ~8% of land. */ private static final double MOISTURE_THRESHOLD = 0.55; - /** Below this block Y we resolve underground (cave) biomes. */ - private static final int UNDERGROUND_Y = 30; + // ------------------------------------------------------------------ + // Underground tunables (3-zone model) + // ------------------------------------------------------------------ + + /** Top of the deep zone (Y < this = deep underground). Deep Dark can live here. */ + private static final int DEEP_ZONE_TOP = -30; + + /** Top of the mid-cave zone (DEEP_ZONE_TOP ≤ Y < this = mid caves, lush/dripstone pockets). */ + private static final int MID_CAVE_TOP = 0; + + /** Frequency of the mid-cave pocket noise (lush/dripstone). */ + private static final double CAVE_SCALE = 0.003; + + /** Above this value a lush/dripstone pocket overrides the surface biome. ~8% of the mid zone. */ + private static final double CAVE_THRESHOLD = 0.55; + + /** Frequency of the Deep Dark noise (very low = large, rare regions). */ + private static final double DEEP_DARK_SCALE = 0.0011; + + /** Vertical frequency of the Deep Dark noise (keeps it coherent in tall sections). */ + private static final double DEEP_DARK_SCALE_Y = 0.012; + + /** Above this value the Deep Dark overrides. Tuned for ~1% of the deep zone (legendary). */ + private static final double DEEP_DARK_THRESHOLD = 0.88; + + // ------------------------------------------------------------------ + // Spawn safe zone + // ------------------------------------------------------------------ /** Half-size of the guaranteed safe spawn square around the origin (plains/forest). */ private static final int SPAWN_SAFE_RADIUS = 96; @@ -96,6 +128,8 @@ public class LatitudeBiomeSource extends BiomeSource { private final ImprovedNoise selectorNoise; private final ImprovedNoise oceanNoise; private final ImprovedNoise moistureNoise; + private final ImprovedNoise caveNoise; + private final ImprovedNoise deepDarkNoise; /** Vanilla-only pools resolved eagerly (safe: vanilla biomes always exist and are bound). */ private final EnumMap resolvedVanilla; @@ -109,6 +143,11 @@ public class LatitudeBiomeSource extends BiomeSource { private final Holder swampBiome; private final Holder mangroveBiome; + /** Cave biomes resolved eagerly (vanilla, always present). Lush = humid bands, Dripstone = dry bands. */ + private final Holder lushCavesBiome; + private final Holder dripstoneCavesBiome; + private final Holder deepDarkBiome; + /** Mod-aware surface pools resolved lazily from climate tags (safe: tags are bound by then). */ private volatile EnumMap>> surfaceFromTag; @@ -121,19 +160,25 @@ public class LatitudeBiomeSource extends BiomeSource { this.selectorNoise = new ImprovedNoise(RandomSource.create(seed ^ 0x4C415449L)); this.oceanNoise = new ImprovedNoise(RandomSource.create(seed ^ 0x4F434541L)); this.moistureNoise = new ImprovedNoise(RandomSource.create(seed ^ 0x57455421L)); + this.caveNoise = new ImprovedNoise(RandomSource.create(seed ^ 0x43415645L)); + this.deepDarkNoise = new ImprovedNoise(RandomSource.create(seed ^ 0x44454550L)); this.resolvedVanilla = new EnumMap<>(BiomeBand.class); Set> all = new HashSet<>(); for (BiomeBand band : BiomeBand.values()) { List> surface = BiomeBand.resolve(biomeGetter, band.surfaceVanilla()); List> ocean = BiomeBand.resolve(biomeGetter, band.ocean()); - List> underground = BiomeBand.resolve(biomeGetter, band.underground()); - resolvedVanilla.put(band, new ResolvedBand(surface, ocean, underground)); + resolvedVanilla.put(band, new ResolvedBand(surface, ocean)); all.addAll(surface); all.addAll(ocean); - all.addAll(underground); } + + // Cave biomes (vanilla) declared so the source advertises them as possible. + this.lushCavesBiome = resolveAndAdd(biomeGetter, Biomes.LUSH_CAVES, all); + this.dripstoneCavesBiome = resolveAndAdd(biomeGetter, Biomes.DRIPSTONE_CAVES, all); + this.deepDarkBiome = resolveAndAdd(biomeGetter, Biomes.DEEP_DARK, all); + this.possibleBiomes = all; List> safe = new ArrayList<>(); @@ -151,6 +196,14 @@ public class LatitudeBiomeSource extends BiomeSource { this.fallback = plains != null ? plains : (possibleBiomes.isEmpty() ? null : possibleBiomes.iterator().next()); } + private static Holder resolveAndAdd(HolderGetter getter, net.minecraft.resources.ResourceKey key, Set> into) { + Holder holder = getter.get(key).orElse(null); + if (holder != null) { + into.add(holder); + } + return holder; + } + @Override protected MapCodec codec() { return CODEC; @@ -182,19 +235,20 @@ public class LatitudeBiomeSource extends BiomeSource { return pickBiome(spawnSafeBiomes, blockX, blockZ, SPAWN_SELECTOR_SCALE); } - // Underground (caves) layer. - if (blockY < UNDERGROUND_Y && !pool.underground().isEmpty()) { - return pickBiome(pool.underground(), blockX, blockZ, 0.004); - } + // Determine the column's base biome: ocean mask, rare wet pocket, or latitude surface biome. + Holder base = baseSurfaceBiome(band, pool, blockX, blockZ); - // Land vs ocean mask. + // Underground cave overrides (3-zone model) extend latitude rather than replace it. + return applyCaveOverrides(base, band, blockX, blockY, blockZ); + } + + /** Resolve the surface biome for a column: ocean, swamp/mangrove pocket, or the band's surface tag. */ + private Holder baseSurfaceBiome(BiomeBand band, ResolvedBand pool, int blockX, int blockZ) { double oceanValue = oceanNoise.noise(blockX * OCEAN_NOISE_SCALE, 0.0, blockZ * OCEAN_NOISE_SCALE); if (oceanValue > OCEAN_THRESHOLD && !pool.ocean().isEmpty()) { return pickBiome(pool.ocean(), blockX, blockZ, 0.002); } - // Rare wet pockets: high moisture carves a swamp (temperate) or mangrove swamp (warm/hot), - // matching vanilla where these are uncommon humid biomes rather than equal-weighted surface ones. double moisture = moistureNoise.noise(blockX * MOISTURE_SCALE, 0.0, blockZ * MOISTURE_SCALE); if (moisture > MOISTURE_THRESHOLD) { if (band == BiomeBand.TEMPERATE && swampBiome != null) { @@ -205,7 +259,6 @@ public class LatitudeBiomeSource extends BiomeSource { } } - // Surface biome from the (mod-aware) climate tag, with a vanilla fallback. List> surface = resolveSurfaceTag(band); if (surface == null || surface.isEmpty()) { surface = pool.surface(); @@ -216,6 +269,48 @@ public class LatitudeBiomeSource extends BiomeSource { return pickBiome(surface, blockX, blockZ, SURFACE_SELECTOR_SCALE); } + /** + * Underground overrides keep the latitude biome by default and only carve rare cave features: + *
    + *
  • Deep zone (Y < {@value #DEEP_ZONE_TOP}): legendary Deep Dark pockets (~1%), else surface biome.
  • + *
  • Mid caves (DEEP_ZONE_TOP ≤ Y < {@value #MID_CAVE_TOP}): lush/dripstone pockets (~8%) matching the climate.
  • + *
  • Near surface: no override, latitude biome as-is.
  • + *
+ */ + private Holder applyCaveOverrides(Holder surfaceBiome, BiomeBand band, int blockX, int blockY, int blockZ) { + // Deep zone: legendary Deep Dark. + if (blockY < DEEP_ZONE_TOP) { + if (deepDarkBiome != null) { + double dd = deepDarkNoise.noise(blockX * DEEP_DARK_SCALE, blockY * DEEP_DARK_SCALE_Y, blockZ * DEEP_DARK_SCALE); + if (dd > DEEP_DARK_THRESHOLD) { + return deepDarkBiome; + } + } + return surfaceBiome; + } + + // Mid caves: rare lush/dripstone pockets by climate. + if (blockY < MID_CAVE_TOP) { + Holder caveBiome = caveBiomeFor(band); + if (caveBiome != null) { + double cave = caveNoise.noise(blockX * CAVE_SCALE, 0.0, blockZ * CAVE_SCALE); + if (cave > CAVE_THRESHOLD) { + return caveBiome; + } + } + } + + return surfaceBiome; + } + + /** Lush caves for humid/warm bands, dripstone for dry/cold bands. */ + private Holder caveBiomeFor(BiomeBand band) { + if (band == BiomeBand.FROZEN || band == BiomeBand.COLD) { + return dripstoneCavesBiome; + } + return lushCavesBiome; + } + /** Lazily resolve surface biomes from climate tags. Tags are bound after the registry freezes, * so this is safe and never creates unbound holders. */ private List> resolveSurfaceTag(BiomeBand band) { @@ -258,6 +353,6 @@ public class LatitudeBiomeSource extends BiomeSource { return value < min ? min : (value > max ? max : value); } - private record ResolvedBand(List> surface, List> ocean, List> underground) { + private record ResolvedBand(List> surface, List> ocean) { } }