steel_core/behavior/blocks/vegetation/
hanging_moss_block.rs1use std::sync::Arc;
2
3use crate::behavior::block::BlockBehavior;
4use crate::behavior::blocks::vegetation::bonemealable::{BonemealAction, Bonemealable};
5use crate::behavior::context::BlockPlaceContext;
6use crate::world::LevelReader;
7use crate::world::{ScheduledTickAccess, World};
8use rand::Rng;
9use steel_macros::block_behavior;
10use steel_registry::blocks::block_state_ext::BlockStateExt;
11use steel_registry::blocks::properties::{BlockStateProperties, BoolProperty};
12use steel_utils::types::UpdateFlags;
13use steel_utils::{BlockPos, BlockStateId, Direction};
14
15use super::{BlockRef, can_attach_to_multiface, default_surviving_state};
16
17const TIP: BoolProperty = BlockStateProperties::TIP;
18
19#[block_behavior]
21pub struct HangingMossBlock {
22 block: BlockRef,
23}
24
25impl HangingMossBlock {
26 #[must_use]
28 pub const fn new(block: BlockRef) -> Self {
29 Self { block }
30 }
31 fn get_tip(&self, world: &dyn LevelReader, pos: BlockPos) -> BlockPos {
34 let mut forward_pos = pos;
35 let mut forward_state;
36
37 loop {
38 forward_pos = forward_pos.below();
39 forward_state = world.get_block_state(forward_pos);
40
41 if forward_state.get_block() != self.block {
42 break;
43 }
44 }
45 forward_pos.above()
46 }
47 fn can_grow_into(state: BlockStateId) -> bool {
48 state.is_air()
49 }
50}
51
52impl BlockBehavior for HangingMossBlock {
53 fn can_survive(&self, _state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
54 let above_pos = pos.above();
58 if can_attach_to_multiface(world, above_pos, Direction::Up) {
59 return true;
60 }
61 world.get_block_state(above_pos).get_block() == self.block
62 }
63 fn update_shape(
64 &self,
65 state: BlockStateId,
66 world: &dyn ScheduledTickAccess,
67 pos: BlockPos,
68 _direction: steel_utils::Direction,
69 _neighbor_pos: BlockPos,
70 _neighbor_state: BlockStateId,
71 ) -> BlockStateId {
72 if !self.can_survive(state, world, pos) {
73 world.schedule_block_tick_default(pos, self.block, 1);
74 }
75 let is_tip = world.get_block_state(pos.below()).get_block() != self.block;
76 state.set_value(&TIP, is_tip)
77 }
78 fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
79 if !self.can_survive(state, world, pos) {
80 world.destroy_block(pos, true);
81 }
82 }
83
84 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
85 default_surviving_state(self.block, self, context)
86 }
87 fn as_bonemealable(&self) -> Option<&dyn Bonemealable> {
88 Some(self)
89 }
90}
91impl Bonemealable for HangingMossBlock {
92 fn is_valid_bonemeal_target(
93 &self,
94 _state: BlockStateId,
95 world: &dyn LevelReader,
96 pos: BlockPos,
97 ) -> bool {
98 let grow_pos = self.get_tip(world, pos).below();
99 HangingMossBlock::can_grow_into(world.get_block_state(grow_pos))
100 && !world.is_outside_build_height(grow_pos.y())
101 }
102
103 fn perform_bonemeal(
104 &self,
105 state: BlockStateId,
106 world: &Arc<World>,
107 _rng: &mut dyn Rng,
108 pos: BlockPos,
109 ) {
110 let tip_pos = self.get_tip(world, pos).below();
111 if HangingMossBlock::can_grow_into(world.get_block_state(tip_pos)) {
112 world.set_block(
113 tip_pos,
114 state.set_value(&TIP, true),
115 UpdateFlags::UPDATE_ALL,
116 );
117 }
118 }
119
120 fn bonemeal_action_type(&self) -> BonemealAction {
121 BonemealAction::Grower
122 }
123}