steel_core/behavior/blocks/vegetation/
mod.rs1use std::sync::Arc;
4
5mod azalea_block;
6mod bamboo;
7mod bamboo_sapling;
8mod base_coral_fan_block;
9mod base_coral_plant_block;
10mod base_coral_wall_fan_block;
11mod beetroots;
12mod big_dripleaf_block;
13mod big_dripleaf_stem_block;
14pub mod bonemealable;
15mod bush_block;
16mod cactus_block;
17mod cactus_flower_block;
18mod carpet_block;
19mod carrot;
20mod cave_vines_block;
21mod cave_vines_plant_block;
22mod chorus_flower_block;
23mod chorus_plant_block;
24mod cocoa_block;
25mod coral_block;
26mod coral_fan_block;
27mod coral_plant_block;
28mod coral_wall_fan_block;
29mod crop_block;
30mod dirt_path_block;
31mod double_plant_block;
32mod dry_vegetation_block;
33mod eyeblossom_block;
34mod farmland_block;
35mod firefly_bush_block;
36mod flower_bed_block;
37mod flower_block;
38mod glow_lichen_block;
39mod growing_plant_body_block;
40mod growing_plant_head_block;
41mod hanging_moss_block;
42mod hanging_roots_block;
43mod kelp_block;
44mod kelp_plant_block;
45mod leaf_litter_block;
46mod leaves_block;
47mod lily_pad_block;
48mod mangrove_propagule_block;
49mod mossy_carpet_block;
50mod mushroom_block;
51mod nether_fungus_block;
52mod nether_roots_block;
53mod nether_sprouts;
54mod nether_wart;
55mod pitcher_crop;
56mod pointed_dripstone_block;
57mod potato;
58mod pumpkin_block;
59mod rooted_dirt_block;
60mod sapling_block;
61mod sculk_vein_block;
62mod sea_pickle_block;
63mod seagrass_block;
64mod segmentable_block;
65mod short_dry_grass_block;
66mod small_dripleaf_block;
67mod spore_blossom_block;
68mod sugar_cane;
69mod sweet_berry_bush;
70mod tall_dry_grass_block;
71mod tall_flower_block;
72mod tall_grass_block;
73mod tall_seagrass_block;
74mod torchflower;
75mod twisting_vines_block;
76mod twisting_vines_plant_block;
77mod vegetation_block;
78mod vine_block;
79mod weeping_vines_block;
80mod weeping_vines_plant_block;
81mod wither_rose_block;
82
83pub use azalea_block::AzaleaBlock;
84pub use bamboo::BambooStalkBlock;
85pub use bamboo_sapling::BambooSaplingBlock;
86pub use base_coral_fan_block::BaseCoralFanBlock;
87pub use base_coral_plant_block::BaseCoralPlantBlock;
88pub use base_coral_wall_fan_block::BaseCoralWallFanBlock;
89pub use beetroots::BeetrootBlock;
90pub use big_dripleaf_block::BigDripleafBlock;
91pub use big_dripleaf_stem_block::BigDripleafStemBlock;
92pub use bush_block::BushBlock;
93pub use cactus_block::CactusBlock;
94pub use cactus_flower_block::CactusFlowerBlock;
95pub use carpet_block::{CarpetBlock, WoolCarpetBlock};
96pub use carrot::CarrotBlock;
97pub use cave_vines_block::CaveVinesBlock;
98pub use cave_vines_plant_block::CaveVinesPlantBlock;
99pub use chorus_flower_block::ChorusFlowerBlock;
100pub use chorus_plant_block::ChorusPlantBlock;
101pub use cocoa_block::CocoaBlock;
102pub use coral_block::CoralBlock;
103pub use coral_fan_block::CoralFanBlock;
104pub use coral_plant_block::CoralPlantBlock;
105pub use coral_wall_fan_block::CoralWallFanBlock;
106pub use crop_block::CropBlock;
107pub use dirt_path_block::DirtPathBlock;
108pub use double_plant_block::DoublePlantBlock;
109pub use dry_vegetation_block::DryVegetationBlock;
110pub use eyeblossom_block::{EyeblossomBlock, EyeblossomType};
111pub use farmland_block::FarmlandBlock;
112pub use firefly_bush_block::FireflyBushBlock;
113pub use flower_bed_block::FlowerBedBlock;
114pub use flower_block::FlowerBlock;
115pub use glow_lichen_block::GlowLichenBlock;
116pub use hanging_moss_block::HangingMossBlock;
117pub use hanging_roots_block::HangingRootsBlock;
118pub use kelp_block::KelpBlock;
119pub use kelp_plant_block::KelpPlantBlock;
120pub use leaf_litter_block::LeafLitterBlock;
121pub use leaves_block::{
122 MangroveLeavesBlock, TintedParticleLeavesBlock, UntintedParticleLeavesBlock,
123};
124pub use lily_pad_block::LilyPadBlock;
125pub use mangrove_propagule_block::MangrovePropaguleBlock;
126pub use mossy_carpet_block::MossyCarpetBlock;
127pub use mushroom_block::MushroomBlock;
128pub use nether_fungus_block::NetherFungusBlock;
129pub use nether_roots_block::NetherRootsBlock;
130pub use nether_sprouts::NetherSproutsBlock;
131pub use nether_wart::NetherWartBlock;
132pub use pitcher_crop::PitcherCropBlock;
133pub use pointed_dripstone_block::{
134 PointedDripstoneBlock, SulfurSpikeBlock, find_stalactite_tip_above_cauldron,
135 get_cauldron_fill_fluid_type,
136};
137pub use potato::PotatoBlock;
138pub use pumpkin_block::PumpkinBlock;
139pub use rooted_dirt_block::RootedDirtBlock;
140pub use sapling_block::SaplingBlock;
141pub use sculk_vein_block::SculkVeinBlock;
142pub use sea_pickle_block::SeaPickleBlock;
143pub use seagrass_block::SeagrassBlock;
144pub use short_dry_grass_block::ShortDryGrassBlock;
145pub use small_dripleaf_block::SmallDripleafBlock;
146pub use spore_blossom_block::SporeBlossomBlock;
147pub use sugar_cane::SugarCaneBlock;
148pub use sweet_berry_bush::SweetBerryBushBlock;
149pub use tall_dry_grass_block::TallDryGrassBlock;
150pub use tall_flower_block::TallFlowerBlock;
151pub use tall_grass_block::TallGrassBlock;
152pub use tall_seagrass_block::TallSeagrassBlock;
153pub use torchflower::TorchflowerCropBlock;
154pub use twisting_vines_block::TwistingVinesBlock;
155pub use twisting_vines_plant_block::TwistingVinesPlantBlock;
156pub use vegetation_block::Vegetation;
157pub use vine_block::VineBlock;
158pub use weeping_vines_block::WeepingVinesBlock;
159pub use weeping_vines_plant_block::WeepingVinesPlantBlock;
160pub use wither_rose_block::WitherRoseBlock;
161
162use rand::{Rng, RngExt};
163use steel_registry::blocks::properties::{BlockStateProperties, BoolProperty, Direction};
164use steel_registry::blocks::shapes::{self, SupportType, is_block_local_face_sturdy};
165use steel_registry::blocks::{BlockRef, block_state_ext::BlockStateExt};
166use steel_registry::fluid::{FluidState, FluidStateExt as _};
167use steel_registry::vanilla_block_tags::BlockTag;
168use steel_registry::vanilla_fluids;
169use steel_registry::{vanilla_blocks, vanilla_game_events};
170use steel_utils::{BlockPos, BlockStateId, types::UpdateFlags};
171
172use crate::behavior::block::push_entities_up;
173use crate::behavior::context::BlockPlaceContext;
174use crate::behavior::{
175 BLOCK_BEHAVIORS, BlockCollisionContext,
176 block::{BlockBehavior, schedule_water_tick_if_waterlogged},
177};
178use crate::entity::Entity;
179use crate::world::game_event::GameEventContext;
180use crate::world::{LevelReader, ScheduledTickAccess, World};
181
182pub(super) type BlockTagRef<'a> = &'a steel_utils::Identifier;
183
184pub(crate) fn turn_to_dirt(
186 state: BlockStateId,
187 world: &Arc<World>,
188 pos: BlockPos,
189 source_entity: Option<&dyn Entity>,
190) {
191 let dirt_state = push_entities_up(state, vanilla_blocks::DIRT.default_state(), world, pos);
192 if world.set_block(pos, dirt_state, UpdateFlags::UPDATE_ALL) {
193 world.game_event(
194 &vanilla_game_events::BLOCK_CHANGE,
195 pos,
196 &GameEventContext::new(source_entity, Some(dirt_state)),
197 );
198 }
199}
200
201pub(super) fn survives_on_tag(
202 world: &dyn LevelReader,
203 pos: BlockPos,
204 tag: BlockTagRef<'_>,
205) -> bool {
206 let below = world.get_block_state(pos.below());
207 below.get_block().has_tag(tag)
208}
209
210pub(super) fn default_surviving_state(
211 block: BlockRef,
212 behavior: &dyn BlockBehavior,
213 context: &BlockPlaceContext<'_>,
214) -> Option<BlockStateId> {
215 let state = block.default_state();
216 behavior
217 .can_survive(state, context.world, context.place_pos())
218 .then_some(state)
219}
220
221pub(super) fn water_source_fluid_state() -> FluidState {
222 FluidState::source(&vanilla_fluids::WATER)
223}
224
225pub(super) fn can_attach_to_multiface(
231 world: &dyn LevelReader,
232 neighbor_pos: BlockPos,
233 direction_to_neighbor: Direction,
234) -> bool {
235 let neighbor_state = world.get_block_state(neighbor_pos);
236 can_attach_to_multiface_state(world, neighbor_state, neighbor_pos, direction_to_neighbor)
237}
238
239fn can_attach_to_multiface_state(
240 world: &dyn LevelReader,
241 neighbor_state: BlockStateId,
242 neighbor_pos: BlockPos,
243 direction_to_neighbor: Direction,
244) -> bool {
245 let support_direction = direction_to_neighbor.opposite();
246 if neighbor_state.get_block().config.dynamic_shape {
247 let behavior = BLOCK_BEHAVIORS.get_behavior(neighbor_state.get_block());
248 return is_block_local_face_sturdy(
249 &behavior.get_block_support_boxes(neighbor_state, world, neighbor_pos),
250 support_direction,
251 SupportType::Full,
252 ) || is_block_local_face_sturdy(
253 &behavior.get_collision_boxes(
254 neighbor_state,
255 world,
256 neighbor_pos,
257 BlockCollisionContext::empty(),
258 ),
259 support_direction,
260 SupportType::Full,
261 );
262 }
263
264 shapes::is_offset_face_full(
265 neighbor_state.get_support_shape_at(neighbor_pos),
266 support_direction,
267 ) || shapes::is_offset_face_full(
268 neighbor_state.get_collision_shape_at(neighbor_pos),
269 support_direction,
270 )
271}
272
273fn multiface_has_any_face(state: BlockStateId) -> bool {
274 Direction::ALL.iter().any(|direction| {
275 state
276 .try_get_value(multiface_face_property(*direction))
277 .unwrap_or(false)
278 })
279}
280
281pub(super) fn update_multiface_shape(
282 state: BlockStateId,
283 world: &dyn ScheduledTickAccess,
284 pos: BlockPos,
285 direction: Direction,
286 neighbor_pos: BlockPos,
287 neighbor_state: BlockStateId,
288) -> BlockStateId {
289 schedule_water_tick_if_waterlogged(state, world, pos);
290
291 if !multiface_has_any_face(state) {
292 return vanilla_blocks::AIR.default_state();
293 }
294
295 let face_property = multiface_face_property(direction);
296 if state.try_get_value(face_property) != Some(true)
297 || can_attach_to_multiface_state(world, neighbor_state, neighbor_pos, direction)
298 {
299 return state;
300 }
301
302 let state_without_face = state.set_value(face_property, false);
303 if multiface_has_any_face(state_without_face) {
304 state_without_face
305 } else {
306 vanilla_blocks::AIR.default_state()
307 }
308}
309
310pub(super) const fn multiface_face_property(direction: Direction) -> &'static BoolProperty {
312 match direction {
313 Direction::Up => &BlockStateProperties::UP,
314 Direction::Down => &BlockStateProperties::DOWN,
315 Direction::North => &BlockStateProperties::NORTH,
316 Direction::South => &BlockStateProperties::SOUTH,
317 Direction::East => &BlockStateProperties::EAST,
318 Direction::West => &BlockStateProperties::WEST,
319 }
320}
321pub fn nether_vines_get_blocks_to_grow_when_bonemealed(rng: &mut dyn Rng) -> i32 {
323 let mut grow_probability = 1.0;
324
325 let mut count = 0;
326
327 while rng.random::<f64>() < grow_probability {
328 grow_probability *= 0.826;
329 count += 1;
330 }
331 count
332}
333pub fn get_top_connected_block(
335 world: &dyn LevelReader,
336 pos: BlockPos,
337 body_block: BlockRef,
338 growth_direction: Direction,
339 head_block: BlockRef,
340) -> Option<BlockPos> {
341 let mut forward_pos = pos;
342 let mut forward_state;
343
344 loop {
345 forward_pos = forward_pos.relative(growth_direction);
346 forward_state = world.get_block_state(forward_pos);
347
348 if forward_state.get_block() != body_block {
349 break;
350 }
351 }
352
353 if forward_state.get_block() == head_block {
354 Some(forward_pos)
355 } else {
356 None
357 }
358}
359pub(super) fn multiface_can_survive(
366 state: BlockStateId,
367 world: &dyn LevelReader,
368 pos: BlockPos,
369) -> bool {
370 let mut has_face = false;
371 for direction in Direction::ALL {
372 let property = multiface_face_property(direction);
373 if state.try_get_value(property).unwrap_or(false) {
374 if !can_attach_to_multiface(world, pos.relative(direction), direction) {
375 return false;
376 }
377 has_face = true;
378 }
379 }
380 has_face
381}
382
383pub(super) fn coral_plant_can_survive(world: &dyn LevelReader, pos: BlockPos) -> bool {
388 let below_pos = pos.below();
389 let below = world.get_block_state(below_pos);
390 world.is_face_sturdy(below, below_pos, Direction::Up)
391}
392
393pub(super) fn coral_wall_fan_can_survive(
398 world: &dyn LevelReader,
399 pos: BlockPos,
400 facing: Direction,
401) -> bool {
402 let relative_pos = pos.relative(facing.opposite());
403 let relative_state = world.get_block_state(relative_pos);
404 world.is_face_sturdy(relative_state, relative_pos, facing)
405}
406
407pub(super) fn coral_scan_for_water(
409 state: BlockStateId,
410 world: &dyn LevelReader,
411 pos: BlockPos,
412) -> bool {
413 if state.try_get_value(&BlockStateProperties::WATERLOGGED) == Some(true) {
414 return true;
415 }
416
417 Direction::ALL.iter().any(|direction| {
418 world
419 .get_block_state(pos.relative(*direction))
420 .get_fluid_state()
421 .is_water()
422 })
423}
424
425pub(super) fn schedule_coral_die_tick(
427 state: BlockStateId,
428 world: &dyn ScheduledTickAccess,
429 pos: BlockPos,
430 block: BlockRef,
431) {
432 if coral_scan_for_water(state, world, pos) {
433 return;
434 }
435
436 let delay = 60 + rand::random_range(0..40);
438 let _ = world.schedule_block_tick_default(pos, block, delay);
439}
440
441pub(super) fn growing_plant_can_survive(
446 world: &dyn LevelReader,
447 pos: BlockPos,
448 growth_direction: Direction,
449 head: BlockRef,
450 body: BlockRef,
451) -> bool {
452 let attached_pos = pos.relative(growth_direction.opposite());
453 let attached_state = world.get_block_state(attached_pos);
454 let attached_block = attached_state.get_block();
455 attached_block == head
456 || attached_block == body
457 || world.is_face_sturdy(attached_state, attached_pos, growth_direction)
458}
459
460pub(super) fn kelp_can_survive(world: &dyn LevelReader, pos: BlockPos) -> bool {
461 let attached_pos = pos.below();
462 let attached_state = world.get_block_state(attached_pos);
463 if attached_state
464 .get_block()
465 .has_tag(&BlockTag::CANNOT_SUPPORT_KELP)
466 {
467 return false;
468 }
469 growing_plant_can_survive(
470 world,
471 pos,
472 Direction::Up,
473 &vanilla_blocks::KELP,
474 &vanilla_blocks::KELP_PLANT,
475 )
476}
477
478#[cfg(test)]
479mod tests {
480 use steel_registry::init_vanilla_registry;
481
482 use super::*;
483 use crate::behavior::init_behaviors;
484 use crate::test_support::TestLevel;
485
486 #[test]
487 fn multiface_update_uses_supplied_neighbor_state_and_schedules_water_first() {
488 init_vanilla_registry();
489 init_behaviors();
490 let pos = BlockPos::new(0, 64, 0);
491 let state = vanilla_blocks::GLOW_LICHEN
492 .default_state()
493 .set_value(&BlockStateProperties::NORTH, true)
494 .set_value(&BlockStateProperties::WATERLOGGED, true);
495 let level =
496 TestLevel::default().with_block(pos.north(), vanilla_blocks::STONE.default_state());
497
498 let updated = update_multiface_shape(
499 state,
500 &level,
501 pos,
502 Direction::North,
503 pos.north(),
504 vanilla_blocks::AIR.default_state(),
505 );
506
507 assert!(updated.is_air());
508 assert!(level.scheduled_water_tick());
509 }
510}