steel_core/behavior/blocks/redstone/piston/
moving.rs1use 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]
25pub struct MovingPistonBlock;
27
28impl MovingPistonBlock {
29 #[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}