Skip to main content

steel_core/behavior/blocks/redstone/piston/
moving.rs

1//! Vanilla transient moving-piston block behavior.
2
3use std::sync::{Arc, Weak};
4
5use steel_macros::block_behavior;
6use steel_registry::block_entity_type::BlockEntityTypeRef;
7use steel_registry::blocks::BlockRef;
8use steel_registry::blocks::block_state_ext::BlockStateExt as _;
9use steel_registry::blocks::properties::{
10    BlockStateProperties, BoolProperty, Direction, EnumProperty,
11};
12use steel_registry::item_stack::ItemStack;
13use steel_registry::{vanilla_block_entity_types, vanilla_blocks};
14use steel_utils::{BlockPos, BlockStateId, Downcast as _};
15
16use crate::behavior::{
17    BLOCK_BEHAVIORS, BlockBehavior, BlockCollisionBoxes, BlockCollisionContext,
18    BlockEntityCreation, BlockHitResult, BlockLootContext, BlockPlaceContext, InteractionResult,
19    InventoryAccess,
20};
21use crate::block_entity::{BlockEntityTicker, entities::PistonMovingBlockEntity};
22use crate::entity::ai::path::PathComputationType;
23use crate::player::Player;
24use crate::world::{LevelReader, World};
25
26#[block_behavior]
27/// Vanilla transient moving-piston block.
28pub struct MovingPistonBlock;
29
30const EXTENDED: &BoolProperty = &BlockStateProperties::EXTENDED;
31const FACING: &EnumProperty<Direction> = &BlockStateProperties::FACING;
32impl MovingPistonBlock {
33    /// Creates moving-piston behavior.
34    #[must_use]
35    pub const fn new(_block: BlockRef) -> Self {
36        Self
37    }
38}
39
40impl BlockBehavior for MovingPistonBlock {
41    fn get_state_for_placement(&self, _context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
42        Some(vanilla_blocks::MOVING_PISTON.default_state())
43    }
44
45    fn new_block_entity(
46        &self,
47        _level: Weak<World>,
48        _pos: BlockPos,
49        _state: BlockStateId,
50    ) -> BlockEntityCreation {
51        BlockEntityCreation::NoEntity
52    }
53
54    fn get_block_entity_ticker(
55        &self,
56        _world: &Arc<World>,
57        _state: BlockStateId,
58        block_entity_type: BlockEntityTypeRef,
59    ) -> Option<BlockEntityTicker> {
60        BlockEntityTicker::for_matching_entity_tick(
61            block_entity_type,
62            &vanilla_block_entity_types::PISTON,
63        )
64    }
65
66    fn get_collision_boxes(
67        &self,
68        _state: BlockStateId,
69        world: &dyn LevelReader,
70        pos: BlockPos,
71        _context: BlockCollisionContext,
72    ) -> BlockCollisionBoxes {
73        let Some(block_entity) = world.get_block_entity(pos) else {
74            return BlockCollisionBoxes::new();
75        };
76        block_entity
77            .downcast_ref::<PistonMovingBlockEntity>()
78            .map_or_else(BlockCollisionBoxes::new, |piston| {
79                piston.collision_boxes(world, pos)
80            })
81    }
82
83    fn get_block_support_boxes(
84        &self,
85        state: BlockStateId,
86        world: &dyn LevelReader,
87        pos: BlockPos,
88    ) -> BlockCollisionBoxes {
89        self.get_collision_boxes(state, world, pos, BlockCollisionContext::empty())
90    }
91
92    fn destroy(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
93        let base_pos = pos.relative(state.get_value(FACING).opposite());
94        let base_state = world.get_block_state(base_pos);
95        let behavior = BLOCK_BEHAVIORS.get_behavior(base_state.get_block());
96        if behavior.is_piston_base() && base_state.get_value(EXTENDED) {
97            world.remove_block(base_pos, false);
98        }
99    }
100
101    fn get_drops(
102        &self,
103        _state: BlockStateId,
104        context: &BlockLootContext<'_>,
105    ) -> Option<Vec<ItemStack>> {
106        let Some(block_entity) = context.world().get_block_entity(context.pos()) else {
107            return Some(Vec::new());
108        };
109        let Some(piston) = block_entity.downcast_ref::<PistonMovingBlockEntity>() else {
110            return Some(Vec::new());
111        };
112        let moved_state = piston.moved_state();
113        Some(context.get_drops(moved_state))
114    }
115
116    fn use_without_item(
117        &self,
118        _state: BlockStateId,
119        world: &Arc<World>,
120        pos: BlockPos,
121        _player: &Player,
122        _hit_result: &BlockHitResult,
123        _inv: &mut InventoryAccess,
124    ) -> InteractionResult {
125        if world.get_block_entity(pos).is_none() {
126            world.remove_block(pos, false);
127            InteractionResult::Consume
128        } else {
129            InteractionResult::Pass
130        }
131    }
132
133    fn get_clone_item_stack(
134        &self,
135        _block: BlockRef,
136        _state: BlockStateId,
137        _include_data: bool,
138    ) -> Option<ItemStack> {
139        Some(ItemStack::empty())
140    }
141
142    fn is_pathfindable(&self, _state: BlockStateId, _type: PathComputationType) -> bool {
143        false
144    }
145}
146
147#[cfg(test)]
148mod tests {
149    use steel_registry::init_vanilla_registry;
150
151    use super::*;
152    use crate::test_support::fresh_test_world;
153
154    #[test]
155    fn moving_piston_selects_only_the_piston_block_entity_ticker() {
156        init_vanilla_registry();
157        let world = fresh_test_world("moving_piston_ticker");
158        let behavior = MovingPistonBlock::new(&vanilla_blocks::MOVING_PISTON);
159        let state = vanilla_blocks::MOVING_PISTON.default_state();
160
161        assert!(
162            behavior
163                .get_block_entity_ticker(&world, state, &vanilla_block_entity_types::PISTON)
164                .is_some()
165        );
166        assert!(
167            behavior
168                .get_block_entity_ticker(&world, state, &vanilla_block_entity_types::CHEST)
169                .is_none()
170        );
171    }
172}