diff --git a/crates/pumpkin-data/src/generated/configured_features_generated.rs b/crates/pumpkin-data/src/generated/configured_features_generated.rs index 10a55762c..066bfc1d3 100644 --- a/crates/pumpkin-data/src/generated/configured_features_generated.rs +++ b/crates/pumpkin-data/src/generated/configured_features_generated.rs @@ -873,13 +873,169 @@ fn build_configured_features() map.insert( pumpkin_data::configured_feature::ConfiguredFeature::CrimsonFungus, ConfiguredFeature::HugeFungus( - crate::generation::feature::features::huge_fungus::HugeFungusFeature {}, + crate::generation::feature::features::huge_fungus::HugeFungusFeature { + valid_base_block: pumpkin_data::Block::CRIMSON_NYLIUM.default_state, + stem_state: { + let mut props = std::collections::HashMap::new(); + props.insert("axis".to_string(), "y".to_string()); + BlockStateCodec { + name: &pumpkin_data::Block::CRIMSON_STEM, + properties: Some(props), + } + .get_state() + }, + hat_state: pumpkin_data::Block::NETHER_WART_BLOCK.default_state, + decor_state: pumpkin_data::Block::SHROOMLIGHT.default_state, + replaceable_blocks: BlockPredicate::MatchingBlocks(MatchingBlocksBlockPredicate { + offset: OffsetBlocksBlockPredicate { offset: None }, + blocks: MatchingBlocksWrapper::Multiple(vec![ + "minecraft:oak_sapling".to_string(), + "minecraft:spruce_sapling".to_string(), + "minecraft:birch_sapling".to_string(), + "minecraft:jungle_sapling".to_string(), + "minecraft:acacia_sapling".to_string(), + "minecraft:cherry_sapling".to_string(), + "minecraft:dark_oak_sapling".to_string(), + "minecraft:pale_oak_sapling".to_string(), + "minecraft:mangrove_propagule".to_string(), + "minecraft:dandelion".to_string(), + "minecraft:torchflower".to_string(), + "minecraft:poppy".to_string(), + "minecraft:blue_orchid".to_string(), + "minecraft:allium".to_string(), + "minecraft:azure_bluet".to_string(), + "minecraft:red_tulip".to_string(), + "minecraft:orange_tulip".to_string(), + "minecraft:white_tulip".to_string(), + "minecraft:pink_tulip".to_string(), + "minecraft:oxeye_daisy".to_string(), + "minecraft:cornflower".to_string(), + "minecraft:wither_rose".to_string(), + "minecraft:lily_of_the_valley".to_string(), + "minecraft:brown_mushroom".to_string(), + "minecraft:red_mushroom".to_string(), + "minecraft:wheat".to_string(), + "minecraft:sugar_cane".to_string(), + "minecraft:attached_pumpkin_stem".to_string(), + "minecraft:attached_melon_stem".to_string(), + "minecraft:pumpkin_stem".to_string(), + "minecraft:melon_stem".to_string(), + "minecraft:lily_pad".to_string(), + "minecraft:nether_wart".to_string(), + "minecraft:cocoa".to_string(), + "minecraft:carrots".to_string(), + "minecraft:potatoes".to_string(), + "minecraft:chorus_plant".to_string(), + "minecraft:chorus_flower".to_string(), + "minecraft:torchflower_crop".to_string(), + "minecraft:pitcher_crop".to_string(), + "minecraft:beetroots".to_string(), + "minecraft:sweet_berry_bush".to_string(), + "minecraft:warped_fungus".to_string(), + "minecraft:crimson_fungus".to_string(), + "minecraft:weeping_vines".to_string(), + "minecraft:weeping_vines_plant".to_string(), + "minecraft:twisting_vines".to_string(), + "minecraft:twisting_vines_plant".to_string(), + "minecraft:cave_vines".to_string(), + "minecraft:cave_vines_plant".to_string(), + "minecraft:spore_blossom".to_string(), + "minecraft:azalea".to_string(), + "minecraft:flowering_azalea".to_string(), + "minecraft:moss_carpet".to_string(), + "minecraft:pink_petals".to_string(), + "minecraft:wildflowers".to_string(), + "minecraft:big_dripleaf".to_string(), + "minecraft:big_dripleaf_stem".to_string(), + "minecraft:small_dripleaf".to_string(), + ]), + }), + planted: false, + }, ), ); map.insert( pumpkin_data::configured_feature::ConfiguredFeature::CrimsonFungusPlanted, ConfiguredFeature::HugeFungus( - crate::generation::feature::features::huge_fungus::HugeFungusFeature {}, + crate::generation::feature::features::huge_fungus::HugeFungusFeature { + valid_base_block: pumpkin_data::Block::CRIMSON_NYLIUM.default_state, + stem_state: { + let mut props = std::collections::HashMap::new(); + props.insert("axis".to_string(), "y".to_string()); + BlockStateCodec { + name: &pumpkin_data::Block::CRIMSON_STEM, + properties: Some(props), + } + .get_state() + }, + hat_state: pumpkin_data::Block::NETHER_WART_BLOCK.default_state, + decor_state: pumpkin_data::Block::SHROOMLIGHT.default_state, + replaceable_blocks: BlockPredicate::MatchingBlocks(MatchingBlocksBlockPredicate { + offset: OffsetBlocksBlockPredicate { offset: None }, + blocks: MatchingBlocksWrapper::Multiple(vec![ + "minecraft:oak_sapling".to_string(), + "minecraft:spruce_sapling".to_string(), + "minecraft:birch_sapling".to_string(), + "minecraft:jungle_sapling".to_string(), + "minecraft:acacia_sapling".to_string(), + "minecraft:cherry_sapling".to_string(), + "minecraft:dark_oak_sapling".to_string(), + "minecraft:pale_oak_sapling".to_string(), + "minecraft:mangrove_propagule".to_string(), + "minecraft:dandelion".to_string(), + "minecraft:torchflower".to_string(), + "minecraft:poppy".to_string(), + "minecraft:blue_orchid".to_string(), + "minecraft:allium".to_string(), + "minecraft:azure_bluet".to_string(), + "minecraft:red_tulip".to_string(), + "minecraft:orange_tulip".to_string(), + "minecraft:white_tulip".to_string(), + "minecraft:pink_tulip".to_string(), + "minecraft:oxeye_daisy".to_string(), + "minecraft:cornflower".to_string(), + "minecraft:wither_rose".to_string(), + "minecraft:lily_of_the_valley".to_string(), + "minecraft:brown_mushroom".to_string(), + "minecraft:red_mushroom".to_string(), + "minecraft:wheat".to_string(), + "minecraft:sugar_cane".to_string(), + "minecraft:attached_pumpkin_stem".to_string(), + "minecraft:attached_melon_stem".to_string(), + "minecraft:pumpkin_stem".to_string(), + "minecraft:melon_stem".to_string(), + "minecraft:lily_pad".to_string(), + "minecraft:nether_wart".to_string(), + "minecraft:cocoa".to_string(), + "minecraft:carrots".to_string(), + "minecraft:potatoes".to_string(), + "minecraft:chorus_plant".to_string(), + "minecraft:chorus_flower".to_string(), + "minecraft:torchflower_crop".to_string(), + "minecraft:pitcher_crop".to_string(), + "minecraft:beetroots".to_string(), + "minecraft:sweet_berry_bush".to_string(), + "minecraft:warped_fungus".to_string(), + "minecraft:crimson_fungus".to_string(), + "minecraft:weeping_vines".to_string(), + "minecraft:weeping_vines_plant".to_string(), + "minecraft:twisting_vines".to_string(), + "minecraft:twisting_vines_plant".to_string(), + "minecraft:cave_vines".to_string(), + "minecraft:cave_vines_plant".to_string(), + "minecraft:spore_blossom".to_string(), + "minecraft:azalea".to_string(), + "minecraft:flowering_azalea".to_string(), + "minecraft:moss_carpet".to_string(), + "minecraft:pink_petals".to_string(), + "minecraft:wildflowers".to_string(), + "minecraft:big_dripleaf".to_string(), + "minecraft:big_dripleaf_stem".to_string(), + "minecraft:small_dripleaf".to_string(), + ]), + }), + planted: true, + }, ), ); map.insert( @@ -963,7 +1119,26 @@ fn build_configured_features() map.insert( pumpkin_data::configured_feature::ConfiguredFeature::Delta, ConfiguredFeature::DeltaFeature( - crate::generation::feature::features::delta_feature::DeltaFeatureFeature {}, + crate::generation::feature::features::delta_feature::DeltaFeatureFeature { + contents: { + let mut props = std::collections::HashMap::new(); + props.insert("level".to_string(), "0".to_string()); + BlockStateCodec { + name: &pumpkin_data::Block::LAVA, + properties: Some(props), + } + .get_state() + }, + rim: pumpkin_data::Block::MAGMA_BLOCK.default_state, + size: IntProvider::Object(NormalIntProvider::Uniform(UniformIntProvider { + min_inclusive: 3i32, + max_inclusive: 7i32, + })), + rim_size: IntProvider::Object(NormalIntProvider::Uniform(UniformIntProvider { + min_inclusive: 0i32, + max_inclusive: 2i32, + })), + }, ), ); map.insert( @@ -1591,6 +1766,27 @@ fn build_configured_features() .get_state() }, }), + log_length: IntProvider::Object(NormalIntProvider::Uniform(UniformIntProvider { + min_inclusive: 5i32, + max_inclusive: 8i32, + })), + stump_decorators: vec![], + log_decorators: vec![TreeDecorator::AttachedToLogs(AttachedToLogsTreeDecorator { + probability: 0.1f32, + block_provider: BlockStateProvider::Weighted(WeightedBlockStateProvider { + entries: vec![ + Weighted { + data: pumpkin_data::Block::RED_MUSHROOM.default_state, + weight: 2i32, + }, + Weighted { + data: pumpkin_data::Block::BROWN_MUSHROOM.default_state, + weight: 1i32, + }, + ], + }), + directions: vec![BlockDirection::Up], + })], }), ); map.insert( @@ -1607,6 +1803,27 @@ fn build_configured_features() .get_state() }, }), + log_length: IntProvider::Object(NormalIntProvider::Uniform(UniformIntProvider { + min_inclusive: 4i32, + max_inclusive: 11i32, + })), + stump_decorators: vec![TreeDecorator::TrunkVine(TrunkVineTreeDecorator)], + log_decorators: vec![TreeDecorator::AttachedToLogs(AttachedToLogsTreeDecorator { + probability: 0.1f32, + block_provider: BlockStateProvider::Weighted(WeightedBlockStateProvider { + entries: vec![ + Weighted { + data: pumpkin_data::Block::RED_MUSHROOM.default_state, + weight: 2i32, + }, + Weighted { + data: pumpkin_data::Block::BROWN_MUSHROOM.default_state, + weight: 1i32, + }, + ], + }), + directions: vec![BlockDirection::Up], + })], }), ); map.insert( @@ -1623,6 +1840,27 @@ fn build_configured_features() .get_state() }, }), + log_length: IntProvider::Object(NormalIntProvider::Uniform(UniformIntProvider { + min_inclusive: 4i32, + max_inclusive: 7i32, + })), + stump_decorators: vec![TreeDecorator::TrunkVine(TrunkVineTreeDecorator)], + log_decorators: vec![TreeDecorator::AttachedToLogs(AttachedToLogsTreeDecorator { + probability: 0.1f32, + block_provider: BlockStateProvider::Weighted(WeightedBlockStateProvider { + entries: vec![ + Weighted { + data: pumpkin_data::Block::RED_MUSHROOM.default_state, + weight: 2i32, + }, + Weighted { + data: pumpkin_data::Block::BROWN_MUSHROOM.default_state, + weight: 1i32, + }, + ], + }), + directions: vec![BlockDirection::Up], + })], }), ); map.insert( @@ -1639,6 +1877,27 @@ fn build_configured_features() .get_state() }, }), + log_length: IntProvider::Object(NormalIntProvider::Uniform(UniformIntProvider { + min_inclusive: 6i32, + max_inclusive: 10i32, + })), + stump_decorators: vec![], + log_decorators: vec![TreeDecorator::AttachedToLogs(AttachedToLogsTreeDecorator { + probability: 0.1f32, + block_provider: BlockStateProvider::Weighted(WeightedBlockStateProvider { + entries: vec![ + Weighted { + data: pumpkin_data::Block::RED_MUSHROOM.default_state, + weight: 2i32, + }, + Weighted { + data: pumpkin_data::Block::BROWN_MUSHROOM.default_state, + weight: 1i32, + }, + ], + }), + directions: vec![BlockDirection::Up], + })], }), ); map.insert( @@ -1655,6 +1914,27 @@ fn build_configured_features() .get_state() }, }), + log_length: IntProvider::Object(NormalIntProvider::Uniform(UniformIntProvider { + min_inclusive: 5i32, + max_inclusive: 15i32, + })), + stump_decorators: vec![], + log_decorators: vec![TreeDecorator::AttachedToLogs(AttachedToLogsTreeDecorator { + probability: 0.1f32, + block_provider: BlockStateProvider::Weighted(WeightedBlockStateProvider { + entries: vec![ + Weighted { + data: pumpkin_data::Block::RED_MUSHROOM.default_state, + weight: 2i32, + }, + Weighted { + data: pumpkin_data::Block::BROWN_MUSHROOM.default_state, + weight: 1i32, + }, + ], + }), + directions: vec![BlockDirection::Up], + })], }), ); map . insert (pumpkin_data :: configured_feature :: ConfiguredFeature :: FancyOak , 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 :: OAK_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 3u8 , height_rand_a : 11u8 , height_rand_b : 0u8 , r#type : TrunkType :: Fancy (FancyTrunkPlacer) , } , 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 :: OAK_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (2i32) , offset : IntProvider :: Constant (4i32) , r#type : FoliageType :: Fancy (LargeOakFoliagePlacer { height : 4i32 }) } , minimum_size : FeatureSize { min_clipped_height : Some (4u8) , r#type : FeatureSizeType :: TwoLayersFeatureSize (TwoLayersFeatureSize { limit : 0u8 , lower_size : 0u8 , upper_size : 0u8 , }) } , 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 ! [] , root_placer : None , }))) ; @@ -2300,7 +2580,26 @@ fn build_configured_features() map.insert( pumpkin_data::configured_feature::ConfiguredFeature::GlowLichen, ConfiguredFeature::MultifaceGrowth( - crate::generation::feature::features::multiface_growth::MultifaceGrowthFeature {}, + crate::generation::feature::features::multiface_growth::MultifaceGrowthFeature { + place_block: pumpkin_data::BlockId::GLOW_LICHEN, + search_range: 20i32, + can_place_on_floor: false, + can_place_on_ceiling: true, + can_place_on_wall: true, + chance_of_spreading: 0.5f32, + can_be_placed_on: vec![ + pumpkin_data::BlockId::STONE, + pumpkin_data::BlockId::ANDESITE, + pumpkin_data::BlockId::DIORITE, + pumpkin_data::BlockId::GRANITE, + pumpkin_data::BlockId::DRIPSTONE_BLOCK, + pumpkin_data::BlockId::CALCITE, + pumpkin_data::BlockId::TUFF, + pumpkin_data::BlockId::DEEPSLATE, + pumpkin_data::BlockId::SULFUR, + pumpkin_data::BlockId::CINNABAR, + ], + }, ), ); map.insert( @@ -2402,7 +2701,7 @@ fn build_configured_features() ), ); map . insert (pumpkin_data :: configured_feature :: ConfiguredFeature :: JungleBush , 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 :: JUNGLE_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 1u8 , height_rand_a : 0u8 , height_rand_b : 0u8 , r#type : TrunkType :: Straight (StraightTrunkPlacer) , } , 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 :: OAK_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (2i32) , offset : IntProvider :: Constant (1i32) , r#type : FoliageType :: Bush (BushFoliagePlacer { height : 2i32 }) } , minimum_size : FeatureSize { min_clipped_height : None , r#type : FeatureSizeType :: TwoLayersFeatureSize (TwoLayersFeatureSize { limit : 0u8 , lower_size : 0u8 , upper_size : 0u8 , }) } , ignore_vines : false , 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 ! [] , root_placer : None , }))) ; - map . insert (pumpkin_data :: configured_feature :: ConfiguredFeature :: JungleTree , 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 :: JUNGLE_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 4u8 , height_rand_a : 8u8 , height_rand_b : 0u8 , r#type : TrunkType :: Straight (StraightTrunkPlacer) , } , 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 :: JUNGLE_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (2i32) , offset : IntProvider :: Constant (0i32) , r#type : FoliageType :: Blob (BlobFoliagePlacer { height : 3i32 }) } , minimum_size : FeatureSize { min_clipped_height : None , r#type : FeatureSizeType :: TwoLayersFeatureSize (TwoLayersFeatureSize { limit : 1u8 , lower_size : 0u8 , upper_size : 1u8 , }) } , 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 :: Cocoa (CocoaTreeDecorator { }) , TreeDecorator :: TrunkVine (TrunkVineTreeDecorator) , TreeDecorator :: LeaveVine (LeavesVineTreeDecorator { probability : 0.25f32 })] , root_placer : None , }))) ; + map . insert (pumpkin_data :: configured_feature :: ConfiguredFeature :: JungleTree , 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 :: JUNGLE_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 4u8 , height_rand_a : 8u8 , height_rand_b : 0u8 , r#type : TrunkType :: Straight (StraightTrunkPlacer) , } , 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 :: JUNGLE_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (2i32) , offset : IntProvider :: Constant (0i32) , r#type : FoliageType :: Blob (BlobFoliagePlacer { height : 3i32 }) } , minimum_size : FeatureSize { min_clipped_height : None , r#type : FeatureSizeType :: TwoLayersFeatureSize (TwoLayersFeatureSize { limit : 1u8 , lower_size : 0u8 , upper_size : 1u8 , }) } , 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 :: Cocoa (CocoaTreeDecorator { probability : 0.2f32 }) , TreeDecorator :: TrunkVine (TrunkVineTreeDecorator) , TreeDecorator :: LeaveVine (LeavesVineTreeDecorator { probability : 0.25f32 })] , root_placer : None , }))) ; map . insert (pumpkin_data :: configured_feature :: ConfiguredFeature :: JungleTreeNoVine , 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 :: JUNGLE_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 4u8 , height_rand_a : 8u8 , height_rand_b : 0u8 , r#type : TrunkType :: Straight (StraightTrunkPlacer) , } , 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 :: JUNGLE_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (2i32) , offset : IntProvider :: Constant (0i32) , r#type : FoliageType :: Blob (BlobFoliagePlacer { height : 3i32 }) } , minimum_size : FeatureSize { min_clipped_height : None , r#type : FeatureSizeType :: TwoLayersFeatureSize (TwoLayersFeatureSize { limit : 1u8 , lower_size : 0u8 , upper_size : 1u8 , }) } , 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 ! [] , root_placer : None , }))) ; map.insert( pumpkin_data::configured_feature::ConfiguredFeature::Kelp, @@ -2648,7 +2947,7 @@ fn build_configured_features() })), }), ); - map . insert (pumpkin_data :: configured_feature :: ConfiguredFeature :: Mangrove , 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 { probability : 0.125f32 }) , TreeDecorator :: AttachedToLeaves (AttachedToLeavesTreeDecorator { }) , TreeDecorator :: Beehive (BeehiveTreeDecorator { probability : 0.01f32 })] , root_placer : Some (RootPlacer :: Mangrove (MangroveRootPlacer { trunk_offset_y : IntProvider :: Object (NormalIntProvider :: Uniform (UniformIntProvider { min_inclusive : 1i32 , max_inclusive : 3i32 })) , root_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("waterlogged" . to_string () , "false" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: MANGROVE_ROOTS , properties : Some (props) , } . get_state () } }) , above_root_placement : Some (AboveRootPlacement { above_root_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : pumpkin_data :: Block :: MOSS_CARPET . default_state }) , above_root_placement_chance : 0.5f32 , }) , mangrove_root_placement : MangroveRootPlacement { can_grow_through : & pumpkin_data :: tag :: Block :: MINECRAFT_MANGROVE_ROOTS_CAN_GROW_THROUGH . 1 , muddy_roots_in : const { & [pumpkin_data :: BlockId :: MUD . as_u16 () , pumpkin_data :: BlockId :: MUDDY_MANGROVE_ROOTS . as_u16 ()] } , muddy_roots_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 :: MUDDY_MANGROVE_ROOTS , properties : Some (props) , } . get_state () } }) , max_root_width : 8i32 , max_root_length : 15i32 , random_skew_chance : 0.2f32 , } , })) , }))) ; + map . insert (pumpkin_data :: configured_feature :: ConfiguredFeature :: Mangrove , 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 { probability : 0.125f32 }) , TreeDecorator :: AttachedToLeaves (AttachedToLeavesTreeDecorator { probability : 0.14f32 , exclusion_radius_xz : 1i32 , exclusion_radius_y : 0i32 , block_provider : BlockStateProvider :: RandomizedInt (RandomizedIntBlockStateProvider { source : Box :: new (BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("age" . to_string () , "0" . to_string ()) ; props . insert ("hanging" . to_string () , "true" . to_string ()) ; props . insert ("stage" . to_string () , "0" . to_string ()) ; props . insert ("waterlogged" . to_string () , "false" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: MANGROVE_PROPAGULE , properties : Some (props) , } . get_state () } })) , property : "age" . to_string () , values : IntProvider :: Object (NormalIntProvider :: Uniform (UniformIntProvider { min_inclusive : 0i32 , max_inclusive : 4i32 })) , }) , required_empty_blocks : 2i32 , directions : vec ! [BlockDirection :: Down] , }) , TreeDecorator :: Beehive (BeehiveTreeDecorator { probability : 0.01f32 })] , root_placer : Some (RootPlacer :: Mangrove (MangroveRootPlacer { trunk_offset_y : IntProvider :: Object (NormalIntProvider :: Uniform (UniformIntProvider { min_inclusive : 1i32 , max_inclusive : 3i32 })) , root_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("waterlogged" . to_string () , "false" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: MANGROVE_ROOTS , properties : Some (props) , } . get_state () } }) , above_root_placement : Some (AboveRootPlacement { above_root_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : pumpkin_data :: Block :: MOSS_CARPET . default_state }) , above_root_placement_chance : 0.5f32 , }) , mangrove_root_placement : MangroveRootPlacement { can_grow_through : & pumpkin_data :: tag :: Block :: MINECRAFT_MANGROVE_ROOTS_CAN_GROW_THROUGH . 1 , muddy_roots_in : const { & [pumpkin_data :: BlockId :: MUD . as_u16 () , pumpkin_data :: BlockId :: MUDDY_MANGROVE_ROOTS . as_u16 ()] } , muddy_roots_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 :: MUDDY_MANGROVE_ROOTS , properties : Some (props) , } . get_state () } }) , max_root_width : 8i32 , max_root_length : 15i32 , random_skew_chance : 0.2f32 , } , })) , }))) ; map.insert( pumpkin_data::configured_feature::ConfiguredFeature::MangroveVegetation, ConfiguredFeature::RandomSelector(RandomFeature { @@ -2678,8 +2977,8 @@ fn build_configured_features() }), ); map . insert (pumpkin_data :: configured_feature :: ConfiguredFeature :: MegaJungleTree , 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 :: JUNGLE_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 10u8 , height_rand_a : 2u8 , height_rand_b : 19u8 , r#type : TrunkType :: MegaJungle (MegaJungleTrunkPlacer) , } , 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 :: JUNGLE_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (2i32) , offset : IntProvider :: Constant (0i32) , r#type : FoliageType :: Jungle (JungleFoliagePlacer { height : 2i32 }) } , minimum_size : FeatureSize { min_clipped_height : None , r#type : FeatureSizeType :: TwoLayersFeatureSize (TwoLayersFeatureSize { limit : 1u8 , lower_size : 1u8 , upper_size : 2u8 , }) } , ignore_vines : false , 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 :: TrunkVine (TrunkVineTreeDecorator) , TreeDecorator :: LeaveVine (LeavesVineTreeDecorator { probability : 0.25f32 })] , root_placer : None , }))) ; - map . insert (pumpkin_data :: configured_feature :: ConfiguredFeature :: MegaPine , 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 :: SPRUCE_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 13u8 , height_rand_a : 2u8 , height_rand_b : 14u8 , r#type : TrunkType :: Giant (GiantTrunkPlacer) , } , 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 :: SPRUCE_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (0i32) , offset : IntProvider :: Constant (0i32) , r#type : FoliageType :: MegaPine (MegaPineFoliagePlacer { crown_height : IntProvider :: Object (NormalIntProvider :: Uniform (UniformIntProvider { min_inclusive : 3i32 , max_inclusive : 7i32 })) }) } , minimum_size : FeatureSize { min_clipped_height : None , r#type : FeatureSizeType :: TwoLayersFeatureSize (TwoLayersFeatureSize { limit : 1u8 , lower_size : 1u8 , upper_size : 2u8 , }) } , ignore_vines : false , 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 :: AlterGround (AlterGroundTreeDecorator { })] , root_placer : None , }))) ; - map . insert (pumpkin_data :: configured_feature :: ConfiguredFeature :: MegaSpruce , 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 :: SPRUCE_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 13u8 , height_rand_a : 2u8 , height_rand_b : 14u8 , r#type : TrunkType :: Giant (GiantTrunkPlacer) , } , 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 :: SPRUCE_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (0i32) , offset : IntProvider :: Constant (0i32) , r#type : FoliageType :: MegaPine (MegaPineFoliagePlacer { crown_height : IntProvider :: Object (NormalIntProvider :: Uniform (UniformIntProvider { min_inclusive : 13i32 , max_inclusive : 17i32 })) }) } , minimum_size : FeatureSize { min_clipped_height : None , r#type : FeatureSizeType :: TwoLayersFeatureSize (TwoLayersFeatureSize { limit : 1u8 , lower_size : 1u8 , upper_size : 2u8 , }) } , ignore_vines : false , 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 :: AlterGround (AlterGroundTreeDecorator { })] , root_placer : None , }))) ; + map . insert (pumpkin_data :: configured_feature :: ConfiguredFeature :: MegaPine , 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 :: SPRUCE_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 13u8 , height_rand_a : 2u8 , height_rand_b : 14u8 , r#type : TrunkType :: Giant (GiantTrunkPlacer) , } , 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 :: SPRUCE_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (0i32) , offset : IntProvider :: Constant (0i32) , r#type : FoliageType :: MegaPine (MegaPineFoliagePlacer { crown_height : IntProvider :: Object (NormalIntProvider :: Uniform (UniformIntProvider { min_inclusive : 3i32 , max_inclusive : 7i32 })) }) } , minimum_size : FeatureSize { min_clipped_height : None , r#type : FeatureSizeType :: TwoLayersFeatureSize (TwoLayersFeatureSize { limit : 1u8 , lower_size : 1u8 , upper_size : 2u8 , }) } , ignore_vines : false , 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 :: AlterGround (AlterGroundTreeDecorator { provider : BlockStateProvider :: Rule (RuleBasedBlockStateProvider { fallback : None , rules : vec ! [BlockStateRule { if_true : BlockPredicate :: MatchingBlockTag (MatchingBlockTagPredicate { offset : OffsetBlocksBlockPredicate { offset : None } , tag : pumpkin_data :: tag :: Block :: MINECRAFT_BENEATH_TREE_PODZOL_REPLACEABLE , }) , then : BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("snowy" . to_string () , "false" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: PODZOL , properties : Some (props) , } . get_state () } }) }] , }) })] , root_placer : None , }))) ; + map . insert (pumpkin_data :: configured_feature :: ConfiguredFeature :: MegaSpruce , 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 :: SPRUCE_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 13u8 , height_rand_a : 2u8 , height_rand_b : 14u8 , r#type : TrunkType :: Giant (GiantTrunkPlacer) , } , 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 :: SPRUCE_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (0i32) , offset : IntProvider :: Constant (0i32) , r#type : FoliageType :: MegaPine (MegaPineFoliagePlacer { crown_height : IntProvider :: Object (NormalIntProvider :: Uniform (UniformIntProvider { min_inclusive : 13i32 , max_inclusive : 17i32 })) }) } , minimum_size : FeatureSize { min_clipped_height : None , r#type : FeatureSizeType :: TwoLayersFeatureSize (TwoLayersFeatureSize { limit : 1u8 , lower_size : 1u8 , upper_size : 2u8 , }) } , ignore_vines : false , 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 :: AlterGround (AlterGroundTreeDecorator { provider : BlockStateProvider :: Rule (RuleBasedBlockStateProvider { fallback : None , rules : vec ! [BlockStateRule { if_true : BlockPredicate :: MatchingBlockTag (MatchingBlockTagPredicate { offset : OffsetBlocksBlockPredicate { offset : None } , tag : pumpkin_data :: tag :: Block :: MINECRAFT_BENEATH_TREE_PODZOL_REPLACEABLE , }) , then : BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("snowy" . to_string () , "false" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: PODZOL , properties : Some (props) , } . get_state () } }) }] , }) })] , root_placer : None , }))) ; map.insert( pumpkin_data::configured_feature::ConfiguredFeature::Melon, ConfiguredFeature::SimpleBlock(SimpleBlockFeature { @@ -3571,9 +3870,9 @@ fn build_configured_features() schedule_tick: None, }), ); - map . insert (pumpkin_data :: configured_feature :: ConfiguredFeature :: PaleOak , 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 :: PALE_OAK_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 6u8 , height_rand_a : 2u8 , height_rand_b : 1u8 , r#type : TrunkType :: DarkOak (DarkOakTrunkPlacer) , } , 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 :: PALE_OAK_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (0i32) , offset : IntProvider :: Constant (0i32) , r#type : FoliageType :: DarkOak (DarkOakFoliagePlacer) } , minimum_size : FeatureSize { min_clipped_height : None , r#type : FeatureSizeType :: ThreeLayersFeatureSize (ThreeLayersFeatureSize { limit : 1u8 , upper_limit : 1u8 , lower_size : 0u8 , middle_size : 1u8 , 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 :: PaleMoss (PaleMossTreeDecorator { })] , root_placer : None , }))) ; + map . insert (pumpkin_data :: configured_feature :: ConfiguredFeature :: PaleOak , 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 :: PALE_OAK_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 6u8 , height_rand_a : 2u8 , height_rand_b : 1u8 , r#type : TrunkType :: DarkOak (DarkOakTrunkPlacer) , } , 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 :: PALE_OAK_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (0i32) , offset : IntProvider :: Constant (0i32) , r#type : FoliageType :: DarkOak (DarkOakFoliagePlacer) } , minimum_size : FeatureSize { min_clipped_height : None , r#type : FeatureSizeType :: ThreeLayersFeatureSize (ThreeLayersFeatureSize { limit : 1u8 , upper_limit : 1u8 , lower_size : 0u8 , middle_size : 1u8 , 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 :: PaleMoss (PaleMossTreeDecorator { leaves_probability : 0.15f32 , trunk_probability : 0.4f32 , ground_probability : 0.8f32 , })] , root_placer : None , }))) ; map . insert (pumpkin_data :: configured_feature :: ConfiguredFeature :: PaleOakBonemeal , 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 :: PALE_OAK_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 6u8 , height_rand_a : 2u8 , height_rand_b : 1u8 , r#type : TrunkType :: DarkOak (DarkOakTrunkPlacer) , } , 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 :: PALE_OAK_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (0i32) , offset : IntProvider :: Constant (0i32) , r#type : FoliageType :: DarkOak (DarkOakFoliagePlacer) } , minimum_size : FeatureSize { min_clipped_height : None , r#type : FeatureSizeType :: ThreeLayersFeatureSize (ThreeLayersFeatureSize { limit : 1u8 , upper_limit : 1u8 , lower_size : 0u8 , middle_size : 1u8 , 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 ! [] , root_placer : None , }))) ; - map . insert (pumpkin_data :: configured_feature :: ConfiguredFeature :: PaleOakCreaking , 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 :: PALE_OAK_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 6u8 , height_rand_a : 2u8 , height_rand_b : 1u8 , r#type : TrunkType :: DarkOak (DarkOakTrunkPlacer) , } , 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 :: PALE_OAK_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (0i32) , offset : IntProvider :: Constant (0i32) , r#type : FoliageType :: DarkOak (DarkOakFoliagePlacer) } , minimum_size : FeatureSize { min_clipped_height : None , r#type : FeatureSizeType :: ThreeLayersFeatureSize (ThreeLayersFeatureSize { limit : 1u8 , upper_limit : 1u8 , lower_size : 0u8 , middle_size : 1u8 , 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 :: PaleMoss (PaleMossTreeDecorator { }) , TreeDecorator :: CreakingHeart (CreakingHeartTreeDecorator { })] , root_placer : None , }))) ; + map . insert (pumpkin_data :: configured_feature :: ConfiguredFeature :: PaleOakCreaking , 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 :: PALE_OAK_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 6u8 , height_rand_a : 2u8 , height_rand_b : 1u8 , r#type : TrunkType :: DarkOak (DarkOakTrunkPlacer) , } , 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 :: PALE_OAK_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (0i32) , offset : IntProvider :: Constant (0i32) , r#type : FoliageType :: DarkOak (DarkOakFoliagePlacer) } , minimum_size : FeatureSize { min_clipped_height : None , r#type : FeatureSizeType :: ThreeLayersFeatureSize (ThreeLayersFeatureSize { limit : 1u8 , upper_limit : 1u8 , lower_size : 0u8 , middle_size : 1u8 , 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 :: PaleMoss (PaleMossTreeDecorator { leaves_probability : 0.15f32 , trunk_probability : 0.4f32 , ground_probability : 0.8f32 , }) , TreeDecorator :: CreakingHeart (CreakingHeartTreeDecorator { probability : 1f32 })] , root_placer : None , }))) ; map.insert( pumpkin_data::configured_feature::ConfiguredFeature::PatchFire, ConfiguredFeature::SimpleBlock(SimpleBlockFeature { @@ -3608,31 +3907,93 @@ fn build_configured_features() map.insert( pumpkin_data::configured_feature::ConfiguredFeature::PileHay, ConfiguredFeature::BlockPile( - crate::generation::feature::features::block_pile::BlockPileFeature {}, + crate::generation::feature::features::block_pile::BlockPileFeature { + state_provider: BlockStateProvider::Pillar(PillarBlockStateProvider { + state: { + let mut props = std::collections::HashMap::new(); + props.insert("axis".to_string(), "y".to_string()); + BlockStateCodec { + name: &pumpkin_data::Block::HAY_BLOCK, + properties: Some(props), + } + .get_state() + }, + }), + }, ), ); map.insert( pumpkin_data::configured_feature::ConfiguredFeature::PileIce, ConfiguredFeature::BlockPile( - crate::generation::feature::features::block_pile::BlockPileFeature {}, + crate::generation::feature::features::block_pile::BlockPileFeature { + state_provider: BlockStateProvider::Weighted(WeightedBlockStateProvider { + entries: vec![ + Weighted { + data: pumpkin_data::Block::BLUE_ICE.default_state, + weight: 1i32, + }, + Weighted { + data: pumpkin_data::Block::PACKED_ICE.default_state, + weight: 5i32, + }, + ], + }), + }, ), ); map.insert( pumpkin_data::configured_feature::ConfiguredFeature::PileMelon, ConfiguredFeature::BlockPile( - crate::generation::feature::features::block_pile::BlockPileFeature {}, + crate::generation::feature::features::block_pile::BlockPileFeature { + state_provider: BlockStateProvider::Simple(SimpleStateProvider { + state: pumpkin_data::Block::MELON.default_state, + }), + }, ), ); map.insert( pumpkin_data::configured_feature::ConfiguredFeature::PilePumpkin, ConfiguredFeature::BlockPile( - crate::generation::feature::features::block_pile::BlockPileFeature {}, + crate::generation::feature::features::block_pile::BlockPileFeature { + state_provider: BlockStateProvider::Weighted(WeightedBlockStateProvider { + entries: vec![ + Weighted { + data: pumpkin_data::Block::PUMPKIN.default_state, + weight: 19i32, + }, + Weighted { + data: { + let mut props = std::collections::HashMap::new(); + props.insert("facing".to_string(), "north".to_string()); + BlockStateCodec { + name: &pumpkin_data::Block::JACK_O_LANTERN, + properties: Some(props), + } + .get_state() + }, + weight: 1i32, + }, + ], + }), + }, ), ); map.insert( pumpkin_data::configured_feature::ConfiguredFeature::PileSnow, ConfiguredFeature::BlockPile( - crate::generation::feature::features::block_pile::BlockPileFeature {}, + crate::generation::feature::features::block_pile::BlockPileFeature { + state_provider: BlockStateProvider::Simple(SimpleStateProvider { + state: { + let mut props = std::collections::HashMap::new(); + props.insert("layers".to_string(), "1".to_string()); + BlockStateCodec { + name: &pumpkin_data::Block::SNOW, + properties: Some(props), + } + .get_state() + }, + }), + }, ), ); map . insert (pumpkin_data :: configured_feature :: ConfiguredFeature :: Pine , 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 :: SPRUCE_LOG , properties : Some (props) , } . get_state () } }) , trunk_placer : TrunkPlacer { base_height : 6u8 , height_rand_a : 4u8 , height_rand_b : 0u8 , r#type : TrunkType :: Straight (StraightTrunkPlacer) , } , 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 :: SPRUCE_LEAVES , properties : Some (props) , } . get_state () } }) , foliage_placer : FoliagePlacer { radius : IntProvider :: Constant (1i32) , offset : IntProvider :: Constant (1i32) , r#type : FoliageType :: Pine (PineFoliagePlacer { height : IntProvider :: Object (NormalIntProvider :: Uniform (UniformIntProvider { min_inclusive : 3i32 , max_inclusive : 4i32 })) }) } , 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 ! [] , root_placer : None , }))) ; @@ -3881,7 +4242,24 @@ fn build_configured_features() map.insert( pumpkin_data::configured_feature::ConfiguredFeature::SculkVein, ConfiguredFeature::MultifaceGrowth( - crate::generation::feature::features::multiface_growth::MultifaceGrowthFeature {}, + crate::generation::feature::features::multiface_growth::MultifaceGrowthFeature { + place_block: pumpkin_data::BlockId::SCULK_VEIN, + search_range: 20i32, + can_place_on_floor: true, + can_place_on_ceiling: true, + can_place_on_wall: true, + chance_of_spreading: 1f32, + can_be_placed_on: vec![ + pumpkin_data::BlockId::STONE, + pumpkin_data::BlockId::ANDESITE, + pumpkin_data::BlockId::DIORITE, + pumpkin_data::BlockId::GRANITE, + pumpkin_data::BlockId::DRIPSTONE_BLOCK, + pumpkin_data::BlockId::CALCITE, + pumpkin_data::BlockId::TUFF, + pumpkin_data::BlockId::DEEPSLATE, + ], + }, ), ); map.insert( @@ -4267,7 +4645,7 @@ fn build_configured_features() schedule_tick: None, }), ); - map . insert (pumpkin_data :: configured_feature :: ConfiguredFeature :: TallMangrove , 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 { probability : 0.125f32 }) , TreeDecorator :: AttachedToLeaves (AttachedToLeavesTreeDecorator { }) , TreeDecorator :: Beehive (BeehiveTreeDecorator { probability : 0.01f32 })] , root_placer : Some (RootPlacer :: Mangrove (MangroveRootPlacer { trunk_offset_y : IntProvider :: Object (NormalIntProvider :: Uniform (UniformIntProvider { min_inclusive : 3i32 , max_inclusive : 7i32 })) , root_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("waterlogged" . to_string () , "false" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: MANGROVE_ROOTS , properties : Some (props) , } . get_state () } }) , above_root_placement : Some (AboveRootPlacement { above_root_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : pumpkin_data :: Block :: MOSS_CARPET . default_state }) , above_root_placement_chance : 0.5f32 , }) , mangrove_root_placement : MangroveRootPlacement { can_grow_through : & pumpkin_data :: tag :: Block :: MINECRAFT_MANGROVE_ROOTS_CAN_GROW_THROUGH . 1 , muddy_roots_in : const { & [pumpkin_data :: BlockId :: MUD . as_u16 () , pumpkin_data :: BlockId :: MUDDY_MANGROVE_ROOTS . as_u16 ()] } , muddy_roots_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 :: MUDDY_MANGROVE_ROOTS , properties : Some (props) , } . get_state () } }) , max_root_width : 8i32 , max_root_length : 15i32 , random_skew_chance : 0.2f32 , } , })) , }))) ; + map . insert (pumpkin_data :: configured_feature :: ConfiguredFeature :: TallMangrove , 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 { probability : 0.125f32 }) , TreeDecorator :: AttachedToLeaves (AttachedToLeavesTreeDecorator { probability : 0.14f32 , exclusion_radius_xz : 1i32 , exclusion_radius_y : 0i32 , block_provider : BlockStateProvider :: RandomizedInt (RandomizedIntBlockStateProvider { source : Box :: new (BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("age" . to_string () , "0" . to_string ()) ; props . insert ("hanging" . to_string () , "true" . to_string ()) ; props . insert ("stage" . to_string () , "0" . to_string ()) ; props . insert ("waterlogged" . to_string () , "false" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: MANGROVE_PROPAGULE , properties : Some (props) , } . get_state () } })) , property : "age" . to_string () , values : IntProvider :: Object (NormalIntProvider :: Uniform (UniformIntProvider { min_inclusive : 0i32 , max_inclusive : 4i32 })) , }) , required_empty_blocks : 2i32 , directions : vec ! [BlockDirection :: Down] , }) , TreeDecorator :: Beehive (BeehiveTreeDecorator { probability : 0.01f32 })] , root_placer : Some (RootPlacer :: Mangrove (MangroveRootPlacer { trunk_offset_y : IntProvider :: Object (NormalIntProvider :: Uniform (UniformIntProvider { min_inclusive : 3i32 , max_inclusive : 7i32 })) , root_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : { let mut props = std :: collections :: HashMap :: new () ; props . insert ("waterlogged" . to_string () , "false" . to_string ()) ; BlockStateCodec { name : & pumpkin_data :: Block :: MANGROVE_ROOTS , properties : Some (props) , } . get_state () } }) , above_root_placement : Some (AboveRootPlacement { above_root_provider : BlockStateProvider :: Simple (SimpleStateProvider { state : pumpkin_data :: Block :: MOSS_CARPET . default_state }) , above_root_placement_chance : 0.5f32 , }) , mangrove_root_placement : MangroveRootPlacement { can_grow_through : & pumpkin_data :: tag :: Block :: MINECRAFT_MANGROVE_ROOTS_CAN_GROW_THROUGH . 1 , muddy_roots_in : const { & [pumpkin_data :: BlockId :: MUD . as_u16 () , pumpkin_data :: BlockId :: MUDDY_MANGROVE_ROOTS . as_u16 ()] } , muddy_roots_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 :: MUDDY_MANGROVE_ROOTS , properties : Some (props) , } . get_state () } }) , max_root_width : 8i32 , max_root_length : 15i32 , random_skew_chance : 0.2f32 , } , })) , }))) ; map.insert( pumpkin_data::configured_feature::ConfiguredFeature::TreesBadlands, ConfiguredFeature::RandomSelector(RandomFeature { @@ -4728,13 +5106,169 @@ fn build_configured_features() map.insert( pumpkin_data::configured_feature::ConfiguredFeature::WarpedFungus, ConfiguredFeature::HugeFungus( - crate::generation::feature::features::huge_fungus::HugeFungusFeature {}, + crate::generation::feature::features::huge_fungus::HugeFungusFeature { + valid_base_block: pumpkin_data::Block::WARPED_NYLIUM.default_state, + stem_state: { + let mut props = std::collections::HashMap::new(); + props.insert("axis".to_string(), "y".to_string()); + BlockStateCodec { + name: &pumpkin_data::Block::WARPED_STEM, + properties: Some(props), + } + .get_state() + }, + hat_state: pumpkin_data::Block::WARPED_WART_BLOCK.default_state, + decor_state: pumpkin_data::Block::SHROOMLIGHT.default_state, + replaceable_blocks: BlockPredicate::MatchingBlocks(MatchingBlocksBlockPredicate { + offset: OffsetBlocksBlockPredicate { offset: None }, + blocks: MatchingBlocksWrapper::Multiple(vec![ + "minecraft:oak_sapling".to_string(), + "minecraft:spruce_sapling".to_string(), + "minecraft:birch_sapling".to_string(), + "minecraft:jungle_sapling".to_string(), + "minecraft:acacia_sapling".to_string(), + "minecraft:cherry_sapling".to_string(), + "minecraft:dark_oak_sapling".to_string(), + "minecraft:pale_oak_sapling".to_string(), + "minecraft:mangrove_propagule".to_string(), + "minecraft:dandelion".to_string(), + "minecraft:torchflower".to_string(), + "minecraft:poppy".to_string(), + "minecraft:blue_orchid".to_string(), + "minecraft:allium".to_string(), + "minecraft:azure_bluet".to_string(), + "minecraft:red_tulip".to_string(), + "minecraft:orange_tulip".to_string(), + "minecraft:white_tulip".to_string(), + "minecraft:pink_tulip".to_string(), + "minecraft:oxeye_daisy".to_string(), + "minecraft:cornflower".to_string(), + "minecraft:wither_rose".to_string(), + "minecraft:lily_of_the_valley".to_string(), + "minecraft:brown_mushroom".to_string(), + "minecraft:red_mushroom".to_string(), + "minecraft:wheat".to_string(), + "minecraft:sugar_cane".to_string(), + "minecraft:attached_pumpkin_stem".to_string(), + "minecraft:attached_melon_stem".to_string(), + "minecraft:pumpkin_stem".to_string(), + "minecraft:melon_stem".to_string(), + "minecraft:lily_pad".to_string(), + "minecraft:nether_wart".to_string(), + "minecraft:cocoa".to_string(), + "minecraft:carrots".to_string(), + "minecraft:potatoes".to_string(), + "minecraft:chorus_plant".to_string(), + "minecraft:chorus_flower".to_string(), + "minecraft:torchflower_crop".to_string(), + "minecraft:pitcher_crop".to_string(), + "minecraft:beetroots".to_string(), + "minecraft:sweet_berry_bush".to_string(), + "minecraft:warped_fungus".to_string(), + "minecraft:crimson_fungus".to_string(), + "minecraft:weeping_vines".to_string(), + "minecraft:weeping_vines_plant".to_string(), + "minecraft:twisting_vines".to_string(), + "minecraft:twisting_vines_plant".to_string(), + "minecraft:cave_vines".to_string(), + "minecraft:cave_vines_plant".to_string(), + "minecraft:spore_blossom".to_string(), + "minecraft:azalea".to_string(), + "minecraft:flowering_azalea".to_string(), + "minecraft:moss_carpet".to_string(), + "minecraft:pink_petals".to_string(), + "minecraft:wildflowers".to_string(), + "minecraft:big_dripleaf".to_string(), + "minecraft:big_dripleaf_stem".to_string(), + "minecraft:small_dripleaf".to_string(), + ]), + }), + planted: false, + }, ), ); map.insert( pumpkin_data::configured_feature::ConfiguredFeature::WarpedFungusPlanted, ConfiguredFeature::HugeFungus( - crate::generation::feature::features::huge_fungus::HugeFungusFeature {}, + crate::generation::feature::features::huge_fungus::HugeFungusFeature { + valid_base_block: pumpkin_data::Block::WARPED_NYLIUM.default_state, + stem_state: { + let mut props = std::collections::HashMap::new(); + props.insert("axis".to_string(), "y".to_string()); + BlockStateCodec { + name: &pumpkin_data::Block::WARPED_STEM, + properties: Some(props), + } + .get_state() + }, + hat_state: pumpkin_data::Block::WARPED_WART_BLOCK.default_state, + decor_state: pumpkin_data::Block::SHROOMLIGHT.default_state, + replaceable_blocks: BlockPredicate::MatchingBlocks(MatchingBlocksBlockPredicate { + offset: OffsetBlocksBlockPredicate { offset: None }, + blocks: MatchingBlocksWrapper::Multiple(vec![ + "minecraft:oak_sapling".to_string(), + "minecraft:spruce_sapling".to_string(), + "minecraft:birch_sapling".to_string(), + "minecraft:jungle_sapling".to_string(), + "minecraft:acacia_sapling".to_string(), + "minecraft:cherry_sapling".to_string(), + "minecraft:dark_oak_sapling".to_string(), + "minecraft:pale_oak_sapling".to_string(), + "minecraft:mangrove_propagule".to_string(), + "minecraft:dandelion".to_string(), + "minecraft:torchflower".to_string(), + "minecraft:poppy".to_string(), + "minecraft:blue_orchid".to_string(), + "minecraft:allium".to_string(), + "minecraft:azure_bluet".to_string(), + "minecraft:red_tulip".to_string(), + "minecraft:orange_tulip".to_string(), + "minecraft:white_tulip".to_string(), + "minecraft:pink_tulip".to_string(), + "minecraft:oxeye_daisy".to_string(), + "minecraft:cornflower".to_string(), + "minecraft:wither_rose".to_string(), + "minecraft:lily_of_the_valley".to_string(), + "minecraft:brown_mushroom".to_string(), + "minecraft:red_mushroom".to_string(), + "minecraft:wheat".to_string(), + "minecraft:sugar_cane".to_string(), + "minecraft:attached_pumpkin_stem".to_string(), + "minecraft:attached_melon_stem".to_string(), + "minecraft:pumpkin_stem".to_string(), + "minecraft:melon_stem".to_string(), + "minecraft:lily_pad".to_string(), + "minecraft:nether_wart".to_string(), + "minecraft:cocoa".to_string(), + "minecraft:carrots".to_string(), + "minecraft:potatoes".to_string(), + "minecraft:chorus_plant".to_string(), + "minecraft:chorus_flower".to_string(), + "minecraft:torchflower_crop".to_string(), + "minecraft:pitcher_crop".to_string(), + "minecraft:beetroots".to_string(), + "minecraft:sweet_berry_bush".to_string(), + "minecraft:warped_fungus".to_string(), + "minecraft:crimson_fungus".to_string(), + "minecraft:weeping_vines".to_string(), + "minecraft:weeping_vines_plant".to_string(), + "minecraft:twisting_vines".to_string(), + "minecraft:twisting_vines_plant".to_string(), + "minecraft:cave_vines".to_string(), + "minecraft:cave_vines_plant".to_string(), + "minecraft:spore_blossom".to_string(), + "minecraft:azalea".to_string(), + "minecraft:flowering_azalea".to_string(), + "minecraft:moss_carpet".to_string(), + "minecraft:pink_petals".to_string(), + "minecraft:wildflowers".to_string(), + "minecraft:big_dripleaf".to_string(), + "minecraft:big_dripleaf_stem".to_string(), + "minecraft:small_dripleaf".to_string(), + ]), + }), + planted: true, + }, ), ); map.insert( diff --git a/crates/pumpkin-world/src/chunk_system/generation_cache.rs b/crates/pumpkin-world/src/chunk_system/generation_cache.rs index 7dbc55756..6d9b68757 100644 --- a/crates/pumpkin-world/src/chunk_system/generation_cache.rs +++ b/crates/pumpkin-world/src/chunk_system/generation_cache.rs @@ -454,6 +454,10 @@ impl GenerationCache for Cache { fn is_air(&self, local_pos: &Vector3) -> bool { is_air(GenerationCache::get_block_state(self, local_pos)) } + + fn get_sea_level(&self) -> i32 { + self.get_center_chunk().get_sea_level() + } } impl Cache { diff --git a/crates/pumpkin-world/src/generation/block_state_provider.rs b/crates/pumpkin-world/src/generation/block_state_provider.rs index 96ef9285d..72e2c4b49 100644 --- a/crates/pumpkin-world/src/generation/block_state_provider.rs +++ b/crates/pumpkin-world/src/generation/block_state_provider.rs @@ -38,8 +38,9 @@ impl BlockStateProvider { Self::Simple(provider) => Some(provider.get(pos)), Self::Weighted(provider) => Some(provider.get(random)), Self::DualNoise(provider) => Some(provider.get(pos)), - Self::Pillar(provider) => Some(provider.get(pos)), - Self::RandomizedInt(_) | Self::Rule(_) => None, + Self::Pillar(provider) => Some(provider.get_with_random(random, pos)), + Self::RandomizedInt(provider) => provider.get_for_bonemeal(random, pos), + Self::Rule(_) => None, } } @@ -55,10 +56,8 @@ impl BlockStateProvider { Self::NoiseThreshold(provider) => provider.get(random, pos), Self::NoiseProvider(provider) => provider.get(pos), Self::DualNoise(provider) => provider.get(pos), - Self::Pillar(provider) => provider.get(pos), - Self::RandomizedInt(provider) => { - provider.source.get_state_for_world(world, random, pos) - } + Self::Pillar(provider) => provider.get_with_random(random, pos), + Self::RandomizedInt(provider) => provider.get_state_for_world(world, random, pos), Self::Rule(provider) => { for rule in &provider.rules { if rule.if_true.test_world(world, None, &pos) { @@ -86,7 +85,7 @@ impl BlockStateProvider { Self::Simple(provider) => provider.get(pos), Self::Weighted(provider) => provider.get(random), Self::DualNoise(provider) => provider.get(pos), - Self::Pillar(provider) => provider.get(pos), + Self::Pillar(provider) => provider.get_with_random(random, pos), Self::RandomizedInt(provider) => provider.get(random, pos, chunk, block_registry), Self::Rule(provider) => provider.get(block_registry, chunk, random, pos), } @@ -170,6 +169,63 @@ pub struct RandomizedIntBlockStateProvider { } impl RandomizedIntBlockStateProvider { + fn apply_property( + &self, + base_state: &'static BlockState, + random: &mut RandomGenerator, + ) -> &'static BlockState { + let value = self.values.get(random); + let val_str = value.to_string(); + let block = Block::from_id(base_state.id.to_block_id()); + block.properties(base_state.id).map_or_else( + || { + let props = [(self.property.as_str(), val_str.as_str())]; + let new_state_id = block.from_properties(&props).to_state_id(block); + BlockState::from_id(new_state_id) + }, + |props_source| { + let mut props: Vec<(&str, &str)> = props_source + .to_props() + .iter() + .map(|(k, v)| (*k, *v)) + .collect(); + let mut found = false; + for (k, v) in &mut props { + if *k == self.property { + *v = &val_str; + found = true; + break; + } + } + if !found { + props.push((&self.property, &val_str)); + } + let new_state_id = block.from_properties(&props).to_state_id(block); + BlockState::from_id(new_state_id) + }, + ) + } + + pub fn get_for_bonemeal( + &self, + random: &mut RandomGenerator, + pos: BlockPos, + ) -> Option<&'static BlockState> { + self.source + .get_for_bonemeal(random, pos) + .map(|state| self.apply_property(state, random)) + } + + pub fn get_state_for_world( + &self, + world: &dyn BlockAccessor, + random: &mut RandomGenerator, + pos: BlockPos, + ) -> &'static BlockState { + let state = self.source.get_state_for_world(world, random, pos); + self.apply_property(state, random) + } + pub fn get( &self, random: &mut RandomGenerator, @@ -177,8 +233,8 @@ impl RandomizedIntBlockStateProvider { chunk: &T, block_registry: &dyn WorldPortalExt, ) -> &'static BlockState { - // TODO - self.source.get(random, pos, chunk, block_registry) + let state = self.source.get(random, pos, chunk, block_registry); + self.apply_property(state, random) } } @@ -189,7 +245,33 @@ pub struct PillarBlockStateProvider { impl PillarBlockStateProvider { #[must_use] pub const fn get(&self, _pos: BlockPos) -> &'static BlockState { - // TODO: random axis + self.state + } + + #[must_use] + pub fn get_with_random( + &self, + random: &mut RandomGenerator, + _pos: BlockPos, + ) -> &'static BlockState { + let axis_str = match random.next_bounded_i32(3) { + 0 => "x", + 1 => "y", + _ => "z", + }; + let block = Block::from_id(self.state.id.to_block_id()); + if let Some(props_source) = block.properties(self.state.id) { + let prop_list = props_source.to_props(); + if prop_list.iter().any(|(k, _)| *k == "axis") { + let new_props: Vec<(&str, &str)> = prop_list + .iter() + .map(|(k, v)| (*k, if *k == "axis" { axis_str } else { *v })) + .collect(); + let new_props_obj = block.from_properties(new_props.as_slice()); + let new_state_id = new_props_obj.to_state_id(block); + return BlockState::from_id(new_state_id); + } + } self.state } } diff --git a/crates/pumpkin-world/src/generation/feature/configured_features.rs b/crates/pumpkin-world/src/generation/feature/configured_features.rs index dd970e4b9..44537a29c 100644 --- a/crates/pumpkin-world/src/generation/feature/configured_features.rs +++ b/crates/pumpkin-world/src/generation/feature/configured_features.rs @@ -422,21 +422,45 @@ impl ConfiguredFeature { Self::BonusChest(_feature) => { BonusChestFeature::generate(chunk, min_y, height, feature_name, random, pos) } - Self::DeltaFeature(_feature) => { - DeltaFeatureFeature::generate(chunk, min_y, height, feature_name, random, pos) + Self::DeltaFeature(feature) => { + feature.generate(chunk, min_y, height, feature_name, random, pos) } + Self::BlockPile(feature) => feature.generate( + chunk, + block_registry, + min_y, + height, + feature_name, + random, + pos, + ), Self::DripstoneCluster(feature) => feature.generate(chunk, pos), Self::LargeDripstone(feature) => feature.generate(chunk, random, pos), Self::EndGateway(feature) => feature.generate(chunk, pos), Self::FillLayer(feature) => { feature.generate(chunk, min_y, height, feature_name, random, pos) } + Self::FallenTree(feature) => feature.generate( + chunk, + block_registry, + min_y, + height, + feature_name, + random, + pos, + ), Self::HugeBrownMushroom(feature) => { feature.generate(chunk, min_y, height, feature_name, random, pos) } - Self::HugeFungus(feature) => { - feature.generate(chunk, min_y, height, feature_name, random, pos) - } + Self::HugeFungus(feature) => feature.generate( + chunk, + block_registry, + min_y, + height, + feature_name, + random, + pos, + ), Self::HugeRedMushroom(feature) => { feature.generate(chunk, min_y, height, feature_name, random, pos) } @@ -452,7 +476,7 @@ impl ConfiguredFeature { Self::WeepingVines(feature) => { feature.generate(chunk, min_y, height, feature_name, random, pos) } - _ => false, + Self::NoOp => false, } } } diff --git a/crates/pumpkin-world/src/generation/feature/features/bamboo.rs b/crates/pumpkin-world/src/generation/feature/features/bamboo.rs index 681b8549a..7e20abd98 100644 --- a/crates/pumpkin-world/src/generation/feature/features/bamboo.rs +++ b/crates/pumpkin-world/src/generation/feature/features/bamboo.rs @@ -23,65 +23,88 @@ impl BambooFeature { block_registry: &dyn WorldPortalExt, _min_y: i8, _height: u16, - _feature: pumpkin_data::placed_feature::PlacedFeature, // This placed feature + _feature: pumpkin_data::placed_feature::PlacedFeature, random: &mut RandomGenerator, pos: BlockPos, ) -> bool { - let mut i = 0; - if chunk.is_air(&pos.0) { - if block_registry.can_place_at(&Block::BAMBOO, Block::BAMBOO.default_state, chunk, &pos) - { - let height = random.next_bounded_i32(12) + 5; - if random.next_f32() < self.probability { - let rnd = random.next_bounded_i32(4) + 1; - for x in pos.0.x - rnd..pos.0.x + rnd { - for z in pos.0.z - rnd..pos.0.z + rnd { - let block_below = - BlockPos::new(x, chunk.top_block_height_exclusive(x, z) - 1, z); - let block = GenerationCache::get_block_state(chunk, &block_below.0); - if !block.to_block_id().has_tag(tag::Block::MINECRAFT_DIRT) { - continue; + let mut placed = 0; + if chunk.is_air(&pos.0) + && block_registry.can_place_at(&Block::BAMBOO, Block::BAMBOO.default_state, chunk, &pos) + { + let height = random.next_bounded_i32(12) + 5; + if random.next_f32() < self.probability { + let r = random.next_bounded_i32(4) + 1; + for xx in pos.0.x - r..=pos.0.x + r { + for zz in pos.0.z - r..=pos.0.z + r { + let xd = xx - pos.0.x; + let zd = zz - pos.0.z; + if xd * xd + zd * zd <= r * r { + let podzol_y = chunk.top_block_height_exclusive(xx, zz) - 1; + let podzol_pos = BlockPos::new(xx, podzol_y, zz); + let block_id = GenerationCache::get_block_state(chunk, &podzol_pos.0) + .to_block_id(); + if block_id + .has_tag(tag::Block::MINECRAFT_BENEATH_BAMBOO_PODZOL_REPLACEABLE) + { + chunk.set_block_state(&podzol_pos.0, Block::PODZOL.default_state); } - chunk.set_block_state(&block_below.0, Block::PODZOL.default_state); } } } - let mut bpos = pos; - let bamboo = Block::BAMBOO.default_state; - for _ in 0..height { - if chunk.is_air(&bpos.0) { - chunk.set_block_state(&bpos.0, bamboo); - bpos = bpos.up(); - } else { - break; - } - } - // Top block - if bpos.0.y - pos.0.y >= 3 { - let mut props = BambooLikeProperties::default(&Block::BAMBOO); - props.leaves = BambooLeaves::Large; - props.stage = 1; + } - chunk.set_block_state( - &bpos.0, - BlockState::from_id(props.to_state_id(&Block::BAMBOO)), - ); - props.stage = 0; + let trunk_props = BambooLikeProperties { + age: 1, + leaves: BambooLeaves::None, + stage: 0, + }; + let trunk_state = BlockState::from_id(trunk_props.to_state_id(&Block::BAMBOO)); - chunk.set_block_state( - &bpos.down().0, - BlockState::from_id(props.to_state_id(&Block::BAMBOO)), - ); - props.leaves = BambooLeaves::Small; - - chunk.set_block_state( - &bpos.down().down().0, - BlockState::from_id(props.to_state_id(&Block::BAMBOO)), - ); + let mut bpos = pos; + for _ in 0..height { + if chunk.is_air(&bpos.0) { + chunk.set_block_state(&bpos.0, trunk_state); + bpos = bpos.up(); + } else { + break; } } - i += 1; + + if bpos.0.y - pos.0.y >= 3 { + let final_large_props = BambooLikeProperties { + age: 1, + leaves: BambooLeaves::Large, + stage: 1, + }; + chunk.set_block_state( + &bpos.down().0, + BlockState::from_id(final_large_props.to_state_id(&Block::BAMBOO)), + ); + + let top_large_props = BambooLikeProperties { + age: 1, + leaves: BambooLeaves::Large, + stage: 0, + }; + chunk.set_block_state( + &bpos.down().down().0, + BlockState::from_id(top_large_props.to_state_id(&Block::BAMBOO)), + ); + + let top_small_props = BambooLikeProperties { + age: 1, + leaves: BambooLeaves::Small, + stage: 0, + }; + chunk.set_block_state( + &bpos.down().down().down().0, + BlockState::from_id(top_small_props.to_state_id(&Block::BAMBOO)), + ); + } + + placed += 1; } - i > 0 + + placed > 0 } } diff --git a/crates/pumpkin-world/src/generation/feature/features/basalt_columns.rs b/crates/pumpkin-world/src/generation/feature/features/basalt_columns.rs index ff17efefe..a5b996bdb 100644 --- a/crates/pumpkin-world/src/generation/feature/features/basalt_columns.rs +++ b/crates/pumpkin-world/src/generation/feature/features/basalt_columns.rs @@ -6,7 +6,6 @@ use pumpkin_util::{ use crate::{generation::proto_chunk::GenerationCache, world::WorldPortalExt}; -const LAVA_SEA_LEVEL: i32 = 32; // TODO: use getSeaLevel() instead of hardcoding this const CLUSTERED_REACH: i32 = 5; const UNCLUSTERED_REACH: i32 = 8; const CLUSTERED_SIZE: i32 = 50; @@ -143,7 +142,7 @@ fn find_air(chunk: &T, pos: BlockPos, mut limit: i32) -> Opt fn is_air_or_lava_ocean(chunk: &T, pos: BlockPos) -> bool { let state = GenerationCache::get_block_state(chunk, &pos.0); - is_air(state) || (state.to_block_id() == Block::LAVA.id && pos.0.y <= LAVA_SEA_LEVEL) + is_air(state) || (state.to_block_id() == Block::LAVA.id && pos.0.y <= chunk.get_sea_level()) } const fn is_cannot_place_on(id: BlockId) -> bool { diff --git a/crates/pumpkin-world/src/generation/feature/features/block_pile.rs b/crates/pumpkin-world/src/generation/feature/features/block_pile.rs index 399de19e3..635ce3aa3 100644 --- a/crates/pumpkin-world/src/generation/feature/features/block_pile.rs +++ b/crates/pumpkin-world/src/generation/feature/features/block_pile.rs @@ -1 +1,75 @@ -pub struct BlockPileFeature; +use pumpkin_data::{Block, BlockDirection}; +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; + +use crate::generation::block_state_provider::BlockStateProvider; +use crate::generation::proto_chunk::GenerationCache; +use crate::world::WorldPortalExt; + +pub struct BlockPileFeature { + pub state_provider: BlockStateProvider, +} + +impl BlockPileFeature { + #[expect(clippy::too_many_arguments)] + pub fn generate( + &self, + chunk: &mut T, + block_registry: &dyn WorldPortalExt, + min_y: i8, + _height: u16, + _feature: pumpkin_data::placed_feature::PlacedFeature, + random: &mut RandomGenerator, + pos: BlockPos, + ) -> bool { + if pos.0.y < min_y as i32 + 5 { + return false; + } + + let xr = 2 + random.next_bounded_i32(2); + let zr = 2 + random.next_bounded_i32(2); + + for block_pos in BlockPos::iterate(pos.add(-xr, 0, -zr), pos.add(xr, 1, zr)) { + let xd = pos.0.x - block_pos.0.x; + let zd = pos.0.z - block_pos.0.z; + if (xd * xd + zd * zd) as f32 <= random.next_f32() * 10.0 - random.next_f32() * 6.0 + || random.next_f32() < 0.031 + { + self.try_place_block(chunk, block_registry, block_pos, random); + } + } + + true + } + + fn may_place_on( + chunk: &T, + block_pos: BlockPos, + random: &mut RandomGenerator, + ) -> bool { + let below = block_pos.down(); + let below_state = GenerationCache::get_block_state(chunk, &below.0).to_state(); + if below_state.id == Block::DIRT_PATH.default_state.id { + random.next_bool() + } else { + below_state.is_side_solid(BlockDirection::Up) + } + } + + fn try_place_block( + &self, + chunk: &mut T, + block_registry: &dyn WorldPortalExt, + block_pos: BlockPos, + random: &mut RandomGenerator, + ) { + if chunk.is_air(&block_pos.0) && Self::may_place_on(chunk, block_pos, random) { + let state = self + .state_provider + .get(random, block_pos, chunk, block_registry); + chunk.set_block_state(&block_pos.0, state); + } + } +} diff --git a/crates/pumpkin-world/src/generation/feature/features/blue_ice.rs b/crates/pumpkin-world/src/generation/feature/features/blue_ice.rs index d18a42d40..fe18c239e 100644 --- a/crates/pumpkin-world/src/generation/feature/features/blue_ice.rs +++ b/crates/pumpkin-world/src/generation/feature/features/blue_ice.rs @@ -1,4 +1,4 @@ -use pumpkin_data::Block; +use pumpkin_data::{Block, BlockDirection}; use pumpkin_util::{ math::position::BlockPos, random::{RandomGenerator, RandomImpl}, @@ -6,8 +6,6 @@ use pumpkin_util::{ use crate::generation::proto_chunk::GenerationCache; -const SEA_LEVEL: i32 = 63; // TODO: use getSeaLevel() instead of hardcoding - pub struct BlueIceFeature; impl BlueIceFeature { @@ -16,7 +14,7 @@ impl BlueIceFeature { random: &mut RandomGenerator, pos: BlockPos, ) -> bool { - if pos.0.y >= SEA_LEVEL - 1 { + if pos.0.y > chunk.get_sea_level() - 1 { return false; } @@ -27,64 +25,57 @@ impl BlueIceFeature { return false; } - let mut has_ice_neighbor = false; - for neighbor_pos in [ - pos.up(), - pos.down(), - pos.north(), - pos.south(), - pos.east(), - pos.west(), - ] { - if GenerationCache::get_block_state(chunk, &neighbor_pos.0).to_state() - == Block::ICE.default_state - { - has_ice_neighbor = true; - break; + let mut found_packed_ice = false; + for dir in BlockDirection::all() { + if dir != BlockDirection::Down { + let neighbor_pos = pos.offset(dir.to_offset()); + if GenerationCache::get_block_state(chunk, &neighbor_pos.0).to_block_id() + == Block::PACKED_ICE.id + { + found_packed_ice = true; + break; + } } } - if !has_ice_neighbor { + if !found_packed_ice { return false; } chunk.set_block_state(&pos.0, Block::BLUE_ICE.default_state); for _ in 0..200 { - let offset_x = random.next_bounded_i32(8) - random.next_bounded_i32(8); - let offset_y = random.next_bounded_i32(8) - random.next_bounded_i32(8); - let offset_z = random.next_bounded_i32(8) - random.next_bounded_i32(8); - let target_pos = pos.add(offset_x, offset_y, offset_z); - - if chunk.out_of_height(target_pos.0.y as i16) { - continue; + let y_off = random.next_bounded_i32(5) - random.next_bounded_i32(6); + let mut xz_diff = 3; + if y_off < 2 { + xz_diff += y_off / 2; } - let target_state = GenerationCache::get_block_state(chunk, &target_pos.0).to_state(); - if target_state.is_air() - || target_state == Block::WATER.default_state - || target_state == Block::ICE.default_state - || target_state == Block::PACKED_ICE.default_state - { - let mut has_blue_ice_neighbor = false; - for neighbor_pos in [ - target_pos.up(), - target_pos.down(), - target_pos.north(), - target_pos.south(), - target_pos.east(), - target_pos.west(), - ] { - if GenerationCache::get_block_state(chunk, &neighbor_pos.0).to_state() - == Block::BLUE_ICE.default_state - { - has_blue_ice_neighbor = true; - break; - } + if xz_diff >= 1 { + let place_pos = pos.add( + random.next_bounded_i32(xz_diff) - random.next_bounded_i32(xz_diff), + y_off, + random.next_bounded_i32(xz_diff) - random.next_bounded_i32(xz_diff), + ); + if chunk.out_of_height(place_pos.0.y as i16) { + continue; } - if has_blue_ice_neighbor { - chunk.set_block_state(&target_pos.0, Block::BLUE_ICE.default_state); + let place_state = GenerationCache::get_block_state(chunk, &place_pos.0).to_state(); + if place_state.is_air() + || place_state == Block::WATER.default_state + || place_state == Block::PACKED_ICE.default_state + || place_state == Block::ICE.default_state + { + for dir in BlockDirection::all() { + let rel_pos = place_pos.offset(dir.to_offset()); + if GenerationCache::get_block_state(chunk, &rel_pos.0).to_block_id() + == Block::BLUE_ICE.id + { + chunk.set_block_state(&place_pos.0, Block::BLUE_ICE.default_state); + break; + } + } } } } diff --git a/crates/pumpkin-world/src/generation/feature/features/bonus_chest.rs b/crates/pumpkin-world/src/generation/feature/features/bonus_chest.rs index 641f96d37..2180f451d 100644 --- a/crates/pumpkin-world/src/generation/feature/features/bonus_chest.rs +++ b/crates/pumpkin-world/src/generation/feature/features/bonus_chest.rs @@ -1,5 +1,10 @@ -use pumpkin_data::Block; -use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; +use pumpkin_data::{Block, BlockDirection}; +use pumpkin_nbt::compound::NbtCompound; +use pumpkin_util::{ + HeightMap, + math::{position::BlockPos, vector3::Vector3}, + random::{RandomGenerator, RandomImpl}, +}; use crate::generation::proto_chunk::GenerationCache; @@ -11,28 +16,57 @@ impl BonusChestFeature { _min_y: i8, _height: u16, _feature: pumpkin_data::placed_feature::PlacedFeature, - _random: &mut RandomGenerator, + random: &mut RandomGenerator, pos: BlockPos, ) -> bool { - let mut block_pos = pos; - while chunk.is_air(&block_pos.0) && block_pos.0.y > chunk.bottom_y() as i32 + 2 { - block_pos = block_pos.down(); + let chunk_min_x = (pos.0.x >> 4) << 4; + let chunk_min_z = (pos.0.z >> 4) << 4; + + let mut x_poses: Vec = (chunk_min_x..=chunk_min_x + 15).collect(); + let mut z_poses: Vec = (chunk_min_z..=chunk_min_z + 15).collect(); + + for i in (1..x_poses.len()).rev() { + let j = random.next_bounded_i32(i as i32 + 1) as usize; + x_poses.swap(i, j); + } + for i in (1..z_poses.len()).rev() { + let j = random.next_bounded_i32(i as i32 + 1) as usize; + z_poses.swap(i, j); } - let chest_pos = block_pos.up(); - if !chunk.is_air(&chest_pos.0) { - return false; - } + for &x in &x_poses { + for &z in &z_poses { + let y = chunk.get_top_y(&HeightMap::MotionBlockingNoLeaves, x, z); + let chest_pos = Vector3::new(x, y, z); + if chunk.is_air(&chest_pos) { + chunk.set_block_state(&chest_pos, Block::CHEST.default_state); - chunk.set_block_state(&chest_pos.0, Block::CHEST.default_state); + let loot_seed = random.next_i64(); + let mut chest_nbt = NbtCompound::new(); + chest_nbt.put_string("id", "minecraft:chest".to_string()); + chest_nbt.put_int("x", chest_pos.x); + chest_nbt.put_int("y", chest_pos.y); + chest_nbt.put_int("z", chest_pos.z); + chest_nbt.put_string( + "LootTable", + "minecraft:chests/spawn_bonus_chest".to_string(), + ); + chest_nbt.put_long("LootTableSeed", loot_seed); + chunk.add_block_entity(&chest_pos, chest_nbt); - let torch_offsets = [(1, 0, 0), (-1, 0, 0), (0, 0, 1), (0, 0, -1)]; - for &(dx, dy, dz) in &torch_offsets { - let t_pos = BlockPos::new(chest_pos.0.x + dx, chest_pos.0.y + dy, chest_pos.0.z + dz); - if chunk.is_air(&t_pos.0) { - chunk.set_block_state(&t_pos.0, Block::TORCH.default_state); + for dir in BlockDirection::horizontal() { + let torch_pos = chest_pos.add(&dir.to_offset()); + let below_torch = torch_pos.add(&Vector3::new(0, -1, 0)); + if !chunk.is_air(&below_torch) && chunk.is_air(&torch_pos) { + chunk.set_block_state(&torch_pos, Block::TORCH.default_state); + } + } + + return true; + } } } - true + + false } } diff --git a/crates/pumpkin-world/src/generation/feature/features/coral/coral_claw.rs b/crates/pumpkin-world/src/generation/feature/features/coral/coral_claw.rs index 281e43580..4005cf108 100644 --- a/crates/pumpkin-world/src/generation/feature/features/coral/coral_claw.rs +++ b/crates/pumpkin-world/src/generation/feature/features/coral/coral_claw.rs @@ -16,53 +16,69 @@ impl CoralClawFeature { random: &mut RandomGenerator, pos: BlockPos, ) -> bool { - // First lets get a random coral let block = CoralFeature::get_random_tag_entry(tag::Block::MINECRAFT_CORAL_BLOCKS, random); if !CoralFeature::generate_coral_piece(chunk, block_registry, random, block, pos) { return false; } - let direction = BlockDirection::random_horizontal(random); - let i = random.next_bounded_i32(2) + 2; - // TODO: vanilla iterates the first `i` of Util.toShuffledList([direction, - // direction.getClockWise(), direction.getCounterClockWise()], random) — the - // shuffle consumes RNG draws and the opposite of `direction` is never visited. - let directions = BlockDirection::horizontal_worldgen() - .into_iter() - .take(i as usize); - 'block0: for direction2 in directions { - let mut pos = pos; - let j = random.next_bounded_i32(2) + 1; - pos = pos.offset(direction2.to_offset()); + let claw_dir = + BlockDirection::from_cardinal_direction(BlockDirection::random_horizontal(random)); + let n_branches = random.next_bounded_i32(2) + 2; + let mut possible_directions = [ + claw_dir, + claw_dir.rotate_clockwise(), + claw_dir.rotate_counter_clockwise(), + ]; + for i in (1..possible_directions.len()).rev() { + let j = random.next_bounded_i32(i as i32 + 1) as usize; + possible_directions.swap(i, j); + } - let branch_direction; + for &branch_direction in possible_directions.iter().take(n_branches as usize) { + let mut mut_pos = pos; + let sideway_length = random.next_bounded_i32(2) + 1; + mut_pos = mut_pos.offset(branch_direction.to_offset()); - let k = if direction2 == direction { - branch_direction = direction; - random.next_bounded_i32(3) + 2 + let (segment_direction, inway_length) = if branch_direction == claw_dir { + (claw_dir, random.next_bounded_i32(3) + 2) } else { - pos = pos.up(); - //let _directions = [direction2, BlockDirection::Up]; - branch_direction = direction2; // TODO: make this random - random.next_bounded_i32(3) + 5 + mut_pos = mut_pos.up(); + let seg_dir = if random.next_bool() { + branch_direction + } else { + BlockDirection::Up + }; + (seg_dir, random.next_bounded_i32(3) + 3) }; - for _ in 0..j { - if !CoralFeature::generate_coral_piece(chunk, block_registry, random, block, pos) { + for _ in 0..sideway_length { + if !CoralFeature::generate_coral_piece( + chunk, + block_registry, + random, + block, + mut_pos, + ) { break; } - pos = pos.offset(branch_direction.to_offset()); + mut_pos = mut_pos.offset(segment_direction.to_offset()); } - pos = pos.offset(branch_direction.to_offset()); - pos = pos.up(); + mut_pos = mut_pos.offset(segment_direction.opposite().to_offset()); + mut_pos = mut_pos.up(); - for _l in 0..k { - pos = pos.offset(direction.opposite().to_offset()); - if !CoralFeature::generate_coral_piece(chunk, block_registry, random, block, pos) { - continue 'block0; + for _ in 0..inway_length { + mut_pos = mut_pos.offset(claw_dir.to_offset()); + if !CoralFeature::generate_coral_piece( + chunk, + block_registry, + random, + block, + mut_pos, + ) { + break; } if random.next_f32() < 0.25 { - pos = pos.up(); + mut_pos = mut_pos.up(); } } } diff --git a/crates/pumpkin-world/src/generation/feature/features/coral/coral_mushroom.rs b/crates/pumpkin-world/src/generation/feature/features/coral/coral_mushroom.rs index 6aba4bcde..f764e5582 100644 --- a/crates/pumpkin-world/src/generation/feature/features/coral/coral_mushroom.rs +++ b/crates/pumpkin-world/src/generation/feature/features/coral/coral_mushroom.rs @@ -1,7 +1,7 @@ use crate::{generation::proto_chunk::GenerationCache, world::WorldPortalExt}; use pumpkin_data::tag; use pumpkin_util::{ - math::{position::BlockPos, vector3::Vector3}, + math::position::BlockPos, random::{RandomGenerator, RandomImpl}, }; @@ -10,47 +10,47 @@ use super::CoralFeature; pub struct CoralMushroomFeature; impl CoralMushroomFeature { - #[allow(clippy::too_many_arguments)] pub fn generate( chunk: &mut T, block_registry: &dyn WorldPortalExt, _min_y: i8, _height: u16, - _feature: pumpkin_data::placed_feature::PlacedFeature, // This placed feature + _feature: pumpkin_data::placed_feature::PlacedFeature, random: &mut RandomGenerator, pos: BlockPos, ) -> bool { - // First lets get a random coral let block = CoralFeature::get_random_tag_entry(tag::Block::MINECRAFT_CORAL_BLOCKS, random); - let i = random.next_bounded_i32(3) + 3; - let j = random.next_bounded_i32(3) + 3; - let k = random.next_bounded_i32(3) + 3; - let l = random.next_bounded_i32(3) + 1; + let height = random.next_bounded_i32(3) + 3; + let width = random.next_bounded_i32(3) + 3; + let length = random.next_bounded_i32(3) + 3; + let sink_value = random.next_bounded_i32(3) + 1; - for m in 0..=j { - for n in 0..=i { - for o in 0..=k { - let mut pos = pos; - pos = pos.offset(Vector3::new(pos.0.x + m, pos.0.y + n, pos.0.z + o)); - pos = pos.down_height(l); + for x in 0..=width { + for y in 0..=height { + for z in 0..=length { + let cur_pos = BlockPos::new(pos.0.x + x, pos.0.y + y - sink_value, pos.0.z + z); - let condition_a = (m != 0 && m != j) || (n != 0 && n != i); - let condition_b = (o != 0 && o != k) || (n != 0 && n != i); - let condition_c = (m != 0 && m != j) || (o != 0 && o != k); - let condition_d = m == 0 || m == j || n == 0 || n == i || o == 0 || o == k; - let random_check = random.next_f32() < 0.1f32; + let condition_a = (x != 0 && x != width) || (y != 0 && y != height); + let condition_b = (z != 0 && z != length) || (y != 0 && y != height); + let condition_c = (x != 0 && x != width) || (z != 0 && z != length); + let condition_d = + x == 0 || x == width || y == 0 || y == height || z == 0 || z == length; - if !((condition_a && condition_b && condition_c && condition_d) - && !random_check - && CoralFeature::generate_coral_piece( + if condition_a + && condition_b + && condition_c + && condition_d + && random.next_f32() >= 0.1 + { + CoralFeature::generate_coral_piece( chunk, block_registry, random, block, - pos, - )) - {} + cur_pos, + ); + } } } } diff --git a/crates/pumpkin-world/src/generation/feature/features/coral/coral_tree.rs b/crates/pumpkin-world/src/generation/feature/features/coral/coral_tree.rs index b29783d8f..3813aba30 100644 --- a/crates/pumpkin-world/src/generation/feature/features/coral/coral_tree.rs +++ b/crates/pumpkin-world/src/generation/feature/features/coral/coral_tree.rs @@ -10,49 +10,59 @@ use super::CoralFeature; pub struct CoralTreeFeature; impl CoralTreeFeature { - #[allow(clippy::too_many_arguments)] pub fn generate( chunk: &mut T, block_registry: &dyn WorldPortalExt, _min_y: i8, _height: u16, - _feature: pumpkin_data::placed_feature::PlacedFeature, // This placed feature + _feature: pumpkin_data::placed_feature::PlacedFeature, random: &mut RandomGenerator, pos: BlockPos, ) -> bool { - // First lets get a random coral let block = CoralFeature::get_random_tag_entry(tag::Block::MINECRAFT_CORAL_BLOCKS, random); - let mut pos = pos; - let i = random.next_bounded_i32(3) + 1; - for _ in 0..i { - if !CoralFeature::generate_coral_piece(chunk, block_registry, random, block, pos) { + let mut cur_pos = pos; + let trunk_height = random.next_bounded_i32(3) + 1; + for _ in 0..trunk_height { + if !CoralFeature::generate_coral_piece(chunk, block_registry, random, block, cur_pos) { return true; } - pos = pos.up(); + cur_pos = cur_pos.up(); } - let i = random.next_bounded_i32(3) + 2; - // TODO: vanilla takes the first `i` of Plane.HORIZONTAL.shuffledCopy(random) — a - // Fisher–Yates that must run over the horizontal_worldgen() [N, E, S, W] base - // order and consumes RNG draws. - let directions = BlockDirection::horizontal_worldgen() - .into_iter() - .take(i as usize); - for dir in directions { - pos = pos.offset(dir.to_offset()); - let times = random.next_bounded_i32(5) + 2; - let mut m = 0; - for n in 0..times { - if !CoralFeature::generate_coral_piece(chunk, block_registry, random, block, pos) { + let trunk_top_pos = cur_pos; + let n_branches = random.next_bounded_i32(3) + 2; + + let mut directions: Vec = BlockDirection::horizontal_worldgen() + .iter() + .copied() + .map(BlockDirection::from_cardinal_direction) + .collect(); + for i in (1..directions.len()).rev() { + let j = random.next_bounded_i32(i as i32 + 1) as usize; + directions.swap(i, j); + } + + for &branch_direction in directions.iter().take(n_branches as usize) { + let mut branch_pos = trunk_top_pos.offset(branch_direction.to_offset()); + let branch_height = random.next_bounded_i32(5) + 2; + let mut segment_length = 0; + + for j in 0..branch_height { + if !CoralFeature::generate_coral_piece( + chunk, + block_registry, + random, + block, + branch_pos, + ) { break; } - pos = pos.up(); - m += 1; - if n != 0 && (m < 2 || random.next_f32() >= 0.25) { - continue; + segment_length += 1; + branch_pos = branch_pos.up(); + if j == 0 || (segment_length >= 2 && random.next_f32() < 0.25) { + branch_pos = branch_pos.offset(branch_direction.to_offset()); + segment_length = 0; } - pos = pos.offset(dir.to_offset()); - m = 0; } } true diff --git a/crates/pumpkin-world/src/generation/feature/features/coral/mod.rs b/crates/pumpkin-world/src/generation/feature/features/coral/mod.rs index 1729c8a38..5125d2574 100644 --- a/crates/pumpkin-world/src/generation/feature/features/coral/mod.rs +++ b/crates/pumpkin-world/src/generation/feature/features/coral/mod.rs @@ -24,9 +24,10 @@ impl CoralFeature { pos: BlockPos, ) -> bool { let block = GenerationCache::get_block_state(chunk, &pos.0).to_block_id(); - let above_block = GenerationCache::get_block_state(chunk, &pos.up().0).to_block_id(); + let above_pos = pos.up(); + let above_block = GenerationCache::get_block_state(chunk, &above_pos.0).to_block_id(); - if block != BlockId::WATER && !block.has_tag(tag::Block::MINECRAFT_CORALS) + if (block != BlockId::WATER && !block.has_tag(tag::Block::MINECRAFT_CORALS)) || above_block != BlockId::WATER { return false; @@ -41,18 +42,22 @@ impl CoralFeature { Block::from_state_id(block_to_place_state.id), block_to_place_state, chunk, - &pos, + &above_pos, ) { - chunk.set_block_state(&pos.0, block_to_place_state); + chunk.set_block_state(&above_pos.0, block_to_place_state); } } else if random.next_f32() < 0.05 { let mut props = SeaPickleLikeProperties::default(&Block::SEA_PICKLE); props.pickles = (random.next_bounded_i32(4) as u8) + 1; let state_id = props.to_state_id(&Block::SEA_PICKLE); let block_state = BlockState::from_id(state_id); - if block_registry.can_place_at(Block::from_state_id(state_id), block_state, chunk, &pos) - { - chunk.set_block_state(&pos.0, block_state); + if block_registry.can_place_at( + Block::from_state_id(state_id), + block_state, + chunk, + &above_pos, + ) { + chunk.set_block_state(&above_pos.0, block_state); } } for dir in BlockDirection::horizontal_worldgen() { @@ -68,7 +73,6 @@ impl CoralFeature { continue; }; let original_props = &properties.to_props(); - // Set the right Axis let props: Vec<(&str, &str)> = original_props .iter() .map(|(key, value)| { diff --git a/crates/pumpkin-world/src/generation/feature/features/delta_feature.rs b/crates/pumpkin-world/src/generation/feature/features/delta_feature.rs index 721690c6f..7c63b793a 100644 --- a/crates/pumpkin-world/src/generation/feature/features/delta_feature.rs +++ b/crates/pumpkin-world/src/generation/feature/features/delta_feature.rs @@ -1,12 +1,21 @@ -use pumpkin_data::Block; -use pumpkin_util::{math::position::BlockPos, random::RandomGenerator, random::RandomImpl}; +use pumpkin_data::{BlockDirection, BlockId, BlockState}; +use pumpkin_util::{ + math::{int_provider::IntProvider, position::BlockPos}, + random::{RandomGenerator, RandomImpl}, +}; use crate::generation::proto_chunk::GenerationCache; -pub struct DeltaFeatureFeature; +pub struct DeltaFeatureFeature { + pub contents: &'static BlockState, + pub rim: &'static BlockState, + pub size: IntProvider, + pub rim_size: IntProvider, +} impl DeltaFeatureFeature { pub fn generate( + &self, chunk: &mut T, _min_y: i8, _height: u16, @@ -14,32 +23,81 @@ impl DeltaFeatureFeature { random: &mut RandomGenerator, pos: BlockPos, ) -> bool { - let size = random.next_bounded_i32(3) + 2; - let mut generated = false; + let mut any_placed = false; + let spawn_rim = random.next_f64() < 0.9; + let rim_x = if spawn_rim { + self.rim_size.get(random) + } else { + 0 + }; + let rim_z = if spawn_rim { + self.rim_size.get(random) + } else { + 0 + }; + let has_rim = spawn_rim && rim_x != 0 && rim_z != 0; + let radius_x = self.size.get(random); + let radius_z = self.size.get(random); + let radius_limit = radius_x.max(radius_z); - for dx in -size..=size { - for dy in -size..=size { - for dz in -size..=size { - if dx * dx + dy * dy + dz * dz <= size * size { - let target = BlockPos::new(pos.0.x + dx, pos.0.y + dy, pos.0.z + dz); - let block_id = - GenerationCache::get_block_state(chunk, &target.0).to_block_id(); - if block_id == Block::NETHERRACK.id - || block_id == Block::LAVA.id - || block_id == Block::AIR.id - { - let replacement = if random.next_f32() < 0.6 { - Block::BASALT - } else { - Block::BLACKSTONE - }; - chunk.set_block_state(&target.0, replacement.default_state); - generated = true; - } - } + for pos_iter in BlockPos::iterate_outwards(pos, radius_x, 0, radius_z) { + if pos_iter.manhattan_distance(pos) > radius_limit { + break; + } + + if Self::is_clear(chunk, pos_iter, self.contents) { + if has_rim { + any_placed = true; + chunk.set_block_state(&pos_iter.0, self.rim); + } + + let pos_offset = pos_iter.add(rim_x, 0, rim_z); + if Self::is_clear(chunk, pos_offset, self.contents) { + any_placed = true; + chunk.set_block_state(&pos_offset.0, self.contents); } } } - generated + + any_placed + } + + fn is_clear( + chunk: &T, + pos: BlockPos, + contents: &'static BlockState, + ) -> bool { + let state = GenerationCache::get_block_state(chunk, &pos.0); + if state.to_block_id() == contents.id.to_block_id() { + return false; + } + + if is_cannot_replace(state.to_block_id()) { + return false; + } + + for dir in BlockDirection::all() { + let neighbor_state = + GenerationCache::get_block_state(chunk, &pos.offset(dir.to_offset()).0); + let is_air = neighbor_state.to_state().is_air(); + if (is_air && dir != BlockDirection::Up) || (!is_air && dir == BlockDirection::Up) { + return false; + } + } + + true } } + +const fn is_cannot_replace(id: BlockId) -> bool { + matches!( + id, + BlockId::BEDROCK + | BlockId::NETHER_BRICKS + | BlockId::NETHER_BRICK_FENCE + | BlockId::NETHER_BRICK_STAIRS + | BlockId::NETHER_WART + | BlockId::CHEST + | BlockId::SPAWNER + ) +} diff --git a/crates/pumpkin-world/src/generation/feature/features/desert_well.rs b/crates/pumpkin-world/src/generation/feature/features/desert_well.rs index 0115d6eb7..52011726e 100644 --- a/crates/pumpkin-world/src/generation/feature/features/desert_well.rs +++ b/crates/pumpkin-world/src/generation/feature/features/desert_well.rs @@ -1,7 +1,8 @@ use pumpkin_data::{Block, BlockDirection}; +use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::{ math::{position::BlockPos, vector3::Vector3}, - random::RandomGenerator, + random::{RandomGenerator, RandomImpl}, }; use crate::generation::{noise::WATER_BLOCK, proto_chunk::GenerationCache}; @@ -19,8 +20,8 @@ impl DesertWellFeature { chunk: &mut T, _min_y: i8, _height: u16, - _feature: pumpkin_data::placed_feature::PlacedFeature, // This placed feature - _random: &mut RandomGenerator, + _feature: pumpkin_data::placed_feature::PlacedFeature, + random: &mut RandomGenerator, pos: BlockPos, ) -> bool { const CAN_GENERATE: Block = Block::SAND; @@ -29,7 +30,7 @@ impl DesertWellFeature { while chunk.is_air(&block_pos.0) && block_pos.0.y > chunk.bottom_y() as i32 + 2 { block_pos = block_pos.down(); } - let block = GenerationCache::get_block_state(chunk, &pos.0).to_block_id(); + let block = GenerationCache::get_block_state(chunk, &block_pos.0).to_block_id(); if CAN_GENERATE.id != block { return false; } @@ -139,6 +140,33 @@ impl DesertWellFeature { ); } + let water_center = block_pos.0; + let water_positions = [ + water_center, + water_center.add(&Vector3::new(1, 0, 0)), + water_center.add(&Vector3::new(0, 0, 1)), + water_center.add(&Vector3::new(-1, 0, 0)), + water_center.add(&Vector3::new(0, 0, -1)), + ]; + let pos1 = + water_positions[random.next_bounded_i32(5) as usize].add(&Vector3::new(0, -1, 0)); + let pos2 = + water_positions[random.next_bounded_i32(5) as usize].add(&Vector3::new(0, -2, 0)); + Self::place_sus_sand(chunk, &pos1); + Self::place_sus_sand(chunk, &pos2); + true } + + fn place_sus_sand(chunk: &mut T, pos: &Vector3) { + chunk.set_block_state(pos, Block::SUSPICIOUS_SAND.default_state); + let mut nbt = NbtCompound::new(); + nbt.put_string("id", "minecraft:brushable_block".to_string()); + nbt.put_int("x", pos.x); + nbt.put_int("y", pos.y); + nbt.put_int("z", pos.z); + nbt.put_string("LootTable", "minecraft:archaeology/desert_well".to_string()); + nbt.put_long("LootTableSeed", BlockPos(*pos).as_long()); + chunk.add_block_entity(pos, nbt); + } } diff --git a/crates/pumpkin-world/src/generation/feature/features/drip_stone/mod.rs b/crates/pumpkin-world/src/generation/feature/features/drip_stone/mod.rs index 2583f3c0e..b63941530 100644 --- a/crates/pumpkin-world/src/generation/feature/features/drip_stone/mod.rs +++ b/crates/pumpkin-world/src/generation/feature/features/drip_stone/mod.rs @@ -1,7 +1,9 @@ use crate::generation::proto_chunk::GenerationCache; -use pumpkin_data::Block; -use pumpkin_data::BlockId; +use pumpkin_data::block_properties::{ + PointedDripstoneLikeProperties, SpeleothemThickness, VerticalDirection, +}; use pumpkin_data::tag; +use pumpkin_data::{Block, BlockDirection, BlockId, BlockState}; use pumpkin_util::math::position::BlockPos; pub mod cluster; @@ -20,3 +22,55 @@ pub(super) fn gen_dripstone(chunk: &mut T, pos: BlockPos) -> } false } + +pub(super) fn is_empty_or_water(chunk: &T, pos: BlockPos) -> bool { + let block = GenerationCache::get_block_state(chunk, &pos.0).to_block_id(); + block == BlockId::AIR + || block == BlockId::CAVE_AIR + || block == BlockId::VOID_AIR + || block == BlockId::WATER +} + +pub(super) fn grow_pointed_dripstone( + chunk: &mut T, + start_pos: BlockPos, + tip_direction: BlockDirection, + height: i32, + merged_tip: bool, +) { + let mut cur = start_pos; + let vert_dir = match tip_direction { + BlockDirection::Down => VerticalDirection::Down, + _ => VerticalDirection::Up, + }; + + let mut thicknesses = Vec::with_capacity(height as usize); + if height >= 3 { + thicknesses.push(SpeleothemThickness::Base); + for _ in 0..(height - 3) { + thicknesses.push(SpeleothemThickness::Middle); + } + } + if height >= 2 { + thicknesses.push(SpeleothemThickness::Frustum); + } + if height >= 1 { + thicknesses.push(if merged_tip { + SpeleothemThickness::TipMerge + } else { + SpeleothemThickness::Tip + }); + } + + for thickness in thicknesses { + let is_water = + GenerationCache::get_block_state(chunk, &cur.0).to_block_id() == BlockId::WATER; + let mut props = PointedDripstoneLikeProperties::default(&Block::POINTED_DRIPSTONE); + props.thickness = thickness; + props.vertical_direction = vert_dir; + props.waterlogged = is_water; + let state_id = props.to_state_id(&Block::POINTED_DRIPSTONE); + chunk.set_block_state(&cur.0, BlockState::from_id(state_id)); + cur = cur.offset(tip_direction.to_offset()); + } +} diff --git a/crates/pumpkin-world/src/generation/feature/features/drip_stone/small.rs b/crates/pumpkin-world/src/generation/feature/features/drip_stone/small.rs index a6f6f8760..1cce1f554 100644 --- a/crates/pumpkin-world/src/generation/feature/features/drip_stone/small.rs +++ b/crates/pumpkin-world/src/generation/feature/features/drip_stone/small.rs @@ -20,9 +20,17 @@ impl SmallDripstoneFeature { pos: BlockPos, ) -> bool { if let Some(dir) = Self::get_direction(chunk, pos, random) { - let pos = pos.offset(dir.opposite().to_offset()); - self.gen_dripstone_blocks(chunk, pos, random); - // TODO + let root_pos = pos.offset(dir.opposite().to_offset()); + self.gen_dripstone_blocks(chunk, root_pos, random); + let next_pos = pos.offset(dir.to_offset()); + let height = if random.next_f32() < self.taller_dripstone + && super::is_empty_or_water(chunk, next_pos) + { + 2 + } else { + 1 + }; + super::grow_pointed_dripstone(chunk, pos, dir, height, false); return true; } false diff --git a/crates/pumpkin-world/src/generation/feature/features/end_podium.rs b/crates/pumpkin-world/src/generation/feature/features/end_podium.rs index be6f4ade5..d90b905e7 100644 --- a/crates/pumpkin-world/src/generation/feature/features/end_podium.rs +++ b/crates/pumpkin-world/src/generation/feature/features/end_podium.rs @@ -44,7 +44,6 @@ impl EndPodiumFeature { let mut oy = origin.0.y; let oz = origin.0.z; - // Podium spans [ox - 4, ox + 4] and [oz - 4, oz + 4] if ox + 4 < min_x || ox - 4 > max_x || oz + 4 < min_z || oz - 4 > max_z { return false; } @@ -76,7 +75,7 @@ impl EndPodiumFeature { if y < oy { if closer_than_2_5 { chunk.set_block_state(&target, Block::BEDROCK.default_state); - } else if self.active { + } else { chunk.set_block_state(&target, Block::END_STONE.default_state); } } else if y > oy { diff --git a/crates/pumpkin-world/src/generation/feature/features/end_spike.rs b/crates/pumpkin-world/src/generation/feature/features/end_spike.rs index 7632a86fe..41ef3bf17 100644 --- a/crates/pumpkin-world/src/generation/feature/features/end_spike.rs +++ b/crates/pumpkin-world/src/generation/feature/features/end_spike.rs @@ -182,7 +182,6 @@ impl EndSpikeFeature { chunk.set_block_state(&pos.0, Block::AIR.default_state); } - // Bedrock cap serves as the crystal base, fire sits on top of it chunk.set_block_state( &pumpkin_util::math::vector3::Vector3::new( spike.center_x, @@ -200,12 +199,10 @@ impl EndSpikeFeature { Block::FIRE.default_state, ); - // Iron-bar cage for guarded spikes: 5x5 walls + open top frame at dy=3. if spike.guarded { for dy in 0i32..=3 { for dx in -2i32..=2 { for dz in -2i32..=2 { - // Only place on perimeter walls and the top frame let x_wall_present = dx.abs() == 2; let z_wall_present = dz.abs() == 2; let on_top = dy == 3; @@ -213,15 +210,14 @@ impl EndSpikeFeature { continue; } - // Connectivity rules let x_edge = x_wall_present || on_top; let z_edge = z_wall_present || on_top; let mut props = OakFenceLikeProperties::default(&Block::IRON_BARS); - props.north = x_edge && dz != 2; - props.south = x_edge && dz != -2; - props.west = z_edge && dx != 2; - props.east = z_edge && dx != -2; + props.north = x_edge && dz != -2; + props.south = x_edge && dz != 2; + props.west = z_edge && dx != -2; + props.east = z_edge && dx != 2; let bar_state = BlockState::from_id(props.to_state_id(&Block::IRON_BARS)); chunk.set_block_state( diff --git a/crates/pumpkin-world/src/generation/feature/features/fallen_tree.rs b/crates/pumpkin-world/src/generation/feature/features/fallen_tree.rs index f31783a43..658519bed 100644 --- a/crates/pumpkin-world/src/generation/feature/features/fallen_tree.rs +++ b/crates/pumpkin-world/src/generation/feature/features/fallen_tree.rs @@ -1,29 +1,204 @@ -use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; +use pumpkin_data::{Block, BlockDirection, BlockState}; +use pumpkin_util::{ + math::{int_provider::IntProvider, position::BlockPos}, + random::{RandomGenerator, RandomImpl}, +}; -use crate::{ProtoChunk, generation::block_state_provider::BlockStateProvider}; +use super::tree::{TreeFeature, decorator::TreeDecorator}; +use crate::{ + generation::{block_state_provider::BlockStateProvider, proto_chunk::GenerationCache}, + world::WorldPortalExt, +}; pub struct FallenTreeFeature { pub trunk_provider: BlockStateProvider, + pub log_length: IntProvider, + pub stump_decorators: Vec, + pub log_decorators: Vec, } impl FallenTreeFeature { - pub const fn generate( - _chunk: &mut ProtoChunk, + #[allow(clippy::too_many_arguments)] + pub fn generate( + &self, + chunk: &mut T, + block_registry: &dyn WorldPortalExt, _min_y: i8, _height: u16, - _feature: pumpkin_data::placed_feature::PlacedFeature, // This placed feature - _random: &mut RandomGenerator, - _pos: BlockPos, + _feature: pumpkin_data::placed_feature::PlacedFeature, + random: &mut RandomGenerator, + pos: BlockPos, ) -> bool { - false + self.place_stump(chunk, block_registry, random, pos); + let direction = + BlockDirection::from_cardinal_direction(BlockDirection::random_horizontal(random)); + let log_length = self.log_length.get(random) - 2; + let mut log_start_pos = + pos.offset(direction.to_offset() * (2 + random.next_bounded_i32(2))); + Self::set_ground_height_for_fallen_log_start_pos(chunk, &mut log_start_pos); + if Self::can_place_entire_fallen_log(chunk, log_length, log_start_pos, direction) { + self.place_fallen_log( + chunk, + block_registry, + random, + log_length, + log_start_pos, + direction, + ); + } + true } - // fn gen_stump(&self, chunk: &mut ProtoChunk, random: &mut RandomGenerator, pos: BlockPos) { - // // chunk.set_block_state( - // // pos.0.x, - // // pos.0.y, - // // pos.0.z, - // // self.trunk_provider.get(random, pos, chunk), - // // ); - // } + fn set_ground_height_for_fallen_log_start_pos( + chunk: &T, + log_start_pos: &mut BlockPos, + ) { + *log_start_pos = log_start_pos.up(); + for _ in 0..6 { + if Self::may_place_on(chunk, *log_start_pos) { + return; + } + *log_start_pos = log_start_pos.down(); + } + } + + fn place_stump( + &self, + chunk: &mut T, + block_registry: &dyn WorldPortalExt, + random: &mut RandomGenerator, + stump_pos: BlockPos, + ) { + let stump = self.place_log_block(chunk, block_registry, random, stump_pos, None); + Self::decorate_logs( + chunk, + block_registry, + random, + &[stump], + &self.stump_decorators, + ); + } + + fn can_place_entire_fallen_log( + chunk: &T, + log_length: i32, + mut log_start_pos: BlockPos, + direction: BlockDirection, + ) -> bool { + let mut gap_in_ground = 0; + let offset = direction.to_offset(); + + for _ in 0..log_length { + let state = GenerationCache::get_block_state(chunk, &log_start_pos.0); + if !TreeFeature::can_replace(state.to_state(), state.to_block_id()) { + return false; + } + + if Self::is_over_solid_ground(chunk, log_start_pos) { + gap_in_ground = 0; + } else { + gap_in_ground += 1; + if gap_in_ground > 2 { + return false; + } + } + + log_start_pos = log_start_pos.offset(offset); + } + + true + } + + fn place_fallen_log( + &self, + chunk: &mut T, + block_registry: &dyn WorldPortalExt, + random: &mut RandomGenerator, + log_length: i32, + mut log_start_pos: BlockPos, + direction: BlockDirection, + ) { + let mut fallen_log = Vec::with_capacity(log_length.max(0) as usize); + let offset = direction.to_offset(); + + for _ in 0..log_length { + let placed = self.place_log_block( + chunk, + block_registry, + random, + log_start_pos, + Some(direction), + ); + fallen_log.push(placed); + log_start_pos = log_start_pos.offset(offset); + } + + Self::decorate_logs( + chunk, + block_registry, + random, + &fallen_log, + &self.log_decorators, + ); + } + + fn may_place_on(chunk: &T, block_pos: BlockPos) -> bool { + let state = GenerationCache::get_block_state(chunk, &block_pos.0); + TreeFeature::can_replace(state.to_state(), state.to_block_id()) + && Self::is_over_solid_ground(chunk, block_pos) + } + + fn is_over_solid_ground(chunk: &T, block_pos: BlockPos) -> bool { + let below = block_pos.down(); + GenerationCache::get_block_state(chunk, &below.0) + .to_state() + .is_side_solid(BlockDirection::Up) + } + + fn place_log_block( + &self, + chunk: &mut T, + block_registry: &dyn WorldPortalExt, + random: &mut RandomGenerator, + block_pos: BlockPos, + direction: Option, + ) -> BlockPos { + let mut state = self + .trunk_provider + .get(random, block_pos, chunk, block_registry); + if let Some(dir) = direction { + let block = Block::from_state_id(state.id); + let axis_str = match dir { + BlockDirection::East | BlockDirection::West => "x", + BlockDirection::North | BlockDirection::South => "z", + _ => "y", + }; + if let Some(props) = block.properties(state.id) { + let prop_list = props.to_props(); + if prop_list.iter().any(|(k, _)| *k == "axis") { + let new_props: Vec<(&str, &str)> = prop_list + .iter() + .map(|(k, v)| (*k, if *k == "axis" { axis_str } else { *v })) + .collect(); + let new_props_obj = block.from_properties(new_props.as_slice()); + let new_state_id = new_props_obj.to_state_id(block); + state = BlockState::from_id(new_state_id); + } + } + } + chunk.set_block_state(&block_pos.0, state); + block_pos + } + + fn decorate_logs( + chunk: &mut T, + block_registry: &dyn WorldPortalExt, + random: &mut RandomGenerator, + logs: &[BlockPos], + decorators: &[TreeDecorator], + ) { + for decorator in decorators { + decorator.generate(chunk, block_registry, random, &[], logs, &[]); + } + } } diff --git a/crates/pumpkin-world/src/generation/feature/features/fill_layer.rs b/crates/pumpkin-world/src/generation/feature/features/fill_layer.rs index 1d2748de2..b1e2bbbac 100644 --- a/crates/pumpkin-world/src/generation/feature/features/fill_layer.rs +++ b/crates/pumpkin-world/src/generation/feature/features/fill_layer.rs @@ -1,25 +1,30 @@ -use pumpkin_data::Block; +use pumpkin_data::BlockState; use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; use crate::generation::proto_chunk::GenerationCache; -pub struct FillLayerFeature; +pub struct FillLayerFeature { + pub height: i32, + pub state: &'static BlockState, +} impl FillLayerFeature { - #[allow(clippy::unused_self)] pub fn generate( &self, chunk: &mut T, - _min_y: i8, + min_y: i8, _height: u16, _feature: pumpkin_data::placed_feature::PlacedFeature, _random: &mut RandomGenerator, pos: BlockPos, ) -> bool { - for dx in -1..=1 { - for dz in -1..=1 { - let target = BlockPos::new(pos.0.x + dx, pos.0.y, pos.0.z + dz); - chunk.set_block_state(&target.0, Block::SNOW.default_state); + let y = min_y as i32 + self.height; + for dx in 0..16 { + for dz in 0..16 { + let target = BlockPos::new(pos.0.x + dx, y, pos.0.z + dz); + if chunk.is_air(&target.0) { + chunk.set_block_state(&target.0, self.state); + } } } true diff --git a/crates/pumpkin-world/src/generation/feature/features/freeze_top_layer.rs b/crates/pumpkin-world/src/generation/feature/features/freeze_top_layer.rs index 37357ea91..8e542f49a 100644 --- a/crates/pumpkin-world/src/generation/feature/features/freeze_top_layer.rs +++ b/crates/pumpkin-world/src/generation/feature/features/freeze_top_layer.rs @@ -4,8 +4,6 @@ use pumpkin_data::{Block, BlockState}; use pumpkin_util::math::position::BlockPos; use pumpkin_util::random::RandomGenerator; -const SEA_LEVEL: i32 = 63; // TODO: getSeaLevel() from the worldgen context instead of hardcoding - pub struct FreezeTopLayerFeature; impl FreezeTopLayerFeature { @@ -42,9 +40,10 @@ impl FreezeTopLayerFeature { } // Snow check - let top_temp = biome - .weather - .compute_temperature(x as f64, y, z as f64, SEA_LEVEL); + let top_temp = + biome + .weather + .compute_temperature(x as f64, y, z as f64, chunk.get_sea_level()); if top_temp < 0.15 { let top_raw = GenerationCache::get_block_state(chunk, &top_vec); diff --git a/crates/pumpkin-world/src/generation/feature/features/huge_fungus.rs b/crates/pumpkin-world/src/generation/feature/features/huge_fungus.rs index f5c371bb3..84e54e52b 100644 --- a/crates/pumpkin-world/src/generation/feature/features/huge_fungus.rs +++ b/crates/pumpkin-world/src/generation/feature/features/huge_fungus.rs @@ -1,56 +1,246 @@ -use pumpkin_data::Block; -use pumpkin_util::{math::position::BlockPos, random::RandomGenerator, random::RandomImpl}; +use pumpkin_data::{Block, BlockState}; +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; -use crate::generation::proto_chunk::GenerationCache; +use super::weeping_vines::WeepingVinesFeature; +use crate::{ + generation::{block_predicate::BlockPredicate, proto_chunk::GenerationCache}, + world::WorldPortalExt, +}; -pub struct HugeFungusFeature; +pub struct HugeFungusFeature { + pub valid_base_block: &'static BlockState, + pub stem_state: &'static BlockState, + pub hat_state: &'static BlockState, + pub decor_state: &'static BlockState, + pub replaceable_blocks: BlockPredicate, + pub planted: bool, +} impl HugeFungusFeature { - #[allow(clippy::unused_self)] + #[allow(clippy::too_many_arguments)] pub fn generate( &self, chunk: &mut T, + block_registry: &dyn WorldPortalExt, _min_y: i8, _height: u16, _feature: pumpkin_data::placed_feature::PlacedFeature, random: &mut RandomGenerator, pos: BlockPos, ) -> bool { - let height = random.next_bounded_i32(4) + 6; - let is_warped = random.next_bool(); - - let stem_state = if is_warped { - Block::WARPED_STEM.default_state - } else { - Block::CRIMSON_STEM.default_state - }; - let wart_state = if is_warped { - Block::WARPED_WART_BLOCK.default_state - } else { - Block::NETHER_WART_BLOCK.default_state - }; - - for i in 0..height { - let stem_pos = BlockPos::new(pos.0.x, pos.0.y + i, pos.0.z); - chunk.set_block_state(&stem_pos.0, stem_state); + let below = pos.down(); + let below_block = GenerationCache::get_block_state(chunk, &below.0).to_block_id(); + let valid_base_id = Block::from_state_id(self.valid_base_block.id).id; + if below_block != valid_base_id { + return false; } - let cap_y = pos.0.y + height - 2; - for dy in 0..=3 { - let radius = if dy == 0 || dy == 3 { 1 } else { 2 }; - for dx in -radius..=radius { - for dz in -radius..=radius { - let cap_pos = BlockPos::new(pos.0.x + dx, cap_y + dy, pos.0.z + dz); - let block_state = if (dx == 0 || dz == 0) && dy == 1 && random.next_f32() < 0.3 - { - Block::SHROOMLIGHT.default_state - } else { - wart_state - }; - chunk.set_block_state(&cap_pos.0, block_state); + let mut total_height = random.next_inbetween_i32(4, 13); + if random.next_bounded_i32(12) == 0 { + total_height *= 2; + } + + if !self.planted && pos.0.y + total_height + 1 >= chunk.top_y() as i32 { + return false; + } + + let is_huge = !self.planted && random.next_f32() < 0.06; + chunk.set_block_state(&pos.0, Block::AIR.default_state); + self.place_stem(chunk, block_registry, random, pos, total_height, is_huge); + self.place_hat(chunk, block_registry, random, pos, total_height, is_huge); + true + } + + fn is_replaceable( + &self, + chunk: &T, + block_registry: &dyn WorldPortalExt, + pos: BlockPos, + check_non_replaceable_plants: bool, + ) -> bool { + if chunk.is_air(&pos.0) + || GenerationCache::get_block_state(chunk, &pos.0) + .to_state() + .is_air() + { + true + } else if check_non_replaceable_plants { + self.replaceable_blocks.test(block_registry, chunk, &pos) + } else { + false + } + } + + fn place_stem( + &self, + chunk: &mut T, + block_registry: &dyn WorldPortalExt, + random: &mut RandomGenerator, + pos: BlockPos, + total_height: i32, + is_huge: bool, + ) { + let stem_radius = i32::from(is_huge); + + for dx in -stem_radius..=stem_radius { + for dz in -stem_radius..=stem_radius { + let corner_of_huge_stem = + is_huge && dx.abs() == stem_radius && dz.abs() == stem_radius; + + for dy in 0..total_height { + let block_pos = pos.add(dx, dy, dz); + if self.is_replaceable(chunk, block_registry, block_pos, true) { + if self.planted { + chunk.set_block_state(&block_pos.0, self.stem_state); + } else if corner_of_huge_stem { + if random.next_f32() < 0.1 { + chunk.set_block_state(&block_pos.0, self.stem_state); + } + } else { + chunk.set_block_state(&block_pos.0, self.stem_state); + } + } } } } - true + } + + fn place_hat( + &self, + chunk: &mut T, + block_registry: &dyn WorldPortalExt, + random: &mut RandomGenerator, + pos: BlockPos, + total_height: i32, + is_huge: bool, + ) { + let place_vines = Block::from_state_id(self.hat_state.id).id == Block::NETHER_WART_BLOCK.id; + let hat_height = (random.next_bounded_i32(1 + total_height / 3) + 5).min(total_height); + let hat_start_y = total_height - hat_height; + + for dy in hat_start_y..=total_height { + let mut radius = if hat_height > 8 && dy < hat_start_y + 4 { + 3 + } else if dy < total_height - random.next_bounded_i32(3) { + 2 + } else { + 1 + }; + if is_huge { + radius += 1; + } + + for dx in -radius..=radius { + for dz in -radius..=radius { + let is_edge_x = dx == -radius || dx == radius; + let is_edge_z = dz == -radius || dz == radius; + let inside = !is_edge_x && !is_edge_z && dy != total_height; + let corner = is_edge_x && is_edge_z; + let is_hat_bottom = dy < hat_start_y + 3; + let block_pos = pos.add(dx, dy, dz); + + if self.is_replaceable(chunk, block_registry, block_pos, false) { + if is_hat_bottom { + if !inside { + self.place_hat_drop_block(chunk, random, block_pos, place_vines); + } + } else if inside { + self.place_hat_block( + chunk, + random, + block_pos, + 0.1, + 0.2, + if place_vines { 0.1 } else { 0.0 }, + ); + } else if corner { + self.place_hat_block( + chunk, + random, + block_pos, + 0.01, + 0.7, + if place_vines { 0.083 } else { 0.0 }, + ); + } else { + self.place_hat_block( + chunk, + random, + block_pos, + 5.0e-4, + 0.98, + if place_vines { 0.07 } else { 0.0 }, + ); + } + } + } + } + } + } + + fn place_hat_block( + &self, + chunk: &mut T, + random: &mut RandomGenerator, + block_pos: BlockPos, + decor_prob: f32, + hat_prob: f32, + vines_prob: f32, + ) { + if random.next_f32() < decor_prob { + chunk.set_block_state(&block_pos.0, self.decor_state); + } else if random.next_f32() < hat_prob { + chunk.set_block_state(&block_pos.0, self.hat_state); + if random.next_f32() < vines_prob { + Self::try_place_weeping_vines(chunk, random, block_pos); + } + } + } + + fn place_hat_drop_block( + &self, + chunk: &mut T, + random: &mut RandomGenerator, + block_pos: BlockPos, + place_vines: bool, + ) { + let below = block_pos.down(); + let below_id = GenerationCache::get_block_state(chunk, &below.0).to_block_id(); + let hat_id = Block::from_state_id(self.hat_state.id).id; + + if below_id == hat_id { + chunk.set_block_state(&block_pos.0, self.hat_state); + } else if random.next_f32() < 0.15 { + chunk.set_block_state(&block_pos.0, self.hat_state); + if place_vines && random.next_bounded_i32(11) == 0 { + Self::try_place_weeping_vines(chunk, random, block_pos); + } + } + } + + fn try_place_weeping_vines( + chunk: &mut T, + random: &mut RandomGenerator, + hat_block_pos: BlockPos, + ) { + let place_pos = hat_block_pos.down(); + if chunk.is_air(&place_pos.0) { + let mut goal_vine_height = random.next_inbetween_i32(1, 5); + if random.next_bounded_i32(7) == 0 { + goal_vine_height *= 2; + } + + WeepingVinesFeature::place_weeping_vines_column( + chunk, + random, + place_pos, + goal_vine_height, + 23, + 25, + ); + } } } diff --git a/crates/pumpkin-world/src/generation/feature/features/iceberg.rs b/crates/pumpkin-world/src/generation/feature/features/iceberg.rs index 5ccd5486e..cd971f9d3 100644 --- a/crates/pumpkin-world/src/generation/feature/features/iceberg.rs +++ b/crates/pumpkin-world/src/generation/feature/features/iceberg.rs @@ -8,8 +8,6 @@ use pumpkin_util::{ use crate::{block::BlockStateCodec, generation::proto_chunk::GenerationCache}; -const SEA_LEVEL: i32 = 63; // TODO: use getSeaLevel() instead of hardcoding - pub struct IcebergFeature { pub main_block: BlockStateCodec, } @@ -25,7 +23,7 @@ impl IcebergFeature { random: &mut RandomGenerator, pos: BlockPos, ) -> bool { - let origin = Vector3::new(pos.0.x, SEA_LEVEL, pos.0.z); + let origin = Vector3::new(pos.0.x, chunk.get_sea_level(), pos.0.z); let main_block = self.main_block.get_state(); let snow_on_top = random.next_f64() > 0.7; diff --git a/crates/pumpkin-world/src/generation/feature/features/kelp.rs b/crates/pumpkin-world/src/generation/feature/features/kelp.rs index b53677b84..39a7ee2ec 100644 --- a/crates/pumpkin-world/src/generation/feature/features/kelp.rs +++ b/crates/pumpkin-world/src/generation/feature/features/kelp.rs @@ -1,10 +1,11 @@ -use crate::generation::proto_chunk::GenerationCache; -use pumpkin_data::{Block, BlockState}; +use pumpkin_data::{Block, BlockDirection, BlockState, block_properties::KelpLikeProperties, tag}; use pumpkin_util::{ math::position::BlockPos, random::{RandomGenerator, RandomImpl}, }; +use crate::generation::proto_chunk::GenerationCache; + pub struct KelpFeature; impl KelpFeature { @@ -19,67 +20,66 @@ impl KelpFeature { ) -> bool { let mut placed = 0; - // Start on the ocean floor let y = chunk.ocean_floor_height_exclusive(pos.0.x, pos.0.z); let mut kelp_pos = BlockPos::new(pos.0.x, y, pos.0.z); - // Must be in water if GenerationCache::get_block_state(chunk, &kelp_pos.0).to_block_id() == Block::WATER { - let height_rand = 1 + random.next_bounded_i32(10); + let height = 1 + random.next_bounded_i32(10); + + for h in 0..=height { + let above = kelp_pos.up(); + let can_survive = Self::can_survive(chunk, kelp_pos); - // Iterate from base up to height_rand - for h in 0..=height_rand { - // Check there is water at this position and one above if GenerationCache::get_block_state(chunk, &kelp_pos.0).to_block_id() == Block::WATER - && GenerationCache::get_block_state( - chunk, - &BlockPos::new(kelp_pos.0.x, kelp_pos.0.y + 1, kelp_pos.0.z).0, - ) - .to_block_id() + && GenerationCache::get_block_state(chunk, &above.0).to_block_id() == Block::WATER + && can_survive { - // If this is the last iteration place the kelp head with age - if h == height_rand { - let age = random.next_bounded_i32(4) + 20; - // Clamp in case it goes past available states - let age = age.min((Block::KELP.states.len() - 1) as i32) as usize; - let state_id = Block::KELP.states[age].id; - let state = BlockState::from_id(state_id); - chunk.set_block_state(&kelp_pos.0, state); + if h == height { + let age = (random.next_bounded_i32(4) + 20) as u8; + let mut props = KelpLikeProperties::default(&Block::KELP); + props.age = age; + let state_id = props.to_state_id(&Block::KELP); + chunk.set_block_state(&kelp_pos.0, BlockState::from_id(state_id)); placed += 1; } else { - // Place kelp plant body - let state_id = Block::KELP_PLANT.default_state.id; - let state = BlockState::from_id(state_id); - chunk.set_block_state(&kelp_pos.0, state); + chunk.set_block_state(&kelp_pos.0, Block::KELP_PLANT.default_state); } } else if h > 0 { - // Can't place further but we have already placed at least one segment, try to put head below - let below = BlockPos::new(kelp_pos.0.x, kelp_pos.0.y - 1, kelp_pos.0.z); - // Check head block can survive and that below-below is not a kelp head - if GenerationCache::get_block_state(chunk, &below.0).to_block_id() - == Block::WATER - && GenerationCache::get_block_state( - chunk, - &BlockPos::new(below.0.x, below.0.y - 1, below.0.z).0, - ) - .to_block_id() - != Block::KELP - { - let age = random.next_bounded_i32(4) + 20; - let age = age.min((Block::KELP.states.len() - 1) as i32) as usize; - let state_id = Block::KELP.states[age].id; - let state = BlockState::from_id(state_id); - chunk.set_block_state(&below.0, state); + let below = kelp_pos.down(); + let below_below = below.down(); + let below_below_id = + GenerationCache::get_block_state(chunk, &below_below.0).to_block_id(); + if Self::can_survive(chunk, below) && below_below_id != Block::KELP.id { + let age = (random.next_bounded_i32(4) + 20) as u8; + let mut props = KelpLikeProperties::default(&Block::KELP); + props.age = age; + let state_id = props.to_state_id(&Block::KELP); + chunk.set_block_state(&below.0, BlockState::from_id(state_id)); placed += 1; } break; } - kelp_pos = BlockPos::new(kelp_pos.0.x, kelp_pos.0.y + 1, kelp_pos.0.z); + + kelp_pos = kelp_pos.up(); } } placed > 0 } + + fn can_survive(chunk: &T, pos: BlockPos) -> bool { + let below = pos.down(); + let below_state = GenerationCache::get_block_state(chunk, &below.0); + let below_id = below_state.to_block_id(); + + if below_id.has_tag(tag::Block::MINECRAFT_CANNOT_SUPPORT_KELP) { + return false; + } + + below_id == Block::KELP.id + || below_id == Block::KELP_PLANT.id + || below_state.to_state().is_side_solid(BlockDirection::Up) + } } diff --git a/crates/pumpkin-world/src/generation/feature/features/monster_room.rs b/crates/pumpkin-world/src/generation/feature/features/monster_room.rs index 45d659419..cdfc1bade 100644 --- a/crates/pumpkin-world/src/generation/feature/features/monster_room.rs +++ b/crates/pumpkin-world/src/generation/feature/features/monster_room.rs @@ -7,9 +7,6 @@ use pumpkin_util::{ use crate::generation::proto_chunk::GenerationCache; -/// The three mob types that can appear in a dungeon spawner. -/// -/// Skeleton 25%, Zombie 50%, Spider 25%. const DUNGEON_MOBS: [&str; 4] = [ "minecraft:skeleton", "minecraft:zombie", @@ -20,7 +17,6 @@ const DUNGEON_MOBS: [&str; 4] = [ pub struct DungeonFeature; impl DungeonFeature { - /// Generate a monster room (dungeon) at `pos`. #[expect(clippy::too_many_lines)] pub fn generate( chunk: &mut T, @@ -46,13 +42,12 @@ impl DungeonFeature { let solid = !chunk.is_air(&check_pos); if dy == -1 && !solid { - return false; // Floor has a gap + return false; } if dy == 4 && !solid { - return false; // Ceiling has a gap + return false; } - // Wall openings let on_wall = dx == min_x || dx == max_x || dz == min_z || dz == max_z; if on_wall && dy == 0 @@ -94,18 +89,15 @@ impl DungeonFeature { chunk.set_block_state(&wall_pos, Block::CAVE_AIR.default_state); } else if cur_solid && !is_chest { if dy == -1 && random.next_bounded_i32(4) != 0 { - // 75% mossy cobblestone chunk.set_block_state( &wall_pos, Block::MOSSY_COBBLESTONE.default_state, ); } else { - // 25% cobblestone (or 100% if it's not the floor) chunk.set_block_state(&wall_pos, Block::COBBLESTONE.default_state); } } } else { - // Clear interior, but preserve chests and spawners let state = GenerationCache::get_block_state(chunk, &wall_pos); let is_chest = state.to_block() == &Block::CHEST; let is_spawner = state.to_block() == &Block::SPAWNER; @@ -117,7 +109,6 @@ impl DungeonFeature { } } - // Chest placement attempts (rooms have 1-2 chests) for _ in 0..2 { for _ in 0..3 { let cx = pos.0.x + random.next_bounded_i32(xr * 2 + 1) - xr; @@ -129,20 +120,17 @@ impl DungeonFeature { continue; } - // Count solid horizontal neighbours let wall_count = BlockDirection::horizontal() .iter() .filter(|d| !chunk.is_air(&chest_pos.add(&d.to_offset()))) .count(); if wall_count == 1 { - // Orient the chest toward its single solid neighbor. let facing_dir = BlockDirection::horizontal() .iter() .find(|d| !chunk.is_air(&chest_pos.add(&d.to_offset()))) .copied(); - // Build the chest state: face away from the wall. let chest_state = facing_dir.map_or(Block::CHEST.default_state, |dir| { use pumpkin_data::block_properties::ChestLikeProperties; let mut props = ChestLikeProperties::default(&Block::CHEST); @@ -153,7 +141,6 @@ impl DungeonFeature { chunk.set_block_state(&chest_pos, chest_state); - // Write the block-entity NBT with a deferred loot table. let loot_seed = random.next_i64(); let mut chest_nbt = NbtCompound::new(); chest_nbt.put_string("id", "minecraft:chest".to_string()); @@ -170,7 +157,6 @@ impl DungeonFeature { } } - // TODO: set spawner entity type let mob = DUNGEON_MOBS[random.next_bounded_i32(DUNGEON_MOBS.len() as i32) as usize]; chunk.set_block_state(&pos.0, Block::SPAWNER.default_state); diff --git a/crates/pumpkin-world/src/generation/feature/features/multiface_growth.rs b/crates/pumpkin-world/src/generation/feature/features/multiface_growth.rs index 4f7e57280..e06a57e11 100644 --- a/crates/pumpkin-world/src/generation/feature/features/multiface_growth.rs +++ b/crates/pumpkin-world/src/generation/feature/features/multiface_growth.rs @@ -1,9 +1,20 @@ -use pumpkin_data::Block; -use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; +use pumpkin_data::{Block, BlockDirection, BlockState, block_properties::GlowLichenLikeProperties}; +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; use crate::generation::proto_chunk::GenerationCache; -pub struct MultifaceGrowthFeature; +pub struct MultifaceGrowthFeature { + pub place_block: pumpkin_data::BlockId, + pub search_range: i32, + pub can_place_on_floor: bool, + pub can_place_on_ceiling: bool, + pub can_place_on_wall: bool, + pub chance_of_spreading: f32, + pub can_be_placed_on: Vec, +} impl MultifaceGrowthFeature { #[allow(clippy::unused_self)] @@ -13,27 +24,133 @@ impl MultifaceGrowthFeature { _min_y: i8, _height: u16, _feature: pumpkin_data::placed_feature::PlacedFeature, - _random: &mut RandomGenerator, + random: &mut RandomGenerator, pos: BlockPos, ) -> bool { - let mut generated = false; - let directions = [ - (1, 0, 0), - (-1, 0, 0), - (0, 1, 0), - (0, -1, 0), - (0, 0, 1), - (0, 0, -1), - ]; - for &(dx, dy, dz) in &directions { - let neighbor = BlockPos::new(pos.0.x + dx, pos.0.y + dy, pos.0.z + dz); - let state = GenerationCache::get_block_state(chunk, &neighbor.0); - if state.to_block_id() != Block::AIR.id && chunk.is_air(&pos.0) { - chunk.set_block_state(&pos.0, Block::GLOW_LICHEN.default_state); - generated = true; - break; + if !Self::is_air_or_water(chunk, pos) { + return false; + } + + let shuffled_dirs = self.get_shuffled_directions(random); + if self.place_growth_if_possible(chunk, pos, random, &shuffled_dirs) { + return true; + } + + for search_direction in shuffled_dirs { + let placement_directions = + self.get_shuffled_directions_except(random, search_direction.opposite()); + let mut cur_pos = pos; + + for _ in 0..self.search_range { + cur_pos = cur_pos.offset(search_direction.to_offset()); + let block_id = GenerationCache::get_block_state(chunk, &cur_pos.0).to_block_id(); + if !Self::is_air_or_water(chunk, cur_pos) && block_id != self.place_block { + break; + } + + if self.place_growth_if_possible(chunk, cur_pos, random, &placement_directions) { + return true; + } } } - generated + + false + } + + fn place_growth_if_possible( + &self, + chunk: &mut T, + pos: BlockPos, + _random: &mut RandomGenerator, + placement_directions: &[BlockDirection], + ) -> bool { + for &direction in placement_directions { + let neighbor_pos = pos.offset(direction.to_offset()); + let neighbor_id = + GenerationCache::get_block_state(chunk, &neighbor_pos.0).to_block_id(); + + if self.can_be_placed_on.contains(&neighbor_id) { + let is_water = GenerationCache::get_block_state(chunk, &pos.0).to_block_id() + == Block::WATER.id; + let block = Block::from_id(self.place_block); + + let mut props = GlowLichenLikeProperties { + down: direction == BlockDirection::Down, + up: direction == BlockDirection::Up, + north: direction == BlockDirection::North, + south: direction == BlockDirection::South, + west: direction == BlockDirection::West, + east: direction == BlockDirection::East, + waterlogged: is_water, + }; + + let cur_state = GenerationCache::get_block_state(chunk, &pos.0); + if cur_state.to_block_id() == self.place_block + && (self.place_block == Block::GLOW_LICHEN.id + || self.place_block == Block::SCULK_VEIN.id) + { + let cur_props = GlowLichenLikeProperties::from_state_id(cur_state); + props.down |= cur_props.down; + props.up |= cur_props.up; + props.north |= cur_props.north; + props.south |= cur_props.south; + props.west |= cur_props.west; + props.east |= cur_props.east; + } + + let state_id = props.to_state_id(block); + chunk.set_block_state(&pos.0, BlockState::from_id(state_id)); + return true; + } + } + + false + } + + fn get_valid_directions(&self) -> Vec { + let mut dirs = Vec::with_capacity(6); + if self.can_place_on_ceiling { + dirs.push(BlockDirection::Up); + } + if self.can_place_on_floor { + dirs.push(BlockDirection::Down); + } + if self.can_place_on_wall { + dirs.extend( + BlockDirection::horizontal_worldgen().map(BlockDirection::from_cardinal_direction), + ); + } + dirs + } + + fn get_shuffled_directions(&self, random: &mut RandomGenerator) -> Vec { + let mut dirs = self.get_valid_directions(); + for i in (1..dirs.len()).rev() { + let j = random.next_bounded_i32((i + 1) as i32) as usize; + dirs.swap(i, j); + } + dirs + } + + fn get_shuffled_directions_except( + &self, + random: &mut RandomGenerator, + exclude: BlockDirection, + ) -> Vec { + let mut dirs: Vec = self + .get_valid_directions() + .into_iter() + .filter(|&d| d != exclude) + .collect(); + for i in (1..dirs.len()).rev() { + let j = random.next_bounded_i32((i + 1) as i32) as usize; + dirs.swap(i, j); + } + dirs + } + + fn is_air_or_water(chunk: &T, pos: BlockPos) -> bool { + let id = GenerationCache::get_block_state(chunk, &pos.0).to_block_id(); + id == Block::AIR.id || id == Block::WATER.id } } diff --git a/crates/pumpkin-world/src/generation/feature/features/ore.rs b/crates/pumpkin-world/src/generation/feature/features/ore.rs index c6bdc8733..9324fe3cc 100644 --- a/crates/pumpkin-world/src/generation/feature/features/ore.rs +++ b/crates/pumpkin-world/src/generation/feature/features/ore.rs @@ -50,12 +50,11 @@ impl OreFeature { let q = 2 * (g.ceil() as i32 + i); let r = 2 * (2 + i); - for _ in n..=(n + q) { - for _ in p..=(p + q) { - if o > chunk.ocean_floor_height_exclusive(pos.0.x, pos.0.z) { - continue; + for xprobe in n..=(n + q) { + for zprobe in p..=(p + q) { + if o <= chunk.ocean_floor_height_exclusive(xprobe, zprobe) { + return self.generate_vein_part(chunk, random, d, e, h, j, l, m, n, o, p, q, r); } - return self.generate_vein_part(chunk, random, d, e, h, j, l, m, n, o, p, q, r); } } false diff --git a/crates/pumpkin-world/src/generation/feature/features/replace_single_block.rs b/crates/pumpkin-world/src/generation/feature/features/replace_single_block.rs index 8ea881df0..42b6f48c3 100644 --- a/crates/pumpkin-world/src/generation/feature/features/replace_single_block.rs +++ b/crates/pumpkin-world/src/generation/feature/features/replace_single_block.rs @@ -1,22 +1,29 @@ -use pumpkin_data::Block; use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; +use crate::generation::feature::features::ore::OreTarget; use crate::generation::proto_chunk::GenerationCache; -pub struct ReplaceSingleBlockFeature; +pub struct ReplaceSingleBlockFeature { + pub targets: Vec, +} impl ReplaceSingleBlockFeature { - #[allow(clippy::unused_self)] pub fn generate( &self, chunk: &mut T, _min_y: i8, _height: u16, _feature: pumpkin_data::placed_feature::PlacedFeature, - _random: &mut RandomGenerator, + random: &mut RandomGenerator, pos: BlockPos, ) -> bool { - chunk.set_block_state(&pos.0, Block::SAND.default_state); + let block_state = GenerationCache::get_block_state(chunk, &pos.0); + for target in &self.targets { + if target.target.test(block_state, random) { + chunk.set_block_state(&pos.0, target.state); + break; + } + } true } } diff --git a/crates/pumpkin-world/src/generation/feature/features/root_system.rs b/crates/pumpkin-world/src/generation/feature/features/root_system.rs index 0ed043df3..7e97c7cb1 100644 --- a/crates/pumpkin-world/src/generation/feature/features/root_system.rs +++ b/crates/pumpkin-world/src/generation/feature/features/root_system.rs @@ -1,3 +1,4 @@ +use pumpkin_data::{Block, BlockDirection}; use pumpkin_util::{ math::position::BlockPos, random::{RandomGenerator, RandomImpl}, @@ -36,52 +37,156 @@ impl RootSystemFeature { random: &mut RandomGenerator, pos: BlockPos, ) -> bool { - if !self.allowed_tree_position.test(block_registry, chunk, &pos) { + if !chunk.is_air(&pos.0) { return false; } - if !self.can_place_tree(chunk, pos) { - return false; - } - - if !self.feature.generate( + let mut working_pos = pos; + if self.place_dirt_and_tree( chunk, block_registry, min_y, height, feature_name, random, + &mut working_pos, pos, ) { - return false; + self.place_roots(chunk, block_registry, random, pos); } - self.place_roots(chunk, block_registry, random, pos); true } - fn can_place_tree(&self, chunk: &T, pos: BlockPos) -> bool { - let mut mutable_pos = pos; + fn space_for_tree(&self, chunk: &T, pos: BlockPos) -> bool { + let mut column_up = pos; for i in 1..=self.required_vertical_space_for_tree { - mutable_pos = mutable_pos.add(0, 1, 0); - if !self.is_allowed_tree_space(chunk, mutable_pos, i) { + column_up = column_up.up(); + let state = GenerationCache::get_block_state(chunk, &column_up.0); + if !self.is_allowed_tree_space(state.to_block_id(), i) { return false; } } true } - fn is_allowed_tree_space( + fn is_allowed_tree_space( &self, - chunk: &T, - pos: BlockPos, - vertical_space: i32, + block_id: pumpkin_data::BlockId, + blocks_above_origin: i32, ) -> bool { - if chunk.is_air(&pos.0) { + if block_id == Block::AIR.id { return true; } - // TODO: check for water. - vertical_space <= self.allowed_vertical_water_for_tree + let blocks_above_ground = blocks_above_origin + 1; + blocks_above_ground <= self.allowed_vertical_water_for_tree && block_id == Block::WATER.id + } + + #[allow(clippy::too_many_arguments)] + fn place_dirt_and_tree( + &self, + chunk: &mut T, + block_registry: &dyn WorldPortalExt, + min_y: i8, + height: u16, + feature_name: pumpkin_data::placed_feature::PlacedFeature, + random: &mut RandomGenerator, + working_pos: &mut BlockPos, + pos: BlockPos, + ) -> bool { + for y in 0..self.root_column_max_height { + *working_pos = working_pos.up(); + if chunk.top_block_height_exclusive(working_pos.0.x, working_pos.0.z) < working_pos.0.y + { + return false; + } + + if self + .allowed_tree_position + .test(block_registry, chunk, working_pos) + && self.space_for_tree(chunk, *working_pos) + { + let below_pos = working_pos.down(); + let below_state = GenerationCache::get_block_state(chunk, &below_pos.0); + if below_state.to_block_id() == Block::LAVA.id + || !below_state.to_state().is_side_solid(BlockDirection::Up) + { + return false; + } + + if self.feature.generate( + chunk, + block_registry, + min_y, + height, + feature_name, + random, + *working_pos, + ) { + self.place_dirt(chunk, block_registry, random, pos, pos.0.y + y); + return true; + } + } + } + + false + } + + fn place_dirt( + &self, + chunk: &mut T, + block_registry: &dyn WorldPortalExt, + random: &mut RandomGenerator, + origin: BlockPos, + target_height: i32, + ) { + let origin_x = origin.0.x; + let origin_z = origin.0.z; + + for y in origin.0.y..target_height { + self.place_rooted_dirt( + chunk, + block_registry, + random, + origin_x, + origin_z, + BlockPos::new(origin_x, y, origin_z), + ); + } + } + + fn place_rooted_dirt( + &self, + chunk: &mut T, + block_registry: &dyn WorldPortalExt, + random: &mut RandomGenerator, + origin_x: i32, + origin_z: i32, + mut working_pos: BlockPos, + ) { + let root_radius = self.root_radius.max(1); + + for _ in 0..self.root_placement_attempts { + working_pos = working_pos.add( + random.next_bounded_i32(root_radius) - random.next_bounded_i32(root_radius), + 0, + random.next_bounded_i32(root_radius) - random.next_bounded_i32(root_radius), + ); + + if self + .root_replaceable + .test(block_registry, chunk, &working_pos) + { + chunk.set_block_state( + &working_pos.0, + self.root_state_provider + .get(random, working_pos, chunk, block_registry), + ); + } + + working_pos.0.x = origin_x; + working_pos.0.z = origin_z; + } } fn place_roots( @@ -91,92 +196,29 @@ impl RootSystemFeature { random: &mut RandomGenerator, pos: BlockPos, ) { - self.place_rooted_dirt(chunk, block_registry, random, pos); - self.place_hanging_roots(chunk, block_registry, random, pos); - } + let root_radius = self.hanging_root_radius.max(1); + let vertical_span = self.hanging_roots_vertical_span.max(1); - fn place_rooted_dirt( - &self, - chunk: &mut T, - block_registry: &dyn WorldPortalExt, - random: &mut RandomGenerator, - pos: BlockPos, - ) { - for _ in 0..self.root_placement_attempts { - let mut mutable_pos = pos.add( - random.next_bounded_i32(self.root_radius.max(1)) - - random.next_bounded_i32(self.root_radius.max(1)), - 0, - random.next_bounded_i32(self.root_radius.max(1)) - - random.next_bounded_i32(self.root_radius.max(1)), - ); - - if self - .root_replaceable - .test(block_registry, chunk, &mutable_pos) - { - chunk.set_block_state( - &mutable_pos.0, - self.root_state_provider.get_with_context( - block_registry, - chunk, - random, - mutable_pos, - ), - ); - } - - for _ in 0..self.root_column_max_height { - mutable_pos = mutable_pos.add(0, -1, 0); - if chunk.out_of_height(mutable_pos.0.y as i16) - || !self - .root_replaceable - .test(block_registry, chunk, &mutable_pos) - { - break; - } - chunk.set_block_state( - &mutable_pos.0, - self.root_state_provider.get_with_context( - block_registry, - chunk, - random, - mutable_pos, - ), - ); - } - } - } - - fn place_hanging_roots( - &self, - chunk: &mut T, - block_registry: &dyn WorldPortalExt, - random: &mut RandomGenerator, - pos: BlockPos, - ) { for _ in 0..self.hanging_root_placement_attempts { - let mutable_pos = pos.add( - random.next_bounded_i32(self.hanging_root_radius.max(1)) - - random.next_bounded_i32(self.hanging_root_radius.max(1)), - random.next_bounded_i32(self.hanging_roots_vertical_span.max(1)) - - random.next_bounded_i32(self.hanging_roots_vertical_span.max(1)), - random.next_bounded_i32(self.hanging_root_radius.max(1)) - - random.next_bounded_i32(self.hanging_root_radius.max(1)), + let working_pos = pos.add( + random.next_bounded_i32(root_radius) - random.next_bounded_i32(root_radius), + random.next_bounded_i32(vertical_span) - random.next_bounded_i32(vertical_span), + random.next_bounded_i32(root_radius) - random.next_bounded_i32(root_radius), ); - if chunk.is_air(&mutable_pos.0) { - let state = self.hanging_root_state_provider.get_with_context( - block_registry, - chunk, - random, - mutable_pos, - ); - // In vanilla, it checks if it can survive and if the block above is sturdy. - // For now, let's just check if the block above is NOT air. - let above = mutable_pos.add(0, 1, 0); - if !chunk.is_air(&above.0) { - chunk.set_block_state(&mutable_pos.0, state); + if chunk.is_air(&working_pos.0) { + let above = working_pos.up(); + let above_state = GenerationCache::get_block_state(chunk, &above.0); + if above_state.to_state().is_side_solid(BlockDirection::Down) { + chunk.set_block_state( + &working_pos.0, + self.hanging_root_state_provider.get( + random, + working_pos, + chunk, + block_registry, + ), + ); } } } diff --git a/crates/pumpkin-world/src/generation/feature/features/scattered_ore.rs b/crates/pumpkin-world/src/generation/feature/features/scattered_ore.rs index 5a36ff023..9b437fd30 100644 --- a/crates/pumpkin-world/src/generation/feature/features/scattered_ore.rs +++ b/crates/pumpkin-world/src/generation/feature/features/scattered_ore.rs @@ -27,12 +27,11 @@ impl ScatteredOreFeature { pos: BlockPos, ) -> bool { let count = random.next_bounded_i32(self.size + 1); - let mut placed = false; - - for _ in 0..count { - let offset_x = self.get_random_offset(random); - let offset_y = self.get_random_offset(random); - let offset_z = self.get_random_offset(random); + for i in 0..count { + let max_dist = i.min(7); + let offset_x = Self::get_random_offset(random, max_dist); + let offset_y = Self::get_random_offset(random, max_dist); + let offset_z = Self::get_random_offset(random, max_dist); let target_pos = pos.add(offset_x, offset_y, offset_z); @@ -52,18 +51,17 @@ impl ScatteredOreFeature { &target_pos, ) { chunk.set_block_state(&target_pos.0, target.state); - placed = true; break; } } } - placed + true } - fn get_random_offset(&self, random: &mut RandomGenerator) -> i32 { + fn get_random_offset(random: &mut RandomGenerator, max_dist: i32) -> i32 { let f1 = random.next_f32(); let f2 = random.next_f32(); - ((f1 - f2) * self.size as f32).round() as i32 + ((f1 - f2) * max_dist as f32).round() as i32 } } diff --git a/crates/pumpkin-world/src/generation/feature/features/sea_pickle.rs b/crates/pumpkin-world/src/generation/feature/features/sea_pickle.rs index 137da13d6..23d534eab 100644 --- a/crates/pumpkin-world/src/generation/feature/features/sea_pickle.rs +++ b/crates/pumpkin-world/src/generation/feature/features/sea_pickle.rs @@ -1,10 +1,11 @@ -use crate::generation::proto_chunk::GenerationCache; -use pumpkin_data::{Block, BlockState, block_properties::SeaPickleLikeProperties}; +use pumpkin_data::{Block, BlockDirection, BlockState, block_properties::SeaPickleLikeProperties}; use pumpkin_util::{ math::{int_provider::IntProvider, position::BlockPos}, random::{RandomGenerator, RandomImpl}, }; +use crate::generation::proto_chunk::GenerationCache; + pub struct SeaPickleFeature { pub count: IntProvider, } @@ -15,28 +16,37 @@ impl SeaPickleFeature { chunk: &mut T, _min_y: i8, _height: u16, - _feature: pumpkin_data::placed_feature::PlacedFeature, // This placed feature + _feature: pumpkin_data::placed_feature::PlacedFeature, random: &mut RandomGenerator, pos: BlockPos, ) -> bool { - let mut times = 0; + let mut placed = 0; let count = self.count.get(random); + for _ in 0..count { let x = random.next_bounded_i32(8) - random.next_bounded_i32(8); let z = random.next_bounded_i32(8) - random.next_bounded_i32(8); let y = chunk.ocean_floor_height_exclusive(pos.0.x + x, pos.0.z + z); - if GenerationCache::get_block_state(chunk, &pos.0).to_block_id() != Block::WATER { - continue; + let pickle_pos = BlockPos::new(pos.0.x + x, y, pos.0.z + z); + + if GenerationCache::get_block_state(chunk, &pickle_pos.0).to_block_id() == Block::WATER + && Self::can_survive(chunk, pickle_pos) + { + let mut props = SeaPickleLikeProperties::default(&Block::SEA_PICKLE); + props.pickles = (random.next_bounded_i32(4) as u8) + 1; + let state_id = props.to_state_id(&Block::SEA_PICKLE); + chunk.set_block_state(&pickle_pos.0, BlockState::from_id(state_id)); + placed += 1; } - let mut props = SeaPickleLikeProperties::default(&Block::SEA_PICKLE); - props.pickles = (random.next_bounded_i32(4) as u8) + 1; - let pos = BlockPos::new(pos.0.x + x, y, pos.0.z + z); - chunk.set_block_state( - &pos.0, - BlockState::from_id(props.to_state_id(&Block::SEA_PICKLE)), - ); - times += 1; } - times > 0 + + placed > 0 + } + + fn can_survive(chunk: &T, pos: BlockPos) -> bool { + let below = pos.down(); + GenerationCache::get_block_state(chunk, &below.0) + .to_state() + .is_side_solid(BlockDirection::Up) } } diff --git a/crates/pumpkin-world/src/generation/feature/features/seagrass.rs b/crates/pumpkin-world/src/generation/feature/features/seagrass.rs index efb213dd9..afa2dbd1e 100644 --- a/crates/pumpkin-world/src/generation/feature/features/seagrass.rs +++ b/crates/pumpkin-world/src/generation/feature/features/seagrass.rs @@ -1,13 +1,15 @@ -use crate::generation::proto_chunk::GenerationCache; use pumpkin_data::{ - Block, BlockState, + Block, BlockDirection, BlockState, block_properties::{DoubleBlockHalf, TallSeagrassLikeProperties}, + tag, }; use pumpkin_util::{ math::position::BlockPos, random::{RandomGenerator, RandomImpl}, }; +use crate::generation::proto_chunk::GenerationCache; + pub struct SeagrassFeature { pub probability: f32, } @@ -18,34 +20,51 @@ impl SeagrassFeature { chunk: &mut T, _min_y: i8, _height: u16, - _feature: pumpkin_data::placed_feature::PlacedFeature, // This placed feature + _feature: pumpkin_data::placed_feature::PlacedFeature, random: &mut RandomGenerator, pos: BlockPos, ) -> bool { + let mut placed_any = false; let x = random.next_bounded_i32(8) - random.next_bounded_i32(8); let z = random.next_bounded_i32(8) - random.next_bounded_i32(8); let y = chunk.ocean_floor_height_exclusive(pos.0.x + x, pos.0.z + z); - let top_pos = BlockPos::new(pos.0.x + x, y, pos.0.z + z); - if GenerationCache::get_block_state(chunk, &top_pos.0).to_block_id() == Block::WATER { - let tall = random.next_f64() < self.probability as f64; - if tall { - let tall_pos = top_pos.up(); - if GenerationCache::get_block_state(chunk, &tall_pos.0).to_block_id() - == Block::WATER - { - let mut props = TallSeagrassLikeProperties::default(&Block::TALL_SEAGRASS); - props.half = DoubleBlockHalf::Upper; - chunk.set_block_state(&top_pos.0, Block::TALL_SEAGRASS.default_state); - chunk.set_block_state( - &tall_pos.0, - BlockState::from_id(props.to_state_id(&Block::TALL_SEAGRASS)), - ); + let grass_pos = BlockPos::new(pos.0.x + x, y, pos.0.z + z); + + if GenerationCache::get_block_state(chunk, &grass_pos.0).to_block_id() == Block::WATER + && Self::can_survive(chunk, grass_pos) + { + let is_tall = random.next_f64() < self.probability as f64; + if is_tall { + let above = grass_pos.up(); + if GenerationCache::get_block_state(chunk, &above.0).to_block_id() == Block::WATER { + let mut upper_props = + TallSeagrassLikeProperties::default(&Block::TALL_SEAGRASS); + upper_props.half = DoubleBlockHalf::Upper; + let upper_state = + BlockState::from_id(upper_props.to_state_id(&Block::TALL_SEAGRASS)); + + chunk.set_block_state(&grass_pos.0, Block::TALL_SEAGRASS.default_state); + chunk.set_block_state(&above.0, upper_state); + placed_any = true; } } else { - chunk.set_block_state(&top_pos.0, Block::SEAGRASS.default_state); + chunk.set_block_state(&grass_pos.0, Block::SEAGRASS.default_state); + placed_any = true; } - return true; } - false + + placed_any + } + + fn can_survive(chunk: &T, pos: BlockPos) -> bool { + let below = pos.down(); + let below_state = GenerationCache::get_block_state(chunk, &below.0); + let below_id = below_state.to_block_id(); + + if below_id.has_tag(tag::Block::MINECRAFT_CANNOT_SUPPORT_SEAGRASS) { + return false; + } + + below_state.to_state().is_side_solid(BlockDirection::Up) } } diff --git a/crates/pumpkin-world/src/generation/feature/features/simple_block.rs b/crates/pumpkin-world/src/generation/feature/features/simple_block.rs index 90f4b2dff..b1391d720 100644 --- a/crates/pumpkin-world/src/generation/feature/features/simple_block.rs +++ b/crates/pumpkin-world/src/generation/feature/features/simple_block.rs @@ -27,9 +27,7 @@ impl SimpleBlockFeature { return false; } - // TODO: check things.. chunk.set_block_state(&pos.0, state); - // TODO: schedule tick when needed true } } diff --git a/crates/pumpkin-world/src/generation/feature/features/tree/decorator/alter_ground.rs b/crates/pumpkin-world/src/generation/feature/features/tree/decorator/alter_ground.rs index fec63f07b..4a13a04bc 100644 --- a/crates/pumpkin-world/src/generation/feature/features/tree/decorator/alter_ground.rs +++ b/crates/pumpkin-world/src/generation/feature/features/tree/decorator/alter_ground.rs @@ -1 +1,92 @@ -pub struct AlterGroundTreeDecorator; +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; + +use super::TreeDecorator; +use crate::{ + generation::{block_state_provider::BlockStateProvider, proto_chunk::GenerationCache}, + world::WorldPortalExt, +}; + +pub struct AlterGroundTreeDecorator { + pub provider: BlockStateProvider, +} + +impl AlterGroundTreeDecorator { + pub fn generate( + &self, + chunk: &mut T, + block_registry: &dyn WorldPortalExt, + random: &mut RandomGenerator, + root_positions: &[BlockPos], + log_positions: &[BlockPos], + ) { + let list = TreeDecorator::get_leaf_litter_positions(root_positions, log_positions); + let Some(first) = list.first() else { + return; + }; + + let min_y = list.iter().map(|p| p.0.y).min().unwrap_or(first.0.y); + + for pos in list.iter().filter(|p| p.0.y == min_y) { + self.place_circle(chunk, block_registry, random, pos.west().north()); + self.place_circle(chunk, block_registry, random, pos.east().east().north()); + self.place_circle(chunk, block_registry, random, pos.west().south().south()); + self.place_circle( + chunk, + block_registry, + random, + pos.east().east().south().south(), + ); + + for _ in 0..5 { + let placement = random.next_bounded_i32(64); + let xx = placement % 8; + let zz = placement / 8; + if xx == 0 || xx == 7 || zz == 0 || zz == 7 { + self.place_circle(chunk, block_registry, random, pos.add(-3 + xx, 0, -3 + zz)); + } + } + } + } + + fn place_circle( + &self, + chunk: &mut T, + block_registry: &dyn WorldPortalExt, + random: &mut RandomGenerator, + pos: BlockPos, + ) { + for xx in -2..=2i32 { + for zz in -2..=2i32 { + if xx.abs() != 2 || zz.abs() != 2 { + self.place_block_at(chunk, block_registry, random, pos.add(xx, 0, zz)); + } + } + } + } + + fn place_block_at( + &self, + chunk: &mut T, + block_registry: &dyn WorldPortalExt, + random: &mut RandomGenerator, + pos: BlockPos, + ) { + for dy in (-3..=2).rev() { + let cursor = pos.up_height(dy); + if let Some(replace_with) = + self.provider + .get_optional(block_registry, chunk, random, cursor) + { + chunk.set_block_state(&cursor.0, replace_with); + break; + } + + if !chunk.is_air(&cursor.0) && dy < 0 { + break; + } + } + } +} diff --git a/crates/pumpkin-world/src/generation/feature/features/tree/decorator/attached_to_leaves.rs b/crates/pumpkin-world/src/generation/feature/features/tree/decorator/attached_to_leaves.rs index 9364c67d7..2463912bd 100644 --- a/crates/pumpkin-world/src/generation/feature/features/tree/decorator/attached_to_leaves.rs +++ b/crates/pumpkin-world/src/generation/feature/features/tree/decorator/attached_to_leaves.rs @@ -1 +1,84 @@ -pub struct AttachedToLeavesTreeDecorator; +use std::collections::HashSet; + +use pumpkin_data::BlockDirection; +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; + +use crate::{ + generation::{block_state_provider::BlockStateProvider, proto_chunk::GenerationCache}, + world::WorldPortalExt, +}; + +pub struct AttachedToLeavesTreeDecorator { + pub probability: f32, + pub exclusion_radius_xz: i32, + pub exclusion_radius_y: i32, + pub block_provider: BlockStateProvider, + pub required_empty_blocks: i32, + pub directions: Vec, +} + +impl AttachedToLeavesTreeDecorator { + pub fn generate( + &self, + chunk: &mut T, + block_registry: &dyn WorldPortalExt, + random: &mut RandomGenerator, + foliage_positions: &[BlockPos], + ) { + if self.directions.is_empty() || foliage_positions.is_empty() { + return; + } + + let mut blacklist: HashSet = HashSet::new(); + + let mut shuffled = foliage_positions.to_vec(); + for i in (1..shuffled.len()).rev() { + let j = random.next_bounded_i32((i + 1) as i32) as usize; + shuffled.swap(i, j); + } + + for leaf_pos in shuffled { + let dir_idx = random.next_bounded_i32(self.directions.len() as i32) as usize; + let direction = self.directions[dir_idx]; + let placement_pos = leaf_pos.offset(direction.to_offset()); + + if !blacklist.contains(&placement_pos) + && random.next_f32() < self.probability + && self.has_required_empty_blocks(chunk, leaf_pos, direction) + { + for dx in -self.exclusion_radius_xz..=self.exclusion_radius_xz { + for dy in -self.exclusion_radius_y..=self.exclusion_radius_y { + for dz in -self.exclusion_radius_xz..=self.exclusion_radius_xz { + blacklist.insert(placement_pos.add(dx, dy, dz)); + } + } + } + + chunk.set_block_state( + &placement_pos.0, + self.block_provider + .get(random, placement_pos, chunk, block_registry), + ); + } + } + } + + fn has_required_empty_blocks( + &self, + chunk: &T, + leaf_pos: BlockPos, + direction: BlockDirection, + ) -> bool { + let offset = direction.to_offset(); + for i in 1..=self.required_empty_blocks { + let check_pos = leaf_pos.offset(offset * i); + if !chunk.is_air(&check_pos.0) { + return false; + } + } + true + } +} diff --git a/crates/pumpkin-world/src/generation/feature/features/tree/decorator/attached_to_logs.rs b/crates/pumpkin-world/src/generation/feature/features/tree/decorator/attached_to_logs.rs index 035de5614..a52efbc47 100644 --- a/crates/pumpkin-world/src/generation/feature/features/tree/decorator/attached_to_logs.rs +++ b/crates/pumpkin-world/src/generation/feature/features/tree/decorator/attached_to_logs.rs @@ -21,21 +21,28 @@ impl AttachedToLogsTreeDecorator { random: &mut RandomGenerator, log_positions: &[BlockPos], ) { - // TODO: shuffle - for pos in log_positions { - // TODO: random - let pos = pos.offset(self.directions[0].to_offset()); - if random.next_f32() > self.probability - || !GenerationCache::get_block_state(chunk, &pos.0) - .to_state() - .is_air() - { - continue; + if self.directions.is_empty() || log_positions.is_empty() { + return; + } + + let mut shuffled = log_positions.to_vec(); + for i in (1..shuffled.len()).rev() { + let j = random.next_bounded_i32((i + 1) as i32) as usize; + shuffled.swap(i, j); + } + + for log_pos in shuffled { + let dir_idx = random.next_bounded_i32(self.directions.len() as i32) as usize; + let direction = self.directions[dir_idx]; + let placement_pos = log_pos.offset(direction.to_offset()); + + if random.next_f32() <= self.probability && chunk.is_air(&placement_pos.0) { + chunk.set_block_state( + &placement_pos.0, + self.block_provider + .get(random, placement_pos, chunk, block_registry), + ); } - chunk.set_block_state( - &pos.0, - self.block_provider.get(random, pos, chunk, block_registry), - ); } } } diff --git a/crates/pumpkin-world/src/generation/feature/features/tree/decorator/beehive.rs b/crates/pumpkin-world/src/generation/feature/features/tree/decorator/beehive.rs index b4164ee34..f28e35d69 100644 --- a/crates/pumpkin-world/src/generation/feature/features/tree/decorator/beehive.rs +++ b/crates/pumpkin-world/src/generation/feature/features/tree/decorator/beehive.rs @@ -1,3 +1,70 @@ +use pumpkin_data::{ + Block, BlockDirection, BlockState, + block_properties::{BeehiveProperties, HorizontalFacing}, +}; +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; + +use crate::generation::proto_chunk::GenerationCache; + pub struct BeehiveTreeDecorator { pub probability: f32, } + +impl BeehiveTreeDecorator { + pub fn generate( + &self, + chunk: &mut T, + random: &mut RandomGenerator, + log_positions: &[BlockPos], + foliage_positions: &[BlockPos], + ) { + if log_positions.is_empty() || random.next_f32() >= self.probability { + return; + } + + let worldgen_facing = BlockDirection::South; + let spawn_directions = [ + BlockDirection::East, + BlockDirection::West, + BlockDirection::South, + ]; + + let hive_y = foliage_positions.first().map_or_else( + || { + let last_log_y = log_positions.last().map_or(0, |p| p.0.y); + (log_positions[0].0.y + 1 + random.next_bounded_i32(3)).min(last_log_y) + }, + |first_leaf| (first_leaf.0.y - 1).max(log_positions[0].0.y + 1), + ); + + let mut hive_placements: Vec = Vec::new(); + for log_pos in log_positions.iter().filter(|p| p.0.y == hive_y) { + for dir in spawn_directions { + hive_placements.push(log_pos.offset(dir.to_offset())); + } + } + + if hive_placements.is_empty() { + return; + } + + for i in (1..hive_placements.len()).rev() { + let j = random.next_bounded_i32((i + 1) as i32) as usize; + hive_placements.swap(i, j); + } + + for pos in hive_placements { + let in_front = pos.offset(worldgen_facing.to_offset()); + if chunk.is_air(&pos.0) && chunk.is_air(&in_front.0) { + let mut props = BeehiveProperties::default(&Block::BEE_NEST); + props.facing = HorizontalFacing::South; + let state_id = props.to_state_id(&Block::BEE_NEST); + chunk.set_block_state(&pos.0, BlockState::from_id(state_id)); + break; + } + } + } +} diff --git a/crates/pumpkin-world/src/generation/feature/features/tree/decorator/cocoa.rs b/crates/pumpkin-world/src/generation/feature/features/tree/decorator/cocoa.rs index 8a2f03fb5..11765da0f 100644 --- a/crates/pumpkin-world/src/generation/feature/features/tree/decorator/cocoa.rs +++ b/crates/pumpkin-world/src/generation/feature/features/tree/decorator/cocoa.rs @@ -1 +1,43 @@ -pub struct CocoaTreeDecorator; +use pumpkin_data::{Block, BlockDirection, BlockState, block_properties::CocoaLikeProperties}; +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; + +use crate::generation::proto_chunk::GenerationCache; + +pub struct CocoaTreeDecorator { + pub probability: f32, +} + +impl CocoaTreeDecorator { + pub fn generate( + &self, + chunk: &mut T, + random: &mut RandomGenerator, + log_positions: &[BlockPos], + ) { + if log_positions.is_empty() || random.next_f32() >= self.probability { + return; + } + + let tree_y = log_positions[0].0.y; + for pos in log_positions.iter().filter(|p| p.0.y - tree_y <= 2) { + for facing in BlockDirection::horizontal_worldgen() { + if random.next_f32() <= 0.25 { + let dir = BlockDirection::from_cardinal_direction(facing); + let opposite = dir.opposite(); + let cocoa_pos = pos.offset(opposite.to_offset()); + if chunk.is_air(&cocoa_pos.0) { + let props = CocoaLikeProperties { + age: random.next_bounded_i32(3) as u8, + facing, + }; + let state_id = props.to_state_id(&Block::COCOA); + chunk.set_block_state(&cocoa_pos.0, BlockState::from_id(state_id)); + } + } + } + } + } +} diff --git a/crates/pumpkin-world/src/generation/feature/features/tree/decorator/creaking_heart.rs b/crates/pumpkin-world/src/generation/feature/features/tree/decorator/creaking_heart.rs index 81b17f909..8f1501252 100644 --- a/crates/pumpkin-world/src/generation/feature/features/tree/decorator/creaking_heart.rs +++ b/crates/pumpkin-world/src/generation/feature/features/tree/decorator/creaking_heart.rs @@ -1 +1,57 @@ -pub struct CreakingHeartTreeDecorator; +use pumpkin_data::{ + Block, BlockDirection, BlockState, + block_properties::{Axis, CreakingHeartLikeProperties, CreakingHeartState}, + tag, +}; +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; + +use crate::generation::proto_chunk::GenerationCache; + +pub struct CreakingHeartTreeDecorator { + pub probability: f32, +} + +impl CreakingHeartTreeDecorator { + pub fn generate( + &self, + chunk: &mut T, + random: &mut RandomGenerator, + log_positions: &[BlockPos], + ) { + if log_positions.is_empty() || random.next_f32() >= self.probability { + return; + } + + let mut shuffled = log_positions.to_vec(); + for i in (1..shuffled.len()).rev() { + let j = random.next_bounded_i32((i + 1) as i32) as usize; + shuffled.swap(i, j); + } + + for pos in shuffled { + let mut all_logs = true; + for dir in BlockDirection::all() { + let neighbor = pos.offset(dir.to_offset()); + let block_id = GenerationCache::get_block_state(chunk, &neighbor.0).to_block_id(); + if !block_id.has_tag(tag::Block::MINECRAFT_LOGS) { + all_logs = false; + break; + } + } + + if all_logs { + let props = CreakingHeartLikeProperties { + r#axis: Axis::Y, + r#creaking_heart_state: CreakingHeartState::Dormant, + r#natural: true, + }; + let state_id = props.to_state_id(&Block::CREAKING_HEART); + chunk.set_block_state(&pos.0, BlockState::from_id(state_id)); + break; + } + } + } +} diff --git a/crates/pumpkin-world/src/generation/feature/features/tree/decorator/mod.rs b/crates/pumpkin-world/src/generation/feature/features/tree/decorator/mod.rs index 1507378a8..d9fbb80ca 100644 --- a/crates/pumpkin-world/src/generation/feature/features/tree/decorator/mod.rs +++ b/crates/pumpkin-world/src/generation/feature/features/tree/decorator/mod.rs @@ -53,15 +53,27 @@ impl TreeDecorator { TrunkVineTreeDecorator::generate(chunk, random, log_positions); } Self::LeaveVine(decorator) => decorator.generate(chunk, random, foliage_positions), - Self::PaleMoss(_decorator) => {} - Self::CreakingHeart(_decorator) => {} - Self::Cocoa(_decorator) => {} - Self::Beehive(_decorator) => {} - Self::AlterGround(_decorator) => {} + Self::PaleMoss(decorator) => { + decorator.generate(chunk, random, log_positions, foliage_positions); + } + Self::CreakingHeart(decorator) => { + decorator.generate(chunk, random, log_positions); + } + Self::Cocoa(decorator) => { + decorator.generate(chunk, random, log_positions); + } + Self::Beehive(decorator) => { + decorator.generate(chunk, random, log_positions, foliage_positions); + } + Self::AlterGround(decorator) => { + decorator.generate(chunk, block_registry, random, root_positions, log_positions); + } Self::PlaceOnGround(decorator) => { decorator.generate(chunk, block_registry, random, root_positions, log_positions); } - Self::AttachedToLeaves(_decorator) => {} + Self::AttachedToLeaves(decorator) => { + decorator.generate(chunk, block_registry, random, foliage_positions); + } Self::AttachedToLogs(decorator) => { decorator.generate(chunk, block_registry, random, log_positions); } diff --git a/crates/pumpkin-world/src/generation/feature/features/tree/decorator/pale_moss.rs b/crates/pumpkin-world/src/generation/feature/features/tree/decorator/pale_moss.rs index 1e05ac1be..d0218f42e 100644 --- a/crates/pumpkin-world/src/generation/feature/features/tree/decorator/pale_moss.rs +++ b/crates/pumpkin-world/src/generation/feature/features/tree/decorator/pale_moss.rs @@ -1 +1,62 @@ -pub struct PaleMossTreeDecorator; +use pumpkin_data::{Block, BlockState, block_properties::PaleHangingMossLikeProperties}; +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; + +use crate::generation::proto_chunk::GenerationCache; + +pub struct PaleMossTreeDecorator { + pub leaves_probability: f32, + pub trunk_probability: f32, + pub ground_probability: f32, +} + +impl PaleMossTreeDecorator { + pub fn generate( + &self, + chunk: &mut T, + random: &mut RandomGenerator, + log_positions: &[BlockPos], + foliage_positions: &[BlockPos], + ) { + if log_positions.is_empty() { + return; + } + + for &pos in log_positions { + if random.next_f32() < self.trunk_probability { + let down = pos.down(); + if chunk.is_air(&down.0) { + Self::add_moss_hanger(chunk, random, down); + } + } + } + + for &pos in foliage_positions { + if random.next_f32() < self.leaves_probability { + let down = pos.down(); + if chunk.is_air(&down.0) { + Self::add_moss_hanger(chunk, random, down); + } + } + } + } + + fn add_moss_hanger( + chunk: &mut T, + random: &mut RandomGenerator, + mut pos: BlockPos, + ) { + while chunk.is_air(&pos.down().0) && random.next_f32() >= 0.5 { + let props = PaleHangingMossLikeProperties { tip: false }; + let state_id = props.to_state_id(&Block::PALE_HANGING_MOSS); + chunk.set_block_state(&pos.0, BlockState::from_id(state_id)); + pos = pos.down(); + } + + let props = PaleHangingMossLikeProperties { tip: true }; + let state_id = props.to_state_id(&Block::PALE_HANGING_MOSS); + chunk.set_block_state(&pos.0, BlockState::from_id(state_id)); + } +} diff --git a/crates/pumpkin-world/src/generation/feature/features/tree/decorator/place_on_ground.rs b/crates/pumpkin-world/src/generation/feature/features/tree/decorator/place_on_ground.rs index fc2d1b7dd..8bc8be2d8 100644 --- a/crates/pumpkin-world/src/generation/feature/features/tree/decorator/place_on_ground.rs +++ b/crates/pumpkin-world/src/generation/feature/features/tree/decorator/place_on_ground.rs @@ -66,18 +66,16 @@ impl PlaceOnGroundTreeDecorator { random: &mut RandomGenerator, ) { let state = GenerationCache::get_block_state(chunk, &pos.0); - let up_pos = pos.up(); - let up_state = GenerationCache::get_block_state(chunk, &up_pos.0); - // TODO - if (up_state.to_state().is_air() || up_state.to_block_id() == Block::VINE) - && state.to_state().is_full_cube() - && !state.to_block_id().has_tag(MINECRAFT_LEAVES) - // TODO: using heightmap seems not to work + let ground_pos = pos.down(); + let ground_state = GenerationCache::get_block_state(chunk, &ground_pos.0); + if (state.to_state().is_air() || state.to_block_id() == Block::VINE) + && ground_state.to_state().is_full_cube() + && !ground_state.to_block_id().has_tag(MINECRAFT_LEAVES) { chunk.set_block_state( - &up_pos.0, + &pos.0, self.block_state_provider - .get(random, up_pos, chunk, block_registry), + .get(random, pos, chunk, block_registry), ); } } diff --git a/crates/pumpkin-world/src/generation/feature/features/twisting_vines.rs b/crates/pumpkin-world/src/generation/feature/features/twisting_vines.rs index 82782cfae..53efa85ff 100644 --- a/crates/pumpkin-world/src/generation/feature/features/twisting_vines.rs +++ b/crates/pumpkin-world/src/generation/feature/features/twisting_vines.rs @@ -1,4 +1,4 @@ -use pumpkin_data::Block; +use pumpkin_data::{Block, BlockState, block_properties::KelpLikeProperties}; use pumpkin_util::{ math::position::BlockPos, random::{RandomGenerator, RandomImpl}, @@ -22,34 +22,31 @@ impl TwistingVinesFeature { random: &mut RandomGenerator, pos: BlockPos, ) -> bool { - if Self::is_invalid_location(chunk, &pos) { + if Self::is_invalid_placement_location(chunk, pos) { return false; } let mut placed = false; for _ in 0..self.spread_width * self.spread_width { - let offset_x = random.next_bounded_i32(self.spread_width) - - random.next_bounded_i32(self.spread_width); - let offset_y = random.next_bounded_i32(self.spread_height) - - random.next_bounded_i32(self.spread_height); - let offset_z = random.next_bounded_i32(self.spread_width) - - random.next_bounded_i32(self.spread_width); + let offset_x = random.next_inbetween_i32(-self.spread_width, self.spread_width); + let offset_y = random.next_inbetween_i32(-self.spread_height, self.spread_height); + let offset_z = random.next_inbetween_i32(-self.spread_width, self.spread_width); - let mut mutable_pos = pos.add(offset_x, offset_y, offset_z); + let mut place_pos = pos.add(offset_x, offset_y, offset_z); - if self.find_target_y(chunk, &mut mutable_pos) - && !Self::is_invalid_location(chunk, &mutable_pos) + if Self::find_first_air_block_above_ground(chunk, &mut place_pos) + && !Self::is_invalid_placement_location(chunk, place_pos) { - let mut height = random.next_bounded_i32(self.max_height) + 1; + let mut vine_height = random.next_inbetween_i32(1, self.max_height); if random.next_bounded_i32(6) == 0 { - height *= 2; + vine_height *= 2; } - if random.next_bounded_i32(10) == 0 { - height = 1; + if random.next_bounded_i32(5) == 0 { + vine_height = 1; } - Self::generate_column(chunk, random, &mutable_pos, height); + Self::place_twisting_vines_column(chunk, random, place_pos, vine_height, 17, 25); placed = true; } } @@ -57,75 +54,59 @@ impl TwistingVinesFeature { placed } - fn generate_column( + fn find_first_air_block_above_ground( + chunk: &T, + place_pos: &mut BlockPos, + ) -> bool { + loop { + *place_pos = place_pos.down(); + if place_pos.0.y <= chunk.bottom_y() as i32 { + return false; + } + if !chunk.is_air(&place_pos.0) { + break; + } + } + + *place_pos = place_pos.up(); + true + } + + pub fn place_twisting_vines_column( chunk: &mut T, random: &mut RandomGenerator, - pos: &BlockPos, - height: i32, + mut place_pos: BlockPos, + total_height: i32, + min_age: i32, + max_age: i32, ) { - let mut current_pos = *pos; - for i in 0..height { - if !GenerationCache::get_block_state(chunk, ¤t_pos.0) - .to_state() - .is_air() - { - break; + for height in 1..=total_height { + if chunk.is_air(&place_pos.0) { + if height == total_height || !chunk.is_air(&place_pos.up().0) { + let age = random.next_inbetween_i32(min_age, max_age); + let mut props = KelpLikeProperties::default(&Block::TWISTING_VINES); + props.age = age as u8; + let state_id = props.to_state_id(&Block::TWISTING_VINES); + chunk.set_block_state(&place_pos.0, BlockState::from_id(state_id)); + break; + } + + chunk.set_block_state(&place_pos.0, Block::TWISTING_VINES_PLANT.default_state); } - if i == height - 1 - || !GenerationCache::get_block_state(chunk, ¤t_pos.up().0) - .to_state() - .is_air() - { - // Top part - let _age = 17 + random.next_bounded_i32(25 - 17 + 1); - // We should set the age property here, but Pumpkin's BlockState might not support it easily yet or we just use default - // For now, let's just use the block. - // TODO: Set age property - chunk.set_block_state(¤t_pos.0, Block::TWISTING_VINES.default_state); - break; - } - chunk.set_block_state(¤t_pos.0, Block::TWISTING_VINES_PLANT.default_state); - current_pos = current_pos.up(); + place_pos = place_pos.up(); } } - fn is_invalid_location(chunk: &T, pos: &BlockPos) -> bool { - if !GenerationCache::get_block_state(chunk, &pos.0) - .to_state() - .is_air() - { + fn is_invalid_placement_location(chunk: &T, pos: BlockPos) -> bool { + if !chunk.is_air(&pos.0) { return true; } - let block_below = GenerationCache::get_block_state(chunk, &pos.down().0).to_state(); - block_below != Block::WARPED_NYLIUM.default_state - && block_below != Block::WARPED_WART_BLOCK.default_state - && block_below != Block::TWISTING_VINES.default_state - && block_below != Block::TWISTING_VINES_PLANT.default_state - } - - fn find_target_y(&self, chunk: &T, pos: &mut BlockPos) -> bool { - // Try to find a valid floor by looking down - let mut current = *pos; - for _ in 0..self.spread_height { - if GenerationCache::get_block_state(chunk, ¤t.0) - .to_state() - .is_air() - { - let below = current.down(); - let block_below = GenerationCache::get_block_state(chunk, &below.0).to_state(); - if block_below == Block::WARPED_NYLIUM.default_state - || block_below == Block::WARPED_WART_BLOCK.default_state - || block_below == Block::TWISTING_VINES.default_state - || block_below == Block::TWISTING_VINES_PLANT.default_state - { - *pos = current; - return true; - } - } - current = current.down(); - } - false + let below = pos.down(); + let below_id = GenerationCache::get_block_state(chunk, &below.0).to_block_id(); + below_id != Block::NETHERRACK.id + && below_id != Block::WARPED_NYLIUM.id + && below_id != Block::WARPED_WART_BLOCK.id } } diff --git a/crates/pumpkin-world/src/generation/feature/features/vines.rs b/crates/pumpkin-world/src/generation/feature/features/vines.rs index ce19dce89..d1d7971bf 100644 --- a/crates/pumpkin-world/src/generation/feature/features/vines.rs +++ b/crates/pumpkin-world/src/generation/feature/features/vines.rs @@ -1,4 +1,4 @@ -use pumpkin_data::{Block, BlockDirection, BlockState}; +use pumpkin_data::{Block, BlockDirection, BlockState, block_properties::VineLikeProperties}; use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; use crate::generation::proto_chunk::GenerationCache; @@ -19,17 +19,19 @@ impl VinesFeature { if !chunk.is_air(&pos.0) { return false; } + for dir in BlockDirection::all() { - // TODO - if dir == BlockDirection::Down - || !GenerationCache::get_block_state(chunk, &pos.offset(dir.to_offset()).0) - .to_state() - .is_full_cube() - { + if dir == BlockDirection::Down { continue; } - let mut vine = - pumpkin_data::block_properties::VineLikeProperties::default(&Block::VINE); + + let neighbor = pos.offset(dir.to_offset()); + let neighbor_state = GenerationCache::get_block_state(chunk, &neighbor.0); + if !neighbor_state.to_state().is_side_solid(dir.opposite()) { + continue; + } + + let mut vine = VineLikeProperties::default(&Block::VINE); vine.north = dir == BlockDirection::North; vine.east = dir == BlockDirection::East; vine.south = dir == BlockDirection::South; @@ -38,6 +40,7 @@ impl VinesFeature { chunk.set_block_state(&pos.0, BlockState::from_id(vine.to_state_id(&Block::VINE))); return true; } + false } } diff --git a/crates/pumpkin-world/src/generation/feature/features/void_start_platform.rs b/crates/pumpkin-world/src/generation/feature/features/void_start_platform.rs index 90f0ad68e..dc3fb2954 100644 --- a/crates/pumpkin-world/src/generation/feature/features/void_start_platform.rs +++ b/crates/pumpkin-world/src/generation/feature/features/void_start_platform.rs @@ -1,12 +1,14 @@ use pumpkin_data::Block; -use pumpkin_util::{math::position::BlockPos, random::RandomGenerator}; +use pumpkin_util::{ + math::{position::BlockPos, vector3::Vector3}, + random::RandomGenerator, +}; use crate::generation::proto_chunk::GenerationCache; pub struct VoidStartPlatformFeature; impl VoidStartPlatformFeature { - #[allow(clippy::unused_self)] pub fn generate( &self, chunk: &mut T, @@ -16,12 +18,33 @@ impl VoidStartPlatformFeature { _random: &mut RandomGenerator, pos: BlockPos, ) -> bool { - for dx in -1..=1 { - for dz in -1..=1 { - let target = BlockPos::new(pos.0.x + dx, pos.0.y, pos.0.z + dz); - chunk.set_block_state(&target.0, Block::OBSIDIAN.default_state); + let chunk_x = pos.0.x >> 4; + let chunk_z = pos.0.z >> 4; + + if chunk_x.abs().max(chunk_z.abs()) > 1 { + return true; + } + + let platform_y = pos.0.y + 3; + let min_x = chunk_x * 16; + let max_x = min_x + 15; + let min_z = chunk_z * 16; + let max_z = min_z + 15; + + for z in min_z..=max_z { + for x in min_x..=max_x { + if (8 - x).abs().max((8 - z).abs()) <= 16 { + let target = Vector3::new(x, platform_y, z); + let state = if x == 8 && z == 8 { + Block::COBBLESTONE.default_state + } else { + Block::STONE.default_state + }; + chunk.set_block_state(&target, state); + } } } + true } } diff --git a/crates/pumpkin-world/src/generation/feature/features/weeping_vines.rs b/crates/pumpkin-world/src/generation/feature/features/weeping_vines.rs index c26503034..9cd154ccc 100644 --- a/crates/pumpkin-world/src/generation/feature/features/weeping_vines.rs +++ b/crates/pumpkin-world/src/generation/feature/features/weeping_vines.rs @@ -1,5 +1,8 @@ -use pumpkin_data::Block; -use pumpkin_util::{math::position::BlockPos, random::RandomGenerator, random::RandomImpl}; +use pumpkin_data::{Block, BlockDirection, BlockState, block_properties::KelpLikeProperties}; +use pumpkin_util::{ + math::position::BlockPos, + random::{RandomGenerator, RandomImpl}, +}; use crate::generation::proto_chunk::GenerationCache; @@ -16,21 +19,109 @@ impl WeepingVinesFeature { random: &mut RandomGenerator, pos: BlockPos, ) -> bool { - let ceiling_pos = pos.up(); - let ceiling_block = GenerationCache::get_block_state(chunk, &ceiling_pos.0).to_block_id(); - if ceiling_block == Block::AIR.id { + if !chunk.is_air(&pos.0) { return false; } - let length = random.next_bounded_i32(4) + 3; - for i in 0..length { - let target = BlockPos::new(pos.0.x, pos.0.y - i, pos.0.z); - if chunk.is_air(&target.0) { - chunk.set_block_state(&target.0, Block::WEEPING_VINES.default_state); - } else { - break; - } + let above = pos.up(); + let above_id = GenerationCache::get_block_state(chunk, &above.0).to_block_id(); + if above_id != Block::NETHERRACK.id && above_id != Block::NETHER_WART_BLOCK.id { + return false; } + + Self::place_roof_nether_wart(chunk, random, pos); + Self::place_roof_weeping_vines(chunk, random, pos); true } + + fn place_roof_nether_wart( + chunk: &mut T, + random: &mut RandomGenerator, + origin: BlockPos, + ) { + chunk.set_block_state(&origin.0, Block::NETHER_WART_BLOCK.default_state); + + for _ in 0..200 { + let place_pos = origin.add( + random.next_bounded_i32(6) - random.next_bounded_i32(6), + random.next_bounded_i32(2) - random.next_bounded_i32(5), + random.next_bounded_i32(6) - random.next_bounded_i32(6), + ); + + if chunk.is_air(&place_pos.0) { + let mut neighbours = 0; + for dir in BlockDirection::all() { + let neighbour_pos = place_pos.offset(dir.to_offset()); + let id = + GenerationCache::get_block_state(chunk, &neighbour_pos.0).to_block_id(); + if id == Block::NETHERRACK.id || id == Block::NETHER_WART_BLOCK.id { + neighbours += 1; + } + if neighbours > 1 { + break; + } + } + + if neighbours == 1 { + chunk.set_block_state(&place_pos.0, Block::NETHER_WART_BLOCK.default_state); + } + } + } + } + + fn place_roof_weeping_vines( + chunk: &mut T, + random: &mut RandomGenerator, + origin: BlockPos, + ) { + for _ in 0..100 { + let place_pos = origin.add( + random.next_bounded_i32(8) - random.next_bounded_i32(8), + random.next_bounded_i32(2) - random.next_bounded_i32(7), + random.next_bounded_i32(8) - random.next_bounded_i32(8), + ); + + if chunk.is_air(&place_pos.0) { + let above = place_pos.up(); + let above_id = GenerationCache::get_block_state(chunk, &above.0).to_block_id(); + if above_id == Block::NETHERRACK.id || above_id == Block::NETHER_WART_BLOCK.id { + let mut vine_height = random.next_bounded_i32(8) + 1; + if random.next_bounded_i32(6) == 0 { + vine_height *= 2; + } + if random.next_bounded_i32(5) == 0 { + vine_height = 1; + } + + Self::place_weeping_vines_column(chunk, random, place_pos, vine_height, 17, 25); + } + } + } + } + + pub fn place_weeping_vines_column( + chunk: &mut T, + random: &mut RandomGenerator, + mut place_pos: BlockPos, + total_height: i32, + min_age: i32, + max_age: i32, + ) { + for height in 0..=total_height { + if chunk.is_air(&place_pos.0) { + if height == total_height || !chunk.is_air(&place_pos.down().0) { + let age = random.next_inbetween_i32(min_age, max_age); + let mut props = KelpLikeProperties::default(&Block::WEEPING_VINES); + props.age = age as u8; + let state_id = props.to_state_id(&Block::WEEPING_VINES); + chunk.set_block_state(&place_pos.0, BlockState::from_id(state_id)); + break; + } + + chunk.set_block_state(&place_pos.0, Block::WEEPING_VINES_PLANT.default_state); + } + + place_pos = place_pos.down(); + } + } } diff --git a/crates/pumpkin-world/src/generation/proto_chunk.rs b/crates/pumpkin-world/src/generation/proto_chunk.rs index 7cdb462d1..d7ebd49b5 100644 --- a/crates/pumpkin-world/src/generation/proto_chunk.rs +++ b/crates/pumpkin-world/src/generation/proto_chunk.rs @@ -96,6 +96,9 @@ pub trait GenerationCache: HeightLimitView + BlockAccessor { chunk_x: i32, chunk_z: i32, ) -> Option<&crate::generation::blender::blending_data::BlendingData>; + fn get_sea_level(&self) -> i32 { + 63 + } } const AIR_BLOCK: Block = Block::AIR; @@ -184,6 +187,11 @@ impl TerrainCache { } impl ProtoChunk { + #[must_use] + pub const fn get_sea_level(&self) -> i32 { + 63 + } + #[cfg(test)] pub(crate) fn has_structure(&self, key: StructureKeys) -> bool { self.structure_starts.contains_key(&key) @@ -1717,4 +1725,7 @@ impl GenerationCache for ProtoChunk { ) -> Option<&crate::generation::blender::blending_data::BlendingData> { None } + fn get_sea_level(&self) -> i32 { + Self::get_sea_level(self) + } } diff --git a/crates/pumpkin-world/src/generation/structure/placement.rs b/crates/pumpkin-world/src/generation/structure/placement.rs index 8921d583e..250263ff7 100644 --- a/crates/pumpkin-world/src/generation/structure/placement.rs +++ b/crates/pumpkin-world/src/generation/structure/placement.rs @@ -7,7 +7,6 @@ use pumpkin_util::{ math::floor_div, random::{ RandomGenerator, RandomImpl, get_carver_seed, get_region_seed, legacy_rand::LegacyRand, - xoroshiro128::Xoroshiro, }, }; use std::f64::consts::PI; @@ -250,8 +249,8 @@ fn should_generate_frequency( ) -> bool { match method { FrequencyReductionMethod::Default => { - let region_seed = get_region_seed(seed as u64, chunk_x, chunk_z, salt); - let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(region_seed)); + let region_seed = get_region_seed(seed as u64, salt as i32, chunk_x, chunk_z as u32); + let mut random = LegacyRand::from_seed(region_seed); random.next_f32() < frequency } FrequencyReductionMethod::LegacyType1 => { @@ -263,12 +262,12 @@ fn should_generate_frequency( } FrequencyReductionMethod::LegacyType2 => { let region_seed = get_region_seed(seed as u64, chunk_x, chunk_z, 10387320); - let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(region_seed)); + let mut random = LegacyRand::from_seed(region_seed); random.next_f32() < frequency } FrequencyReductionMethod::LegacyType3 => { let carver_seed = get_carver_seed(seed as u64, chunk_x, chunk_z); - let mut random = RandomGenerator::Xoroshiro(Xoroshiro::from_seed(carver_seed)); + let mut random = LegacyRand::from_seed(carver_seed); random.next_f64() < f64::from(frequency) } } diff --git a/crates/pumpkin-world/src/generation/structure/structures/buried_treasure.rs b/crates/pumpkin-world/src/generation/structure/structures/buried_treasure.rs index b8fee5f1c..cead8e620 100644 --- a/crates/pumpkin-world/src/generation/structure/structures/buried_treasure.rs +++ b/crates/pumpkin-world/src/generation/structure/structures/buried_treasure.rs @@ -1,23 +1,20 @@ use std::sync::Arc; -use pumpkin_data::{Block, BlockDirection, BlockId}; +use pumpkin_data::Block; use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::{ - HeightMap, - math::{block_box::BlockBox, position::BlockPos}, + BlockDirection, HeightMap, + math::{block_box::BlockBox, position::BlockPos, vector3::Vector3}, random::{RandomGenerator, RandomImpl, hash_block_pos, legacy_rand::LegacyRand}, }; use crate::{ ProtoChunk, - generation::{ - positions::chunk_pos::{get_center_x, get_center_z}, - structure::{ - piece::StructurePieceType, - structures::{ - StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase, - StructurePiecesCollector, StructurePosition, WorldPortalExt, - }, + generation::structure::{ + piece::StructurePieceType, + structures::{ + StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase, + StructurePiecesCollector, StructurePosition, WorldPortalExt, }, }, }; @@ -29,13 +26,12 @@ impl StructureGenerator for BuriedTreasureGenerator { &self, context: StructureGeneratorContext<'_>, ) -> Option { - let x = get_center_x(context.chunk_x); - let z = get_center_z(context.chunk_z); + let x = (context.chunk_x << 4) + 9; + let z = (context.chunk_z << 4) + 9; - let bounding_box = BlockBox::new(x, -64, z, x, 320, z); + let bounding_box = BlockBox::new(x, 90, z, x, 90, z); let mut collector = StructurePiecesCollector::default(); - collector.add_piece(Box::new(BuriedTreasurePiece { piece: StructurePiece::new(StructurePieceType::BuriedTreasure, bounding_box, 0), })); @@ -55,88 +51,6 @@ impl StructurePieceBase for BuriedTreasurePiece { fn as_any(&self) -> &dyn std::any::Any { self } - fn place( - &mut self, - chunk: &mut ProtoChunk, - _block_registry: &dyn WorldPortalExt, - _random: &mut RandomGenerator, - _seed: i64, - _chunk_box: &BlockBox, - ) { - let boundingbox = self.bounding_box(); - let y = chunk.get_top_y( - &HeightMap::OceanFloorWg, - boundingbox.min.x, - boundingbox.min.z, - ); - - let mut pos = BlockPos::new(boundingbox.min.x, y, boundingbox.min.z); - let bottom_y = chunk.bottom_y() as i32; - - for _ in (bottom_y..=y).rev() { - let state = chunk.get_block_state(&pos.0); - let down_pos = pos.down(); - let down_raw_state = chunk.get_block_state(&down_pos.0); - let down_block = down_raw_state.to_block_id(); - - if down_block == Block::SANDSTONE - || down_block == Block::STONE - || down_block == Block::ANDESITE - || down_block == Block::GRANITE - || down_block == Block::DIORITE - { - for dir in BlockDirection::all() { - let offset_pos = pos.offset(dir.to_offset()); - let dir_state = chunk.get_block_state(&offset_pos.0); - - if !dir_state.to_state().is_air() && !Self::is_liquid(dir_state.to_block_id()) { - continue; - } - - let down_offset_pos = offset_pos.down(); - let down_offset_state = chunk.get_block_state(&down_offset_pos.0); - - if (down_offset_state.to_state().is_air() - || Self::is_liquid(down_offset_state.to_block_id())) - && dir != BlockDirection::Up - { - chunk.set_block_state( - offset_pos.0.x, - offset_pos.0.y, - offset_pos.0.z, - down_raw_state.to_state(), - ); - continue; - } - - let state1 = - if state.to_state().is_air() || Self::is_liquid(state.to_block_id()) { - Block::SAND.default_state - } else { - state.to_state() - }; - chunk.set_block_state(offset_pos.0.x, offset_pos.0.y, offset_pos.0.z, state1); - } - - chunk.set_block_state(pos.0.x, pos.0.y, pos.0.z, Block::CHEST.default_state); - - let mut chest_nbt = NbtCompound::new(); - chest_nbt.put_string("id", "minecraft:chest".to_string()); - chest_nbt.put_int("x", pos.0.x); - chest_nbt.put_int("y", pos.0.y); - chest_nbt.put_int("z", pos.0.z); - chest_nbt.put_string("LootTable", "minecraft:chests/buried_treasure".to_string()); - - let mut random = - LegacyRand::from_seed(hash_block_pos(pos.0.x, pos.0.y, pos.0.z) as u64); - chest_nbt.put_long("LootTableSeed", random.next_i64()); - - chunk.add_block_entity(chest_nbt); - return; - } - pos = pos.down(); - } - } fn get_structure_piece(&self) -> &StructurePiece { &self.piece @@ -145,10 +59,88 @@ impl StructurePieceBase for BuriedTreasurePiece { fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { &mut self.piece } -} -impl BuriedTreasurePiece { - fn is_liquid(id: BlockId) -> bool { - id == BlockId::WATER || id == BlockId::LAVA + fn place( + &mut self, + chunk: &mut ProtoChunk, + _block_registry: &dyn WorldPortalExt, + _random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { + let bb = self.bounding_box(); + let y = chunk.get_top_y(&HeightMap::OceanFloorWg, bb.min.x, bb.min.z); + let min_y = chunk.bottom_y() as i32; + let mut cur_y = y; + + while cur_y > min_y { + let pos = Vector3::new(bb.min.x, cur_y, bb.min.z); + let below_pos = Vector3::new(bb.min.x, cur_y - 1, bb.min.z); + + let current_state = chunk.get_block_state(&pos).to_state(); + let below_state = chunk.get_block_state(&below_pos).to_state(); + let below_block = Block::from_state_id(below_state.id); + + if *below_block == Block::SANDSTONE + || *below_block == Block::STONE + || *below_block == Block::ANDESITE + || *below_block == Block::GRANITE + || *below_block == Block::DIORITE + { + let soft_state = if !current_state.is_air() && !current_state.is_liquid() { + current_state + } else { + Block::SAND.default_state + }; + + for dir in [ + BlockDirection::Down, + BlockDirection::Up, + BlockDirection::North, + BlockDirection::South, + BlockDirection::West, + BlockDirection::East, + ] { + let offset = dir.to_vector(); + let rel_pos = pos + offset; + let rel_state = chunk.get_block_state(&rel_pos).to_state(); + + if rel_state.is_air() || rel_state.is_liquid() { + let below_rel_pos = rel_pos - Vector3::new(0, 1, 0); + let below_rel_state = chunk.get_block_state(&below_rel_pos).to_state(); + + if (below_rel_state.is_air() || below_rel_state.is_liquid()) + && dir != BlockDirection::Up + { + chunk.set_block_state(rel_pos.x, rel_pos.y, rel_pos.z, below_state); + } else { + chunk.set_block_state(rel_pos.x, rel_pos.y, rel_pos.z, soft_state); + } + } + } + + self.piece.bounding_box = BlockBox::new(pos.x, pos.y, pos.z, pos.x, pos.y, pos.z); + + if chunk_box.contains_pos(&pos) { + chunk.set_block_state(pos.x, pos.y, pos.z, Block::CHEST.default_state); + + let mut nbt = NbtCompound::new(); + nbt.put_string("id", "minecraft:chest".to_string()); + nbt.put_int("x", pos.x); + nbt.put_int("y", pos.y); + nbt.put_int("z", pos.z); + nbt.put_string("LootTable", "minecraft:chests/buried_treasure".to_string()); + + let mut random = + LegacyRand::from_seed(hash_block_pos(pos.x, pos.y, pos.z) as u64); + nbt.put_long("LootTableSeed", random.next_i64()); + + chunk.add_block_entity(nbt); + } + return; + } + + cur_y -= 1; + } } } diff --git a/crates/pumpkin-world/src/generation/structure/structures/desert_pyramid.rs b/crates/pumpkin-world/src/generation/structure/structures/desert_pyramid.rs index 08b3bd7c6..5544d0de8 100644 --- a/crates/pumpkin-world/src/generation/structure/structures/desert_pyramid.rs +++ b/crates/pumpkin-world/src/generation/structure/structures/desert_pyramid.rs @@ -103,9 +103,19 @@ impl DesertPyramidPiece { true } - fn place_sand(&self, chunk: &mut ProtoChunk, bb: &BlockBox, x: i32, y: i32, z: i32) { + fn place_sand( + &self, + chunk: &mut ProtoChunk, + bb: &BlockBox, + x: i32, + y: i32, + z: i32, + potential_positions: &mut Vec>, + ) { self.piece .add_block(chunk, Block::SAND.default_state, x, y, z, bb); + let world_pos = self.piece.offset_pos(x, y, z); + potential_positions.push(world_pos); } #[expect(clippy::too_many_arguments)] @@ -119,11 +129,12 @@ impl DesertPyramidPiece { x1: i32, y1: i32, z1: i32, + potential_positions: &mut Vec>, ) { for y in y0..=y1 { for x in x0..=x1 { for z in z0..=z1 { - self.place_sand(chunk, bb, x, y, z); + self.place_sand(chunk, bb, x, y, z, potential_positions); } } } @@ -158,14 +169,17 @@ impl DesertPyramidPiece { z0: i32, x1: i32, z1: i32, + potential_positions: &mut Vec>, ) { for x in x0..=x1 { for z in z0..=z1 { self.place_collapsed_roof_piece(chunk, bb, random, x, y0, z); } } - // TODO: Pick a random collapsed roof position for sus sand placement in - // the structure-level afterPlace pass when brushable support exists. + let rx = x0 + random.next_bounded_i32(x1 - x0 + 1); + let rz = z0 + random.next_bounded_i32(z1 - z0 + 1); + let sus_pos = self.piece.offset_pos(rx, y0, rz); + potential_positions.push(sus_pos); } fn try_place_chest( @@ -205,8 +219,36 @@ impl DesertPyramidPiece { } fn add_cellar(&self, chunk: &mut ProtoChunk, bb: &BlockBox, random: &mut RandomGenerator) { - self.add_cellar_stairs(chunk, bb, random); - self.add_cellar_room(chunk, bb, random); + let mut potential_positions = Vec::new(); + self.add_cellar_stairs(chunk, bb, random, &mut potential_positions); + self.add_cellar_room(chunk, bb, random, &mut potential_positions); + + for i in (1..potential_positions.len()).rev() { + let j = random.next_bounded_i32(i as i32 + 1) as usize; + potential_positions.swap(i, j); + } + + let sus_count = + (random.next_bounded_i32(5) + 4).min(potential_positions.len() as i32) as usize; + for pos in potential_positions.into_iter().take(sus_count) { + if bb.contains_pos(&pos) { + chunk.set_block_state(pos.x, pos.y, pos.z, Block::SUSPICIOUS_SAND.default_state); + let mut nbt = NbtCompound::new(); + nbt.put_string("id", "minecraft:brushable_block".to_string()); + nbt.put_int("x", pos.x); + nbt.put_int("y", pos.y); + nbt.put_int("z", pos.z); + nbt.put_string( + "LootTable", + "minecraft:archaeology/desert_pyramid".to_string(), + ); + nbt.put_long( + "LootTableSeed", + BlockPos::new(pos.x, pos.y, pos.z).as_long(), + ); + chunk.add_block_entity(nbt); + } + } } fn add_cellar_stairs( @@ -214,6 +256,7 @@ impl DesertPyramidPiece { chunk: &mut ProtoChunk, bb: &BlockBox, random: &mut RandomGenerator, + potential_positions: &mut Vec>, ) { let west_stairs = Self::sandstone_stairs(HorizontalFacing::West); self.piece.add_block(chunk, west_stairs, 13, -1, 17, bb); @@ -225,12 +268,12 @@ impl DesertPyramidPiece { let (x, y, z) = (16, -4, 13); let variant = random.next_bool(); - self.piece.add_block(chunk, sand, x - 4, y + 4, z + 4, bb); - self.piece.add_block(chunk, sand, x - 3, y + 4, z + 4, bb); - self.piece.add_block(chunk, sand, x - 2, y + 4, z + 4, bb); - self.piece.add_block(chunk, sand, x - 1, y + 4, z + 4, bb); - self.piece.add_block(chunk, sand, x, y + 4, z + 4, bb); - self.piece.add_block(chunk, sand, x - 2, y + 3, z + 4, bb); + self.place_sand(chunk, bb, x - 4, y + 4, z + 4, potential_positions); + self.place_sand(chunk, bb, x - 3, y + 4, z + 4, potential_positions); + self.place_sand(chunk, bb, x - 2, y + 4, z + 4, potential_positions); + self.place_sand(chunk, bb, x - 1, y + 4, z + 4, potential_positions); + self.place_sand(chunk, bb, x, y + 4, z + 4, potential_positions); + self.place_sand(chunk, bb, x - 2, y + 3, z + 4, potential_positions); self.piece.add_block( chunk, if variant { sand } else { sandstone }, @@ -247,13 +290,19 @@ impl DesertPyramidPiece { z + 4, bb, ); - self.piece.add_block(chunk, sand, x - 1, y + 2, z + 4, bb); + self.place_sand(chunk, bb, x - 1, y + 2, z + 4, potential_positions); self.piece.add_block(chunk, sandstone, x, y + 2, z + 4, bb); - self.piece.add_block(chunk, sand, x, y + 1, z + 4, bb); + self.place_sand(chunk, bb, x, y + 1, z + 4, potential_positions); } #[expect(clippy::too_many_lines)] - fn add_cellar_room(&self, chunk: &mut ProtoChunk, bb: &BlockBox, random: &mut RandomGenerator) { + fn add_cellar_room( + &self, + chunk: &mut ProtoChunk, + bb: &BlockBox, + random: &mut RandomGenerator, + potential_positions: &mut Vec>, + ) { let (x, y, z) = (16, -4, 13); let cut = Block::CUT_SANDSTONE.default_state; let chiseled = Block::CHISELED_SANDSTONE.default_state; @@ -416,8 +465,28 @@ impl DesertPyramidPiece { cut, ); - self.place_sand_box(chunk, bb, x - 2, y + 1, z - 2, x + 2, y + 3, z + 2); - self.place_collapsed_roof(chunk, bb, random, x - 2, y + 4, z - 2, x + 2, z + 2); + self.place_sand_box( + chunk, + bb, + x - 2, + y + 1, + z - 2, + x + 2, + y + 3, + z + 2, + potential_positions, + ); + self.place_collapsed_roof( + chunk, + bb, + random, + x - 2, + y + 4, + z - 2, + x + 2, + z + 2, + potential_positions, + ); self.piece.add_block(chunk, blue, x, y, z, bb); self.piece.add_block(chunk, orange, x + 1, y, z - 1, bb); self.piece.add_block(chunk, orange, x + 1, y, z + 1, bb); @@ -429,24 +498,24 @@ impl DesertPyramidPiece { self.piece.add_block(chunk, orange, x, y, z - 2, bb); self.piece.add_block(chunk, orange, x + 3, y, z, bb); - self.place_sand(chunk, bb, x + 3, y + 1, z); - self.place_sand(chunk, bb, x + 3, y + 2, z); + self.place_sand(chunk, bb, x + 3, y + 1, z, potential_positions); + self.place_sand(chunk, bb, x + 3, y + 2, z, potential_positions); self.piece.add_block(chunk, cut, x + 4, y + 1, z, bb); self.piece.add_block(chunk, chiseled, x + 4, y + 2, z, bb); self.piece.add_block(chunk, orange, x - 3, y, z, bb); - self.place_sand(chunk, bb, x - 3, y + 1, z); - self.place_sand(chunk, bb, x - 3, y + 2, z); + self.place_sand(chunk, bb, x - 3, y + 1, z, potential_positions); + self.place_sand(chunk, bb, x - 3, y + 2, z, potential_positions); self.piece.add_block(chunk, cut, x - 4, y + 1, z, bb); self.piece.add_block(chunk, chiseled, x - 4, y + 2, z, bb); self.piece.add_block(chunk, orange, x, y, z + 3, bb); - self.place_sand(chunk, bb, x, y + 1, z + 3); - self.place_sand(chunk, bb, x, y + 2, z + 3); + self.place_sand(chunk, bb, x, y + 1, z + 3, potential_positions); + self.place_sand(chunk, bb, x, y + 2, z + 3, potential_positions); self.piece.add_block(chunk, orange, x, y, z - 3, bb); - self.place_sand(chunk, bb, x, y + 1, z - 3); - self.place_sand(chunk, bb, x, y + 2, z - 3); + self.place_sand(chunk, bb, x, y + 1, z - 3, potential_positions); + self.place_sand(chunk, bb, x, y + 2, z - 3, potential_positions); self.piece.add_block(chunk, cut, x, y + 1, z - 4, bb); self.piece.add_block(chunk, chiseled, x, -2, z - 4, bb); } diff --git a/crates/pumpkin-world/src/generation/structure/structures/jigsaw.rs b/crates/pumpkin-world/src/generation/structure/structures/jigsaw.rs index 4fc2e7f09..53f6df451 100644 --- a/crates/pumpkin-world/src/generation/structure/structures/jigsaw.rs +++ b/crates/pumpkin-world/src/generation/structure/structures/jigsaw.rs @@ -10,7 +10,7 @@ use crate::generation::structure::template::{ use pumpkin_util::math::block_box::BlockBox; use pumpkin_util::math::position::BlockPos; use pumpkin_util::math::vector3::Vector3; -use pumpkin_util::random::RandomImpl; +use pumpkin_util::random::{RandomDeriverImpl, RandomImpl}; use serde::Deserialize; use std::sync::Arc; @@ -880,7 +880,11 @@ impl StructureGenerator for JigsawGenerator { &[] }; - let pool_alias_lookup = PoolAliasLookup::from_bindings(pool_aliases, &mut context.random); + let mut alias_random = + pumpkin_util::random::legacy_rand::LegacyRand::from_seed(context.seed as u64) + .next_splitter() + .split_pos(start_pos.0.x, start_pos.0.y, start_pos.0.z); + let pool_alias_lookup = PoolAliasLookup::from_bindings(pool_aliases, &mut alias_random); let start_pool = if self.start_pool.is_empty() { structure diff --git a/crates/pumpkin-world/src/generation/structure/structures/jungle_temple.rs b/crates/pumpkin-world/src/generation/structure/structures/jungle_temple.rs index 5c20bf59d..210e58c0a 100644 --- a/crates/pumpkin-world/src/generation/structure/structures/jungle_temple.rs +++ b/crates/pumpkin-world/src/generation/structure/structures/jungle_temple.rs @@ -56,10 +56,10 @@ impl StructureGenerator for JungleTempleGenerator { collector.add_piece(Box::new(JungleTemplePiece { piece, height_adjusted: false, - placed_main_chest: true, - placed_hidden_chest: true, - placed_trap_1: true, - placed_trap_2: true, + placed_main_chest: false, + placed_hidden_chest: false, + placed_trap_1: false, + placed_trap_2: false, })); Some(StructurePosition { @@ -508,18 +508,18 @@ impl StructurePieceBase for JungleTemplePiece { ); self.piece .add_block(chunk, Block::MOSSY_COBBLESTONE.default_state, 3, -3, 1, bb); - /*if !self.placed_trap_1 { - self.placed_trap_1 = this.createDispenser( + if !self.placed_trap_1 { + self.placed_trap_1 = self.piece.add_dispenser( chunk, bb, random, 3, -2, 1, - Direction.NORTH, - BuiltInLootTables.JUNGLE_TEMPLE_DISPENSER, + Facing::North, + "minecraft:chests/jungle_temple_dispenser", ); - }*/ + } self.piece .add_block(chunk, Self::vine_facing(Facing::South), 3, -2, 2, bb); @@ -592,27 +592,34 @@ impl StructurePieceBase for JungleTemplePiece { .add_block(chunk, Block::MOSSY_COBBLESTONE.default_state, 9, -3, 4, bb); }; self.piece.add_block(chunk, redstone_wire_ns, 9, -2, 4, bb); - /*if !self.placed_trap_2 { - self.placed_trap_2 = this.createDispenser( + if !self.placed_trap_2 { + self.placed_trap_2 = self.piece.add_dispenser( chunk, bb, random, 9, -2, 3, - Direction.WEST, - BuiltInLootTables.JUNGLE_TEMPLE_DISPENSER, + Facing::West, + "minecraft:chests/jungle_temple_dispenser", ); - }*/ + } self.piece .add_block(chunk, Self::vine_facing(Facing::East), 8, -1, 3, bb); self.piece .add_block(chunk, Self::vine_facing(Facing::East), 8, -2, 3, bb); - /*if !self.placed_main_chest { - self.placed_main_chest = - this.createChest(chunk, bb, random, 8, -3, 3, BuiltInLootTables.JUNGLE_TEMPLE); - }*/ + if !self.placed_main_chest { + self.placed_main_chest = self.piece.add_chest( + chunk, + bb, + random, + 8, + -3, + 3, + "minecraft:chests/jungle_temple", + ); + } self.piece .add_block(chunk, Block::MOSSY_COBBLESTONE.default_state, 9, -3, 2, bb); @@ -738,17 +745,17 @@ impl StructurePieceBase for JungleTemplePiece { BlockState::from_id(props.to_state_id(&Block::REPEATER)) }; self.piece.add_block(chunk, repeater_state, 10, -2, 10, bb); - /*if !self.placed_hidden_chest { - self.placed_hidden_chest = this.createChest( + if !self.placed_hidden_chest { + self.placed_hidden_chest = self.piece.add_chest( chunk, bb, random, 9, -3, 10, - BuiltInLootTables.JUNGLE_TEMPLE, + "minecraft:chests/jungle_temple", ); - }*/ + } } } const fn set_side_dir(props: &mut RedstoneWireLikeProperties, facing: HorizontalFacing) { diff --git a/crates/pumpkin-world/src/generation/structure/structures/mineshaft.rs b/crates/pumpkin-world/src/generation/structure/structures/mineshaft.rs index b963ddc48..e227603c2 100644 --- a/crates/pumpkin-world/src/generation/structure/structures/mineshaft.rs +++ b/crates/pumpkin-world/src/generation/structure/structures/mineshaft.rs @@ -1,25 +1,59 @@ use std::sync::Arc; -use pumpkin_data::Block; +use pumpkin_data::{ + Block, BlockState, + block_properties::{HorizontalFacing, WallTorchLikeProperties}, +}; +use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::{ - math::{block_box::BlockBox, position::BlockPos}, - random::RandomGenerator, + BlockDirection, + math::{block_box::BlockBox, position::BlockPos, vector3::Vector3}, + random::{RandomGenerator, RandomImpl}, }; use crate::{ ProtoChunk, - generation::{ - positions::chunk_pos::{get_center_x, get_center_z}, - structure::{ - piece::StructurePieceType, - structures::{ - StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase, - StructurePiecesCollector, StructurePosition, WorldPortalExt, - }, + generation::structure::{ + piece::StructurePieceType, + structures::{ + StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase, + StructurePiecesCollector, StructurePosition, WorldPortalExt, }, }, }; +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum MineshaftType { + Normal, + Mesa, +} + +impl MineshaftType { + #[must_use] + pub const fn wood(self) -> &'static BlockState { + match self { + Self::Normal => Block::OAK_LOG.default_state, + Self::Mesa => Block::DARK_OAK_LOG.default_state, + } + } + + #[must_use] + pub const fn planks(self) -> &'static BlockState { + match self { + Self::Normal => Block::OAK_PLANKS.default_state, + Self::Mesa => Block::DARK_OAK_PLANKS.default_state, + } + } + + #[must_use] + pub const fn fence(self) -> &'static BlockState { + match self { + Self::Normal => Block::OAK_FENCE.default_state, + Self::Mesa => Block::DARK_OAK_FENCE.default_state, + } + } +} + pub struct MineshaftGenerator { pub is_mesa: bool, } @@ -27,48 +61,423 @@ pub struct MineshaftGenerator { impl StructureGenerator for MineshaftGenerator { fn get_structure_position( &self, - context: StructureGeneratorContext<'_>, + mut context: StructureGeneratorContext<'_>, ) -> Option { - let chunk_center_x = get_center_x(context.chunk_x); - let chunk_center_z = get_center_z(context.chunk_z); + context.random.next_f64(); + let start_x = context.chunk_x << 4; + let start_z = context.chunk_z << 4; + let room_x = start_x + 2; + let room_z = start_z + 2; - let bounding_box = BlockBox::new( - chunk_center_x - 16, - context.min_y, - chunk_center_z - 16, - chunk_center_x + 16, - 256, - chunk_center_z + 16, - ); + let shaft_type = if self.is_mesa { + MineshaftType::Mesa + } else { + MineshaftType::Normal + }; + + let mut start_room = MineShaftRoom::new(0, &mut context.random, room_x, room_z, shaft_type); let mut collector = StructurePiecesCollector::default(); - collector.add_piece(Box::new(MineshaftPiece { - piece: StructurePiece::new(StructurePieceType::MineshaftCrossing, bounding_box, 0), - is_mesa: self.is_mesa, - })); + let start_piece_box = start_room.piece.bounding_box; + + let mut children = Vec::new(); + start_room.add_children_to_list( + &start_piece_box, + shaft_type, + &collector, + &mut context.random, + &mut children, + ); + + collector.add_piece(Box::new(start_room)); + + while !children.is_empty() { + let next_piece = children.remove(0); + next_piece.build_children( + &start_piece_box, + shaft_type, + &collector, + &mut context.random, + &mut children, + ); + collector.add_piece(next_piece.into_piece_base()); + } + + if self.is_mesa { + let bbox = collector.get_bounding_box(); + let center_x = i32::midpoint(bbox.min.x, bbox.max.x); + let center_z = i32::midpoint(bbox.min.z, bbox.max.z); + let center_y = i32::midpoint(bbox.min.y, bbox.max.y); + + let surface_height = context + .height_sampler + .as_deref_mut() + .map_or(context.sea_level, |s| s.estimate_height(center_x, center_z)); + + let target_y = if surface_height <= context.sea_level { + context.sea_level + } else { + let range = surface_height - context.sea_level + 1; + context.sea_level + context.random.next_bounded_i32(range) + }; + + let dy = target_y - center_y; + collector.shift(dy); + } else { + collector.shift_into(context.sea_level, context.min_y, &mut context.random, 10); + } Some(StructurePosition { - start_pos: BlockPos::new(chunk_center_x, 64, chunk_center_z), + start_pos: BlockPos::new(start_x + 8, 50, start_z), collector: Arc::new(collector.into()), }) } } -pub struct MineshaftPiece { - piece: StructurePiece, - is_mesa: bool, +enum GeneratedPiece { + Corridor(MineShaftCorridor), + Crossing(MineShaftCrossing), + Stairs(MineShaftStairs), } -impl StructurePieceBase for MineshaftPiece { +impl GeneratedPiece { + fn build_children( + &self, + start_piece_box: &BlockBox, + shaft_type: MineshaftType, + collector: &StructurePiecesCollector, + random: &mut RandomGenerator, + children: &mut Vec, + ) { + match self { + Self::Corridor(c) => { + c.add_children_to_list(start_piece_box, shaft_type, collector, random, children); + } + Self::Crossing(cr) => { + cr.add_children_to_list(start_piece_box, shaft_type, collector, random, children); + } + Self::Stairs(s) => { + s.add_children_to_list(start_piece_box, shaft_type, collector, random, children); + } + } + } + + fn into_piece_base(self) -> Box { + match self { + Self::Corridor(c) => Box::new(c), + Self::Crossing(cr) => Box::new(cr), + Self::Stairs(s) => Box::new(s), + } + } +} + +#[expect(clippy::too_many_arguments)] +fn create_random_shaft_piece( + collector: &StructurePiecesCollector, + random: &mut RandomGenerator, + foot_x: i32, + foot_y: i32, + foot_z: i32, + direction: BlockDirection, + gen_depth: u32, + shaft_type: MineshaftType, +) -> Option { + let random_selection = random.next_bounded_i32(100); + if random_selection >= 80 { + if let Some(crossing_box) = + MineShaftCrossing::find_crossing(collector, random, foot_x, foot_y, foot_z, direction) + { + return Some(GeneratedPiece::Crossing(MineShaftCrossing::new( + gen_depth, + crossing_box, + Some(direction), + shaft_type, + ))); + } + } else if random_selection >= 70 { + if let Some(stairs_box) = + MineShaftStairs::find_stairs(collector, random, foot_x, foot_y, foot_z, direction) + { + return Some(GeneratedPiece::Stairs(MineShaftStairs::new( + gen_depth, stairs_box, direction, shaft_type, + ))); + } + } else if let Some(corridor_box) = + MineShaftCorridor::find_corridor_size(collector, random, foot_x, foot_y, foot_z, direction) + { + return Some(GeneratedPiece::Corridor(MineShaftCorridor::new( + gen_depth, + random, + corridor_box, + direction, + shaft_type, + ))); + } + None +} + +#[expect(clippy::too_many_arguments)] +fn generate_and_add_piece( + start_piece_box: &BlockBox, + collector: &StructurePiecesCollector, + random: &mut RandomGenerator, + foot_x: i32, + foot_y: i32, + foot_z: i32, + direction: BlockDirection, + depth: u32, + shaft_type: MineshaftType, + children: &mut Vec, +) { + if depth <= 8 + && (foot_x - start_piece_box.min.x).abs() <= 80 + && (foot_z - start_piece_box.min.z).abs() <= 80 + && let Some(new_piece) = create_random_shaft_piece( + collector, + random, + foot_x, + foot_y, + foot_z, + direction, + depth + 1, + shaft_type, + ) + { + children.push(new_piece); + } +} + +pub struct MineShaftRoom { + pub piece: StructurePiece, + pub shaft_type: MineshaftType, + pub child_entrance_boxes: Vec, +} + +impl MineShaftRoom { + #[must_use] + pub fn new( + gen_depth: u32, + random: &mut RandomGenerator, + west: i32, + north: i32, + shaft_type: MineshaftType, + ) -> Self { + let max_x = west + 7 + random.next_bounded_i32(6); + let max_y = 54 + random.next_bounded_i32(6); + let max_z = north + 7 + random.next_bounded_i32(6); + let bounding_box = BlockBox::new(west, 50, north, max_x, max_y, max_z); + Self { + piece: StructurePiece::new(StructurePieceType::MineshaftRoom, bounding_box, gen_depth), + shaft_type, + child_entrance_boxes: Vec::new(), + } + } + + #[expect(clippy::too_many_lines)] + fn add_children_to_list( + &mut self, + start_piece_box: &BlockBox, + shaft_type: MineshaftType, + collector: &StructurePiecesCollector, + random: &mut RandomGenerator, + children: &mut Vec, + ) { + let depth = self.piece.chain_length; + let y_span = self.piece.bounding_box.max.y - self.piece.bounding_box.min.y + 1; + let mut height_space = y_span - 4; + if height_space <= 0 { + height_space = 1; + } + + let x_span = self.piece.bounding_box.max.x - self.piece.bounding_box.min.x + 1; + let z_span = self.piece.bounding_box.max.z - self.piece.bounding_box.min.z + 1; + + let mut pos = 0; + while pos < x_span { + pos += random.next_bounded_i32(x_span); + if pos + 3 > x_span { + break; + } + let child_x = self.piece.bounding_box.min.x + pos; + let child_y = self.piece.bounding_box.min.y + random.next_bounded_i32(height_space) + 1; + let child_z = self.piece.bounding_box.min.z - 1; + + let prev_len = children.len(); + generate_and_add_piece( + start_piece_box, + collector, + random, + child_x, + child_y, + child_z, + BlockDirection::North, + depth, + shaft_type, + children, + ); + if children.len() > prev_len { + let bb = match &children[prev_len] { + GeneratedPiece::Corridor(c) => c.piece.bounding_box, + GeneratedPiece::Crossing(c) => c.piece.bounding_box, + GeneratedPiece::Stairs(s) => s.piece.bounding_box, + }; + self.child_entrance_boxes.push(BlockBox::new( + bb.min.x, + bb.min.y, + self.piece.bounding_box.min.z, + bb.max.x, + bb.max.y, + self.piece.bounding_box.min.z + 1, + )); + } + pos += 4; + } + + pos = 0; + while pos < x_span { + pos += random.next_bounded_i32(x_span); + if pos + 3 > x_span { + break; + } + let child_x = self.piece.bounding_box.min.x + pos; + let child_y = self.piece.bounding_box.min.y + random.next_bounded_i32(height_space) + 1; + let child_z = self.piece.bounding_box.max.z + 1; + + let prev_len = children.len(); + generate_and_add_piece( + start_piece_box, + collector, + random, + child_x, + child_y, + child_z, + BlockDirection::South, + depth, + shaft_type, + children, + ); + if children.len() > prev_len { + let bb = match &children[prev_len] { + GeneratedPiece::Corridor(c) => c.piece.bounding_box, + GeneratedPiece::Crossing(c) => c.piece.bounding_box, + GeneratedPiece::Stairs(s) => s.piece.bounding_box, + }; + self.child_entrance_boxes.push(BlockBox::new( + bb.min.x, + bb.min.y, + self.piece.bounding_box.max.z - 1, + bb.max.x, + bb.max.y, + self.piece.bounding_box.max.z, + )); + } + pos += 4; + } + + pos = 0; + while pos < z_span { + pos += random.next_bounded_i32(z_span); + if pos + 3 > z_span { + break; + } + let child_x = self.piece.bounding_box.min.x - 1; + let child_y = self.piece.bounding_box.min.y + random.next_bounded_i32(height_space) + 1; + let child_z = self.piece.bounding_box.min.z + pos; + + let prev_len = children.len(); + generate_and_add_piece( + start_piece_box, + collector, + random, + child_x, + child_y, + child_z, + BlockDirection::West, + depth, + shaft_type, + children, + ); + if children.len() > prev_len { + let bb = match &children[prev_len] { + GeneratedPiece::Corridor(c) => c.piece.bounding_box, + GeneratedPiece::Crossing(c) => c.piece.bounding_box, + GeneratedPiece::Stairs(s) => s.piece.bounding_box, + }; + self.child_entrance_boxes.push(BlockBox::new( + self.piece.bounding_box.min.x, + bb.min.y, + bb.min.z, + self.piece.bounding_box.min.x + 1, + bb.max.y, + bb.max.z, + )); + } + pos += 4; + } + + pos = 0; + while pos < z_span { + pos += random.next_bounded_i32(z_span); + if pos + 3 > z_span { + break; + } + let child_x = self.piece.bounding_box.max.x + 1; + let child_y = self.piece.bounding_box.min.y + random.next_bounded_i32(height_space) + 1; + let child_z = self.piece.bounding_box.min.z + pos; + + let prev_len = children.len(); + generate_and_add_piece( + start_piece_box, + collector, + random, + child_x, + child_y, + child_z, + BlockDirection::East, + depth, + shaft_type, + children, + ); + if children.len() > prev_len { + let bb = match &children[prev_len] { + GeneratedPiece::Corridor(c) => c.piece.bounding_box, + GeneratedPiece::Crossing(c) => c.piece.bounding_box, + GeneratedPiece::Stairs(s) => s.piece.bounding_box, + }; + self.child_entrance_boxes.push(BlockBox::new( + self.piece.bounding_box.max.x - 1, + bb.min.y, + bb.min.z, + self.piece.bounding_box.max.x, + bb.max.y, + bb.max.z, + )); + } + pos += 4; + } + } +} + +impl StructurePieceBase for MineShaftRoom { fn as_any(&self) -> &dyn std::any::Any { self } + fn get_structure_piece(&self) -> &StructurePiece { &self.piece } + fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { &mut self.piece } + + fn translate(&mut self, x: i32, y: i32, z: i32) { + self.piece.translate(x, y, z); + for bb in &mut self.child_entrance_boxes { + bb.move_pos(x, y, z); + } + } + fn place( &mut self, chunk: &mut ProtoChunk, @@ -77,64 +486,1188 @@ impl StructurePieceBase for MineshaftPiece { _seed: i64, chunk_box: &BlockBox, ) { - let origin = self.piece.bounding_box.min; - let start_y = 15; + let air = Block::CAVE_AIR.default_state; + let bb = self.piece.bounding_box; - let wood_planks = if self.is_mesa { - Block::DARK_OAK_PLANKS - } else { - Block::OAK_PLANKS - }; - let wood_fence = if self.is_mesa { - Block::DARK_OAK_FENCE - } else { - Block::OAK_FENCE - }; + let max_clear_y = (bb.min.y + 3).min(bb.max.y); + for y in (bb.min.y + 1)..=max_clear_y { + for x in bb.min.x..=bb.max.x { + for z in bb.min.z..=bb.max.z { + if chunk_box.contains(x, y, z) { + chunk.set_block_state(x, y, z, air); + } + } + } + } - // Draw underground corridors - for y in start_y..(start_y + 3) { - for x in (origin.x - 12)..=(origin.x + 12) { - for z in (origin.z - 12)..=(origin.z + 12) { - let in_center = (x - origin.x).abs() <= 2 && (z - origin.z).abs() <= 2; - let in_ns_corridor = (x - origin.x).abs() <= 1; - let in_ew_corridor = (z - origin.z).abs() <= 1; - - if (in_center || in_ns_corridor || in_ew_corridor) - && chunk_box.contains(x, y, z) - { - chunk.set_block_state(x, y, z, Block::AIR.default_state); - - if (x - origin.x).abs() == 2 && y == start_y { - chunk.set_block_state(x, y, z, wood_fence.default_state); - } - if (z - origin.z).abs() == 2 && y == start_y { - chunk.set_block_state(x, y, z, wood_fence.default_state); + for entrance in &self.child_entrance_boxes { + for y in (entrance.max.y - 2)..=entrance.max.y { + for x in entrance.min.x..=entrance.max.x { + for z in entrance.min.z..=entrance.max.z { + if chunk_box.contains(x, y, z) { + chunk.set_block_state(x, y, z, air); } } } } } - // Place rails and supports - for x in (origin.x - 12)..=(origin.x + 12) { - let y = start_y; - let z = origin.z; - if chunk_box.contains(x, y, z) { - chunk.set_block_state(x, y, z, Block::RAIL.default_state); - } - if x % 5 == 0 { - for sy in start_y..=(start_y + 2) { - if chunk_box.contains(x, sy, z - 1) { - chunk.set_block_state(x, sy, z - 1, wood_fence.default_state); + let diag_x = (bb.max.x - bb.min.x + 1) as f32; + let diag_y = (bb.max.y - (bb.min.y + 4) + 1) as f32; + let diag_z = (bb.max.z - bb.min.z + 1) as f32; + let cx = bb.min.x as f32 + diag_x / 2.0; + let cz = bb.min.z as f32 + diag_z / 2.0; + let y0 = bb.min.y + 4; + + if diag_y > 0.0 { + for y in y0..=bb.max.y { + let ny = (y - y0) as f32 / diag_y; + for x in bb.min.x..=bb.max.x { + let nx = (x as f32 - cx) / (diag_x * 0.5); + for z in bb.min.z..=bb.max.z { + let nz = (z as f32 - cz) / (diag_z * 0.5); + if nx * nx + ny * ny + nz * nz <= 1.05 && chunk_box.contains(x, y, z) { + chunk.set_block_state(x, y, z, air); + } } - if chunk_box.contains(x, sy, z + 1) { - chunk.set_block_state(x, sy, z + 1, wood_fence.default_state); - } - } - if chunk_box.contains(x, start_y + 2, z) { - chunk.set_block_state(x, start_y + 2, z, wood_planks.default_state); } } } } } + +pub struct MineShaftCorridor { + pub piece: StructurePiece, + pub shaft_type: MineshaftType, + pub has_rails: bool, + pub spider_corridor: bool, + pub has_placed_spider: bool, + pub num_sections: i32, +} + +impl MineShaftCorridor { + #[must_use] + pub fn new( + gen_depth: u32, + random: &mut RandomGenerator, + bounding_box: BlockBox, + direction: BlockDirection, + shaft_type: MineshaftType, + ) -> Self { + let has_rails = random.next_bounded_i32(3) == 0; + let spider_corridor = !has_rails && random.next_bounded_i32(23) == 0; + let axis = direction.get_axis(); + let num_sections = if axis == pumpkin_util::math::vector3::Axis::Z { + (bounding_box.max.z - bounding_box.min.z + 1) / 5 + } else { + (bounding_box.max.x - bounding_box.min.x + 1) / 5 + }; + + let mut piece = StructurePiece::new( + StructurePieceType::MineshaftCorridor, + bounding_box, + gen_depth, + ); + piece.set_facing(Some(direction)); + + Self { + piece, + shaft_type, + has_rails, + spider_corridor, + has_placed_spider: false, + num_sections, + } + } + + #[must_use] + pub fn find_corridor_size( + collector: &StructurePiecesCollector, + random: &mut RandomGenerator, + foot_x: i32, + foot_y: i32, + foot_z: i32, + direction: BlockDirection, + ) -> Option { + let max_len = random.next_bounded_i32(3) + 2; + for corridor_length in (1..=max_len).rev() { + let block_length = corridor_length * 5; + let mut box_cand = match direction { + BlockDirection::South => BlockBox::new(0, 0, 0, 2, 2, block_length - 1), + BlockDirection::West => BlockBox::new(-(block_length - 1), 0, 0, 0, 2, 2), + BlockDirection::East => BlockBox::new(0, 0, 0, block_length - 1, 2, 2), + _ => BlockBox::new(0, 0, -(block_length - 1), 2, 2, 0), + }; + box_cand.move_pos(foot_x, foot_y, foot_z); + if collector.get_intersecting(&box_cand).is_none() { + return Some(box_cand); + } + } + None + } + + #[expect(clippy::too_many_lines)] + fn add_children_to_list( + &self, + start_piece_box: &BlockBox, + shaft_type: MineshaftType, + collector: &StructurePiecesCollector, + random: &mut RandomGenerator, + children: &mut Vec, + ) { + let depth = self.piece.chain_length; + let end_selection = random.next_bounded_i32(4); + let orientation = self.piece.facing.unwrap_or(BlockDirection::North); + let rand_y = self.piece.bounding_box.min.y - 1 + random.next_bounded_i32(3); + + match orientation { + BlockDirection::North => { + if end_selection <= 1 { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x, + rand_y, + self.piece.bounding_box.min.z - 1, + orientation, + depth, + shaft_type, + children, + ); + } else if end_selection == 2 { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x - 1, + rand_y, + self.piece.bounding_box.min.z, + BlockDirection::West, + depth, + shaft_type, + children, + ); + } else { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.max.x + 1, + rand_y, + self.piece.bounding_box.min.z, + BlockDirection::East, + depth, + shaft_type, + children, + ); + } + } + BlockDirection::South => { + if end_selection <= 1 { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x, + rand_y, + self.piece.bounding_box.max.z + 1, + orientation, + depth, + shaft_type, + children, + ); + } else if end_selection == 2 { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x - 1, + rand_y, + self.piece.bounding_box.max.z - 3, + BlockDirection::West, + depth, + shaft_type, + children, + ); + } else { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.max.x + 1, + rand_y, + self.piece.bounding_box.max.z - 3, + BlockDirection::East, + depth, + shaft_type, + children, + ); + } + } + BlockDirection::West => { + if end_selection <= 1 { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x - 1, + rand_y, + self.piece.bounding_box.min.z, + orientation, + depth, + shaft_type, + children, + ); + } else if end_selection == 2 { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x, + rand_y, + self.piece.bounding_box.min.z - 1, + BlockDirection::North, + depth, + shaft_type, + children, + ); + } else { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x, + rand_y, + self.piece.bounding_box.max.z + 1, + BlockDirection::South, + depth, + shaft_type, + children, + ); + } + } + BlockDirection::East => { + if end_selection <= 1 { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.max.x + 1, + rand_y, + self.piece.bounding_box.min.z, + orientation, + depth, + shaft_type, + children, + ); + } else if end_selection == 2 { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.max.x - 3, + rand_y, + self.piece.bounding_box.min.z - 1, + BlockDirection::North, + depth, + shaft_type, + children, + ); + } else { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.max.x - 3, + rand_y, + self.piece.bounding_box.max.z + 1, + BlockDirection::South, + depth, + shaft_type, + children, + ); + } + } + _ => {} + } + + if depth < 8 { + if orientation != BlockDirection::North && orientation != BlockDirection::South { + let mut x = self.piece.bounding_box.min.x + 3; + while x + 3 <= self.piece.bounding_box.max.x { + let sel = random.next_bounded_i32(5); + if sel == 0 { + generate_and_add_piece( + start_piece_box, + collector, + random, + x, + self.piece.bounding_box.min.y, + self.piece.bounding_box.min.z - 1, + BlockDirection::North, + depth + 1, + shaft_type, + children, + ); + } else if sel == 1 { + generate_and_add_piece( + start_piece_box, + collector, + random, + x, + self.piece.bounding_box.min.y, + self.piece.bounding_box.max.z + 1, + BlockDirection::South, + depth + 1, + shaft_type, + children, + ); + } + x += 5; + } + } else { + let mut z = self.piece.bounding_box.min.z + 3; + while z + 3 <= self.piece.bounding_box.max.z { + let sel = random.next_bounded_i32(5); + if sel == 0 { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x - 1, + self.piece.bounding_box.min.y, + z, + BlockDirection::West, + depth + 1, + shaft_type, + children, + ); + } else if sel == 1 { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.max.x + 1, + self.piece.bounding_box.min.y, + z, + BlockDirection::East, + depth + 1, + shaft_type, + children, + ); + } + z += 5; + } + } + } + } + + #[expect(clippy::too_many_arguments)] + fn place_support( + &self, + chunk: &mut ProtoChunk, + chunk_box: &BlockBox, + x0: i32, + y0: i32, + z: i32, + y1: i32, + x1: i32, + random: &mut RandomGenerator, + ) { + let planks = self.shaft_type.planks(); + let fence = self.shaft_type.fence(); + + self.piece + .fill(chunk, chunk_box, x0, y0, z, x0, y1 - 1, z, fence); + self.piece + .fill(chunk, chunk_box, x1, y0, z, x1, y1 - 1, z, fence); + + if random.next_bounded_i32(4) == 0 { + self.piece + .fill(chunk, chunk_box, x0, y1, z, x0, y1, z, planks); + self.piece + .fill(chunk, chunk_box, x1, y1, z, x1, y1, z, planks); + } else { + self.piece + .fill(chunk, chunk_box, x0, y1, z, x1, y1, z, planks); + + let mut props_s = WallTorchLikeProperties::default(&Block::WALL_TORCH); + props_s.facing = HorizontalFacing::South; + let torch_s = BlockState::from_id(props_s.to_state_id(&Block::WALL_TORCH)); + + let mut props_n = WallTorchLikeProperties::default(&Block::WALL_TORCH); + props_n.facing = HorizontalFacing::North; + let torch_n = BlockState::from_id(props_n.to_state_id(&Block::WALL_TORCH)); + + if random.next_f32() < 0.05 { + self.piece + .add_block(chunk, torch_s, x0 + 1, y1, z - 1, chunk_box); + } + if random.next_f32() < 0.05 { + self.piece + .add_block(chunk, torch_n, x0 + 1, y1, z + 1, chunk_box); + } + } + } + + fn fill_pillar_down_or_chain_up( + &self, + chunk: &mut ProtoChunk, + x: i32, + y: i32, + z: i32, + chunk_box: &BlockBox, + ) { + let world_pos = self.piece.offset_pos(x, y, z); + if !chunk_box.contains_pos(&world_pos) { + return; + } + + let world_y = world_pos.y; + let mut dist = 1; + let mut check_below = true; + let mut check_above = true; + + while check_below || check_above { + if check_below { + let below_y = world_y - dist; + let state_below = + chunk.get_block_state(&Vector3::new(world_pos.x, below_y, world_pos.z)); + let empty_below = + state_below.to_state().is_air() || state_below.to_block_id() == Block::WATER.id; + if !empty_below && state_below.to_block_id() != Block::LAVA.id { + for py in (below_y + 1)..=world_y { + chunk.set_block_state(world_pos.x, py, world_pos.z, self.shaft_type.wood()); + } + return; + } + check_below = dist <= 20 && empty_below && below_y > chunk.bottom_y() as i32 + 1; + } + + if check_above { + let above_y = world_y + dist; + let state_above = + chunk.get_block_state(&Vector3::new(world_pos.x, above_y, world_pos.z)); + let empty_above = state_above.to_state().is_air(); + if !empty_above { + chunk.set_block_state( + world_pos.x, + world_y + 1, + world_pos.z, + self.shaft_type.fence(), + ); + for py in (world_y + 2)..=above_y { + chunk.set_block_state( + world_pos.x, + py, + world_pos.z, + Block::IRON_CHAIN.default_state, + ); + } + return; + } + check_above = dist <= 50 && empty_above && above_y < 319; + } + + dist += 1; + } + } +} + +impl StructurePieceBase for MineShaftCorridor { + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn get_structure_piece(&self) -> &StructurePiece { + &self.piece + } + + fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { + &mut self.piece + } + + #[allow(clippy::too_many_lines)] + fn place( + &mut self, + chunk: &mut ProtoChunk, + _block_registry: &dyn WorldPortalExt, + random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { + let air = Block::CAVE_AIR.default_state; + let length = self.num_sections * 5 - 1; + let planks = self.shaft_type.planks(); + + self.piece + .fill(chunk, chunk_box, 0, 0, 0, 2, 1, length, air); + + for z in 0..=length { + for x in 0..=2 { + if random.next_f32() < 0.8 { + self.piece.add_block(chunk, air, x, 2, z, chunk_box); + } + if self.spider_corridor && random.next_f32() < 0.6 { + self.piece + .add_block(chunk, Block::COBWEB.default_state, x, 0, z, chunk_box); + } + } + } + + for section in 0..self.num_sections { + let z = 2 + section * 5; + self.place_support(chunk, chunk_box, 0, 0, z, 2, 2, random); + + for (cx, cy, cz, prob) in [ + (0, 2, z - 1, 0.1f32), + (2, 2, z - 1, 0.1), + (0, 2, z + 1, 0.1), + (2, 2, z + 1, 0.1), + (0, 2, z - 2, 0.05), + (2, 2, z - 2, 0.05), + (0, 2, z + 2, 0.05), + (2, 2, z + 2, 0.05), + ] { + if random.next_f32() < prob { + self.piece + .add_block(chunk, Block::COBWEB.default_state, cx, cy, cz, chunk_box); + } + } + + if random.next_bounded_i32(100) == 0 { + self.piece.add_chest( + chunk, + chunk_box, + random, + 2, + 0, + z - 1, + "minecraft:chests/abandoned_mineshaft", + ); + } + if random.next_bounded_i32(100) == 0 { + self.piece.add_chest( + chunk, + chunk_box, + random, + 0, + 0, + z + 1, + "minecraft:chests/abandoned_mineshaft", + ); + } + + if self.spider_corridor && !self.has_placed_spider { + let spawner_z = z - 1 + random.next_bounded_i32(3); + let spawner_pos = self.piece.offset_pos(1, 0, spawner_z); + if chunk_box.contains_pos(&spawner_pos) { + self.has_placed_spider = true; + chunk.set_block_state( + spawner_pos.x, + spawner_pos.y, + spawner_pos.z, + Block::SPAWNER.default_state, + ); + + let mut nbt = NbtCompound::new(); + nbt.put_string("id", "minecraft:mob_spawner".to_string()); + nbt.put_int("x", spawner_pos.x); + nbt.put_int("y", spawner_pos.y); + nbt.put_int("z", spawner_pos.z); + let mut spawn_data = NbtCompound::new(); + let mut entity = NbtCompound::new(); + entity.put_string("id", "minecraft:cave_spider".to_string()); + spawn_data.put_compound("entity", entity); + nbt.put_compound("SpawnData", spawn_data); + chunk.add_block_entity(nbt); + } + } + } + + for x in 0..=2 { + for z in 0..=length { + let world_pos = self.piece.offset_pos(x, -1, z); + if chunk_box.contains_pos(&world_pos) { + let below = chunk.get_block_state(&world_pos); + if below.to_state().is_air() { + chunk.set_block_state(world_pos.x, world_pos.y, world_pos.z, planks); + } + } + } + } + + self.fill_pillar_down_or_chain_up(chunk, 0, -1, 2, chunk_box); + self.fill_pillar_down_or_chain_up(chunk, 2, -1, 2, chunk_box); + if self.num_sections > 1 { + let last_support = length - 2; + self.fill_pillar_down_or_chain_up(chunk, 0, -1, last_support, chunk_box); + self.fill_pillar_down_or_chain_up(chunk, 2, -1, last_support, chunk_box); + } + + if self.has_rails { + let rail = Block::RAIL.default_state; + for z in 0..=length { + let floor_pos = self.piece.offset_pos(1, -1, z); + if chunk_box.contains_pos(&floor_pos) { + let floor_state = chunk.get_block_state(&floor_pos); + if !floor_state.to_state().is_air() && random.next_f32() < 0.7 { + self.piece.add_block(chunk, rail, 1, 0, z, chunk_box); + } + } + } + } + } +} + +pub struct MineShaftCrossing { + pub piece: StructurePiece, + pub shaft_type: MineshaftType, + pub direction: Option, + pub is_two_floored: bool, +} + +impl MineShaftCrossing { + #[must_use] + pub const fn new( + gen_depth: u32, + bounding_box: BlockBox, + direction: Option, + shaft_type: MineshaftType, + ) -> Self { + let is_two_floored = bounding_box.max.y - bounding_box.min.y + 1 > 3; + let mut piece = StructurePiece::new( + StructurePieceType::MineshaftCrossing, + bounding_box, + gen_depth, + ); + piece.set_facing(direction); + Self { + piece, + shaft_type, + direction, + is_two_floored, + } + } + + #[must_use] + pub fn find_crossing( + collector: &StructurePiecesCollector, + random: &mut RandomGenerator, + foot_x: i32, + foot_y: i32, + foot_z: i32, + direction: BlockDirection, + ) -> Option { + let y1 = if random.next_bounded_i32(4) == 0 { + 6 + } else { + 2 + }; + let mut box_cand = match direction { + BlockDirection::South => BlockBox::new(-1, 0, 0, 3, y1, 4), + BlockDirection::West => BlockBox::new(-4, 0, -1, 0, y1, 3), + BlockDirection::East => BlockBox::new(0, 0, -1, 4, y1, 3), + _ => BlockBox::new(-1, 0, -4, 3, y1, 0), + }; + box_cand.move_pos(foot_x, foot_y, foot_z); + collector + .get_intersecting(&box_cand) + .is_none() + .then_some(box_cand) + } + + #[expect(clippy::too_many_lines)] + fn add_children_to_list( + &self, + start_piece_box: &BlockBox, + shaft_type: MineshaftType, + collector: &StructurePiecesCollector, + random: &mut RandomGenerator, + children: &mut Vec, + ) { + let depth = self.piece.chain_length; + let dir = self.direction.unwrap_or(BlockDirection::North); + + match dir { + BlockDirection::North => { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x + 1, + self.piece.bounding_box.min.y, + self.piece.bounding_box.min.z - 1, + BlockDirection::North, + depth, + shaft_type, + children, + ); + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x - 1, + self.piece.bounding_box.min.y, + self.piece.bounding_box.min.z + 1, + BlockDirection::West, + depth, + shaft_type, + children, + ); + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.max.x + 1, + self.piece.bounding_box.min.y, + self.piece.bounding_box.min.z + 1, + BlockDirection::East, + depth, + shaft_type, + children, + ); + } + BlockDirection::South => { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x + 1, + self.piece.bounding_box.min.y, + self.piece.bounding_box.max.z + 1, + BlockDirection::South, + depth, + shaft_type, + children, + ); + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x - 1, + self.piece.bounding_box.min.y, + self.piece.bounding_box.min.z + 1, + BlockDirection::West, + depth, + shaft_type, + children, + ); + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.max.x + 1, + self.piece.bounding_box.min.y, + self.piece.bounding_box.min.z + 1, + BlockDirection::East, + depth, + shaft_type, + children, + ); + } + BlockDirection::West => { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x + 1, + self.piece.bounding_box.min.y, + self.piece.bounding_box.min.z - 1, + BlockDirection::North, + depth, + shaft_type, + children, + ); + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x + 1, + self.piece.bounding_box.min.y, + self.piece.bounding_box.max.z + 1, + BlockDirection::South, + depth, + shaft_type, + children, + ); + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x - 1, + self.piece.bounding_box.min.y, + self.piece.bounding_box.min.z + 1, + BlockDirection::West, + depth, + shaft_type, + children, + ); + } + BlockDirection::East => { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x + 1, + self.piece.bounding_box.min.y, + self.piece.bounding_box.min.z - 1, + BlockDirection::North, + depth, + shaft_type, + children, + ); + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x + 1, + self.piece.bounding_box.min.y, + self.piece.bounding_box.max.z + 1, + BlockDirection::South, + depth, + shaft_type, + children, + ); + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.max.x + 1, + self.piece.bounding_box.min.y, + self.piece.bounding_box.min.z + 1, + BlockDirection::East, + depth, + shaft_type, + children, + ); + } + _ => {} + } + + if self.is_two_floored { + if random.next_bool() { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x + 1, + self.piece.bounding_box.min.y + 4, + self.piece.bounding_box.min.z - 1, + BlockDirection::North, + depth, + shaft_type, + children, + ); + } + if random.next_bool() { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x - 1, + self.piece.bounding_box.min.y + 4, + self.piece.bounding_box.min.z + 1, + BlockDirection::West, + depth, + shaft_type, + children, + ); + } + if random.next_bool() { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.max.x + 1, + self.piece.bounding_box.min.y + 4, + self.piece.bounding_box.min.z + 1, + BlockDirection::East, + depth, + shaft_type, + children, + ); + } + if random.next_bool() { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x + 1, + self.piece.bounding_box.min.y + 4, + self.piece.bounding_box.max.z + 1, + BlockDirection::South, + depth, + shaft_type, + children, + ); + } + } + } +} + +impl StructurePieceBase for MineShaftCrossing { + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn get_structure_piece(&self) -> &StructurePiece { + &self.piece + } + + fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { + &mut self.piece + } + + #[allow(clippy::too_many_lines)] + fn place( + &mut self, + chunk: &mut ProtoChunk, + _block_registry: &dyn WorldPortalExt, + _random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { + let air = Block::CAVE_AIR.default_state; + let planks = self.shaft_type.planks(); + let bb = self.piece.bounding_box; + + if self.is_two_floored { + for y in bb.min.y..=(bb.min.y + 2) { + for x in (bb.min.x + 1)..bb.max.x { + for z in bb.min.z..=bb.max.z { + if chunk_box.contains(x, y, z) { + chunk.set_block_state(x, y, z, air); + } + } + } + for x in bb.min.x..=bb.max.x { + for z in (bb.min.z + 1)..bb.max.z { + if chunk_box.contains(x, y, z) { + chunk.set_block_state(x, y, z, air); + } + } + } + } + for y in (bb.max.y - 2)..=bb.max.y { + for x in (bb.min.x + 1)..bb.max.x { + for z in bb.min.z..=bb.max.z { + if chunk_box.contains(x, y, z) { + chunk.set_block_state(x, y, z, air); + } + } + } + for x in bb.min.x..=bb.max.x { + for z in (bb.min.z + 1)..bb.max.z { + if chunk_box.contains(x, y, z) { + chunk.set_block_state(x, y, z, air); + } + } + } + } + let mid_y = bb.min.y + 3; + for x in (bb.min.x + 1)..bb.max.x { + for z in (bb.min.z + 1)..bb.max.z { + if chunk_box.contains(x, mid_y, z) { + chunk.set_block_state(x, mid_y, z, air); + } + } + } + } else { + for y in bb.min.y..=bb.max.y { + for x in (bb.min.x + 1)..bb.max.x { + for z in bb.min.z..=bb.max.z { + if chunk_box.contains(x, y, z) { + chunk.set_block_state(x, y, z, air); + } + } + } + for x in bb.min.x..=bb.max.x { + for z in (bb.min.z + 1)..bb.max.z { + if chunk_box.contains(x, y, z) { + chunk.set_block_state(x, y, z, air); + } + } + } + } + } + + for (cx, cz) in [ + (bb.min.x + 1, bb.min.z + 1), + (bb.min.x + 1, bb.max.z - 1), + (bb.max.x - 1, bb.min.z + 1), + (bb.max.x - 1, bb.max.z - 1), + ] { + let above = chunk.get_block_state(&Vector3::new(cx, bb.max.y + 1, cz)); + if !above.to_state().is_air() { + for py in bb.min.y..=bb.max.y { + if chunk_box.contains(cx, py, cz) { + chunk.set_block_state(cx, py, cz, planks); + } + } + } + } + + let floor_y = bb.min.y - 1; + for x in bb.min.x..=bb.max.x { + for z in bb.min.z..=bb.max.z { + if chunk_box.contains(x, floor_y, z) { + let state = chunk.get_block_state(&Vector3::new(x, floor_y, z)); + if state.to_state().is_air() { + chunk.set_block_state(x, floor_y, z, planks); + } + } + } + } + } +} + +pub struct MineShaftStairs { + pub piece: StructurePiece, + pub shaft_type: MineshaftType, +} + +impl MineShaftStairs { + #[must_use] + pub const fn new( + gen_depth: u32, + bounding_box: BlockBox, + direction: BlockDirection, + shaft_type: MineshaftType, + ) -> Self { + let mut piece = + StructurePiece::new(StructurePieceType::MineshaftStairs, bounding_box, gen_depth); + piece.set_facing(Some(direction)); + Self { piece, shaft_type } + } + + #[must_use] + pub fn find_stairs( + collector: &StructurePiecesCollector, + _random: &mut RandomGenerator, + foot_x: i32, + foot_y: i32, + foot_z: i32, + direction: BlockDirection, + ) -> Option { + let mut box_cand = match direction { + BlockDirection::South => BlockBox::new(0, -5, 0, 2, 2, 8), + BlockDirection::West => BlockBox::new(-8, -5, 0, 0, 2, 2), + BlockDirection::East => BlockBox::new(0, -5, 0, 8, 2, 2), + _ => BlockBox::new(0, -5, -8, 2, 2, 0), + }; + box_cand.move_pos(foot_x, foot_y, foot_z); + collector + .get_intersecting(&box_cand) + .is_none() + .then_some(box_cand) + } + + fn add_children_to_list( + &self, + start_piece_box: &BlockBox, + shaft_type: MineshaftType, + collector: &StructurePiecesCollector, + random: &mut RandomGenerator, + children: &mut Vec, + ) { + let depth = self.piece.chain_length; + let dir = self.piece.facing.unwrap_or(BlockDirection::North); + + match dir { + BlockDirection::North => { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x, + self.piece.bounding_box.min.y, + self.piece.bounding_box.min.z - 1, + BlockDirection::North, + depth, + shaft_type, + children, + ); + } + BlockDirection::South => { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x, + self.piece.bounding_box.min.y, + self.piece.bounding_box.max.z + 1, + BlockDirection::South, + depth, + shaft_type, + children, + ); + } + BlockDirection::West => { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.min.x - 1, + self.piece.bounding_box.min.y, + self.piece.bounding_box.min.z, + BlockDirection::West, + depth, + shaft_type, + children, + ); + } + BlockDirection::East => { + generate_and_add_piece( + start_piece_box, + collector, + random, + self.piece.bounding_box.max.x + 1, + self.piece.bounding_box.min.y, + self.piece.bounding_box.min.z, + BlockDirection::East, + depth, + shaft_type, + children, + ); + } + _ => {} + } + } +} + +impl StructurePieceBase for MineShaftStairs { + fn as_any(&self) -> &dyn std::any::Any { + self + } + + fn get_structure_piece(&self) -> &StructurePiece { + &self.piece + } + + fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { + &mut self.piece + } + + fn place( + &mut self, + chunk: &mut ProtoChunk, + _block_registry: &dyn WorldPortalExt, + _random: &mut RandomGenerator, + _seed: i64, + chunk_box: &BlockBox, + ) { + let air = Block::CAVE_AIR.default_state; + self.piece.fill(chunk, chunk_box, 0, 5, 0, 2, 7, 1, air); + self.piece.fill(chunk, chunk_box, 0, 0, 7, 2, 2, 8, air); + + for i in 0..5 { + let y_min = 5 - i - i32::from(i < 4); + let y_max = 7 - i; + let z = 2 + i; + self.piece + .fill(chunk, chunk_box, 0, y_min, z, 2, y_max, z, air); + } + } +} diff --git a/crates/pumpkin-world/src/generation/structure/structures/mod.rs b/crates/pumpkin-world/src/generation/structure/structures/mod.rs index a2660e89b..cab6acaec 100644 --- a/crates/pumpkin-world/src/generation/structure/structures/mod.rs +++ b/crates/pumpkin-world/src/generation/structure/structures/mod.rs @@ -74,7 +74,6 @@ pub trait StructurePieceBase: Send + Sync { &self, _start: &StructurePiece, _random: &mut RandomGenerator, - // TODO: this is only for Stronghold and should not be here _weights: &mut Vec, _last_piece_type: &mut Option, _has_portal_room: &mut bool, @@ -388,15 +387,16 @@ impl StructurePiece { } } + #[must_use] pub const fn is_under_sea_level( &self, - chunk: &mut ProtoChunk, + chunk: &ProtoChunk, x: i32, y: i32, z: i32, box_limit: &BlockBox, ) -> bool { - let block_pos = self.offset_pos(x, y, z); + let block_pos = self.offset_pos(x, y + 1, z); if !box_limit.contains_pos(&block_pos) { return false; @@ -503,6 +503,43 @@ impl StructurePiece { true } + + #[allow(clippy::too_many_arguments)] + pub fn add_dispenser( + &self, + chunk: &mut ProtoChunk, + bb: &BlockBox, + random: &mut RandomGenerator, + x: i32, + y: i32, + z: i32, + facing: pumpkin_data::block_properties::Facing, + loot_table: &str, + ) -> bool { + use pumpkin_data::block_properties::DispenserLikeProperties; + use pumpkin_nbt::compound::NbtCompound; + + let world_pos = self.offset_pos(x, y, z); + if !bb.contains_pos(&world_pos) { + return false; + } + + let mut props = DispenserLikeProperties::default(&Block::DISPENSER); + props.facing = facing; + let state = BlockState::from_id(props.to_state_id(&Block::DISPENSER)); + chunk.set_block_state(world_pos.x, world_pos.y, world_pos.z, state); + + let mut nbt = NbtCompound::new(); + nbt.put_string("id", "minecraft:dispenser".to_string()); + nbt.put_int("x", world_pos.x); + nbt.put_int("y", world_pos.y); + nbt.put_int("z", world_pos.z); + nbt.put_string("LootTable", loot_table.to_string()); + nbt.put_long("LootTableSeed", random.next_i64()); + chunk.add_block_entity(nbt); + + true + } } impl StructurePieceBase for StructurePiece { @@ -625,6 +662,24 @@ impl StructurePiecesCollector { k } + pub fn move_inside_heights( + &mut self, + random: &mut RandomGenerator, + lowest_allowed: i32, + highest_allowed: i32, + ) { + let bounding_box = self.get_bounding_box(); + let y_span = bounding_box.max.y - bounding_box.min.y + 1; + let height_span = highest_allowed - lowest_allowed + 1 - y_span; + let y0_pos = if height_span > 1 { + lowest_allowed + random.next_bounded_i32(height_span) + } else { + lowest_allowed + }; + let dy = y0_pos - bounding_box.min.y; + self.shift(dy); + } + pub fn get_bounding_box(&mut self) -> BlockBox { if let Some(bbox) = self.cached_box { return bbox; diff --git a/crates/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_exit.rs b/crates/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_exit.rs index b50f9d60b..0e53247e2 100644 --- a/crates/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_exit.rs +++ b/crates/pumpkin-world/src/generation/structure/structures/nether_fortress/corridor_exit.rs @@ -223,11 +223,14 @@ impl StructurePieceBase for CorridorExitPiece { p.add_block(chunk, nb, 6, 0, 6, &bb); p.add_block(chunk, Block::LAVA.default_state, 6, 5, 6, &bb); - // Schedule lava fluid tick - // TODO - // let lava_pos = p.offset_pos(6, 5, 6); - // if bb.contains_pos(&lava_pos) { - // chunk.schedule_fluid_tick(&lava_pos, 0); - // } + let lava_pos = p.offset_pos(6, 5, 6); + if bb.contains_pos(&lava_pos) { + chunk.schedule_fluid_tick( + lava_pos.x, + lava_pos.y, + lava_pos.z, + &pumpkin_data::fluid::Fluid::LAVA, + ); + } } } diff --git a/crates/pumpkin-world/src/generation/structure/structures/nether_fortress/mod.rs b/crates/pumpkin-world/src/generation/structure/structures/nether_fortress/mod.rs index 761d6ddd2..688d2c88a 100644 --- a/crates/pumpkin-world/src/generation/structure/structures/nether_fortress/mod.rs +++ b/crates/pumpkin-world/src/generation/structure/structures/nether_fortress/mod.rs @@ -83,6 +83,8 @@ impl StructureGenerator for NetherFortressGenerator { collector.add_piece(piece); } + collector.move_inside_heights(&mut random, 48, 70); + Some(StructurePosition { start_pos: BlockPos::new( section_coords::section_to_block(context.chunk_x), diff --git a/crates/pumpkin-world/src/generation/structure/structures/ocean_ruin.rs b/crates/pumpkin-world/src/generation/structure/structures/ocean_ruin.rs index b4f555853..5823f93bd 100644 --- a/crates/pumpkin-world/src/generation/structure/structures/ocean_ruin.rs +++ b/crates/pumpkin-world/src/generation/structure/structures/ocean_ruin.rs @@ -1,43 +1,28 @@ use std::sync::Arc; -use pumpkin_data::block_rotation::Rotation; +use pumpkin_data::{ + Block, BlockState, Mirror, Rotation, + block_properties::{ChestLikeProperties, HorizontalFacing}, +}; +use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::{ - math::{block_box::BlockBox, position::BlockPos}, - random::{RandomGenerator, RandomImpl}, + HeightMap, + math::{block_box::BlockBox, position::BlockPos, vector3::Vector3}, + random::{RandomGenerator, RandomImpl, hash_block_pos, legacy_rand::LegacyRand}, }; use crate::{ ProtoChunk, - generation::{ - positions::chunk_pos::{get_center_x, get_center_z}, - structure::{ - piece::StructurePieceType, - structures::{ - StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase, - StructurePiecesCollector, StructurePosition, WorldPortalExt, - }, - template::{StructureTemplate, get_template, place_template}, + generation::structure::{ + piece::StructurePieceType, + structures::{ + StructureGenerator, StructureGeneratorContext, StructurePiece, StructurePieceBase, + StructurePiecesCollector, StructurePosition, WorldPortalExt, }, + template::{BlockStateResolver, StructurePlaceSettings, StructureTemplate, get_template}, }, }; -const COLD_RUINS: &[&str] = &[ - "underwater_ruin/brick_1", - "underwater_ruin/brick_2", - "underwater_ruin/brick_3", - "underwater_ruin/brick_4", - "underwater_ruin/brick_5", - "underwater_ruin/brick_6", - "underwater_ruin/brick_7", - "underwater_ruin/brick_8", - "underwater_ruin/cracked_1", - "underwater_ruin/cracked_2", - "underwater_ruin/cracked_3", - "underwater_ruin/mossy_1", - "underwater_ruin/mossy_2", - "underwater_ruin/mossy_3", -]; - const WARM_RUINS: &[&str] = &[ "underwater_ruin/warm_1", "underwater_ruin/warm_2", @@ -47,8 +32,67 @@ const WARM_RUINS: &[&str] = &[ "underwater_ruin/warm_6", "underwater_ruin/warm_7", "underwater_ruin/warm_8", +]; + +const BIG_WARM_RUINS: &[&str] = &[ "underwater_ruin/big_warm_4", "underwater_ruin/big_warm_5", + "underwater_ruin/big_warm_6", + "underwater_ruin/big_warm_7", +]; + +const RUINS_BRICK: &[&str] = &[ + "underwater_ruin/brick_1", + "underwater_ruin/brick_2", + "underwater_ruin/brick_3", + "underwater_ruin/brick_4", + "underwater_ruin/brick_5", + "underwater_ruin/brick_6", + "underwater_ruin/brick_7", + "underwater_ruin/brick_8", +]; + +const RUINS_CRACKED: &[&str] = &[ + "underwater_ruin/cracked_1", + "underwater_ruin/cracked_2", + "underwater_ruin/cracked_3", + "underwater_ruin/cracked_4", + "underwater_ruin/cracked_5", + "underwater_ruin/cracked_6", + "underwater_ruin/cracked_7", + "underwater_ruin/cracked_8", +]; + +const RUINS_MOSSY: &[&str] = &[ + "underwater_ruin/mossy_1", + "underwater_ruin/mossy_2", + "underwater_ruin/mossy_3", + "underwater_ruin/mossy_4", + "underwater_ruin/mossy_5", + "underwater_ruin/mossy_6", + "underwater_ruin/mossy_7", + "underwater_ruin/mossy_8", +]; + +const BIG_RUINS_BRICK: &[&str] = &[ + "underwater_ruin/big_brick_1", + "underwater_ruin/big_brick_2", + "underwater_ruin/big_brick_3", + "underwater_ruin/big_brick_8", +]; + +const BIG_RUINS_CRACKED: &[&str] = &[ + "underwater_ruin/big_cracked_1", + "underwater_ruin/big_cracked_2", + "underwater_ruin/big_cracked_3", + "underwater_ruin/big_cracked_8", +]; + +const BIG_RUINS_MOSSY: &[&str] = &[ + "underwater_ruin/big_mossy_1", + "underwater_ruin/big_mossy_2", + "underwater_ruin/big_mossy_3", + "underwater_ruin/big_mossy_8", ]; pub struct OceanRuinGenerator { @@ -60,81 +104,566 @@ impl StructureGenerator for OceanRuinGenerator { &self, mut context: StructureGeneratorContext<'_>, ) -> Option { - let chunk_center_x = get_center_x(context.chunk_x); - let chunk_center_z = get_center_z(context.chunk_z); + let start_x = context.chunk_x << 4; + let start_z = context.chunk_z << 4; + let position = Vector3::new(start_x, 90, start_z); let rotation_idx = context.random.next_bounded_i32(4) as u8; let rotation = Rotation::from_index(rotation_idx); - let pool = if self.is_warm { WARM_RUINS } else { COLD_RUINS }; - let template_idx = context.random.next_bounded_i32(pool.len() as i32) as usize; - let template_name = pool[template_idx]; - let template = get_template(template_name)?; - - let size = template.size; - let bounding_box = BlockBox::new( - chunk_center_x - size.x / 2, - context.min_y, - chunk_center_z - size.z / 2, - chunk_center_x + size.x / 2, - 256, - chunk_center_z + size.z / 2, + let mut collector = StructurePiecesCollector::default(); + add_pieces( + &mut collector, + position, + rotation, + &mut context.random, + self.is_warm, + 0.3, + 0.7, ); - let mut collector = StructurePiecesCollector::default(); - collector.add_piece(Box::new(OceanRuinPiece { - piece: StructurePiece::new(StructurePieceType::OceanTemple, bounding_box, 0), - template, - rotation, - })); - Some(StructurePosition { - start_pos: BlockPos::new(chunk_center_x, 64, chunk_center_z), + start_pos: BlockPos::new(start_x + 8, 64, start_z + 8), collector: Arc::new(collector.into()), }) } } -pub struct OceanRuinPiece { - piece: StructurePiece, - template: Arc, +fn add_pieces( + collector: &mut StructurePiecesCollector, + position: Vector3, rotation: Rotation, + random: &mut RandomGenerator, + is_warm: bool, + large_probability: f32, + cluster_probability: f32, +) { + let is_large = random.next_f32() <= large_probability; + let base_integrity = if is_large { 0.9 } else { 0.8 }; + add_piece( + collector, + position, + rotation, + random, + is_warm, + is_large, + base_integrity, + ); + if is_large && random.next_f32() <= cluster_probability { + add_cluster_ruins(collector, random, rotation, position, is_warm); + } +} + +fn add_cluster_ruins( + collector: &mut StructurePiecesCollector, + random: &mut RandomGenerator, + rotation: Rotation, + p: Vector3, + is_warm: bool, +) { + let parent_pos = Vector3::new(p.x, 90, p.z); + let parent_corner = StructureTemplate::transform_block_pos( + Vector3::new(15, 0, 15), + Mirror::None, + rotation, + Vector3::new(0, 0, 0), + ) + parent_pos; + + let parent_bb = BlockBox::new( + parent_pos.x.min(parent_corner.x), + parent_pos.y.min(parent_corner.y), + parent_pos.z.min(parent_corner.z), + parent_pos.x.max(parent_corner.x), + parent_pos.y.max(parent_corner.y), + parent_pos.z.max(parent_corner.z), + ); + + let parent_bottom_left = Vector3::new( + parent_pos.x.min(parent_corner.x), + parent_pos.y, + parent_pos.z.min(parent_corner.z), + ); + + let mut all_positions = generate_cluster_positions(random, parent_bottom_left); + let ruins_count = random.next_bounded_i32(5) + 4; + + for _ in 0..ruins_count { + if all_positions.is_empty() { + break; + } + let idx = random.next_bounded_i32(all_positions.len() as i32) as usize; + let pos = all_positions.swap_remove(idx); + let next_rotation = Rotation::from_index(random.next_bounded_i32(4) as u8); + let next_corner = StructureTemplate::transform_block_pos( + Vector3::new(5, 0, 6), + Mirror::None, + next_rotation, + Vector3::new(0, 0, 0), + ) + pos; + + let next_bb = BlockBox::new( + pos.x.min(next_corner.x), + pos.y.min(next_corner.y), + pos.z.min(next_corner.z), + pos.x.max(next_corner.x), + pos.y.max(next_corner.y), + pos.z.max(next_corner.z), + ); + + if !next_bb.intersects(&parent_bb) { + add_piece(collector, pos, next_rotation, random, is_warm, false, 0.8); + } + } +} + +fn generate_cluster_positions( + random: &mut RandomGenerator, + origin: Vector3, +) -> Vec> { + vec![ + origin + + Vector3::new( + -16 + random.next_bounded_i32(8) + 1, + 0, + 16 + random.next_bounded_i32(7) + 1, + ), + origin + + Vector3::new( + -16 + random.next_bounded_i32(8) + 1, + 0, + random.next_bounded_i32(7) + 1, + ), + origin + + Vector3::new( + -16 + random.next_bounded_i32(8) + 1, + 0, + -16 + random.next_bounded_i32(5) + 4, + ), + origin + + Vector3::new( + random.next_bounded_i32(7) + 1, + 0, + 16 + random.next_bounded_i32(7) + 1, + ), + origin + + Vector3::new( + random.next_bounded_i32(7) + 1, + 0, + -16 + random.next_bounded_i32(3) + 4, + ), + origin + + Vector3::new( + 16 + random.next_bounded_i32(7) + 1, + 0, + 16 + random.next_bounded_i32(6) + 3, + ), + origin + + Vector3::new( + 16 + random.next_bounded_i32(7) + 1, + 0, + random.next_bounded_i32(7) + 1, + ), + origin + + Vector3::new( + 16 + random.next_bounded_i32(7) + 1, + 0, + -16 + random.next_bounded_i32(5) + 4, + ), + ] +} + +fn add_piece( + collector: &mut StructurePiecesCollector, + position: Vector3, + rotation: Rotation, + random: &mut RandomGenerator, + is_warm: bool, + is_large: bool, + base_integrity: f32, +) { + if is_warm { + let pool = if is_large { BIG_WARM_RUINS } else { WARM_RUINS }; + let template_idx = random.next_bounded_i32(pool.len() as i32) as usize; + let template_name = pool[template_idx]; + if let Some(template) = get_template(template_name) { + collector.add_piece(Box::new(OceanRuinPiece::new( + template, + template_name.to_string(), + position, + rotation, + base_integrity, + is_warm, + is_large, + ))); + } + } else { + let (bricks, cracked, mossy) = if is_large { + (BIG_RUINS_BRICK, BIG_RUINS_CRACKED, BIG_RUINS_MOSSY) + } else { + (RUINS_BRICK, RUINS_CRACKED, RUINS_MOSSY) + }; + let idx = random.next_bounded_i32(bricks.len() as i32) as usize; + if let Some(template) = get_template(bricks[idx]) { + collector.add_piece(Box::new(OceanRuinPiece::new( + template, + bricks[idx].to_string(), + position, + rotation, + base_integrity, + is_warm, + is_large, + ))); + } + if let Some(template) = get_template(cracked[idx]) { + collector.add_piece(Box::new(OceanRuinPiece::new( + template, + cracked[idx].to_string(), + position, + rotation, + 0.7, + is_warm, + is_large, + ))); + } + if let Some(template) = get_template(mossy[idx]) { + collector.add_piece(Box::new(OceanRuinPiece::new( + template, + mossy[idx].to_string(), + position, + rotation, + 0.5, + is_warm, + is_large, + ))); + } + } +} + +pub struct OceanRuinPiece { + pub piece: StructurePiece, + pub template: Arc, + pub template_name: String, + pub template_position: Vector3, + pub rotation: Rotation, + pub integrity: f32, + pub is_warm: bool, + pub is_large: bool, +} + +impl OceanRuinPiece { + #[must_use] + pub fn new( + template: Arc, + template_name: String, + position: Vector3, + rotation: Rotation, + integrity: f32, + is_warm: bool, + is_large: bool, + ) -> Self { + let place_settings = StructurePlaceSettings::new() + .set_rotation(rotation) + .set_mirror(Mirror::None); + let bounding_box = template.get_bounding_box(&place_settings, position); + Self { + piece: StructurePiece::new(StructurePieceType::OceanTemple, bounding_box, 0), + template, + template_name, + template_position: position, + rotation, + integrity, + is_warm, + is_large, + } + } + + fn calculate_height(pos: Vector3, corner: Vector3, chunk: &ProtoChunk) -> i32 { + let mut new_y = pos.y; + let mut min_y = 512; + let top_y = new_y - 1; + let mut area = 0; + let min_x = pos.x.min(corner.x); + let max_x = pos.x.max(corner.x); + let min_z = pos.z.min(corner.z); + let max_z = pos.z.max(corner.z); + + for x in min_x..=max_x { + for z in min_z..=max_z { + let floor_y = chunk.get_top_y(&HeightMap::OceanFloorWg, x, z) - 1; + min_y = min_y.min(floor_y); + if floor_y < top_y - 2 { + area += 1; + } + } + } + + let width = (pos.x - corner.x).abs(); + if top_y - min_y > 2 && area > width - 2 { + new_y = min_y + 1; + } + + new_y + } } impl StructurePieceBase for OceanRuinPiece { fn as_any(&self) -> &dyn std::any::Any { self } + fn get_structure_piece(&self) -> &StructurePiece { &self.piece } + fn get_structure_piece_mut(&mut self) -> &mut StructurePiece { &mut self.piece } + + fn translate(&mut self, x: i32, y: i32, z: i32) { + self.piece.translate(x, y, z); + self.template_position += Vector3::new(x, y, z); + } + + #[allow(clippy::too_many_lines)] fn place( &mut self, chunk: &mut ProtoChunk, _block_registry: &dyn WorldPortalExt, - _random: &mut RandomGenerator, + random: &mut RandomGenerator, _seed: i64, chunk_box: &BlockBox, ) { - let origin = self.piece.bounding_box.min; - let sample_y = chunk.get_top_y(&pumpkin_util::HeightMap::OceanFloorWg, origin.x, origin.z); - let target_y = sample_y - 2; // slightly buried on ocean floor - let mut final_origin = origin; - final_origin.y = target_y; - - place_template( - chunk, - &self.template, - final_origin, - (0, 0), - self.rotation, - true, // skip air - true, // apply waterlogging - &[], - Some(chunk_box), + let height = chunk.get_top_y( + &HeightMap::OceanFloorWg, + self.template_position.x, + self.template_position.z, ); + let mut pos = Vector3::new(self.template_position.x, height, self.template_position.z); + let size = self.template.size; + let corner = StructureTemplate::transform_block_pos( + Vector3::new(size.x - 1, 0, size.z - 1), + Mirror::None, + self.rotation, + Vector3::new(0, 0, 0), + ) + pos; + + pos.y = Self::calculate_height(pos, corner, chunk); + self.template_position = pos; + let place_settings = StructurePlaceSettings::new() + .set_rotation(self.rotation) + .set_mirror(Mirror::None); + self.piece.bounding_box = self + .template + .get_bounding_box(&place_settings, self.template_position); + let box_limit = self.piece.bounding_box; + + let mut suspicious_count = 0; + + for block in &self.template.blocks { + let palette_entry = &self.template.palette[block.state as usize]; + + if palette_entry.name == "minecraft:structure_void" + || palette_entry.name == "structure_void" + { + continue; + } + + let rel_pos = StructureTemplate::transform_block_pos( + block.pos, + Mirror::None, + self.rotation, + Vector3::new(0, 0, 0), + ); + let world_pos = self.template_position + rel_pos; + + if !box_limit.contains_pos(&world_pos) || !chunk_box.contains_pos(&world_pos) { + continue; + } + + if palette_entry.name == "minecraft:structure_block" { + if let Some(nbt) = &block.nbt { + let mode = nbt.get_string("mode").unwrap_or(""); + if mode == "DATA" { + let metadata = nbt.get_string("metadata").unwrap_or(""); + if metadata == "chest" { + let is_water = + chunk.get_block_state(&world_pos).to_block_id() == Block::WATER.id; + let mut props = ChestLikeProperties::default(&Block::CHEST); + props.facing = HorizontalFacing::North; + props.waterlogged = is_water; + let chest_state = BlockState::from_id(props.to_state_id(&Block::CHEST)); + chunk.set_block_state( + world_pos.x, + world_pos.y, + world_pos.z, + chest_state, + ); + + let mut chest_nbt = NbtCompound::new(); + chest_nbt.put_string("id", "minecraft:chest".to_string()); + chest_nbt.put_int("x", world_pos.x); + chest_nbt.put_int("y", world_pos.y); + chest_nbt.put_int("z", world_pos.z); + let loot_table = if self.is_large { + "minecraft:chests/underwater_ruin_big" + } else { + "minecraft:chests/underwater_ruin_small" + }; + chest_nbt.put_string("LootTable", loot_table.to_string()); + let mut rng = LegacyRand::from_seed(hash_block_pos( + world_pos.x, + world_pos.y, + world_pos.z, + ) + as u64); + chest_nbt.put_long("LootTableSeed", rng.next_i64()); + chunk.add_block_entity(chest_nbt); + } else if metadata == "drowned" { + let replacement = if world_pos.y > chunk.get_sea_level() { + Block::AIR.default_state + } else { + Block::WATER.default_state + }; + chunk.set_block_state( + world_pos.x, + world_pos.y, + world_pos.z, + replacement, + ); + } + } + } + continue; + } + + if self.integrity < 0.999 && random.next_f32() > self.integrity { + continue; + } + + if palette_entry.name == "minecraft:air" || palette_entry.name == "air" { + continue; + } + + if self.is_warm + && palette_entry.name == "minecraft:sand" + && suspicious_count < 5 + && random.next_f32() < 0.45 + { + suspicious_count += 1; + chunk.set_block_state( + world_pos.x, + world_pos.y, + world_pos.z, + Block::SUSPICIOUS_SAND.default_state, + ); + + let mut nbt = NbtCompound::new(); + nbt.put_string("id", "minecraft:brushable_block".to_string()); + nbt.put_int("x", world_pos.x); + nbt.put_int("y", world_pos.y); + nbt.put_int("z", world_pos.z); + nbt.put_string( + "LootTable", + "minecraft:archaeology/ocean_ruin_warm".to_string(), + ); + let mut rng = + LegacyRand::from_seed( + hash_block_pos(world_pos.x, world_pos.y, world_pos.z) as u64 + ); + nbt.put_long("LootTableSeed", rng.next_i64()); + chunk.add_block_entity(nbt); + continue; + } else if !self.is_warm + && palette_entry.name == "minecraft:gravel" + && suspicious_count < 5 + && random.next_f32() < 0.45 + { + suspicious_count += 1; + chunk.set_block_state( + world_pos.x, + world_pos.y, + world_pos.z, + Block::SUSPICIOUS_GRAVEL.default_state, + ); + + let mut nbt = NbtCompound::new(); + nbt.put_string("id", "minecraft:brushable_block".to_string()); + nbt.put_int("x", world_pos.x); + nbt.put_int("y", world_pos.y); + nbt.put_int("z", world_pos.z); + nbt.put_string( + "LootTable", + "minecraft:archaeology/ocean_ruin_cold".to_string(), + ); + let mut rng = + LegacyRand::from_seed( + hash_block_pos(world_pos.x, world_pos.y, world_pos.z) as u64 + ); + nbt.put_long("LootTableSeed", rng.next_i64()); + chunk.add_block_entity(nbt); + continue; + } + + let Some(mut state) = + BlockStateResolver::resolve(palette_entry, self.rotation, Mirror::None) + else { + continue; + }; + + if chunk.get_block_state(&world_pos).to_block_id() == Block::WATER.id { + let mut modified_entry = palette_entry.clone(); + if let Some((_, waterlogged)) = modified_entry + .properties + .iter_mut() + .find(|(name, _)| name == "waterlogged") + { + *waterlogged = "true".to_string(); + if let Some(waterlogged_state) = + BlockStateResolver::resolve(&modified_entry, self.rotation, Mirror::None) + { + state = waterlogged_state; + } + } + } + + chunk.set_block_state(world_pos.x, world_pos.y, world_pos.z, state); + + let final_block = Block::from_id(state.id.to_block_id()); + let block_entity_id = + crate::generation::structure::template::get_block_entity_id(final_block.name); + if block.nbt.is_some() || block_entity_id.is_some() { + let fallback_id = block_entity_id.unwrap_or(final_block.name); + let mut placed_nbt = NbtCompound::new(); + + placed_nbt.put_string("id", fallback_id.to_string()); + placed_nbt.put_int("x", world_pos.x); + placed_nbt.put_int("y", world_pos.y); + placed_nbt.put_int("z", world_pos.z); + + if let Some(template_nbt) = &block.nbt { + for (key, value) in &template_nbt.child_tags { + if key.as_ref() != "x" + && key.as_ref() != "y" + && key.as_ref() != "z" + && key.as_ref() != "id" + { + placed_nbt.child_tags.insert(key.clone(), value.clone()); + } + } + } + + if placed_nbt.get_string("LootTable").is_some() + && placed_nbt.get_long("LootTableSeed").is_none() + { + let mut rng = + LegacyRand::from_seed( + hash_block_pos(world_pos.x, world_pos.y, world_pos.z) as u64, + ); + placed_nbt.put_long("LootTableSeed", rng.next_i64()); + } + + chunk.add_block_entity(placed_nbt); + } + } } } diff --git a/crates/pumpkin-world/src/generation/structure/structures/stronghold/mod.rs b/crates/pumpkin-world/src/generation/structure/structures/stronghold/mod.rs index a5d82a3f4..5ae403461 100644 --- a/crates/pumpkin-world/src/generation/structure/structures/stronghold/mod.rs +++ b/crates/pumpkin-world/src/generation/structure/structures/stronghold/mod.rs @@ -63,10 +63,16 @@ impl StructureGenerator for StrongholdGenerator { context: StructureGeneratorContext<'_>, ) -> Option { let mut collector = StructurePiecesCollector::default(); - let mut random = context.random; + let mut tries: i64 = 0; loop { collector.clear(); + let mut random = crate::generation::structure::structures::create_chunk_random( + context.seed.wrapping_add(tries), + context.chunk_x, + context.chunk_z, + ); + tries += 1; let mut weights = get_initial_weights(); let mut last_piece_type: Option = None; let mut has_portal_room = false; diff --git a/crates/pumpkin-world/src/generation/structure/structures/stronghold/portal_room.rs b/crates/pumpkin-world/src/generation/structure/structures/stronghold/portal_room.rs index 0d76105b3..2694b10fc 100644 --- a/crates/pumpkin-world/src/generation/structure/structures/stronghold/portal_room.rs +++ b/crates/pumpkin-world/src/generation/structure/structures/stronghold/portal_room.rs @@ -81,7 +81,6 @@ impl StructurePieceBase for PortalRoomPiece { &self, _start: &StructurePiece, _random: &mut RandomGenerator, - // TODO: this is only for Stronghold and should not be here _weights: &mut Vec, _last_piece_type: &mut Option, has_portal_room: &mut bool, diff --git a/crates/pumpkin-world/src/generation/structure/structures/swamp_hut.rs b/crates/pumpkin-world/src/generation/structure/structures/swamp_hut.rs index cf9001923..5722fe73a 100644 --- a/crates/pumpkin-world/src/generation/structure/structures/swamp_hut.rs +++ b/crates/pumpkin-world/src/generation/structure/structures/swamp_hut.rs @@ -1,6 +1,10 @@ use std::sync::Arc; -use pumpkin_data::Block; +use pumpkin_data::{ + Block, BlockState, + block_properties::{HorizontalFacing, OakStairsLikeProperties, StairsShape}, +}; +use pumpkin_nbt::compound::NbtCompound; use pumpkin_util::{ BlockDirection, math::{block_box::BlockBox, position::BlockPos}, @@ -11,13 +15,13 @@ use serde::Deserialize; use crate::{ ProtoChunk, generation::{ - positions::chunk_pos::{get_center_x, get_center_z}, + positions::chunk_pos::{start_block_x, start_block_z}, structure::{ piece::StructurePieceType, shiftable_piece::ShiftableStructurePiece, structures::{ StructureGenerator, StructureGeneratorContext, StructurePieceBase, - StructurePiecesCollector, StructurePosition, + StructurePiecesCollector, StructurePosition, WorldPortalExt, }, }, }, @@ -31,21 +35,27 @@ impl StructureGenerator for SwampHutGenerator { &self, mut context: StructureGeneratorContext<'_>, ) -> Option { - let x = get_center_x(context.chunk_x); - let z = get_center_z(context.chunk_z); + let x = start_block_x(context.chunk_x); + let z = start_block_z(context.chunk_z); + + let facing = BlockDirection::get_random_horizontal_direction(&mut context.random); + let mut shiftable_piece = ShiftableStructurePiece::new( + StructurePieceType::SwampHut, + x, + 64, + z, + 7, + 7, + 9, + facing.get_axis(), + ); + shiftable_piece.piece.set_facing(Some(facing)); let mut collector = StructurePiecesCollector::default(); collector.add_piece(Box::new(SwampHutPiece { - shiftable_structure_piece: ShiftableStructurePiece::new( - StructurePieceType::SwampHut, - x, - 64, - z, - 7, - 7, - 9, - BlockDirection::get_random_horizontal_direction(&mut context.random).get_axis(), - ), + shiftable_structure_piece: shiftable_piece, + spawned_witch: false, + spawned_cat: false, })); Some(StructurePosition { @@ -57,17 +67,72 @@ impl StructureGenerator for SwampHutGenerator { pub struct SwampHutPiece { shiftable_structure_piece: ShiftableStructurePiece, + spawned_witch: bool, + spawned_cat: bool, +} + +impl SwampHutPiece { + fn spruce_stairs(facing: HorizontalFacing, shape: StairsShape) -> &'static BlockState { + let mut props = OakStairsLikeProperties::default(&Block::SPRUCE_STAIRS); + props.facing = facing; + props.shape = shape; + BlockState::from_id(props.to_state_id(&Block::SPRUCE_STAIRS)) + } + + fn spawn_witch(&mut self, chunk: &mut ProtoChunk, chunk_box: &BlockBox) { + if !self.spawned_witch { + let pos = self.shiftable_structure_piece.piece.offset_pos(2, 2, 5); + if chunk_box.contains_pos(&pos) { + self.spawned_witch = true; + let mut nbt = NbtCompound::new(); + nbt.put_string("id", "minecraft:witch".to_string()); + nbt.put_list( + "Pos", + vec![ + (f64::from(pos.x) + 0.5).into(), + f64::from(pos.y).into(), + (f64::from(pos.z) + 0.5).into(), + ], + ); + nbt.put_bool("PersistenceRequired", true); + chunk.add_structure_entity(nbt); + } + } + } + + fn spawn_cat(&mut self, chunk: &mut ProtoChunk, chunk_box: &BlockBox) { + if !self.spawned_cat { + let pos = self.shiftable_structure_piece.piece.offset_pos(2, 2, 5); + if chunk_box.contains_pos(&pos) { + self.spawned_cat = true; + let mut nbt = NbtCompound::new(); + nbt.put_string("id", "minecraft:cat".to_string()); + nbt.put_list( + "Pos", + vec![ + (f64::from(pos.x) + 0.5).into(), + f64::from(pos.y).into(), + (f64::from(pos.z) + 0.5).into(), + ], + ); + nbt.put_bool("PersistenceRequired", true); + nbt.put_string("variant", "minecraft:all_black".to_string()); + chunk.add_structure_entity(nbt); + } + } + } } impl StructurePieceBase for SwampHutPiece { fn as_any(&self) -> &dyn std::any::Any { self } + #[allow(clippy::too_many_lines)] fn place( &mut self, chunk: &mut ProtoChunk, - _block_registry: &dyn crate::world::WorldPortalExt, + block_registry: &dyn WorldPortalExt, _random: &mut RandomGenerator, _seed: i64, chunk_box: &BlockBox, @@ -210,20 +275,60 @@ impl StructurePieceBase for SwampHutPiece { p.add_block(chunk, oak_fence, 1, 2, 1, &box_limit); p.add_block(chunk, oak_fence, 5, 2, 1, &box_limit); - // let stairs_n = Block::SPRUCE_STAIRS.default_state.with("facing", "north"); - // let stairs_e = Block::SPRUCE_STAIRS.default_state.with("facing", "east"); - // let stairs_w = Block::SPRUCE_STAIRS.default_state.with("facing", "west"); - // let stairs_s = Block::SPRUCE_STAIRS.default_state.with("facing", "south"); + let stairs_n = Self::spruce_stairs(HorizontalFacing::North, StairsShape::Straight); + let stairs_e = Self::spruce_stairs(HorizontalFacing::East, StairsShape::Straight); + let stairs_w = Self::spruce_stairs(HorizontalFacing::West, StairsShape::Straight); + let stairs_s = Self::spruce_stairs(HorizontalFacing::South, StairsShape::Straight); - // p.fill_with_outline(chunk, &box_limit, false, 0, 4, 1, 6, 4, 1, &stairs_n, &stairs_n); - // p.fill_with_outline(chunk, &box_limit, false, 0, 4, 2, 0, 4, 7, &stairs_e, &stairs_e); - // p.fill_with_outline(chunk, &box_limit, false, 6, 4, 2, 6, 4, 7, &stairs_w, &stairs_w); - // p.fill_with_outline(chunk, &box_limit, false, 0, 4, 8, 6, 4, 8, &stairs_s, &stairs_s); + p.fill_with_outline( + chunk, &box_limit, false, 0, 4, 1, 6, 4, 1, stairs_n, stairs_n, + ); + p.fill_with_outline( + chunk, &box_limit, false, 0, 4, 2, 0, 4, 7, stairs_e, stairs_e, + ); + p.fill_with_outline( + chunk, &box_limit, false, 6, 4, 2, 6, 4, 7, stairs_w, stairs_w, + ); + p.fill_with_outline( + chunk, &box_limit, false, 0, 4, 8, 6, 4, 8, stairs_s, stairs_s, + ); - // p.add_block(chunk, &stairs_n.with("shape", "outer_right"), 0, 4, 1, &box_limit); - // p.add_block(chunk, &stairs_n.with("shape", "outer_left"), 6, 4, 1, &box_limit); - // p.add_block(chunk, &stairs_s.with("shape", "outer_left"), 0, 4, 8, &box_limit); - // p.add_block(chunk, &stairs_s.with("shape", "outer_right"), 6, 4, 8, &box_limit); + p.place_block( + chunk, + block_registry, + Self::spruce_stairs(HorizontalFacing::North, StairsShape::OuterRight), + 0, + 4, + 1, + &box_limit, + ); + p.place_block( + chunk, + block_registry, + Self::spruce_stairs(HorizontalFacing::North, StairsShape::OuterLeft), + 6, + 4, + 1, + &box_limit, + ); + p.place_block( + chunk, + block_registry, + Self::spruce_stairs(HorizontalFacing::South, StairsShape::OuterLeft), + 0, + 4, + 8, + &box_limit, + ); + p.place_block( + chunk, + block_registry, + Self::spruce_stairs(HorizontalFacing::South, StairsShape::OuterRight), + 6, + 4, + 8, + &box_limit, + ); for i in [2, 7] { for j in [1, 5] { @@ -231,15 +336,10 @@ impl StructurePieceBase for SwampHutPiece { } } - // if !self.has_witch { - // let world_coords = p.get_world_coords(2, 2, 5); - // if box_limit.contains(world_coords.0, world_coords.1, world_coords.2) { - // self.has_witch = true; - // // TODO: chunk.add_entity(Witch, world_coords) - // } - // } - // TODO: self.spawn_cat(chunk, &box_limit); + self.spawn_witch(chunk, &box_limit); + self.spawn_cat(chunk, &box_limit); } + fn get_structure_piece(&self) -> &super::StructurePiece { &self.shiftable_structure_piece.piece } diff --git a/crates/pumpkin-world/src/generation/structure/template/processor.rs b/crates/pumpkin-world/src/generation/structure/template/processor.rs index bc1159ac6..7db823462 100644 --- a/crates/pumpkin-world/src/generation/structure/template/processor.rs +++ b/crates/pumpkin-world/src/generation/structure/template/processor.rs @@ -232,7 +232,7 @@ impl PosRuleTest { let dist = dx + dy + dz; let chance = calculate_linear_chance(dist, *min_dist, *max_dist, *min_chance, *max_chance); - rng.next_f32() < chance + rng.next_f32() <= chance } Self::AxisAlignedLinearPos { axis, @@ -248,7 +248,7 @@ impl PosRuleTest { }; let chance = calculate_linear_chance(dist, *min_dist, *max_dist, *min_chance, *max_chance); - rng.next_f32() < chance + rng.next_f32() <= chance } } } @@ -526,6 +526,19 @@ fn replace_preserving_properties( }) } +fn get_random_facing_stairs( + rng: &mut impl RandomImpl, + stair_block: &'static Block, +) -> &'static BlockState { + let facings = ["north", "south", "east", "west"]; + let halves = ["bottom", "top"]; + let facing = facings[rng.next_bounded_i32(4) as usize]; + let half = halves[rng.next_bounded_i32(2) as usize]; + let props = [("facing", facing), ("half", half)]; + let state_id = stair_block.from_properties(&props).to_state_id(stair_block); + BlockState::from_id(state_id) +} + fn process_block_age( state: &'static BlockState, mossiness: f32, @@ -537,45 +550,50 @@ fn process_block_age( block_id, BlockId::STONE_BRICKS | BlockId::STONE - | BlockId::CRACKED_STONE_BRICKS | BlockId::CHISELED_STONE_BRICKS | BlockId::INFESTED_STONE | BlockId::INFESTED_STONE_BRICKS - | BlockId::INFESTED_CRACKED_STONE_BRICKS | BlockId::INFESTED_CHISELED_STONE_BRICKS ) { if rng.next_f32() < 0.5 { + let non_mossy = [ + Block::CRACKED_STONE_BRICKS.default_state, + get_random_facing_stairs(rng, &Block::STONE_BRICK_STAIRS), + ]; + let mossy = [ + Block::MOSSY_STONE_BRICKS.default_state, + get_random_facing_stairs(rng, &Block::MOSSY_STONE_BRICK_STAIRS), + ]; + let idx = rng.next_bounded_i32(2) as usize; if rng.next_f32() < mossiness { - return Block::MOSSY_STONE_BRICKS.default_state; + return mossy[idx]; } - return Block::CRACKED_STONE_BRICKS.default_state; + return non_mossy[idx]; } - } else if block_id == BlockId::STONE_BRICK_STAIRS - || block_id == BlockId::INFESTED_MOSSY_STONE_BRICKS - { - if rng.next_f32() < 0.5 && rng.next_f32() < mossiness { - return replace_preserving_properties(state, &Block::MOSSY_STONE_BRICK_STAIRS); + } else if check_block_has_tag(block_id, "stairs") { + if rng.next_f32() < 0.5 { + let non_mossy = [ + Block::STONE_SLAB.default_state, + Block::STONE_BRICK_SLAB.default_state, + ]; + let mossy = [ + replace_preserving_properties(state, &Block::MOSSY_STONE_BRICK_STAIRS), + Block::MOSSY_STONE_BRICK_SLAB.default_state, + ]; + let idx = rng.next_bounded_i32(2) as usize; + if rng.next_f32() < mossiness { + return mossy[idx]; + } + return non_mossy[idx]; } - } else if block_id == BlockId::STONE_BRICK_SLAB { - if rng.next_f32() < 0.5 && rng.next_f32() < mossiness { + } else if check_block_has_tag(block_id, "slabs") { + if rng.next_f32() < mossiness { return replace_preserving_properties(state, &Block::MOSSY_STONE_BRICK_SLAB); } - } else if block_id == BlockId::STONE_BRICK_WALL { - if rng.next_f32() < 0.5 && rng.next_f32() < mossiness { + } else if check_block_has_tag(block_id, "walls") { + if rng.next_f32() < mossiness { return replace_preserving_properties(state, &Block::MOSSY_STONE_BRICK_WALL); } - } else if block_id == BlockId::COBBLESTONE_STAIRS { - if rng.next_f32() < 0.5 && rng.next_f32() < mossiness { - return replace_preserving_properties(state, &Block::MOSSY_COBBLESTONE_STAIRS); - } - } else if block_id == BlockId::COBBLESTONE_SLAB { - if rng.next_f32() < 0.5 && rng.next_f32() < mossiness { - return replace_preserving_properties(state, &Block::MOSSY_COBBLESTONE_SLAB); - } - } else if block_id == BlockId::COBBLESTONE_WALL { - if rng.next_f32() < 0.5 && rng.next_f32() < mossiness { - return replace_preserving_properties(state, &Block::MOSSY_COBBLESTONE_WALL); - } } else if block_id == BlockId::OBSIDIAN && rng.next_f32() < 0.15 { return Block::CRYING_OBSIDIAN.default_state; } diff --git a/tools/pumpkin-codegen/src/configured_feature.rs b/tools/pumpkin-codegen/src/configured_feature.rs index 77e554790..d1b3b8c81 100644 --- a/tools/pumpkin-codegen/src/configured_feature.rs +++ b/tools/pumpkin-codegen/src/configured_feature.rs @@ -372,9 +372,21 @@ pub fn value_to_configured_feature(v: &Value) -> TokenStream { } "minecraft:fallen_tree" => { let trunk = value_to_block_state_provider(&config["trunk_provider"]); + let log_length = value_to_int_provider(&config["log_length"]); + let stump_decorators: Vec = config["stump_decorators"] + .as_array() + .map(|arr| arr.iter().map(value_to_tree_decorator).collect()) + .unwrap_or_default(); + let log_decorators: Vec = config["log_decorators"] + .as_array() + .map(|arr| arr.iter().map(value_to_tree_decorator).collect()) + .unwrap_or_default(); quote! { ConfiguredFeature::FallenTree(FallenTreeFeature { trunk_provider: #trunk, + log_length: #log_length, + stump_decorators: vec![#(#stump_decorators),*], + log_decorators: vec![#(#log_decorators),*], }) } } @@ -810,7 +822,42 @@ pub fn value_to_configured_feature(v: &Value) -> TokenStream { } } "minecraft:multiface_growth" => { - quote! { ConfiguredFeature::MultifaceGrowth(crate::generation::feature::features::multiface_growth::MultifaceGrowthFeature {}) } + let block_name = config["block"].as_str().unwrap_or("minecraft:glow_lichen"); + let stripped = block_name.strip_prefix("minecraft:").unwrap_or(block_name); + let block_ident = + quote::format_ident!("{}", stripped.to_uppercase().replace([':', '-'], "_")); + let search_range = config["search_range"].as_i64().unwrap_or(10) as i32; + let can_place_on_floor = config["can_place_on_floor"].as_bool().unwrap_or(false); + let can_place_on_ceiling = config["can_place_on_ceiling"].as_bool().unwrap_or(false); + let can_place_on_wall = config["can_place_on_wall"].as_bool().unwrap_or(false); + let chance_of_spreading = config["chance_of_spreading"].as_f64().unwrap_or(0.5) as f32; + let can_be_placed_on: Vec = config["can_be_placed_on"] + .as_array() + .map(|arr| { + arr.iter() + .filter_map(|v| v.as_str()) + .map(|name| { + let stripped = name.strip_prefix("minecraft:").unwrap_or(name); + let ident = quote::format_ident!( + "{}", + stripped.to_uppercase().replace([':', '-'], "_") + ); + quote! { pumpkin_data::BlockId::#ident } + }) + .collect() + }) + .unwrap_or_default(); + quote! { + ConfiguredFeature::MultifaceGrowth(crate::generation::feature::features::multiface_growth::MultifaceGrowthFeature { + place_block: pumpkin_data::BlockId::#block_ident, + search_range: #search_range, + can_place_on_floor: #can_place_on_floor, + can_place_on_ceiling: #can_place_on_ceiling, + can_place_on_wall: #can_place_on_wall, + chance_of_spreading: #chance_of_spreading, + can_be_placed_on: vec![#(#can_be_placed_on),*], + }) + } } "minecraft:blue_ice" => { quote! { ConfiguredFeature::BlueIce(crate::generation::feature::features::blue_ice::BlueIceFeature {}) } @@ -842,7 +889,22 @@ pub fn value_to_configured_feature(v: &Value) -> TokenStream { quote! { ConfiguredFeature::CoralClaw(crate::generation::feature::features::coral::coral_claw::CoralClawFeature) } } "minecraft:huge_fungus" => { - quote! { ConfiguredFeature::HugeFungus(crate::generation::feature::features::huge_fungus::HugeFungusFeature {}) } + let valid_base_block = value_to_block_state(&config["valid_base_block"]); + let stem_state = value_to_block_state(&config["stem_state"]); + let hat_state = value_to_block_state(&config["hat_state"]); + let decor_state = value_to_block_state(&config["decor_state"]); + let replaceable_blocks = value_to_block_predicate(&config["replaceable_blocks"]); + let planted = config["planted"].as_bool().unwrap_or(false); + quote! { + ConfiguredFeature::HugeFungus(crate::generation::feature::features::huge_fungus::HugeFungusFeature { + valid_base_block: #valid_base_block, + stem_state: #stem_state, + hat_state: #hat_state, + decor_state: #decor_state, + replaceable_blocks: #replaceable_blocks, + planted: #planted, + }) + } } "minecraft:weeping_vines" => { quote! { ConfiguredFeature::WeepingVines(crate::generation::feature::features::weeping_vines::WeepingVinesFeature {}) } @@ -860,10 +922,28 @@ pub fn value_to_configured_feature(v: &Value) -> TokenStream { } } "minecraft:delta_feature" => { - quote! { ConfiguredFeature::DeltaFeature(crate::generation::feature::features::delta_feature::DeltaFeatureFeature {}) } + let contents = value_to_block_state(&config["contents"]); + let rim = value_to_block_state(&config["rim"]); + let size = value_to_int_provider(&config["size"]); + let rim_size = value_to_int_provider(&config["rim_size"]); + quote! { + ConfiguredFeature::DeltaFeature(crate::generation::feature::features::delta_feature::DeltaFeatureFeature { + contents: #contents, + rim: #rim, + size: #size, + rim_size: #rim_size, + }) + } } "minecraft:fill_layer" => { - quote! { ConfiguredFeature::FillLayer(crate::generation::feature::features::fill_layer::FillLayerFeature {}) } + let height = config["height"].as_i64().unwrap_or(0) as i32; + let state = value_to_block_state(&config["state"]); + quote! { + ConfiguredFeature::FillLayer(crate::generation::feature::features::fill_layer::FillLayerFeature { + height: #height, + state: #state, + }) + } } "minecraft:bonus_chest" => { quote! { ConfiguredFeature::BonusChest(crate::generation::feature::features::bonus_chest::BonusChestFeature {}) } @@ -898,10 +978,31 @@ pub fn value_to_configured_feature(v: &Value) -> TokenStream { } } "minecraft:block_pile" => { - quote! { ConfiguredFeature::BlockPile(crate::generation::feature::features::block_pile::BlockPileFeature {}) } + let provider = value_to_block_state_provider(&config["state_provider"]); + quote! { + ConfiguredFeature::BlockPile(crate::generation::feature::features::block_pile::BlockPileFeature { + state_provider: #provider, + }) + } } "minecraft:replace_single_block" => { - quote! { ConfiguredFeature::ReplaceSingleBlock(crate::generation::feature::features::replace_single_block::ReplaceSingleBlockFeature {}) } + let targets: Vec = config["targets"] + .as_array() + .map(|arr| { + arr.iter() + .map(|t| { + let rule = value_to_rule_test(&t["target"]); + let state = value_to_block_state(&t["state"]); + quote! { OreTarget { target: #rule, state: #state } } + }) + .collect() + }) + .unwrap_or_default(); + quote! { + ConfiguredFeature::ReplaceSingleBlock(crate::generation::feature::features::replace_single_block::ReplaceSingleBlockFeature { + targets: vec![#(#targets),*], + }) + } } "minecraft:void_start_platform" => { quote! { ConfiguredFeature::VoidStartPlatform(crate::generation::feature::features::void_start_platform::VoidStartPlatformFeature {}) } @@ -1506,13 +1607,17 @@ fn value_to_tree_decorator(v: &Value) -> TokenStream { let prob = v["probability"].as_f64().unwrap_or(0.0) as f32; quote! { TreeDecorator::LeaveVine(LeavesVineTreeDecorator { probability: #prob }) } } - "minecraft:cocoa" => quote! { TreeDecorator::Cocoa(CocoaTreeDecorator {}) }, + "minecraft:cocoa" => { + let prob = v["probability"].as_f64().unwrap_or(0.0) as f32; + quote! { TreeDecorator::Cocoa(CocoaTreeDecorator { probability: #prob }) } + } "minecraft:beehive" => { let prob = v["probability"].as_f64().unwrap_or(0.0) as f32; quote! { TreeDecorator::Beehive(BeehiveTreeDecorator { probability: #prob }) } } "minecraft:alter_ground" => { - quote! { TreeDecorator::AlterGround(AlterGroundTreeDecorator {}) } + let provider = value_to_block_state_provider(&v["provider"]); + quote! { TreeDecorator::AlterGround(AlterGroundTreeDecorator { provider: #provider }) } } "minecraft:attached_to_logs" => { let prob = v["probability"].as_f64().unwrap_or(0.0) as f32; @@ -1534,7 +1639,29 @@ fn value_to_tree_decorator(v: &Value) -> TokenStream { } } "minecraft:attached_to_leaves" => { - quote! { TreeDecorator::AttachedToLeaves(AttachedToLeavesTreeDecorator {}) } + let prob = v["probability"].as_f64().unwrap_or(0.0) as f32; + let exclusion_radius_xz = v["exclusion_radius_xz"].as_i64().unwrap_or(0) as i32; + let exclusion_radius_y = v["exclusion_radius_y"].as_i64().unwrap_or(0) as i32; + let bp = value_to_block_state_provider(&v["block_provider"]); + let required_empty_blocks = v["required_empty_blocks"].as_i64().unwrap_or(1) as i32; + let dirs: Vec = v["directions"] + .as_array() + .map(|arr| { + arr.iter() + .filter_map(|d| d.as_str().map(value_to_block_direction)) + .collect() + }) + .unwrap_or_default(); + quote! { + TreeDecorator::AttachedToLeaves(AttachedToLeavesTreeDecorator { + probability: #prob, + exclusion_radius_xz: #exclusion_radius_xz, + exclusion_radius_y: #exclusion_radius_y, + block_provider: #bp, + required_empty_blocks: #required_empty_blocks, + directions: vec![#(#dirs),*], + }) + } } "minecraft:place_on_ground" => { let tries = v["tries"].as_i64().unwrap_or(1) as i32; @@ -1551,10 +1678,20 @@ fn value_to_tree_decorator(v: &Value) -> TokenStream { } } "minecraft:creaking_heart" => { - quote! { TreeDecorator::CreakingHeart(CreakingHeartTreeDecorator {}) } + let prob = v["probability"].as_f64().unwrap_or(0.0) as f32; + quote! { TreeDecorator::CreakingHeart(CreakingHeartTreeDecorator { probability: #prob }) } } "minecraft:pale_moss" => { - quote! { TreeDecorator::PaleMoss(PaleMossTreeDecorator {}) } + let leaves_probability = v["leaves_probability"].as_f64().unwrap_or(0.0) as f32; + let trunk_probability = v["trunk_probability"].as_f64().unwrap_or(0.0) as f32; + let ground_probability = v["ground_probability"].as_f64().unwrap_or(0.0) as f32; + quote! { + TreeDecorator::PaleMoss(PaleMossTreeDecorator { + leaves_probability: #leaves_probability, + trunk_probability: #trunk_probability, + ground_probability: #ground_probability, + }) + } } other => { let msg = format!("unknown tree decorator: {other}");