steel_core/behavior/blocks/vegetation/
hanging_moss_block.rs1use std::sync::Arc;
2
3use crate::behavior::block::BlockBehavior;
4use crate::behavior::blocks::MultifaceBlock;
5use crate::behavior::blocks::vegetation::bonemealable::{BonemealAction, Bonemealable};
6use crate::behavior::context::BlockPlaceContext;
7use crate::world::LevelReader;
8use crate::world::{ScheduledTickAccess, World};
9use rand::Rng;
10use steel_macros::block_behavior;
11use steel_registry::blocks::block_state_ext::BlockStateExt;
12use steel_registry::blocks::properties::{BlockStateProperties, BoolProperty};
13use steel_utils::types::UpdateFlags;
14use steel_utils::{BlockPos, BlockStateId, Direction};
15
16use super::{BlockRef, default_surviving_state};
17
18const TIP: &BoolProperty = &BlockStateProperties::TIP;
19
20#[block_behavior]
22pub struct HangingMossBlock {
23 block: BlockRef,
24}
25
26impl HangingMossBlock {
27 #[must_use]
29 pub const fn new(block: BlockRef) -> Self {
30 Self { block }
31 }
32 fn get_tip(&self, world: &dyn LevelReader, pos: BlockPos) -> BlockPos {
35 let mut forward_pos = pos;
36 let mut forward_state;
37
38 loop {
39 forward_pos = forward_pos.below();
40 forward_state = world.get_block_state(forward_pos);
41
42 if forward_state.get_block() != self.block {
43 break;
44 }
45 }
46 forward_pos.above()
47 }
48 fn can_grow_into(state: BlockStateId) -> bool {
49 state.is_air()
50 }
51}
52
53impl BlockBehavior for HangingMossBlock {
54 fn can_survive(&self, _state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
55 let above_pos = pos.above();
59 let above_state = world.get_block_state(above_pos);
60 MultifaceBlock::can_attach_to_state(world, Direction::Up, above_pos, above_state)
61 || above_state.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(tip_pos, state.set_value(TIP, true), UpdateFlags::UPDATE_ALL);
113 }
114 }
115
116 fn bonemeal_action_type(&self) -> BonemealAction {
117 BonemealAction::Grower
118 }
119}