Files
Pumpkin/pumpkin-codegen/src/carver.rs
Alexander Medvedev 2fcc9bcf4a chore: enforce some more clippy lints
pumpkin contributors will love me :cap:
2026-05-20 22:27:54 +02:00

277 lines
11 KiB
Rust

use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use serde_json::Value;
use std::collections::BTreeMap;
use std::fs;
pub fn build() -> TokenStream {
let carvers: BTreeMap<String, Value> =
serde_json::from_str(&fs::read_to_string("../assets/carver.json").unwrap())
.expect("Failed to parse carver.json");
let mut carver_instances = Vec::new();
for (name, data) in carvers {
let variant_name = format_ident!("{}", name.to_uppercase());
let config = &data["config"];
let carver_type = data["type"].as_str().unwrap_or("");
let prob = config["probability"].as_f64().unwrap_or(0.0) as f32;
let y = value_to_height_provider(&config["y"]);
let y_scale = value_to_float_provider(&config["yScale"]);
let lava_level = value_to_y_offset(&config["lava_level"]);
let replaceable_str = config["replaceable"].as_str().unwrap_or("");
let replaceable = if let Some(tag_name) = replaceable_str.strip_prefix('#') {
let tag_name = tag_name.to_uppercase().replace([':', '.', '-'], "_");
let tag_ident = format_ident!("{}", tag_name);
quote! { crate::tag::Block::#tag_ident }
} else {
// Handle non-tag replaceables if any
quote! { crate::tag::Block::MINECRAFT_OVERWORLD_CARVER_REPLACEABLES }
};
let additional = match carver_type {
"minecraft:cave" => {
let horizontal_radius_multiplier =
value_to_float_provider(&config["horizontal_radius_multiplier"]);
let vertical_radius_multiplier =
value_to_float_provider(&config["vertical_radius_multiplier"]);
let floor_level = value_to_float_provider(&config["floor_level"]);
quote! {
CarverAdditionalConfig::Cave(CaveCarverConfig {
horizontal_radius_multiplier: #horizontal_radius_multiplier,
vertical_radius_multiplier: #vertical_radius_multiplier,
floor_level: #floor_level,
})
}
}
"minecraft:nether_cave" => {
let horizontal_radius_multiplier =
value_to_float_provider(&config["horizontal_radius_multiplier"]);
let vertical_radius_multiplier =
value_to_float_provider(&config["vertical_radius_multiplier"]);
let floor_level = value_to_float_provider(&config["floor_level"]);
quote! {
CarverAdditionalConfig::NetherCave(CaveCarverConfig {
horizontal_radius_multiplier: #horizontal_radius_multiplier,
vertical_radius_multiplier: #vertical_radius_multiplier,
floor_level: #floor_level,
})
}
}
"minecraft:canyon" => {
let vertical_rotation = value_to_float_provider(&config["vertical_rotation"]);
let shape = &config["shape"];
let distance_factor = value_to_float_provider(&shape["distance_factor"]);
let thickness = value_to_float_provider(&shape["thickness"]);
let width_smoothness = shape["width_smoothness"].as_i64().unwrap_or(0) as i32;
let horizontal_radius_factor =
value_to_float_provider(&shape["horizontal_radius_factor"]);
let vertical_radius_default_factor = shape["vertical_radius_default_factor"]
.as_f64()
.unwrap_or(0.0) as f32;
let vertical_radius_center_factor = shape["vertical_radius_center_factor"]
.as_f64()
.unwrap_or(0.0) as f32;
quote! {
CarverAdditionalConfig::Canyon(CanyonCarverConfig {
vertical_rotation: #vertical_rotation,
shape: CanyonShapeConfig {
distance_factor: #distance_factor,
thickness: #thickness,
width_smoothness: #width_smoothness,
horizontal_radius_factor: #horizontal_radius_factor,
vertical_radius_default_factor: #vertical_radius_default_factor,
vertical_radius_center_factor: #vertical_radius_center_factor,
}
})
}
}
_ => quote! { CarverAdditionalConfig::Cave(CaveCarverConfig::default()) },
};
carver_instances.push(quote! {
pub const #variant_name: CarverConfig = CarverConfig {
probability: #prob,
y: #y,
y_scale: #y_scale,
lava_level: #lava_level,
replaceable: #replaceable,
additional: #additional,
};
});
}
quote! {
use pumpkin_util::math::float_provider::{FloatProvider, NormalFloatProvider, ConstantFloatProvider, UniformFloatProvider, TrapezoidFloatProvider, ClampedNormalFloatProvider};
use pumpkin_util::y_offset::{YOffset, Absolute, AboveBottom, BelowTop};
pub enum HeightProvider {
Uniform(UniformHeightProvider),
Trapezoid(TrapezoidHeightProvider),
VeryBiasedToBottom(VeryBiasedToBottomHeightProvider),
}
pub struct UniformHeightProvider {
pub min_inclusive: YOffset,
pub max_inclusive: YOffset,
}
pub struct TrapezoidHeightProvider {
pub min_inclusive: YOffset,
pub max_inclusive: YOffset,
pub plateau: Option<i32>,
}
pub struct VeryBiasedToBottomHeightProvider {
pub min_inclusive: YOffset,
pub max_inclusive: YOffset,
pub inner: Option<std::num::NonZeroU32>,
}
pub struct CaveCarverConfig {
pub horizontal_radius_multiplier: FloatProvider,
pub vertical_radius_multiplier: FloatProvider,
pub floor_level: FloatProvider,
}
impl CaveCarverConfig {
#[must_use]
pub const fn default() -> Self {
Self {
horizontal_radius_multiplier: FloatProvider::Constant(1.0),
vertical_radius_multiplier: FloatProvider::Constant(1.0),
floor_level: FloatProvider::Constant(-0.7),
}
}
}
pub struct CanyonShapeConfig {
pub distance_factor: FloatProvider,
pub thickness: FloatProvider,
pub width_smoothness: i32,
pub horizontal_radius_factor: FloatProvider,
pub vertical_radius_default_factor: f32,
pub vertical_radius_center_factor: f32,
}
pub struct CanyonCarverConfig {
pub vertical_rotation: FloatProvider,
pub shape: CanyonShapeConfig,
}
pub enum CarverAdditionalConfig {
Cave(CaveCarverConfig),
NetherCave(CaveCarverConfig),
Canyon(CanyonCarverConfig),
}
pub struct CarverConfig {
pub probability: f32,
pub y: HeightProvider,
pub y_scale: FloatProvider,
pub lava_level: YOffset,
pub replaceable: crate::tag::Tag,
pub additional: CarverAdditionalConfig,
}
use super::*;
#(#carver_instances)*
}
}
fn value_to_float_provider(v: &Value) -> TokenStream {
if v.is_number() {
let f = v.as_f64().unwrap_or(0.0) as f32;
quote! { FloatProvider::Constant(#f) }
} else {
// This is complex because we need to match the structure of FloatProvider in pumpkin-util
// For now, let's handle the common case of uniform
let type_str = v["type"].as_str().unwrap_or("");
match type_str {
"minecraft:uniform" => {
let min = v["min_inclusive"].as_f64().unwrap_or(0.0) as f32;
let max = v["max_exclusive"].as_f64().unwrap_or(0.0) as f32;
quote! { FloatProvider::Object(NormalFloatProvider::Uniform(UniformFloatProvider::new(#min, #max))) }
}
"minecraft:constant" => {
let val = v["value"].as_f64().unwrap_or(0.0) as f32;
quote! { FloatProvider::Constant(#val) }
}
"minecraft:trapezoid" => {
let min = v["min"].as_f64().unwrap_or(0.0) as f32;
let max = v["max"].as_f64().unwrap_or(0.0) as f32;
let plateau = v["plateau"].as_f64().unwrap_or(0.0) as f32;
quote! { FloatProvider::Object(NormalFloatProvider::Trapezoid(TrapezoidFloatProvider::new(#min, #max, #plateau))) }
}
_ => {
// Fallback to constant 0 if unknown
quote! { FloatProvider::Constant(0.0) }
}
}
}
}
fn value_to_height_provider(v: &Value) -> TokenStream {
let type_str = v["type"].as_str().unwrap_or("");
match type_str {
"minecraft:uniform" => {
let min = value_to_y_offset(&v["min_inclusive"]);
let max = value_to_y_offset(&v["max_inclusive"]);
quote! {
HeightProvider::Uniform(UniformHeightProvider {
min_inclusive: #min,
max_inclusive: #max,
})
}
}
"minecraft:trapezoid" => {
let min = value_to_y_offset(&v["min_inclusive"]);
let max = value_to_y_offset(&v["max_inclusive"]);
let plateau = if v["plateau"].is_number() {
let p = v["plateau"].as_i64().unwrap_or(0) as i32;
quote! { Some(#p) }
} else {
quote! { None }
};
quote! {
HeightProvider::Trapezoid(TrapezoidHeightProvider {
min_inclusive: #min,
max_inclusive: #max,
plateau: #plateau,
})
}
}
_ => {
let zero = value_to_y_offset(&Value::Null);
quote! {
HeightProvider::Uniform(UniformHeightProvider {
min_inclusive: #zero,
max_inclusive: #zero,
})
}
}
}
}
fn value_to_y_offset(v: &Value) -> TokenStream {
if v.is_object() {
if let Some(abs) = v.get("absolute") {
let val = abs.as_i64().unwrap_or(0) as i16;
quote! { YOffset::Absolute(Absolute { absolute: #val }) }
} else if let Some(ab) = v.get("above_bottom") {
let val = ab.as_i64().unwrap_or(0) as i8;
quote! { YOffset::AboveBottom(AboveBottom { above_bottom: #val }) }
} else if let Some(bt) = v.get("below_top") {
let val = bt.as_i64().unwrap_or(0) as i8;
quote! { YOffset::BelowTop(BelowTop { below_top: #val }) }
} else {
quote! { YOffset::Absolute(Absolute { absolute: 0 }) }
}
} else {
quote! { YOffset::Absolute(Absolute { absolute: 0 }) }
}
}