steel_core/behavior/blocks/building/
potent_sulfur_block.rs1use std::sync::{Arc, Weak};
4
5use steel_macros::block_behavior;
6use steel_registry::block_entity_type::BlockEntityTypeRef;
7use steel_registry::blocks::BlockRef;
8use steel_registry::blocks::block_state_ext::BlockStateExt as _;
9use steel_registry::blocks::properties::{BlockStateProperties, PotentSulfurState};
10use steel_registry::sound_events;
11use steel_registry::vanilla_block_entity_types;
12use steel_registry::vanilla_block_tags::BlockTag;
13use steel_registry::vanilla_game_events;
14use steel_utils::{BlockPos, BlockStateId, Direction, Downcast as _};
15
16use crate::behavior::{BlockBehavior, BlockEntityCreation, BlockPlaceContext};
17use crate::block_entity::entities::PotentSulfurBlockEntity;
18use crate::block_entity::{BLOCK_ENTITIES, BlockEntityTicker};
19use crate::fluid::FluidStateExt as _;
20use crate::world::{LevelReader, ScheduledTickAccess, World, game_event::GameEventContext};
21
22#[block_behavior]
24pub struct PotentSulfurBlock {
25 block: BlockRef,
26}
27
28impl PotentSulfurBlock {
29 #[must_use]
31 pub const fn new(block: BlockRef) -> Self {
32 Self { block }
33 }
34
35 fn valid_state(state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> BlockStateId {
36 let above_fluid = world.get_block_state(pos.above()).get_fluid_state();
37 if !above_fluid.is_source() || !above_fluid.is_water() {
38 return state.set_value(
39 &BlockStateProperties::POTENT_SULFUR_STATE,
40 PotentSulfurState::Dry,
41 );
42 }
43
44 let below = world.get_block_state(pos.below());
45 let below_fluid = below.get_fluid_state();
46 let fluid_ok = below_fluid.is_empty() || below_fluid.is_source();
47
48 if below
49 .get_block()
50 .has_tag(&BlockTag::CAUSES_CONTINUOUS_GEYSER_ERUPTIONS)
51 && fluid_ok
52 {
53 return state.set_value(
54 &BlockStateProperties::POTENT_SULFUR_STATE,
55 PotentSulfurState::Continuous,
56 );
57 }
58
59 if below
60 .get_block()
61 .has_tag(&BlockTag::CAUSES_PERIODIC_GEYSER_ERUPTIONS)
62 && fluid_ok
63 {
64 let is_geyser = matches!(
65 state.get_value(&BlockStateProperties::POTENT_SULFUR_STATE),
66 PotentSulfurState::Dormant | PotentSulfurState::Erupting
67 );
68 if !is_geyser
69 && let Some(block_entity) = world.get_block_entity(pos)
70 && let Some(potent_sulfur) = block_entity.downcast_ref::<PotentSulfurBlockEntity>()
71 {
72 potent_sulfur.reset_countdown();
73 }
74
75 if state.get_value(&BlockStateProperties::POTENT_SULFUR_STATE)
76 == PotentSulfurState::Erupting
77 {
78 return state;
79 }
80 return state.set_value(
81 &BlockStateProperties::POTENT_SULFUR_STATE,
82 PotentSulfurState::Dormant,
83 );
84 }
85
86 state.set_value(
87 &BlockStateProperties::POTENT_SULFUR_STATE,
88 PotentSulfurState::Wet,
89 )
90 }
91}
92
93impl BlockBehavior for PotentSulfurBlock {
94 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
95 Some(Self::valid_state(
96 self.block.default_state(),
97 context.world,
98 context.place_pos(),
99 ))
100 }
101
102 fn update_shape(
103 &self,
104 state: BlockStateId,
105 world: &dyn ScheduledTickAccess,
106 pos: BlockPos,
107 _direction: Direction,
108 _neighbor_pos: BlockPos,
109 _neighbor_state: BlockStateId,
110 ) -> BlockStateId {
111 Self::valid_state(state, world, pos)
112 }
113
114 fn on_place(
115 &self,
116 state: BlockStateId,
117 world: &Arc<World>,
118 pos: BlockPos,
119 _old_state: BlockStateId,
120 _moved_by_piston: bool,
121 ) {
122 let current = state.get_value(&BlockStateProperties::POTENT_SULFUR_STATE);
123 if !matches!(
124 current,
125 PotentSulfurState::Erupting | PotentSulfurState::Continuous
126 ) {
127 return;
128 }
129
130 world.block_event(pos, self.block, 0, 0);
131 let sound = if current == PotentSulfurState::Continuous {
132 &sound_events::BLOCK_POTENT_SULFUR_GEYSER_CONTINUOUS_ERUPTION
133 } else {
134 &sound_events::BLOCK_POTENT_SULFUR_GEYSER_ERUPTION
135 };
136 world.play_block_sound(sound, pos, 1.0, 1.0, None);
137 world.game_event(
138 &vanilla_game_events::BLOCK_ACTIVATE,
139 pos,
140 &GameEventContext::new(None, Some(state)),
141 );
142 }
143
144 fn trigger_event(
148 &self,
149 _state: BlockStateId,
150 world: &Arc<World>,
151 pos: BlockPos,
152 _param_a: i32,
153 _param_b: i32,
154 ) -> bool {
155 if let Some(block_entity) = world.get_block_entity(pos)
156 && let Some(sulfur) = block_entity.downcast_ref::<PotentSulfurBlockEntity>()
157 {
158 sulfur.set_eruption_tick(world.game_time());
159 }
160 true
161 }
162
163 fn new_block_entity(
164 &self,
165 level: Weak<World>,
166 pos: BlockPos,
167 state: BlockStateId,
168 ) -> BlockEntityCreation {
169 BlockEntityCreation::from_registered_factory(BLOCK_ENTITIES.create(
170 &vanilla_block_entity_types::POTENT_SULFUR,
171 level,
172 pos,
173 state,
174 ))
175 }
176
177 fn get_block_entity_ticker(
178 &self,
179 _world: &Arc<World>,
180 state: BlockStateId,
181 block_entity_type: BlockEntityTypeRef,
182 ) -> Option<BlockEntityTicker> {
183 if state.get_value(&BlockStateProperties::POTENT_SULFUR_STATE) == PotentSulfurState::Dry {
184 return None;
185 }
186 BlockEntityTicker::for_matching_entity_tick(
187 block_entity_type,
188 &vanilla_block_entity_types::POTENT_SULFUR,
189 )
190 }
191}
192
193#[cfg(test)]
194mod tests {
195 use steel_registry::init_vanilla_registry;
196 use steel_registry::vanilla_blocks;
197
198 use super::*;
199 use crate::test_support::fresh_test_world;
200
201 #[test]
202 fn potent_sulfur_ticker_selection_matches_live_geyser_state() {
203 init_vanilla_registry();
204 let world = fresh_test_world("potent_sulfur_ticker");
205 let behavior = PotentSulfurBlock::new(&vanilla_blocks::POTENT_SULFUR);
206 let base = vanilla_blocks::POTENT_SULFUR.default_state();
207
208 let dry = base.set_value(
209 &BlockStateProperties::POTENT_SULFUR_STATE,
210 PotentSulfurState::Dry,
211 );
212 assert!(
213 behavior
214 .get_block_entity_ticker(&world, dry, &vanilla_block_entity_types::POTENT_SULFUR,)
215 .is_none()
216 );
217
218 let wet = base.set_value(
219 &BlockStateProperties::POTENT_SULFUR_STATE,
220 PotentSulfurState::Wet,
221 );
222
223 for sulfur_state in [
224 PotentSulfurState::Wet,
225 PotentSulfurState::Dormant,
226 PotentSulfurState::Erupting,
227 PotentSulfurState::Continuous,
228 ] {
229 let state = base.set_value(&BlockStateProperties::POTENT_SULFUR_STATE, sulfur_state);
230 assert!(
231 behavior
232 .get_block_entity_ticker(
233 &world,
234 state,
235 &vanilla_block_entity_types::POTENT_SULFUR,
236 )
237 .is_some()
238 );
239 }
240
241 assert!(
242 behavior
243 .get_block_entity_ticker(&world, wet, &vanilla_block_entity_types::CHEST)
244 .is_none()
245 );
246 }
247}