chunkgen 0.2.0

Minecraft world generation library
Documentation
use serde::Deserialize;
use shulkr::util::Key;
use shulkr::world::block::Block;

use crate::world::chunk::heightmap::HeightmapType;
use shulkr::util::BoundingBox;

use crate::util::pos::ChunkPos;
use crate::util::rng::RandomSource;
use crate::util::rotation::RotationExt;
use crate::world::worldgen::region::WorldGenRegion;
use crate::world::worldgen::structure::templatesystem::structure_place_settings::StructurePlaceSettings;
use crate::world::worldgen::structure::templatesystem::structure_processor::{
    StructureProcessorList, deserialize_processor_list_holder,
};
use crate::world::worldgen::structure::templatesystem::structure_template_manager::templates;
use shulkr::util::BlockPosition;
use shulkr::world::block::Mirror;
use shulkr::world::block::Rotation;

#[derive(Debug, Clone, Deserialize)]
pub struct FossilConfig {
    pub fossil_structures: Vec<Key>,
    pub overlay_structures: Vec<Key>,
    #[serde(deserialize_with = "deserialize_processor_list_holder")]
    pub fossil_processors: StructureProcessorList,
    #[serde(deserialize_with = "deserialize_processor_list_holder")]
    pub overlay_processors: StructureProcessorList,
    pub max_empty_corners_allowed: i32,
}

pub fn place(
    region: &WorldGenRegion<'_>,
    random: &mut RandomSource,
    origin: BlockPosition,
    config: &FossilConfig,
) -> bool {
    let rotation = Rotation::get_random(random);
    let fossil_index = random.next_i32_bounded(config.fossil_structures.len() as i32) as usize;
    let fossil_base = templates().get_or_create(&config.fossil_structures[fossil_index]);
    let fossil_overlay = templates().get_or_create(&config.overlay_structures[fossil_index]);

    let chunk_pos = ChunkPos::from_block_pos(origin);
    let bounding_box = BoundingBox::from_corners(
        BlockPosition::new(
            chunk_pos.0.x * 16 - 16,
            region.min_y(),
            chunk_pos.0.y * 16 - 16,
        ),
        BlockPosition::new(
            chunk_pos.0.x * 16 + 15 + 16,
            region.max_y(),
            chunk_pos.0.y * 16 + 15 + 16,
        ),
    );

    let mut settings = StructurePlaceSettings::new()
        .set_rotation(rotation)
        .set_bounding_box(bounding_box)
        .use_shared_random(true);

    let size = fossil_base.size_rotated(rotation);
    let low_corner = origin.offset(-size.x / 2, 0, -size.z / 2);
    let mut lowest_surface_y = origin.y();
    for x_scan in 0..size.x {
        for z_scan in 0..size.z {
            lowest_surface_y = lowest_surface_y.min(region.get_height(
                HeightmapType::OceanFloorWg,
                low_corner.x() + x_scan,
                low_corner.z() + z_scan,
            ));
        }
    }

    let target_y = (lowest_surface_y - 15 - random.next_i32_bounded(10)).max(region.min_y() + 10);
    let target_pos =
        fossil_base.zero_position_with_transform(low_corner.at_y(target_y), Mirror::None, rotation);

    if count_empty_corners(region, fossil_base.bounding_box(&settings, target_pos))
        > config.max_empty_corners_allowed
    {
        return false;
    }

    settings.clear_processors();
    for processor in &config.fossil_processors.processors {
        settings.add_processor(processor.clone());
    }
    fossil_base.place_in_world(region, target_pos, target_pos, &settings, random);

    settings.clear_processors();
    for processor in &config.overlay_processors.processors {
        settings.add_processor(processor.clone());
    }
    fossil_overlay.place_in_world(region, target_pos, target_pos, &settings, random);
    true
}

fn count_empty_corners(region: &WorldGenRegion<'_>, structure_bounds: BoundingBox) -> i32 {
    let mut count = 0;
    structure_bounds.for_all_corners(|pos| {
        let state = region.get_block(pos);
        let block = Some(state.as_block());
        if state.is_air()
            || block.is_some_and(|block| block == Block::LAVA || block == Block::WATER)
        {
            count += 1;
        }
    });
    count
}