Pumpkin/crates/pumpkin-world/src/generation/feature/features/basalt_columns.rs
2026-09-02 17:36:22 +02:00

163 lines
5.0 KiB
Rust

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<T: GenerationCache>(
&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<T: GenerationCache>(
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<T: GenerationCache>(chunk: &T, pos: BlockPos, mut limit: i32) -> Option<BlockPos> {
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<T: GenerationCache>(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<T: GenerationCache>(chunk: &T, pos: BlockPos, mut limit: i32) -> Option<BlockPos> {
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<T: GenerationCache>(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
)
}