From c4b683ea59bd5cd1ad73c97e55a09c89752b2c10 Mon Sep 17 00:00:00 2001 From: JMc Date: Mon, 13 Jul 2026 22:11:47 +0200 Subject: [PATCH 1/5] fix: carver crash below y=0 on extended-height worlds The carving mask index used the raw block Y (relativeX | relativeZ << 4 | y << 8), which goes negative on 1.18+ worlds with MinY below zero and crashes BitSet with IndexOutOfBoundsException as soon as a cave or ravine carves below y=0. Offset the Y by the world minimum, like the ravine height cache a few lines above already does. --- .../src/main/java/com/pg85/otg/gen/carver/Carver.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/common/common-generator/src/main/java/com/pg85/otg/gen/carver/Carver.java b/common/common-generator/src/main/java/com/pg85/otg/gen/carver/Carver.java index 11765d1d3..7b268e0db 100644 --- a/common/common-generator/src/main/java/com/pg85/otg/gen/carver/Carver.java +++ b/common/common-generator/src/main/java/com/pg85/otg/gen/carver/Carver.java @@ -135,7 +135,8 @@ protected boolean carveRegion( currentY, currentZ, foundSurface, - biomeConfig + biomeConfig, + otgWorldInfo ); } } @@ -155,10 +156,13 @@ protected boolean carveAtPoint( int y, int relativeZ, MutableBoolean foundSurface, - BiomeSettings biomeConfig + BiomeSettings biomeConfig, + OTGWorldInfo otgWorldInfo ) { SurfaceSettings surface = biomeConfig.getSurfaceSettings(); - int i = relativeX | relativeZ << 4 | y << 8; + // Offset Y by minY to ensure a non-negative BitSet index (1.18+ worlds can have negative Y) + int offsetY = y - otgWorldInfo.minY(); + int i = relativeX | relativeZ << 4 | offsetY << 8; if (carvingMask.get(i)) { return false; } From 4de15d39aa5a63976f3a84d7992aaeae7bdeba2d Mon Sep 17 00:00:00 2001 From: JMc Date: Mon, 13 Jul 2026 22:12:23 +0200 Subject: [PATCH 2/5] fix: accept legacy 4-argument Dungeon() format Old presets (Biome Bundle era) use Dungeon(Frequency,Rarity,MinAltitude, MaxAltitude); the current parser expects 3 arguments and misreads the legacy ones, shifting rarity/altitudes by one. Detect the 4-argument form and skip the frequency (fixed to 1 either way). --- .../java/com/pg85/otg/gen/resource/DungeonResource.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/common/common-generator/src/main/java/com/pg85/otg/gen/resource/DungeonResource.java b/common/common-generator/src/main/java/com/pg85/otg/gen/resource/DungeonResource.java index 91d102e8e..8938b7ef3 100644 --- a/common/common-generator/src/main/java/com/pg85/otg/gen/resource/DungeonResource.java +++ b/common/common-generator/src/main/java/com/pg85/otg/gen/resource/DungeonResource.java @@ -19,9 +19,12 @@ public DungeonResource(BiomeSettings biomeConfig, List args) throws Inva super(biomeConfig, args); assureSize(3, args); + // Legacy presets use Dungeon(Frequency,Rarity,MinAltitude,MaxAltitude); + // the current format drops Frequency (always 1). Accept both. + int offset = args.size() >= 4 ? 1 : 0; this.frequency = 1; - this.rarity = readRarity(args.get(0)); - Pair elevations = readElevations(args.get(1), args.get(2)); + this.rarity = readRarity(args.get(offset)); + Pair elevations = readElevations(args.get(offset + 1), args.get(offset + 2)); this.minAltitude = elevations.getFirst(); this.maxAltitude = elevations.getSecond(); From 0a9787997901ead618b71db0b400b115679e907b Mon Sep 17 00:00:00 2001 From: JMc Date: Mon, 13 Jul 2026 22:13:35 +0200 Subject: [PATCH 3/5] fix: parse Entity() .txt NBT files as SNBT and don't kill chunk gen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Entity() .txt files contain text NBT (the old mojangson format that presets like Biome Bundle ship), but the code fed them to the binary NbtIo reader, which throws EOFException on any such file — and the handler rethrew it as RuntimeException, crashing the whole chunk generation. Parse .txt with TagParser and log + skip the entity on failure instead of taking the world down with one broken preset file. --- .../otg/fabric/gen/FabricWorldGenRegion.java | 35 ++++++------------- 1 file changed, 11 insertions(+), 24 deletions(-) diff --git a/platforms/fabric/src/main/java/com/pg85/otg/fabric/gen/FabricWorldGenRegion.java b/platforms/fabric/src/main/java/com/pg85/otg/fabric/gen/FabricWorldGenRegion.java index 2af1f37ad..f1ebf78a8 100644 --- a/platforms/fabric/src/main/java/com/pg85/otg/fabric/gen/FabricWorldGenRegion.java +++ b/platforms/fabric/src/main/java/com/pg85/otg/fabric/gen/FabricWorldGenRegion.java @@ -20,7 +20,6 @@ import com.pg85.otg.util.materials.LocalMaterials; import com.pg85.otg.util.minecraft.TreeType; import com.pg85.otg.util.nbt.NamedBinaryTag; -import net.minecraft.ReportedException; import net.minecraft.core.BlockPos; import net.minecraft.core.registries.Registries; import net.minecraft.data.worldgen.features.CaveFeatures; @@ -28,7 +27,7 @@ import net.minecraft.data.worldgen.features.TreeFeatures; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.IntTag; -import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.TagParser; import net.minecraft.network.chat.Component; import net.minecraft.world.entity.*; import net.minecraft.world.entity.monster.Guardian; @@ -46,9 +45,6 @@ import net.minecraft.world.level.levelgen.feature.Feature; import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration; -import java.io.ByteArrayInputStream; -import java.io.DataInputStream; -import java.io.IOException; import java.text.MessageFormat; import java.util.Optional; import java.util.Random; @@ -513,26 +509,17 @@ public void spawnEntity(IEntityFunction entityData) { nbtTagCompound = new CompoundTag(); if (entityData.getNameTagOrNBTFileName().toLowerCase().trim().endsWith(".txt")) { try { - var inputStream = - new DataInputStream(new ByteArrayInputStream(entityData.getMetaData().getBytes())); - nbtTagCompound = NbtIo.read(inputStream); - } catch (IOException | ReportedException e) { - if (OTGLog.getLogger().getLogCategoryEnabled(LogCategory.CUSTOM_OBJECTS)) { - OTGLog.log( - LogLevel.ERROR, - LogCategory.CUSTOM_OBJECTS, - "Could not parse nbt for Entity() " - + entityData.makeString() - + ", file: " - + entityData.getNameTagOrNBTFileName() - ); - } - throw new RuntimeException( - "Could not parse nbt for Entity() " - + entityData.makeString() - + ", file: " - + entityData.getNameTagOrNBTFileName(), e + // .txt files contain text NBT (SNBT), not the binary format + nbtTagCompound = TagParser.parseTag(entityData.getMetaData()); + } catch (Exception e) { + // A broken preset file shouldn't kill chunk generation — skip the entity + OTGLog.getLogger().error(LogCategory.CUSTOM_OBJECTS, + "Could not parse nbt for Entity() %s, file: %s, error: %s", + entityData.makeString(), + entityData.getNameTagOrNBTFileName(), + e.getMessage() ); + return; } // Specify which type of entity to spawn nbtTagCompound.putString("id", entityData.getResourceLocation()); From 17c600d41ffe8e8775df44cd56bbbb9f8d1819cc Mon Sep 17 00:00:00 2001 From: JMc Date: Mon, 13 Jul 2026 22:15:25 +0200 Subject: [PATCH 4/5] fix: guard biome smoothing and CHC lookups against out-of-range access Legacy presets ship CustomHeightControl lists sized for 256-high worlds; on extended-height worlds the CHC smoothing indexes past them and crashes. Treat missing layers as no height control. Also bounds/null-check the biome array lookups in both smoothing loops so a short or sparse region from the biome provider degrades to a skipped sample instead of killing the noise column. --- .../main/java/com/pg85/otg/gen/OTGChunkGenerator.java | 11 +++++++++-- .../pg85/otg/config/settings/biome/BiomeSettings.java | 8 +++++++- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/common/common-core/src/main/java/com/pg85/otg/gen/OTGChunkGenerator.java b/common/common-core/src/main/java/com/pg85/otg/gen/OTGChunkGenerator.java index 7653c9949..b9a1e2e5b 100644 --- a/common/common-core/src/main/java/com/pg85/otg/gen/OTGChunkGenerator.java +++ b/common/common-core/src/main/java/com/pg85/otg/gen/OTGChunkGenerator.java @@ -249,7 +249,11 @@ private BlendedColumn blendColumnParams(int noiseX, int noiseZ) { cacheX = x1 + largestRadius; for (int z1 = -smoothRadius; z1 <= smoothRadius; ++z1) { cacheZ = z1 + largestRadius; - biome = biomes[cacheX * areaSize + cacheZ]; + int biomeIndex = cacheX * areaSize + cacheZ; + if (biomeIndex >= biomes.length || (biome = biomes[biomeIndex]) == null) { + // Provider can come up short at region edges; skip instead of crashing + continue; + } biomeTerrainSettings = biome.getTerrainSettings(); heightAt = biomeTerrainSettings.getBiomeHeight(); // TODO: vanilla reduces the weight by half when the depth here is greater than the center depth, but OTG doesn't do that? @@ -277,7 +281,10 @@ private BlendedColumn blendColumnParams(int noiseX, int noiseZ) { cacheX = x1 + largestRadius; for (int z1 = -chcSmoothRadius; z1 <= chcSmoothRadius; ++z1) { cacheZ = z1 + largestRadius; - biome = biomes[cacheX * areaSize + cacheZ]; + int chcBiomeIndex = cacheX * areaSize + cacheZ; + if (chcBiomeIndex >= biomes.length || (biome = biomes[chcBiomeIndex]) == null) { + continue; + } heightAt = biome.getTerrainSettings().getBiomeHeight(); weightAt = BIOME_WEIGHT_TABLE[x1 + 32 + (z1 + 32) * 65] / (heightAt + 2.0F); diff --git a/common/common-util/src/main/java/com/pg85/otg/config/settings/biome/BiomeSettings.java b/common/common-util/src/main/java/com/pg85/otg/config/settings/biome/BiomeSettings.java index be0c68a2c..a17b6a272 100644 --- a/common/common-util/src/main/java/com/pg85/otg/config/settings/biome/BiomeSettings.java +++ b/common/common-util/src/main/java/com/pg85/otg/config/settings/biome/BiomeSettings.java @@ -73,7 +73,13 @@ public String getConfigName() { // Height / volatility public double getCHCData(int controlLayer) { - return this.getTerrainSettings().getCustomHeightControl().get(controlLayer); + var chc = this.getTerrainSettings().getCustomHeightControl(); + if (controlLayer < 0 || controlLayer >= chc.size()) { + // Legacy presets carry CHC arrays sized for 256-high worlds; extended + // height worlds index past them. Treat missing layers as no control. + return 0.0; + } + return chc.get(controlLayer); } // OTG Custom structures (BO's) From 993da35411a9683fc4c440b48ed05e63315a2535 Mon Sep 17 00:00:00 2001 From: JMc Date: Mon, 13 Jul 2026 22:16:50 +0200 Subject: [PATCH 5/5] fix: enable vanilla aquifers so caves below the water level aren't flooded MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With aquifers disabled, NoiseChunk uses Aquifer.createDisabled(fluidPicker) and every carved or noise-cave block below the biome water level becomes water — all underground below sea level was one big flooded cave system. Wire the vanilla aquifer noises (NoiseRouterData.overworld values) into the router's aquifer slots and enable aquifers in the runtime settings. OTGFluidPicker remains the global picker, so open terrain still floods to the per-biome water level while caves stay dry apart from vanilla-style aquifer pockets. --- .../gen/noise/OTGNoiseRouterFactory.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/platforms/fabric/src/main/java/com/pg85/otg/fabric/gen/noise/OTGNoiseRouterFactory.java b/platforms/fabric/src/main/java/com/pg85/otg/fabric/gen/noise/OTGNoiseRouterFactory.java index 0dbb42ebc..a66553212 100644 --- a/platforms/fabric/src/main/java/com/pg85/otg/fabric/gen/noise/OTGNoiseRouterFactory.java +++ b/platforms/fabric/src/main/java/com/pg85/otg/fabric/gen/noise/OTGNoiseRouterFactory.java @@ -39,7 +39,9 @@ * cave threshold depth; caves are carved out of solid terrain only. * - No slideOverworld: OTG's pipeline already fades the top of the world, and vanilla's slide * anchors are hardcoded to -64..320, which mis-anchors on configurable world heights. - * - Aquifers and ore veins disabled; fluid placement comes from {@link OTGFluidPicker}. + * - Ore veins disabled. Aquifers run with vanilla noises on top of {@link OTGFluidPicker} + * as the global picker: open terrain floods to the per-biome water level, while caves + * below it stay dry apart from vanilla-style aquifer pockets. * - Climate/biome slots are zero; OTG does its own biome placement. */ public final class OTGNoiseRouterFactory { @@ -73,11 +75,15 @@ public static OTGNoiseCaveContext create( DensityFunction depthProxy = new OTGDepthProxyFunction(internalGenerator, depthGradient); DensityFunction finalDensity = finalDensity(otgTerrain, depthProxy, functions, noises); + // Vanilla aquifer noises (NoiseRouterData.overworld, 1.20.1). With aquifers enabled, + // the aquifer keeps caves below the water level dry apart from local pockets; + // OTGFluidPicker stays the global picker, so per-biome water levels still apply + // to open terrain (oceans, lakes). NoiseRouter router = new NoiseRouter( - DensityFunctions.zero(), // barrierNoise - DensityFunctions.zero(), // fluidLevelFloodednessNoise - DensityFunctions.zero(), // fluidLevelSpreadNoise - DensityFunctions.zero(), // lavaNoise + DensityFunctions.noise(noises.getOrThrow(Noises.AQUIFER_BARRIER), 0.5), // barrierNoise + DensityFunctions.noise(noises.getOrThrow(Noises.AQUIFER_FLUID_LEVEL_FLOODEDNESS), 0.67), // fluidLevelFloodednessNoise + DensityFunctions.noise(noises.getOrThrow(Noises.AQUIFER_FLUID_LEVEL_SPREAD), 0.7142857142857143), // fluidLevelSpreadNoise + DensityFunctions.noise(noises.getOrThrow(Noises.AQUIFER_LAVA)), // lavaNoise DensityFunctions.zero(), // temperature DensityFunctions.zero(), // vegetation DensityFunctions.zero(), // continents @@ -100,7 +106,7 @@ public static OTGNoiseCaveContext create( registeredSettings.spawnTarget(), registeredSettings.seaLevel(), registeredSettings.disableMobGeneration(), - false, // aquifersEnabled: NoiseChunk uses Aquifer.createDisabled(fluidPicker) + true, // aquifersEnabled: without them every cave below the water level floods false, // oreVeinsEnabled: vein router slots are zero registeredSettings.useLegacyRandomSource() );