chunkgen 0.2.0

Minecraft world generation library
Documentation
use crate::util::rng::RandomSource;
use crate::world::chunk::heightmap::HeightmapType;
use crate::world::worldgen::block_predicate::{BlockPredicate, HolderSet};
use crate::world::worldgen::block_survival;
use crate::world::worldgen::feature::PlacedFeature;
use crate::world::worldgen::feature::state_provider::ProviderHolder;
use crate::world::worldgen::region::WorldGenRegion;
use shulkr::util::BlockPosition;
use shulkr::util::Direction;
use shulkr::util::Key;
use shulkr::world::block::BlockState;

const WATER: Key = Key::const_vanilla("water");
const LAVA: Key = Key::const_vanilla("lava");

#[derive(Debug)]
pub struct RootSystemConfig {
    pub feature: &'static PlacedFeature,
    pub required_vertical_space_for_tree: i32,
    pub level_test_distance: i32,
    pub max_level_deviation: i32,
    pub root_radius: i32,
    pub root_replaceable: HolderSet,
    pub root_state_provider: ProviderHolder,
    pub root_placement_attempts: i32,
    pub root_column_max_height: i32,
    pub hanging_root_radius: i32,
    pub hanging_roots_vertical_span: i32,
    pub hanging_root_state_provider: ProviderHolder,
    pub hanging_root_placement_attempts: i32,
    pub allowed_vertical_water_for_tree: i32,
    pub allowed_tree_position: BlockPredicate,
}

pub fn place(
    region: &WorldGenRegion<'_>,
    random: &mut RandomSource,
    origin: BlockPosition,
    config: &RootSystemConfig,
) -> bool {
    if !region.get_block(origin).is_air() {
        return false;
    }

    if place_dirt_and_tree(region, config, random, origin) {
        place_roots(region, config, random, origin);
    }

    true
}

fn is_allowed_tree_space(
    state: BlockState,
    blocks_above_origin: i32,
    allowed_vertical_water_height: i32,
) -> bool {
    if state.is_air() {
        return true;
    }

    let blocks_above_ground = blocks_above_origin + 1;
    blocks_above_ground <= allowed_vertical_water_height
        && state.get_fluid_state().fluid().has_tag(&WATER)
}

fn space_for_tree(
    region: &WorldGenRegion<'_>,
    config: &RootSystemConfig,
    pos: BlockPosition,
) -> bool {
    let mut column_up_pos = pos;
    for i in 1..=config.required_vertical_space_for_tree {
        column_up_pos = column_up_pos.above();
        let state = region.get_block(column_up_pos);
        if !is_allowed_tree_space(state, i, config.allowed_vertical_water_for_tree) {
            return false;
        }
    }

    if config.level_test_distance > 0 {
        for direction in Direction::HORIZONTAL {
            let corner_pos = pos.relative_n(direction, config.level_test_distance);
            let below = region.get_block(corner_pos.below_n(config.max_level_deviation));
            let above = region.get_block(corner_pos.above_n(config.max_level_deviation));
            if below.is_air() || !above.is_air() {
                return false;
            }
        }
    }

    true
}

fn place_dirt_and_tree(
    region: &WorldGenRegion<'_>,
    config: &RootSystemConfig,
    random: &mut RandomSource,
    origin: BlockPosition,
) -> bool {
    let mut working_pos = origin;
    for y in 0..config.root_column_max_height {
        working_pos = working_pos.above();
        if region.get_height(
            HeightmapType::WorldSurface,
            working_pos.x(),
            working_pos.z(),
        ) < working_pos.y()
        {
            return false;
        }

        if config.allowed_tree_position.test(region, working_pos)
            && space_for_tree(region, config, working_pos)
        {
            let below_pos = working_pos.below();
            let below_state = region.get_block(below_pos);
            if below_state.get_fluid_state().fluid().has_tag(&LAVA) || !below_state.is_solid() {
                return false;
            }

            if config.feature.place(region, random, working_pos) {
                place_dirt(region, config, random, origin, origin.y() + y);
                return true;
            }
        }
    }

    false
}

fn place_dirt(
    region: &WorldGenRegion<'_>,
    config: &RootSystemConfig,
    random: &mut RandomSource,
    origin: BlockPosition,
    target_height: i32,
) {
    for y in origin.y()..target_height {
        place_rooted_dirt(region, config, random, origin.at_y(y));
    }
}

fn place_rooted_dirt(
    region: &WorldGenRegion<'_>,
    config: &RootSystemConfig,
    random: &mut RandomSource,
    working_pos: BlockPosition,
) {
    let root_radius = config.root_radius;

    for _ in 0..config.root_placement_attempts {
        let pos = working_pos.offset(
            random.next_i32_bounded(root_radius) - random.next_i32_bounded(root_radius),
            0,
            random.next_i32_bounded(root_radius) - random.next_i32_bounded(root_radius),
        );
        if config
            .root_replaceable
            .contains_block(region.get_block(pos))
        {
            let state = config.root_state_provider.get_state(region, random, pos);
            region.set_block(pos, state);
        }
    }
}

fn place_roots(
    region: &WorldGenRegion<'_>,
    config: &RootSystemConfig,
    random: &mut RandomSource,
    origin: BlockPosition,
) {
    let root_radius = config.hanging_root_radius;
    let vertical_span = config.hanging_roots_vertical_span;

    for _ in 0..config.hanging_root_placement_attempts {
        let working_pos = origin.offset(
            random.next_i32_bounded(root_radius) - random.next_i32_bounded(root_radius),
            random.next_i32_bounded(vertical_span) - random.next_i32_bounded(vertical_span),
            random.next_i32_bounded(root_radius) - random.next_i32_bounded(root_radius),
        );
        if region.get_block(working_pos).is_air() {
            let target_state =
                config
                    .hanging_root_state_provider
                    .get_state(region, random, working_pos);
            if block_survival::can_survive(region, working_pos, target_state)
                && region
                    .get_block(working_pos.above())
                    .is_side_solid_at(working_pos, Direction::Down)
            {
                region.set_block(working_pos, target_state);
            }
        }
    }
}