Skip to main content

azalea_physics/
travel.rs

1use azalea_block::{BlockState, 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::{World, WorldName, Worlds};
11use bevy_ecs::prelude::*;
12
13use crate::{
14    client_movement::ClientMovementState,
15    collision::{
16        MoveCtx, MoverType, Shapes,
17        entity_collisions::{AabbQuery, CollidableEntityQuery, get_entity_collisions},
18        move_colliding,
19        world_collisions::{get_block_and_liquid_collisions, get_block_collisions},
20    },
21    get_block_pos_below_that_affects_movement, handle_relative_friction_and_calculate_movement,
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            &WorldName,
33            &OnClimbable,
34            &Jumping,
35            Option<&ClientMovementState>,
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    worlds: Res<Worlds>,
46    aabb_query: AabbQuery,
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) = worlds.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            aabb_query: &aabb_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_below = ctx
109        .world
110        .chunks
111        .get_block_state(block_pos_below)
112        .unwrap_or(BlockState::AIR);
113
114    let block_friction = block_below.behavior().friction;
115    let inertia = if ctx.physics.on_ground() {
116        block_friction * 0.91
117    } else {
118        0.91
119    };
120
121    // this applies the current delta
122    let mut movement = handle_relative_friction_and_calculate_movement(ctx, block_friction);
123
124    movement.y -= gravity;
125
126    // if (this.shouldDiscardFriction()) {
127    //     this.setDeltaMovement(movement.x, yMovement, movement.z);
128    // } else {
129    //     this.setDeltaMovement(movement.x * (double)inertia, yMovement *
130    // 0.9800000190734863D, movement.z * (double)inertia); }
131
132    // if should_discard_friction(self) {
133    if false {
134        ctx.physics.velocity = movement;
135    } else {
136        ctx.physics.velocity = Vec3 {
137            x: movement.x * inertia as f64,
138            y: movement.y * 0.9800000190734863f64,
139            z: movement.z * inertia as f64,
140        };
141    }
142}
143
144fn travel_in_fluid(ctx: &mut MoveCtx) {
145    let moving_down = ctx.physics.velocity.y <= 0.;
146    let y = ctx.position.y;
147    let gravity = get_effective_gravity();
148
149    let acceleration = Vec3::new(
150        ctx.physics.x_acceleration as f64,
151        ctx.physics.y_acceleration as f64,
152        ctx.physics.z_acceleration as f64,
153    );
154
155    if ctx.physics.was_touching_water {
156        let mut water_movement_speed = if *ctx.sprinting { 0.9 } else { 0.8 };
157        let mut speed = 0.02;
158        let mut water_efficiency_modifier =
159            ctx.attributes.water_movement_efficiency.calculate() as f32;
160        if !ctx.physics.on_ground() {
161            water_efficiency_modifier *= 0.5;
162        }
163
164        if water_efficiency_modifier > 0. {
165            water_movement_speed += (0.54600006 - water_movement_speed) * water_efficiency_modifier;
166            speed += (ctx.attributes.movement_speed.calculate() as f32 - speed)
167                * water_efficiency_modifier;
168        }
169
170        // if (this.hasEffect(MobEffects.DOLPHINS_GRACE)) {
171        //     waterMovementSpeed = 0.96F;
172        // }
173
174        move_relative(ctx.physics, ctx.direction, speed, acceleration);
175        move_colliding(ctx, ctx.physics.velocity);
176
177        let mut new_velocity = ctx.physics.velocity;
178        if ctx.physics.horizontal_collision && *ctx.on_climbable {
179            // underwater ladders
180            new_velocity.y = 0.2;
181        }
182        new_velocity.x *= water_movement_speed as f64;
183        new_velocity.y *= 0.8;
184        new_velocity.z *= water_movement_speed as f64;
185        ctx.physics.velocity =
186            get_fluid_falling_adjusted_movement(gravity, moving_down, new_velocity, ctx.sprinting);
187    } else {
188        move_relative(ctx.physics, ctx.direction, 0.02, acceleration);
189        move_colliding(ctx, ctx.physics.velocity);
190
191        if ctx.physics.lava_fluid_height <= fluid_jump_threshold() {
192            ctx.physics.velocity.x *= 0.5;
193            ctx.physics.velocity.y *= 0.8;
194            ctx.physics.velocity.z *= 0.5;
195            let new_velocity = get_fluid_falling_adjusted_movement(
196                gravity,
197                moving_down,
198                ctx.physics.velocity,
199                ctx.sprinting,
200            );
201            ctx.physics.velocity = new_velocity;
202        } else {
203            ctx.physics.velocity *= 0.5;
204        }
205
206        if gravity != 0.0 {
207            ctx.physics.velocity.y -= gravity / 4.0;
208        }
209    }
210
211    let velocity = ctx.physics.velocity;
212    if ctx.physics.horizontal_collision
213        && is_free(
214            ctx.world,
215            ctx.source_entity,
216            ctx.aabb_query,
217            ctx.collidable_entity_query,
218            ctx.physics,
219            ctx.physics.bounding_box,
220            velocity.up(0.6).down(ctx.position.y).up(y),
221        )
222    {
223        ctx.physics.velocity.y = 0.3;
224    }
225}
226
227fn get_fluid_falling_adjusted_movement(
228    gravity: f64,
229    moving_down: bool,
230    new_velocity: Vec3,
231    sprinting: Sprinting,
232) -> Vec3 {
233    if gravity != 0. && !*sprinting {
234        let new_y_velocity = if moving_down
235            && (new_velocity.y - 0.005).abs() >= 0.003
236            && f64::abs(new_velocity.y - gravity / 16.0) < 0.003
237        {
238            -0.003
239        } else {
240            new_velocity.y - gravity / 16.0
241        };
242
243        Vec3 {
244            x: new_velocity.x,
245            y: new_y_velocity,
246            z: new_velocity.z,
247        }
248    } else {
249        new_velocity
250    }
251}
252
253fn is_free(
254    world: &World,
255    source_entity: Entity,
256    aabb_query: &AabbQuery,
257    collidable_entity_query: &CollidableEntityQuery,
258    entity_physics: &Physics,
259    bounding_box: Aabb,
260    delta: Vec3,
261) -> bool {
262    let bounding_box = bounding_box.move_relative(delta);
263
264    no_collision(
265        world,
266        Some(source_entity),
267        aabb_query,
268        collidable_entity_query,
269        entity_physics,
270        &bounding_box,
271        false,
272    ) && !contains_any_liquid(world, bounding_box)
273}
274
275pub fn no_collision(
276    world: &World,
277    source_entity: Option<Entity>,
278    aabb_query: &AabbQuery,
279    collidable_entity_query: &CollidableEntityQuery,
280    entity_physics: &Physics,
281    aabb: &Aabb,
282    include_liquid_collisions: bool,
283) -> bool {
284    let collisions = if include_liquid_collisions {
285        get_block_and_liquid_collisions(world, aabb)
286    } else {
287        get_block_collisions(world, aabb)
288    };
289
290    for collision in collisions {
291        if !collision.is_empty() {
292            return false;
293        }
294    }
295
296    if !get_entity_collisions(
297        world,
298        aabb,
299        source_entity,
300        aabb_query,
301        collidable_entity_query,
302    )
303    .is_empty()
304    {
305        false
306    } else if source_entity.is_none() {
307        true
308    } else {
309        let collision = border_collision(entity_physics, aabb);
310        if let Some(collision) = collision {
311            // !Shapes.joinIsNotEmpty(collision, Shapes.create(aabb), BooleanOp.AND);
312            !Shapes::matches_anywhere(&collision.into(), &aabb.into(), |a, b| a && b)
313        } else {
314            true
315        }
316    }
317}
318
319fn border_collision(_entity_physics: &Physics, _aabb: &Aabb) -> Option<Aabb> {
320    // TODO: implement world border, see CollisionGetter.borderCollision
321
322    None
323}
324
325fn contains_any_liquid(world: &World, bounding_box: Aabb) -> bool {
326    let min = bounding_box.min.to_block_pos_floor();
327    let max = bounding_box.max.to_block_pos_ceil();
328
329    for x in min.x..max.x {
330        for y in min.y..max.y {
331            for z in min.z..max.z {
332                let block_state = world
333                    .chunks
334                    .get_block_state(BlockPos::new(x, y, z))
335                    .unwrap_or_default();
336                if !FluidState::from(block_state).is_empty() {
337                    return true;
338                }
339            }
340        }
341    }
342
343    false
344}
345
346fn get_effective_gravity() -> f64 {
347    // TODO: slow falling effect
348    0.08
349}
350
351pub fn fluid_jump_threshold() -> f64 {
352    // this is 0.0 for entities with an eye height lower than 0.4, but that's not
353    // implemented since it's usually not relevant for players (unless the player
354    // was shrunk)
355    0.4
356}