chunkgen 0.2.0

Minecraft world generation library
Documentation
use std::sync::{Arc, OnceLock};

use shulkr::world::block::BlockState;

use crate::util::rng::RandomFactory;
use crate::world::worldgen::densityfunction::function::Df;

use super::condition::{ConditionEvaluator, MaterialCondition};
use super::rule_context::{DensityGetter, MaterialRuleContext};

const ORE_RANDOM: &str = "minecraft:ore";

#[derive(Clone)]
pub struct OreVeinRule {
    pub ore_block: BlockState,
    pub raw_ore_block: BlockState,
    pub filler_block: BlockState,
    pub raw_ore_chance: f32,
    pub density: Df,
    pub richness: Df,
    pub filler_gap: Df,
}

#[derive(Clone)]
pub enum MaterialRule {
    Holder(Arc<OnceLock<MaterialRule>>),
    Block(BlockState),
    Bandlands,
    Sequence(Vec<MaterialRule>),
    Condition {
        if_true: MaterialCondition,
        then_run: Box<MaterialRule>,
    },
    OreVein(Box<OreVeinRule>),
}

pub struct OreVeinEvaluator {
    rule: OreVeinRule,
    density_sampler: DensityGetter,
    richness_sampler: DensityGetter,
    filler_gap_sampler: DensityGetter,
    random_factory: RandomFactory,
}

pub enum RuleEvaluator {
    Block(BlockState),
    Bandlands,
    Sequence(Vec<RuleEvaluator>),
    Condition {
        if_true: ConditionEvaluator,
        then_run: Box<RuleEvaluator>,
    },
    OreVein(Box<OreVeinEvaluator>),
}

impl MaterialRule {
    pub fn compile(&self, context: &mut MaterialRuleContext<'_, '_>) -> RuleEvaluator {
        match self {
            Self::Holder(holder) => holder
                .get()
                .expect("material rule holder bound")
                .compile(context),
            Self::Block(state) => RuleEvaluator::Block(*state),
            Self::Bandlands => RuleEvaluator::Bandlands,
            Self::Sequence(sequence) => {
                if sequence.len() == 1 {
                    return sequence[0].compile(context);
                }
                RuleEvaluator::Sequence(sequence.iter().map(|rule| rule.compile(context)).collect())
            }
            Self::Condition { if_true, then_run } => {
                let if_true = if_true.compile(context);
                let then_run = Box::new(then_run.compile(context));
                RuleEvaluator::Condition { if_true, then_run }
            }
            Self::OreVein(rule) => {
                let density_sampler = context.get_densities_in_chunk(&rule.density, true);
                let richness_sampler = context.get_densities_in_chunk(&rule.richness, true);
                let filler_gap_sampler = context.get_densities_in_chunk(&rule.filler_gap, false);
                let random_factory = context.get_or_create_random_factory(ORE_RANDOM);
                RuleEvaluator::OreVein(Box::new(OreVeinEvaluator {
                    rule: (**rule).clone(),
                    density_sampler,
                    richness_sampler,
                    filler_gap_sampler,
                    random_factory,
                }))
            }
        }
    }
}

impl RuleEvaluator {
    pub fn try_apply(
        &mut self,
        context: &mut MaterialRuleContext<'_, '_>,
        block_x: i32,
        block_y: i32,
        block_z: i32,
    ) -> Option<BlockState> {
        match self {
            Self::Block(state) => Some(*state),
            Self::Bandlands => Some(context.get_band(block_x, block_y, block_z)),
            Self::Sequence(sequence) => sequence
                .iter_mut()
                .find_map(|rule| rule.try_apply(context, block_x, block_y, block_z)),
            Self::Condition { if_true, then_run } => {
                if if_true.test(context) {
                    then_run.try_apply(context, block_x, block_y, block_z)
                } else {
                    None
                }
            }
            Self::OreVein(ore_vein) => ore_vein.try_apply(context, block_x, block_y, block_z),
        }
    }
}

impl OreVeinEvaluator {
    fn try_apply(
        &self,
        context: &mut MaterialRuleContext<'_, '_>,
        block_x: i32,
        block_y: i32,
        block_z: i32,
    ) -> Option<BlockState> {
        let density = context.get_density(&self.density_sampler);
        if density <= 0.0 {
            return None;
        }
        let mut random = self.random_factory.at(block_x, block_y, block_z);
        if random.next_f32() > density {
            return None;
        }
        let richness = context.get_density(&self.richness_sampler);
        if random.next_f32() < richness && context.get_density(&self.filler_gap_sampler) < 0.0 {
            Some(if random.next_f32() < self.rule.raw_ore_chance {
                self.rule.raw_ore_block
            } else {
                self.rule.ore_block
            })
        } else {
            Some(self.rule.filler_block)
        }
    }
}