Skip to main content

steel_core/behavior/blocks/building/
lava_cauldron_block.rs

1use std::sync::Arc;
2
3use steel_macros::block_behavior;
4use steel_registry::blocks::{BlockRef, properties::Direction, shapes::VoxelShape};
5use steel_utils::{BlockLocalAabb, BlockPos, BlockStateId};
6
7use crate::{
8    behavior::{BlockBehavior, BlockPlaceContext},
9    entity::ai::path::PathComputationType,
10    entity::{Entity, InsideBlockEffectCollector, InsideBlockEffectType},
11    world::{LevelReader, World},
12};
13
14const CAULDRON_FILLED_ENTITY_INSIDE_BOXES: &[BlockLocalAabb] = &[
15    BlockLocalAabb::new(0.0, 0.0, 0.0, 0.1875, 0.5625, 0.1875),
16    BlockLocalAabb::new(0.8125, 0.0, 0.0, 1.0, 0.5625, 0.1875),
17    BlockLocalAabb::new(0.0, 0.1875, 0.1875, 1.0, 0.5625, 1.0),
18    BlockLocalAabb::new(0.1875, 0.1875, 0.0, 0.8125, 0.5625, 0.1875),
19    BlockLocalAabb::new(0.0, 0.0, 0.8125, 0.1875, 0.5625, 1.0),
20    BlockLocalAabb::new(0.8125, 0.0, 0.8125, 1.0, 0.5625, 1.0),
21    BlockLocalAabb::new(0.0, 0.1875, 0.0, 1.0, 0.5625, 0.8125),
22    BlockLocalAabb::new(0.1875, 0.1875, 0.8125, 0.8125, 0.5625, 1.0),
23    BlockLocalAabb::new(0.125, 0.25, 0.125, 0.875, 0.9375, 0.875),
24];
25const CAULDRON_FILLED_ENTITY_INSIDE_SHAPE: VoxelShape =
26    VoxelShape::from_boxes(CAULDRON_FILLED_ENTITY_INSIDE_BOXES);
27
28/// Behavior for lava cauldrons.
29///
30/// Cauldron interactions and drip filling remain separate block mechanics.
31#[block_behavior]
32pub struct LavaCauldronBlock {
33    block: BlockRef,
34}
35
36impl LavaCauldronBlock {
37    /// Creates a lava cauldron block behavior.
38    #[must_use]
39    pub const fn new(block: BlockRef) -> Self {
40        Self { block }
41    }
42}
43
44impl BlockBehavior for LavaCauldronBlock {
45    fn get_state_for_placement(&self, _context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
46        Some(self.block.default_state())
47    }
48
49    fn get_entity_inside_collision_shape(
50        &self,
51        _state: BlockStateId,
52        _world: &dyn LevelReader,
53        _pos: BlockPos,
54        _entity: &dyn Entity,
55    ) -> VoxelShape {
56        CAULDRON_FILLED_ENTITY_INSIDE_SHAPE
57    }
58
59    fn has_analog_output_signal(&self, _state: BlockStateId) -> bool {
60        true
61    }
62
63    fn get_analog_output_signal(
64        &self,
65        _state: BlockStateId,
66        _world: &dyn LevelReader,
67        _pos: BlockPos,
68        _direction: Direction,
69    ) -> i32 {
70        3
71    }
72
73    fn is_pathfindable(
74        &self,
75        _state: BlockStateId,
76        _computation_type: PathComputationType,
77    ) -> bool {
78        false
79    }
80
81    fn entity_inside(
82        &self,
83        _state: BlockStateId,
84        _world: &Arc<World>,
85        _pos: BlockPos,
86        _entity: &dyn Entity,
87        effect_collector: &mut InsideBlockEffectCollector,
88        _is_precise: bool,
89    ) {
90        effect_collector.apply(InsideBlockEffectType::ClearFreeze);
91        effect_collector.apply(InsideBlockEffectType::LavaIgnite);
92        effect_collector.run_after(
93            InsideBlockEffectType::LavaIgnite,
94            Box::new(Entity::lava_hurt),
95        );
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use steel_registry::{init_vanilla_registry, vanilla_blocks};
102
103    use super::*;
104    use crate::{
105        behavior::{BLOCK_BEHAVIORS, init_behaviors},
106        test_support::TestLevel,
107    };
108
109    fn assert_f64_close(actual: f64, expected: f64) {
110        assert!(
111            (actual - expected).abs() <= f64::EPSILON,
112            "expected {actual} to equal {expected}"
113        );
114    }
115
116    #[test]
117    fn filled_entity_inside_shape_includes_lava_column() {
118        let Some(bounds) = CAULDRON_FILLED_ENTITY_INSIDE_SHAPE.bounds() else {
119            panic!("lava cauldron entity-inside shape is non-empty");
120        };
121
122        assert_f64_close(bounds.min_x(), 0.0);
123        assert_f64_close(bounds.min_y(), 0.0);
124        assert_f64_close(bounds.min_z(), 0.0);
125        assert_f64_close(bounds.max_x(), 1.0);
126        assert_f64_close(bounds.max_y(), 0.9375);
127        assert_f64_close(bounds.max_z(), 1.0);
128        assert_eq!(CAULDRON_FILLED_ENTITY_INSIDE_SHAPE.len(), 9);
129    }
130
131    #[test]
132    fn registered_lava_cauldron_outputs_three() {
133        init_vanilla_registry();
134        init_behaviors();
135        let state = vanilla_blocks::LAVA_CAULDRON.default_state();
136        let behavior = BLOCK_BEHAVIORS.get_behavior(&vanilla_blocks::LAVA_CAULDRON);
137
138        assert!(behavior.has_analog_output_signal(state));
139        assert_eq!(
140            behavior.get_analog_output_signal(
141                state,
142                &TestLevel::default(),
143                BlockPos::ZERO,
144                Direction::North,
145            ),
146            3,
147        );
148    }
149}