azalea_physics/
travel.rs

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