Skip to main content

steel_core/behavior/blocks/redstone/piston/
head.rs

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