diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/RuneLiteWebWalkRuntime.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/RuneLiteWebWalkRuntime.java index 14c8dcfce4..4612d7c58d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/RuneLiteWebWalkRuntime.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/RuneLiteWebWalkRuntime.java @@ -570,15 +570,16 @@ static int routeActionIndex(List path, int currentIndex, { return index; } - if (reachable.contains(from) && !reachable.contains(to)) + // A reachable-from / unreachable-to frontier is not, by itself, proof that an + // executable route action exists. Promoting that generic collision boundary caused + // the executor to repeatedly call runtimeHandleRouteEdge(), whose handlers could not + // identify an actual door/transport/object; replanning then selected the same edge + // again. Only catalog-backed boundaries are action edges here. Unconfirmed frontiers + // remain normal pathfinding/movement responsibility. They still terminate this + // lookahead so a later catalog transport cannot leapfrog an unresolved frontier. + if (!catalogEdge && reachable.contains(from) && !reachable.contains(to)) { - // Preserve route ordering: a later catalog transport must not leapfrog an - // unresolved door/gate/frontier that is still outside generic interaction range. - boolean genericActionInRange = index <= currentIndex + GENERIC_ROUTE_EDGE_INDEX_LOOKAHEAD - && distance <= (catalogDoorEdge - ? CATALOG_DOOR_EDGE_ACTION_DISTANCE - : GENERIC_ROUTE_EDGE_ACTION_DISTANCE); - return genericActionInRange ? index : -1; + return -1; } } return -1; @@ -866,21 +867,33 @@ private boolean isCameraRequestCurrentLocked(CameraRequest request, WorldPoint p || request.targetGeneration != targetGeneration || request.routeSource != observedPathfinder || request.routeSource != Rs2PathApi.getPathfinder() - || request.path != lastRawPath || request.currentPathIndex != lastObservedPathIndex + || request.path != lastRawPath || lastObservedPathIndex < request.currentPathIndex || player == null || player.getPlane() != request.lookAhead.getPlane() || !isRouteOwnershipCurrent(target, targetGeneration, Rs2Walker.getCurrentTarget(), Rs2Walker.getCurrentTargetGeneration())) { return false; } - for (int index = request.currentPathIndex + 1; index < request.path.size(); index++) + return isCameraLookAheadStillAhead(request.path, + Math.max(request.currentPathIndex, lastObservedPathIndex), request.lookAhead, + player.getPlane()); + } + + static boolean isCameraLookAheadStillAhead(List path, int currentPathIndex, + WorldPoint lookAhead, int plane) + { + if (path == null || lookAhead == null || currentPathIndex < -1) { - WorldPoint candidate = request.path.get(index); - if (candidate == null || candidate.getPlane() != player.getPlane()) + return false; + } + for (int index = currentPathIndex + 1; index < path.size(); index++) + { + WorldPoint candidate = path.get(index); + if (candidate == null || candidate.getPlane() != plane) { break; } - if (candidate.equals(request.lookAhead)) + if (candidate.equals(lookAhead)) { return true; } diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/microbot/util/walker/Rs2WalkerWalkingCameraTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/microbot/util/walker/Rs2WalkerWalkingCameraTest.java index 39e284eb6c..4607b48b8b 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/microbot/util/walker/Rs2WalkerWalkingCameraTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/microbot/util/walker/Rs2WalkerWalkingCameraTest.java @@ -327,6 +327,17 @@ public void routeProgressPastLookAheadSuppressesQueuedYaw() throws Exception } } + @Test + public void forwardRouteProgressKeepsCameraLookaheadValid() + { + List path = List.of(PLAYER, point(6, 0), point(9, 0), point(12, 0)); + + assertTrue(RuneLiteWebWalkRuntime.isCameraLookAheadStillAhead( + path, 1, point(9, 0), PLAYER.getPlane())); + assertFalse(RuneLiteWebWalkRuntime.isCameraLookAheadStillAhead( + path, 2, point(9, 0), PLAYER.getPlane())); + } + @Test public void livePathfinderReplacementSuppressesQueuedYaw() throws Exception { diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/microbot/util/walker/RuneLiteWebWalkRuntimeTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/microbot/util/walker/RuneLiteWebWalkRuntimeTest.java index c0f86b1083..86b06c6b83 100644 --- a/runelite-client/src/test/java/net/runelite/client/plugins/microbot/util/walker/RuneLiteWebWalkRuntimeTest.java +++ b/runelite-client/src/test/java/net/runelite/client/plugins/microbot/util/walker/RuneLiteWebWalkRuntimeTest.java @@ -282,6 +282,27 @@ public void distantGenericRouteEdgeStillAllowsCloserApproach() } } + @Test + public void nearbyUnconfirmedRouteFrontierDoesNotBecomeAnActionEdge() + { + WorldPoint player = new WorldPoint(3005, 3336, 0); + WorldPoint blockedEdge = new WorldPoint(3006, 3337, 0); + List path = List.of(player, blockedEdge); + PathfinderConfig previousConfig = ShortestPathPlugin.pathfinderConfig; + PathfinderConfig config = mock(PathfinderConfig.class); + when(config.getTransports()).thenReturn(new java.util.concurrent.ConcurrentHashMap<>()); + try + { + ShortestPathPlugin.pathfinderConfig = config; + assertEquals("a collision frontier without a confirmed catalog action must remain ground movement", + -1, RuneLiteWebWalkRuntime.routeActionIndex(path, 0, player, Set.of(player))); + } + finally + { + ShortestPathPlugin.pathfinderConfig = previousConfig; + } + } + @Test public void unresolvedGenericFrontierPreventsLookaheadToLaterTransport() {