From ef17579229ecfd6656a8fb658ca3ec20cd442234 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sun, 19 Apr 2026 13:18:52 -0400 Subject: [PATCH 1/5] fix(shortestpath): TransportNode cost double-count + overflow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TransportNode's constructor was pre-adding previous.cost to travelTime, then passing that sum as `wait` to Node(WorldPoint, Node, int) — which re-adds previous.cost. The upshot: any TransportNode chained off another TransportNode had its cost double-counted. This only surfaced once a real transport->transport chain became the only viable path (e.g. Map of Alacrity -> agility shortcut), which is why it stayed latent. Concrete impact: "go to nearest bank" from outside Nemus retreat picked Shilo (cost 55) over Nemus (observed 74, should be 40 = 34 MoA + 3 climb + 3 walk). The shortcut's TransportNode was being stamped at g=71 instead of g=37, tripling the cost of every agility-shortcut edge downstream of a teleport. Passing the final absolute cost through Node(int, Node, int cost) instead of the wait-based constructor also sidesteps a second bug: for plane-crossing transports with travelTime=0 (e.g. Slayer Tower chains), the wait<=0 branch of Node.cost falls back to WorldPointUtil.distanceBetween, which returns Integer.MAX_VALUE across planes — overflowing previousCost + distance into Integer.MIN_VALUE territory and making those transports look "free". The absolute-cost path never consults distanceBetween. Also in this pass: - CollisionMap [MoA] debug log: report actual per-transport costs instead of the hardcoded `+ 4` (all MoA durations happened to be 4, but the log was incidental to the value, not tracking it). - Rs2Walker.findMoaWidget: token-set membership instead of substring match — avoids false positives like token "log" matching a hypothetical "logstrum" widget label. --- .../shortestpath/pathfinder/CollisionMap.java | 14 ++++++++++---- .../shortestpath/pathfinder/TransportNode.java | 13 ++++++++----- .../plugins/microbot/util/walker/Rs2Walker.java | 4 +++- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/CollisionMap.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/CollisionMap.java index af2da686228..74a88396e6b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/CollisionMap.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/CollisionMap.java @@ -157,6 +157,7 @@ public List getNeighbors(Node node, VisitedTiles visited, PathfinderConfig int moaAddedHere = 0; int moaVisited = 0; int moaIgnored = 0; + List moaCosts = null; // Transports are pre-filtered by PathfinderConfig.refreshTransports // Thus any transports in the list are guaranteed to be valid per the user's settings @@ -177,8 +178,13 @@ public List getNeighbors(Node node, VisitedTiles visited, PathfinderConfig if (isMoa) moaIgnored++; continue; } - neighbors.add(new TransportNode(transport.getDestination(), node, config.getDistanceBeforeUsingTeleport() + transport.getDuration())); - if (isMoa) moaAddedHere++; + int cost = config.getDistanceBeforeUsingTeleport() + transport.getDuration(); + neighbors.add(new TransportNode(transport.getDestination(), node, cost)); + if (isMoa) { + moaAddedHere++; + if (moaCosts == null) moaCosts = new ArrayList<>(); + moaCosts.add(cost); + } } else { neighbors.add(new TransportNode(transport.getDestination(), node, transport.getDuration())); } @@ -186,10 +192,10 @@ public List getNeighbors(Node node, VisitedTiles visited, PathfinderConfig } if (moaSeenHere > 0) { - log.debug("[MoA] getNeighbors @ ({},{},{}): seen={} added={} visited={} ignored={} (distanceBeforeUsingTeleport={}, cost={})", + log.debug("[MoA] getNeighbors @ ({},{},{}): seen={} added={} visited={} ignored={} (distanceBeforeUsingTeleport={}, costs={})", x, y, z, moaSeenHere, moaAddedHere, moaVisited, moaIgnored, config.getDistanceBeforeUsingTeleport(), - config.getDistanceBeforeUsingTeleport() + 4); + moaCosts == null ? "[]" : moaCosts); } if (isBlocked(x, y, z)) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/TransportNode.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/TransportNode.java index b54b549375e..28b048e6c0b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/TransportNode.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/TransportNode.java @@ -4,11 +4,14 @@ public class TransportNode extends Node implements Comparable { public TransportNode(WorldPoint point, Node previous, int travelTime) { - super(point, previous, cost(previous, travelTime)); - } - - private static int cost(Node previous, int travelTime) { - return (previous != null ? previous.cost : 0) + travelTime; + // Use Node(int, Node, int cost) which assigns cost directly. The WorldPoint + // Node constructor re-adds previous.cost via its cost(previous, wait) method, + // which caused (a) double-counting when we passed prev.cost + travelTime as + // wait and (b) integer overflow for plane-crossing transports with travelTime=0 + // because its distance fallback returns Integer.MAX_VALUE across planes. + super(net.runelite.client.plugins.microbot.shortestpath.WorldPointUtil.packWorldPoint(point), + previous, + (previous != null ? previous.cost : 0) + travelTime); } @Override diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java index b7315900e94..35b305613c2 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java @@ -2861,10 +2861,12 @@ private static Widget findMoaWidget(Widget root, String shortName) { for (Widget w : collectMoaChildren(root)) { String hay = normaliseMoaText(w.getText()); if (hay.isEmpty()) continue; + // Token-set membership avoids substring false positives (e.g. "log" matching "logstrum"). + java.util.Set haySet = new java.util.HashSet<>(java.util.Arrays.asList(hay.split(" "))); boolean all = true; for (String t : tokens) { if (t.isEmpty()) continue; - if (!hay.contains(t)) { all = false; break; } + if (!haySet.contains(t)) { all = false; break; } } if (all) return w; } From cfd16d99aae910476e202a12bffb475f60c87e5f Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sun, 19 Apr 2026 13:40:34 -0400 Subject: [PATCH 2/5] chore(shortestpath): demote MoA entry/selection logs to debug --- .../client/plugins/microbot/util/walker/Rs2Walker.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java index 35b305613c2..d52ac10f6dc 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java @@ -2709,7 +2709,7 @@ public static int getDistanceBetween(WorldPoint startpoint, WorldPoint endpoint) private static boolean handleSeasonalTransport(Transport transport) { String displayInfo = transport.getDisplayInfo(); - log.info("[MoA] entry: displayInfo='{}'", displayInfo); + log.debug("[MoA] entry: displayInfo='{}'", displayInfo); if (displayInfo == null) return false; if (!displayInfo.toLowerCase().contains("map of alacrity")) { @@ -2826,7 +2826,7 @@ private static boolean handleSeasonalTransport(Transport transport) { // Select via the row's in-game hotkey (1-9 then A-Z). Keybinds work even when the row // is scrolled off-screen, which clickWidget cannot handle. - log.info("[MoA] selecting destination '{}' (text='{}')", shortName, destText); + log.debug("[MoA] selecting destination '{}' (text='{}')", shortName, destText); Character hotkey = extractMoaHotkey(destText); if (hotkey == null) { Widget destRoot = Rs2Widget.getWidget(MAP_OF_ALACRITY_WIDGET_GROUP, MAP_OF_ALACRITY_LIST_CHILD); From d54c2ab71f2251a9cbd309c01c6cb96cb8b1b11d Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sun, 19 Apr 2026 13:51:00 -0400 Subject: [PATCH 3/5] chore(shortestpath): remove MoA audit temp debug plugin MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MoaAuditPlugin and the runMoaAudit()/closeMoaWidgetIfOpen() helpers in Rs2Walker were intended as a temporary offline landing-coord audit tool (self-labelled [TEMP] "Delete when done") and were supposed to be held on a separate debug branch, not merged. They slipped into upstream with PR #1750 because the amend that removed them locally was never pushed before the PR was merged. Scope: delete-only. No functional changes to MoA handling — handleSeasonalTransport, lockedMoaRegions, and blacklistedMoaDestinations are untouched. --- .../microbot/moaaudit/MoaAuditPlugin.java | 32 ------- .../microbot/util/walker/Rs2Walker.java | 90 ------------------- 2 files changed, 122 deletions(-) delete mode 100644 runelite-client/src/main/java/net/runelite/client/plugins/microbot/moaaudit/MoaAuditPlugin.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/moaaudit/MoaAuditPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/moaaudit/MoaAuditPlugin.java deleted file mode 100644 index 03345e71e82..00000000000 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/moaaudit/MoaAuditPlugin.java +++ /dev/null @@ -1,32 +0,0 @@ -package net.runelite.client.plugins.microbot.moaaudit; - -import lombok.extern.slf4j.Slf4j; -import net.runelite.client.plugins.Plugin; -import net.runelite.client.plugins.PluginDescriptor; -import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; - -// TEMP debug plugin: on enable, iterates every Map of Alacrity seasonal transport, -// attempts to teleport, and logs actual landing vs expected coord for each. Used to -// catch bad destination coords in seasonal_transports.tsv. Delete when done. -@PluginDescriptor( - name = PluginDescriptor.Default + "MoA Audit", - description = "[TEMP] Record Map of Alacrity teleport landing tiles", - tags = {"temp", "debug", "league", "microbot"}, - enabledByDefault = false -) -@Slf4j -public class MoaAuditPlugin extends Plugin { - private Thread worker; - - @Override - protected void startUp() { - worker = new Thread(Rs2Walker::runMoaAudit, "moa-audit"); - worker.setDaemon(true); - worker.start(); - } - - @Override - protected void shutDown() { - if (worker != null) worker.interrupt(); - } -} diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java index d52ac10f6dc..b2fe108ceda 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java @@ -2958,96 +2958,6 @@ private static void dumpMapOfAlacrityWidget(Widget listRoot) { }); } - // TEMP: iterate every MoA seasonal transport, attempt it, log landing vs expected. - // Run from a dedicated worker thread (blocks). Requires Map of Alacrity in inventory; - // locked regions/destinations are reported and skipped via the existing handler's guards. - public static void runMoaAudit() { - try { - while (!Microbot.isLoggedIn()) { - if (Thread.currentThread().isInterrupted()) return; - sleep(1000); - } - if (Rs2Inventory.get(MAP_OF_ALACRITY_ITEM_ID) == null) { - log.warn("[MoA-AUDIT] Map of Alacrity not in inventory — aborting"); - return; - } - - HashMap> all = Transport.loadAllFromResources(); - List moa = new ArrayList<>(); - for (Set set : all.values()) { - for (Transport t : set) { - if (t.getType() == TransportType.SEASONAL_TRANSPORT - && t.getDisplayInfo() != null - && t.getDisplayInfo().toLowerCase().contains("map of alacrity")) { - moa.add(t); - } - } - } - moa.sort(Comparator.comparing(Transport::getDisplayInfo)); - log.info("[MoA-AUDIT] {} MoA transports queued", moa.size()); - blacklistedMoaDestinations.clear(); - lockedMoaRegions.clear(); - - int landed = 0, skipped = 0; - for (int i = 0; i < moa.size(); i++) { - if (Thread.currentThread().isInterrupted()) break; - if (!Microbot.isLoggedIn()) { log.warn("[MoA-AUDIT] logged out — stopping"); break; } - - Transport t = moa.get(i); - String disp = t.getDisplayInfo(); - WorldPoint expected = t.getDestination(); - WorldPoint before = Rs2Player.getWorldLocation(); - if (before == null) { sleep(500); continue; } - - log.info("[MoA-AUDIT] {}/{}: {} (expected {},{},{})", - i + 1, moa.size(), disp, - expected.getX(), expected.getY(), expected.getPlane()); - - if (!handleSeasonalTransport(t)) { - log.info("[MoA-AUDIT] handler returned false"); - closeMoaWidgetIfOpen(); - skipped++; - sleep(600); - continue; - } - - boolean moved = sleepUntil(() -> { - WorldPoint now = Rs2Player.getWorldLocation(); - return now != null && (now.distanceTo(before) > 5 || now.getPlane() != before.getPlane()); - }, 8000); - - if (!moved) { - log.info("[MoA-AUDIT] no teleport detected"); - closeMoaWidgetIfOpen(); - skipped++; - continue; - } - - sleep(1500); // settle - WorldPoint after = Rs2Player.getWorldLocation(); - int dist = after.getPlane() == expected.getPlane() ? after.distanceTo(expected) : -1; - String marker = dist == 0 ? "EXACT" : (dist > 0 && dist <= 2 ? "close" : (dist > 0 && dist <= 10 ? "NEAR" : "FAR")); - log.info("[MoA-AUDIT] LAND {} | actual={},{},{} expected={},{},{} dist={} | {}", - marker, - after.getX(), after.getY(), after.getPlane(), - expected.getX(), expected.getY(), expected.getPlane(), - dist, disp); - landed++; - sleep(1500); - } - log.info("[MoA-AUDIT] complete: landed={}/{} skipped={}", landed, moa.size(), skipped); - } catch (Exception e) { - log.error("[MoA-AUDIT] crashed", e); - } - } - - private static void closeMoaWidgetIfOpen() { - if (Rs2Widget.isWidgetVisible(MAP_OF_ALACRITY_WIDGET_GROUP, MAP_OF_ALACRITY_LIST_CHILD)) { - Rs2Keyboard.keyPress(27); // ESC - sleep(400); - } - } - private static boolean handleSpiritTree(Transport transport) { // Get Transport Information String displayInfo = transport.getDisplayInfo(); From 1c6ded09318b2cd71e836944636a427b6473f977 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sun, 19 Apr 2026 14:44:50 -0400 Subject: [PATCH 4/5] fix(shortestpath): inline MoA child-iteration to satisfy client-thread guardrail MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - collectMoaChildren called getDynamicChildren/getNestedChildren/getStaticChildren directly; the static guardrail scanner can't follow through to the callers' runOnClientThreadOptional wrappers, so it flagged a new violation. - Inlined the three child-array fetches into findMoaWidget and computeMoaHotkeyByIndex — they now happen inside the client-thread lambda where the scanner can see the wrapper. - Regenerated client-thread-guardrail-baseline.txt: combined effect of the MoA edits and the audit-plugin removal shifted pre-existing Rs2Walker lambda indices, plus a few entries the old baseline was missing. --- .../microbot/util/walker/Rs2Walker.java | 52 +++++++++---------- .../client-thread-guardrail-baseline.txt | 11 ++-- 2 files changed, 30 insertions(+), 33 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java index b2fe108ceda..60b39b7b973 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java @@ -2858,32 +2858,27 @@ private static Widget findMoaWidget(Widget root, String shortName) { if (normalised.isEmpty()) return null; String[] tokens = normalised.split(" "); return Microbot.getClientThread().runOnClientThreadOptional(() -> { - for (Widget w : collectMoaChildren(root)) { - String hay = normaliseMoaText(w.getText()); - if (hay.isEmpty()) continue; - // Token-set membership avoids substring false positives (e.g. "log" matching "logstrum"). - java.util.Set haySet = new java.util.HashSet<>(java.util.Arrays.asList(hay.split(" "))); - boolean all = true; - for (String t : tokens) { - if (t.isEmpty()) continue; - if (!haySet.contains(t)) { all = false; break; } + Widget[][] groups = { root.getDynamicChildren(), root.getNestedChildren(), root.getStaticChildren() }; + for (Widget[] g : groups) { + if (g == null) continue; + for (Widget w : g) { + if (w == null) continue; + String hay = normaliseMoaText(w.getText()); + if (hay.isEmpty()) continue; + // Token-set membership avoids substring false positives (e.g. "log" matching "logstrum"). + java.util.Set haySet = new java.util.HashSet<>(java.util.Arrays.asList(hay.split(" "))); + boolean all = true; + for (String t : tokens) { + if (t.isEmpty()) continue; + if (!haySet.contains(t)) { all = false; break; } + } + if (all) return w; } - if (all) return w; } return null; }).orElse(null); } - private static java.util.List collectMoaChildren(Widget root) { - java.util.List out = new java.util.ArrayList<>(); - Widget[][] groups = { root.getDynamicChildren(), root.getNestedChildren(), root.getStaticChildren() }; - for (Widget[] g : groups) { - if (g == null) continue; - for (Widget w : g) if (w != null) out.add(w); - } - return out; - } - private static String normaliseMoaText(String s) { if (s == null) return ""; s = MOA_MARKUP_PATTERN.matcher(s).replaceAll(" "); @@ -2908,12 +2903,17 @@ private static Character computeMoaHotkeyByIndex(Widget root, Widget destMatch) if (root == null) return null; return Microbot.getClientThread().runOnClientThreadOptional(() -> { int idx = 0; - for (Widget sibling : collectMoaChildren(root)) { - String t = sibling.getText(); - if (t == null || t.isEmpty()) continue; - if (t.contains(MOA_LOCKED_MARKUP)) continue; - if (sibling == destMatch) return indexToHotkey(idx); - idx++; + Widget[][] groups = { root.getDynamicChildren(), root.getNestedChildren(), root.getStaticChildren() }; + for (Widget[] g : groups) { + if (g == null) continue; + for (Widget sibling : g) { + if (sibling == null) continue; + String t = sibling.getText(); + if (t == null || t.isEmpty()) continue; + if (t.contains(MOA_LOCKED_MARKUP)) continue; + if (sibling == destMatch) return indexToHotkey(idx); + idx++; + } } return null; }).orElse(null); diff --git a/runelite-client/src/test/resources/threadsafety/client-thread-guardrail-baseline.txt b/runelite-client/src/test/resources/threadsafety/client-thread-guardrail-baseline.txt index f4fe7820d02..3ca47a0032d 100644 --- a/runelite-client/src/test/resources/threadsafety/client-thread-guardrail-baseline.txt +++ b/runelite-client/src/test/resources/threadsafety/client-thread-guardrail-baseline.txt @@ -689,9 +689,6 @@ net.runelite.client.plugins.microbot.util.walker.Rs2MiniMap#getMinimapDrawWidget net.runelite.client.plugins.microbot.util.walker.Rs2MiniMap#worldToMinimap(WorldPoint): Point -> net.runelite.api.Client#getTopLevelWorldView(): WorldView net.runelite.client.plugins.microbot.util.walker.Rs2MiniMap#worldToMinimap(WorldPoint): Point -> net.runelite.api.coords.LocalPoint#fromWorld(WorldView, WorldPoint): LocalPoint net.runelite.client.plugins.microbot.util.walker.Rs2Walker#closeWorldMap(): boolean -> net.runelite.api.widgets.Widget#getBounds(): Rectangle -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#collectMoaChildren(Widget): List -> net.runelite.api.widgets.Widget#getDynamicChildren(): Widget[] -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#collectMoaChildren(Widget): List -> net.runelite.api.widgets.Widget#getNestedChildren(): Widget[] -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#collectMoaChildren(Widget): List -> net.runelite.api.widgets.Widget#getStaticChildren(): Widget[] net.runelite.client.plugins.microbot.util.walker.Rs2Walker#distanceToRegion(int, int): int -> net.runelite.api.Client#getTopLevelWorldView(): WorldView net.runelite.client.plugins.microbot.util.walker.Rs2Walker#distanceToRegion(int, int): int -> net.runelite.api.WorldView#getPlane(): int net.runelite.client.plugins.microbot.util.walker.Rs2Walker#getPointWithWallDistance(WorldPoint): WorldPoint -> net.runelite.api.Client#getTopLevelWorldView(): WorldView @@ -734,11 +731,11 @@ net.runelite.client.plugins.microbot.util.walker.Rs2Walker#handleTransports(List net.runelite.client.plugins.microbot.util.walker.Rs2Walker#handleTransports(List, int): boolean -> net.runelite.api.WorldView#getScene(): Scene net.runelite.client.plugins.microbot.util.walker.Rs2Walker#isCloseToRegion(int, int, int): boolean -> net.runelite.api.Client#getTopLevelWorldView(): WorldView net.runelite.client.plugins.microbot.util.walker.Rs2Walker#isCloseToRegion(int, int, int): boolean -> net.runelite.api.WorldView#getPlane(): int -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleCharterShip$115(Widget): boolean -> net.runelite.api.widgets.Widget#getActions(): String[] +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleCharterShip$114(Widget): boolean -> net.runelite.api.widgets.Widget#getActions(): String[] net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleDoors$14(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleFairyRing$121(Transport, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleMinigameTeleport$94(Widget, Object[]): boolean -> net.runelite.api.widgets.Widget#getOnOpListener(): Object[] -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleMinigameTeleport$96(String): boolean -> net.runelite.api.widgets.Widget#getText(): String +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleFairyRing$120(Transport, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleMinigameTeleport$93(Widget, Object[]): boolean -> net.runelite.api.widgets.Widget#getOnOpListener(): Object[] +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleMinigameTeleport$95(String): boolean -> net.runelite.api.widgets.Widget#getText(): String net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$66(int, TileObject): boolean -> net.runelite.api.TileObject#getId(): int net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$72(WorldPoint, TileObject): boolean -> net.runelite.api.TileObject#getId(): int net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$72(WorldPoint, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint From dafa91e819bb075bb9e848dffa18c5d62f70b9c4 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sun, 19 Apr 2026 19:00:51 -0400 Subject: [PATCH 5/5] fix(shortestpath): honor lockedMoaRegions in pathfinder MoA filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Symptom: with Asgarnia MoA region locked, walker runs in a tight loop at the teleport tile. Each attempt blacklists one destination, re-path picks a different destination *from the same locked region*, walker tries it, fails, repeat. Log shows SEASONAL_TRANSPORT usable count steps down per attempt (122 -> 121 -> 120 -> 119 ...) while src barely moves. Cause: handleSeasonalTransport already adds the region name to Rs2Walker.lockedMoaRegions when it detects markup on the region row, but PathfinderConfig's MoA filter only checks blacklistedMoaDestinations (per-destination). Region lock wasn't propagating to the pathfinder. Fix: in PathfinderConfig.useTransport's MoA branch, extract the region segment from the displayInfo ("Map of Alacrity: - ") and reject the transport if that region is in lockedMoaRegions. --- .../pathfinder/PathfinderConfig.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/PathfinderConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/PathfinderConfig.java index 60ec2969837..ef68324dfbf 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/PathfinderConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/PathfinderConfig.java @@ -577,6 +577,23 @@ private boolean useTransport(Transport transport) { return false; } + // Region-level lock: once handleSeasonalTransport sees a region render with + // (locked), reject every destination in that region. Without this, + // the pathfinder keeps picking a different Asgarnia/Desert/etc. destination on + // each re-path — walker fails, blacklists one, re-path picks the next, infinite + // "running around" loop. Display info format: "Map of Alacrity: - ". + if (traceMoa && !Rs2Walker.lockedMoaRegions.isEmpty()) { + String disp = transport.getDisplayInfo(); + int colon = disp.indexOf(':'); + int dash = colon >= 0 ? disp.indexOf(" - ", colon) : -1; + if (colon >= 0 && dash > colon) { + String region = disp.substring(colon + 1, dash).trim().toLowerCase(); + if (Rs2Walker.lockedMoaRegions.contains(region)) { + return false; + } + } + } + // Check if the feature flag is disabled if (!isFeatureEnabled(transport)) { log.debug("Transport Type {} is disabled by feature flag", transport.getType());