chunkgen 0.2.0

Minecraft world generation library
Documentation
use serde::Deserialize;

use crate::util::rng::RandomSource;
use crate::world::worldgen::feature::features::HORIZONTAL_DIRECTIONS;
use crate::world::worldgen::feature::features::tree::{TreeConfig, is_air_or_leaves};
use crate::world::worldgen::feature::foliageplacers::FoliageAttachment;
use crate::world::worldgen::region::WorldGenRegion;
use shulkr::util::BlockPosition;
use shulkr::world::block::BlockState;

#[derive(Debug, Deserialize)]
pub struct DarkOakTrunkPlacer {
    pub base_height: i32,
    pub height_rand_a: i32,
    pub height_rand_b: i32,
}

impl DarkOakTrunkPlacer {
    #[expect(
        clippy::too_many_arguments,
        reason = "mirrors vanilla DarkOakTrunkPlacer.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();
        let below = origin.below();
        let east = |pos: BlockPosition| pos.offset(1, 0, 0);
        let south = |pos: BlockPosition| pos.offset(0, 0, 1);
        super::place_below_trunk_block(region, trunk_setter, random, below, config);
        super::place_below_trunk_block(region, trunk_setter, random, east(below), config);
        super::place_below_trunk_block(region, trunk_setter, random, south(below), config);
        super::place_below_trunk_block(region, trunk_setter, random, east(south(below)), config);

        let lean_direction = HORIZONTAL_DIRECTIONS[random.next_i32_bounded(4) as usize];
        let lean_height = tree_height - random.next_i32_bounded(4);
        let mut lean_steps = 2 - random.next_i32_bounded(3);
        let (x, y, z) = (origin.x(), origin.y(), origin.z());
        let mut tx = x;
        let mut tz = z;
        let ey = y + tree_height - 1;

        for dy in 0..tree_height {
            if dy >= lean_height && lean_steps > 0 {
                tx += lean_direction.offset().0;
                tz += lean_direction.offset().2;
                lean_steps -= 1;
            }

            let pos = BlockPosition::new(tx, y + dy, tz);
            if is_air_or_leaves(region, pos) {
                placer.place_log(region, trunk_setter, random, pos, config);
                placer.place_log(region, trunk_setter, random, east(pos), config);
                placer.place_log(region, trunk_setter, random, south(pos), config);
                placer.place_log(region, trunk_setter, random, east(south(pos)), config);
            }
        }

        attachments.push(FoliageAttachment {
            pos: BlockPosition::new(tx, ey, tz),
            radius_offset: 0,
            double_trunk: true,
        });

        for ox in -1..=2 {
            for oz in -1..=2 {
                if (!(0..=1).contains(&ox) || !(0..=1).contains(&oz))
                    && random.next_i32_bounded(3) <= 0
                {
                    let length = random.next_i32_bounded(3) + 2;
                    for branch_y in 0..length {
                        placer.place_log(
                            region,
                            trunk_setter,
                            random,
                            BlockPosition::new(x + ox, ey - branch_y - 1, z + oz),
                            config,
                        );
                    }
                    attachments.push(FoliageAttachment {
                        pos: BlockPosition::new(x + ox, ey, z + oz),
                        radius_offset: 0,
                        double_trunk: false,
                    });
                }
            }
        }

        attachments
    }
}