Skip to main content

steel_core/behavior/blocks/vegetation/
segmentable_block.rs

1use 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}