chunkgen 0.2.0

Minecraft world generation library
Documentation
use serde::Deserialize;

use crate::util::hash_set::JavaHashSet;
use crate::util::rng::RandomSource;
use crate::util::value_providers::int::IntProvider;
use crate::world::worldgen::feature::features::mark_above_for_post_processing;
use crate::world::worldgen::feature::features::tree;
use crate::world::worldgen::feature::state_provider::ProviderHolder;
use crate::world::worldgen::feature::treedecorators::{TreeDecorator, TreeDecoratorContext};
use crate::world::worldgen::region::WorldGenRegion;
use shulkr::util::BlockPosition;
use shulkr::util::Direction;
use shulkr::world::block::Properties;
use std::iter;

#[derive(Debug, Deserialize)]
pub struct FallenTreeConfig {
    pub trunk_provider: ProviderHolder,
    pub log_length: IntProvider,
    pub stump_decorators: Vec<TreeDecorator>,
    pub log_decorators: Vec<TreeDecorator>,
}

pub fn place(
    region: &WorldGenRegion<'_>,
    random: &mut RandomSource,
    origin: BlockPosition,
    config: &FallenTreeConfig,
) -> bool {
    place_stump(region, random, origin, config);

    let direction = Direction::HORIZONTAL[random.next_i32_bounded(4) as usize];
    let log_length = config.log_length.sample(random) - 2;
    let log_start = set_ground_height(
        region,
        origin.relative_n(direction, 2 + random.next_i32_bounded(2)),
    );

    if can_place_entire_fallen_log(region, log_length, log_start, direction) {
        place_fallen_log(region, random, log_length, log_start, direction, config);
    }
    true
}

fn set_ground_height(region: &WorldGenRegion<'_>, pos: BlockPosition) -> BlockPosition {
    let mut pos = pos.above();
    for _ in 0..6 {
        if may_place_on(region, pos) {
            return pos;
        }
        pos = pos.below();
    }
    pos
}

fn may_place_on(region: &WorldGenRegion<'_>, pos: BlockPosition) -> bool {
    tree::valid_tree_pos(region, pos) && is_over_solid_ground(region, pos)
}

fn is_over_solid_ground(region: &WorldGenRegion<'_>, pos: BlockPosition) -> bool {
    region
        .get_block(pos.below())
        .is_side_solid_at(pos, Direction::Up)
}

fn can_place_entire_fallen_log(
    region: &WorldGenRegion<'_>,
    log_length: i32,
    log_start: BlockPosition,
    direction: Direction,
) -> bool {
    let mut pos = log_start;
    let mut gap_in_ground = 0;
    for _ in 0..log_length {
        if !tree::valid_tree_pos(region, pos) {
            return false;
        }
        if is_over_solid_ground(region, pos) {
            gap_in_ground = 0;
        } else {
            gap_in_ground += 1;
            if gap_in_ground > 2 {
                return false;
            }
        }
        pos = pos.relative(direction);
    }
    true
}

fn place_log_block(
    region: &WorldGenRegion<'_>,
    random: &mut RandomSource,
    pos: BlockPosition,
    config: &FallenTreeConfig,
    axis: Option<Direction>,
) {
    let mut state = config.trunk_provider.get_state(region, random, pos);
    if let Some(direction) = axis
        && state.get(Properties::AXIS).is_some()
    {
        state = state.with(Properties::AXIS, direction.axis());
    }
    region.set_block(pos, state);
    mark_above_for_post_processing(region, pos);
}

fn place_stump(
    region: &WorldGenRegion<'_>,
    random: &mut RandomSource,
    origin: BlockPosition,
    config: &FallenTreeConfig,
) {
    place_log_block(region, random, origin, config, None);
    let stump: JavaHashSet = iter::once(origin).collect();
    decorate(region, random, &stump, &config.stump_decorators);
}

fn place_fallen_log(
    region: &WorldGenRegion<'_>,
    random: &mut RandomSource,
    log_length: i32,
    log_start: BlockPosition,
    direction: Direction,
    config: &FallenTreeConfig,
) {
    let mut pos = log_start;
    let mut fallen_log = JavaHashSet::new();
    for _ in 0..log_length {
        place_log_block(region, random, pos, config, Some(direction));
        fallen_log.insert(pos);
        pos = pos.relative(direction);
    }
    decorate(region, random, &fallen_log, &config.log_decorators);
}

fn decorate(
    region: &WorldGenRegion<'_>,
    random: &mut RandomSource,
    logs: &JavaHashSet,
    decorators: &[TreeDecorator],
) {
    if decorators.is_empty() {
        return;
    }
    let empty = JavaHashSet::new();
    let mut decorations = JavaHashSet::new();
    let mut context =
        TreeDecoratorContext::new(region, random, logs, &empty, &empty, &mut decorations);
    for decorator in decorators {
        decorator.place(&mut context);
    }
}