steel_core/behavior/blocks/vegetation/
pitcher_crop.rs1use std::sync::Arc;
2
3use rand::RngExt;
4use steel_macros::block_behavior;
5use steel_registry::{
6 blocks::{
7 BlockRef,
8 block_state_ext::BlockStateExt,
9 properties::{BlockStateProperties, DoubleBlockHalf, EnumProperty, IntProperty},
10 },
11 vanilla_block_tags::BlockTag,
12 vanilla_blocks,
13};
14use steel_utils::{BlockPos, BlockStateId, types::UpdateFlags};
15
16use crate::{
17 behavior::{
18 BlockBehavior, BlockPlaceContext,
19 blocks::vegetation::{
20 Vegetation,
21 bonemealable::Bonemealable,
22 crop_block::destroy_crop_on_ravager_contact,
23 vegetation_block::{double_plant_can_survive, double_plant_update_shape},
24 },
25 },
26 entity::{Entity, InsideBlockEffectCollector},
27 world::{LevelReader, ScheduledTickAccess, World},
28};
29
30const HALF_PROPERTY: EnumProperty<DoubleBlockHalf> = BlockStateProperties::DOUBLE_BLOCK_HALF;
31const AGE_PROPERTY: IntProperty = BlockStateProperties::AGE_4;
32
33#[block_behavior]
35pub struct PitcherCropBlock {
36 block: BlockRef,
37}
38
39impl PitcherCropBlock {
40 #[must_use]
42 pub const fn new(block: BlockRef) -> Self {
43 Self { block }
44 }
45
46 fn is_lower(state: BlockStateId) -> bool {
47 state.get_block() == &vanilla_blocks::PITCHER_CROP
48 && state.get_value(&BlockStateProperties::DOUBLE_BLOCK_HALF) == DoubleBlockHalf::Lower
49 }
50
51 fn get_growth_speed(&self, world: &Arc<World>, pos: BlockPos) -> f32 {
52 let mut speed = 1.0f32;
53 let below = pos.below();
54
55 for dx in -1..=1 {
57 for dz in -1..=1 {
58 let check_pos = below.offset(dx, 0, dz);
59 let block_state = world.get_block_state(check_pos);
60 let mut block_speed = 0.0f32;
61
62 if block_state.get_block().has_tag(&BlockTag::GROWS_CROPS) {
63 block_speed = 1.0;
64 let moisture = block_state
66 .try_get_value(&BlockStateProperties::MOISTURE)
67 .unwrap_or(0);
68 if moisture > 0 {
69 block_speed = 3.0;
70 }
71 }
72
73 if dx != 0 || dz != 0 {
75 block_speed /= 4.0;
76 }
77
78 speed += block_speed;
79 }
80 }
81
82 let north = world.get_block_state(pos.north());
84 let south = world.get_block_state(pos.south());
85 let west = world.get_block_state(pos.west());
86 let east = world.get_block_state(pos.east());
87
88 let horizontal_row = self.block == west.get_block() || self.block == east.get_block();
89 let vertical_row = self.block == north.get_block() || self.block == south.get_block();
90
91 if horizontal_row && vertical_row {
92 speed /= 2.0;
94 } else {
95 let nw = world.get_block_state(pos.north().west());
97 let ne = world.get_block_state(pos.north().east());
98 let sw = world.get_block_state(pos.south().west());
99 let se = world.get_block_state(pos.south().east());
100
101 let has_diagonal = self.block == nw.get_block()
102 || self.block == ne.get_block()
103 || self.block == sw.get_block()
104 || self.block == se.get_block();
105
106 if has_diagonal {
107 speed /= 2.0;
108 }
109 }
110
111 speed
112 }
113
114 fn get_lower_half(
115 state: BlockStateId,
116 world: &dyn LevelReader,
117 pos: BlockPos,
118 ) -> Option<(BlockStateId, BlockPos)> {
119 if Self::is_lower(state) {
120 Some((state, pos))
121 } else {
122 let (state_below, pos_below) = (world.get_block_state(pos.below()), pos.below());
123 if Self::is_lower(state_below) {
124 Some((state_below, pos_below))
125 } else {
126 None
127 }
128 }
129 }
130
131 fn grow(world: &Arc<World>, lower_state: BlockStateId, lower_pos: BlockPos, increase: u8) {
132 let new_age = (lower_state.get_value(&AGE_PROPERTY) + increase).min(4);
133 if !Self::can_grow(world, lower_state, lower_pos, new_age) {
134 return;
135 }
136
137 let new_state = lower_state.set_value(&AGE_PROPERTY, new_age);
138 world.set_block(lower_pos, new_state, UpdateFlags::UPDATE_CLIENTS);
139
140 if new_age >= 3 {
141 world.set_block(
142 lower_pos.above(),
143 new_state.set_value(&HALF_PROPERTY, DoubleBlockHalf::Upper),
144 UpdateFlags::UPDATE_ALL,
145 );
146 }
147 }
148
149 fn can_grow(world: &dyn LevelReader, state: BlockStateId, pos: BlockPos, new_age: u8) -> bool {
150 let state_above = world.get_block_state(pos.above());
151 state.get_value(&AGE_PROPERTY) < 4
152 && world.raw_brightness(pos, 0) >= 8
153 && !world.is_outside_build_height(pos.above().y())
154 && (new_age < 3
155 || state_above.is_air()
156 || state_above.get_block() == &vanilla_blocks::PITCHER_CROP)
157 }
158}
159
160impl BlockBehavior for PitcherCropBlock {
161 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
162 if self.may_place_on(
163 context.world.get_block_state(context.place_pos().below()),
164 context.world,
165 context.place_pos().below(),
166 ) {
167 Some(self.block.default_state())
168 } else {
169 None
170 }
171 }
172
173 fn can_survive(&self, state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
174 if Self::is_lower(state) && world.raw_brightness(pos, 0) < 8 {
175 return false;
176 }
177
178 double_plant_can_survive(self, state, world, pos)
179 }
180
181 fn update_shape(
182 &self,
183 state: BlockStateId,
184 world: &dyn ScheduledTickAccess,
185 pos: BlockPos,
186 direction: steel_utils::Direction,
187 _neighbor_pos: BlockPos,
188 neighbor_state: BlockStateId,
189 ) -> BlockStateId {
190 if state.get_value(&AGE_PROPERTY) >= 3 {
191 double_plant_update_shape(self, state, world, pos, direction, neighbor_state)
192 } else if self.can_survive(state, world, pos) {
193 state
194 } else {
195 vanilla_blocks::AIR.default_state()
196 }
197 }
198
199 fn entity_inside(
200 &self,
201 _state: BlockStateId,
202 world: &Arc<World>,
203 pos: BlockPos,
204 entity: &dyn Entity,
205 _effect_collector: &mut InsideBlockEffectCollector,
206 _is_precise: bool,
207 ) {
208 destroy_crop_on_ravager_contact(world, pos, entity);
209 }
210
211 fn random_tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
212 let Some((lower_state, lower_pos)) = Self::get_lower_half(state, world, pos) else {
213 return;
214 };
215 let growth_speed = self.get_growth_speed(world, lower_pos);
216 let should_progress_growth =
217 rand::rng().random_range(0_i32..((25.0 / growth_speed) as i32 + 1)) == 0;
218 if should_progress_growth {
219 Self::grow(world, lower_state, lower_pos, 1);
220 }
221 }
222
223 fn as_bonemealable(&self) -> Option<&dyn Bonemealable> {
224 Some(self)
225 }
226}
227
228impl Vegetation for PitcherCropBlock {
229 fn may_place_on(&self, state: BlockStateId, _world: &dyn LevelReader, _pos: BlockPos) -> bool {
230 state.get_block().has_tag(&BlockTag::SUPPORTS_CROPS)
231 }
232}
233
234impl Bonemealable for PitcherCropBlock {
235 fn is_valid_bonemeal_target(
236 &self,
237 state: BlockStateId,
238 world: &dyn LevelReader,
239 pos: BlockPos,
240 ) -> bool {
241 let Some((lower_state, lower_pos)) = Self::get_lower_half(state, world, pos) else {
242 return false;
243 };
244 if lower_state.get_block() != &vanilla_blocks::PITCHER_CROP
245 || lower_state.get_value(&HALF_PROPERTY) != DoubleBlockHalf::Lower
246 {
247 return false;
248 }
249
250 let new_age = lower_state.get_value(&AGE_PROPERTY) + 1;
251
252 Self::can_grow(world, lower_state, lower_pos, new_age)
253 }
254
255 fn perform_bonemeal(
256 &self,
257 state: BlockStateId,
258 world: &Arc<World>,
259 _rng: &mut dyn rand::Rng,
260 pos: BlockPos,
261 ) {
262 if let Some((lower_state, lower_pos)) = Self::get_lower_half(state, world, pos) {
263 Self::grow(world, lower_state, lower_pos, 1);
264 }
265 }
266}