chunkgen 0.2.0

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

use shulkr::util::Axis;
use shulkr::util::Direction;

use crate::util::rng::RandomSource;
use crate::util::rotation::RotationExt;
use crate::world::worldgen::region::WorldGenRegion;
use crate::world::worldgen::structure::templatesystem::structure_place_settings::StructurePlaceSettings;
use crate::world::worldgen::structure::templatesystem::structure_processor::{
    StructureProcessorList, deserialize_optional_processor_list_holder,
};
use crate::world::worldgen::structure::templatesystem::structure_template::StructureTemplate;
use crate::world::worldgen::structure::templatesystem::structure_template_manager::templates;
use shulkr::util::BlockPosition;
use shulkr::world::block::Rotation;

#[derive(Debug, Clone, Deserialize)]
pub struct TemplateConfig {
    pub templates: Vec<WeightedTemplateEntry>,
    #[serde(
        default,
        deserialize_with = "deserialize_optional_processor_list_holder"
    )]
    pub processors: Option<StructureProcessorList>,
}

#[derive(Debug, Clone, Deserialize)]
pub struct WeightedTemplateEntry {
    pub data: TemplateEntry,
    pub weight: i32,
}

#[derive(Debug, Clone, Deserialize)]
pub struct TemplateEntry {
    pub id: Key,
    #[serde(default = "all_rotations")]
    pub rotations: Vec<Rotation>,
}

fn all_rotations() -> Vec<Rotation> {
    vec![
        Rotation::None,
        Rotation::Clockwise90,
        Rotation::Clockwise180,
        Rotation::CounterClockwise90,
    ]
}

pub fn place(
    region: &WorldGenRegion<'_>,
    random: &mut RandomSource,
    origin: BlockPosition,
    config: &TemplateConfig,
) -> bool {
    let total_weight: i32 = config.templates.iter().map(|entry| entry.weight).sum();
    if total_weight <= 0 {
        return false;
    }
    let mut roll = random.next_i32_bounded(total_weight);
    let mut chosen = None;
    for entry in &config.templates {
        roll -= entry.weight;
        if roll < 0 {
            chosen = Some(&entry.data);
            break;
        }
    }
    let Some(entry) = chosen else {
        return false;
    };

    if entry.rotations.is_empty() {
        return false;
    }
    let rotation = entry.rotations[random.next_i32_bounded(entry.rotations.len() as i32) as usize];

    let template = templates().get_or_create(&entry.id);
    let offset_x = rotated_offset(rotation, Axis::X, template);
    let offset_z = rotated_offset(rotation, Axis::Z, template);
    let pos = origin
        .offset(offset_x.x(), offset_x.y(), offset_x.z())
        .offset(offset_z.x(), offset_z.y(), offset_z.z());

    let mut settings = StructurePlaceSettings::new()
        .set_rotation(rotation)
        .use_shared_random(true);
    if let Some(list) = &config.processors {
        for processor in &list.processors {
            settings.add_processor(processor.clone());
        }
    }
    template.place_in_world(region, pos, pos, &settings, random)
}

fn rotated_offset(rotation: Rotation, axis: Axis, template: &StructureTemplate) -> BlockPosition {
    let negative = match axis {
        Axis::X => Direction::West,
        Axis::Y => Direction::Down,
        Axis::Z => Direction::North,
    };
    let size = template.size();
    let length = match axis {
        Axis::X => size.x,
        Axis::Y => size.y,
        Axis::Z => size.z,
    } / 2;
    BlockPosition::ZERO.relative_n(rotation.rotate(negative), length)
}