use std::sync::Arc; use async_trait::async_trait; use pumpkin_data::{ Block, BlockState, block_properties::{blocks_movement, get_block_and_state_by_state_id, get_block_by_state_id}, chunk::Biome, tag::Tagable, }; use pumpkin_util::{ HeightMap, math::{position::BlockPos, vector2::Vector2, vector3::Vector3}, random::{RandomGenerator, RandomImpl, get_decorator_seed, xoroshiro128::Xoroshiro}, }; use crate::{ BlockStateId, biome::{BiomeSupplier, MultiNoiseBiomeSupplier, end::TheEndBiomeSupplier, hash_seed}, block::RawBlockState, chunk::CHUNK_AREA, dimension::Dimension, generation::{biome, positions::chunk_pos}, level::Level, world::{BlockAccessor, BlockRegistryExt}, }; use super::{ GlobalRandomConfig, aquifer_sampler::{FluidLevel, FluidLevelSampler, FluidLevelSamplerImpl}, biome_coords, chunk_noise::{CHUNK_DIM, ChunkNoiseGenerator, LAVA_BLOCK, WATER_BLOCK}, feature::placed_features::PLACED_FEATURES, height_limit::HeightLimitView, noise_router::{ multi_noise_sampler::{MultiNoiseSampler, MultiNoiseSamplerBuilderOptions}, proto_noise_router::{DoublePerlinNoiseBuilder, ProtoNoiseRouters}, surface_height_sampler::{ SurfaceHeightEstimateSampler, SurfaceHeightSamplerBuilderOptions, }, }, positions::chunk_pos::{start_block_x, start_block_z}, section_coords, settings::GenerationSettings, surface::{MaterialRuleContext, estimate_surface_height, terrain::SurfaceTerrainBuilder}, }; const AIR_BLOCK: Block = Block::AIR; pub struct StandardChunkFluidLevelSampler { top_fluid: FluidLevel, bottom_fluid: FluidLevel, bottom_y: i32, } impl StandardChunkFluidLevelSampler { pub fn new(top_fluid: FluidLevel, bottom_fluid: FluidLevel) -> Self { let bottom_y = top_fluid .max_y_exclusive() .min(bottom_fluid.max_y_exclusive()); Self { top_fluid, bottom_fluid, bottom_y, } } } impl FluidLevelSamplerImpl for StandardChunkFluidLevelSampler { fn get_fluid_level(&self, _x: i32, y: i32, _z: i32) -> FluidLevel { if y < self.bottom_y { self.bottom_fluid.clone() } else { self.top_fluid.clone() } } } /// Vanilla Chunk Steps /// /// 1. empty: The chunk is not yet loaded or generated. /// /// 2. structures_starts: This step calculates the starting points for structure pieces. For structures that are the starting in this chunk, the position of all pieces are generated and stored. /// /// 3. structures_references: A reference to nearby chunks that have a structures' starting point are stored. /// /// 4. biomes: Biomes are determined and stored. No terrain is generated at this stage. /// /// 5. noise: The base terrain shape and liquid bodies are placed. /// /// 6. surface: The surface of the terrain is replaced with biome-dependent blocks. /// /// 7. carvers: Carvers carve certain parts of the terrain and replace solid blocks with air. /// /// 8. features: Features and structure pieces are placed and heightmaps are generated. /// /// 9. initialize_light: The lighting engine is initialized and light sources are identified. /// /// 10. light: The lighting engine calculates the light level for blocks. /// /// 11. spawn: Mobs are spawned. /// /// 12. full: Generation is done and a chunk can now be loaded. The proto-chunk is now converted to a level chunk and all block updates deferred in the above steps are executed. /// pub struct ProtoChunk<'a> { pub chunk_pos: Vector2, pub noise_sampler: ChunkNoiseGenerator<'a>, // TODO: These can technically go to an even higher level and we can reuse them across chunks pub multi_noise_sampler: MultiNoiseSampler<'a>, pub surface_height_estimate_sampler: SurfaceHeightEstimateSampler<'a>, pub default_block: BlockState, random_config: &'a GlobalRandomConfig, settings: &'a GenerationSettings, biome_mixer_seed: i64, // These are local positions flat_block_map: Box<[BlockStateId]>, flat_biome_map: Box<[&'static Biome]>, /// HEIGHTMAPS /// /// Top block that is not air pub flat_surface_height_map: Box<[i64]>, flat_ocean_floor_height_map: Box<[i64]>, pub flat_motion_blocking_height_map: Box<[i64]>, pub flat_motion_blocking_no_leaves_height_map: Box<[i64]>, // may want to use chunk status } impl<'a> ProtoChunk<'a> { pub fn new( chunk_pos: Vector2, base_router: &'a ProtoNoiseRouters, random_config: &'a GlobalRandomConfig, settings: &'a GenerationSettings, ) -> Self { let generation_shape = &settings.shape; let horizontal_cell_count = CHUNK_DIM / generation_shape.horizontal_cell_block_count(); let sampler = FluidLevelSampler::Chunk(StandardChunkFluidLevelSampler::new( FluidLevel::new( settings.sea_level, settings.default_fluid.get_state().unwrap().block(), ), FluidLevel::new(-54, LAVA_BLOCK), // this is always the same for every dimension )); let height = generation_shape.height; let start_x = chunk_pos::start_block_x(&chunk_pos); let start_z = chunk_pos::start_block_z(&chunk_pos); let sampler = ChunkNoiseGenerator::new( &base_router.noise, random_config, horizontal_cell_count as usize, start_x, start_z, generation_shape, sampler, settings.aquifers_enabled, settings.ore_veins_enabled, ); // TODO: This is duplicate code already in ChunkNoiseGenerator::new let biome_pos = Vector2::new( biome_coords::from_block(start_x), biome_coords::from_block(start_z), ); let horizontal_biome_end = biome_coords::from_block( horizontal_cell_count * generation_shape.horizontal_cell_block_count(), ); let multi_noise_config = MultiNoiseSamplerBuilderOptions::new( biome_pos.x, biome_pos.z, horizontal_biome_end as usize, ); let multi_noise_sampler = MultiNoiseSampler::generate(&base_router.multi_noise, &multi_noise_config); let surface_config = SurfaceHeightSamplerBuilderOptions::new( biome_pos.x, biome_pos.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_estimate_sampler = SurfaceHeightEstimateSampler::generate(&base_router.surface_estimator, &surface_config); let default_block = settings.default_block.get_state().unwrap(); let default_heightmap = vec![i64::MIN; CHUNK_AREA].into_boxed_slice(); Self { chunk_pos, settings, default_block, random_config, noise_sampler: sampler, multi_noise_sampler, surface_height_estimate_sampler, flat_block_map: vec![0; CHUNK_AREA * height as usize].into_boxed_slice(), flat_biome_map: vec![ &Biome::PLAINS; biome_coords::from_block(CHUNK_DIM as usize) * biome_coords::from_block(CHUNK_DIM as usize) * biome_coords::from_block(height as usize) ] .into_boxed_slice(), biome_mixer_seed: hash_seed(random_config.seed), flat_surface_height_map: default_heightmap.clone(), flat_ocean_floor_height_map: default_heightmap.clone(), flat_motion_blocking_height_map: default_heightmap.clone(), flat_motion_blocking_no_leaves_height_map: default_heightmap, } } pub fn generation_settings(&self) -> &GenerationSettings { self.settings } fn maybe_update_surface_height_map(&mut self, pos: &Vector3) { let local_x = (pos.x & 15) as usize; let local_z = (pos.z & 15) as usize; let index = Self::local_position_to_height_map_index(local_x, local_z); let current_height = self.flat_surface_height_map[index]; if pos.y > current_height as i32 { self.flat_surface_height_map[index] = pos.y as i64; } } fn maybe_update_ocean_floor_height_map(&mut self, pos: &Vector3) { let local_x = (pos.x & 15) as usize; let local_z = (pos.z & 15) as usize; let index = Self::local_position_to_height_map_index(local_x, local_z); let current_height = self.flat_ocean_floor_height_map[index]; if pos.y > current_height as i32 { self.flat_ocean_floor_height_map[index] = pos.y as i64; } } fn maybe_update_motion_blocking_height_map(&mut self, pos: &Vector3) { let local_x = (pos.x & 15) as usize; let local_z = (pos.z & 15) as usize; let index = Self::local_position_to_height_map_index(local_x, local_z); let current_height = self.flat_motion_blocking_height_map[index]; if pos.y > current_height as i32 { self.flat_motion_blocking_height_map[index] = pos.y as i64; } } fn maybe_update_motion_blocking_no_leaves_height_map(&mut self, pos: &Vector3) { let local_x = (pos.x & 15) as usize; let local_z = (pos.z & 15) as usize; let index = Self::local_position_to_height_map_index(local_x, local_z); let current_height = self.flat_motion_blocking_no_leaves_height_map[index]; if pos.y > current_height as i32 { self.flat_motion_blocking_no_leaves_height_map[index] = pos.y as i64; } } pub fn get_top_y(&self, heightmap: &HeightMap, pos: &Vector2) -> i64 { match heightmap { HeightMap::WorldSurfaceWg => self.top_block_height_exclusive(pos), HeightMap::WorldSurface => self.top_block_height_exclusive(pos), HeightMap::OceanFloorWg => self.ocean_floor_height_exclusive(pos), HeightMap::OceanFloor => self.ocean_floor_height_exclusive(pos), HeightMap::MotionBlocking => self.top_motion_blocking_block_height_exclusive(pos), HeightMap::MotionBlockingNoLeaves => { self.top_motion_blocking_block_no_leaves_height_exclusive(pos) } } } pub fn top_block_height_exclusive(&self, pos: &Vector2) -> i64 { let local_x = (pos.x & 15) as usize; let local_z = (pos.z & 15) as usize; let index = Self::local_position_to_height_map_index(local_x, local_z); self.flat_surface_height_map[index] + 1 } pub fn ocean_floor_height_exclusive(&self, pos: &Vector2) -> i64 { let local_x = (pos.x & 15) as usize; let local_z = (pos.z & 15) as usize; let index = Self::local_position_to_height_map_index(local_x, local_z); self.flat_ocean_floor_height_map[index] + 1 } pub fn top_motion_blocking_block_height_exclusive(&self, pos: &Vector2) -> i64 { let local_x = (pos.x & 15) as usize; let local_z = (pos.z & 15) as usize; let index = Self::local_position_to_height_map_index(local_x, local_z); self.flat_motion_blocking_height_map[index] + 1 } pub fn top_motion_blocking_block_no_leaves_height_exclusive(&self, pos: &Vector2) -> i64 { let local_x = (pos.x & 15) as usize; let local_z = (pos.z & 15) as usize; let index = Self::local_position_to_height_map_index(local_x, local_z); self.flat_motion_blocking_no_leaves_height_map[index] + 1 } fn local_position_to_height_map_index(x: usize, z: usize) -> usize { x * CHUNK_DIM as usize + z } #[inline] fn local_pos_to_block_index(&self, local_pos: &Vector3) -> usize { #[cfg(debug_assertions)] { assert!(local_pos.x >= 0 && local_pos.x <= 15); assert!(local_pos.y < self.height() as i32); assert!(local_pos.y >= 0); assert!(local_pos.z >= 0 && local_pos.z <= 15); } self.height() as usize * CHUNK_DIM as usize * local_pos.x as usize + CHUNK_DIM as usize * local_pos.y as usize + local_pos.z as usize } #[inline] fn local_biome_pos_to_biome_index(&self, local_biome_pos: &Vector3) -> usize { #[cfg(debug_assertions)] { assert!(local_biome_pos.x >= 0 && local_biome_pos.x <= 3); assert!( local_biome_pos.y < biome_coords::from_chunk(self.height() as i32) && local_biome_pos.y >= 0, "{} - {} vs {}", 0, biome_coords::from_chunk(self.height() as i32), local_biome_pos.y ); assert!(local_biome_pos.z >= 0 && local_biome_pos.z <= 3); } biome_coords::from_block(self.noise_sampler.height() as usize) * biome_coords::from_block(CHUNK_DIM as usize) * local_biome_pos.x as usize + biome_coords::from_block(CHUNK_DIM as usize) * local_biome_pos.y as usize + local_biome_pos.z as usize } #[inline] pub fn is_air(&self, local_pos: &Vector3) -> bool { let state = self.get_block_state(local_pos).to_state(); state.is_air() } #[inline] pub fn get_block_state(&self, local_pos: &Vector3) -> RawBlockState { let local_pos = Vector3::new( local_pos.x & 15, local_pos.y - self.bottom_y() as i32, local_pos.z & 15, ); if local_pos.y < 0 || local_pos.y >= self.height() as i32 { return RawBlockState::AIR; } let index = self.local_pos_to_block_index(&local_pos); RawBlockState(self.flat_block_map[index]) } pub fn set_block_state(&mut self, pos: &Vector3, block_state: &BlockState) { let local_pos = Vector3::new(pos.x & 15, pos.y - self.bottom_y() as i32, pos.z & 15); if local_pos.y < 0 || local_pos.y >= self.height() as i32 { return; } if !block_state.is_air() { self.maybe_update_surface_height_map(pos); } if blocks_movement(block_state) { self.maybe_update_ocean_floor_height_map(pos); } if blocks_movement(block_state) || block_state.is_liquid() { self.maybe_update_motion_blocking_height_map(pos); if let Some(block) = get_block_by_state_id(block_state.id) { if !block.is_tagged_with("minecraft:leaves").unwrap() { { self.maybe_update_motion_blocking_no_leaves_height_map(pos); } } } } let index = self.local_pos_to_block_index(&local_pos); self.flat_block_map[index] = block_state.id; } #[inline] pub fn get_biome(&self, global_biome_pos: &Vector3) -> &'static Biome { let local_pos = Vector3::new( global_biome_pos.x & biome_coords::from_block(15), global_biome_pos.y - biome_coords::from_block(self.bottom_y() as i32), global_biome_pos.z & biome_coords::from_block(15), ); let index = self.local_biome_pos_to_biome_index(&local_pos); self.flat_biome_map[index] } pub fn populate_biomes(&mut self, dimension: Dimension) { let min_y = self.noise_sampler.min_y(); let bottom_section = section_coords::block_to_section(min_y) as i32; let top_section = section_coords::block_to_section(min_y as i32 + self.noise_sampler.height() as i32 - 1); let start_block_x = chunk_pos::start_block_x(&self.chunk_pos); let start_block_z = chunk_pos::start_block_z(&self.chunk_pos); let start_biome_x = biome_coords::from_block(start_block_x); let start_biome_z = biome_coords::from_block(start_block_z); #[cfg(debug_assertions)] let mut indices = (0..self.flat_biome_map.len()).collect::>(); for i in bottom_section..=top_section { let start_block_y = section_coords::section_to_block(i); let start_biome_y = biome_coords::from_block(start_block_y); let biomes_per_section = biome_coords::from_block(CHUNK_DIM) as i32; for x in 0..biomes_per_section { for y in 0..biomes_per_section { for z in 0..biomes_per_section { // panic!("{}:{}", start_y, y); let biome_pos = Vector3::new(start_biome_x + x, start_biome_y + y, start_biome_z + z); let biome = if dimension == Dimension::End { TheEndBiomeSupplier::biome( &biome_pos, &mut self.multi_noise_sampler, dimension, ) } else { MultiNoiseBiomeSupplier::biome( &biome_pos, &mut self.multi_noise_sampler, dimension, ) }; //panic!("Populating biome: {:?} -> {:?}", biome_pos, biome); let local_biome_pos = Vector3 { x, // Make the y start from 0 y: start_biome_y + y - biome_coords::from_block(min_y as i32), z, }; let index = self.local_biome_pos_to_biome_index(&local_biome_pos); #[cfg(debug_assertions)] indices.retain(|i| *i != index); self.flat_biome_map[index] = biome; } } } } #[cfg(debug_assertions)] assert!(indices.is_empty(), "Not all biome indices were set!"); } pub fn populate_noise(&mut self) { let horizontal_cell_block_count = self.noise_sampler.horizontal_cell_block_count(); let vertical_cell_block_count = self.noise_sampler.vertical_cell_block_count(); let horizontal_cells = CHUNK_DIM / horizontal_cell_block_count; let min_y = self.noise_sampler.min_y(); let minimum_cell_y = min_y / vertical_cell_block_count as i8; let cell_height = self.noise_sampler.height() / vertical_cell_block_count as u16; // TODO: Block state updates when we implement those self.noise_sampler.sample_start_density(); for cell_x in 0..horizontal_cells { self.noise_sampler.sample_end_density(cell_x); let sample_start_x = (self.start_cell_x() + cell_x as i32) * horizontal_cell_block_count as i32; for cell_z in 0..horizontal_cells { for cell_y in (0..cell_height).rev() { self.noise_sampler .on_sampled_cell_corners(cell_x, cell_y, cell_z); let sample_start_y = (minimum_cell_y as i32 + cell_y as i32) * vertical_cell_block_count as i32; let sample_start_z = (self.start_cell_z() + cell_z as i32) * horizontal_cell_block_count as i32; for local_y in (0..vertical_cell_block_count).rev() { let block_y = (minimum_cell_y as i32 + cell_y as i32) * vertical_cell_block_count as i32 + local_y as i32; let delta_y = local_y as f64 / vertical_cell_block_count as f64; self.noise_sampler.interpolate_y(delta_y); for local_x in 0..horizontal_cell_block_count { let block_x = self.start_block_x() + cell_x as i32 * horizontal_cell_block_count as i32 + local_x as i32; let delta_x = local_x as f64 / horizontal_cell_block_count as f64; self.noise_sampler.interpolate_x(delta_x); for local_z in 0..horizontal_cell_block_count { let block_z = self.start_block_z() + cell_z as i32 * horizontal_cell_block_count as i32 + local_z as i32; let delta_z = local_z as f64 / horizontal_cell_block_count as f64; self.noise_sampler.interpolate_z(delta_z); // TODO: Can the math here be simplified? Do the above values come // to the same results? let cell_offset_x = block_x - sample_start_x; let cell_offset_y = block_y - sample_start_y; let cell_offset_z = block_z - sample_start_z; #[cfg(debug_assertions)] { assert!(cell_offset_x >= 0); assert!(cell_offset_y >= 0); assert!(cell_offset_z >= 0); } let block_state = self .noise_sampler .sample_block_state( Vector3::new( sample_start_x, sample_start_y, sample_start_z, ), Vector3::new(cell_offset_x, cell_offset_y, cell_offset_z), &mut self.surface_height_estimate_sampler, ) .unwrap_or(self.default_block.clone()); self.set_block_state( &Vector3::new(block_x, block_y, block_z), &block_state, ); } } } } } self.noise_sampler.swap_buffers(); } } pub fn generate_entities(&self) { let start_x = self.start_block_x(); let start_z = self.start_block_z(); let population_seed = Xoroshiro::get_population_seed(self.random_config.seed, start_x, start_z); let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(population_seed)); let biome = self.get_biome(&Vector3::new( start_x, self.bottom_section_coord() as i32 + self.height() as i32 - 1, start_z, )); while random.next_f32() < biome.creature_spawn_probability {} todo!() } pub fn get_biome_for_terrain_gen(&self, global_block_pos: &Vector3) -> &'static Biome { let seed_biome_pos = biome::get_biome_blend( self.bottom_y(), self.height(), self.biome_mixer_seed, global_block_pos, ); self.get_biome(&seed_biome_pos) } /// Constructs the terrain surface, although "surface" is a misnomer as it also places underground blocks like bedrock and deepslate. /// This stage also generates larger decorative structures, such as badlands pillars and icebergs. /// /// It is crucial that biome assignments are determined before this process begins. pub fn build_surface(&mut self) { let start_x = chunk_pos::start_block_x(&self.chunk_pos); let start_z = chunk_pos::start_block_z(&self.chunk_pos); let min_y = self.bottom_y(); let random = &self.random_config.base_random_deriver; let mut noise_builder = DoublePerlinNoiseBuilder::new(self.random_config); let terrain_builder = SurfaceTerrainBuilder::new(&mut noise_builder, random); let mut context = MaterialRuleContext::new( min_y, self.height(), noise_builder, random, &terrain_builder, ); for local_x in 0..16 { for local_z in 0..16 { let x = start_x + local_x; let z = start_z + local_z; let mut top_block = self.top_block_height_exclusive(&Vector2::new(local_x, local_z)) as i32; let biome_y = if self.settings.legacy_random_source { 0 } else { top_block }; let this_biome = self.get_biome_for_terrain_gen(&Vector3::new(x, biome_y, z)); if this_biome == &Biome::ERODED_BADLANDS { terrain_builder.place_badlands_pillar(self, x, z, top_block); // Get the top block again if we placed a pillar! top_block = self.top_block_height_exclusive(&Vector2::new(local_x, local_z)) as i32; } context.init_horizontal(x, z); let mut stone_depth_above = 0; let mut min = i32::MAX; let mut fluid_height = i32::MIN; for y in (min_y as i32..top_block).rev() { let pos = Vector3::new(x, y, z); let state = self.get_block_state(&pos).to_state(); if state.is_air() { stone_depth_above = 0; fluid_height = i32::MIN; continue; } if state.is_liquid() { if fluid_height == i32::MIN { fluid_height = y + 1; } continue; } if min >= y { let shift = min_y << 4; min = shift as i32; for search_y in (min_y as i32 - 1..=y - 1).rev() { if search_y < min_y as i32 { min = search_y + 1; break; } let state = self .get_block_state(&Vector3::new(local_x, search_y, local_z)) .to_block(); // TODO: Is there a better way to check that its not a fluid? if !(state != AIR_BLOCK && state != WATER_BLOCK && state != LAVA_BLOCK) { min = search_y + 1; break; } } } // let biome_pos = Vector3::new(x, biome_y as i32, z); stone_depth_above += 1; let stone_depth_below = y - min + 1; context.init_vertical(stone_depth_above, stone_depth_below, y, fluid_height); // panic!("Blending with biome {:?} at: {:?}", biome, biome_pos); if state.id == self.default_block.id { context.biome = self.get_biome_for_terrain_gen(&context.block_pos); let new_state = self.settings.surface_rule.try_apply(self, &mut context); if let Some(state) = new_state { self.set_block_state(&pos, &state); } } } if this_biome == &Biome::FROZEN_OCEAN || this_biome == &Biome::DEEP_FROZEN_OCEAN { let surface_estimate = estimate_surface_height( &mut context, &mut self.surface_height_estimate_sampler, ); terrain_builder.place_iceberg( self, this_biome, x, z, surface_estimate, top_block, self.settings.sea_level, &self.random_config.base_random_deriver, ); } } } } /// This generates "Features," also known as decorations, which include things like trees, grass, ores, and more. /// Essentially, it encompasses everything above the surface or underground. It's crucial that this step is executed after biomes are generated, /// as the decoration directly depends on the biome. Similarly, running this after the surface is built is logical, as it often involves checking block types. /// For example, flowers are typically placed only on grass blocks. /// /// Features are defined across two separate asset files, each serving a distinct purpose: /// /// 1. First, we determine **whether** to generate a feature and **at which block positions** to place it. /// 2. Then, using the second file, we determine **how** to generate the feature. pub async fn generate_features( &mut self, level: &Arc, block_registry: &dyn BlockRegistryExt, ) { let chunk_pos = self.chunk_pos; let min_y = self.noise_sampler.min_y(); let height = self.noise_sampler.height(); let bottom_section = section_coords::block_to_section(min_y) as i32; let block_pos = BlockPos(Vector3::new( section_coords::section_to_block(chunk_pos.x), bottom_section, section_coords::section_to_block(chunk_pos.z), )); let population_seed = Xoroshiro::get_population_seed(self.random_config.seed, block_pos.0.x, block_pos.0.z); // TODO: This needs to be different depending on what biomes are in the chunk -> affects the // random for (name, feature) in PLACED_FEATURES.iter() { // TODO: Properly set index and step let decorator_seed = get_decorator_seed(population_seed, 0, 0); let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(decorator_seed)); feature .generate( self, level, block_registry, min_y, height, name, &mut random, block_pos, ) .await; } } fn start_cell_x(&self) -> i32 { self.start_block_x() / self.noise_sampler.horizontal_cell_block_count() as i32 } fn start_cell_z(&self) -> i32 { self.start_block_z() / self.noise_sampler.horizontal_cell_block_count() as i32 } fn start_block_x(&self) -> i32 { start_block_x(&self.chunk_pos) } fn start_block_z(&self) -> i32 { start_block_z(&self.chunk_pos) } } #[async_trait] impl BlockAccessor for ProtoChunk<'_> { async fn get_block(&self, position: &BlockPos) -> pumpkin_data::Block { self.get_block_state(&position.0).to_block() } async fn get_block_state(&self, position: &BlockPos) -> pumpkin_data::BlockState { self.get_block_state(&position.0).to_state() } async fn get_block_and_block_state( &self, position: &BlockPos, ) -> (pumpkin_data::Block, pumpkin_data::BlockState) { let id = self.get_block_state(&position.0); get_block_and_state_by_state_id(id.0).unwrap_or((Block::AIR, Block::AIR.default_state)) } } #[cfg(test)] mod test { use std::sync::LazyLock; use pumpkin_data::noise_router::{OVERWORLD_BASE_NOISE_ROUTER, WrapperType}; use pumpkin_util::math::vector2::Vector2; use crate::{ dimension::Dimension, generation::{ GlobalRandomConfig, noise_router::{ density_function::{NoiseFunctionComponentRange, PassThrough}, proto_noise_router::{ProtoNoiseFunctionComponent, ProtoNoiseRouters}, }, settings::{GENERATION_SETTINGS, GeneratorSetting}, }, read_data_from_file, }; use super::ProtoChunk; const SEED: u64 = 0; static RANDOM_CONFIG: LazyLock = LazyLock::new(|| GlobalRandomConfig::new(SEED, false)); // TODO: use legacy when needed static BASE_NOISE_ROUTER: LazyLock = LazyLock::new(|| ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &RANDOM_CONFIG)); const SEED2: u64 = 13579; static RANDOM_CONFIG2: LazyLock = LazyLock::new(|| GlobalRandomConfig::new(SEED2, false)); // TODO: use legacy when needed static BASE_NOISE_ROUTER2: LazyLock = LazyLock::new(|| { ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &RANDOM_CONFIG2) }); #[test] fn test_no_blend_no_beard_only_cell_cache() { // We say no wrapper, but it technically has a top-level cell cache let expected_data: Vec = read_data_from_file!("../../assets/no_blend_no_beard_only_cell_cache_0_0.chunk"); let mut base_router = ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &RANDOM_CONFIG); macro_rules! set_wrappers { ($stack: expr) => { $stack.iter_mut().for_each(|component| { if let ProtoNoiseFunctionComponent::Wrapper(wrapper) = component { match wrapper.wrapper_type() { WrapperType::CellCache => (), _ => { *component = ProtoNoiseFunctionComponent::PassThrough(PassThrough::new( wrapper.input_index(), wrapper.min(), wrapper.max(), )); } } } }); }; } set_wrappers!(base_router.noise.full_component_stack); set_wrappers!(base_router.surface_estimator.full_component_stack); set_wrappers!(base_router.multi_noise.full_component_stack); let surface_config = GENERATION_SETTINGS .get(&GeneratorSetting::Overworld) .unwrap(); let mut chunk = ProtoChunk::new( Vector2::new(0, 0), &base_router, &RANDOM_CONFIG, surface_config, ); chunk.populate_noise(); expected_data .into_iter() .zip(chunk.flat_block_map) .enumerate() .for_each(|(index, (expected, actual))| { if expected != actual { panic!("{} vs {} ({})", expected, actual, index); } }); } #[test] fn test_no_blend_no_beard_only_cell_2d_cache() { // it technically has a top-level cell cache // should be the same as only cell_cache let expected_data: Vec = read_data_from_file!("../../assets/no_blend_no_beard_only_cell_cache_0_0.chunk"); let mut base_router = ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &RANDOM_CONFIG); macro_rules! set_wrappers { ($stack: expr) => { $stack.iter_mut().for_each(|component| { if let ProtoNoiseFunctionComponent::Wrapper(wrapper) = component { match wrapper.wrapper_type() { WrapperType::CellCache => (), WrapperType::Cache2D => (), _ => { *component = ProtoNoiseFunctionComponent::PassThrough(PassThrough::new( wrapper.input_index(), wrapper.min(), wrapper.max(), )); } } } }); }; } set_wrappers!(base_router.noise.full_component_stack); set_wrappers!(base_router.surface_estimator.full_component_stack); set_wrappers!(base_router.multi_noise.full_component_stack); let surface_config = GENERATION_SETTINGS .get(&GeneratorSetting::Overworld) .unwrap(); let mut chunk = ProtoChunk::new( Vector2::new(0, 0), &base_router, &RANDOM_CONFIG, surface_config, ); chunk.populate_noise(); expected_data .into_iter() .zip(chunk.flat_block_map) .enumerate() .for_each(|(index, (expected, actual))| { if expected != actual { panic!("{} vs {} ({})", expected, actual, index); } }); } #[test] fn test_no_blend_no_beard_only_cell_flat_cache() { // it technically has a top-level cell cache let expected_data: Vec = read_data_from_file!( "../../assets/no_blend_no_beard_only_cell_cache_flat_cache_0_0.chunk" ); let mut base_router = ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &RANDOM_CONFIG); macro_rules! set_wrappers { ($stack: expr) => { $stack.iter_mut().for_each(|component| { if let ProtoNoiseFunctionComponent::Wrapper(wrapper) = component { match wrapper.wrapper_type() { WrapperType::CellCache => (), WrapperType::CacheFlat => (), _ => { *component = ProtoNoiseFunctionComponent::PassThrough(PassThrough::new( wrapper.input_index(), wrapper.min(), wrapper.max(), )); } } } }); }; } set_wrappers!(base_router.noise.full_component_stack); set_wrappers!(base_router.surface_estimator.full_component_stack); set_wrappers!(base_router.multi_noise.full_component_stack); let surface_config = GENERATION_SETTINGS .get(&GeneratorSetting::Overworld) .unwrap(); let mut chunk = ProtoChunk::new( Vector2::new(0, 0), &base_router, &RANDOM_CONFIG, surface_config, ); chunk.populate_noise(); expected_data .into_iter() .zip(chunk.flat_block_map) .enumerate() .for_each(|(index, (expected, actual))| { if expected != actual { panic!("{} vs {} ({})", expected, actual, index); } }); } #[test] fn test_no_blend_no_beard_only_cell_once_cache() { // it technically has a top-level cell cache let expected_data: Vec = read_data_from_file!( "../../assets/no_blend_no_beard_only_cell_cache_once_cache_0_0.chunk" ); let mut base_router = ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &RANDOM_CONFIG); macro_rules! set_wrappers { ($stack: expr) => { $stack.iter_mut().for_each(|component| { if let ProtoNoiseFunctionComponent::Wrapper(wrapper) = component { match wrapper.wrapper_type() { WrapperType::CellCache => (), WrapperType::CacheOnce => (), _ => { *component = ProtoNoiseFunctionComponent::PassThrough(PassThrough::new( wrapper.input_index(), wrapper.min(), wrapper.max(), )); } } } }); }; } set_wrappers!(base_router.noise.full_component_stack); set_wrappers!(base_router.surface_estimator.full_component_stack); set_wrappers!(base_router.multi_noise.full_component_stack); let surface_config = GENERATION_SETTINGS .get(&GeneratorSetting::Overworld) .unwrap(); let mut chunk = ProtoChunk::new( Vector2::new(0, 0), &base_router, &RANDOM_CONFIG, surface_config, ); chunk.populate_noise(); expected_data .into_iter() .zip(chunk.flat_block_map) .enumerate() .for_each(|(index, (expected, actual))| { if expected != actual { panic!("{} vs {} ({})", expected, actual, index); } }); } #[test] fn test_no_blend_no_beard_only_cell_interpolated() { // it technically has a top-level cell cache let expected_data: Vec = read_data_from_file!( "../../assets/no_blend_no_beard_only_cell_cache_interpolated_0_0.chunk" ); let mut base_router = ProtoNoiseRouters::generate(&OVERWORLD_BASE_NOISE_ROUTER, &RANDOM_CONFIG); macro_rules! set_wrappers { ($stack: expr) => { $stack.iter_mut().for_each(|component| { if let ProtoNoiseFunctionComponent::Wrapper(wrapper) = component { match wrapper.wrapper_type() { WrapperType::CellCache => (), WrapperType::Interpolated => (), _ => { *component = ProtoNoiseFunctionComponent::PassThrough(PassThrough::new( wrapper.input_index(), wrapper.min(), wrapper.max(), )); } } } }); }; } set_wrappers!(base_router.noise.full_component_stack); set_wrappers!(base_router.surface_estimator.full_component_stack); set_wrappers!(base_router.multi_noise.full_component_stack); let surface_config = GENERATION_SETTINGS .get(&GeneratorSetting::Overworld) .unwrap(); let mut chunk = ProtoChunk::new( Vector2::new(0, 0), &base_router, &RANDOM_CONFIG, surface_config, ); chunk.populate_noise(); expected_data .into_iter() .zip(chunk.flat_block_map) .enumerate() .for_each(|(index, (expected, actual))| { if expected != actual { panic!("{} vs {} ({})", expected, actual, index); } }); } #[test] fn test_no_blend_no_beard() { let expected_data: Vec = read_data_from_file!("../../assets/no_blend_no_beard_0_0.chunk"); let surface_config = GENERATION_SETTINGS .get(&GeneratorSetting::Overworld) .unwrap(); let mut chunk = ProtoChunk::new( Vector2::new(0, 0), &BASE_NOISE_ROUTER, &RANDOM_CONFIG, surface_config, ); chunk.populate_noise(); assert_eq!( expected_data, chunk.flat_block_map.into_iter().collect::>() ); } #[test] fn test_no_blend_no_beard_aquifer() { let expected_data: Vec = read_data_from_file!("../../assets/no_blend_no_beard_7_4.chunk"); let surface_config = GENERATION_SETTINGS .get(&GeneratorSetting::Overworld) .unwrap(); let mut chunk = ProtoChunk::new( Vector2::new(7, 4), &BASE_NOISE_ROUTER, &RANDOM_CONFIG, surface_config, ); chunk.populate_noise(); assert_eq!( expected_data, chunk.flat_block_map.into_iter().collect::>() ); } #[test] fn test_no_blend_no_beard_badlands() { let expected_data: Vec = read_data_from_file!("../../assets/no_blend_no_beard_-595_544.chunk"); let surface_config = GENERATION_SETTINGS .get(&GeneratorSetting::Overworld) .unwrap(); let mut chunk = ProtoChunk::new( Vector2::new(-595, 544), &BASE_NOISE_ROUTER, &RANDOM_CONFIG, surface_config, ); chunk.populate_noise(); expected_data .into_iter() .zip(chunk.flat_block_map) .enumerate() .for_each(|(index, (expected, actual))| { if expected != actual { panic!("expected {}, was {} (at {})", expected, actual, index); } }); } #[test] fn test_no_blend_no_beard_frozen_ocean() { let expected_data: Vec = read_data_from_file!("../../assets/no_blend_no_beard_-119_183.chunk"); let surface_config = GENERATION_SETTINGS .get(&GeneratorSetting::Overworld) .unwrap(); let mut chunk = ProtoChunk::new( Vector2::new(-119, 183), &BASE_NOISE_ROUTER, &RANDOM_CONFIG, surface_config, ); chunk.populate_noise(); expected_data .into_iter() .zip(chunk.flat_block_map) .enumerate() .for_each(|(index, (expected, actual))| { if expected != actual { panic!("expected {}, was {} (at {})", expected, actual, index); } }); } #[test] fn test_no_blend_no_beard_badlands2() { let expected_data: Vec = read_data_from_file!("../../assets/no_blend_no_beard_13579_-6_11.chunk"); let surface_config = GENERATION_SETTINGS .get(&GeneratorSetting::Overworld) .unwrap(); let mut chunk = ProtoChunk::new( Vector2::new(-6, 11), &BASE_NOISE_ROUTER2, &RANDOM_CONFIG2, surface_config, ); chunk.populate_noise(); expected_data .into_iter() .zip(chunk.flat_block_map) .enumerate() .for_each(|(index, (expected, actual))| { if expected != actual { panic!("expected {}, was {} (at {})", expected, actual, index); } }); } #[test] fn test_no_blend_no_beard_badlands3() { let expected_data: Vec = read_data_from_file!("../../assets/no_blend_no_beard_13579_-2_15.chunk"); let surface_config = GENERATION_SETTINGS .get(&GeneratorSetting::Overworld) .unwrap(); let mut chunk = ProtoChunk::new( Vector2::new(-2, 15), &BASE_NOISE_ROUTER2, &RANDOM_CONFIG2, surface_config, ); chunk.populate_noise(); expected_data .into_iter() .zip(chunk.flat_block_map) .enumerate() .for_each(|(index, (expected, actual))| { if expected != actual { panic!("expected {}, was {} (at {})", expected, actual, index); } }); } #[test] fn test_no_blend_no_beard_surface() { let expected_data: Vec = read_data_from_file!("../../assets/no_blend_no_beard_surface_0_0.chunk"); let surface_config = GENERATION_SETTINGS .get(&GeneratorSetting::Overworld) .unwrap(); let mut chunk = ProtoChunk::new( Vector2::new(0, 0), &BASE_NOISE_ROUTER, &RANDOM_CONFIG, surface_config, ); chunk.populate_biomes(Dimension::Overworld); chunk.populate_noise(); chunk.build_surface(); expected_data .into_iter() .zip(chunk.flat_block_map) .enumerate() .for_each(|(index, (expected, actual))| { if expected != actual { panic!("expected {}, was {} (at {})", expected, actual, index); } }); } #[test] fn test_no_blend_no_beard_surface_badlands() { let expected_data: Vec = read_data_from_file!("../../assets/no_blend_no_beard_surface_badlands_-595_544.chunk"); let surface_config = GENERATION_SETTINGS .get(&GeneratorSetting::Overworld) .unwrap(); let mut chunk = ProtoChunk::new( Vector2::new(-595, 544), &BASE_NOISE_ROUTER, &RANDOM_CONFIG, surface_config, ); chunk.populate_biomes(Dimension::Overworld); chunk.populate_noise(); chunk.build_surface(); expected_data .into_iter() .zip(chunk.flat_block_map) .enumerate() .for_each(|(index, (expected, actual))| { if expected != actual { panic!("expected {}, was {} (at {})", expected, actual, index); } }); } #[test] fn test_no_blend_no_beard_surface_badlands2() { let expected_data: Vec = read_data_from_file!("../../assets/no_blend_no_beard_surface_13579_-6_11.chunk"); let surface_config = GENERATION_SETTINGS .get(&GeneratorSetting::Overworld) .unwrap(); let mut chunk = ProtoChunk::new( Vector2::new(-6, 11), &BASE_NOISE_ROUTER2, &RANDOM_CONFIG2, surface_config, ); chunk.populate_biomes(Dimension::Overworld); chunk.populate_noise(); chunk.build_surface(); expected_data .into_iter() .zip(chunk.flat_block_map) .enumerate() .for_each(|(index, (expected, actual))| { if expected != actual { panic!("expected {}, was {} (at {})", expected, actual, index); } }); } #[test] fn test_no_blend_no_beard_surface_badlands3() { let expected_data: Vec = read_data_from_file!("../../assets/no_blend_no_beard_surface_13579_-7_9.chunk"); let surface_config = GENERATION_SETTINGS .get(&GeneratorSetting::Overworld) .unwrap(); let mut chunk = ProtoChunk::new( Vector2::new(-7, 9), &BASE_NOISE_ROUTER2, &RANDOM_CONFIG2, surface_config, ); chunk.populate_biomes(Dimension::Overworld); chunk.populate_noise(); chunk.build_surface(); expected_data .into_iter() .zip(chunk.flat_block_map) .enumerate() .for_each(|(index, (expected, actual))| { if expected != actual { panic!("expected {}, was {} (at {})", expected, actual, index); } }); } #[test] fn test_no_blend_no_beard_surface_biome_blend() { let expected_data: Vec = read_data_from_file!("../../assets/no_blend_no_beard_surface_13579_-2_15.chunk"); let surface_config = GENERATION_SETTINGS .get(&GeneratorSetting::Overworld) .unwrap(); let mut chunk = ProtoChunk::new( Vector2::new(-2, 15), &BASE_NOISE_ROUTER2, &RANDOM_CONFIG2, surface_config, ); chunk.populate_biomes(Dimension::Overworld); chunk.populate_noise(); chunk.build_surface(); expected_data .into_iter() .zip(chunk.flat_block_map) .enumerate() .for_each(|(index, (expected, actual))| { if expected != actual { panic!("expected {}, was {} (at {})", expected, actual, index); } }); } #[test] fn test_no_blend_no_beard_surface_frozen_ocean() { let expected_data: Vec = read_data_from_file!( "../../assets/no_blend_no_beard_surface_frozen_ocean_-119_183.chunk" ); let surface_config = GENERATION_SETTINGS .get(&GeneratorSetting::Overworld) .unwrap(); let mut chunk = ProtoChunk::new( Vector2::new(-119, 183), &BASE_NOISE_ROUTER, &RANDOM_CONFIG, surface_config, ); chunk.populate_biomes(Dimension::Overworld); chunk.populate_noise(); chunk.build_surface(); expected_data .into_iter() .zip(chunk.flat_block_map) .enumerate() .for_each(|(index, (expected, actual))| { if expected != actual { panic!("expected {}, was {} (at {})", expected, actual, index); } }); } }