azalea_physics/
travel.rs

1use azalea_block::{BlockState, BlockTrait, fluid_state::FluidState};
2use azalea_core::{
3    aabb::AABB,
4    position::{BlockPos, Vec3},
5};
6use azalea_entity::{
7    Attributes, HasClientLoaded, Jumping, LocalEntity, LookDirection, OnClimbable, Physics,
8    PlayerAbilities, Pose, Position, metadata::Sprinting, move_relative,
9};
10use azalea_world::{Instance, InstanceContainer, InstanceName};
11use bevy_ecs::prelude::*;
12
13use crate::{
14    collision::{
15        MoveCtx, MoverType, Shapes,
16        entity_collisions::{CollidableEntityQuery, PhysicsQuery, get_entity_collisions},
17        move_colliding,
18        world_collisions::{get_block_and_liquid_collisions, get_block_collisions},
19    },
20    get_block_pos_below_that_affects_movement, handle_relative_friction_and_calculate_movement,
21    local_player::PhysicsState,
22};
23
24/// Move the entity with the given acceleration while handling friction,
25/// gravity, collisions, and some other stuff.
26#[allow(clippy::type_complexity)]
27pub fn travel(
28    mut query: Query<
29        (
30            Entity,
31            &Attributes,
32            &InstanceName,
33            &OnClimbable,
34            &Jumping,
35            Option<&PhysicsState>,
36            Option<&Sprinting>,
37            Option<&Pose>,
38            Option<&PlayerAbilities>,
39            &mut Physics,
40            &mut LookDirection,
41            &mut Position,
42        ),
43        (With<LocalEntity>, With<HasClientLoaded>),
44    >,
45    instance_container: Res<InstanceContainer>,
46    physics_query: PhysicsQuery,
47    collidable_entity_query: CollidableEntityQuery,
48) {
49    for (
50        entity,
51        attributes,
52        world_name,
53        on_climbable,
54        jumping,
55        physics_state,
56        sprinting,
57        pose,
58        abilities,
59        mut physics,
60        direction,
61        position,
62    ) in &mut query
63    {
64        let Some(world_lock) = instance_container.get(world_name) else {
65            continue;
66        };
67        let world = world_lock.read();
68
69        let sprinting = *sprinting.unwrap_or(&Sprinting(false));
70
71        // TODO: elytras
72
73        let mut ctx = MoveCtx {
74            mover_type: MoverType::Own,
75            world: &world,
76            position,
77            physics: &mut physics,
78            source_entity: entity,
79            physics_query: &physics_query,
80            collidable_entity_query: &collidable_entity_query,
81            physics_state,
82            attributes,
83            abilities,
84            direction: *direction,
85            sprinting,
86            on_climbable: *on_climbable,
87            pose: pose.copied(),
88            jumping: *jumping,
89        };
90
91        if ctx.physics.is_in_water() || ctx.physics.is_in_lava() {
92            // minecraft also checks for `this.isAffectedByFluids() &&
93            // !this.canStandOnFluid(fluidAtBlock)` here but it doesn't matter
94            // for players
95            travel_in_fluid(&mut ctx);
96        } else {
97            travel_in_air(&mut ctx);
98        }
99    }
100}
101
102/// The usual movement when we're not in water or using an elytra.
103fn travel_in_air(ctx: &mut MoveCtx) {
104    let gravity = get_effective_gravity();
105
106    let block_pos_below = get_block_pos_below_that_affects_movement(*ctx.position);
107
108    let block_state_below = ctx
109        .world
110        .chunks
111        .get_block_state(block_pos_below)
112        .unwrap_or(BlockState::AIR);
113    let block_below: Box<dyn BlockTrait> = block_state_below.into();
114    let block_friction = block_below.behavior().friction;
115
116    let inertia = if ctx.physics.on_ground() {
117        block_friction * 0.91
118    } else {
119        0.91
120    };
121
122    // this applies the current delta
123    let mut movement = handle_relative_friction_and_calculate_movement(ctx, block_friction);
124
125    movement.y -= gravity;
126
127    // if (this.shouldDiscardFriction()) {
128    //     this.setDeltaMovement(movement.x, yMovement, movement.z);
129    // } else {
130    //     this.setDeltaMovement(movement.x * (double)inertia, yMovement *
131    // 0.9800000190734863D, movement.z * (double)inertia); }
132
133    // if should_discard_friction(self) {
134    if false {
135        ctx.physics.velocity = movement;
136    } else {
137        ctx.physics.velocity = Vec3 {
138            x: movement.x * inertia as f64,
139            y: movement.y * 0.9800000190734863f64,
140            z: movement.z * inertia as f64,
141        };
142    }
143}
144
145fn travel_in_fluid(ctx: &mut MoveCtx) {
146    let moving_down = ctx.physics.velocity.y <= 0.;
147    let y = ctx.position.y;
148    let gravity = get_effective_gravity();
149
150    let acceleration = Vec3::new(
151        ctx.physics.x_acceleration as f64,
152        ctx.physics.y_acceleration as f64,
153        ctx.physics.z_acceleration as f64,
154    );
155
156    if ctx.physics.was_touching_water {
157        let mut water_movement_speed = if *ctx.sprinting { 0.9 } else { 0.8 };
158        let mut speed = 0.02;
159        let mut water_efficiency_modifier =
160            ctx.attributes.water_movement_efficiency.calculate() as f32;
161        if !ctx.physics.on_ground() {
162            water_efficiency_modifier *= 0.5;
163        }
164
165        if water_efficiency_modifier > 0. {
166            water_movement_speed += (0.54600006 - water_movement_speed) * water_efficiency_modifier;
167            speed += (ctx.attributes.movement_speed.calculate() as f32 - speed)
168                * water_efficiency_modifier;
169        }
170
171        // if (this.hasEffect(MobEffects.DOLPHINS_GRACE)) {
172        //     waterMovementSpeed = 0.96F;
173        // }
174
175        move_relative(ctx.physics, ctx.direction, speed, acceleration);
176        move_colliding(ctx, ctx.physics.velocity).expect("Entity should exist");
177
178        let mut new_velocity = ctx.physics.velocity;
179        if ctx.physics.horizontal_collision && *ctx.on_climbable {
180            // underwater ladders
181            new_velocity.y = 0.2;
182        }
183        new_velocity.x *= water_movement_speed as f64;
184        new_velocity.y *= 0.8;
185        new_velocity.z *= water_movement_speed as f64;
186        ctx.physics.velocity =
187            get_fluid_falling_adjusted_movement(gravity, moving_down, new_velocity, ctx.sprinting);
188    } else {
189        move_relative(ctx.physics, ctx.direction, 0.02, acceleration);
190        move_colliding(ctx, ctx.physics.velocity).expect("Entity should exist");
191
192        if ctx.physics.lava_fluid_height <= fluid_jump_threshold() {
193            ctx.physics.velocity.x *= 0.5;
194            ctx.physics.velocity.y *= 0.8;
195            ctx.physics.velocity.z *= 0.5;
196            let new_velocity = get_fluid_falling_adjusted_movement(
197                gravity,
198                moving_down,
199                ctx.physics.velocity,
200                ctx.sprinting,
201            );
202            ctx.physics.velocity = new_velocity;
203        } else {
204            ctx.physics.velocity *= 0.5;
205        }
206
207        if gravity != 0.0 {
208            ctx.physics.velocity.y -= gravity / 4.0;
209        }
210    }
211
212    let velocity = ctx.physics.velocity;
213    if ctx.physics.horizontal_collision
214        && is_free(
215            ctx.world,
216            ctx.source_entity,
217            ctx.physics_query,
218            ctx.collidable_entity_query,
219            ctx.physics,
220            ctx.physics.bounding_box,
221            velocity.up(0.6).down(ctx.position.y).up(y),
222        )
223    {
224        ctx.physics.velocity.y = 0.3;
225    }
226}
227
228fn get_fluid_falling_adjusted_movement(
229    gravity: f64,
230    moving_down: bool,
231    new_velocity: Vec3,
232    sprinting: Sprinting,
233) -> Vec3 {
234    if gravity != 0. && !*sprinting {
235        let new_y_velocity = if moving_down
236            && (new_velocity.y - 0.005).abs() >= 0.003
237            && f64::abs(new_velocity.y - gravity / 16.0) < 0.003
238        {
239            -0.003
240        } else {
241            new_velocity.y - gravity / 16.0
242        };
243
244        Vec3 {
245            x: new_velocity.x,
246            y: new_y_velocity,
247            z: new_velocity.z,
248        }
249    } else {
250        new_velocity
251    }
252}
253
254fn is_free(
255    world: &Instance,
256    source_entity: Entity,
257    physics_query: &PhysicsQuery,
258    collidable_entity_query: &CollidableEntityQuery,
259    entity_physics: &Physics,
260    bounding_box: AABB,
261    delta: Vec3,
262) -> bool {
263    let bounding_box = bounding_box.move_relative(delta);
264
265    no_collision(
266        world,
267        Some(source_entity),
268        physics_query,
269        collidable_entity_query,
270        entity_physics,
271        &bounding_box,
272        false,
273    ) && !contains_any_liquid(world, bounding_box)
274}
275
276pub fn no_collision(
277    world: &Instance,
278    source_entity: Option<Entity>,
279    physics_query: &PhysicsQuery,
280    collidable_entity_query: &CollidableEntityQuery,
281    entity_physics: &Physics,
282    aabb: &AABB,
283    include_liquid_collisions: bool,
284) -> bool {
285    let collisions = if include_liquid_collisions {
286        get_block_and_liquid_collisions(world, aabb)
287    } else {
288        get_block_collisions(world, aabb)
289    };
290
291    for collision in collisions {
292        if !collision.is_empty() {
293            return false;
294        }
295    }
296
297    if !get_entity_collisions(
298        world,
299        aabb,
300        source_entity,
301        physics_query,
302        collidable_entity_query,
303    )
304    .is_empty()
305    {
306        false
307    } else if source_entity.is_none() {
308        true
309    } else {
310        let collision = border_collision(entity_physics, aabb);
311        if let Some(collision) = collision {
312            // !Shapes.joinIsNotEmpty(collision, Shapes.create(aabb), BooleanOp.AND);
313            !Shapes::matches_anywhere(&collision.into(), &aabb.into(), |a, b| a && b)
314        } else {
315            true
316        }
317    }
318}
319
320fn border_collision(_entity_physics: &Physics, _aabb: &AABB) -> Option<AABB> {
321    // TODO: implement world border, see CollisionGetter.borderCollision
322
323    None
324}
325
326fn contains_any_liquid(world: &Instance, bounding_box: AABB) -> bool {
327    let min = bounding_box.min.to_block_pos_floor();
328    let max = bounding_box.max.to_block_pos_ceil();
329
330    for x in min.x..max.x {
331        for y in min.y..max.y {
332            for z in min.z..max.z {
333                let block_state = world
334                    .chunks
335                    .get_block_state(BlockPos::new(x, y, z))
336                    .unwrap_or_default();
337                if !FluidState::from(block_state).is_empty() {
338                    return true;
339                }
340            }
341        }
342    }
343
344    false
345}
346
347fn get_effective_gravity() -> f64 {
348    // TODO: slow falling effect
349    0.08
350}
351
352pub fn fluid_jump_threshold() -> f64 {
353    // this is 0.0 for entities with an eye height lower than 0.4, but that's not
354    // implemented since it's usually not relevant for players (unless the player
355    // was shrunk)
356    0.4
357}