--- a/net/minecraft/world/entity/ai/navigation/PathNavigation.java
+++ b/net/minecraft/world/entity/ai/navigation/PathNavigation.java
@@ -109,7 +109,13 @@
 
     @Nullable
     public Path createPath(BlockPos target, int distance) {
-        return this.createPath(ImmutableSet.of(target), 8, false, distance);
+        // Paper start - EntityPathfindEvent
+        return this.createPath(target, null, distance);
+    }
+    @Nullable
+    public Path createPath(BlockPos target, @Nullable Entity entity, int distance) {
+        return this.createPath(ImmutableSet.of(target), entity, 8, false, distance);
+        // Paper end - EntityPathfindEvent
     }
 
     @Nullable
@@ -119,7 +125,7 @@
 
     @Nullable
     public Path createPath(Entity entity, int distance) {
-        return this.createPath(ImmutableSet.of(entity.blockPosition()), 16, true, distance);
+        return this.createPath(ImmutableSet.of(entity.blockPosition()), entity, 16, true, distance); // Paper - EntityPathfindEvent
     }
 
     @Nullable
@@ -129,6 +135,17 @@
 
     @Nullable
     protected Path createPath(Set<BlockPos> positions, int range, boolean useHeadPos, int distance, float followRange) {
+        // Paper start - EntityPathfindEvent
+        return this.createPath(positions, null, range, useHeadPos, distance, followRange);
+    }
+
+    @Nullable
+    protected Path createPath(Set<BlockPos> positions, @Nullable Entity target, int range, boolean useHeadPos, int distance) {
+        return this.createPath(positions, target, range, useHeadPos, distance, (float) this.mob.getAttributeValue(Attributes.FOLLOW_RANGE));
+    }
+
+    @Nullable protected Path createPath(Set<BlockPos> positions, @Nullable Entity target, int range, boolean useHeadPos, int distance, float followRange) {
+        // Paper end - EntityPathfindEvent
         if (positions.isEmpty()) {
             return null;
         } else if (this.mob.getY() < (double)this.level.getMinBuildHeight()) {
@@ -138,6 +155,23 @@
         } else if (this.path != null && !this.path.isDone() && positions.contains(this.targetPos)) {
             return this.path;
         } else {
+            // Paper start - EntityPathfindEvent
+            boolean copiedSet = false;
+            for (BlockPos possibleTarget : positions) {
+                if (!this.mob.getCommandSenderWorld().getWorldBorder().isWithinBounds(possibleTarget) || !new com.destroystokyo.paper.event.entity.EntityPathfindEvent(this.mob.getBukkitEntity(), // Paper - don't path out of world border
+                    io.papermc.paper.util.MCUtil.toLocation(this.mob.level(), possibleTarget), target == null ? null : target.getBukkitEntity()).callEvent()) {
+                    if (!copiedSet) {
+                        copiedSet = true;
+                        positions = new java.util.HashSet<>(positions);
+                    }
+                    // note: since we copy the set this remove call is safe, since we're iterating over the old copy
+                    positions.remove(possibleTarget);
+                    if (positions.isEmpty()) {
+                        return null;
+                    }
+                }
+            }
+            // Paper end - EntityPathfindEvent
             this.level.getProfiler().push("pathfind");
             BlockPos blockPos = useHeadPos ? this.mob.blockPosition().above() : this.mob.blockPosition();
             int i = (int)(followRange + (float)range);
@@ -158,13 +192,33 @@
         return this.moveTo(this.createPath(x, y, z, 1), speed);
     }
 
+    // Paper start - Perf: Optimise pathfinding
+    private int lastFailure = 0;
+    private int pathfindFailures = 0;
+    // Paper end - Perf: Optimise pathfinding
+
     public boolean moveTo(double x, double y, double z, int distance, double speed) {
         return this.moveTo(this.createPath(x, y, z, distance), speed);
     }
 
     public boolean moveTo(Entity entity, double speed) {
+        // Paper start - Perf: Optimise pathfinding
+        if (this.pathfindFailures > 10 && this.path == null && net.minecraft.server.MinecraftServer.currentTick < this.lastFailure + 40) {
+            return false;
+        }
+        // Paper end - Perf: Optimise pathfinding
         Path path = this.createPath(entity, 1);
-        return path != null && this.moveTo(path, speed);
+        // Paper start - Perf: Optimise pathfinding
+        if (path != null && this.moveTo(path, speed)) {
+            this.lastFailure = 0;
+            this.pathfindFailures = 0;
+            return true;
+        } else {
+            this.pathfindFailures++;
+            this.lastFailure = net.minecraft.server.MinecraftServer.currentTick;
+            return false;
+        }
+        // Paper end - Perf: Optimise pathfinding
     }
 
     public boolean moveTo(@Nullable Path path, double speed) {
