--- a/net/minecraft/world/entity/ai/navigation/PathNavigation.java
+++ b/net/minecraft/world/entity/ai/navigation/PathNavigation.java
@@ -125,7 +_,13 @@
 
     @Nullable
     public Path createPath(BlockPos pos, int accuracy) {
-        return this.createPath(ImmutableSet.of(pos), 8, false, accuracy);
+        // Paper start - EntityPathfindEvent
+        return this.createPath(pos, null, accuracy);
+    }
+    @Nullable
+    public Path createPath(BlockPos target, @Nullable Entity entity, int accuracy) {
+        return this.createPath(ImmutableSet.of(target), entity, 8, false, accuracy);
+        // Paper end - EntityPathfindEvent
     }
 
     @Nullable
@@ -135,7 +_,7 @@
 
     @Nullable
     public Path createPath(Entity entity, int accuracy) {
-        return this.createPath(ImmutableSet.of(entity.blockPosition()), 16, true, accuracy);
+        return this.createPath(ImmutableSet.of(entity.blockPosition()), entity, 16, true, accuracy); // Paper - EntityPathfindEvent
     }
 
     @Nullable
@@ -145,6 +_,18 @@
 
     @Nullable
     protected Path createPath(Set<BlockPos> targets, int regionOffset, boolean offsetUpward, int accuracy, float followRange) {
+        // Paper start - EntityPathfindEvent
+        return this.createPath(targets, null, regionOffset, offsetUpward, accuracy, followRange);
+    }
+
+    @Nullable
+    protected Path createPath(Set<BlockPos> targets, @Nullable Entity target, int regionOffset, boolean offsetUpward, int accuracy) {
+        return this.createPath(targets, target, regionOffset, offsetUpward, accuracy, this.getMaxPathLength());
+    }
+
+    @Nullable
+    protected Path createPath(Set<BlockPos> targets, @Nullable Entity target, int regionOffset, boolean offsetUpward, int accuracy, float followRange) {
+        // Paper end - EntityPathfindEvent
         if (targets.isEmpty()) {
             return null;
         } else if (this.mob.getY() < this.level.getMinY()) {
@@ -154,6 +_,23 @@
         } else if (this.path != null && !this.path.isDone() && targets.contains(this.targetPos)) {
             return this.path;
         } else {
+            // Paper start - EntityPathfindEvent
+            boolean copiedSet = false;
+            for (BlockPos possibleTarget : targets) {
+                if (!this.mob.level().getWorldBorder().isWithinBounds(possibleTarget) || !new com.destroystokyo.paper.event.entity.EntityPathfindEvent(this.mob.getBukkitEntity(), // Paper - don't path out of world border
+                    org.bukkit.craftbukkit.util.CraftLocation.toBukkit(possibleTarget, this.mob.level()), target == null ? null : target.getBukkitEntity()).callEvent()) {
+                    if (!copiedSet) {
+                        copiedSet = true;
+                        targets = new java.util.HashSet<>(targets);
+                    }
+                    // note: since we copy the set this remove call is safe, since we're iterating over the old copy
+                    targets.remove(possibleTarget);
+                    if (targets.isEmpty()) {
+                        return null;
+                    }
+                }
+            }
+            // Paper end - EntityPathfindEvent
             ProfilerFiller profilerFiller = Profiler.get();
             profilerFiller.push("pathfind");
             BlockPos blockPos = offsetUpward ? this.mob.blockPosition().above() : this.mob.blockPosition();
@@ -171,6 +_,11 @@
         }
     }
 
+    // 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, double speed) {
         return this.moveTo(this.createPath(x, y, z, 1), speed);
     }
@@ -180,8 +_,23 @@
     }
 
     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 pathentity, double speed) {
