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::{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]
23pub struct SpongeBlock {
25 block: BlockRef,
26}
27
28impl SpongeBlock {
29 #[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}