Skip to main content

steel_core/behavior/blocks/vegetation/
shelf_mushroom_block.rs

1use crate::behavior::{
2    BlockBehavior, EntityFallOnContext, blocks::vegetation::bonemealable::Bonemealable,
3    context::BlockPlaceContext,
4};
5use crate::entity::ai::path::PathComputationType;
6use crate::world::{LevelReader, ScheduledTickAccess, World};
7use rand::Rng;
8use std::sync::Arc;
9use steel_macros::block_behavior;
10use steel_protocol::packets::game::SoundSource;
11use steel_registry::sound_events::BLOCK_SHELF_MUSHROOM_BOUNCE;
12use steel_registry::vanilla_entities::{ITEM, TNT};
13use steel_registry::{
14    blocks::{
15        BlockRef,
16        block_state_ext::BlockStateExt,
17        properties::{BlockStateProperties, Direction, EnumProperty, IntProperty},
18    },
19    vanilla_blocks,
20};
21use steel_utils::types::UpdateFlags;
22use steel_utils::{BlockPos, BlockStateId};
23
24const MAX_AGE: u8 = 1;
25const AGE: &IntProperty = &BlockStateProperties::AGE_1;
26const FACING_PROPERTY: &EnumProperty<Direction> = &BlockStateProperties::HORIZONTAL_FACING;
27
28/// Shelf Mushroom Block behavior
29#[block_behavior(class = "ShelfMushroomBlock")]
30pub struct ShelfMushroomBlock {
31    block: BlockRef,
32}
33
34impl ShelfMushroomBlock {
35    /// Creates a new shelf mushroom block
36    #[must_use]
37    pub const fn new(block: BlockRef) -> Self {
38        Self { block }
39    }
40
41    #[inline]
42    fn age(state: BlockStateId) -> u8 {
43        state.get_value(AGE)
44    }
45
46    #[inline]
47    fn facing(state: BlockStateId) -> Direction {
48        state.get_value(FACING_PROPERTY)
49    }
50}
51
52impl BlockBehavior for ShelfMushroomBlock {
53    fn can_survive(&self, state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
54        let facing = Self::facing(state);
55        let support_pos = pos.relative(facing.opposite());
56        let support_state = world.get_block_state(support_pos);
57        world.is_face_sturdy(support_state, support_pos, facing)
58    }
59
60    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
61        for direction in context.get_nearest_looking_directions() {
62            if !direction.is_horizontal() {
63                continue;
64            }
65
66            let state = self
67                .block
68                .default_state()
69                .set_value(FACING_PROPERTY, direction.opposite());
70            if self.can_survive(state, context.world, context.place_pos()) {
71                return Some(state);
72            }
73        }
74
75        None
76    }
77
78    fn update_shape(
79        &self,
80        state: BlockStateId,
81        world: &dyn ScheduledTickAccess,
82        pos: BlockPos,
83        direction: Direction,
84        _neighbor_pos: BlockPos,
85        _neighbor_state: BlockStateId,
86    ) -> BlockStateId {
87        let facing = Self::facing(state);
88        if direction == facing.opposite() && !self.can_survive(state, world, pos) {
89            return vanilla_blocks::AIR.default_state();
90        }
91
92        state
93    }
94
95    fn is_pathfindable(
96        &self,
97        _state: BlockStateId,
98        _computation_type: PathComputationType,
99    ) -> bool {
100        false
101    }
102
103    fn as_bonemealable(&self) -> Option<&dyn Bonemealable> {
104        Some(self)
105    }
106
107    fn bounce_on(
108        &self,
109        _state: BlockStateId,
110        world: &Arc<World>,
111        pos: BlockPos,
112        context: EntityFallOnContext<'_>,
113    ) {
114        if context.entity.entity_type != &ITEM && context.entity.entity_type != &TNT {
115            world.play_sound(
116                &BLOCK_SHELF_MUSHROOM_BOUNCE,
117                SoundSource::Blocks,
118                pos,
119                1.0f32,
120                1.0f32,
121                None,
122            );
123        }
124    }
125}
126
127impl Bonemealable for ShelfMushroomBlock {
128    #[inline]
129    fn is_valid_bonemeal_target(
130        &self,
131        state: BlockStateId,
132        _world: &dyn LevelReader,
133        _pos: BlockPos,
134    ) -> bool {
135        Self::age(state) < MAX_AGE
136    }
137
138    #[inline]
139    fn perform_bonemeal(
140        &self,
141        state: BlockStateId,
142        world: &Arc<World>,
143        _rng: &mut dyn Rng,
144        pos: BlockPos,
145    ) {
146        world.set_block(
147            pos,
148            state.set_value(AGE, MAX_AGE),
149            UpdateFlags::UPDATE_CLIENTS,
150        );
151    }
152}