mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
* fix: use noise_sampler * fix: end portal teleportation and generation * feature: dragon fight start (improved main end island)
722 lines
29 KiB
Rust
722 lines
29 KiB
Rust
use proc_macro2::TokenStream;
|
||
use quote::quote;
|
||
use serde_json::Value;
|
||
use std::fs;
|
||
|
||
/// Reads `placed_feature.json` and emits the complete `build_placed_features()` function `TokenStream`.
|
||
pub fn build() -> TokenStream {
|
||
let json_content = fs::read_to_string("../assets/placed_feature.json")
|
||
.expect("Failed to read placed_feature.json");
|
||
let json: Value =
|
||
serde_json::from_str(&json_content).expect("Failed to parse placed_feature.json");
|
||
|
||
let entries: Vec<TokenStream> = json
|
||
.as_object()
|
||
.unwrap()
|
||
.iter()
|
||
.map(|(name, value)| {
|
||
let pf = value_to_placed_feature(value);
|
||
quote! {
|
||
map.insert(#name.to_string(), #pf);
|
||
}
|
||
})
|
||
.collect();
|
||
|
||
quote! {
|
||
#[allow(clippy::all, unused_imports, dead_code)]
|
||
fn build_placed_features() -> std::collections::HashMap<String, PlacedFeature> {
|
||
use crate::generation::block_predicate::{
|
||
AllOfBlockPredicate, AnyOfBlockPredicate, BlockPredicate,
|
||
HasSturdyFacePredicate, InsideWorldBoundsBlockPredicate,
|
||
MatchingBlockTagPredicate, MatchingBlocksBlockPredicate, MatchingBlocksWrapper,
|
||
MatchingFluidsBlockPredicate, NotBlockPredicate, OffsetBlocksBlockPredicate,
|
||
ReplaceableBlockPredicate, SolidBlockPredicate, WouldSurviveBlockPredicate,
|
||
};
|
||
use crate::generation::height_provider::{
|
||
HeightProvider, TrapezoidHeightProvider, UniformHeightProvider,
|
||
VeryBiasedToBottomHeightProvider,
|
||
};
|
||
use pumpkin_util::y_offset::{AboveBottom, Absolute, BelowTop, YOffset};
|
||
use pumpkin_util::math::int_provider::{
|
||
BiasedToBottomIntProvider, ClampedIntProvider, ClampedNormalIntProvider,
|
||
ConstantIntProvider, IntProvider, NormalIntProvider, UniformIntProvider,
|
||
WeightedEntry, WeightedListIntProvider,
|
||
};
|
||
use crate::block::BlockStateCodec;
|
||
use pumpkin_data::{Block, BlockDirection};
|
||
use pumpkin_util::math::vector3::Vector3;
|
||
use pumpkin_util::HeightMap;
|
||
let mut map = std::collections::HashMap::new();
|
||
#(#entries)*
|
||
map
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Converts a single placed-feature JSON value into a `PlacedFeature` token stream.
|
||
///
|
||
/// # Arguments
|
||
/// – `v` – the JSON object for the entry, expected to contain `"feature"` and `"placement"` fields.
|
||
fn value_to_placed_feature(v: &Value) -> TokenStream {
|
||
let feature = value_to_feature(&v["feature"]);
|
||
let placement_arr = v["placement"]
|
||
.as_array()
|
||
.map(|a| a.as_slice())
|
||
.unwrap_or(&[]);
|
||
let placement: Vec<TokenStream> = placement_arr
|
||
.iter()
|
||
.map(value_to_placement_modifier)
|
||
.collect();
|
||
quote! {
|
||
PlacedFeature {
|
||
feature: #feature,
|
||
placement: vec![#(#placement),*],
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Converts a JSON `"feature"` value into a `Feature` token stream.
|
||
///
|
||
/// # Arguments
|
||
/// - `v` – Either a string (named reference) or object (inline configured feature).
|
||
///
|
||
/// # Returns
|
||
/// `Feature::Named(…)` for string values or `Feature::Inlined(…)` for object values.
|
||
fn value_to_feature(v: &Value) -> TokenStream {
|
||
match v {
|
||
Value::String(s) => {
|
||
let name = s.strip_prefix("minecraft:").unwrap_or(s);
|
||
quote! { Feature::Named(#name.to_string()) }
|
||
}
|
||
Value::Object(_) => {
|
||
let cf = value_to_inline_configured_feature(v);
|
||
quote! { Feature::Inlined(Box::new(#cf)) }
|
||
}
|
||
_ => quote! { Feature::Named(String::new()) },
|
||
}
|
||
}
|
||
|
||
/// Converts a single placement-modifier JSON object into its corresponding `PlacementModifier` token stream.
|
||
///
|
||
/// # Arguments
|
||
/// - `v` – JSON object with a `"type"` discriminant and modifier-specific fields.
|
||
///
|
||
/// # Returns
|
||
/// A `PlacementModifier` variant token stream, or `compile_error!` for unknown types.
|
||
fn value_to_placement_modifier(v: &Value) -> TokenStream {
|
||
let type_str = v["type"].as_str().unwrap_or("");
|
||
match type_str {
|
||
"minecraft:biome" => quote! { PlacementModifier::Biome(BiomePlacementModifier) },
|
||
"minecraft:in_square" => quote! { PlacementModifier::InSquare(SquarePlacementModifier) },
|
||
"minecraft:fixed_placement" => {
|
||
let positions = v["positions"]
|
||
.as_array()
|
||
.map(|arr| {
|
||
arr.iter().map(|p| {
|
||
let coords = p.as_array().unwrap();
|
||
let x = coords[0].as_i64().unwrap_or(0) as i32;
|
||
let y = coords[1].as_i64().unwrap_or(0) as i32;
|
||
let z = coords[2].as_i64().unwrap_or(0) as i32;
|
||
quote! { BlockPos::new(#x, #y, #z) }
|
||
}).collect::<Vec<_>>()
|
||
})
|
||
.unwrap_or_default();
|
||
quote! { PlacementModifier::FixedPlacement(vec![#(#positions),*]) }
|
||
},
|
||
"minecraft:heightmap" => {
|
||
let heightmap =
|
||
value_to_height_map(v["heightmap"].as_str().unwrap_or("MOTION_BLOCKING"));
|
||
quote! {
|
||
PlacementModifier::Heightmap(HeightmapPlacementModifier {
|
||
heightmap: #heightmap,
|
||
})
|
||
}
|
||
}
|
||
"minecraft:height_range" => {
|
||
let height = value_to_height_provider(&v["height"]);
|
||
quote! {
|
||
PlacementModifier::HeightRange(HeightRangePlacementModifier {
|
||
height: #height,
|
||
})
|
||
}
|
||
}
|
||
"minecraft:count" => {
|
||
let count = value_to_int_provider(&v["count"]);
|
||
quote! {
|
||
PlacementModifier::Count(CountPlacementModifier {
|
||
count: #count,
|
||
})
|
||
}
|
||
}
|
||
"minecraft:count_on_every_layer" => {
|
||
let count = value_to_int_provider(&v["count"]);
|
||
quote! {
|
||
PlacementModifier::CountOnEveryLayer(CountOnEveryLayerPlacementModifier {
|
||
count: #count,
|
||
})
|
||
}
|
||
}
|
||
"minecraft:rarity_filter" => {
|
||
let chance = v["chance"].as_u64().unwrap_or(1) as u32;
|
||
quote! {
|
||
PlacementModifier::RarityFilter(RarityFilterPlacementModifier {
|
||
chance: #chance,
|
||
})
|
||
}
|
||
}
|
||
"minecraft:block_predicate_filter" => {
|
||
let predicate = value_to_block_predicate(&v["predicate"]);
|
||
quote! {
|
||
PlacementModifier::BlockPredicateFilter(BlockFilterPlacementModifier {
|
||
predicate: #predicate,
|
||
})
|
||
}
|
||
}
|
||
"minecraft:surface_relative_threshold_filter" => {
|
||
let heightmap =
|
||
value_to_height_map(v["heightmap"].as_str().unwrap_or("MOTION_BLOCKING"));
|
||
let min_inc = value_to_option_i32(&v["min_inclusive"]);
|
||
let max_inc = value_to_option_i32(&v["max_inclusive"]);
|
||
quote! {
|
||
PlacementModifier::SurfaceRelativeThresholdFilter(SurfaceThresholdFilterPlacementModifier {
|
||
heightmap: #heightmap,
|
||
min_inclusive: #min_inc,
|
||
max_inclusive: #max_inc,
|
||
})
|
||
}
|
||
}
|
||
"minecraft:surface_water_depth_filter" => {
|
||
let depth = v["max_water_depth"].as_i64().unwrap_or(0) as i32;
|
||
quote! {
|
||
PlacementModifier::SurfaceWaterDepthFilter(SurfaceWaterDepthFilterPlacementModifier {
|
||
max_water_depth: #depth,
|
||
})
|
||
}
|
||
}
|
||
"minecraft:noise_based_count" => {
|
||
let ratio = v["noise_to_count_ratio"].as_i64().unwrap_or(0) as i32;
|
||
let factor = v["noise_factor"].as_f64().unwrap_or(1.0);
|
||
let offset = v["noise_offset"].as_f64().unwrap_or(0.0);
|
||
quote! {
|
||
PlacementModifier::NoiseBasedCount(NoiseBasedCountPlacementModifier {
|
||
noise_to_count_ratio: #ratio,
|
||
noise_factor: #factor,
|
||
noise_offset: #offset,
|
||
})
|
||
}
|
||
}
|
||
"minecraft:noise_threshold_count" => {
|
||
let level = v["noise_level"].as_f64().unwrap_or(0.0);
|
||
let below = v["below_noise"].as_i64().unwrap_or(0) as i32;
|
||
let above = v["above_noise"].as_i64().unwrap_or(0) as i32;
|
||
quote! {
|
||
PlacementModifier::NoiseThresholdCount(NoiseThresholdCountPlacementModifier {
|
||
noise_level: #level,
|
||
below_noise: #below,
|
||
above_noise: #above,
|
||
})
|
||
}
|
||
}
|
||
"minecraft:environment_scan" => {
|
||
let dir = value_to_block_direction(v["direction_of_search"].as_str().unwrap_or("down"));
|
||
let target = value_to_block_predicate(&v["target_condition"]);
|
||
let allowed = if v["allowed_search_condition"].is_null()
|
||
|| v["allowed_search_condition"].is_object()
|
||
{
|
||
if v["allowed_search_condition"].is_object() {
|
||
let p = value_to_block_predicate(&v["allowed_search_condition"]);
|
||
quote! { Some(#p) }
|
||
} else {
|
||
quote! { None }
|
||
}
|
||
} else {
|
||
quote! { None }
|
||
};
|
||
let steps = v["max_steps"].as_i64().unwrap_or(1) as i32;
|
||
quote! {
|
||
PlacementModifier::EnvironmentScan(EnvironmentScanPlacementModifier {
|
||
direction_of_search: #dir,
|
||
target_condition: #target,
|
||
allowed_search_condition: #allowed,
|
||
max_steps: #steps,
|
||
})
|
||
}
|
||
}
|
||
"minecraft:random_offset" => {
|
||
let xz = value_to_int_provider(&v["xz_spread"]);
|
||
let y = value_to_int_provider(&v["y_spread"]);
|
||
quote! {
|
||
PlacementModifier::RandomOffset(RandomOffsetPlacementModifier {
|
||
xz_spread: #xz,
|
||
y_spread: #y,
|
||
})
|
||
}
|
||
}
|
||
other => {
|
||
let msg = format!("unknown placement modifier: {other}");
|
||
quote! { compile_error!(#msg) }
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Converts a block-predicate JSON object into its corresponding `BlockPredicate` token stream.
|
||
///
|
||
/// # Arguments
|
||
/// - `v` – JSON object with a `"type"` discriminant and predicate-specific fields.
|
||
///
|
||
/// # Returns
|
||
/// A `BlockPredicate` variant token stream, or `compile_error!` for unknown types.
|
||
pub fn value_to_block_predicate(v: &Value) -> TokenStream {
|
||
// Handle bare string values: "#minecraft:some_tag" is a block-tag predicate,
|
||
// "true" (or any non-# string) is AlwaysTrue.
|
||
if let Some(s) = v.as_str() {
|
||
if let Some(tag) = s.strip_prefix('#') {
|
||
return quote! {
|
||
BlockPredicate::MatchingBlockTag(MatchingBlockTagPredicate {
|
||
offset: OffsetBlocksBlockPredicate { offset: None },
|
||
tag: #tag.to_string(),
|
||
})
|
||
};
|
||
}
|
||
return quote! { BlockPredicate::AlwaysTrue };
|
||
}
|
||
|
||
let type_str = v["type"].as_str().unwrap_or("");
|
||
match type_str {
|
||
"minecraft:true" | "" => quote! { BlockPredicate::AlwaysTrue },
|
||
"minecraft:matching_blocks" => {
|
||
let offset = value_to_offset_predicate(&v["offset"]);
|
||
let blocks = value_to_matching_blocks_wrapper(&v["blocks"]);
|
||
quote! {
|
||
BlockPredicate::MatchingBlocks(MatchingBlocksBlockPredicate {
|
||
offset: #offset,
|
||
blocks: #blocks,
|
||
})
|
||
}
|
||
}
|
||
"minecraft:matching_block_tag" => {
|
||
let offset = value_to_offset_predicate(&v["offset"]);
|
||
let tag = v["tag"].as_str().unwrap_or("");
|
||
quote! {
|
||
BlockPredicate::MatchingBlockTag(MatchingBlockTagPredicate {
|
||
offset: #offset,
|
||
tag: #tag.to_string(),
|
||
})
|
||
}
|
||
}
|
||
"minecraft:matching_fluids" => {
|
||
let offset = value_to_offset_predicate(&v["offset"]);
|
||
let fluids = value_to_matching_blocks_wrapper(&v["fluids"]);
|
||
quote! {
|
||
BlockPredicate::MatchingFluids(MatchingFluidsBlockPredicate {
|
||
offset: #offset,
|
||
fluids: #fluids,
|
||
})
|
||
}
|
||
}
|
||
"minecraft:has_sturdy_face" => {
|
||
let offset = value_to_offset_predicate(&v["offset"]);
|
||
let dir = value_to_block_direction(v["direction"].as_str().unwrap_or("down"));
|
||
quote! {
|
||
BlockPredicate::HasSturdyFace(HasSturdyFacePredicate {
|
||
offset: #offset,
|
||
direction: #dir,
|
||
})
|
||
}
|
||
}
|
||
"minecraft:solid" => {
|
||
let offset = value_to_offset_predicate(&v["offset"]);
|
||
quote! {
|
||
BlockPredicate::Solid(SolidBlockPredicate { offset: #offset })
|
||
}
|
||
}
|
||
"minecraft:replaceable" => {
|
||
let offset = value_to_offset_predicate(&v["offset"]);
|
||
quote! {
|
||
BlockPredicate::Replaceable(ReplaceableBlockPredicate { offset: #offset })
|
||
}
|
||
}
|
||
"minecraft:would_survive" => {
|
||
let offset = value_to_offset_predicate(&v["offset"]);
|
||
let state = value_to_block_state_codec(&v["state"]);
|
||
quote! {
|
||
BlockPredicate::WouldSurvive(WouldSurviveBlockPredicate {
|
||
offset: #offset,
|
||
state: #state,
|
||
})
|
||
}
|
||
}
|
||
"minecraft:inside_world_bounds" => {
|
||
let offset_v = &v["offset"];
|
||
let offset = if offset_v.is_array() {
|
||
let x = offset_v[0].as_i64().unwrap_or(0) as i32;
|
||
let y = offset_v[1].as_i64().unwrap_or(0) as i32;
|
||
let z = offset_v[2].as_i64().unwrap_or(0) as i32;
|
||
quote! { Vector3::new(#x, #y, #z) }
|
||
} else {
|
||
quote! { Vector3::new(0, 0, 0) }
|
||
};
|
||
quote! {
|
||
BlockPredicate::InsideWorldBounds(InsideWorldBoundsBlockPredicate {
|
||
offset: #offset,
|
||
})
|
||
}
|
||
}
|
||
"minecraft:any_of" => {
|
||
let predicates: Vec<TokenStream> = v["predicates"]
|
||
.as_array()
|
||
.map(|a| a.iter().map(value_to_block_predicate).collect())
|
||
.unwrap_or_default();
|
||
quote! {
|
||
BlockPredicate::AnyOf(AnyOfBlockPredicate {
|
||
predicates: vec![#(#predicates),*],
|
||
})
|
||
}
|
||
}
|
||
"minecraft:all_of" => {
|
||
let predicates: Vec<TokenStream> = v["predicates"]
|
||
.as_array()
|
||
.map(|a| a.iter().map(value_to_block_predicate).collect())
|
||
.unwrap_or_default();
|
||
quote! {
|
||
BlockPredicate::AllOf(AllOfBlockPredicate {
|
||
predicates: vec![#(#predicates),*],
|
||
})
|
||
}
|
||
}
|
||
"minecraft:not" => {
|
||
let inner = value_to_block_predicate(&v["predicate"]);
|
||
quote! {
|
||
BlockPredicate::Not(NotBlockPredicate {
|
||
predicate: Box::new(#inner),
|
||
})
|
||
}
|
||
}
|
||
other => {
|
||
let msg = format!("unknown block predicate: {other}");
|
||
quote! { compile_error!(#msg) }
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Converts a height-provider JSON object into its corresponding `HeightProvider` token stream.
|
||
///
|
||
/// # Arguments
|
||
/// - `v` – JSON object with a `"type"` discriminant and provider-specific fields.
|
||
///
|
||
/// # Returns
|
||
/// A `HeightProvider` variant token stream, or `compile_error!` for unknown types.
|
||
pub 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 = value_to_option_i32(&v["plateau"]);
|
||
quote! {
|
||
HeightProvider::Trapezoid(TrapezoidHeightProvider {
|
||
min_inclusive: #min,
|
||
max_inclusive: #max,
|
||
plateau: #plateau,
|
||
})
|
||
}
|
||
}
|
||
"minecraft:very_biased_to_bottom" => {
|
||
let min = value_to_y_offset(&v["min_inclusive"]);
|
||
let max = value_to_y_offset(&v["max_inclusive"]);
|
||
let inner = if v["inner"].is_null() || !v["inner"].is_number() {
|
||
quote! { None }
|
||
} else {
|
||
let n = v["inner"].as_u64().unwrap_or(1) as u32;
|
||
quote! { std::num::NonZeroU32::new(#n) }
|
||
};
|
||
quote! {
|
||
HeightProvider::VeryBiasedToBottom(VeryBiasedToBottomHeightProvider {
|
||
min_inclusive: #min,
|
||
max_inclusive: #max,
|
||
inner: #inner,
|
||
})
|
||
}
|
||
}
|
||
"minecraft:constant" => {
|
||
// constant height provider wraps a y_offset
|
||
let y = value_to_y_offset(&v["value"]);
|
||
// Map to uniform with same min/max
|
||
quote! {
|
||
HeightProvider::Uniform(UniformHeightProvider {
|
||
min_inclusive: #y,
|
||
max_inclusive: #y,
|
||
})
|
||
}
|
||
}
|
||
other => {
|
||
let msg = format!("unknown height provider: {other}");
|
||
quote! { compile_error!(#msg) }
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Converts a Y-offset JSON object into its corresponding `YOffset` token stream.
|
||
///
|
||
/// # Arguments
|
||
/// - `v` – JSON object with one of the keys `"absolute"`, `"above_bottom"`, or `"below_top"`.
|
||
///
|
||
/// # Returns
|
||
/// A `YOffset` variant token stream; defaults to `YOffset::Absolute(0)` when unrecognized.
|
||
pub fn value_to_y_offset(v: &Value) -> TokenStream {
|
||
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 }) }
|
||
}
|
||
}
|
||
|
||
/// Converts an integer-provider JSON value into its corresponding `IntProvider` token stream.
|
||
///
|
||
/// # Arguments
|
||
/// - `v` – Either a bare JSON number (constant) or an object with a `"type"` discriminant.
|
||
///
|
||
/// # Returns
|
||
/// An `IntProvider` variant token stream; defaults to `IntProvider::Constant(0)` when unrecognized.
|
||
pub fn value_to_int_provider(v: &Value) -> TokenStream {
|
||
match v {
|
||
Value::Number(n) => {
|
||
let val = n.as_i64().unwrap_or(0) as i32;
|
||
quote! { IntProvider::Constant(#val) }
|
||
}
|
||
Value::Object(_) => {
|
||
let type_str = v["type"].as_str().unwrap_or("");
|
||
match type_str {
|
||
"minecraft:constant" => {
|
||
let val = v["value"].as_i64().unwrap_or(0) as i32;
|
||
quote! { IntProvider::Object(NormalIntProvider::Constant(ConstantIntProvider { value: #val })) }
|
||
}
|
||
"minecraft:uniform" => {
|
||
let min = v["min_inclusive"].as_i64().unwrap_or(0) as i32;
|
||
let max = v["max_inclusive"].as_i64().unwrap_or(0) as i32;
|
||
quote! { IntProvider::Object(NormalIntProvider::Uniform(UniformIntProvider { min_inclusive: #min, max_inclusive: #max })) }
|
||
}
|
||
"minecraft:biased_to_bottom" => {
|
||
let min = v["min_inclusive"].as_i64().unwrap_or(0) as i32;
|
||
let max = v["max_inclusive"].as_i64().unwrap_or(0) as i32;
|
||
quote! { IntProvider::Object(NormalIntProvider::BiasedToBottom(BiasedToBottomIntProvider { min_inclusive: #min, max_inclusive: #max })) }
|
||
}
|
||
"minecraft:clamped" => {
|
||
let min = v["min_inclusive"].as_i64().unwrap_or(0) as i32;
|
||
let max = v["max_inclusive"].as_i64().unwrap_or(0) as i32;
|
||
let src = value_to_int_provider(&v["source"]);
|
||
quote! { IntProvider::Object(NormalIntProvider::Clamped(ClampedIntProvider { source: Box::new(#src), min_inclusive: #min, max_inclusive: #max })) }
|
||
}
|
||
"minecraft:clamped_normal" => {
|
||
let mean = v["mean"].as_f64().unwrap_or(0.0) as f32;
|
||
let dev = v["deviation"].as_f64().unwrap_or(1.0) as f32;
|
||
let min = v["min_inclusive"].as_i64().unwrap_or(0) as i32;
|
||
let max = v["max_inclusive"].as_i64().unwrap_or(0) as i32;
|
||
quote! { IntProvider::Object(NormalIntProvider::ClampedNormal(ClampedNormalIntProvider { mean: #mean, deviation: #dev, min_inclusive: #min, max_inclusive: #max })) }
|
||
}
|
||
"minecraft:weighted_list" => {
|
||
let entries: Vec<TokenStream> = v["distribution"]
|
||
.as_array()
|
||
.map(|arr| {
|
||
arr.iter()
|
||
.map(|e| {
|
||
let data = value_to_int_provider(&e["data"]);
|
||
let weight = e["weight"].as_i64().unwrap_or(1) as i32;
|
||
quote! { WeightedEntry { data: #data, weight: #weight } }
|
||
})
|
||
.collect()
|
||
})
|
||
.unwrap_or_default();
|
||
quote! { IntProvider::Object(NormalIntProvider::WeightedList(WeightedListIntProvider { distribution: vec![#(#entries),*] })) }
|
||
}
|
||
_ => {
|
||
let val = v["value"].as_i64().unwrap_or(0) as i32;
|
||
quote! { IntProvider::Constant(#val) }
|
||
}
|
||
}
|
||
}
|
||
_ => quote! { IntProvider::Constant(0) },
|
||
}
|
||
}
|
||
|
||
/// Converts a SCREAMING_SNAKE_CASE heightmap name into its `HeightMap` token stream.
|
||
///
|
||
/// # Arguments
|
||
/// - `s` – Heightmap name string (e.g. `"MOTION_BLOCKING"`).
|
||
///
|
||
/// # Returns
|
||
/// A `HeightMap` variant token stream; defaults to `HeightMap::MotionBlocking` when unrecognized.
|
||
pub fn value_to_height_map(s: &str) -> TokenStream {
|
||
match s {
|
||
"WORLD_SURFACE_WG" => quote! { HeightMap::WorldSurfaceWg },
|
||
"WORLD_SURFACE" => quote! { HeightMap::WorldSurface },
|
||
"OCEAN_FLOOR_WG" => quote! { HeightMap::OceanFloorWg },
|
||
"OCEAN_FLOOR" => quote! { HeightMap::OceanFloor },
|
||
"MOTION_BLOCKING" => quote! { HeightMap::MotionBlocking },
|
||
"MOTION_BLOCKING_NO_LEAVES" => quote! { HeightMap::MotionBlockingNoLeaves },
|
||
_ => quote! { HeightMap::MotionBlocking },
|
||
}
|
||
}
|
||
|
||
/// Converts a lowercase direction string into its `BlockDirection` token stream.
|
||
///
|
||
/// # Arguments
|
||
/// - `s` – Direction name (e.g. `"down"`, `"up"`, `"north"`, etc.).
|
||
///
|
||
/// # Returns
|
||
/// A `BlockDirection` variant token stream; defaults to `BlockDirection::Down` when unrecognized.
|
||
pub fn value_to_block_direction(s: &str) -> TokenStream {
|
||
match s.to_lowercase().as_str() {
|
||
"down" => quote! { BlockDirection::Down },
|
||
"up" => quote! { BlockDirection::Up },
|
||
"north" => quote! { BlockDirection::North },
|
||
"south" => quote! { BlockDirection::South },
|
||
"west" => quote! { BlockDirection::West },
|
||
"east" => quote! { BlockDirection::East },
|
||
_ => quote! { BlockDirection::Down },
|
||
}
|
||
}
|
||
|
||
/// Converts an optional offset JSON value into an `OffsetBlocksBlockPredicate` token stream.
|
||
///
|
||
/// # Arguments
|
||
/// - `v` – Either `null`/empty object (no offset) or a 3-element integer array `[x, y, z]`.
|
||
///
|
||
/// # Returns
|
||
/// An `OffsetBlocksBlockPredicate` with `offset: None` or `offset: Some(Vector3::new(x, y, z))`.
|
||
fn value_to_offset_predicate(v: &Value) -> TokenStream {
|
||
if v.is_null() || v.is_object() && v.as_object().is_none_or(|o| o.is_empty()) {
|
||
quote! { OffsetBlocksBlockPredicate { offset: None } }
|
||
} else if v.is_array() {
|
||
let x = v[0].as_i64().unwrap_or(0) as i32;
|
||
let y = v[1].as_i64().unwrap_or(0) as i32;
|
||
let z = v[2].as_i64().unwrap_or(0) as i32;
|
||
quote! { OffsetBlocksBlockPredicate { offset: Some(Vector3::new(#x, #y, #z)) } }
|
||
} else {
|
||
quote! { OffsetBlocksBlockPredicate { offset: None } }
|
||
}
|
||
}
|
||
|
||
/// Converts a blocks/fluids JSON value into a `MatchingBlocksWrapper` token stream.
|
||
///
|
||
/// # Arguments
|
||
/// - `v` – Either a single string (one block/fluid name) or an array of strings.
|
||
///
|
||
/// # Returns
|
||
/// `MatchingBlocksWrapper::Single(…)` or `MatchingBlocksWrapper::Multiple(…)`.
|
||
fn value_to_matching_blocks_wrapper(v: &Value) -> TokenStream {
|
||
match v {
|
||
Value::String(s) => {
|
||
quote! { MatchingBlocksWrapper::Single(#s.to_string()) }
|
||
}
|
||
Value::Array(arr) => {
|
||
let items: Vec<TokenStream> = arr
|
||
.iter()
|
||
.filter_map(|s| s.as_str().map(|s| quote! { #s.to_string() }))
|
||
.collect();
|
||
quote! { MatchingBlocksWrapper::Multiple(vec![#(#items),*]) }
|
||
}
|
||
_ => quote! { MatchingBlocksWrapper::Single(String::new()) },
|
||
}
|
||
}
|
||
|
||
/// Converts a block-state JSON object into a `BlockStateCodec` token stream.
|
||
///
|
||
/// # Arguments
|
||
/// - `v` – JSON object with a `"Name"` field and optional `"Properties"` map.
|
||
///
|
||
/// # Returns
|
||
/// A `BlockStateCodec` token stream referencing the corresponding `Block` constant and optional property map.
|
||
pub fn value_to_block_state_codec(v: &Value) -> TokenStream {
|
||
let name = v["Name"].as_str().unwrap_or("minecraft:air");
|
||
let name_stripped = name.strip_prefix("minecraft:").unwrap_or(name);
|
||
let block_ident =
|
||
quote::format_ident!("{}", name_stripped.to_uppercase().replace([':', '-'], "_"));
|
||
if let Some(props) = v["Properties"].as_object() {
|
||
let keys: Vec<&str> = props.keys().map(|k| k.as_str()).collect();
|
||
let vals: Vec<&str> = props.values().filter_map(|v| v.as_str()).collect();
|
||
quote! {
|
||
{
|
||
let mut props = std::collections::HashMap::new();
|
||
#(props.insert(#keys.to_string(), #vals.to_string());)*
|
||
BlockStateCodec {
|
||
name: &pumpkin_data::Block::#block_ident,
|
||
properties: Some(props),
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
quote! {
|
||
BlockStateCodec {
|
||
name: &pumpkin_data::Block::#block_ident,
|
||
properties: None,
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
pub fn value_to_block_state(v: &Value) -> TokenStream {
|
||
let name = v["Name"].as_str().unwrap_or("minecraft:air");
|
||
let name_stripped = name.strip_prefix("minecraft:").unwrap_or(name);
|
||
let block_ident =
|
||
quote::format_ident!("{}", name_stripped.to_uppercase().replace([':', '-'], "_"));
|
||
if let Some(props) = v["Properties"].as_object() {
|
||
let keys: Vec<&str> = props.keys().map(|k| k.as_str()).collect();
|
||
let vals: Vec<&str> = props.values().filter_map(|v| v.as_str()).collect();
|
||
quote! {
|
||
{
|
||
let mut props = std::collections::HashMap::new();
|
||
#(props.insert(#keys.to_string(), #vals.to_string());)*
|
||
BlockStateCodec {
|
||
name: &pumpkin_data::Block::#block_ident,
|
||
properties: Some(props),
|
||
}.get_state()
|
||
}
|
||
}
|
||
} else {
|
||
quote! {
|
||
pumpkin_data::Block::#block_ident.default_state
|
||
}
|
||
}
|
||
}
|
||
|
||
/// Converts a nullable JSON integer into an `Option<i32>` token stream.
|
||
///
|
||
/// # Arguments
|
||
/// - `v` – A JSON number or `null`.
|
||
///
|
||
/// # Returns
|
||
/// `Some(n)` if `v` is a valid integer, or `None` otherwise.
|
||
fn value_to_option_i32(v: &Value) -> TokenStream {
|
||
if v.is_null() {
|
||
quote! { None }
|
||
} else if let Some(n) = v.as_i64() {
|
||
let val = n as i32;
|
||
quote! { Some(#val) }
|
||
} else {
|
||
quote! { None }
|
||
}
|
||
}
|
||
|
||
/// Stub for configured feature inline in placed feature
|
||
fn value_to_inline_configured_feature(_v: &Value) -> TokenStream {
|
||
quote! { crate::generation::feature::configured_features::ConfiguredFeature::NoOp }
|
||
}
|