chunkgen 0.2.0

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

use crate::util::rng::RandomSource;
use crate::util::rng::legacy_random::LegacyRandomSource;
use crate::util::rotation::RotationExt;
use crate::world::worldgen::block_predicate::HolderSet;
use crate::world::worldgen::region::WorldGenRegion;
use crate::world::worldgen::structure::pieces::structure_pieces_builder::StructurePiecesBuilder;
use crate::world::worldgen::structure::structure_piece::{PieceBase, PieceKind, StructurePiece};
use crate::world::worldgen::structure::template_structure_piece::TemplateStructurePiece;
use crate::world::worldgen::structure::templatesystem::structure_place_settings::StructurePlaceSettings;
use crate::world::worldgen::structure::templatesystem::structure_processor::StructureProcessor;
use shulkr::util::BlockPosition;
use shulkr::world::block::Mirror;
use shulkr::world::block::Rotation;

const FOSSILS: &[&str] = &[
    "nether_fossils/fossil_1",
    "nether_fossils/fossil_2",
    "nether_fossils/fossil_3",
    "nether_fossils/fossil_4",
    "nether_fossils/fossil_5",
    "nether_fossils/fossil_6",
    "nether_fossils/fossil_7",
    "nether_fossils/fossil_8",
    "nether_fossils/fossil_9",
    "nether_fossils/fossil_10",
    "nether_fossils/fossil_11",
    "nether_fossils/fossil_12",
    "nether_fossils/fossil_13",
    "nether_fossils/fossil_14",
];

fn make_settings(rotation: Rotation) -> StructurePlaceSettings {
    let mut settings = StructurePlaceSettings::new()
        .set_rotation(rotation)
        .set_mirror(Mirror::None);
    settings.add_processor(StructureProcessor::BlockIgnore {
        blocks: HolderSet::Entries(vec![
            Key::const_vanilla("structure_block"),
            Key::const_vanilla("air"),
        ]),
    });
    settings
}

pub fn add_pieces(
    builder: &mut StructurePiecesBuilder,
    random: &mut RandomSource,
    position: BlockPosition,
) {
    let rotation = Rotation::get_random(random);
    let index = random.next_i32_bounded(FOSSILS.len() as i32) as usize;
    let location = Key::of(format!("minecraft:{}", FOSSILS[index]));
    builder.add_piece(NetherFossilPiece::new(&location, position, rotation));
}

#[derive(Debug)]
pub struct NetherFossilPiece {
    pub template: TemplateStructurePiece,
}

impl NetherFossilPiece {
    #[expect(
        clippy::new_ret_no_self,
        reason = "mirrors the vanilla piece constructor"
    )]
    fn new(location: &Key, position: BlockPosition, rotation: Rotation) -> StructurePiece {
        let (base, template) =
            TemplateStructurePiece::new(0, location, make_settings(rotation), position);
        StructurePiece::new(base, PieceKind::NetherFossil(Self { template }))
    }

    pub fn post_process(
        &mut self,
        base: &mut PieceBase,
        region: &WorldGenRegion<'_>,
        random: &mut RandomSource,
        chunk_bb: BoundingBox,
        reference_pos: BlockPosition,
    ) {
        let fossil_bb = self.template.template.get_bounding_box(
            &self.template.place_settings,
            self.template.template_position,
        );
        let chunk_bb = chunk_bb.encapsulate_box(fossil_bb);
        self.template.post_process(
            base,
            region,
            random,
            chunk_bb,
            reference_pos,
            |_, _, _, _, _| {},
        );
        place_dried_ghast(region, fossil_bb, chunk_bb);
    }
}

fn place_dried_ghast(region: &WorldGenRegion<'_>, fossil_bb: BoundingBox, chunk_bb: BoundingBox) {
    let center = fossil_bb.center();
    let mut positional_random = LegacyRandomSource::from_seed(region.seed() as u64)
        .next_positional()
        .at(center.x(), center.y(), center.z());
    if positional_random.next_f32() >= 0.5 {
        return;
    }
    let x = fossil_bb.min_x() + positional_random.next_i32_bounded(fossil_bb.x_span());
    let y = fossil_bb.min_y();
    let z = fossil_bb.min_z() + positional_random.next_i32_bounded(fossil_bb.z_span());
    let pos = BlockPosition::new(x, y, z);
    if region.get_block(pos).is_air() && chunk_bb.is_inside(pos) {
        let rotation = Rotation::get_random(&mut positional_random);
        region.set_block(pos, Block::DRIED_GHAST.default_state().rotate(rotation));
    }
}