chunkgen 0.2.0

Minecraft world generation library
Documentation
use crate::world::worldgen::structure::templatesystem::structure_template::{
    JointType, empty_jigsaw_id, empty_pool_id,
};
use std::sync::LazyLock;

use shulkr::util::Key;
use shulkr::world::block::{Block, BlockState};

use glam::IVec3;

use shulkr::util::BoundingBox;

use crate::util::rng::RandomSource;
use crate::util::shuffle;
use crate::world::worldgen::block_predicate::HolderSet;
use crate::world::worldgen::feature::PlacedFeature;
use crate::world::worldgen::region::WorldGenRegion;
use crate::world::worldgen::structure::pools::structure_template_pool::Projection;
use crate::world::worldgen::structure::templatesystem::liquid_settings::LiquidSettings;
use crate::world::worldgen::structure::templatesystem::structure_place_settings::StructurePlaceSettings;
use crate::world::worldgen::structure::templatesystem::structure_processor::StructureProcessor;
use crate::world::worldgen::structure::templatesystem::structure_template::{
    JigsawBlockInfo, StructureBlockInfo, StructureTemplate,
};
use crate::world::worldgen::structure::templatesystem::structure_template_manager::templates;
use shulkr::util::BlockPosition;
use shulkr::world::block::Rotation;

#[derive(Debug)]
pub struct SinglePoolElement {
    pub template: Key,
    pub processors: Vec<StructureProcessor>,
    pub projection: Projection,
    pub override_liquid_settings: Option<LiquidSettings>,
}

#[derive(Debug)]
pub struct FeaturePoolElement {
    pub feature: &'static PlacedFeature,
    pub projection: Projection,
}

#[derive(Debug)]
pub enum StructurePoolElement {
    Single(SinglePoolElement),
    LegacySingle(SinglePoolElement),
    List {
        elements: Vec<StructurePoolElement>,
        projection: Projection,
    },
    Feature(FeaturePoolElement),
    Empty,
}

static EMPTY: LazyLock<&'static StructurePoolElement> =
    LazyLock::new(|| Box::leak(Box::new(StructurePoolElement::Empty)));

