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