Skip to main content

steel_core/behavior/blocks/vegetation/
chorus_flower_block.rs

1use std::sync::Arc;
2
3use steel_macros::block_behavior;
4use steel_registry::blocks::block_state_ext::BlockStateExt;
5use steel_registry::vanilla_block_tags::BlockTag;
6use steel_utils::{BlockPos, BlockStateId, Direction};
7
8use crate::behavior::block::BlockBehavior;
9use crate::behavior::context::BlockPlaceContext;
10use crate::entity::projectile::Projectile;
11use crate::world::{ClipHitResult, LevelReader, World};
12
13use super::{BlockRef, default_surviving_state};
14
15const HORIZONTAL_DIRECTIONS: [Direction; 4] = [
16    Direction::North,
17    Direction::East,
18    Direction::South,
19    Direction::West,
20];
21
22/// Vanilla `ChorusFlowerBlock` survival behavior.
23// TODO: Implement ticking and growth outside worldgen.
24#[block_behavior]
25pub struct ChorusFlowerBlock {
26    block: BlockRef,
27    #[json_arg(vanilla_blocks)]
28    plant: BlockRef,
29}
30
31impl ChorusFlowerBlock {
32    /// Creates a new chorus flower block behavior.
33    #[must_use]
34    pub const fn new(block: BlockRef, plant: BlockRef) -> Self {
35        Self { block, plant }
36    }
37
38    fn projectile_can_break(projectile: &dyn Projectile, world: &World, pos: BlockPos) -> bool {
39        projectile.projectile_may_interact(world, pos) && projectile.may_break(world)
40    }
41}
42
43impl BlockBehavior for ChorusFlowerBlock {
44    fn can_survive(&self, _state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> bool {
45        let below_state = world.get_block_state(pos.below());
46        if below_state.get_block() == self.plant
47            || below_state
48                .get_block()
49                .has_tag(&BlockTag::SUPPORTS_CHORUS_FLOWER)
50        {
51            return true;
52        }
53
54        if !below_state.is_air() {
55            return false;
56        }
57
58        let mut has_single_plant_neighbor = false;
59        for direction in HORIZONTAL_DIRECTIONS {
60            let neighbor_state = world.get_block_state(pos.relative(direction));
61            if neighbor_state.get_block() == self.plant {
62                if has_single_plant_neighbor {
63                    return false;
64                }
65                has_single_plant_neighbor = true;
66            } else if !neighbor_state.is_air() {
67                return false;
68            }
69        }
70
71        has_single_plant_neighbor
72    }
73
74    fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
75        default_surviving_state(self.block, self, context)
76    }
77
78    fn on_projectile_hit(
79        &self,
80        _state: BlockStateId,
81        world: &Arc<World>,
82        hit: &ClipHitResult,
83        projectile: &dyn Projectile,
84    ) {
85        if Self::projectile_can_break(projectile, world, hit.block_pos) {
86            world.destroy_block_by_entity(hit.block_pos, true, projectile.as_entity_event_source());
87        }
88    }
89}