impl StructurePoolElement {
    pub fn empty() -> &'static Self {
        *EMPTY
    }

    pub const fn projection(&self) -> Projection {
        match self {
            Self::Single(single) | Self::LegacySingle(single) => single.projection,
            Self::List { projection, .. } => *projection,
            Self::Feature(feature) => feature.projection,
            Self::Empty => Projection::TerrainMatching,
        }
    }

    pub const fn ground_level_delta(&self) -> i32 {
        1
    }

    fn template(single: &SinglePoolElement) -> &'static StructureTemplate {
        templates().get_or_create(&single.template)
    }

    pub fn get_size(&self, rotation: Rotation) -> IVec3 {
        match self {
            Self::Single(single) | Self::LegacySingle(single) => {
                Self::template(single).size_rotated(rotation)
            }
            Self::List { elements, .. } => elements
                .iter()
                .map(|element| element.get_size(rotation))
                .fold(IVec3::ZERO, IVec3::max),
            Self::Feature(_) | Self::Empty => IVec3::ZERO,
        }
    }

    pub fn get_shuffled_jigsaw_blocks(
        &self,
        position: BlockPosition,
        rotation: Rotation,
        random: &mut RandomSource,
    ) -> Vec<JigsawBlockInfo> {
        match self {
            Self::Single(single) | Self::LegacySingle(single) => {
                let mut jigsaws = Self::template(single).get_jigsaws(position, rotation, random);
                shuffle(&mut jigsaws, random);
                jigsaws.sort_by_key(|jigsaw| -jigsaw.selection_priority);
                jigsaws
            }
            Self::List { elements, .. } => elements
                .first()
                .map(|element| element.get_shuffled_jigsaw_blocks(position, rotation, random))
                .unwrap_or_default(),
            Self::Feature(_) => vec![JigsawBlockInfo::new(
                StructureBlockInfo {
                    pos: position,
                    state: default_feature_jigsaw_state(),
                    nbt: None,
                },
                JointType::Rollable,
                None,
                empty_pool_id(),
                empty_jigsaw_id(),
                0,
                0,
            )],
            Self::Empty => Vec::new(),
        }
    }

    pub fn get_bounding_box(&self, position: BlockPosition, rotation: Rotation) -> BoundingBox {
        match self {
            Self::Single(single) | Self::LegacySingle(single) => Self::template(single)
                .get_bounding_box(
                    &StructurePlaceSettings::new().set_rotation(rotation),
                    position,
                ),
            Self::List { elements, .. } => elements
                .iter()
                .filter(|element| !matches!(element, Self::Empty))
                .map(|element| element.get_bounding_box(position, rotation))
                .reduce(BoundingBox::encapsulate_box)
                .expect("Unable to calculate boundingbox for ListPoolElement"),
            Self::Feature(_) | Self::Empty => {
                let size = self.get_size(rotation);
                BoundingBox::from_corners(
                    position,
                    BlockPosition::new(
                        position.x() + size.x,
                        position.y() + size.y,
                        position.z() + size.z,
                    ),
                )
            }
        }
    }

    #[expect(
        clippy::too_many_arguments,
        reason = "mirrors vanilla StructurePoolElement::place"
    )]
    pub fn place(
        &self,
        region: &WorldGenRegion<'_>,
        position: BlockPosition,
        reference_pos: BlockPosition,
        rotation: Rotation,
        chunk_bb: BoundingBox,
        random: &mut RandomSource,
        liquid_settings: LiquidSettings,
        keep_jigsaws: bool,
    ) -> bool {
        match self {
            Self::Single(single) | Self::LegacySingle(single) => {
                let template = Self::template(single);
                let settings = self.settings(rotation, chunk_bb, liquid_settings, keep_jigsaws);
                template.place_in_world(region, position, reference_pos, &settings, random)
            }
            Self::List { elements, .. } => elements.iter().all(|element| {
                element.place(
                    region,
                    position,
                    reference_pos,
                    rotation,
                    chunk_bb,
                    random,
                    liquid_settings,
                    keep_jigsaws,
                )
            }),
            Self::Feature(feature) => feature.feature.place(region, random, position),
            Self::Empty => true,
        }
    }

    fn settings(
        &self,
        rotation: Rotation,
        chunk_bb: BoundingBox,
        liquid_settings: LiquidSettings,
        keep_jigsaws: bool,
    ) -> StructurePlaceSettings {
        let (Self::Single(single) | Self::LegacySingle(single)) = self else {
            return StructurePlaceSettings::new();
        };

        let mut settings = StructurePlaceSettings::new()
            .set_bounding_box(chunk_bb)
            .set_rotation(rotation)
            .set_known_shape(true)
            .set_ignore_entities(false)
            .set_finalize_entities(true)
            .set_liquid_settings(single.override_liquid_settings.unwrap_or(liquid_settings));

        if matches!(self, Self::LegacySingle(_)) {
            settings.add_processor(block_ignore_structure_and_air());
        } else {
            settings.add_processor(block_ignore_structure_block());
        }

        if !keep_jigsaws {
            settings.add_processor(StructureProcessor::JigsawReplacement);
        }

        for processor in &single.processors {
            settings.add_processor(processor.clone());
        }
        for processor in single.projection.processors() {
            settings.add_processor(processor);
        }

        settings
    }
}

fn block_ignore_structure_block() -> StructureProcessor {
    StructureProcessor::BlockIgnore {
        blocks: HolderSet::Entries(vec![Block::STRUCTURE_BLOCK.key().clone()]),
    }
}

fn block_ignore_structure_and_air() -> StructureProcessor {
    StructureProcessor::BlockIgnore {
        blocks: HolderSet::Entries(vec![
            Block::AIR.key().clone(),
            Block::STRUCTURE_BLOCK.key().clone(),
        ]),
    }
}

fn default_feature_jigsaw_state() -> BlockState {
    BlockState::parse("minecraft:jigsaw[orientation=down_south]")
        .expect("jigsaw block state should exist")
}