Skip to main content

steel_core/behavior/blocks/building/
potent_sulfur_block.rs

1//! `PotentSulfurBlock` behavior
2
3use 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/// Potent sulfur block behavior.
23#[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    /// New potent sulfur block behavior
33    #[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    // `animateTick` only creates client-local dust particles; the server has no
134    // corresponding work to perform.
135
136    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_fixture = fresh_test_world("potent_sulfur_ticker");
194        let world = &world_fixture.world;
195        let behavior = PotentSulfurBlock::new(&vanilla_blocks::POTENT_SULFUR);
196        let base = vanilla_blocks::POTENT_SULFUR.default_state();
197
198        let dry = base.set_value(POTENT_SULFUR_STATE, PotentSulfurState::Dry);
199        assert!(
200            behavior
201                .get_block_entity_ticker(world, dry, &vanilla_block_entity_types::POTENT_SULFUR,)
202                .is_none()
203        );
204
205        let wet = base.set_value(POTENT_SULFUR_STATE, PotentSulfurState::Wet);
206
207        for sulfur_state in [
208            PotentSulfurState::Wet,
209            PotentSulfurState::Dormant,
210            PotentSulfurState::Erupting,
211            PotentSulfurState::Continuous,
212        ] {
213            let state = base.set_value(POTENT_SULFUR_STATE, sulfur_state);
214            assert!(
215                behavior
216                    .get_block_entity_ticker(
217                        world,
218                        state,
219                        &vanilla_block_entity_types::POTENT_SULFUR,
220                    )
221                    .is_some()
222            );
223        }
224
225        assert!(
226            behavior
227                .get_block_entity_ticker(world, wet, &vanilla_block_entity_types::CHEST)
228                .is_none()
229        );
230    }
231}