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