steel_core/behavior/blocks/building/
cauldron_block.rs1use std::sync::Arc;
8
9use steel_macros::block_behavior;
10use steel_registry::{
11 blocks::{
12 BlockRef,
13 block_state_ext::BlockStateExt as _,
14 properties::{BlockStateProperties, Direction},
15 },
16 level_events, vanilla_blocks, vanilla_fluids, vanilla_game_events,
17};
18use steel_utils::{BlockPos, BlockStateId, types::UpdateFlags};
19
20use crate::{
21 behavior::{BlockBehavior, BlockPlaceContext, blocks::vegetation},
22 entity::ai::path::PathComputationType,
23 world::{LevelReader, World, game_event::GameEventContext},
24};
25
26#[block_behavior]
28pub struct CauldronBlock {
29 block: BlockRef,
30}
31
32impl CauldronBlock {
33 #[must_use]
35 pub const fn new(block: BlockRef) -> Self {
36 Self { block }
37 }
38}
39
40impl BlockBehavior for CauldronBlock {
41 fn get_state_for_placement(&self, _context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
42 Some(self.block.default_state())
43 }
44
45 fn has_analog_output_signal(&self, _state: BlockStateId) -> bool {
46 true
47 }
48
49 fn get_analog_output_signal(
50 &self,
51 _state: BlockStateId,
52 _world: &dyn LevelReader,
53 _pos: BlockPos,
54 _direction: Direction,
55 ) -> i32 {
56 0
57 }
58
59 fn is_pathfindable(
60 &self,
61 _state: BlockStateId,
62 _computation_type: PathComputationType,
63 ) -> bool {
64 false
65 }
66
67 fn tick(&self, _state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
68 let Some(stalactite_pos) =
69 vegetation::find_stalactite_tip_above_cauldron(world.as_ref(), pos)
70 else {
71 return;
72 };
73 let Some(fluid) = vegetation::get_cauldron_fill_fluid_type(world, stalactite_pos) else {
74 return;
75 };
76
77 if fluid == &vanilla_fluids::WATER {
78 let new_state = vanilla_blocks::WATER_CAULDRON
79 .default_state()
80 .set_value(&BlockStateProperties::LEVEL_CAULDRON, 1);
81 world.set_block(pos, new_state, UpdateFlags::UPDATE_ALL);
82 world.game_event(
83 &vanilla_game_events::BLOCK_CHANGE,
84 pos,
85 &GameEventContext::new(None, Some(new_state)),
86 );
87 world.level_event(level_events::SOUND_DRIP_WATER_INTO_CAULDRON, pos, 0, None);
88 } else if fluid == &vanilla_fluids::LAVA {
89 let new_state = vanilla_blocks::LAVA_CAULDRON.default_state();
90 world.set_block(pos, new_state, UpdateFlags::UPDATE_ALL);
91 world.game_event(
92 &vanilla_game_events::BLOCK_CHANGE,
93 pos,
94 &GameEventContext::new(None, Some(new_state)),
95 );
96 world.level_event(level_events::SOUND_DRIP_LAVA_INTO_CAULDRON, pos, 0, None);
97 }
98 }
99}
100
101#[block_behavior]
103pub struct LayeredCauldronBlock {
104 block: BlockRef,
105}
106
107impl LayeredCauldronBlock {
108 #[must_use]
110 pub const fn new(block: BlockRef) -> Self {
111 Self { block }
112 }
113}
114
115impl BlockBehavior for LayeredCauldronBlock {
116 fn get_state_for_placement(&self, _context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
117 Some(self.block.default_state())
118 }
119
120 fn has_analog_output_signal(&self, _state: BlockStateId) -> bool {
121 true
122 }
123
124 fn get_analog_output_signal(
125 &self,
126 state: BlockStateId,
127 _world: &dyn LevelReader,
128 _pos: BlockPos,
129 _direction: Direction,
130 ) -> i32 {
131 i32::from(state.get_value(&BlockStateProperties::LEVEL_CAULDRON))
132 }
133
134 fn is_pathfindable(
135 &self,
136 _state: BlockStateId,
137 _computation_type: PathComputationType,
138 ) -> bool {
139 false
140 }
141
142 fn tick(&self, state: BlockStateId, world: &Arc<World>, pos: BlockPos) {
143 let Some(stalactite_pos) =
144 vegetation::find_stalactite_tip_above_cauldron(world.as_ref(), pos)
145 else {
146 return;
147 };
148 let Some(fluid) = vegetation::get_cauldron_fill_fluid_type(world, stalactite_pos) else {
149 return;
150 };
151
152 if fluid == &vanilla_fluids::WATER {
153 let level = state.get_value(&BlockStateProperties::LEVEL_CAULDRON);
154 if level < BlockStateProperties::LEVEL_CAULDRON.max {
155 let new_state = state.set_value(&BlockStateProperties::LEVEL_CAULDRON, level + 1);
156 world.set_block(pos, new_state, UpdateFlags::UPDATE_ALL);
157 world.game_event(
158 &vanilla_game_events::BLOCK_CHANGE,
159 pos,
160 &GameEventContext::new(None, Some(new_state)),
161 );
162 world.level_event(level_events::SOUND_DRIP_WATER_INTO_CAULDRON, pos, 0, None);
163 }
164 }
165 }
166}
167
168#[cfg(test)]
169mod tests {
170 use steel_registry::{init_vanilla_registry, vanilla_blocks};
171
172 use super::*;
173 use crate::{
174 behavior::{BLOCK_BEHAVIORS, init_behaviors},
175 test_support::TestLevel,
176 };
177
178 #[test]
179 fn registered_cauldron_behaviors_expose_vanilla_fill_levels() {
180 init_vanilla_registry();
181 init_behaviors();
182 let level = TestLevel::default();
183 let pos = BlockPos::ZERO;
184
185 let empty = vanilla_blocks::CAULDRON.default_state();
186 let empty_behavior = BLOCK_BEHAVIORS.get_behavior(empty.get_block());
187 assert!(empty_behavior.has_analog_output_signal(empty));
188 assert_eq!(
189 empty_behavior.get_analog_output_signal(empty, &level, pos, Direction::North),
190 0,
191 );
192
193 for level_value in 1..=3 {
194 let state = vanilla_blocks::WATER_CAULDRON
195 .default_state()
196 .set_value(&BlockStateProperties::LEVEL_CAULDRON, level_value);
197 let behavior = BLOCK_BEHAVIORS.get_behavior(state.get_block());
198 assert_eq!(
199 behavior.get_analog_output_signal(state, &level, pos, Direction::North),
200 i32::from(level_value),
201 );
202 }
203 }
204}