--- a/net/minecraft/world/entity/ai/navigation/PathNavigation.java
+++ b/net/minecraft/world/entity/ai/navigation/PathNavigation.java
@@ -10,6 +10,7 @@
 import net.minecraft.core.Vec3i;
 import net.minecraft.network.protocol.game.DebugPackets;
 import net.minecraft.tags.BlockTags;
+import net.minecraft.server.MCUtil;
 import net.minecraft.util.Mth;
 import net.minecraft.world.entity.Entity;
 import net.minecraft.world.entity.Mob;
@@ -28,7 +29,7 @@
 
 public abstract class PathNavigation {
     private static final int MAX_TIME_RECOMPUTE = 20;
-    protected final Mob mob;
+    protected final Mob mob; public final Mob getEntity() { return this.mob; } // Paper - public accessor
     protected final Level level;
     @Nullable
     protected Path path;
@@ -41,7 +42,7 @@
     protected long lastTimeoutCheck;
     protected double timeoutLimit;
     protected float maxDistanceToWaypoint = 0.5F;
-    protected boolean hasDelayedRecomputation;
+    protected boolean hasDelayedRecomputation; protected final boolean needsPathRecalculation() { return this.hasDelayedRecomputation; } // Paper - public accessor
     protected long timeLastRecompute;
     protected NodeEvaluator nodeEvaluator;
     @Nullable
@@ -108,7 +109,13 @@
 
     @Nullable
     public Path createPath(BlockPos target, int distance) {
-        return this.createPath(ImmutableSet.of(target), 8, false, distance);
+        // Paper start - add target entity parameter
+        return this.createPath(target, null, distance);
+    }
+    @Nullable
+    public Path createPath(BlockPos target, Entity entity, int distance) {
+        return this.createPath(ImmutableSet.of(target), entity, 8, false, distance);
+        // Paper end
     }
 
     @Nullable
@@ -118,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
     }
 
     @Nullable
@@ -128,6 +135,16 @@
 
     @Nullable
     protected Path createPath(Set<BlockPos> positions, int range, boolean useHeadPos, int distance, float followRange) {
+        return this.createPath(positions, null, range, useHeadPos, distance, (float) this.mob.getAttributeValue(Attributes.FOLLOW_RANGE));
+    }
+
+    @Nullable
+    protected Path createPath(Set<BlockPos> positions, 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, Entity target, int range, boolean useHeadPos, int distance, float followRange) {
+        // Paper end
         if (positions.isEmpty()) {
             return null;
         } else if (this.mob.getY() < (double)this.level.getMinBuildHeight()) {
@@ -137,6 +154,23 @@
         } else if (this.path != null && !this.path.isDone() && positions.contains(this.targetPos)) {
             return this.path;
         } else {
+            // Paper start - Pathfind event
+            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
+                    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
             this.level.getProfiler().push("pathfind");
             BlockPos blockPos = useHeadPos ? this.mob.blockPosition().above() : this.mob.blockPosition();
             int i = (int)(followRange + (float)range);
@@ -157,9 +191,29 @@
         return this.moveTo(this.createPath(x, y, z, 1), speed);
     }
 
+    // Paper start - optimise pathfinding
+    private int lastFailure = 0;
+    private int pathfindFailures = 0;
+    // Paper end
+
     public boolean moveTo(Entity entity, double speed) {
+        // Paper start - Pathfinding optimizations
+        if (this.pathfindFailures > 10 && this.path == null && net.minecraft.server.MinecraftServer.currentTick < this.lastFailure + 40) {
+            return false;
+        }
+        // Paper end
         Path path = this.createPath(entity, 1);
-        return path != null && this.moveTo(path, speed);
+        // Paper start - Pathfinding optimizations
+        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
     }
 
     public boolean moveTo(@Nullable Path path, double speed) {
@@ -366,7 +420,7 @@
     public boolean shouldRecomputePath(BlockPos pos) {
         if (this.hasDelayedRecomputation) {
             return false;
-        } else if (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0) {
+        } else if (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0) { // Paper - diff on change - needed for isViableForPathRecalculationChecking()
             Node node = this.path.getEndNode();
             Vec3 vec3 = new Vec3(((double)node.x + this.mob.getX()) / 2.0D, ((double)node.y + this.mob.getY()) / 2.0D, ((double)node.z + this.mob.getZ()) / 2.0D);
             return pos.closerToCenterThan(vec3, (double)(this.path.getNodeCount() - this.path.getNextNodeIndex()));
@@ -382,4 +436,11 @@
     public boolean isStuck() {
         return this.isStuck;
     }
+
+    // Paper start
+    public boolean isViableForPathRecalculationChecking() {
+        return !this.needsPathRecalculation() &&
+            (this.path != null && !this.path.isDone() && this.path.getNodeCount() != 0);
+    }
+    // Paper end
 }
