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#[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 if physics.is_in_water() || physics.is_in_lava() {
69 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#[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 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 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 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 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::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: &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 0.08
410}
411
412pub fn fluid_jump_threshold() -> f64 {
413 0.4
417}