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::{
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]
27pub struct MovingPistonBlock;
29
30const EXTENDED: &BoolProperty = &BlockStateProperties::EXTENDED;
31const FACING: &EnumProperty<Direction> = &BlockStateProperties::FACING;
32impl MovingPistonBlock {
33 #[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}