Pumpkin/tools/pumpkin-codegen/src/noise_settings.rs
2026-09-09 18:01:31 +02:00

425 lines
14 KiB
Rust

use proc_macro2::TokenStream;
use quote::{ToTokens, format_ident, quote};
use serde::Deserialize;
use std::{collections::BTreeMap, fs};
/// Deserialized block reference used in noise settings (e.g., default block or fluid).
#[derive(Deserialize, Clone)]
#[serde(untagged)]
pub enum BlockStateCodecStruct {
Name(String),
Structured {
#[serde(rename = "Name")]
name: String,
#[serde(rename = "Properties")]
#[allow(dead_code)]
properties: Option<BTreeMap<String, String>>,
},
}
impl ToTokens for BlockStateCodecStruct {
fn to_tokens(&self, tokens: &mut TokenStream) {
let name = match self {
Self::Name(name) => name,
Self::Structured { name, .. } => name,
};
let name_stripped = name.strip_prefix("minecraft:").unwrap_or(name);
let block_ident =
quote::format_ident!("{}", name_stripped.to_uppercase().replace([':', '-'], "_"));
tokens.extend(quote! {
crate::Block::#block_ident.default_state
});
}
}
/// Raw deserialized noise settings file from `worldgen/noise_settings`.
#[derive(Deserialize)]
pub struct NoiseSettingsFileStruct {
#[serde(default)]
pub aquifers: Option<serde_json::Value>,
#[serde(default)]
pub aquifers_enabled: Option<bool>,
#[serde(default)]
pub ore_veins_enabled: Option<bool>,
#[serde(default)]
pub legacy_random_source: bool,
pub sea_level: i32,
pub default_fluid: BlockStateCodecStruct,
pub default_block: BlockStateCodecStruct,
#[serde(rename = "noise")]
pub shape: GenerationShapeConfigStruct,
#[serde(default)]
pub material_rule: Option<serde_json::Value>,
#[serde(default)]
pub surface_rule: Option<serde_json::Value>,
#[serde(default)]
pub spawn_target: Vec<RawSpawnTarget>,
}
/// Raw spawn target deserialization supporting both noise map format and structured format.
#[derive(Deserialize, Clone)]
#[serde(untagged)]
pub enum RawSpawnTarget {
Structured(ParameterPointStruct),
NoiseMap(BTreeMap<String, ParameterStruct>),
}
impl RawSpawnTarget {
pub fn to_parameter_point(&self) -> ParameterPointStruct {
match self {
Self::Structured(p) => p.clone(),
Self::NoiseMap(map) => {
let mut temp = ParameterStruct::Span([-1.0, 1.0]);
let mut hum = ParameterStruct::Span([-1.0, 1.0]);
let mut cont = ParameterStruct::Span([-1.0, 1.0]);
let mut erosion = ParameterStruct::Span([-1.0, 1.0]);
let mut depth = ParameterStruct::Span([0.0, 0.0]);
let mut weirdness = ParameterStruct::Span([-1.0, 1.0]);
let mut offset = ParameterStruct::Point(0.0);
for (key, val) in map {
if key.contains("temperature") {
temp = val.clone();
} else if key.contains("vegetation") || key.contains("humidity") {
hum = val.clone();
} else if key.contains("continents") || key.contains("continentalness") {
cont = val.clone();
} else if key.contains("erosion") {
erosion = val.clone();
} else if key.contains("depth") {
depth = val.clone();
} else if key.contains("ridges") || key.contains("weirdness") {
weirdness = val.clone();
} else if key.contains("offset") {
offset = val.clone();
}
}
ParameterPointStruct {
temperature: temp,
humidity: hum,
continentalness: cont,
erosion,
depth,
weirdness,
offset,
}
}
}
}
}
/// Deserialized parameter interval or point.
#[derive(Deserialize, Clone)]
#[serde(untagged)]
pub enum ParameterStruct {
Point(f32),
Span([f32; 2]),
}
impl Default for ParameterStruct {
fn default() -> Self {
Self::Point(0.0)
}
}
impl ToTokens for ParameterStruct {
fn to_tokens(&self, tokens: &mut TokenStream) {
match self {
Self::Point(val) => {
let quantized = (*val * 10000.0) as i64;
tokens.extend(quote!(
crate::biome::Parameter::new(#quantized, #quantized)
));
}
Self::Span([min, max]) => {
let min_q = (*min * 10000.0) as i64;
let max_q = (*max * 10000.0) as i64;
tokens.extend(quote!(
crate::biome::Parameter::new(#min_q, #max_q)
));
}
}
}
}
/// Deserialized parameter point for spawn target configuration.
#[derive(Deserialize, Clone)]
pub struct ParameterPointStruct {
pub temperature: ParameterStruct,
pub humidity: ParameterStruct,
pub continentalness: ParameterStruct,
pub erosion: ParameterStruct,
pub depth: ParameterStruct,
pub weirdness: ParameterStruct,
#[serde(default)]
pub offset: ParameterStruct,
}
impl ToTokens for ParameterPointStruct {
fn to_tokens(&self, tokens: &mut TokenStream) {
let temp = &self.temperature;
let hum = &self.humidity;
let cont = &self.continentalness;
let erosion = &self.erosion;
let depth = &self.depth;
let weirdness = &self.weirdness;
let offset = match self.offset {
ParameterStruct::Point(val) => (val * 10000.0) as i64,
ParameterStruct::Span([min, _]) => (min * 10000.0) as i64,
};
tokens.extend(quote!(
crate::biome::ParameterPoint {
temperature: #temp,
humidity: #hum,
continentalness: #cont,
erosion: #erosion,
depth: #depth,
weirdness: #weirdness,
offset: #offset,
}
));
}
}
/// Deserialized noise-shape configuration controlling terrain cell dimensions.
#[derive(Deserialize, Clone)]
pub struct GenerationShapeConfigStruct {
pub min_y: i8,
pub height: u16,
#[serde(default)]
pub size_horizontal: u8,
#[serde(default)]
pub size_vertical: u8,
}
impl ToTokens for GenerationShapeConfigStruct {
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 }
));
}
}
/// Deserialized noise settings struct.
#[derive(Clone)]
pub struct NoiseSettingsStruct {
pub aquifers_enabled: bool,
pub ore_veins_enabled: bool,
pub legacy_random_source: bool,
pub sea_level: i32,
pub default_fluid: BlockStateCodecStruct,
pub default_block: BlockStateCodecStruct,
pub shape: GenerationShapeConfigStruct,
pub spawn_target: Vec<ParameterPointStruct>,
}
impl ToTokens for NoiseSettingsStruct {
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 spawn_target = &self.spawn_target;
tokens.extend(quote!(
NoiseSettings {
aquifers_enabled: #aquifers,
ore_veins_enabled: #ores,
legacy_random_source: #legacy,
sea_level: #sea_level,
default_fluid: #fluid,
shape: #shape,
default_block: #block,
spawn_target: &[#(#spawn_target),*],
}
));
}
}
/// Reads noise_settings files from datapack and generates NoiseSettings constants.
pub fn build() -> TokenStream {
let noise_settings_dir =
std::path::Path::new("../../assets/datapacks/26_2/data/minecraft/worldgen/noise_settings");
let mut json: BTreeMap<String, NoiseSettingsStruct> = BTreeMap::new();
let mut entries: Vec<_> = fs::read_dir(noise_settings_dir)
.expect("Missing worldgen/noise_settings directory")
.flatten()
.filter(|e| e.path().extension().is_some_and(|ext| ext == "json"))
.collect();
entries.sort_by_key(|e| e.path());
for entry in entries {
let path = entry.path();
let stem = path.file_stem().unwrap().to_string_lossy().into_owned();
if stem.starts_with('_') {
continue;
}
let content = fs::read_to_string(&path).expect("Failed to read noise_settings file");
let noise_settings: NoiseSettingsFileStruct =
serde_json::from_str(&content).expect("Failed to parse noise_settings JSON");
let aquifers_enabled =
noise_settings.aquifers.is_some() || noise_settings.aquifers_enabled.unwrap_or(false);
let ore_veins_enabled = noise_settings.ore_veins_enabled.unwrap_or(aquifers_enabled);
let spawn_target: Vec<ParameterPointStruct> = noise_settings
.spawn_target
.iter()
.map(RawSpawnTarget::to_parameter_point)
.collect();
let (def_h, def_v) = match stem.as_str() {
"end" | "floating_islands" => (2, 1),
_ => (1, 2),
};
let shape = GenerationShapeConfigStruct {
min_y: noise_settings.shape.min_y,
height: noise_settings.shape.height,
size_horizontal: if noise_settings.shape.size_horizontal != 0 {
noise_settings.shape.size_horizontal
} else {
def_h
},
size_vertical: if noise_settings.shape.size_vertical != 0 {
noise_settings.shape.size_vertical
} else {
def_v
},
};
let settings = NoiseSettingsStruct {
aquifers_enabled,
ore_veins_enabled,
legacy_random_source: noise_settings.legacy_random_source,
sea_level: noise_settings.sea_level,
default_fluid: noise_settings.default_fluid,
default_block: noise_settings.default_block,
shape,
spawn_target,
};
json.insert(stem, settings);
}
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: NoiseSettings = #settings;
));
}
quote!(
use crate::dimension::Dimension;
use crate::BlockState;
use crate::biome::ParameterPoint;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct NoiseSettings {
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 default_block: &'static BlockState,
pub spawn_target: &'static [ParameterPoint],
}
pub type GenerationSettings = NoiseSettings;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
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,
}
}
}
impl NoiseSettings {
#const_defs
#[must_use]
pub fn from_name(name: &str) -> Option<&'static Self> {
let name = name.strip_prefix("minecraft:").unwrap_or(name);
match name {
"overworld" => Some(&Self::OVERWORLD),
"amplified" => Some(&Self::AMPLIFIED),
"large_biomes" => Some(&Self::LARGE_BIOMES),
"nether" => Some(&Self::NETHER),
"end" => Some(&Self::END),
"caves" => Some(&Self::CAVES),
"floating_islands" => Some(&Self::FLOATING_ISLANDS),
_ => None,
}
}
#[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
}
}
}
)
}