use pumpkin_data::{Block, BlockId, block_properties::is_air}; use pumpkin_util::{ math::{int_provider::IntProvider, position::BlockPos}, random::{RandomGenerator, RandomImpl}, }; use crate::{generation::proto_chunk::GenerationCache, world::WorldPortalExt}; const CLUSTERED_REACH: i32 = 5; const UNCLUSTERED_REACH: i32 = 8; const CLUSTERED_SIZE: i32 = 50; const UNCLUSTERED_SIZE: i32 = 15; pub struct BasaltColumnsFeature { pub height: IntProvider, pub reach: IntProvider, } impl BasaltColumnsFeature { #[expect(clippy::too_many_arguments)] pub fn generate( &self, chunk: &mut T, _block_registry: &dyn WorldPortalExt, _min_y: i8, _height_limit: u16, _feature: pumpkin_data::placed_feature::PlacedFeature, random: &mut RandomGenerator, pos: BlockPos, ) -> bool { if !can_place_at(chunk, pos) { return false; } let column_height = self.height.get(random); let clustered = random.next_f32() < 0.9; let spread = column_height.min(if clustered { CLUSTERED_REACH } else { UNCLUSTERED_REACH }); let count = if clustered { CLUSTERED_SIZE } else { UNCLUSTERED_SIZE }; let mut placed = false; for _ in 0..count { let x = random.next_inbetween_i32(pos.0.x - spread, pos.0.x + spread); let z = random.next_inbetween_i32(pos.0.z - spread, pos.0.z + spread); let iter_pos = BlockPos::new(x, pos.0.y, z); let blocks_to_place_y = column_height - iter_pos.manhattan_distance(pos); if blocks_to_place_y >= 0 { let inner_reach = self.reach.get(random); placed |= place_column(chunk, iter_pos, blocks_to_place_y, inner_reach); } } placed } } fn place_column( chunk: &mut T, origin: BlockPos, column_height: i32, reach: i32, ) -> bool { let mut placed_any = false; for x in (origin.0.x - reach)..=(origin.0.x + reach) { for z in (origin.0.z - reach)..=(origin.0.z + reach) { let pos = BlockPos::new(x, origin.0.y, z); let step_limit = pos.manhattan_distance(origin); let column_pos = if is_air_or_lava_ocean(chunk, pos) { find_surface(chunk, pos, step_limit) } else { find_air(chunk, pos, step_limit) }; if let Some(mut cursor) = column_pos { let mut blocks_y = column_height - step_limit / 2; while blocks_y >= 0 { if is_air_or_lava_ocean(chunk, cursor) { chunk.set_block_state(&cursor.0, Block::BASALT.default_state); cursor = cursor.up(); placed_any = true; } else { let block_id = GenerationCache::get_block_state(chunk, &cursor.0).to_block_id(); if block_id != Block::BASALT.id { break; } cursor = cursor.up(); } blocks_y -= 1; } } } } placed_any } fn find_surface(chunk: &T, pos: BlockPos, mut limit: i32) -> Option { let mut cursor = pos; while cursor.0.y > chunk.bottom_y() as i32 + 1 && limit > 0 { limit -= 1; if can_place_at(chunk, cursor) { return Some(cursor); } cursor = cursor.down(); } None } fn can_place_at(chunk: &T, pos: BlockPos) -> bool { if !is_air_or_lava_ocean(chunk, pos) { return false; } let below = GenerationCache::get_block_state(chunk, &pos.down().0); !is_air(below) && !is_cannot_place_on(below.to_block_id()) } fn find_air(chunk: &T, pos: BlockPos, mut limit: i32) -> Option { let mut cursor = pos; while cursor.0.y < chunk.top_y() as i32 && limit > 0 { limit -= 1; let state = GenerationCache::get_block_state(chunk, &cursor.0); if is_cannot_place_on(state.to_block_id()) { return None; } if is_air(state) { return Some(cursor); } cursor = cursor.up(); } None } fn is_air_or_lava_ocean(chunk: &T, pos: BlockPos) -> bool { let state = GenerationCache::get_block_state(chunk, &pos.0); is_air(state) || (state.to_block_id() == Block::LAVA.id && pos.0.y <= chunk.get_sea_level()) } const fn is_cannot_place_on(id: BlockId) -> bool { matches!( id, BlockId::LAVA | BlockId::BEDROCK | BlockId::MAGMA_BLOCK | BlockId::SOUL_SAND | BlockId::NETHER_BRICKS | BlockId::NETHER_BRICK_FENCE | BlockId::NETHER_BRICK_STAIRS | BlockId::NETHER_WART | BlockId::CHEST | BlockId::SPAWNER ) }