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::entity::ai::path::PathComputationType;
17use crate::player::Player;
18use crate::world::{LevelReader, ScheduledTickAccess, World};
19
20#[block_behavior]
21/// Vanilla piston-head block.
22pub 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    /// Creates piston-head behavior.
29    #[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}