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::{
127        blocks::properties::{BlockStateProperties, IntProperty},
128        init_vanilla_registry,
129    };
130
131    const LEVEL: &IntProperty = &BlockStateProperties::LEVEL;
132    const WATERLOGGED: &BoolProperty = &BlockStateProperties::WATERLOGGED;
133
134    #[test]
135    fn growth_state_waterlogs_when_replacing_water_block() {
136        init_vanilla_registry();
137        init_behaviors();
138
139        let state = BuddingAmethystBlock::growth_state(
140            &vanilla_blocks::SMALL_AMETHYST_BUD,
141            vanilla_blocks::WATER.default_state(),
142            Direction::Up,
143        );
144
145        assert!(state.get_value(WATERLOGGED));
146    }
147
148    #[test]
149    fn cluster_can_grow_in_falling_full_water() {
150        init_vanilla_registry();
151        init_behaviors();
152
153        let falling_full_water = vanilla_blocks::WATER
154            .default_state()
155            .set_value(LEVEL, LEVEL.max);
156
157        assert!(BuddingAmethystBlock::can_cluster_grow_at_state(
158            falling_full_water,
159            vanilla_blocks::WATER.id()
160        ));
161    }
162
163    #[test]
164    fn cluster_cannot_grow_in_partial_flowing_water() {
165        init_vanilla_registry();
166        init_behaviors();
167
168        let partial_flowing_water = vanilla_blocks::WATER.default_state().set_value(LEVEL, 1);
169
170        assert!(!BuddingAmethystBlock::can_cluster_grow_at_state(
171            partial_flowing_water,
172            vanilla_blocks::WATER.id()
173        ));
174    }
175}