pub mod canyon; pub mod cave; pub mod mask; use crate::ProtoChunk; use crate::generation::GlobalRandomConfig; use crate::generation::generator::VanillaGenerator; use crate::generation::noise::aquifer_sampler::CarverAquiferSampler; use crate::generation::noise::perlin::DoublePerlinNoiseSampler; use crate::generation::noise::router::surface_height_sampler::{ SurfaceHeightEstimateSampler, SurfaceHeightSamplerBuilderOptions, }; use crate::generation::surface::rule::try_apply_material_rule; use crate::generation::surface::terrain::SurfaceTerrainBuilder; use crate::generation::surface::{MaterialRuleContext, steep_material_condition}; use pumpkin_data::block_state::BlockState; use pumpkin_data::carver::{CANYON, CAVE, CAVE_EXTRA_UNDERGROUND, NETHER_CAVE}; use pumpkin_data::carver::{CarverAdditionalConfig, CarverConfig}; use pumpkin_data::chunk_gen_settings::MaterialRule; use pumpkin_data::dimension::Dimension; use pumpkin_data::fluid::Fluid; use pumpkin_util::math::vector2::Vector2; use pumpkin_util::math::vector3::Vector3; use pumpkin_util::random::{RandomGenerator, RandomImpl}; const OVERWORLD_CARVERS: [&CarverConfig; 3] = [&CAVE, &CAVE_EXTRA_UNDERGROUND, &CANYON]; const NETHER_CARVERS: [&CarverConfig; 1] = [&NETHER_CAVE]; pub struct CarverBlockIds { pub air: &'static BlockState, pub cave_air: &'static BlockState, pub lava: &'static BlockState, pub dirt: &'static BlockState, pub grass_block: &'static BlockState, pub mycelium: &'static BlockState, } impl Default for CarverBlockIds { fn default() -> Self { Self::new() } } impl CarverBlockIds { #[must_use] pub fn new() -> Self { Self { air: pumpkin_data::Block::AIR.default_state, cave_air: pumpkin_data::Block::CAVE_AIR.default_state, lava: pumpkin_data::Block::LAVA.default_state, dirt: pumpkin_data::Block::DIRT.default_state, grass_block: pumpkin_data::Block::GRASS_BLOCK.default_state, mycelium: pumpkin_data::Block::MYCELIUM.default_state, } } } pub struct CarvingContext<'a> { pub min_y: i8, pub height: u16, pub random_config: &'a GlobalRandomConfig, pub surface_noise: &'a DoublePerlinNoiseSampler, pub secondary_noise: &'a DoublePerlinNoiseSampler, pub terrain_builder: &'a SurfaceTerrainBuilder, pub sea_level: i32, pub surface_rule: &'a MaterialRule, pub surface_height_sampler: SurfaceHeightEstimateSampler<'a>, pub carver_aquifer: Option>, } pub struct CarveRun<'a, 'b> { pub ctx: &'a mut CarvingContext<'b>, pub chunk: &'a mut ProtoChunk, pub ids: CarverBlockIds, } impl CarvingContext<'_> { pub fn top_material( &mut self, chunk: &mut ProtoChunk, x: i32, y: i32, z: i32, under_fluid: bool, steep: bool, ) -> Option<&'static BlockState> { let mut context = MaterialRuleContext::new( self.min_y, self.height, &self.random_config.base_random_deriver, self.terrain_builder, self.surface_noise, self.secondary_noise, self.sea_level, ); context.init_horizontal(x, z); context.biome = chunk.get_terrain_gen_biome(x, y, z); context.set_steep_material_condition(steep); context.init_vertical(1, 1, y, if under_fluid { y + 1 } else { i32::MIN }); try_apply_material_rule( self.surface_rule, chunk, &mut context, &mut self.surface_height_sampler, ) } } pub trait Carver { fn carve( &self, config: &CarverConfig, run: &mut CarveRun, random: &mut RandomGenerator, chunk_pos: &Vector2, carver_chunk_pos: &Vector2, legacy_random_source: bool, ); } pub fn carve(chunk: &mut ProtoChunk, generator: &VanillaGenerator) { // Vanilla applyCarvers uses a range of 8 chunks (17x17 area) let radius = 8; let chunk_x = chunk.x; let chunk_z = chunk.z; let chunk_pos = Vector2::new(chunk_x, chunk_z); let carvers_to_use = carvers_for_dimension(&generator.dimension); let start_x = crate::generation::positions::chunk_pos::start_block_x(chunk_x); let start_z = crate::generation::positions::chunk_pos::start_block_z(chunk_z); let generation_shape = &generator.settings.shape; let horizontal_cell_count = 16 / generation_shape.horizontal_cell_block_count(); let horizontal_biome_end = crate::generation::biome_coords::from_block( horizontal_cell_count as i32 * generation_shape.horizontal_cell_block_count() as i32, ); let surface_config = SurfaceHeightSamplerBuilderOptions::new( crate::generation::biome_coords::from_block(start_x), crate::generation::biome_coords::from_block(start_z), horizontal_biome_end as usize, generation_shape.min_y as i32, generation_shape.max_y() as i32, generation_shape.vertical_cell_block_count() as usize, ); let surface_height_sampler = SurfaceHeightEstimateSampler::generate( &generator.base_router.surface_estimator, &surface_config, ); let carver_aquifer = generator.settings.aquifers_enabled.then(|| { CarverAquiferSampler::new( chunk_x, chunk_z, &generator.base_router, &generator.random_config, generator.settings, ) }); let mut context = CarvingContext { min_y: generator.dimension.min_y as i8, height: generator.dimension.logical_height as u16, random_config: &generator.random_config, surface_noise: &generator.terrain_cache.surface_noise, secondary_noise: &generator.terrain_cache.secondary_noise, terrain_builder: &generator.terrain_cache.terrain_builder, sea_level: generator.settings.sea_level, surface_rule: &generator.settings.surface_rule, surface_height_sampler, carver_aquifer, }; let mut run = CarveRun { ctx: &mut context, chunk, ids: CarverBlockIds::new(), }; let cave_carver = cave::CaveCarver; let canyon_carver = canyon::CanyonCarver; for dx in -radius..=radius { for dz in -radius..=radius { let carver_x = chunk_x + dx; let carver_z = chunk_z + dz; let carver_chunk_pos = Vector2::new(carver_x, carver_z); // In vanilla, carvers are per-biome. Here we use the hardcoded list but // maintain the random seed logic. for (index, &config) in carvers_to_use.iter().enumerate() { let seed = get_large_feature_seed( generator.random_config.seed + index as u64, carver_x, carver_z, ); let mut carver_random = new_carver_random(seed, generator.settings.legacy_random_source); if should_carve(config, &mut carver_random) { match config.additional { CarverAdditionalConfig::Cave(_) | CarverAdditionalConfig::NetherCave(_) => { cave_carver.carve( config, &mut run, &mut carver_random, &chunk_pos, &carver_chunk_pos, generator.settings.legacy_random_source, ); } CarverAdditionalConfig::Canyon(_) => { canyon_carver.carve( config, &mut run, &mut carver_random, &chunk_pos, &carver_chunk_pos, generator.settings.legacy_random_source, ); } } } } } } } fn should_carve(config: &CarverConfig, random: &mut RandomGenerator) -> bool { random.next_f32() <= config.probability } fn get_large_feature_seed(seed: u64, chunk_x: i32, chunk_z: i32) -> u64 { let mut random = pumpkin_util::random::legacy_rand::LegacyRand::from_seed(seed); let x_scale = random.next_i64(); let z_scale = random.next_i64(); let seed = seed as i64; let result = (chunk_x as i64).wrapping_mul(x_scale) ^ (chunk_z as i64).wrapping_mul(z_scale) ^ seed; result as u64 } const fn new_carver_random(seed: u64, non_vanilla_random: bool) -> RandomGenerator { if non_vanilla_random { RandomGenerator::Xoroshiro(pumpkin_util::random::xoroshiro128::Xoroshiro::from_seed( seed, )) } else { RandomGenerator::Legacy(pumpkin_util::random::legacy_rand::LegacyRand::from_seed( seed, )) } } fn carvers_for_dimension(dimension: &Dimension) -> &'static [&'static CarverConfig] { if dimension == &Dimension::OVERWORLD { &OVERWORLD_CARVERS } else if dimension == &Dimension::THE_NETHER { &NETHER_CARVERS } else { &[] } } fn carve_top_material( run: &mut CarveRun, x: i32, carved_y: i32, z: i32, carved_state: &'static BlockState, has_grass: bool, overworld: bool, ) { if !overworld || !has_grass { return; } let below_y = carved_y - 1; let below_state = run.chunk.get_block_state(&Vector3::new(x, below_y, z)); if below_state != run.ids.dirt.id { return; } let steep = steep_material_condition(run.chunk, x, z); if let Some(top_material) = run.ctx .top_material(run.chunk, x, below_y, z, carved_state.is_liquid(), steep) { run.chunk.set_block_state(x, below_y, z, top_material); schedule_fluid_tick_for_state(run.chunk, x, below_y, z, top_material); } } fn overworld_carve_state( run: &mut CarveRun, config: &CarverConfig, x: i32, y: i32, z: i32, ) -> Option<(&'static BlockState, bool)> { let lava_y = config .lava_level .get_y(run.chunk.bottom_y() as i16, run.chunk.height()); if y <= lava_y { return Some((run.ids.lava, false)); } let Some(aquifer) = run.ctx.carver_aquifer.as_mut() else { return Some((run.ids.air, false)); }; let result = aquifer.compute(&Vector3::new(x, y, z), 0.0); result .state .map(|state| (state, result.should_schedule_fluid_update)) } fn place_carved_block( run: &mut CarveRun, pos: Vector3, state: &'static BlockState, should_schedule_fluid_update: bool, has_grass: bool, overworld: bool, ) { run.chunk.set_block_state(pos.x, pos.y, pos.z, state); if overworld && should_schedule_fluid_update && state.is_liquid() { schedule_fluid_tick_for_state(run.chunk, pos.x, pos.y, pos.z, state); } carve_top_material(run, pos.x, pos.y, pos.z, state, has_grass, overworld); } fn schedule_fluid_tick_for_state( chunk: &mut ProtoChunk, x: i32, y: i32, z: i32, state: &'static BlockState, ) { if state.id == pumpkin_data::Block::WATER.default_state.id { chunk.schedule_fluid_tick(x, y, z, &Fluid::WATER); } else if state.id == pumpkin_data::Block::LAVA.default_state.id { chunk.schedule_fluid_tick(x, y, z, &Fluid::LAVA); } } #[cfg(test)] fn with_carve_run(dimension: Dimension, test: F) where F: FnOnce(&mut CarveRun<'_, '_>), { with_carve_run_options(dimension, None, true, test); } #[cfg(test)] fn with_carve_run_options( dimension: Dimension, surface_rule: Option<&MaterialRule>, use_carver_aquifer: bool, test: F, ) where F: FnOnce(&mut CarveRun<'_, '_>), { use crate::generation::generator::{GeneratorInit, VanillaGenerator, WorldGenerator}; use pumpkin_util::world_seed::Seed; let world_gen = WorldGenerator::Noise(Box::new(VanillaGenerator::new(Seed(42), dimension))); let WorldGenerator::Noise(generator) = &world_gen else { unreachable!() }; let mut chunk = ProtoChunk::new(0, 0, &world_gen); let start_x = crate::generation::positions::chunk_pos::start_block_x(chunk.x); let start_z = crate::generation::positions::chunk_pos::start_block_z(chunk.z); let generation_shape = &generator.settings.shape; let horizontal_cell_count = 16 / generation_shape.horizontal_cell_block_count(); let horizontal_biome_end = crate::generation::biome_coords::from_block( horizontal_cell_count as i32 * generation_shape.horizontal_cell_block_count() as i32, ); let surface_config = SurfaceHeightSamplerBuilderOptions::new( crate::generation::biome_coords::from_block(start_x), crate::generation::biome_coords::from_block(start_z), horizontal_biome_end as usize, generation_shape.min_y as i32, generation_shape.max_y() as i32, generation_shape.vertical_cell_block_count() as usize, ); let surface_height_sampler = SurfaceHeightEstimateSampler::generate( &generator.base_router.surface_estimator, &surface_config, ); let carver_aquifer = use_carver_aquifer.then(|| { CarverAquiferSampler::new( chunk.x, chunk.z, &generator.base_router, &generator.random_config, generator.settings, ) }); let mut context = CarvingContext { min_y: generator.dimension.min_y as i8, height: generator.dimension.logical_height as u16, random_config: &generator.random_config, surface_noise: &generator.terrain_cache.surface_noise, secondary_noise: &generator.terrain_cache.secondary_noise, terrain_builder: &generator.terrain_cache.terrain_builder, sea_level: generator.settings.sea_level, surface_rule: surface_rule.unwrap_or(&generator.settings.surface_rule), surface_height_sampler, carver_aquifer, }; let mut run = CarveRun { ctx: &mut context, chunk: &mut chunk, ids: CarverBlockIds::new(), }; test(&mut run); } #[cfg(test)] mod tests { use super::*; use pumpkin_data::Block; use pumpkin_data::chunk_gen_settings::{ BlockMaterialRule, ConditionMaterialRule, MaterialCondition, SequenceMaterialRule, WaterMaterialCondition, }; static PODZOL_RULE: MaterialRule = MaterialRule::Block(BlockMaterialRule { result_state: Block::PODZOL.default_state, }); static GRASS_RULE: MaterialRule = MaterialRule::Block(BlockMaterialRule { result_state: Block::GRASS_BLOCK.default_state, }); static WATER_SENSITIVE_RULES: [MaterialRule; 2] = [ MaterialRule::Condition(ConditionMaterialRule { if_true: MaterialCondition::Water(WaterMaterialCondition { offset: 0, surface_depth_multiplier: 0, add_stone_depth: false, }), then_run: &GRASS_RULE, }), MaterialRule::Block(BlockMaterialRule { result_state: Block::DIRT.default_state, }), ]; static WATER_SENSITIVE_RULE: MaterialRule = MaterialRule::Sequence(SequenceMaterialRule { sequence: &WATER_SENSITIVE_RULES, }); #[test] fn overworld_has_aquifer() { with_carve_run(Dimension::OVERWORLD, |run| { assert!(run.ctx.carver_aquifer.is_some()); }); } #[test] fn restores_surface() { with_carve_run_options(Dimension::OVERWORLD, Some(&PODZOL_RULE), false, |run| { let x = 4; let y = 70; let z = 5; run.chunk .set_block_state(x, y - 1, z, Block::DIRT.default_state); carve_top_material(run, x, y, z, Block::AIR.default_state, true, true); assert_eq!( run.chunk.get_block_state(&Vector3::new(x, y - 1, z)), Block::PODZOL.default_state.id, ); }); } #[test] fn skips_surface_restore() { with_carve_run_options(Dimension::OVERWORLD, Some(&PODZOL_RULE), false, |run| { let x = 4; let y = 70; let z = 5; run.chunk .set_block_state(x, y - 1, z, Block::DIRT.default_state); carve_top_material(run, x, y, z, Block::AIR.default_state, false, true); assert_eq!( run.chunk.get_block_state(&Vector3::new(x, y - 1, z)), Block::DIRT.default_state.id, ); run.chunk .set_block_state(x, y - 1, z, Block::STONE.default_state); carve_top_material(run, x, y, z, Block::AIR.default_state, true, true); assert_eq!( run.chunk.get_block_state(&Vector3::new(x, y - 1, z)), Block::STONE.default_state.id, ); run.chunk .set_block_state(x, y - 1, z, Block::DIRT.default_state); carve_top_material(run, x, y, z, Block::AIR.default_state, true, false); assert_eq!( run.chunk.get_block_state(&Vector3::new(x, y - 1, z)), Block::DIRT.default_state.id, ); }); } #[test] fn passes_fluid_to_rule() { with_carve_run_options( Dimension::OVERWORLD, Some(&WATER_SENSITIVE_RULE), false, |run| { let x = 6; let y = 70; let z = 7; let dry = run .ctx .top_material(run.chunk, x, y - 1, z, false, false) .unwrap(); let under_fluid = run .ctx .top_material(run.chunk, x, y - 1, z, true, false) .unwrap(); assert_eq!(dry.id, Block::GRASS_BLOCK.default_state.id); assert_eq!(under_fluid.id, Block::DIRT.default_state.id); }, ); } #[test] fn steep_matches_vanilla() { with_carve_run(Dimension::OVERWORLD, |run| { let x = 5; let z = 5; run.chunk.flat_surface_height_map = [64; crate::chunk::CHUNK_AREA]; set_surface_height(run.chunk, x, z - 1, 60); set_surface_height(run.chunk, x, z + 1, 64); assert!(steep_material_condition(run.chunk, x, z)); run.chunk.flat_surface_height_map = [64; crate::chunk::CHUNK_AREA]; set_surface_height(run.chunk, x, z - 1, 64); set_surface_height(run.chunk, x, z + 1, 60); assert!(!steep_material_condition(run.chunk, x, z)); run.chunk.flat_surface_height_map = [64; crate::chunk::CHUNK_AREA]; set_surface_height(run.chunk, x - 1, z, 64); set_surface_height(run.chunk, x + 1, z, 60); assert!(steep_material_condition(run.chunk, x, z)); run.chunk.flat_surface_height_map = [64; crate::chunk::CHUNK_AREA]; set_surface_height(run.chunk, x - 1, z, 60); set_surface_height(run.chunk, x + 1, z, 64); assert!(!steep_material_condition(run.chunk, x, z)); }); } fn set_surface_height(chunk: &mut ProtoChunk, x: i32, z: i32, height: i16) { let index = (x & 15) as usize * 16 + (z & 15) as usize; chunk.flat_surface_height_map[index] = height; } }