steel_core/behavior/blocks/building/
sponge_block.rs1use 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]
24pub struct SpongeBlock {
26 block: BlockRef,
27}
28
29impl SpongeBlock {
30 #[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}