Skip to main content

steel_core/behavior/blocks/building/
sponge_block.rs

1use std::sync::Arc;
2
3use steel_macros::block_behavior;
4use steel_protocol::packets::game::SoundSource;
5use steel_registry::blocks::BlockRef;
6use steel_registry::blocks::block_state_ext::BlockStateExt;
7use steel_registry::blocks::properties::Direction;
8use steel_registry::vanilla_fluid_tags::FluidTag;
9use steel_registry::{sound_events, vanilla_blocks};
10use steel_utils::{
11    BlockPos, BlockStateId,
12    types::{TraversalNodeStatus, UpdateFlags},
13};
14
15use crate::behavior::{
16    BLOCK_BEHAVIORS, BlockBehavior, BlockPlaceContext, pickup_waterlogged_block,
17};
18use crate::world::{ConditionalBlockSetResult, World};
19
20const MAX_DEPTH: i32 = 6;
21const MAX_COUNT: i32 = 64;
22
23#[block_behavior]
24/// Sponge behavior
25pub struct SpongeBlock {
26    block: BlockRef,
27}
28
29impl SpongeBlock {
30    /// Creates a new sponge block
31    #[must_use]
32    pub const fn new(block: BlockRef) -> Self {
33        Self { block }
34    }
35
36    fn try_absorb_water(world: &Arc<World>, pos: BlockPos) {
37        if !Self::remove_water_breadth_first_search(world, pos) {
38            return;
39        }
40
41        world.set_block(
42            pos,
43            vanilla_blocks::WET_SPONGE.default_state(),
44            UpdateFlags::UPDATE_CLIENTS,
45        );
46        world.play_sound(
47            &sound_events::BLOCK_SPONGE_ABSORB,
48            SoundSource::Blocks,
49            pos,
50            1.0,
51            1.0,
52            None,
53        );
54    }
55
56    fn remove_water_breadth_first_search(world: &Arc<World>, start_pos: BlockPos) -> bool {
57        BlockPos::breadth_first_traversal(
58            start_pos,
59            MAX_DEPTH,
60            MAX_COUNT + 1,
61            |pos, consumer| {
62                for direction in Direction::ALL {
63                    consumer(pos.relative(direction));
64                }
65            },
66            |pos| {
67                if pos == start_pos || Self::remove_water_at(world, pos) {
68                    TraversalNodeStatus::Accept
69                } else {
70                    TraversalNodeStatus::Skip
71                }
72            },
73        ) > 1
74    }
75
76    fn remove_water_at(world: &Arc<World>, pos: BlockPos) -> bool {
77        let state = world.get_block_state(pos);
78        let fluid_state = state.get_fluid_state();
79        if !fluid_state.fluid_id.has_tag(&FluidTag::WATER) {
80            return false;
81        }
82
83        let behavior = BLOCK_BEHAVIORS.get_behavior(state.get_block());
84        if behavior.pickup_block(world, pos, state, None).is_some() {
85            return true;
86        }
87
88        if pickup_waterlogged_block(behavior, world, pos, state, None).is_some() {
89            return true;
90        }
91
92        if state.get_block() == &vanilla_blocks::WATER {
93            return world.set_block_if_unchanged(
94                pos,
95                state,
96                vanilla_blocks::AIR.default_state(),
97                UpdateFlags::UPDATE_ALL,
98            ) == ConditionalBlockSetResult::Changed;
99        }
100
101        if !Self::is_absorbable_water_plant(state) {
102            return false;
103        }
104
105        world.drop_resources(state, pos);
106        world.set_block(
107            pos,
108            vanilla_blocks::AIR.default_state(),
109            UpdateFlags::UPDATE_ALL,
110        )
111    }
112
113    fn is_absorbable_water_plant(state: BlockStateId) -> bool {
114        let block = state.get_block();
115        block == &vanilla_blocks::KELP
116            || block == &vanilla_blocks::KELP_PLANT
117            || block == &vanilla_blocks::SEAGRASS
118            || block == &vanilla_blocks::TALL_SEAGRASS
119    }
120}
121
122impl BlockBehavior for SpongeBlock {
123    fn get_state_for_placement(&self, _context: &BlockPlaceContext<'_>) -> Option<BlockStateId> {
124        Some(self.block.default_state())
125    }
126
127    fn on_place(
128        &self,
129        state: BlockStateId,
130        world: &Arc<World>,
131        pos: BlockPos,
132        old_state: BlockStateId,
133        _moved_by_piston: bool,
134    ) {
135        if old_state.get_block() != state.get_block() {
136            Self::try_absorb_water(world, pos);
137        }
138    }
139
140    fn handle_neighbor_changed(
141        &self,
142        _state: BlockStateId,
143        world: &Arc<World>,
144        pos: BlockPos,
145        _source_block: BlockRef,
146        _moved_by_piston: bool,
147    ) {
148        Self::try_absorb_water(world, pos);
149    }
150}