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::{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]
19/// Vanilla piston-head block.
20pub struct PistonHeadBlock;
21
22impl PistonHeadBlock {
23    /// Creates piston-head behavior.
24    #[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}