azalea_physics/
travel.rs

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