Skip to content
Merged

2.5.8 #1767

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ target/
### Test loop artifacts ###
logs/
REVIEW_FINDINGS.md.bak
.claude/scheduled_tasks.lock
__pycache__/
*.py[cod]

### Stray Node lockfiles (this is a Java/Gradle project) ###
node_modules/
Expand Down
1 change: 1 addition & 0 deletions docs/F2P_WEBWALKER_HARNESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,4 @@ The runner forwards route settings through `microbot.test.webwalker.*` system pr
| F2P-14 | `3092,3245,0` | `3109,3341,0` | Draynor Manor approach |
| F2P-15 | `3109,3341,0` | `3106,3363,0` | Draynor Manor door/object handling |
| F2P-16 | `3106,3363,0` | `3092,3245,0` | Reverse manor exit behavior |
| F2P-17 | current live player tile | `3237,9858,0` | Captures current origin, then walks to Varrock Sewers 5 times on a F2P world with agility shortcuts and teleports disabled |
102 changes: 102 additions & 0 deletions docs/entity-guides/movement.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,105 @@ waitForDoorInteractionProgress(fromWp, toWp);
**Where this applies:** `Rs2Walker.handleNearbyRawPathSceneObjects`, door handlers that call `Rs2GameObject.interact`, and any recovery logic that recurses into `processWalk`.

**Defensive check:** In live testing, a door should produce one interaction followed by movement/path progress, not repeated `Raw path door handler resolved obstacle` messages every tick while the player is moving.

## 5. Suppress the inverse adjacent transport after crossing a same-plane door

Some doors are represented in `transports.tsv` as two adjacent same-plane transports, one for each direction. After the walker clicks one side and arrives on the other, immediately accepting the inverse transport can bounce the player back through the same door instead of letting the next minimap step continue away from it. Mark both tiles of a successful adjacent same-plane transport as recently handled for a short window.

**Why this matters:** Leaving Draynor Manor through the east/back door can alternate between `3123,3360,0` and `3123,3361,0`, repeatedly logging raw-path/current-tile transport handling and burning the route timeout before walking back to Draynor.

**Pattern to follow:**

```java
boolean reachedDestination = sleepUntil(() -> atTransportDestination(transport), 5000);
if (reachedDestination && isAdjacentSamePlaneTransport(transport)) {
markStationaryDoorOpened(transport.getOrigin());
markStationaryDoorOpened(transport.getDestination());
}
```

**Where this applies:** `Rs2Walker.handleTransports`, current-tile transport recovery, raw-path transport probing, and bidirectional same-plane door/gate transports.

**Defensive check:** A successful adjacent same-plane transport should be followed by a minimap/path step away from the doorway, not by alternating `Raw path transport handler` and `Current-tile transport handler` logs for the same two tiles.

## 6. Recalculate after long-distance object transports

Not every large map transition changes plane or uses a teleport type. Some object transports, such as the Varrock Sewers ladder, remain on plane 0 while jumping between coordinate bands. After a successful object interaction reaches one of these destinations, run the normal transport finalizer so the shortest path is rebuilt from the new location.

**Why this matters:** A route from Varrock Sewers back to a surface origin can climb the ladder successfully, then continue using a path that was calculated from the underground coordinate band. The walker may drift off path or exit during setup even though the transport itself worked.

**Pattern to follow:**

```java
if (reachedDestination) {
markAdjacentSamePlaneTransportHandled(transport, object);
return finishHandledTransport(transport);
}
```

**Where this applies:** `Rs2Walker.handleTransports` object interactions and any object-transport handler that waits for the destination tile directly.

**Defensive check:** Same-plane object transports with a large `distanceTo2D` delta should produce a fresh pathfinder start near the post-transport player location before the next minimap step.

## 7. Model missing collision edges before tuning walker retries

Some static collision gaps are specific edges, not whole tiles. If the pathfinder repeatedly routes through a visible fence/wall and the live client keeps clicking fallback tiles near that boundary, add an explicit blocked edge to pathfinding and smoothing instead of trying to solve it with longer timeouts or broader minimap fallback.

