Pumpkin/pumpkin-codegen/src/chunk_gen_settings.rs
Alexander Medvedev 23e7992b2d Port to 26.2
2026-06-20 20:07:31 +02:00

652 lines
23 KiB
Rust

use heck::ToShoutySnakeCase;
use proc_macro2::TokenStream;
use quote::{ToTokens, format_ident, quote};
use serde::Deserialize;
use std::{collections::BTreeMap, fs};
/// Deserialized block reference used in chunk generation settings (e.g., default block or fluid).
#[derive(Deserialize)]
pub struct BlockStateCodecStruct {
/// Block registry name including the `minecraft:` namespace prefix.
#[serde(rename = "Name")]
pub name: String,
/// Optional block state properties (e.g., `{"facing": "north"}`).
#[serde(rename = "Properties")]
#[allow(dead_code)]
pub properties: Option<BTreeMap<String, String>>,
}
/// Deserialized chunk generation settings for a dimension, sourced from `chunk_gen_settings.json`.
#[derive(Deserialize)]
pub struct GenerationSettingsStruct {
/// Whether aquifer (underground water pocket) generation is enabled.
#[serde(default)]
pub aquifers_enabled: bool,
/// Whether ore-vein generation is enabled.
#[serde(default)]
pub ore_veins_enabled: bool,
/// Whether to use the legacy random number source for this dimension.
#[serde(default)]
pub legacy_random_source: bool,
/// Y-level treated as sea level for this dimension.
pub sea_level: i32,
/// Default fluid block (usually water or lava) placed by the aquifer generator.
pub default_fluid: BlockStateCodecStruct,
/// Default solid block used to fill the terrain.
pub default_block: BlockStateCodecStruct,
/// Noise shape parameters controlling vertical and horizontal cell sizes.
#[serde(rename = "noise")]
pub shape: GenerationShapeConfigStruct,
/// Hierarchical surface material rule determining which block is placed at each surface point.
pub surface_rule: MaterialRuleStruct,
}
/// Deserialized noise-shape configuration controlling terrain cell dimensions.
#[derive(Deserialize)]
pub struct GenerationShapeConfigStruct {
/// Minimum Y level for terrain generation.
pub min_y: i8,
/// Total vertical span of the generation region in blocks.
pub height: u16,
/// Log₂ of the horizontal cell block count (cell width = `1 << size_horizontal`).
pub size_horizontal: u8,
/// Log₂ of the vertical cell block count (cell height = `1 << size_vertical`).
pub size_vertical: u8,
}
/// Deserialized surface material rule that determines which block to place at a given surface point.
#[derive(Deserialize)]
#[serde(tag = "type")]
pub enum MaterialRuleStruct {
/// Place a specific block state unconditionally.
#[serde(rename = "minecraft:block")]
Block {
/// The block state to place.
result_state: BlockStateCodecStruct,
},
/// Evaluate each rule in order, stopping at the first match.
#[serde(rename = "minecraft:sequence")]
Sequence {
/// The ordered list of child rules.
sequence: Vec<Self>,
},
/// Apply `then_run` only when `if_true` evaluates to true.
#[serde(rename = "minecraft:condition")]
Condition {
/// The condition that must be satisfied.
if_true: MaterialConditionStruct,
/// The rule to apply when the condition is met.
then_run: Box<Self>,
},
/// Special Badlands terrain coloring rule.
#[serde(rename = "minecraft:bandlands")]
Badlands,
}
fn deserialize_string_or_vec<'de, D>(deserializer: D) -> Result<Vec<String>, D::Error>
where
D: serde::Deserializer<'de>,
{
struct StringOrVec;
impl<'de> serde::de::Visitor<'de> for StringOrVec {
type Value = Vec<String>;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("string or list of strings")
}
fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
Ok(vec![value.to_owned()])
}
fn visit_seq<S>(self, mut seq: S) -> Result<Self::Value, S::Error>
where
S: serde::de::SeqAccess<'de>,
{
let mut vec = Vec::new();
while let Some(el) = seq.next_element()? {
vec.push(el);
}
Ok(vec)
}
}
deserializer.deserialize_any(StringOrVec)
}
/// Deserialized surface material condition that gates a material rule.
#[derive(Deserialize)]
#[serde(tag = "type")]
pub enum MaterialConditionStruct {
/// True when the current position is in one of the listed biomes.
#[serde(rename = "minecraft:biome")]
Biome {
/// List of biome resource locations to match against.
#[serde(deserialize_with = "deserialize_string_or_vec")]
biome_is: Vec<String>,
},
/// True when a named noise value is within the given range.
#[serde(rename = "minecraft:noise_threshold")]
NoiseThreshold {
/// Resource location of the noise parameter.
noise: String,
/// Minimum threshold (inclusive).
min_threshold: f64,
/// Maximum threshold (inclusive).
max_threshold: f64,
},
/// True below a Y offset and false above another, with a linear gradient in between.
#[serde(rename = "minecraft:vertical_gradient")]
VerticalGradient {
/// Name of the random source used for this gradient.
random_name: String,
/// Y offset below which the condition is always true.
true_at_and_below: YOffsetStruct,
/// Y offset above which the condition is always false.
false_at_and_above: YOffsetStruct,
},
/// True when the position is above a given Y anchor.
#[serde(rename = "minecraft:y_above")]
YAbove {
/// The Y offset anchor to compare against.
anchor: YOffsetStruct,
/// Multiplier applied to surface depth when computing the threshold.
surface_depth_multiplier: i32,
/// Whether to add stone depth to the comparison value.
add_stone_depth: bool,
},
/// True when the position is above a water surface within a certain offset.
#[serde(rename = "minecraft:water")]
Water {
/// Y offset relative to the water surface.
offset: i32,
/// Multiplier applied to surface depth.
surface_depth_multiplier: i32,
/// Whether to add stone depth to the comparison value.
add_stone_depth: bool,
},
/// True when the biome temperature is cold (below freezing).
#[serde(rename = "minecraft:temperature")]
Temperature,
/// True when the terrain is steep (high slope).
#[serde(rename = "minecraft:steep")]
Steep,
/// Inverts the inner condition.
#[serde(rename = "minecraft:not")]
Not {
/// The condition to invert.
invert: Box<Self>,
},
/// True when there is a hole (cave opening) at the position.
#[serde(rename = "minecraft:hole")]
Hole,
/// True when the position is above the preliminary surface estimate.
#[serde(rename = "minecraft:above_preliminary_surface")]
AbovePreliminarySurface,
/// True based on the depth of stone/ceiling relative to the surface.
#[serde(rename = "minecraft:stone_depth")]
StoneDepth {
/// Y offset added to the depth value.
offset: i32,
/// Whether to include surface depth in the calculation.
add_surface_depth: bool,
/// Additional secondary depth range.
secondary_depth_range: i32,
/// Surface type to measure from: `"ceiling"` or `"floor"`.
surface_type: String,
},
}
/// Deserialized Y offset that can be expressed relative to different reference points.
#[derive(Deserialize)]
#[serde(untagged)]
pub enum YOffsetStruct {
/// An absolute Y coordinate.
Absolute {
/// The absolute Y level.
absolute: i16,
},
/// A Y level measured upward from the dimension's minimum Y.
AboveBottom {
/// Number of blocks above the bottom of the dimension.
above_bottom: i8,
},
/// A Y level measured downward from the dimension's maximum Y.
BelowTop {
/// Number of blocks below the top of the dimension.
below_top: i8,
},
}
// --- ToTokens Implementations ---
impl ToTokens for BlockStateCodecStruct {
/// Emits a `BlockBlueprint` literal, stripping the `minecraft:` namespace prefix from the block name.
fn to_tokens(&self, tokens: &mut TokenStream) {
let name = &self.name.strip_prefix("minecraft:").unwrap_or(&self.name);
let name_stripped = name.strip_prefix("minecraft:").unwrap_or(name);
let block_ident =
quote::format_ident!("{}", name_stripped.to_uppercase().replace([':', '-'], "_"));
// TODO: use props
tokens.extend(quote! {
crate::Block::#block_ident.default_state
});
}
}
impl ToTokens for GenerationSettingsStruct {
/// Emits a `GenerationSettings` struct literal with all fields populated from the deserialized data.
fn to_tokens(&self, tokens: &mut TokenStream) {
let aquifers = self.aquifers_enabled;
let ores = self.ore_veins_enabled;
let legacy = self.legacy_random_source;
let sea_level = self.sea_level;
let fluid = &self.default_fluid;
let block = &self.default_block;
let shape = &self.shape;
let rule = &self.surface_rule;
tokens.extend(quote!(
GenerationSettings {
aquifers_enabled: #aquifers,
ore_veins_enabled: #ores,
legacy_random_source: #legacy,
sea_level: #sea_level,
default_fluid: #fluid,
shape: #shape,
surface_rule: #rule,
default_block: #block,
}
));
}
}
impl ToTokens for GenerationShapeConfigStruct {
/// Emits a `GenerationShapeConfig` struct literal with all noise-shape dimensions.
fn to_tokens(&self, tokens: &mut TokenStream) {
let min_y = self.min_y;
let height = self.height;
let hor = self.size_horizontal;
let ver = self.size_vertical;
tokens.extend(quote!(
GenerationShapeConfig { min_y: #min_y, height: #height, size_horizontal: #hor, size_vertical: #ver }
));
}
}
impl ToTokens for YOffsetStruct {
/// Emits a `YOffset` enum variant literal corresponding to the deserialized offset kind.
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::Absolute { absolute } => {
tokens.extend(quote!(YOffset::Absolute(pumpkin_util::y_offset::Absolute { absolute: #absolute })));
}
Self::AboveBottom { above_bottom } => {
tokens.extend(quote!(YOffset::AboveBottom(pumpkin_util::y_offset::AboveBottom { above_bottom: #above_bottom })));
}
Self::BelowTop { below_top } => {
tokens.extend(quote!(YOffset::BelowTop(pumpkin_util::y_offset::BelowTop { below_top: #below_top })));
}
}
}
}
impl ToTokens for MaterialConditionStruct {
/// Emits a `MaterialCondition` enum variant literal for each surface condition kind.
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::Biome { biome_is } => {
let biomes = biome_is
.iter()
.map(|b| b.strip_prefix("minecraft:").unwrap_or(b).to_uppercase());
let biome_refs: Vec<TokenStream> = biomes
.map(|b| {
let ident = format_ident!("{}", b);
quote!(&crate::biome::Biome::#ident)
})
.collect();
tokens.extend(quote!(
MaterialCondition::Biome(BiomeMaterialCondition {
biome_is: &[#(#biome_refs),*],
})
));
}
Self::NoiseThreshold {
noise,
min_threshold,
max_threshold,
} => {
let noise_id = quote::format_ident!(
"{}",
noise
.strip_prefix("minecraft:")
.unwrap()
.to_shouty_snake_case()
);
tokens.extend(quote!(
MaterialCondition::NoiseThreshold(NoiseThresholdMaterialCondition {
noise: DoublePerlinNoiseParameters::#noise_id,
min_threshold: #min_threshold,
max_threshold: #max_threshold,
})
));
}
Self::VerticalGradient {
random_name,
true_at_and_below,
false_at_and_above,
} => {
// Pre calc for speed :D
let bytes = md5::compute(random_name.as_bytes());
let lo = u64::from_be_bytes(bytes[0..8].try_into().expect("incorrect length"));
let hi = u64::from_be_bytes(bytes[8..16].try_into().expect("incorrect length"));
tokens.extend(quote!(
MaterialCondition::VerticalGradient(VerticalGradientMaterialCondition {
random_lo: #lo,
random_hi: #hi,
true_at_and_below: #true_at_and_below,
false_at_and_above: #false_at_and_above,
})
));
}
Self::YAbove {
anchor,
surface_depth_multiplier,
add_stone_depth,
} => {
tokens.extend(quote!(
MaterialCondition::YAbove(AboveYMaterialCondition {
anchor: #anchor,
surface_depth_multiplier: #surface_depth_multiplier,
add_stone_depth: #add_stone_depth,
})
));
}
Self::Water {
offset,
surface_depth_multiplier,
add_stone_depth,
} => {
tokens.extend(quote!(
MaterialCondition::Water(WaterMaterialCondition {
offset: #offset,
surface_depth_multiplier: #surface_depth_multiplier,
add_stone_depth: #add_stone_depth,
})
));
}
Self::Temperature => {
tokens.extend(quote!(MaterialCondition::Temperature));
}
Self::Steep => {
tokens.extend(quote!(MaterialCondition::Steep));
}
Self::Not { invert } => {
tokens.extend(quote!(
MaterialCondition::Not(NotMaterialCondition {
invert: &#invert,
})
));
}
Self::Hole => {
tokens.extend(quote!(MaterialCondition::Hole(HoleMaterialCondition)));
}
Self::AbovePreliminarySurface => {
tokens.extend(quote!(MaterialCondition::AbovePreliminarySurface(
SurfaceMaterialCondition
)));
}
Self::StoneDepth {
offset,
add_surface_depth,
secondary_depth_range,
surface_type,
} => {
let surface_type_token = match surface_type.as_str() {
"ceiling" => quote!(
pumpkin_util::math::vertical_surface_type::VerticalSurfaceType::Ceiling
),
"floor" => quote!(
pumpkin_util::math::vertical_surface_type::VerticalSurfaceType::Floor
),
_ => quote!(panic!("Unknown surface type")),
};
tokens.extend(quote!(
MaterialCondition::StoneDepth(StoneDepthMaterialCondition {
offset: #offset,
add_surface_depth: #add_surface_depth,
secondary_depth_range: #secondary_depth_range,
surface_type: #surface_type_token,
})
));
}
}
}
}
impl ToTokens for MaterialRuleStruct {
/// Emits a `MaterialRule` enum variant literal for each surface placement rule kind.
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::Block { result_state } => {
tokens.extend(quote!(
MaterialRule::Block(BlockMaterialRule {
result_state: #result_state
})
));
}
Self::Sequence { sequence } => {
tokens.extend(quote!(
MaterialRule::Sequence(SequenceMaterialRule {
sequence: &[#(#sequence),*]
})
));
}
Self::Condition { if_true, then_run } => {
tokens.extend(quote!(
MaterialRule::Condition(ConditionMaterialRule {
if_true: #if_true,
then_run: &#then_run
})
));
}
Self::Badlands => {
tokens.extend(quote!(MaterialRule::Badlands(BadLandsMaterialRule)));
}
}
}
}
/// Reads `chunk_gen_settings.json` and emits the complete chunk generation settings `TokenStream`.
pub fn build() -> TokenStream {
let json: BTreeMap<String, GenerationSettingsStruct> =
serde_json::from_str(&fs::read_to_string("../assets/chunk_gen_settings.json").unwrap())
.expect("Failed to parse settings.json");
let mut const_defs = TokenStream::new();
for (name, settings) in &json {
let upper_name = name.to_uppercase();
let const_name = format_ident!("{}", upper_name);
const_defs.extend(quote!(
pub const #const_name: GenerationSettings = #settings;
));
}
quote!(
use crate::dimension::Dimension;
use crate::chunk::DoublePerlinNoiseParameters;
use crate::BlockState;
use std::{cell::RefCell, num::NonZeroUsize};
use pumpkin_util::random::RandomDeriver;
use pumpkin_util::y_offset::YOffset;
use crate::biome::Biome;
use pumpkin_util::y_offset::Absolute;
pub struct GenerationSettings {
pub aquifers_enabled: bool,
pub ore_veins_enabled: bool,
pub legacy_random_source: bool,
pub sea_level: i32,
pub default_fluid: &'static BlockState,
pub shape: GenerationShapeConfig,
pub surface_rule: MaterialRule,
pub default_block: &'static BlockState,
}
pub struct GenerationShapeConfig {
pub min_y: i8,
pub height: u16,
pub size_horizontal: u8,
pub size_vertical: u8,
}
impl GenerationShapeConfig {
#[inline]
#[must_use]
pub const fn vertical_cell_block_count(&self) -> u8 { self.size_vertical << 2 }
#[inline]
#[must_use]
pub const fn horizontal_cell_block_count(&self) -> u8 { self.size_horizontal << 2 }
#[must_use]
pub const fn max_y(&self) -> u16 {
if self.min_y >= 0 {
self.height + self.min_y as u16
} else {
(self.height as i32 + self.min_y as i32) as u16
}
}
#[must_use]
pub fn trim_height(&self, bottom_y: i8, top_y: u16) -> Self {
let new_min = self.min_y.max(bottom_y);
let this_top = if self.min_y >= 0 {
self.height + self.min_y as u16
} else {
self.height - self.min_y.unsigned_abs() as u16
};
let new_top = this_top.min(top_y);
let new_height = if new_min >= 0 {
new_top - new_min as u16
} else {
new_top + new_min.unsigned_abs() as u16
};
Self {
min_y: new_min,
height: new_height,
size_horizontal: self.size_horizontal,
size_vertical: self.size_vertical,
}
}
}
pub struct BlockMaterialRule {
pub result_state: &'static BlockState,
}
pub struct SequenceMaterialRule {
pub sequence: &'static [MaterialRule],
}
pub struct ConditionMaterialRule {
pub if_true: MaterialCondition,
pub then_run: &'static MaterialRule,
}
pub struct BadLandsMaterialRule;
pub enum MaterialRule {
Block(BlockMaterialRule),
Sequence(SequenceMaterialRule),
Condition(ConditionMaterialRule),
Badlands(BadLandsMaterialRule),
}
pub struct BiomeMaterialCondition {
pub biome_is: &'static [&'static Biome],
}
pub struct NoiseThresholdMaterialCondition {
pub noise: DoublePerlinNoiseParameters,
pub min_threshold: f64,
pub max_threshold: f64,
}
pub struct VerticalGradientMaterialCondition {
pub random_lo: u64,
pub random_hi: u64,
pub true_at_and_below: YOffset,
pub false_at_and_above: YOffset,
}
pub struct AboveYMaterialCondition {
pub anchor: YOffset,
pub surface_depth_multiplier: i32,
pub add_stone_depth: bool,
}
pub struct WaterMaterialCondition {
pub offset: i32,
pub surface_depth_multiplier: i32,
pub add_stone_depth: bool,
}
pub struct HoleMaterialCondition;
pub struct NotMaterialCondition {
pub invert: &'static MaterialCondition,
}
pub struct SurfaceMaterialCondition;
pub struct StoneDepthMaterialCondition {
pub offset: i32,
pub add_surface_depth: bool,
pub secondary_depth_range: i32,
pub surface_type: pumpkin_util::math::vertical_surface_type::VerticalSurfaceType,
}
pub enum MaterialCondition {
Biome(BiomeMaterialCondition),
NoiseThreshold(NoiseThresholdMaterialCondition),
VerticalGradient(VerticalGradientMaterialCondition),
YAbove(AboveYMaterialCondition),
Water(WaterMaterialCondition),
Temperature,
Steep,
Not(NotMaterialCondition),
Hole(HoleMaterialCondition),
AbovePreliminarySurface(SurfaceMaterialCondition),
StoneDepth(StoneDepthMaterialCondition),
}
impl GenerationSettings {
#const_defs
#[must_use]
pub fn from_dimension(dimension: &Dimension) -> &'static Self {
if dimension == &Dimension::OVERWORLD {
&Self::OVERWORLD
} else if dimension == &Dimension::THE_NETHER {
&Self::NETHER
} else {
&Self::END
}
}
}
)
}