chunkgen 0.2.0

Minecraft world generation library
Documentation
use serde::Deserialize;

use crate::util::rng::RandomSource;
use crate::util::value_providers::int::IntProvider;
use crate::world::worldgen::block_predicate::HolderSet;
use crate::world::worldgen::feature::features::HORIZONTAL_DIRECTIONS;
use crate::world::worldgen::feature::features::tree::TreeConfig;
use crate::world::worldgen::feature::foliageplacers::FoliageAttachment;
use crate::world::worldgen::region::WorldGenRegion;
use shulkr::util::BlockPosition;
use shulkr::util::Direction;
use shulkr::world::block::BlockState;

#[derive(Debug, Deserialize)]
pub struct UpwardsBranchingTrunkPlacer {
    pub base_height: i32,
    pub height_rand_a: i32,
    pub height_rand_b: i32,
    pub extra_branch_steps: IntProvider,
    pub place_branch_per_log_probability: f32,
    pub extra_branch_length: IntProvider,
    pub can_grow_through: HolderSet,
}

impl UpwardsBranchingTrunkPlacer {
    #[expect(
        clippy::too_many_arguments,
        reason = "mirrors vanilla UpwardsBranchingTrunkPlacer.placeTrunk"
    )]
    pub fn place_trunk(
        &self,
        placer: &super::TrunkPlacer,
        region: &WorldGenRegion<'_>,
        trunk_setter: &mut dyn FnMut(BlockPosition, BlockState),
        random: &mut RandomSource,
        tree_height: i32,
        origin: BlockPosition,
        config: &TreeConfig,
    ) -> Vec<FoliageAttachment> {
        let mut attachments = Vec::new();

        for height_pos in 0..tree_height {
            let current_height = origin.y() + height_pos;
            let log_pos = origin.at_y(current_height);
            if placer.place_log(region, trunk_setter, random, log_pos, config)
                && height_pos < tree_height - 1
                && random.next_f32() < self.place_branch_per_log_probability
            {
                let branch_dir = HORIZONTAL_DIRECTIONS[random.next_i32_bounded(4) as usize];
                let branch_len = self.extra_branch_length.sample(random);
                let branch_pos = (branch_len - self.extra_branch_length.sample(random) - 1).max(0);
                let branch_steps = self.extra_branch_steps.sample(random);
                self.place_branch(
                    placer,
                    region,
                    trunk_setter,
                    random,
                    tree_height,
                    config,
                    &mut attachments,
                    log_pos,
                    current_height,
                    branch_dir,
                    branch_pos,
                    branch_steps,
                );
            }

            if height_pos == tree_height - 1 {
                attachments.push(FoliageAttachment {
                    pos: origin.at_y(current_height + 1),
                    radius_offset: 0,
                    double_trunk: false,
                });
            }
        }

        attachments
    }

    #[expect(
        clippy::too_many_arguments,
        reason = "mirrors vanilla UpwardsBranchingTrunkPlacer.placeBranch"
    )]
    fn place_branch(
        &self,
        placer: &super::TrunkPlacer,
        region: &WorldGenRegion<'_>,
        trunk_setter: &mut dyn FnMut(BlockPosition, BlockState),
        random: &mut RandomSource,
        tree_height: i32,
        config: &TreeConfig,
        attachments: &mut Vec<FoliageAttachment>,
        log_pos: BlockPosition,
        current_height: i32,
        branch_dir: Direction,
        branch_pos: i32,
        mut branch_steps: i32,
    ) {
        let mut height_along_branch = current_height + branch_pos;
        let mut log_x = log_pos.x();
        let mut log_z = log_pos.z();
        let mut branch_placement_index = branch_pos;

        while branch_placement_index < tree_height && branch_steps > 0 {
            if branch_placement_index >= 1 {
                let placement_height = current_height + branch_placement_index;
                log_x += branch_dir.offset().0;
                log_z += branch_dir.offset().2;
                height_along_branch = placement_height;
                let pos = BlockPosition::new(log_x, placement_height, log_z);
                if placer.place_log(region, trunk_setter, random, pos, config) {
                    height_along_branch += 1;
                }

                attachments.push(FoliageAttachment {
                    pos,
                    radius_offset: 0,
                    double_trunk: false,
                });
            }

            branch_placement_index += 1;
            branch_steps -= 1;
        }

        if height_along_branch - current_height > 1 {
            let foliage_pos = BlockPosition::new(log_x, height_along_branch, log_z);
            attachments.push(FoliageAttachment {
                pos: foliage_pos,
                radius_offset: 0,
                double_trunk: false,
            });
            attachments.push(FoliageAttachment {
                pos: foliage_pos.below_n(2),
                radius_offset: 0,
                double_trunk: false,
            });
        }
    }
}