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::rootplacers::{AboveRootPlacement, RootPlacer};
use crate::world::worldgen::feature::state_provider::ProviderHolder;
use crate::world::worldgen::region::WorldGenRegion;
use shulkr::util::BlockPosition;
use shulkr::util::Direction;
use shulkr::world::block::BlockState;

#[derive(Debug, Deserialize)]
pub struct MangroveRootPlacement {
    pub can_grow_through: HolderSet,
    pub muddy_roots_in: HolderSet,
    pub muddy_roots_provider: ProviderHolder,
    pub max_root_width: i32,
    pub max_root_length: i32,
    pub random_skew_chance: f32,
}

#[derive(Debug, Deserialize)]
pub struct MangroveRootPlacer {
    pub trunk_offset_y: IntProvider,
    pub root_provider: ProviderHolder,
    #[serde(default)]
    pub above_root_placement: Option<AboveRootPlacement>,
    pub mangrove_root_placement: MangroveRootPlacement,
}

impl MangroveRootPlacer {
    #[expect(
        clippy::too_many_arguments,
        reason = "mirrors vanilla MangroveRootPlacer.placeRoots"
    )]
    pub fn place_roots(
        &self,
        placer: &RootPlacer,
        region: &WorldGenRegion<'_>,
        root_setter: &mut dyn FnMut(BlockPosition, BlockState),
        random: &mut RandomSource,
        origin: BlockPosition,
        trunk_origin: BlockPosition,
        config: &TreeConfig,
    ) -> bool {
        let mut root_positions: Vec<BlockPosition> = Vec::new();
        let mut column_pos = origin;
        while column_pos.y() < trunk_origin.y() {
            if !placer.can_place_root(region, column_pos) {
                return false;
            }
            column_pos = column_pos.above();
        }

        root_positions.push(trunk_origin.below());

        for dir in HORIZONTAL_DIRECTIONS {
            let pos = trunk_origin.offset(dir.offset().0, 0, dir.offset().2);
            let mut positions_in_direction = Vec::new();
            if !self.simulate_roots(
                placer,
                region,
                random,
                pos,
                dir,
                trunk_origin,
                &mut positions_in_direction,
                0,
            ) {
                return false;
            }

            root_positions.extend(positions_in_direction);
            root_positions.push(pos);
        }

        for root_pos in root_positions {
            self.place_root(placer, region, root_setter, random, root_pos, config);
        }

        true
    }

    #[expect(
        clippy::too_many_arguments,
        reason = "mirrors vanilla MangroveRootPlacer.simulateRoots"
    )]
    fn simulate_roots(
        &self,
        placer: &RootPlacer,
        region: &WorldGenRegion<'_>,
        random: &mut RandomSource,
        root_pos: BlockPosition,
        dir: Direction,
        root_origin: BlockPosition,
        root_positions: &mut Vec<BlockPosition>,
        layer: i32,
    ) -> bool {
        let max_root_length = self.mangrove_root_placement.max_root_length;
        if layer == max_root_length || root_positions.len() as i32 > max_root_length {
            return false;
        }

        for pos in self.potential_root_positions(root_pos, dir, random, root_origin) {
            if placer.can_place_root(region, pos) {
                root_positions.push(pos);
                if !self.simulate_roots(
                    placer,
                    region,
                    random,
                    pos,
                    dir,
                    root_origin,
                    root_positions,
                    layer + 1,
                ) {
                    return false;
                }
            }
        }

        true
    }

    fn potential_root_positions(
        &self,
        pos: BlockPosition,
        prev_dir: Direction,
        random: &mut RandomSource,
        root_origin: BlockPosition,
    ) -> Vec<BlockPosition> {
        let below = pos.below();
        let next_to = pos.offset(prev_dir.offset().0, 0, prev_dir.offset().2);
        let width = (pos.x() - root_origin.x()).abs()
            + (pos.y() - root_origin.y()).abs()
            + (pos.z() - root_origin.z()).abs();
        let max_root_width = self.mangrove_root_placement.max_root_width;
        let random_skew_chance = self.mangrove_root_placement.random_skew_chance;
        #[expect(
            clippy::if_same_then_else,
            reason = "branches match but each arm gates a different RNG draw, matching vanilla MangroveRootPlacer.potentialRootPositions"
        )]
        if width > max_root_width - 3 && width <= max_root_width {
            if random.next_f32() < random_skew_chance {
                vec![below, next_to.below()]
            } else {
                vec![below]
            }
        } else if width > max_root_width {
            vec![below]
        } else if random.next_f32() < random_skew_chance {
            vec![below]
        } else if random.next_bool() {
            vec![next_to]
        } else {
            vec![below]
        }
    }

    fn place_root(
        &self,
        placer: &RootPlacer,
        region: &WorldGenRegion<'_>,
        root_setter: &mut dyn FnMut(BlockPosition, BlockState),
        random: &mut RandomSource,
        pos: BlockPosition,
        config: &TreeConfig,
    ) {
        if self
            .mangrove_root_placement
            .muddy_roots_in
            .contains_block(region.get_block(pos))
        {
            let muddy_roots = self
                .mangrove_root_placement
                .muddy_roots_provider
                .get_state(region, random, pos);
            root_setter(
                pos,
                super::potentially_waterlogged_state(region, pos, muddy_roots),
            );
        } else {
            placer.place_root(region, root_setter, random, pos, config);
        }
    }
}