Skip to main content

steel_core/behavior/blocks/vegetation/
mod.rs

1//! Block behavior implementations for crops and feature-placed vegetation.
2
3use 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
184/// Turns farmland or a dirt path into dirt.
185pub(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
225/// Vanilla `MultifaceBlock.canAttachTo(level, directionTowardsNeighbor, neighborPos, neighborState)`.
226///
227/// Returns whether the block at `neighbor_pos` has a full face on the side
228/// facing back toward us. Checks the support shape first, then the collision
229/// shape, matching vanilla's `Block.isFaceFull` OR pattern.
230pub(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
310/// Vanilla `MultifaceBlock.getFaceProperty(faceDirection)`.
311pub(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}
321/// Vanilla `getBlocksToGrowWhenBonemealed()`
322pub 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}
333/// Vanilla equal `getTopConnectedBlock()`
334pub 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}
359/// Vanilla `MultifaceBlock.canSurvive`.
360///
361/// Every direction whose face property is `true` must have a neighbor that
362/// allows attachment, and at least one face must be set. Subclasses without a
363/// face property for a given direction (vanilla's `isFaceSupported`) treat that
364/// face as not set, hence `try_get_value(...).unwrap_or(false)`.
365pub(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
383/// Vanilla `BaseCoralPlantTypeBlock.canSurvive` (also `BaseCoralFanBlock`,
384/// `CoralPlantBlock`, `CoralFanBlock`).
385///
386/// The block below must be face-sturdy on its UP face.
387pub(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
393/// Vanilla `BaseCoralWallFanBlock.canSurvive`.
394///
395/// The block behind the wall fan (`pos.relative(facing.opposite())`) must be
396/// face-sturdy on the face pointing toward us (i.e. `facing`).
397pub(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
407/// Vanilla `BaseCoralPlantTypeBlock.scanForWater`.
408pub(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
425/// Vanilla `BaseCoralPlantTypeBlock.tryScheduleDieTick`.
426pub(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    // Intentional Steel divergence: incidental runtime timing does not use world RNG.
437    let delay = 60 + rand::random_range(0..40);
438    let _ = world.schedule_block_tick_default(pos, block, delay);
439}
440
441/// Vanilla `GrowingPlantBlock.canSurvive`.
442///
443/// The block opposite the growth direction must be the head, the body, or
444/// face-sturdy on the face pointing toward us (i.e. `growth_direction`).
445pub(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}