package net.minecraft.world.level; import com.google.common.collect.Iterables; import java.util.List; import java.util.Optional; import java.util.stream.StreamSupport; import net.minecraft.core.BlockPos; import net.minecraft.core.Direction; import net.minecraft.world.entity.Entity; import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.border.WorldBorder; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.BlockHitResult; import net.minecraft.world.phys.Vec3; import net.minecraft.world.phys.shapes.BooleanOp; import net.minecraft.world.phys.shapes.CollisionContext; import net.minecraft.world.phys.shapes.Shapes; import net.minecraft.world.phys.shapes.VoxelShape; import org.jspecify.annotations.Nullable; public interface CollisionGetter extends BlockGetter { WorldBorder getWorldBorder(); @Nullable BlockGetter getChunkForCollisions(int chunkX, int chunkZ); default boolean isUnobstructed(@Nullable final Entity source, final VoxelShape shape) { return true; } default boolean isUnobstructed(final BlockState state, final BlockPos pos, final CollisionContext context) { VoxelShape shape = state.getCollisionShape(this, pos, context); return shape.isEmpty() || this.isUnobstructed(null, shape.move(pos)); } default boolean isUnobstructed(final Entity ignore) { return this.isUnobstructed(ignore, Shapes.create(ignore.getBoundingBox())); } default boolean noCollision(final AABB aabb) { return this.noCollision(null, aabb); } default boolean noCollision(final Entity source) { return this.noCollision(source, source.getBoundingBox()); } default boolean noCollision(@Nullable final Entity entity, final AABB aabb) { return this.noCollision(entity, aabb, false); } default boolean noCollision(@Nullable final Entity entity, final AABB aabb, final boolean alwaysCollideWithFluids) { return this.noBlockCollision(entity, aabb, alwaysCollideWithFluids) && this.noEntityCollision(entity, aabb) && this.noBorderCollision(entity, aabb); } default boolean noBlockCollision(@Nullable final Entity entity, final AABB aabb) { return this.noBlockCollision(entity, aabb, false); } default boolean noBlockCollision(@Nullable final Entity entity, final AABB aabb, final boolean alwaysCollideWithFluids) { for (VoxelShape blockCollision : alwaysCollideWithFluids ? this.getBlockAndLiquidCollisions(entity, aabb) : this.getBlockCollisions(entity, aabb)) { if (!blockCollision.isEmpty()) { return false; } } return true; } default boolean noEntityCollision(@Nullable final Entity entity, final AABB aabb) { return this.getEntityCollisions(entity, aabb).isEmpty(); } default boolean noBorderCollision(@Nullable final Entity entity, final AABB aabb) { if (entity == null) { return true; } else { VoxelShape borderShape = this.borderCollision(entity, aabb); return borderShape == null || !Shapes.joinIsNotEmpty(borderShape, Shapes.create(aabb), BooleanOp.AND); } } List getEntityCollisions(@Nullable final Entity source, final AABB testArea); default Iterable getCollisions(@Nullable final Entity source, final AABB box) { List entityCollisions = this.getEntityCollisions(source, box); Iterable blockCollisions = this.getBlockCollisions(source, box); return entityCollisions.isEmpty() ? blockCollisions : Iterables.concat(entityCollisions, blockCollisions); } default Iterable getPreMoveCollisions(@Nullable final Entity source, final AABB box, final Vec3 oldPos) { List entityCollisions = this.getEntityCollisions(source, box); Iterable blockCollisions = this.getBlockCollisionsFromContext(CollisionContext.withPosition(source, oldPos.y), box); return entityCollisions.isEmpty() ? blockCollisions : Iterables.concat(entityCollisions, blockCollisions); } default Iterable getBlockCollisions(@Nullable final Entity source, final AABB box) { return this.getBlockCollisionsFromContext(source == null ? CollisionContext.empty() : CollisionContext.of(source), box); } default Iterable getBlockAndLiquidCollisions(@Nullable final Entity source, final AABB box) { return this.getBlockCollisionsFromContext(source == null ? CollisionContext.emptyWithFluidCollisions() : CollisionContext.of(source, true), box); } default Iterable getBlockCollisionsFromContext(final CollisionContext source, final AABB box) { return () -> new BlockCollisions(this, source, box, false, (p, shape) -> shape); } @Nullable private VoxelShape borderCollision(final Entity source, final AABB box) { WorldBorder worldBorder = this.getWorldBorder(); return worldBorder.isInsideCloseToBorder(source, box) ? worldBorder.getCollisionShape() : null; } default BlockHitResult clipIncludingBorder(final ClipContext c) { BlockHitResult hitResult = this.clip(c); WorldBorder worldBorder = this.getWorldBorder(); if (worldBorder.isWithinBounds(c.getFrom()) && !worldBorder.isWithinBounds(hitResult.getLocation())) { Vec3 delta = hitResult.getLocation().subtract(c.getFrom()); Direction deltaDirection = Direction.getApproximateNearest(delta.x, delta.y, delta.z); Vec3 hit = worldBorder.clampVec3ToBound(hitResult.getLocation()); return new BlockHitResult(hit, deltaDirection, BlockPos.containing(hit), false, true); } else { return hitResult; } } default boolean collidesWithSuffocatingBlock(@Nullable final Entity source, final AABB box) { BlockCollisions blockCollisions = new BlockCollisions<>(this, source, box, true, (p, shape) -> shape); while (blockCollisions.hasNext()) { if (!blockCollisions.next().isEmpty()) { return true; } } return false; } default Optional findSupportingBlock(final Entity source, final AABB box) { BlockPos mainSupport = null; double mainSupportDistance = Double.MAX_VALUE; BlockCollisions blockCollisions = new BlockCollisions<>(this, source, box, false, (posx, shape) -> posx); while (blockCollisions.hasNext()) { BlockPos pos = blockCollisions.next(); double distance = pos.distToCenterSqr(source.position()); if (distance < mainSupportDistance || distance == mainSupportDistance && (mainSupport == null || mainSupport.compareTo(pos) < 0)) { mainSupport = pos.immutable(); mainSupportDistance = distance; } } return Optional.ofNullable(mainSupport); } default Optional findFreePosition( @Nullable final Entity source, final VoxelShape allowedCenters, final Vec3 preferredCenter, final double sizeX, final double sizeY, final double sizeZ ) { if (allowedCenters.isEmpty()) { return Optional.empty(); } else { AABB searchArea = allowedCenters.bounds().inflate(sizeX, sizeY, sizeZ); VoxelShape expandedCollisions = (VoxelShape)StreamSupport.stream(this.getBlockCollisions(source, searchArea).spliterator(), false) .filter(shape -> this.getWorldBorder() == null || this.getWorldBorder().isWithinBounds(shape.bounds())) .flatMap(shape -> shape.toAabbs().stream()) .map(aabb -> aabb.inflate(sizeX / 2.0, sizeY / 2.0, sizeZ / 2.0)) .map(Shapes::create) .reduce(Shapes.empty(), Shapes::or); VoxelShape freeSpots = Shapes.join(allowedCenters, expandedCollisions, BooleanOp.ONLY_FIRST); return freeSpots.closestPointTo(preferredCenter); } } }