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