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 fn place_context(
71 item_in_hand: &mut ItemStack,
72 is_secondary_use_active: bool,
73 ) -> BlockPlaceContext<'_> {
74 let hit_result = BlockHitResult {
75 location: DVec3::ZERO,
76 direction: Direction::Up,
77 block_pos: BlockPos::ZERO,
78 miss: false,
79 inside: false,
80 world_border_hit: false,
81 };
82 let source = PlacementSource::direct(
83 None,
84 InteractionHand::MainHand,
85 item_in_hand,
86 PlacementOrientation::Player {
87 rotation: 0.0,
88 pitch: 0.0,
89 },
90 is_secondary_use_active,
91 );
92 BlockPlaceContext::new(test_world(), source, &hit_result)
93 }
94
95 #[test]
96 fn placement_uses_horizontal_direction_and_preserves_existing_facing() {
97 init_vanilla_registry();
98
99 let placed = segmentable_placement_state(
100 &vanilla_blocks::LEAF_LITTER,
101 &BlockStateProperties::SEGMENT_AMOUNT,
102 vanilla_blocks::AIR.default_state(),
103 Direction::East,
104 );
105 assert_eq!(
106 placed.get_value(&BlockStateProperties::HORIZONTAL_FACING),
107 Direction::West
108 );
109
110 let existing = vanilla_blocks::LEAF_LITTER
111 .default_state()
112 .set_value(&BlockStateProperties::HORIZONTAL_FACING, Direction::North)
113 .set_value(&BlockStateProperties::SEGMENT_AMOUNT, 2);
114 let stacked = segmentable_placement_state(
115 &vanilla_blocks::LEAF_LITTER,
116 &BlockStateProperties::SEGMENT_AMOUNT,
117 existing,
118 Direction::East,
119 );
120 assert_eq!(
121 stacked.get_value(&BlockStateProperties::HORIZONTAL_FACING),
122 Direction::North
123 );
124 assert_eq!(stacked.get_value(&BlockStateProperties::SEGMENT_AMOUNT), 3);
125 }
126
127 #[test]
128 fn replacement_matches_vanilla_segmentable_and_default_rules() {
129 init_vanilla_registry();
130 init_behaviors();
131
132 let leaf_litter = vanilla_blocks::LEAF_LITTER.default_state();
133 let mut leaf_litter_item = ItemStack::new(&vanilla_items::LEAF_LITTER);
134 assert!(leaf_litter.can_be_replaced(&place_context(&mut leaf_litter_item, false)));
135 assert!(!leaf_litter.can_be_replaced(&place_context(&mut leaf_litter_item, true)));
136
137 let full_leaf_litter =
138 leaf_litter.set_value(&BlockStateProperties::SEGMENT_AMOUNT, MAX_SEGMENT_AMOUNT);
139 assert!(!full_leaf_litter.can_be_replaced(&place_context(&mut leaf_litter_item, false,)));
140 let mut stone = ItemStack::new(&vanilla_items::STONE);
141 assert!(leaf_litter.can_be_replaced(&place_context(&mut stone, false)));
142 let mut empty = ItemStack::empty();
143 assert!(leaf_litter.can_be_replaced(&place_context(&mut empty, false)));
144 }
145
146 #[test]
147 fn only_flower_beds_are_bonemealable() {
148 init_vanilla_registry();
149 init_behaviors();
150
151 assert!(
152 BLOCK_BEHAVIORS
153 .get_behavior(&vanilla_blocks::LEAF_LITTER)
154 .as_bonemealable()
155 .is_none()
156 );
157 assert!(
158 BLOCK_BEHAVIORS
159 .get_behavior(&vanilla_blocks::PINK_PETALS)
160 .as_bonemealable()
161 .is_some()
162 );
163 }
164}