Pumpkin/pumpkin-world/src/generation/feature/features/root_system.rs
2026-05-19 20:08:58 +02:00

185 lines
5.9 KiB
Rust

use pumpkin_util::{
math::position::BlockPos,
random::{RandomGenerator, RandomImpl},
};
use crate::generation::block_predicate::BlockPredicate;
use crate::generation::block_state_provider::BlockStateProvider;
use crate::generation::proto_chunk::GenerationCache;
use crate::world::WorldPortalExt;
pub struct RootSystemFeature {
pub feature: Box<crate::generation::feature::placed_features::PlacedFeature>,
pub required_vertical_space_for_tree: i32,
pub root_radius: i32,
pub root_replaceable: BlockPredicate,
pub root_state_provider: BlockStateProvider,
pub root_placement_attempts: i32,
pub root_column_max_height: i32,
pub hanging_root_radius: i32,
pub hanging_roots_vertical_span: i32,
pub hanging_root_state_provider: BlockStateProvider,
pub hanging_root_placement_attempts: i32,
pub allowed_vertical_water_for_tree: i32,
pub allowed_tree_position: BlockPredicate,
}
impl RootSystemFeature {
#[allow(clippy::too_many_arguments)]
pub fn generate<T: GenerationCache>(
&self,
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
min_y: i8,
height: u16,
feature_name: pumpkin_data::placed_feature::PlacedFeature,
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
if !self.allowed_tree_position.test(block_registry, chunk, &pos) {
return false;
}
if !self.can_place_tree(chunk, pos) {
return false;
}
if !self.feature.generate(
chunk,
block_registry,
min_y,
height,
feature_name,
random,
pos,
) {
return false;
}
self.place_roots(chunk, block_registry, random, pos);
true
}
fn can_place_tree<T: GenerationCache>(&self, chunk: &T, pos: BlockPos) -> bool {
let mut mutable_pos = pos;
for i in 1..=self.required_vertical_space_for_tree {
mutable_pos = mutable_pos.add(0, 1, 0);
if !self.is_allowed_tree_space(chunk, mutable_pos, i) {
return false;
}
}
true
}
fn is_allowed_tree_space<T: GenerationCache>(
&self,
chunk: &T,
pos: BlockPos,
vertical_space: i32,
) -> bool {
if chunk.is_air(&pos.0) {
return true;
}
// TODO: check for water.
vertical_space <= self.allowed_vertical_water_for_tree
}
fn place_roots<T: GenerationCache>(
&self,
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
random: &mut RandomGenerator,
pos: BlockPos,
) {
self.place_rooted_dirt(chunk, block_registry, random, pos);
self.place_hanging_roots(chunk, block_registry, random, pos);
}
fn place_rooted_dirt<T: GenerationCache>(
&self,
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
random: &mut RandomGenerator,
pos: BlockPos,
) {
for _ in 0..self.root_placement_attempts {
let mut mutable_pos = pos.add(
random.next_bounded_i32(self.root_radius.max(1))
- random.next_bounded_i32(self.root_radius.max(1)),
0,
random.next_bounded_i32(self.root_radius.max(1))
- random.next_bounded_i32(self.root_radius.max(1)),
);
if self
.root_replaceable
.test(block_registry, chunk, &mutable_pos)
{
chunk.set_block_state(
&mutable_pos.0,
self.root_state_provider.get_with_context(
block_registry,
chunk,
random,
mutable_pos,
),
);
}
for _ in 0..self.root_column_max_height {
mutable_pos = mutable_pos.add(0, -1, 0);
if chunk.out_of_height(mutable_pos.0.y as i16)
|| !self
.root_replaceable
.test(block_registry, chunk, &mutable_pos)
{
break;
}
chunk.set_block_state(
&mutable_pos.0,
self.root_state_provider.get_with_context(
block_registry,
chunk,
random,
mutable_pos,
),
);
}
}
}
fn place_hanging_roots<T: GenerationCache>(
&self,
chunk: &mut T,
block_registry: &dyn WorldPortalExt,
random: &mut RandomGenerator,
pos: BlockPos,
) {
for _ in 0..self.hanging_root_placement_attempts {
let mutable_pos = pos.add(
random.next_bounded_i32(self.hanging_root_radius.max(1))
- random.next_bounded_i32(self.hanging_root_radius.max(1)),
random.next_bounded_i32(self.hanging_roots_vertical_span.max(1))
- random.next_bounded_i32(self.hanging_roots_vertical_span.max(1)),
random.next_bounded_i32(self.hanging_root_radius.max(1))
- random.next_bounded_i32(self.hanging_root_radius.max(1)),
);
if chunk.is_air(&mutable_pos.0) {
let state = self.hanging_root_state_provider.get_with_context(
block_registry,
chunk,
random,
mutable_pos,
);
// In vanilla, it checks if it can survive and if the block above is sturdy.
// For now, let's just check if the block above is NOT air.
let above = mutable_pos.add(0, 1, 0);
if !chunk.is_air(&above.0) {
chunk.set_block_state(&mutable_pos.0, state);
}
}
}
}
}