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