Skip to main content

steel_core/behavior/blocks/building/
budding_amethyst.rs

1use crate::{
2    behavior::{BlockBehavior, BlockPlaceContext, blocks::AmethystBlock},
3    entity::projectile::Projectile,
4    fluid::FluidStateExt as _,
5    world::{ClipHitResult, World},
6};
7use std::sync::Arc;
8use steel_macros::block_behavior;
9use steel_registry::{
10    REGISTRY, RegistryEntry,
11    blocks::{
12        BlockRef,
13        block_state_ext::BlockStateExt,
14        properties::{BlockStateProperties, BoolProperty, EnumProperty},
15    },
16    vanilla_blocks,
17};
18use steel_utils::{BlockPos, BlockStateId, Direction, types::UpdateFlags};
19
20/// Behavior for vanilla budding amethyst blocks.
21#[block_behavior]
22pub struct BuddingAmethystBlock {
23    block: BlockRef,
24}
25
26const FACING: &EnumProperty<Direction> = &BlockStateProperties::FACING;
27const WATERLOGGED: &BoolProperty = &BlockStateProperties::WATERLOGGED;
28
29impl BuddingAmethystBlock {
30    /// Creates a new budding amethyst block behavior.
31    #[must_use]
32    pub const fn new(block: BlockRef) -> Self {
33        Self { block }
34    }
35
36    fn can_cluster_grow_at_state(state: BlockStateId, block_id: usize) -> bool {
37        state.is_air()
38            || (block_id == vanilla_blocks::WATER.id() && state.get_fluid_state().is_full())
39    }
40
41    fn check_cluster(
42        state: BlockStateId,
43        block_id: usize,
44        direction: Direction,
45        block: BlockRef,
46    ) -> bool {
47        block_id == block.id() && state.get_value(FACING) == direction
48    }
49
50    fn growth_state(
51        block: BlockRef,
52        replaced_state: BlockStateId,
53        direction: Direction,
54    ) -> BlockStateId {
55        block
56            .default_state()
57            .set_value(FACING, direction)
58            .set_value(WATERLOGGED, replaced_state.get_fluid_state().is_water())
59    }
60}
61
62impl BlockBehavior for BuddingAmethystBlock {
63    fn get_state_for_placement(&self, _context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
64        Some(self.block.default_state())
65    }
66
67    fn random_tick(&self, _state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
68        if rand::random_range(0..5) == 0 {
69            let direction = Direction::random();
70            let grow_pos = pos.relative(direction);
71            let state = world.get_block_state(grow_pos);
72            let Some(&block_id) = REGISTRY.blocks.state_to_block_id.get(state.0 as usize) else {
73                panic!(
74                    "budding amethyst received invalid block state id {}",
75                    state.0
76                );
77            };
78
79            let mut growth_stage: Option<BlockRef> = None;
80            if Self::can_cluster_grow_at_state(state, block_id) {
81                growth_stage = Some(&vanilla_blocks::SMALL_AMETHYST_BUD);
82            } else if Self::check_cluster(
83                state,
84                block_id,
85                direction,
86                &vanilla_blocks::SMALL_AMETHYST_BUD,
87            ) {
88                growth_stage = Some(&vanilla_blocks::MEDIUM_AMETHYST_BUD);
89            } else if Self::check_cluster(
90                state,
91                block_id,
92                direction,
93                &vanilla_blocks::MEDIUM_AMETHYST_BUD,
94            ) {
95                growth_stage = Some(&vanilla_blocks::LARGE_AMETHYST_BUD);
96            } else if Self::check_cluster(
97                state,
98                block_id,
99                direction,
100                &vanilla_blocks::LARGE_AMETHYST_BUD,
101            ) {
102                growth_stage = Some(&vanilla_blocks::AMETHYST_CLUSTER);
103            }
104
105            if let Some(growth_stage) = growth_stage {
106                let block_state = Self::growth_state(growth_stage, state, direction);
107                world.set_block(grow_pos, block_state, UpdateFlags::UPDATE_ALL);
108            }
109        }
110    }
111    fn on_projectile_hit(
112        &self,
113        _state: BlockStateId,
114        world: &Arc<World>,
115        hit: &ClipHitResult,
116        _projectile: &dyn Projectile,
117    ) {
118        AmethystBlock::play_projectile_hit_sound(world, hit.block_pos);
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use crate::behavior::init_behaviors;
126    use steel_registry::{blocks::properties::BlockStateProperties, init_vanilla_registry};
127
128    #[test]
129    fn growth_state_waterlogs_when_replacing_water_block() {
130        init_vanilla_registry();
131        init_behaviors();
132
133        let state = BuddingAmethystBlock::growth_state(
134            &vanilla_blocks::SMALL_AMETHYST_BUD,
135            vanilla_blocks::WATER.default_state(),
136            Direction::Up,
137        );
138
139        assert!(state.get_value(WATERLOGGED));
140    }
141
142    #[test]
143    fn cluster_can_grow_in_falling_full_water() {
144        init_vanilla_registry();
145        init_behaviors();
146
147        let falling_full_water = vanilla_blocks::WATER
148            .default_state()
149            .set_value(&BlockStateProperties::LEVEL, 8);
150
151        assert!(BuddingAmethystBlock::can_cluster_grow_at_state(
152            falling_full_water,
153            vanilla_blocks::WATER.id()
154        ));
155    }
156
157    #[test]
158    fn cluster_cannot_grow_in_partial_flowing_water() {
159        init_vanilla_registry();
160        init_behaviors();
161
162        let partial_flowing_water = vanilla_blocks::WATER
163            .default_state()
164            .set_value(&BlockStateProperties::LEVEL, 1);
165
166        assert!(!BuddingAmethystBlock::can_cluster_grow_at_state(
167            partial_flowing_water,
168            vanilla_blocks::WATER.id()
169        ));
170    }
171}