**Why this matters:** The Varrock Palace garden south fence can be missing from the bundled collision map near `3229..3241,3472 -> 3471`. A no-agility F2P route to the Varrock Sewers manhole can walk around the trellis correctly, then stall against that garden boundary because the path says the south edge is traversable.

**Pattern to follow:**

```java
if (config.isBlockedTransportEdge(node.packedPosition, neighborPacked)) {
continue;
}
```

**Where this applies:** `CollisionMap.getNeighbors`, `PathSmoother.lineOfSight`, and any path data correction where only one edge between adjacent tiles is invalid.

**Defensive check:** Add a core pathfinder regression from the observed stuck tile; assert neither the raw path nor smoothed path crosses the blocked edge, and that the route still reaches the original destination.

## 8. Do not click a visible endpoint before honoring pending route interactions

An endpoint being visible on the minimap does not mean it is the next correct click. If the computed shortest path reaches that endpoint through an intermediate door, gate, transport, shortcut, ladder, or other route object, the walker must process the first route interaction before issuing a direct endpoint click.

**Why this matters:** From Varrock Palace, a destination such as `3229,3473,0` can be visible on the minimap while the shorter route requires opening the palace doors first. Clicking the endpoint lets the game choose a longer collision-valid detour and bypasses the webwalker's route.

**Pattern to follow:**

```java
if (handleNearbyRawPathSceneObjects(rawPath, HANDLER_RANGE)) {
return true;
}
if (!hasPendingExplicitTransportStepBeforeArrival(rawPath, target, distance)
&& !localRouteDetoursFromComputedRoute(rawPath, end, DIRECT_CLICK_MAX_DISTANCE)) {
walkMiniMap(end);
}
```

**Where this applies:** `Rs2Walker.walkWithStateInternal`, short local walk kick-starts, final/minimap endpoint clicks, and any future fast-path that bypasses normal path iteration.

**Defensive check:** Reproduce with closed Varrock Palace doors toward `3229,3473,0`; the first action should target the door or route waypoint, not the final endpoint tile.

## 9. Preserve interrupts so walker cancellation stops waits immediately

Ctrl+X and script shutdown cancel the active walk task with `Future.cancel(true)` and clear the walker target. Shared sleep/poll helpers must preserve the interrupted flag and stop polling when interruption is observed; otherwise the walker can continue through several timeout cycles before noticing the cleared target.

**Why this matters:** A user pressing Ctrl+X expects the webwalker to stop issuing route actions immediately. If `InterruptedException` is swallowed, long waits in object, transport, dialogue, or animation handling can keep cycling until their normal timeout elapses.

**Pattern to follow:**

```java
try {
Thread.sleep(delayMs);
} catch (InterruptedException ignored) {
Thread.currentThread().interrupt();
}
while (!Thread.currentThread().isInterrupted() && !condition.getAsBoolean()) {
sleep(pollMs);
}
```

**Where this applies:** `Global.sleep*`, `Global.sleepUntil*`, `Rs2Walker.setTarget(null)`, and any walker helper that waits after clicking a door, shortcut, transport, or minimap tile.

**Defensive check:** Start a long webwalk, press Ctrl+X during movement or a route-object wait, and verify no additional path recalculations or route-object interactions occur after the cancel log.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ project.build.group=net.runelite
project.build.version=1.12.26.2

