chunkgen 0.2.0

Minecraft world generation library
Documentation
use crate::world::chunk::heightmap::HeightmapType;
use shulkr::util::BoundingBox;
use shulkr::util::Key;
use shulkr::world::block::Block;

use crate::util::rng::RandomSource;
use crate::world::worldgen::block_predicate::HolderSet;
use crate::world::worldgen::region::WorldGenRegion;
use crate::world::worldgen::structure::structure_piece::{PieceBase, PieceKind, StructurePiece};
use crate::world::worldgen::structure::template_structure_piece::TemplateStructurePiece;
use crate::world::worldgen::structure::templatesystem::structure_place_settings::StructurePlaceSettings;
use crate::world::worldgen::structure::templatesystem::structure_processor::StructureProcessor;
use shulkr::util::BlockPosition;
use shulkr::world::block::Mirror;
use shulkr::world::block::Rotation;

const NUMBER_OF_BLOCKS_ALLOWED_IN_WORLD_GEN_REGION: i32 = 32;
const PIVOT: BlockPosition = BlockPosition::new(4, 0, 15);

const STRUCTURE_LOCATION_BEACHED: &[&str] = &[
    "shipwreck/with_mast",
    "shipwreck/sideways_full",
    "shipwreck/sideways_fronthalf",
    "shipwreck/sideways_backhalf",
    "shipwreck/rightsideup_full",
    "shipwreck/rightsideup_fronthalf",
    "shipwreck/rightsideup_backhalf",
    "shipwreck/with_mast_degraded",
    "shipwreck/rightsideup_full_degraded",
    "shipwreck/rightsideup_fronthalf_degraded",
    "shipwreck/rightsideup_backhalf_degraded",
];

const STRUCTURE_LOCATION_OCEAN: &[&str] = &[
    "shipwreck/with_mast",
    "shipwreck/upsidedown_full",
    "shipwreck/upsidedown_fronthalf",
    "shipwreck/upsidedown_backhalf",
    "shipwreck/sideways_full",
    "shipwreck/sideways_fronthalf",
    "shipwreck/sideways_backhalf",
    "shipwreck/rightsideup_full",
    "shipwreck/rightsideup_fronthalf",
    "shipwreck/rightsideup_backhalf",
    "shipwreck/with_mast_degraded",
    "shipwreck/upsidedown_full_degraded",
    "shipwreck/upsidedown_fronthalf_degraded",
    "shipwreck/upsidedown_backhalf_degraded",
    "shipwreck/sideways_full_degraded",
    "shipwreck/sideways_fronthalf_degraded",
    "shipwreck/sideways_backhalf_degraded",
    "shipwreck/rightsideup_full_degraded",
    "shipwreck/rightsideup_fronthalf_degraded",
    "shipwreck/rightsideup_backhalf_degraded",
];

fn make_settings(rotation: Rotation) -> StructurePlaceSettings {
    let mut settings = StructurePlaceSettings::new()
        .set_rotation(rotation)
        .set_mirror(Mirror::None)
        .set_rotation_pivot(PIVOT);
    settings.add_processor(StructureProcessor::BlockIgnore {
        blocks: HolderSet::Entries(vec![
            Key::const_vanilla("structure_block"),
            Key::const_vanilla("air"),
        ]),
    });
    settings
}

pub fn random_piece(
    position: BlockPosition,
    rotation: Rotation,
    random: &mut RandomSource,
    is_beached: bool,
) -> StructurePiece {
    let pool = if is_beached {
        STRUCTURE_LOCATION_BEACHED
    } else {
        STRUCTURE_LOCATION_OCEAN
    };
    let index = random.next_i32_bounded(pool.len() as i32) as usize;
    let location = Key::of(format!("minecraft:{}", pool[index]));
    ShipwreckPiece::new(&location, position, rotation, is_beached)
}

#[derive(Debug)]
pub struct ShipwreckPiece {
    pub template: TemplateStructurePiece,
    is_beached: bool,
    height_adjusted: bool,
}

impl ShipwreckPiece {
    #[expect(
        clippy::new_ret_no_self,
        reason = "mirrors the vanilla piece constructor"
    )]
    fn new(
        location: &Key,
        position: BlockPosition,
        rotation: Rotation,
        is_beached: bool,
    ) -> StructurePiece {
        let (base, template) =
            TemplateStructurePiece::new(0, location, make_settings(rotation), position);
        StructurePiece::new(
            base,
            PieceKind::Shipwreck(Self {
                template,
                is_beached,
                height_adjusted: false,
            }),
        )
    }

    pub const fn is_too_big_to_fit_in_world_gen_region(&self) -> bool {
        let size = self.template.template.size();
        size.x > NUMBER_OF_BLOCKS_ALLOWED_IN_WORLD_GEN_REGION
            || size.y > NUMBER_OF_BLOCKS_ALLOWED_IN_WORLD_GEN_REGION
    }

    pub fn calculate_beached_position(&self, min_y: i32, random: &mut RandomSource) -> i32 {
        min_y - self.template.template.size().y / 2 - random.next_i32_bounded(3)
    }

    pub const fn adjust_position_height(&mut self, new_height: i32) {
        self.height_adjusted = true;
        self.template.template_position = BlockPosition::new(
            self.template.template_position.x(),
            new_height,
            self.template.template_position.z(),
        );
    }

    pub fn post_process(
        &mut self,
        base: &mut PieceBase,
        region: &WorldGenRegion<'_>,
        random: &mut RandomSource,
        chunk_bb: BoundingBox,
        reference_pos: BlockPosition,
    ) {
        if !self.height_adjusted && !self.is_too_big_to_fit_in_world_gen_region() {
            let mut min_y = region.max_y() + 1;
            let mut mean = 0;
            let template_size = self.template.template.size();
            let heightmap_type = if self.is_beached {
                HeightmapType::WorldSurfaceWg
            } else {
                HeightmapType::OceanFloorWg
            };
            let base_size = template_size.x * template_size.z;
            if base_size == 0 {
                mean = region.get_height(
                    heightmap_type,
                    self.template.template_position.x(),
                    self.template.template_position.z(),
                );
            } else {
                let origin = self.template.template_position;
                for x in origin.x()..origin.x() + template_size.x {
                    for z in origin.z()..origin.z() + template_size.z {
                        let heightmap = region.get_height(heightmap_type, x, z);
                        mean += heightmap;
                        min_y = min_y.min(heightmap);
                    }
                }
                mean /= base_size;
            }

            let new_height = if self.is_beached {
                self.calculate_beached_position(min_y, random)
            } else {
                mean
            };
            self.adjust_position_height(new_height);
        }

        self.template.post_process(
            base,
            region,
            random,
            chunk_bb,
            reference_pos,
            |marker_id, position, region, random, _chunk_bb| {
                if matches!(marker_id, "map_chest" | "treasure_chest" | "supply_chest")
                    && is_randomizable_container(region, position.below())
                {
                    random.next_i64();
                }
            },
        );
    }
}

fn is_randomizable_container(region: &WorldGenRegion<'_>, pos: BlockPosition) -> bool {
    let block = region.get_block(pos).as_block();
    block == Block::CHEST
        || block == Block::TRAPPED_CHEST
        || block == Block::BARREL
        || block == Block::DISPENSER
        || block == Block::DROPPER
        || block == Block::HOPPER
}