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