azalea_physics/
fluids.rs

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