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, EnumProperty, 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
28const POTENT_SULFUR_STATE: &EnumProperty<PotentSulfurState> =
29 &BlockStateProperties::POTENT_SULFUR_STATE;
30
31impl PotentSulfurBlock {
32 #[must_use]
34 pub const fn new(block: BlockRef) -> Self {
35 Self { block }
36 }
37
38 fn valid_state(state: BlockStateId, world: &dyn LevelReader, pos: BlockPos) -> BlockStateId {
39 let above_fluid = world.get_block_state(pos.above()).get_fluid_state();
40 if !above_fluid.is_source() || !above_fluid.is_water() {
41 return state.set_value(POTENT_SULFUR_STATE, PotentSulfurState::Dry);
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(POTENT_SULFUR_STATE, PotentSulfurState::Continuous);
54 }
55
56 if below
57 .get_block()
58 .has_tag(&BlockTag::CAUSES_PERIODIC_GEYSER_ERUPTIONS)
59 && fluid_ok
60 {
61 let is_geyser = matches!(
62 state.get_value(POTENT_SULFUR_STATE),
63 PotentSulfurState::Dormant | PotentSulfurState::Erupting
64 );
65 if !is_geyser
66 && let Some(block_entity) = world.get_block_entity(pos)
67 && let Some(potent_sulfur) = block_entity.downcast_ref::<PotentSulfurBlockEntity>()
68 {
69 potent_sulfur.reset_countdown();
70 }
71
72 if state.get_value(POTENT_SULFUR_STATE) == PotentSulfurState::Erupting {
73 return state;
74 }
75 return state.set_value(POTENT_SULFUR_STATE, PotentSulfurState::Dormant);
76 }
77
78 state.set_value(POTENT_SULFUR_STATE, PotentSulfurState::Wet)
79 }
80}
81
82impl BlockBehavior for PotentSulfurBlock {
83 fn get_state_for_placement(&self, context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
84 Some(Self::valid_state(
85 self.block.default_state(),
86 context.world,
87 context.place_pos(),
88 ))
89 }
90
91 fn update_shape(
92 &self,
93 state: BlockStateId,
94 world: &dyn ScheduledTickAccess,
95 pos: BlockPos,
96 _direction: Direction,
97 _neighbor_pos: BlockPos,
98 _neighbor_state: BlockStateId,
99 ) -> BlockStateId {
100 Self::valid_state(state, world, pos)
101 }
102
103 fn on_place(
104 &self,
105 state: BlockStateId,
106 world: &Arc<World>,
107 pos: BlockPos,
108 _old_state: BlockStateId,
109 _moved_by_piston: bool,
110 ) {
111 let current = state.get_value(POTENT_SULFUR_STATE);
112 if !matches!(
113 current,
114 PotentSulfurState::Erupting | PotentSulfurState::Continuous
115 ) {
116 return;
117 }
118
119 world.block_event(pos, self.block, 0, 0);
120 let sound = if current == PotentSulfurState::Continuous {
121 &sound_events::BLOCK_POTENT_SULFUR_GEYSER_CONTINUOUS_ERUPTION
122 } else {
123 &sound_events::BLOCK_POTENT_SULFUR_GEYSER_ERUPTION
124 };
125 world.play_block_sound(sound, pos, 1.0, 1.0, None);
126 world.game_event(
127 &vanilla_game_events::BLOCK_ACTIVATE,
128 pos,
129 &GameEventContext::new(None, Some(state)),
130 );
131 }
132
133 fn trigger_event(
137 &self,
138 _state: BlockStateId,
139 world: &Arc<World>,
140 pos: BlockPos,
141 _param_a: i32,
142 _param_b: i32,
143 ) -> bool {
144 if let Some(block_entity) = world.get_block_entity(pos)
145 && let Some(sulfur) = block_entity.downcast_ref::<PotentSulfurBlockEntity>()
146 {
147 sulfur.set_eruption_tick(world.game_time());
148 }
149 true
150 }
151
152 fn new_block_entity(
153 &self,
154 level: Weak<World>,
155 pos: BlockPos,
156 state: BlockStateId,
157 ) -> BlockEntityCreation {
158 BlockEntityCreation::from_registered_factory(BLOCK_ENTITIES.create(
159 &vanilla_block_entity_types::POTENT_SULFUR,
160 level,
161 pos,
162 state,
163 ))
164 }
165
166 fn get_block_entity_ticker(
167 &self,
168 _world: &Arc<World>,
169 state: BlockStateId,
170 block_entity_type: BlockEntityTypeRef,
171 ) -> Option<BlockEntityTicker> {
172 if state.get_value(POTENT_SULFUR_STATE) == PotentSulfurState::Dry {
173 return None;
174 }
175 BlockEntityTicker::for_matching_entity_tick(
176 block_entity_type,
177 &vanilla_block_entity_types::POTENT_SULFUR,
178 )
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use steel_registry::init_vanilla_registry;
185 use steel_registry::vanilla_blocks;
186
187 use super::*;
188 use crate::test_support::fresh_test_world;
189
190 #[test]
191 fn potent_sulfur_ticker_selection_matches_live_geyser_state() {
192 init_vanilla_registry();
193 let world = fresh_test_world("potent_sulfur_ticker");
194 let behavior = PotentSulfurBlock::new(&vanilla_blocks::POTENT_SULFUR);
195 let base = vanilla_blocks::POTENT_SULFUR.default_state();
196
197 let dry = base.set_value(POTENT_SULFUR_STATE, PotentSulfurState::Dry);
198 assert!(
199 behavior
200 .get_block_entity_ticker(&world, dry, &vanilla_block_entity_types::POTENT_SULFUR,)
201 .is_none()
202 );
203
204 let wet = base.set_value(POTENT_SULFUR_STATE, PotentSulfurState::Wet);
205
206 for sulfur_state in [
207 PotentSulfurState::Wet,
208 PotentSulfurState::Dormant,
209 PotentSulfurState::Erupting,
210 PotentSulfurState::Continuous,
211 ] {
212 let state = base.set_value(POTENT_SULFUR_STATE, sulfur_state);
213 assert!(
214 behavior
215 .get_block_entity_ticker(
216 &world,
217 state,
218 &vanilla_block_entity_types::POTENT_SULFUR,
219 )
220 .is_some()
221 );
222 }
223
224 assert!(
225 behavior
226 .get_block_entity_ticker(&world, wet, &vanilla_block_entity_types::CHEST)
227 .is_none()
228 );
229 }
230}