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