1mod blocks;
2mod discrete_voxel_shape;
3pub mod entity_collisions;
4mod mergers;
5mod shape;
6mod shape_offset;
7pub mod world_collisions;
8
9use std::{ops::Add, sync::LazyLock};
10
11use azalea_block::{BlockState, BlockTrait, fluid_state::FluidState};
12use azalea_core::{
13 aabb::Aabb,
14 direction::Axis,
15 math::{self, EPSILON},
16 position::{BlockPos, Vec3},
17};
18use azalea_entity::{
19 Attributes, Jumping, LookDirection, OnClimbable, Physics, PlayerAbilities, Pose, Position,
20 metadata::Sprinting,
21};
22use azalea_registry::builtin::BlockKind;
23use azalea_world::{ChunkStorage, World};
24use bevy_ecs::{entity::Entity, world::Mut};
25pub use blocks::BlockWithShape;
26pub use discrete_voxel_shape::*;
27use entity_collisions::{CollidableEntityQuery, get_entity_collisions};
28pub use shape::*;
29use tracing::warn;
30
31use self::world_collisions::get_block_collisions;
32use crate::{
33 client_movement::ClientMovementState, collision::entity_collisions::AabbQuery,
34 travel::no_collision,
35};
36
37#[derive(Clone, Copy, Debug, Eq, PartialEq)]
38pub enum MoverType {
39 Own,
40 Player,
41 Piston,
42 ShulkerBox,
43 Shulker,
44}
45
46fn collide(ctx: &MoveCtx, movement: Vec3) -> Vec3 {
48 let entity_bounding_box = ctx.physics.bounding_box;
49 let entity_collisions = get_entity_collisions(
50 ctx.world,
51 &entity_bounding_box.expand_towards(movement),
52 Some(ctx.source_entity),
53 ctx.aabb_query,
54 ctx.collidable_entity_query,
55 );
56 let world = ctx.world;
57 let collided_delta = if movement.length_squared() == 0.0 {
58 movement
59 } else {
60 collide_bounding_box(movement, &entity_bounding_box, world, &entity_collisions)
61 };
62
63 let x_collision = movement.x != collided_delta.x;
64 let y_collision = movement.y != collided_delta.y;
65 let z_collision = movement.z != collided_delta.z;
66
67 let on_ground = ctx.physics.on_ground() || y_collision && movement.y < 0.;
68
69 let max_up_step = 0.6;
70 if max_up_step > 0. && on_ground && (x_collision || z_collision) {
71 let mut step_to_delta = collide_bounding_box(
72 movement.with_y(max_up_step),
73 &entity_bounding_box,
74 world,
75 &entity_collisions,
76 );
77 let directly_up_delta = collide_bounding_box(
78 Vec3::ZERO.with_y(max_up_step),
79 &entity_bounding_box.expand_towards(Vec3::new(movement.x, 0., movement.z)),
80 world,
81 &entity_collisions,
82 );
83 if directly_up_delta.y < max_up_step {
84 let target_movement = collide_bounding_box(
85 movement.with_y(0.),
86 &entity_bounding_box.move_relative(directly_up_delta),
87 world,
88 &entity_collisions,
89 )
90 .add(directly_up_delta);
91 if target_movement.horizontal_distance_squared()
92 > step_to_delta.horizontal_distance_squared()
93 {
94 step_to_delta = target_movement;
95 }
96 }
97
98 if step_to_delta.horizontal_distance_squared()
99 > collided_delta.horizontal_distance_squared()
100 {
101 return step_to_delta.add(collide_bounding_box(
102 Vec3::ZERO.with_y(-step_to_delta.y + movement.y),
103 &entity_bounding_box.move_relative(step_to_delta),
104 world,
105 &entity_collisions,
106 ));
107 }
108 }
109
110 collided_delta
111}
112
113pub struct MoveCtx<'world, 'state, 'a, 'b> {
114 pub mover_type: MoverType,
115 pub world: &'a World,
116 pub position: Mut<'a, Position>,
117 pub physics: &'a mut Physics,
118 pub source_entity: Entity,
119 pub aabb_query: &'a AabbQuery<'world, 'state, 'b>,
120 pub collidable_entity_query: &'a CollidableEntityQuery<'world, 'state>,
121 pub physics_state: Option<&'a ClientMovementState>,
122 pub attributes: &'a Attributes,
123 pub abilities: Option<&'a PlayerAbilities>,
124
125 pub direction: LookDirection,
126 pub sprinting: Sprinting,
127 pub on_climbable: OnClimbable,
128 pub pose: Option<Pose>,
129 pub jumping: Jumping,
130}
131
132pub fn move_colliding(ctx: &mut MoveCtx, mut movement: Vec3) {
136 movement = maybe_back_off_from_edge(ctx, movement);
156 let collide_result = collide(ctx, movement);
157
158 let move_distance_sqr = collide_result.length_squared();
159
160 let position = &mut ctx.position;
161 let physics = &mut *ctx.physics;
162 let world = ctx.world;
163
164 if move_distance_sqr > EPSILON || movement.length_squared() - move_distance_sqr < EPSILON {
165 let new_pos = {
168 Vec3 {
169 x: position.x + collide_result.x,
170 y: position.y + collide_result.y,
171 z: position.z + collide_result.z,
172 }
173 };
174
175 if new_pos != ***position {
176 ***position = new_pos;
177 }
178 }
179
180 let x_collision = !math::equal(movement.x, collide_result.x);
181 let z_collision = !math::equal(movement.z, collide_result.z);
182 let horizontal_collision = x_collision || z_collision;
183 physics.horizontal_collision = horizontal_collision;
184
185 let vertical_collision = movement.y != collide_result.y;
186 physics.vertical_collision = vertical_collision;
187 let on_ground = vertical_collision && movement.y < 0.;
188 physics.set_on_ground(on_ground);
189
190 let block_pos_below = azalea_entity::on_pos_legacy(&world.chunks, **position);
193 let block_state_below = world.get_block_state(block_pos_below).unwrap_or_default();
194
195 check_fall_damage(
196 physics,
197 collide_result.y,
198 block_state_below,
199 block_pos_below,
200 );
201
202 if horizontal_collision {
205 let delta_movement = &physics.velocity;
206 physics.velocity = Vec3 {
207 x: if x_collision { 0. } else { delta_movement.x },
208 y: delta_movement.y,
209 z: if z_collision { 0. } else { delta_movement.z },
210 }
211 }
212
213 if vertical_collision {
214 physics.velocity.y = 0.;
218 }
219
220 if on_ground {
221 }
224
225 }
248
249fn check_fall_damage(
250 physics: &mut Physics,
251 delta_y: f64,
252 _block_state_below: BlockState,
253 _block_pos_below: BlockPos,
254) {
255 if !physics.is_in_water() && delta_y < 0. {
256 physics.fall_distance -= delta_y as f32 as f64;
257 }
258
259 if physics.on_ground() {
260 physics.fall_distance = 0.;
263 }
264}
265
266fn maybe_back_off_from_edge(move_ctx: &mut MoveCtx, mut movement: Vec3) -> Vec3 {
267 let is_staying_on_ground_surface = move_ctx.physics_state.is_some_and(|s| s.trying_to_crouch);
268 let max_up_step = get_max_up_step(move_ctx.attributes);
269
270 let fall_ctx = CanFallAtLeastCtx {
271 physics: move_ctx.physics,
272 world: move_ctx.world,
273 source_entity: move_ctx.source_entity,
274 aabb_query: move_ctx.aabb_query,
275 collidable_entity_query: move_ctx.collidable_entity_query,
276 };
277
278 let Some(abilities) = move_ctx.abilities else {
279 return movement;
280 };
281
282 let is_backing_off = !abilities.flying
283 && movement.y <= 0.
284 && matches!(move_ctx.mover_type, MoverType::Own | MoverType::Player)
285 && is_staying_on_ground_surface
286 && is_above_ground(&fall_ctx, max_up_step);
287 if !is_backing_off {
288 return movement;
289 }
290
291 let min_movement = 0.05;
292 let min_movement_x = movement.x.signum() * min_movement;
293 let min_movement_z = movement.z.signum() * min_movement;
294
295 while movement.x != 0. && can_fall_at_least(&fall_ctx, movement.x, 0., max_up_step as f64) {
296 if movement.x.abs() <= min_movement {
297 movement.x = 0.;
298 break;
299 }
300
301 movement.x -= min_movement_x
302 }
303 while movement.z != 0. && can_fall_at_least(&fall_ctx, 0., movement.z, max_up_step as f64) {
304 if movement.z.abs() <= min_movement {
305 movement.z = 0.;
306 break;
307 }
308
309 movement.z -= min_movement_z
310 }
311 while movement.x != 0.0
312 && movement.z != 0.0
313 && can_fall_at_least(&fall_ctx, movement.x, movement.z, max_up_step as f64)
314 {
315 if movement.x.abs() <= min_movement {
316 movement.x = 0.;
317 } else {
318 movement.x -= min_movement_x;
319 }
320 if movement.z.abs() <= min_movement {
321 movement.z = 0.;
322 } else {
323 movement.z -= min_movement_z;
324 }
325 }
326
327 movement
328}
329
330fn get_max_up_step(attributes: &Attributes) -> f32 {
331 attributes.step_height.calculate() as f32
333}
334
335fn is_above_ground(ctx: &CanFallAtLeastCtx, max_up_step: f32) -> bool {
336 ctx.physics.on_ground()
337 && ctx.physics.fall_distance < max_up_step as f64
338 && !can_fall_at_least(ctx, 0., 0., max_up_step as f64 - ctx.physics.fall_distance)
339}
340
341pub struct CanFallAtLeastCtx<'world, 'state, 'a, 'b> {
342 physics: &'a Physics,
343 world: &'a World,
344 source_entity: Entity,
345 aabb_query: &'a AabbQuery<'world, 'state, 'b>,
346 collidable_entity_query: &'a CollidableEntityQuery<'world, 'state>,
347}
348
349fn can_fall_at_least(
350 ctx: &CanFallAtLeastCtx,
351 delta_x: f64,
352 delta_z: f64,
353 max_up_step: f64,
354) -> bool {
355 let aabb = ctx.physics.bounding_box;
356 let aabb = Aabb {
357 min: Vec3 {
358 x: aabb.min.x + EPSILON + delta_x,
359 y: aabb.min.y - max_up_step - EPSILON,
360 z: aabb.min.z + EPSILON + delta_z,
361 },
362 max: Vec3 {
363 x: aabb.max.x - EPSILON + delta_x,
364 y: aabb.min.y,
365 z: aabb.max.z - EPSILON + delta_z,
366 },
367 };
368 no_collision(
369 ctx.world,
370 Some(ctx.source_entity),
371 ctx.aabb_query,
372 ctx.collidable_entity_query,
373 ctx.physics,
374 &aabb,
375 false,
376 )
377}
378
379fn collide_bounding_box(
380 movement: Vec3,
381 entity_bounding_box: &Aabb,
382 world: &World,
383 entity_collisions: &[VoxelShape],
384) -> Vec3 {
385 let mut collision_boxes: Vec<VoxelShape> = Vec::with_capacity(entity_collisions.len() + 1);
386
387 if !entity_collisions.is_empty() {
388 collision_boxes.extend_from_slice(entity_collisions);
389 }
390
391 let block_collisions =
394 get_block_collisions(world, &entity_bounding_box.expand_towards(movement));
395 collision_boxes.extend(block_collisions);
396 collide_with_shapes(movement, *entity_bounding_box, &collision_boxes)
397}
398
399fn collide_with_shapes(
400 mut movement: Vec3,
401 mut entity_box: Aabb,
402 collision_boxes: &[VoxelShape],
403) -> Vec3 {
404 if collision_boxes.is_empty() {
405 return movement;
406 }
407
408 if movement.y != 0. {
409 movement.y = Shapes::collide(Axis::Y, &entity_box, collision_boxes, movement.y);
410 if movement.y != 0. {
411 entity_box = entity_box.move_relative(Vec3::new(0., movement.y, 0.));
412 }
413 }
414
415 let more_z_movement = movement.x.abs() < movement.z.abs();
418
419 if more_z_movement && movement.z != 0. {
420 movement.z = Shapes::collide(Axis::Z, &entity_box, collision_boxes, movement.z);
421 if movement.z != 0. {
422 entity_box = entity_box.move_relative(Vec3::new(0., 0., movement.z));
423 }
424 }
425
426 if movement.x != 0. {
427 movement.x = Shapes::collide(Axis::X, &entity_box, collision_boxes, movement.x);
428 if movement.x != 0. {
429 entity_box = entity_box.move_relative(Vec3::new(movement.x, 0., 0.));
430 }
431 }
432
433 if !more_z_movement && movement.z != 0. {
434 movement.z = Shapes::collide(Axis::Z, &entity_box, collision_boxes, movement.z);
435 }
436
437 movement
438}
439
440pub fn fluid_shape(fluid: &FluidState, world: &ChunkStorage, pos: BlockPos) -> &'static VoxelShape {
445 if fluid.amount == 9 {
446 let fluid_state_above = world.get_fluid_state(pos.up(1)).unwrap_or_default();
447 if fluid_state_above.kind == fluid.kind {
448 return &BLOCK_SHAPE;
449 }
450 }
451 if fluid.amount > 9 {
452 warn!("Tried to calculate shape for fluid with height > 9: {fluid:?} at {pos}");
453 return &EMPTY_SHAPE;
454 }
455
456 static FLUID_SHAPES: LazyLock<[VoxelShape; 10]> = LazyLock::new(|| {
460 [
461 calculate_shape_for_fluid(0),
462 calculate_shape_for_fluid(1),
463 calculate_shape_for_fluid(2),
464 calculate_shape_for_fluid(3),
465 calculate_shape_for_fluid(4),
466 calculate_shape_for_fluid(5),
467 calculate_shape_for_fluid(6),
468 calculate_shape_for_fluid(7),
469 calculate_shape_for_fluid(8),
470 calculate_shape_for_fluid(9),
471 ]
472 });
473
474 &FLUID_SHAPES[fluid.amount as usize]
475}
476fn calculate_shape_for_fluid(amount: u8) -> VoxelShape {
477 box_shape(0.0, 0.0, 0.0, 1.0, (f32::from(amount) / 9.0) as f64, 1.0)
478}
479
480pub fn legacy_blocks_motion(block: BlockState) -> bool {
484 if block == BlockState::AIR {
485 return false;
487 }
488
489 let registry_block = BlockKind::from(block);
490 legacy_calculate_solid(block)
491 && registry_block != BlockKind::Cobweb
492 && registry_block != BlockKind::BambooSapling
493}
494
495pub fn legacy_calculate_solid(block: BlockState) -> bool {
496 let block_trait = Box::<dyn BlockTrait>::from(block);
498 if let Some(solid) = block_trait.behavior().force_solid {
499 return solid;
500 }
501
502 let shape = block.base_collision_shape();
503 if shape.is_empty() {
504 return false;
505 }
506 let bounds = shape.bounds();
507 bounds.size() >= 0.7291666666666666 || bounds.get_size(Axis::Y) >= 1.0
508}