From 32c5530d6c4698e4312b6e64582c7024fa7ded26 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Thu, 16 Apr 2026 22:30:43 +0200 Subject: [PATCH] feat: implement remaining tree types --- pumpkin-codegen/src/configured_feature.rs | 53 +++- .../configured_features_generated.rs | 8 +- .../feature/features/tree/foliage/cherry.rs | 46 ++-- .../feature/features/tree/foliage/mod.rs | 105 +++++++- .../feature/features/tree/trunk/bending.rs | 33 ++- .../feature/features/tree/trunk/cherry.rs | 233 +++++++++++++++++- .../feature/features/tree/trunk/dark_oak.rs | 146 +++++------ .../feature/features/tree/trunk/forking.rs | 106 ++++++++ .../feature/features/tree/trunk/mod.rs | 33 ++- .../features/tree/trunk/upwards_branching.rs | 151 +++++++++++- 10 files changed, 786 insertions(+), 128 deletions(-) diff --git a/pumpkin-codegen/src/configured_feature.rs b/pumpkin-codegen/src/configured_feature.rs index cf9e877e4..4fed9e61f 100644 --- a/pumpkin-codegen/src/configured_feature.rs +++ b/pumpkin-codegen/src/configured_feature.rs @@ -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. /// /// # Arguments @@ -994,10 +1018,35 @@ fn value_to_trunk_placer(v: &Value) -> TokenStream { } } "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" => { - 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) }, }; diff --git a/pumpkin-data/src/generated/configured_features_generated.rs b/pumpkin-data/src/generated/configured_features_generated.rs index 08dbe5ce8..a5d042d2e 100644 --- a/pumpkin-data/src/generated/configured_features_generated.rs +++ b/pumpkin-data/src/generated/configured_features_generated.rs @@ -733,8 +733,8 @@ fn build_configured_features() -> std::collections::HashMap std::collections::HashMap std::collections::HashMap i32 { @@ -97,20 +101,24 @@ impl LeaveValidator for CherryFoliagePlacer { dx: i32, y: i32, dz: i32, - radius: i32, + current_radius: i32, _giant_trunk: bool, ) -> bool { if y == -1 - && (dx == radius || dz == radius) + && (dx == current_radius || dz == current_radius) && random.next_f32() < self.wide_bottom_layer_hole_chance { return true; } - let in_radius = dx == radius && dz == radius; - if radius > 2 { - return in_radius - || dx + dz > radius * 2 - 2 && random.next_f32() < self.corner_hole_chance; + + let corner = dx == current_radius && dz == current_radius; + let wide_layer = current_radius > 2; + + 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 } } diff --git a/pumpkin-world/src/generation/feature/features/tree/foliage/mod.rs b/pumpkin-world/src/generation/feature/features/tree/foliage/mod.rs index 926732650..0f46a755a 100644 --- a/pumpkin-world/src/generation/feature/features/tree/foliage/mod.rs +++ b/pumpkin-world/src/generation/feature/features/tree/foliage/mod.rs @@ -7,10 +7,11 @@ use fancy::LargeOakFoliagePlacer; use jungle::JungleFoliagePlacer; use mega_pine::MegaPineFoliagePlacer; use pine::PineFoliagePlacer; +use pumpkin_data::BlockDirection; use pumpkin_data::BlockState; use pumpkin_util::{ math::{int_provider::IntProvider, position::BlockPos, vector3::Vector3}, - random::RandomGenerator, + random::{RandomGenerator, RandomImpl}, }; use random_spread::RandomSpreadFoliagePlacer; @@ -128,12 +129,110 @@ impl FoliagePlacer { chunk: &mut T, pos: BlockPos, block_state: &BlockState, - ) { + ) -> bool { let block = GenerationCache::get_block_state(chunk, &pos.0); if !TreeFeature::can_replace(block.to_state(), block.to_block_id()) { - return; + return false; } chunk.set_block_state(&pos.0, block_state); + true + } + + pub fn is_set( + chunk: &T, + pos: BlockPos, + foliage_provider: &BlockState, + ) -> bool { + GenerationCache::get_block_state(chunk, &pos.0).0 == foliage_provider.id + } + + fn try_place_extension( + 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( + 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); + } + } } } diff --git a/pumpkin-world/src/generation/feature/features/tree/trunk/bending.rs b/pumpkin-world/src/generation/feature/features/tree/trunk/bending.rs index 2b370bfb4..ce6469ea8 100644 --- a/pumpkin-world/src/generation/feature/features/tree/trunk/bending.rs +++ b/pumpkin-world/src/generation/feature/features/tree/trunk/bending.rs @@ -31,48 +31,57 @@ impl BendingTrunkPlacer { below_trunk_provider: &BlockStateProvider, trunk_block: &BlockState, ) -> (Vec, Vec) { + 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( block_registry, chunk, random, - &start_pos.down(), + &pos.down(), 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 nodes = Vec::new(); - for y in 0..height { - if y + 1 >= height + random.next_bounded_i32(2) as u32 { - pos = pos.offset(random_direction.to_offset()); + + for i in 0..=log_height { + if i + 1 >= log_height + random.next_bounded_i32(2) { + pos = pos.offset(direction.to_offset()); } + if placer.place(chunk, &pos, trunk_block) { trunk_poses.push(pos); } - if y >= 1 { + + if i >= self.min_height_for_leaves as i32 { nodes.push(TreeNode { center: pos, foliage_radius: 0, giant_trunk: false, }); } + 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) { trunk_poses.push(pos); } + nodes.push(TreeNode { center: pos, foliage_radius: 0, giant_trunk: false, }); - pos = pos.offset(random_direction.to_offset()); + pos = pos.offset(direction.to_offset()); } + (nodes, trunk_poses) } } diff --git a/pumpkin-world/src/generation/feature/features/tree/trunk/cherry.rs b/pumpkin-world/src/generation/feature/features/tree/trunk/cherry.rs index daf382f34..fd91d01c2 100644 --- a/pumpkin-world/src/generation/feature/features/tree/trunk/cherry.rs +++ b/pumpkin-world/src/generation/feature/features/tree/trunk/cherry.rs @@ -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( + &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, Vec) { + 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( + &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) { + 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) + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/trunk/dark_oak.rs b/pumpkin-world/src/generation/feature/features/tree/trunk/dark_oak.rs index 504f228fc..75bd8a394 100644 --- a/pumpkin-world/src/generation/feature/features/tree/trunk/dark_oak.rs +++ b/pumpkin-world/src/generation/feature/features/tree/trunk/dark_oak.rs @@ -27,120 +27,100 @@ impl DarkOakTrunkPlacer { below_trunk_provider: &BlockStateProvider, trunk_block: &BlockState, ) -> (Vec, Vec) { - let pos = start_pos.down(); - placer.set_dirt(block_registry, chunk, random, &pos, below_trunk_provider); + let below = start_pos.down(); + placer.set_dirt(block_registry, chunk, random, &below, below_trunk_provider); placer.set_dirt( block_registry, chunk, random, - &pos.offset(BlockDirection::East.to_offset()), + &below.east(), below_trunk_provider, ); placer.set_dirt( block_registry, chunk, random, - &pos.offset(BlockDirection::South.to_offset()), + &below.south(), below_trunk_provider, ); placer.set_dirt( block_registry, chunk, random, - &pos.offset(BlockDirection::South.to_offset()) - .offset(BlockDirection::East.to_offset()), + &below.south().east(), below_trunk_provider, ); - let start_y = start_pos.0.y; - let y_height = start_y + height as i32 - 1; - let max_height = height - random.next_bounded_i32(4) as u32; + + 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); + 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 nodes = Vec::new(); - let mut rand = random.next_bounded_i32(3); - - let mut x = pos.0.x; - let mut z = pos.0.z; - - // 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; + for dy in 0..height as i32 { + if dy >= lean_height && lean_steps > 0 { + let offset = lean_direction.to_offset(); + tx += offset.x; + tz += offset.z; + lean_steps -= 1; } - let current_y = start_y + y as i32; - let pos = BlockPos::new(x, current_y, z); - // TODO: support multiple chunks + let yy = start_pos.0.y + dy; + let pos = BlockPos::new(tx, yy, tz); + + // Check if air or leaves at the main 2x2 position let state = GenerationCache::get_block_state(chunk, &pos.0); - 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.offset(BlockDirection::East.to_offset()), - trunk_block, - ) { - trunk_poses.push(pos.offset(BlockDirection::East.to_offset())); - } - 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()), - ); + if TreeFeature::is_air_or_leaves(state.to_state(), state.to_block_id()) { + 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, &pos.south(), trunk_block) { + trunk_poses.push(pos.south()); + } + if placer.try_place(chunk, &pos.east().south(), trunk_block) { + trunk_poses.push(pos.east().south()); + } } } + + let mut nodes = Vec::new(); nodes.push(TreeNode { - center: BlockPos::new(x, y_height, z), + center: BlockPos::new(tx, ey, tz), foliage_radius: 0, giant_trunk: true, }); - for xd in -1..2 { - for zd in -1..2 { - if (0..=1).contains(&xd) && (0..=1).contains(&zd) || random.next_bounded_i32(3) > 0 + + for ox in -1..=2 { + for oz in -1..=2 { + if (!(0..=1).contains(&ox) || !(0..=1).contains(&oz)) + && random.next_bounded_i32(3) <= 0 { - continue; - } - let h = random.next_bounded_i32(3) + 2; - for height in 0..h { - if placer.try_place( - chunk, - &BlockPos::new( - start_pos.0.x + xd, - y_height - height - 1, - start_pos.0.z + zd, - ), - trunk_block, - ) { - trunk_poses.push(BlockPos::new( - start_pos.0.x + xd, - y_height - height - 1, - start_pos.0.z + zd, - )); + let length = random.next_bounded_i32(3) + 2; + + for branch_y in 0..length { + let pos = BlockPos::new( + start_pos.0.x + ox, + ey - branch_y - 1, + start_pos.0.z + oz, + ); + if placer.try_place(chunk, &pos, trunk_block) { + trunk_poses.push(pos); + } } + + 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, - }); } } diff --git a/pumpkin-world/src/generation/feature/features/tree/trunk/forking.rs b/pumpkin-world/src/generation/feature/features/tree/trunk/forking.rs index 1146653d9..8846f0077 100644 --- a/pumpkin-world/src/generation/feature/features/tree/trunk/forking.rs +++ b/pumpkin-world/src/generation/feature/features/tree/trunk/forking.rs @@ -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; + +impl ForkingTrunkPlacer { + #[expect(clippy::too_many_arguments)] + pub fn generate( + 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, Vec) { + 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) + } +} diff --git a/pumpkin-world/src/generation/feature/features/tree/trunk/mod.rs b/pumpkin-world/src/generation/feature/features/tree/trunk/mod.rs index 664d58afb..3b8dd6355 100644 --- a/pumpkin-world/src/generation/feature/features/tree/trunk/mod.rs +++ b/pumpkin-world/src/generation/feature/features/tree/trunk/mod.rs @@ -142,7 +142,16 @@ impl TrunkType { below_trunk_provider, 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( block_registry, placer, @@ -193,8 +202,26 @@ impl TrunkType { below_trunk_provider, trunk_state, ), - Self::UpwardsBranching(_) => (vec![], vec![]), // TODO - Self::Cherry(_) => (vec![], vec![]), // TODO + Self::UpwardsBranching(upwards_branching) => upwards_branching.generate( + 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, + ), } } } diff --git a/pumpkin-world/src/generation/feature/features/tree/trunk/upwards_branching.rs b/pumpkin-world/src/generation/feature/features/tree/trunk/upwards_branching.rs index ace3a77b8..e560f329a 100644 --- a/pumpkin-world/src/generation/feature/features/tree/trunk/upwards_branching.rs +++ b/pumpkin-world/src/generation/feature/features/tree/trunk/upwards_branching.rs @@ -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( + &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, Vec) { + 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, ¤t_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( + &self, + _placer: &TrunkPlacer, + tree_height: u32, + chunk: &mut T, + _random: &mut RandomGenerator, + trunk_state: &BlockState, + nodes: &mut Vec, + logs: &mut Vec, + 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( + &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 + } +}