steel_core/behavior/blocks/redstone/piston/
head.rs1use std::sync::Arc;
4
5use steel_macros::block_behavior;
6use steel_registry::blocks::BlockRef;
7use steel_registry::blocks::block_state_ext::BlockStateExt as _;
8use steel_registry::blocks::properties::{BlockStateProperties, Direction, PistonType};
9use steel_registry::item_stack::ItemStack;
10use steel_registry::{vanilla_blocks, vanilla_items};
11use steel_utils::{BlockPos, BlockStateId};
12
13use crate::behavior::{BlockBehavior, BlockPlaceContext};
14use crate::entity::ai::path::PathComputationType;
15use crate::player::Player;
16use crate::world::{LevelReader, ScheduledTickAccess, World};
17
18#[block_behavior]
19pub struct PistonHeadBlock;
21
22impl PistonHeadBlock {
23 #[must_use]
25 pub const fn new(_block: BlockRef) -> Self {
26 Self
27 }
28
29 fn is_fitting_base(arm_state: BlockStateId, potential_base: BlockStateId) -> bool {
30 let base_block = match arm_state.get_value(&BlockStateProperties::PISTON_TYPE) {
31 PistonType::Normal => &vanilla_blocks::PISTON,
32 PistonType::Sticky => &vanilla_blocks::STICKY_PISTON,
33 };
34 potential_base.get_block() == base_block
35 && potential_base.get_value(&BlockStateProperties::EXTENDED)
36 && potential_base.get_value(&BlockStateProperties::FACING)
37 == arm_state.get_value(&BlockStateProperties::FACING)
38 }
39}
40
41impl BlockBehavior for PistonHeadBlock {
42 fn get_state_for_placement(&self, _context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
43 Some(vanilla_blocks::PISTON_HEAD.default_state())
44 }
45
46 fn player_will_destroy(
47 &self,
48 state: BlockStateId,
49 world: &Arc<World>,
50 pos: BlockPos,
51 player: &Player,
52 ) -> BlockStateId {
53 if player.has_infinite_materials() {
54 let base_pos = pos.relative(state.get_value(&BlockStateProperties::FACING).opposite());
55 if Self::is_fitting_base(state, world.get_block_state(base_pos)) {
56 world.destroy_block(base_pos, false);
57 }
58 }
59 state
60 }
61
62 fn affect_neighbors_after_removal(
63 &self,
64 state: BlockStateId,
65 world: &Arc<World>,
66 pos: BlockPos,
67 _moved_by_piston: bool,
68 ) {
69 let base_pos = pos.relative(state.get_value(&BlockStateProperties::FACING).opposite());
70 if Self::is_fitting_base(state, world.get_block_state(base_pos)) {
71 world.destroy_block(base_pos, true);
72 }
73 }
74
75 fn update_shape(
76 &self,
77 state: BlockStateId,
78 world: &dyn ScheduledTickAccess,
79 pos: BlockPos,
80 direction: Direction,
81 _neighbor_pos: BlockPos,
82 _neighbor_state: BlockStateId,
83 ) -> BlockStateId {
84 if direction.opposite() == state.get_value(&BlockStateProperties::FACING)
85 && !self.can_survive(state, world, pos)
86 {
87 vanilla_blocks::AIR.default_state()
88 } else {
89 state
90 }
91 }
92
93 fn can_survive(&self, state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
94 let facing = state.get_value(&BlockStateProperties::FACING);
95 let base = world.get_block_state(pos.relative(facing.opposite()));
96 Self::is_fitting_base(state, base)
97 || (base.get_block() == &vanilla_blocks::MOVING_PISTON
98 && base.get_value(&BlockStateProperties::FACING) == facing)
99 }
100
101 fn handle_neighbor_changed(
102 &self,
103 state: BlockStateId,
104 world: &Arc<World>,
105 pos: BlockPos,
106 source_block: BlockRef,
107 _moved_by_piston: bool,
108 ) {
109 if self.can_survive(state, world.as_ref(), pos) {
110 let base_pos = pos.relative(state.get_value(&BlockStateProperties::FACING).opposite());
111 world.neighbor_changed(base_pos, source_block);
112 }
113 }
114
115 fn get_clone_item_stack(
116 &self,
117 _block: BlockRef,
118 state: BlockStateId,
119 _include_data: bool,
120 ) -> Option<ItemStack> {
121 Some(ItemStack::new(
122 match state.get_value(&BlockStateProperties::PISTON_TYPE) {
123 PistonType::Normal => &vanilla_items::PISTON,
124 PistonType::Sticky => &vanilla_items::STICKY_PISTON,
125 },
126 ))
127 }
128
129 fn is_pathfindable(&self, _state: BlockStateId, _type: PathComputationType) -> bool {
130 false
131 }
132}