feat: implement remaining tree types

This commit is contained in:
Alexander Medvedev 2026-04-16 22:30:43 +02:00
parent d8a124cf92
commit 32c5530d6c
10 changed files with 786 additions and 128 deletions

View File

@ -965,6 +965,30 @@ fn value_to_tree_feature(config: &Value) -> TokenStream {
} }
} }
fn value_to_block_list(v: &Value) -> TokenStream {
if let Some(s) = v.as_str() {
if let Some(tag) = 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::Block::#ident.id });
}
}
}
quote! { &[#(#blocks),*] }
}
/// Converts a trunk-placer JSON object into a `TrunkPlacer` token stream. /// Converts a trunk-placer JSON object into a `TrunkPlacer` token stream.
/// ///
/// # Arguments /// # Arguments
@ -994,10 +1018,35 @@ fn value_to_trunk_placer(v: &Value) -> TokenStream {
} }
} }
"minecraft:upwards_branching_trunk_placer" => { "minecraft:upwards_branching_trunk_placer" => {
quote! { TrunkType::UpwardsBranching(UpwardsBranchingTrunkPlacer {}) } 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" => { "minecraft:cherry_trunk_placer" => {
quote! { TrunkType::Cherry(CherryTrunkPlacer {}) } 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 {
branch_count: #branch_count,
branch_horizontal_length: #branch_horizontal_length,
branch_start_offset_from_top: UniformIntProvider { min_inclusive: #min, max_inclusive: #max },
branch_end_offset_from_top: #branch_end_offset_from_top,
})
}
} }
_ => quote! { TrunkType::Straight(StraightTrunkPlacer) }, _ => quote! { TrunkType::Straight(StraightTrunkPlacer) },
}; };

View File

