azalea_physics/
fluids.rs

1use azalea_block::{
2    BlockState,
3    fluid_state::{FluidKind, FluidState},
4};
5use azalea_core::{
6    direction::Direction,
7    position::{BlockPos, Vec3},
8    resource_location::ResourceLocation,
9};
10use azalea_entity::{InLoadedChunk, LocalEntity, Physics, Position};
11use azalea_world::{Instance, InstanceContainer, InstanceName};
12use bevy_ecs::prelude::*;
13
14use crate::collision::legacy_blocks_motion;
15
16#[allow(clippy::type_complexity)]
17pub fn update_in_water_state_and_do_fluid_pushing(
18    mut query: Query<
19        (&mut Physics, &Position, &InstanceName),
20        (With<LocalEntity>, With<InLoadedChunk>),
21    >,
22    instance_container: Res<InstanceContainer>,
23) {
24    for (mut physics, position, instance_name) in &mut query {
25        let world_lock = instance_container
26            .get(instance_name)
27            .expect("All entities with InLoadedChunk should be in a valid world");
28        let world = world_lock.read();
29
30        // reset the heights since they're going to be set in
31        // update_in_water_state_and_do_water_current_pushing
32        physics.water_fluid_height = 0.;
33        physics.lava_fluid_height = 0.;
34
35        update_in_water_state_and_do_water_current_pushing(&mut physics, &world, position);
36
37        // right now doing registries.dimension_type() clones the entire registry which
38        // is very inefficient, so for now we're doing this instead
39
40        let is_ultrawarm = world
41            .registries
42            .map
43            .get(&ResourceLocation::new("minecraft:dimension_type"))
44            .and_then(|d| {
45                d.get(&**instance_name)
46                    .map(|d| d.byte("ultrawarm") != Some(0))
47            })
48            .unwrap_or_default();
49        let lava_push_factor = if is_ultrawarm {
50            0.007
51        } else {
52            0.0023333333333333335
53        };
54
55        update_fluid_height_and_do_fluid_pushing(
56            &mut physics,
57            &world,
58            FluidKind::Lava,
59            lava_push_factor,
60        );
61    }
62}
63fn update_in_water_state_and_do_water_current_pushing(
64    physics: &mut Physics,
65    world: &Instance,
66    _position: &Position,
67) {
68    // TODO: implement vehicles and boats
69    // if vehicle == AbstractBoat {
70    //     if !boat.is_underwater() {
71    //         *was_touching_water = false;
72    //     }
73    // }
74
75    // updateFluidHeightAndDoFluidPushing
76    if update_fluid_height_and_do_fluid_pushing(physics, world, FluidKind::Water, 0.014) {
77        // if !was_touching_water && !first_tick {
78        //     do_water_splash_effect();
79        // }
80
81        physics.reset_fall_distance();
82        physics.was_touching_water = true;
83        physics.clear_fire();
84    } else {
85        physics.was_touching_water = false;
86    }
87}
88
89fn update_fluid_height_and_do_fluid_pushing(
90    physics: &mut Physics,
91    world: &Instance,
92    checking_fluid: FluidKind,
93    fluid_push_factor: f64,
94) -> bool {
95    // if touching_unloaded_chunk() {
96    //     return false;
97    // }
98
99    let checking_liquids_aabb = physics.bounding_box.deflate_all(0.001);
100
101    let min_x = checking_liquids_aabb.min.x.floor() as i32;
102    let min_y = checking_liquids_aabb.min.y.floor() as i32;
103    let min_z = checking_liquids_aabb.min.z.floor() as i32;
104
105    let max_x = checking_liquids_aabb.max.x.ceil() as i32;
106    let max_y = checking_liquids_aabb.max.y.ceil() as i32;
107    let max_z = checking_liquids_aabb.max.z.ceil() as i32;
108
109    let mut min_height_touching = 0.;
110    let is_entity_pushable_by_fluid = true;
111    let mut touching_fluid = false;
112    let mut additional_player_delta = Vec3::ZERO;
113    let mut num_fluids_being_touched = 0;
114
115    for cur_x in min_x..max_x {
116        for cur_y in min_y..max_y {
117            for cur_z in min_z..max_z {
118                let cur_pos = BlockPos::new(cur_x, cur_y, cur_z);
119                let Some(fluid_at_cur_pos) = world.get_fluid_state(&cur_pos) else {
120                    continue;
121                };
122                if fluid_at_cur_pos.kind != checking_fluid {
123                    continue;
124                }
125                let fluid_max_y = (cur_y as f32 + fluid_at_cur_pos.height()) as f64;
126                if fluid_max_y < checking_liquids_aabb.min.y {
127                    continue;
128                }
129                touching_fluid = true;
130                min_height_touching = f64::max(
131                    fluid_max_y - checking_liquids_aabb.min.y,
132                    min_height_touching,
133                );
134                if !is_entity_pushable_by_fluid {
135                    continue;
136                }
137                let mut additional_player_delta_for_fluid =
138                    get_fluid_flow(&fluid_at_cur_pos, world, cur_pos);
139                if min_height_touching < 0.4 {
140                    additional_player_delta_for_fluid *= min_height_touching;
141                };
142
143                additional_player_delta += additional_player_delta_for_fluid;
144                num_fluids_being_touched += 1;
145            }
146        }
147    }
148
149    if additional_player_delta.length() > 0. {
150        additional_player_delta /= num_fluids_being_touched as f64;
151
152        // if entity_kind != EntityKind::Player {
153        //     additional_player_delta = additional_player_delta.normalize();
154        // }
155
156        let player_delta = physics.velocity;
157        additional_player_delta *= fluid_push_factor;
158        const MIN_PUSH: f64 = 0.003;
159        const MIN_PUSH_LENGTH: f64 = MIN_PUSH * 1.5;
160
161        if player_delta.x.abs() < MIN_PUSH
162            && player_delta.z.abs() < MIN_PUSH
163            && additional_player_delta.length() < MIN_PUSH_LENGTH
164        {
165            additional_player_delta = additional_player_delta.normalize() * MIN_PUSH_LENGTH;
166        }
167
168        physics.velocity += additional_player_delta;
169    }
170
171    match checking_fluid {
172        FluidKind::Water => physics.water_fluid_height = min_height_touching,
173        FluidKind::Lava => physics.lava_fluid_height = min_height_touching,
174        FluidKind::Empty => panic!("FluidKind::Empty should not be passed to update_fluid_height"),
175    };
176
177    touching_fluid
178}
179
180pub fn update_swimming() {
181    // TODO: swimming
182}
183
184// FlowingFluid.getFlow
185pub fn get_fluid_flow(fluid: &FluidState, world: &Instance, pos: BlockPos) -> Vec3 {
186    let mut z_flow: f64 = 0.;
187    let mut x_flow: f64 = 0.;
188
189    let cur_fluid_height = fluid.height();
190
191    for direction in Direction::HORIZONTAL {
192        let adjacent_block_pos = pos.offset_with_direction(direction);
193
194        let adjacent_block_state = world
195            .get_block_state(&adjacent_block_pos)
196            .unwrap_or_default();
197        let adjacent_fluid_state = FluidState::from(adjacent_block_state);
198
199        if !fluid.affects_flow(&adjacent_fluid_state) {
200            continue;
201        };
202        let mut adjacent_fluid_height = adjacent_fluid_state.height();
203        let mut adjacent_height_difference: f32 = 0.;
204
205        if adjacent_fluid_height == 0. {
206            if !legacy_blocks_motion(adjacent_block_state) {
207                let block_pos_below_adjacent = adjacent_block_pos.down(1);
208                let fluid_below_adjacent = world
209                    .get_fluid_state(&block_pos_below_adjacent)
210                    .unwrap_or_default();
211
212                if fluid.affects_flow(&fluid_below_adjacent) {
213                    adjacent_fluid_height = fluid_below_adjacent.height();
214                    if adjacent_fluid_height > 0. {
215                        adjacent_height_difference =
216                            cur_fluid_height - (adjacent_fluid_height - 0.8888889);
217                    }
218                }
219            }
220        } else if adjacent_fluid_height > 0. {
221            adjacent_height_difference = cur_fluid_height - adjacent_fluid_height;
222        }
223
224        if adjacent_height_difference != 0. {
225            x_flow += (direction.x() as f32 * adjacent_height_difference) as f64;
226            z_flow += (direction.z() as f32 * adjacent_height_difference) as f64;
227        }
228    }
229
230    let mut flow = Vec3::new(x_flow, 0., z_flow);
231    if fluid.falling {
232        for direction in Direction::HORIZONTAL {
233            let adjacent_block_pos = pos.offset_with_direction(direction);
234            if is_solid_face(fluid, world, adjacent_block_pos, direction)
235                || is_solid_face(fluid, world, adjacent_block_pos.up(1), direction)
236            {
237                flow = flow.normalize() + Vec3::new(0., -6., 0.);
238                break;
239            }
240        }
241    }
242
243    flow.normalize()
244}
245
246// i don't really get what this is for
247fn is_solid_face(
248    fluid: &FluidState,
249    world: &Instance,
250    adjacent_pos: BlockPos,
251    direction: Direction,
252) -> bool {
253    let block_state = world.get_block_state(&adjacent_pos).unwrap_or_default();
254    let fluid_state = world.get_fluid_state(&adjacent_pos).unwrap_or_default();
255    if fluid_state.is_same_kind(fluid) {
256        return false;
257    }
258    if direction == Direction::Up {
259        return true;
260    }
261    let registry_block = azalea_registry::Block::from(block_state);
262    if matches!(
263        registry_block,
264        // frosted ice is from frost walker
265        azalea_registry::Block::Ice | azalea_registry::Block::FrostedIce
266    ) {
267        return false;
268    }
269    is_face_sturdy(block_state, world, adjacent_pos, direction)
270}
271
272fn is_face_sturdy(
273    _block_state: BlockState,
274    _world: &Instance,
275    _pos: BlockPos,
276    _direction: Direction,
277) -> bool {
278    // TODO: this does a whole bunch of physics shape checks for waterlogged blocks
279    // that i honestly cannot be bothered to implement right now
280
281    // see BlockBehavior.isFaceSturdy in the decompiled minecraft source
282
283    // also, this probably should be in a module other than fluids.rs
284
285    false
286}