163 lines
5.0 KiB
Rust
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
|
|
)
|
|
}
|