Pumpkin/tools/pumpkin-codegen/src/configured_feature.rs
Alexander Medvedev f4571a9d63 fix: ci
2026-08-06 13:10:57 +02:00

1710 lines
79 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

use heck::ToPascalCase;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use serde_json::Value;
use std::fs;
use crate::placed_feature::{
value_to_block_direction, value_to_block_predicate, value_to_block_state,
value_to_block_state_codec, value_to_height_provider, value_to_int_provider,
};
/// Reads `configured_features.json` and emits the complete `ConfiguredFeature` enum `TokenStream`.
pub fn build_enum() -> TokenStream {
let json_content = fs::read_to_string("../../assets/configured_features.json")
.expect("Failed to read configured_features.json");
let json: Value =
serde_json::from_str(&json_content).expect("Failed to parse configured_features.json");
let mut from_name_arms = Vec::new();
let mut to_name_arms = Vec::new();
let variants: Vec<TokenStream> = json
.as_object()
.unwrap()
.iter()
.map(|(name, _)| {
let variant_name = format_ident!("{}", name.to_pascal_case());
from_name_arms.push(quote! {
#name => Some(Self::#variant_name),
});
to_name_arms.push(quote! {
Self::#variant_name => #name,
});
quote! {
#variant_name,
}
})
.collect();
quote! {
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub enum ConfiguredFeature {
#(#variants)*
}
impl ConfiguredFeature {
pub fn from_name(name: &str) -> Option<Self> {
let name = name.strip_prefix("minecraft:").unwrap_or(name);
match name {
#(#from_name_arms)*
_ => None,
}
}
pub const fn to_name(&self) -> &'static str {
match self {
#(#to_name_arms)*
}
}
}
}
}
/// Reads `configured_features.json` and emits a `build_configured_features()` function `TokenStream`.
pub fn build() -> TokenStream {
let json_content = fs::read_to_string("../../assets/configured_features.json")
.expect("Failed to read configured_features.json");
let json: Value =
serde_json::from_str(&json_content).expect("Failed to parse configured_features.json");
let entries: Vec<TokenStream> = json
.as_object()
.unwrap()
.iter()
.map(|(name, value)| {
let cf = value_to_configured_feature(value);
let variant_name = format_ident!("{}", name.to_pascal_case());
quote! {
map.insert(pumpkin_data::configured_feature::ConfiguredFeature::#variant_name, #cf);
}
})
.collect();
quote! {
#[allow(clippy::all, unused_imports, dead_code, clippy::large_stack_frames, clippy::too_many_lines)]
fn build_configured_features() -> std::collections::HashMap<pumpkin_data::configured_feature::ConfiguredFeature, ConfiguredFeature> {
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, TrapezoidIntProvider, ClampedNormalIntProvider,
ConstantIntProvider, IntProvider, NormalIntProvider, UniformIntProvider,
WeightedEntry, WeightedListIntProvider,
};
use crate::block::BlockStateCodec;
use crate::generation::block_state_provider::{
BlockStateProvider, BlockStateRule, DualNoiseBlockStateProvider,
NoiseBlockStateProvider, NoiseBlockStateProviderBase,
NoiseThresholdBlockStateProvider, PillarBlockStateProvider,
RandomizedIntBlockStateProvider, RuleBasedBlockStateProvider, SimpleStateProvider,
WeightedBlockStateProvider,
};
use pumpkin_util::math::pool::Weighted;
use pumpkin_util::DoublePerlinNoiseParametersCodec;
use crate::generation::rule::{
RuleTest,
block_match::BlockMatchRuleTest,
block_state_match::BlockStateMatchRuleTest,
tag_match::TagMatchRuleTest,
random_block_match::RandomBlockMatchRuleTest,
random_block_state_match::RandomBlockStateMatchRuleTest,
};
use crate::generation::feature::placed_features::{
Feature, PlacedFeature, PlacementModifier,
BiomePlacementModifier, BlockFilterPlacementModifier,
CountOnEveryLayerPlacementModifier, CountPlacementModifier,
EnvironmentScanPlacementModifier, HeightRangePlacementModifier,
HeightmapPlacementModifier, NoiseBasedCountPlacementModifier,
NoiseThresholdCountPlacementModifier, RandomOffsetPlacementModifier,
RarityFilterPlacementModifier, SquarePlacementModifier,
SurfaceThresholdFilterPlacementModifier, SurfaceWaterDepthFilterPlacementModifier,
PlacedFeatureWrapper,
};
use crate::generation::feature::features::{
bamboo::BambooFeature,
block_column::{BlockColumnFeature, Layer},
end_spike::{EndSpikeFeature, Spike},
fallen_tree::FallenTreeFeature,
nether_forest_vegetation::NetherForestVegetationFeature,
netherrack_replace_blobs::ReplaceBlobsFeature,
ore::{OreFeature, OreTarget},
random_boolean_selector::RandomBooleanFeature,
random_patch::RandomPatchFeature,
random_selector::{RandomFeature, RandomFeatureEntry},
sea_pickle::SeaPickleFeature,
seagrass::SeagrassFeature,
simple_block::SimpleBlockFeature,
simple_random_selector::SimpleRandomFeature,
spring_feature::{BlockWrapper, SpringFeatureFeature},
geode::GeodeFeature,
tree::TreeFeature,
vegetation_patch::VegetationPatchFeature,
waterlogged_vegetation_patch::WaterloggedVegetationPatchFeature,
tree::trunk::{TrunkPlacer, TrunkType,
bending::BendingTrunkPlacer,
cherry::CherryTrunkPlacer,
dark_oak::DarkOakTrunkPlacer,
fancy::FancyTrunkPlacer,
forking::ForkingTrunkPlacer,
giant::GiantTrunkPlacer,
mega_jungle::MegaJungleTrunkPlacer,
straight::StraightTrunkPlacer,
upwards_branching::UpwardsBranchingTrunkPlacer,
},
tree::foliage::{FoliagePlacer, FoliageType,
acacia::AcaciaFoliagePlacer,
blob::BlobFoliagePlacer,
bush::BushFoliagePlacer,
cherry::CherryFoliagePlacer,
dark_oak::DarkOakFoliagePlacer,
fancy::LargeOakFoliagePlacer,
jungle::JungleFoliagePlacer,
mega_pine::MegaPineFoliagePlacer,
pine::PineFoliagePlacer,
random_spread::RandomSpreadFoliagePlacer,
spruce::SpruceFoliagePlacer,
},
tree::decorator::{
TreeDecorator,
alter_ground::AlterGroundTreeDecorator,
attached_to_leaves::AttachedToLeavesTreeDecorator,
attached_to_logs::AttachedToLogsTreeDecorator,
beehive::BeehiveTreeDecorator,
cocoa::CocoaTreeDecorator,
creaking_heart::CreakingHeartTreeDecorator,
leave_vine::LeavesVineTreeDecorator,
pale_moss::PaleMossTreeDecorator,
place_on_ground::PlaceOnGroundTreeDecorator,
trunk_vine::TrunkVineTreeDecorator,
},
tree::root::{
RootPlacer,
mangrove::{AboveRootPlacement, MangroveRootPlacement, MangroveRootPlacer},
},
};
use crate::generation::feature::size::{FeatureSize, FeatureSizeType, ThreeLayersFeatureSize, TwoLayersFeatureSize};
use pumpkin_data::{Block, BlockDirection};
use pumpkin_util::math::vector3::Vector3;
use pumpkin_util::HeightMap;
use crate::generation::feature::features::drip_stone::small::SmallDripstoneFeature;
let mut map = std::collections::HashMap::new();
#(#entries)*
map
}
}
}
fn value_to_fossil_processor(value: &Value) -> TokenStream {
let variant = match value
.as_str()
.expect("fossil processor names must be strings")
{
"minecraft:fossil_rot" => format_ident!("FossilRot"),
"minecraft:fossil_coal" => format_ident!("Coal"),
"minecraft:fossil_diamonds" => format_ident!("Diamonds"),
processor => panic!("Unsupported fossil processor: {processor}"),
};
quote! {
crate::generation::feature::features::fossil::FossilProcessor::#variant
}
}
/// Converts a single configured-feature JSON value into its `ConfiguredFeature` token stream.
///
/// # Arguments
/// – `v` – the JSON object for the feature, expected to contain `"type"` and `"config"` fields.
pub fn value_to_configured_feature(v: &Value) -> TokenStream {
let type_str = v["type"].as_str().unwrap_or("");
let config = &v["config"];
match type_str {
"minecraft:no_op" => quote! { ConfiguredFeature::NoOp },
"minecraft:bamboo" => {
let prob = config["probability"].as_f64().unwrap_or(0.0) as f32;
quote! { ConfiguredFeature::Bamboo(BambooFeature { probability: #prob }) }
}
"minecraft:seagrass" => {
let prob = config["probability"].as_f64().unwrap_or(0.0) as f32;
quote! { ConfiguredFeature::Seagrass(SeagrassFeature { probability: #prob }) }
}
"minecraft:sea_pickle" => {
let count = value_to_int_provider(&config["count"]);
quote! { ConfiguredFeature::SeaPickle(SeaPickleFeature { count: #count }) }
}
"minecraft:nether_forest_vegetation" => {
let provider = value_to_block_state_provider(&config["state_provider"]);
let w = config["spread_width"].as_i64().unwrap_or(8) as i32;
let h = config["spread_height"].as_i64().unwrap_or(4) as i32;
quote! {
ConfiguredFeature::NetherForestVegetation(NetherForestVegetationFeature {
state_provider: #provider,
spread_width: #w,
spread_height: #h,
})
}
}
"minecraft:netherrack_replace_blobs" => {
let target = value_to_block_state(&config["target"]);
let state = value_to_block_state(&config["state"]);
let radius = value_to_int_provider(&config["radius"]);
quote! {
ConfiguredFeature::NetherrackReplaceBlobs(ReplaceBlobsFeature {
target: #target,
state: #state,
radius: #radius,
})
}
}
"minecraft:simple_block" => {
let to_place = value_to_block_state_provider(&config["to_place"]);
let schedule = if config["schedule_tick"].is_boolean() {
let b = config["schedule_tick"].as_bool().unwrap_or(false);
quote! { Some(#b) }
} else {
quote! { None }
};
quote! {
ConfiguredFeature::SimpleBlock(SimpleBlockFeature {
to_place: #to_place,
schedule_tick: #schedule,
})
}
}
"minecraft:ore" | "minecraft:scattered_ore" => {
let size = config["size"].as_i64().unwrap_or(0) as i32;
let discard = config["discard_chance_on_air_exposure"]
.as_f64()
.unwrap_or(0.0) as f32;
let targets: Vec<TokenStream> = config["targets"]
.as_array()
.map(|arr| {
arr.iter()
.map(|t| {
let rule = value_to_rule_test(&t["target"]);
let state = value_to_block_state(&t["state"]);
quote! { OreTarget { target: #rule, state: #state } }
})
.collect()
})
.unwrap_or_default();
if type_str == "minecraft:scattered_ore" {
quote! {
ConfiguredFeature::ScatteredOre(crate::generation::feature::features::scattered_ore::ScatteredOreFeature {
size: #size,
discard_chance_on_air_exposure: #discard,
targets: vec![#(#targets),*],
})
}
} else {
quote! {
ConfiguredFeature::Ore(OreFeature {
size: #size,
discard_chance_on_air_exposure: #discard,
targets: vec![#(#targets),*],
})
}
}
}
"minecraft:spring_feature" => {
let state = value_to_block_state(&config["state"]);
let req = config["requires_block_below"].as_bool().unwrap_or(true);
let rock = config["rock_count"].as_i64().unwrap_or(4) as i32;
let hole = config["hole_count"].as_i64().unwrap_or(1) as i32;
let valid = value_to_block_wrapper(&config["valid_blocks"]);
quote! {
ConfiguredFeature::SpringFeature(SpringFeatureFeature {
state: #state,
requires_block_below: #req,
rock_count: #rock,
hole_count: #hole,
valid_blocks: #valid,
})
}
}
"minecraft:block_column" => {
let layers: Vec<TokenStream> = config["layers"]
.as_array()
.map(|arr| {
arr.iter()
.map(|l| {
let h = value_to_int_provider(&l["height"]);
let p = value_to_block_state_provider(&l["provider"]);
quote! { Layer { height: #h, provider: #p } }
})
.collect()
})
.unwrap_or_default();
let dir = value_to_block_direction(config["direction"].as_str().unwrap_or("up"));
let allowed = value_to_block_predicate(&config["allowed_placement"]);
let tip = config["prioritize_tip"].as_bool().unwrap_or(false);
quote! {
ConfiguredFeature::BlockColumn(BlockColumnFeature {
layers: vec![#(#layers),*],
direction: #dir,
allowed_placement: #allowed,
prioritize_tip: #tip,
})
}
}
"minecraft:fallen_tree" => {
let trunk = value_to_block_state_provider(&config["trunk_provider"]);
quote! {
ConfiguredFeature::FallenTree(FallenTreeFeature {
trunk_provider: #trunk,
})
}
}
"minecraft:random_patch" | "minecraft:flower" | "minecraft:no_bonemeal_flower" => {
let tries = config["tries"].as_u64().unwrap_or(128) as u8;
let xz = config["xz_spread"].as_u64().unwrap_or(7) as u8;
let y = config["y_spread"].as_u64().unwrap_or(3) as u8;
let feature = value_to_inline_placed_feature(&config["feature"]);
let variant = match type_str {
"minecraft:flower" => quote! { ConfiguredFeature::Flower },
"minecraft:no_bonemeal_flower" => quote! { ConfiguredFeature::NoBonemealFlower },
_ => quote! { ConfiguredFeature::RandomPatch },
};
quote! {
#variant(RandomPatchFeature {
tries: #tries,
xz_spread: #xz,
y_spread: #y,
feature: Box::new(#feature),
})
}
}
"minecraft:random_selector" => {
let entries: Vec<TokenStream> = config["features"]
.as_array()
.map(|arr| {
arr.iter()
.map(|e| {
let feat = value_to_placed_feature_wrapper(&e["feature"]);
let chance = e["chance"].as_f64().unwrap_or(0.1) as f32;
quote! { RandomFeatureEntry { feature: #feat, chance: #chance } }
})
.collect()
})
.unwrap_or_default();
let default = value_to_placed_feature_wrapper(&config["default"]);
quote! {
ConfiguredFeature::RandomSelector(RandomFeature {
features: vec![#(#entries),*],
default: Box::new(#default),
})
}
}
"minecraft:simple_random_selector" => {
let features: Vec<TokenStream> = config["features"]
.as_array()
.map(|arr| arr.iter().map(value_to_inline_placed_feature).collect())
.unwrap_or_default();
quote! {
ConfiguredFeature::SimpleRandomSelector(SimpleRandomFeature {
features: vec![#(#features),*],
})
}
}
"minecraft:random_boolean_selector" => {
let ft = value_to_placed_feature_wrapper(&config["feature_true"]);
let ff = value_to_placed_feature_wrapper(&config["feature_false"]);
quote! {
ConfiguredFeature::RandomBooleanSelector(RandomBooleanFeature {
feature_true: Box::new(#ft),
feature_false: Box::new(#ff),
})
}
}
"minecraft:end_spike" => {
let inv = config["crystal_invulnerable"].as_bool().unwrap_or(false);
let spikes: Vec<TokenStream> = config["spikes"]
.as_array()
.map(|arr| {
arr.iter().map(|s| {
let cx = s["centerX"].as_i64().unwrap_or(0) as i32;
let cz = s["centerZ"].as_i64().unwrap_or(0) as i32;
let r = s["radius"].as_i64().unwrap_or(0) as i32;
let h = s["height"].as_i64().unwrap_or(0) as i32;
let g = s["guarded"].as_bool().unwrap_or(false);
quote! {
Spike { center_x: #cx, center_z: #cz, radius: #r, height: #h, guarded: #g }
}
}).collect()
})
.unwrap_or_default();
quote! {
ConfiguredFeature::EndSpike(EndSpikeFeature {
crystal_invulnerable: #inv,
spikes: vec![#(#spikes),*],
})
}
}
"minecraft:tree" => {
let tree = value_to_tree_feature(config);
quote! { ConfiguredFeature::Tree(Box::new(#tree)) }
}
"minecraft:pointed_dripstone" | "minecraft:speleothem" => {
let taller = config["chance_of_taller_dripstone"].as_f64().unwrap_or(0.2) as f32;
let dir_spread = config["chance_of_directional_spread"]
.as_f64()
.unwrap_or(0.7) as f32;
let r2 = config["chance_of_spread_radius2"].as_f64().unwrap_or(0.5) as f32;
let r3 = config["chance_of_spread_radius3"].as_f64().unwrap_or(0.5) as f32;
quote! {
ConfiguredFeature::PointedDripstone(SmallDripstoneFeature {
taller_dripstone: #taller,
directional_spread: #dir_spread,
spread_radius2: #r2,
spread_radius3: #r3,
})
}
}
"minecraft:geode" => {
let blocks = &config["blocks"];
let filling_provider = value_to_block_state_provider(&blocks["filling_provider"]);
let inner_layer_provider =
value_to_block_state_provider(&blocks["inner_layer_provider"]);
let alternate_inner_layer_provider =
value_to_block_state_provider(&blocks["alternate_inner_layer_provider"]);
let middle_layer_provider =
value_to_block_state_provider(&blocks["middle_layer_provider"]);
let outer_layer_provider =
value_to_block_state_provider(&blocks["outer_layer_provider"]);
let inner_placements: Vec<TokenStream> = blocks["inner_placements"]
.as_array()
.map(|arr| arr.iter().map(value_to_block_state_codec).collect())
.unwrap_or_default();
let cannot_replace = value_to_block_wrapper(&blocks["cannot_replace"]);
let invalid_blocks = value_to_block_wrapper(&blocks["invalid_blocks"]);
let layers = &config["layers"];
let filling = layers["filling"].as_f64().unwrap_or(1.7);
let inner_layer = layers["inner_layer"].as_f64().unwrap_or(2.2);
let middle_layer = layers["middle_layer"].as_f64().unwrap_or(3.2);
let outer_layer = layers["outer_layer"].as_f64().unwrap_or(4.2);
let crack = &config["crack"];
let generate_crack_chance = crack["generate_crack_chance"].as_f64().unwrap_or(1.0);
let base_crack_size = crack["base_crack_size"].as_f64().unwrap_or(2.0);
let crack_point_offset = crack["crack_point_offset"].as_i64().unwrap_or(2) as i32;
let use_potential_placements_chance = config["use_potential_placements_chance"]
.as_f64()
.unwrap_or(0.35);
let use_alternate_layer0_chance = config["use_alternate_layer0_chance"]
.as_f64()
.unwrap_or(0.0);
let placements_require_layer0_alternate = config["placements_require_layer0_alternate"]
.as_bool()
.unwrap_or(true);
let outer_wall_distance = value_to_int_provider(&config["outer_wall_distance"]);
let distribution_points = value_to_int_provider(&config["distribution_points"]);
let point_offset = value_to_int_provider(&config["point_offset"]);
let min_gen_offset = config["min_gen_offset"].as_i64().unwrap_or(-16) as i32;
let max_gen_offset = config["max_gen_offset"].as_i64().unwrap_or(16) as i32;
let noise_multiplier = config["noise_multiplier"].as_f64().unwrap_or(0.05);
let invalid_blocks_threshold =
config["invalid_blocks_threshold"].as_i64().unwrap_or(0) as i32;
quote! {
ConfiguredFeature::Geode(Box::new(GeodeFeature {
filling_provider: #filling_provider,
inner_layer_provider: #inner_layer_provider,
alternate_inner_layer_provider: #alternate_inner_layer_provider,
middle_layer_provider: #middle_layer_provider,
outer_layer_provider: #outer_layer_provider,
inner_placements: vec![#(#inner_placements),*],
cannot_replace: #cannot_replace,
invalid_blocks: #invalid_blocks,
filling: #filling,
inner_layer: #inner_layer,
middle_layer: #middle_layer,
outer_layer: #outer_layer,
generate_crack_chance: #generate_crack_chance,
base_crack_size: #base_crack_size,
crack_point_offset: #crack_point_offset,
use_potential_placements_chance: #use_potential_placements_chance,
use_alternate_layer0_chance: #use_alternate_layer0_chance,
placements_require_layer0_alternate: #placements_require_layer0_alternate,
outer_wall_distance: #outer_wall_distance,
distribution_points: #distribution_points,
point_offset: #point_offset,
min_gen_offset: #min_gen_offset,
max_gen_offset: #max_gen_offset,
noise_multiplier: #noise_multiplier,
invalid_blocks_threshold: #invalid_blocks_threshold,
}))
}
}
"minecraft:monster_room" => {
quote! { ConfiguredFeature::MonsterRoom(crate::generation::feature::features::monster_room::DungeonFeature {}) }
}
"minecraft:underwater_magma" => {
let floor_range = config["floor_search_range"].as_i64().unwrap_or(0) as i32;
let placement_radius = config["placement_radius_around_floor"]
.as_i64()
.unwrap_or(0) as i32;
let placement_prob = config["placement_probability_per_valid_position"]
.as_f64()
.unwrap_or(0.0) as f32;
quote! {
ConfiguredFeature::UnderwaterMagma(
crate::generation::feature::features::underwater_magma::UnderwaterMagmaFeature {
floor_search_range: #floor_range,
placement_radius: #placement_radius,
placement_probability: #placement_prob,
}
)
}
}
"minecraft:vegetation_patch" | "minecraft:waterlogged_vegetation_patch" => {
let replaceable = value_to_block_predicate(&config["replaceable"]);
let ground_state = value_to_block_state_provider(&config["ground_state"]);
let vegetation_feature = value_to_inline_placed_feature(&config["vegetation_feature"]);
let surface = match config["surface"].as_str().unwrap_or("floor") {
"ceiling" => {
quote! { pumpkin_util::math::vertical_surface_type::VerticalSurfaceType::Ceiling }
}
_ => {
quote! { pumpkin_util::math::vertical_surface_type::VerticalSurfaceType::Floor }
}
};
let depth = value_to_int_provider(&config["depth"]);
let extra_bottom = config["extra_bottom_block_chance"].as_f64().unwrap_or(0.0) as f32;
let vert_range = config["vertical_range"].as_i64().unwrap_or(0) as i32;
let veg_chance = config["vegetation_chance"].as_f64().unwrap_or(0.0) as f32;
let xz = value_to_int_provider(&config["xz_radius"]);
let extra_edge = config["extra_edge_column_chance"].as_f64().unwrap_or(0.0) as f32;
if type_str == "minecraft:vegetation_patch" {
quote! {
ConfiguredFeature::VegetationPatch(vegetation_patch::VegetationPatchFeature {
replaceable: #replaceable,
ground_state: #ground_state,
vegetation_feature: Box::new(#vegetation_feature),
surface: #surface,
depth: #depth,
extra_bottom_block_chance: #extra_bottom,
vertical_range: #vert_range,
vegetation_chance: #veg_chance,
xz_radius: #xz,
extra_edge_column_chance: #extra_edge,
})
}
} else {
quote! {
ConfiguredFeature::WaterloggedVegetationPatch(waterlogged_vegetation_patch::WaterloggedVegetationPatchFeature {
base: vegetation_patch::VegetationPatchFeature {
replaceable: #replaceable,
ground_state: #ground_state,
vegetation_feature: Box::new(#vegetation_feature),
surface: #surface,
depth: #depth,
extra_bottom_block_chance: #extra_bottom,
vertical_range: #vert_range,
vegetation_chance: #veg_chance,
xz_radius: #xz,
extra_edge_column_chance: #extra_edge,
}
})
}
}
}
"minecraft:glowstone_blob" => {
quote! { ConfiguredFeature::GlowstoneBlob(crate::generation::feature::features::glowstone_blob::GlowstoneBlobFeature {}) }
}
"minecraft:disk" => {
let state_provider = value_to_block_state_provider(&config["state_provider"]);
let target = value_to_block_predicate(&config["target"]);
let radius = value_to_int_provider(&config["radius"]);
let half_height = config["half_height"].as_i64().unwrap_or(1) as i32;
quote! {
ConfiguredFeature::Disk(crate::generation::feature::features::disk::DiskFeature {
state_provider: #state_provider,
target: #target,
radius: #radius,
half_height: #half_height,
})
}
}
"minecraft:basalt_columns" => {
let height = value_to_int_provider(&config["height"]);
let reach = value_to_int_provider(&config["reach"]);
quote! {
ConfiguredFeature::BasaltColumns(crate::generation::feature::features::basalt_columns::BasaltColumnsFeature {
height: #height,
reach: #reach,
})
}
}
"minecraft:basalt_pillar" => {
quote! { ConfiguredFeature::BasaltPillar(crate::generation::feature::features::basalt_pillar::BasaltPillarFeature {}) }
}
"minecraft:block_blob" => {
let state = value_to_block_state(&config["state"]);
quote! {
ConfiguredFeature::ForestRock(crate::generation::feature::features::forest_rock::ForestRockFeature {
state: #state,
})
}
}
"minecraft:freeze_top_layer" => {
quote! { ConfiguredFeature::FreezeTopLayer(crate::generation::feature::features::freeze_top_layer::FreezeTopLayerFeature {}) }
}
"minecraft:ice_spike" => {
quote! { ConfiguredFeature::IceSpike(crate::generation::feature::features::ice_spike::IceSpikeFeature {}) }
}
"minecraft:spike" => {
quote! { ConfiguredFeature::IceSpike(crate::generation::feature::features::ice_spike::IceSpikeFeature {}) }
}
"minecraft:iceberg" => {
let state = value_to_block_state_codec(&config["state"]);
quote! { ConfiguredFeature::Iceberg(crate::generation::feature::features::iceberg::IcebergFeature { main_block: #state }) }
}
"minecraft:chorus_plant" => {
quote! { ConfiguredFeature::ChorusPlant(crate::generation::feature::features::chorus_plant::ChorusPlantFeature {}) }
}
"minecraft:end_platform" => {
quote! { ConfiguredFeature::EndPlatform(crate::generation::feature::features::end_platform::EndPlatformFeature) }
}
"minecraft:end_island" => {
quote! { ConfiguredFeature::EndIsland(crate::generation::feature::features::end_island::EndIslandFeature {}) }
}
"minecraft:kelp" => {
quote! { ConfiguredFeature::Kelp(crate::generation::feature::features::kelp::KelpFeature {}) }
}
"minecraft:fossil" => {
let fossil_structures = config["fossil_structures"]
.as_array()
.expect("fossil_structures must be an array")
.iter()
.map(|value| {
let name = value
.as_str()
.expect("fossil structure names must be strings");
quote! { #name }
});
let overlay_structures = config["overlay_structures"]
.as_array()
.expect("overlay_structures must be an array")
.iter()
.map(|value| {
let name = value
.as_str()
.expect("fossil overlay structure names must be strings");
quote! { #name }
});
let fossil_processor = value_to_fossil_processor(&config["fossil_processors"]);
let overlay_processor = value_to_fossil_processor(&config["overlay_processors"]);
let max_empty_corners_allowed = config["max_empty_corners_allowed"]
.as_u64()
.expect("max_empty_corners_allowed must be an unsigned integer")
as u8;
quote! {
ConfiguredFeature::Fossil(
crate::generation::feature::features::fossil::FossilFeature {
fossil_structures: vec![#(#fossil_structures),*],
overlay_structures: vec![#(#overlay_structures),*],
fossil_processor: #fossil_processor,
overlay_processor: #overlay_processor,
max_empty_corners_allowed: #max_empty_corners_allowed,
}
)
}
}
"minecraft:lake" => {
let fluid = value_to_block_state_provider(&config["fluid"]);
let barrier = value_to_block_state_provider(&config["barrier"]);
quote! {
ConfiguredFeature::Lake(crate::generation::feature::features::lake::LakeFeature {
fluid: #fluid,
barrier: #barrier,
})
}
}
"minecraft:huge_brown_mushroom" => {
quote! { ConfiguredFeature::HugeBrownMushroom(crate::generation::feature::features::huge_brown_mushroom::HugeBrownMushroomFeature {}) }
}
"minecraft:huge_red_mushroom" => {
quote! { ConfiguredFeature::HugeRedMushroom(crate::generation::feature::features::huge_red_mushroom::HugeRedMushroomFeature {}) }
}
"minecraft:vines" => {
quote! { ConfiguredFeature::Vines(crate::generation::feature::features::vines::VinesFeature) }
}
"minecraft:root_system" => {
let feature = value_to_inline_placed_feature(&config["feature"]);
let required_vertical_space_for_tree = config["required_vertical_space_for_tree"]
.as_i64()
.unwrap_or(0) as i32;
let root_radius = config["root_radius"].as_i64().unwrap_or(0) as i32;
let root_replaceable = value_to_block_predicate(&config["root_replaceable"]);
let root_state_provider = value_to_block_state_provider(&config["root_state_provider"]);
let root_placement_attempts =
config["root_placement_attempts"].as_i64().unwrap_or(0) as i32;
let root_column_max_height =
config["root_column_max_height"].as_i64().unwrap_or(0) as i32;
let hanging_root_radius = config["hanging_root_radius"].as_i64().unwrap_or(0) as i32;
let hanging_roots_vertical_span = config["hanging_roots_vertical_span"]
.as_i64()
.or(config["hanging_root_vertical_span"].as_i64())
.unwrap_or(0) as i32;
let hanging_root_state_provider =
value_to_block_state_provider(&config["hanging_root_state_provider"]);
let hanging_root_placement_attempts = config["hanging_root_placement_attempts"]
.as_i64()
.unwrap_or(0) as i32;
let allowed_vertical_water_for_tree = config["allowed_vertical_water_for_tree"]
.as_i64()
.unwrap_or(0) as i32;
let allowed_tree_position = value_to_block_predicate(&config["allowed_tree_position"]);
quote! {
ConfiguredFeature::RootSystem(crate::generation::feature::features::root_system::RootSystemFeature {
feature: Box::new(#feature),
required_vertical_space_for_tree: #required_vertical_space_for_tree,
root_radius: #root_radius,
root_replaceable: #root_replaceable,
root_state_provider: #root_state_provider,
root_placement_attempts: #root_placement_attempts,
root_column_max_height: #root_column_max_height,
hanging_root_radius: #hanging_root_radius,
hanging_roots_vertical_span: #hanging_roots_vertical_span,
hanging_root_state_provider: #hanging_root_state_provider,
hanging_root_placement_attempts: #hanging_root_placement_attempts,
allowed_vertical_water_for_tree: #allowed_vertical_water_for_tree,
allowed_tree_position: #allowed_tree_position,
})
}
}
"minecraft:multiface_growth" => {
quote! { ConfiguredFeature::MultifaceGrowth(crate::generation::feature::features::multiface_growth::MultifaceGrowthFeature {}) }
}
"minecraft:blue_ice" => {
quote! { ConfiguredFeature::BlueIce(crate::generation::feature::features::blue_ice::BlueIceFeature {}) }
}
"minecraft:end_gateway" => {
quote! { ConfiguredFeature::EndGateway(crate::generation::feature::features::end_gateway::EndGatewayFeature {}) }
}
"minecraft:coral_tree" => {
quote! { ConfiguredFeature::CoralTree(crate::generation::feature::features::coral::coral_tree::CoralTreeFeature) }
}
"minecraft:coral_mushroom" => {
quote! { ConfiguredFeature::CoralMushroom(crate::generation::feature::features::coral::coral_mushroom::CoralMushroomFeature) }
}
"minecraft:coral_claw" => {
quote! { ConfiguredFeature::CoralClaw(crate::generation::feature::features::coral::coral_claw::CoralClawFeature) }
}
"minecraft:huge_fungus" => {
quote! { ConfiguredFeature::HugeFungus(crate::generation::feature::features::huge_fungus::HugeFungusFeature {}) }
}
"minecraft:weeping_vines" => {
quote! { ConfiguredFeature::WeepingVines(crate::generation::feature::features::weeping_vines::WeepingVinesFeature {}) }
}
"minecraft:twisting_vines" => {
let spread_width = config["spread_width"].as_i64().unwrap_or(0) as i32;
let spread_height = config["spread_height"].as_i64().unwrap_or(0) as i32;
let max_height = config["max_height"].as_i64().unwrap_or(0) as i32;
quote! {
ConfiguredFeature::TwistingVines(crate::generation::feature::features::twisting_vines::TwistingVinesFeature {
spread_width: #spread_width,
spread_height: #spread_height,
max_height: #max_height,
})
}
}
"minecraft:delta_feature" => {
quote! { ConfiguredFeature::DeltaFeature(crate::generation::feature::features::delta_feature::DeltaFeatureFeature {}) }
}
"minecraft:fill_layer" => {
quote! { ConfiguredFeature::FillLayer(crate::generation::feature::features::fill_layer::FillLayerFeature {}) }
}
"minecraft:bonus_chest" => {
quote! { ConfiguredFeature::BonusChest(crate::generation::feature::features::bonus_chest::BonusChestFeature {}) }
}
"minecraft:dripstone_cluster" | "minecraft:speleothem_cluster" => {
quote! { ConfiguredFeature::DripstoneCluster(crate::generation::feature::features::drip_stone::cluster::DripstoneClusterFeature {}) }
}
"minecraft:sequence" | "minecraft:weighted_random_selector" => {
quote! { ConfiguredFeature::NoOp }
}
"minecraft:large_dripstone" => {
quote! { ConfiguredFeature::LargeDripstone(crate::generation::feature::features::drip_stone::large::LargeDripstoneFeature {}) }
}
"minecraft:sculk_patch" => {
let charge_count = config["charge_count"].as_u64().unwrap() as i32;
let amount_per_charge = config["amount_per_charge"].as_u64().unwrap() as i32;
let spread_attempts = config["spread_attempts"].as_u64().unwrap() as i32;
let growth_rounds = config["growth_rounds"].as_u64().unwrap() as i32;
let spread_rounds = config["spread_rounds"].as_u64().unwrap() as i32;
let extra_rare_growths = value_to_int_provider(&config["extra_rare_growths"]);
let catalyst_chance = config["catalyst_chance"].as_f64().unwrap() as f32;
quote! {
ConfiguredFeature::SculkPatch(crate::generation::feature::features::sculk_patch::SculkPatchFeature {
charge_count: #charge_count,
amount_per_charge: #amount_per_charge,
spread_attempts: #spread_attempts,
growth_rounds: #growth_rounds,
spread_rounds: #spread_rounds,
extra_rare_growths: #extra_rare_growths,
catalyst_chance: #catalyst_chance,
})
}
}
"minecraft:block_pile" => {
quote! { ConfiguredFeature::BlockPile(crate::generation::feature::features::block_pile::BlockPileFeature {}) }
}
"minecraft:replace_single_block" => {
quote! { ConfiguredFeature::ReplaceSingleBlock(crate::generation::feature::features::replace_single_block::ReplaceSingleBlockFeature {}) }
}
"minecraft:void_start_platform" => {
quote! { ConfiguredFeature::VoidStartPlatform(crate::generation::feature::features::void_start_platform::VoidStartPlatformFeature {}) }
}
"minecraft:desert_well" => {
quote! { ConfiguredFeature::DesertWell(crate::generation::feature::features::desert_well::DesertWellFeature) }
}
other => {
let msg = format!("unknown configured feature type: {other}");
quote! { compile_error!(#msg) }
}
}
}
/// Converts a block-state-provider JSON object into its `BlockStateProvider` token stream.
///
/// # Arguments
/// – `v` – the JSON value for the provider, expected to contain a `"type"` field.
///
/// # Returns
/// A `TokenStream` for the appropriate `BlockStateProvider` variant; defaults to `BlockStateProvider::Simple` with air if the type is unrecognised.
fn value_to_block_state_provider(v: &Value) -> TokenStream {
let type_str = v["type"].as_str().unwrap_or("");
match type_str {
"minecraft:simple_state_provider" => {
let state = value_to_block_state(&v["state"]);
quote! { BlockStateProvider::Simple(SimpleStateProvider { state: #state }) }
}
"minecraft:weighted_state_provider" => {
let entries: Vec<TokenStream> = v["entries"]
.as_array()
.map(|arr| {
arr.iter()
.map(|e| {
let data = value_to_block_state(&e["data"]);
let weight = e["weight"].as_i64().unwrap_or(1) as i32;
quote! { Weighted { data: #data, weight: #weight } }
})
.collect()
})
.unwrap_or_default();
quote! {
BlockStateProvider::Weighted(WeightedBlockStateProvider {
entries: vec![#(#entries),*],
})
}
}
"minecraft:rotated_block_provider" => {
let state = value_to_block_state(&v["state"]);
quote! { BlockStateProvider::Pillar(PillarBlockStateProvider { state: #state }) }
}
"minecraft:noise_provider" => {
let base = value_to_noise_base(v);
let states: Vec<TokenStream> = v["states"]
.as_array()
.map(|arr| arr.iter().map(value_to_block_state).collect())
.unwrap_or_default();
quote! {
BlockStateProvider::NoiseProvider(NoiseBlockStateProvider {
base: #base,
states: vec![#(#states),*],
})
}
}
"minecraft:dual_noise_provider" => {
let base_provider = value_to_noise_base(v);
let states: Vec<TokenStream> = v["states"]
.as_array()
.map(|arr| arr.iter().map(value_to_block_state).collect())
.unwrap_or_default();
let base_noise_provider = quote! {
NoiseBlockStateProvider { base: #base_provider, states: vec![#(#states),*] }
};
let v0 = v["variety"][0].as_u64().unwrap_or(2) as u32;
let v1 = v["variety"][1].as_u64().unwrap_or(4) as u32;
let slow_noise = value_to_dpnp(&v["slow_noise"]);
let slow_scale = v["slow_scale"].as_f64().unwrap_or(1.0);
quote! {
BlockStateProvider::DualNoise(DualNoiseBlockStateProvider {
base: #base_noise_provider,
variety: [#v0, #v1],
slow_noise: #slow_noise,
slow_scale: #slow_scale,
})
}
}
"minecraft:noise_threshold_provider" => {
let base = value_to_noise_base(v);
let threshold = v["threshold"].as_f64().unwrap_or(0.0) as f32;
let high_chance = v["high_chance"].as_f64().unwrap_or(0.0) as f32;
let default = value_to_block_state(&v["default_state"]);
let low: Vec<TokenStream> = v["low_states"]
.as_array()
.map(|a| a.iter().map(value_to_block_state).collect())
.unwrap_or_default();
let high: Vec<TokenStream> = v["high_states"]
.as_array()
.map(|a| a.iter().map(value_to_block_state).collect())
.unwrap_or_default();
quote! {
BlockStateProvider::NoiseThreshold(NoiseThresholdBlockStateProvider {
base: #base,
threshold: #threshold,
high_chance: #high_chance,
default_state: #default,
low_states: vec![#(#low),*],
high_states: vec![#(#high),*],
})
}
}
"minecraft:randomized_int_state_provider" => {
let src = value_to_block_state_provider(&v["source"]);
let prop = v["property"].as_str().unwrap_or("");
let vals = value_to_int_provider(&v["values"]);
quote! {
BlockStateProvider::RandomizedInt(RandomizedIntBlockStateProvider {
source: Box::new(#src),
property: #prop.to_string(),
values: #vals,
})
}
}
"minecraft:rule_based_state_provider" => {
let fallback = if !v["fallback"].is_null() {
let provider = value_to_block_state_provider(&v["fallback"]);
quote! { Some(Box::new(#provider))}
} else {
quote! { None }
};
let rules: Vec<TokenStream> = v["rules"]
.as_array()
.map(|arr| {
arr.iter()
.map(|rule| {
let if_true = value_to_block_predicate(&rule["if_true"]);
let then = value_to_block_state_provider(&rule["then"]);
quote! { BlockStateRule { if_true: #if_true, then: #then } }
})
.collect()
})
.unwrap_or_default();
quote! {
BlockStateProvider::Rule(RuleBasedBlockStateProvider {
fallback: #fallback,
rules: vec![#(#rules),*],
})
}
}
_ => {
// Default to air
quote! {
BlockStateProvider::Simple(SimpleStateProvider {
state: pumpkin_data::Block::AIR.default_state,
})
}
}
}
}
/// Converts a noise-provider JSON object into a `NoiseBlockStateProviderBase` token stream.
///
/// # Arguments
/// – `v` – the JSON object containing `"seed"`, `"noise"`, and `"scale"` fields.
fn value_to_noise_base(v: &Value) -> TokenStream {
let seed = v["seed"].as_i64().unwrap_or(0);
let noise = value_to_dpnp(&v["noise"]);
let scale = v["scale"].as_f64().unwrap_or(1.0) as f32;
quote! {
NoiseBlockStateProviderBase { seed: #seed, noise: #noise, scale: #scale }
}
}
/// Converts a double-perlin-noise parameters JSON object into a `DoublePerlinNoiseParametersCodec` token stream.
///
/// # Arguments
/// – `v` – the JSON object containing `"firstOctave"` and `"amplitudes"` fields.
fn value_to_dpnp(v: &Value) -> TokenStream {
let first_octave = v["firstOctave"].as_i64().unwrap_or(-7) as i32;
let amplitudes: Vec<f64> = v["amplitudes"]
.as_array()
.map(|a| a.iter().filter_map(|x| x.as_f64()).collect())
.unwrap_or_default();
let mut min_octave = i32::MAX;
let mut max_octave = i32::MIN;
for (index, amp) in amplitudes.iter().enumerate() {
if *amp != 0.0 {
min_octave = i32::min(min_octave, index as i32);
max_octave = i32::max(max_octave, index as i32);
}
}
let octaves = max_octave - min_octave;
let create_amp_val = 0.1f64 * (1.0f64 + 1.0f64 / (octaves + 1) as f64);
let final_amplitude = 0.16666666666666666f64 / create_amp_val;
quote! {
DoublePerlinNoiseParametersCodec {
first_octave: #first_octave,
amplitudes: vec![#(#amplitudes),*],
amplitude: #final_amplitude,
}
}
}
/// Converts a rule-test JSON object into its `RuleTest` enum variant token stream.
///
/// # Arguments
/// – `v` – the JSON object for the rule test, expected to contain a `"predicate_type"` field.
///
/// # Returns
/// A `TokenStream` for the appropriate `RuleTest` variant; emits a `compile_error!` for unrecognised types.
fn value_to_rule_test(v: &Value) -> TokenStream {
let type_str = v["predicate_type"].as_str().unwrap_or("");
match type_str {
"minecraft:always_true" | "" => quote! { RuleTest::AlwaysTrue },
"minecraft:block_match" => {
let block = v["block"].as_str().unwrap_or("minecraft:stone");
let name_stripped = block.strip_prefix("minecraft:").unwrap_or(block);
let block_ident =
quote::format_ident!("{}", name_stripped.to_uppercase().replace([':', '-'], "_"));
quote! { RuleTest::BlockMatch(BlockMatchRuleTest { block: pumpkin_data::BlockId::#block_ident }) }
}
"minecraft:blockstate_match" => {
let state = value_to_block_state(&v["block_state"]);
quote! { RuleTest::BlockStateMatch(BlockStateMatchRuleTest { block_state: #state.id }) }
}
"minecraft:tag_match" => {
let tag = v["tag"].as_str().unwrap_or("");
let tag_ident = quote::format_ident!("{}", tag.to_uppercase().replace([':', '-'], "_"));
quote! { RuleTest::TagMatch(TagMatchRuleTest { tag: pumpkin_data::tag::Block::#tag_ident }) }
}
"minecraft:random_block_match" => {
let block = v["block"].as_str().unwrap_or("minecraft:stone");
let prob = v["probability"].as_f64().unwrap_or(0.5) as f32;
let block_ident =
quote::format_ident!("{}", block.to_uppercase().replace([':', '-'], "_"));
quote! { RuleTest::RandomBlockMatch(RandomBlockMatchRuleTest { block: pumpkin_data::BlockId::#block_ident, probability: #prob }) }
}
"minecraft:random_blockstate_match" => {
let state = value_to_block_state(&v["block_state"]);
let prob = v["probability"].as_f64().unwrap_or(0.5) as f32;
quote! { RuleTest::RandomBlockStateMatch(RandomBlockStateMatchRuleTest { block_state: #state.id, probability: #prob }) }
}
other => {
let msg = format!("unknown rule test: {other}");
quote! { compile_error!(#msg) }
}
}
}
/// Converts a block-list JSON value (string or array) into a `BlockWrapper` token stream.
///
/// # Arguments
/// – `v` – a JSON string (single block) or array of strings (multiple blocks).
///
/// # Returns
/// `BlockWrapper::Single` for a string value or `BlockWrapper::Multi` for an array; defaults to `BlockWrapper::Single("")` for other types.
fn value_to_block_wrapper(v: &Value) -> TokenStream {
match v {
Value::String(s) => quote! { BlockWrapper::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! { BlockWrapper::Multi(vec![#(#items),*]) }
}
_ => quote! { BlockWrapper::Single(String::new()) },
}
}
/// Converts a tree-feature config JSON object into a `TreeFeature` token stream.
///
/// # Arguments
/// – `config` – the `"config"` sub-object of a `minecraft:tree` configured feature JSON entry.
fn value_to_tree_feature(config: &Value) -> TokenStream {
let trunk = value_to_block_state_provider(&config["trunk_provider"]);
let trunk_placer = value_to_trunk_placer(&config["trunk_placer"]);
let foliage = value_to_block_state_provider(&config["foliage_provider"]);
let foliage_placer = value_to_foliage_placer(&config["foliage_placer"]);
let min_size = value_to_feature_size(&config["minimum_size"]);
let ignore_vines = config["ignore_vines"].as_bool().unwrap_or(true);
let below_trunk_provider = value_to_block_state_provider(&config["below_trunk_provider"]);
let decorators: Vec<TokenStream> = config["decorators"]
.as_array()
.map(|arr| arr.iter().map(value_to_tree_decorator).collect())
.unwrap_or_default();
let root_placer = match config.get("root_placer") {
Some(v) if !v.is_null() => {
let inner = value_to_root_placer(v);
quote! { Some(#inner) }
}
_ => quote! { None },
};
quote! {
TreeFeature {
trunk_provider: #trunk,
trunk_placer: #trunk_placer,
foliage_provider: #foliage,
foliage_placer: #foliage_placer,
minimum_size: #min_size,
ignore_vines: #ignore_vines,
below_trunk_provider: #below_trunk_provider,
decorators: vec![#(#decorators),*],
root_placer: #root_placer,
}
}
}
fn value_to_root_placer(v: &Value) -> TokenStream {
let type_str = v["type"].as_str().unwrap_or("");
match type_str {
"minecraft:mangrove_root_placer" => {
let trunk_offset_y = value_to_int_provider(&v["trunk_offset_y"]);
let root_provider = value_to_block_state_provider(&v["root_provider"]);
let above = match v.get("above_root_placement") {
Some(av) if !av.is_null() => {
let provider = value_to_block_state_provider(&av["above_root_provider"]);
let chance = av["above_root_placement_chance"].as_f64().unwrap_or(0.0) as f32;
quote! {
Some(AboveRootPlacement {
above_root_provider: #provider,
above_root_placement_chance: #chance,
})
}
}
_ => quote! { None },
};
let mrp = &v["mangrove_root_placement"];
let can_grow_through = value_to_block_list(&mrp["can_grow_through"]);
let muddy_roots_in = value_to_block_list(&mrp["muddy_roots_in"]);
let muddy_roots_provider = value_to_block_state_provider(&mrp["muddy_roots_provider"]);
let max_root_width = mrp["max_root_width"].as_i64().unwrap_or(8) as i32;
let max_root_length = mrp["max_root_length"].as_i64().unwrap_or(15) as i32;
let random_skew_chance = mrp["random_skew_chance"].as_f64().unwrap_or(0.0) as f32;
quote! {
RootPlacer::Mangrove(MangroveRootPlacer {
trunk_offset_y: #trunk_offset_y,
root_provider: #root_provider,
above_root_placement: #above,
mangrove_root_placement: MangroveRootPlacement {
can_grow_through: #can_grow_through,
muddy_roots_in: const { #muddy_roots_in },
muddy_roots_provider: #muddy_roots_provider,
max_root_width: #max_root_width,
max_root_length: #max_root_length,
random_skew_chance: #random_skew_chance,
},
})
}
}
other => {
let msg = format!("Unknown root placer type: {other}");
quote! { compile_error!(#msg) }
}
}
}
fn value_to_block_list(v: &Value) -> TokenStream {
if let Some(tag) = v.as_str().and_then(|s| s.strip_prefix('#')) {
let name = format!(
"MINECRAFT_{}",
tag.strip_prefix("minecraft:").unwrap_or(tag).to_uppercase()
);
let ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
return quote! { &pumpkin_data::tag::Block::#ident.1 };
}
let mut blocks = Vec::new();
if let Some(arr) = v.as_array() {
for b in arr {
if let Some(s) = b.as_str() {
let name = s.strip_prefix("minecraft:").unwrap_or(s).to_uppercase();
let ident = syn::Ident::new(&name, proc_macro2::Span::call_site());
blocks.push(quote! { pumpkin_data::BlockId::#ident.as_u16() });
}
}
}
quote! { &[#(#blocks),*] }
}
/// Converts a trunk-placer JSON object into a `TrunkPlacer` token stream.
///
/// # Arguments
/// – `v` – the `"trunk_placer"` sub-object from a tree config, containing `"type"`, `"base_height"`, `"height_rand_a"`, and `"height_rand_b"` fields.
fn value_to_trunk_placer(v: &Value) -> TokenStream {
let base = v["base_height"].as_u64().unwrap_or(5) as u8;
let rand_a = v["height_rand_a"].as_u64().unwrap_or(0) as u8;
let rand_b = v["height_rand_b"].as_u64().unwrap_or(0) as u8;
let type_str = v["type"].as_str().unwrap_or("");
let trunk_type = match type_str {
"minecraft:straight_trunk_placer" => quote! { TrunkType::Straight(StraightTrunkPlacer) },
"minecraft:forking_trunk_placer" => quote! { TrunkType::Forking(ForkingTrunkPlacer) },
"minecraft:giant_trunk_placer" => quote! { TrunkType::Giant(GiantTrunkPlacer) },
"minecraft:mega_jungle_trunk_placer" => {
quote! { TrunkType::MegaJungle(MegaJungleTrunkPlacer) }
}
"minecraft:dark_oak_trunk_placer" => quote! { TrunkType::DarkOak(DarkOakTrunkPlacer) },
"minecraft:fancy_trunk_placer" => quote! { TrunkType::Fancy(FancyTrunkPlacer) },
"minecraft:bending_trunk_placer" => {
let min_h = v["min_height_for_leaves"].as_u64().unwrap_or(1) as u32;
let bend = value_to_int_provider(&v["bend_length"]);
quote! {
TrunkType::Bending(BendingTrunkPlacer {
min_height_for_leaves: #min_h,
bend_length: #bend,
})
}
}
"minecraft:upwards_branching_trunk_placer" => {
let extra_branch_steps = value_to_int_provider(&v["extra_branch_steps"]);
let place_branch_per_log_probability = v["place_branch_per_log_probability"]
.as_f64()
.unwrap_or(0.0) as f32;
let extra_branch_length = value_to_int_provider(&v["extra_branch_length"]);
let can_grow_through = value_to_block_list(&v["can_grow_through"]);
quote! {
TrunkType::UpwardsBranching(UpwardsBranchingTrunkPlacer {
extra_branch_steps: #extra_branch_steps,
place_branch_per_log_probability: #place_branch_per_log_probability,
extra_branch_length: #extra_branch_length,
can_grow_through: #can_grow_through,
})
}
}
"minecraft:cherry_trunk_placer" => {
let branch_count = value_to_int_provider(&v["branch_count"]);
let branch_horizontal_length = value_to_int_provider(&v["branch_horizontal_length"]);
let branch_start_offset_v = &v["branch_start_offset_from_top"];
let min = branch_start_offset_v["min_inclusive"].as_i64().unwrap_or(0) as i32;
let max = branch_start_offset_v["max_inclusive"].as_i64().unwrap_or(0) as i32;
let branch_end_offset_from_top =
value_to_int_provider(&v["branch_end_offset_from_top"]);
quote! {
TrunkType::Cherry(CherryTrunkPlacer {
count: #branch_count,
horizontal_length: #branch_horizontal_length,
start_offset_from_top: UniformIntProvider { min_inclusive: #min, max_inclusive: #max },
end_offset_from_top: #branch_end_offset_from_top,
})
}
}
_ => quote! { TrunkType::Straight(StraightTrunkPlacer) },
};
quote! {
TrunkPlacer {
base_height: #base,
height_rand_a: #rand_a,
height_rand_b: #rand_b,
r#type: #trunk_type,
}
}
}
/// Converts a foliage-placer JSON object into a `FoliagePlacer` token stream.
///
/// # Arguments
/// – `v` – the `"foliage_placer"` sub-object from a tree config, containing `"type"`, `"radius"`, and `"offset"` fields.
fn value_to_foliage_placer(v: &Value) -> TokenStream {
let radius = value_to_int_provider(&v["radius"]);
let offset = value_to_int_provider(&v["offset"]);
let type_str = v["type"].as_str().unwrap_or("");
let foliage_type = match type_str {
"minecraft:blob_foliage_placer" => {
let h = v["height"].as_i64().unwrap_or(3) as i32;
quote! { FoliageType::Blob(BlobFoliagePlacer { height: #h }) }
}
"minecraft:spruce_foliage_placer" => {
let th = value_to_int_provider(&v["trunk_height"]);
quote! { FoliageType::Spruce(SpruceFoliagePlacer { trunk_height: #th }) }
}
"minecraft:pine_foliage_placer" => {
let h = value_to_int_provider(&v["height"]);
quote! { FoliageType::Pine(PineFoliagePlacer { height: #h }) }
}
"minecraft:acacia_foliage_placer" => {
quote! { FoliageType::Acacia(AcaciaFoliagePlacer) }
}
"minecraft:bush_foliage_placer" => {
let h = v["height"].as_i64().unwrap_or(2) as i32;
quote! { FoliageType::Bush(BushFoliagePlacer { height: #h }) }
}
"minecraft:fancy_foliage_placer" => {
let h = v["height"].as_i64().unwrap_or(4) as i32;
quote! { FoliageType::Fancy(LargeOakFoliagePlacer { height: #h }) }
}
"minecraft:jungle_foliage_placer" => {
let h = v["height"].as_i64().unwrap_or(2) as i32;
quote! { FoliageType::Jungle(JungleFoliagePlacer { height: #h }) }
}
"minecraft:mega_pine_foliage_placer" => {
let ch = value_to_int_provider(&v["crown_height"]);
quote! { FoliageType::MegaPine(MegaPineFoliagePlacer { crown_height: #ch }) }
}
"minecraft:dark_oak_foliage_placer" => {
quote! { FoliageType::DarkOak(DarkOakFoliagePlacer) }
}
"minecraft:random_spread_foliage_placer" => {
let fh = value_to_int_provider(&v["foliage_height"]);
let lpa = v["leaf_placement_attempts"].as_i64().unwrap_or(128) as i32;
quote! {
FoliageType::RandomSpread(RandomSpreadFoliagePlacer {
foliage_height: #fh,
leaf_placement_attempts: #lpa,
})
}
}
"minecraft:cherry_foliage_placer" => {
let h = value_to_int_provider(&v["height"]);
let wblh = v["wide_bottom_layer_hole_chance"].as_f64().unwrap_or(0.0) as f32;
let ch = v["corner_hole_chance"].as_f64().unwrap_or(0.0) as f32;
let hlc = v["hanging_leaves_chance"].as_f64().unwrap_or(0.0) as f32;
let hlec = v["hanging_leaves_extension_chance"].as_f64().unwrap_or(0.0) as f32;
quote! {
FoliageType::Cherry(CherryFoliagePlacer {
height: #h,
wide_bottom_layer_hole_chance: #wblh,
corner_hole_chance: #ch,
hanging_leaves_chance: #hlc,
hanging_leaves_extension_chance: #hlec,
})
}
}
_ => {
let h = v["height"].as_i64().unwrap_or(3) as i32;
quote! { FoliageType::Blob(BlobFoliagePlacer { height: #h }) }
}
};
quote! {
FoliagePlacer { radius: #radius, offset: #offset, r#type: #foliage_type }
}
}
/// Converts a feature-size JSON object into a `FeatureSize` token stream.
///
/// # Arguments
/// – `v` – the `"minimum_size"` sub-object from a tree config, containing a `"type"` field and size parameters.
fn value_to_feature_size(v: &Value) -> TokenStream {
let min_clipped = if v["min_clipped_height"].is_number() {
let val = v["min_clipped_height"].as_u64().unwrap_or(0) as u8;
quote! { Some(#val) }
} else {
quote! { None }
};
let type_str = v["type"].as_str().unwrap_or("");
let size_type = match type_str {
"minecraft:two_layers_feature_size" => {
let limit = v["limit"].as_u64().unwrap_or(1) as u8;
let lower = v["lower_size"].as_u64().unwrap_or(0) as u8;
let upper = v["upper_size"].as_u64().unwrap_or(1) as u8;
quote! {
FeatureSizeType::TwoLayersFeatureSize(TwoLayersFeatureSize {
limit: #limit,
lower_size: #lower,
upper_size: #upper,
})
}
}
"minecraft:three_layers_feature_size" => {
let limit = v["limit"].as_u64().unwrap_or(1) as u8;
let upper_limit = v["upper_limit"].as_u64().unwrap_or(1) as u8;
let lower = v["lower_size"].as_u64().unwrap_or(0) as u8;
let middle = v["middle_size"].as_u64().unwrap_or(1) as u8;
let upper = v["upper_size"].as_u64().unwrap_or(1) as u8;
quote! {
FeatureSizeType::ThreeLayersFeatureSize(ThreeLayersFeatureSize {
limit: #limit,
upper_limit: #upper_limit,
lower_size: #lower,
middle_size: #middle,
upper_size: #upper,
})
}
}
_ => {
quote! {
FeatureSizeType::TwoLayersFeatureSize(TwoLayersFeatureSize {
limit: 1,
lower_size: 0,
upper_size: 1,
})
}
}
};
quote! {
FeatureSize { min_clipped_height: #min_clipped, r#type: #size_type }
}
}
/// Converts a tree-decorator JSON object into its `TreeDecorator` enum variant token stream.
///
/// # Arguments
/// – `v` – a JSON object for one decorator entry, expected to contain a `"type"` field.
///
/// # Returns
/// A `TokenStream` for the appropriate `TreeDecorator` variant; emits a `compile_error!` for unrecognised types.
fn value_to_tree_decorator(v: &Value) -> TokenStream {
let type_str = v["type"].as_str().unwrap_or("");
match type_str {
"minecraft:trunk_vine" => quote! { TreeDecorator::TrunkVine(TrunkVineTreeDecorator) },
"minecraft:leave_vine" => {
let prob = v["probability"].as_f64().unwrap_or(0.0) as f32;
quote! { TreeDecorator::LeaveVine(LeavesVineTreeDecorator { probability: #prob }) }
}
"minecraft:cocoa" => quote! { TreeDecorator::Cocoa(CocoaTreeDecorator {}) },
"minecraft:beehive" => {
let prob = v["probability"].as_f64().unwrap_or(0.0) as f32;
quote! { TreeDecorator::Beehive(BeehiveTreeDecorator { probability: #prob }) }
}
"minecraft:alter_ground" => {
quote! { TreeDecorator::AlterGround(AlterGroundTreeDecorator {}) }
}
"minecraft:attached_to_logs" => {
let prob = v["probability"].as_f64().unwrap_or(0.0) as f32;
let bp = value_to_block_state_provider(&v["block_provider"]);
let dirs: Vec<TokenStream> = v["directions"]
.as_array()
.map(|arr| {
arr.iter()
.filter_map(|d| d.as_str().map(value_to_block_direction))
.collect()
})
.unwrap_or_default();
quote! {
TreeDecorator::AttachedToLogs(AttachedToLogsTreeDecorator {
probability: #prob,
block_provider: #bp,
directions: vec![#(#dirs),*],
})
}
}
"minecraft:attached_to_leaves" => {
quote! { TreeDecorator::AttachedToLeaves(AttachedToLeavesTreeDecorator {}) }
}
"minecraft:place_on_ground" => {
let tries = v["tries"].as_i64().unwrap_or(1) as i32;
let radius = v["radius"].as_i64().unwrap_or(1) as i32;
let height = v["height"].as_i64().unwrap_or(1) as i32;
let bsp = value_to_block_state_provider(&v["block_state_provider"]);
quote! {
TreeDecorator::PlaceOnGround(PlaceOnGroundTreeDecorator {
tries: #tries,
radius: #radius,
height: #height,
block_state_provider: #bsp,
})
}
}
"minecraft:creaking_heart" => {
quote! { TreeDecorator::CreakingHeart(CreakingHeartTreeDecorator {}) }
}
"minecraft:pale_moss" => {
quote! { TreeDecorator::PaleMoss(PaleMossTreeDecorator {}) }
}
other => {
let msg = format!("unknown tree decorator: {other}");
quote! { compile_error!(#msg) }
}
}
}
/// Converts an inline placed-feature JSON object into a `PlacedFeature` token stream.
///
/// # Arguments
/// – `v` – the JSON object containing `"feature"` and `"placement"` fields.
fn value_to_inline_placed_feature(v: &Value) -> TokenStream {
let feature = value_to_feature_ref(&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_cf)
.collect();
quote! {
PlacedFeature {
feature: #feature,
placement: vec![#(#placement),*],
}
}
}
/// Converts a placed-feature reference JSON value into a `PlacedFeatureWrapper` token stream.
///
/// # Arguments
/// – `v` – a JSON string (named reference) or object (inline placed feature).
///
/// # Returns
/// `PlacedFeatureWrapper::Named` for a string value or `PlacedFeatureWrapper::Direct` for an inline object; defaults to `PlacedFeatureWrapper::Named(pumpkin_data::placed_feature::PlacedFeature::Acacia)` for other types.
fn value_to_placed_feature_wrapper(v: &Value) -> TokenStream {
match v {
Value::String(s) => {
let name = s.strip_prefix("minecraft:").unwrap_or(s);
let variant_name = format_ident!("{}", name.to_pascal_case());
quote! { PlacedFeatureWrapper::Named(pumpkin_data::placed_feature::PlacedFeature::#variant_name) }
}
Value::Object(_) => {
// It might be a PlacedFeature object
let pf = value_to_inline_placed_feature(v);
quote! { PlacedFeatureWrapper::Direct(#pf) }
}
_ => {
quote! { PlacedFeatureWrapper::Named(pumpkin_data::placed_feature::PlacedFeature::Acacia) }
}
}
}
/// Converts a feature reference JSON value into a `Feature` enum token stream.
///
/// # Arguments
/// – `v` – a JSON string (named reference) or object (inline configured feature).
///
/// # Returns
/// `Feature::Named` for a string value or `Feature::Inlined` for an object; defaults to `Feature::Named(pumpkin_data::configured_feature::ConfiguredFeature::NoOp)` for other types.
fn value_to_feature_ref(v: &Value) -> TokenStream {
match v {
Value::String(s) => {
let name = s.strip_prefix("minecraft:").unwrap_or(s);
let variant_name = format_ident!("{}", name.to_pascal_case());
quote! { Feature::Named(pumpkin_data::configured_feature::ConfiguredFeature::#variant_name) }
}
Value::Object(_) => {
let cf = value_to_configured_feature(v);
quote! { Feature::Inlined(Box::new(#cf)) }
}
_ => quote! { Feature::Named(pumpkin_data::configured_feature::ConfiguredFeature::NoOp) },
}
}
// Placement modifier in context of configured_features (same as placed_feature but re-imported)
/// Converts a placement-modifier JSON object into its `PlacementModifier` token stream within the configured-feature context.
///
/// # Arguments
/// – `v` – a JSON object for one modifier entry, expected to contain a `"type"` field.
///
/// # Returns
/// A `TokenStream` for the appropriate `PlacementModifier` variant; defaults to `PlacementModifier::Biome` for unrecognised types.
fn value_to_placement_modifier_cf(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 hm = crate::placed_feature::value_to_height_map(
v["heightmap"].as_str().unwrap_or("MOTION_BLOCKING"),
);
quote! { PlacementModifier::Heightmap(HeightmapPlacementModifier { heightmap: #hm }) }
}
"minecraft:height_range" => {
let h = value_to_height_provider(&v["height"]);
quote! { PlacementModifier::HeightRange(HeightRangePlacementModifier { height: #h }) }
}
"minecraft:count" => {
let c = value_to_int_provider(&v["count"]);
quote! { PlacementModifier::Count(CountPlacementModifier { count: #c }) }
}
"minecraft:count_on_every_layer" => {
let c = value_to_int_provider(&v["count"]);
quote! { PlacementModifier::CountOnEveryLayer(CountOnEveryLayerPlacementModifier { count: #c }) }
}
"minecraft:rarity_filter" => {
let ch = v["chance"].as_u64().unwrap_or(1) as u32;
quote! { PlacementModifier::RarityFilter(RarityFilterPlacementModifier { chance: #ch }) }
}
"minecraft:block_predicate_filter" => {
let pred = value_to_block_predicate(&v["predicate"]);
quote! { PlacementModifier::BlockPredicateFilter(BlockFilterPlacementModifier { predicate: #pred }) }
}
"minecraft:surface_relative_threshold_filter" => {
let hm = crate::placed_feature::value_to_height_map(
v["heightmap"].as_str().unwrap_or("MOTION_BLOCKING"),
);
let mn = if v["min_inclusive"].is_number() {
let x = v["min_inclusive"].as_i64().unwrap() as i32;
quote! {Some(#x)}
} else {
quote! {None}
};
let mx = if v["max_inclusive"].is_number() {
let x = v["max_inclusive"].as_i64().unwrap() as i32;
quote! {Some(#x)}
} else {
quote! {None}
};
quote! { PlacementModifier::SurfaceRelativeThresholdFilter(SurfaceThresholdFilterPlacementModifier { heightmap: #hm, min_inclusive: #mn, max_inclusive: #mx }) }
}
"minecraft:surface_water_depth_filter" => {
let d = v["max_water_depth"].as_i64().unwrap_or(0) as i32;
quote! { PlacementModifier::SurfaceWaterDepthFilter(SurfaceWaterDepthFilterPlacementModifier { max_water_depth: #d }) }
}
"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 }) }
}
"minecraft:noise_based_count" => {
let r = v["noise_to_count_ratio"].as_i64().unwrap_or(0) as i32;
let f = v["noise_factor"].as_f64().unwrap_or(1.0);
let o = v["noise_offset"].as_f64().unwrap_or(0.0);
quote! { PlacementModifier::NoiseBasedCount(NoiseBasedCountPlacementModifier { to_count_ratio: #r, factor: #f, offset: #o }) }
}
"minecraft:noise_threshold_count" => {
let l = v["noise_level"].as_f64().unwrap_or(0.0);
let b = v["below_noise"].as_i64().unwrap_or(0) as i32;
let a = v["above_noise"].as_i64().unwrap_or(0) as i32;
quote! { PlacementModifier::NoiseThresholdCount(NoiseThresholdCountPlacementModifier { noise_level: #l, below_noise: #b, above_noise: #a }) }
}
"minecraft:environment_scan" => {
let dir = value_to_block_direction(v["direction_of_search"].as_str().unwrap_or("down"));
let tc = value_to_block_predicate(&v["target_condition"]);
let asc = if v["allowed_search_condition"].is_object() {
let p = value_to_block_predicate(&v["allowed_search_condition"]);
quote! { Some(#p) }
} 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: #tc, allowed_search_condition: #asc, max_steps: #steps }) }
}
_ => quote! { PlacementModifier::Biome(BiomePlacementModifier) },
}
}