steel_core/behavior/blocks/vegetation/
shelf_mushroom_block.rs1use 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#[block_behavior(class = "ShelfMushroomBlock")]
30pub struct ShelfMushroomBlock {
31 block: BlockRef,
32}
33
34impl ShelfMushroomBlock {
35 #[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}