chunkgen 0.2.0

Minecraft world generation library
Documentation
use serde::Deserialize;
use shulkr::util::Key;

use shulkr::world::block::{BoolProperty, Properties};

use crate::util::rng::RandomSource;
use crate::world::worldgen::block_predicate::BlockPredicate;
use crate::world::worldgen::feature::state_provider::ProviderHolder;
use crate::world::worldgen::region::WorldGenRegion;
use shulkr::util::BlockPosition;
use shulkr::world::block::BlockState;

const LEAVES: Key = Key::const_vanilla("leaves");
const REPLACEABLE_BY_MUSHROOMS: Key = Key::const_vanilla("replaceable_by_mushrooms");

#[derive(Debug, Deserialize)]
pub struct HugeMushroomConfig {
    pub can_place_on: BlockPredicate,
    pub cap_provider: ProviderHolder,
    pub stem_provider: ProviderHolder,
    #[serde(default = "default_foliage_radius")]
    pub foliage_radius: i32,
}

const fn default_foliage_radius() -> i32 {
    2
}

#[derive(Debug, Clone, Copy)]
pub enum MushroomVariant {
    Red,
    Brown,
}

pub fn place(
    region: &WorldGenRegion<'_>,
    random: &mut RandomSource,
    origin: BlockPosition,
    config: &HugeMushroomConfig,
    variant: MushroomVariant,
) -> bool {
    let mut height = 4 + random.next_i32_bounded(3);
    if random.next_i32_bounded(12) == 0 {
        height *= 2;
    }

    if !is_valid_position(region, origin, height, config, variant) {
        return false;
    }

    make_cap(region, random, origin, height, config, variant);
    place_trunk(region, random, origin, height, config);
    true
}

const fn tree_radius_for_height(
    variant: MushroomVariant,
    tree_height: i32,
    leaf_radius: i32,
    yo: i32,
) -> i32 {
    match variant {
        MushroomVariant::Brown => {
            if yo <= 3 {
                0
            } else {
                leaf_radius
            }
        }
        MushroomVariant::Red => {
            if yo >= tree_height - 3 && yo <= tree_height {
                leaf_radius
            } else {
                0
            }
        }
    }
}

fn is_valid_position(
    region: &WorldGenRegion<'_>,
    origin: BlockPosition,
    height: i32,
    config: &HugeMushroomConfig,
    variant: MushroomVariant,
) -> bool {
    let y = origin.y();
    if y < region.min_y() + 1 || y + height + 1 > region.max_y() - 1 {
        return false;
    }
    if !config.can_place_on.test(region, origin.offset(0, -1, 0)) {
        return false;
    }
    for dy in 0..=height {
        let radius = tree_radius_for_height(variant, -1, config.foliage_radius, dy);
        for dx in -radius..=radius {
            for dz in -radius..=radius {
                let state = region.get_block(origin.offset(dx, dy, dz));
                if !state.is_air() && !state.as_block().has_tag(&LEAVES) {
                    return false;
                }
            }
        }
    }
    true
}

fn place_mushroom_block(region: &WorldGenRegion<'_>, pos: BlockPosition, state: BlockState) {
    let current = region.get_block(pos);
    if current.is_air() || current.as_block().has_tag(&REPLACEABLE_BY_MUSHROOMS) {
        region.set_block(pos, state);
    }
}

fn place_trunk(
    region: &WorldGenRegion<'_>,
    random: &mut RandomSource,
    origin: BlockPosition,
    height: i32,
    config: &HugeMushroomConfig,
) {
    for dy in 0..height {
        let pos = origin.offset(0, dy, 0);
        let state = config.stem_provider.get_state(region, random, origin);
        place_mushroom_block(region, pos, state);
    }
}

const HAS_ALL_FACES: [BoolProperty; 5] = [
    Properties::WEST,
    Properties::EAST,
    Properties::NORTH,
    Properties::SOUTH,
    Properties::UP,
];

fn make_cap(
    region: &WorldGenRegion<'_>,
    random: &mut RandomSource,
    origin: BlockPosition,
    height: i32,
    config: &HugeMushroomConfig,
    variant: MushroomVariant,
) {
    match variant {
        MushroomVariant::Red => {
            for dy in (height - 3)..=height {
                let radius = if dy < height {
                    config.foliage_radius
                } else {
                    config.foliage_radius - 1
                };
                let center = config.foliage_radius - 2;
                for dx in -radius..=radius {
                    for dz in -radius..=radius {
                        let min_x = dx == -radius;
                        let max_x = dx == radius;
                        let min_z = dz == -radius;
                        let max_z = dz == radius;
                        let x_edge = min_x || max_x;
                        let z_edge = min_z || max_z;
                        if dy < height && x_edge == z_edge {
                            continue;
                        }
                        let pos = origin.offset(dx, dy, dz);
                        let mut state = config.cap_provider.get_state(region, random, origin);
                        if HAS_ALL_FACES.iter().all(|p| state.get(*p).is_some()) {
                            state = state
                                .with(Properties::UP, dy >= height - 1)
                                .with(Properties::WEST, dx < -center)
                                .with(Properties::EAST, dx > center)
                                .with(Properties::NORTH, dz < -center)
                                .with(Properties::SOUTH, dz > center);
                        }
                        place_mushroom_block(region, pos, state);
                    }
                }
            }
        }
        MushroomVariant::Brown => {
            let radius = config.foliage_radius;
            for dx in -radius..=radius {
                for dz in -radius..=radius {
                    let min_x = dx == -radius;
                    let max_x = dx == radius;
                    let min_z = dz == -radius;
                    let max_z = dz == radius;
                    let x_edge = min_x || max_x;
                    let z_edge = min_z || max_z;
                    if x_edge && z_edge {
                        continue;
                    }
                    let pos = origin.offset(dx, height, dz);
                    let west = min_x || (z_edge && dx == 1 - radius);
                    let east = max_x || (z_edge && dx == radius - 1);
                    let north = min_z || (x_edge && dz == 1 - radius);
                    let south = max_z || (x_edge && dz == radius - 1);
                    let mut state = config.cap_provider.get_state(region, random, origin);
                    if [
                        Properties::WEST,
                        Properties::EAST,
                        Properties::NORTH,
                        Properties::SOUTH,
                    ]
                    .iter()
                    .all(|p| state.get(*p).is_some())
                    {
                        state = state
                            .with(Properties::WEST, west)
                            .with(Properties::EAST, east)
                            .with(Properties::NORTH, north)
                            .with(Properties::SOUTH, south);
                    }
                    place_mushroom_block(region, pos, state);
                }
            }
        }
    }
}