chunkgen 0.2.0

Minecraft world generation library
Documentation
use crate::util::rng::RandomSource;
use crate::world::worldgen::region::WorldGenRegion;
use shulkr::util::BlockPosition;
use shulkr::util::Direction;
use shulkr::util::Key;
use shulkr::world::block::Block;
use shulkr::world::block::BlockState;
use shulkr::world::block::Properties;

const FEATURES_CANNOT_REPLACE: Key = Key::const_vanilla("features_cannot_replace");

fn safe_set_block(region: &WorldGenRegion<'_>, pos: BlockPosition, state: BlockState) {
    if !region
        .get_block(pos)
        .as_block()
        .has_tag(&FEATURES_CANNOT_REPLACE)
    {
        region.set_block(pos, state);
    }
}

fn reorient(region: &WorldGenRegion<'_>, pos: BlockPosition, state: BlockState) -> BlockState {
    let mut solid_neighbor = None;

    for direction in Direction::HORIZONTAL {
        let neighbor = region.get_block(pos.relative(direction));
        if neighbor.is(Block::CHEST) {
            return state;
        }

        if neighbor.is_solid_render() {
            if solid_neighbor.is_some() {
                solid_neighbor = None;
                break;
            }
            solid_neighbor = Some(direction);
        }
    }

    if let Some(direction) = solid_neighbor {
        return state.with(Properties::HORIZONTAL_FACING, direction.opposite());
    }

    let mut lock_dir = state.try_get(Properties::HORIZONTAL_FACING);
    if region.get_block(pos.relative(lock_dir)).is_solid_render() {
        lock_dir = lock_dir.opposite();
    }

    if region.get_block(pos.relative(lock_dir)).is_solid_render() {
        lock_dir = lock_dir.rotate_y_clockwise();
    }

    if region.get_block(pos.relative(lock_dir)).is_solid_render() {
        lock_dir = lock_dir.opposite();
    }

    state.with(Properties::HORIZONTAL_FACING, lock_dir)
}

pub fn place(
    region: &WorldGenRegion<'_>,
    random: &mut RandomSource,
    origin: BlockPosition,
) -> bool {
    let air = Block::CAVE_AIR.default_state();
    let xr = random.next_i32_bounded(2) + 2;
    let min_x = -xr - 1;
    let max_x = xr + 1;
    let zr = random.next_i32_bounded(2) + 2;
    let min_z = -zr - 1;
    let max_z = zr + 1;
    let mut hole_count = 0;

    for dx in min_x..=max_x {
        for dy in -1..=4 {
            for dz in min_z..=max_z {
                let hole_pos = origin.offset(dx, dy, dz);
                let solid = region.get_block(hole_pos).is_solid();
                if dy == -1 && !solid {
                    return false;
                }

                if dy == 4 && !solid {
                    return false;
                }

                if (dx == min_x || dx == max_x || dz == min_z || dz == max_z)
                    && dy == 0
                    && region.get_block(hole_pos).is_air()
                    && region.get_block(hole_pos.above()).is_air()
                {
                    hole_count += 1;
                }
            }
        }
    }

    if !(1..=5).contains(&hole_count) {
        return false;
    }

    for dx in min_x..=max_x {
        for dy in (-1..=3).rev() {
            for dz in min_z..=max_z {
                let wall_pos = origin.offset(dx, dy, dz);
                let wall_state = region.get_block(wall_pos);
                if dx == min_x || dy == -1 || dz == min_z || dx == max_x || dz == max_z {
                    if wall_pos.y() >= region.min_y()
                        && !region.get_block(wall_pos.below()).is_solid()
                    {
                        region.set_block(wall_pos, air);
                    } else if wall_state.is_solid() && !wall_state.is(Block::CHEST) {
                        if dy == -1 && random.next_i32_bounded(4) != 0 {
                            safe_set_block(
                                region,
                                wall_pos,
                                Block::MOSSY_COBBLESTONE.default_state(),
                            );
                        } else {
                            safe_set_block(region, wall_pos, Block::COBBLESTONE.default_state());
                        }
                    }
                } else if !wall_state.is(Block::CHEST) && !wall_state.is(Block::SPAWNER) {
                    safe_set_block(region, wall_pos, air);
                }
            }
        }
    }

    for _ in 0..2 {
        for _ in 0..3 {
            let x = origin.x() + random.next_i32_bounded(xr * 2 + 1) - xr;
            let z = origin.z() + random.next_i32_bounded(zr * 2 + 1) - zr;
            let chest_pos = BlockPosition::new(x, origin.y(), z);
            if region.get_block(chest_pos).is_air() {
                let mut wall_count = 0;
                for direction in Direction::HORIZONTAL {
                    if region.get_block(chest_pos.relative(direction)).is_solid() {
                        wall_count += 1;
                    }
                }

                if wall_count == 1 {
                    let chest = reorient(region, chest_pos, Block::CHEST.default_state());
                    safe_set_block(region, chest_pos, chest);
                    random.next_i64();
                    break;
                }
            }
        }
    }

    safe_set_block(region, origin, Block::SPAWNER.default_state());
    random.next_i32_bounded(4);
    true
}