chore: finish 26.1 chunk generation port

This commit is contained in:
Alexander Medvedev 2026-04-02 17:12:10 +02:00
parent ed15c00307
commit 4848ca52cc
31 changed files with 29041 additions and 17527 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -43,7 +43,7 @@ pub fn build() -> TokenStream {
}; };
use pumpkin_util::y_offset::{AboveBottom, Absolute, BelowTop, YOffset}; use pumpkin_util::y_offset::{AboveBottom, Absolute, BelowTop, YOffset};
use pumpkin_util::math::int_provider::{ use pumpkin_util::math::int_provider::{
BiasedToBottomIntProvider, ClampedIntProvider, ClampedNormalIntProvider, BiasedToBottomIntProvider, ClampedIntProvider, TrapezoidIntProvider, ClampedNormalIntProvider,
ConstantIntProvider, IntProvider, NormalIntProvider, UniformIntProvider, ConstantIntProvider, IntProvider, NormalIntProvider, UniformIntProvider,
WeightedEntry, WeightedListIntProvider, WeightedEntry, WeightedListIntProvider,
}; };
@ -566,6 +566,9 @@ pub fn value_to_configured_feature(v: &Value) -> TokenStream {
"minecraft:fossil" => { "minecraft:fossil" => {
quote! { ConfiguredFeature::Fossil(crate::generation::feature::features::fossil::FossilFeature {}) } quote! { ConfiguredFeature::Fossil(crate::generation::feature::features::fossil::FossilFeature {}) }
} }
"minecraft:fossil" => {
quote! { ConfiguredFeature::Fossil(crate::generation::feature::features::fossil::FossilFeature {}) }
}
"minecraft:lake" => { "minecraft:lake" => {
quote! { ConfiguredFeature::Lake(crate::generation::feature::features::lake::LakeFeature {}) } quote! { ConfiguredFeature::Lake(crate::generation::feature::features::lake::LakeFeature {}) }
} }
@ -575,7 +578,7 @@ pub fn value_to_configured_feature(v: &Value) -> TokenStream {
"minecraft:huge_red_mushroom" => { "minecraft:huge_red_mushroom" => {
quote! { ConfiguredFeature::HugeRedMushroom(crate::generation::feature::features::huge_red_mushroom::HugeRedMushroomFeature {}) } quote! { ConfiguredFeature::HugeRedMushroom(crate::generation::feature::features::huge_red_mushroom::HugeRedMushroomFeature {}) }
} }
"minecraft:ice_spike" => { "minecraft:spike" => {
quote! { ConfiguredFeature::IceSpike(crate::generation::feature::features::ice_spike::IceSpikeFeature {}) } quote! { ConfiguredFeature::IceSpike(crate::generation::feature::features::ice_spike::IceSpikeFeature {}) }
} }
"minecraft:freeze_top_layer" => { "minecraft:freeze_top_layer" => {
@ -596,7 +599,7 @@ pub fn value_to_configured_feature(v: &Value) -> TokenStream {
"minecraft:iceberg" => { "minecraft:iceberg" => {
quote! { ConfiguredFeature::Iceberg(crate::generation::feature::features::iceberg::IcebergFeature {}) } quote! { ConfiguredFeature::Iceberg(crate::generation::feature::features::iceberg::IcebergFeature {}) }
} }
"minecraft:forest_rock" => { "minecraft:block_blob" => {
quote! { ConfiguredFeature::ForestRock(crate::generation::feature::features::forest_rock::ForestRockFeature {}) } quote! { ConfiguredFeature::ForestRock(crate::generation::feature::features::forest_rock::ForestRockFeature {}) }
} }
"minecraft:end_platform" => { "minecraft:end_platform" => {
@ -783,8 +786,14 @@ fn value_to_block_state_provider(v: &Value) -> TokenStream {
}) })
} }
} }
_ if !v["fallback"].is_null() => { "minecraft:rule_based_state_provider" => {
let fallback = value_to_block_state_provider(&v["fallback"]); 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"] let rules: Vec<TokenStream> = v["rules"]
.as_array() .as_array()
.map(|arr| { .map(|arr| {
@ -799,7 +808,7 @@ fn value_to_block_state_provider(v: &Value) -> TokenStream {
.unwrap_or_default(); .unwrap_or_default();
quote! { quote! {
BlockStateProvider::Rule(RuleBasedBlockStateProvider { BlockStateProvider::Rule(RuleBasedBlockStateProvider {
fallback: Box::new(#fallback), fallback: #fallback,
rules: vec![#(#rules),*], rules: vec![#(#rules),*],
}) })
} }
@ -859,7 +868,7 @@ fn value_to_rule_test(v: &Value) -> TokenStream {
"minecraft:always_true" | "" => quote! { RuleTest::AlwaysTrue }, "minecraft:always_true" | "" => quote! { RuleTest::AlwaysTrue },
"minecraft:block_match" => { "minecraft:block_match" => {
let block = v["block"].as_str().unwrap_or("minecraft:stone"); let block = v["block"].as_str().unwrap_or("minecraft:stone");
let name_stripped = block.strip_prefix("minecraft:").unwrap_or(block); let name_stripped = block.strip_prefix("minecraft:").unwrap_or(block);
let block_ident = let block_ident =
quote::format_ident!("{}", name_stripped.to_uppercase().replace([':', '-'], "_")); quote::format_ident!("{}", name_stripped.to_uppercase().replace([':', '-'], "_"));
quote! { RuleTest::BlockMatch(BlockMatchRuleTest { block: pumpkin_data::Block::#block_ident }) } quote! { RuleTest::BlockMatch(BlockMatchRuleTest { block: pumpkin_data::Block::#block_ident }) }
@ -870,12 +879,13 @@ fn value_to_rule_test(v: &Value) -> TokenStream {
} }
"minecraft:tag_match" => { "minecraft:tag_match" => {
let tag = v["tag"].as_str().unwrap_or(""); let tag = v["tag"].as_str().unwrap_or("");
quote! { RuleTest::TagMatch(TagMatchRuleTest { tag: #tag.to_string() }) } 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" => { "minecraft:random_block_match" => {
let block = v["block"].as_str().unwrap_or("minecraft:stone"); let block = v["block"].as_str().unwrap_or("minecraft:stone");
let prob = v["probability"].as_f64().unwrap_or(0.5) as f32; let prob = v["probability"].as_f64().unwrap_or(0.5) as f32;
let name_stripped = block.strip_prefix("minecraft:").unwrap_or(block); let name_stripped = block.strip_prefix("minecraft:").unwrap_or(block);
let block_ident = let block_ident =
quote::format_ident!("{}", block.to_uppercase().replace([':', '-'], "_")); quote::format_ident!("{}", block.to_uppercase().replace([':', '-'], "_"));
quote! { RuleTest::RandomBlockMatch(RandomBlockMatchRuleTest { block: pumpkin_data::Block::#block_ident, probability: #prob }) } quote! { RuleTest::RandomBlockMatch(RandomBlockMatchRuleTest { block: pumpkin_data::Block::#block_ident, probability: #prob }) }
@ -918,28 +928,26 @@ fn value_to_block_wrapper(v: &Value) -> TokenStream {
/// # Arguments /// # Arguments
/// – `config` – the `"config"` sub-object of a `minecraft:tree` configured feature JSON entry. /// – `config` – the `"config"` sub-object of a `minecraft:tree` configured feature JSON entry.
fn value_to_tree_feature(config: &Value) -> TokenStream { fn value_to_tree_feature(config: &Value) -> TokenStream {
let dirt = value_to_block_state_provider(&config["dirt_provider"]);
let trunk = value_to_block_state_provider(&config["trunk_provider"]); let trunk = value_to_block_state_provider(&config["trunk_provider"]);
let trunk_placer = value_to_trunk_placer(&config["trunk_placer"]); let trunk_placer = value_to_trunk_placer(&config["trunk_placer"]);
let foliage = value_to_block_state_provider(&config["foliage_provider"]); let foliage = value_to_block_state_provider(&config["foliage_provider"]);
let foliage_placer = value_to_foliage_placer(&config["foliage_placer"]); let foliage_placer = value_to_foliage_placer(&config["foliage_placer"]);
let min_size = value_to_feature_size(&config["minimum_size"]); let min_size = value_to_feature_size(&config["minimum_size"]);
let ignore_vines = config["ignore_vines"].as_bool().unwrap_or(true); let ignore_vines = config["ignore_vines"].as_bool().unwrap_or(true);
let force_dirt = config["force_dirt"].as_bool().unwrap_or(false); let below_trunk_provider = value_to_block_state_provider(&config["below_trunk_provider"]);
let decorators: Vec<TokenStream> = config["decorators"] let decorators: Vec<TokenStream> = config["decorators"]
.as_array() .as_array()
.map(|arr| arr.iter().map(value_to_tree_decorator).collect()) .map(|arr| arr.iter().map(value_to_tree_decorator).collect())
.unwrap_or_default(); .unwrap_or_default();
quote! { quote! {
TreeFeature { TreeFeature {
dirt_provider: #dirt,
trunk_provider: #trunk, trunk_provider: #trunk,
trunk_placer: #trunk_placer, trunk_placer: #trunk_placer,
foliage_provider: #foliage, foliage_provider: #foliage,
foliage_placer: #foliage_placer, foliage_placer: #foliage_placer,
minimum_size: #min_size, minimum_size: #min_size,
ignore_vines: #ignore_vines, ignore_vines: #ignore_vines,
force_dirt: #force_dirt, below_trunk_provider: #below_trunk_provider,
decorators: vec![#(#decorators),*], decorators: vec![#(#decorators),*],
} }
} }

View File

@ -38,7 +38,7 @@ pub fn build() -> TokenStream {
}; };
use pumpkin_util::y_offset::{AboveBottom, Absolute, BelowTop, YOffset}; use pumpkin_util::y_offset::{AboveBottom, Absolute, BelowTop, YOffset};
use pumpkin_util::math::int_provider::{ use pumpkin_util::math::int_provider::{
BiasedToBottomIntProvider, ClampedIntProvider, ClampedNormalIntProvider, BiasedToBottomIntProvider, ClampedIntProvider, TrapezoidIntProvider, ClampedNormalIntProvider,
ConstantIntProvider, IntProvider, NormalIntProvider, UniformIntProvider, ConstantIntProvider, IntProvider, NormalIntProvider, UniformIntProvider,
WeightedEntry, WeightedListIntProvider, WeightedEntry, WeightedListIntProvider,
}; };
@ -92,7 +92,7 @@ fn value_to_feature(v: &Value) -> TokenStream {
let cf = value_to_inline_configured_feature(v); let cf = value_to_inline_configured_feature(v);
quote! { Feature::Inlined(Box::new(#cf)) } quote! { Feature::Inlined(Box::new(#cf)) }
} }
_ => quote! { Feature::Named(String::new()) }, _ => panic!("Wrong feature value"),
} }
} }
@ -273,10 +273,11 @@ pub fn value_to_block_predicate(v: &Value) -> TokenStream {
// "true" (or any non-# string) is AlwaysTrue. // "true" (or any non-# string) is AlwaysTrue.
if let Some(s) = v.as_str() { if let Some(s) = v.as_str() {
if let Some(tag) = s.strip_prefix('#') { if let Some(tag) = s.strip_prefix('#') {
let tag_ident = quote::format_ident!("{}", tag.to_uppercase().replace([':', '-'], "_"));
return quote! { return quote! {
BlockPredicate::MatchingBlockTag(MatchingBlockTagPredicate { BlockPredicate::MatchingBlockTag(MatchingBlockTagPredicate {
offset: OffsetBlocksBlockPredicate { offset: None }, offset: OffsetBlocksBlockPredicate { offset: None },
tag: #tag.to_string(), tag: pumpkin_data::tag::Block::#tag_ident,
}) })
}; };
} }
@ -299,10 +300,11 @@ pub fn value_to_block_predicate(v: &Value) -> TokenStream {
"minecraft:matching_block_tag" => { "minecraft:matching_block_tag" => {
let offset = value_to_offset_predicate(&v["offset"]); let offset = value_to_offset_predicate(&v["offset"]);
let tag = v["tag"].as_str().unwrap_or(""); let tag = v["tag"].as_str().unwrap_or("");
let tag_ident = quote::format_ident!("{}", tag.to_uppercase().replace([':', '-'], "_"));
quote! { quote! {
BlockPredicate::MatchingBlockTag(MatchingBlockTagPredicate { BlockPredicate::MatchingBlockTag(MatchingBlockTagPredicate {
offset: #offset, offset: #offset,
tag: #tag.to_string(), tag: pumpkin_data::tag::Block::#tag_ident,
}) })
} }
} }
@ -526,6 +528,12 @@ pub fn value_to_int_provider(v: &Value) -> TokenStream {
let src = value_to_int_provider(&v["source"]); let src = value_to_int_provider(&v["source"]);
quote! { IntProvider::Object(NormalIntProvider::Clamped(ClampedIntProvider { source: Box::new(#src), min_inclusive: #min, max_inclusive: #max })) } quote! { IntProvider::Object(NormalIntProvider::Clamped(ClampedIntProvider { source: Box::new(#src), min_inclusive: #min, max_inclusive: #max })) }
} }
"minecraft:trapezoid" => {
let min = v["min"].as_i64().unwrap_or(0) as i32;
let max = v["max"].as_i64().unwrap_or(0) as i32;
let plateau = v["plateau"].as_i64().unwrap_or(0) as i32;
quote! { IntProvider::Object(NormalIntProvider::Trapezoid(TrapezoidIntProvider { min_inclusive: #min, max_inclusive: #max, plateau: #plateau })) }
}
"minecraft:clamped_normal" => { "minecraft:clamped_normal" => {
let mean = v["mean"].as_f64().unwrap_or(0.0) as f32; 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 dev = v["deviation"].as_f64().unwrap_or(1.0) as f32;
@ -549,12 +557,11 @@ pub fn value_to_int_provider(v: &Value) -> TokenStream {
quote! { IntProvider::Object(NormalIntProvider::WeightedList(WeightedListIntProvider { distribution: vec![#(#entries),*] })) } quote! { IntProvider::Object(NormalIntProvider::WeightedList(WeightedListIntProvider { distribution: vec![#(#entries),*] })) }
} }
_ => { _ => {
let val = v["value"].as_i64().unwrap_or(0) as i32; panic!("Unknown Int Provider, Seems like Mojang added a new one")
quote! { IntProvider::Constant(#val) }
} }
} }
} }
_ => quote! { IntProvider::Constant(0) }, _ => panic!("Unknown Int Provider, Seems like Mojang added a new one"),
} }
} }
@ -573,7 +580,7 @@ pub fn value_to_height_map(s: &str) -> TokenStream {
"OCEAN_FLOOR" => quote! { HeightMap::OceanFloor }, "OCEAN_FLOOR" => quote! { HeightMap::OceanFloor },
"MOTION_BLOCKING" => quote! { HeightMap::MotionBlocking }, "MOTION_BLOCKING" => quote! { HeightMap::MotionBlocking },
"MOTION_BLOCKING_NO_LEAVES" => quote! { HeightMap::MotionBlockingNoLeaves }, "MOTION_BLOCKING_NO_LEAVES" => quote! { HeightMap::MotionBlockingNoLeaves },
_ => quote! { HeightMap::MotionBlocking }, _ => panic!("Unknown Height map"),
} }
} }
@ -592,7 +599,7 @@ pub fn value_to_block_direction(s: &str) -> TokenStream {
"south" => quote! { BlockDirection::South }, "south" => quote! { BlockDirection::South },
"west" => quote! { BlockDirection::West }, "west" => quote! { BlockDirection::West },
"east" => quote! { BlockDirection::East }, "east" => quote! { BlockDirection::East },
_ => quote! { BlockDirection::Down }, _ => panic!("Unknown Block direction"),
} }
} }

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -789,94 +789,94 @@ mod tests {
// ── weapon category predicates ─────────────────────────────────── // ── weapon category predicates ───────────────────────────────────
// /// 2-durability combat weapons (axes/pickaxes/shovels/hoes) must match their category predicate. /// 2-durability combat weapons (axes/pickaxes/shovels/hoes) must match their category predicate.
// #[test] #[test]
// fn weapon_categories_identify_2_cost_items() { fn weapon_categories_identify_2_cost_items() {
// // Items that should have is_axe / is_pickaxe / is_shovel / is_hoe = true. // Items that should have is_axe / is_pickaxe / is_shovel / is_hoe = true.
// let axes: &[&Item] = &[ let axes: &[&Item] = &[
// &Item::WOODEN_AXE, &Item::WOODEN_AXE,
// &Item::STONE_AXE, &Item::STONE_AXE,
// &Item::IRON_AXE, &Item::IRON_AXE,
// &Item::GOLDEN_AXE, &Item::GOLDEN_AXE,
// &Item::DIAMOND_AXE, &Item::DIAMOND_AXE,
// &Item::NETHERITE_AXE, &Item::NETHERITE_AXE,
// ]; ];
// let pickaxes: &[&Item] = &[ let pickaxes: &[&Item] = &[
// &Item::WOODEN_PICKAXE, &Item::WOODEN_PICKAXE,
// &Item::STONE_PICKAXE, &Item::STONE_PICKAXE,
// &Item::IRON_PICKAXE, &Item::IRON_PICKAXE,
// &Item::GOLDEN_PICKAXE, &Item::GOLDEN_PICKAXE,
// &Item::DIAMOND_PICKAXE, &Item::DIAMOND_PICKAXE,
// &Item::NETHERITE_PICKAXE, &Item::NETHERITE_PICKAXE,
// ]; ];
// let shovels: &[&Item] = &[ let shovels: &[&Item] = &[
// &Item::WOODEN_SHOVEL, &Item::WOODEN_SHOVEL,
// &Item::STONE_SHOVEL, &Item::STONE_SHOVEL,
// &Item::IRON_SHOVEL, &Item::IRON_SHOVEL,
// &Item::GOLDEN_SHOVEL, &Item::GOLDEN_SHOVEL,
// &Item::DIAMOND_SHOVEL, &Item::DIAMOND_SHOVEL,
// &Item::NETHERITE_SHOVEL, &Item::NETHERITE_SHOVEL,
// ]; ];
// let hoes: &[&Item] = &[ let hoes: &[&Item] = &[
// &Item::WOODEN_HOE, &Item::WOODEN_HOE,
// &Item::STONE_HOE, &Item::STONE_HOE,
// &Item::IRON_HOE, &Item::IRON_HOE,
// &Item::GOLDEN_HOE, &Item::GOLDEN_HOE,
// &Item::DIAMOND_HOE, &Item::DIAMOND_HOE,
// &Item::NETHERITE_HOE, &Item::NETHERITE_HOE,
// ]; ];
// for item in axes { for item in axes {
// let stack = ItemStack::new(1, item); let stack = ItemStack::new(1, item);
// assert!(stack.is_axe(), "{} should be an axe", item.registry_key); assert!(stack.is_axe(), "{} should be an axe", item.registry_key);
// assert!( assert!(
// !stack.is_sword(), !stack.is_sword(),
// "{} should not be a sword", "{} should not be a sword",
// item.registry_key item.registry_key
// ); );
// } }
// for item in pickaxes { for item in pickaxes {
// let stack = ItemStack::new(1, item); let stack = ItemStack::new(1, item);
// assert!( assert!(
// stack.is_pickaxe(), stack.is_pickaxe(),
// "{} should be a pickaxe", "{} should be a pickaxe",
// item.registry_key item.registry_key
// ); );
// } }
// for item in shovels { for item in shovels {
// let stack = ItemStack::new(1, item); let stack = ItemStack::new(1, item);
// assert!( assert!(
// stack.is_shovel(), stack.is_shovel(),
// "{} should be a shovel", "{} should be a shovel",
// item.registry_key item.registry_key
// ); );
// } }
// for item in hoes { for item in hoes {
// let stack = ItemStack::new(1, item); let stack = ItemStack::new(1, item);
// assert!(stack.is_hoe(), "{} should be a hoe", item.registry_key); assert!(stack.is_hoe(), "{} should be a hoe", item.registry_key);
// } }
// // Swords should cost 1, so they must NOT match any 2-cost predicate. // Swords should cost 1, so they must NOT match any 2-cost predicate.
// let swords: &[&Item] = &[ let swords: &[&Item] = &[
// &Item::IRON_SWORD, &Item::IRON_SWORD,
// &Item::DIAMOND_SWORD, &Item::DIAMOND_SWORD,
// &Item::NETHERITE_SWORD, &Item::NETHERITE_SWORD,
// ]; ];
// for item in swords { for item in swords {
// let stack = ItemStack::new(1, item); let stack = ItemStack::new(1, item);
// assert!(stack.is_sword(), "{} should be a sword", item.registry_key); assert!(stack.is_sword(), "{} should be a sword", item.registry_key);
// assert!( assert!(
// !stack.is_axe(), !stack.is_axe(),
// "{} should not be an axe", "{} should not be an axe",
// item.registry_key item.registry_key
// ); );
// assert!( assert!(
// !stack.is_pickaxe(), !stack.is_pickaxe(),
// "{} should not be a pickaxe", "{} should not be a pickaxe",
// item.registry_key item.registry_key
// ); );
// } }
// } }
// ── Unbreaking (statistical) ───────────────────────────────────── // ── Unbreaking (statistical) ─────────────────────────────────────

View File

@ -20,6 +20,8 @@ pub enum NormalIntProvider {
/// Wraps another provider and clamps its output to a specified range. /// Wraps another provider and clamps its output to a specified range.
#[serde(rename = "minecraft:clamped")] #[serde(rename = "minecraft:clamped")]
Clamped(ClampedIntProvider), Clamped(ClampedIntProvider),
#[serde(rename = "minecraft:trapezoid")]
Trapezoid(TrapezoidIntProvider),
/// Returns values from a normal (Gaussian) distribution, clamped to a specified inclusive range. /// Returns values from a normal (Gaussian) distribution, clamped to a specified inclusive range.
#[serde(rename = "minecraft:clamped_normal")] #[serde(rename = "minecraft:clamped_normal")]
ClampedNormal(ClampedNormalIntProvider), ClampedNormal(ClampedNormalIntProvider),
@ -56,6 +58,11 @@ impl ToTokens for NormalIntProvider {
NormalIntProvider::ClampedNormal(#clamped_normal) NormalIntProvider::ClampedNormal(#clamped_normal)
}); });
} }
Self::Trapezoid(trapezoid) => {
tokens.extend(quote! {
NormalIntProvider::Trapezoid(#trapezoid)
});
}
Self::WeightedList(weighted_list) => { Self::WeightedList(weighted_list) => {
tokens.extend(quote! { tokens.extend(quote! {
NormalIntProvider::WeightedList(#weighted_list) NormalIntProvider::WeightedList(#weighted_list)
@ -103,6 +110,7 @@ impl IntProvider {
NormalIntProvider::Uniform(uniform) => uniform.get_min(), NormalIntProvider::Uniform(uniform) => uniform.get_min(),
NormalIntProvider::BiasedToBottom(biased) => biased.get_min(), NormalIntProvider::BiasedToBottom(biased) => biased.get_min(),
NormalIntProvider::Clamped(clamped) => clamped.get_min(), NormalIntProvider::Clamped(clamped) => clamped.get_min(),
NormalIntProvider::Trapezoid(trapezoid) => trapezoid.get_min(),
NormalIntProvider::ClampedNormal(clamped_normal) => clamped_normal.get_min(), NormalIntProvider::ClampedNormal(clamped_normal) => clamped_normal.get_min(),
NormalIntProvider::WeightedList(weighted_list) => weighted_list.get_min(), NormalIntProvider::WeightedList(weighted_list) => weighted_list.get_min(),
}, },
@ -126,6 +134,9 @@ impl IntProvider {
NormalIntProvider::Clamped(clamped) => clamped.get(random), NormalIntProvider::Clamped(clamped) => clamped.get(random),
NormalIntProvider::ClampedNormal(clamped_normal) => clamped_normal.get(random), NormalIntProvider::ClampedNormal(clamped_normal) => clamped_normal.get(random),
NormalIntProvider::WeightedList(weighted_list) => weighted_list.get(random), NormalIntProvider::WeightedList(weighted_list) => weighted_list.get(random),
NormalIntProvider::Trapezoid(trapezoid_int_provider) => {
trapezoid_int_provider.get(random)
}
}, },
Self::Constant(i) => *i, Self::Constant(i) => *i,
} }
@ -145,6 +156,9 @@ impl IntProvider {
NormalIntProvider::Clamped(clamped) => clamped.get_max(), NormalIntProvider::Clamped(clamped) => clamped.get_max(),
NormalIntProvider::ClampedNormal(clamped_normal) => clamped_normal.get_max(), NormalIntProvider::ClampedNormal(clamped_normal) => clamped_normal.get_max(),
NormalIntProvider::WeightedList(weighted_list) => weighted_list.get_max(), NormalIntProvider::WeightedList(weighted_list) => weighted_list.get_max(),
NormalIntProvider::Trapezoid(trapezoid_int_provider) => {
trapezoid_int_provider.get_max()
}
}, },
Self::Constant(i) => *i, Self::Constant(i) => *i,
} }
@ -357,6 +371,91 @@ impl ClampedIntProvider {
} }
} }
#[derive(Deserialize, Clone, Debug)]
pub struct TrapezoidIntProvider {
/// The minimum value (inclusive) to clamp to.
pub min_inclusive: i32,
/// The maximum value (inclusive) to clamp to.
pub max_inclusive: i32,
pub plateau: i32,
}
impl ToTokens for TrapezoidIntProvider {
fn to_tokens(&self, tokens: &mut TokenStream) {
let min_inclusive = LitInt::new(&self.min_inclusive.to_string(), Span::call_site());
let max_inclusive = LitInt::new(&self.max_inclusive.to_string(), Span::call_site());
let plateau = LitInt::new(&self.plateau.to_string(), Span::call_site());
tokens.extend(quote! {
TrapezoidIntProvider {
min_inclusive: #min_inclusive,
max_inclusive: #max_inclusive,
plateau: #plateau
}
});
}
}
impl TrapezoidIntProvider {
/// Creates a new clamped provider with the specified source and range.
///
/// # Arguments
/// - `min_inclusive` – The minimum value (inclusive) to clamp to.
/// - `max_inclusive` – The maximum value (inclusive) to clamp to.
///
/// # Returns
/// A new `ClampedIntProvider` instance.
#[must_use]
pub const fn new(min_inclusive: i32, max_inclusive: i32, plateau: i32) -> Self {
Self {
min_inclusive,
max_inclusive,
plateau,
}
}
/// Returns the minimum value after clamping.
///
/// # Returns
/// The larger of the source's minimum and the clamp minimum.
#[must_use]
pub const fn get_min(&self) -> i32 {
self.min_inclusive
}
/// Generates a random value from the source and clamps it to the configured range.
///
/// # Arguments
/// - `random` – The random number generator to use.
///
/// # Returns
/// A random integer from the source provider, clamped to [`min_inclusive`, `max_inclusive`].
pub fn get(&self, random: &mut impl RandomImpl) -> i32 {
if self.plateau == 0 && self.max_inclusive == -self.min_inclusive {
return random.next_bounded_i32(self.max_inclusive + 1)
- random.next_bounded_i32(self.max_inclusive + 1);
}
let range = self.max_inclusive - self.min_inclusive;
if self.plateau == range {
return random.next_bounded_i32(self.max_inclusive - self.min_inclusive + 1)
+ self.min_inclusive;
}
let plateau_start = (range - self.plateau) / 2;
let plateau_end = range - plateau_start;
self.min_inclusive
+ random.next_bounded_i32(plateau_end + 1)
+ random.next_bounded_i32(plateau_start + 1)
}
/// Returns the maximum value after clamping.
///
/// # Returns
/// The smaller of the source's maximum and the clamp maximum.
#[must_use]
pub const fn get_max(&self) -> i32 {
self.max_inclusive
}
}
/// An integer provider that generates values from a clamped normal distribution. /// An integer provider that generates values from a clamped normal distribution.
#[derive(Deserialize, Clone, Debug)] #[derive(Deserialize, Clone, Debug)]
pub struct ClampedNormalIntProvider { pub struct ClampedNormalIntProvider {

View File

@ -1,6 +1,6 @@
use itertools::Itertools; use itertools::Itertools;
use pumpkin_data::fluid::{Fluid, FluidState}; use pumpkin_data::fluid::{Fluid, FluidState};
use pumpkin_data::tag::{RegistryKey, get_tag_ids}; use pumpkin_data::tag::{self};
use pumpkin_data::{Block, BlockDirection, BlockState}; use pumpkin_data::{Block, BlockDirection, BlockState};
use pumpkin_util::math::{position::BlockPos, vector3::Vector3}; use pumpkin_util::math::{position::BlockPos, vector3::Vector3};
@ -102,15 +102,13 @@ impl MatchingFluidsBlockPredicate {
pub struct MatchingBlockTagPredicate { pub struct MatchingBlockTagPredicate {
pub offset: OffsetBlocksBlockPredicate, pub offset: OffsetBlocksBlockPredicate,
pub tag: String, pub tag: tag::Tag,
} }
impl MatchingBlockTagPredicate { impl MatchingBlockTagPredicate {
pub fn test<T: GenerationCache>(&self, chunk: &T, pos: &BlockPos) -> bool { pub fn test<T: GenerationCache>(&self, chunk: &T, pos: &BlockPos) -> bool {
let block = self.offset.get_raw(chunk, pos); let state = self.offset.get_raw(chunk, pos);
get_tag_ids(RegistryKey::Block, &self.tag) self.tag.1.contains(&state.to_block_id())
.unwrap()
.contains(&block.to_block_id())
} }
} }

View File

@ -37,7 +37,7 @@ impl BlockStateProvider {
Self::Pillar(provider) => provider.get(pos), Self::Pillar(provider) => provider.get(pos),
Self::RandomizedInt(provider) => provider.get(random, pos), Self::RandomizedInt(provider) => provider.get(random, pos),
// Without chunk context, fall through to fallback (rules cannot be evaluated) // Without chunk context, fall through to fallback (rules cannot be evaluated)
Self::Rule(provider) => provider.fallback.get(random, pos), Self::Rule(_provider) => todo!(), //provider.get(random, pos),
} }
} }
@ -53,10 +53,23 @@ impl BlockStateProvider {
_ => self.get(random, pos), _ => self.get(random, pos),
} }
} }
pub fn get_optional<T: GenerationCache>(
&self,
block_registry: &dyn BlockRegistryExt,
chunk: &T,
random: &mut RandomGenerator,
pos: BlockPos,
) -> Option<&'static BlockState> {
match self {
Self::Rule(provider) => provider.get_optional(block_registry, chunk, random, pos),
_ => Some(self.get(random, pos)),
}
}
} }
pub struct RuleBasedBlockStateProvider { pub struct RuleBasedBlockStateProvider {
pub fallback: Box<BlockStateProvider>, pub fallback: Option<Box<BlockStateProvider>>,
pub rules: Vec<BlockStateRule>, pub rules: Vec<BlockStateRule>,
} }
@ -68,14 +81,27 @@ impl RuleBasedBlockStateProvider {
random: &mut RandomGenerator, random: &mut RandomGenerator,
pos: BlockPos, pos: BlockPos,
) -> &'static BlockState { ) -> &'static BlockState {
if let Some(optional) = self.get_optional(block_registry, chunk, random, pos) {
return optional;
}
GenerationCache::get_block_state(chunk, &pos.0).to_state()
}
pub fn get_optional<T: GenerationCache>(
&self,
block_registry: &dyn BlockRegistryExt,
chunk: &T,
random: &mut RandomGenerator,
pos: BlockPos,
) -> Option<&'static BlockState> {
for rule in &self.rules { for rule in &self.rules {
if rule.if_true.test(block_registry, chunk, &pos) { if rule.if_true.test(block_registry, chunk, &pos) {
return rule return Some(
.then rule.then
.get_with_context(block_registry, chunk, random, pos); .get_with_context(block_registry, chunk, random, pos),
);
} }
} }
self.fallback.get(random, pos) self.fallback.as_ref().map(|f| f.get(random, pos))
} }
} }

View File

@ -278,9 +278,15 @@ impl ConfiguredFeature {
random, random,
pos, pos,
), ),
Self::Tree(feature) => { Self::Tree(feature) => feature.generate(
feature.generate(chunk, min_y, height, feature_name, random, pos) block_registry,
} chunk,
min_y,
height,
feature_name,
random,
pos,
),
Self::RandomSelector(feature) => feature.generate( Self::RandomSelector(feature) => feature.generate(
chunk, chunk,
block_registry, block_registry,

View File

@ -8,21 +8,21 @@ use trunk::TrunkPlacer;
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
use crate::generation::{block_state_provider::BlockStateProvider, feature::size::FeatureSize}; use crate::generation::{block_state_provider::BlockStateProvider, feature::size::FeatureSize};
use crate::world::BlockRegistryExt;
pub mod decorator; pub mod decorator;
pub mod foliage; pub mod foliage;
pub mod trunk; pub mod trunk;
pub struct TreeFeature { pub struct TreeFeature {
pub dirt_provider: BlockStateProvider,
pub trunk_provider: BlockStateProvider, pub trunk_provider: BlockStateProvider,
pub trunk_placer: TrunkPlacer, pub trunk_placer: TrunkPlacer,
pub foliage_provider: BlockStateProvider, pub foliage_provider: BlockStateProvider,
pub foliage_placer: FoliagePlacer, pub foliage_placer: FoliagePlacer,
pub minimum_size: FeatureSize, pub minimum_size: FeatureSize,
pub ignore_vines: bool, pub ignore_vines: bool,
pub force_dirt: bool,
pub decorators: Vec<TreeDecorator>, pub decorators: Vec<TreeDecorator>,
pub below_trunk_provider: BlockStateProvider,
} }
pub struct TreeNode { pub struct TreeNode {
@ -32,8 +32,10 @@ pub struct TreeNode {
} }
impl TreeFeature { impl TreeFeature {
#[expect(clippy::too_many_arguments)]
pub fn generate<T: GenerationCache>( pub fn generate<T: GenerationCache>(
&self, &self,
block_registry: &dyn BlockRegistryExt,
chunk: &mut T, chunk: &mut T,
min_y: i8, min_y: i8,
height: u16, height: u16,
@ -42,7 +44,15 @@ impl TreeFeature {
pos: BlockPos, pos: BlockPos,
) -> bool { ) -> bool {
// TODO // TODO
let log_positions = self.generate_main(chunk, min_y, height, feature_name, random, pos); let log_positions = self.generate_main(
block_registry,
chunk,
min_y,
height,
feature_name,
random,
pos,
);
for decorator in &self.decorators { for decorator in &self.decorators {
decorator.generate(chunk, random, &[], &log_positions); decorator.generate(chunk, random, &[], &log_positions);
@ -65,8 +75,10 @@ impl TreeFeature {
.contains(&block) .contains(&block)
} }
#[expect(clippy::too_many_arguments)]
fn generate_main<T: GenerationCache>( fn generate_main<T: GenerationCache>(
&self, &self,
block_registry: &dyn BlockRegistryExt,
chunk: &mut T, chunk: &mut T,
_min_y: i8, _min_y: i8,
_height: u16, _height: u16,
@ -82,15 +94,14 @@ impl TreeFeature {
return vec![]; return vec![];
} }
let trunk_state = self.trunk_provider.get(random, pos); let trunk_state = self.trunk_provider.get(random, pos);
let dirt_state = self.dirt_provider.get(random, pos);
let (nodes, logs) = self.trunk_placer.generate( let (nodes, logs) = self.trunk_placer.generate(
block_registry,
top, top,
pos, pos,
chunk, chunk,
random, random,
self.force_dirt, &self.below_trunk_provider,
dirt_state,
trunk_state, trunk_state,
); );

View File

@ -4,8 +4,14 @@ use pumpkin_util::{
random::{RandomGenerator, RandomImpl}, random::{RandomGenerator, RandomImpl},
}; };
use crate::generation::feature::features::tree::{TreeNode, trunk::TrunkPlacer};
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
use crate::{
generation::{
block_state_provider::BlockStateProvider,
feature::features::tree::{TreeNode, trunk::TrunkPlacer},
},
world::BlockRegistryExt,
};
pub struct BendingTrunkPlacer { pub struct BendingTrunkPlacer {
pub min_height_for_leaves: u32, pub min_height_for_leaves: u32,
@ -16,16 +22,22 @@ impl BendingTrunkPlacer {
#[expect(clippy::too_many_arguments)] #[expect(clippy::too_many_arguments)]
pub fn generate<T: GenerationCache>( pub fn generate<T: GenerationCache>(
&self, &self,
block_registry: &dyn BlockRegistryExt,
placer: &TrunkPlacer, placer: &TrunkPlacer,
height: u32, height: u32,
start_pos: BlockPos, start_pos: BlockPos,
chunk: &mut T, chunk: &mut T,
random: &mut RandomGenerator, random: &mut RandomGenerator,
force_dirt: bool, below_trunk_provider: &BlockStateProvider,
dirt_state: &BlockState,
trunk_block: &BlockState, trunk_block: &BlockState,
) -> (Vec<TreeNode>, Vec<BlockPos>) { ) -> (Vec<TreeNode>, Vec<BlockPos>) {
placer.set_dirt(chunk, &start_pos.down(), force_dirt, dirt_state); placer.set_dirt(
block_registry,
chunk,
random,
&start_pos.down(),
below_trunk_provider,
);
// TODO: make this random // TODO: make this random
let random_direction = BlockDirection::North; let random_direction = BlockDirection::North;

View File

@ -4,43 +4,52 @@ use pumpkin_util::{
random::{RandomGenerator, RandomImpl}, random::{RandomGenerator, RandomImpl},
}; };
use crate::generation::feature::features::tree::{TreeFeature, TreeNode, trunk::TrunkPlacer};
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
use crate::{
generation::{
block_state_provider::BlockStateProvider,
feature::features::tree::{TreeFeature, TreeNode, trunk::TrunkPlacer},
},
world::BlockRegistryExt,
};
pub struct DarkOakTrunkPlacer; pub struct DarkOakTrunkPlacer;
impl DarkOakTrunkPlacer { impl DarkOakTrunkPlacer {
#[expect(clippy::too_many_arguments)] #[expect(clippy::too_many_arguments)]
pub fn generate<T: GenerationCache>( pub fn generate<T: GenerationCache>(
block_registry: &dyn BlockRegistryExt,
placer: &TrunkPlacer, placer: &TrunkPlacer,
height: u32, height: u32,
start_pos: BlockPos, start_pos: BlockPos,
chunk: &mut T, chunk: &mut T,
random: &mut RandomGenerator, random: &mut RandomGenerator,
force_dirt: bool, below_trunk_provider: &BlockStateProvider,
dirt_state: &BlockState,
trunk_block: &BlockState, trunk_block: &BlockState,
) -> (Vec<TreeNode>, Vec<BlockPos>) { ) -> (Vec<TreeNode>, Vec<BlockPos>) {
let pos = start_pos.down(); let pos = start_pos.down();
placer.set_dirt(chunk, &pos, force_dirt, dirt_state); placer.set_dirt(block_registry, chunk, random, &pos, below_trunk_provider);
placer.set_dirt( placer.set_dirt(
block_registry,
chunk, chunk,
random,
&pos.offset(BlockDirection::East.to_offset()), &pos.offset(BlockDirection::East.to_offset()),
force_dirt, below_trunk_provider,
dirt_state,
); );
placer.set_dirt( placer.set_dirt(
block_registry,
chunk, chunk,
random,
&pos.offset(BlockDirection::South.to_offset()), &pos.offset(BlockDirection::South.to_offset()),
force_dirt, below_trunk_provider,
dirt_state,
); );
placer.set_dirt( placer.set_dirt(
block_registry,
chunk, chunk,
random,
&pos.offset(BlockDirection::South.to_offset()) &pos.offset(BlockDirection::South.to_offset())
.offset(BlockDirection::East.to_offset()), .offset(BlockDirection::East.to_offset()),
force_dirt, below_trunk_provider,
dirt_state,
); );
let start_y = start_pos.0.y; let start_y = start_pos.0.y;
let y_height = start_y + height as i32 - 1; let y_height = start_y + height as i32 - 1;

View File

@ -10,27 +10,39 @@ use pumpkin_util::{
}; };
use super::TrunkPlacer; use super::TrunkPlacer;
use crate::generation::feature::features::tree::{TreeFeature, TreeNode};
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
use crate::{
generation::{
block_state_provider::BlockStateProvider,
feature::features::tree::{TreeFeature, TreeNode},
},
world::BlockRegistryExt,
};
pub struct FancyTrunkPlacer; pub struct FancyTrunkPlacer;
impl FancyTrunkPlacer { impl FancyTrunkPlacer {
#[expect(clippy::too_many_arguments)] #[expect(clippy::too_many_arguments)]
pub fn generate<T: GenerationCache>( pub fn generate<T: GenerationCache>(
block_registry: &dyn BlockRegistryExt,
placer: &TrunkPlacer, placer: &TrunkPlacer,
height: u32, height: u32,
start_pos: BlockPos, start_pos: BlockPos,
chunk: &mut T, chunk: &mut T,
random: &mut RandomGenerator, random: &mut RandomGenerator,
force_dirt: bool, below_trunk_provider: &BlockStateProvider,
dirt_state: &BlockState,
trunk_block: &BlockState, trunk_block: &BlockState,
) -> (Vec<TreeNode>, Vec<BlockPos>) { ) -> (Vec<TreeNode>, Vec<BlockPos>) {
let j = height as i32 + 2; let j = height as i32 + 2;
let k = ((j as f64) * 0.618).floor() as i32; let k = ((j as f64) * 0.618).floor() as i32;
placer.set_dirt(chunk, &start_pos.down(), force_dirt, dirt_state); placer.set_dirt(
block_registry,
chunk,
random,
&start_pos.down(),
below_trunk_provider,
);
let l = ((1.382 + (1.0 * (j as f64) / 13.0).powf(2.0)).floor() as i32).min(1); let l = ((1.382 + (1.0 * (j as f64) / 13.0).powf(2.0)).floor() as i32).min(1);
let m = start_pos.0.y + k; let m = start_pos.0.y + k;

View File

@ -4,43 +4,52 @@ use pumpkin_util::{
random::RandomGenerator, random::RandomGenerator,
}; };
use crate::generation::feature::features::tree::{TreeNode, trunk::TrunkPlacer};
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
use crate::{
generation::{
block_state_provider::BlockStateProvider,
feature::features::tree::{TreeNode, trunk::TrunkPlacer},
},
world::BlockRegistryExt,
};
pub struct GiantTrunkPlacer; pub struct GiantTrunkPlacer;
impl GiantTrunkPlacer { impl GiantTrunkPlacer {
#[expect(clippy::too_many_arguments)] #[expect(clippy::too_many_arguments)]
pub fn generate<T: GenerationCache>( pub fn generate<T: GenerationCache>(
block_registry: &dyn BlockRegistryExt,
placer: &TrunkPlacer, placer: &TrunkPlacer,
height: u32, height: u32,
start_pos: BlockPos, start_pos: BlockPos,
chunk: &mut T, chunk: &mut T,
_random: &mut RandomGenerator, random: &mut RandomGenerator,
force_dirt: bool, below_trunk_provider: &BlockStateProvider,
dirt_state: &BlockState,
trunk_block: &BlockState, trunk_block: &BlockState,
) -> (Vec<TreeNode>, Vec<BlockPos>) { ) -> (Vec<TreeNode>, Vec<BlockPos>) {
let pos = start_pos.down(); let pos = start_pos.down();
placer.set_dirt(chunk, &pos, force_dirt, dirt_state); placer.set_dirt(block_registry, chunk, random, &pos, below_trunk_provider);
placer.set_dirt( placer.set_dirt(
block_registry,
chunk, chunk,
random,
&pos.offset(BlockDirection::East.to_offset()), &pos.offset(BlockDirection::East.to_offset()),
force_dirt, below_trunk_provider,
dirt_state,
); );
placer.set_dirt( placer.set_dirt(
block_registry,
chunk, chunk,
random,
&pos.offset(BlockDirection::South.to_offset()), &pos.offset(BlockDirection::South.to_offset()),
force_dirt, below_trunk_provider,
dirt_state,
); );
placer.set_dirt( placer.set_dirt(
block_registry,
chunk, chunk,
random,
&pos.offset(BlockDirection::South.to_offset()) &pos.offset(BlockDirection::South.to_offset())
.offset(BlockDirection::South.to_offset()), .offset(BlockDirection::South.to_offset()),
force_dirt, below_trunk_provider,
dirt_state,
); );
let mut trunk_poses = Vec::new(); let mut trunk_poses = Vec::new();

View File

@ -6,34 +6,40 @@ use pumpkin_util::{
random::{RandomGenerator, RandomImpl}, random::{RandomGenerator, RandomImpl},
}; };
use crate::generation::feature::features::tree::{
TreeNode,
trunk::{TrunkPlacer, giant::GiantTrunkPlacer},
};
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
use crate::{
generation::{
block_state_provider::BlockStateProvider,
feature::features::tree::{
TreeNode,
trunk::{TrunkPlacer, giant::GiantTrunkPlacer},
},
},
world::BlockRegistryExt,
};
pub struct MegaJungleTrunkPlacer; pub struct MegaJungleTrunkPlacer;
impl MegaJungleTrunkPlacer { impl MegaJungleTrunkPlacer {
#[expect(clippy::too_many_arguments)] #[expect(clippy::too_many_arguments)]
pub fn generate<T: GenerationCache>( pub fn generate<T: GenerationCache>(
block_registry: &dyn BlockRegistryExt,
placer: &TrunkPlacer, placer: &TrunkPlacer,
height: u32, height: u32,
start_pos: BlockPos, start_pos: BlockPos,
chunk: &mut T, chunk: &mut T,
random: &mut RandomGenerator, random: &mut RandomGenerator,
force_dirt: bool, below_trunk_provider: &BlockStateProvider,
dirt_state: &BlockState,
trunk_block: &BlockState, trunk_block: &BlockState,
) -> (Vec<TreeNode>, Vec<BlockPos>) { ) -> (Vec<TreeNode>, Vec<BlockPos>) {
let (mut nodes, mut trunk_poses) = GiantTrunkPlacer::generate( let (mut nodes, mut trunk_poses) = GiantTrunkPlacer::generate(
block_registry,
placer, placer,
height, height,
start_pos, start_pos,
chunk, chunk,
random, random,
force_dirt, below_trunk_provider,
dirt_state,
trunk_block, trunk_block,
); );
let mut i = height as i32 - 2 - random.next_bounded_i32(4); let mut i = height as i32 - 2 - random.next_bounded_i32(4);

View File

@ -1,6 +1,5 @@
use fancy::FancyTrunkPlacer; use fancy::FancyTrunkPlacer;
use pumpkin_data::tag; use pumpkin_data::BlockState;
use pumpkin_data::{Block, BlockState};
use pumpkin_util::{ use pumpkin_util::{
math::position::BlockPos, math::position::BlockPos,
random::{RandomGenerator, RandomImpl}, random::{RandomGenerator, RandomImpl},
@ -9,12 +8,14 @@ use pumpkin_util::{
use straight::StraightTrunkPlacer; use straight::StraightTrunkPlacer;
use super::{TreeFeature, TreeNode}; use super::{TreeFeature, TreeNode};
use crate::generation::block_state_provider::BlockStateProvider;
use crate::generation::feature::features::tree::trunk::{ use crate::generation::feature::features::tree::trunk::{
bending::BendingTrunkPlacer, cherry::CherryTrunkPlacer, dark_oak::DarkOakTrunkPlacer, bending::BendingTrunkPlacer, cherry::CherryTrunkPlacer, dark_oak::DarkOakTrunkPlacer,
forking::ForkingTrunkPlacer, giant::GiantTrunkPlacer, mega_jungle::MegaJungleTrunkPlacer, forking::ForkingTrunkPlacer, giant::GiantTrunkPlacer, mega_jungle::MegaJungleTrunkPlacer,
upwards_branching::UpwardsBranchingTrunkPlacer, upwards_branching::UpwardsBranchingTrunkPlacer,
}; };
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
use crate::world::BlockRegistryExt;
pub mod bending; pub mod bending;
pub mod cherry; pub mod cherry;
@ -42,18 +43,15 @@ impl TrunkPlacer {
pub fn set_dirt<T: GenerationCache>( pub fn set_dirt<T: GenerationCache>(
&self, &self,
block_registry: &dyn BlockRegistryExt,
chunk: &mut T, chunk: &mut T,
random: &mut RandomGenerator,
pos: &BlockPos, pos: &BlockPos,
force_dirt: bool, below_trunk_provider: &BlockStateProvider,
dirt_state: &BlockState,
) { ) {
let block = GenerationCache::get_block_state(chunk, &pos.0).to_block_id(); if let Some(state) = below_trunk_provider.get_optional(block_registry, chunk, random, *pos)
if force_dirt
|| !(tag::Block::MINECRAFT_DIRT.1.contains(&block)
&& block != Block::GRASS_BLOCK
&& block != Block::MYCELIUM)
{ {
chunk.set_block_state(&pos.0, dirt_state); chunk.set_block_state(&pos.0, state);
} }
} }
@ -87,22 +85,22 @@ impl TrunkPlacer {
#[expect(clippy::too_many_arguments)] #[expect(clippy::too_many_arguments)]
pub fn generate<T: GenerationCache>( pub fn generate<T: GenerationCache>(
&self, &self,
block_registry: &dyn BlockRegistryExt,
height: u32, height: u32,
start_pos: BlockPos, start_pos: BlockPos,
chunk: &mut T, chunk: &mut T,
random: &mut RandomGenerator, random: &mut RandomGenerator,
force_dirt: bool, below_trunk_provider: &BlockStateProvider,
dirt_state: &BlockState,
trunk_state: &BlockState, trunk_state: &BlockState,
) -> (Vec<TreeNode>, Vec<BlockPos>) { ) -> (Vec<TreeNode>, Vec<BlockPos>) {
self.r#type.generate( self.r#type.generate(
block_registry,
self, self,
height, height,
start_pos, start_pos,
chunk, chunk,
random, random,
force_dirt, below_trunk_provider,
dirt_state,
trunk_state, trunk_state,
) )
} }
@ -124,74 +122,75 @@ impl TrunkType {
#[expect(clippy::too_many_arguments)] #[expect(clippy::too_many_arguments)]
pub fn generate<T: GenerationCache>( pub fn generate<T: GenerationCache>(
&self, &self,
block_registry: &dyn BlockRegistryExt,
placer: &TrunkPlacer, placer: &TrunkPlacer,
height: u32, height: u32,
start_pos: BlockPos, start_pos: BlockPos,
chunk: &mut T, chunk: &mut T,
random: &mut RandomGenerator, random: &mut RandomGenerator,
force_dirt: bool, below_trunk_provider: &BlockStateProvider,
dirt_state: &BlockState,
trunk_state: &BlockState, trunk_state: &BlockState,
) -> (Vec<TreeNode>, Vec<BlockPos>) { ) -> (Vec<TreeNode>, Vec<BlockPos>) {
match self { match self {
Self::Straight(_) => StraightTrunkPlacer::generate( Self::Straight(_) => StraightTrunkPlacer::generate(
block_registry,
placer, placer,
height, height,
start_pos, start_pos,
chunk, chunk,
force_dirt, random,
dirt_state, below_trunk_provider,
trunk_state, trunk_state,
), ),
Self::Forking(_) => (vec![], vec![]), // TODO Self::Forking(_) => (vec![], vec![]), // TODO
Self::Giant(_) => GiantTrunkPlacer::generate( Self::Giant(_) => GiantTrunkPlacer::generate(
block_registry,
placer, placer,
height, height,
start_pos, start_pos,
chunk, chunk,
random, random,
force_dirt, below_trunk_provider,
dirt_state,
trunk_state, trunk_state,
), ),
Self::MegaJungle(_) => MegaJungleTrunkPlacer::generate( Self::MegaJungle(_) => MegaJungleTrunkPlacer::generate(
block_registry,
placer, placer,
height, height,
start_pos, start_pos,
chunk, chunk,
random, random,
force_dirt, below_trunk_provider,
dirt_state,
trunk_state, trunk_state,
), ),
Self::DarkOak(_) => DarkOakTrunkPlacer::generate( Self::DarkOak(_) => DarkOakTrunkPlacer::generate(
block_registry,
placer, placer,
height, height,
start_pos, start_pos,
chunk, chunk,
random, random,
force_dirt, below_trunk_provider,
dirt_state,
trunk_state, trunk_state,
), ),
Self::Fancy(_) => FancyTrunkPlacer::generate( Self::Fancy(_) => FancyTrunkPlacer::generate(
block_registry,
placer, placer,
height, height,
start_pos, start_pos,
chunk, chunk,
random, random,
force_dirt, below_trunk_provider,
dirt_state,
trunk_state, trunk_state,
), ),
Self::Bending(bending) => bending.generate( Self::Bending(bending) => bending.generate(
block_registry,
placer, placer,
height, height,
start_pos, start_pos,
chunk, chunk,
random, random,
force_dirt, below_trunk_provider,
dirt_state,
trunk_state, trunk_state,
), ),
Self::UpwardsBranching(_) => (vec![], vec![]), // TODO Self::UpwardsBranching(_) => (vec![], vec![]), // TODO

View File

@ -1,23 +1,34 @@
use pumpkin_data::BlockState; use pumpkin_data::BlockState;
use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::position::BlockPos;
use pumpkin_util::random::RandomGenerator;
use super::TrunkPlacer; use super::TrunkPlacer;
use crate::generation::block_state_provider::BlockStateProvider;
use crate::generation::feature::features::tree::TreeNode; use crate::generation::feature::features::tree::TreeNode;
use crate::generation::proto_chunk::GenerationCache; use crate::generation::proto_chunk::GenerationCache;
use crate::world::BlockRegistryExt;
pub struct StraightTrunkPlacer; pub struct StraightTrunkPlacer;
impl StraightTrunkPlacer { impl StraightTrunkPlacer {
#[expect(clippy::too_many_arguments)]
pub fn generate<T: GenerationCache>( pub fn generate<T: GenerationCache>(
block_registry: &dyn BlockRegistryExt,
placer: &TrunkPlacer, placer: &TrunkPlacer,
height: u32, height: u32,
start_pos: BlockPos, start_pos: BlockPos,
chunk: &mut T, chunk: &mut T,
force_dirt: bool, random: &mut RandomGenerator,
dirt_state: &BlockState, below_trunk_provider: &BlockStateProvider,
trunk_state: &BlockState, trunk_state: &BlockState,
) -> (Vec<TreeNode>, Vec<BlockPos>) { ) -> (Vec<TreeNode>, Vec<BlockPos>) {
placer.set_dirt(chunk, &start_pos.down(), force_dirt, dirt_state); placer.set_dirt(
block_registry,
chunk,
random,
&start_pos.down(),
below_trunk_provider,
);
let mut logs = Vec::new(); let mut logs = Vec::new();
for i in 0..height { for i in 0..height {
let pos = start_pos.up_height(i as i32); let pos = start_pos.up_height(i as i32);

View File

@ -1,90 +1,90 @@
// use pumpkin_data::noise_router::FindTopSurfaceData; use pumpkin_data::noise_router::FindTopSurfaceData;
// use pumpkin_util::math::vector3::Vector3; use pumpkin_util::math::vector3::Vector3;
// use crate::generation::noise::router::{ use crate::generation::noise::router::{
// chunk_density_function::ChunkNoiseFunctionSampleOptions, chunk_density_function::ChunkNoiseFunctionSampleOptions,
// chunk_noise_router::{ChunkNoiseFunctionComponent, StaticChunkNoiseFunctionComponentImpl}, chunk_noise_router::{ChunkNoiseFunctionComponent, StaticChunkNoiseFunctionComponentImpl},
// density_function::NoiseFunctionComponentRange, density_function::NoiseFunctionComponentRange,
// }; };
// #[derive(Clone)] #[derive(Clone)]
// pub struct FindTopSurface { pub struct FindTopSurface {
// density_index: usize, density_index: usize,
// upper_bound_index: usize, upper_bound_index: usize,
// min_value: f64, min_value: f64,
// max_value: f64, max_value: f64,
// data: &'static FindTopSurfaceData, data: &'static FindTopSurfaceData,
// } }
// impl FindTopSurface { impl FindTopSurface {
// pub const fn new( pub const fn new(
// density_index: usize, density_index: usize,
// upper_bound_index: usize, upper_bound_index: usize,
// min_value: f64, min_value: f64,
// max_value: f64, max_value: f64,
// data: &'static FindTopSurfaceData, data: &'static FindTopSurfaceData,
// ) -> Self { ) -> Self {
// Self { Self {
// density_index, density_index,
// upper_bound_index, upper_bound_index,
// min_value, min_value,
// max_value, max_value,
// data, data,
// } }
// } }
// } }
// impl NoiseFunctionComponentRange for FindTopSurface { impl NoiseFunctionComponentRange for FindTopSurface {
// #[inline] #[inline]
// fn min(&self) -> f64 { fn min(&self) -> f64 {
// self.min_value self.min_value
// } }
// #[inline] #[inline]
// fn max(&self) -> f64 { fn max(&self) -> f64 {
// self.max_value self.max_value
// } }
// } }
// impl StaticChunkNoiseFunctionComponentImpl for FindTopSurface { impl StaticChunkNoiseFunctionComponentImpl for FindTopSurface {
// fn sample( fn sample(
// &self, &self,
// component_stack: &mut [ChunkNoiseFunctionComponent], component_stack: &mut [ChunkNoiseFunctionComponent],
// pos: &Vector3<i32>, pos: &Vector3<i32>,
// sample_options: &ChunkNoiseFunctionSampleOptions, sample_options: &ChunkNoiseFunctionSampleOptions,
// ) -> f64 { ) -> f64 {
// let upper = ChunkNoiseFunctionComponent::sample_from_stack( let upper = ChunkNoiseFunctionComponent::sample_from_stack(
// &mut component_stack[..=self.upper_bound_index], &mut component_stack[..=self.upper_bound_index],
// pos, pos,
// sample_options, sample_options,
// ); );
// let cell_height = self.data.cell_height; let cell_height = self.data.cell_height;
// let lower_bound = self.data.lower_bound; let lower_bound = self.data.lower_bound;
// // Snap upper bound down to nearest cell boundary, matching Java: // Snap upper bound down to nearest cell boundary, matching Java:
// // int topY = Mth.floor(this.upperBound.compute(context) / this.cellHeight) * this.cellHeight // int topY = Mth.floor(this.upperBound.compute(context) / this.cellHeight) * this.cellHeight
// let top_y = (upper / cell_height as f64).floor() as i32 * cell_height; let top_y = (upper / cell_height as f64).floor() as i32 * cell_height;
// if top_y <= lower_bound { if top_y <= lower_bound {
// return lower_bound as f64; return lower_bound as f64;
// } }
// // Walk downward in cellHeight steps, return the first Y where density > 0.0 // Walk downward in cellHeight steps, return the first Y where density > 0.0
// let mut y = top_y; let mut y = top_y;
// while y >= lower_bound { while y >= lower_bound {
// let sample_pos = Vector3::new(pos.x, y, pos.z); let sample_pos = Vector3::new(pos.x, y, pos.z);
// let density = ChunkNoiseFunctionComponent::sample_from_stack( let density = ChunkNoiseFunctionComponent::sample_from_stack(
// &mut component_stack[..=self.density_index], &mut component_stack[..=self.density_index],
// &sample_pos, &sample_pos,
// sample_options, sample_options,
// ); );
// if density > 0.0 { if density > 0.0 {
// return y as f64; return y as f64;
// } }
// y -= cell_height; y -= cell_height;
// } }
// lower_bound as f64 lower_bound as f64
// } }
// } }

View File

@ -8,7 +8,12 @@ use pumpkin_data::{
}; };
use pumpkin_util::random::xoroshiro128::XoroshiroSplitter; use pumpkin_util::random::xoroshiro128::XoroshiroSplitter;
use crate::{GlobalRandomConfig, generation::noise::perlin::DoublePerlinNoiseSampler}; use crate::{
GlobalRandomConfig,
generation::noise::{
perlin::DoublePerlinNoiseSampler, router::find_top_surface::FindTopSurface,
},
};
use super::{ use super::{
chunk_density_function::ChunkNoiseFunctionSampleOptions, chunk_density_function::ChunkNoiseFunctionSampleOptions,
@ -45,6 +50,7 @@ pub enum DependentProtoNoiseFunctionComponent {
Binary(Binary), Binary(Binary),
ShiftedNoise(ShiftedNoise), ShiftedNoise(ShiftedNoise),
WeirdScaled(WeirdScaled), WeirdScaled(WeirdScaled),
FindTopSurface(FindTopSurface),
Clamp(Clamp), Clamp(Clamp),
RangeChoice(RangeChoice), RangeChoice(RangeChoice),
Spline(SplineFunction), Spline(SplineFunction),
@ -151,6 +157,24 @@ impl ProtoNoiseRouters {
)), )),
) )
} }
BaseNoiseFunctionComponent::FindTopSurface {
density_index,
upper_bound_index,
data,
} => {
let min_value = data.lower_bound as f64;
let max_value = stack[*upper_bound_index].max().max(min_value);
ProtoNoiseFunctionComponent::Dependent(
DependentProtoNoiseFunctionComponent::FindTopSurface(FindTopSurface::new(
*density_index,
*upper_bound_index,
min_value,
max_value,
data,
)),
)
}
BaseNoiseFunctionComponent::EndIslands => ProtoNoiseFunctionComponent::Independent( BaseNoiseFunctionComponent::EndIslands => ProtoNoiseFunctionComponent::Independent(
IndependentProtoNoiseFunctionComponent::EndIsland(EndIsland::new( IndependentProtoNoiseFunctionComponent::EndIsland(EndIsland::new(
random_config.seed, random_config.seed,
@ -387,6 +411,13 @@ impl ProtoNoiseRouters {
| UnaryOperation::Cube | UnaryOperation::Cube
| UnaryOperation::QuarterNegative | UnaryOperation::QuarterNegative
| UnaryOperation::HalfNegative => (applied_min_value, applied_max_value), | UnaryOperation::HalfNegative => (applied_min_value, applied_max_value),
UnaryOperation::Invert => {
if arg1_min < 0.0 && arg1_max > 0.0 {
(f64::NEG_INFINITY, f64::INFINITY)
} else {
(applied_max_value, applied_min_value)
}
}
}; };
ProtoNoiseFunctionComponent::Dependent( ProtoNoiseFunctionComponent::Dependent(

View File

@ -79,33 +79,25 @@ impl<'a> SurfaceHeightEstimateSampler<'a> {
} }
fn calculate_height_estimate(&mut self, aligned_x: i32, aligned_z: i32) -> i32 { fn calculate_height_estimate(&mut self, aligned_x: i32, aligned_z: i32) -> i32 {
let mut low = self.minimum_y;
let mut high = self.maximum_y;
let mut result = i32::MAX;
let sample_options = let sample_options =
ChunkNoiseFunctionSampleOptions::new(false, SampleAction::SkipCellCaches, 0, 0, 0); ChunkNoiseFunctionSampleOptions::new(false, SampleAction::SkipCellCaches, 0, 0, 0);
while low <= high { // preliminarySurfaceLevel (FindTopSurface) returns the surface Y directly.
let mid = low + ((high - low) / 2); // Sample at y=0 — FindTopSurface ignores the incoming Y and computes its own.
let stepped_mid = mid - (mid % self.y_level_step_count as i32); let pos = Vector3::new(aligned_x, 0, aligned_z);
let surface_y = ChunkNoiseFunctionComponent::sample_from_stack(
&mut self.component_stack,
&pos,
&sample_options,
);
let pos = Vector3::new(aligned_x, stepped_mid, aligned_z); // FindTopSurface returns lowerBound as f64 when no solid block found,
let density = ChunkNoiseFunctionComponent::sample_from_stack( // which is below minimum_y — treat that as "no surface"
&mut self.component_stack, if surface_y <= self.minimum_y as f64 {
&pos, i32::MAX
&sample_options, } else {
); surface_y as i32
if density > Self::NOTCHIAN_SAMPLE_CUTOFF {
result = stepped_mid;
low = stepped_mid + self.y_level_step_count as i32;
} else {
high = stepped_mid - self.y_level_step_count as i32;
}
} }
result
} }
#[must_use] #[must_use]

View File

@ -1,15 +1,14 @@
use pumpkin_data::tag::{RegistryKey, get_tag_ids}; use pumpkin_data::tag::{self};
use crate::block::RawBlockState; use crate::block::RawBlockState;
pub struct TagMatchRuleTest { pub struct TagMatchRuleTest {
pub tag: String, pub tag: tag::Tag,
} }
impl TagMatchRuleTest { impl TagMatchRuleTest {
#[must_use] #[must_use]
pub fn test(&self, state: RawBlockState) -> bool { pub fn test(&self, state: RawBlockState) -> bool {
let values = get_tag_ids(RegistryKey::Block, &self.tag).unwrap(); self.tag.1.contains(&state.to_block_id())
values.contains(&state.to_block_id())
} }
} }

View File

@ -4,7 +4,7 @@ use pumpkin_data::block_properties::{
BambooLeaves, BambooLikeProperties, BlockProperties, EnumVariants, Integer0To1, BambooLeaves, BambooLikeProperties, BlockProperties, EnumVariants, Integer0To1,
}; };
use pumpkin_data::item::Item; use pumpkin_data::item::Item;
use pumpkin_data::tag::Block::MINECRAFT_BAMBOO_PLANTABLE_ON; use pumpkin_data::tag::Block::MINECRAFT_SUPPORTS_BAMBOO;
use pumpkin_data::tag::Taggable; use pumpkin_data::tag::Taggable;
use pumpkin_data::tag::{self}; use pumpkin_data::tag::{self};
use pumpkin_data::{Block, BlockDirection}; use pumpkin_data::{Block, BlockDirection};
@ -34,7 +34,7 @@ impl BlockBehaviour for BambooBlock {
.get_block_and_state_id(&args.position.down()) .get_block_and_state_id(&args.position.down())
.await; .await;
if block_below.has_tag(&MINECRAFT_BAMBOO_PLANTABLE_ON) { if block_below.has_tag(&MINECRAFT_SUPPORTS_BAMBOO) {
let mut props = BambooLikeProperties::from_state_id( let mut props = BambooLikeProperties::from_state_id(
Block::BAMBOO.default_state.id, Block::BAMBOO.default_state.id,
&Block::BAMBOO, &Block::BAMBOO,
@ -277,7 +277,7 @@ impl PlantBlockBase for BambooBlock {
pos: &pumpkin_util::math::position::BlockPos, pos: &pumpkin_util::math::position::BlockPos,
) -> bool { ) -> bool {
let block = block_accessor.get_block(pos).await; let block = block_accessor.get_block(pos).await;
block.has_tag(&tag::Block::MINECRAFT_BAMBOO_PLANTABLE_ON) block.has_tag(&tag::Block::MINECRAFT_SUPPORTS_BAMBOO)
} }
async fn can_place_at(&self, block_accessor: &dyn BlockAccessor, block_pos: &BlockPos) -> bool { async fn can_place_at(&self, block_accessor: &dyn BlockAccessor, block_pos: &BlockPos) -> bool {

View File

@ -114,6 +114,6 @@ impl PlantBlockBase for BambooSaplingBlock {
async fn can_plant_on_top(&self, block_accessor: &dyn BlockAccessor, pos: &BlockPos) -> bool { async fn can_plant_on_top(&self, block_accessor: &dyn BlockAccessor, pos: &BlockPos) -> bool {
let block = block_accessor.get_block(pos).await; let block = block_accessor.get_block(pos).await;
block.has_tag(&pumpkin_data::tag::Block::MINECRAFT_BAMBOO_PLANTABLE_ON) block.has_tag(&pumpkin_data::tag::Block::MINECRAFT_SUPPORTS_BAMBOO)
} }
} }

View File

@ -51,6 +51,6 @@ impl PlantBlockBase for DryVegetationBlock {
block_pos: &BlockPos, block_pos: &BlockPos,
) -> bool { ) -> bool {
let block_below = block_accessor.get_block(block_pos).await; let block_below = block_accessor.get_block(block_pos).await;
block_below.has_tag(&tag::Block::MINECRAFT_DRY_VEGETATION_MAY_PLACE_ON) block_below.has_tag(&tag::Block::MINECRAFT_SUPPORTS_DRY_VEGETATION)
} }
} }

View File

@ -32,7 +32,7 @@ pub mod wither_rose;
trait PlantBlockBase { trait PlantBlockBase {
async fn can_plant_on_top(&self, block_accessor: &dyn BlockAccessor, pos: &BlockPos) -> bool { async fn can_plant_on_top(&self, block_accessor: &dyn BlockAccessor, pos: &BlockPos) -> bool {
let block = block_accessor.get_block(pos).await; let block = block_accessor.get_block(pos).await;
block.has_tag(&tag::Block::MINECRAFT_DIRT) || block == &Block::FARMLAND block.has_tag(&tag::Block::MINECRAFT_SUPPORTS_VEGETATION)
} }
async fn get_state_for_neighbor_update( async fn get_state_for_neighbor_update(

View File

@ -42,7 +42,7 @@ impl BlockBehaviour for MushroomPlantBlock {
impl PlantBlockBase for MushroomPlantBlock { impl PlantBlockBase for MushroomPlantBlock {
async fn can_plant_on_top(&self, block_accessor: &dyn BlockAccessor, pos: &BlockPos) -> bool { async fn can_plant_on_top(&self, block_accessor: &dyn BlockAccessor, pos: &BlockPos) -> bool {
let block = block_accessor.get_block(pos).await; let block = block_accessor.get_block(pos).await;
block.has_tag(&tag::Block::MINECRAFT_MUSHROOM_GROW_BLOCK) block.has_tag(&tag::Block::MINECRAFT_OVERRIDES_MUSHROOM_LIGHT_REQUIREMENT)
// TODO: Check light level and isOpaqueFullCube // TODO: Check light level and isOpaqueFullCube
} }
} }