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#[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 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 travel_in_fluid(&mut ctx);
96 } else {
97 travel_in_air(&mut ctx);
98 }
99 }
100}
101
102fn 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 let mut movement = handle_relative_friction_and_calculate_movement(ctx, block_friction);
123
124 movement.y -= gravity;
125
126 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 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 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::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 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 0.08
349}
350
351pub fn fluid_jump_threshold() -> f64 {
352 0.4
356}