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,
9 metadata::{FallFlying, Sprinting},
10 move_relative, view_vector,
11};
12use azalea_world::{World, WorldName, Worlds};
13use bevy_ecs::prelude::*;
14
15use crate::{
16 client_movement::ClientMovementState,
17 collision::{
18 MoveCtx, MoverType, Shapes,
19 entity_collisions::{AabbQuery, CollidableEntityQuery, get_entity_collisions},
20 move_colliding,
21 world_collisions::{get_block_and_liquid_collisions, get_block_collisions},
22 },
23 get_block_pos_below_that_affects_movement, handle_relative_friction_and_calculate_movement,
24};
25
26#[allow(clippy::type_complexity)]
29pub fn travel(
30 mut query: Query<
31 (
32 Entity,
33 &Attributes,
34 &WorldName,
35 &OnClimbable,
36 &Jumping,
37 Option<&ClientMovementState>,
38 Option<&Sprinting>,
39 Option<&Pose>,
40 Option<&PlayerAbilities>,
41 &mut Physics,
42 &mut LookDirection,
43 &mut Position,
44 Option<&mut FallFlying>,
45 ),
46 (With<LocalEntity>, With<HasClientLoaded>),
47 >,
48 worlds: Res<Worlds>,
49 aabb_query: AabbQuery,
50 collidable_entity_query: CollidableEntityQuery,
51) {
52 for (
53 entity,
54 attributes,
55 world_name,
56 on_climbable,
57 jumping,
58 physics_state,
59 sprinting,
60 pose,
61 abilities,
62 mut physics,
63 direction,
64 position,
65 fall_flying,
66 ) in &mut query
67 {
68 let Some(world_lock) = worlds.get(world_name) else {
69 continue;
70 };
71 let world = world_lock.read();
72
73 let sprinting = *sprinting.unwrap_or(&Sprinting(false));
74
75 let mut ctx = MoveCtx {
76 mover_type: MoverType::Own,
77 world: &world,
78 position,
79 physics: &mut physics,
80 source_entity: entity,
81 aabb_query: &aabb_query,
82 collidable_entity_query: &collidable_entity_query,
83 physics_state,
84 attributes,
85 abilities,
86 direction: *direction,
87 sprinting,
88 on_climbable: *on_climbable,
89 pose: pose.copied(),
90 jumping: *jumping,
91 };
92
93 if ctx.physics.is_in_water() || ctx.physics.is_in_lava() {
94 travel_in_fluid(&mut ctx);
98 } else if fall_flying
99 .as_deref()
100 .is_some_and(|fall_flying| **fall_flying)
101 {
102 travel_fall_flying(&mut ctx, &mut fall_flying.unwrap());
103 } else {
104 travel_in_air(&mut ctx);
105 }
106 }
107}
108
109fn travel_in_air(ctx: &mut MoveCtx) {
111 let gravity = get_effective_gravity();
112
113 let block_pos_below = get_block_pos_below_that_affects_movement(*ctx.position);
114
115 let block_below = ctx
116 .world
117 .chunks
118 .get_block_state(block_pos_below)
119 .unwrap_or(BlockState::AIR);
120
121 let block_friction = block_below.behavior().friction;
122 let inertia = if ctx.physics.on_ground() {
123 block_friction * 0.91
124 } else {
125 0.91
126 };
127
128 let mut movement = handle_relative_friction_and_calculate_movement(ctx, block_friction);
130
131 movement.y -= gravity;
132
133 if false {
141 ctx.physics.velocity = movement;
142 } else {
143 ctx.physics.velocity = Vec3 {
144 x: movement.x * inertia as f64,
145 y: movement.y * 0.9800000190734863f64,
146 z: movement.z * inertia as f64,
147 };
148 }
149}
150
151fn travel_in_fluid(ctx: &mut MoveCtx) {
152 let moving_down = ctx.physics.velocity.y <= 0.;
153 let y = ctx.position.y;
154 let gravity = get_effective_gravity();
155
156 let acceleration = Vec3::new(
157 ctx.physics.x_acceleration as f64,
158 ctx.physics.y_acceleration as f64,
159 ctx.physics.z_acceleration as f64,
160 );
161
162 if ctx.physics.was_touching_water {
163 let mut water_movement_speed = if *ctx.sprinting { 0.9 } else { 0.8 };
164 let mut speed = 0.02;
165 let mut water_efficiency_modifier =
166 ctx.attributes.water_movement_efficiency.calculate() as f32;
167 if !ctx.physics.on_ground() {
168 water_efficiency_modifier *= 0.5;
169 }
170
171 if water_efficiency_modifier > 0. {
172 water_movement_speed += (0.54600006 - water_movement_speed) * water_efficiency_modifier;
173 speed += (ctx.attributes.movement_speed.calculate() as f32 - speed)
174 * water_efficiency_modifier;
175 }
176
177 move_relative(ctx.physics, ctx.direction, speed, acceleration);
182 move_colliding(ctx, ctx.physics.velocity);
183
184 let mut new_velocity = ctx.physics.velocity;
185 if ctx.physics.horizontal_collision && *ctx.on_climbable {
186 new_velocity.y = 0.2;
188 }
189 new_velocity.x *= water_movement_speed as f64;
190 new_velocity.y *= 0.8;
191 new_velocity.z *= water_movement_speed as f64;
192 ctx.physics.velocity =
193 get_fluid_falling_adjusted_movement(gravity, moving_down, new_velocity, ctx.sprinting);
194 } else {
195 move_relative(ctx.physics, ctx.direction, 0.02, acceleration);
196 move_colliding(ctx, ctx.physics.velocity);
197
198 if ctx.physics.lava_fluid_height <= fluid_jump_threshold() {
199 ctx.physics.velocity.x *= 0.5;
200 ctx.physics.velocity.y *= 0.8;
201 ctx.physics.velocity.z *= 0.5;
202 let new_velocity = get_fluid_falling_adjusted_movement(
203 gravity,
204 moving_down,
205 ctx.physics.velocity,
206 ctx.sprinting,
207 );
208 ctx.physics.velocity = new_velocity;
209 } else {
210 ctx.physics.velocity *= 0.5;
211 }
212
213 if gravity != 0.0 {
214 ctx.physics.velocity.y -= gravity / 4.0;
215 }
216 }
217
218 let velocity = ctx.physics.velocity;
219 if ctx.physics.horizontal_collision
220 && is_free(
221 ctx.world,
222 ctx.source_entity,
223 ctx.aabb_query,
224 ctx.collidable_entity_query,
225 ctx.physics,
226 ctx.physics.bounding_box,
227 velocity.up(0.6).down(ctx.position.y).up(y),
228 )
229 {
230 ctx.physics.velocity.y = 0.3;
231 }
232}
233
234fn travel_fall_flying(ctx: &mut MoveCtx, fall_flying: &mut FallFlying) {
235 if *ctx.on_climbable {
236 travel_in_air(ctx);
237 **fall_flying = false; } else {
239 let look = ctx.direction;
240 let look_angle = view_vector(look);
241
242 let lean_angle = look.x_rot().to_radians();
243
244 let look_horizontal_length =
245 (look_angle.x * look_angle.x + look_angle.z * look_angle.z).sqrt();
246 let move_horizontal_length = ctx.physics.velocity.horizontal_distance();
247 let gravity = get_effective_gravity();
248
249 let lift_force = f64::from(lean_angle).cos().powi(2);
251
252 let mut movement = ctx.physics.velocity;
253
254 movement.y += gravity * (-1.0 + lift_force * 0.75);
255 if movement.y < 0.0 && look_horizontal_length > 0.0 {
256 let convert = movement.y * -0.1 * lift_force;
257 movement += Vec3::new(
258 look_angle.x * convert / look_horizontal_length,
259 convert,
260 look_angle.z * convert / look_horizontal_length,
261 );
262 }
263
264 if lean_angle < 0.0 && look_horizontal_length > 0.0 {
265 let convert =
266 move_horizontal_length * -azalea_core::math::sin(lean_angle) as f64 * 0.04;
267 movement += Vec3::new(
268 -look_angle.x * convert / look_horizontal_length,
269 convert * 3.2,
270 -look_angle.z * convert / look_horizontal_length,
271 );
272 }
273
274 if look_horizontal_length > 0.0 {
275 movement += Vec3::new(
276 (look_angle.x / look_horizontal_length * move_horizontal_length - movement.x) * 0.1,
277 0.0,
278 (look_angle.z / look_horizontal_length * move_horizontal_length - movement.z) * 0.1,
279 );
280 }
281
282 ctx.physics.velocity = movement.multiply(0.99f32 as f64, 0.98f32 as f64, 0.99f32 as f64);
283
284 move_colliding(ctx, ctx.physics.velocity);
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: &World,
316 source_entity: Entity,
317 aabb_query: &AabbQuery,
318 collidable_entity_query: &CollidableEntityQuery,
319 entity_physics: &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 aabb_query,
329 collidable_entity_query,
330 entity_physics,
331 &bounding_box,
332 false,
333 ) && !contains_any_liquid(world, bounding_box)
334}
335
336pub fn no_collision(
337 world: &World,
338 source_entity: Option<Entity>,
339 aabb_query: &AabbQuery,
340 collidable_entity_query: &CollidableEntityQuery,
341 entity_physics: &Physics,
342 aabb: &Aabb,
343 include_liquid_collisions: bool,
344) -> bool {
345 let collisions = if include_liquid_collisions {
346 get_block_and_liquid_collisions(world, aabb)
347 } else {
348 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 aabb_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::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 None
384}
385
386fn contains_any_liquid(world: &World, 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 0.08
410}
411
412pub fn fluid_jump_threshold() -> f64 {
413 0.4
417}