steel_core/behavior/blocks/vegetation/
segmentable_block.rs1use steel_registry::REGISTRY;
2use steel_registry::blocks::BlockRef;
3use steel_registry::blocks::block_state_ext::BlockStateExt;
4use steel_registry::blocks::properties::{BlockStateProperties, EnumProperty, IntProperty};
5use steel_utils::{BlockStateId, Direction};
6
7use crate::behavior::{BlockPlaceContext, block::default_can_be_replaced};
8use crate::world::LevelReader;
9
10pub const MAX_SEGMENT_AMOUNT: u8 = 4;
11const FACING_PROPERTY: &EnumProperty<Direction> = &BlockStateProperties::HORIZONTAL_FACING;
12
13pub fn segmentable_get_state_for_placement(
14 block_ref: BlockRef,
15 segment_property: &IntProperty,
16 context: &BlockPlaceContext<'_>,
17) -> BlockStateId {
18 let existing_state = context.world.get_block_state(context.place_pos());
19 segmentable_placement_state(
20 block_ref,
21 segment_property,
22 existing_state,
23 context.horizontal_direction(),
24 )
25}
26
27pub fn segmentable_can_be_replaced(
28 segment_property: &IntProperty,
29 state: BlockStateId,
30 context: &BlockPlaceContext<'_>,
31) -> bool {
32 (!context.is_secondary_use_active()
33 && context.with_item(|item| item.item() == REGISTRY.items.by_block(state.get_block()))
34 && state.get_value(segment_property) < MAX_SEGMENT_AMOUNT)
35 || default_can_be_replaced(state, context)
36}
37
38fn segmentable_placement_state(
39 block_ref: BlockRef,
40 segment_property: &IntProperty,
41 existing_state: BlockStateId,
42 horizontal_direction: Direction,
43) -> BlockStateId {
44 if existing_state.get_block() == block_ref {
45 let amount = (existing_state.get_value(segment_property) + 1).min(MAX_SEGMENT_AMOUNT);
46 existing_state.set_value(segment_property, amount)
47 } else {
48 block_ref
49 .default_state()
50 .set_value(FACING_PROPERTY, horizontal_direction.opposite())
51 }
52}
53
54#[cfg(test)]
55mod tests {
56 use glam::DVec3;
57 use steel_registry::item_stack::ItemStack;
58 use steel_registry::{init_vanilla_registry, vanilla_blocks, vanilla_items};
59 use steel_utils::{BlockPos, types::InteractionHand};
60
61 use super::*;
62 use crate::{
63 behavior::{
64 BLOCK_BEHAVIORS, BlockHitResult, BlockStateBehaviorExt, PlacementOrientation,
65 PlacementSource, init_behaviors,
66 },
67 test_support::test_world,
68 };
69
70 const SEGMENT_AMOUNT: &IntProperty = &BlockStateProperties::SEGMENT_AMOUNT;
71
72 fn place_context(
73 item_in_hand: &mut ItemStack,
74 is_secondary_use_active: bool,
75 ) -> BlockPlaceContext<'_> {
76 let hit_result = BlockHitResult {
77 location: DVec3::ZERO,
78 direction: Direction::Up,
79 block_pos: BlockPos::ZERO,
80 miss: false,
81 inside: false,
82 world_border_hit: false,
83 };
84 let source = PlacementSource::direct(
85 None,
86 InteractionHand::MainHand,
87 item_in_hand,
88 PlacementOrientation::Player {
89 rotation: 0.0,
90 pitch: 0.0,
91 },
92 is_secondary_use_active,
93 );
94 BlockPlaceContext::new(test_world(), source, &hit_result)
95 }
96
97 #[test]
98 fn placement_uses_horizontal_direction_and_preserves_existing_facing() {
99 init_vanilla_registry();
100
101 let placed = segmentable_placement_state(
102 &vanilla_blocks::LEAF_LITTER,
103 SEGMENT_AMOUNT,
104 vanilla_blocks::AIR.default_state(),
105 Direction::East,
106 );
107 assert_eq!(placed.get_value(FACING_PROPERTY), Direction::West);
108
109 let existing = vanilla_blocks::LEAF_LITTER
110 .default_state()
111 .set_value(FACING_PROPERTY, Direction::North)
112 .set_value(SEGMENT_AMOUNT, 2);
113 let stacked = segmentable_placement_state(
114 &vanilla_blocks::LEAF_LITTER,
115 SEGMENT_AMOUNT,
116 existing,
117 Direction::East,
118 );
119 assert_eq!(stacked.get_value(FACING_PROPERTY), Direction::North);
120 assert_eq!(stacked.get_value(SEGMENT_AMOUNT), 3);
121 }
122
123 #[test]
124 fn replacement_matches_vanilla_segmentable_and_default_rules() {
125 init_vanilla_registry();
126 init_behaviors();
127
128 let leaf_litter = vanilla_blocks::LEAF_LITTER.default_state();
129 let mut leaf_litter_item = ItemStack::new(&vanilla_items::LEAF_LITTER);
130 assert!(leaf_litter.can_be_replaced(&place_context(&mut leaf_litter_item, false)));
131 assert!(!leaf_litter.can_be_replaced(&place_context(&mut leaf_litter_item, true)));
132
133 let full_leaf_litter = leaf_litter.set_value(SEGMENT_AMOUNT, MAX_SEGMENT_AMOUNT);
134 assert!(!full_leaf_litter.can_be_replaced(&place_context(&mut leaf_litter_item, false,)));
135 let mut stone = ItemStack::new(&vanilla_items::STONE);
136 assert!(leaf_litter.can_be_replaced(&place_context(&mut stone, false)));
137 let mut empty = ItemStack::empty();
138 assert!(leaf_litter.can_be_replaced(&place_context(&mut empty, false)));
139 }
140
141 #[test]
142 fn only_flower_beds_are_bonemealable() {
143 init_vanilla_registry();
144 init_behaviors();
145
146 assert!(
147 BLOCK_BEHAVIORS
148 .get_behavior(&vanilla_blocks::LEAF_LITTER)
149 .as_bonemealable()
150 .is_none()
151 );
152 assert!(
153 BLOCK_BEHAVIORS
154 .get_behavior(&vanilla_blocks::PINK_PETALS)
155 .as_bonemealable()
156 .is_some()
157 );
158 }
159}