From 223f4848db4dbf9c272e2dbb8b85b4c78b31860b Mon Sep 17 00:00:00 2001 From: JeanTKG <102908437+jeantkg@users.noreply.github.com> Date: Thu, 9 Oct 2025 13:23:21 +0300 Subject: [PATCH 1/3] Made worlds generate in the new 1.18+ heights and added old cave system --- .../generator/VanillaBiomeGrid.php | 24 + .../generator/VanillaGenerator.php | 8 +- .../generator/biomegrid/BiomeGrid.php | 24 +- .../generator/biomegrid/ErosionMapLayer.php | 6 +- .../generator/biomegrid/NoiseMapLayer.php | 24 +- .../generator/cave/CaveGenerator.php | 488 ++++++++++++++++++ .../generator/ground/GroundGenerator.php | 17 +- .../generator/ground/MesaGroundGenerator.php | 17 +- .../generator/object/DoubleTallPlant.php | 5 + .../generator/object/Lake.php | 46 +- .../generator/object/TerrainObject.php | 3 +- .../generator/object/tree/MegaPineTree.php | 17 +- .../overworld/OverworldGenerator.php | 95 +++- .../overworld/biome/Biome3DGenerator.php | 102 ++++ .../overworld/biome/BiomeHeightManager.php | 12 + .../generator/overworld/biome/BiomeIds.php | 14 + .../overworld/decorator/CactusDecorator.php | 3 +- .../overworld/decorator/DeadBushDecorator.php | 3 +- .../overworld/decorator/DecoratorUtils.php | 53 ++ .../decorator/DoublePlantDecorator.php | 3 +- .../overworld/decorator/FlowerDecorator.php | 3 +- .../overworld/decorator/LakeDecorator.php | 7 +- .../overworld/decorator/MushroomDecorator.php | 3 +- .../overworld/decorator/PumpkinDecorator.php | 3 +- .../decorator/SugarCaneDecorator.php | 3 +- .../decorator/TallGrassDecorator.php | 3 +- .../decorator/WaterLilyDecorator.php | 3 +- .../populator/OverworldPopulator.php | 4 +- .../populator/biome/FlowerForestPopulator.php | 3 +- .../populator/biome/ForestPopulator.php | 3 +- .../populator/biome/OrePopulator.php | 29 +- .../populator/biome/PlainsPopulator.php | 7 +- 32 files changed, 948 insertions(+), 87 deletions(-) create mode 100644 src/muqsit/vanillagenerator/generator/cave/CaveGenerator.php create mode 100644 src/muqsit/vanillagenerator/generator/overworld/biome/Biome3DGenerator.php create mode 100644 src/muqsit/vanillagenerator/generator/overworld/decorator/DecoratorUtils.php diff --git a/src/muqsit/vanillagenerator/generator/VanillaBiomeGrid.php b/src/muqsit/vanillagenerator/generator/VanillaBiomeGrid.php index 78cdeae..bda8630 100644 --- a/src/muqsit/vanillagenerator/generator/VanillaBiomeGrid.php +++ b/src/muqsit/vanillagenerator/generator/VanillaBiomeGrid.php @@ -12,13 +12,37 @@ class VanillaBiomeGrid implements BiomeGrid{ /** @var int[] */ public array $biomes = []; + + /** @var int[] */ + private array $biomes_3d = []; public function getBiome(int $x, int $z) : ?int{ // upcasting is very important to get extended biomes return array_key_exists($hash = $x | $z << Chunk::COORD_BIT_SIZE, $this->biomes) ? $this->biomes[$hash] & 0xFF : null; } + public function getBiome3D(int $x, int $y, int $z) : ?int{ + // For 3D biomes, we use a different hash that includes Y coordinate + // We sample biomes every 4 blocks in Y (like 1.18+) to save memory + $sample_y = $y >> 2; // Sample every 4 blocks in Y direction + $hash = ($x) | ($z << 4) | ($sample_y << 8); + + if(array_key_exists($hash, $this->biomes_3d)) { + return $this->biomes_3d[$hash] & 0xFF; + } + + // Fall back to surface biome if no 3D biome is set + return $this->getBiome($x, $z); + } + public function setBiome(int $x, int $z, int $biome_id) : void{ $this->biomes[$x | $z << Chunk::COORD_BIT_SIZE] = $biome_id; } + + public function setBiome3D(int $x, int $y, int $z, int $biome_id) : void{ + // Sample biomes every 4 blocks in Y (like 1.18+) + $sample_y = $y >> 2; + $hash = ($x) | ($z << 4) | ($sample_y << 8); + $this->biomes_3d[$hash] = $biome_id; + } } \ No newline at end of file diff --git a/src/muqsit/vanillagenerator/generator/VanillaGenerator.php b/src/muqsit/vanillagenerator/generator/VanillaGenerator.php index 4459abf..c20b21c 100644 --- a/src/muqsit/vanillagenerator/generator/VanillaGenerator.php +++ b/src/muqsit/vanillagenerator/generator/VanillaGenerator.php @@ -98,6 +98,12 @@ public function populateChunk(ChunkManager $world, int $chunk_x, int $chunk_z) : } public function getMaxY() : int{ - return World::Y_MAX; + // 1.18+ world height: Y=319 (subchunk 19 × 16 + 15) + return 319; + } + + public function getMinY() : int{ + // 1.18+ world height: Y=-64 (subchunk -4 × 16) + return -64; } } \ No newline at end of file diff --git a/src/muqsit/vanillagenerator/generator/biomegrid/BiomeGrid.php b/src/muqsit/vanillagenerator/generator/biomegrid/BiomeGrid.php index 74f756e..29874e8 100644 --- a/src/muqsit/vanillagenerator/generator/biomegrid/BiomeGrid.php +++ b/src/muqsit/vanillagenerator/generator/biomegrid/BiomeGrid.php @@ -7,7 +7,7 @@ interface BiomeGrid{ /** - * Get biome at x, z within chunk being generated + * Get biome at x, z within chunk being generated (2D surface biome) * * @param int $x - 0-15 * @param int $z - 0-15 @@ -16,11 +16,31 @@ interface BiomeGrid{ public function getBiome(int $x, int $z) : ?int; /** - * Set biome at x, z within chunk being generated + * Get biome at x, y, z within chunk being generated (3D biome support for 1.18+) + * + * @param int $x - 0-15 + * @param int $y - world min to world max + * @param int $z - 0-15 + * @return int|null + */ + public function getBiome3D(int $x, int $y, int $z) : ?int; + + /** + * Set biome at x, z within chunk being generated (2D surface biome) * * @param int $x - 0-15 * @param int $z - 0-15 * @param int $biome_id */ public function setBiome(int $x, int $z, int $biome_id) : void; + + /** + * Set biome at x, y, z within chunk being generated (3D biome support for 1.18+) + * + * @param int $x - 0-15 + * @param int $y - world min to world max + * @param int $z - 0-15 + * @param int $biome_id + */ + public function setBiome3D(int $x, int $y, int $z, int $biome_id) : void; } \ No newline at end of file diff --git a/src/muqsit/vanillagenerator/generator/biomegrid/ErosionMapLayer.php b/src/muqsit/vanillagenerator/generator/biomegrid/ErosionMapLayer.php index 253487b..d0b0ec7 100644 --- a/src/muqsit/vanillagenerator/generator/biomegrid/ErosionMapLayer.php +++ b/src/muqsit/vanillagenerator/generator/biomegrid/ErosionMapLayer.php @@ -42,8 +42,10 @@ public function generateValues(int $x, int $z, int $size_x, int $size_z) : array $this->setCoordsSeed($x + $j, $z + $i); $final_values[$j + $i * $size_x] = match(true){ - $center_val !== 0 && ($upper_left_val === 0 || $upper_right_val === 0 || $lower_left_val === 0 || $lower_right_val === 0) => $this->nextInt(5) === 0 ? 0 : $center_val, - $center_val === 0 && ($upper_left_val !== 0 || $upper_right_val !== 0 || $lower_left_val !== 0 || $lower_right_val !== 0) => $this->nextInt(3) === 0 ? $upper_left_val : 0, + // Reduced erosion rate for 1.18+ style (1/8 instead of 4/5) to preserve more land + $center_val !== 0 && ($upper_left_val === 0 || $upper_right_val === 0 || $lower_left_val === 0 || $lower_right_val === 0) => $this->nextInt(8) === 0 ? 0 : $center_val, + // Increased land expansion rate (1/2 instead of 1/3) to create more land from ocean + $center_val === 0 && ($upper_left_val !== 0 || $upper_right_val !== 0 || $lower_left_val !== 0 || $lower_right_val !== 0) => $this->nextInt(2) === 0 ? $upper_left_val : 0, default => $center_val }; } diff --git a/src/muqsit/vanillagenerator/generator/biomegrid/NoiseMapLayer.php b/src/muqsit/vanillagenerator/generator/biomegrid/NoiseMapLayer.php index 842de03..d462958 100644 --- a/src/muqsit/vanillagenerator/generator/biomegrid/NoiseMapLayer.php +++ b/src/muqsit/vanillagenerator/generator/biomegrid/NoiseMapLayer.php @@ -13,32 +13,26 @@ class NoiseMapLayer extends MapLayer{ public function __construct(int $seed){ parent::__construct($seed); - $this->noise_gen = new SimplexOctaveGenerator(new Random($seed), 2); + // Adjusted noise for 1.18+ style: more octaves for better continental structure + $this->noise_gen = new SimplexOctaveGenerator(new Random($seed), 3); } public function generateValues(int $x, int $z, int $size_x, int $size_z) : array{ $values = []; for($i = 0; $i < $size_z; ++$i){ for($j = 0; $j < $size_x; ++$j){ - $noise = $this->noise_gen->octaveNoise($x + $j, $z + $i, 0, 0.175, 0.8, true) * 4.0; + // Updated for 1.18+ style generation with less ocean coverage + // Improved noise sampling for better continental structure + $noise = $this->noise_gen->octaveNoise($x + $j, $z + $i, 0, 0.2, 0.75, true) * 3.5; $val = 0; - if($noise >= 0.05){ - $val = $noise <= 0.2 ? 3 : 2; + if($noise >= -0.1){ // Much lower ocean threshold (was 0.05) + $val = $noise <= 0.15 ? 3 : 2; // Adjusted land threshold (was 0.2) }else{ $this->setCoordsSeed($x + $j, $z + $i); - $val = $this->nextInt(2) === 0 ? 3 : 0; + // Reduced ocean probability for remaining areas (30% chance instead of 50%) + $val = $this->nextInt(10) < 3 ? 0 : 3; } $values[$j + $i * $size_x] = $val; - //$values[$j + $i * $size_x] = - // $noise >= -0.5 - // ? (float) $noise >= 0.57 - // ? 2 - // : $noise <= 0.2 - // ? 3 - // : 2 - // : $this->nextInt(2) === 0 - // ? 3 - // : 0; } } return $values; diff --git a/src/muqsit/vanillagenerator/generator/cave/CaveGenerator.php b/src/muqsit/vanillagenerator/generator/cave/CaveGenerator.php new file mode 100644 index 0000000..927c1e1 --- /dev/null +++ b/src/muqsit/vanillagenerator/generator/cave/CaveGenerator.php @@ -0,0 +1,488 @@ +random = new Random($seed); + + // 2D noise for aquifer levels + $this->aquiferNoise = SimplexOctaveGenerator::fromRandomAndOctaves(new Random($seed + 3), 2, 16, 1, 16); + $this->aquiferNoise->setScale(1.0 / 32.0); + } + + /** + * Generate natural cave systems that flow organically through chunks + */ + public function carveDirectly(ChunkManager $world, int $chunkX, int $chunkZ): void { + $chunk = $world->getChunk($chunkX, $chunkZ); + if ($chunk === null) return; + + // Use regional seeding for consistent cave generation across chunks + $this->generateRegionalCaves($world, $chunkX, $chunkZ); + } + + /** + * Generate caves considering a larger region for natural flow + */ + private function generateRegionalCaves(ChunkManager $world, int $chunkX, int $chunkZ): void { + $carvedBlocks = 0; + + // Check a 3x3 region of chunks centered on current chunk for cave generation + for ($regionX = $chunkX - 1; $regionX <= $chunkX + 1; $regionX++) { + for ($regionZ = $chunkZ - 1; $regionZ <= $chunkZ + 1; $regionZ++) { + // Seed based on the region chunk for consistent generation + $this->random->setSeed($regionX * 341873128712 + $regionZ * 132897987541); + + // Generate caves that might flow into our target chunk + $caveAttempts = 6 + $this->random->nextBoundedInt(8); // 6-13 attempts per region chunk + + for ($i = 0; $i < $caveAttempts; $i++) { + if ($this->random->nextFloat() < self::CAVE_FREQUENCY) { + $regionBaseX = $regionX << 4; + $regionBaseZ = $regionZ << 4; + + // Start somewhere in the region chunk + $startX = $regionBaseX + $this->random->nextBoundedInt(16); + $startY = self::MIN_CAVE_Y + $this->random->nextBoundedInt(self::MAX_CAVE_Y - self::MIN_CAVE_Y); + $startZ = $regionBaseZ + $this->random->nextBoundedInt(16); + + $carvedBlocks += $this->generateNaturalCaveSystem($world, $startX, $startY, $startZ, $chunkX, $chunkZ); + } + } + + // Generate occasional large caverns + if ($this->random->nextFloat() < self::CAVERN_FREQUENCY) { + $regionBaseX = $regionX << 4; + $regionBaseZ = $regionZ << 4; + + $centerX = $regionBaseX + $this->random->nextBoundedInt(16); + $centerY = self::MIN_CAVE_Y + $this->random->nextBoundedInt(self::MAX_CAVE_Y - self::MIN_CAVE_Y); + $centerZ = $regionBaseZ + $this->random->nextBoundedInt(16); + + $carvedBlocks += $this->generateNaturalCavern($world, $centerX, $centerY, $centerZ, $chunkX, $chunkZ); + } + } + } + + // if ($carvedBlocks > 0) { + // error_log("CaveGenerator: Carved $carvedBlocks blocks in chunk $chunkX, $chunkZ"); + // } + } + + /** + * Generate a natural winding cave system + */ + private function generateNaturalCaveSystem(ChunkManager $world, int $startX, int $startY, int $startZ, int $targetChunkX, int $targetChunkZ): int { + $length = self::TUNNEL_LENGTH_MIN + $this->random->nextBoundedInt(self::TUNNEL_LENGTH_MAX - self::TUNNEL_LENGTH_MIN); + + $x = (float)$startX; + $y = (float)$startY; + $z = (float)$startZ; + + // Start with random direction + $yaw = $this->random->nextFloat() * M_PI * 2.0; + $pitch = ($this->random->nextFloat() - 0.5) * 0.4; + + $carvedBlocks = 0; + + for ($i = 0; $i < $length; $i++) { + // Natural radius variation - more organic shape + $progress = $i / (float)$length; + $baseRadius = 1.5 + $this->random->nextFloat() * 2.0; // 1.5-3.5 base radius + + // Add natural variation using sine wave and random factors + $sizeVariation = sin($progress * M_PI * 3) * 0.5 + ($this->random->nextFloat() - 0.5) * 0.8; + $radius = max(0.8, $baseRadius + $sizeVariation); + + // Carve current position + $carvedBlocks += $this->carveSphere($world, (int)round($x), (int)round($y), (int)round($z), $radius, $targetChunkX, $targetChunkZ); + + // Natural direction changes - more organic movement + $yawChange = ($this->random->nextFloat() - 0.5) * 0.25; // More natural turning + $pitchChange = ($this->random->nextFloat() - 0.5) * 0.15; + + // Occasionally make sharper turns for more interesting caves + if ($this->random->nextFloat() < 0.05) { // 5% chance for sharp turn + $yawChange = ($this->random->nextFloat() - 0.5) * 0.8; + $pitchChange = ($this->random->nextFloat() - 0.5) * 0.4; + } + + $yaw += $yawChange; + $pitch += $pitchChange; + + // Keep pitch reasonable but allow more variation than before + $pitch = max(-0.6, min(0.6, $pitch)); + + // Natural movement - variable speed + $moveSpeed = 0.7 + $this->random->nextFloat() * 0.6; // 0.7-1.3 speed variation + + $x += cos($yaw) * cos($pitch) * $moveSpeed; + $y += sin($pitch) * $moveSpeed; + $z += sin($yaw) * cos($pitch) * $moveSpeed; + + // Natural branching - less frequent but more organic + if ($this->random->nextFloat() < 0.04 && $i > 15) { // 4% chance after 15 blocks + $branchLength = 20 + $this->random->nextBoundedInt(40); + $carvedBlocks += $this->generateNaturalBranch($world, $x, $y, $z, $branchLength, $targetChunkX, $targetChunkZ); + } + + // Very rarely, create a small chamber + if ($this->random->nextFloat() < 0.008 && $i > 20) { // 0.8% chance + $carvedBlocks += $this->generateSmallChamber($world, $x, $y, $z, $targetChunkX, $targetChunkZ); + } + } + + return $carvedBlocks; + } + + /** + * Generate natural branch tunnels + */ + private function generateNaturalBranch(ChunkManager $world, float $startX, float $startY, float $startZ, int $length, int $targetChunkX, int $targetChunkZ): int { + $x = $startX; + $y = $startY; + $z = $startZ; + + // Branch direction - split off from main tunnel naturally + $yaw = $this->random->nextFloat() * M_PI * 2.0; + $pitch = ($this->random->nextFloat() - 0.5) * 0.5; + + $carvedBlocks = 0; + + for ($i = 0; $i < $length; $i++) { + // Branches get smaller as they extend + $progress = $i / (float)$length; + $radius = 2.0 - $progress * 0.8 + ($this->random->nextFloat() - 0.5) * 0.4; // 2.0 down to ~1.2 + $radius = max(0.6, $radius); + + $carvedBlocks += $this->carveSphere($world, (int)round($x), (int)round($y), (int)round($z), $radius, $targetChunkX, $targetChunkZ); + + // Natural branch movement + $yaw += ($this->random->nextFloat() - 0.5) * 0.2; + $pitch += ($this->random->nextFloat() - 0.5) * 0.12; + $pitch = max(-0.4, min(0.4, $pitch)); + + // Variable movement speed for branches + $moveSpeed = 0.6 + $this->random->nextFloat() * 0.4; + + $x += cos($yaw) * cos($pitch) * $moveSpeed; + $y += sin($pitch) * $moveSpeed; + $z += sin($yaw) * cos($pitch) * $moveSpeed; + } + + return $carvedBlocks; + } + + /** + * Generate small natural chambers + */ + private function generateSmallChamber(ChunkManager $world, float $centerX, float $centerY, float $centerZ, int $targetChunkX, int $targetChunkZ): int { + $size = 4 + $this->random->nextBoundedInt(6); // 4-9 block chambers + $carvedBlocks = 0; + + // Create irregular chamber with 2-4 overlapping spheres + $sphereCount = 2 + $this->random->nextBoundedInt(3); + + for ($i = 0; $i < $sphereCount; $i++) { + $offsetX = $centerX + ($this->random->nextFloat() - 0.5) * $size * 0.5; + $offsetY = $centerY + ($this->random->nextFloat() - 0.5) * $size * 0.3; + $offsetZ = $centerZ + ($this->random->nextFloat() - 0.5) * $size * 0.5; + + $radius = 2.5 + $this->random->nextFloat() * 2.0; // 2.5-4.5 radius + + $carvedBlocks += $this->carveSphere($world, (int)round($offsetX), (int)round($offsetY), (int)round($offsetZ), $radius, $targetChunkX, $targetChunkZ); + } + + return $carvedBlocks; + } + + /** + * Generate natural large caverns + */ + private function generateNaturalCavern(ChunkManager $world, int $centerX, int $centerY, int $centerZ, int $targetChunkX, int $targetChunkZ): int { + $sizeX = self::CAVERN_SIZE_MIN + $this->random->nextBoundedInt(self::CAVERN_SIZE_MAX - self::CAVERN_SIZE_MIN); + $sizeY = (int)($sizeX * 0.6) + $this->random->nextBoundedInt((int)($sizeX * 0.3)); + $sizeZ = self::CAVERN_SIZE_MIN + $this->random->nextBoundedInt(self::CAVERN_SIZE_MAX - self::CAVERN_SIZE_MIN); + + $carvedBlocks = 0; + + // Create natural, irregular cavern shape + $sphereCount = 4 + $this->random->nextBoundedInt(7); // 4-10 spheres + + for ($i = 0; $i < $sphereCount; $i++) { + // More natural sphere placement + $angle = ($i / (float)$sphereCount) * M_PI * 2.0 + ($this->random->nextFloat() - 0.5) * 1.0; + $distance = $this->random->nextFloat() * $sizeX * 0.4; + + $offsetX = $centerX + cos($angle) * $distance; + $offsetY = $centerY + ($this->random->nextFloat() - 0.5) * $sizeY * 0.8; + $offsetZ = $centerZ + sin($angle) * $distance; + + $radius = 3.0 + $this->random->nextFloat() * 4.0; // 3.0-7.0 radius + + $carvedBlocks += $this->carveSphere($world, (int)round($offsetX), (int)round($offsetY), (int)round($offsetZ), $radius, $targetChunkX, $targetChunkZ); + } + + // Add some natural tunnels leading from the cavern + $tunnelCount = 2 + $this->random->nextBoundedInt(4); // 2-5 tunnels + for ($i = 0; $i < $tunnelCount; $i++) { + $tunnelLength = 15 + $this->random->nextBoundedInt(30); + $yaw = $this->random->nextFloat() * M_PI * 2.0; + $pitch = ($this->random->nextFloat() - 0.5) * 0.4; + + $x = (float)$centerX; + $y = (float)$centerY; + $z = (float)$centerZ; + + for ($j = 0; $j < $tunnelLength; $j++) { + $progress = $j / (float)$tunnelLength; + $radius = 2.8 - $progress * 1.0; // Taper from 2.8 to 1.8 + $radius = max(1.0, $radius); + + $carvedBlocks += $this->carveSphere($world, (int)round($x), (int)round($y), (int)round($z), $radius, $targetChunkX, $targetChunkZ); + + // Natural tunnel movement + $yaw += ($this->random->nextFloat() - 0.5) * 0.15; + $pitch += ($this->random->nextFloat() - 0.5) * 0.08; + + $x += cos($yaw) * cos($pitch) * 0.9; + $y += sin($pitch) * 0.9; + $z += sin($yaw) * cos($pitch) * 0.9; + } + } + + return $carvedBlocks; + } + + /** + * Carve a spherical area (for tunnels and caverns) + */ + private function carveSphere(ChunkManager $world, int $centerX, int $centerY, int $centerZ, float $radius, int $targetChunkX, int $targetChunkZ): int { + $chunk = $world->getChunk($targetChunkX, $targetChunkZ); + if ($chunk === null) return 0; + + $chunkBaseX = $targetChunkX << 4; + $chunkBaseZ = $targetChunkZ << 4; + + $carvedBlocks = 0; + $radiusSquared = $radius * $radius; + + // Only carve within the target chunk boundaries + $minX = max(0, $centerX - (int)ceil($radius) - $chunkBaseX); + $maxX = min(15, $centerX + (int)ceil($radius) - $chunkBaseX); + $minY = max(self::MIN_CAVE_Y, $centerY - (int)ceil($radius)); + $maxY = min(self::MAX_CAVE_Y, $centerY + (int)ceil($radius)); + $minZ = max(0, $centerZ - (int)ceil($radius) - $chunkBaseZ); + $maxZ = min(15, $centerZ + (int)ceil($radius) - $chunkBaseZ); + + for ($x = $minX; $x <= $maxX; $x++) { + for ($y = $minY; $y <= $maxY; $y++) { + for ($z = $minZ; $z <= $maxZ; $z++) { + $worldX = $chunkBaseX + $x; + $worldY = $y; + $worldZ = $chunkBaseZ + $z; + + // Calculate distance from center + $dx = $worldX - $centerX; + $dy = $worldY - $centerY; + $dz = $worldZ - $centerZ; + $distanceSquared = $dx * $dx + $dy * $dy + $dz * $dz; + + // Only carve if within radius and the block is stone + if ($distanceSquared <= $radiusSquared) { + $block = $chunk->getBlockStateId($x, $y, $z); + if ($block === VanillaBlocks::STONE()->getStateId()) { + $chunk->setBlockStateId($x, $y, $z, VanillaBlocks::AIR()->getStateId()); + $carvedBlocks++; + } + } + } + } + } + + return $carvedBlocks; + } + + /** + * Apply natural aquifer system with proper water and lava placement + */ + public function applyAquifers(ChunkManager $world, int $chunkX, int $chunkZ): void { + $chunk = $world->getChunk($chunkX, $chunkZ); + if ($chunk === null) return; + + $baseX = $chunkX << 4; + $baseZ = $chunkZ << 4; + + // Reset random for consistent aquifer placement + $this->random->setSeed($chunkX * 871236847 + $chunkZ * 321487613); + + // Water pools disabled by request; only generate lava pools + $this->applyLavaPools($chunk, $chunkX, $chunkZ); + } + + /** + * Apply water aquifers - only in specific areas, not filling all caves + */ + private function applyWaterAquifers(Chunk $chunk, int $chunkX, int $chunkZ): void { + // Disabled: do not place water in caves + return; + $baseX = $chunkX << 4; + $baseZ = $chunkZ << 4; + + // Only some chunks get water aquifers + if ($this->random->nextFloat() > self::WATER_FILL_CHANCE) { + return; + } + + // Generate aquifer level map for chunk + $aquiferMap = $this->aquiferNoise->getFractalBrownianMotion($baseX, 0, $baseZ, 0.5, 0.5); + + $waterCount = 0; + + // Find connected cave areas and fill them with water up to a certain level + for ($x = 0; $x < 16; ++$x) { + for ($z = 0; $z < 16; ++$z) { + $index = $x | ($z << 4); + $localWaterLevel = (int)(self::AQUIFER_BASE + $aquiferMap[$index] * self::AQUIFER_VARIANCE); + + // Only create water if there's a cave opening at this location + $hasWaterSource = false; + for ($y = $localWaterLevel; $y >= self::MIN_CAVE_Y; $y--) { + $block = $chunk->getBlockStateId($x, $y, $z); + + if ($block === VanillaBlocks::AIR()->getStateId()) { + $hasWaterSource = true; + break; + } + } + + // Fill connected cave areas with water up to the water level + if ($hasWaterSource) { + for ($y = self::MIN_CAVE_Y; $y <= $localWaterLevel && $y < self::MAX_CAVE_Y; ++$y) { + $block = $chunk->getBlockStateId($x, $y, $z); + + if ($block === VanillaBlocks::AIR()->getStateId()) { + $chunk->setBlockStateId($x, $y, $z, VanillaBlocks::WATER()->getStateId()); + $waterCount++; + } + } + } + } + } + + // if ($waterCount > 0) { + // error_log("CaveGenerator: Applied $waterCount water blocks in chunk $chunkX, $chunkZ"); + // } + } + + /** + * Apply lava pools - small isolated pools, not connected systems + */ + private function applyLavaPools(Chunk $chunk, int $chunkX, int $chunkZ): void { + // Chance for lava pools to generate + $lavaPoolChance = 0.08; // 8% chance per chunk + + if ($this->random->nextFloat() > $lavaPoolChance) { + return; + } + + $lavaCount = 0; + $poolsGenerated = 0; + $maxPools = 1 + $this->random->nextBoundedInt(3); // 1-3 lava pools per chunk + + for ($poolAttempt = 0; $poolAttempt < $maxPools; $poolAttempt++) { + // Random location for lava pool + $poolX = $this->random->nextBoundedInt(16); + $poolZ = $this->random->nextBoundedInt(16); + $poolY = self::MIN_CAVE_Y + $this->random->nextBoundedInt(self::LAVA_LEVEL - self::MIN_CAVE_Y + 1); + + // Check if there's a cave area here + $block = $chunk->getBlockStateId($poolX, $poolY, $poolZ); + if ($block !== VanillaBlocks::AIR()->getStateId()) { + continue; // No cave here, try next location + } + + // Create small lava pool (3-7 blocks wide) + $poolSize = 3 + $this->random->nextBoundedInt(5); + $poolRadius = $poolSize / 2.0; + + $poolLavaCount = 0; + + for ($dx = -$poolSize; $dx <= $poolSize; $dx++) { + for ($dz = -$poolSize; $dz <= $poolSize; $dz++) { + for ($dy = -2; $dy <= 1; $dy++) { // Lava pools are shallow + $lx = $poolX + $dx; + $lz = $poolZ + $dz; + $ly = $poolY + $dy; + + // Check bounds + if ($lx < 0 || $lx >= 16 || $lz < 0 || $lz >= 16 || + $ly < self::MIN_CAVE_Y || $ly > self::LAVA_LEVEL) { + continue; + } + + // Check if within pool radius + $distance = sqrt($dx * $dx + $dz * $dz + $dy * $dy * 0.5); // Flatten vertically + if ($distance > $poolRadius) { + continue; + } + + $currentBlock = $chunk->getBlockStateId($lx, $ly, $lz); + + // Only place lava in air blocks (cave areas) + if ($currentBlock === VanillaBlocks::AIR()->getStateId()) { + $chunk->setBlockStateId($lx, $ly, $lz, VanillaBlocks::LAVA()->getStateId()); + $poolLavaCount++; + $lavaCount++; + } + } + } + } + + // Only count as successful pool if we placed enough lava + if ($poolLavaCount >= 5) { + $poolsGenerated++; + } + } + + // if ($lavaCount > 0) { + // error_log("CaveGenerator: Generated $poolsGenerated lava pools ($lavaCount lava blocks) in chunk $chunkX, $chunkZ"); + // } + } +} \ No newline at end of file diff --git a/src/muqsit/vanillagenerator/generator/ground/GroundGenerator.php b/src/muqsit/vanillagenerator/generator/ground/GroundGenerator.php index 0a1a693..25e9700 100644 --- a/src/muqsit/vanillagenerator/generator/ground/GroundGenerator.php +++ b/src/muqsit/vanillagenerator/generator/ground/GroundGenerator.php @@ -52,7 +52,7 @@ final protected function setGroundMaterial(Block $ground_material) : void{ * @param float $surface_noise the amplitude of random variation in surface height */ public function generateTerrainColumn(ChunkManager $world, Random $random, int $x, int $z, int $biome, float $surface_noise) : void{ - $sea_level = 64; + $sea_level = 63; // 1.18+ sea level $top_mat = $this->top_material->getStateId(); $ground_mat = $this->ground_material->getStateId(); @@ -77,8 +77,19 @@ public function generateTerrainColumn(ChunkManager $world, Random $random, int $ $block_x = $x & Chunk::COORD_MASK; $block_z = $z & Chunk::COORD_MASK; - for($y = 255; $y >= 0; --$y){ - if($y <= $random->nextBoundedInt($this->bedrock_roughness)){ + // Use actual world bounds but validate subchunk access + $world_max_y = $world->getMaxY(); + $world_min_y = $world->getMinY(); + + for($y = $world_max_y; $y >= $world_min_y; --$y){ + // Validate subchunk index before accessing chunk + $subchunk_index = $y >> 4; + if($subchunk_index < -4 || $subchunk_index > 19) { + continue; // Skip invalid subchunk indices + } + + // Place bedrock at the bottom of the world + if($y <= $world_min_y + $random->nextBoundedInt($this->bedrock_roughness)){ $chunk->setBlockStateId($block_x, $y, $block_z, $bedrock); }else{ $mat = $block_state_registry->fromStateId($chunk->getBlockStateId($block_x, $y, $block_z)); diff --git a/src/muqsit/vanillagenerator/generator/ground/MesaGroundGenerator.php b/src/muqsit/vanillagenerator/generator/ground/MesaGroundGenerator.php index 3d5bdf8..c8a7f16 100644 --- a/src/muqsit/vanillagenerator/generator/ground/MesaGroundGenerator.php +++ b/src/muqsit/vanillagenerator/generator/ground/MesaGroundGenerator.php @@ -51,7 +51,7 @@ private function initialize(int $seed) : void{ public function generateTerrainColumn(ChunkManager $world, Random $random, int $x, int $z, int $biome, float $surface_noise) : void{ $this->initialize($random->getSeed()); - $sea_level = 64; + $sea_level = 63; // 1.18+ sea level $top_mat = $this->top_material; $ground_mat = $this->ground_material; @@ -83,11 +83,22 @@ public function generateTerrainColumn(ChunkManager $world, Random $random, int $ $grass = VanillaBlocks::GRASS(); $coarse_dirt = VanillaBlocks::DIRT()->setDirtType(DirtType::COARSE); - for($y = 255; $y >= 0; --$y){ + // Use actual world bounds but validate subchunk access + $world_max_y = $world->getMaxY(); + $world_min_y = $world->getMinY(); + + for($y = $world_max_y; $y >= $world_min_y; --$y){ + // Validate subchunk index before accessing world + $subchunk_index = $y >> 4; + if($subchunk_index < -4 || $subchunk_index > 19) { + continue; // Skip invalid subchunk indices + } + if($y < (int) $bryce_canyon_height && $world->getBlockAt($x, $y, $z)->getTypeId() === BlockTypeIds::AIR){ $world->setBlockAt($x, $y, $z, VanillaBlocks::STONE()); } - if($y <= $random->nextBoundedInt(5)){ + // Place bedrock at the bottom of the world + if($y <= $world_min_y + $random->nextBoundedInt(5)){ $world->setBlockAt($x, $y, $z, VanillaBlocks::BEDROCK()); }else{ $mat_id = $world->getBlockAt($x, $y, $z)->getTypeId(); diff --git a/src/muqsit/vanillagenerator/generator/object/DoubleTallPlant.php b/src/muqsit/vanillagenerator/generator/object/DoubleTallPlant.php index fb8d720..2c7c0dc 100644 --- a/src/muqsit/vanillagenerator/generator/object/DoubleTallPlant.php +++ b/src/muqsit/vanillagenerator/generator/object/DoubleTallPlant.php @@ -33,6 +33,11 @@ public function generate(ChunkManager $world, Random $random, int $source_x, int $z = $source_z + $random->nextBoundedInt(8) - $random->nextBoundedInt(8); $y = $source_y + $random->nextBoundedInt(4) - $random->nextBoundedInt(4); + // Check bounds for both Y and Y+1 + if($y < $world->getMinY() || $y + 1 > $world->getMaxY()) { + continue; + } + $block = $world->getBlockAt($x, $y, $z); $top_block = $world->getBlockAt($x, $y + 1, $z); if($y < $height && $block->getTypeId() === BlockTypeIds::AIR && $top_block->getTypeId() === BlockTypeIds::AIR && $world->getBlockAt($x, $y - 1, $z)->getTypeId() === BlockTypeIds::GRASS){ diff --git a/src/muqsit/vanillagenerator/generator/object/Lake.php b/src/muqsit/vanillagenerator/generator/object/Lake.php index b085d67..0a3c37c 100644 --- a/src/muqsit/vanillagenerator/generator/object/Lake.php +++ b/src/muqsit/vanillagenerator/generator/object/Lake.php @@ -83,9 +83,19 @@ public function generate(ChunkManager $world, Random $random, int $source_x, int continue; } + // Check if coordinates are within world bounds + $world_x = $source_x + $x; + $world_y = $source_y + $y; + $world_z = $source_z + $z; + + if($world_y < $world->getMinY() || $world_y > $world->getMaxY() || + $world_y + 1 > $world->getMaxY()) { + continue; // Skip if Y coordinate or Y+1 is outside world bounds + } + $type = $this->type; - $block = $world->getBlockAt($source_x + $x, $source_y + $y, $source_z + $z); - $block_above = $world->getBlockAt($source_x + $x, $source_y + $y + 1, $source_z + $z); + $block = $world->getBlockAt($world_x, $world_y, $world_z); + $block_above = $world->getBlockAt($world_x, $world_y + 1, $world_z); $block_type = $block->getTypeId(); if(($block_type === BlockTypeIds::DIRT && $block_above instanceof Wood) || $block instanceof Wood){ continue; @@ -93,7 +103,7 @@ public function generate(ChunkManager $world, Random $random, int $source_x, int if($y >= (int) (self::MAX_HEIGHT / 2)){ $type = VanillaBlocks::AIR(); - if(TerrainObject::killWeakBlocksAbove($world, $source_x + $x, $source_y + $y, $source_z + $z)){ + if(TerrainObject::killWeakBlocksAbove($world, $world_x, $world_y, $world_z)){ break; } @@ -101,11 +111,11 @@ public function generate(ChunkManager $world, Random $random, int $source_x, int $type = $block; } }elseif($y === (int) (self::MAX_HEIGHT / 2 - 1)){ - if($type instanceof Water && $type->isStill() && BiomeClimateManager::isCold($chunk->getBiomeId($x & Chunk::COORD_MASK, $y, $z & Chunk::COORD_MASK), $source_x + $x, $y, $source_z + $z)){ + if($type instanceof Water && $type->isStill() && BiomeClimateManager::isCold($chunk->getBiomeId($x & Chunk::COORD_MASK, $y, $z & Chunk::COORD_MASK), $world_x, $y, $world_z)){ $type = VanillaBlocks::ICE(); } } - $world->setBlockAt($source_x + $x, $source_y + $y, $source_z + $z, $type); + $world->setBlockAt($world_x, $world_y, $world_z, $type); } } } @@ -117,10 +127,21 @@ public function generate(ChunkManager $world, Random $random, int $source_x, int continue; } - $block = $world->getBlockAt($source_x + $x, $source_y + $y - 1, $source_z + $z); - $block_above = $world->getBlockAt($source_x + $x, $source_y + $y, $source_z + $z); + // Check if coordinates are within world bounds + $world_x = $source_x + $x; + $world_y_below = $source_y + $y - 1; + $world_y = $source_y + $y; + $world_z = $source_z + $z; + + if($world_y_below < $world->getMinY() || $world_y_below > $world->getMaxY() || + $world_y < $world->getMinY() || $world_y > $world->getMaxY()) { + continue; // Skip if Y coordinates are outside world bounds + } + + $block = $world->getBlockAt($world_x, $world_y_below, $world_z); + $block_above = $world->getBlockAt($world_x, $world_y, $world_z); if($block->getTypeId() === BlockTypeIds::DIRT && $block_above->isTransparent() && $block_above->getLightLevel() > 0){ - $world->setBlockAt($source_x + $x, $source_y + $y - 1, $source_z + $z, $mycel_biome ? VanillaBlocks::MYCELIUM() : VanillaBlocks::GRASS()); + $world->setBlockAt($world_x, $world_y_below, $world_z, $mycel_biome ? VanillaBlocks::MYCELIUM() : VanillaBlocks::GRASS()); } } } @@ -149,7 +170,14 @@ private function canPlace(array $lake_map, ChunkManager $world, int $sourceX, in && (($z <= 0) || !$this->isLakeBlock($lake_map, $x, $y - 1, $z)))){ continue; } - $block = $world->getBlockAt($sourceX + $x, $sourceY + $y, $sourceZ + $z); + + // Check if coordinates are within world bounds + $world_y = $sourceY + $y; + if($world_y < $world->getMinY() || $world_y > $world->getMaxY()) { + continue; // Skip if Y coordinate is outside world bounds + } + + $block = $world->getBlockAt($sourceX + $x, $world_y, $sourceZ + $z); if($y >= self::MAX_HEIGHT / 2 && (($block instanceof Liquid) || $block->getTypeId() === BlockTypeIds::ICE)){ return false; // there's already some liquids above } diff --git a/src/muqsit/vanillagenerator/generator/object/TerrainObject.php b/src/muqsit/vanillagenerator/generator/object/TerrainObject.php index 8ba92a0..40f2169 100644 --- a/src/muqsit/vanillagenerator/generator/object/TerrainObject.php +++ b/src/muqsit/vanillagenerator/generator/object/TerrainObject.php @@ -25,8 +25,9 @@ abstract class TerrainObject{ public static function killWeakBlocksAbove(ChunkManager $world, int $x, int $y, int $z) : bool{ $cur_y = $y + 1; $changed = false; + $max_y = $world->getMaxY(); - while($cur_y < World::Y_MAX){ + while($cur_y < $max_y){ // Use < instead of <= to prevent accessing Y=320 $block = $world->getBlockAt($x, $cur_y, $z); if(!($block instanceof Flowable)){ break; diff --git a/src/muqsit/vanillagenerator/generator/object/tree/MegaPineTree.php b/src/muqsit/vanillagenerator/generator/object/tree/MegaPineTree.php index faf05d3..0a24f5b 100644 --- a/src/muqsit/vanillagenerator/generator/object/tree/MegaPineTree.php +++ b/src/muqsit/vanillagenerator/generator/object/tree/MegaPineTree.php @@ -56,15 +56,24 @@ private function generatePodzolPatch(int $source_x, int $source_y, int $source_z continue; } for($y = 2; $y >= -3; --$y){ - $block_id = $world->getBlockAt($source_x + $x, $source_y + $y, $source_z + $z)->getTypeId(); + $check_y = $source_y + $y; + $check_y_above = $source_y + $y + 1; + + // Check bounds for both Y and Y+1 + if($check_y < $world->getMinY() || $check_y > $world->getMaxY() || + $check_y_above > $world->getMaxY()) { + continue; + } + + $block_id = $world->getBlockAt($source_x + $x, $check_y, $source_z + $z)->getTypeId(); if($block_id === BlockTypeIds::GRASS || $block_id === BlockTypeIds::DIRT){ - if($world->getBlockAt($source_x + $x, $source_y + $y + 1, $source_z + $z)->isSolid()){ + if($world->getBlockAt($source_x + $x, $check_y_above, $source_z + $z)->isSolid()){ $dirt = VanillaBlocks::DIRT(); }else{ $dirt = VanillaBlocks::PODZOL(); } - $world->setBlockAt($source_x + $x, $source_y + $y, $source_z + $z, $dirt); - }elseif($block_id !== BlockTypeIds::AIR && $source_y + $y < $source_y){ + $world->setBlockAt($source_x + $x, $check_y, $source_z + $z, $dirt); + }elseif($block_id !== BlockTypeIds::AIR && $check_y < $source_y){ break; } } diff --git a/src/muqsit/vanillagenerator/generator/overworld/OverworldGenerator.php b/src/muqsit/vanillagenerator/generator/overworld/OverworldGenerator.php index f847353..1d5ffbb 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/OverworldGenerator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/OverworldGenerator.php @@ -19,17 +19,22 @@ use muqsit\vanillagenerator\generator\noise\glowstone\SimplexOctaveGenerator; use muqsit\vanillagenerator\generator\overworld\biome\BiomeHeightManager; use muqsit\vanillagenerator\generator\overworld\biome\BiomeIds; +use muqsit\vanillagenerator\generator\overworld\biome\Biome3DGenerator; use muqsit\vanillagenerator\generator\overworld\populator\OverworldPopulator; use muqsit\vanillagenerator\generator\overworld\populator\SnowPopulator; use muqsit\vanillagenerator\generator\utils\preset\SimpleGeneratorPreset; use muqsit\vanillagenerator\generator\utils\WorldOctaves; use muqsit\vanillagenerator\generator\VanillaBiomeGrid; use muqsit\vanillagenerator\generator\VanillaGenerator; +use muqsit\vanillagenerator\generator\cave\ModernCaveGenerator; +use muqsit\vanillagenerator\generator\cave\CaveGenerator; use pocketmine\block\VanillaBlocks; use pocketmine\utils\Random; use pocketmine\world\ChunkManager; use pocketmine\world\format\Chunk; use function array_key_exists; +use function max; +use function min; /** * @extends VanillaGenerator> @@ -62,6 +67,7 @@ private static function densityHash(int $i, int $j, int $k) : int{ } public static function init() : void{ + // Biome-specific ground generators self::setBiomeSpecificGround(new SandyGroundGenerator(), BiomeIds::BEACH, BiomeIds::COLD_BEACH, BiomeIds::DESERT, BiomeIds::DESERT_HILLS, BiomeIds::DESERT_MUTATED); self::setBiomeSpecificGround(new RockyGroundGenerator(), BiomeIds::STONE_BEACH); self::setBiomeSpecificGround(new SnowyGroundGenerator(), BiomeIds::ICE_PLAINS_SPIKES); @@ -103,7 +109,7 @@ protected static function setBiomeSpecificGround(GroundGenerator $gen, int ...$b protected const DETAIL_NOISE_SCALE_Y = 160.0; protected const DETAIL_NOISE_SCALE_Z = 80.0; protected const SURFACE_SCALE = 0.0625; - protected const BASE_SIZE = 8.5; + protected const BASE_SIZE = 16.0; // Adjusted for 1.18+ height: sea level Y=64 maps to density level ~16 protected const STRETCH_Y = 12.0; protected const BIOME_HEIGHT_OFFSET = 0.0; protected const BIOME_HEIGHT_WEIGHT = 1.0; @@ -115,6 +121,7 @@ protected static function setBiomeSpecificGround(GroundGenerator $gen, int ...$b private GroundGenerator $ground_gen; private string $type = WorldType::NORMAL; + private ?CaveGenerator $cave_generator = null; public function __construct(int $seed, string $preset_string){ $preset = SimpleGeneratorPreset::parse($preset_string); @@ -124,13 +131,22 @@ public function __construct(int $seed, string $preset_string){ $preset->exists("worldtype") ? WorldType::fromString($preset->getString("worldtype")) : null, $preset ); - $this->ground_gen = new GroundGenerator(); - $this->addPopulators(new OverworldPopulator(), new SnowPopulator()); + // Ground generation + $this->ground_gen = new GroundGenerator(); + + // Use legacy cave generator + $this->cave_generator = new CaveGenerator($seed); + + // Add populators + $this->addPopulators(new OverworldPopulator(), new SnowPopulator()); } + + /* public function getGroundGenerator() : GroundGenerator{ return $this->ground_gen; } + */ protected function generateChunkData(ChunkManager $world, int $chunk_x, int $chunk_z, VanillaBiomeGrid $grid) : void{ $this->generateRawTerrain($world, $chunk_x, $chunk_z); @@ -150,19 +166,39 @@ protected function generateChunkData(ChunkManager $world, int $chunk_x, int $chu $min_y = $world->getMinY(); $max_y = $world->getMaxY(); + + // Generate 3D biomes for this chunk + Biome3DGenerator::generate3DBiomes($grid, $this->random, $chunk_x, $chunk_z, $min_y, $max_y); + for($x = 0; $x < $size_x; ++$x){ for($z = 0; $z < $size_z; ++$z){ - $id = $grid->getBiome($x, $z); + $surface_biome_id = $grid->getBiome($x, $z); + + // Set biomes for all Y levels using 3D biome data for($y = $min_y; $y < $max_y; ++$y){ - $chunk->setBiomeId($x, $y, $z, $id); + $biome_3d = $grid->getBiome3D($x, $y, $z); + if($biome_3d !== null){ + $chunk->setBiomeId($x, $y, $z, $biome_3d); + }else{ + // Fallback to surface biome if no 3D biome is set + $chunk->setBiomeId($x, $y, $z, $surface_biome_id); + } } - if($id !== null && array_key_exists($id, self::$GROUND_MAP)){ - self::$GROUND_MAP[$id]->generateTerrainColumn($world, $this->random, $cx + $x, $cz + $z, $id, $surface_noise[$x | $z << Chunk::COORD_BIT_SIZE]); + + // Surface terrain (top/ground materials) + if($surface_biome_id !== null && array_key_exists($surface_biome_id, self::$GROUND_MAP)){ + self::$GROUND_MAP[$surface_biome_id]->generateTerrainColumn($world, $this->random, $cx + $x, $cz + $z, $surface_biome_id, $surface_noise[$x | $z << Chunk::COORD_BIT_SIZE]); }else{ - $this->ground_gen->generateTerrainColumn($world, $this->random, $cx + $x, $cz + $z, $id, $surface_noise[$x | $z << Chunk::COORD_BIT_SIZE]); + $this->ground_gen->generateTerrainColumn($world, $this->random, $cx + $x, $cz + $z, $surface_biome_id, $surface_noise[$x | $z << Chunk::COORD_BIT_SIZE]); } } } + + // Caves: carve after base stone terrain and biome assignment (legacy) + if($this->cave_generator !== null){ + $this->cave_generator->carveDirectly($world, $chunk_x, $chunk_z); + $this->cave_generator->applyAquifers($world, $chunk_x, $chunk_z); + } } protected function createWorldOctaves() : WorldOctaves{ @@ -172,17 +208,18 @@ protected function createWorldOctaves() : WorldOctaves{ $height->x_scale = self::HEIGHT_NOISE_SCALE_X; $height->z_scale = self::HEIGHT_NOISE_SCALE_Z; - $roughness = PerlinOctaveGenerator::fromRandomAndOctaves($seed, 16, 5, 33, 5); + // 1.18+ height: 48 Y-levels for full height range (Y=-64 to Y=319) + $roughness = PerlinOctaveGenerator::fromRandomAndOctaves($seed, 16, 5, 48, 5); $roughness->x_scale = self::COORDINATE_SCALE; $roughness->y_scale = self::HEIGHT_SCALE; $roughness->z_scale = self::COORDINATE_SCALE; - $roughness2 = PerlinOctaveGenerator::fromRandomAndOctaves($seed, 16, 5, 33, 5); + $roughness2 = PerlinOctaveGenerator::fromRandomAndOctaves($seed, 16, 5, 48, 5); $roughness2->x_scale = self::COORDINATE_SCALE; $roughness2->y_scale = self::HEIGHT_SCALE; $roughness2->z_scale = self::COORDINATE_SCALE; - $detail = PerlinOctaveGenerator::fromRandomAndOctaves($seed, 8, 5, 33, 5); + $detail = PerlinOctaveGenerator::fromRandomAndOctaves($seed, 8, 5, 48, 5); $detail->x_scale = self::COORDINATE_SCALE / self::DETAIL_NOISE_SCALE_X; $detail->y_scale = self::HEIGHT_SCALE / self::DETAIL_NOISE_SCALE_Y; $detail->z_scale = self::COORDINATE_SCALE / self::DETAIL_NOISE_SCALE_Z; @@ -196,7 +233,7 @@ protected function createWorldOctaves() : WorldOctaves{ protected function generateRawTerrain(ChunkManager $world, int $chunk_x, int $chunk_z) : void{ $density = $this->generateTerrainDensity($chunk_x, $chunk_z); - $sea_level = 64; + $sea_level = 63; // 1.18+ standard sea level // Terrain densities are sampled at different resolutions (1/4x on x,z and 1/8x on y by // default) @@ -216,7 +253,8 @@ protected function generateRawTerrain(ChunkManager $world, int $chunk_x, int $ch for($i = 0; $i < 5 - 1; ++$i){ for($j = 0; $j < 5 - 1; ++$j){ - for($k = 0; $k < 33 - 1; ++$k){ + // 1.18+ height: 48 - 1 = 47 density interpolation steps + for($k = 0; $k < 48 - 1; ++$k){ // 2x2 grid $d1 = $density[self::densityHash($i, $j, $k)]; $d2 = $density[self::densityHash($i + 1, $j, $k)]; @@ -231,9 +269,24 @@ protected function generateRawTerrain(ChunkManager $world, int $chunk_x, int $ch $d9 = $d1; $d10 = $d3; - $y_pos = $l + ($k << 3); + // 1.18+ world height: Y=-64 to Y=319 + $y_pos = $l + ($k << 3) - 64; + + // Clamp Y position to valid range as safety measure + $y_pos = max(-64, min(319, $y_pos)); + $y_block_pos = $y_pos & 0xf; - $sub_chunk = $chunk->getSubChunk($y_pos >> Chunk::COORD_BIT_SIZE); + // Calculate subchunk index: Y=-64 maps to subchunk -4, Y=319 maps to subchunk 19 + $subchunk_index = $y_pos >> Chunk::COORD_BIT_SIZE; + + // Final safety check for subchunk index + if($subchunk_index < -4 || $subchunk_index > 19){ + // This should never happen with proper clamping + error_log("CRITICAL: Invalid subchunk index: $subchunk_index for Y=$y_pos (k=$k, l=$l)"); + continue; + } + + $sub_chunk = $chunk->getSubChunk($subchunk_index); for($m = 0; $m < 4; ++$m){ $dens = $d9; @@ -385,10 +438,13 @@ protected function generateTerrainDensity(int $x, int $z) : array{ } $noise_h = ($noise_h * 0.2 + $avg_height_base) * self::BASE_SIZE / 8.0 * 4.0 + self::BASE_SIZE; - for($k = 0; $k < 33; ++$k){ + + // 1.18+ height: 384 total height / 8 = 48 density steps (Y=-64 to Y=319) + for($k = 0; $k < 48; ++$k){ // density should be lower and lower as we climb up, this gets a height value to // subtract from the noise. - $nh = ($k - $noise_h) * self::STRETCH_Y * 128.0 / 256.0 / $avg_height_scale; + // Updated calculation for 1.18+ height range (384 total height) + $nh = ($k - $noise_h) * self::STRETCH_Y * 128.0 / 384.0 / $avg_height_scale; if($nh < 0.0){ $nh *= 4.0; } @@ -401,8 +457,9 @@ protected function generateTerrainDensity(int $x, int $z) : array{ $dens = $noise_d < 0 ? $noise_r : ($noise_d > 1 ? $noise_r_2 : $noise_r + ($noise_r_2 - $noise_r) * $noise_d); $dens -= $nh; ++$index; - if($k > 29){ - $lowering = ($k - 29) / 3.0; + // Adjusted for 1.18+ heights: start lowering at level 38 instead of 29 (48 * 0.8 ≈ 38) + if($k > 38){ + $lowering = ($k - 38) / 9.0; // Spread over 9 levels instead of 3 // linear interpolation $dens = $dens * (1.0 - $lowering) + -10.0 * $lowering; } diff --git a/src/muqsit/vanillagenerator/generator/overworld/biome/Biome3DGenerator.php b/src/muqsit/vanillagenerator/generator/overworld/biome/Biome3DGenerator.php new file mode 100644 index 0000000..01b6f07 --- /dev/null +++ b/src/muqsit/vanillagenerator/generator/overworld/biome/Biome3DGenerator.php @@ -0,0 +1,102 @@ +getBiome($x, $z); + if($surface_biome === null) continue; + + // Generate biomes for different depth layers + for($y = $world_min_y; $y <= $world_max_y; $y += 4){ + $depth_biome = self::getBiomeForDepth($surface_biome, $y, $random, $chunk_x * 16 + $x, $chunk_z * 16 + $z); + + // Set biome for 4x4x4 region + for($dx = 0; $dx < 4 && $x + $dx < 16; $dx++){ + for($dz = 0; $dz < 4 && $z + $dz < 16; $dz++){ + for($dy = 0; $dy < 4 && $y + $dy <= $world_max_y; $dy++){ + $biome_grid->setBiome3D($x + $dx, $y + $dy, $z + $dz, $depth_biome); + } + } + } + } + } + } + } + + /** + * Determine appropriate biome based on surface biome and Y level + * + * @param int $surface_biome + * @param int $y + * @param Random $random + * @param int $world_x + * @param int $world_z + * @return int + */ + private static function getBiomeForDepth(int $surface_biome, int $y, Random $random, int $world_x, int $world_z) : int{ + // Underground biome generation based on depth and surface biome + + // Deep Dark biome (Y -64 to -10, rare) + if($y >= -64 && $y <= -10){ + // 5% chance for Deep Dark in deep areas, higher chance near Y -52 + $deep_dark_chance = ($y <= -40 && $y >= -60) ? 0.08 : 0.02; + if($random->nextFloat() < $deep_dark_chance){ + return BiomeIds::DEEP_DARK; + } + } + + // Lush Caves (Y -20 to Y 40, forest and jungle areas) + if($y >= -20 && $y <= 40){ + $is_lush_surface = in_array($surface_biome, [ + BiomeIds::FOREST, BiomeIds::BIRCH_FOREST, BiomeIds::FLOWER_FOREST, + BiomeIds::JUNGLE, BiomeIds::JUNGLE_HILLS, BiomeIds::ROOFED_FOREST + ], true); + + if($is_lush_surface && $random->nextFloat() < 0.15){ + return BiomeIds::LUSH_CAVES; + } + } + + // Dripstone Caves (Y -60 to Y 20, desert and mesa areas primarily) + if($y >= -60 && $y <= 20){ + $is_dry_surface = in_array($surface_biome, [ + BiomeIds::DESERT, BiomeIds::DESERT_HILLS, BiomeIds::MESA, + BiomeIds::MESA_PLATEAU, BiomeIds::MESA_PLATEAU_STONE, BiomeIds::SAVANNA + ], true); + + if($is_dry_surface && $random->nextFloat() < 0.2){ + return BiomeIds::DRIPSTONE_CAVES; + } + + // Lower chance in other biomes + if($random->nextFloat() < 0.05){ + return BiomeIds::DRIPSTONE_CAVES; + } + } + + // Default: use surface biome for underground areas + // This maintains existing behavior for most underground areas + return $surface_biome; + } +} \ No newline at end of file diff --git a/src/muqsit/vanillagenerator/generator/overworld/biome/BiomeHeightManager.php b/src/muqsit/vanillagenerator/generator/overworld/biome/BiomeHeightManager.php index 678b600..5c7fa21 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/biome/BiomeHeightManager.php +++ b/src/muqsit/vanillagenerator/generator/overworld/biome/BiomeHeightManager.php @@ -56,6 +56,18 @@ public static function init() : void{ self::register(new BiomeHeight(0.1, 0.4), BiomeIds::FLOWER_FOREST); self::register(new BiomeHeight(0.4125, 1.325), BiomeIds::SAVANNA_MUTATED); self::register(new BiomeHeight(1.1, 1.3125), BiomeIds::SAVANNA_PLATEAU_MUTATED); + + // 1.18+ Biome Heights + self::register(new BiomeHeight(-1.0, 0.1), BiomeIds::DEEP_DARK); // Deep underground + self::register(new BiomeHeight(-0.7, 0.4), BiomeIds::DRIPSTONE_CAVES); // Cave biome + self::register(new BiomeHeight(-0.5, 0.4), BiomeIds::LUSH_CAVES); // Cave biome + self::register(new BiomeHeight(-0.2, 0.1), BiomeIds::MANGROVE_SWAMP); // Swamp-like + self::register(new BiomeHeight(0.2, 0.8), BiomeIds::MEADOW); // Mountain meadow + self::register(new BiomeHeight(0.25, 0.8), BiomeIds::GROVE); // Mountain grove + self::register(new BiomeHeight(1.0, 0.3), BiomeIds::SNOWY_SLOPES); // Mountain slopes + self::register(new BiomeHeight(1.4, 0.8), BiomeIds::FROZEN_PEAKS); // High peaks + self::register(new BiomeHeight(1.4, 0.8), BiomeIds::JAGGED_PEAKS); // High peaks + self::register(new BiomeHeight(1.4, 0.8), BiomeIds::STONY_PEAKS); // High peaks } public static function register(BiomeHeight $height, int ...$biomes) : void{ diff --git a/src/muqsit/vanillagenerator/generator/overworld/biome/BiomeIds.php b/src/muqsit/vanillagenerator/generator/overworld/biome/BiomeIds.php index f3eb00e..c3938f2 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/biome/BiomeIds.php +++ b/src/muqsit/vanillagenerator/generator/overworld/biome/BiomeIds.php @@ -69,4 +69,18 @@ interface BiomeIds{ public const MESA_BRYCE = VanillaBiomeIds::MESA_BRYCE; public const MESA_PLATEAU_STONE_MUTATED = VanillaBiomeIds::MESA_PLATEAU_STONE_MUTATED; public const MESA_PLATEAU_MUTATED = VanillaBiomeIds::MESA_PLATEAU_MUTATED; + + // 1.18+ Underground Biomes + public const DEEP_DARK = VanillaBiomeIds::DEEP_DARK; // Deep Dark biome for Ancient Cities + public const DRIPSTONE_CAVES = VanillaBiomeIds::DRIPSTONE_CAVES; // Dripstone cave biome + public const LUSH_CAVES = VanillaBiomeIds::LUSH_CAVES; // Lush cave biome + + // 1.18+ Surface Biomes + public const MANGROVE_SWAMP = VanillaBiomeIds::MANGROVE_SWAMP; // Mangrove swamp biome + public const MEADOW = VanillaBiomeIds::MEADOW; // Mountain meadow biome + public const GROVE = VanillaBiomeIds::GROVE; // Grove biome (snowy) + public const SNOWY_SLOPES = VanillaBiomeIds::SNOWY_SLOPES; // Snowy mountain slopes + public const FROZEN_PEAKS = VanillaBiomeIds::FROZEN_PEAKS; // Frozen mountain peaks + public const JAGGED_PEAKS = VanillaBiomeIds::JAGGED_PEAKS; // Jagged mountain peaks + public const STONY_PEAKS = VanillaBiomeIds::STONY_PEAKS; // Stony mountain peaks } \ No newline at end of file diff --git a/src/muqsit/vanillagenerator/generator/overworld/decorator/CactusDecorator.php b/src/muqsit/vanillagenerator/generator/overworld/decorator/CactusDecorator.php index ad21019..9c09814 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/decorator/CactusDecorator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/decorator/CactusDecorator.php @@ -6,6 +6,7 @@ use muqsit\vanillagenerator\generator\Decorator; use muqsit\vanillagenerator\generator\object\Cactus; +use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use pocketmine\utils\Random; use pocketmine\world\ChunkManager; use pocketmine\world\format\Chunk; @@ -17,7 +18,7 @@ public function decorate(ChunkManager $world, Random $random, int $chunk_x, int $source_z = $chunk_z << Chunk::COORD_BIT_SIZE; $x = $random->nextBoundedInt(16); $z = $random->nextBoundedInt(16); - $sourceY = $random->nextBoundedInt($chunk->getHighestBlockAt($x, $z) << 1); + $sourceY = DecoratorUtils::getPlantY($random, $chunk, $x, $z); // Safe Y for cactus for($l = 0; $l < 10; ++$l){ $i = $source_x + $random->nextBoundedInt(8) - $random->nextBoundedInt(8); diff --git a/src/muqsit/vanillagenerator/generator/overworld/decorator/DeadBushDecorator.php b/src/muqsit/vanillagenerator/generator/overworld/decorator/DeadBushDecorator.php index 94d529e..9093071 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/decorator/DeadBushDecorator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/decorator/DeadBushDecorator.php @@ -5,6 +5,7 @@ namespace muqsit\vanillagenerator\generator\overworld\decorator; use muqsit\vanillagenerator\generator\Decorator; +use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use pocketmine\block\BlockTypeIds; use pocketmine\block\Leaves; use pocketmine\block\VanillaBlocks; @@ -19,7 +20,7 @@ class DeadBushDecorator extends Decorator{ public function decorate(ChunkManager $world, Random $random, int $chunk_x, int $chunk_z, Chunk $chunk) : void{ $source_x = ($chunk_x << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); $source_z = ($chunk_z << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); - $source_y = $random->nextBoundedInt($chunk->getHighestBlockAt($source_x & Chunk::COORD_MASK, $source_z & Chunk::COORD_MASK) << 1); + $source_y = DecoratorUtils::getPlantY($random, $chunk, $source_x, $source_z); // Safe Y for dead bush while($source_y > 0 && ($world->getBlockAt($source_x, $source_y, $source_z)->getTypeId() === BlockTypeIds::AIR || $world->getBlockAt($source_x, $source_y, $source_z) instanceof Leaves)){ diff --git a/src/muqsit/vanillagenerator/generator/overworld/decorator/DecoratorUtils.php b/src/muqsit/vanillagenerator/generator/overworld/decorator/DecoratorUtils.php new file mode 100644 index 0000000..0dad5a4 --- /dev/null +++ b/src/muqsit/vanillagenerator/generator/overworld/decorator/DecoratorUtils.php @@ -0,0 +1,53 @@ +getHighestBlockAt($x & Chunk::COORD_MASK, $z & Chunk::COORD_MASK); + if($highest_block <= 0){ + return 0; + } + + // Limit surface generation to reasonable heights for 1.18+ + $safe_max = min($highest_block + $surface_offset, $max_y_limit); + return $random->nextBoundedInt($safe_max); + } + + /** + * Get Y coordinate for plant generation near ground level + * + * @param Random $random + * @param Chunk $chunk + * @param int $x + * @param int $z + * @return int Safe Y coordinate for plant generation + */ + public static function getPlantY(Random $random, Chunk $chunk, int $x, int $z) : int{ + $highest_block = $chunk->getHighestBlockAt($x & Chunk::COORD_MASK, $z & Chunk::COORD_MASK); + if($highest_block <= 0){ + return 0; + } + + // Plants should generate close to surface level, max Y=120 for 1.18+ + $plant_max = min($highest_block + 16, 120); + return $random->nextBoundedInt($plant_max); + } +} \ No newline at end of file diff --git a/src/muqsit/vanillagenerator/generator/overworld/decorator/DoublePlantDecorator.php b/src/muqsit/vanillagenerator/generator/overworld/decorator/DoublePlantDecorator.php index 9688327..4d62130 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/decorator/DoublePlantDecorator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/decorator/DoublePlantDecorator.php @@ -6,6 +6,7 @@ use muqsit\vanillagenerator\generator\Decorator; use muqsit\vanillagenerator\generator\object\DoubleTallPlant; +use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use muqsit\vanillagenerator\generator\overworld\decorator\types\DoublePlantDecoration; use pocketmine\block\DoublePlant; use pocketmine\utils\Random; @@ -44,7 +45,7 @@ final public function setDoublePlants(DoublePlantDecoration ...$doublePlants) : public function decorate(ChunkManager $world, Random $random, int $chunk_x, int $chunk_z, Chunk $chunk) : void{ $x = $random->nextBoundedInt(16); $z = $random->nextBoundedInt(16); - $source_y = $random->nextBoundedInt($chunk->getHighestBlockAt($x, $z) + 32); + $source_y = DecoratorUtils::getPlantY($random, $chunk, $x, $z); // Safe Y for double plants $species = self::getRandomDoublePlant($random, $this->doublePlants); (new DoubleTallPlant($species))->generate($world, $random, ($chunk_x << Chunk::COORD_BIT_SIZE) + $x, $source_y, ($chunk_z << Chunk::COORD_BIT_SIZE) + $z); diff --git a/src/muqsit/vanillagenerator/generator/overworld/decorator/FlowerDecorator.php b/src/muqsit/vanillagenerator/generator/overworld/decorator/FlowerDecorator.php index d57ed43..31c5025 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/decorator/FlowerDecorator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/decorator/FlowerDecorator.php @@ -7,6 +7,7 @@ use muqsit\vanillagenerator\generator\Decorator; use muqsit\vanillagenerator\generator\object\Flower; use muqsit\vanillagenerator\generator\overworld\decorator\types\FlowerDecoration; +use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use pocketmine\block\Block; use pocketmine\utils\Random; use pocketmine\world\ChunkManager; @@ -48,7 +49,7 @@ final public function setFlowers(FlowerDecoration ...$flowers) : void{ public function decorate(ChunkManager $world, Random $random, int $chunk_x, int $chunk_z, Chunk $chunk) : void{ $x = $random->nextBoundedInt(16); $z = $random->nextBoundedInt(16); - $source_y = $random->nextBoundedInt($chunk->getHighestBlockAt($x & Chunk::COORD_MASK, $z & Chunk::COORD_MASK) + 32); + $source_y = DecoratorUtils::getSurfaceY($random, $chunk, $x, $z, 16, 120); // Limited to Y=120 for flowers // the flower can change on each decoration pass $flower = self::getRandomFlower($random, $this->flowers); diff --git a/src/muqsit/vanillagenerator/generator/overworld/decorator/LakeDecorator.php b/src/muqsit/vanillagenerator/generator/overworld/decorator/LakeDecorator.php index 23250ec..6a53903 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/decorator/LakeDecorator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/decorator/LakeDecorator.php @@ -24,7 +24,12 @@ public function decorate(ChunkManager $world, Random $random, int $chunk_x, int if($random->nextBoundedInt($this->rarity) === 0){ $source_x = ($chunk_x << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); $source_z = ($chunk_z << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); - $source_y = $random->nextBoundedInt($world->getMaxY() - $this->base_offset) + $this->base_offset; + + // Limit lake generation to reasonable heights for 1.18+ (Y=0 to Y=120) + // This prevents lakes from generating at extreme heights like Y=300+ + $max_lake_y = min(120, $world->getMaxY() - 20); // Cap at Y=120 or world max - 20 + $source_y = $random->nextBoundedInt($max_lake_y - $this->base_offset) + $this->base_offset; + if($this->type->getTypeId() === BlockTypeIds::LAVA && ($source_y >= 64 || $random->nextBoundedInt(10) > 0)){ return; } diff --git a/src/muqsit/vanillagenerator/generator/overworld/decorator/MushroomDecorator.php b/src/muqsit/vanillagenerator/generator/overworld/decorator/MushroomDecorator.php index d22b35e..ef3e18b 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/decorator/MushroomDecorator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/decorator/MushroomDecorator.php @@ -5,6 +5,7 @@ namespace muqsit\vanillagenerator\generator\overworld\decorator; use muqsit\vanillagenerator\generator\Decorator; +use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use pocketmine\block\Block; use pocketmine\block\BlockTypeIds; use pocketmine\block\Dirt; @@ -37,7 +38,7 @@ public function decorate(ChunkManager $world, Random $random, int $chunk_x, int $source_x = ($chunk_x << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); $source_z = ($chunk_z << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); $source_y = $chunk->getHighestBlockAt($source_x & Chunk::COORD_MASK, $source_z & Chunk::COORD_MASK); - $source_y = $this->fixed_height_range ? $source_y : $random->nextBoundedInt($source_y << 1); + $source_y = $this->fixed_height_range ? $source_y : DecoratorUtils::getPlantY($random, $chunk, $source_x, $source_z); $height = $world->getMaxY(); for($i = 0; $i < 64; ++$i){ diff --git a/src/muqsit/vanillagenerator/generator/overworld/decorator/PumpkinDecorator.php b/src/muqsit/vanillagenerator/generator/overworld/decorator/PumpkinDecorator.php index 8c43dcc..bdb0d5d 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/decorator/PumpkinDecorator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/decorator/PumpkinDecorator.php @@ -5,6 +5,7 @@ namespace muqsit\vanillagenerator\generator\overworld\decorator; use muqsit\vanillagenerator\generator\Decorator; +use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use pocketmine\block\BlockTypeIds; use pocketmine\block\VanillaBlocks; use pocketmine\math\Facing; @@ -20,7 +21,7 @@ public function decorate(ChunkManager $world, Random $random, int $chunk_x, int if($random->nextBoundedInt(32) === 0){ $source_x = ($chunk_x << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); $source_z = ($chunk_z << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); - $source_y = $random->nextBoundedInt($chunk->getHighestBlockAt($source_x & Chunk::COORD_MASK, $source_z & Chunk::COORD_MASK) << 1); + $source_y = DecoratorUtils::getPlantY($random, $chunk, $source_x, $source_z); // Safe Y for pumpkins for($i = 0; $i < 64; ++$i){ $x = $source_x + $random->nextBoundedInt(8) - $random->nextBoundedInt(8); $z = $source_z + $random->nextBoundedInt(8) - $random->nextBoundedInt(8); diff --git a/src/muqsit/vanillagenerator/generator/overworld/decorator/SugarCaneDecorator.php b/src/muqsit/vanillagenerator/generator/overworld/decorator/SugarCaneDecorator.php index ddfa6c0..63c81a8 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/decorator/SugarCaneDecorator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/decorator/SugarCaneDecorator.php @@ -6,6 +6,7 @@ use muqsit\vanillagenerator\generator\Decorator; use muqsit\vanillagenerator\generator\object\SugarCane; +use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use pocketmine\utils\Random; use pocketmine\world\ChunkManager; use pocketmine\world\format\Chunk; @@ -19,7 +20,7 @@ public function decorate(ChunkManager $world, Random $random, int $chunk_x, int if($max_y <= 0){ return; } - $source_y = $random->nextBoundedInt($max_y << 1); + $source_y = DecoratorUtils::getPlantY($random, $chunk, $source_x, $source_z); // Safe Y for sugar cane for($j = 0; $j < 20; ++$j){ $x = $source_x + $random->nextBoundedInt(4) - $random->nextBoundedInt(4); $z = $source_z + $random->nextBoundedInt(4) - $random->nextBoundedInt(4); diff --git a/src/muqsit/vanillagenerator/generator/overworld/decorator/TallGrassDecorator.php b/src/muqsit/vanillagenerator/generator/overworld/decorator/TallGrassDecorator.php index 184adc0..7ce64aa 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/decorator/TallGrassDecorator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/decorator/TallGrassDecorator.php @@ -6,6 +6,7 @@ use muqsit\vanillagenerator\generator\Decorator; use muqsit\vanillagenerator\generator\object\TallGrass; +use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use pocketmine\block\VanillaBlocks; use pocketmine\utils\Random; use pocketmine\world\ChunkManager; @@ -28,7 +29,7 @@ public function decorate(ChunkManager $world, Random $random, int $chunk_x, int return; } - $source_y = $random->nextBoundedInt(abs($top_block << 1)); + $source_y = DecoratorUtils::getPlantY($random, $chunk, $x, $z); // Safe Y generation for plants // the grass species can change on each decoration pass (new TallGrass($this->fern_density > 0 && $random->nextFloat() < $this->fern_density ? diff --git a/src/muqsit/vanillagenerator/generator/overworld/decorator/WaterLilyDecorator.php b/src/muqsit/vanillagenerator/generator/overworld/decorator/WaterLilyDecorator.php index 7b1db9c..ff4c796 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/decorator/WaterLilyDecorator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/decorator/WaterLilyDecorator.php @@ -5,6 +5,7 @@ namespace muqsit\vanillagenerator\generator\overworld\decorator; use muqsit\vanillagenerator\generator\Decorator; +use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use pocketmine\block\BlockTypeIds; use pocketmine\block\VanillaBlocks; use pocketmine\block\Water; @@ -18,7 +19,7 @@ class WaterLilyDecorator extends Decorator{ public function decorate(ChunkManager $world, Random $random, int $chunk_x, int $chunk_z, Chunk $chunk) : void{ $source_x = ($chunk_x << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); $source_z = ($chunk_z << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); - $source_y = $random->nextBoundedInt($chunk->getHighestBlockAt($source_x & Chunk::COORD_MASK, $source_z & Chunk::COORD_MASK) << 1); + $source_y = DecoratorUtils::getPlantY($random, $chunk, $source_x, $source_z); // Safe Y for water lilies while($world->getBlockAt($source_x, $source_y - 1, $source_z)->getTypeId() === BlockTypeIds::AIR && $source_y > 0){ --$source_y; } diff --git a/src/muqsit/vanillagenerator/generator/overworld/populator/OverworldPopulator.php b/src/muqsit/vanillagenerator/generator/overworld/populator/OverworldPopulator.php index d301c3b..8282093 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/populator/OverworldPopulator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/populator/OverworldPopulator.php @@ -72,7 +72,9 @@ public function __construct(){ } public function populate(ChunkManager $world, Random $random, int $chunk_x, int $chunk_z, Chunk $chunk) : void{ - $biome = $chunk->getBiomeId(8, 8, 8); + // Sample surface biome (around sea level Y=63) for population decisions + // This ensures we use the intended surface biome, not underground biomes + $biome = $chunk->getBiomeId(8, 63, 8); if(array_key_exists($biome, $this->biome_populators)){ $this->biome_populators[$biome]->populate($world, $random, $chunk_x, $chunk_z, $chunk); } diff --git a/src/muqsit/vanillagenerator/generator/overworld/populator/biome/FlowerForestPopulator.php b/src/muqsit/vanillagenerator/generator/overworld/populator/biome/FlowerForestPopulator.php index 3fdeb89..9d66017 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/populator/biome/FlowerForestPopulator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/populator/biome/FlowerForestPopulator.php @@ -8,6 +8,7 @@ use muqsit\vanillagenerator\generator\noise\glowstone\SimplexOctaveGenerator; use muqsit\vanillagenerator\generator\object\Flower; use muqsit\vanillagenerator\generator\overworld\biome\BiomeIds; +use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use pocketmine\block\Block; use pocketmine\block\VanillaBlocks; use pocketmine\utils\Random; @@ -58,7 +59,7 @@ public function populateOnGround(ChunkManager $world, Random $random, int $chunk for($i = 0; $i < 100; ++$i){ $x = $random->nextBoundedInt(16); $z = $random->nextBoundedInt(16); - $y = $random->nextBoundedInt($chunk->getHighestBlockAt($x, $z) + 32); + $y = DecoratorUtils::getPlantY($random, $chunk, $x, $z); $noise = ($this->noise_gen->noise($x, $z, 0.5, 0, 2.0, false) + 1.0) / 2.0; $noise = $noise < 0 ? 0 : ($noise > 0.9999 ? 0.9999 : $noise); $flower = self::$FOREST_FLOWERS[(int) ($noise * count(self::$FOREST_FLOWERS))]; diff --git a/src/muqsit/vanillagenerator/generator/overworld/populator/biome/ForestPopulator.php b/src/muqsit/vanillagenerator/generator/overworld/populator/biome/ForestPopulator.php index 3940cd5..ebd1ceb 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/populator/biome/ForestPopulator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/populator/biome/ForestPopulator.php @@ -8,6 +8,7 @@ use muqsit\vanillagenerator\generator\object\tree\BirchTree; use muqsit\vanillagenerator\generator\object\tree\GenericTree; use muqsit\vanillagenerator\generator\overworld\biome\BiomeIds; +use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use muqsit\vanillagenerator\generator\overworld\decorator\types\TreeDecoration; use pocketmine\block\DoublePlant; use pocketmine\block\VanillaBlocks; @@ -63,7 +64,7 @@ public function populateOnGround(ChunkManager $world, Random $random, int $chunk for($j = 0; $j < 5; ++$j, ++$i){ $x = $random->nextBoundedInt(16); $z = $random->nextBoundedInt(16); - $y = $random->nextBoundedInt($chunk->getHighestBlockAt($x, $z) + 32); + $y = DecoratorUtils::getPlantY($random, $chunk, $x, $z); $species = self::$DOUBLE_PLANTS[$random->nextBoundedInt(count(self::$DOUBLE_PLANTS))]; if((new DoubleTallPlant($species))->generate($world, $random, $source_x + $x, $y, $source_z + $z)){ ++$i; diff --git a/src/muqsit/vanillagenerator/generator/overworld/populator/biome/OrePopulator.php b/src/muqsit/vanillagenerator/generator/overworld/populator/biome/OrePopulator.php index 8d6eef1..6b522fb 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/populator/biome/OrePopulator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/populator/biome/OrePopulator.php @@ -20,20 +20,25 @@ class OrePopulator implements Populator{ /** * Creates a populator for dirt, gravel, andesite, diorite, granite; and coal, iron, gold, - * redstone, diamond and lapis lazuli ores. + * redstone, diamond and lapis lazuli ores with full 1.18+ distribution. */ public function __construct(){ - $this->addOre(new OreType(VanillaBlocks::DIRT(), 0, 256, 32), 10); - $this->addOre(new OreType(VanillaBlocks::GRAVEL(), 0, 256, 32), 8); - $this->addOre(new OreType(VanillaBlocks::GRANITE(), 0, 80, 32), 10); - $this->addOre(new OreType(VanillaBlocks::DIORITE(), 0, 80, 32), 10); - $this->addOre(new OreType(VanillaBlocks::ANDESITE(), 0, 80, 32), 10); - $this->addOre(new OreType(VanillaBlocks::COAL_ORE(), 0, 128, 16), 20); - $this->addOre(new OreType(VanillaBlocks::IRON_ORE(), 0, 64, 8), 20); - $this->addOre(new OreType(VanillaBlocks::GOLD_ORE(), 0, 32, 8), 2); - $this->addOre(new OreType(VanillaBlocks::REDSTONE_ORE(), 0, 16, 7), 8); - $this->addOre(new OreType(VanillaBlocks::DIAMOND_ORE(), 0, 16, 7), 1); - $this->addOre(new OreType(VanillaBlocks::LAPIS_LAZULI_ORE(), 16, 16, 6), 1); + // Full 1.18+ ore distributions for world height Y=-64 to Y=319 + + // Stone variants - distribute throughout most of the world + $this->addOre(new OreType(VanillaBlocks::DIRT(), -64, 319, 32), 10); + $this->addOre(new OreType(VanillaBlocks::GRAVEL(), -64, 319, 32), 8); + $this->addOre(new OreType(VanillaBlocks::GRANITE(), -64, 64, 32), 10); + $this->addOre(new OreType(VanillaBlocks::DIORITE(), -64, 64, 32), 10); + $this->addOre(new OreType(VanillaBlocks::ANDESITE(), -64, 64, 32), 10); + + // Ores with authentic 1.18+ distributions + $this->addOre(new OreType(VanillaBlocks::COAL_ORE(), 0, 190, 16), 20); // Coal: Y=0 to Y=190 + $this->addOre(new OreType(VanillaBlocks::IRON_ORE(), -63, 72, 8), 20); // Iron: Y=-63 to Y=72 + $this->addOre(new OreType(VanillaBlocks::GOLD_ORE(), -64, -48, 8), 4); // Gold: Deep underground Y=-64 to Y=-48 + $this->addOre(new OreType(VanillaBlocks::REDSTONE_ORE(), -64, 15, 7), 8); // Redstone: Y=-64 to Y=15 + $this->addOre(new OreType(VanillaBlocks::DIAMOND_ORE(), -64, 16, 7), 1); // Diamond: Y=-64 to Y=16 (peak at Y=-59) + $this->addOre(new OreType(VanillaBlocks::LAPIS_LAZULI_ORE(), -32, 32, 6), 1); // Lapis: Y=-32 to Y=32 } protected function addOre(OreType $type, int $value) : void{ diff --git a/src/muqsit/vanillagenerator/generator/overworld/populator/biome/PlainsPopulator.php b/src/muqsit/vanillagenerator/generator/overworld/populator/biome/PlainsPopulator.php index 2ea0ea6..16a7a7f 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/populator/biome/PlainsPopulator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/populator/biome/PlainsPopulator.php @@ -10,6 +10,7 @@ use muqsit\vanillagenerator\generator\object\Flower; use muqsit\vanillagenerator\generator\object\TallGrass; use muqsit\vanillagenerator\generator\overworld\biome\BiomeIds; +use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use pocketmine\block\Block; use pocketmine\block\VanillaBlocks; use pocketmine\utils\Random; @@ -70,7 +71,7 @@ public function populateOnGround(ChunkManager $world, Random $random, int $chunk for($i = 0; $i < 7; ++$i){ $x = $random->nextBoundedInt(16); $z = $random->nextBoundedInt(16); - $y = $random->nextBoundedInt($chunk->getHighestBlockAt($x, $z) + 32); + $y = DecoratorUtils::getSurfaceY($random, $chunk, $x, $z); (new DoubleTallPlant(VanillaBlocks::DOUBLE_TALLGRASS()))->generate($world, $random, $source_x + $x, $y, $source_z + $z); } } @@ -84,14 +85,14 @@ public function populateOnGround(ChunkManager $world, Random $random, int $chunk for($i = 0; $i < $flower_amount; ++$i){ $x = $random->nextBoundedInt(16); $z = $random->nextBoundedInt(16); - $y = $random->nextBoundedInt($chunk->getHighestBlockAt($x, $z) + 32); + $y = DecoratorUtils::getPlantY($random, $chunk, $x, $z); (new Flower($flower))->generate($world, $random, $source_x + $x, $y, $source_z + $z); } for($i = 0; $i < $tall_grass_amount; ++$i){ $x = $random->nextBoundedInt(16); $z = $random->nextBoundedInt(16); - $y = $random->nextBoundedInt($chunk->getHighestBlockAt($x, $z) << 1); + $y = DecoratorUtils::getPlantY($random, $chunk, $x, $z); (new TallGrass(VanillaBlocks::TALL_GRASS()))->generate($world, $random, $source_x + $x, $y, $source_z + $z); } From 954643303a8b3d221c5ef18a3179a2322549238a Mon Sep 17 00:00:00 2001 From: JeanTKG <102908437+jeantkg@users.noreply.github.com> Date: Fri, 10 Oct 2025 20:37:55 +0300 Subject: [PATCH 2/3] remove alot of useless AI stuff --- src/muqsit/vanillagenerator/Loader.php | 2 + .../generator/VanillaBiomeGrid.php | 9 +- .../generator/VanillaGenerator.php | 6 +- .../generator/biomegrid/ErosionMapLayer.php | 2 - .../generator/biomegrid/NoiseMapLayer.php | 8 +- .../generator/cave/CaveGenerator.php | 224 +++--------------- .../generator/ground/GroundGenerator.php | 34 ++- .../generator/ground/MesaGroundGenerator.php | 2 +- .../generator/object/DoubleTallPlant.php | 5 - .../generator/object/Lake.php | 46 +--- .../generator/object/OreVein.php | 11 +- .../generator/object/tree/MegaPineTree.php | 17 +- .../overworld/OverworldGenerator.php | 69 +++--- .../overworld/biome/Biome3DGenerator.php | 38 +-- .../overworld/biome/BiomeHeightManager.php | 23 +- .../generator/overworld/biome/BiomeIds.php | 22 +- .../overworld/decorator/CactusDecorator.php | 3 +- .../overworld/decorator/DeadBushDecorator.php | 3 +- .../decorator/DoublePlantDecorator.php | 3 +- .../overworld/decorator/FlowerDecorator.php | 3 +- .../overworld/decorator/LakeDecorator.php | 7 +- .../overworld/decorator/MushroomDecorator.php | 3 +- .../overworld/decorator/PumpkinDecorator.php | 3 +- .../decorator/SugarCaneDecorator.php | 3 +- .../decorator/TallGrassDecorator.php | 3 +- .../decorator/WaterLilyDecorator.php | 3 +- .../populator/biome/BiomePopulator.php | 5 - .../populator/biome/FlowerForestPopulator.php | 3 +- .../populator/biome/ForestPopulator.php | 3 +- .../populator/biome/OrePopulator.php | 48 ++-- .../populator/biome/PlainsPopulator.php | 7 +- .../generator/test/TestGenerator.php | 67 ++++++ 32 files changed, 292 insertions(+), 393 deletions(-) create mode 100644 src/muqsit/vanillagenerator/generator/test/TestGenerator.php diff --git a/src/muqsit/vanillagenerator/Loader.php b/src/muqsit/vanillagenerator/Loader.php index 527f691..64c1df9 100644 --- a/src/muqsit/vanillagenerator/Loader.php +++ b/src/muqsit/vanillagenerator/Loader.php @@ -6,6 +6,7 @@ use muqsit\vanillagenerator\generator\nether\NetherGenerator; use muqsit\vanillagenerator\generator\overworld\OverworldGenerator; +use muqsit\vanillagenerator\generator\test\TestGenerator; use pocketmine\plugin\PluginBase; use pocketmine\world\generator\GeneratorManager; @@ -15,5 +16,6 @@ public function onLoad() : void{ $generator_manager = GeneratorManager::getInstance(); $generator_manager->addGenerator(NetherGenerator::class, "vanilla_nether", fn() => null); $generator_manager->addGenerator(OverworldGenerator::class, "vanilla_overworld", fn() => null); + $generator_manager->addGenerator(TestGenerator::class, "test", fn() => null); } } diff --git a/src/muqsit/vanillagenerator/generator/VanillaBiomeGrid.php b/src/muqsit/vanillagenerator/generator/VanillaBiomeGrid.php index bda8630..4002588 100644 --- a/src/muqsit/vanillagenerator/generator/VanillaBiomeGrid.php +++ b/src/muqsit/vanillagenerator/generator/VanillaBiomeGrid.php @@ -16,12 +16,12 @@ class VanillaBiomeGrid implements BiomeGrid{ /** @var int[] */ private array $biomes_3d = []; - public function getBiome(int $x, int $z) : ?int{ + public function getBiome(int $x, int $z): ?int { // upcasting is very important to get extended biomes return array_key_exists($hash = $x | $z << Chunk::COORD_BIT_SIZE, $this->biomes) ? $this->biomes[$hash] & 0xFF : null; } - public function getBiome3D(int $x, int $y, int $z) : ?int{ + public function getBiome3D(int $x, int $y, int $z): ?int { // For 3D biomes, we use a different hash that includes Y coordinate // We sample biomes every 4 blocks in Y (like 1.18+) to save memory $sample_y = $y >> 2; // Sample every 4 blocks in Y direction @@ -35,12 +35,11 @@ public function getBiome3D(int $x, int $y, int $z) : ?int{ return $this->getBiome($x, $z); } - public function setBiome(int $x, int $z, int $biome_id) : void{ + public function setBiome(int $x, int $z, int $biome_id): void { $this->biomes[$x | $z << Chunk::COORD_BIT_SIZE] = $biome_id; } - public function setBiome3D(int $x, int $y, int $z, int $biome_id) : void{ - // Sample biomes every 4 blocks in Y (like 1.18+) + public function setBiome3D(int $x, int $y, int $z, int $biome_id): void { $sample_y = $y >> 2; $hash = ($x) | ($z << 4) | ($sample_y << 8); $this->biomes_3d[$hash] = $biome_id; diff --git a/src/muqsit/vanillagenerator/generator/VanillaGenerator.php b/src/muqsit/vanillagenerator/generator/VanillaGenerator.php index c20b21c..26579bf 100644 --- a/src/muqsit/vanillagenerator/generator/VanillaGenerator.php +++ b/src/muqsit/vanillagenerator/generator/VanillaGenerator.php @@ -97,13 +97,11 @@ public function populateChunk(ChunkManager $world, int $chunk_x, int $chunk_z) : } } - public function getMaxY() : int{ - // 1.18+ world height: Y=319 (subchunk 19 × 16 + 15) + public function getMaxY(): int { return 319; } - public function getMinY() : int{ - // 1.18+ world height: Y=-64 (subchunk -4 × 16) + public function getMinY(): int { return -64; } } \ No newline at end of file diff --git a/src/muqsit/vanillagenerator/generator/biomegrid/ErosionMapLayer.php b/src/muqsit/vanillagenerator/generator/biomegrid/ErosionMapLayer.php index d0b0ec7..11d2425 100644 --- a/src/muqsit/vanillagenerator/generator/biomegrid/ErosionMapLayer.php +++ b/src/muqsit/vanillagenerator/generator/biomegrid/ErosionMapLayer.php @@ -42,9 +42,7 @@ public function generateValues(int $x, int $z, int $size_x, int $size_z) : array $this->setCoordsSeed($x + $j, $z + $i); $final_values[$j + $i * $size_x] = match(true){ - // Reduced erosion rate for 1.18+ style (1/8 instead of 4/5) to preserve more land $center_val !== 0 && ($upper_left_val === 0 || $upper_right_val === 0 || $lower_left_val === 0 || $lower_right_val === 0) => $this->nextInt(8) === 0 ? 0 : $center_val, - // Increased land expansion rate (1/2 instead of 1/3) to create more land from ocean $center_val === 0 && ($upper_left_val !== 0 || $upper_right_val !== 0 || $lower_left_val !== 0 || $lower_right_val !== 0) => $this->nextInt(2) === 0 ? $upper_left_val : 0, default => $center_val }; diff --git a/src/muqsit/vanillagenerator/generator/biomegrid/NoiseMapLayer.php b/src/muqsit/vanillagenerator/generator/biomegrid/NoiseMapLayer.php index d462958..73cab38 100644 --- a/src/muqsit/vanillagenerator/generator/biomegrid/NoiseMapLayer.php +++ b/src/muqsit/vanillagenerator/generator/biomegrid/NoiseMapLayer.php @@ -13,7 +13,6 @@ class NoiseMapLayer extends MapLayer{ public function __construct(int $seed){ parent::__construct($seed); - // Adjusted noise for 1.18+ style: more octaves for better continental structure $this->noise_gen = new SimplexOctaveGenerator(new Random($seed), 3); } @@ -21,15 +20,12 @@ public function generateValues(int $x, int $z, int $size_x, int $size_z) : array $values = []; for($i = 0; $i < $size_z; ++$i){ for($j = 0; $j < $size_x; ++$j){ - // Updated for 1.18+ style generation with less ocean coverage - // Improved noise sampling for better continental structure $noise = $this->noise_gen->octaveNoise($x + $j, $z + $i, 0, 0.2, 0.75, true) * 3.5; $val = 0; - if($noise >= -0.1){ // Much lower ocean threshold (was 0.05) - $val = $noise <= 0.15 ? 3 : 2; // Adjusted land threshold (was 0.2) + if($noise >= -0.1){ + $val = $noise <= 0.15 ? 3 : 2; }else{ $this->setCoordsSeed($x + $j, $z + $i); - // Reduced ocean probability for remaining areas (30% chance instead of 50%) $val = $this->nextInt(10) < 3 ? 0 : 3; } $values[$j + $i * $size_x] = $val; diff --git a/src/muqsit/vanillagenerator/generator/cave/CaveGenerator.php b/src/muqsit/vanillagenerator/generator/cave/CaveGenerator.php index 927c1e1..e19c29b 100644 --- a/src/muqsit/vanillagenerator/generator/cave/CaveGenerator.php +++ b/src/muqsit/vanillagenerator/generator/cave/CaveGenerator.php @@ -10,30 +10,19 @@ use muqsit\vanillagenerator\generator\noise\glowstone\SimplexOctaveGenerator; use pocketmine\world\format\Chunk; -/** - * Natural cave generator that mimics Minecraft's organic cave systems - * Creates winding, natural-looking tunnels and caverns - */ class CaveGenerator { - - // More natural cave generation parameters - private const CAVE_FREQUENCY = 0.08; // 8% chance - more realistic frequency - private const CAVERN_FREQUENCY = 0.03; // 3% chance for large caverns - private const TUNNEL_LENGTH_MIN = 40; // Longer, more natural tunnels + + private const CAVE_FREQUENCY = 0.08; + private const CAVERN_FREQUENCY = 0.03; + private const TUNNEL_LENGTH_MIN = 40; private const TUNNEL_LENGTH_MAX = 120; private const CAVERN_SIZE_MIN = 6; private const CAVERN_SIZE_MAX = 15; - // Y-level constraints - 1.18+ world height support - private const MIN_CAVE_Y = -55; // Caves can go much deeper now - private const MAX_CAVE_Y = 50; // Caves can still reach near surface - private const SURFACE_BUFFER = 10; - - // Aquifer settings - 1.18+ world height support - private const AQUIFER_BASE = 0; // Water level around Y=0 (new sea level area) - private const AQUIFER_VARIANCE = 6; // Variance range (Y=-6 to Y=6) - private const LAVA_LEVEL = -54; // Lava appears in deep caves (below Y=-54) - private const WATER_FILL_CHANCE = 0.12; // 12% of chunks get water aquifers + private const MIN_CAVE_Y = -59; + private const MAX_CAVE_Y = 50; + + private const LAVA_LEVEL = -54; private Random $random; private SimplexOctaveGenerator $aquiferNoise; @@ -41,7 +30,6 @@ class CaveGenerator { public function __construct(int $seed) { $this->random = new Random($seed); - // 2D noise for aquifer levels $this->aquiferNoise = SimplexOctaveGenerator::fromRandomAndOctaves(new Random($seed + 3), 2, 16, 1, 16); $this->aquiferNoise->setScale(1.0 / 32.0); } @@ -53,7 +41,6 @@ public function carveDirectly(ChunkManager $world, int $chunkX, int $chunkZ): vo $chunk = $world->getChunk($chunkX, $chunkZ); if ($chunk === null) return; - // Use regional seeding for consistent cave generation across chunks $this->generateRegionalCaves($world, $chunkX, $chunkZ); } @@ -63,21 +50,17 @@ public function carveDirectly(ChunkManager $world, int $chunkX, int $chunkZ): vo private function generateRegionalCaves(ChunkManager $world, int $chunkX, int $chunkZ): void { $carvedBlocks = 0; - // Check a 3x3 region of chunks centered on current chunk for cave generation for ($regionX = $chunkX - 1; $regionX <= $chunkX + 1; $regionX++) { for ($regionZ = $chunkZ - 1; $regionZ <= $chunkZ + 1; $regionZ++) { - // Seed based on the region chunk for consistent generation $this->random->setSeed($regionX * 341873128712 + $regionZ * 132897987541); - // Generate caves that might flow into our target chunk - $caveAttempts = 6 + $this->random->nextBoundedInt(8); // 6-13 attempts per region chunk + $caveAttempts = 6 + $this->random->nextBoundedInt(8); for ($i = 0; $i < $caveAttempts; $i++) { if ($this->random->nextFloat() < self::CAVE_FREQUENCY) { $regionBaseX = $regionX << 4; $regionBaseZ = $regionZ << 4; - // Start somewhere in the region chunk $startX = $regionBaseX + $this->random->nextBoundedInt(16); $startY = self::MIN_CAVE_Y + $this->random->nextBoundedInt(self::MAX_CAVE_Y - self::MIN_CAVE_Y); $startZ = $regionBaseZ + $this->random->nextBoundedInt(16); @@ -86,7 +69,6 @@ private function generateRegionalCaves(ChunkManager $world, int $chunkX, int $ch } } - // Generate occasional large caverns if ($this->random->nextFloat() < self::CAVERN_FREQUENCY) { $regionBaseX = $regionX << 4; $regionBaseZ = $regionZ << 4; @@ -99,10 +81,6 @@ private function generateRegionalCaves(ChunkManager $world, int $chunkX, int $ch } } } - - // if ($carvedBlocks > 0) { - // error_log("CaveGenerator: Carved $carvedBlocks blocks in chunk $chunkX, $chunkZ"); - // } } /** @@ -115,30 +93,24 @@ private function generateNaturalCaveSystem(ChunkManager $world, int $startX, int $y = (float)$startY; $z = (float)$startZ; - // Start with random direction $yaw = $this->random->nextFloat() * M_PI * 2.0; $pitch = ($this->random->nextFloat() - 0.5) * 0.4; $carvedBlocks = 0; for ($i = 0; $i < $length; $i++) { - // Natural radius variation - more organic shape $progress = $i / (float)$length; - $baseRadius = 1.5 + $this->random->nextFloat() * 2.0; // 1.5-3.5 base radius + $baseRadius = 1.5 + $this->random->nextFloat() * 2.0; - // Add natural variation using sine wave and random factors $sizeVariation = sin($progress * M_PI * 3) * 0.5 + ($this->random->nextFloat() - 0.5) * 0.8; $radius = max(0.8, $baseRadius + $sizeVariation); - // Carve current position $carvedBlocks += $this->carveSphere($world, (int)round($x), (int)round($y), (int)round($z), $radius, $targetChunkX, $targetChunkZ); - // Natural direction changes - more organic movement - $yawChange = ($this->random->nextFloat() - 0.5) * 0.25; // More natural turning + $yawChange = ($this->random->nextFloat() - 0.5) * 0.25; $pitchChange = ($this->random->nextFloat() - 0.5) * 0.15; - // Occasionally make sharper turns for more interesting caves - if ($this->random->nextFloat() < 0.05) { // 5% chance for sharp turn + if ($this->random->nextFloat() < 0.05) { $yawChange = ($this->random->nextFloat() - 0.5) * 0.8; $pitchChange = ($this->random->nextFloat() - 0.5) * 0.4; } @@ -146,24 +118,20 @@ private function generateNaturalCaveSystem(ChunkManager $world, int $startX, int $yaw += $yawChange; $pitch += $pitchChange; - // Keep pitch reasonable but allow more variation than before $pitch = max(-0.6, min(0.6, $pitch)); - // Natural movement - variable speed - $moveSpeed = 0.7 + $this->random->nextFloat() * 0.6; // 0.7-1.3 speed variation + $moveSpeed = 0.7 + $this->random->nextFloat() * 0.6; $x += cos($yaw) * cos($pitch) * $moveSpeed; $y += sin($pitch) * $moveSpeed; $z += sin($yaw) * cos($pitch) * $moveSpeed; - // Natural branching - less frequent but more organic - if ($this->random->nextFloat() < 0.04 && $i > 15) { // 4% chance after 15 blocks + if ($this->random->nextFloat() < 0.04 && $i > 15) { $branchLength = 20 + $this->random->nextBoundedInt(40); $carvedBlocks += $this->generateNaturalBranch($world, $x, $y, $z, $branchLength, $targetChunkX, $targetChunkZ); } - // Very rarely, create a small chamber - if ($this->random->nextFloat() < 0.008 && $i > 20) { // 0.8% chance + if ($this->random->nextFloat() < 0.008 && $i > 20) { $carvedBlocks += $this->generateSmallChamber($world, $x, $y, $z, $targetChunkX, $targetChunkZ); } } @@ -179,26 +147,22 @@ private function generateNaturalBranch(ChunkManager $world, float $startX, float $y = $startY; $z = $startZ; - // Branch direction - split off from main tunnel naturally $yaw = $this->random->nextFloat() * M_PI * 2.0; $pitch = ($this->random->nextFloat() - 0.5) * 0.5; $carvedBlocks = 0; for ($i = 0; $i < $length; $i++) { - // Branches get smaller as they extend $progress = $i / (float)$length; $radius = 2.0 - $progress * 0.8 + ($this->random->nextFloat() - 0.5) * 0.4; // 2.0 down to ~1.2 $radius = max(0.6, $radius); $carvedBlocks += $this->carveSphere($world, (int)round($x), (int)round($y), (int)round($z), $radius, $targetChunkX, $targetChunkZ); - // Natural branch movement $yaw += ($this->random->nextFloat() - 0.5) * 0.2; $pitch += ($this->random->nextFloat() - 0.5) * 0.12; $pitch = max(-0.4, min(0.4, $pitch)); - // Variable movement speed for branches $moveSpeed = 0.6 + $this->random->nextFloat() * 0.4; $x += cos($yaw) * cos($pitch) * $moveSpeed; @@ -213,10 +177,9 @@ private function generateNaturalBranch(ChunkManager $world, float $startX, float * Generate small natural chambers */ private function generateSmallChamber(ChunkManager $world, float $centerX, float $centerY, float $centerZ, int $targetChunkX, int $targetChunkZ): int { - $size = 4 + $this->random->nextBoundedInt(6); // 4-9 block chambers + $size = 4 + $this->random->nextBoundedInt(6); $carvedBlocks = 0; - // Create irregular chamber with 2-4 overlapping spheres $sphereCount = 2 + $this->random->nextBoundedInt(3); for ($i = 0; $i < $sphereCount; $i++) { @@ -224,7 +187,7 @@ private function generateSmallChamber(ChunkManager $world, float $centerX, float $offsetY = $centerY + ($this->random->nextFloat() - 0.5) * $size * 0.3; $offsetZ = $centerZ + ($this->random->nextFloat() - 0.5) * $size * 0.5; - $radius = 2.5 + $this->random->nextFloat() * 2.0; // 2.5-4.5 radius + $radius = 2.5 + $this->random->nextFloat() * 2.0; $carvedBlocks += $this->carveSphere($world, (int)round($offsetX), (int)round($offsetY), (int)round($offsetZ), $radius, $targetChunkX, $targetChunkZ); } @@ -242,11 +205,9 @@ private function generateNaturalCavern(ChunkManager $world, int $centerX, int $c $carvedBlocks = 0; - // Create natural, irregular cavern shape $sphereCount = 4 + $this->random->nextBoundedInt(7); // 4-10 spheres for ($i = 0; $i < $sphereCount; $i++) { - // More natural sphere placement $angle = ($i / (float)$sphereCount) * M_PI * 2.0 + ($this->random->nextFloat() - 0.5) * 1.0; $distance = $this->random->nextFloat() * $sizeX * 0.4; @@ -254,13 +215,12 @@ private function generateNaturalCavern(ChunkManager $world, int $centerX, int $c $offsetY = $centerY + ($this->random->nextFloat() - 0.5) * $sizeY * 0.8; $offsetZ = $centerZ + sin($angle) * $distance; - $radius = 3.0 + $this->random->nextFloat() * 4.0; // 3.0-7.0 radius + $radius = 3.0 + $this->random->nextFloat() * 4.0; $carvedBlocks += $this->carveSphere($world, (int)round($offsetX), (int)round($offsetY), (int)round($offsetZ), $radius, $targetChunkX, $targetChunkZ); } - // Add some natural tunnels leading from the cavern - $tunnelCount = 2 + $this->random->nextBoundedInt(4); // 2-5 tunnels + $tunnelCount = 2 + $this->random->nextBoundedInt(4); for ($i = 0; $i < $tunnelCount; $i++) { $tunnelLength = 15 + $this->random->nextBoundedInt(30); $yaw = $this->random->nextFloat() * M_PI * 2.0; @@ -272,12 +232,11 @@ private function generateNaturalCavern(ChunkManager $world, int $centerX, int $c for ($j = 0; $j < $tunnelLength; $j++) { $progress = $j / (float)$tunnelLength; - $radius = 2.8 - $progress * 1.0; // Taper from 2.8 to 1.8 + $radius = 2.8 - $progress * 1.0; $radius = max(1.0, $radius); $carvedBlocks += $this->carveSphere($world, (int)round($x), (int)round($y), (int)round($z), $radius, $targetChunkX, $targetChunkZ); - // Natural tunnel movement $yaw += ($this->random->nextFloat() - 0.5) * 0.15; $pitch += ($this->random->nextFloat() - 0.5) * 0.08; @@ -302,8 +261,11 @@ private function carveSphere(ChunkManager $world, int $centerX, int $centerY, in $carvedBlocks = 0; $radiusSquared = $radius * $radius; + + $airId = VanillaBlocks::AIR()->getStateId(); + $stoneId = VanillaBlocks::STONE()->getStateId(); + $deepslateId = VanillaBlocks::DEEPSLATE()->getStateId(); - // Only carve within the target chunk boundaries $minX = max(0, $centerX - (int)ceil($radius) - $chunkBaseX); $maxX = min(15, $centerX + (int)ceil($radius) - $chunkBaseX); $minY = max(self::MIN_CAVE_Y, $centerY - (int)ceil($radius)); @@ -318,17 +280,15 @@ private function carveSphere(ChunkManager $world, int $centerX, int $centerY, in $worldY = $y; $worldZ = $chunkBaseZ + $z; - // Calculate distance from center $dx = $worldX - $centerX; $dy = $worldY - $centerY; $dz = $worldZ - $centerZ; $distanceSquared = $dx * $dx + $dy * $dy + $dz * $dz; - // Only carve if within radius and the block is stone if ($distanceSquared <= $radiusSquared) { $block = $chunk->getBlockStateId($x, $y, $z); - if ($block === VanillaBlocks::STONE()->getStateId()) { - $chunk->setBlockStateId($x, $y, $z, VanillaBlocks::AIR()->getStateId()); + if ($block === $stoneId || $block === $deepslateId) { + $chunk->setBlockStateId($x, $y, $z, $airId); $carvedBlocks++; } } @@ -348,141 +308,25 @@ public function applyAquifers(ChunkManager $world, int $chunkX, int $chunkZ): vo $baseX = $chunkX << 4; $baseZ = $chunkZ << 4; - - // Reset random for consistent aquifer placement + $this->random->setSeed($chunkX * 871236847 + $chunkZ * 321487613); - // Water pools disabled by request; only generate lava pools $this->applyLavaPools($chunk, $chunkX, $chunkZ); } - /** - * Apply water aquifers - only in specific areas, not filling all caves - */ - private function applyWaterAquifers(Chunk $chunk, int $chunkX, int $chunkZ): void { - // Disabled: do not place water in caves - return; - $baseX = $chunkX << 4; - $baseZ = $chunkZ << 4; - - // Only some chunks get water aquifers - if ($this->random->nextFloat() > self::WATER_FILL_CHANCE) { - return; - } - - // Generate aquifer level map for chunk - $aquiferMap = $this->aquiferNoise->getFractalBrownianMotion($baseX, 0, $baseZ, 0.5, 0.5); - - $waterCount = 0; - - // Find connected cave areas and fill them with water up to a certain level - for ($x = 0; $x < 16; ++$x) { - for ($z = 0; $z < 16; ++$z) { - $index = $x | ($z << 4); - $localWaterLevel = (int)(self::AQUIFER_BASE + $aquiferMap[$index] * self::AQUIFER_VARIANCE); - - // Only create water if there's a cave opening at this location - $hasWaterSource = false; - for ($y = $localWaterLevel; $y >= self::MIN_CAVE_Y; $y--) { - $block = $chunk->getBlockStateId($x, $y, $z); - - if ($block === VanillaBlocks::AIR()->getStateId()) { - $hasWaterSource = true; - break; - } - } - - // Fill connected cave areas with water up to the water level - if ($hasWaterSource) { - for ($y = self::MIN_CAVE_Y; $y <= $localWaterLevel && $y < self::MAX_CAVE_Y; ++$y) { - $block = $chunk->getBlockStateId($x, $y, $z); - - if ($block === VanillaBlocks::AIR()->getStateId()) { - $chunk->setBlockStateId($x, $y, $z, VanillaBlocks::WATER()->getStateId()); - $waterCount++; - } - } - } - } - } - - // if ($waterCount > 0) { - // error_log("CaveGenerator: Applied $waterCount water blocks in chunk $chunkX, $chunkZ"); - // } - } - /** * Apply lava pools - small isolated pools, not connected systems */ private function applyLavaPools(Chunk $chunk, int $chunkX, int $chunkZ): void { - // Chance for lava pools to generate - $lavaPoolChance = 0.08; // 8% chance per chunk - - if ($this->random->nextFloat() > $lavaPoolChance) { - return; - } - - $lavaCount = 0; - $poolsGenerated = 0; - $maxPools = 1 + $this->random->nextBoundedInt(3); // 1-3 lava pools per chunk - - for ($poolAttempt = 0; $poolAttempt < $maxPools; $poolAttempt++) { - // Random location for lava pool - $poolX = $this->random->nextBoundedInt(16); - $poolZ = $this->random->nextBoundedInt(16); - $poolY = self::MIN_CAVE_Y + $this->random->nextBoundedInt(self::LAVA_LEVEL - self::MIN_CAVE_Y + 1); - - // Check if there's a cave area here - $block = $chunk->getBlockStateId($poolX, $poolY, $poolZ); - if ($block !== VanillaBlocks::AIR()->getStateId()) { - continue; // No cave here, try next location - } - - // Create small lava pool (3-7 blocks wide) - $poolSize = 3 + $this->random->nextBoundedInt(5); - $poolRadius = $poolSize / 2.0; - - $poolLavaCount = 0; - - for ($dx = -$poolSize; $dx <= $poolSize; $dx++) { - for ($dz = -$poolSize; $dz <= $poolSize; $dz++) { - for ($dy = -2; $dy <= 1; $dy++) { // Lava pools are shallow - $lx = $poolX + $dx; - $lz = $poolZ + $dz; - $ly = $poolY + $dy; - - // Check bounds - if ($lx < 0 || $lx >= 16 || $lz < 0 || $lz >= 16 || - $ly < self::MIN_CAVE_Y || $ly > self::LAVA_LEVEL) { - continue; - } - - // Check if within pool radius - $distance = sqrt($dx * $dx + $dz * $dz + $dy * $dy * 0.5); // Flatten vertically - if ($distance > $poolRadius) { - continue; - } - - $currentBlock = $chunk->getBlockStateId($lx, $ly, $lz); - - // Only place lava in air blocks (cave areas) - if ($currentBlock === VanillaBlocks::AIR()->getStateId()) { - $chunk->setBlockStateId($lx, $ly, $lz, VanillaBlocks::LAVA()->getStateId()); - $poolLavaCount++; - $lavaCount++; - } - } + $airId = VanillaBlocks::AIR()->getStateId(); + $lavaId = VanillaBlocks::LAVA()->getStateId(); + $y = self::LAVA_LEVEL; // -54 + for ($x = 0; $x < 16; $x++) { + for ($z = 0; $z < 16; $z++) { + if ($chunk->getBlockStateId($x, $y, $z) === $airId) { + $chunk->setBlockStateId($x, $y, $z, $lavaId); } } - - // Only count as successful pool if we placed enough lava - if ($poolLavaCount >= 5) { - $poolsGenerated++; - } } - - // if ($lavaCount > 0) { - // error_log("CaveGenerator: Generated $poolsGenerated lava pools ($lavaCount lava blocks) in chunk $chunkX, $chunkZ"); - // } } } \ No newline at end of file diff --git a/src/muqsit/vanillagenerator/generator/ground/GroundGenerator.php b/src/muqsit/vanillagenerator/generator/ground/GroundGenerator.php index 25e9700..a43d012 100644 --- a/src/muqsit/vanillagenerator/generator/ground/GroundGenerator.php +++ b/src/muqsit/vanillagenerator/generator/ground/GroundGenerator.php @@ -52,7 +52,7 @@ final protected function setGroundMaterial(Block $ground_material) : void{ * @param float $surface_noise the amplitude of random variation in surface height */ public function generateTerrainColumn(ChunkManager $world, Random $random, int $x, int $z, int $biome, float $surface_noise) : void{ - $sea_level = 63; // 1.18+ sea level + $sea_level = 63; $top_mat = $this->top_material->getStateId(); $ground_mat = $this->ground_material->getStateId(); @@ -67,6 +67,7 @@ public function generateTerrainColumn(ChunkManager $world, Random $random, int $ $block_state_registry = RuntimeBlockStateRegistry::getInstance(); $air = VanillaBlocks::AIR()->getStateId(); $stone = VanillaBlocks::STONE()->getStateId(); + $deepslate = VanillaBlocks::DEEPSLATE()->getStateId(); $sandstone = VanillaBlocks::SANDSTONE()->getStateId(); $gravel = VanillaBlocks::GRAVEL()->getStateId(); $bedrock = VanillaBlocks::BEDROCK()->getStateId(); @@ -81,6 +82,22 @@ public function generateTerrainColumn(ChunkManager $world, Random $random, int $ $world_max_y = $world->getMaxY(); $world_min_y = $world->getMinY(); + // Function to pick stone/deepslate by Y with a transition from Y=8 down to Y=0 + $pickStoneId = static function(int $y, int $abs_x, int $abs_z) use ($stone, $deepslate) : int{ + if($y <= 0){ + return $deepslate; + } + if($y > 8){ + return $stone; + } + $prob = (8 - $y) / 8.0; + $h = ($abs_x * 73428767) ^ ($abs_z * 912367) ^ ($y * 42331); + $h ^= ($h >> 13); + $h = ($h * 1274126177) & 0x7fffffff; + $rand01 = ($h % 100000) / 100000.0; + return ($rand01 < $prob) ? $deepslate : $stone; + }; + for($y = $world_max_y; $y >= $world_min_y; --$y){ // Validate subchunk index before accessing chunk $subchunk_index = $y >> 4; @@ -109,15 +126,24 @@ public function generateTerrainColumn(ChunkManager $world, Random $random, int $ $chunk->setBlockStateId($block_x, $y, $block_z, $top_mat); }elseif($y < $sea_level - 8 - $surface_height){ $top_mat = $air; - $ground_mat = $stone; + $ground_mat = $pickStoneId($y, $chunk_x, $chunk_z); $ground_mat_id = BlockTypeIds::STONE; $chunk->setBlockStateId($block_x, $y, $block_z, $gravel); }else{ - $chunk->setBlockStateId($block_x, $y, $block_z, $ground_mat); + // If the current ground material is stone, swap to deepslate when appropriate + $mat_to_set = $ground_mat; + if($ground_mat_id === BlockTypeIds::STONE){ + $mat_to_set = $pickStoneId($y, $chunk_x, $chunk_z); + } + $chunk->setBlockStateId($block_x, $y, $block_z, $mat_to_set); } }elseif($deep > 0){ --$deep; - $chunk->setBlockStateId($block_x, $y, $block_z, $ground_mat); + $mat_to_set = $ground_mat; + if($ground_mat_id === BlockTypeIds::STONE){ + $mat_to_set = $pickStoneId($y, $chunk_x, $chunk_z); + } + $chunk->setBlockStateId($block_x, $y, $block_z, $mat_to_set); if($deep === 0 && $ground_mat_id === BlockTypeIds::SAND){ $deep = $random->nextBoundedInt(4) + max(0, $y - $sea_level - 1); diff --git a/src/muqsit/vanillagenerator/generator/ground/MesaGroundGenerator.php b/src/muqsit/vanillagenerator/generator/ground/MesaGroundGenerator.php index c8a7f16..6954446 100644 --- a/src/muqsit/vanillagenerator/generator/ground/MesaGroundGenerator.php +++ b/src/muqsit/vanillagenerator/generator/ground/MesaGroundGenerator.php @@ -51,7 +51,7 @@ private function initialize(int $seed) : void{ public function generateTerrainColumn(ChunkManager $world, Random $random, int $x, int $z, int $biome, float $surface_noise) : void{ $this->initialize($random->getSeed()); - $sea_level = 63; // 1.18+ sea level + $sea_level = 63; $top_mat = $this->top_material; $ground_mat = $this->ground_material; diff --git a/src/muqsit/vanillagenerator/generator/object/DoubleTallPlant.php b/src/muqsit/vanillagenerator/generator/object/DoubleTallPlant.php index 2c7c0dc..fb8d720 100644 --- a/src/muqsit/vanillagenerator/generator/object/DoubleTallPlant.php +++ b/src/muqsit/vanillagenerator/generator/object/DoubleTallPlant.php @@ -33,11 +33,6 @@ public function generate(ChunkManager $world, Random $random, int $source_x, int $z = $source_z + $random->nextBoundedInt(8) - $random->nextBoundedInt(8); $y = $source_y + $random->nextBoundedInt(4) - $random->nextBoundedInt(4); - // Check bounds for both Y and Y+1 - if($y < $world->getMinY() || $y + 1 > $world->getMaxY()) { - continue; - } - $block = $world->getBlockAt($x, $y, $z); $top_block = $world->getBlockAt($x, $y + 1, $z); if($y < $height && $block->getTypeId() === BlockTypeIds::AIR && $top_block->getTypeId() === BlockTypeIds::AIR && $world->getBlockAt($x, $y - 1, $z)->getTypeId() === BlockTypeIds::GRASS){ diff --git a/src/muqsit/vanillagenerator/generator/object/Lake.php b/src/muqsit/vanillagenerator/generator/object/Lake.php index 0a3c37c..b085d67 100644 --- a/src/muqsit/vanillagenerator/generator/object/Lake.php +++ b/src/muqsit/vanillagenerator/generator/object/Lake.php @@ -83,19 +83,9 @@ public function generate(ChunkManager $world, Random $random, int $source_x, int continue; } - // Check if coordinates are within world bounds - $world_x = $source_x + $x; - $world_y = $source_y + $y; - $world_z = $source_z + $z; - - if($world_y < $world->getMinY() || $world_y > $world->getMaxY() || - $world_y + 1 > $world->getMaxY()) { - continue; // Skip if Y coordinate or Y+1 is outside world bounds - } - $type = $this->type; - $block = $world->getBlockAt($world_x, $world_y, $world_z); - $block_above = $world->getBlockAt($world_x, $world_y + 1, $world_z); + $block = $world->getBlockAt($source_x + $x, $source_y + $y, $source_z + $z); + $block_above = $world->getBlockAt($source_x + $x, $source_y + $y + 1, $source_z + $z); $block_type = $block->getTypeId(); if(($block_type === BlockTypeIds::DIRT && $block_above instanceof Wood) || $block instanceof Wood){ continue; @@ -103,7 +93,7 @@ public function generate(ChunkManager $world, Random $random, int $source_x, int if($y >= (int) (self::MAX_HEIGHT / 2)){ $type = VanillaBlocks::AIR(); - if(TerrainObject::killWeakBlocksAbove($world, $world_x, $world_y, $world_z)){ + if(TerrainObject::killWeakBlocksAbove($world, $source_x + $x, $source_y + $y, $source_z + $z)){ break; } @@ -111,11 +101,11 @@ public function generate(ChunkManager $world, Random $random, int $source_x, int $type = $block; } }elseif($y === (int) (self::MAX_HEIGHT / 2 - 1)){ - if($type instanceof Water && $type->isStill() && BiomeClimateManager::isCold($chunk->getBiomeId($x & Chunk::COORD_MASK, $y, $z & Chunk::COORD_MASK), $world_x, $y, $world_z)){ + if($type instanceof Water && $type->isStill() && BiomeClimateManager::isCold($chunk->getBiomeId($x & Chunk::COORD_MASK, $y, $z & Chunk::COORD_MASK), $source_x + $x, $y, $source_z + $z)){ $type = VanillaBlocks::ICE(); } } - $world->setBlockAt($world_x, $world_y, $world_z, $type); + $world->setBlockAt($source_x + $x, $source_y + $y, $source_z + $z, $type); } } } @@ -127,21 +117,10 @@ public function generate(ChunkManager $world, Random $random, int $source_x, int continue; } - // Check if coordinates are within world bounds - $world_x = $source_x + $x; - $world_y_below = $source_y + $y - 1; - $world_y = $source_y + $y; - $world_z = $source_z + $z; - - if($world_y_below < $world->getMinY() || $world_y_below > $world->getMaxY() || - $world_y < $world->getMinY() || $world_y > $world->getMaxY()) { - continue; // Skip if Y coordinates are outside world bounds - } - - $block = $world->getBlockAt($world_x, $world_y_below, $world_z); - $block_above = $world->getBlockAt($world_x, $world_y, $world_z); + $block = $world->getBlockAt($source_x + $x, $source_y + $y - 1, $source_z + $z); + $block_above = $world->getBlockAt($source_x + $x, $source_y + $y, $source_z + $z); if($block->getTypeId() === BlockTypeIds::DIRT && $block_above->isTransparent() && $block_above->getLightLevel() > 0){ - $world->setBlockAt($world_x, $world_y_below, $world_z, $mycel_biome ? VanillaBlocks::MYCELIUM() : VanillaBlocks::GRASS()); + $world->setBlockAt($source_x + $x, $source_y + $y - 1, $source_z + $z, $mycel_biome ? VanillaBlocks::MYCELIUM() : VanillaBlocks::GRASS()); } } } @@ -170,14 +149,7 @@ private function canPlace(array $lake_map, ChunkManager $world, int $sourceX, in && (($z <= 0) || !$this->isLakeBlock($lake_map, $x, $y - 1, $z)))){ continue; } - - // Check if coordinates are within world bounds - $world_y = $sourceY + $y; - if($world_y < $world->getMinY() || $world_y > $world->getMaxY()) { - continue; // Skip if Y coordinate is outside world bounds - } - - $block = $world->getBlockAt($sourceX + $x, $world_y, $sourceZ + $z); + $block = $world->getBlockAt($sourceX + $x, $sourceY + $y, $sourceZ + $z); if($y >= self::MAX_HEIGHT / 2 && (($block instanceof Liquid) || $block->getTypeId() === BlockTypeIds::ICE)){ return false; // there's already some liquids above } diff --git a/src/muqsit/vanillagenerator/generator/object/OreVein.php b/src/muqsit/vanillagenerator/generator/object/OreVein.php index 18445b7..3321846 100644 --- a/src/muqsit/vanillagenerator/generator/object/OreVein.php +++ b/src/muqsit/vanillagenerator/generator/object/OreVein.php @@ -5,6 +5,7 @@ namespace muqsit\vanillagenerator\generator\object; use pocketmine\block\Block; +use pocketmine\block\BlockTypeIds; use pocketmine\utils\Random; use pocketmine\world\ChunkManager; @@ -80,9 +81,13 @@ public function generate(ChunkManager $world, Random $random, int $source_x, int } for($z = $min_z; $z <= $max_z; ++$z){ $squared_normalized_z = self::normalizedSquaredCoordinate($origin_z, $radius_h, $z); - if($squared_normalized_x + $squared_normalized_y + $squared_normalized_z < 1 && $world->getBlockAt($x, $y, $z)->getTypeId() === $this->target_type){ - $world->setBlockAt($x, $y, $z, $this->type); - $succeeded = true; + if($squared_normalized_x + $squared_normalized_y + $squared_normalized_z < 1){ + $under_id = $world->getBlockAt($x, $y, $z)->getTypeId(); + // Replace only the configured target type (STONE for stone ores, DEEPSLATE for deepslate ores) + if($under_id === $this->target_type){ + $world->setBlockAt($x, $y, $z, $this->type); + $succeeded = true; + } } } } diff --git a/src/muqsit/vanillagenerator/generator/object/tree/MegaPineTree.php b/src/muqsit/vanillagenerator/generator/object/tree/MegaPineTree.php index 0a24f5b..faf05d3 100644 --- a/src/muqsit/vanillagenerator/generator/object/tree/MegaPineTree.php +++ b/src/muqsit/vanillagenerator/generator/object/tree/MegaPineTree.php @@ -56,24 +56,15 @@ private function generatePodzolPatch(int $source_x, int $source_y, int $source_z continue; } for($y = 2; $y >= -3; --$y){ - $check_y = $source_y + $y; - $check_y_above = $source_y + $y + 1; - - // Check bounds for both Y and Y+1 - if($check_y < $world->getMinY() || $check_y > $world->getMaxY() || - $check_y_above > $world->getMaxY()) { - continue; - } - - $block_id = $world->getBlockAt($source_x + $x, $check_y, $source_z + $z)->getTypeId(); + $block_id = $world->getBlockAt($source_x + $x, $source_y + $y, $source_z + $z)->getTypeId(); if($block_id === BlockTypeIds::GRASS || $block_id === BlockTypeIds::DIRT){ - if($world->getBlockAt($source_x + $x, $check_y_above, $source_z + $z)->isSolid()){ + if($world->getBlockAt($source_x + $x, $source_y + $y + 1, $source_z + $z)->isSolid()){ $dirt = VanillaBlocks::DIRT(); }else{ $dirt = VanillaBlocks::PODZOL(); } - $world->setBlockAt($source_x + $x, $check_y, $source_z + $z, $dirt); - }elseif($block_id !== BlockTypeIds::AIR && $check_y < $source_y){ + $world->setBlockAt($source_x + $x, $source_y + $y, $source_z + $z, $dirt); + }elseif($block_id !== BlockTypeIds::AIR && $source_y + $y < $source_y){ break; } } diff --git a/src/muqsit/vanillagenerator/generator/overworld/OverworldGenerator.php b/src/muqsit/vanillagenerator/generator/overworld/OverworldGenerator.php index 1d5ffbb..d26ae4e 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/OverworldGenerator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/OverworldGenerator.php @@ -26,7 +26,6 @@ use muqsit\vanillagenerator\generator\utils\WorldOctaves; use muqsit\vanillagenerator\generator\VanillaBiomeGrid; use muqsit\vanillagenerator\generator\VanillaGenerator; -use muqsit\vanillagenerator\generator\cave\ModernCaveGenerator; use muqsit\vanillagenerator\generator\cave\CaveGenerator; use pocketmine\block\VanillaBlocks; use pocketmine\utils\Random; @@ -109,7 +108,7 @@ protected static function setBiomeSpecificGround(GroundGenerator $gen, int ...$b protected const DETAIL_NOISE_SCALE_Y = 160.0; protected const DETAIL_NOISE_SCALE_Z = 80.0; protected const SURFACE_SCALE = 0.0625; - protected const BASE_SIZE = 16.0; // Adjusted for 1.18+ height: sea level Y=64 maps to density level ~16 + protected const BASE_SIZE = 16.0; protected const STRETCH_Y = 12.0; protected const BIOME_HEIGHT_OFFSET = 0.0; protected const BIOME_HEIGHT_WEIGHT = 1.0; @@ -131,23 +130,16 @@ public function __construct(int $seed, string $preset_string){ $preset->exists("worldtype") ? WorldType::fromString($preset->getString("worldtype")) : null, $preset ); - // Ground generation - $this->ground_gen = new GroundGenerator(); - - // Use legacy cave generator - $this->cave_generator = new CaveGenerator($seed); - - // Add populators - $this->addPopulators(new OverworldPopulator(), new SnowPopulator()); + // Ground generation + $this->ground_gen = new GroundGenerator(); + + // Use legacy cave generator + $this->cave_generator = new CaveGenerator($seed); + + // Add populators + $this->addPopulators(new OverworldPopulator(), new SnowPopulator()); } - - /* - public function getGroundGenerator() : GroundGenerator{ - return $this->ground_gen; - } - */ - protected function generateChunkData(ChunkManager $world, int $chunk_x, int $chunk_z, VanillaBiomeGrid $grid) : void{ $this->generateRawTerrain($world, $chunk_x, $chunk_z); @@ -208,7 +200,6 @@ protected function createWorldOctaves() : WorldOctaves{ $height->x_scale = self::HEIGHT_NOISE_SCALE_X; $height->z_scale = self::HEIGHT_NOISE_SCALE_Z; - // 1.18+ height: 48 Y-levels for full height range (Y=-64 to Y=319) $roughness = PerlinOctaveGenerator::fromRandomAndOctaves($seed, 16, 5, 48, 5); $roughness->x_scale = self::COORDINATE_SCALE; $roughness->y_scale = self::HEIGHT_SCALE; @@ -233,7 +224,7 @@ protected function createWorldOctaves() : WorldOctaves{ protected function generateRawTerrain(ChunkManager $world, int $chunk_x, int $chunk_z) : void{ $density = $this->generateTerrainDensity($chunk_x, $chunk_z); - $sea_level = 63; // 1.18+ standard sea level + $sea_level = 63; // Terrain densities are sampled at different resolutions (1/4x on x,z and 1/8x on y by // default) @@ -247,13 +238,35 @@ protected function generateRawTerrain(ChunkManager $world, int $chunk_x, int $ch $still_water = VanillaBlocks::WATER()->getStillForm()->getStateId(); $water = VanillaBlocks::WATER()->getFlowingForm()->getStateId(); $stone = VanillaBlocks::STONE()->getStateId(); + $deepslate = VanillaBlocks::DEEPSLATE()->getStateId(); + + // Decide whether a block placement at a given Y should be stone or deepslate. + // Policy: + // - Y <= 0: always deepslate + // - 0 < Y <= 8: gradual transition, deepslate probability increases linearly from 0 at Y=8 to 1 at Y=0 + // - Y > 8: always stone + $pickStoneId = static function(int $y, int $abs_x, int $abs_z) use ($stone, $deepslate) : int{ + if($y <= 0){ + return $deepslate; + } + if($y > 8){ + return $stone; + } + // Linear probability for deepslate in (0,8] + $prob = (8 - $y) / 8.0; // at y=8 => 0, y=0 => 1 + // Deterministic hash-based noise per-block to avoid affecting PRNG streams + $h = ($abs_x * 73428767) ^ ($abs_z * 912367) ^ ($y * 42331); + $h ^= ($h >> 13); + $h = ($h * 1274126177) & 0x7fffffff; + $rand01 = ($h % 100000) / 100000.0; // [0,1) + return ($rand01 < $prob) ? $deepslate : $stone; + }; /** @var Chunk $chunk */ $chunk = $world->getChunk($chunk_x, $chunk_z); for($i = 0; $i < 5 - 1; ++$i){ for($j = 0; $j < 5 - 1; ++$j){ - // 1.18+ height: 48 - 1 = 47 density interpolation steps for($k = 0; $k < 48 - 1; ++$k){ // 2x2 grid $d1 = $density[self::densityHash($i, $j, $k)]; @@ -269,7 +282,6 @@ protected function generateRawTerrain(ChunkManager $world, int $chunk_x, int $ch $d9 = $d1; $d10 = $d3; - // 1.18+ world height: Y=-64 to Y=319 $y_pos = $l + ($k << 3) - 64; // Clamp Y position to valid range as safety measure @@ -281,8 +293,6 @@ protected function generateRawTerrain(ChunkManager $world, int $chunk_x, int $ch // Final safety check for subchunk index if($subchunk_index < -4 || $subchunk_index > 19){ - // This should never happen with proper clamping - error_log("CRITICAL: Invalid subchunk index: $subchunk_index for Y=$y_pos (k=$k, l=$l)"); continue; } @@ -291,6 +301,10 @@ protected function generateRawTerrain(ChunkManager $world, int $chunk_x, int $ch for($m = 0; $m < 4; ++$m){ $dens = $d9; for($n = 0; $n < 4; ++$n){ + $block_x = $m + ($i << 2); + $block_z = $n + ($j << 2); + $abs_x = ($chunk_x << 4) + $block_x; + $abs_z = ($chunk_z << 4) + $block_z; // any density higher than density offset is ground, any density // lower or equal to the density offset is air // (or water if under the sea level). @@ -304,12 +318,12 @@ protected function generateRawTerrain(ChunkManager $world, int $chunk_x, int $ch if($afill === 1 || $afill === 10 || $afill === 13 || $afill === 16){ $sub_chunk->setBlockStateId($m + ($i << 2), $y_block_pos, $n + ($j << 2), $water); }elseif($afill === 2 || $afill === 9 || $afill === 12 || $afill === 15){ - $sub_chunk->setBlockStateId($m + ($i << 2), $y_block_pos, $n + ($j << 2), $stone); + $sub_chunk->setBlockStateId($m + ($i << 2), $y_block_pos, $n + ($j << 2), $pickStoneId($y_pos, $abs_x, $abs_z)); } if(($dens > $density_offset && $fill > -1) || ($dens <= $density_offset && $fill < 0)){ if($afill === 0 || $afill === 3 || $afill === 6 || $afill === 9 || $afill === 12){ - $sub_chunk->setBlockStateId($m + ($i << 2), $y_block_pos, $n + ($j << 2), $stone); + $sub_chunk->setBlockStateId($m + ($i << 2), $y_block_pos, $n + ($j << 2), $pickStoneId($y_pos, $abs_x, $abs_z)); }elseif($afill === 2 || $afill === 7 || $afill === 10 || $afill === 16){ $sub_chunk->setBlockStateId($m + ($i << 2), $y_block_pos, $n + ($j << 2), $still_water); } @@ -317,7 +331,7 @@ protected function generateRawTerrain(ChunkManager $world, int $chunk_x, int $ch if($afill === 0 || $afill === 3 || $afill === 7 || $afill === 10 || $afill === 13){ $sub_chunk->setBlockStateId($m + ($i << 2), $y_block_pos, $n + ($j << 2), $still_water); }elseif($afill === 1 || $afill === 6 || $afill === 9 || $afill === 15){ - $sub_chunk->setBlockStateId($m + ($i << 2), $y_block_pos, $n + ($j << 2), $stone); + $sub_chunk->setBlockStateId($m + ($i << 2), $y_block_pos, $n + ($j << 2), $pickStoneId($y_pos, $abs_x, $abs_z)); } } @@ -439,11 +453,9 @@ protected function generateTerrainDensity(int $x, int $z) : array{ $noise_h = ($noise_h * 0.2 + $avg_height_base) * self::BASE_SIZE / 8.0 * 4.0 + self::BASE_SIZE; - // 1.18+ height: 384 total height / 8 = 48 density steps (Y=-64 to Y=319) for($k = 0; $k < 48; ++$k){ // density should be lower and lower as we climb up, this gets a height value to // subtract from the noise. - // Updated calculation for 1.18+ height range (384 total height) $nh = ($k - $noise_h) * self::STRETCH_Y * 128.0 / 384.0 / $avg_height_scale; if($nh < 0.0){ $nh *= 4.0; @@ -457,7 +469,6 @@ protected function generateTerrainDensity(int $x, int $z) : array{ $dens = $noise_d < 0 ? $noise_r : ($noise_d > 1 ? $noise_r_2 : $noise_r + ($noise_r_2 - $noise_r) * $noise_d); $dens -= $nh; ++$index; - // Adjusted for 1.18+ heights: start lowering at level 38 instead of 29 (48 * 0.8 ≈ 38) if($k > 38){ $lowering = ($k - 38) / 9.0; // Spread over 9 levels instead of 3 // linear interpolation diff --git a/src/muqsit/vanillagenerator/generator/overworld/biome/Biome3DGenerator.php b/src/muqsit/vanillagenerator/generator/overworld/biome/Biome3DGenerator.php index 01b6f07..bdeb9fa 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/biome/Biome3DGenerator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/biome/Biome3DGenerator.php @@ -7,7 +7,7 @@ use muqsit\vanillagenerator\generator\VanillaBiomeGrid; use pocketmine\utils\Random; -class Biome3DGenerator{ +class Biome3DGenerator { /** * Generate 3D biomes for a chunk based on surface biomes and depth @@ -19,22 +19,21 @@ class Biome3DGenerator{ * @param int $world_min_y * @param int $world_max_y */ - public static function generate3DBiomes(VanillaBiomeGrid $biome_grid, Random $random, int $chunk_x, int $chunk_z, int $world_min_y, int $world_max_y) : void{ - // Sample every 4th block for 3D biomes (like 1.18+) - for($x = 0; $x < 16; $x += 4){ - for($z = 0; $z < 16; $z += 4){ + public static function generate3DBiomes(VanillaBiomeGrid $biome_grid, Random $random, int $chunk_x, int $chunk_z, int $world_min_y, int $world_max_y): void { + for($x = 0; $x < 16; $x += 4) { + for($z = 0; $z < 16; $z += 4) { // Get surface biome $surface_biome = $biome_grid->getBiome($x, $z); if($surface_biome === null) continue; // Generate biomes for different depth layers - for($y = $world_min_y; $y <= $world_max_y; $y += 4){ + for($y = $world_min_y; $y <= $world_max_y; $y += 4) { $depth_biome = self::getBiomeForDepth($surface_biome, $y, $random, $chunk_x * 16 + $x, $chunk_z * 16 + $z); // Set biome for 4x4x4 region - for($dx = 0; $dx < 4 && $x + $dx < 16; $dx++){ - for($dz = 0; $dz < 4 && $z + $dz < 16; $dz++){ - for($dy = 0; $dy < 4 && $y + $dy <= $world_max_y; $dy++){ + for($dx = 0; $dx < 4 && $x + $dx < 16; $dx++) { + for($dz = 0; $dz < 4 && $z + $dz < 16; $dz++) { + for($dy = 0; $dy < 4 && $y + $dy <= $world_max_y; $dy++) { $biome_grid->setBiome3D($x + $dx, $y + $dy, $z + $dz, $depth_biome); } } @@ -54,14 +53,22 @@ public static function generate3DBiomes(VanillaBiomeGrid $biome_grid, Random $ra * @param int $world_z * @return int */ - private static function getBiomeForDepth(int $surface_biome, int $y, Random $random, int $world_x, int $world_z) : int{ + private static function getBiomeForDepth(int $surface_biome, int $y, Random $random, int $world_x, int $world_z): int { // Underground biome generation based on depth and surface biome + // IMPORTANT: Use deterministic, position-based randomness so results don't depend on PRNG state/order. + $rand01 = static function(float $chance) use ($random, $world_x, $world_z, $y): bool { + $seed = method_exists($random, 'getSeed') ? (string)$random->getSeed() : '0'; + // crc32 gives a stable 32-bit unsigned int; normalize to [0,1) + $h = crc32($seed . '|b3d|' . $world_x . '|' . $world_z . '|' . $y); + $val = $h / 4294967296.0; // 2^32 + return $val < $chance; + }; // Deep Dark biome (Y -64 to -10, rare) if($y >= -64 && $y <= -10){ // 5% chance for Deep Dark in deep areas, higher chance near Y -52 $deep_dark_chance = ($y <= -40 && $y >= -60) ? 0.08 : 0.02; - if($random->nextFloat() < $deep_dark_chance){ + if($rand01($deep_dark_chance)){ return BiomeIds::DEEP_DARK; } } @@ -73,7 +80,7 @@ private static function getBiomeForDepth(int $surface_biome, int $y, Random $ran BiomeIds::JUNGLE, BiomeIds::JUNGLE_HILLS, BiomeIds::ROOFED_FOREST ], true); - if($is_lush_surface && $random->nextFloat() < 0.15){ + if($is_lush_surface && $rand01(0.15)){ return BiomeIds::LUSH_CAVES; } } @@ -85,18 +92,15 @@ private static function getBiomeForDepth(int $surface_biome, int $y, Random $ran BiomeIds::MESA_PLATEAU, BiomeIds::MESA_PLATEAU_STONE, BiomeIds::SAVANNA ], true); - if($is_dry_surface && $random->nextFloat() < 0.2){ + if($is_dry_surface && $rand01(0.2)){ return BiomeIds::DRIPSTONE_CAVES; } // Lower chance in other biomes - if($random->nextFloat() < 0.05){ + if($rand01(0.05)){ return BiomeIds::DRIPSTONE_CAVES; } } - - // Default: use surface biome for underground areas - // This maintains existing behavior for most underground areas return $surface_biome; } } \ No newline at end of file diff --git a/src/muqsit/vanillagenerator/generator/overworld/biome/BiomeHeightManager.php b/src/muqsit/vanillagenerator/generator/overworld/biome/BiomeHeightManager.php index 5c7fa21..c000b4b 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/biome/BiomeHeightManager.php +++ b/src/muqsit/vanillagenerator/generator/overworld/biome/BiomeHeightManager.php @@ -56,18 +56,17 @@ public static function init() : void{ self::register(new BiomeHeight(0.1, 0.4), BiomeIds::FLOWER_FOREST); self::register(new BiomeHeight(0.4125, 1.325), BiomeIds::SAVANNA_MUTATED); self::register(new BiomeHeight(1.1, 1.3125), BiomeIds::SAVANNA_PLATEAU_MUTATED); - - // 1.18+ Biome Heights - self::register(new BiomeHeight(-1.0, 0.1), BiomeIds::DEEP_DARK); // Deep underground - self::register(new BiomeHeight(-0.7, 0.4), BiomeIds::DRIPSTONE_CAVES); // Cave biome - self::register(new BiomeHeight(-0.5, 0.4), BiomeIds::LUSH_CAVES); // Cave biome - self::register(new BiomeHeight(-0.2, 0.1), BiomeIds::MANGROVE_SWAMP); // Swamp-like - self::register(new BiomeHeight(0.2, 0.8), BiomeIds::MEADOW); // Mountain meadow - self::register(new BiomeHeight(0.25, 0.8), BiomeIds::GROVE); // Mountain grove - self::register(new BiomeHeight(1.0, 0.3), BiomeIds::SNOWY_SLOPES); // Mountain slopes - self::register(new BiomeHeight(1.4, 0.8), BiomeIds::FROZEN_PEAKS); // High peaks - self::register(new BiomeHeight(1.4, 0.8), BiomeIds::JAGGED_PEAKS); // High peaks - self::register(new BiomeHeight(1.4, 0.8), BiomeIds::STONY_PEAKS); // High peaks + + self::register(new BiomeHeight(-1.0, 0.1), BiomeIds::DEEP_DARK); + self::register(new BiomeHeight(-0.7, 0.4), BiomeIds::DRIPSTONE_CAVES); + self::register(new BiomeHeight(-0.5, 0.4), BiomeIds::LUSH_CAVES); + self::register(new BiomeHeight(-0.2, 0.1), BiomeIds::MANGROVE_SWAMP); + self::register(new BiomeHeight(0.2, 0.8), BiomeIds::MEADOW); + self::register(new BiomeHeight(0.25, 0.8), BiomeIds::GROVE); + self::register(new BiomeHeight(1.0, 0.3), BiomeIds::SNOWY_SLOPES); + self::register(new BiomeHeight(1.4, 0.8), BiomeIds::FROZEN_PEAKS); + self::register(new BiomeHeight(1.4, 0.8), BiomeIds::JAGGED_PEAKS); + self::register(new BiomeHeight(1.4, 0.8), BiomeIds::STONY_PEAKS); } public static function register(BiomeHeight $height, int ...$biomes) : void{ diff --git a/src/muqsit/vanillagenerator/generator/overworld/biome/BiomeIds.php b/src/muqsit/vanillagenerator/generator/overworld/biome/BiomeIds.php index c3938f2..7ecbe93 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/biome/BiomeIds.php +++ b/src/muqsit/vanillagenerator/generator/overworld/biome/BiomeIds.php @@ -70,17 +70,15 @@ interface BiomeIds{ public const MESA_PLATEAU_STONE_MUTATED = VanillaBiomeIds::MESA_PLATEAU_STONE_MUTATED; public const MESA_PLATEAU_MUTATED = VanillaBiomeIds::MESA_PLATEAU_MUTATED; - // 1.18+ Underground Biomes - public const DEEP_DARK = VanillaBiomeIds::DEEP_DARK; // Deep Dark biome for Ancient Cities - public const DRIPSTONE_CAVES = VanillaBiomeIds::DRIPSTONE_CAVES; // Dripstone cave biome - public const LUSH_CAVES = VanillaBiomeIds::LUSH_CAVES; // Lush cave biome + public const DEEP_DARK = VanillaBiomeIds::DEEP_DARK; + public const DRIPSTONE_CAVES = VanillaBiomeIds::DRIPSTONE_CAVES; + public const LUSH_CAVES = VanillaBiomeIds::LUSH_CAVES; - // 1.18+ Surface Biomes - public const MANGROVE_SWAMP = VanillaBiomeIds::MANGROVE_SWAMP; // Mangrove swamp biome - public const MEADOW = VanillaBiomeIds::MEADOW; // Mountain meadow biome - public const GROVE = VanillaBiomeIds::GROVE; // Grove biome (snowy) - public const SNOWY_SLOPES = VanillaBiomeIds::SNOWY_SLOPES; // Snowy mountain slopes - public const FROZEN_PEAKS = VanillaBiomeIds::FROZEN_PEAKS; // Frozen mountain peaks - public const JAGGED_PEAKS = VanillaBiomeIds::JAGGED_PEAKS; // Jagged mountain peaks - public const STONY_PEAKS = VanillaBiomeIds::STONY_PEAKS; // Stony mountain peaks + public const MANGROVE_SWAMP = VanillaBiomeIds::MANGROVE_SWAMP; + public const MEADOW = VanillaBiomeIds::MEADOW; + public const GROVE = VanillaBiomeIds::GROVE; + public const SNOWY_SLOPES = VanillaBiomeIds::SNOWY_SLOPES; + public const FROZEN_PEAKS = VanillaBiomeIds::FROZEN_PEAKS; + public const JAGGED_PEAKS = VanillaBiomeIds::JAGGED_PEAKS; + public const STONY_PEAKS = VanillaBiomeIds::STONY_PEAKS; } \ No newline at end of file diff --git a/src/muqsit/vanillagenerator/generator/overworld/decorator/CactusDecorator.php b/src/muqsit/vanillagenerator/generator/overworld/decorator/CactusDecorator.php index 9c09814..ad21019 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/decorator/CactusDecorator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/decorator/CactusDecorator.php @@ -6,7 +6,6 @@ use muqsit\vanillagenerator\generator\Decorator; use muqsit\vanillagenerator\generator\object\Cactus; -use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use pocketmine\utils\Random; use pocketmine\world\ChunkManager; use pocketmine\world\format\Chunk; @@ -18,7 +17,7 @@ public function decorate(ChunkManager $world, Random $random, int $chunk_x, int $source_z = $chunk_z << Chunk::COORD_BIT_SIZE; $x = $random->nextBoundedInt(16); $z = $random->nextBoundedInt(16); - $sourceY = DecoratorUtils::getPlantY($random, $chunk, $x, $z); // Safe Y for cactus + $sourceY = $random->nextBoundedInt($chunk->getHighestBlockAt($x, $z) << 1); for($l = 0; $l < 10; ++$l){ $i = $source_x + $random->nextBoundedInt(8) - $random->nextBoundedInt(8); diff --git a/src/muqsit/vanillagenerator/generator/overworld/decorator/DeadBushDecorator.php b/src/muqsit/vanillagenerator/generator/overworld/decorator/DeadBushDecorator.php index 9093071..94d529e 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/decorator/DeadBushDecorator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/decorator/DeadBushDecorator.php @@ -5,7 +5,6 @@ namespace muqsit\vanillagenerator\generator\overworld\decorator; use muqsit\vanillagenerator\generator\Decorator; -use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use pocketmine\block\BlockTypeIds; use pocketmine\block\Leaves; use pocketmine\block\VanillaBlocks; @@ -20,7 +19,7 @@ class DeadBushDecorator extends Decorator{ public function decorate(ChunkManager $world, Random $random, int $chunk_x, int $chunk_z, Chunk $chunk) : void{ $source_x = ($chunk_x << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); $source_z = ($chunk_z << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); - $source_y = DecoratorUtils::getPlantY($random, $chunk, $source_x, $source_z); // Safe Y for dead bush + $source_y = $random->nextBoundedInt($chunk->getHighestBlockAt($source_x & Chunk::COORD_MASK, $source_z & Chunk::COORD_MASK) << 1); while($source_y > 0 && ($world->getBlockAt($source_x, $source_y, $source_z)->getTypeId() === BlockTypeIds::AIR || $world->getBlockAt($source_x, $source_y, $source_z) instanceof Leaves)){ diff --git a/src/muqsit/vanillagenerator/generator/overworld/decorator/DoublePlantDecorator.php b/src/muqsit/vanillagenerator/generator/overworld/decorator/DoublePlantDecorator.php index 4d62130..9688327 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/decorator/DoublePlantDecorator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/decorator/DoublePlantDecorator.php @@ -6,7 +6,6 @@ use muqsit\vanillagenerator\generator\Decorator; use muqsit\vanillagenerator\generator\object\DoubleTallPlant; -use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use muqsit\vanillagenerator\generator\overworld\decorator\types\DoublePlantDecoration; use pocketmine\block\DoublePlant; use pocketmine\utils\Random; @@ -45,7 +44,7 @@ final public function setDoublePlants(DoublePlantDecoration ...$doublePlants) : public function decorate(ChunkManager $world, Random $random, int $chunk_x, int $chunk_z, Chunk $chunk) : void{ $x = $random->nextBoundedInt(16); $z = $random->nextBoundedInt(16); - $source_y = DecoratorUtils::getPlantY($random, $chunk, $x, $z); // Safe Y for double plants + $source_y = $random->nextBoundedInt($chunk->getHighestBlockAt($x, $z) + 32); $species = self::getRandomDoublePlant($random, $this->doublePlants); (new DoubleTallPlant($species))->generate($world, $random, ($chunk_x << Chunk::COORD_BIT_SIZE) + $x, $source_y, ($chunk_z << Chunk::COORD_BIT_SIZE) + $z); diff --git a/src/muqsit/vanillagenerator/generator/overworld/decorator/FlowerDecorator.php b/src/muqsit/vanillagenerator/generator/overworld/decorator/FlowerDecorator.php index 31c5025..d57ed43 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/decorator/FlowerDecorator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/decorator/FlowerDecorator.php @@ -7,7 +7,6 @@ use muqsit\vanillagenerator\generator\Decorator; use muqsit\vanillagenerator\generator\object\Flower; use muqsit\vanillagenerator\generator\overworld\decorator\types\FlowerDecoration; -use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use pocketmine\block\Block; use pocketmine\utils\Random; use pocketmine\world\ChunkManager; @@ -49,7 +48,7 @@ final public function setFlowers(FlowerDecoration ...$flowers) : void{ public function decorate(ChunkManager $world, Random $random, int $chunk_x, int $chunk_z, Chunk $chunk) : void{ $x = $random->nextBoundedInt(16); $z = $random->nextBoundedInt(16); - $source_y = DecoratorUtils::getSurfaceY($random, $chunk, $x, $z, 16, 120); // Limited to Y=120 for flowers + $source_y = $random->nextBoundedInt($chunk->getHighestBlockAt($x & Chunk::COORD_MASK, $z & Chunk::COORD_MASK) + 32); // the flower can change on each decoration pass $flower = self::getRandomFlower($random, $this->flowers); diff --git a/src/muqsit/vanillagenerator/generator/overworld/decorator/LakeDecorator.php b/src/muqsit/vanillagenerator/generator/overworld/decorator/LakeDecorator.php index 6a53903..23250ec 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/decorator/LakeDecorator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/decorator/LakeDecorator.php @@ -24,12 +24,7 @@ public function decorate(ChunkManager $world, Random $random, int $chunk_x, int if($random->nextBoundedInt($this->rarity) === 0){ $source_x = ($chunk_x << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); $source_z = ($chunk_z << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); - - // Limit lake generation to reasonable heights for 1.18+ (Y=0 to Y=120) - // This prevents lakes from generating at extreme heights like Y=300+ - $max_lake_y = min(120, $world->getMaxY() - 20); // Cap at Y=120 or world max - 20 - $source_y = $random->nextBoundedInt($max_lake_y - $this->base_offset) + $this->base_offset; - + $source_y = $random->nextBoundedInt($world->getMaxY() - $this->base_offset) + $this->base_offset; if($this->type->getTypeId() === BlockTypeIds::LAVA && ($source_y >= 64 || $random->nextBoundedInt(10) > 0)){ return; } diff --git a/src/muqsit/vanillagenerator/generator/overworld/decorator/MushroomDecorator.php b/src/muqsit/vanillagenerator/generator/overworld/decorator/MushroomDecorator.php index ef3e18b..d22b35e 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/decorator/MushroomDecorator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/decorator/MushroomDecorator.php @@ -5,7 +5,6 @@ namespace muqsit\vanillagenerator\generator\overworld\decorator; use muqsit\vanillagenerator\generator\Decorator; -use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use pocketmine\block\Block; use pocketmine\block\BlockTypeIds; use pocketmine\block\Dirt; @@ -38,7 +37,7 @@ public function decorate(ChunkManager $world, Random $random, int $chunk_x, int $source_x = ($chunk_x << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); $source_z = ($chunk_z << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); $source_y = $chunk->getHighestBlockAt($source_x & Chunk::COORD_MASK, $source_z & Chunk::COORD_MASK); - $source_y = $this->fixed_height_range ? $source_y : DecoratorUtils::getPlantY($random, $chunk, $source_x, $source_z); + $source_y = $this->fixed_height_range ? $source_y : $random->nextBoundedInt($source_y << 1); $height = $world->getMaxY(); for($i = 0; $i < 64; ++$i){ diff --git a/src/muqsit/vanillagenerator/generator/overworld/decorator/PumpkinDecorator.php b/src/muqsit/vanillagenerator/generator/overworld/decorator/PumpkinDecorator.php index bdb0d5d..8c43dcc 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/decorator/PumpkinDecorator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/decorator/PumpkinDecorator.php @@ -5,7 +5,6 @@ namespace muqsit\vanillagenerator\generator\overworld\decorator; use muqsit\vanillagenerator\generator\Decorator; -use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use pocketmine\block\BlockTypeIds; use pocketmine\block\VanillaBlocks; use pocketmine\math\Facing; @@ -21,7 +20,7 @@ public function decorate(ChunkManager $world, Random $random, int $chunk_x, int if($random->nextBoundedInt(32) === 0){ $source_x = ($chunk_x << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); $source_z = ($chunk_z << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); - $source_y = DecoratorUtils::getPlantY($random, $chunk, $source_x, $source_z); // Safe Y for pumpkins + $source_y = $random->nextBoundedInt($chunk->getHighestBlockAt($source_x & Chunk::COORD_MASK, $source_z & Chunk::COORD_MASK) << 1); for($i = 0; $i < 64; ++$i){ $x = $source_x + $random->nextBoundedInt(8) - $random->nextBoundedInt(8); $z = $source_z + $random->nextBoundedInt(8) - $random->nextBoundedInt(8); diff --git a/src/muqsit/vanillagenerator/generator/overworld/decorator/SugarCaneDecorator.php b/src/muqsit/vanillagenerator/generator/overworld/decorator/SugarCaneDecorator.php index 63c81a8..ddfa6c0 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/decorator/SugarCaneDecorator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/decorator/SugarCaneDecorator.php @@ -6,7 +6,6 @@ use muqsit\vanillagenerator\generator\Decorator; use muqsit\vanillagenerator\generator\object\SugarCane; -use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use pocketmine\utils\Random; use pocketmine\world\ChunkManager; use pocketmine\world\format\Chunk; @@ -20,7 +19,7 @@ public function decorate(ChunkManager $world, Random $random, int $chunk_x, int if($max_y <= 0){ return; } - $source_y = DecoratorUtils::getPlantY($random, $chunk, $source_x, $source_z); // Safe Y for sugar cane + $source_y = $random->nextBoundedInt($max_y << 1); for($j = 0; $j < 20; ++$j){ $x = $source_x + $random->nextBoundedInt(4) - $random->nextBoundedInt(4); $z = $source_z + $random->nextBoundedInt(4) - $random->nextBoundedInt(4); diff --git a/src/muqsit/vanillagenerator/generator/overworld/decorator/TallGrassDecorator.php b/src/muqsit/vanillagenerator/generator/overworld/decorator/TallGrassDecorator.php index 7ce64aa..184adc0 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/decorator/TallGrassDecorator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/decorator/TallGrassDecorator.php @@ -6,7 +6,6 @@ use muqsit\vanillagenerator\generator\Decorator; use muqsit\vanillagenerator\generator\object\TallGrass; -use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use pocketmine\block\VanillaBlocks; use pocketmine\utils\Random; use pocketmine\world\ChunkManager; @@ -29,7 +28,7 @@ public function decorate(ChunkManager $world, Random $random, int $chunk_x, int return; } - $source_y = DecoratorUtils::getPlantY($random, $chunk, $x, $z); // Safe Y generation for plants + $source_y = $random->nextBoundedInt(abs($top_block << 1)); // the grass species can change on each decoration pass (new TallGrass($this->fern_density > 0 && $random->nextFloat() < $this->fern_density ? diff --git a/src/muqsit/vanillagenerator/generator/overworld/decorator/WaterLilyDecorator.php b/src/muqsit/vanillagenerator/generator/overworld/decorator/WaterLilyDecorator.php index ff4c796..7b1db9c 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/decorator/WaterLilyDecorator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/decorator/WaterLilyDecorator.php @@ -5,7 +5,6 @@ namespace muqsit\vanillagenerator\generator\overworld\decorator; use muqsit\vanillagenerator\generator\Decorator; -use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use pocketmine\block\BlockTypeIds; use pocketmine\block\VanillaBlocks; use pocketmine\block\Water; @@ -19,7 +18,7 @@ class WaterLilyDecorator extends Decorator{ public function decorate(ChunkManager $world, Random $random, int $chunk_x, int $chunk_z, Chunk $chunk) : void{ $source_x = ($chunk_x << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); $source_z = ($chunk_z << Chunk::COORD_BIT_SIZE) + $random->nextBoundedInt(16); - $source_y = DecoratorUtils::getPlantY($random, $chunk, $source_x, $source_z); // Safe Y for water lilies + $source_y = $random->nextBoundedInt($chunk->getHighestBlockAt($source_x & Chunk::COORD_MASK, $source_z & Chunk::COORD_MASK) << 1); while($world->getBlockAt($source_x, $source_y - 1, $source_z)->getTypeId() === BlockTypeIds::AIR && $source_y > 0){ --$source_y; } diff --git a/src/muqsit/vanillagenerator/generator/overworld/populator/biome/BiomePopulator.php b/src/muqsit/vanillagenerator/generator/overworld/populator/biome/BiomePopulator.php index 1ad15f6..230b77d 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/populator/biome/BiomePopulator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/populator/biome/BiomePopulator.php @@ -14,7 +14,6 @@ use muqsit\vanillagenerator\generator\overworld\decorator\MushroomDecorator; use muqsit\vanillagenerator\generator\overworld\decorator\PumpkinDecorator; use muqsit\vanillagenerator\generator\overworld\decorator\SugarCaneDecorator; -use muqsit\vanillagenerator\generator\overworld\decorator\SurfaceCaveDecorator; use muqsit\vanillagenerator\generator\overworld\decorator\TallGrassDecorator; use muqsit\vanillagenerator\generator\overworld\decorator\TreeDecorator; use muqsit\vanillagenerator\generator\overworld\decorator\types\FlowerDecoration; @@ -69,7 +68,6 @@ protected static function initFlowers() : void{ protected SugarCaneDecorator $sugar_cane_decorator; protected PumpkinDecorator $pumpkin_decorator; protected CactusDecorator $cactus_decorator; - protected SurfaceCaveDecorator $surface_cave_decorator; /** @var Populator[] */ private array $in_ground_populators = []; @@ -98,12 +96,10 @@ public function __construct(){ $this->sugar_cane_decorator = new SugarCaneDecorator(); $this->pumpkin_decorator = new PumpkinDecorator(); $this->cactus_decorator = new CactusDecorator(); - $this->surface_cave_decorator = new SurfaceCaveDecorator(); array_push($this->in_ground_populators, $this->water_lake_decorator, $this->lava_lake_decorator, - $this->surface_cave_decorator, $this->ore_populator, $this->sand_patch_decorator, $this->clay_patch_decorator, @@ -129,7 +125,6 @@ public function __construct(){ protected function initPopulators() : void{ $this->water_lake_decorator->setAmount(1); $this->lava_lake_decorator->setAmount(1); - $this->surface_cave_decorator->setAmount(1); $this->sand_patch_decorator->setAmount(3); $this->sand_patch_decorator->setRadii(7, 2); $this->sand_patch_decorator->setOverridableBlocks(VanillaBlocks::DIRT(), VanillaBlocks::GRASS()); diff --git a/src/muqsit/vanillagenerator/generator/overworld/populator/biome/FlowerForestPopulator.php b/src/muqsit/vanillagenerator/generator/overworld/populator/biome/FlowerForestPopulator.php index 9d66017..3fdeb89 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/populator/biome/FlowerForestPopulator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/populator/biome/FlowerForestPopulator.php @@ -8,7 +8,6 @@ use muqsit\vanillagenerator\generator\noise\glowstone\SimplexOctaveGenerator; use muqsit\vanillagenerator\generator\object\Flower; use muqsit\vanillagenerator\generator\overworld\biome\BiomeIds; -use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use pocketmine\block\Block; use pocketmine\block\VanillaBlocks; use pocketmine\utils\Random; @@ -59,7 +58,7 @@ public function populateOnGround(ChunkManager $world, Random $random, int $chunk for($i = 0; $i < 100; ++$i){ $x = $random->nextBoundedInt(16); $z = $random->nextBoundedInt(16); - $y = DecoratorUtils::getPlantY($random, $chunk, $x, $z); + $y = $random->nextBoundedInt($chunk->getHighestBlockAt($x, $z) + 32); $noise = ($this->noise_gen->noise($x, $z, 0.5, 0, 2.0, false) + 1.0) / 2.0; $noise = $noise < 0 ? 0 : ($noise > 0.9999 ? 0.9999 : $noise); $flower = self::$FOREST_FLOWERS[(int) ($noise * count(self::$FOREST_FLOWERS))]; diff --git a/src/muqsit/vanillagenerator/generator/overworld/populator/biome/ForestPopulator.php b/src/muqsit/vanillagenerator/generator/overworld/populator/biome/ForestPopulator.php index ebd1ceb..3940cd5 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/populator/biome/ForestPopulator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/populator/biome/ForestPopulator.php @@ -8,7 +8,6 @@ use muqsit\vanillagenerator\generator\object\tree\BirchTree; use muqsit\vanillagenerator\generator\object\tree\GenericTree; use muqsit\vanillagenerator\generator\overworld\biome\BiomeIds; -use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use muqsit\vanillagenerator\generator\overworld\decorator\types\TreeDecoration; use pocketmine\block\DoublePlant; use pocketmine\block\VanillaBlocks; @@ -64,7 +63,7 @@ public function populateOnGround(ChunkManager $world, Random $random, int $chunk for($j = 0; $j < 5; ++$j, ++$i){ $x = $random->nextBoundedInt(16); $z = $random->nextBoundedInt(16); - $y = DecoratorUtils::getPlantY($random, $chunk, $x, $z); + $y = $random->nextBoundedInt($chunk->getHighestBlockAt($x, $z) + 32); $species = self::$DOUBLE_PLANTS[$random->nextBoundedInt(count(self::$DOUBLE_PLANTS))]; if((new DoubleTallPlant($species))->generate($world, $random, $source_x + $x, $y, $source_z + $z)){ ++$i; diff --git a/src/muqsit/vanillagenerator/generator/overworld/populator/biome/OrePopulator.php b/src/muqsit/vanillagenerator/generator/overworld/populator/biome/OrePopulator.php index 6b522fb..8aec43b 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/populator/biome/OrePopulator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/populator/biome/OrePopulator.php @@ -9,11 +9,12 @@ use muqsit\vanillagenerator\generator\overworld\populator\biome\utils\OreTypeHolder; use muqsit\vanillagenerator\generator\Populator; use pocketmine\block\VanillaBlocks; +use pocketmine\block\BlockTypeIds; use pocketmine\utils\Random; use pocketmine\world\ChunkManager; use pocketmine\world\format\Chunk; -class OrePopulator implements Populator{ +class OrePopulator implements Populator { /** @var OreTypeHolder[] */ private array $ores = []; @@ -22,35 +23,50 @@ class OrePopulator implements Populator{ * Creates a populator for dirt, gravel, andesite, diorite, granite; and coal, iron, gold, * redstone, diamond and lapis lazuli ores with full 1.18+ distribution. */ - public function __construct(){ - // Full 1.18+ ore distributions for world height Y=-64 to Y=319 - + public function __construct() { // Stone variants - distribute throughout most of the world $this->addOre(new OreType(VanillaBlocks::DIRT(), -64, 319, 32), 10); $this->addOre(new OreType(VanillaBlocks::GRAVEL(), -64, 319, 32), 8); $this->addOre(new OreType(VanillaBlocks::GRANITE(), -64, 64, 32), 10); $this->addOre(new OreType(VanillaBlocks::DIORITE(), -64, 64, 32), 10); $this->addOre(new OreType(VanillaBlocks::ANDESITE(), -64, 64, 32), 10); - - // Ores with authentic 1.18+ distributions - $this->addOre(new OreType(VanillaBlocks::COAL_ORE(), 0, 190, 16), 20); // Coal: Y=0 to Y=190 - $this->addOre(new OreType(VanillaBlocks::IRON_ORE(), -63, 72, 8), 20); // Iron: Y=-63 to Y=72 - $this->addOre(new OreType(VanillaBlocks::GOLD_ORE(), -64, -48, 8), 4); // Gold: Deep underground Y=-64 to Y=-48 - $this->addOre(new OreType(VanillaBlocks::REDSTONE_ORE(), -64, 15, 7), 8); // Redstone: Y=-64 to Y=15 - $this->addOre(new OreType(VanillaBlocks::DIAMOND_ORE(), -64, 16, 7), 1); // Diamond: Y=-64 to Y=16 (peak at Y=-59) - $this->addOre(new OreType(VanillaBlocks::LAPIS_LAZULI_ORE(), -32, 32, 6), 1); // Lapis: Y=-32 to Y=32 + + // Coal + $this->addOre(new OreType(VanillaBlocks::COAL_ORE(), 1, 190, 16), 20); + $this->addOre(new OreType(VanillaBlocks::DEEPSLATE_COAL_ORE(), -64, 0, 16, BlockTypeIds::DEEPSLATE), 20); + // Iron + $this->addOre(new OreType(VanillaBlocks::IRON_ORE(), 1, 72, 8), 20); + $this->addOre(new OreType(VanillaBlocks::DEEPSLATE_IRON_ORE(), -64, 0, 8, BlockTypeIds::DEEPSLATE), 20); + // Gold + $this->addOre(new OreType(VanillaBlocks::GOLD_ORE(), 1, 32, 8), 4); + $this->addOre(new OreType(VanillaBlocks::DEEPSLATE_GOLD_ORE(), -64, -48, 8, BlockTypeIds::DEEPSLATE), 4); + // Redstone + $this->addOre(new OreType(VanillaBlocks::REDSTONE_ORE(), 1, 15, 7), 8); + $this->addOre(new OreType(VanillaBlocks::DEEPSLATE_REDSTONE_ORE(), -64, 0, 7, BlockTypeIds::DEEPSLATE), 8); + // Diamond + $this->addOre(new OreType(VanillaBlocks::DIAMOND_ORE(), 1, 16, 7), 1); + $this->addOre(new OreType(VanillaBlocks::DEEPSLATE_DIAMOND_ORE(), -64, 0, 7, BlockTypeIds::DEEPSLATE), 1); + // Lapis + $this->addOre(new OreType(VanillaBlocks::LAPIS_LAZULI_ORE(), 1, 32, 6), 1); + $this->addOre(new OreType(VanillaBlocks::DEEPSLATE_LAPIS_LAZULI_ORE(), -32, 0, 6, BlockTypeIds::DEEPSLATE), 1); + // Copper + $this->addOre(new OreType(VanillaBlocks::COPPER_ORE(), 1, 112, 8), 20); + $this->addOre(new OreType(VanillaBlocks::DEEPSLATE_COPPER_ORE(), -64, 0, 8, BlockTypeIds::DEEPSLATE), 20); + // Emerald + $this->addOre(new OreType(VanillaBlocks::EMERALD_ORE(), 4, 31, 3, BlockTypeIds::STONE), 1); + $this->addOre(new OreType(VanillaBlocks::DEEPSLATE_EMERALD_ORE(), -64, 0, 3, BlockTypeIds::DEEPSLATE), 1); } - protected function addOre(OreType $type, int $value) : void{ + protected function addOre(OreType $type, int $value): void { $this->ores[] = new OreTypeHolder($type, $value); } - public function populate(ChunkManager $world, Random $random, int $chunk_x, int $chunk_z, Chunk $chunk) : void{ + public function populate(ChunkManager $world, Random $random, int $chunk_x, int $chunk_z, Chunk $chunk): void { $cx = $chunk_x << Chunk::COORD_BIT_SIZE; $cz = $chunk_z << Chunk::COORD_BIT_SIZE; - foreach($this->ores as $ore_type_holder){ - for($n = 0; $n < $ore_type_holder->value; ++$n){ + foreach($this->ores as $ore_type_holder) { + for($n = 0; $n < $ore_type_holder->value; ++$n) { $source_x = $cx + $random->nextBoundedInt(16); $source_z = $cz + $random->nextBoundedInt(16); $source_y = $ore_type_holder->type->getRandomHeight($random); diff --git a/src/muqsit/vanillagenerator/generator/overworld/populator/biome/PlainsPopulator.php b/src/muqsit/vanillagenerator/generator/overworld/populator/biome/PlainsPopulator.php index 16a7a7f..2ea0ea6 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/populator/biome/PlainsPopulator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/populator/biome/PlainsPopulator.php @@ -10,7 +10,6 @@ use muqsit\vanillagenerator\generator\object\Flower; use muqsit\vanillagenerator\generator\object\TallGrass; use muqsit\vanillagenerator\generator\overworld\biome\BiomeIds; -use muqsit\vanillagenerator\generator\overworld\decorator\DecoratorUtils; use pocketmine\block\Block; use pocketmine\block\VanillaBlocks; use pocketmine\utils\Random; @@ -71,7 +70,7 @@ public function populateOnGround(ChunkManager $world, Random $random, int $chunk for($i = 0; $i < 7; ++$i){ $x = $random->nextBoundedInt(16); $z = $random->nextBoundedInt(16); - $y = DecoratorUtils::getSurfaceY($random, $chunk, $x, $z); + $y = $random->nextBoundedInt($chunk->getHighestBlockAt($x, $z) + 32); (new DoubleTallPlant(VanillaBlocks::DOUBLE_TALLGRASS()))->generate($world, $random, $source_x + $x, $y, $source_z + $z); } } @@ -85,14 +84,14 @@ public function populateOnGround(ChunkManager $world, Random $random, int $chunk for($i = 0; $i < $flower_amount; ++$i){ $x = $random->nextBoundedInt(16); $z = $random->nextBoundedInt(16); - $y = DecoratorUtils::getPlantY($random, $chunk, $x, $z); + $y = $random->nextBoundedInt($chunk->getHighestBlockAt($x, $z) + 32); (new Flower($flower))->generate($world, $random, $source_x + $x, $y, $source_z + $z); } for($i = 0; $i < $tall_grass_amount; ++$i){ $x = $random->nextBoundedInt(16); $z = $random->nextBoundedInt(16); - $y = DecoratorUtils::getPlantY($random, $chunk, $x, $z); + $y = $random->nextBoundedInt($chunk->getHighestBlockAt($x, $z) << 1); (new TallGrass(VanillaBlocks::TALL_GRASS()))->generate($world, $random, $source_x + $x, $y, $source_z + $z); } diff --git a/src/muqsit/vanillagenerator/generator/test/TestGenerator.php b/src/muqsit/vanillagenerator/generator/test/TestGenerator.php new file mode 100644 index 0000000..af29da7 --- /dev/null +++ b/src/muqsit/vanillagenerator/generator/test/TestGenerator.php @@ -0,0 +1,67 @@ + Default: 1 (3x3 chunks visible) + * - centerx= Default: 0 (chunk coordinate) + * - centerz= Default: 0 (chunk coordinate) + * + * You can still pass other Overworld preset options like environment/worldtype. + */ +final class TestGenerator extends OverworldGenerator{ + + private int $centerChunkX = 0; + private int $centerChunkZ = 0; + private int $radius = 1; // radius in chunks; 1 => 3x3 area + + public function __construct(int $seed, string $preset_string){ + parent::__construct($seed, $preset_string); + + // Parse custom options from preset string + $preset = SimpleGeneratorPreset::parse($preset_string); + if($preset->exists("centerx")){ + $this->centerChunkX = (int) $preset->getString("centerx"); + } + if($preset->exists("centerz")){ + $this->centerChunkZ = (int) $preset->getString("centerz"); + } + if($preset->exists("radius")){ + $r = (int) $preset->getString("radius"); + $this->radius = $r < 0 ? 0 : $r; + } + } + + private function isWithinAllowedArea(int $chunk_x, int $chunk_z) : bool{ + return (abs($chunk_x - $this->centerChunkX) <= $this->radius) + && (abs($chunk_z - $this->centerChunkZ) <= $this->radius); + } + + protected function generateChunkData(ChunkManager $world, int $chunk_x, int $chunk_z, VanillaBiomeGrid $biomes) : void{ + if(!$this->isWithinAllowedArea($chunk_x, $chunk_z)){ + // Leave chunk as all air outside the allowed area + return; + } + parent::generateChunkData($world, $chunk_x, $chunk_z, $biomes); + } + + public function populateChunk(ChunkManager $world, int $chunk_x, int $chunk_z) : void{ + if(!$this->isWithinAllowedArea($chunk_x, $chunk_z)){ + // Skip populators outside the allowed area + return; + } + parent::populateChunk($world, $chunk_x, $chunk_z); + } +} From 908ea450507ea3b00a5e7dc90994539a79ed86a3 Mon Sep 17 00:00:00 2001 From: JeanTKG <102908437+jeantkg@users.noreply.github.com> Date: Mon, 13 Oct 2025 21:53:51 +0300 Subject: [PATCH 3/3] cave update --- src/muqsit/vanillagenerator/Loader.php | 2 + .../generator/VanillaGenerator.php | 1 - .../generator/cave/CaveGenerator.php | 217 ++++++++++++------ .../generator/ground/GroundGenerator.php | 16 +- .../generator/ground/MesaGroundGenerator.php | 2 +- .../overworld/OverworldGenerator.php | 47 ++-- .../test/StoneWaterTestGenerator.php | 32 +++ .../generator/test/TestGenerator.php | 2 +- 8 files changed, 209 insertions(+), 110 deletions(-) create mode 100644 src/muqsit/vanillagenerator/generator/test/StoneWaterTestGenerator.php diff --git a/src/muqsit/vanillagenerator/Loader.php b/src/muqsit/vanillagenerator/Loader.php index 64c1df9..b427425 100644 --- a/src/muqsit/vanillagenerator/Loader.php +++ b/src/muqsit/vanillagenerator/Loader.php @@ -6,6 +6,7 @@ use muqsit\vanillagenerator\generator\nether\NetherGenerator; use muqsit\vanillagenerator\generator\overworld\OverworldGenerator; +use muqsit\vanillagenerator\generator\test\StoneWaterTestGenerator; use muqsit\vanillagenerator\generator\test\TestGenerator; use pocketmine\plugin\PluginBase; use pocketmine\world\generator\GeneratorManager; @@ -17,5 +18,6 @@ public function onLoad() : void{ $generator_manager->addGenerator(NetherGenerator::class, "vanilla_nether", fn() => null); $generator_manager->addGenerator(OverworldGenerator::class, "vanilla_overworld", fn() => null); $generator_manager->addGenerator(TestGenerator::class, "test", fn() => null); + $generator_manager->addGenerator(StoneWaterTestGenerator::class, "lol", fn() => null); } } diff --git a/src/muqsit/vanillagenerator/generator/VanillaGenerator.php b/src/muqsit/vanillagenerator/generator/VanillaGenerator.php index 26579bf..2035661 100644 --- a/src/muqsit/vanillagenerator/generator/VanillaGenerator.php +++ b/src/muqsit/vanillagenerator/generator/VanillaGenerator.php @@ -12,7 +12,6 @@ use pocketmine\world\ChunkManager; use pocketmine\world\format\Chunk; use pocketmine\world\generator\Generator; -use pocketmine\world\World; /** * @template T of WorldOctaves diff --git a/src/muqsit/vanillagenerator/generator/cave/CaveGenerator.php b/src/muqsit/vanillagenerator/generator/cave/CaveGenerator.php index e19c29b..a4a1f6b 100644 --- a/src/muqsit/vanillagenerator/generator/cave/CaveGenerator.php +++ b/src/muqsit/vanillagenerator/generator/cave/CaveGenerator.php @@ -4,38 +4,46 @@ namespace muqsit\vanillagenerator\generator\cave; +use muqsit\vanillagenerator\generator\noise\glowstone\SimplexOctaveGenerator; use pocketmine\block\VanillaBlocks; use pocketmine\world\ChunkManager; use pocketmine\utils\Random; -use muqsit\vanillagenerator\generator\noise\glowstone\SimplexOctaveGenerator; use pocketmine\world\format\Chunk; class CaveGenerator { private const CAVE_FREQUENCY = 0.08; private const CAVERN_FREQUENCY = 0.03; - private const TUNNEL_LENGTH_MIN = 40; - private const TUNNEL_LENGTH_MAX = 120; - private const CAVERN_SIZE_MIN = 6; + + private const TUNNEL_LENGTH_MIN = 380; + private const TUNNEL_LENGTH_MAX = 900; + private const CAVERN_SIZE_MIN = 8; private const CAVERN_SIZE_MAX = 15; private const MIN_CAVE_Y = -59; private const MAX_CAVE_Y = 50; private const LAVA_LEVEL = -54; + private const LAVA_MIN_Y = -62; private Random $random; - private SimplexOctaveGenerator $aquiferNoise; + private int $seed; + private SimplexOctaveGenerator $flowNoise; public function __construct(int $seed) { + $this->seed = $seed; $this->random = new Random($seed); - - $this->aquiferNoise = SimplexOctaveGenerator::fromRandomAndOctaves(new Random($seed + 3), 2, 16, 1, 16); - $this->aquiferNoise->setScale(1.0 / 32.0); + + // Low-frequency field to steer tunnels (Perlin worms) + $this->flowNoise = SimplexOctaveGenerator::fromRandomAndOctaves(new Random($seed + 11), 3, 1, 0, 0); + $this->flowNoise->setScale(1.0 / 96.0); } /** - * Generate natural cave systems that flow organically through chunks + * Carve caves in the specified chunk + * @param ChunkManager $world the affected world + * @param int $chunkX chunk X coordinate + * @param int $chunkZ chunk Z coordinate */ public function carveDirectly(ChunkManager $world, int $chunkX, int $chunkZ): void { $chunk = $world->getChunk($chunkX, $chunkZ); @@ -46,13 +54,15 @@ public function carveDirectly(ChunkManager $world, int $chunkX, int $chunkZ): vo /** * Generate caves considering a larger region for natural flow + * @param ChunkManager $world the affected world + * @param int $chunkX chunk X coordinate + * @param int $chunkZ chunk Z coordinate */ private function generateRegionalCaves(ChunkManager $world, int $chunkX, int $chunkZ): void { - $carvedBlocks = 0; - for ($regionX = $chunkX - 1; $regionX <= $chunkX + 1; $regionX++) { for ($regionZ = $chunkZ - 1; $regionZ <= $chunkZ + 1; $regionZ++) { - $this->random->setSeed($regionX * 341873128712 + $regionZ * 132897987541); + // Include world seed so cave layout differs between worlds + $this->random->setSeed(($regionX * 341873128712 + $regionZ * 132897987541) ^ $this->seed); $caveAttempts = 6 + $this->random->nextBoundedInt(8); @@ -64,11 +74,10 @@ private function generateRegionalCaves(ChunkManager $world, int $chunkX, int $ch $startX = $regionBaseX + $this->random->nextBoundedInt(16); $startY = self::MIN_CAVE_Y + $this->random->nextBoundedInt(self::MAX_CAVE_Y - self::MIN_CAVE_Y); $startZ = $regionBaseZ + $this->random->nextBoundedInt(16); - - $carvedBlocks += $this->generateNaturalCaveSystem($world, $startX, $startY, $startZ, $chunkX, $chunkZ); + $this->generateCaveSystem($world, $startX, $startY, $startZ, $chunkX, $chunkZ); } } - + if ($this->random->nextFloat() < self::CAVERN_FREQUENCY) { $regionBaseX = $regionX << 4; $regionBaseZ = $regionZ << 4; @@ -76,94 +85,123 @@ private function generateRegionalCaves(ChunkManager $world, int $chunkX, int $ch $centerX = $regionBaseX + $this->random->nextBoundedInt(16); $centerY = self::MIN_CAVE_Y + $this->random->nextBoundedInt(self::MAX_CAVE_Y - self::MIN_CAVE_Y); $centerZ = $regionBaseZ + $this->random->nextBoundedInt(16); - - $carvedBlocks += $this->generateNaturalCavern($world, $centerX, $centerY, $centerZ, $chunkX, $chunkZ); + $this->generateNaturalCavern($world, $centerX, $centerY, $centerZ, $chunkX, $chunkZ); } + } } } /** - * Generate a natural winding cave system + * Generate SpaghettiCaves: Perlin-worm style long, curvy tunnels with occasional branches and chambers. + * @param ChunkManager $world the affected world + * @param int $startX starting X coordinate + * @param int $startY starting Y coordinate + * @param int $startZ starting Z coordinate + * @param int $targetChunkX chunk X coordinate to limit carving to + * @param int $targetChunkZ chunk Z coordinate to limit carving to + * @return int number of blocks carved */ - private function generateNaturalCaveSystem(ChunkManager $world, int $startX, int $startY, int $startZ, int $targetChunkX, int $targetChunkZ): int { + private function generateCaveSystem(ChunkManager $world, int $startX, int $startY, int $startZ, int $targetChunkX, int $targetChunkZ): int { $length = self::TUNNEL_LENGTH_MIN + $this->random->nextBoundedInt(self::TUNNEL_LENGTH_MAX - self::TUNNEL_LENGTH_MIN); - + $x = (float)$startX; $y = (float)$startY; $z = (float)$startZ; - + + // Initialize direction as a 3D unit vector $yaw = $this->random->nextFloat() * M_PI * 2.0; - $pitch = ($this->random->nextFloat() - 0.5) * 0.4; - + $pitch = ($this->random->nextFloat() - 0.5) * 0.25; + + // Perlin-worm + $yawStrength = 0.35; + $pitchStrength = 0.22; + $maxPitch = 0.46; + $speed = 0.95; + $carvedBlocks = 0; - + for ($i = 0; $i < $length; $i++) { $progress = $i / (float)$length; - $baseRadius = 1.5 + $this->random->nextFloat() * 2.0; - - $sizeVariation = sin($progress * M_PI * 3) * 0.5 + ($this->random->nextFloat() - 0.5) * 0.8; - $radius = max(0.8, $baseRadius + $sizeVariation); - + $baseRadius = 1.9 + $this->random->nextFloat() * 1.9; // thicker tunnels + + // Mild size variation to keep shape organic but not jagged + $sizeVariation = sin($progress * M_PI * 2.4) * 0.35 + ($this->random->nextFloat() - 0.5) * 0.4; + $radius = max(1.1, $baseRadius + $sizeVariation); + $carvedBlocks += $this->carveSphere($world, (int)round($x), (int)round($y), (int)round($z), $radius, $targetChunkX, $targetChunkZ); - - $yawChange = ($this->random->nextFloat() - 0.5) * 0.25; - $pitchChange = ($this->random->nextFloat() - 0.5) * 0.15; - - if ($this->random->nextFloat() < 0.05) { - $yawChange = ($this->random->nextFloat() - 0.5) * 0.8; - $pitchChange = ($this->random->nextFloat() - 0.5) * 0.4; + + // Perlin-worm heading updates from flow field + $noiseYaw = $this->flowNoise->noise($x, $y, $z, 2.0, 0.5, true); // [-1,1] + $noisePitch = $this->flowNoise->noise($x + 1000.0, $y + 1000.0, $z + 1000.0, 2.0, 0.5, true); // decorrelated + $yaw += $noiseYaw * $yawStrength; + $pitch += $noisePitch * $pitchStrength; + $pitch = max(-$maxPitch, min($maxPitch, $pitch)); + + $x += cos($yaw) * cos($pitch) * $speed; + $y += sin($pitch) * $speed; + $z += sin($yaw) * cos($pitch) * $speed; + + // Keep within reasonable Y bounds by reflecting direction softly + if ($y < self::MIN_CAVE_Y + 2) { + $y = (float)(self::MIN_CAVE_Y + 2); + $pitch = abs($pitch); // go up + } elseif ($y > self::MAX_CAVE_Y - 2) { + $y = (float)(self::MAX_CAVE_Y - 2); + $pitch = -abs($pitch); // go down } - - $yaw += $yawChange; - $pitch += $pitchChange; - - $pitch = max(-0.6, min(0.6, $pitch)); - - $moveSpeed = 0.7 + $this->random->nextFloat() * 0.6; - - $x += cos($yaw) * cos($pitch) * $moveSpeed; - $y += sin($pitch) * $moveSpeed; - $z += sin($yaw) * cos($pitch) * $moveSpeed; - - if ($this->random->nextFloat() < 0.04 && $i > 15) { - $branchLength = 20 + $this->random->nextBoundedInt(40); - $carvedBlocks += $this->generateNaturalBranch($world, $x, $y, $z, $branchLength, $targetChunkX, $targetChunkZ); + + // Less frequent branches, but longer + if ($this->random->nextFloat() < 0.03 && $i > 30) { + $branchLength = 100 + $this->random->nextBoundedInt(140); + $carvedBlocks += $this->generateBranch($world, $x, $y, $z, $branchLength, $targetChunkX, $targetChunkZ); } - - if ($this->random->nextFloat() < 0.008 && $i > 20) { + + // Chambers occur rarely to keep the "worm" feel + if ($this->random->nextFloat() < 0.006 && $i > 40) { $carvedBlocks += $this->generateSmallChamber($world, $x, $y, $z, $targetChunkX, $targetChunkZ); } } - + return $carvedBlocks; } /** * Generate natural branch tunnels + * @param ChunkManager $world the affected world + * @param float $startX starting X coordinate + * @param float $startY starting Y coordinate + * @param float $startZ starting Z coordinate + * @param int $length length of the branch + * @param int $targetChunkX chunk X coordinate to limit carving to + * @param int $targetChunkZ chunk Z coordinate to limit carving to + * @return int number of blocks carved */ - private function generateNaturalBranch(ChunkManager $world, float $startX, float $startY, float $startZ, int $length, int $targetChunkX, int $targetChunkZ): int { + private function generateBranch(ChunkManager $world, float $startX, float $startY, float $startZ, int $length, int $targetChunkX, int $targetChunkZ): int { $x = $startX; $y = $startY; $z = $startZ; $yaw = $this->random->nextFloat() * M_PI * 2.0; - $pitch = ($this->random->nextFloat() - 0.5) * 0.5; + $pitch = ($this->random->nextFloat() - 0.5) * 0.3; $carvedBlocks = 0; for ($i = 0; $i < $length; $i++) { $progress = $i / (float)$length; - $radius = 2.0 - $progress * 0.8 + ($this->random->nextFloat() - 0.5) * 0.4; // 2.0 down to ~1.2 - $radius = max(0.6, $radius); + $radius = 2.1 - $progress * 0.55 + ($this->random->nextFloat() - 0.5) * 0.38; + $radius = max(0.9, $radius); $carvedBlocks += $this->carveSphere($world, (int)round($x), (int)round($y), (int)round($z), $radius, $targetChunkX, $targetChunkZ); - $yaw += ($this->random->nextFloat() - 0.5) * 0.2; - $pitch += ($this->random->nextFloat() - 0.5) * 0.12; - $pitch = max(-0.4, min(0.4, $pitch)); + // steer by flow field (weaker than main tunnels) + $noiseYaw = $this->flowNoise->noise($x, $y, $z, 2.0, 0.5, true); + $noisePitch = $this->flowNoise->noise($x + 1000.0, $y + 1000.0, $z + 1000.0, 2.0, 0.5, true); + $yaw += $noiseYaw * 0.22; + $pitch += $noisePitch * 0.15; + $pitch = max(-0.35, min(0.35, $pitch)); - $moveSpeed = 0.6 + $this->random->nextFloat() * 0.4; + $moveSpeed = 0.7 + $this->random->nextFloat() * 0.35; $x += cos($yaw) * cos($pitch) * $moveSpeed; $y += sin($pitch) * $moveSpeed; @@ -174,7 +212,14 @@ private function generateNaturalBranch(ChunkManager $world, float $startX, float } /** - * Generate small natural chambers + * Generate a small chamber by overlapping several spheres + * @param ChunkManager $world the affected world + * @param float $centerX center X coordinate + * @param float $centerY center Y coordinate + * @param float $centerZ center Z coordinate + * @param int $targetChunkX chunk X coordinate to limit carving to + * @param int $targetChunkZ chunk Z coordinate to limit carving to + * @return int number of blocks carved */ private function generateSmallChamber(ChunkManager $world, float $centerX, float $centerY, float $centerZ, int $targetChunkX, int $targetChunkZ): int { $size = 4 + $this->random->nextBoundedInt(6); @@ -197,6 +242,13 @@ private function generateSmallChamber(ChunkManager $world, float $centerX, float /** * Generate natural large caverns + * @param ChunkManager $world the affected world + * @param int $centerX center X coordinate + * @param int $centerY center Y coordinate + * @param int $centerZ center Z coordinate + * @param int $targetChunkX chunk X coordinate to limit carving to + * @param int $targetChunkZ chunk Z coordinate to limit carving to + * @return int number of blocks carved */ private function generateNaturalCavern(ChunkManager $world, int $centerX, int $centerY, int $centerZ, int $targetChunkX, int $targetChunkZ): int { $sizeX = self::CAVERN_SIZE_MIN + $this->random->nextBoundedInt(self::CAVERN_SIZE_MAX - self::CAVERN_SIZE_MIN); @@ -222,7 +274,8 @@ private function generateNaturalCavern(ChunkManager $world, int $centerX, int $c $tunnelCount = 2 + $this->random->nextBoundedInt(4); for ($i = 0; $i < $tunnelCount; $i++) { - $tunnelLength = 15 + $this->random->nextBoundedInt(30); + // Longer tunnels extending from caverns + $tunnelLength = 60 + $this->random->nextBoundedInt(120); $yaw = $this->random->nextFloat() * M_PI * 2.0; $pitch = ($this->random->nextFloat() - 0.5) * 0.4; @@ -250,7 +303,15 @@ private function generateNaturalCavern(ChunkManager $world, int $centerX, int $c } /** - * Carve a spherical area (for tunnels and caverns) + * Carve out a sphere of given radius at the specified coordinates within the target chunk + * @param ChunkManager $world the affected world + * @param int $centerX center X coordinate + * @param int $centerY center Y coordinate + * @param int $centerZ center Z coordinate + * @param float $radius radius of the sphere + * @param int $targetChunkX chunk X coordinate to limit carving to + * @param int $targetChunkZ chunk Z coordinate to limit carving to + * @return int number of blocks carved */ private function carveSphere(ChunkManager $world, int $centerX, int $centerY, int $centerZ, float $radius, int $targetChunkX, int $targetChunkZ): int { $chunk = $world->getChunk($targetChunkX, $targetChunkZ); @@ -300,31 +361,35 @@ private function carveSphere(ChunkManager $world, int $centerX, int $centerY, in } /** - * Apply natural aquifer system with proper water and lava placement + * Apply aquifers such as lava pools at low depths + * @param ChunkManager $world the affected world + * @param int $chunkX chunk X coordinate + * @param int $chunkZ chunk Z coordinate */ public function applyAquifers(ChunkManager $world, int $chunkX, int $chunkZ): void { $chunk = $world->getChunk($chunkX, $chunkZ); if ($chunk === null) return; - - $baseX = $chunkX << 4; - $baseZ = $chunkZ << 4; - $this->random->setSeed($chunkX * 871236847 + $chunkZ * 321487613); + // Include world seed in aquifer-related randomness as well + $this->random->setSeed(($chunkX * 871236847 + $chunkZ * 321487613) ^ $this->seed); - $this->applyLavaPools($chunk, $chunkX, $chunkZ); + $this->applyLavaPools($chunk); } /** - * Apply lava pools - small isolated pools, not connected systems + * Fill air pockets below a certain Y level with lava + * @param Chunk $chunk the affected chunk */ - private function applyLavaPools(Chunk $chunk, int $chunkX, int $chunkZ): void { + private function applyLavaPools(Chunk $chunk): void { $airId = VanillaBlocks::AIR()->getStateId(); $lavaId = VanillaBlocks::LAVA()->getStateId(); - $y = self::LAVA_LEVEL; // -54 + for ($x = 0; $x < 16; $x++) { for ($z = 0; $z < 16; $z++) { - if ($chunk->getBlockStateId($x, $y, $z) === $airId) { - $chunk->setBlockStateId($x, $y, $z, $lavaId); + for ($y = self::LAVA_LEVEL; $y >= self::LAVA_MIN_Y; $y--) { + if ($chunk->getBlockStateId($x, $y, $z) === $airId) { + $chunk->setBlockStateId($x, $y, $z, $lavaId); + } } } } diff --git a/src/muqsit/vanillagenerator/generator/ground/GroundGenerator.php b/src/muqsit/vanillagenerator/generator/ground/GroundGenerator.php index a43d012..d451bea 100644 --- a/src/muqsit/vanillagenerator/generator/ground/GroundGenerator.php +++ b/src/muqsit/vanillagenerator/generator/ground/GroundGenerator.php @@ -52,7 +52,7 @@ final protected function setGroundMaterial(Block $ground_material) : void{ * @param float $surface_noise the amplitude of random variation in surface height */ public function generateTerrainColumn(ChunkManager $world, Random $random, int $x, int $z, int $biome, float $surface_noise) : void{ - $sea_level = 63; + $sea_level = 64; $top_mat = $this->top_material->getStateId(); $ground_mat = $this->ground_material->getStateId(); @@ -82,20 +82,18 @@ public function generateTerrainColumn(ChunkManager $world, Random $random, int $ $world_max_y = $world->getMaxY(); $world_min_y = $world->getMinY(); - // Function to pick stone/deepslate by Y with a transition from Y=8 down to Y=0 - $pickStoneId = static function(int $y, int $abs_x, int $abs_z) use ($stone, $deepslate) : int{ + $rng = $random; + $pickStoneId = static function(int $y) use ($stone, $deepslate, $rng) : int{ if($y <= 0){ return $deepslate; } if($y > 8){ return $stone; } - $prob = (8 - $y) / 8.0; - $h = ($abs_x * 73428767) ^ ($abs_z * 912367) ^ ($y * 42331); - $h ^= ($h >> 13); - $h = ($h * 1274126177) & 0x7fffffff; - $rand01 = ($h % 100000) / 100000.0; - return ($rand01 < $prob) ? $deepslate : $stone; + $p = (8 - $y) / 8.0; + $p = $p * $p; + $rand01 = $rng->nextFloat(); + return ($rand01 < $p) ? $deepslate : $stone; }; for($y = $world_max_y; $y >= $world_min_y; --$y){ diff --git a/src/muqsit/vanillagenerator/generator/ground/MesaGroundGenerator.php b/src/muqsit/vanillagenerator/generator/ground/MesaGroundGenerator.php index 6954446..f785293 100644 --- a/src/muqsit/vanillagenerator/generator/ground/MesaGroundGenerator.php +++ b/src/muqsit/vanillagenerator/generator/ground/MesaGroundGenerator.php @@ -51,7 +51,7 @@ private function initialize(int $seed) : void{ public function generateTerrainColumn(ChunkManager $world, Random $random, int $x, int $z, int $biome, float $surface_noise) : void{ $this->initialize($random->getSeed()); - $sea_level = 63; + $sea_level = 64; $top_mat = $this->top_material; $ground_mat = $this->ground_material; diff --git a/src/muqsit/vanillagenerator/generator/overworld/OverworldGenerator.php b/src/muqsit/vanillagenerator/generator/overworld/OverworldGenerator.php index d26ae4e..f06e278 100644 --- a/src/muqsit/vanillagenerator/generator/overworld/OverworldGenerator.php +++ b/src/muqsit/vanillagenerator/generator/overworld/OverworldGenerator.php @@ -130,13 +130,8 @@ public function __construct(int $seed, string $preset_string){ $preset->exists("worldtype") ? WorldType::fromString($preset->getString("worldtype")) : null, $preset ); - // Ground generation $this->ground_gen = new GroundGenerator(); - - // Use legacy cave generator $this->cave_generator = new CaveGenerator($seed); - - // Add populators $this->addPopulators(new OverworldPopulator(), new SnowPopulator()); } @@ -191,6 +186,7 @@ protected function generateChunkData(ChunkManager $world, int $chunk_x, int $chu $this->cave_generator->carveDirectly($world, $chunk_x, $chunk_z); $this->cave_generator->applyAquifers($world, $chunk_x, $chunk_z); } + } protected function createWorldOctaves() : WorldOctaves{ @@ -221,10 +217,13 @@ protected function createWorldOctaves() : WorldOctaves{ return new WorldOctaves($height, $roughness, $roughness2, $detail, $surface); } + /** + * Generates raw terrain for a chunk at chunk coordinates x,z (does not accept block coords). + */ protected function generateRawTerrain(ChunkManager $world, int $chunk_x, int $chunk_z) : void{ $density = $this->generateTerrainDensity($chunk_x, $chunk_z); - $sea_level = 63; + $sea_level = 64; // Terrain densities are sampled at different resolutions (1/4x on x,z and 1/8x on y by // default) @@ -240,26 +239,18 @@ protected function generateRawTerrain(ChunkManager $world, int $chunk_x, int $ch $stone = VanillaBlocks::STONE()->getStateId(); $deepslate = VanillaBlocks::DEEPSLATE()->getStateId(); - // Decide whether a block placement at a given Y should be stone or deepslate. - // Policy: - // - Y <= 0: always deepslate - // - 0 < Y <= 8: gradual transition, deepslate probability increases linearly from 0 at Y=8 to 1 at Y=0 - // - Y > 8: always stone - $pickStoneId = static function(int $y, int $abs_x, int $abs_z) use ($stone, $deepslate) : int{ + $rng = $this->random; + $pickStoneId = static function(int $y) use ($stone, $deepslate, $rng) : int{ if($y <= 0){ return $deepslate; } if($y > 8){ return $stone; } - // Linear probability for deepslate in (0,8] - $prob = (8 - $y) / 8.0; // at y=8 => 0, y=0 => 1 - // Deterministic hash-based noise per-block to avoid affecting PRNG streams - $h = ($abs_x * 73428767) ^ ($abs_z * 912367) ^ ($y * 42331); - $h ^= ($h >> 13); - $h = ($h * 1274126177) & 0x7fffffff; - $rand01 = ($h % 100000) / 100000.0; // [0,1) - return ($rand01 < $prob) ? $deepslate : $stone; + $p = (8 - $y) / 8.0; + $p = $p * $p; + $rand01 = $rng->nextFloat(); + return ($rand01 < $p) ? $deepslate : $stone; }; /** @var Chunk $chunk */ @@ -358,6 +349,10 @@ protected function generateRawTerrain(ChunkManager $world, int $chunk_x, int $ch } /** + * Generates terrain density for a chunk at chunk coordinates x,z (does not accept block coords). + * The returned array contains 5x5x33 = 825 density values, sampled at every 4 blocks on x and z + * axis, and every 8 blocks on y axis. The values are used later to generate raw terrain by + * re-scaling it to 16x16x256 (or other height if world height is different) using linear interpolation. * @param int $x * @param int $z * @return float[] @@ -468,9 +463,17 @@ protected function generateTerrainDensity(int $x, int $z) : array{ // linear interpolation $dens = $noise_d < 0 ? $noise_r : ($noise_d > 1 ? $noise_r_2 : $noise_r + ($noise_r_2 - $noise_r) * $noise_d); $dens -= $nh; + + $targetSeabedY = 40; + $bandMidY = ($k << 3) + 3 - 64; + if($bandMidY < $targetSeabedY){ + $bias = ($targetSeabedY - $bandMidY) * 0.09; + $dens += $bias; + } ++$index; - if($k > 38){ - $lowering = ($k - 38) / 9.0; // Spread over 9 levels instead of 3 + // Allow mountains up to Y=255 (k=39 => y 248..255), start tapering above that + if($k >= 40){ + $lowering = ($k - 40) / 9.0; // Spread over 9 levels instead of 3 // linear interpolation $dens = $dens * (1.0 - $lowering) + -10.0 * $lowering; } diff --git a/src/muqsit/vanillagenerator/generator/test/StoneWaterTestGenerator.php b/src/muqsit/vanillagenerator/generator/test/StoneWaterTestGenerator.php new file mode 100644 index 0000000..e6d3b8d --- /dev/null +++ b/src/muqsit/vanillagenerator/generator/test/StoneWaterTestGenerator.php @@ -0,0 +1,32 @@ +generateRawTerrain($world, $chunk_x, $chunk_z); + } + + public function populateChunk(ChunkManager $world, int $chunk_x, int $chunk_z) : void{ + // Intentionally no-op: do not run any populators/decorators. + } +} diff --git a/src/muqsit/vanillagenerator/generator/test/TestGenerator.php b/src/muqsit/vanillagenerator/generator/test/TestGenerator.php index af29da7..ba7cd70 100644 --- a/src/muqsit/vanillagenerator/generator/test/TestGenerator.php +++ b/src/muqsit/vanillagenerator/generator/test/TestGenerator.php @@ -25,7 +25,7 @@ final class TestGenerator extends OverworldGenerator{ private int $centerChunkX = 0; private int $centerChunkZ = 0; - private int $radius = 1; // radius in chunks; 1 => 3x3 area + private int $radius = 5; // radius in chunks; 1 => 3x3 area public function __construct(int $seed, string $preset_string){ parent::__construct($seed, $preset_string);