@ -733,8 +733,8 @@ fn build_configured_features() -> std::collections::HashMap<String, ConfiguredFe
prioritize_tip: true, prioritize_tip: true,
}), }),
); );
map . insert ("cherry" . to_string () , ConfiguredFeature :: Tree (Box :: new (TreeFeature { trunk_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("axis" . to_string () , "y" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: CHERRY_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 7u8 , height_rand_a : 1u8 , height_rand_b : 0u8 , r#type : TrunkType :: Cherry (CherryTrunkPlacer { }) , } , foliage_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("distance" . to_string () , "7" . to_string ()) ; props . insert ("persistent" . to_string () , "false" . to_string ()) ; props . insert ("waterlogged" . to_string () , "false" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: CHERRY_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (4i32) , offset : IntProvider :: Constant (0i32) , r#type : FoliageType :: Cherry (CherryFoliagePlacer { height : IntProvider :: Constant (5i32) , wide_bottom_layer_hole_chance : 0.25f32 , corner_hole_chance : 0.25f32 , hanging_leaves_chance : 0.16666667f32 , hanging_leaves_extension_chance : 0.33333334f32 , }) } , minimum_size : FeatureSize { min_clipped_height : None , r#type : FeatureSizeType :: TwoLayersFeatureSize (TwoLayersFeatureSize { limit : 1u8 , lower_size : 0u8 , upper_size : 2u8 , }) } , ignore_vines : true , below_trunk_provider : BlockStateProvider :: Rule (RuleBasedBlockStateProvider { fallback : None , rules : vec ! [BlockStateRule { if_true : BlockPredicate :: Not (NotBlockPredicate { predicate : Box :: new (BlockPredicate :: MatchingBlockTag (MatchingBlockTagPredicate { offset : OffsetBlocksBlockPredicate { offset : None } , tag : pumpkin_data :: tag :: Block :: MINECRAFT_CANNOT_REPLACE_BELOW_TREE_TRUNK , })) , }) , then : BlockStateProvider :: Simple (SimpleStateProvider { state : pumpkin_data :: Block :: DIRT . default_state }) }] , }) , decorators : vec ! [] , }))) ; map . insert ("cherry" . to_string () , ConfiguredFeature :: Tree (Box :: new (TreeFeature { trunk_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("axis" . to_string () , "y" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: CHERRY_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 7u8 , height_rand_a : 1u8 , height_rand_b : 0u8 , r#type : TrunkType :: Cherry (CherryTrunkPlacer { branch_count : IntProvider :: Object (NormalIntProvider :: WeightedList (WeightedListIntProvider { distribution : vec ! [WeightedEntry { data : IntProvider :: Constant (1i32) , weight : 1i32 } , WeightedEntry { data : IntProvider :: Constant (2i32) , weight : 1i32 } , WeightedEntry { data : IntProvider :: Constant (3i32) , weight : 1i32 }] })) , branch_horizontal_length : IntProvider :: Object (NormalIntProvider :: Uniform (UniformIntProvider { min_inclusive : 2i32 , max_inclusive : 4i32 })) , branch_start_offset_from_top : UniformIntProvider { min_inclusive : - 4i32 , max_inclusive : - 3i32 } , branch_end_offset_from_top : IntProvider :: Object (NormalIntProvider :: Uniform (UniformIntProvider { min_inclusive : - 1i32 , max_inclusive : 0i32 })) , }) , } , foliage_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("distance" . to_string () , "7" . to_string ()) ; props . insert ("persistent" . to_string () , "false" . to_string ()) ; props . insert ("waterlogged" . to_string () , "false" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: CHERRY_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (4i32) , offset : IntProvider :: Constant (0i32) , r#type : FoliageType :: Cherry (CherryFoliagePlacer { height : IntProvider :: Constant (5i32) , wide_bottom_layer_hole_chance : 0.25f32 , corner_hole_chance : 0.25f32 , hanging_leaves_chance : 0.16666667f32 , hanging_leaves_extension_chance : 0.33333334f32 , }) } , minimum_size : FeatureSize { min_clipped_height : None , r#type : FeatureSizeType :: TwoLayersFeatureSize (TwoLayersFeatureSize { limit : 1u8 , lower_size : 0u8 , upper_size : 2u8 , }) } , ignore_vines : true , below_trunk_provider : BlockStateProvider :: Rule (RuleBasedBlockStateProvider { fallback : None , rules : vec ! [BlockStateRule { if_true : BlockPredicate :: Not (NotBlockPredicate { predicate : Box :: new (BlockPredicate :: MatchingBlockTag (MatchingBlockTagPredicate { offset : OffsetBlocksBlockPredicate { offset : None } , tag : pumpkin_data :: tag :: Block :: MINECRAFT_CANNOT_REPLACE_BELOW_TREE_TRUNK , })) , }) , then : BlockStateProvider :: Simple (SimpleStateProvider { state : pumpkin_data :: Block :: DIRT . default_state }) }] , }) , decorators : vec ! [] , }))) ;
map . insert ("cherry_bees_005" . to_string () , ConfiguredFeature :: Tree (Box :: new (TreeFeature { trunk_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("axis" . to_string () , "y" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: CHERRY_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 7u8 , height_rand_a : 1u8 , height_rand_b : 0u8 , r#type : TrunkType :: Cherry (CherryTrunkPlacer { }) , } , foliage_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("distance" . to_string () , "7" . to_string ()) ; props . insert ("persistent" . to_string () , "false" . to_string ()) ; props . insert ("waterlogged" . to_string () , "false" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: CHERRY_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (4i32) , offset : IntProvider :: Constant (0i32) , r#type : FoliageType :: Cherry (CherryFoliagePlacer { height : IntProvider :: Constant (5i32) , wide_bottom_layer_hole_chance : 0.25f32 , corner_hole_chance : 0.25f32 , hanging_leaves_chance : 0.16666667f32 , hanging_leaves_extension_chance : 0.33333334f32 , }) } , minimum_size : FeatureSize { min_clipped_height : None , r#type : FeatureSizeType :: TwoLayersFeatureSize (TwoLayersFeatureSize { limit : 1u8 , lower_size : 0u8 , upper_size : 2u8 , }) } , ignore_vines : true , below_trunk_provider : BlockStateProvider :: Rule (RuleBasedBlockStateProvider { fallback : None , rules : vec ! [BlockStateRule { if_true : BlockPredicate :: Not (NotBlockPredicate { predicate : Box :: new (BlockPredicate :: MatchingBlockTag (MatchingBlockTagPredicate { offset : OffsetBlocksBlockPredicate { offset : None } , tag : pumpkin_data :: tag :: Block :: MINECRAFT_CANNOT_REPLACE_BELOW_TREE_TRUNK , })) , }) , then : BlockStateProvider :: Simple (SimpleStateProvider { state : pumpkin_data :: Block :: DIRT . default_state }) }] , }) , decorators : vec ! [TreeDecorator :: Beehive (BeehiveTreeDecorator { probability : 0.05f32 })] , }))) ; map . insert ("cherry_bees_005" . to_string () , ConfiguredFeature :: Tree (Box :: new (TreeFeature { trunk_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("axis" . to_string () , "y" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: CHERRY_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 7u8 , height_rand_a : 1u8 , height_rand_b : 0u8 , r#type : TrunkType :: Cherry (CherryTrunkPlacer { branch_count : IntProvider :: Object (NormalIntProvider :: WeightedList (WeightedListIntProvider { distribution : vec ! [WeightedEntry { data : IntProvider :: Constant (1i32) , weight : 1i32 } , WeightedEntry { data : IntProvider :: Constant (2i32) , weight : 1i32 } , WeightedEntry { data : IntProvider :: Constant (3i32) , weight : 1i32 }] })) , branch_horizontal_length : IntProvider :: Object (NormalIntProvider :: Uniform (UniformIntProvider { min_inclusive : 2i32 , max_inclusive : 4i32 })) , branch_start_offset_from_top : UniformIntProvider { min_inclusive : - 4i32 , max_inclusive : - 3i32 } , branch_end_offset_from_top : IntProvider :: Object (NormalIntProvider :: Uniform (UniformIntProvider { min_inclusive : - 1i32 , max_inclusive : 0i32 })) , }) , } , foliage_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("distance" . to_string () , "7" . to_string ()) ; props . insert ("persistent" . to_string () , "false" . to_string ()) ; props . insert ("waterlogged" . to_string () , "false" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: CHERRY_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (4i32) , offset : IntProvider :: Constant (0i32) , r#type : FoliageType :: Cherry (CherryFoliagePlacer { height : IntProvider :: Constant (5i32) , wide_bottom_layer_hole_chance : 0.25f32 , corner_hole_chance : 0.25f32 , hanging_leaves_chance : 0.16666667f32 , hanging_leaves_extension_chance : 0.33333334f32 , }) } , minimum_size : FeatureSize { min_clipped_height : None , r#type : FeatureSizeType :: TwoLayersFeatureSize (TwoLayersFeatureSize { limit : 1u8 , lower_size : 0u8 , upper_size : 2u8 , }) } , ignore_vines : true , below_trunk_provider : BlockStateProvider :: Rule (RuleBasedBlockStateProvider { fallback : None , rules : vec ! [BlockStateRule { if_true : BlockPredicate :: Not (NotBlockPredicate { predicate : Box :: new (BlockPredicate :: MatchingBlockTag (MatchingBlockTagPredicate { offset : OffsetBlocksBlockPredicate { offset : None } , tag : pumpkin_data :: tag :: Block :: MINECRAFT_CANNOT_REPLACE_BELOW_TREE_TRUNK , })) , }) , then : BlockStateProvider :: Simple (SimpleStateProvider { state : pumpkin_data :: Block :: DIRT . default_state }) }] , }) , decorators : vec ! [TreeDecorator :: Beehive (BeehiveTreeDecorator { probability : 0.05f32 })] , }))) ;
map.insert( map.insert(
"chorus_plant".to_string(), "chorus_plant".to_string(),
ConfiguredFeature::ChorusPlant( ConfiguredFeature::ChorusPlant(
@ -2520,7 +2520,7 @@ fn build_configured_features() -> std::collections::HashMap<String, ConfiguredFe
})), })),
}), }),
); );
map . insert ("mangrove" . to_string () , ConfiguredFeature :: Tree (Box :: new (TreeFeature { trunk_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("axis" . to_string () , "y" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: MANGROVE_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 2u8 , height_rand_a : 1u8 , height_rand_b : 4u8 , r#type : TrunkType :: UpwardsBranching (UpwardsBranchingTrunkPlacer { }) , } , foliage_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("distance" . to_string () , "7" . to_string ()) ; props . insert ("persistent" . to_string () , "false" . to_string ()) ; props . insert ("waterlogged" . to_string () , "false" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: MANGROVE_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (3i32) , offset : IntProvider :: Constant (0i32) , r#type : FoliageType :: RandomSpread (RandomSpreadFoliagePlacer { foliage_height : IntProvider :: Constant (2i32) , leaf_placement_attempts : 70i32 , }) } , minimum_size : FeatureSize { min_clipped_height : None , r#type : FeatureSizeType :: TwoLayersFeatureSize (TwoLayersFeatureSize { limit : 2u8 , lower_size : 0u8 , upper_size : 2u8 , }) } , ignore_vines : true , below_trunk_provider : BlockStateProvider :: Rule (RuleBasedBlockStateProvider { fallback : None , rules : vec ! [BlockStateRule { if_true : BlockPredicate :: Not (NotBlockPredicate { predicate : Box :: new (BlockPredicate :: MatchingBlockTag (MatchingBlockTagPredicate { offset : OffsetBlocksBlockPredicate { offset : None } , tag : pumpkin_data :: tag :: Block :: MINECRAFT_CANNOT_REPLACE_BELOW_TREE_TRUNK , })) , }) , then : BlockStateProvider :: Simple (SimpleStateProvider { state : pumpkin_data :: Block :: DIRT . default_state }) }] , }) , decorators : vec ! [TreeDecorator :: LeaveVine (LeavesVineTreeDecorator { }) , TreeDecorator :: AttachedToLeaves (AttachedToLeavesTreeDecorator { }) , TreeDecorator :: Beehive (BeehiveTreeDecorator { probability : 0.01f32 })] , }))) ; map . insert ("mangrove" . to_string () , ConfiguredFeature :: Tree (Box :: new (TreeFeature { trunk_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("axis" . to_string () , "y" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: MANGROVE_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 2u8 , height_rand_a : 1u8 , height_rand_b : 4u8 , r#type : TrunkType :: UpwardsBranching (UpwardsBranchingTrunkPlacer { extra_branch_steps : IntProvider :: Object (NormalIntProvider :: Uniform (UniformIntProvider { min_inclusive : 1i32 , max_inclusive : 4i32 })) , place_branch_per_log_probability : 0.5f32 , extra_branch_length : IntProvider :: Object (NormalIntProvider :: Uniform (UniformIntProvider { min_inclusive : 0i32 , max_inclusive : 1i32 })) , can_grow_through : & pumpkin_data :: tag :: Block :: MINECRAFT_MANGROVE_LOGS_CAN_GROW_THROUGH . 1 , }) , } , foliage_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("distance" . to_string () , "7" . to_string ()) ; props . insert ("persistent" . to_string () , "false" . to_string ()) ; props . insert ("waterlogged" . to_string () , "false" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: MANGROVE_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (3i32) , offset : IntProvider :: Constant (0i32) , r#type : FoliageType :: RandomSpread (RandomSpreadFoliagePlacer { foliage_height : IntProvider :: Constant (2i32) , leaf_placement_attempts : 70i32 , }) } , minimum_size : FeatureSize { min_clipped_height : None , r#type : FeatureSizeType :: TwoLayersFeatureSize (TwoLayersFeatureSize { limit : 2u8 , lower_size : 0u8 , upper_size : 2u8 , }) } , ignore_vines : true , below_trunk_provider : BlockStateProvider :: Rule (RuleBasedBlockStateProvider { fallback : None , rules : vec ! [BlockStateRule { if_true : BlockPredicate :: Not (NotBlockPredicate { predicate : Box :: new (BlockPredicate :: MatchingBlockTag (MatchingBlockTagPredicate { offset : OffsetBlocksBlockPredicate { offset : None } , tag : pumpkin_data :: tag :: Block :: MINECRAFT_CANNOT_REPLACE_BELOW_TREE_TRUNK , })) , }) , then : BlockStateProvider :: Simple (SimpleStateProvider { state : pumpkin_data :: Block :: DIRT . default_state }) }] , }) , decorators : vec ! [TreeDecorator :: LeaveVine (LeavesVineTreeDecorator { }) , TreeDecorator :: AttachedToLeaves (AttachedToLeavesTreeDecorator { }) , TreeDecorator :: Beehive (BeehiveTreeDecorator { probability : 0.01f32 })] , }))) ;
map.insert( map.insert(
"mangrove_vegetation".to_string(), "mangrove_vegetation".to_string(),
ConfiguredFeature::RandomSelector(RandomFeature { ConfiguredFeature::RandomSelector(RandomFeature {
@ -3873,7 +3873,7 @@ fn build_configured_features() -> std::collections::HashMap<String, ConfiguredFe
schedule_tick: None, schedule_tick: None,
}), }),
); );
map . insert ("tall_mangrove" . to_string () , ConfiguredFeature :: Tree (Box :: new (TreeFeature { trunk_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("axis" . to_string () , "y" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: MANGROVE_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 4u8 , height_rand_a : 1u8 , height_rand_b : 9u8 , r#type : TrunkType :: UpwardsBranching (UpwardsBranchingTrunkPlacer { }) , } , foliage_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("distance" . to_string () , "7" . to_string ()) ; props . insert ("persistent" . to_string () , "false" . to_string ()) ; props . insert ("waterlogged" . to_string () , "false" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: MANGROVE_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (3i32) , offset : IntProvider :: Constant (0i32) , r#type : FoliageType :: RandomSpread (RandomSpreadFoliagePlacer { foliage_height : IntProvider :: Constant (2i32) , leaf_placement_attempts : 70i32 , }) } , minimum_size : FeatureSize { min_clipped_height : None , r#type : FeatureSizeType :: TwoLayersFeatureSize (TwoLayersFeatureSize { limit : 3u8 , lower_size : 0u8 , upper_size : 2u8 , }) } , ignore_vines : true , below_trunk_provider : BlockStateProvider :: Rule (RuleBasedBlockStateProvider { fallback : None , rules : vec ! [BlockStateRule { if_true : BlockPredicate :: Not (NotBlockPredicate { predicate : Box :: new (BlockPredicate :: MatchingBlockTag (MatchingBlockTagPredicate { offset : OffsetBlocksBlockPredicate { offset : None } , tag : pumpkin_data :: tag :: Block :: MINECRAFT_CANNOT_REPLACE_BELOW_TREE_TRUNK , })) , }) , then : BlockStateProvider :: Simple (SimpleStateProvider { state : pumpkin_data :: Block :: DIRT . default_state }) }] , }) , decorators : vec ! [TreeDecorator :: LeaveVine (LeavesVineTreeDecorator { }) , TreeDecorator :: AttachedToLeaves (AttachedToLeavesTreeDecorator { }) , TreeDecorator :: Beehive (BeehiveTreeDecorator { probability : 0.01f32 })] , }))) ; map . insert ("tall_mangrove" . to_string () , ConfiguredFeature :: Tree (Box :: new (TreeFeature { trunk_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("axis" . to_string () , "y" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: MANGROVE_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 4u8 , height_rand_a : 1u8 , height_rand_b : 9u8 , r#type : TrunkType :: UpwardsBranching (UpwardsBranchingTrunkPlacer { extra_branch_steps : IntProvider :: Object (NormalIntProvider :: Uniform (UniformIntProvider { min_inclusive : 1i32 , max_inclusive : 6i32 })) , place_branch_per_log_probability : 0.5f32 , extra_branch_length : IntProvider :: Object (NormalIntProvider :: Uniform (UniformIntProvider { min_inclusive : 0i32 , max_inclusive : 1i32 })) , can_grow_through : & pumpkin_data :: tag :: Block :: MINECRAFT_MANGROVE_LOGS_CAN_GROW_THROUGH . 1 , }) , } , foliage_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("distance" . to_string () , "7" . to_string ()) ; props . insert ("persistent" . to_string () , "false" . to_string ()) ; props . insert ("waterlogged" . to_string () , "false" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: MANGROVE_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (3i32) , offset : IntProvider :: Constant (0i32) , r#type : FoliageType :: RandomSpread (RandomSpreadFoliagePlacer { foliage_height : IntProvider :: Constant (2i32) , leaf_placement_attempts : 70i32 , }) } , minimum_size : FeatureSize { min_clipped_height : None , r#type : FeatureSizeType :: TwoLayersFeatureSize (TwoLayersFeatureSize { limit : 3u8 , lower_size : 0u8 , upper_size : 2u8 , }) } , ignore_vines : true , below_trunk_provider : BlockStateProvider :: Rule (RuleBasedBlockStateProvider { fallback : None , rules : vec ! [BlockStateRule { if_true : BlockPredicate :: Not (NotBlockPredicate { predicate : Box :: new (BlockPredicate :: MatchingBlockTag (MatchingBlockTagPredicate { offset : OffsetBlocksBlockPredicate { offset : None } , tag : pumpkin_data :: tag :: Block :: MINECRAFT_CANNOT_REPLACE_BELOW_TREE_TRUNK , })) , }) , then : BlockStateProvider :: Simple (SimpleStateProvider { state : pumpkin_data :: Block :: DIRT . default_state }) }] , }) , decorators : vec ! [TreeDecorator :: LeaveVine (LeavesVineTreeDecorator { }) , TreeDecorator :: AttachedToLeaves (AttachedToLeavesTreeDecorator { }) , TreeDecorator :: Beehive (BeehiveTreeDecorator { probability : 0.01f32 })] , }))) ;
map.insert( map.insert(
"trees_badlands".to_string(), "trees_badlands".to_string(),
ConfiguredFeature::RandomSelector(RandomFeature { ConfiguredFeature::RandomSelector(RandomFeature {

View File

@ -24,18 +24,18 @@ impl CherryFoliagePlacer {
random: &mut RandomGenerator, random: &mut RandomGenerator,
node: &TreeNode, node: &TreeNode,
foliage_height: i32, foliage_height: i32,
radius: i32, leaf_radius: i32,
offset: i32, offset: i32,
foliage_provider: &BlockState, foliage_provider: &BlockState,
) { ) {
let pos = node.center.up_height(offset); let pos = node.center.up_height(offset);
let radius = radius + node.foliage_radius - 1; let current_radius = leaf_radius + node.foliage_radius - 1;
FoliagePlacer::generate_square( FoliagePlacer::generate_square(
self, self,
chunk, chunk,
random, random,
pos, pos,
radius - 2, current_radius - 2,
foliage_height - 3, foliage_height - 3,
node.giant_trunk, node.giant_trunk,
foliage_provider, foliage_provider,
@ -45,44 +45,48 @@ impl CherryFoliagePlacer {
chunk, chunk,
random, random,
pos, pos,
radius - 1, current_radius - 1,
foliage_height - 4, foliage_height - 4,
node.giant_trunk, node.giant_trunk,
foliage_provider, foliage_provider,
); );
for y in foliage_height - 5..0 { for y in (0..=foliage_height - 5).rev() {
FoliagePlacer::generate_square( FoliagePlacer::generate_square(
self, self,
chunk, chunk,
random, random,
pos, pos,
radius, current_radius,
y, y,
node.giant_trunk, node.giant_trunk,
foliage_provider, foliage_provider,
); );
} }
// TODO: generateSquareWithHangingLeaves
FoliagePlacer::generate_square( FoliagePlacer::generate_square_with_hanging_leaves(
self, self,
chunk, chunk,
random, random,
pos, pos,
radius, current_radius,
-1, -1,
node.giant_trunk, node.giant_trunk,
foliage_provider, foliage_provider,
self.hanging_leaves_chance,
self.hanging_leaves_extension_chance,
); );
// TODO: generateSquareWithHangingLeaves
FoliagePlacer::generate_square( FoliagePlacer::generate_square_with_hanging_leaves(
self, self,
chunk, chunk,
random, random,
pos, pos,
radius - 1, current_radius - 1,
-2, -2,
node.giant_trunk, node.giant_trunk,
foliage_provider, foliage_provider,
self.hanging_leaves_chance,
self.hanging_leaves_extension_chance,
); );
} }
pub fn get_random_height(&self, random: &mut RandomGenerator) -> i32 { pub fn get_random_height(&self, random: &mut RandomGenerator) -> i32 {
@ -97,20 +101,24 @@ impl LeaveValidator for CherryFoliagePlacer {
dx: i32, dx: i32,
y: i32, y: i32,
dz: i32, dz: i32,
radius: i32, current_radius: i32,
_giant_trunk: bool, _giant_trunk: bool,
) -> bool { ) -> bool {
if y == -1 if y == -1
&& (dx == radius || dz == radius) && (dx == current_radius || dz == current_radius)
&& random.next_f32() < self.wide_bottom_layer_hole_chance && random.next_f32() < self.wide_bottom_layer_hole_chance
{ {
return true; return true;
} }
let in_radius = dx == radius && dz == radius;
if radius > 2 { let corner = dx == current_radius && dz == current_radius;
return in_radius let wide_layer = current_radius > 2;
|| dx + dz > radius * 2 - 2 && random.next_f32() < self.corner_hole_chance;
if wide_layer {
corner
|| dx + dz > current_radius * 2 - 2 && random.next_f32() < self.corner_hole_chance
} else {
corner && random.next_f32() < self.corner_hole_chance
} }
in_radius && random.next_f32() < self.corner_hole_chance
} }
} }

View File

@ -7,10 +7,11 @@ use fancy::LargeOakFoliagePlacer;
use jungle::JungleFoliagePlacer; use jungle::JungleFoliagePlacer;
use mega_pine::MegaPineFoliagePlacer; use mega_pine::MegaPineFoliagePlacer;
use pine::PineFoliagePlacer; use pine::PineFoliagePlacer;
use pumpkin_data::BlockDirection;
use pumpkin_data::BlockState; use pumpkin_data::BlockState;
use pumpkin_util::{ use pumpkin_util::{
math::{int_provider::IntProvider, position::BlockPos, vector3::Vector3}, math::{int_provider::IntProvider, position::BlockPos, vector3::Vector3},
random::RandomGenerator, random::{RandomGenerator, RandomImpl},
}; };
use random_spread::RandomSpreadFoliagePlacer; use random_spread::RandomSpreadFoliagePlacer;
@ -128,12 +129,110 @@ impl FoliagePlacer {
chunk: &mut T, chunk: &mut T,
pos: BlockPos, pos: BlockPos,
block_state: &BlockState, block_state: &BlockState,
) { ) -> bool {
let block = GenerationCache::get_block_state(chunk, &pos.0); let block = GenerationCache::get_block_state(chunk, &pos.0);
if !TreeFeature::can_replace(block.to_state(), block.to_block_id()) { if !TreeFeature::can_replace(block.to_state(), block.to_block_id()) {
return; return false;
} }
chunk.set_block_state(&pos.0, block_state); chunk.set_block_state(&pos.0, block_state);
true
}
pub fn is_set<T: GenerationCache>(
chunk: &T,
pos: BlockPos,
foliage_provider: &BlockState,
) -> bool {
GenerationCache::get_block_state(chunk, &pos.0).0 == foliage_provider.id
}
fn try_place_extension<T: GenerationCache>(
chunk: &mut T,
random: &mut RandomGenerator,
chance: f32,
log_pos: BlockPos,
pos: BlockPos,
foliage_provider: &BlockState,
) -> bool {
if pos.manhattan_distance(log_pos) >= 7 || random.next_f32() > chance {
false
} else {
Self::place_foliage_block(chunk, pos, foliage_provider)
}
}
#[expect(clippy::too_many_arguments)]
pub fn generate_square_with_hanging_leaves<T: LeaveValidator, T2: GenerationCache>(
validator: &T,
chunk: &mut T2,
random: &mut RandomGenerator,
center_pos: BlockPos,
radius: i32,
y: i32,
giant_trunk: bool,
foliage_provider: &BlockState,
hanging_leaves_chance: f32,
hanging_leaves_extension_chance: f32,
) {
Self::generate_square(
validator,
chunk,
random,
center_pos,
radius,
y,
giant_trunk,
foliage_provider,
);
let i = i32::from(giant_trunk);
let log_pos = center_pos.down();
let directions = [
BlockDirection::North,
BlockDirection::South,
BlockDirection::East,
BlockDirection::West,
];
for along_edge in directions {
let to_edge = along_edge.rotate_clockwise();
let offset_to_edge = if to_edge.positive() {
radius + i
} else {
radius
};
let mut pos = center_pos
.add(0, y - 1, 0)
.offset_dir(to_edge.to_offset(), offset_to_edge)
.offset_dir(along_edge.to_offset(), -radius);
for _ in -radius..(radius + i) {
let leaves_above = Self::is_set(chunk, pos.up(), foliage_provider);
if leaves_above
&& Self::try_place_extension(
chunk,
random,
hanging_leaves_chance,
log_pos,
pos,
foliage_provider,
)
{
Self::try_place_extension(
chunk,
random,
hanging_leaves_extension_chance,
log_pos,
pos.down(),
foliage_provider,
);
}
pos = pos.offset_dir(along_edge.to_offset(), 1);
}
}
} }
} }

View File

@ -31,48 +31,57 @@ impl BendingTrunkPlacer {
below_trunk_provider: &BlockStateProvider, below_trunk_provider: &BlockStateProvider,
trunk_block: &BlockState, trunk_block: &BlockState,
) -> (Vec<TreeNode>, Vec<BlockPos>) { ) -> (Vec<TreeNode>, Vec<BlockPos>) {
let horizontal_directions = BlockDirection::horizontal();
let direction = horizontal_directions[random.next_bounded_i32(4) as usize];
let log_height = height as i32 - 1;
let mut pos = start_pos;
placer.set_dirt( placer.set_dirt(
block_registry, block_registry,
chunk, chunk,
random, random,
&start_pos.down(), &pos.down(),
below_trunk_provider, below_trunk_provider,
); );
// TODO: make this random
let random_direction = BlockDirection::North;
let height = height - 1;
let mut pos = start_pos;
let mut trunk_poses = Vec::new(); let mut trunk_poses = Vec::new();
let mut nodes = Vec::new(); let mut nodes = Vec::new();
for y in 0..height {
if y + 1 >= height + random.next_bounded_i32(2) as u32 { for i in 0..=log_height {
pos = pos.offset(random_direction.to_offset()); if i + 1 >= log_height + random.next_bounded_i32(2) {
pos = pos.offset(direction.to_offset());
} }
if placer.place(chunk, &pos, trunk_block) { if placer.place(chunk, &pos, trunk_block) {
trunk_poses.push(pos); trunk_poses.push(pos);
} }
if y >= 1 {
if i >= self.min_height_for_leaves as i32 {
nodes.push(TreeNode { nodes.push(TreeNode {
center: pos, center: pos,
foliage_radius: 0, foliage_radius: 0,
giant_trunk: false, giant_trunk: false,
}); });
} }
pos = pos.up(); pos = pos.up();
} }
let bend = self.bend_length.get(random);
for _ in 0..bend { let dir_length = self.bend_length.get(random);
for _ in 0..=dir_length {
if placer.place(chunk, &pos, trunk_block) { if placer.place(chunk, &pos, trunk_block) {
trunk_poses.push(pos); trunk_poses.push(pos);
} }
nodes.push(TreeNode { nodes.push(TreeNode {
center: pos, center: pos,
foliage_radius: 0, foliage_radius: 0,
giant_trunk: false, giant_trunk: false,
}); });
pos = pos.offset(random_direction.to_offset()); pos = pos.offset(direction.to_offset());
} }
(nodes, trunk_poses) (nodes, trunk_poses)
} }
} }

View File

@ -1 +1,232 @@
pub struct CherryTrunkPlacer {} use pumpkin_data::{Block, BlockState};
use pumpkin_util::math::int_provider::{IntProvider, UniformIntProvider};
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::random::{RandomGenerator, RandomImpl};
use super::TrunkPlacer;
use crate::generation::block_state_provider::BlockStateProvider;
use crate::generation::feature::features::tree::TreeNode;
use crate::generation::proto_chunk::GenerationCache;
use crate::world::BlockRegistryExt;
use pumpkin_data::block_properties::Axis;
pub struct CherryTrunkPlacer {
pub branch_count: IntProvider,
pub branch_horizontal_length: IntProvider,
pub branch_start_offset_from_top: UniformIntProvider,
pub branch_end_offset_from_top: IntProvider,
}
impl CherryTrunkPlacer {
#[expect(clippy::too_many_arguments)]
pub fn generate<T: GenerationCache>(
&self,
block_registry: &dyn BlockRegistryExt,
placer: &TrunkPlacer,
height: u32,
start_pos: BlockPos,
chunk: &mut T,
random: &mut RandomGenerator,
below_trunk_provider: &BlockStateProvider,
trunk_state: &BlockState,
) -> (Vec<TreeNode>, Vec<BlockPos>) {
placer.set_dirt(
block_registry,
chunk,
random,
&start_pos.down(),
below_trunk_provider,
);
let mut logs = Vec::new();
let first_branch_offset =
(height as i32 - 1 + self.branch_start_offset_from_top.get(random)).max(0);
let second_branch_start_offset_from_top = UniformIntProvider::new(
self.branch_start_offset_from_top.min_inclusive,
self.branch_start_offset_from_top.max_inclusive - 1,
);
let mut second_branch_offset =
(height as i32 - 1 + second_branch_start_offset_from_top.get(random)).max(0);
if second_branch_offset >= first_branch_offset {
second_branch_offset += 1;
}
let branch_count = self.branch_count.get(random);
let has_middle_branch = branch_count == 3;
let has_both_side_branches = branch_count >= 2;
let trunk_height = if has_middle_branch {
height
} else if has_both_side_branches {
(first_branch_offset.max(second_branch_offset) + 1) as u32
} else {
(first_branch_offset + 1) as u32
};
for y in 0..trunk_height {
let pos = start_pos.up_height(y as i32);
if placer.place(chunk, &pos, trunk_state) {
logs.push(pos);
}
}
let mut nodes = Vec::new();
if has_middle_branch {
nodes.push(TreeNode {
center: start_pos.up_height(trunk_height as i32),
foliage_radius: 0,
giant_trunk: false,
});
}
let directions = [
BlockPos::new(0, 0, -1), // North
BlockPos::new(0, 0, 1), // South
BlockPos::new(-1, 0, 0), // West
BlockPos::new(1, 0, 0), // East
];
let branch_direction = directions[random.next_bounded_i32(4) as usize];
let (node1, logs1) = self.generate_branch(
placer,
height,
start_pos,
chunk,
random,
trunk_state,
branch_direction,
first_branch_offset,
first_branch_offset < trunk_height as i32 - 1,
);
nodes.push(node1);
logs.extend(logs1);
if has_both_side_branches {
let opposite_direction = BlockPos::new(-branch_direction.0.x, 0, -branch_direction.0.z);
let (node2, logs2) = self.generate_branch(
placer,
height,
start_pos,
chunk,
random,
trunk_state,
opposite_direction,
second_branch_offset,
second_branch_offset < trunk_height as i32 - 1,
);
nodes.push(node2);
logs.extend(logs2);
}
(nodes, logs)
}
#[expect(clippy::too_many_arguments)]
fn generate_branch<T: GenerationCache>(
&self,
placer: &TrunkPlacer,
tree_height: u32,
origin: BlockPos,
chunk: &mut T,
random: &mut RandomGenerator,
trunk_state: &BlockState,
branch_direction: BlockPos,
offset_from_origin: i32,
middle_continues_upwards: bool,
) -> (TreeNode, Vec<BlockPos>) {
let mut logs = Vec::new();
let mut log_pos = origin.up_height(offset_from_origin);
let branch_end_pos_offset =
tree_height as i32 - 1 + self.branch_end_offset_from_top.get(random);
let extend_branch_away_from_trunk =
middle_continues_upwards || branch_end_pos_offset < offset_from_origin;
let distance_to_trunk = self.branch_horizontal_length.get(random)
+ if extend_branch_away_from_trunk { 1 } else { 0 };
let branch_end_pos = origin
.add(
branch_direction.0.x * distance_to_trunk,
0,
branch_direction.0.z * distance_to_trunk,
)
.up_height(branch_end_pos_offset);
let steps_horizontally = if extend_branch_away_from_trunk { 2 } else { 1 };
let axis = if branch_direction.0.x != 0 {
Axis::X
} else {
Axis::Z
};
let sideways_state = self.get_sideways_state(trunk_state, axis);
for _ in 0..steps_horizontally {
log_pos = log_pos.add(branch_direction.0.x, 0, branch_direction.0.z);
if placer.place(chunk, &log_pos, sideways_state) {
logs.push(log_pos);
}
}
let vertical_direction = if branch_end_pos.0.y > log_pos.0.y {
1
} else {
-1
};
loop {
let distance = log_pos.manhattan_distance(branch_end_pos);
if distance == 0 {
return (
TreeNode {
center: branch_end_pos.up(),
foliage_radius: 0,
giant_trunk: false,
},
logs,
);
}
let chance_to_grow_vertically =
(branch_end_pos.0.y - log_pos.0.y).abs() as f32 / distance as f32;
let grow_vertically = random.next_f32() < chance_to_grow_vertically;
if grow_vertically {
log_pos = log_pos.up_height(vertical_direction);
if placer.place(chunk, &log_pos, trunk_state) {
logs.push(log_pos);
}
} else {
log_pos = log_pos.add(branch_direction.0.x, 0, branch_direction.0.z);
if placer.place(chunk, &log_pos, sideways_state) {
logs.push(log_pos);
}
}
}
}
fn get_sideways_state(&self, trunk_state: &BlockState, axis: Axis) -> &'static BlockState {
let block = Block::from_state_id(trunk_state.id);
if let Some(props_source) = block.properties(trunk_state.id) {
let mut props = props_source.to_props();
let axis_str = match axis {
Axis::X => "x",
Axis::Y => "y",
Axis::Z => "z",
};
if let Some(idx) = props.iter().position(|(k, _)| *k == "axis") {
props[idx] = ("axis", axis_str);
} else {
props.push(("axis", axis_str));
}
let new_state_id = block.from_properties(&props).to_state_id(block);
return BlockState::from_id(new_state_id);
}
BlockState::from_id(trunk_state.id)
}
}

View File

@ -27,120 +27,100 @@ impl DarkOakTrunkPlacer {
below_trunk_provider: &BlockStateProvider, below_trunk_provider: &BlockStateProvider,
trunk_block: &BlockState, trunk_block: &BlockState,
) -> (Vec<TreeNode>, Vec<BlockPos>) { ) -> (Vec<TreeNode>, Vec<BlockPos>) {
let pos = start_pos.down(); let below = start_pos.down();
placer.set_dirt(block_registry, chunk, random, &pos, below_trunk_provider); placer.set_dirt(block_registry, chunk, random, &below, below_trunk_provider);
placer.set_dirt( placer.set_dirt(
block_registry, block_registry,
chunk, chunk,
random, random,
&pos.offset(BlockDirection::East.to_offset()), &below.east(),
below_trunk_provider, below_trunk_provider,
); );
placer.set_dirt( placer.set_dirt(
block_registry, block_registry,
chunk, chunk,
random, random,
&pos.offset(BlockDirection::South.to_offset()), &below.south(),
below_trunk_provider, below_trunk_provider,
); );
placer.set_dirt( placer.set_dirt(
block_registry, block_registry,
chunk, chunk,
random, random,
&pos.offset(BlockDirection::South.to_offset()) &below.south().east(),
.offset(BlockDirection::East.to_offset()),
below_trunk_provider, below_trunk_provider,
); );
let start_y = start_pos.0.y;
let y_height = start_y + height as i32 - 1; let horizontal_directions = BlockDirection::horizontal();
let max_height = height - random.next_bounded_i32(4) as u32; let lean_direction = horizontal_directions[random.next_bounded_i32(4) as usize];
let lean_height = height as i32 - random.next_bounded_i32(4);
let mut lean_steps = 2 - random.next_bounded_i32(3);
let mut tx = start_pos.0.x;
let mut tz = start_pos.0.z;
let ey = start_pos.0.y + height as i32 - 1;
let mut trunk_poses = Vec::new(); let mut trunk_poses = Vec::new();
let mut nodes = Vec::new(); for dy in 0..height as i32 {
let mut rand = random.next_bounded_i32(3); if dy >= lean_height && lean_steps > 0 {
let offset = lean_direction.to_offset();
let mut x = pos.0.x; tx += offset.x;
let mut z = pos.0.z; tz += offset.z;
lean_steps -= 1;
// TODO: make this random
let random_direction = BlockDirection::North;
for y in 0..height {
if y >= max_height && rand > 0 {
x += random_direction.to_offset().x;
z += random_direction.to_offset().z;
rand -= 1;
} }
let current_y = start_y + y as i32; let yy = start_pos.0.y + dy;
let pos = BlockPos::new(x, current_y, z); let pos = BlockPos::new(tx, yy, tz);
// TODO: support multiple chunks
// Check if air or leaves at the main 2x2 position
let state = GenerationCache::get_block_state(chunk, &pos.0); let state = GenerationCache::get_block_state(chunk, &pos.0);
if !TreeFeature::is_air_or_leaves(state.to_state(), state.to_block_id()) { if TreeFeature::is_air_or_leaves(state.to_state(), state.to_block_id()) {
continue; if placer.try_place(chunk, &pos, trunk_block) {
} trunk_poses.push(pos);
if placer.try_place(chunk, &pos, trunk_block) { }
trunk_poses.push(pos); if placer.try_place(chunk, &pos.east(), trunk_block) {
} trunk_poses.push(pos.east());
if placer.try_place( }
chunk, if placer.try_place(chunk, &pos.south(), trunk_block) {
&pos.offset(BlockDirection::East.to_offset()), trunk_poses.push(pos.south());
trunk_block, }
) { if placer.try_place(chunk, &pos.east().south(), trunk_block) {
trunk_poses.push(pos.offset(BlockDirection::East.to_offset())); trunk_poses.push(pos.east().south());
} }
if placer.try_place(
chunk,
&pos.offset(BlockDirection::South.to_offset()),
trunk_block,
) {
trunk_poses.push(pos.offset(BlockDirection::South.to_offset()));
}
if placer.try_place(
chunk,
&pos.offset(BlockDirection::East.to_offset())
.offset(BlockDirection::South.to_offset()),
trunk_block,
) {
trunk_poses.push(
pos.offset(BlockDirection::East.to_offset())
.offset(BlockDirection::South.to_offset()),
);
} }
} }
let mut nodes = Vec::new();
nodes.push(TreeNode { nodes.push(TreeNode {
center: BlockPos::new(x, y_height, z), center: BlockPos::new(tx, ey, tz),
foliage_radius: 0, foliage_radius: 0,
giant_trunk: true, giant_trunk: true,
}); });
for xd in -1..2 {
for zd in -1..2 { for ox in -1..=2 {
if (0..=1).contains(&xd) && (0..=1).contains(&zd) || random.next_bounded_i32(3) > 0 for oz in -1..=2 {
if (!(0..=1).contains(&ox) || !(0..=1).contains(&oz))
&& random.next_bounded_i32(3) <= 0
{ {
continue; let length = random.next_bounded_i32(3) + 2;
}
let h = random.next_bounded_i32(3) + 2; for branch_y in 0..length {
for height in 0..h { let pos = BlockPos::new(
if placer.try_place( start_pos.0.x + ox,
chunk, ey - branch_y - 1,
&BlockPos::new( start_pos.0.z + oz,
start_pos.0.x + xd, );
y_height - height - 1, if placer.try_place(chunk, &pos, trunk_block) {
start_pos.0.z + zd, trunk_poses.push(pos);
), }
trunk_block,
) {
trunk_poses.push(BlockPos::new(
start_pos.0.x + xd,
y_height - height - 1,
start_pos.0.z + zd,
));
} }
nodes.push(TreeNode {
center: BlockPos::new(start_pos.0.x + ox, ey, start_pos.0.z + oz),
foliage_radius: 0,
giant_trunk: false,
});
} }
nodes.push(TreeNode {
center: BlockPos::new(start_pos.0.x + xd, y_height, start_pos.0.z + zd),
foliage_radius: 0,
giant_trunk: false,
});
} }
} }

View File

@ -1 +1,107 @@
use pumpkin_data::BlockState;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::random::{RandomGenerator, RandomImpl};
use super::TrunkPlacer;
use crate::generation::block_state_provider::BlockStateProvider;
use crate::generation::feature::features::tree::TreeNode;
use crate::generation::proto_chunk::GenerationCache;
use crate::world::BlockRegistryExt;
use pumpkin_data::BlockDirection;
pub struct ForkingTrunkPlacer; pub struct ForkingTrunkPlacer;
impl ForkingTrunkPlacer {
#[expect(clippy::too_many_arguments)]
pub fn generate<T: GenerationCache>(
block_registry: &dyn BlockRegistryExt,
placer: &TrunkPlacer,
height: u32,
start_pos: BlockPos,
chunk: &mut T,
random: &mut RandomGenerator,
below_trunk_provider: &BlockStateProvider,
trunk_state: &BlockState,
) -> (Vec<TreeNode>, Vec<BlockPos>) {
placer.set_dirt(
block_registry,
chunk,
random,
&start_pos.down(),
below_trunk_provider,
);
let mut nodes = Vec::new();
let mut logs = Vec::new();
let horizontal_directions = BlockDirection::horizontal();
let lean_direction = horizontal_directions[random.next_bounded_i32(4) as usize];
let lean_height = height as i32 - random.next_bounded_i32(4) - 1;
let mut lean_steps = 3 - random.next_bounded_i32(3);
let mut current_tx = start_pos.0.x;
let mut current_tz = start_pos.0.z;
let mut last_y = None;
for yo in 0..height as i32 {
let yy = start_pos.0.y + yo;
if yo >= lean_height && lean_steps > 0 {
let offset = lean_direction.to_offset();
current_tx += offset.x;
current_tz += offset.z;
lean_steps -= 1;
}
let pos = BlockPos::new(current_tx, yy, current_tz);
if placer.place(chunk, &pos, trunk_state) {
logs.push(pos);
last_y = Some(yy + 1);
}
}
if let Some(ey) = last_y {
nodes.push(TreeNode {
center: BlockPos::new(current_tx, ey, current_tz),
foliage_radius: 1,
giant_trunk: false,
});
}
let mut branch_tx = start_pos.0.x;
let mut branch_tz = start_pos.0.z;
let branch_direction = horizontal_directions[random.next_bounded_i32(4) as usize];
if branch_direction != lean_direction {
let branch_pos = lean_height - random.next_bounded_i32(2) - 1;
let mut branch_steps = 1 + random.next_bounded_i32(3);
let mut branch_last_y = None;
let mut yo = branch_pos;
while yo < height as i32 && branch_steps > 0 {
if yo >= 1 {
let yyx = start_pos.0.y + yo;
let offset = branch_direction.to_offset();
branch_tx += offset.x;
branch_tz += offset.z;
let pos = BlockPos::new(branch_tx, yyx, branch_tz);
if placer.place(chunk, &pos, trunk_state) {
logs.push(pos);
branch_last_y = Some(yyx + 1);
}
}
yo += 1;
branch_steps -= 1;
}
if let Some(ey) = branch_last_y {
nodes.push(TreeNode {
center: BlockPos::new(branch_tx, ey, branch_tz),
foliage_radius: 0,
giant_trunk: false,
});
}
}
(nodes, logs)
}
}

View File

@ -142,7 +142,16 @@ impl TrunkType {
below_trunk_provider, below_trunk_provider,
trunk_state, trunk_state,
), ),
Self::Forking(_) => (vec![], vec![]), // TODO Self::Forking(_) => ForkingTrunkPlacer::generate(
block_registry,
placer,
height,
start_pos,
chunk,
random,
below_trunk_provider,
trunk_state,
),
Self::Giant(_) => GiantTrunkPlacer::generate( Self::Giant(_) => GiantTrunkPlacer::generate(
block_registry, block_registry,
placer, placer,
@ -193,8 +202,26 @@ impl TrunkType {
below_trunk_provider, below_trunk_provider,
trunk_state, trunk_state,
), ),
Self::UpwardsBranching(_) => (vec![], vec![]), // TODO Self::UpwardsBranching(upwards_branching) => upwards_branching.generate(
Self::Cherry(_) => (vec![], vec![]), // TODO block_registry,
placer,
height,
start_pos,
chunk,
random,
below_trunk_provider,
trunk_state,
),
Self::Cherry(cherry) => cherry.generate(
block_registry,
placer,
height,
start_pos,
chunk,
random,
below_trunk_provider,
trunk_state,
),
} }
} }
} }

View File

@ -1 +1,150 @@
pub struct UpwardsBranchingTrunkPlacer {} use pumpkin_data::BlockState;
use pumpkin_util::math::int_provider::IntProvider;
use pumpkin_util::math::position::BlockPos;
use pumpkin_util::random::{RandomGenerator, RandomImpl};
use super::TrunkPlacer;
use crate::generation::block_state_provider::BlockStateProvider;
use crate::generation::feature::features::tree::{TreeFeature, TreeNode};
use crate::generation::proto_chunk::GenerationCache;
use crate::world::BlockRegistryExt;
use pumpkin_data::BlockDirection;
pub struct UpwardsBranchingTrunkPlacer {
pub extra_branch_steps: IntProvider,
pub place_branch_per_log_probability: f32,
pub extra_branch_length: IntProvider,
pub can_grow_through: &'static [u16],
}
impl UpwardsBranchingTrunkPlacer {
#[expect(clippy::too_many_arguments)]
pub fn generate<T: GenerationCache>(
&self,
_block_registry: &dyn BlockRegistryExt,
placer: &TrunkPlacer,
height: u32,
start_pos: BlockPos,
chunk: &mut T,
random: &mut RandomGenerator,
_below_trunk_provider: &BlockStateProvider,
trunk_state: &BlockState,
) -> (Vec<TreeNode>, Vec<BlockPos>) {
let mut nodes = Vec::new();
let mut logs = Vec::new();
for height_pos in 0..height as i32 {
let current_pos = start_pos.up_height(height_pos);
if self.place_log_special(chunk, &current_pos, trunk_state) {
logs.push(current_pos);
if height_pos < height as i32 - 1
&& random.next_f32() < self.place_branch_per_log_probability
{
let branch_dir =
BlockDirection::horizontal()[random.next_bounded_i32(4) as usize];
let branch_len = self.extra_branch_length.get(random);
let branch_pos = (branch_len - self.extra_branch_length.get(random) - 1).max(0);
let branch_steps = self.extra_branch_steps.get(random);
self.place_branch(
placer,
height,
chunk,
random,
trunk_state,
&mut nodes,
&mut logs,
current_pos,
branch_dir,
branch_pos,
branch_steps,
);
}
}
if height_pos == height as i32 - 1 {
nodes.push(TreeNode {
center: current_pos.up(),
foliage_radius: 0,
giant_trunk: false,
});
}
}
(nodes, logs)
}
#[expect(clippy::too_many_arguments)]
fn place_branch<T: GenerationCache>(
&self,
_placer: &TrunkPlacer,
tree_height: u32,
chunk: &mut T,
_random: &mut RandomGenerator,
trunk_state: &BlockState,
nodes: &mut Vec<TreeNode>,
logs: &mut Vec<BlockPos>,
current_pos: BlockPos,
branch_dir: BlockDirection,
branch_pos: i32,
mut branch_steps: i32,
) {
let mut height_along_branch = current_pos.0.y + branch_pos;
let mut log_x = current_pos.0.x;
let mut log_z = current_pos.0.z;
let mut branch_placement_index = branch_pos;
while branch_placement_index < tree_height as i32 && branch_steps > 0 {
if branch_placement_index >= 1 {
let placement_height = current_pos.0.y + branch_placement_index;
let offset = branch_dir.to_offset();
log_x += offset.x;
log_z += offset.z;
height_along_branch = placement_height;
let pos = BlockPos::new(log_x, placement_height, log_z);
if self.place_log_special(chunk, &pos, trunk_state) {
logs.push(pos);
height_along_branch = placement_height + 1;
}
nodes.push(TreeNode {
center: pos,
foliage_radius: 0,
giant_trunk: false,
});
}
branch_placement_index += 1;
branch_steps -= 1;
}
if height_along_branch - current_pos.0.y > 1 {
let foliage_pos = BlockPos::new(log_x, height_along_branch, log_z);
nodes.push(TreeNode {
center: foliage_pos,
foliage_radius: 0,
giant_trunk: false,
});
nodes.push(TreeNode {
center: foliage_pos.down_height(2),
foliage_radius: 0,
giant_trunk: false,
});
}
}
fn place_log_special<T: GenerationCache>(
&self,
chunk: &mut T,
pos: &BlockPos,
trunk_state: &BlockState,
) -> bool {
let block = GenerationCache::get_block_state(chunk, &pos.0);
if TreeFeature::can_replace(block.to_state(), block.to_block_id())
|| self.can_grow_through.contains(&block.to_block_id())
{
chunk.set_block_state(&pos.0, trunk_state);
return true;
}
false
}
}