glslang.path=
microbot.version=2.5.7
microbot.version=2.5.8
microbot.commit.sha=nogit
microbot.repo.url=http://138.201.81.246:8081/repository/microbot-snapshot/
microbot.repo.username=
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,10 @@ private static synchronized Future<WalkerState> submitWalk(WorldPoint destinatio
&& reachedDistance == activeWalkReachedDistance) {
return activeWalk;
}
if (activeWalk != null && !activeWalk.isDone()) {
activeWalk.cancel(true);
Rs2Walker.setTarget(null);
}
activeWalkTarget = destination;
activeWalkReachedDistance = reachedDistance;
activeWalk = WALK_EXECUTOR.submit(() -> Rs2Walker.walkWithState(destination, reachedDistance));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1027,6 +1027,7 @@ public void keyPressed(KeyEvent e) {
*/
if (e.getKeyCode() == KeyEvent.VK_X && e.isControlDown()) {
shortestPathScript.setTriggerWalker(null);
e.consume();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ public List<Node> getNeighbors(Node node, VisitedTiles visited, PathfinderConfig
if (visited.get(neighborPacked)) continue;
if (config.getRestrictedPointsPacked().contains(neighborPacked)) continue;
if (config.getCustomRestrictions().contains(neighborPacked)) continue;
if (config.isBlockedTransportStep(node.packedPosition, neighborPacked)) continue;

if (ignoreCollisionPacked.contains(node.packedPosition)) {
neighbors.add(new Node(neighborPacked, node));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package net.runelite.client.plugins.microbot.shortestpath.pathfinder;

import net.runelite.api.coords.WorldPoint;
import net.runelite.client.plugins.microbot.shortestpath.WorldPointUtil;

import java.util.ArrayList;
import java.util.Collections;
Expand Down Expand Up @@ -46,12 +47,20 @@ public static List<WorldPoint> smooth(List<WorldPoint> path, CollisionMap map) {
}

public static List<WorldPoint> smooth(List<WorldPoint> path, CollisionMap map, Set<WorldPoint> transportAnchors) {
return smooth(path, map, transportAnchors, Collections.emptySet());
}

public static List<WorldPoint> smooth(List<WorldPoint> path, CollisionMap map, Set<WorldPoint> transportAnchors,
Set<Long> blockedTransportEdges) {
if (path == null || path.size() < 3 || map == null) {
return path;
}
if (transportAnchors == null) {
transportAnchors = Collections.emptySet();
}
if (blockedTransportEdges == null) {
blockedTransportEdges = Collections.emptySet();
}

final int n = path.size();
List<WorldPoint> result = new ArrayList<>(n);
Expand All @@ -64,7 +73,7 @@ public static List<WorldPoint> smooth(List<WorldPoint> path, CollisionMap map, S
&& !transportAnchors.contains(path.get(j))
&& isChebyshevAdjacentSamePlane(path.get(j), path.get(j + 1))
&& chebyshev(path.get(i), path.get(j + 1)) <= MAX_SEGMENT_CHEBYSHEV
&& lineOfSight(path.get(i), path.get(j + 1), map)) {
&& lineOfSight(path.get(i), path.get(j + 1), map, blockedTransportEdges)) {
j++;
}
result.add(path.get(j));
Expand All @@ -83,7 +92,7 @@ private static int chebyshev(WorldPoint a, WorldPoint b) {
return Math.max(Math.abs(a.getX() - b.getX()), Math.abs(a.getY() - b.getY()));
}

private static boolean lineOfSight(WorldPoint from, WorldPoint to, CollisionMap map) {
private static boolean lineOfSight(WorldPoint from, WorldPoint to, CollisionMap map, Set<Long> blockedTransportEdges) {
if (from.getPlane() != to.getPlane()) return false;
final int z = from.getPlane();
int x = from.getX();
Expand All @@ -93,6 +102,11 @@ private static boolean lineOfSight(WorldPoint from, WorldPoint to, CollisionMap
while (x != tx || y != ty) {
int dx = Integer.signum(tx - x);
int dy = Integer.signum(ty - y);
int fromPacked = WorldPointUtil.packWorldPoint(x, y, z);
int toPacked = WorldPointUtil.packWorldPoint(x + dx, y + dy, z);
if (PathfinderConfig.isBlockedTransportStep(fromPacked, toPacked, blockedTransportEdges)) {
return false;
}
if (!map.canStep(x, y, z, dx, dy)) {
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ public List<WorldPoint> getWalkablePath() {
return raw;
}
if (!smoothed) {
smoothedPath = PathSmoother.smooth(raw, map, buildTransportAnchors(raw));
smoothedPath = PathSmoother.smooth(raw, map, buildTransportAnchors(raw), config.getBlockedTransportEdgesPacked());
smoothed = true;
}
return smoothedPath;
Expand Down
Loading
Loading