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