1use std::sync::Arc;
8
9use steel_registry::REGISTRY;
10use steel_registry::blocks::block_state_ext::BlockStateExt;
11use steel_registry::blocks::properties::Direction;
12use steel_registry::vanilla_blocks;
13use steel_utils::types::UpdateFlags;
14use steel_utils::{BlockPos, BlockStateId};
15
16use crate::behavior::{BLOCK_BEHAVIORS, BlockStateBehaviorExt, FLUID_BEHAVIORS};
17use crate::fluid::{
18 FluidBehavior, FluidState, can_hold_any_fluid_state, can_hold_specific_fluid,
19 can_pass_through_wall, fluid_state_to_block, fluid_state_to_block_with_existing,
20 get_new_liquid, get_spread, is_hole,
21};
22use crate::world::World;
23
24pub trait FlowingFluid: FluidBehavior {
27 fn base_tick(
29 &self,
30 world: &Arc<World>,
31 pos: BlockPos,
32 mut block_state: BlockStateId,
33 mut current_fluid: FluidState,
34 ) {
35 if current_fluid.is_empty() || !self.is_same(current_fluid.fluid_id) {
36 return;
37 }
38
39 if !current_fluid.is_source() {
40 let new_fluid = get_new_liquid(
41 world,
42 pos,
43 block_state,
44 self.fluid_type(),
45 self.drop_off(world),
46 );
47
48 if new_fluid.is_empty() {
49 current_fluid = new_fluid;
50 block_state = REGISTRY.blocks.get_default_state_id(&vanilla_blocks::AIR);
52 world.set_block(pos, block_state, UpdateFlags::UPDATE_ALL);
53 } else if new_fluid != current_fluid {
54 let old_fluid = current_fluid;
55 current_fluid = new_fluid;
56 block_state = fluid_state_to_block_with_existing(new_fluid, block_state);
57 world.set_block(pos, block_state, UpdateFlags::UPDATE_ALL);
58
59 world.schedule_fluid_tick_default(
60 pos,
61 new_fluid.fluid_id,
62 self.get_spread_delay(world, pos, old_fluid, new_fluid),
63 );
64 }
65 }
66
67 self.spread(world, pos, block_state, current_fluid);
68 }
69
70 fn base_spread(
74 &self,
75 world: &Arc<World>,
76 pos: BlockPos,
77 block_state: BlockStateId,
78 fluid_state: FluidState,
79 ) {
80 if fluid_state.is_empty() {
81 return;
82 }
83
84 let below = pos.below();
85 let below_state = world.get_block_state(below);
86 let below_fluid = below_state.get_fluid_state();
87
88 let can_spread_down = world.is_in_valid_bounds(below)
91 && !(self.is_same(below_fluid.fluid_id) && below_fluid.is_source())
92 && can_hold_any_fluid_state(below_state)
93 && can_pass_through_wall(world, pos, block_state, below, below_state, Direction::Down);
94
95 if can_spread_down {
96 let new_below_fluid = get_new_liquid(
97 world,
98 below,
99 below_state,
100 self.fluid_type(),
101 self.drop_off(world),
102 );
103
104 if !new_below_fluid.is_empty() {
105 let existing_behavior = FLUID_BEHAVIORS.get_behavior(below_fluid.fluid_id);
106 let can_replace = existing_behavior.can_be_replaced_with(
107 below_fluid,
108 world,
109 below,
110 new_below_fluid.fluid_id,
111 Direction::Down,
112 );
113
114 if can_replace && can_hold_specific_fluid(below_state, new_below_fluid.fluid_id) {
115 self.spread_to(world, below, new_below_fluid, Direction::Down);
116
117 if self.source_neighbor_count(world, pos) >= 3 {
118 self.spread_to_sides(world, pos, block_state, fluid_state);
119 }
120 return;
121 }
122 }
123 }
124
125 if fluid_state.is_source()
126 || !is_hole(
127 world,
128 pos,
129 block_state,
130 below,
131 below_state,
132 self.fluid_type(),
133 )
134 {
135 self.spread_to_sides(world, pos, block_state, fluid_state);
136 }
137 }
138
139 fn base_spread_to(&self, world: &Arc<World>, pos: BlockPos, fluid_state: FluidState) {
145 let target_state = world.get_block_state(pos);
146
147 if target_state.is_liquid_container() {
148 let behavior = BLOCK_BEHAVIORS.get_behavior(target_state.get_block());
149 behavior.place_liquid(world, pos, target_state, fluid_state);
150 return;
151 }
152
153 let target_block = target_state.get_block();
155 if !target_block.config.is_air {
156 self.before_destroying_block(world, pos, target_state);
157 }
158
159 let block_state = fluid_state_to_block(fluid_state);
160 world.set_block(pos, block_state, UpdateFlags::UPDATE_ALL);
163 }
164
165 fn spread_to(
167 &self,
168 world: &Arc<World>,
169 pos: BlockPos,
170 fluid_state: FluidState,
171 _direction: Direction,
172 ) {
173 self.base_spread_to(world, pos, fluid_state);
174 }
175
176 fn source_neighbor_count(&self, world: &Arc<World>, pos: BlockPos) -> u8 {
178 let mut count = 0u8;
179 for dir in Direction::HORIZONTAL {
180 let neighbor = dir.relative(pos);
181 let f = world.get_block_state(neighbor).get_fluid_state();
182 if self.is_same(f.fluid_id) && f.is_source() {
183 count += 1;
184 }
185 }
186 count
187 }
188
189 fn spread_to_sides(
195 &self,
196 world: &Arc<World>,
197 pos: BlockPos,
198 block_state: BlockStateId,
199 fluid_state: FluidState,
200 ) {
201 let mut neighbor = fluid_state.amount.saturating_sub(self.drop_off(world));
203 if fluid_state.falling {
204 neighbor = 7;
205 }
206 if neighbor == 0 {
207 return;
208 }
209
210 let spreads = get_spread(
211 world,
212 pos,
213 block_state,
214 self.fluid_type(),
215 self.drop_off(world),
216 self.slope_find_distance(world),
217 );
218
219 for (direction, new_fluid) in spreads {
220 let target: BlockPos = direction.relative(pos);
221 self.spread_to(world, target, new_fluid, direction);
222 }
223 }
224}