Pumpkin/pumpkin-world/src/generation/feature/placed_features.rs
2026-05-19 20:19:46 +02:00

538 lines
17 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use pumpkin_data::block_properties::is_air;
use pumpkin_data::{Block, BlockDirection};
use pumpkin_util::HeightMap;
use std::collections::HashMap;
use std::iter;
use std::sync::LazyLock;
use pumpkin_util::biome::FOLIAGE_NOISE;
use pumpkin_util::math::int_provider::IntProvider;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::random::{RandomGenerator, RandomImpl};
use crate::block::RawBlockState;
use crate::generation::block_predicate::BlockPredicate;
use crate::generation::height_provider::HeightProvider;
use crate::generation::proto_chunk::GenerationCache;
use crate::world::WorldPortalExt;
use super::configured_features::{CONFIGURED_FEATURES, ConfiguredFeature};
pub static PLACED_FEATURES: LazyLock<
HashMap<pumpkin_data::placed_feature::PlacedFeature, PlacedFeature>,
> = LazyLock::new(build_placed_features);
pub enum PlacedFeatureWrapper {
Direct(PlacedFeature),
Named(pumpkin_data::placed_feature::PlacedFeature),
}
impl PlacedFeatureWrapper {
pub fn get(&self) -> &PlacedFeature {
match self {
Self::Named(name) => PLACED_FEATURES.get(name).unwrap(),
Self::Direct(feature) => feature,
}
}
}
pub struct PlacedFeature {
/// The name of the configuired feature
pub feature: Feature,
pub placement: Vec<PlacementModifier>,
}
pub enum Feature {
Named(pumpkin_data::configured_feature::ConfiguredFeature),
Inlined(Box<ConfiguredFeature>),
}
impl PlacedFeature {
#[expect(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, // This placed feature
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
let mut stream: Vec<BlockPos> = vec![pos];
for modifier in &self.placement {
let mut new_stream = Vec::with_capacity(stream.len());
for block_pos in stream {
let positions = modifier.get_positions(
chunk,
block_registry,
min_y,
height,
feature_name,
random,
block_pos,
);
new_stream.extend(positions);
}
stream = new_stream;
}
let feature = match &self.feature {
Feature::Named(name) => CONFIGURED_FEATURES
.get(name)
.expect("Name: {name:?} not found"),
Feature::Inlined(feature) => feature,
};
let mut ret = false;
for pos in stream {
if feature.generate(
chunk,
block_registry,
min_y,
height,
feature_name,
random,
pos,
) {
ret = true;
}
}
ret
}
}
pub enum PlacementModifier {
BlockPredicateFilter(BlockFilterPlacementModifier),
RarityFilter(RarityFilterPlacementModifier),
SurfaceRelativeThresholdFilter(SurfaceThresholdFilterPlacementModifier),
SurfaceWaterDepthFilter(SurfaceWaterDepthFilterPlacementModifier),
Biome(BiomePlacementModifier),
Count(CountPlacementModifier),
NoiseBasedCount(NoiseBasedCountPlacementModifier),
NoiseThresholdCount(NoiseThresholdCountPlacementModifier),
CountOnEveryLayer(CountOnEveryLayerPlacementModifier),
EnvironmentScan(EnvironmentScanPlacementModifier),
Heightmap(HeightmapPlacementModifier),
HeightRange(HeightRangePlacementModifier),
InSquare(SquarePlacementModifier),
RandomOffset(RandomOffsetPlacementModifier),
FixedPlacement(Vec<BlockPos>),
}
impl PlacementModifier {
#[expect(clippy::too_many_arguments)]
pub fn get_positions<T: GenerationCache>(
&self,
chunk: &T,
block_registry: &dyn WorldPortalExt,
min_y: i8,
height: u16,
feature: pumpkin_data::placed_feature::PlacedFeature,
random: &mut RandomGenerator,
pos: BlockPos,
) -> Box<dyn Iterator<Item = BlockPos>> {
match self {
Self::BlockPredicateFilter(modifier) => {
modifier.get_positions(block_registry, chunk, feature, random, pos)
}
Self::RarityFilter(modifier) => {
modifier.get_positions(block_registry, chunk, feature, random, pos)
}
Self::SurfaceRelativeThresholdFilter(modifier) => {
modifier.get_positions(block_registry, chunk, feature, random, pos)
}
Self::SurfaceWaterDepthFilter(modifier) => {
modifier.get_positions(block_registry, chunk, feature, random, pos)
}
Self::Biome(modifier) => {
modifier.get_positions(block_registry, chunk, feature, random, pos)
}
Self::Count(modifier) => modifier.get_positions(random, pos),
Self::NoiseBasedCount(modifier) => Box::new(modifier.get_positions(random, pos)),
Self::NoiseThresholdCount(modifier) => modifier.get_positions(random, pos),
Self::CountOnEveryLayer(modifier) => modifier.get_positions(random, chunk, pos),
Self::EnvironmentScan(modifier) => modifier.get_positions(chunk, block_registry, pos),
Self::Heightmap(modifier) => modifier.get_positions(chunk, min_y, height, random, pos),
Self::HeightRange(modifier) => modifier.get_positions(min_y, height, random, pos),
Self::InSquare(_) => SquarePlacementModifier::get_positions(random, pos),
Self::RandomOffset(modifier) => modifier.get_positions(random, pos),
Self::FixedPlacement(positions) => Box::new(positions.clone().into_iter()),
}
}
}
pub struct NoiseBasedCountPlacementModifier {
pub noise_to_count_ratio: i32,
pub noise_factor: f64,
pub noise_offset: f64,
}
impl CountPlacementModifierBase for NoiseBasedCountPlacementModifier {
fn get_count(&self, _random: &mut RandomGenerator, pos: BlockPos) -> i32 {
let noise = FOLIAGE_NOISE
.sample(
pos.0.x as f64 / self.noise_factor,
pos.0.z as f64 / self.noise_factor,
false,
)
.max(0.0); // TODO: max is wrong
((noise + self.noise_offset) * self.noise_to_count_ratio as f64).ceil() as i32
}
}
pub struct NoiseThresholdCountPlacementModifier {
pub noise_level: f64,
pub below_noise: i32,
pub above_noise: i32,
}
impl CountPlacementModifierBase for NoiseThresholdCountPlacementModifier {
fn get_count(&self, _random: &mut RandomGenerator, pos: BlockPos) -> i32 {
let noise = FOLIAGE_NOISE.sample(pos.0.x as f64 / 200.0, pos.0.z as f64 / 200.0, false);
if noise < self.noise_level {
self.below_noise
} else {
self.above_noise
}
}
}
pub struct EnvironmentScanPlacementModifier {
pub direction_of_search: BlockDirection,
pub target_condition: BlockPredicate,
pub allowed_search_condition: Option<BlockPredicate>,
pub max_steps: i32,
}
impl EnvironmentScanPlacementModifier {
pub fn get_positions<T: GenerationCache>(
&self,
chunk: &T,
block_registry: &dyn WorldPortalExt,
pos: BlockPos,
) -> Box<dyn Iterator<Item = BlockPos>> {
let allowed_search_condition = self
.allowed_search_condition
.as_ref()
.unwrap_or(&BlockPredicate::AlwaysTrue);
if !allowed_search_condition.test(block_registry, chunk, &pos) {
return Box::new(iter::empty());
}
let mut pos = pos;
for _ in 0..self.max_steps {
if self.target_condition.test(block_registry, chunk, &pos) {
return Box::new(iter::once(pos));
}
pos = pos.offset(self.direction_of_search.to_offset());
if chunk.out_of_height(pos.0.y as i16) {
return Box::new(iter::empty());
}
if !allowed_search_condition.test(block_registry, chunk, &pos) {
break;
}
}
if self.target_condition.test(block_registry, chunk, &pos) {
return Box::new(iter::once(pos));
}
Box::new(iter::empty())
}
}
pub struct RandomOffsetPlacementModifier {
pub xz_spread: IntProvider,
pub y_spread: IntProvider,
}
impl RandomOffsetPlacementModifier {
pub fn get_positions(
&self,
random: &mut RandomGenerator,
pos: BlockPos,
) -> Box<dyn Iterator<Item = BlockPos>> {
let x = pos.0.x + self.xz_spread.get(random);
let y = pos.0.y + self.y_spread.get(random);
let z = pos.0.z + self.xz_spread.get(random);
Box::new(iter::once(BlockPos(Vector3::new(x, y, z))))
}
}
pub struct CountOnEveryLayerPlacementModifier {
pub count: IntProvider,
}
impl CountOnEveryLayerPlacementModifier {
pub fn get_positions<T: GenerationCache>(
&self,
random: &mut RandomGenerator,
chunk: &T,
pos: BlockPos,
) -> Box<dyn Iterator<Item = BlockPos>> {
let mut positions = Vec::new(); // Using a Vec to collect results, analogous to Stream.builder()
let mut i = 0; // Represents the 'targetY' in findPos
let mut bl;
loop {
bl = false;
for _j in 0..self.count.get(random) {
let x = random.next_bounded_i32(16) + pos.0.x;
let z = random.next_bounded_i32(16) + pos.0.z;
let y = chunk.top_motion_blocking_block_height_exclusive(x, z);
let n = Self::find_pos(chunk, x, y, z, i);
if n == i32::MAX {
continue;
}
positions.push(BlockPos::new(x, n, z));
bl = true;
}
i += 1;
if !bl {
break;
}
}
Box::new(positions.into_iter())
}
fn find_pos<T: GenerationCache>(chunk: &T, x: i32, y: i32, z: i32, target_y: i32) -> i32 {
let mut mutable_pos = BlockPos::new(x, y, z);
let mut found_count = 0;
let mut current_block_state = GenerationCache::get_block_state(chunk, &mutable_pos.0);
for j in (chunk.bottom_y() as i32 + 1..=y).rev() {
mutable_pos.0.y = j - 1;
let next_block_state = GenerationCache::get_block_state(chunk, &mutable_pos.0);
if !Self::blocks_spawn(&next_block_state)
&& Self::blocks_spawn(&current_block_state)
&& next_block_state.to_block_id() != Block::BEDROCK
{
if found_count == target_y {
return mutable_pos.0.y + 1;
}
found_count += 1;
}
current_block_state = next_block_state;
}
i32::MAX
}
fn blocks_spawn(state: &RawBlockState) -> bool {
let block = state.to_block_id();
is_air(state.0) || block == Block::WATER || block == Block::LAVA
}
}
pub struct BlockFilterPlacementModifier {
pub predicate: BlockPredicate,
}
impl ConditionalPlacementModifier for BlockFilterPlacementModifier {
fn should_place<T: GenerationCache>(
&self,
block_registry: &dyn WorldPortalExt,
_feature: pumpkin_data::placed_feature::PlacedFeature,
chunk: &T,
_random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
self.predicate.test(block_registry, chunk, &pos)
}
}
pub struct SurfaceThresholdFilterPlacementModifier {
pub heightmap: HeightMap,
pub min_inclusive: Option<i32>,
pub max_inclusive: Option<i32>,
}
impl ConditionalPlacementModifier for SurfaceThresholdFilterPlacementModifier {
fn should_place<T: GenerationCache>(
&self,
_block_registry: &dyn WorldPortalExt,
_feature: pumpkin_data::placed_feature::PlacedFeature,
chunk: &T,
_random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
let y = chunk.get_top_y(&self.heightmap, pos.0.x, pos.0.z);
let min = y.saturating_add(self.min_inclusive.unwrap_or(i32::MIN));
let max = y.saturating_add(self.max_inclusive.unwrap_or(i32::MAX));
min <= pos.0.y && pos.0.y <= max
}
}
pub struct RarityFilterPlacementModifier {
pub chance: u32,
}
impl ConditionalPlacementModifier for RarityFilterPlacementModifier {
fn should_place<T: GenerationCache>(
&self,
_block_registry: &dyn WorldPortalExt,
_feature: pumpkin_data::placed_feature::PlacedFeature,
_chunk: &T,
random: &mut RandomGenerator,
_pos: BlockPos,
) -> bool {
random.next_f32() < 1.0 / self.chance as f32
}
}
pub struct SquarePlacementModifier;
impl SquarePlacementModifier {
pub fn get_positions(
random: &mut RandomGenerator,
pos: BlockPos,
) -> Box<dyn Iterator<Item = BlockPos>> {
let x = random.next_bounded_i32(16) + pos.0.x;
let z = random.next_bounded_i32(16) + pos.0.z;
Box::new(iter::once(BlockPos(Vector3::new(x, pos.0.y, z))))
}
}
pub struct CountPlacementModifier {
pub count: IntProvider,
}
impl CountPlacementModifierBase for CountPlacementModifier {
fn get_count(&self, random: &mut RandomGenerator, _pos: BlockPos) -> i32 {
self.count.get(random)
}
}
pub struct SurfaceWaterDepthFilterPlacementModifier {
pub max_water_depth: i32,
}
impl ConditionalPlacementModifier for SurfaceWaterDepthFilterPlacementModifier {
fn should_place<T: GenerationCache>(
&self,
_block_registry: &dyn WorldPortalExt,
_feature: pumpkin_data::placed_feature::PlacedFeature,
chunk: &T,
_random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
let world_top = chunk.top_block_height_exclusive(pos.0.x, pos.0.z);
let ocean = chunk.ocean_floor_height_exclusive(pos.0.x, pos.0.z);
world_top - ocean <= self.max_water_depth
}
}
pub struct BiomePlacementModifier;
impl ConditionalPlacementModifier for BiomePlacementModifier {
fn should_place<T: GenerationCache>(
&self,
_block_registry: &dyn WorldPortalExt,
this_feature: pumpkin_data::placed_feature::PlacedFeature,
chunk: &T,
_random: &mut RandomGenerator,
pos: BlockPos,
) -> bool {
let biome = chunk.get_biome_for_terrain_gen(pos.0.x, pos.0.y, pos.0.z);
for step in biome.features {
if step.contains(&this_feature) {
return true;
}
}
false
}
}
pub struct HeightRangePlacementModifier {
pub height: HeightProvider,
}
impl HeightRangePlacementModifier {
pub fn get_positions(
&self,
min_y: i8,
height: u16,
random: &mut RandomGenerator,
pos: BlockPos,
) -> Box<dyn Iterator<Item = BlockPos>> {
let mut pos = pos;
pos.0.y = self.height.get(random, min_y, height);
Box::new(iter::once(pos))
}
}
pub struct HeightmapPlacementModifier {
pub heightmap: HeightMap,
}
impl HeightmapPlacementModifier {
pub fn get_positions<T: GenerationCache>(
&self,
chunk: &T,
min_y: i8,
_height: u16,
_random: &mut RandomGenerator,
pos: BlockPos,
) -> Box<dyn Iterator<Item = BlockPos>> {
let x = pos.0.x;
let z = pos.0.z;
let top = chunk.get_top_y(&self.heightmap, x, z);
if top > min_y as i32 {
return Box::new(iter::once(BlockPos(Vector3::new(x, top, z))));
}
Box::new(iter::empty())
}
}
pub trait CountPlacementModifierBase {
fn get_positions(
&self,
random: &mut RandomGenerator,
pos: BlockPos,
) -> Box<dyn Iterator<Item = BlockPos>> {
let count = self.get_count(random, pos);
Box::new(std::iter::repeat_n(pos, count as usize))
}
fn get_count(&self, random: &mut RandomGenerator, pos: BlockPos) -> i32;
}
pub trait ConditionalPlacementModifier {
fn get_positions<T: GenerationCache>(
&self,
block_registry: &dyn WorldPortalExt,
chunk: &T,
feature: pumpkin_data::placed_feature::PlacedFeature,
random: &mut RandomGenerator,
pos: BlockPos,
) -> Box<dyn Iterator<Item = BlockPos>> {
if self.should_place(block_registry, feature, chunk, random, pos) {
Box::new(iter::once(pos))
} else {
Box::new(iter::empty())
}
}
fn should_place<T: GenerationCache>(
&self,
block_registry: &dyn WorldPortalExt,
feature: pumpkin_data::placed_feature::PlacedFeature,
chunk: &T,
random: &mut RandomGenerator,
pos: BlockPos,
) -> bool;
}
// generated code is now placed alongside other codegen outputs
// in `src/generated` so it’s easier to find when upgrading MC versions.
// the path is relative to this file (up two levels to reach `src`).
include!("../../../../pumpkin-data/src/generated/placed_features_generated.rs");