From 958e457f6093fb0aeb12feee95d8fd046afdb944 Mon Sep 17 00:00:00 2001 From: Daniel-Tudose Date: Wed, 27 May 2026 20:21:10 +0300 Subject: [PATCH 01/51] clues: fix South of Dragontooth Island hot-cold location --- .../plugins/cluescrolls/clues/hotcold/HotColdLocation.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java index 6b9fb0c0a5d..107bfbe5218 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/cluescrolls/clues/hotcold/HotColdLocation.java @@ -146,7 +146,7 @@ public enum HotColdLocation MORYTANIA_MOS_LES_HARMLESS(MASTER, new WorldPoint(3740, 3041, 0), MORYTANIA, "Northern area of Mos Le'Harmless, between the lakes.", BRASSICAN_MAGE), MORYTANIA_MOS_LES_HARMLESS_BAR(MASTER, new WorldPoint(3666, 2972, 0), MORYTANIA, "Near Mos Le'Harmless southern bar.", BRASSICAN_MAGE), MORYTANIA_DRAGONTOOTH_NORTH(MASTER, new WorldPoint(3811, 3569, 0), MORYTANIA, "Northern part of Dragontooth Island.", BRASSICAN_MAGE), - MORYTANIA_DRAGONTOOTH_SOUTH(MASTER, new WorldPoint(3803, 3532, 0), MORYTANIA, "Southern part of Dragontooth Island.", BRASSICAN_MAGE), + MORYTANIA_DRAGONTOOTH_SOUTH(MASTER, new WorldPoint(3803, 3529, 0), MORYTANIA, "Southern part of Dragontooth Island.", BRASSICAN_MAGE), MORYTANIA_SLEPE_TENTS(MASTER, new WorldPoint(3769, 3383, 0), MORYTANIA, "North-east of Slepe, near the tents.", BRASSICAN_MAGE), NORTHEAST_OF_AL_KHARID_MINE(BEGINNER, new WorldPoint(3332, 3313, 0), MISTHALIN, "Northeast of Al Kharid Mine"), SAIL_GREAT_CONCH(MASTER, new WorldPoint(3249, 2349, 0), OCEAN, "In the south-eastern mine of the Great Conch", BRASSICAN_MAGE), From f9a262d67f880326d63c08802634aa7d183ca79e Mon Sep 17 00:00:00 2001 From: Besevse <101194381+Besevse@users.noreply.github.com> Date: Thu, 28 May 2026 21:11:29 +0200 Subject: [PATCH 02/51] Exposed stamina pot usage and run toggle in microbot plugin --- .../plugins/microbot/MicrobotConfig.java | 26 +++++++++++++++++++ .../plugins/microbot/MicrobotPlugin.java | 8 ++++++ 2 files changed, 34 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotConfig.java index eacfe82ff6f..1dd9bc1cd11 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotConfig.java @@ -59,6 +59,32 @@ default boolean hideRoofs() return true; } + String keyEnableAutoRunOn = "enableAutoRunOn"; + @ConfigItem( + keyName = keyEnableAutoRunOn, + name = "Enable auto run", + description = "Automatically toggle run on when you have run energy", + position = 3, + section = generalSection + ) + default boolean enableAutoRunOn() + { + return true; + } + + String keyUseStaminaPotsIfNeeded = "useStaminaPotsIfNeeded"; + @ConfigItem( + keyName = keyUseStaminaPotsIfNeeded, + name = "Use stamina potions", + description = "Automatically use stamina potions from inventory when run energy is low and the player is moving", + position = 4, + section = generalSection + ) + default boolean useStaminaPotsIfNeeded() + { + return true; + } + @ConfigSection( name = "Logging", description = "Game chat logging configuration", diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotPlugin.java index 01e43bc0178..f0f37a9cfb5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotPlugin.java @@ -163,6 +163,8 @@ protected void startUp() throws AWTException ); Microbot.pauseAllScripts.set(false); + Microbot.enableAutoRunOn = microbotConfig.enableAutoRunOn(); + Microbot.useStaminaPotsIfNeeded = microbotConfig.useStaminaPotsIfNeeded(); Microbot.getBlockingEventManager().start(); MicrobotPluginListPanel pluginListPanel = pluginListPanelProvider.get(); @@ -474,6 +476,12 @@ public void onConfigChanged(ConfigChanged ev) { if (ev.getGroup().equals(MicrobotConfig.configGroup)) { switch (ev.getKey()) { + case MicrobotConfig.keyEnableAutoRunOn: + Microbot.enableAutoRunOn = microbotConfig.enableAutoRunOn(); + break; + case MicrobotConfig.keyUseStaminaPotsIfNeeded: + Microbot.useStaminaPotsIfNeeded = microbotConfig.useStaminaPotsIfNeeded(); + break; case MicrobotConfig.keyEnableGameChatLogging: case MicrobotConfig.keyGameChatLogPattern: case MicrobotConfig.keyGameChatLogLevel: From 7c2b528e58f60846871b1e7812894b9c80253999 Mon Sep 17 00:00:00 2001 From: Besevse <101194381+Besevse@users.noreply.github.com> Date: Thu, 28 May 2026 21:45:57 +0200 Subject: [PATCH 03/51] Exposed the auto click play setting and added logic for disabling it --- .../runelite/client/plugins/microbot/Microbot.java | 1 + .../client/plugins/microbot/MicrobotConfig.java | 13 +++++++++++++ .../client/plugins/microbot/MicrobotPlugin.java | 4 ++++ .../microbot/util/events/WelcomeScreenEvent.java | 2 +- 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/Microbot.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/Microbot.java index 1270bfa3c37..2c9aab47c9f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/Microbot.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/Microbot.java @@ -102,6 +102,7 @@ public class Microbot { // Feature Flags public static boolean enableAutoRunOn = true; public static boolean useStaminaPotsIfNeeded = true; + public static boolean enableAutoLogin = true; public static int runEnergyThreshold = 1000; public static boolean isCantReachTargetDetectionEnabled = false; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotConfig.java index 1dd9bc1cd11..c83c572c523 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotConfig.java @@ -85,6 +85,19 @@ default boolean useStaminaPotsIfNeeded() return true; } + String keyEnableAutoLogin = "enableAutoLogin"; + @ConfigItem( + keyName = keyEnableAutoLogin, + name = "Enable auto login", + description = "Automatically click the Play button on the welcome screen after logging in", + position = 5, + section = generalSection + ) + default boolean enableAutoLogin() + { + return true; + } + @ConfigSection( name = "Logging", description = "Game chat logging configuration", diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotPlugin.java index f0f37a9cfb5..ea9d2e6a92f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotPlugin.java @@ -165,6 +165,7 @@ protected void startUp() throws AWTException Microbot.pauseAllScripts.set(false); Microbot.enableAutoRunOn = microbotConfig.enableAutoRunOn(); Microbot.useStaminaPotsIfNeeded = microbotConfig.useStaminaPotsIfNeeded(); + Microbot.enableAutoLogin = microbotConfig.enableAutoLogin(); Microbot.getBlockingEventManager().start(); MicrobotPluginListPanel pluginListPanel = pluginListPanelProvider.get(); @@ -482,6 +483,9 @@ public void onConfigChanged(ConfigChanged ev) case MicrobotConfig.keyUseStaminaPotsIfNeeded: Microbot.useStaminaPotsIfNeeded = microbotConfig.useStaminaPotsIfNeeded(); break; + case MicrobotConfig.keyEnableAutoLogin: + Microbot.enableAutoLogin = microbotConfig.enableAutoLogin(); + break; case MicrobotConfig.keyEnableGameChatLogging: case MicrobotConfig.keyGameChatLogPattern: case MicrobotConfig.keyGameChatLogLevel: diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/events/WelcomeScreenEvent.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/events/WelcomeScreenEvent.java index 498875577b7..72933102511 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/events/WelcomeScreenEvent.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/events/WelcomeScreenEvent.java @@ -18,7 +18,7 @@ public class WelcomeScreenEvent implements BlockingEvent { @Override public boolean validate() { - return Rs2Widget.isWidgetVisible(InterfaceID.WelcomeScreen.PLAY); + return Microbot.enableAutoLogin && Rs2Widget.isWidgetVisible(InterfaceID.WelcomeScreen.PLAY); } @Override From 76bf77e5d1c0f69bfd35158d10dafa11d84a1360 Mon Sep 17 00:00:00 2001 From: Besevse <101194381+Besevse@users.noreply.github.com> Date: Fri, 29 May 2026 15:23:56 +0200 Subject: [PATCH 04/51] Expose auto run and stamina pot settings in Microbot plugin --- .../client/plugins/microbot/Microbot.java | 1 - .../plugins/microbot/MicrobotConfig.java | 33 ++++++++----------- .../plugins/microbot/MicrobotPlugin.java | 4 --- .../client/plugins/microbot/Script.java | 2 ++ .../util/events/WelcomeScreenEvent.java | 2 +- 5 files changed, 17 insertions(+), 25 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/Microbot.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/Microbot.java index 2c9aab47c9f..1270bfa3c37 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/Microbot.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/Microbot.java @@ -102,7 +102,6 @@ public class Microbot { // Feature Flags public static boolean enableAutoRunOn = true; public static boolean useStaminaPotsIfNeeded = true; - public static boolean enableAutoLogin = true; public static int runEnergyThreshold = 1000; public static boolean isCantReachTargetDetectionEnabled = false; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotConfig.java index c83c572c523..a10ee059952 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotConfig.java @@ -59,13 +59,21 @@ default boolean hideRoofs() return true; } + @ConfigSection( + name = "Movement", + description = "Movement and stamina settings. Plugins may override these, but any changes will be reflected here.", + position = 1, + closedByDefault = true + ) + String movementSection = "movementSection"; + String keyEnableAutoRunOn = "enableAutoRunOn"; @ConfigItem( keyName = keyEnableAutoRunOn, name = "Enable auto run", description = "Automatically toggle run on when you have run energy", - position = 3, - section = generalSection + position = 0, + section = movementSection ) default boolean enableAutoRunOn() { @@ -77,37 +85,24 @@ default boolean enableAutoRunOn() keyName = keyUseStaminaPotsIfNeeded, name = "Use stamina potions", description = "Automatically use stamina potions from inventory when run energy is low and the player is moving", - position = 4, - section = generalSection + position = 1, + section = movementSection ) default boolean useStaminaPotsIfNeeded() { return true; } - String keyEnableAutoLogin = "enableAutoLogin"; - @ConfigItem( - keyName = keyEnableAutoLogin, - name = "Enable auto login", - description = "Automatically click the Play button on the welcome screen after logging in", - position = 5, - section = generalSection - ) - default boolean enableAutoLogin() - { - return true; - } - @ConfigSection( name = "Logging", description = "Game chat logging configuration", - position = 1 + position = 2 ) String loggingSection = "loggingSection"; @ConfigSection( name = "Caching", description = "Caching ingame data", - position = 2 + position = 3 ) String cacheSection = "cacheSection"; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotPlugin.java index ea9d2e6a92f..f0f37a9cfb5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/MicrobotPlugin.java @@ -165,7 +165,6 @@ protected void startUp() throws AWTException Microbot.pauseAllScripts.set(false); Microbot.enableAutoRunOn = microbotConfig.enableAutoRunOn(); Microbot.useStaminaPotsIfNeeded = microbotConfig.useStaminaPotsIfNeeded(); - Microbot.enableAutoLogin = microbotConfig.enableAutoLogin(); Microbot.getBlockingEventManager().start(); MicrobotPluginListPanel pluginListPanel = pluginListPanelProvider.get(); @@ -483,9 +482,6 @@ public void onConfigChanged(ConfigChanged ev) case MicrobotConfig.keyUseStaminaPotsIfNeeded: Microbot.useStaminaPotsIfNeeded = microbotConfig.useStaminaPotsIfNeeded(); break; - case MicrobotConfig.keyEnableAutoLogin: - Microbot.enableAutoLogin = microbotConfig.enableAutoLogin(); - break; case MicrobotConfig.keyEnableGameChatLogging: case MicrobotConfig.keyGameChatLogPattern: case MicrobotConfig.keyGameChatLogLevel: diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/Script.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/Script.java index e9b18417d71..9994f5eb40f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/Script.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/Script.java @@ -100,6 +100,8 @@ public boolean run() { if (!hasRunEnergy && Microbot.useStaminaPotsIfNeeded && Rs2Player.isMoving()) { Rs2Inventory.useRestoreEnergyItem(); } + Microbot.getConfigManager().setConfiguration(MicrobotConfig.configGroup, MicrobotConfig.keyEnableAutoRunOn, Microbot.enableAutoRunOn); + Microbot.getConfigManager().setConfiguration(MicrobotConfig.configGroup, MicrobotConfig.keyUseStaminaPotsIfNeeded, Microbot.useStaminaPotsIfNeeded); } return true; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/events/WelcomeScreenEvent.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/events/WelcomeScreenEvent.java index 72933102511..498875577b7 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/events/WelcomeScreenEvent.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/events/WelcomeScreenEvent.java @@ -18,7 +18,7 @@ public class WelcomeScreenEvent implements BlockingEvent { @Override public boolean validate() { - return Microbot.enableAutoLogin && Rs2Widget.isWidgetVisible(InterfaceID.WelcomeScreen.PLAY); + return Rs2Widget.isWidgetVisible(InterfaceID.WelcomeScreen.PLAY); } @Override From a32414c87b349853f7d463c4b938653fe033041a Mon Sep 17 00:00:00 2001 From: Adam Date: Tue, 26 May 2026 15:12:54 -0400 Subject: [PATCH 05/51] api: add Scene getSkybox() --- .../src/main/java/net/runelite/api/Renderable.java | 3 ++- runelite-api/src/main/java/net/runelite/api/Scene.java | 2 ++ .../java/net/runelite/api/hooks/DrawCallbacks.java | 10 ++++++++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/runelite-api/src/main/java/net/runelite/api/Renderable.java b/runelite-api/src/main/java/net/runelite/api/Renderable.java index b3162b3e91b..bce439b4dd2 100644 --- a/runelite-api/src/main/java/net/runelite/api/Renderable.java +++ b/runelite-api/src/main/java/net/runelite/api/Renderable.java @@ -45,11 +45,12 @@ public interface Renderable extends Node int getAnimationHeightOffset(); - @MagicConstant(intValues = {RENDERMODE_DEFAULT, RENDERMODE_SORTED, RENDERMODE_SORTED_NO_DEPTH, RENDERMODE_UNSORTED}) + @MagicConstant(intValues = {RENDERMODE_DEFAULT, RENDERMODE_SORTED, RENDERMODE_SORTED_NO_DEPTH, RENDERMODE_UNSORTED, RENDERMODE_UNSORTED_NO_DEPTH}) int getRenderMode(); int RENDERMODE_DEFAULT = 0; int RENDERMODE_SORTED = 1; int RENDERMODE_SORTED_NO_DEPTH = 2; int RENDERMODE_UNSORTED = 3; + int RENDERMODE_UNSORTED_NO_DEPTH = 4; } diff --git a/runelite-api/src/main/java/net/runelite/api/Scene.java b/runelite-api/src/main/java/net/runelite/api/Scene.java index d660408d2a4..0d3a63c335c 100644 --- a/runelite-api/src/main/java/net/runelite/api/Scene.java +++ b/runelite-api/src/main/java/net/runelite/api/Scene.java @@ -173,4 +173,6 @@ public interface Scene extends Renderable byte getOverrideHue(); byte getOverrideSaturation(); byte getOverrideLuminance(); + + Model getSkybox(); } diff --git a/runelite-api/src/main/java/net/runelite/api/hooks/DrawCallbacks.java b/runelite-api/src/main/java/net/runelite/api/hooks/DrawCallbacks.java index 71aa1356b14..03dfb2ad7f3 100644 --- a/runelite-api/src/main/java/net/runelite/api/hooks/DrawCallbacks.java +++ b/runelite-api/src/main/java/net/runelite/api/hooks/DrawCallbacks.java @@ -73,6 +73,7 @@ public interface DrawCallbacks int PASS_OPAQUE = 0; int PASS_ALPHA = 1; + int PRE_PASS_ALPHA = 2; static int RENDER_THREADS(int num) { @@ -140,6 +141,15 @@ default void despawnWorldView(WorldView worldView) { } + default void preSceneDraw( + Scene scene, Projection entityProjection, + float cameraX, float cameraY, float cameraZ, float cameraPitch, float cameraYaw, + int minLevel, int level, int maxLevel, Set hideRoofIds) + { + preSceneDraw(scene, cameraX, cameraY, cameraZ, cameraPitch, cameraYaw, minLevel, level, maxLevel, hideRoofIds); + } + + @Deprecated default void preSceneDraw( Scene scene, float cameraX, float cameraY, float cameraZ, float cameraPitch, float cameraYaw, From 5771015d52ba6a324194a81aed14e1296261f18a Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 30 May 2026 11:18:47 -0400 Subject: [PATCH 06/51] devools: add zsort command --- .../client/plugins/devtools/DevToolsPlugin.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java index 254c5c79b58..176dd881794 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/devtools/DevToolsPlugin.java @@ -493,6 +493,16 @@ public void onCommandExecuted(CommandExecuted commandExecuted) player.getPlayerComposition().setHash(); break; } + case "zsort": + { + Player player = client.getLocalPlayer(); + player.getPlayerComposition().getEquipmentIds()[KitType.AMULET.getIndex()] = ItemID.ENCHANTED_ONYX_AMULET + PlayerComposition.ITEM_OFFSET; + player.getPlayerComposition().getEquipmentIds()[KitType.CAPE.getIndex()] = ItemID.SKILLCAPE_CONSTRUCTION + PlayerComposition.ITEM_OFFSET; + player.getPlayerComposition().getEquipmentIds()[KitType.TORSO.getIndex()] = ItemID.TORVA_CHEST + PlayerComposition.ITEM_OFFSET; + player.getPlayerComposition().getEquipmentIds()[KitType.LEGS.getIndex()] = ItemID.BARROWS_AHRIM_LEGS + PlayerComposition.ITEM_OFFSET; + player.getPlayerComposition().setHash(); + break; + } case "sound": { int id = Integer.parseInt(args[0]); From 594039a1bce2cc7a4633970517c116fa41c23033 Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 30 May 2026 11:18:51 -0400 Subject: [PATCH 07/51] gpu: combine player vaolist with opaque vaolist This passes the render mode into addRange, which is a few more draw calls, but means adding a new render mode is a lot easier later. --- .../client/plugins/gpu/GpuPlugin.java | 125 ++++++------------ .../net/runelite/client/plugins/gpu/VAO.java | 121 +++++++++++------ 2 files changed, 126 insertions(+), 120 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java index 6f8d80c5e89..51e9265a488 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java @@ -179,13 +179,14 @@ public class GpuPlugin extends Plugin implements DrawCallbacks private VAOList vaoO; private VAOList vaoA; - private VAOList vaoPO; private SceneUploader clientUploader, mapUploader; private FacePrioritySorter facePrioritySorter; static class SceneContext { + final float[] projection = Mat4.identity(); + final int sizeX, sizeZ; Zone[][] zones; @@ -252,7 +253,7 @@ SceneContext context(WorldView wv) private int uniExpandedMapLoadingChunks; private int uniSmoothBanding; private int uniWorldProj; - private static int uniEntityProj; + static int uniEntityProj; static int uniEntityTint; private int uniBrightness; private int uniTex; @@ -268,7 +269,7 @@ SceneContext context(WorldView wv) private int uniUiColorblindIntensity; static int uniBase; - private static Projection lastProjection; + static final float[] IDENTITY = Mat4.identity(); @Override protected void startUp() @@ -668,7 +669,6 @@ private void initBuffers() vaoO = new VAOList(); vaoA = new VAOList(); - vaoPO = new VAOList(); } private void initGlBuffer(GLBuffer glBuffer) @@ -690,11 +690,7 @@ private void shutdownBuffers() { vaoA.free(); } - if (vaoPO != null) - { - vaoPO.free(); - } - vaoO = vaoA = vaoPO = null; + vaoO = vaoA = null; } private void destroyGlBuffer(GLBuffer glBuffer) @@ -792,33 +788,25 @@ private void shutdownFbo() } } - static void updateEntityProjection(Projection projection) - { - if (lastProjection != projection) - { - float[] p = projection instanceof FloatProjection ? ((FloatProjection) projection).getProjection() : Mat4.identity(); - glUniformMatrix4fv(uniEntityProj, false, p); - lastProjection = projection; - } - } - @Override - public void preSceneDraw(Scene scene, + public void preSceneDraw(Scene scene, Projection entityProjection, float cameraX, float cameraY, float cameraZ, float cameraPitch, float cameraYaw, int minLevel, int level, int maxLevel, Set hideRoofIds) { SceneContext ctx = context(scene); - if (ctx != null) + if (ctx == null) { - ctx.cameraX = (int) cameraX; - ctx.cameraY = (int) cameraY; - ctx.cameraZ = (int) cameraZ; - ctx.minLevel = minLevel; - ctx.level = level; - ctx.maxLevel = maxLevel; - ctx.hideRoofIds = hideRoofIds; + return; } + ctx.cameraX = (int) cameraX; + ctx.cameraY = (int) cameraY; + ctx.cameraZ = (int) cameraZ; + ctx.minLevel = minLevel; + ctx.level = level; + ctx.maxLevel = maxLevel; + ctx.hideRoofIds = hideRoofIds; + if (scene.getWorldViewId() == WorldView.TOPLEVEL) { this.cameraYaw = client.getCameraYaw(); @@ -827,9 +815,8 @@ public void preSceneDraw(Scene scene, } else { - Scene toplevel = client.getScene(); - vaoO.addRange(null, toplevel); - vaoPO.addRange(null, toplevel); + System.arraycopy(((FloatProjection) entityProjection).getProjection(), 0, ctx.projection, 0, 16); + glUniformMatrix4fv(uniEntityProj, false, ctx.projection); glUniform4i(uniEntityTint, scene.getOverrideHue(), scene.getOverrideSaturation(), scene.getOverrideLuminance(), scene.getOverrideAmount()); } } @@ -898,12 +885,6 @@ private void preSceneDrawToplevel(Scene scene, glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fboScene); } - // Clear scene - int sky = client.getSkyboxColor(); - glClearColor((sky >> 16 & 0xFF) / 255f, (sky >> 8 & 0xFF) / 255f, (sky & 0xFF) / 255f, 1f); - glClearDepth(0d); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - // Setup anisotropic filtering final int anisotropicFilteringLevel = config.anisotropicFilteringLevel(); @@ -946,6 +927,7 @@ private void preSceneDrawToplevel(Scene scene, // Setup uniforms final int drawDistance = getDrawDistance(); final int fogDepth = config.fogDepth(); + final int sky = client.getSkyboxColor(); glUniform1i(uniUseFog, fogDepth > 0 ? 1 : 0); glUniform4f(uniFogColor, (sky >> 16 & 0xFF) / 255f, (sky >> 8 & 0xFF) / 255f, (sky & 0xFF) / 255f, 1f); glUniform1i(uniFogDepth, fogDepth); @@ -972,8 +954,7 @@ private void preSceneDrawToplevel(Scene scene, Mat4.mul(projectionMatrix, Mat4.translate(-cameraX, -cameraY, -cameraZ)); glUniformMatrix4fv(uniWorldProj, false, projectionMatrix); - projectionMatrix = Mat4.identity(); - glUniformMatrix4fv(uniEntityProj, false, projectionMatrix); + glUniformMatrix4fv(uniEntityProj, false, IDENTITY); glUniform4i(uniEntityTint, 0, 0, 0, 0); @@ -992,6 +973,10 @@ private void preSceneDrawToplevel(Scene scene, glDepthFunc(GL_GREATER); glEnable(GL_DEPTH_TEST); + glClearColor((sky >> 16 & 0xFF) / 255f, (sky >> 8 & 0xFF) / 255f, (sky & 0xFF) / 255f, 1f); + glClearDepth(0d); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + checkGLErrors(); } @@ -1005,6 +990,7 @@ public void postSceneDraw(Scene scene) else { glUniform4i(uniEntityTint, 0, 0, 0, 0); + glUniformMatrix4fv(uniEntityProj, false, IDENTITY); } } @@ -1044,8 +1030,6 @@ private void blitSceneFbo() @Override public void drawZoneOpaque(Projection entityProjection, Scene scene, int zx, int zz) { - updateEntityProjection(entityProjection); - SceneContext ctx = context(scene); if (ctx == null) { @@ -1084,9 +1068,6 @@ public void drawZoneAlpha(Projection entityProjection, Scene scene, int level, i return; } - updateEntityProjection(entityProjection); - glUniform4i(uniEntityTint, scene.getOverrideHue(), scene.getOverrideSaturation(), scene.getOverrideLuminance(), scene.getOverrideAmount()); - int offset = scene.getWorldViewId() == WorldView.TOPLEVEL ? (SCENE_OFFSET >> 3) : 0; int dx = ctx.cameraX - ((zx - offset) << 10); int dz = ctx.cameraZ - ((zz - offset) << 10); @@ -1112,45 +1093,15 @@ public void drawPass(Projection projection, Scene scene, int pass) return; } - updateEntityProjection(projection); - if (pass == DrawCallbacks.PASS_OPAQUE) { - vaoO.addRange(projection, scene); - vaoPO.addRange(projection, scene); - if (scene.getWorldViewId() == WorldView.TOPLEVEL) { - glUniform3i(uniBase, 0, 0, 0); - - int sz = vaoO.unmap(); - for (int i = 0; i < sz; ++i) - { - VAO vao = vaoO.vaos.get(i); - vao.draw(); - vao.reset(); - } - - sz = vaoPO.unmap(); - if (sz > 0) - { - glDepthMask(false); - for (int i = 0; i < sz; ++i) - { - VAO vao = vaoPO.vaos.get(i); - vao.draw(); - } - glDepthMask(true); - - glColorMask(false, false, false, false); - for (int i = 0; i < sz; ++i) - { - VAO vao = vaoPO.vaos.get(i); - vao.draw(); - vao.reset(); - } - glColorMask(true, true, true, true); - } + vaoO.draw(); + } + else + { + glUniformMatrix4fv(uniEntityProj, false, IDENTITY); } } else if (pass == DrawCallbacks.PASS_ALPHA) @@ -1164,6 +1115,11 @@ else if (pass == DrawCallbacks.PASS_ALPHA) } } } + else if (pass == DrawCallbacks.PRE_PASS_ALPHA) + { + glUniformMatrix4fv(uniEntityProj, false, ctx.projection); + glUniform4i(uniEntityTint, scene.getOverrideHue(), scene.getOverrideSaturation(), scene.getOverrideLuminance(), scene.getOverrideAmount()); + } checkGLErrors(); } @@ -1187,6 +1143,7 @@ public void drawDynamic(Projection worldProjection, Scene scene, TileObject tile { VAO o = vaoO.get(size); clientUploader.uploadTempModel(m, orient, x, y, z, o.vbo.vb); + o.addRange(ctx.projection, scene, 0); } else { @@ -1203,6 +1160,8 @@ public void drawDynamic(Projection worldProjection, Scene scene, TileObject tile } int end = a.vbo.vb.position(); + o.addRange(ctx.projection, scene, 0); + if (end > start) { int offset = scene.getWorldViewId() == WorldView.TOPLEVEL ? SCENE_OFFSET : 0; @@ -1238,10 +1197,7 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj int renderMode = renderable.getRenderMode(); if (renderMode == Renderable.RENDERMODE_SORTED_NO_DEPTH || m.getFaceTransparencies() != null) { - // opaque player faces have their own vao and are drawn in a separate pass from normal opaque faces - // because they are not depth tested. transparent player faces don't need their own vao because normal - // transparent faces are already not depth tested - VAO o = renderMode == Renderable.RENDERMODE_SORTED_NO_DEPTH ? vaoPO.get(size) : vaoO.get(size); + VAO o = vaoO.get(size); VAO a = vaoA.get(size); int start = a.vbo.vb.position(); @@ -1256,6 +1212,8 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj } int end = a.vbo.vb.position(); + o.addRange(ctx.projection, scene, renderMode == Renderable.RENDERMODE_SORTED_NO_DEPTH ? renderMode : 0); + if (end > start) { int offset = scene.getWorldViewId() == WorldView.TOPLEVEL ? (SCENE_OFFSET >> 3) : 0; @@ -1270,6 +1228,7 @@ public void drawTemp(Projection worldProjection, Scene scene, GameObject gameObj { VAO o = vaoO.get(size); clientUploader.uploadTempModel(m, orient, x, y, z, o.vbo.vb); + o.addRange(ctx.projection, scene, 0); } } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/VAO.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/VAO.java index a8ae02af5b1..c0ff0d293df 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/VAO.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/VAO.java @@ -28,10 +28,11 @@ import java.util.Arrays; import java.util.List; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.Projection; +import net.runelite.api.Renderable; import net.runelite.api.Scene; +import static net.runelite.client.plugins.gpu.GpuPlugin.uniBase; +import static net.runelite.client.plugins.gpu.GpuPlugin.uniEntityProj; import static net.runelite.client.plugins.gpu.GpuPlugin.uniEntityTint; -import static net.runelite.client.plugins.gpu.GpuPlugin.updateEntityProjection; import static org.lwjgl.opengl.GL33C.*; class VAO @@ -78,51 +79,98 @@ void destroy() vao = 0; } - int[] lengths = new int[4]; - Projection[] projs = new Projection[4]; - Scene[] scenes = new Scene[4]; - int off = 0; + static class Range + { + int endpos; + float[] projection; + byte h, s, l, a; + byte renderMethod; + } + + Range[] ranges = new Range[4]; + int off; + + { + for (int i = 0; i < ranges.length; ++i) + { + ranges[i] = new Range(); + } + } - void addRange(Projection projection, Scene scene) + void addRange(float[] projection, Scene scene, int renderMode) { assert vbo.mapped; - if (off > 0 && lengths[off - 1] == vbo.vb.position()) + if (off > 0) { - return; + Range r = ranges[off - 1]; + int pos = vbo.vb.position(); + if (r.endpos == pos) + { + return; + } + + if (projection == r.projection && renderMode == r.renderMethod) + { + assert pos > r.endpos; + r.endpos = pos; + return; + } } - if (lengths.length == off) + if (ranges.length == off) { - int l = lengths.length << 1; - lengths = Arrays.copyOf(lengths, l); - projs = Arrays.copyOf(projs, l); - scenes = Arrays.copyOf(scenes, l); + int l = ranges.length << 1; + ranges = Arrays.copyOf(ranges, l); + for (int i = ranges.length >> 1; i < ranges.length; ++i) + { + ranges[i] = new Range(); + } } - lengths[off] = vbo.vb.position(); - projs[off] = projection; - scenes[off] = scene; - off++; + Range r = ranges[off++]; + r.endpos = vbo.vb.position(); + r.projection = projection; + r.h = scene.getOverrideHue(); + r.s = scene.getOverrideSaturation(); + r.l = scene.getOverrideLuminance(); + r.a = scene.getOverrideAmount(); + r.renderMethod = (byte) renderMode; } void draw() { assert !vbo.mapped; + glUniform3i(uniBase, 0, 0, 0); + int start = 0; for (int i = 0; i < off; ++i) { - int end = lengths[i]; - Projection p = projs[i]; - Scene scene = scenes[i]; + Range range = ranges[i]; + int end = range.endpos; int count = end - start; - updateEntityProjection(p); - glUniform4i(uniEntityTint, scene.getOverrideHue(), scene.getOverrideSaturation(), scene.getOverrideLuminance(), scene.getOverrideAmount()); + glUniformMatrix4fv(uniEntityProj, false, range.projection); + glUniform4i(uniEntityTint, range.h, range.s, range.l, range.a); + glBindVertexArray(vao); - glDrawArrays(GL_TRIANGLES, start / (VERT_SIZE / 4), count / (VAO.VERT_SIZE / 4)); + + if (range.renderMethod == Renderable.RENDERMODE_SORTED_NO_DEPTH) + { + glDepthMask(false); + glDrawArrays(GL_TRIANGLES, start / (VERT_SIZE / 4), count / (VERT_SIZE / 4)); + glDepthMask(true); + + glColorMask(false, false, false, false); + glDrawArrays(GL_TRIANGLES, start / (VERT_SIZE / 4), count / (VERT_SIZE / 4)); + glColorMask(true, true, true, true); + } + else + { + glDrawArrays(GL_TRIANGLES, start / (VERT_SIZE / 4), count / (VERT_SIZE / 4)); + } start = end; } @@ -130,8 +178,10 @@ void draw() void reset() { - Arrays.fill(projs, 0, off, null); - Arrays.fill(scenes, 0, off, null); + for (int i = 0; i < off; ++i) + { + ranges[i].projection = null; + } off = 0; } } @@ -200,15 +250,14 @@ void free() curIdx = 0; } - void addRange(Projection projection, Scene scene) + void draw() { - for (int i = 0; i <= curIdx && i < vaos.size(); ++i) + int sz = unmap(); + for (int i = 0; i < sz; ++i) { VAO vao = vaos.get(i); - if (vao.vbo.mapped) - { - vao.addRange(projection, scene); - } + vao.draw(); + vao.reset(); } } @@ -218,12 +267,10 @@ void debug() for (VAO vao : vaos) { log.debug("vao {} mapped: {} num ranges: {} length: {}", vao, vao.vbo.mapped, vao.off, vao.vbo.mapped ? vao.vbo.vb.position() : -1); - if (vao.off > 1) + for (int i = 0; i < vao.off; ++i) { - for (int i = 0; i < vao.off; ++i) - { - log.debug(" {} {} {}", vao.lengths[i], vao.projs[i], vao.scenes[i]); - } + VAO.Range r = vao.ranges[i]; + log.debug(" endpos: {} proj: {} hsl: {},{},{},{} renderMethod: {}", r.endpos, r.projection, r.h, r.s, r.l, r.a, r.renderMethod); } } } From 0abd154db0fd6b32c1291018ce28a2b0644c961b Mon Sep 17 00:00:00 2001 From: Adam Date: Sat, 30 May 2026 10:48:33 -0400 Subject: [PATCH 08/51] gpu: add skybox --- .../client/plugins/gpu/GpuPlugin.java | 29 +++++++++++++++++-- .../net/runelite/client/plugins/gpu/VAO.java | 6 ++++ 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java index 51e9265a488..7556fbb4ad8 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/GpuPlugin.java @@ -973,11 +973,36 @@ private void preSceneDrawToplevel(Scene scene, glDepthFunc(GL_GREATER); glEnable(GL_DEPTH_TEST); - glClearColor((sky >> 16 & 0xFF) / 255f, (sky >> 8 & 0xFF) / 255f, (sky & 0xFF) / 255f, 1f); + drawSkybox(scene, sky, cameraX, cameraY, cameraZ); + + checkGLErrors(); + } + + private void drawSkybox(Scene scene, int sky, float cameraX, float cameraY, float cameraZ) + { + Model skybox = scene.getSkybox(); + if (skybox == null) + { + glClearColor((sky >> 16 & 0xFF) / 255f, (sky >> 8 & 0xFF) / 255f, (sky & 0xFF) / 255f, 1f); + glClearDepth(0d); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + return; + } + + glClearColor(0f, 0f, 0f, 1f); glClearDepth(0d); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - checkGLErrors(); + int size = skybox.getFaceCount() * 3 * VAO.VERT_SIZE; + VAO o = vaoO.get(size); + clientUploader.uploadTempModel(skybox, 0, 0, 0, 0, o.vbo.vb); + + float[] skyboxProjection = Mat4.translate(cameraX, cameraY, cameraZ); + o.addRange(skyboxProjection, scene, Renderable.RENDERMODE_UNSORTED_NO_DEPTH); + + vaoO.draw(); + + glUniformMatrix4fv(uniEntityProj, false, IDENTITY); } @Override diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/VAO.java b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/VAO.java index c0ff0d293df..e7b6c498e5b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/gpu/VAO.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/gpu/VAO.java @@ -167,6 +167,12 @@ void draw() glDrawArrays(GL_TRIANGLES, start / (VERT_SIZE / 4), count / (VERT_SIZE / 4)); glColorMask(true, true, true, true); } + else if (range.renderMethod == Renderable.RENDERMODE_UNSORTED_NO_DEPTH) + { + glDepthMask(false); + glDrawArrays(GL_TRIANGLES, start / (VERT_SIZE / 4), count / (VERT_SIZE / 4)); + glDepthMask(true); + } else { glDrawArrays(GL_TRIANGLES, start / (VERT_SIZE / 4), count / (VERT_SIZE / 4)); From b62f24799c4691ac8de34a7a549229756a93e32e Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 1 Jun 2026 13:14:04 -0400 Subject: [PATCH 09/51] world hopper: better handle worlds with no region --- .../runelite/client/plugins/worldhopper/WorldHopperPlugin.java | 2 +- .../runelite/client/plugins/worldhopper/WorldSwitcherPanel.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java index f8ed5447c7a..56f61ec7ea0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldHopperPlugin.java @@ -597,7 +597,7 @@ private void hop(boolean previous) world = worlds.get(worldIdx); // Check world region if filter is enabled - if (!regionFilter.isEmpty() && !regionFilter.contains(RegionFilterMode.of(world.getRegion()))) + if (!regionFilter.isEmpty() && world.getRegion() != null && !regionFilter.contains(RegionFilterMode.of(world.getRegion()))) { continue; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldSwitcherPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldSwitcherPanel.java index 62d83543016..db64ab0bd2d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldSwitcherPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldSwitcherPanel.java @@ -271,7 +271,7 @@ void populate(List worlds, @Nullable EnumComposition worldLocations) break; } - if (!regionFilterMode.isEmpty() && !regionFilterMode.contains(RegionFilterMode.of(world.getRegion()))) + if (!regionFilterMode.isEmpty() && world.getRegion() != null && !regionFilterMode.contains(RegionFilterMode.of(world.getRegion()))) { continue; } From 400ff7b57df419f978870e390c1c593b29907e8d Mon Sep 17 00:00:00 2001 From: pjmarz Date: Mon, 1 Jun 2026 14:31:21 -0400 Subject: [PATCH 10/51] fix(breakhandlerv2): don't treat LOADING transitions as unexpected logout detectUnexpectedLogout() keyed on !Microbot.isLoggedIn(), which is true during transient GameState transitions (LOADING/HOPPING/LOGGING_IN), not only on a real logout. Region or plane transitions such as climbing the Mining Guild ladder to bank therefore fire a false "unexpected logout"; auto-login then "recovers" a session that was never lost, and that recovery reschedules the next break. Because such transitions happen on every bank trip (more often than the break interval), the break timer keeps resetting and real scheduled breaks never fire. Fix: only treat a genuine logged-out GameState (LOGIN_SCREEN or CONNECTION_LOST) as an unexpected logout, and ignore the transient transition states. Strictly narrower than before, so it cannot miss a real logout. Evidence from a 5.5h F2P Mining Guild session: 13 "Unexpected logout detected" warnings, with 0 LOGIN_SCREEN, 0 CONNECTION_LOST, and 0 disconnects in the log, and the break state machine never once entered a real break (every "break cycle" was a 2-3s false recovery). Bump version 2.0.0 to 2.0.1. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../breakhandlerv2/BreakHandlerV2Plugin.java | 2 +- .../breakhandlerv2/BreakHandlerV2Script.java | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Plugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Plugin.java index 0d14ee15370..9f1f553fd03 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Plugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Plugin.java @@ -32,7 +32,7 @@ * - Retry mechanism with configurable attempts and delays * - In-game overlay showing break status and timers * - * @version 2.0.0 + * @version 2.0.1 */ @PluginDescriptor( name = PluginDescriptor.Default + "BreakHandler V2", diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Script.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Script.java index 507aa7c65b4..697b3a24769 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Script.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/breakhandler/breakhandlerv2/BreakHandlerV2Script.java @@ -78,7 +78,7 @@ public BreakHandlerV2Script() { private static final int MAX_SAFETY_CHECK_ATTEMPTS = 60; private static final int SAFETY_CHECK_DELAY_MS = 5000; // 5 seconds between checks - public static String version = "2.0.0"; + public static String version = "2.0.1"; /** * Run the break handler script @@ -525,8 +525,16 @@ private void detectUnexpectedLogout() { return; } + // Transient game states during region/plane transitions (e.g. climbing the Mining Guild + // ladder to bank) briefly report !isLoggedIn() with GameState LOADING/HOPPING/LOGGING_IN. + // Those are NOT logouts; treating them as one fires a false break cycle that reschedules + // the next break and can starve real breaks entirely. Only act on a genuine logged-out + // GameState (LOGIN_SCREEN / CONNECTION_LOST), not the transition states. + GameState gameState = Microbot.getClient() != null ? Microbot.getClient().getGameState() : null; + boolean genuinelyLoggedOut = gameState == GameState.LOGIN_SCREEN || gameState == GameState.CONNECTION_LOST; + // Check if player is logged out unexpectedly - if (!Microbot.isLoggedIn() && !unexpectedLogoutDetected) { + if (genuinelyLoggedOut && !unexpectedLogoutDetected) { long secondsUntilBreak = Instant.now().until(nextBreakTime, ChronoUnit.SECONDS); if (secondsUntilBreak > 0) { From 72d7cbdc6ca7a9d20acd046e556eeaa1dc8291d3 Mon Sep 17 00:00:00 2001 From: Adam Date: Mon, 1 Jun 2026 13:38:47 -0400 Subject: [PATCH 11/51] world hopper: add jp, sg, za --- libs.versions.toml | 2 +- .../plugins/worldhopper/RegionFilterMode.java | 3 +++ .../client/plugins/worldhopper/WorldTableRow.java | 12 ++++++++++++ .../client/plugins/worldhopper/flag_jp.png | Bin 0 -> 162 bytes .../client/plugins/worldhopper/flag_sg.png | Bin 0 -> 184 bytes .../client/plugins/worldhopper/flag_za.png | Bin 0 -> 203 bytes 6 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/worldhopper/flag_jp.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/worldhopper/flag_sg.png create mode 100644 runelite-client/src/main/resources/net/runelite/client/plugins/worldhopper/flag_za.png diff --git a/libs.versions.toml b/libs.versions.toml index aec61918938..6bdf0ead72e 100644 --- a/libs.versions.toml +++ b/libs.versions.toml @@ -39,7 +39,7 @@ rs-cache = { module = "net.runelite.rs:cache", version.ref = "cache" } flatlaf-core = { module = "net.runelite:flatlaf", version.ref = "flatlaf" } flatlaf-extras = { module = "net.runelite:flatlaf-extras", version.ref = "flatlaf" } -rl-http-api = "net.runelite.arn:http-api:1.2.22" +rl-http-api = "net.runelite.arn:http-api:1.2.23" rl-awt = "net.runelite:rlawt:1.8" rl-discord = "net.runelite:discord:1.4" rl-orange = "net.runelite:orange-extensions:1.1" diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/RegionFilterMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/RegionFilterMode.java index 033b5a329fc..481b959e3d3 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/RegionFilterMode.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/RegionFilterMode.java @@ -50,6 +50,9 @@ public String toString() } }, BRAZIL(WorldRegion.BRAZIL), + JAPAN(WorldRegion.JAPAN), + SINGAPORE(WorldRegion.SINGAPORE), + SOUTH_AFRICA(WorldRegion.SOUTH_AFRICA), ; @Getter diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldTableRow.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldTableRow.java index be2c6f99304..1e2890c19a7 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldTableRow.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/WorldTableRow.java @@ -56,6 +56,9 @@ class WorldTableRow extends JPanel private static final ImageIcon FLAG_US_WEST; private static final ImageIcon FLAG_GER; private static final ImageIcon FLAG_BR; + private static final ImageIcon FLAG_JP; + private static final ImageIcon FLAG_SG; + private static final ImageIcon FLAG_ZA; private static final int WORLD_COLUMN_WIDTH = 60; private static final int PLAYERS_COLUMN_WIDTH = 40; @@ -80,6 +83,9 @@ class WorldTableRow extends JPanel FLAG_US_WEST = new ImageIcon(ImageUtil.loadImageResource(WorldHopperPlugin.class, "flag_us_west.png")); FLAG_GER = new ImageIcon(ImageUtil.loadImageResource(WorldHopperPlugin.class, "flag_ger.png")); FLAG_BR = new ImageIcon(ImageUtil.loadImageResource(WorldHopperPlugin.class, "flag_br.png")); + FLAG_JP = new ImageIcon(ImageUtil.loadImageResource(WorldHopperPlugin.class, "flag_jp.png")); + FLAG_SG = new ImageIcon(ImageUtil.loadImageResource(WorldHopperPlugin.class, "flag_sg.png")); + FLAG_ZA = new ImageIcon(ImageUtil.loadImageResource(WorldHopperPlugin.class, "flag_za.png")); } private static final int LOCATION_US_WEST = -73; @@ -432,6 +438,12 @@ private static ImageIcon getFlag(WorldRegion region, int worldLocation) return FLAG_GER; case BRAZIL: return FLAG_BR; + case JAPAN: + return FLAG_JP; + case SINGAPORE: + return FLAG_SG; + case SOUTH_AFRICA: + return FLAG_ZA; default: return null; } diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/worldhopper/flag_jp.png b/runelite-client/src/main/resources/net/runelite/client/plugins/worldhopper/flag_jp.png new file mode 100644 index 0000000000000000000000000000000000000000..a8f83b3adf4f90e7c617c9c7967ac83eb697bacc GIT binary patch literal 162 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh<3?#J|q#XfLJOMr-uK)l42Qq==9tPdUO~!wL z9LAC$zhDN3XE)M-92ZX)#}JO|$q5O(sSHgkLK%5@dRB+&CMG0AZ_QF=ZoaZAbhS*w z6Mco5TmSuE#c5FV|9^Oakf+Qdp+($DOh-&QP9{V%TsG2t7;`>f4QL*Nr>mdKI;Vst E0Q_t3=zpF`~HYnBea4Kvsb cC3CnLLb|oTzx;j;XbA&@r>mdKI;Vst0L}kGy#N3J literal 0 HcmV?d00001 diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/worldhopper/flag_za.png b/runelite-client/src/main/resources/net/runelite/client/plugins/worldhopper/flag_za.png new file mode 100644 index 0000000000000000000000000000000000000000..ea338388e5290cf7ef5591913c06c7abaaf02350 GIT binary patch literal 203 zcmeAS@N?(olHy`uVBq!ia0vp^LLkh-3?!F4n4AKnL<4+6T>t<74`c$#2R4Qb<(~g{ z$S{a>-W3=73KV553GxeOaCmkj4aiCHba4#fxSpJlkRTu;lET33n3|kyF!`eE$s0E! z7)}Tk7BX>3F||6yE@IWTxL|dIlL32EEEhw^PRT#kR^^|8_Az+6`njxgN@xNAD)2!| literal 0 HcmV?d00001 From 9031f9c16f34f56d0393bf0d4f02c0728754288d Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Wed, 20 May 2026 16:10:47 -0400 Subject: [PATCH 12/51] fix(walker): network transport loops, gate detection, post-transport gate skip MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three fixes for Rs2Walker transport and gate handling: 1. Prevent infinite loops with network transports (spirit trees, fairy rings, quetzals, gliders). The current-tile transport handler's "closer to goal" heuristic allowed taking any network transport whose destination was nearer the goal, creating A→B→A loops. Now network transports require the destination to be on the forward path points. 2. Don't skip segment handlers when the next path tile is unreachable. The 15s post-transport optimization blanket-skipped all handlers (including door/gate resolution) when no nearby transport was planned. Adding a reachability check ensures gates are still opened. 3. Widen blocker scan radius from 2 to 4 tiles. Path smoothing can place path points 3+ tiles from a gate object (e.g. Al Kharid gate), causing the blocker scan to filter it out and leaving the walker stuck in recovery clicks that never reach the gate. --- .../microbot/util/walker/Rs2Walker.java | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 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 3fc838f6445..df51d4e6c89 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 @@ -1442,7 +1442,8 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part POST_TRANSPORT_RAW_SCAN_TRANSPORT_MAX_DIST) && !hasRecentDoorAttemptNearIndex(path, i) && !isDoorInteractionSettling() - && !isRecoveryMovementInFlight(); + && !isRecoveryMovementInFlight() + && Rs2Tile.isTileReachable(currentWorldPoint); if (skipPostTransportSegmentHandlers) { tmarkPostTransport("post_transport_segment_handler_skip", target, @@ -3237,7 +3238,8 @@ private static boolean handleCurrentTileTransportTowardPath(List raw || playerLoc.getPlane() != target.getPlane() || t.getDestination().getPlane() == target.getPlane()) .filter(t -> pathPoints.contains(t.getDestination()) - || (target != null && t.getDestination().distanceTo(target) < playerLoc.distanceTo(target))) + || (!isNetworkTransport(t) + && target != null && t.getDestination().distanceTo(target) < playerLoc.distanceTo(target))) .sorted(Comparator .comparingInt((Transport t) -> pathPoints.contains(t.getDestination()) ? 0 : 1) .thenComparingInt(t -> target == null ? 0 : t.getDestination().distanceTo(target))) @@ -4730,7 +4732,7 @@ private static boolean tryResolvePathAdjacentBlocker(WorldPoint playerLoc, List< if (startIdx >= path.size() - 1) return false; int endEdgeIdx = Math.min(path.size() - 2, startIdx + Math.max(0, scanAheadEdges)); - final int pathEdgeDoorMaxDist = 2; + final int pathEdgeDoorMaxDist = 4; Map byIdentity = new LinkedHashMap<>(); for (int edgeIdx = startIdx; edgeIdx <= endEdgeIdx; edgeIdx++) { @@ -6147,6 +6149,19 @@ private static boolean isAdjacentSamePlaneTransport(Transport transport) { && transport.getOrigin().distanceTo(transport.getDestination()) <= 1; } + private static boolean isNetworkTransport(Transport transport) { + if (transport == null || transport.getType() == null) return false; + switch (transport.getType()) { + case SPIRIT_TREE: + case FAIRY_RING: + case QUETZAL: + case GNOME_GLIDER: + return true; + default: + return false; + } + } + private static boolean finishHandledTransport(Transport transport) { long handoffStartedAt = System.currentTimeMillis(); lastTransportHandledAtMs = handoffStartedAt; From 07ecb52a6bb63b59881920f5cb8e8454a85a2f50 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Wed, 20 May 2026 16:13:24 -0400 Subject: [PATCH 13/51] fix(walker): wake post-click wait immediately when player stops moving The post-click sleepUntil waited up to 2000ms for the player to reach within 2-4 tiles of the click target, but had no check for the player stopping. When the player hit a gate or obstacle mid-walk, the walker sat idle for the remainder of the timeout before re-evaluating. Add !Rs2Player.isMoving() to the wake condition (after the 600ms game tick floor) so the walker reacts immediately when movement stops. --- .../runelite/client/plugins/microbot/util/walker/Rs2Walker.java | 1 + 1 file changed, 1 insertion(+) 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 df51d4e6c89..747f5c0a9ad 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 @@ -1822,6 +1822,7 @@ && walkFastCanvas(navTarget)) { if (isWalkCancelled(target)) return true; long elapsed = System.currentTimeMillis() - clickedAt; if (elapsed < 600) return false; + if (!Rs2Player.isMoving()) return true; WorldPoint now = Rs2Player.getWorldLocation(); if (b.distanceTo2D(now) <= proximityWake) return true; return before.distanceTo2D(now) >= progressCap; From 041000198fc878075ba93e210e8f88d8ff3f5db1 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Wed, 20 May 2026 16:25:32 -0400 Subject: [PATCH 14/51] fix(walker): widen off-path variance tolerance to reduce click chain breaks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PATH_VARIANCE_TOLERANCE_CHEBYSHEV raised from 3 to 6. The variance check is the fallback when the BFS-based isNearPath() fails (e.g. a nearby wall blocks BFS despite the player being 2-3 tiles from the path). At 3, normal path-smoothing drift during walking triggered off-path-but-moving breaks that killed the click chain — the walker would watch the player walk without issuing new clicks, causing visible pauses when the player eventually stopped. At 6 tiles the walker keeps clicking through minor drift while still recalculating when genuinely lost. --- .../runelite/client/plugins/microbot/util/walker/Rs2Walker.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 747f5c0a9ad..9b20a209b4c 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 @@ -156,7 +156,7 @@ public static WorldPoint getCurrentTarget() { private static final long POST_TRANSPORT_OFFPATH_WAIT_BUDGET_MS = 2_500L; private static final int POST_TRANSPORT_OFFPATH_WAIT_SLICE_MS = 450; private static final int TRANSPORT_DEST_MATCH_CHEBYSHEV = 1; - private static final int PATH_VARIANCE_TOLERANCE_CHEBYSHEV = 3; + private static final int PATH_VARIANCE_TOLERANCE_CHEBYSHEV = 6; private static final int POST_TRANSPORT_RAW_SCAN_TRANSPORT_LOOKAHEAD_EDGES = 6; private static final int POST_TRANSPORT_RAW_SCAN_TRANSPORT_MAX_DIST = 15; private static final long TRANSPORT_POST_INTERACT_SETTLE_MS = 900L; From 160996c7f68727339fc43d28318b83e0bceb4ed8 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Wed, 20 May 2026 16:44:33 -0400 Subject: [PATCH 15/51] fix(walker): cache reachable tiles to eliminate per-index BFS calls isTileReachable() dispatches an unbounded BFS to the client thread for every path index in the inner walk loop. With 10+ indices per iteration, each blocking on the client thread round-trip, this caused multi-second delays before the first minimap click (9s observed on a 23-point path). Compute reachable tiles once via getReachableTilesFromTile(radius=45) before the inner loop and use HashMap lookups instead. One client-thread BFS call replaces N sequential blocking calls. --- .../client/plugins/microbot/util/walker/Rs2Walker.java | 8 ++++++-- 1 file changed, 6 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 9b20a209b4c..277c6c04d8c 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 @@ -1360,6 +1360,10 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part } } + final int REACHABLE_CACHE_RADIUS = 45; + Map reachableTilesCache = Rs2Tile.getReachableTilesFromTile( + Rs2Player.getWorldLocation(), REACHABLE_CACHE_RADIUS); + for (int i = indexOfStartPoint; !doorOrTransportResult && i < path.size(); i++) { WorldPoint currentWorldPoint = path.get(i); if (walkCancelledDiag(target, "processWalk:path-loop", processWalkTail)) { @@ -1443,7 +1447,7 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part && !hasRecentDoorAttemptNearIndex(path, i) && !isDoorInteractionSettling() && !isRecoveryMovementInFlight() - && Rs2Tile.isTileReachable(currentWorldPoint); + && reachableTilesCache.containsKey(currentWorldPoint); if (skipPostTransportSegmentHandlers) { tmarkPostTransport("post_transport_segment_handler_skip", target, @@ -1495,7 +1499,7 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part } } - boolean tileReachable = Rs2Tile.isTileReachable(currentWorldPoint); + boolean tileReachable = reachableTilesCache.containsKey(currentWorldPoint); if (!tileReachable && !inInstance) { // Common stall case: path steps beyond a closed door are unreachable, so the // loop would otherwise "continue" without ever issuing a minimap click and From 7d0b40d3cb74ae38b3ef47ddb975f9a5044d1e0c Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Thu, 21 May 2026 11:09:59 -0400 Subject: [PATCH 16/51] fix(walker): skip redundant reachable tiles BFS when player hasn't moved MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hoist the cache variables above the outer tail loop and only recompute when the player's position changes. The outer loop can iterate up to 64 times (door settling, recovery clicks, interim yields) — previously each iteration recomputed the full radius-45 BFS on the client thread even when the player was standing still. --- .../plugins/microbot/util/walker/Rs2Walker.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 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 277c6c04d8c..e6613c28d44 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 @@ -1060,6 +1060,9 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part boolean lastAttemptedMinimapClickOk = false; long lastAttemptedMinimapClickAtMs = 0L; long pathfinderPendingSinceMs = 0L; + final int REACHABLE_CACHE_RADIUS = 45; + Map reachableTilesCache = null; + WorldPoint reachableTilesCacheOrigin = null; for (int processWalkTail = 0; processWalkTail < MAX_PROCESS_WALK_TAIL_ITERATIONS; processWalkTail++) { try { walkerDiag("tail iteration begin idx=%d/%d target=%s current=%s interim=%s partialRetries=%d", @@ -1360,9 +1363,11 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part } } - final int REACHABLE_CACHE_RADIUS = 45; - Map reachableTilesCache = Rs2Tile.getReachableTilesFromTile( - Rs2Player.getWorldLocation(), REACHABLE_CACHE_RADIUS); + WorldPoint currentPlayerLoc = Rs2Player.getWorldLocation(); + if (reachableTilesCache == null || !currentPlayerLoc.equals(reachableTilesCacheOrigin)) { + reachableTilesCache = Rs2Tile.getReachableTilesFromTile(currentPlayerLoc, REACHABLE_CACHE_RADIUS); + reachableTilesCacheOrigin = currentPlayerLoc; + } for (int i = indexOfStartPoint; !doorOrTransportResult && i < path.size(); i++) { WorldPoint currentWorldPoint = path.get(i); From 6eb5097d9a6393787ec687fd540548feda08881f Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sat, 23 May 2026 15:59:07 -0400 Subject: [PATCH 17/51] fix(walker): rewrite BFS to O(n), use raw path for obstacle detection - Rewrite getReachableTilesFromTileInternal to use ArrayDeque BFS (O(n)) instead of O(n*d) HashMap scanning with per-tile HashSet allocation - Use raw tile-by-tile path for door/rockfall/transport detection instead of smoothed path, fixing gates missed between widely-spaced smooth points - Add unbounded BFS fallback for reachability check to eliminate false unreachable hits from bounded cache - Always recompute reachable tiles cache each iteration to pick up collision changes (gates opened by other players) - Skip reachable raw edges in door/rockfall scanning for performance; transport scanning always checks all edges (teleports aren't blockages) - Keep smoothed path for click targeting and spatial proximity checks --- .../plugins/microbot/util/tile/Rs2Tile.java | 94 ++++++++------- .../microbot/util/walker/Rs2Walker.java | 107 +++++++++++++++--- 2 files changed, 145 insertions(+), 56 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tile/Rs2Tile.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tile/Rs2Tile.java index a28bf33859f..7c1ae3bc5ea 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tile/Rs2Tile.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tile/Rs2Tile.java @@ -352,49 +352,67 @@ public static HashMap getReachableTilesFromTile(WorldPoint private static HashMap getReachableTilesFromTileInternal(WorldPoint tile, int distance, boolean ignoreCollision) { final HashMap tileDistances = new HashMap<>(); + if (tile == null) return tileDistances; + + final int[][] flags = getFlagsInternal(); + if (flags == null) return tileDistances; + + final WorldView wv = Microbot.getClient().getTopLevelWorldView(); + final boolean isInstance = wv.getScene().isInstance(); + + final ArrayDeque queue = new ArrayDeque<>(); tileDistances.put(tile, 0); + queue.add(tile); - for (int i = 0; i < distance + 1; i++) { - int dist = i; - for (var kvp : tileDistances.entrySet().stream().filter(x -> x.getValue() == dist).collect(Collectors.toList())) { - var point = kvp.getKey(); - LocalPoint localPoint; - if (Microbot.getClient().getTopLevelWorldView().isInstance()) { - WorldPoint worldPoint = WorldPoint.toLocalInstance(Microbot.getClient().getTopLevelWorldView(), point).stream().findFirst().orElse(null); - if (worldPoint == null) break; - localPoint = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), worldPoint); - } else - localPoint = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), point); - - CollisionData[] collisionMap = Microbot.getClient().getTopLevelWorldView().getCollisionMaps(); - if (collisionMap != null && localPoint != null) { - CollisionData collisionData = collisionMap[Microbot.getClient().getTopLevelWorldView().getPlane()]; - int[][] flags = collisionData.getFlags(); - int data = flags[localPoint.getSceneX()][localPoint.getSceneY()]; - - Set movementFlags = MovementFlag.getSetFlags(data); - - if (!ignoreCollision && !tile.equals(point)) { - if (movementFlags.contains(MovementFlag.BLOCK_MOVEMENT_FULL) - || movementFlags.contains(MovementFlag.BLOCK_MOVEMENT_FLOOR)) { - tileDistances.remove(point); - continue; - } - } + while (!queue.isEmpty()) { + final WorldPoint point = queue.poll(); + final int dist = tileDistances.get(point); + + final LocalPoint lp; + if (isInstance) { + WorldPoint instancePoint = WorldPoint.toLocalInstance(wv, point).stream().findFirst().orElse(null); + if (instancePoint == null) continue; + lp = LocalPoint.fromWorld(wv, instancePoint); + } else { + lp = LocalPoint.fromWorld(wv, point); + } + if (lp == null) continue; - if (kvp.getValue() >= distance) - continue; - - if (!movementFlags.contains(MovementFlag.BLOCK_MOVEMENT_EAST)) - tileDistances.putIfAbsent(point.dx(1), dist + 1); - if (!movementFlags.contains(MovementFlag.BLOCK_MOVEMENT_WEST)) - tileDistances.putIfAbsent(point.dx(-1), dist + 1); - if (!movementFlags.contains(MovementFlag.BLOCK_MOVEMENT_NORTH)) - tileDistances.putIfAbsent(point.dy(1), dist + 1); - if (!movementFlags.contains(MovementFlag.BLOCK_MOVEMENT_SOUTH)) - tileDistances.putIfAbsent(point.dy(-1), dist + 1); + final int sx = lp.getSceneX(); + final int sy = lp.getSceneY(); + if (!isWithinBounds(sx, sy)) continue; + + final int data = flags[sx][sy]; + + if (!ignoreCollision && !tile.equals(point)) { + if ((data & CollisionDataFlag.BLOCK_MOVEMENT_FULL) != 0) { + tileDistances.remove(point); + continue; } } + + if (dist >= distance) continue; + + if ((data & CollisionDataFlag.BLOCK_MOVEMENT_EAST) == 0) { + WorldPoint neighbor = point.dx(1); + if (tileDistances.putIfAbsent(neighbor, dist + 1) == null) + queue.add(neighbor); + } + if ((data & CollisionDataFlag.BLOCK_MOVEMENT_WEST) == 0) { + WorldPoint neighbor = point.dx(-1); + if (tileDistances.putIfAbsent(neighbor, dist + 1) == null) + queue.add(neighbor); + } + if ((data & CollisionDataFlag.BLOCK_MOVEMENT_NORTH) == 0) { + WorldPoint neighbor = point.dy(1); + if (tileDistances.putIfAbsent(neighbor, dist + 1) == null) + queue.add(neighbor); + } + if ((data & CollisionDataFlag.BLOCK_MOVEMENT_SOUTH) == 0) { + WorldPoint neighbor = point.dy(-1); + if (tileDistances.putIfAbsent(neighbor, dist + 1) == null) + queue.add(neighbor); + } } return tileDistances; 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 e6613c28d44..066549449f4 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 @@ -1060,7 +1060,7 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part boolean lastAttemptedMinimapClickOk = false; long lastAttemptedMinimapClickAtMs = 0L; long pathfinderPendingSinceMs = 0L; - final int REACHABLE_CACHE_RADIUS = 45; + final int REACHABLE_CACHE_RADIUS = 200; Map reachableTilesCache = null; WorldPoint reachableTilesCacheOrigin = null; for (int processWalkTail = 0; processWalkTail < MAX_PROCESS_WALK_TAIL_ITERATIONS; processWalkTail++) { @@ -1129,6 +1129,7 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part final List rawPath = pathfinder.getPath(); final List path = pathfinder.getWalkablePath(); + final int[] smoothedToRaw = mapSmoothedToRaw(path, rawPath); int rawSize = rawPath == null ? -1 : rawPath.size(); int walkSize = path == null ? -1 : path.size(); markStartupPhase("path_snapshot", target, "raw=" + rawSize + " walk=" + walkSize); @@ -1364,10 +1365,8 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part } WorldPoint currentPlayerLoc = Rs2Player.getWorldLocation(); - if (reachableTilesCache == null || !currentPlayerLoc.equals(reachableTilesCacheOrigin)) { - reachableTilesCache = Rs2Tile.getReachableTilesFromTile(currentPlayerLoc, REACHABLE_CACHE_RADIUS); - reachableTilesCacheOrigin = currentPlayerLoc; - } + reachableTilesCache = Rs2Tile.getReachableTilesFromTile(currentPlayerLoc, REACHABLE_CACHE_RADIUS); + reachableTilesCacheOrigin = currentPlayerLoc; for (int i = indexOfStartPoint; !doorOrTransportResult && i < path.size(); i++) { WorldPoint currentWorldPoint = path.get(i); @@ -1459,9 +1458,12 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part "i=" + i + " reason=no_nearby_planned_transport"); } else { long segmentHandlerStartAt = System.currentTimeMillis(); + int rawI = (i < smoothedToRaw.length) ? smoothedToRaw[i] : 0; + int rawEnd = rawEndForSmoothedIndex(i, smoothedToRaw, rawPath, path); if (!isDoorInteractionSettling() && !isRecoveryMovementInFlight()) { - doorOrTransportResult = handleDoorsWithTimeout(path, i, - obstaclePolicy.segmentDoorTimeoutMs(), doorEdgesAttemptedThisTail); + doorOrTransportResult = handleDoorsInRawSegment(rawPath, rawI, rawEnd, + obstaclePolicy.segmentDoorTimeoutMs(), doorEdgesAttemptedThisTail, + reachableTilesCache); } if (doorOrTransportResult) { tmarkPostTransport("post_transport_segment_handler", target, @@ -1472,7 +1474,7 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part // Chain step 2: path-adjacent probes after exact segment-door attempt. if (!Rs2Player.isMoving() && obstaclePolicy.allowPathAdjacentProbe()) { - if (tryHandleBlockingPathObjectsWithTimeout(path, i, 5, 10, + if (tryHandleBlockingPathObjectsWithTimeout(rawPath, rawI, 5, 10, obstaclePolicy.pathAdjacentProbeTimeoutMs(), doorEdgesAttemptedThisTail)) { tmarkPostTransport("post_transport_segment_handler", target, "stage=path_adj handled=true i=" + i + " ms=" + (System.currentTimeMillis() - segmentHandlerStartAt)); @@ -1481,7 +1483,8 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part } } - doorOrTransportResult = handleRockfall(path, i); + doorOrTransportResult = handleRockfallInRawSegment(rawPath, rawI, rawEnd, + reachableTilesCache); if (doorOrTransportResult) { tmarkPostTransport("post_transport_segment_handler", target, "stage=rockfall handled=true i=" + i + " ms=" + (System.currentTimeMillis() - segmentHandlerStartAt)); @@ -1490,7 +1493,7 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part } if (PohTeleports.isInHouse() || !inInstance) { - doorOrTransportResult = handleTransports(path, i); + doorOrTransportResult = handleTransportsInRawSegment(rawPath, rawI, rawEnd); } if (doorOrTransportResult) { @@ -1505,6 +1508,9 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part } boolean tileReachable = reachableTilesCache.containsKey(currentWorldPoint); + if (!tileReachable && !inInstance) { + tileReachable = Rs2Tile.isTileReachable(currentWorldPoint); + } if (!tileReachable && !inInstance) { // Common stall case: path steps beyond a closed door are unreachable, so the // loop would otherwise "continue" without ever issuing a minimap click and @@ -1520,8 +1526,10 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part currentWorldPoint, i, path.size(), playerLoc, target); int edgeIdx = Math.max(indexOfStartPoint, i - 1); - WorldPoint edgeFrom = edgeIdx >= 0 && edgeIdx < path.size() ? path.get(edgeIdx) : null; - WorldPoint edgeTo = edgeIdx + 1 >= 0 && edgeIdx + 1 < path.size() ? path.get(edgeIdx + 1) : null; + int rawEdgeStart = (edgeIdx < smoothedToRaw.length) ? smoothedToRaw[edgeIdx] : 0; + int rawEdgeEnd = (i < smoothedToRaw.length) ? smoothedToRaw[i] + 1 : rawPath.size(); + WorldPoint edgeFrom = rawEdgeStart >= 0 && rawEdgeStart < rawPath.size() ? rawPath.get(rawEdgeStart) : null; + WorldPoint edgeTo = rawEdgeEnd - 1 >= 0 && rawEdgeEnd - 1 < rawPath.size() ? rawPath.get(rawEdgeEnd - 1) : null; if (hasRecentDoorAttemptOnEdge(edgeFrom, edgeTo)) { boolean resolvedAfterWait = waitForDoorEdgeResolution(edgeFrom, edgeTo, obstaclePolicy.edgeResolutionWaitTimeoutMs()); @@ -1532,8 +1540,8 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part } break; } - if (hasRecentDoorAttemptNearIndex(path, edgeIdx)) { - boolean resolvedAfterNearbyWait = waitForRecentDoorEdgeResolutionNearIndex(path, edgeIdx, + if (hasRecentDoorAttemptNearIndex(rawPath, rawEdgeStart)) { + boolean resolvedAfterNearbyWait = waitForRecentDoorEdgeResolutionNearIndex(rawPath, rawEdgeStart, obstaclePolicy.edgeResolutionWaitTimeoutMs()); WorldPoint afterNearbyWait = Rs2Player.getWorldLocation(); boolean progressedAfterNearbyWait = afterNearbyWait != null @@ -1551,8 +1559,9 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part break; } } - if (handleDoorsWithTimeout(path, edgeIdx, - obstaclePolicy.unreachableDoorTimeoutMs(), doorEdgesAttemptedThisTail)) { + if (handleDoorsInRawSegment(rawPath, rawEdgeStart, rawEdgeEnd, + obstaclePolicy.unreachableDoorTimeoutMs(), doorEdgesAttemptedThisTail, + null)) { exitReason = "door-handled-unreachable"; break; } @@ -1561,7 +1570,7 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part break; } boolean gateDoorInteraction = isDoorInteractionSettling() || isDoorEdgePassSkipCoolingDown(); - long recentDoorAgeMs = recentDoorAttemptAgeNearIndex(path, edgeIdx); + long recentDoorAgeMs = recentDoorAttemptAgeNearIndex(rawPath, rawEdgeStart); boolean pendingDoorTraversal = recentDoorAgeMs >= 0 && recentDoorAgeMs <= DOOR_TRAVERSAL_RECOVERY_BLOCK_MS && !Rs2Player.isMoving(); @@ -1584,7 +1593,7 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part && obstaclePolicy.allowNearbyFallback() && nowMs - lastDoorPathAdjAttemptAtMs > 1200) { lastDoorPathAdjAttemptAtMs = nowMs; - if (tryResolvePathAdjacentBlocker(playerLoc, path, edgeIdx, 3, 15)) { + if (tryResolvePathAdjacentBlocker(playerLoc, rawPath, rawEdgeStart, 5, 15)) { exitReason = "door-handled-path-adj-scan"; break; } @@ -6159,6 +6168,68 @@ private static boolean isAdjacentSamePlaneTransport(Transport transport) { && transport.getOrigin().distanceTo(transport.getDestination()) <= 1; } + private static int[] mapSmoothedToRaw(List smoothed, List raw) { + if (smoothed == null || raw == null || smoothed.isEmpty() || raw.isEmpty()) { + return new int[0]; + } + int[] mapping = new int[smoothed.size()]; + int rawIdx = 0; + for (int si = 0; si < smoothed.size(); si++) { + WorldPoint sp = smoothed.get(si); + while (rawIdx < raw.size() && !raw.get(rawIdx).equals(sp)) { + rawIdx++; + } + mapping[si] = Math.min(rawIdx, raw.size() - 1); + } + return mapping; + } + + private static int rawEndForSmoothedIndex(int smoothedIdx, int[] smoothedToRaw, + List rawPath, List path) { + if (smoothedIdx + 1 < path.size() && smoothedIdx + 1 < smoothedToRaw.length) { + return smoothedToRaw[smoothedIdx + 1]; + } + return rawPath.size(); + } + + private static boolean handleDoorsInRawSegment(List rawPath, int rawFrom, int rawTo, + long timeoutMs, Map attempted, + Map reachableCache) { + for (int ri = rawFrom; ri < rawTo && ri < rawPath.size() - 1; ri++) { + if (reachableCache != null && reachableCache.containsKey(rawPath.get(ri)) + && reachableCache.containsKey(rawPath.get(ri + 1))) { + continue; + } + if (handleDoorsWithTimeout(rawPath, ri, timeoutMs, attempted)) { + return true; + } + } + return false; + } + + private static boolean handleRockfallInRawSegment(List rawPath, int rawFrom, int rawTo, + Map reachableCache) { + for (int ri = rawFrom; ri < rawTo && ri < rawPath.size() - 1; ri++) { + if (reachableCache != null && reachableCache.containsKey(rawPath.get(ri)) + && reachableCache.containsKey(rawPath.get(ri + 1))) { + continue; + } + if (handleRockfall(rawPath, ri)) { + return true; + } + } + return false; + } + + private static boolean handleTransportsInRawSegment(List rawPath, int rawFrom, int rawTo) { + for (int ri = rawFrom; ri < rawTo && ri < rawPath.size() - 1; ri++) { + if (handleTransports(rawPath, ri)) { + return true; + } + } + return false; + } + private static boolean isNetworkTransport(Transport transport) { if (transport == null || transport.getType() == null) return false; switch (transport.getType()) { From ce81385e3ab926b639fbf9623c559a0c840f7df4 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sat, 23 May 2026 16:59:41 -0400 Subject: [PATCH 18/51] fix(pathfinder): merge transport nodes into A* boundary for optimal routing Transport nodes (fairy rings, spirit trees, etc.) were placed in a separate pending queue ordered by g-cost, only processed when cheaper than the boundary's g-cost. This caused the pathfinder to walk 988 tiles instead of using fairy ring CIR (~260 tiles) because transport nodes at g=58 were starved behind walking nodes at g<58. Now all nodes share the A* boundary queue with proper heuristic, so a fairy ring destination at f=111 is explored immediately over a walking path at f=2475. Also: unbounded reachable tiles cache, remove isTileReachable fallback. --- .../shortestpath/pathfinder/Pathfinder.java | 52 +++++-------------- .../plugins/microbot/util/tile/Rs2Tile.java | 4 ++ .../microbot/util/walker/Rs2Walker.java | 7 +-- 3 files changed, 20 insertions(+), 43 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java index be6f264dd07..867668a7b08 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java @@ -210,12 +210,11 @@ private void addNeighbors(Node node) { } visited.set(neighbor.packedPosition); + neighbor.heuristic = heuristicToNearestTarget(neighbor.packedPosition); + boundary.add(neighbor); if (neighbor instanceof TransportNode) { - pending.add(neighbor); ++stats.transportsChecked; } else { - neighbor.heuristic = heuristicToNearestTarget(neighbor.packedPosition); - boundary.add(neighbor); ++stats.nodesChecked; } } @@ -314,12 +313,11 @@ private void addNeighborsForwardWithMeet(Node node, Map forwardAt } visited.set(neighbor.packedPosition); + neighbor.heuristic = heuristicToNearestTarget(neighbor.packedPosition); + boundary.add(neighbor); if (neighbor instanceof TransportNode) { - pending.add(neighbor); ++stats.transportsChecked; } else { - neighbor.heuristic = heuristicToNearestTarget(neighbor.packedPosition); - boundary.add(neighbor); ++stats.nodesChecked; } forwardAt.putIfAbsent(neighbor.packedPosition, neighbor); @@ -340,12 +338,11 @@ private void addNeighborsBackwardWithMeet(Node node, VisitedTiles visitedB, Map< } visitedB.set(pred.packedPosition); + pred.heuristic = heuristicFromStart(pred.packedPosition); + boundaryBackward.add(pred); if (pred instanceof TransportNode) { - pendingBackward.add(pred); ++stats.transportsChecked; } else { - pred.heuristic = heuristicFromStart(pred.packedPosition); - boundaryBackward.add(pred); ++stats.nodesChecked; } backwardAt.putIfAbsent(pred.packedPosition, pred); @@ -368,15 +365,8 @@ private void runUnidirectional() { config.refreshTeleports(start, 31); boolean reachedGoal = false; boolean timedOut = false; - while (!cancelled && (!boundary.isEmpty() || !pending.isEmpty())) { - Node b = boundary.peek(); - Node p = pending.peek(); - Node node; - if (p != null && (b == null || p.cost < b.cost)) { - node = pending.poll(); - } else { - node = boundary.poll(); - } + while (!cancelled && !boundary.isEmpty()) { + Node node = boundary.poll(); if (wildernessLevel > 0) { boolean update = false; @@ -434,7 +424,7 @@ private void runUnidirectional() { String uniExit = cancelled ? "cancelled" : reachedGoal ? "reached-goal" : timedOut ? "time-cutoff" - : (boundary.isEmpty() && pending.isEmpty()) ? "queues-drained" : "loop-ended"; + : boundary.isEmpty() ? "queues-drained" : "loop-ended"; pathfinderDiag("uni finished exit=%s cancelled=%s boundaryEmpty=%s pendingEmpty=%s bestLastNode=%s cutoffMs=%d", uniExit, cancelled, @@ -482,16 +472,9 @@ private void runBidirectional() { long cutoffTimeMillis = System.currentTimeMillis() + cutoffDurationMillis; config.refreshTeleports(start, 31); - while (!cancelled && (!boundary.isEmpty() || !pending.isEmpty() || !boundaryBackward.isEmpty() || !pendingBackward.isEmpty())) { - if (!boundary.isEmpty() || !pending.isEmpty()) { - Node b = boundary.peek(); - Node p = pending.peek(); - Node node; - if (p != null && (b == null || p.cost < b.cost)) { - node = pending.poll(); - } else { - node = boundary.poll(); - } + while (!cancelled && (!boundary.isEmpty() || !boundaryBackward.isEmpty())) { + if (!boundary.isEmpty()) { + Node node = boundary.poll(); if (wildernessLevel > 0) { boolean update = false; @@ -540,15 +523,8 @@ private void runBidirectional() { break; } - if (!boundaryBackward.isEmpty() || !pendingBackward.isEmpty()) { - Node b = boundaryBackward.peek(); - Node p = pendingBackward.peek(); - Node node; - if (p != null && (b == null || p.cost < b.cost)) { - node = pendingBackward.poll(); - } else { - node = boundaryBackward.poll(); - } + if (!boundaryBackward.isEmpty()) { + Node node = boundaryBackward.poll(); if (node.packedPosition == start) { joinedPath = combineBidirectionalPath(forwardAt.get(start), node); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tile/Rs2Tile.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tile/Rs2Tile.java index 7c1ae3bc5ea..f74a81a20a2 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tile/Rs2Tile.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tile/Rs2Tile.java @@ -418,6 +418,10 @@ private static HashMap getReachableTilesFromTileInternal(Wo return tileDistances; } + public static HashMap getReachableTilesFromTile(WorldPoint tile) { + return getReachableTilesFromTile(tile, Integer.MAX_VALUE, false); + } + /** * This method calculates the reachable tiles from a given starting tile * considering collision data during the distance calculation. It is a wrapper 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 066549449f4..ea54eeaa141 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 @@ -1060,7 +1060,7 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part boolean lastAttemptedMinimapClickOk = false; long lastAttemptedMinimapClickAtMs = 0L; long pathfinderPendingSinceMs = 0L; - final int REACHABLE_CACHE_RADIUS = 200; + Map reachableTilesCache = null; WorldPoint reachableTilesCacheOrigin = null; for (int processWalkTail = 0; processWalkTail < MAX_PROCESS_WALK_TAIL_ITERATIONS; processWalkTail++) { @@ -1365,7 +1365,7 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part } WorldPoint currentPlayerLoc = Rs2Player.getWorldLocation(); - reachableTilesCache = Rs2Tile.getReachableTilesFromTile(currentPlayerLoc, REACHABLE_CACHE_RADIUS); + reachableTilesCache = Rs2Tile.getReachableTilesFromTile(currentPlayerLoc); reachableTilesCacheOrigin = currentPlayerLoc; for (int i = indexOfStartPoint; !doorOrTransportResult && i < path.size(); i++) { @@ -1508,9 +1508,6 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part } boolean tileReachable = reachableTilesCache.containsKey(currentWorldPoint); - if (!tileReachable && !inInstance) { - tileReachable = Rs2Tile.isTileReachable(currentWorldPoint); - } if (!tileReachable && !inInstance) { // Common stall case: path steps beyond a closed door are unreachable, so the // loop would otherwise "continue" without ever issuing a minimap click and From 0c7ef83f148fe974d58d9cc87ce95172de97f8d1 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sat, 23 May 2026 20:16:05 -0400 Subject: [PATCH 19/51] fix(walker): fairy ring latch, isInteracting stall, bank close - refreshTransports: re-read config with = instead of &= so fairy rings / spirit trees / gliders recover after early startup calls when player data isn't loaded yet - stallThresholdMs: replace Rs2Player.isInteracting() (can get stuck returning true) with isMoving()||isAnimating() for the interacting multiplier - close bank before processWalk alongside existing closeWorldMap() --- .../shortestpath/pathfinder/PathfinderConfig.java | 12 ++++++++---- .../plugins/microbot/util/walker/Rs2Walker.java | 5 ++++- 2 files changed, 12 insertions(+), 5 deletions(-) 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 713be9cfc1f..714773fb024 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 @@ -302,14 +302,18 @@ public void filterLocations(Set locations, boolean canReviveFiltered * @param target Optional target destination for optimized filtering (null for standard filtering) */ private void refreshTransports(WorldPoint target) { - useFairyRings &= !QuestState.NOT_STARTED.equals(Rs2Player.getQuestState(Quest.FAIRYTALE_II__CURE_A_QUEEN)) + useFairyRings = ShortestPathPlugin.override("useFairyRings", config.useFairyRings()) + && !QuestState.NOT_STARTED.equals(Rs2Player.getQuestState(Quest.FAIRYTALE_II__CURE_A_QUEEN)) && (Rs2Inventory.contains(ItemID.DRAMEN_STAFF, ItemID.LUNAR_MOONCLAN_LIMINAL_STAFF) || Rs2Equipment.isWearing(ItemID.DRAMEN_STAFF, ItemID.LUNAR_MOONCLAN_LIMINAL_STAFF) || (ShortestPathPlugin.getPathfinderConfig().useBankItems && (Rs2Bank.hasItem(ItemID.DRAMEN_STAFF) || Rs2Bank.hasItem(ItemID.LUNAR_MOONCLAN_LIMINAL_STAFF))) || Microbot.getVarbitValue(VarbitID.LUMBRIDGE_DIARY_ELITE_COMPLETE) == 1); - useGnomeGliders &= QuestState.FINISHED.equals(Rs2Player.getQuestState(Quest.THE_GRAND_TREE)); - useSpiritTrees &= QuestState.FINISHED.equals(Rs2Player.getQuestState(Quest.TREE_GNOME_VILLAGE)); - useQuetzals &= QuestState.FINISHED.equals(Rs2Player.getQuestState(Quest.TWILIGHTS_PROMISE)); + useGnomeGliders = ShortestPathPlugin.override("useGnomeGliders", config.useGnomeGliders()) + && QuestState.FINISHED.equals(Rs2Player.getQuestState(Quest.THE_GRAND_TREE)); + useSpiritTrees = ShortestPathPlugin.override("useSpiritTrees", config.useSpiritTrees()) + && QuestState.FINISHED.equals(Rs2Player.getQuestState(Quest.TREE_GNOME_VILLAGE)); + useQuetzals = ShortestPathPlugin.override("useQuetzals", config.useQuetzals()) + && QuestState.FINISHED.equals(Rs2Player.getQuestState(Quest.TWILIGHTS_PROMISE)); final Rs2LeaguesTransport.LeaguesContext leaguesCtx = Rs2LeaguesTransport.leaguesContext(); final int refreshCacheKeyHash = computeTransportRefreshCacheKeyHash(target, leaguesCtx); 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 ea54eeaa141..f572a252296 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 @@ -1028,6 +1028,9 @@ private static WalkerState walkWithStateInternal(WorldPoint target, int distance } closeWorldMap(); + if (Rs2Bank.isOpen()) { + Rs2Bank.closeBank(); + } markWalkSessionStart(target); return processWalk(target, distance); } @@ -7015,7 +7018,7 @@ private static long stallThresholdMs() { Rs2Player.isAnimating(), Rs2Player.isMoving(), interimTargetWp != null, - Rs2Player.isInteracting() && interactingActorNearWalkablePath()); + (Rs2Player.isMoving() || Rs2Player.isAnimating()) && interactingActorNearWalkablePath()); } private static boolean isStuckTooLong() { From 237a9850bc4fe9802d39dfb0820f9e74a3d9262d Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sun, 24 May 2026 12:37:52 -0400 Subject: [PATCH 20/51] fix(walker): add diagonal movement to reachable tile BFS, fix false unreachable The BFS in getReachableTilesFromTileInternal only checked 4 cardinal directions, but OSRS supports diagonal movement. Areas where the only path requires diagonal movement (around corners, through diagonal gaps) were falsely flagged unreachable, causing 4-second pauses while the walker ran expensive door/blocker scans on tiles that were actually walkable. - Add NE/NW/SE/SW diagonal expansion to the BFS, matching the collision checks used by the pathfinder's CollisionMap - Refresh the reachable cache before entering the heavy unreachable handler so transient blocks (stale cache) are caught cheaply - Add Taverley wall blocked edges (gate at x=2936 excluded) --- .../plugins/microbot/util/tile/Rs2Tile.java | 46 +++++++++++++++++-- .../microbot/util/walker/Rs2Walker.java | 20 +++++--- .../microbot/shortestpath/blocked_edges.tsv | 15 ++++++ 3 files changed, 71 insertions(+), 10 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tile/Rs2Tile.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tile/Rs2Tile.java index f74a81a20a2..4497b085e53 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tile/Rs2Tile.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tile/Rs2Tile.java @@ -393,26 +393,64 @@ private static HashMap getReachableTilesFromTileInternal(Wo if (dist >= distance) continue; - if ((data & CollisionDataFlag.BLOCK_MOVEMENT_EAST) == 0) { + final boolean canE = (data & CollisionDataFlag.BLOCK_MOVEMENT_EAST) == 0; + final boolean canW = (data & CollisionDataFlag.BLOCK_MOVEMENT_WEST) == 0; + final boolean canN = (data & CollisionDataFlag.BLOCK_MOVEMENT_NORTH) == 0; + final boolean canS = (data & CollisionDataFlag.BLOCK_MOVEMENT_SOUTH) == 0; + + if (canE) { WorldPoint neighbor = point.dx(1); if (tileDistances.putIfAbsent(neighbor, dist + 1) == null) queue.add(neighbor); } - if ((data & CollisionDataFlag.BLOCK_MOVEMENT_WEST) == 0) { + if (canW) { WorldPoint neighbor = point.dx(-1); if (tileDistances.putIfAbsent(neighbor, dist + 1) == null) queue.add(neighbor); } - if ((data & CollisionDataFlag.BLOCK_MOVEMENT_NORTH) == 0) { + if (canN) { WorldPoint neighbor = point.dy(1); if (tileDistances.putIfAbsent(neighbor, dist + 1) == null) queue.add(neighbor); } - if ((data & CollisionDataFlag.BLOCK_MOVEMENT_SOUTH) == 0) { + if (canS) { WorldPoint neighbor = point.dy(-1); if (tileDistances.putIfAbsent(neighbor, dist + 1) == null) queue.add(neighbor); } + + if (canN && canE && isWithinBounds(sx + 1, sy) && isWithinBounds(sx, sy + 1) && isWithinBounds(sx + 1, sy + 1) + && (flags[sx + 1][sy] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_NORTH)) == 0 + && (flags[sx][sy + 1] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_EAST)) == 0 + && (flags[sx + 1][sy + 1] & CollisionDataFlag.BLOCK_MOVEMENT_FULL) == 0) { + WorldPoint neighbor = new WorldPoint(point.getX() + 1, point.getY() + 1, point.getPlane()); + if (tileDistances.putIfAbsent(neighbor, dist + 1) == null) + queue.add(neighbor); + } + if (canN && canW && isWithinBounds(sx - 1, sy) && isWithinBounds(sx, sy + 1) && isWithinBounds(sx - 1, sy + 1) + && (flags[sx - 1][sy] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_NORTH)) == 0 + && (flags[sx][sy + 1] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_WEST)) == 0 + && (flags[sx - 1][sy + 1] & CollisionDataFlag.BLOCK_MOVEMENT_FULL) == 0) { + WorldPoint neighbor = new WorldPoint(point.getX() - 1, point.getY() + 1, point.getPlane()); + if (tileDistances.putIfAbsent(neighbor, dist + 1) == null) + queue.add(neighbor); + } + if (canS && canE && isWithinBounds(sx + 1, sy) && isWithinBounds(sx, sy - 1) && isWithinBounds(sx + 1, sy - 1) + && (flags[sx + 1][sy] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_SOUTH)) == 0 + && (flags[sx][sy - 1] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_EAST)) == 0 + && (flags[sx + 1][sy - 1] & CollisionDataFlag.BLOCK_MOVEMENT_FULL) == 0) { + WorldPoint neighbor = new WorldPoint(point.getX() + 1, point.getY() - 1, point.getPlane()); + if (tileDistances.putIfAbsent(neighbor, dist + 1) == null) + queue.add(neighbor); + } + if (canS && canW && isWithinBounds(sx - 1, sy) && isWithinBounds(sx, sy - 1) && isWithinBounds(sx - 1, sy - 1) + && (flags[sx - 1][sy] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_SOUTH)) == 0 + && (flags[sx][sy - 1] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_WEST)) == 0 + && (flags[sx - 1][sy - 1] & CollisionDataFlag.BLOCK_MOVEMENT_FULL) == 0) { + WorldPoint neighbor = new WorldPoint(point.getX() - 1, point.getY() - 1, point.getPlane()); + if (tileDistances.putIfAbsent(neighbor, dist + 1) == null) + queue.add(neighbor); + } } return tileDistances; 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 f572a252296..f4bb323748d 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 @@ -1512,12 +1512,20 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part boolean tileReachable = reachableTilesCache.containsKey(currentWorldPoint); if (!tileReachable && !inInstance) { - // Common stall case: path steps beyond a closed door are unreachable, so the - // loop would otherwise "continue" without ever issuing a minimap click and - // without triggering door logic (if the unreachable tile is further than the - // handler range). Treat the first unreachable tile as a blocker signal: - // try door handling on the edge that leads into it, then break so the outer - // loop can re-evaluate. + WorldPoint playerLoc = Rs2Player.getWorldLocation(); + if (playerLoc != null) { + int unreachableDist = currentWorldPoint.distanceTo2D(playerLoc); + if (unreachableDist <= HANDLER_RANGE + 2) { + reachableTilesCache = Rs2Tile.getReachableTilesFromTile(playerLoc); + reachableTilesCacheOrigin = playerLoc; + tileReachable = reachableTilesCache.containsKey(currentWorldPoint); + if (tileReachable) { + log.debug("[Walker] tile {} reachable after cache refresh from {}", currentWorldPoint, playerLoc); + } + } + } + } + if (!tileReachable && !inInstance) { WorldPoint playerLoc = Rs2Player.getWorldLocation(); if (playerLoc != null) { int unreachableDist = currentWorldPoint.distanceTo2D(playerLoc); diff --git a/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/blocked_edges.tsv b/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/blocked_edges.tsv index 86e50f9d001..49bc9a2141c 100644 --- a/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/blocked_edges.tsv +++ b/runelite-client/src/main/resources/net/runelite/client/plugins/microbot/shortestpath/blocked_edges.tsv @@ -13,3 +13,18 @@ 3239 3472 0 3239 3471 0 true Varrock Palace garden south fence 3240 3472 0 3240 3471 0 true Varrock Palace garden south fence 3241 3472 0 3241 3471 0 true Varrock Palace garden south fence +# Taverley wall (gate at x=2936 is a door, handled by walker) +2924 3450 0 2924 3449 0 true Taverley wall +2925 3450 0 2925 3449 0 true Taverley wall +2926 3450 0 2926 3449 0 true Taverley wall +2927 3450 0 2927 3449 0 true Taverley wall +2928 3450 0 2928 3449 0 true Taverley wall +2929 3450 0 2929 3449 0 true Taverley wall +2930 3450 0 2930 3449 0 true Taverley wall +2931 3450 0 2931 3449 0 true Taverley wall +2932 3450 0 2932 3449 0 true Taverley wall +2933 3450 0 2933 3449 0 true Taverley wall +2934 3450 0 2934 3449 0 true Taverley wall +2935 3450 0 2935 3449 0 true Taverley wall +2937 3450 0 2937 3449 0 true Taverley wall +2938 3450 0 2938 3449 0 true Taverley wall From 03beb96a19e1d701f1190155e84392dde4f35e1f Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sun, 24 May 2026 13:09:41 -0400 Subject: [PATCH 21/51] =?UTF-8?q?fix(pathfinder):=20bridge=20teleport?= =?UTF-8?q?=E2=86=92network=20transport=20chains=20for=20A*=20discovery?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A* with Chebyshev heuristic can't discover multi-hop transport chains where the intermediate leg (walking to a fairy ring) moves AWAY from the target. The heuristic inflates f-cost on those nodes so the A* settles on a worse single-hop teleport before ever exploring the chain. Pre-seed the boundary queue with bridge nodes representing chained routes: item teleport → walk → network transport (fairy ring, spirit tree, gnome glider). Each bridge destination enters the queue with combined g-cost and its own heuristic, so the A* naturally evaluates the chain against direct routes. Also close world map before opening bank in banked-transport flows. --- .../shortestpath/pathfinder/Pathfinder.java | 73 +++++++++++++++++++ .../microbot/util/walker/Rs2Walker.java | 2 + 2 files changed, 75 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java index 867668a7b08..8338f5e4f5b 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java @@ -5,6 +5,7 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.shortestpath.Transport; +import net.runelite.client.plugins.microbot.shortestpath.TransportType; import net.runelite.client.plugins.microbot.shortestpath.WorldPointUtil; import net.runelite.client.plugins.microbot.util.walker.WebWalkLog; @@ -220,6 +221,76 @@ private void addNeighbors(Node node) { } } + private static final int MAX_CHAIN_WALK_DISTANCE = 250; + private static final EnumSet NETWORK_TRANSPORT_TYPES = EnumSet.of( + TransportType.FAIRY_RING, TransportType.SPIRIT_TREE, TransportType.GNOME_GLIDER); + + private void injectTransportChainNodes(Node startNode) { + Set playerTransports = config.getTransportsPacked().getOrDefault(start, Collections.emptySet()); + if (playerTransports.isEmpty()) return; + + List teleportDests = new ArrayList<>(); + for (Transport teleport : playerTransports) { + if (!TransportType.isTeleport(teleport.getType(), teleport.getOrigin())) continue; + WorldPoint dest = teleport.getDestination(); + if (dest == null) continue; + int cost = config.getDistanceBeforeUsingTeleport() + teleport.getDuration(); + teleportDests.add(new int[]{WorldPointUtil.packWorldPoint(dest), cost}); + } + if (teleportDests.isEmpty()) return; + + int injected = 0; + for (Map.Entry> entry : config.getTransports().entrySet()) { + WorldPoint networkOrigin = entry.getKey(); + if (networkOrigin == null) continue; + int noPacked = WorldPointUtil.packWorldPoint(networkOrigin); + int noX = WorldPointUtil.unpackWorldX(noPacked); + int noY = WorldPointUtil.unpackWorldY(noPacked); + int noZ = WorldPointUtil.unpackWorldPlane(noPacked); + + List networkTransports = new ArrayList<>(); + for (Transport t : entry.getValue()) { + if (NETWORK_TRANSPORT_TYPES.contains(t.getType()) && t.getDestination() != null) { + networkTransports.add(t); + } + } + if (networkTransports.isEmpty()) continue; + + for (int[] td : teleportDests) { + int tdPacked = td[0]; + int teleportCost = td[1]; + int tdZ = WorldPointUtil.unpackWorldPlane(tdPacked); + if (tdZ != noZ) continue; + int tdX = WorldPointUtil.unpackWorldX(tdPacked); + int tdY = WorldPointUtil.unpackWorldY(tdPacked); + int walkDist = Math.max(Math.abs(tdX - noX), Math.abs(tdY - noY)); + if (walkDist == 0 || walkDist > MAX_CHAIN_WALK_DISTANCE) continue; + + WorldPoint tdWp = WorldPointUtil.unpackWorldPoint(tdPacked); + TransportNode teleportNode = new TransportNode(tdWp, startNode, teleportCost); + Node walkNode = new Node(noPacked, teleportNode, teleportCost + walkDist); + + for (Transport nt : networkTransports) { + int destPacked = WorldPointUtil.packWorldPoint(nt.getDestination()); + if (visited.get(destPacked)) continue; + int chainCost = teleportCost + walkDist + nt.getDuration(); + int h = heuristicToNearestTarget(destPacked); + if (chainCost + h >= teleportCost + heuristicToNearestTarget(tdPacked)) continue; + + TransportNode destNode = new TransportNode(nt.getDestination(), walkNode, nt.getDuration()); + destNode.heuristic = h; + visited.set(destPacked); + boundary.add(destNode); + injected++; + } + break; + } + } + if (injected > 0) { + log.debug("[Pathfinder] injected {} transport chain bridge nodes", injected); + } + } + // Admissible A* heuristic: Chebyshev 2D to the nearest target, with a modulo-6400 // fallback for the surface↔underground Y-offset convention (OSRS shifts underground // coords by +6400 on the Y axis, so Varrock sewers live at y≈9800 while Varrock sits @@ -363,6 +434,7 @@ private void runUnidirectional() { long cutoffDurationMillis = config.getCalculationCutoffMillis(); long cutoffTimeMillis = System.currentTimeMillis() + cutoffDurationMillis; config.refreshTeleports(start, 31); + injectTransportChainNodes(startNode); boolean reachedGoal = false; boolean timedOut = false; while (!cancelled && !boundary.isEmpty()) { @@ -471,6 +543,7 @@ private void runBidirectional() { long cutoffDurationMillis = config.getCalculationCutoffMillis(); long cutoffTimeMillis = System.currentTimeMillis() + cutoffDurationMillis; config.refreshTeleports(start, 31); + injectTransportChainNodes(startNode); while (!cancelled && (!boundary.isEmpty() || !boundaryBackward.isEmpty())) { if (!boundary.isEmpty()) { 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 f4bb323748d..2dcf856773f 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 @@ -8409,6 +8409,7 @@ private static WalkerState bootstrapBankMirrorForBankedPathing(int distance) { int epochBefore = Rs2Bank.getBankLiveEpoch(); boolean wasOpen = Rs2Bank.isOpen(); + closeWorldMap(); if (!Rs2Bank.openBank()) { WebWalkLog.spWarn("bank_cache_bootstrap | open_bank_failed bank={}", bankLocation); return WalkerState.EXIT; @@ -8463,6 +8464,7 @@ private static WalkerState walkWithBankingState(WorldPoint bankLocation, } log.info("Arrived at bank location: " + bankLocation); // Step 2: Open bank + closeWorldMap(); if (!Rs2Bank.openBank()) { log.warn("Failed to open bank at: " + bankLocation); return WalkerState.EXIT; From 12bfce763f6aaec625414b48ce2e3fc356ffe692 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sun, 24 May 2026 13:21:43 -0400 Subject: [PATCH 22/51] perf(pathfinder): make transport refresh cache target-independent The transport refresh snapshot was captured AFTER filterSimilarTransports ran, baking target-specific filtering into the cached data. This forced targetPacked into the cache key, meaning every pathfind to a new destination triggered a full 450ms refresh of 11,738 transports. Move snapshot capture BEFORE filterSimilarTransports so the cached data is target-independent. filterSimilarTransports still runs after every cache restore (which it already did), so target-specific filtering is always applied fresh. Remove targetPacked from the cache key. Sequential pathfinds (birdhouse runs, farming runs) now hit the cache on the 2nd+ destination instead of paying 450ms each time. --- .../shortestpath/pathfinder/PathfinderConfig.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) 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 714773fb024..2f0b2431a0c 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 @@ -436,12 +436,6 @@ private void refreshTransports(WorldPoint target) { Rs2LeaguesTransport.injectLeaguesTransports(this, leaguesCtx, usableTeleports, transports, transportsPacked, typeStats); long filterTime = System.currentTimeMillis() - filterStart; - long similarStart = System.currentTimeMillis(); - if (useBankItems && config.maxSimilarTransportDistance() > 0) { - filterSimilarTransports(target); - } - long similarTime = System.currentTimeMillis() - similarStart; - int[] sortedVarbits = varbitIds.stream().mapToInt(Integer::intValue).sorted().toArray(); int[] sortedVarplayers = varplayerIds.stream().mapToInt(Integer::intValue).sorted().toArray(); int[] sortedQuestIds = mergedList.values().stream() @@ -459,6 +453,12 @@ private void refreshTransports(WorldPoint target) { transportRefreshSnapshot = TransportRefreshSnapshot.capture( refreshCacheKeyHash, verificationHash, sortedVarbits, sortedVarplayers, sortedQuestIds, transports, usableTeleports); + long similarStart = System.currentTimeMillis(); + if (useBankItems && config.maxSimilarTransportDistance() > 0) { + filterSimilarTransports(target); + } + long similarTime = System.currentTimeMillis() - similarStart; + refreshAvailableItemIds = null; refreshBoostedLevels = null; refreshCurrencyCache = null; @@ -1419,7 +1419,6 @@ private String getTransportTypeName(Transport transport) { private int computeTransportRefreshCacheKeyHash(WorldPoint target, Rs2LeaguesTransport.LeaguesContext leaguesCtx) { assert leaguesCtx != null; - int targetPacked = target == null ? 0 : WorldPointUtil.packWorldPoint(target); int invFp = fingerprintInventoryEquipmentBank(); int members = (client != null && client.getWorldType().contains(WorldType.MEMBERS)) ? 1 : 0; int preferTp = (config != null && config.preferTransportToTarget()) ? 1 : 0; @@ -1430,7 +1429,6 @@ private int computeTransportRefreshCacheKeyHash(WorldPoint target, Rs2LeaguesTra ignoreTeleportAndItems, useBankItems, useNpcs, - targetPacked, invFp, members, Rs2Walker.disableTeleports, From 76d21e4d877feb7debbed762b38048e055e63876 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sun, 24 May 2026 13:27:37 -0400 Subject: [PATCH 23/51] perf(walker): skip banking route comparison for short-distance walks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit walkWithBankedTransportsAndState ran compareRoutes on every walk, which internally calls getWalkPath 3 times + 2 explicit config.refresh calls — 5 full transport refreshes (450ms each) even for a 20-tile path. For short walks (<=200 Chebyshev), skip the comparison entirely and walk directly. Banking detours only make sense for long-distance routes where a teleport item from the bank could save significant tiles. --- .../client/plugins/microbot/util/walker/Rs2Walker.java | 5 +++++ 1 file changed, 5 insertions(+) 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 2dcf856773f..bb0e981c263 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 @@ -8329,6 +8329,11 @@ private static WalkerState walkWithBankedTransportsAndStateLocked(WorldPoint tar return bootstrapState; } } + int chebyshevToTarget = pl.distanceTo(target); + if (!forceBanking && chebyshevToTarget <= 200) { + WebWalkLog.bankWalkDebug("skip_compare_short_distance dist={} goal={}", chebyshevToTarget, target); + return walkWithStateInternal(target, distance); + } // Check what transport items are needed long compareStartedAt = System.currentTimeMillis(); long compareFromWalkStart = walkSessionStartedAtMs > 0 ? compareStartedAt - walkSessionStartedAtMs : 0L; From 85f98bd7bdd135afbeca62fa27050bd800781fe9 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sun, 24 May 2026 13:34:48 -0400 Subject: [PATCH 24/51] fix(walker): prevent current-tile transport handler from reversing transport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The current-tile transport handler oscillated on stairs: after a planned transport UP (segment handler), the current-tile handler saw the stairs at the new tile, the plane filter allowed it (playerPlane != targetPlane), and it took them back DOWN — creating an infinite up/down loop. Block transports whose destination matches lastTransportHandledAtLocation (where the player was before the last transport). This prevents the handler from undoing a transport that just completed. --- .../client/plugins/microbot/util/walker/Rs2Walker.java | 3 +++ 1 file changed, 3 insertions(+) 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 bb0e981c263..676bfe2cd43 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 @@ -3256,11 +3256,14 @@ private static boolean handleCurrentTileTransportTowardPath(List raw addForwardPathPoints(pathPoints, rawPath, playerLoc); addForwardPathPoints(pathPoints, path, playerLoc); + WorldPoint lastTransportOrigin = lastTransportHandledAtLocation; List candidates = transports.stream() .filter(t -> t.getDestination() != null) // Local adjacent same-plane edges (doors/gates) are handled by segment door/object // logic; current-tile transport probing can bounce on these and create loops. .filter(t -> !isAdjacentSamePlaneTransport(t)) + .filter(t -> lastTransportOrigin == null + || !t.getDestination().equals(lastTransportOrigin)) .filter(t -> target == null || playerLoc.getPlane() != target.getPlane() || t.getDestination().getPlane() == target.getPlane()) From 7bcccd0d739ef4b23123862ae8cef4a505ec4a6a Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sun, 24 May 2026 13:50:26 -0400 Subject: [PATCH 25/51] fix(pathfinder): only inject transport chain bridges for long-distance paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chain bridge injection could pick a teleport→network chain over nearby stairs for cross-plane walks because the heuristic ignores plane changes. A games necklace chain with f=200 beats a stair path whose true cost is 50 but explores 2M nodes to discover. Gate injection behind MIN_CHAIN_INJECT_DISTANCE (500 Chebyshev). Short walks use the normal A* which finds stairs through local expansion. Chains are only beneficial for long-distance routes where walking to a fairy ring after a teleport saves hundreds of tiles. --- .../plugins/microbot/shortestpath/pathfinder/Pathfinder.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java index 8338f5e4f5b..633e4e1f4dc 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java @@ -225,7 +225,11 @@ private void addNeighbors(Node node) { private static final EnumSet NETWORK_TRANSPORT_TYPES = EnumSet.of( TransportType.FAIRY_RING, TransportType.SPIRIT_TREE, TransportType.GNOME_GLIDER); + private static final int MIN_CHAIN_INJECT_DISTANCE = 500; + private void injectTransportChainNodes(Node startNode) { + if (minChebyshevStartToAnyTarget() < MIN_CHAIN_INJECT_DISTANCE) return; + Set playerTransports = config.getTransportsPacked().getOrDefault(start, Collections.emptySet()); if (playerTransports.isEmpty()) return; From 385cebef23db0069974c2af969f3281e34e96b90 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sun, 24 May 2026 13:52:54 -0400 Subject: [PATCH 26/51] revert: remove transport chain bridge injection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The chain injection approach was fundamentally wrong — it created phantom parent chains that don't correspond to real transports, could teleport players out of buildings for in-building walks, and required arbitrary distance guards to contain the damage. Reverted. --- .../shortestpath/pathfinder/Pathfinder.java | 79 +------------------ 1 file changed, 2 insertions(+), 77 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java index 633e4e1f4dc..ba70a5ed709 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java @@ -5,7 +5,6 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.shortestpath.Transport; -import net.runelite.client.plugins.microbot.shortestpath.TransportType; import net.runelite.client.plugins.microbot.shortestpath.WorldPointUtil; import net.runelite.client.plugins.microbot.util.walker.WebWalkLog; @@ -221,80 +220,6 @@ private void addNeighbors(Node node) { } } - private static final int MAX_CHAIN_WALK_DISTANCE = 250; - private static final EnumSet NETWORK_TRANSPORT_TYPES = EnumSet.of( - TransportType.FAIRY_RING, TransportType.SPIRIT_TREE, TransportType.GNOME_GLIDER); - - private static final int MIN_CHAIN_INJECT_DISTANCE = 500; - - private void injectTransportChainNodes(Node startNode) { - if (minChebyshevStartToAnyTarget() < MIN_CHAIN_INJECT_DISTANCE) return; - - Set playerTransports = config.getTransportsPacked().getOrDefault(start, Collections.emptySet()); - if (playerTransports.isEmpty()) return; - - List teleportDests = new ArrayList<>(); - for (Transport teleport : playerTransports) { - if (!TransportType.isTeleport(teleport.getType(), teleport.getOrigin())) continue; - WorldPoint dest = teleport.getDestination(); - if (dest == null) continue; - int cost = config.getDistanceBeforeUsingTeleport() + teleport.getDuration(); - teleportDests.add(new int[]{WorldPointUtil.packWorldPoint(dest), cost}); - } - if (teleportDests.isEmpty()) return; - - int injected = 0; - for (Map.Entry> entry : config.getTransports().entrySet()) { - WorldPoint networkOrigin = entry.getKey(); - if (networkOrigin == null) continue; - int noPacked = WorldPointUtil.packWorldPoint(networkOrigin); - int noX = WorldPointUtil.unpackWorldX(noPacked); - int noY = WorldPointUtil.unpackWorldY(noPacked); - int noZ = WorldPointUtil.unpackWorldPlane(noPacked); - - List networkTransports = new ArrayList<>(); - for (Transport t : entry.getValue()) { - if (NETWORK_TRANSPORT_TYPES.contains(t.getType()) && t.getDestination() != null) { - networkTransports.add(t); - } - } - if (networkTransports.isEmpty()) continue; - - for (int[] td : teleportDests) { - int tdPacked = td[0]; - int teleportCost = td[1]; - int tdZ = WorldPointUtil.unpackWorldPlane(tdPacked); - if (tdZ != noZ) continue; - int tdX = WorldPointUtil.unpackWorldX(tdPacked); - int tdY = WorldPointUtil.unpackWorldY(tdPacked); - int walkDist = Math.max(Math.abs(tdX - noX), Math.abs(tdY - noY)); - if (walkDist == 0 || walkDist > MAX_CHAIN_WALK_DISTANCE) continue; - - WorldPoint tdWp = WorldPointUtil.unpackWorldPoint(tdPacked); - TransportNode teleportNode = new TransportNode(tdWp, startNode, teleportCost); - Node walkNode = new Node(noPacked, teleportNode, teleportCost + walkDist); - - for (Transport nt : networkTransports) { - int destPacked = WorldPointUtil.packWorldPoint(nt.getDestination()); - if (visited.get(destPacked)) continue; - int chainCost = teleportCost + walkDist + nt.getDuration(); - int h = heuristicToNearestTarget(destPacked); - if (chainCost + h >= teleportCost + heuristicToNearestTarget(tdPacked)) continue; - - TransportNode destNode = new TransportNode(nt.getDestination(), walkNode, nt.getDuration()); - destNode.heuristic = h; - visited.set(destPacked); - boundary.add(destNode); - injected++; - } - break; - } - } - if (injected > 0) { - log.debug("[Pathfinder] injected {} transport chain bridge nodes", injected); - } - } - // Admissible A* heuristic: Chebyshev 2D to the nearest target, with a modulo-6400 // fallback for the surface↔underground Y-offset convention (OSRS shifts underground // coords by +6400 on the Y axis, so Varrock sewers live at y≈9800 while Varrock sits @@ -438,7 +363,7 @@ private void runUnidirectional() { long cutoffDurationMillis = config.getCalculationCutoffMillis(); long cutoffTimeMillis = System.currentTimeMillis() + cutoffDurationMillis; config.refreshTeleports(start, 31); - injectTransportChainNodes(startNode); + boolean reachedGoal = false; boolean timedOut = false; while (!cancelled && !boundary.isEmpty()) { @@ -547,7 +472,7 @@ private void runBidirectional() { long cutoffDurationMillis = config.getCalculationCutoffMillis(); long cutoffTimeMillis = System.currentTimeMillis() + cutoffDurationMillis; config.refreshTeleports(start, 31); - injectTransportChainNodes(startNode); + while (!cancelled && (!boundary.isEmpty() || !boundaryBackward.isEmpty())) { if (!boundary.isEmpty()) { From 310a80ca97ca46673795c12515d0571b2a5f4116 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sun, 24 May 2026 14:06:52 -0400 Subject: [PATCH 27/51] fix(pathfinder): suppress teleports for short-distance pathfinds When start and target are within 200 Chebyshev tiles, skip refreshTeleports entirely so the A* only considers walking and local transports (stairs, doors). Prevents the pathfinder from choosing absurd teleport routes (games necklace to Corp) for in-building cross-plane walks where the stairs are nearby but the collision map makes them expensive to discover. --- .../plugins/microbot/shortestpath/pathfinder/Pathfinder.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java index ba70a5ed709..55a9e206530 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java @@ -362,7 +362,10 @@ private void runUnidirectional() { long bestHeuristic = Integer.MAX_VALUE; long cutoffDurationMillis = config.getCalculationCutoffMillis(); long cutoffTimeMillis = System.currentTimeMillis() + cutoffDurationMillis; - config.refreshTeleports(start, 31); + boolean shortDistance = minChebyshevStartToAnyTarget() < 200; + if (!shortDistance) { + config.refreshTeleports(start, 31); + } boolean reachedGoal = false; boolean timedOut = false; From 4ee7009da1fc0c733a9d9345c3931bbcc78cb96b Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sun, 24 May 2026 14:35:49 -0400 Subject: [PATCH 28/51] fix(pathfinder): add Arceuus Library tile to collision ignore list Tile (1621, 3822, plane=1) is fully blocked in collision-map.zip (N/E/S/W all false) despite being walkable in-game. This made the target unreachable, causing the A* to explore 2M nodes across the entire map and ultimately choose a teleport-to-Corp route for an in-building walk. --- .../plugins/microbot/shortestpath/pathfinder/CollisionMap.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 b35f09514a7..43344757747 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 @@ -147,7 +147,8 @@ private static int packedPointFromOrdinal(int startPacked, OrdinalDirection dire {3141, 3456, 0}, {3142, 3456, 0}, {2744, 3153, 0}, {2745, 3153, 0}, {3674, 3882, 0}, {3673, 3884, 0}, {3673, 3885, 0}, {3673, 3886, 0}, {3672, 3888, 0}, {3675, 3893, 0}, {3678, 3893, 0}, {3684, 3845, 0}, - {3670, 3836, 0}, {3672, 3862, 0} + {3670, 3836, 0}, {3672, 3862, 0}, + {1621, 3822, 1}, }; Set set = new HashSet<>(coords.length * 2); for (int[] c : coords) { From 2e40168a30db6171c1b6e42d25f4af080af70587 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sun, 24 May 2026 14:47:55 -0400 Subject: [PATCH 29/51] fix(pathfinder): use live collision data for tiles in the loaded scene MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pathfinder used a static collision-map.zip that can have incorrect data (tiles marked blocked when they're walkable in-game). This caused the A* to explore millions of nodes and choose absurd teleport routes for simple in-building walks. On each refresh, snapshot the game engine's live collision flags for all planes of the loaded scene (104x104). CollisionMap.n/e/s/w now check the live snapshot first for tiles within the scene, falling back to the static map only for tiles outside. The live data is always correct — it reflects actual walls, doors, and terrain as the game engine sees them. Removes the ignoreCollisionPacked bandaid for the Arceuus Library tile since the live data handles it properly. --- .../shortestpath/pathfinder/CollisionMap.java | 42 ++++++++++++++++++- .../pathfinder/PathfinderConfig.java | 20 +++++++++ 2 files changed, 60 insertions(+), 2 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 43344757747..2c4fe7eb786 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 @@ -1,6 +1,7 @@ package net.runelite.client.plugins.microbot.shortestpath.pathfinder; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.CollisionDataFlag; import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.shortestpath.Transport; @@ -19,6 +20,16 @@ public class CollisionMap { private final SplitFlagMap collisionData; + private volatile int[][][] liveFlags; + private volatile int liveBaseX, liveBaseY; + private static final int SCENE_SIZE = 104; + + public void setLiveCollisionSnapshot(int[][][] flags, int baseX, int baseY) { + this.liveFlags = flags; + this.liveBaseX = baseX; + this.liveBaseY = baseY; + } + public byte[] getPlanes() { return collisionData.getRegionMapPlaneCounts(); } @@ -32,18 +43,46 @@ private boolean get(int x, int y, int z, int flag) { } public boolean n(int x, int y, int z) { + int[][][] live = liveFlags; + if (live != null && z >= 0 && z < live.length && live[z] != null) { + int sx = x - liveBaseX, sy = y - liveBaseY; + if (sx >= 0 && sx < SCENE_SIZE && sy >= 0 && sy < SCENE_SIZE) { + return (live[z][sx][sy] & CollisionDataFlag.BLOCK_MOVEMENT_NORTH) == 0; + } + } return get(x, y, z, 0); } public boolean s(int x, int y, int z) { + int[][][] live = liveFlags; + if (live != null && z >= 0 && z < live.length && live[z] != null) { + int sx = x - liveBaseX, sy = y - liveBaseY; + if (sx >= 0 && sx < SCENE_SIZE && sy >= 0 && sy < SCENE_SIZE) { + return (live[z][sx][sy] & CollisionDataFlag.BLOCK_MOVEMENT_SOUTH) == 0; + } + } return n(x, y - 1, z); } public boolean e(int x, int y, int z) { + int[][][] live = liveFlags; + if (live != null && z >= 0 && z < live.length && live[z] != null) { + int sx = x - liveBaseX, sy = y - liveBaseY; + if (sx >= 0 && sx < SCENE_SIZE && sy >= 0 && sy < SCENE_SIZE) { + return (live[z][sx][sy] & CollisionDataFlag.BLOCK_MOVEMENT_EAST) == 0; + } + } return get(x, y, z, 1); } public boolean w(int x, int y, int z) { + int[][][] live = liveFlags; + if (live != null && z >= 0 && z < live.length && live[z] != null) { + int sx = x - liveBaseX, sy = y - liveBaseY; + if (sx >= 0 && sx < SCENE_SIZE && sy >= 0 && sy < SCENE_SIZE) { + return (live[z][sx][sy] & CollisionDataFlag.BLOCK_MOVEMENT_WEST) == 0; + } + } return e(x - 1, y, z); } @@ -147,8 +186,7 @@ private static int packedPointFromOrdinal(int startPacked, OrdinalDirection dire {3141, 3456, 0}, {3142, 3456, 0}, {2744, 3153, 0}, {2745, 3153, 0}, {3674, 3882, 0}, {3673, 3884, 0}, {3673, 3885, 0}, {3673, 3886, 0}, {3672, 3888, 0}, {3675, 3893, 0}, {3678, 3893, 0}, {3684, 3845, 0}, - {3670, 3836, 0}, {3672, 3862, 0}, - {1621, 3822, 1}, + {3670, 3836, 0}, {3672, 3862, 0} }; Set set = new HashSet<>(coords.length * 2); for (int[] c : coords) { 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 2f0b2431a0c..b7bb5dd50b3 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 @@ -188,6 +188,25 @@ public CollisionMap getMap() { return map.get(); } + private void captureLiveCollisionSnapshot() { + Microbot.getClientThread().runOnClientThreadOptional(() -> { + WorldView wv = client.getTopLevelWorldView(); + if (wv == null) return true; + CollisionData[] cd = wv.getCollisionMaps(); + if (cd == null) return true; + int[][][] snapshot = new int[cd.length][][]; + for (int p = 0; p < cd.length; p++) { + int[][] src = cd[p].getFlags(); + snapshot[p] = new int[src.length][]; + for (int i = 0; i < src.length; i++) { + snapshot[p][i] = src[i].clone(); + } + } + getMap().setLiveCollisionSnapshot(snapshot, wv.getBaseX(), wv.getBaseY()); + return true; + }); + } + public void refresh(WorldPoint target) { calculationCutoffMillis = (long) config.calculationCutoff() * Constants.GAME_TICK_LENGTH; avoidWilderness = ShortestPathPlugin.override("avoidWilderness", config.avoidWilderness()); @@ -228,6 +247,7 @@ public void refresh(WorldPoint target) { //END microbot variables if (GameState.LOGGED_IN.equals(client.getGameState())) { + captureLiveCollisionSnapshot(); long t0 = System.currentTimeMillis(); refreshTransports(target); long t1 = System.currentTimeMillis(); From 80232f5b61fc7e5a781bb3253068b17a9c05ab15 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sun, 24 May 2026 15:07:39 -0400 Subject: [PATCH 30/51] fix(pathfinder): make live collision snapshot static (ThreadLocal bug) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CollisionMap is stored in a ThreadLocal — each thread gets its own instance. The snapshot was captured on the refresh thread's instance but the pathfinder runs on a different thread with a different CollisionMap that never received the snapshot. Making the snapshot fields and setter static ensures all threads see the same live data. --- .../shortestpath/pathfinder/CollisionMap.java | 12 ++++++------ .../shortestpath/pathfinder/PathfinderConfig.java | 2 +- 2 files changed, 7 insertions(+), 7 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 2c4fe7eb786..397ab0399f8 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 @@ -20,14 +20,14 @@ public class CollisionMap { private final SplitFlagMap collisionData; - private volatile int[][][] liveFlags; - private volatile int liveBaseX, liveBaseY; + private static volatile int[][][] liveFlags; + private static volatile int liveBaseX, liveBaseY; private static final int SCENE_SIZE = 104; - public void setLiveCollisionSnapshot(int[][][] flags, int baseX, int baseY) { - this.liveFlags = flags; - this.liveBaseX = baseX; - this.liveBaseY = baseY; + public static void setLiveCollisionSnapshot(int[][][] flags, int baseX, int baseY) { + liveFlags = flags; + liveBaseX = baseX; + liveBaseY = baseY; } public byte[] getPlanes() { 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 b7bb5dd50b3..aac3fd049a8 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 @@ -202,7 +202,7 @@ private void captureLiveCollisionSnapshot() { snapshot[p][i] = src[i].clone(); } } - getMap().setLiveCollisionSnapshot(snapshot, wv.getBaseX(), wv.getBaseY()); + CollisionMap.setLiveCollisionSnapshot(snapshot, wv.getBaseX(), wv.getBaseY()); return true; }); } From d986b8da3393eacd6d26c639d083636b6023577e Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sun, 24 May 2026 15:49:18 -0400 Subject: [PATCH 31/51] fix(pathfinder): fix live collision snapshot correctness issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two bugs found by review: 1. isBlocked() didn't check BLOCK_MOVEMENT_FULL from live data. A tile occupied by an object (rock, tree) with no directional flags set would appear walkable — the pathfinder could route through walls. Now checks BLOCK_MOVEMENT_FULL first. 2. Three separate volatile fields (liveFlags, liveBaseX, liveBaseY) could be read inconsistently during a scene change. Bundle into a single immutable LiveSnapshot object with one volatile reference. Also consolidate the repeated live-flag lookup into liveFlag(). --- .../shortestpath/pathfinder/CollisionMap.java | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 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 397ab0399f8..00c274da686 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 @@ -20,14 +20,19 @@ public class CollisionMap { private final SplitFlagMap collisionData; - private static volatile int[][][] liveFlags; - private static volatile int liveBaseX, liveBaseY; private static final int SCENE_SIZE = 104; + private static volatile LiveSnapshot liveSnapshot; + + private static final class LiveSnapshot { + final int[][][] flags; + final int baseX, baseY; + LiveSnapshot(int[][][] flags, int baseX, int baseY) { + this.flags = flags; this.baseX = baseX; this.baseY = baseY; + } + } public static void setLiveCollisionSnapshot(int[][][] flags, int baseX, int baseY) { - liveFlags = flags; - liveBaseX = baseX; - liveBaseY = baseY; + liveSnapshot = new LiveSnapshot(flags, baseX, baseY); } public byte[] getPlanes() { @@ -42,48 +47,35 @@ private boolean get(int x, int y, int z, int flag) { return collisionData.get(x, y, z, flag); } - public boolean n(int x, int y, int z) { - int[][][] live = liveFlags; - if (live != null && z >= 0 && z < live.length && live[z] != null) { - int sx = x - liveBaseX, sy = y - liveBaseY; + private int liveFlag(int x, int y, int z) { + LiveSnapshot snap = liveSnapshot; + if (snap != null && z >= 0 && z < snap.flags.length && snap.flags[z] != null) { + int sx = x - snap.baseX, sy = y - snap.baseY; if (sx >= 0 && sx < SCENE_SIZE && sy >= 0 && sy < SCENE_SIZE) { - return (live[z][sx][sy] & CollisionDataFlag.BLOCK_MOVEMENT_NORTH) == 0; + return snap.flags[z][sx][sy]; } } - return get(x, y, z, 0); + return -1; + } + + public boolean n(int x, int y, int z) { + int f = liveFlag(x, y, z); + return f >= 0 ? (f & CollisionDataFlag.BLOCK_MOVEMENT_NORTH) == 0 : get(x, y, z, 0); } public boolean s(int x, int y, int z) { - int[][][] live = liveFlags; - if (live != null && z >= 0 && z < live.length && live[z] != null) { - int sx = x - liveBaseX, sy = y - liveBaseY; - if (sx >= 0 && sx < SCENE_SIZE && sy >= 0 && sy < SCENE_SIZE) { - return (live[z][sx][sy] & CollisionDataFlag.BLOCK_MOVEMENT_SOUTH) == 0; - } - } - return n(x, y - 1, z); + int f = liveFlag(x, y, z); + return f >= 0 ? (f & CollisionDataFlag.BLOCK_MOVEMENT_SOUTH) == 0 : n(x, y - 1, z); } public boolean e(int x, int y, int z) { - int[][][] live = liveFlags; - if (live != null && z >= 0 && z < live.length && live[z] != null) { - int sx = x - liveBaseX, sy = y - liveBaseY; - if (sx >= 0 && sx < SCENE_SIZE && sy >= 0 && sy < SCENE_SIZE) { - return (live[z][sx][sy] & CollisionDataFlag.BLOCK_MOVEMENT_EAST) == 0; - } - } - return get(x, y, z, 1); + int f = liveFlag(x, y, z); + return f >= 0 ? (f & CollisionDataFlag.BLOCK_MOVEMENT_EAST) == 0 : get(x, y, z, 1); } public boolean w(int x, int y, int z) { - int[][][] live = liveFlags; - if (live != null && z >= 0 && z < live.length && live[z] != null) { - int sx = x - liveBaseX, sy = y - liveBaseY; - if (sx >= 0 && sx < SCENE_SIZE && sy >= 0 && sy < SCENE_SIZE) { - return (live[z][sx][sy] & CollisionDataFlag.BLOCK_MOVEMENT_WEST) == 0; - } - } - return e(x - 1, y, z); + int f = liveFlag(x, y, z); + return f >= 0 ? (f & CollisionDataFlag.BLOCK_MOVEMENT_WEST) == 0 : e(x - 1, y, z); } private boolean ne(int x, int y, int z) { @@ -103,6 +95,14 @@ private boolean sw(int x, int y, int z) { } public boolean isBlocked(int x, int y, int z) { + int f = liveFlag(x, y, z); + if (f >= 0) { + return (f & CollisionDataFlag.BLOCK_MOVEMENT_FULL) != 0 + || ((f & (CollisionDataFlag.BLOCK_MOVEMENT_NORTH | CollisionDataFlag.BLOCK_MOVEMENT_SOUTH + | CollisionDataFlag.BLOCK_MOVEMENT_EAST | CollisionDataFlag.BLOCK_MOVEMENT_WEST)) + == (CollisionDataFlag.BLOCK_MOVEMENT_NORTH | CollisionDataFlag.BLOCK_MOVEMENT_SOUTH + | CollisionDataFlag.BLOCK_MOVEMENT_EAST | CollisionDataFlag.BLOCK_MOVEMENT_WEST)); + } return !n(x, y, z) && !s(x, y, z) && !e(x, y, z) && !w(x, y, z); } From 25c367ca84b06a1aad7e77902a2576ff1cea864f Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sun, 24 May 2026 15:59:08 -0400 Subject: [PATCH 32/51] fix(pathfinder): fully suppress teleports for short-distance paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The initial refreshTeleports guard was bypassed by the wilderness level initialization. wildernessLevel starts at 31, so on the FIRST node expansion the A* detected 'not in wilderness' and called refreshTeleports(node, 0) — adding all teleports including games necklace to Corp. This caused the pathfinder to teleport out of buildings for in-building cross-plane walks. For short-distance paths: set wildernessLevel=0 upfront (skipping the initial refresh), and guard the in-loop wilderness refresh with the shortDistance flag so teleports are never injected mid-search. --- .../microbot/shortestpath/pathfinder/Pathfinder.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java index 55a9e206530..b36741264a3 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java @@ -363,7 +363,9 @@ private void runUnidirectional() { long cutoffDurationMillis = config.getCalculationCutoffMillis(); long cutoffTimeMillis = System.currentTimeMillis() + cutoffDurationMillis; boolean shortDistance = minChebyshevStartToAnyTarget() < 200; - if (!shortDistance) { + if (shortDistance) { + wildernessLevel = 0; + } else { config.refreshTeleports(start, 31); } @@ -372,7 +374,7 @@ private void runUnidirectional() { while (!cancelled && !boundary.isEmpty()) { Node node = boundary.poll(); - if (wildernessLevel > 0) { + if (!shortDistance && wildernessLevel > 0) { boolean update = false; if (wildernessLevel > 30 && !config.isInLevel30Wilderness(node.packedPosition)) { From 99561207e225e4e50a2a679f70831034c69494b2 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sun, 24 May 2026 16:05:19 -0400 Subject: [PATCH 33/51] fix(pathfinder): live collision data can only unblock, never add blocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The live snapshot was REPLACING static data for scene tiles, which introduced a regression: closed doors in live data blocked paths that the static map showed as open, preventing the pathfinder from finding stair routes and forcing teleport detours. Now: if the static map says walkable, trust it. Only consult live data when the static map says blocked — the live data can reveal the tile is actually walkable (fixing stale static data) but can never make a walkable tile appear blocked. --- .../shortestpath/pathfinder/CollisionMap.java | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 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 00c274da686..686ac71e398 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 @@ -59,23 +59,31 @@ private int liveFlag(int x, int y, int z) { } public boolean n(int x, int y, int z) { + boolean staticResult = get(x, y, z, 0); + if (staticResult) return true; int f = liveFlag(x, y, z); - return f >= 0 ? (f & CollisionDataFlag.BLOCK_MOVEMENT_NORTH) == 0 : get(x, y, z, 0); + return f >= 0 && (f & CollisionDataFlag.BLOCK_MOVEMENT_NORTH) == 0; } public boolean s(int x, int y, int z) { + boolean staticResult = n(x, y - 1, z); + if (staticResult) return true; int f = liveFlag(x, y, z); - return f >= 0 ? (f & CollisionDataFlag.BLOCK_MOVEMENT_SOUTH) == 0 : n(x, y - 1, z); + return f >= 0 && (f & CollisionDataFlag.BLOCK_MOVEMENT_SOUTH) == 0; } public boolean e(int x, int y, int z) { + boolean staticResult = get(x, y, z, 1); + if (staticResult) return true; int f = liveFlag(x, y, z); - return f >= 0 ? (f & CollisionDataFlag.BLOCK_MOVEMENT_EAST) == 0 : get(x, y, z, 1); + return f >= 0 && (f & CollisionDataFlag.BLOCK_MOVEMENT_EAST) == 0; } public boolean w(int x, int y, int z) { + boolean staticResult = e(x - 1, y, z); + if (staticResult) return true; int f = liveFlag(x, y, z); - return f >= 0 ? (f & CollisionDataFlag.BLOCK_MOVEMENT_WEST) == 0 : e(x - 1, y, z); + return f >= 0 && (f & CollisionDataFlag.BLOCK_MOVEMENT_WEST) == 0; } private boolean ne(int x, int y, int z) { @@ -95,14 +103,6 @@ private boolean sw(int x, int y, int z) { } public boolean isBlocked(int x, int y, int z) { - int f = liveFlag(x, y, z); - if (f >= 0) { - return (f & CollisionDataFlag.BLOCK_MOVEMENT_FULL) != 0 - || ((f & (CollisionDataFlag.BLOCK_MOVEMENT_NORTH | CollisionDataFlag.BLOCK_MOVEMENT_SOUTH - | CollisionDataFlag.BLOCK_MOVEMENT_EAST | CollisionDataFlag.BLOCK_MOVEMENT_WEST)) - == (CollisionDataFlag.BLOCK_MOVEMENT_NORTH | CollisionDataFlag.BLOCK_MOVEMENT_SOUTH - | CollisionDataFlag.BLOCK_MOVEMENT_EAST | CollisionDataFlag.BLOCK_MOVEMENT_WEST)); - } return !n(x, y, z) && !s(x, y, z) && !e(x, y, z) && !w(x, y, z); } From b6123be9e9dc6b3336d69030891952eab93f8485 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Tue, 26 May 2026 16:16:45 -0400 Subject: [PATCH 34/51] fix(pathfinder): revert live collision, restore pending queue, transport-zone Dijkstra - Revert live collision snapshot (CollisionMap + PathfinderConfig): the "unblock only" overlay had structural bugs (missing BLOCK_MOVEMENT_FULL, s()/w() not checking destination tile) causing paths through furniture. Walker already handles dynamic obstacles via door/transport handlers. - Restore upstream Pathfinder with pending queue (transports sorted by g-cost, walking nodes by f-cost). Our prior merge into boundary with heuristic made transport chain discovery worse, not better. - Transport-zone Dijkstra: walking nodes after a transport landing get heuristic=0 (Dijkstra) so A* freely explores to find the next transport in a chain. Pure walking retains full Chebyshev heuristic for performance. This fixes multi-hop routing (e.g. Ardougne cloak -> fairy ring). - Fix transportsPacked overwrite in refreshTeleports: the packed map was unconditionally overwritten with teleports-only, losing existing transports (stairs/doors) at the player's position. Now uses the merged set. --- .../shortestpath/pathfinder/CollisionMap.java | 47 ++----------- .../shortestpath/pathfinder/Pathfinder.java | 66 ++++++++++++------- .../pathfinder/PathfinderConfig.java | 23 +------ 3 files changed, 49 insertions(+), 87 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 686ac71e398..b35f09514a7 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 @@ -1,7 +1,6 @@ package net.runelite.client.plugins.microbot.shortestpath.pathfinder; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.CollisionDataFlag; import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.shortestpath.Transport; @@ -20,21 +19,6 @@ public class CollisionMap { private final SplitFlagMap collisionData; - private static final int SCENE_SIZE = 104; - private static volatile LiveSnapshot liveSnapshot; - - private static final class LiveSnapshot { - final int[][][] flags; - final int baseX, baseY; - LiveSnapshot(int[][][] flags, int baseX, int baseY) { - this.flags = flags; this.baseX = baseX; this.baseY = baseY; - } - } - - public static void setLiveCollisionSnapshot(int[][][] flags, int baseX, int baseY) { - liveSnapshot = new LiveSnapshot(flags, baseX, baseY); - } - public byte[] getPlanes() { return collisionData.getRegionMapPlaneCounts(); } @@ -47,43 +31,20 @@ private boolean get(int x, int y, int z, int flag) { return collisionData.get(x, y, z, flag); } - private int liveFlag(int x, int y, int z) { - LiveSnapshot snap = liveSnapshot; - if (snap != null && z >= 0 && z < snap.flags.length && snap.flags[z] != null) { - int sx = x - snap.baseX, sy = y - snap.baseY; - if (sx >= 0 && sx < SCENE_SIZE && sy >= 0 && sy < SCENE_SIZE) { - return snap.flags[z][sx][sy]; - } - } - return -1; - } - public boolean n(int x, int y, int z) { - boolean staticResult = get(x, y, z, 0); - if (staticResult) return true; - int f = liveFlag(x, y, z); - return f >= 0 && (f & CollisionDataFlag.BLOCK_MOVEMENT_NORTH) == 0; + return get(x, y, z, 0); } public boolean s(int x, int y, int z) { - boolean staticResult = n(x, y - 1, z); - if (staticResult) return true; - int f = liveFlag(x, y, z); - return f >= 0 && (f & CollisionDataFlag.BLOCK_MOVEMENT_SOUTH) == 0; + return n(x, y - 1, z); } public boolean e(int x, int y, int z) { - boolean staticResult = get(x, y, z, 1); - if (staticResult) return true; - int f = liveFlag(x, y, z); - return f >= 0 && (f & CollisionDataFlag.BLOCK_MOVEMENT_EAST) == 0; + return get(x, y, z, 1); } public boolean w(int x, int y, int z) { - boolean staticResult = e(x - 1, y, z); - if (staticResult) return true; - int f = liveFlag(x, y, z); - return f >= 0 && (f & CollisionDataFlag.BLOCK_MOVEMENT_WEST) == 0; + return e(x - 1, y, z); } private boolean ne(int x, int y, int z) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java index b36741264a3..9cb4632459f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java @@ -204,17 +204,19 @@ private Set buildTransportAnchors(List path) { private void addNeighbors(Node node) { List nodes = map.getNeighbors(node, visited, config, targets); + boolean afterTransport = node instanceof TransportNode || node.heuristic == 0; for (Node neighbor : nodes) { if (config.avoidWilderness(node.packedPosition, neighbor.packedPosition, targetInWilderness)) { continue; } visited.set(neighbor.packedPosition); - neighbor.heuristic = heuristicToNearestTarget(neighbor.packedPosition); - boundary.add(neighbor); if (neighbor instanceof TransportNode) { + pending.add(neighbor); ++stats.transportsChecked; } else { + neighbor.heuristic = afterTransport ? 0 : heuristicToNearestTarget(neighbor.packedPosition); + boundary.add(neighbor); ++stats.nodesChecked; } } @@ -307,17 +309,19 @@ private List combineBidirectionalPath(Node forwardAtMeet, Node backw private void addNeighborsForwardWithMeet(Node node, Map forwardAt, Map backwardAt, long[] bestMeetingCost, Node[] meetF, Node[] meetB) { List nodes = map.getNeighbors(node, visited, config, targets); + boolean afterTransport = node instanceof TransportNode || node.heuristic == 0; for (Node neighbor : nodes) { if (config.avoidWilderness(node.packedPosition, neighbor.packedPosition, targetInWilderness)) { continue; } visited.set(neighbor.packedPosition); - neighbor.heuristic = heuristicToNearestTarget(neighbor.packedPosition); - boundary.add(neighbor); if (neighbor instanceof TransportNode) { + pending.add(neighbor); ++stats.transportsChecked; } else { + neighbor.heuristic = afterTransport ? 0 : heuristicToNearestTarget(neighbor.packedPosition); + boundary.add(neighbor); ++stats.nodesChecked; } forwardAt.putIfAbsent(neighbor.packedPosition, neighbor); @@ -332,17 +336,19 @@ private void addNeighborsBackwardWithMeet(Node node, VisitedTiles visitedB, Map< Set puzzleAllow, Map forwardAt, Map backwardAt, long[] bestMeetingCost, Node[] meetF, Node[] meetB) { List nodes = map.getReverseNeighbors(node, visitedB, config, puzzleAllow, incoming); + boolean afterTransport = node instanceof TransportNode || node.heuristic == 0; for (Node pred : nodes) { if (config.avoidWilderness(pred.packedPosition, node.packedPosition, targetInWilderness)) { continue; } visitedB.set(pred.packedPosition); - pred.heuristic = heuristicFromStart(pred.packedPosition); - boundaryBackward.add(pred); if (pred instanceof TransportNode) { + pendingBackward.add(pred); ++stats.transportsChecked; } else { + pred.heuristic = afterTransport ? 0 : heuristicFromStart(pred.packedPosition); + boundaryBackward.add(pred); ++stats.nodesChecked; } backwardAt.putIfAbsent(pred.packedPosition, pred); @@ -362,19 +368,20 @@ private void runUnidirectional() { long bestHeuristic = Integer.MAX_VALUE; long cutoffDurationMillis = config.getCalculationCutoffMillis(); long cutoffTimeMillis = System.currentTimeMillis() + cutoffDurationMillis; - boolean shortDistance = minChebyshevStartToAnyTarget() < 200; - if (shortDistance) { - wildernessLevel = 0; - } else { - config.refreshTeleports(start, 31); - } - + config.refreshTeleports(start, 31); boolean reachedGoal = false; boolean timedOut = false; - while (!cancelled && !boundary.isEmpty()) { - Node node = boundary.poll(); + while (!cancelled && (!boundary.isEmpty() || !pending.isEmpty())) { + Node b = boundary.peek(); + Node p = pending.peek(); + Node node; + if (p != null && (b == null || p.cost < b.cost)) { + node = pending.poll(); + } else { + node = boundary.poll(); + } - if (!shortDistance && wildernessLevel > 0) { + if (wildernessLevel > 0) { boolean update = false; if (wildernessLevel > 30 && !config.isInLevel30Wilderness(node.packedPosition)) { @@ -430,7 +437,7 @@ private void runUnidirectional() { String uniExit = cancelled ? "cancelled" : reachedGoal ? "reached-goal" : timedOut ? "time-cutoff" - : boundary.isEmpty() ? "queues-drained" : "loop-ended"; + : (boundary.isEmpty() && pending.isEmpty()) ? "queues-drained" : "loop-ended"; pathfinderDiag("uni finished exit=%s cancelled=%s boundaryEmpty=%s pendingEmpty=%s bestLastNode=%s cutoffMs=%d", uniExit, cancelled, @@ -478,10 +485,16 @@ private void runBidirectional() { long cutoffTimeMillis = System.currentTimeMillis() + cutoffDurationMillis; config.refreshTeleports(start, 31); - - while (!cancelled && (!boundary.isEmpty() || !boundaryBackward.isEmpty())) { - if (!boundary.isEmpty()) { - Node node = boundary.poll(); + while (!cancelled && (!boundary.isEmpty() || !pending.isEmpty() || !boundaryBackward.isEmpty() || !pendingBackward.isEmpty())) { + if (!boundary.isEmpty() || !pending.isEmpty()) { + Node b = boundary.peek(); + Node p = pending.peek(); + Node node; + if (p != null && (b == null || p.cost < b.cost)) { + node = pending.poll(); + } else { + node = boundary.poll(); + } if (wildernessLevel > 0) { boolean update = false; @@ -530,8 +543,15 @@ private void runBidirectional() { break; } - if (!boundaryBackward.isEmpty()) { - Node node = boundaryBackward.poll(); + if (!boundaryBackward.isEmpty() || !pendingBackward.isEmpty()) { + Node b = boundaryBackward.peek(); + Node p = pendingBackward.peek(); + Node node; + if (p != null && (b == null || p.cost < b.cost)) { + node = pendingBackward.poll(); + } else { + node = boundaryBackward.poll(); + } if (node.packedPosition == start) { joinedPath = combineBidirectionalPath(forwardAt.get(start), node); 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 aac3fd049a8..6cb40e72200 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 @@ -188,25 +188,6 @@ public CollisionMap getMap() { return map.get(); } - private void captureLiveCollisionSnapshot() { - Microbot.getClientThread().runOnClientThreadOptional(() -> { - WorldView wv = client.getTopLevelWorldView(); - if (wv == null) return true; - CollisionData[] cd = wv.getCollisionMaps(); - if (cd == null) return true; - int[][][] snapshot = new int[cd.length][][]; - for (int p = 0; p < cd.length; p++) { - int[][] src = cd[p].getFlags(); - snapshot[p] = new int[src.length][]; - for (int i = 0; i < src.length; i++) { - snapshot[p][i] = src[i].clone(); - } - } - CollisionMap.setLiveCollisionSnapshot(snapshot, wv.getBaseX(), wv.getBaseY()); - return true; - }); - } - public void refresh(WorldPoint target) { calculationCutoffMillis = (long) config.calculationCutoff() * Constants.GAME_TICK_LENGTH; avoidWilderness = ShortestPathPlugin.override("avoidWilderness", config.avoidWilderness()); @@ -247,7 +228,6 @@ public void refresh(WorldPoint target) { //END microbot variables if (GameState.LOGGED_IN.equals(client.getGameState())) { - captureLiveCollisionSnapshot(); long t0 = System.currentTimeMillis(); refreshTransports(target); long t1 = System.currentTimeMillis(); @@ -287,10 +267,11 @@ public void refreshTeleports(int packedLocation, int wildernessLevel) { Set existingTeleports = transports.get(key); if (existingTeleports != null) { existingTeleports.addAll(usableWildyTeleports); + transportsPacked.put(packedLocation, existingTeleports); } else { transports.put(key, usableWildyTeleports); + transportsPacked.put(packedLocation, usableWildyTeleports); } - transportsPacked.put(packedLocation, usableWildyTeleports); } } From 271fdb931eae6c116f4bf50d83baf8fa4026f7a6 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Tue, 26 May 2026 16:16:58 -0400 Subject: [PATCH 35/51] fix(walker): cross-plane skip, bounded BFS, diagonal isTileReachable, short-path clicks - Skip path tiles on a different plane than the player in processWalk loop. These tiles trigger unreachable handlers, BFS refreshes, and door probes that can never succeed from the wrong plane. Eliminates 8-second stalls on cross-plane walks. - Bound getReachableTilesFromTile to HANDLER_RANGE*3 (39 tiles) instead of Integer.MAX_VALUE. Reduces BFS from ~10,816 tiles to ~6,000 max while covering routes around walls. - Add diagonal movement to isTileReachableInternal to match getReachableTilesFromTileInternal. Fixes disagreement between the two methods that caused oscillation on diagonally-reachable tiles. - For paths <= 5 tiles, set nextWalkingDistance to 0 so the path loop can click nearby tiles instead of spinning without issuing movement. - Lower banking skip threshold from 200 to 100 Chebyshev. - Recalculate path instead of giving up on pathfinder-still-null when target is still set. --- .../plugins/microbot/util/tile/Rs2Tile.java | 38 +++++++++++++++++++ .../microbot/util/walker/Rs2Walker.java | 17 +++++++-- 2 files changed, 51 insertions(+), 4 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tile/Rs2Tile.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tile/Rs2Tile.java index 4497b085e53..6a8fd57a98d 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tile/Rs2Tile.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tile/Rs2Tile.java @@ -538,10 +538,48 @@ private static boolean isTileReachableInternal(WorldPoint targetPoint) { int y = point & 0xFFFF; if (isWithinBounds(x, y)) { + boolean canN = (flags[x][y] & CollisionDataFlag.BLOCK_MOVEMENT_NORTH) == 0; + boolean canS = (flags[x][y] & CollisionDataFlag.BLOCK_MOVEMENT_SOUTH) == 0; + boolean canE = (flags[x][y] & CollisionDataFlag.BLOCK_MOVEMENT_EAST) == 0; + boolean canW = (flags[x][y] & CollisionDataFlag.BLOCK_MOVEMENT_WEST) == 0; + checkAndAddNeighbour(queue, visited, flags, x, y, -1, 0, CollisionDataFlag.BLOCK_MOVEMENT_WEST); checkAndAddNeighbour(queue, visited, flags, x, y, 1, 0, CollisionDataFlag.BLOCK_MOVEMENT_EAST); checkAndAddNeighbour(queue, visited, flags, x, y, 0, -1, CollisionDataFlag.BLOCK_MOVEMENT_SOUTH); checkAndAddNeighbour(queue, visited, flags, x, y, 0, 1, CollisionDataFlag.BLOCK_MOVEMENT_NORTH); + + if (canN && canE && isWithinBounds(x + 1, y + 1) + && !visited[x + 1][y + 1] + && (flags[x + 1][y] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_NORTH)) == 0 + && (flags[x][y + 1] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_EAST)) == 0 + && (flags[x + 1][y + 1] & CollisionDataFlag.BLOCK_MOVEMENT_FULL) == 0) { + queue.add(((x + 1) << 16) | (y + 1)); + visited[x + 1][y + 1] = true; + } + if (canN && canW && isWithinBounds(x - 1, y + 1) + && !visited[x - 1][y + 1] + && (flags[x - 1][y] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_NORTH)) == 0 + && (flags[x][y + 1] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_WEST)) == 0 + && (flags[x - 1][y + 1] & CollisionDataFlag.BLOCK_MOVEMENT_FULL) == 0) { + queue.add(((x - 1) << 16) | (y + 1)); + visited[x - 1][y + 1] = true; + } + if (canS && canE && isWithinBounds(x + 1, y - 1) + && !visited[x + 1][y - 1] + && (flags[x + 1][y] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_SOUTH)) == 0 + && (flags[x][y - 1] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_EAST)) == 0 + && (flags[x + 1][y - 1] & CollisionDataFlag.BLOCK_MOVEMENT_FULL) == 0) { + queue.add(((x + 1) << 16) | (y - 1)); + visited[x + 1][y - 1] = true; + } + if (canS && canW && isWithinBounds(x - 1, y - 1) + && !visited[x - 1][y - 1] + && (flags[x - 1][y] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_SOUTH)) == 0 + && (flags[x][y - 1] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_WEST)) == 0 + && (flags[x - 1][y - 1] & CollisionDataFlag.BLOCK_MOVEMENT_FULL) == 0) { + queue.add(((x - 1) << 16) | (y - 1)); + visited[x - 1][y - 1] = true; + } } } 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 676bfe2cd43..d155755b400 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 @@ -1093,6 +1093,11 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part return WalkerState.EXIT; } if (pathfinder == null) { + if (currentTarget != null && currentTarget.equals(target)) { + walkerDiag("pathfinder null but target still set; recalculating"); + recalculatePath(); + continue; + } traceProcessWalkExit("pathfinder-still-null", target, processWalkTail); setTarget(null, "rs2walker:processWalk:pathfinder-still-null"); return WalkerState.EXIT; @@ -1368,11 +1373,15 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part } WorldPoint currentPlayerLoc = Rs2Player.getWorldLocation(); - reachableTilesCache = Rs2Tile.getReachableTilesFromTile(currentPlayerLoc); + reachableTilesCache = Rs2Tile.getReachableTilesFromTile(currentPlayerLoc, HANDLER_RANGE * 3); reachableTilesCacheOrigin = currentPlayerLoc; + final int currentPlayerPlane = currentPlayerLoc != null ? currentPlayerLoc.getPlane() : -1; for (int i = indexOfStartPoint; !doorOrTransportResult && i < path.size(); i++) { WorldPoint currentWorldPoint = path.get(i); + if (currentWorldPoint.getPlane() != currentPlayerPlane) { + continue; + } if (walkCancelledDiag(target, "processWalk:path-loop", processWalkTail)) { return WalkerState.EXIT; } @@ -1516,7 +1525,7 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part if (playerLoc != null) { int unreachableDist = currentWorldPoint.distanceTo2D(playerLoc); if (unreachableDist <= HANDLER_RANGE + 2) { - reachableTilesCache = Rs2Tile.getReachableTilesFromTile(playerLoc); + reachableTilesCache = Rs2Tile.getReachableTilesFromTile(playerLoc, HANDLER_RANGE + 5); reachableTilesCacheOrigin = playerLoc; tileReachable = reachableTilesCache.containsKey(currentWorldPoint); if (tileReachable) { @@ -1664,7 +1673,7 @@ && walkFastCanvas(navTarget)) { } continue; } - nextWalkingDistance = Rs2Random.between(9, 12); + nextWalkingDistance = path.size() <= 5 ? 0 : Rs2Random.between(9, 12); int dist2d = currentWorldPoint.distanceTo2D(Rs2Player.getWorldLocation()); if (dist2d > nextWalkingDistance) { tmarkPostTransport("post_transport_click_eligibility", target, @@ -8333,7 +8342,7 @@ private static WalkerState walkWithBankedTransportsAndStateLocked(WorldPoint tar } } int chebyshevToTarget = pl.distanceTo(target); - if (!forceBanking && chebyshevToTarget <= 200) { + if (!forceBanking && chebyshevToTarget <= 100) { WebWalkLog.bankWalkDebug("skip_compare_short_distance dist={} goal={}", chebyshevToTarget, target); return walkWithStateInternal(target, distance); } From 0af4423a0fe53ff90c5c3cc28036bd49984e8673 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Wed, 27 May 2026 11:31:51 -0400 Subject: [PATCH 36/51] fix(walker): filter spirit tree routes when individual tree toggles are off isSpiritTreeDestinationEnabled only checked the destination, so routes FROM a disabled tree (e.g. Farming Guild) to permanent trees (GE, Gnome Stronghold) still passed. Renamed to isSpiritTreeRouteEnabled and now rejects any route where either endpoint touches a toggled-off tree. Also removes dead pre-quest-gate assignments in refresh() that were immediately overwritten by refreshTransports(), and adds a defensive useSpiritTrees check in the walker's spirit tree dispatch. --- .../pathfinder/PathfinderConfig.java | 28 ++++++++----------- .../microbot/util/walker/Rs2Walker.java | 4 +++ 2 files changed, 16 insertions(+), 16 deletions(-) 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 6cb40e72200..918c52459c3 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 @@ -101,6 +101,8 @@ public class PathfinderConfig { private volatile long calculationCutoffMillis; @Getter private volatile boolean avoidWilderness; + @Getter + private volatile boolean useSpiritTrees; private volatile boolean useAgilityShortcuts, useGrappleShortcuts, useBoats, @@ -112,7 +114,6 @@ public class PathfinderConfig { useMinecarts, usePoh, useQuetzals, - useSpiritTrees, useTeleportationLevers, useTeleportationMinigames, useTeleportationPortals, @@ -197,20 +198,13 @@ public void refresh(WorldPoint target) { useCanoes = ShortestPathPlugin.override("useCanoes", config.useCanoes()); useCharterShips = ShortestPathPlugin.override("useCharterShips", config.useCharterShips()); useShips = ShortestPathPlugin.override("useShips", config.useShips()); - useFairyRings = ShortestPathPlugin.override("useFairyRings", config.useFairyRings()); - useGnomeGliders = ShortestPathPlugin.override("useGnomeGliders", config.useGnomeGliders()); useMinecarts = ShortestPathPlugin.override("useMinecarts", config.useMinecarts()); usePoh = ShortestPathPlugin.override("usePoh", config.usePoh()); - useQuetzals = ShortestPathPlugin.override("useQuetzals", config.useQuetzals()); - useSpiritTrees = ShortestPathPlugin.override("useSpiritTrees", config.useSpiritTrees()); useSpiritTreeEtceteria = ShortestPathPlugin.override("spiritTreeEtceteria", config.spiritTreeEtceteria()); useSpiritTreeBrimhaven = ShortestPathPlugin.override("spiritTreeBrimhaven", config.spiritTreeBrimhaven()); useSpiritTreePortSarim = ShortestPathPlugin.override("spiritTreePortSarim", config.spiritTreePortSarim()); useSpiritTreeHosidius = ShortestPathPlugin.override("spiritTreeHosidius", config.spiritTreeHosidius()); useSpiritTreeFarmingGuild = ShortestPathPlugin.override("spiritTreeFarmingGuild", config.spiritTreeFarmingGuild()); - - // Keep the master spirit-tree toggle authoritative. Destination toggles only - // gate explicit optional destinations listed in SPIRIT_TREE_DESTINATIONS_ORDERED. useTeleportationItems = ShortestPathPlugin.override("useTeleportationItems", config.useTeleportationItems()); useTeleportationMinigames = ShortestPathPlugin.override("useTeleportationMinigames", config.useTeleportationMinigames()); useTeleportationLevers = ShortestPathPlugin.override("useTeleportationLevers", config.useTeleportationLevers()); @@ -811,8 +805,8 @@ private boolean useTransport(Transport transport) { log.debug("Transport ( O: {} D: {} ) requires members world", transport.getOrigin(), transport.getDestination()); return false; } - if (transport.getType() == TransportType.SPIRIT_TREE && !isSpiritTreeDestinationEnabled(transport)) { - log.debug("Transport ( O: {} D: {} ) is a spirit tree route but the destination is disabled", transport.getOrigin(), transport.getDestination()); + if (transport.getType() == TransportType.SPIRIT_TREE && !isSpiritTreeRouteEnabled(transport)) { + log.debug("Transport ( O: {} D: {} ) is a spirit tree route but the tree is disabled", transport.getOrigin(), transport.getDestination()); return false; } // If you don't meet level requirements @@ -964,14 +958,16 @@ private boolean spiritTreeDestinationToggle(int index) { } } - private boolean isSpiritTreeDestinationEnabled(Transport transport) { + private boolean isSpiritTreeRouteEnabled(Transport transport) { + WorldPoint origin = transport.getOrigin(); WorldPoint destination = transport.getDestination(); - if (destination == null) { - return true; - } for (int i = 0; i < SPIRIT_TREE_DESTINATIONS_ORDERED.length; i++) { - if (destination.equals(SPIRIT_TREE_DESTINATIONS_ORDERED[i])) { - return spiritTreeDestinationToggle(i); + if (!spiritTreeDestinationToggle(i)) { + WorldPoint toggledPoint = SPIRIT_TREE_DESTINATIONS_ORDERED[i]; + if ((destination != null && destination.equals(toggledPoint)) + || (origin != null && origin.distanceTo2D(toggledPoint) <= 5)) { + return false; + } } } return true; 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 d155755b400..0ce1e2369c2 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 @@ -5810,6 +5810,10 @@ && recentlyOpenedStationaryDoorOnSegment(path.get(indexOfStartPoint), path.get(i } if (transport.getType() == TransportType.SPIRIT_TREE) { + if (!ShortestPathPlugin.getPathfinderConfig().isUseSpiritTrees()) { + log.debug("[Walker] skip spirit tree transport — setting is off"); + continue; + } if (attemptObserved(transport, () -> handleSpiritTree(transport))) { sleepUntil(() -> !Rs2Player.isAnimating()); boolean spiritLanded = Rs2WalkerRuntimeAwaits.awaitCondition( From 227c58668d7fff91ded41dcaa5b503a5e46346ff Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Wed, 27 May 2026 11:44:49 -0400 Subject: [PATCH 37/51] chore: regenerate client-thread guardrail baseline Pre-existing Rs2Walker/Rs2Tile violations detected by the scanner after rebase onto upstream/development. No new violations introduced. --- .../client-thread-guardrail-baseline.txt | 48 +++++++++---------- 1 file changed, 23 insertions(+), 25 deletions(-) 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 60e57e0e69e..e4b27a4e47d 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 @@ -668,10 +668,8 @@ net.runelite.client.plugins.microbot.util.tile.Rs2Tile#getNearestWalkableTileFor net.runelite.client.plugins.microbot.util.tile.Rs2Tile#getNearestWalkableTileForObjectInternal(GameObject): Rs2WorldPoint -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint net.runelite.client.plugins.microbot.util.tile.Rs2Tile#getNearestWalkableTileWithLineOfSightInternal(WorldPoint): WorldPoint -> net.runelite.api.Client#getTopLevelWorldView(): WorldView net.runelite.client.plugins.microbot.util.tile.Rs2Tile#getReachableTilesFromTileInternal(WorldPoint, int, boolean): HashMap -> net.runelite.api.Client#getTopLevelWorldView(): WorldView -net.runelite.client.plugins.microbot.util.tile.Rs2Tile#getReachableTilesFromTileInternal(WorldPoint, int, boolean): HashMap -> net.runelite.api.CollisionData#getFlags(): int[][] -net.runelite.client.plugins.microbot.util.tile.Rs2Tile#getReachableTilesFromTileInternal(WorldPoint, int, boolean): HashMap -> net.runelite.api.WorldView#getCollisionMaps(): CollisionData[] -net.runelite.client.plugins.microbot.util.tile.Rs2Tile#getReachableTilesFromTileInternal(WorldPoint, int, boolean): HashMap -> net.runelite.api.WorldView#getPlane(): int -net.runelite.client.plugins.microbot.util.tile.Rs2Tile#getReachableTilesFromTileInternal(WorldPoint, int, boolean): HashMap -> net.runelite.api.WorldView#isInstance(): boolean +net.runelite.client.plugins.microbot.util.tile.Rs2Tile#getReachableTilesFromTileInternal(WorldPoint, int, boolean): HashMap -> net.runelite.api.Scene#isInstance(): boolean +net.runelite.client.plugins.microbot.util.tile.Rs2Tile#getReachableTilesFromTileInternal(WorldPoint, int, boolean): HashMap -> net.runelite.api.WorldView#getScene(): Scene net.runelite.client.plugins.microbot.util.tile.Rs2Tile#getReachableTilesFromTileInternal(WorldPoint, int, boolean): HashMap -> net.runelite.api.coords.LocalPoint#fromWorld(WorldView, WorldPoint): LocalPoint net.runelite.client.plugins.microbot.util.tile.Rs2Tile#getTileInternal(int, int): Tile -> net.runelite.api.Client#getTopLevelWorldView(): WorldView net.runelite.client.plugins.microbot.util.tile.Rs2Tile#getTileInternal(int, int): Tile -> net.runelite.api.Scene#getTiles(): Tile[][][] @@ -696,7 +694,7 @@ net.runelite.client.plugins.microbot.util.tile.Rs2Tile#isVisited(WorldPoint, boo net.runelite.client.plugins.microbot.util.tile.Rs2Tile#isVisited(WorldPoint, boolean[][]): boolean -> net.runelite.api.WorldView#getScene(): Scene net.runelite.client.plugins.microbot.util.tile.Rs2Tile#isWalkableWorldPointInternal(WorldPoint): boolean -> net.runelite.api.Client#getTopLevelWorldView(): WorldView net.runelite.client.plugins.microbot.util.tile.Rs2Tile#isWalkableWorldPointInternal(WorldPoint): boolean -> net.runelite.api.coords.LocalPoint#fromWorld(WorldView, WorldPoint): LocalPoint -net.runelite.client.plugins.microbot.util.tile.Rs2Tile#lambda$isBankBoothInternal$33(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.tile.Rs2Tile#lambda$isBankBoothInternal$32(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint net.runelite.client.plugins.microbot.util.tile.Rs2Tile#pathToInternal(Tile, Tile): List -> net.runelite.api.Client#getScene(): Scene net.runelite.client.plugins.microbot.util.tile.Rs2Tile#pathToInternal(Tile, Tile): List -> net.runelite.api.Client#getTopLevelWorldView(): WorldView net.runelite.client.plugins.microbot.util.tile.Rs2Tile#pathToInternal(Tile, Tile): List -> net.runelite.api.CollisionData#getFlags(): int[][] @@ -783,34 +781,34 @@ net.runelite.client.plugins.microbot.util.walker.Rs2Walker#isDoorComposition(Obj net.runelite.client.plugins.microbot.util.walker.Rs2Walker#isDoorOnSegment(TileObject, WorldPoint, WorldPoint): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint net.runelite.client.plugins.microbot.util.walker.Rs2Walker#isKnownWalkableOrUnloaded(WorldPoint): boolean -> net.runelite.api.Client#getTopLevelWorldView(): WorldView net.runelite.client.plugins.microbot.util.walker.Rs2Walker#isKnownWalkableOrUnloaded(WorldPoint): boolean -> net.runelite.api.coords.LocalPoint#fromWorld(WorldView, WorldPoint): LocalPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$doorStillHasAction$42(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$findDoorNearSegment$39(WorldPoint, WorldPoint, WorldPoint, List, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$findDoorNearSegment$40(WorldPoint, TileObject): int -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleCharterShip$173(Widget): boolean -> net.runelite.api.widgets.Widget#getActions(): String[] -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleDoors$37(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$doorStillHasAction$43(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$findDoorNearSegment$40(WorldPoint, WorldPoint, WorldPoint, List, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$findDoorNearSegment$41(WorldPoint, TileObject): int -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleCharterShip$174(Widget): boolean -> net.runelite.api.widgets.Widget#getActions(): String[] net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleDoors$38(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleFairyRing$179(Transport, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleMinigameTeleport$150(Widget, Object[]): boolean -> net.runelite.api.widgets.Widget#getOnOpListener(): Object[] -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleMinigameTeleport$152(String): boolean -> net.runelite.api.widgets.Widget#getText(): String -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$128(int, TileObject): boolean -> net.runelite.api.TileObject#getId(): int -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$134(WorldPoint, TileObject): boolean -> net.runelite.api.TileObject#getId(): int -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$134(WorldPoint, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleDoors$39(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleFairyRing$180(Transport, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleMinigameTeleport$151(Widget, Object[]): boolean -> net.runelite.api.widgets.Widget#getOnOpListener(): Object[] +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleMinigameTeleport$153(String): boolean -> net.runelite.api.widgets.Widget#getText(): String +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$129(int, TileObject): boolean -> net.runelite.api.TileObject#getId(): int net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$135(WorldPoint, TileObject): boolean -> net.runelite.api.TileObject#getId(): int net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$135(WorldPoint, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$136(WorldPoint, TileObject): boolean -> net.runelite.api.TileObject#getId(): int +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$136(WorldPoint, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleRockfall$19(WorldPoint, Tile): boolean -> net.runelite.api.Tile#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$108(int, boolean, TileObject): boolean -> net.runelite.api.ObjectComposition#getName(): String -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$108(int, boolean, TileObject): boolean -> net.runelite.api.TileObject#getId(): int -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$110(Transport, TileObject): int -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$111(TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$112(Transport, Object): Integer -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$109(int, boolean, TileObject): boolean -> net.runelite.api.ObjectComposition#getName(): String +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$109(int, boolean, TileObject): boolean -> net.runelite.api.TileObject#getId(): int +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$111(Transport, TileObject): int -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$112(TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$113(Transport, Object): Integer -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$115(int, List, TileObject): boolean -> net.runelite.api.TileObject#getId(): int -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$116(Transport, TileObject): int -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleWildernessObelisk$142(Transport, GameObject): boolean -> net.runelite.api.GameObject#getId(): int +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$114(Transport, Object): Integer -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$116(int, List, TileObject): boolean -> net.runelite.api.TileObject#getId(): int +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$117(Transport, TileObject): int -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleWildernessObelisk$143(Transport, GameObject): boolean -> net.runelite.api.GameObject#getId(): int +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleWildernessObelisk$144(Transport, GameObject): boolean -> net.runelite.api.GameObject#getId(): int net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$processWalk$2(): boolean -> net.runelite.api.widgets.Widget#getSpriteId(): int net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$processWalk$3(): boolean -> net.runelite.api.widgets.Widget#getSpriteId(): int -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$tryHandleBlockingPathObjectsWithTimeout$61(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$tryHandleBlockingPathObjectsWithTimeout$62(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint net.runelite.client.plugins.microbot.util.walker.Rs2Walker#markNearbyDoorFamilyOpened(TileObject, WorldPoint, String, int): void -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint net.runelite.client.plugins.microbot.util.walker.Rs2Walker#maybeCanvasNudgeAfterDoor(WorldPoint, int, List): void -> net.runelite.api.Client#getTopLevelWorldView(): WorldView net.runelite.client.plugins.microbot.util.walker.Rs2Walker#maybeCanvasNudgeAfterDoor(WorldPoint, int, List): void -> net.runelite.api.coords.LocalPoint#fromWorld(WorldView, WorldPoint): LocalPoint From f490e432add450e23d2bcb04fe65b8a55bf45889 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Wed, 27 May 2026 13:58:55 -0400 Subject: [PATCH 38/51] fix(walker): reverse-transport loop, BFS ignoreCollision, pathfinder heuristic cascade MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three fixes: - Rs2Walker: track transport origin separately from landing tile so the reverse-transport filter correctly blocks A→B→A loops (cave entrance ping-pong). New lastTransportOriginLocation field, cleared on walk session start. - Rs2Tile: getReachableTilesFromTileInternal now fully honours ignoreCollision — cardinal direction flags and diagonal corridor flags are short-circuited, leaving only bounds checks. - Pathfinder: remove `|| node.heuristic == 0` from afterTransport checks in all three addNeighbors variants. The zero-heuristic no longer cascades past the first ring of walking nodes after a transport, restoring A* guidance for post-transport paths. --- .../shortestpath/pathfinder/Pathfinder.java | 6 ++-- .../plugins/microbot/util/tile/Rs2Tile.java | 32 +++++++++---------- .../microbot/util/walker/Rs2Walker.java | 9 ++++-- 3 files changed, 25 insertions(+), 22 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java index 9cb4632459f..d77fae70563 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java @@ -204,7 +204,7 @@ private Set buildTransportAnchors(List path) { private void addNeighbors(Node node) { List nodes = map.getNeighbors(node, visited, config, targets); - boolean afterTransport = node instanceof TransportNode || node.heuristic == 0; + boolean afterTransport = node instanceof TransportNode; for (Node neighbor : nodes) { if (config.avoidWilderness(node.packedPosition, neighbor.packedPosition, targetInWilderness)) { continue; @@ -309,7 +309,7 @@ private List combineBidirectionalPath(Node forwardAtMeet, Node backw private void addNeighborsForwardWithMeet(Node node, Map forwardAt, Map backwardAt, long[] bestMeetingCost, Node[] meetF, Node[] meetB) { List nodes = map.getNeighbors(node, visited, config, targets); - boolean afterTransport = node instanceof TransportNode || node.heuristic == 0; + boolean afterTransport = node instanceof TransportNode; for (Node neighbor : nodes) { if (config.avoidWilderness(node.packedPosition, neighbor.packedPosition, targetInWilderness)) { continue; @@ -336,7 +336,7 @@ private void addNeighborsBackwardWithMeet(Node node, VisitedTiles visitedB, Map< Set puzzleAllow, Map forwardAt, Map backwardAt, long[] bestMeetingCost, Node[] meetF, Node[] meetB) { List nodes = map.getReverseNeighbors(node, visitedB, config, puzzleAllow, incoming); - boolean afterTransport = node instanceof TransportNode || node.heuristic == 0; + boolean afterTransport = node instanceof TransportNode; for (Node pred : nodes) { if (config.avoidWilderness(pred.packedPosition, node.packedPosition, targetInWilderness)) { continue; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tile/Rs2Tile.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tile/Rs2Tile.java index 6a8fd57a98d..3a8a22ef202 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tile/Rs2Tile.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/tile/Rs2Tile.java @@ -393,10 +393,10 @@ private static HashMap getReachableTilesFromTileInternal(Wo if (dist >= distance) continue; - final boolean canE = (data & CollisionDataFlag.BLOCK_MOVEMENT_EAST) == 0; - final boolean canW = (data & CollisionDataFlag.BLOCK_MOVEMENT_WEST) == 0; - final boolean canN = (data & CollisionDataFlag.BLOCK_MOVEMENT_NORTH) == 0; - final boolean canS = (data & CollisionDataFlag.BLOCK_MOVEMENT_SOUTH) == 0; + final boolean canE = ignoreCollision || (data & CollisionDataFlag.BLOCK_MOVEMENT_EAST) == 0; + final boolean canW = ignoreCollision || (data & CollisionDataFlag.BLOCK_MOVEMENT_WEST) == 0; + final boolean canN = ignoreCollision || (data & CollisionDataFlag.BLOCK_MOVEMENT_NORTH) == 0; + final boolean canS = ignoreCollision || (data & CollisionDataFlag.BLOCK_MOVEMENT_SOUTH) == 0; if (canE) { WorldPoint neighbor = point.dx(1); @@ -420,33 +420,33 @@ private static HashMap getReachableTilesFromTileInternal(Wo } if (canN && canE && isWithinBounds(sx + 1, sy) && isWithinBounds(sx, sy + 1) && isWithinBounds(sx + 1, sy + 1) - && (flags[sx + 1][sy] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_NORTH)) == 0 - && (flags[sx][sy + 1] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_EAST)) == 0 - && (flags[sx + 1][sy + 1] & CollisionDataFlag.BLOCK_MOVEMENT_FULL) == 0) { + && (ignoreCollision || (flags[sx + 1][sy] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_NORTH)) == 0) + && (ignoreCollision || (flags[sx][sy + 1] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_EAST)) == 0) + && (ignoreCollision || (flags[sx + 1][sy + 1] & CollisionDataFlag.BLOCK_MOVEMENT_FULL) == 0)) { WorldPoint neighbor = new WorldPoint(point.getX() + 1, point.getY() + 1, point.getPlane()); if (tileDistances.putIfAbsent(neighbor, dist + 1) == null) queue.add(neighbor); } if (canN && canW && isWithinBounds(sx - 1, sy) && isWithinBounds(sx, sy + 1) && isWithinBounds(sx - 1, sy + 1) - && (flags[sx - 1][sy] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_NORTH)) == 0 - && (flags[sx][sy + 1] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_WEST)) == 0 - && (flags[sx - 1][sy + 1] & CollisionDataFlag.BLOCK_MOVEMENT_FULL) == 0) { + && (ignoreCollision || (flags[sx - 1][sy] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_NORTH)) == 0) + && (ignoreCollision || (flags[sx][sy + 1] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_WEST)) == 0) + && (ignoreCollision || (flags[sx - 1][sy + 1] & CollisionDataFlag.BLOCK_MOVEMENT_FULL) == 0)) { WorldPoint neighbor = new WorldPoint(point.getX() - 1, point.getY() + 1, point.getPlane()); if (tileDistances.putIfAbsent(neighbor, dist + 1) == null) queue.add(neighbor); } if (canS && canE && isWithinBounds(sx + 1, sy) && isWithinBounds(sx, sy - 1) && isWithinBounds(sx + 1, sy - 1) - && (flags[sx + 1][sy] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_SOUTH)) == 0 - && (flags[sx][sy - 1] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_EAST)) == 0 - && (flags[sx + 1][sy - 1] & CollisionDataFlag.BLOCK_MOVEMENT_FULL) == 0) { + && (ignoreCollision || (flags[sx + 1][sy] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_SOUTH)) == 0) + && (ignoreCollision || (flags[sx][sy - 1] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_EAST)) == 0) + && (ignoreCollision || (flags[sx + 1][sy - 1] & CollisionDataFlag.BLOCK_MOVEMENT_FULL) == 0)) { WorldPoint neighbor = new WorldPoint(point.getX() + 1, point.getY() - 1, point.getPlane()); if (tileDistances.putIfAbsent(neighbor, dist + 1) == null) queue.add(neighbor); } if (canS && canW && isWithinBounds(sx - 1, sy) && isWithinBounds(sx, sy - 1) && isWithinBounds(sx - 1, sy - 1) - && (flags[sx - 1][sy] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_SOUTH)) == 0 - && (flags[sx][sy - 1] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_WEST)) == 0 - && (flags[sx - 1][sy - 1] & CollisionDataFlag.BLOCK_MOVEMENT_FULL) == 0) { + && (ignoreCollision || (flags[sx - 1][sy] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_SOUTH)) == 0) + && (ignoreCollision || (flags[sx][sy - 1] & (CollisionDataFlag.BLOCK_MOVEMENT_FULL | CollisionDataFlag.BLOCK_MOVEMENT_WEST)) == 0) + && (ignoreCollision || (flags[sx - 1][sy - 1] & CollisionDataFlag.BLOCK_MOVEMENT_FULL) == 0)) { WorldPoint neighbor = new WorldPoint(point.getX() - 1, point.getY() - 1, point.getPlane()); if (tileDistances.putIfAbsent(neighbor, dist + 1) == null) queue.add(neighbor); 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 0ce1e2369c2..79cfb8a4671 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 @@ -170,6 +170,7 @@ public static WorldPoint getCurrentTarget() { private static volatile boolean firstMovementClickMarked = false; private static volatile long lastTransportHandledAtMs = 0L; private static volatile WorldPoint lastTransportHandledAtLocation = null; + private static volatile WorldPoint lastTransportOriginLocation = null; private static final java.util.Deque expectedTransportDestinations = new ArrayDeque<>(); private static final Set startupPhasesLogged = ConcurrentHashMap.newKeySet(); @@ -238,6 +239,7 @@ private static void markWalkSessionStart(WorldPoint target) { firstMovementClickMarked = false; startupPhasesLogged.clear(); lastTransportHandledAtLocation = null; + lastTransportOriginLocation = null; synchronized (expectedTransportDestinations) { expectedTransportDestinations.clear(); } @@ -3265,14 +3267,14 @@ private static boolean handleCurrentTileTransportTowardPath(List raw addForwardPathPoints(pathPoints, rawPath, playerLoc); addForwardPathPoints(pathPoints, path, playerLoc); - WorldPoint lastTransportOrigin = lastTransportHandledAtLocation; + WorldPoint priorOrigin = lastTransportOriginLocation; List candidates = transports.stream() .filter(t -> t.getDestination() != null) // Local adjacent same-plane edges (doors/gates) are handled by segment door/object // logic; current-tile transport probing can bounce on these and create loops. .filter(t -> !isAdjacentSamePlaneTransport(t)) - .filter(t -> lastTransportOrigin == null - || !t.getDestination().equals(lastTransportOrigin)) + .filter(t -> priorOrigin == null + || !t.getDestination().equals(priorOrigin)) .filter(t -> target == null || playerLoc.getPlane() != target.getPlane() || t.getDestination().getPlane() == target.getPlane()) @@ -6271,6 +6273,7 @@ private static boolean finishHandledTransport(Transport transport) { long handoffStartedAt = System.currentTimeMillis(); lastTransportHandledAtMs = handoffStartedAt; lastTransportHandledAtLocation = Rs2Player.getWorldLocation(); + lastTransportOriginLocation = transport != null ? transport.getOrigin() : null; WorldPoint goal = currentTarget; WorldPoint transportDest = transport != null ? transport.getDestination() : null; boolean expectedTransport = consumeExpectedTransportDestination(transportDest); From b1047fd40adb398a96129e74a2da43975252274d Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Thu, 28 May 2026 20:59:23 -0400 Subject: [PATCH 39/51] fix(walker): network-aware heuristic, off-path water-transport guard, scripted-walk pathfinder guard - Pathfinder: network-transport-aware admissible heuristic so teleport-> fairy/spirit-tree/glider/quetzal chains are discovered. Each enabled network hub becomes an A* landmark: h = min(directWalk, dist(node->origin) + min(dest->goal)). Stays admissible AND consistent (landmark set fixed per pathfind) so A* optimality holds, while the search is pulled toward useful hubs instead of ignoring them. Fixes the cloak->fairy->CIR route to the Farming Guild being passed over for a worse single teleport. Adds no graph edges (cannot teleport out of buildings) and never zeroes the heuristic (cannot collapse to whole-map Dijkstra), unlike the prior reverted bridge-injection and cascade attempts. - Rs2Walker: current-tile transport handler no longer fires off-path region-crossing transports (boat/ship/charter/canoe) by straight-line distance. A boat destination can be straight-line "closer" to the goal while sitting on another landmass, which made the walker click the Fossil Island rowboat (~17s of landing-timeout retries) before falling through to the planned teleport. These transports are now only used when explicitly on the planned path. - ShortestPathPlugin: skip onGameTick arrival-detection (which nulls the pathfinder) while a scripted Rs2Walker target is active, fixing intermittent pathfinder-still-null stalls. --- .../shortestpath/ShortestPathPlugin.java | 4 + .../shortestpath/pathfinder/Pathfinder.java | 145 ++++++++++++++++++ .../microbot/util/walker/Rs2Walker.java | 141 +++++++++++------ 3 files changed, 239 insertions(+), 51 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPlugin.java index 9361aefd29d..6a62b53d070 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPlugin.java @@ -567,6 +567,10 @@ void handlePendingLoginRefresh() { public void onGameTick(GameTick tick) { handlePendingLoginRefresh(); + if (Rs2Walker.getCurrentTarget() != null) { + return; + } + final WorldPoint myLoc = Rs2Player.getWorldLocation(); final Pathfinder pathfinder = ShortestPathPlugin.pathfinder; if (myLoc == null || pathfinder == null || !pathfinder.isDone()) { diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java index d77fae70563..51b8e3fac4c 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java @@ -5,6 +5,7 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.shortestpath.Transport; +import net.runelite.client.plugins.microbot.shortestpath.TransportType; import net.runelite.client.plugins.microbot.shortestpath.WorldPointUtil; import net.runelite.client.plugins.microbot.util.walker.WebWalkLog; @@ -233,6 +234,11 @@ private void addNeighbors(Node node) { private static final int UNDERGROUND_Y_OFFSET = 6400; private int heuristicToNearestTarget(int packedPos) { + return applyLandmarks(packedPos, baseHeuristicToNearestTarget(packedPos), + fwdLandmark, fwdLandmarkResidual); + } + + private int baseHeuristicToNearestTarget(int packedPos) { int posX = WorldPointUtil.unpackWorldX(packedPos); int posY = WorldPointUtil.unpackWorldY(packedPos); int best = Integer.MAX_VALUE; @@ -251,6 +257,11 @@ private int heuristicToNearestTarget(int packedPos) { } private int heuristicFromStart(int packedPos) { + return applyLandmarks(packedPos, baseHeuristicFromStart(packedPos), + backLandmark, backLandmarkResidual); + } + + private int baseHeuristicFromStart(int packedPos) { int posX = WorldPointUtil.unpackWorldX(packedPos); int posY = WorldPointUtil.unpackWorldY(packedPos); int sx = WorldPointUtil.unpackWorldX(start); @@ -261,6 +272,139 @@ private int heuristicFromStart(int packedPos) { return Math.min(direct, wrapped); } + // --- Network-transport-aware heuristic --------------------------------------------------- + // + // Network transports (fairy rings, spirit trees, gnome gliders, quetzals) are fully-connected + // hubs: reaching ANY origin lets you hop to ANY destination of that network for ~free. Plain + // Chebyshev is blind to this — a node next to the Ardougne fairy ring reads "~1350 tiles from + // the Farming Guild" by straight line, so A* buries the (optimal) cloak->fairy->CIR chain under + // a single direct teleport that the heuristic makes look closer. We fold the hubs into the + // heuristic as landmarks: for each enabled network whose destinations reach near the goal, every + // network origin is a landmark with residual = min(dest -> goal). Then + // h(node) = min(directWalk, dist(node, nearestOrigin) + residual). + // Each landmark term is a true lower bound (walking to the origin, a free-ish hop, then the + // residual walk to goal), so taking min with the admissible Chebyshev keeps the result both + // admissible AND consistent (the landmark set is fixed for the whole search). A* optimality is + // therefore preserved, while the search is now pulled toward useful hubs instead of ignoring + // them. The backward (bidirectional) arrays are symmetric: landmarks are destinations, residual + // is min(origin -> start). Unlike the reverted chain-bridge injection this adds no graph edges + // (so it can never teleport the player out of a building), and unlike the reverted post-transport + // cascade it never zeroes the heuristic (so it can never collapse into a whole-map Dijkstra). + private static final EnumSet NETWORK_HEURISTIC_TYPES = EnumSet.of( + TransportType.FAIRY_RING, TransportType.SPIRIT_TREE, + TransportType.GNOME_GLIDER, TransportType.QUETZAL); + + private int[] fwdLandmark = null; // packed network origins (reach a hub -> hop toward target) + private int[] fwdLandmarkResidual = null; // parallel: that network's min(dest -> nearest target) Chebyshev + private int[] backLandmark = null; // packed network destinations (symmetric, for backward search) + private int[] backLandmarkResidual = null; // parallel: that network's min(origin -> start) Chebyshev + + private int applyLandmarks(int packedPos, int base, int[] landmarks, int[] residuals) { + if (landmarks == null || landmarks.length == 0) { + return base; + } + int px = WorldPointUtil.unpackWorldX(packedPos); + int py = WorldPointUtil.unpackWorldY(packedPos); + int best = base; + for (int i = 0; i < landmarks.length; i++) { + int lx = WorldPointUtil.unpackWorldX(landmarks[i]); + int ly = WorldPointUtil.unpackWorldY(landmarks[i]); + int viaHub = Math.max(Math.abs(px - lx), Math.abs(py - ly)) + residuals[i]; + if (viaHub < best) { + best = viaHub; + } + } + return best; + } + + /** + * Builds {@link #fwdLandmark}/{@link #backLandmark} once per pathfind from the enabled network + * transports. A network only contributes landmarks if it gets you strictly closer to the goal + * (resp. start) than you already are — otherwise it is pure heuristic overhead with no benefit. + */ + private void computeNetworkLandmarks() { + Map> all = config.getTransports(); + if (all == null || all.isEmpty()) { + return; + } + + EnumMap> originsByType = new EnumMap<>(TransportType.class); + EnumMap> destsByType = new EnumMap<>(TransportType.class); + for (Set set : all.values()) { + if (set == null) { + continue; + } + for (Transport t : set) { + TransportType type = t.getType(); + if (type == null || !NETWORK_HEURISTIC_TYPES.contains(type)) { + continue; + } + WorldPoint o = t.getOrigin(); + WorldPoint d = t.getDestination(); + if (o == null || d == null) { + continue; + } + originsByType.computeIfAbsent(type, k -> new HashSet<>()).add(WorldPointUtil.packWorldPoint(o)); + destsByType.computeIfAbsent(type, k -> new HashSet<>()).add(WorldPointUtil.packWorldPoint(d)); + } + } + if (originsByType.isEmpty()) { + return; + } + + int startToGoal = minChebyshevStartToAnyTarget(); + List fwd = new ArrayList<>(); // {originPacked, residual} + List back = new ArrayList<>(); // {destPacked, residual} + for (Map.Entry> e : originsByType.entrySet()) { + Set origins = e.getValue(); + Set dests = destsByType.getOrDefault(e.getKey(), Collections.emptySet()); + if (origins.isEmpty() || dests.isEmpty()) { + continue; + } + + int residualFwd = Integer.MAX_VALUE; + for (int d : dests) { + residualFwd = Math.min(residualFwd, baseHeuristicToNearestTarget(d)); + } + if (residualFwd < startToGoal) { + for (int o : origins) { + fwd.add(new int[]{o, residualFwd}); + } + } + + int residualBack = Integer.MAX_VALUE; + for (int o : origins) { + residualBack = Math.min(residualBack, baseHeuristicFromStart(o)); + } + if (residualBack < startToGoal) { + for (int d : dests) { + back.add(new int[]{d, residualBack}); + } + } + } + + fwdLandmark = packLandmarkPositions(fwd); + fwdLandmarkResidual = packLandmarkResiduals(fwd); + backLandmark = packLandmarkPositions(back); + backLandmarkResidual = packLandmarkResiduals(back); + } + + private static int[] packLandmarkPositions(List landmarks) { + int[] out = new int[landmarks.size()]; + for (int i = 0; i < out.length; i++) { + out[i] = landmarks.get(i)[0]; + } + return out; + } + + private static int[] packLandmarkResiduals(List landmarks) { + int[] out = new int[landmarks.size()]; + for (int i = 0; i < out.length; i++) { + out[i] = landmarks.get(i)[1]; + } + return out; + } + private int minChebyshevStartToAnyTarget() { int best = Integer.MAX_VALUE; for (int t : targetsPacked) { @@ -599,6 +743,7 @@ public void run() { joinedPath = null; try { stats.start(); + computeNetworkLandmarks(); int minCheb = minChebyshevStartToAnyTarget(); boolean useBidir = targetsPacked.length == 1 && minCheb >= BIDIRECTIONAL_MIN_CHEBYSHEV; 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 79cfb8a4671..a475005074a 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 @@ -1618,56 +1618,70 @@ private static WalkerState processWalk(WorldPoint target, int distance, int part } } - // If we still can't resolve a blocker by interaction, do not stall. - // Click a reachable "progress" tile that advances toward the target/path. - // This keeps the walker responsive and usually moves us into the door's - // interaction range. - Set reachable = Rs2Tile.getReachableTilesFromTile(playerLoc, 5).keySet(); - if (reachable != null && !reachable.isEmpty()) { - WorldPoint best = null; - int bestDist = Integer.MAX_VALUE; - for (WorldPoint wp : reachable) { - if (wp == null) continue; - // Prefer tiles that move closer to ultimate target. - int d = wp.distanceTo2D(target); - if (best == null || d < bestDist) { - best = wp; - bestDist = d; - } - } - if (best != null && !best.equals(playerLoc)) { - WorldPoint navTarget = getPointWithWallDistance(best); - boolean clicked = false; - // Prefer minimap for normal recovery movement. Scene-click fallback only on - // final-adjacent approach to prevent post-door canvas jumps mid-route. - clicked = Rs2Walker.walkMiniMap(navTarget); - if (!clicked - && target != null - && playerLoc.distanceTo2D(target) <= Math.max(2, distance + FINAL_ADJACENT_CANVAS_NUDGE_CHEBYSHEV) - && playerLoc.distanceTo2D(navTarget) <= DOOR_OPEN_CANVAS_NUDGE_MAX_FROM_PLAYER - && Rs2Tile.isTileReachable(navTarget) - && walkFastCanvas(navTarget)) { - clicked = true; - log.debug("[Walker] unreachable recovery: scene click -> {}", navTarget); - } - log.info("[Walker] unreachable recovery click: clicked={} to={} distToTarget={}", - clicked, best, bestDist); - if (clicked) { - markFirstMovementClick("first_recovery_click", target, playerLoc, - "to=" + compactWorldPoint(navTarget)); - lastUnreachableRecoveryClickAtMs = System.currentTimeMillis(); - WorldPoint pathLastRecovery = path.get(path.size() - 1); - int finishThRecovery = tightFinishThreshold(target, pathLastRecovery, distance); - waitUntilIdleAfterSceneWalk(target, POST_SCENE_WALK_IDLE_WAIT_MS_MAX, target, - finishThRecovery); - // Next outer iteration runs checkIfStuck/isStuckTooLong before tile delta — avoid - // spurious stall-recalc right after issuing recovery movement (door still closed). - lastMovedTimeMs = System.currentTimeMillis(); - stuckCount = 0; - } - exitReason = "unreachable-recovery-click"; - break; - } + // Door/obstacle detection above found nothing to open. The local + // reachability BFS is bounded (~39 tiles) and is frequently a FALSE + // negative — a viable route exists, just longer than the BFS radius or + // behind a collision-map quirk. Rather than stall on an uncertain verdict, + // click toward the actual path route on the minimap and let the server's + // walk-here pathfinder take us as far as it can, then recover from there — + // like a human clicking the furthest visible tile. We trust the server path + // (no reachability gate on the click); isKnownWalkableOrUnloaded only keeps + // us from clicking into a known wall, it is NOT the bounded BFS check. + final int RECOVERY_MINIMAP_REACH_EUCLIDEAN = 13; + int recoverIdx = findFurthestClickableIndex(path, i, playerLoc, + wp -> { + Set ts = ShortestPathPlugin.getTransports().get(wp); + return ts != null && !ts.isEmpty(); + }, + RECOVERY_MINIMAP_REACH_EUCLIDEAN); + recoverIdx = Math.min(Math.max(recoverIdx, indexOfStartPoint), path.size() - 1); + WorldPoint recoverTarget = path.get(recoverIdx); + if (euclideanSq(recoverTarget, playerLoc) + > RECOVERY_MINIMAP_REACH_EUCLIDEAN * RECOVERY_MINIMAP_REACH_EUCLIDEAN) { + // Furthest in-range path tile still beyond the minimap clip (e.g. a + // diagonal segment). Interpolate a point near the minimap edge toward + // path[i]; the server routes through whatever blocks line-of-sight. + recoverTarget = interpolateClickableTarget(path, i, playerLoc, + path.get(i), RECOVERY_MINIMAP_REACH_EUCLIDEAN - 1, + wp -> inInstance || isKnownWalkableOrUnloaded(wp)); + } + boolean clicked = recoverTarget != null + && !recoverTarget.equals(playerLoc) + && Rs2Walker.walkMiniMap(recoverTarget); + // Scene-click fallback only on final-adjacent approach (minimap click may + // miss the clip when very close); kept gated on reachability since it is a + // last resort, not the primary recovery path. + if (!clicked && recoverTarget != null + && target != null + && playerLoc.distanceTo2D(target) <= Math.max(2, distance + FINAL_ADJACENT_CANVAS_NUDGE_CHEBYSHEV) + && playerLoc.distanceTo2D(recoverTarget) <= DOOR_OPEN_CANVAS_NUDGE_MAX_FROM_PLAYER + && Rs2Tile.isTileReachable(recoverTarget) + && walkFastCanvas(recoverTarget)) { + clicked = true; + log.debug("[Walker] unreachable recovery: scene click -> {}", recoverTarget); + } + log.info("[Walker] unreachable optimistic recovery: clicked={} to={} pathTile={} idx={}", + clicked, recoverTarget, currentWorldPoint, recoverIdx); + if (clicked) { + markFirstMovementClick("first_recovery_click", target, playerLoc, + "to=" + compactWorldPoint(recoverTarget)); + lastUnreachableRecoveryClickAtMs = System.currentTimeMillis(); + // Sticky interim: subsequent iterations travel toward this point via the + // interim-in-flight path instead of re-running the (false-negative) + // reachability check and re-clicking every tick. + interimTargetWp = recoverTarget; + interimTargetIdx = recoverIdx; + interimSetAtMs = System.currentTimeMillis(); + WorldPoint pathLastRecovery = path.get(path.size() - 1); + int finishThRecovery = tightFinishThreshold(target, pathLastRecovery, distance); + waitUntilIdleAfterSceneWalk(target, POST_SCENE_WALK_IDLE_WAIT_MS_MAX, target, + finishThRecovery); + // Next outer iteration runs checkIfStuck/isStuckTooLong before tile delta — avoid + // spurious stall-recalc right after issuing recovery movement. + lastMovedTimeMs = System.currentTimeMillis(); + stuckCount = 0; + exitReason = "unreachable-recovery-click"; + break; } exitReason = "tile-unreachable-near-player"; break; @@ -3279,7 +3293,7 @@ private static boolean handleCurrentTileTransportTowardPath(List raw || playerLoc.getPlane() != target.getPlane() || t.getDestination().getPlane() == target.getPlane()) .filter(t -> pathPoints.contains(t.getDestination()) - || (!isNetworkTransport(t) + || (!isRegionCrossingTransport(t) && target != null && t.getDestination().distanceTo(target) < playerLoc.distanceTo(target))) .sorted(Comparator .comparingInt((Transport t) -> pathPoints.contains(t.getDestination()) ? 0 : 1) @@ -6269,6 +6283,31 @@ private static boolean isNetworkTransport(Transport transport) { } } + /** + * Region-crossing transports (multi-destination networks plus long-haul water travel) must + * only be taken when their destination is explicitly on the planned path. Straight-line + * {@code distanceTo} is not a valid "progress toward target" metric for them: a boat/ship + * destination can be straight-line closer to the goal while actually sitting on a different + * landmass. Without this guard the opportunistic current-tile-transport branch fires a long + * voyage the pathfinder never chose — e.g. standing on the Fossil Island rowboat dock while + * the planned route teleports to Varlamore, the rowboat dest looked "closer" so the walker + * clicked the boat (and stalled ~17s on landing timeouts) before falling through to the + * actual teleport. + */ + private static boolean isRegionCrossingTransport(Transport transport) { + if (transport == null || transport.getType() == null) return false; + if (isNetworkTransport(transport)) return true; + switch (transport.getType()) { + case BOAT: + case SHIP: + case CHARTER_SHIP: + case CANOE: + return true; + default: + return false; + } + } + private static boolean finishHandledTransport(Transport transport) { long handoffStartedAt = System.currentTimeMillis(); lastTransportHandledAtMs = handoffStartedAt; From f0989ad0aebd540da1e4f54e3fa3e7458e6f510d Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Thu, 28 May 2026 21:06:20 -0400 Subject: [PATCH 40/51] chore: regenerate client-thread guardrail baseline MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The recovery-click rewrite in Rs2Walker added two lambda expressions, shifting every subsequent compiler-assigned lambda ordinal by +2. The guardrail baseline keys on those synthetic lambda names, so 26 entries renamed (same callers, same inferred-client-thread API calls) — no new real violations. Regenerated to match. --- .../client-thread-guardrail-baseline.txt | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) 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 e4b27a4e47d..8993e748f7d 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 @@ -781,34 +781,34 @@ net.runelite.client.plugins.microbot.util.walker.Rs2Walker#isDoorComposition(Obj net.runelite.client.plugins.microbot.util.walker.Rs2Walker#isDoorOnSegment(TileObject, WorldPoint, WorldPoint): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint net.runelite.client.plugins.microbot.util.walker.Rs2Walker#isKnownWalkableOrUnloaded(WorldPoint): boolean -> net.runelite.api.Client#getTopLevelWorldView(): WorldView net.runelite.client.plugins.microbot.util.walker.Rs2Walker#isKnownWalkableOrUnloaded(WorldPoint): boolean -> net.runelite.api.coords.LocalPoint#fromWorld(WorldView, WorldPoint): LocalPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$doorStillHasAction$43(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$findDoorNearSegment$40(WorldPoint, WorldPoint, WorldPoint, List, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$findDoorNearSegment$41(WorldPoint, TileObject): int -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleCharterShip$174(Widget): boolean -> net.runelite.api.widgets.Widget#getActions(): String[] -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleDoors$38(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleDoors$39(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleFairyRing$180(Transport, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleMinigameTeleport$151(Widget, Object[]): boolean -> net.runelite.api.widgets.Widget#getOnOpListener(): Object[] -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleMinigameTeleport$153(String): boolean -> net.runelite.api.widgets.Widget#getText(): String -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$129(int, TileObject): boolean -> net.runelite.api.TileObject#getId(): int -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$135(WorldPoint, TileObject): boolean -> net.runelite.api.TileObject#getId(): int -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$135(WorldPoint, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$136(WorldPoint, TileObject): boolean -> net.runelite.api.TileObject#getId(): int -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$136(WorldPoint, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleRockfall$19(WorldPoint, Tile): boolean -> net.runelite.api.Tile#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$109(int, boolean, TileObject): boolean -> net.runelite.api.ObjectComposition#getName(): String -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$109(int, boolean, TileObject): boolean -> net.runelite.api.TileObject#getId(): int -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$111(Transport, TileObject): int -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$112(TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$113(Transport, Object): Integer -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$114(Transport, Object): Integer -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$116(int, List, TileObject): boolean -> net.runelite.api.TileObject#getId(): int -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$117(Transport, TileObject): int -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleWildernessObelisk$143(Transport, GameObject): boolean -> net.runelite.api.GameObject#getId(): int -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleWildernessObelisk$144(Transport, GameObject): boolean -> net.runelite.api.GameObject#getId(): int +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$doorStillHasAction$45(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$findDoorNearSegment$42(WorldPoint, WorldPoint, WorldPoint, List, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$findDoorNearSegment$43(WorldPoint, TileObject): int -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleCharterShip$176(Widget): boolean -> net.runelite.api.widgets.Widget#getActions(): String[] +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleDoors$40(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleDoors$41(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleFairyRing$182(Transport, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleMinigameTeleport$153(Widget, Object[]): boolean -> net.runelite.api.widgets.Widget#getOnOpListener(): Object[] +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleMinigameTeleport$155(String): boolean -> net.runelite.api.widgets.Widget#getText(): String +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$131(int, TileObject): boolean -> net.runelite.api.TileObject#getId(): int +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$137(WorldPoint, TileObject): boolean -> net.runelite.api.TileObject#getId(): int +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$137(WorldPoint, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$138(WorldPoint, TileObject): boolean -> net.runelite.api.TileObject#getId(): int +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$138(WorldPoint, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleRockfall$21(WorldPoint, Tile): boolean -> net.runelite.api.Tile#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$111(int, boolean, TileObject): boolean -> net.runelite.api.ObjectComposition#getName(): String +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$111(int, boolean, TileObject): boolean -> net.runelite.api.TileObject#getId(): int +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$113(Transport, TileObject): int -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$114(TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$115(Transport, Object): Integer -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$116(Transport, Object): Integer -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$118(int, List, TileObject): boolean -> net.runelite.api.TileObject#getId(): int +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$119(Transport, TileObject): int -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleWildernessObelisk$145(Transport, GameObject): boolean -> net.runelite.api.GameObject#getId(): int +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleWildernessObelisk$146(Transport, GameObject): boolean -> net.runelite.api.GameObject#getId(): int net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$processWalk$2(): boolean -> net.runelite.api.widgets.Widget#getSpriteId(): int net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$processWalk$3(): boolean -> net.runelite.api.widgets.Widget#getSpriteId(): int -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$tryHandleBlockingPathObjectsWithTimeout$62(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$tryHandleBlockingPathObjectsWithTimeout$64(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint net.runelite.client.plugins.microbot.util.walker.Rs2Walker#markNearbyDoorFamilyOpened(TileObject, WorldPoint, String, int): void -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint net.runelite.client.plugins.microbot.util.walker.Rs2Walker#maybeCanvasNudgeAfterDoor(WorldPoint, int, List): void -> net.runelite.api.Client#getTopLevelWorldView(): WorldView net.runelite.client.plugins.microbot.util.walker.Rs2Walker#maybeCanvasNudgeAfterDoor(WorldPoint, int, List): void -> net.runelite.api.coords.LocalPoint#fromWorld(WorldView, WorldPoint): LocalPoint From 2d3b5158d9d5996dd2592f8aa0ad0c0b130b5222 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sun, 31 May 2026 10:41:03 -0400 Subject: [PATCH 41/51] fix(walker): stop off-path transport loops via band-aware distance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The opportunistic current-tile transport handler admitted off-path transports whose destination was straight-line "closer" to the goal, using WorldPoint#distanceTo which ignores the OSRS underground +6400 Y-offset. Across coordinate bands an inner underground region reads numerically closer to a surface goal than the dungeon exit beside it, so the walker re-took transports the pathfinder never chose — looping forever on the Mor Ul Rek cave entrance/exit and bouncing Varrock<-> Kourend teleports. Trust the pathfinder: the current-tile handler now admits only transports whose destination lies on the planned forward route, ordered by route position. Unify the one remaining distance check (madeProgressToward) and the pathfinder heuristic on a single band-aware helper (WorldPointUtil.undergroundAwareDistance) so the coordinate convention lives in exactly one place. - WorldPointUtil: add undergroundAwareDistance (min of direct and Y-band-folded Chebyshev) - Pathfinder: baseHeuristicToNearestTarget/baseHeuristicFromStart call the shared helper (byte-identical formula; admissibility unchanged) - Rs2Walker: forward-route-only admission; drop off-path distance branch and now-unused isRegionCrossingTransport/isNetworkTransport - Rs2WalkerProgress: band-aware madeProgressToward - add Rs2WalkerProgressTest locking the regression - regenerate client-thread guardrail baseline (lambda-ordinal renames) --- .../microbot/shortestpath/WorldPointUtil.java | 23 +++++++ .../shortestpath/pathfinder/Pathfinder.java | 14 ++-- .../microbot/util/walker/Rs2Walker.java | 67 +++++-------------- .../util/walker/shared/Rs2WalkerProgress.java | 7 +- .../walker/shared/Rs2WalkerProgressTest.java | 52 ++++++++++++++ .../client-thread-guardrail-baseline.txt | 40 +++++------ 6 files changed, 123 insertions(+), 80 deletions(-) create mode 100644 runelite-client/src/test/java/net/runelite/client/plugins/microbot/util/walker/shared/Rs2WalkerProgressTest.java diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/WorldPointUtil.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/WorldPointUtil.java index cf1f43fa5b6..ced87478a74 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/WorldPointUtil.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/WorldPointUtil.java @@ -106,6 +106,29 @@ public static int distanceBetween2D(int previousX, int previousY, return Integer.MAX_VALUE; } + // OSRS shifts underground coords by +6400 on the Y axis (e.g. Varrock sewers at y≈9800 sit + // under Varrock at y≈3400). Plain straight-line distance across this offset is meaningless and + // misleads "is this closer to the goal" checks. See Pathfinder's admissible heuristic. + public static final int UNDERGROUND_Y_OFFSET = 6400; + + /** + * Chebyshev (2D) distance that tolerates the surface↔underground Y-offset convention: it takes + * the smaller of the direct distance and the distance after folding both Y coords into the + * [0, {@link #UNDERGROUND_Y_OFFSET}) band. This keeps an underground tile and its surface + * neighbour "close" (one transport apart) instead of ~6400 tiles apart. Same formula the + * pathfinder heuristic uses, so the walker and pathfinder agree on what "toward the goal" means. + */ + public static int undergroundAwareDistance(int x1, int y1, int x2, int y2) { + final int dx = Math.abs(x1 - x2); + final int direct = Math.max(dx, Math.abs(y1 - y2)); + final int wrapped = Math.max(dx, Math.abs((y1 % UNDERGROUND_Y_OFFSET) - (y2 % UNDERGROUND_Y_OFFSET))); + return Math.min(direct, wrapped); + } + + public static int undergroundAwareDistance(WorldPoint a, WorldPoint b) { + return undergroundAwareDistance(a.getX(), a.getY(), b.getX(), b.getY()); + } + public static int distanceBetween(WorldPoint previous, WorldPoint current) { return distanceBetween(previous, current, 1); } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java index 51b8e3fac4c..47d76b138b0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/pathfinder/Pathfinder.java @@ -230,8 +230,8 @@ private void addNeighbors(Node node) { // misdirects A* into expanding the surface southward instead of routing through a // nearby ladder/stairs transport. Taking min(direct, mod-6400) stays admissible // because reaching a y-mirrored underground point still requires ≥ one transport - // (cost ≥ 0) on top of the mod-6400 walking distance. - private static final int UNDERGROUND_Y_OFFSET = 6400; + // (cost ≥ 0) on top of the mod-6400 walking distance. The band-aware distance lives in + // WorldPointUtil.undergroundAwareDistance so the walker uses the same metric. private int heuristicToNearestTarget(int packedPos) { return applyLandmarks(packedPos, baseHeuristicToNearestTarget(packedPos), @@ -245,10 +245,7 @@ private int baseHeuristicToNearestTarget(int packedPos) { for (int target : targetsPacked) { int tx = WorldPointUtil.unpackWorldX(target); int ty = WorldPointUtil.unpackWorldY(target); - int dx = Math.abs(posX - tx); - int direct = Math.max(dx, Math.abs(posY - ty)); - int wrapped = Math.max(dx, Math.abs(((posY % UNDERGROUND_Y_OFFSET) - (ty % UNDERGROUND_Y_OFFSET)))); - int h = Math.min(direct, wrapped); + int h = WorldPointUtil.undergroundAwareDistance(posX, posY, tx, ty); if (h < best) { best = h; } @@ -266,10 +263,7 @@ private int baseHeuristicFromStart(int packedPos) { int posY = WorldPointUtil.unpackWorldY(packedPos); int sx = WorldPointUtil.unpackWorldX(start); int sy = WorldPointUtil.unpackWorldY(start); - int dx = Math.abs(posX - sx); - int direct = Math.max(dx, Math.abs(posY - sy)); - int wrapped = Math.max(dx, Math.abs(((posY % UNDERGROUND_Y_OFFSET) - (sy % UNDERGROUND_Y_OFFSET)))); - return Math.min(direct, wrapped); + return WorldPointUtil.undergroundAwareDistance(posX, posY, sx, sy); } // --- Network-transport-aware heuristic --------------------------------------------------- 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 a475005074a..3eed5b8ac33 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 @@ -3277,11 +3277,19 @@ private static boolean handleCurrentTileTransportTowardPath(List raw return false; } - Set pathPoints = new HashSet<>(); - addForwardPathPoints(pathPoints, rawPath, playerLoc); - addForwardPathPoints(pathPoints, path, playerLoc); + Map forwardIndex = new HashMap<>(); + addForwardPathIndices(forwardIndex, rawPath, playerLoc); + addForwardPathIndices(forwardIndex, path, playerLoc); WorldPoint priorOrigin = lastTransportOriginLocation; + // Trust the pathfinder: only take a current-tile transport whose destination is on the + // planned forward route, ordered by route position (earliest forward transport first). The + // old fallback admitted off-path transports whose destination was straight-line "closer" to + // the goal — but WorldPoint#distanceTo ignores the underground Y-offset, so an inner-region + // tile reads numerically closer to a surface goal. That made the walker re-take transports + // the pathfinder never chose: it looped forever on the Mor Ul Rek cave entrance/exit and + // stalled clicking the Fossil Island rowboat. The pathfinder already routed every transport + // it wants onto the path, so on-route membership is the correct, region-safe admission test. List candidates = transports.stream() .filter(t -> t.getDestination() != null) // Local adjacent same-plane edges (doors/gates) are handled by segment door/object @@ -3292,12 +3300,8 @@ private static boolean handleCurrentTileTransportTowardPath(List raw .filter(t -> target == null || playerLoc.getPlane() != target.getPlane() || t.getDestination().getPlane() == target.getPlane()) - .filter(t -> pathPoints.contains(t.getDestination()) - || (!isRegionCrossingTransport(t) - && target != null && t.getDestination().distanceTo(target) < playerLoc.distanceTo(target))) - .sorted(Comparator - .comparingInt((Transport t) -> pathPoints.contains(t.getDestination()) ? 0 : 1) - .thenComparingInt(t -> target == null ? 0 : t.getDestination().distanceTo(target))) + .filter(t -> forwardIndex.containsKey(t.getDestination())) + .sorted(Comparator.comparingInt(t -> forwardIndex.get(t.getDestination()))) .collect(Collectors.toList()); for (Transport transport : candidates) { @@ -3328,7 +3332,10 @@ private static boolean didCurrentTileTransportProgress(WorldPoint before, WorldP return Rs2WalkerTransportAwaits.didCurrentTileTransportProgress(before, expectedDestination, target); } - private static void addForwardPathPoints(Set pathPoints, List path, WorldPoint playerLoc) { + // Maps each tile on the planned route at/after the player's closest index to its route position. + // Earliest index wins (putIfAbsent) so the raw path's index space is authoritative when the same + // tile appears in both the raw and smoothed paths (the smoothed path is a subset of the raw one). + private static void addForwardPathIndices(Map forwardIndex, List path, WorldPoint playerLoc) { if (path == null || path.isEmpty() || playerLoc == null) { return; } @@ -3338,7 +3345,7 @@ private static void addForwardPathPoints(Set pathPoints, List playerLoc.distanceTo(path.get(i)))) .orElse(0); for (int i = closestIndex; i < path.size(); i++) { - pathPoints.add(path.get(i)); + forwardIndex.putIfAbsent(path.get(i), i); } } @@ -6270,44 +6277,6 @@ private static boolean handleTransportsInRawSegment(List rawPath, in return false; } - private static boolean isNetworkTransport(Transport transport) { - if (transport == null || transport.getType() == null) return false; - switch (transport.getType()) { - case SPIRIT_TREE: - case FAIRY_RING: - case QUETZAL: - case GNOME_GLIDER: - return true; - default: - return false; - } - } - - /** - * Region-crossing transports (multi-destination networks plus long-haul water travel) must - * only be taken when their destination is explicitly on the planned path. Straight-line - * {@code distanceTo} is not a valid "progress toward target" metric for them: a boat/ship - * destination can be straight-line closer to the goal while actually sitting on a different - * landmass. Without this guard the opportunistic current-tile-transport branch fires a long - * voyage the pathfinder never chose — e.g. standing on the Fossil Island rowboat dock while - * the planned route teleports to Varlamore, the rowboat dest looked "closer" so the walker - * clicked the boat (and stalled ~17s on landing timeouts) before falling through to the - * actual teleport. - */ - private static boolean isRegionCrossingTransport(Transport transport) { - if (transport == null || transport.getType() == null) return false; - if (isNetworkTransport(transport)) return true; - switch (transport.getType()) { - case BOAT: - case SHIP: - case CHARTER_SHIP: - case CANOE: - return true; - default: - return false; - } - } - private static boolean finishHandledTransport(Transport transport) { long handoffStartedAt = System.currentTimeMillis(); lastTransportHandledAtMs = handoffStartedAt; diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/shared/Rs2WalkerProgress.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/shared/Rs2WalkerProgress.java index 6e836e750c5..938168efe56 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/shared/Rs2WalkerProgress.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/shared/Rs2WalkerProgress.java @@ -1,6 +1,7 @@ package net.runelite.client.plugins.microbot.util.walker.shared; import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.microbot.shortestpath.WorldPointUtil; public final class Rs2WalkerProgress { private Rs2WalkerProgress() { @@ -23,7 +24,11 @@ public static boolean madeProgressToward(WorldPoint before, WorldPoint now, Worl if (before == null || now == null || target == null) { return false; } - return now.distanceTo2D(target) < before.distanceTo2D(target); + // Band-aware distance: plain distanceTo2D ignores the underground Y-offset, so a sideways + // hop within an underground region can read as "closer" to a surface goal. Using the same + // metric the pathfinder uses keeps this honest (e.g. re-entering Mor Ul Rek is not progress). + return WorldPointUtil.undergroundAwareDistance(now, target) + < WorldPointUtil.undergroundAwareDistance(before, target); } public static boolean isWithinChebyshev(WorldPoint from, WorldPoint to, int maxInclusive) { diff --git a/runelite-client/src/test/java/net/runelite/client/plugins/microbot/util/walker/shared/Rs2WalkerProgressTest.java b/runelite-client/src/test/java/net/runelite/client/plugins/microbot/util/walker/shared/Rs2WalkerProgressTest.java new file mode 100644 index 00000000000..e0edfe82d6d --- /dev/null +++ b/runelite-client/src/test/java/net/runelite/client/plugins/microbot/util/walker/shared/Rs2WalkerProgressTest.java @@ -0,0 +1,52 @@ +package net.runelite.client.plugins.microbot.util.walker.shared; + +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.microbot.shortestpath.WorldPointUtil; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Locks the band-aware distance fix that killed the Mor Ul Rek cave entrance/exit infinite loop. + * + *

OSRS shifts underground coords by +6400 on the Y axis. Plain straight-line distance ignores + * that, so an inner underground region reads numerically "closer" to a surface goal than the + * dungeon exit right next to it — which made the walker re-take the cave entrance forever. + * {@link WorldPointUtil#undergroundAwareDistance} folds the Y band, and + * {@link Rs2WalkerProgress#madeProgressToward} uses it so re-entering the cave is correctly NOT + * counted as progress. + */ +public class Rs2WalkerProgressTest { + + // Karamja volcano dungeon (the cave exit landing) — wraps 9572 % 6400 = 3172, ~12 from surface. + private static final WorldPoint VOLCANO_DUNGEON = new WorldPoint(2862, 9572, 0); + // Mor Ul Rek interior (the cave entrance destination) — 5175 < 6400, stays ~2015 from surface. + private static final WorldPoint MOR_UL_REK = new WorldPoint(2480, 5175, 0); + // A surface goal on Karamja. + private static final WorldPoint SURFACE_TARGET = new WorldPoint(2855, 3160, 0); + + @Test + public void undergroundAwareDistance_wrapsDungeonCloseToSurface() { + assertEquals(12, WorldPointUtil.undergroundAwareDistance(VOLCANO_DUNGEON, SURFACE_TARGET)); + } + + @Test + public void undergroundAwareDistance_keepsInnerRegionFar() { + assertEquals(2015, WorldPointUtil.undergroundAwareDistance(MOR_UL_REK, SURFACE_TARGET)); + } + + @Test + public void reenteringCaveIsNotProgress() { + // Standing at the cave exit (dungeon) and re-taking the entrance back into Mor Ul Rek must + // NOT register as progress toward a surface goal — otherwise the walker loops forever. + assertFalse(Rs2WalkerProgress.madeProgressToward(VOLCANO_DUNGEON, MOR_UL_REK, SURFACE_TARGET)); + } + + @Test + public void steppingTowardSurfaceGoalIsProgress() { + WorldPoint nearTarget = new WorldPoint(2856, 3161, 0); + assertTrue(Rs2WalkerProgress.madeProgressToward(VOLCANO_DUNGEON, nearTarget, SURFACE_TARGET)); + } +} 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 8993e748f7d..c693a2b0f08 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 @@ -781,34 +781,34 @@ net.runelite.client.plugins.microbot.util.walker.Rs2Walker#isDoorComposition(Obj net.runelite.client.plugins.microbot.util.walker.Rs2Walker#isDoorOnSegment(TileObject, WorldPoint, WorldPoint): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint net.runelite.client.plugins.microbot.util.walker.Rs2Walker#isKnownWalkableOrUnloaded(WorldPoint): boolean -> net.runelite.api.Client#getTopLevelWorldView(): WorldView net.runelite.client.plugins.microbot.util.walker.Rs2Walker#isKnownWalkableOrUnloaded(WorldPoint): boolean -> net.runelite.api.coords.LocalPoint#fromWorld(WorldView, WorldPoint): LocalPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$doorStillHasAction$45(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$findDoorNearSegment$42(WorldPoint, WorldPoint, WorldPoint, List, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$findDoorNearSegment$43(WorldPoint, TileObject): int -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleCharterShip$176(Widget): boolean -> net.runelite.api.widgets.Widget#getActions(): String[] +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$doorStillHasAction$44(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$findDoorNearSegment$41(WorldPoint, WorldPoint, WorldPoint, List, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$findDoorNearSegment$42(WorldPoint, TileObject): int -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleCharterShip$175(Widget): boolean -> net.runelite.api.widgets.Widget#getActions(): String[] +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleDoors$39(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleDoors$40(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleDoors$41(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleFairyRing$182(Transport, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleMinigameTeleport$153(Widget, Object[]): boolean -> net.runelite.api.widgets.Widget#getOnOpListener(): Object[] -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleMinigameTeleport$155(String): boolean -> net.runelite.api.widgets.Widget#getText(): String -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$131(int, TileObject): boolean -> net.runelite.api.TileObject#getId(): int +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleFairyRing$181(Transport, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleMinigameTeleport$152(Widget, Object[]): boolean -> net.runelite.api.widgets.Widget#getOnOpListener(): Object[] +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleMinigameTeleport$154(String): boolean -> net.runelite.api.widgets.Widget#getText(): String +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$130(int, TileObject): boolean -> net.runelite.api.TileObject#getId(): int +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$136(WorldPoint, TileObject): boolean -> net.runelite.api.TileObject#getId(): int +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$136(WorldPoint, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$137(WorldPoint, TileObject): boolean -> net.runelite.api.TileObject#getId(): int net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$137(WorldPoint, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$138(WorldPoint, TileObject): boolean -> net.runelite.api.TileObject#getId(): int -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleObjectExceptions$138(WorldPoint, TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleRockfall$21(WorldPoint, Tile): boolean -> net.runelite.api.Tile#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$111(int, boolean, TileObject): boolean -> net.runelite.api.ObjectComposition#getName(): String -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$111(int, boolean, TileObject): boolean -> net.runelite.api.TileObject#getId(): int -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$113(Transport, TileObject): int -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$114(TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$110(int, boolean, TileObject): boolean -> net.runelite.api.ObjectComposition#getName(): String +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$110(int, boolean, TileObject): boolean -> net.runelite.api.TileObject#getId(): int +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$112(Transport, TileObject): int -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$113(TileObject): boolean -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$114(Transport, Object): Integer -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$115(Transport, Object): Integer -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$116(Transport, Object): Integer -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$118(int, List, TileObject): boolean -> net.runelite.api.TileObject#getId(): int -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$119(Transport, TileObject): int -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$117(int, List, TileObject): boolean -> net.runelite.api.TileObject#getId(): int +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleTransports$118(Transport, TileObject): int -> net.runelite.api.TileObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleWildernessObelisk$144(Transport, GameObject): boolean -> net.runelite.api.GameObject#getId(): int net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleWildernessObelisk$145(Transport, GameObject): boolean -> net.runelite.api.GameObject#getId(): int -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$handleWildernessObelisk$146(Transport, GameObject): boolean -> net.runelite.api.GameObject#getId(): int net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$processWalk$2(): boolean -> net.runelite.api.widgets.Widget#getSpriteId(): int net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$processWalk$3(): boolean -> net.runelite.api.widgets.Widget#getSpriteId(): int -net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$tryHandleBlockingPathObjectsWithTimeout$64(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint +net.runelite.client.plugins.microbot.util.walker.Rs2Walker#lambda$tryHandleBlockingPathObjectsWithTimeout$63(WorldPoint, GameObject): boolean -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint net.runelite.client.plugins.microbot.util.walker.Rs2Walker#markNearbyDoorFamilyOpened(TileObject, WorldPoint, String, int): void -> net.runelite.api.GameObject#getWorldLocation(): WorldPoint net.runelite.client.plugins.microbot.util.walker.Rs2Walker#maybeCanvasNudgeAfterDoor(WorldPoint, int, List): void -> net.runelite.api.Client#getTopLevelWorldView(): WorldView net.runelite.client.plugins.microbot.util.walker.Rs2Walker#maybeCanvasNudgeAfterDoor(WorldPoint, int, List): void -> net.runelite.api.coords.LocalPoint#fromWorld(WorldView, WorldPoint): LocalPoint From cc999439618b0eb35e7314f8e2bd6aa977123508 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Mon, 1 Jun 2026 23:25:32 -0400 Subject: [PATCH 42/51] walker: take nearby reachable transports; stop door-open cancel Two walker fixes in handleCurrentTileTransportTowardPath and the path loop: 1. Nearby transport handler: source candidates from transports whose origin is reachable within 5 tiles of the player, not just the exact player tile, and dispatch via the transport's own origin so NPC/'Follow' transports auto-walk the short hop. NPC transports (e.g. Elkoy in the Tree Gnome Village maze) roam and sit a tile off the planned path, so exact-tile matching never took them and the walker thrashed through the maze instead. Keeps the destination-on-forward-route gate (prevents the Mor Ul Rek cave loop) and relies on getTransports() already being the usable/filtered set. 2. Door-settling yield: gate the main forward minimap click on isDoorInteractionSettling(). When a door is interacted with but traversal isn't yet confirmed, handleDoorsInRawSegment returns false and the loop fell through to the forward click in the same pass, clicking the tile behind the door and cancelling the open. Yield that pass instead; the next settled pass walks through. The door-edge-resolution branch is left untouched. --- .../microbot/util/walker/Rs2Walker.java | 49 ++++++++++++++++--- 1 file changed, 41 insertions(+), 8 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 3eed5b8ac33..c90ff191d9d 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 @@ -1689,6 +1689,16 @@ && walkFastCanvas(recoverTarget)) { } continue; } + // A door was just interacted with (settling window still active) but traversal isn't + // yet confirmed, so this forward tile may sit *behind* the opening door. Issuing the + // minimap click now cancels the in-progress open ("click door, then immediately click + // the tile behind it"). Yield this pass; the next settled pass walks through. The + // unreachable / door-edge-resolution branch above is intentionally left alone — it + // waits on the door edge itself and issues its own resolution-aware fast click. + if (isDoorInteractionSettling()) { + exitReason = "door-settling-yield"; + break; + } nextWalkingDistance = path.size() <= 5 ? 0 : Rs2Random.between(9, 12); int dist2d = currentWorldPoint.distanceTo2D(Rs2Player.getWorldLocation()); if (dist2d > nextWalkingDistance) { @@ -3272,8 +3282,26 @@ private static boolean handleCurrentTileTransportTowardPath(List raw return false; } - Set transports = ShortestPathPlugin.getTransports().get(playerLoc); - if (transports == null || transports.isEmpty()) { + // Snappy proximity: consider usable transports whose origin is reachable within a few tiles + // of the player, not just the one on the exact player tile. NPC/"Follow" transports (e.g. Elkoy + // in the Tree Gnome Village maze) roam and sit a tile off the planned path, so exact-tile + // matching never sees them. The destination-on-forward-route gate below keeps this safe against + // off-path loops, and getTransports() is already the usable (config/quest/level-filtered) set, + // so we never grab a transport the pathfinder excluded. + final int NEARBY_TRANSPORT_REACH = 5; + Map> transportsByOrigin = ShortestPathPlugin.getTransports(); + Set transports = new HashSet<>(); + Set transportsOnPlayerTile = transportsByOrigin.get(playerLoc); + if (transportsOnPlayerTile != null) { + transports.addAll(transportsOnPlayerTile); + } + for (WorldPoint reachableTile : Rs2Tile.getReachableTilesFromTile(playerLoc, NEARBY_TRANSPORT_REACH).keySet()) { + Set ts = transportsByOrigin.get(reachableTile); + if (ts != null) { + transports.addAll(ts); + } + } + if (transports.isEmpty()) { return false; } @@ -3282,7 +3310,7 @@ private static boolean handleCurrentTileTransportTowardPath(List raw addForwardPathIndices(forwardIndex, path, playerLoc); WorldPoint priorOrigin = lastTransportOriginLocation; - // Trust the pathfinder: only take a current-tile transport whose destination is on the + // Trust the pathfinder: only take a nearby transport whose destination is on the // planned forward route, ordered by route position (earliest forward transport first). The // old fallback admitted off-path transports whose destination was straight-line "closer" to // the goal — but WorldPoint#distanceTo ignores the underground Y-offset, so an inner-region @@ -3305,19 +3333,24 @@ private static boolean handleCurrentTileTransportTowardPath(List raw .collect(Collectors.toList()); for (Transport transport : candidates) { - if (shouldThrottleCurrentTileTransportAttempt(playerLoc, transport.getDestination())) { + WorldPoint origin = transport.getOrigin() != null ? transport.getOrigin() : playerLoc; + if (shouldThrottleCurrentTileTransportAttempt(origin, transport.getDestination())) { continue; } - markCurrentTileTransportAttempt(playerLoc, transport.getDestination()); + markCurrentTileTransportAttempt(origin, transport.getDestination()); WorldPoint before = Rs2Player.getWorldLocation(); - if (handleTransports(Arrays.asList(playerLoc, transport.getDestination()), 0)) { + // Pass the transport's own origin so handleTransports walks the short hop to it before + // interacting (NPC dispatch already auto-walks via canWalkTo + interact); object/door + // interactions that can't be reached from here simply return false and we fall through. + if (handleTransports(Arrays.asList(origin, transport.getDestination()), 0)) { if (didCurrentTileTransportProgress(before, transport.getDestination(), target)) { - log.info("[Walker] Current-tile transport handler resolved obstacle near {}", playerLoc); + log.info("[Walker] Nearby transport handler resolved obstacle: origin={} dest={} (player {})", + origin, transport.getDestination(), playerLoc); return true; } WebWalkLog.spInfo( "current_tile_transport_no_progress | origin={} dest={} before={} after={} goal={}", - compactWorldPoint(playerLoc), + compactWorldPoint(origin), compactWorldPoint(transport.getDestination()), compactWorldPoint(before), compactWorldPoint(Rs2Player.getWorldLocation()), From bd0082ab46f5db66e5506c6ce09976c98f49a290 Mon Sep 17 00:00:00 2001 From: Blonks <40224407+Blonks-afk@users.noreply.github.com> Date: Tue, 2 Jun 2026 15:15:11 -0400 Subject: [PATCH 43/51] xpupdater: fix fetchXp guard flag --- .../net/runelite/client/plugins/xpupdater/XpUpdaterPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterPlugin.java index b5c13f5c03f..95c9c2e3878 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/xpupdater/XpUpdaterPlugin.java @@ -126,8 +126,8 @@ public void onGameTick(GameTick gameTick) if (local != null) { lastDisplayName = local.getName(); + fetchXp = false; } - fetchXp = false; } } From 8acfccc8b286d92103690553d0e4ef49bb258018 Mon Sep 17 00:00:00 2001 From: RuneLite Cache-Code Autoupdater Date: Tue, 2 Jun 2026 09:01:20 -0400 Subject: [PATCH 44/51] Update Widget IDs to 2026-06-02 --- runelite-api/src/main/interfaces/interfaces.toml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/runelite-api/src/main/interfaces/interfaces.toml b/runelite-api/src/main/interfaces/interfaces.toml index 7d5ca86d34b..31001049613 100644 --- a/runelite-api/src/main/interfaces/interfaces.toml +++ b/runelite-api/src/main/interfaces/interfaces.toml @@ -771,9 +771,9 @@ id=128 [seed_vault] id=631 title_container=2 -item_container=15 -item_text=16 -search_button=24 +item_container=16 +item_text=17 +search_button=26 [seed_vault_inventory] id=630 From cadc0a61d70acd899fd654623db7a4f74ec77182 Mon Sep 17 00:00:00 2001 From: RuneLite Cache-Code Autoupdater Date: Tue, 2 Jun 2026 09:01:21 -0400 Subject: [PATCH 45/51] Update Script arguments to 2026-06-02 --- runelite-api/src/main/java/net/runelite/api/ScriptID.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/runelite-api/src/main/java/net/runelite/api/ScriptID.java b/runelite-api/src/main/java/net/runelite/api/ScriptID.java index d77c75300e2..500fa965fa9 100644 --- a/runelite-api/src/main/java/net/runelite/api/ScriptID.java +++ b/runelite-api/src/main/java/net/runelite/api/ScriptID.java @@ -486,7 +486,7 @@ public final class ScriptID @ScriptArguments(integer = 17) public static final int BANK_DEPOSITBOX_INIT = 144; - @ScriptArguments(integer = 7) + @ScriptArguments(integer = 8) public static final int SEED_VAULT_BUILD = 2819; @ScriptArguments(integer = 4) From cefb5a09b78c04543c494e94caa39e39dccc52b6 Mon Sep 17 00:00:00 2001 From: RuneLite Cache-Code Autoupdater Date: Tue, 2 Jun 2026 09:01:23 -0400 Subject: [PATCH 46/51] Update Item variations to 2026-06-02 --- .../src/main/resources/item_variations.json | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/runelite-client/src/main/resources/item_variations.json b/runelite-client/src/main/resources/item_variations.json index b0fae409853..3a124740b47 100644 --- a/runelite-client/src/main/resources/item_variations.json +++ b/runelite-client/src/main/resources/item_variations.json @@ -7999,7 +7999,13 @@ 33070, 33072, 33338, - 33340 + 33340, + 33439, + 33441, + 33443, + 33445, + 33447, + 33449 ], "slayer ring": [ 11866, @@ -8021,7 +8027,8 @@ 33322, 33323, 33326, - 33328 + 33328, + 33434 ], "box of chocolate strawberries": [ 11912, @@ -8874,7 +8881,8 @@ ], "sulphurous fertiliser": [ 13419, - 18467 + 18467, + 33437 ], "ensouled goblin head": [ 13447, @@ -13900,5 +13908,10 @@ "gem sack": [ 33393, 33395 + ], + "demonic pacts relic hunter armour set": [ + 33451, + 33454, + 33457 ] } \ No newline at end of file From c2a3bf03944cfde99ec14629e16641d36dc8910c Mon Sep 17 00:00:00 2001 From: RuneLite Cache-Code Autoupdater Date: Tue, 2 Jun 2026 09:01:23 -0400 Subject: [PATCH 47/51] Update Legacy ID classes to 2026-06-02 --- .../main/java/net/runelite/api/ItemID.java | 23 +- .../src/main/java/net/runelite/api/NpcID.java | 5 +- .../java/net/runelite/api/NullItemID.java | 20 +- .../main/java/net/runelite/api/NullNpcID.java | 10 + .../main/java/net/runelite/api/ObjectID.java | 206 +++++++++++------- 5 files changed, 174 insertions(+), 90 deletions(-) diff --git a/runelite-api/src/main/java/net/runelite/api/ItemID.java b/runelite-api/src/main/java/net/runelite/api/ItemID.java index 6840cc722ca..8958898b2ae 100644 --- a/runelite-api/src/main/java/net/runelite/api/ItemID.java +++ b/runelite-api/src/main/java/net/runelite/api/ItemID.java @@ -16248,17 +16248,17 @@ public final class ItemID public static final int TRIDENT_OF_THE_SWAMP_O = 33314; public static final int UNCHARGED_TOXIC_TRIDENT_O = 33316; public static final int TRIDENT_OF_THE_SWAMP_E_O = 33318; - public static final int UNCHARGED_TOXIC_TRIDENT_E_33320 = 33320; + public static final int UNCHARGED_TOXIC_TRIDENT_E_O = 33320; public static final int TRIDENT_OF_THE_SEAS_O = 33322; public static final int TRIDENT_OF_THE_SEAS_FULL_O = 33323; public static final int TRIDENT_OF_THE_SEAS_E_O = 33326; public static final int UNCHARGED_TRIDENT_E_O = 33328; - public static final int IBANS_STAFF_33330 = 33330; + public static final int IBANS_STAFF_O = 33330; public static final int IBANS_STAFF_U_O = 33332; - public static final int IBANS_STAFF_O = 33333; + public static final int IBANS_STAFF_BO = 33333; public static final int SOULREAPER_AXE_O = 33335; - public static final int DEMONIC_SLAYER_HELMET = 33338; - public static final int DEMONIC_SLAYER_HELMET_33340 = 33340; + public static final int OATHPLATE_SLAYER_HELMET = 33338; + public static final int RADIANT_SLAYER_HELMET = 33340; public static final int DEMONIC_QUILL = 33342; public static final int DEMONIC_PACTS_DRAGON_TROPHY = 33345; public static final int DEMONIC_PACTS_RUNE_TROPHY = 33347; @@ -16267,7 +16267,7 @@ public final class ItemID public static final int DEMONIC_PACTS_STEEL_TROPHY = 33353; public static final int DEMONIC_PACTS_IRON_TROPHY = 33355; public static final int DEMONIC_PACTS_BRONZE_TROPHY = 33357; - public static final int IMPISH_RITUAL_KIT = 33359; + public static final int IMPISH_RITUAL_SCROLL = 33359; public static final int DEMONIC_PACTS_THRONE_SCROLL = 33362; public static final int IMPISH_WHISTLE = 33365; public static final int DEMONIC_PACTS_DEMON_BUTLER_SCROLL = 33368; @@ -16293,5 +16293,16 @@ public final class ItemID public static final int SPECTATOR_OCULUS = 33427; public static final int TRINKET_OF_VENGEANCE_2 = 33428; public static final int TRINKET_OF_VENGEANCE_1 = 33431; + public static final int UNCHARGED_TRIDENT_O = 33434; + public static final int SULPHUROUS_FERTILISER_33437 = 33437; + public static final int OATHPLATE_SLAYER_HELMET_I = 33439; + public static final int OATHPLATE_SLAYER_HELMET_I_33441 = 33441; + public static final int OATHPLATE_SLAYER_HELMET_I_33443 = 33443; + public static final int RADIANT_SLAYER_HELMET_I = 33445; + public static final int RADIANT_SLAYER_HELMET_I_33447 = 33447; + public static final int RADIANT_SLAYER_HELMET_I_33449 = 33449; + public static final int DEMONIC_PACTS_RELIC_HUNTER_T1_ARMOUR_SET = 33451; + public static final int DEMONIC_PACTS_RELIC_HUNTER_T2_ARMOUR_SET = 33454; + public static final int DEMONIC_PACTS_RELIC_HUNTER_T3_ARMOUR_SET = 33457; /* This file is automatically generated. Do not edit. */ } diff --git a/runelite-api/src/main/java/net/runelite/api/NpcID.java b/runelite-api/src/main/java/net/runelite/api/NpcID.java index bc5cc5b1d40..0a4141305ec 100644 --- a/runelite-api/src/main/java/net/runelite/api/NpcID.java +++ b/runelite-api/src/main/java/net/runelite/api/NpcID.java @@ -227,7 +227,6 @@ public final class NpcID public static final int MAID = 223; public static final int COOK = 225; public static final int BUTLER = 227; - public static final int DEMON_BUTLER = 229; public static final int WOLF_231 = 231; public static final int JUNGLE_WOLF = 232; public static final int MACARONI_PENGUIN = 233; @@ -6559,7 +6558,7 @@ public final class NpcID public static final int MAID_7328 = 7328; public static final int COOK_7329 = 7329; public static final int BUTLER_7330 = 7330; - public static final int DEMON_BUTLER_7331 = 7331; + public static final int DEMON_BUTLER = 7331; public static final int FAIRY_FIXIT = 7332; public static final int FAIRY_FIXIT_7333 = 7333; public static final int GIANT_SQUIRREL = 7334; @@ -13130,5 +13129,7 @@ public final class NpcID public static final int ELIDINIS_WARDEN_15698 = 15698; public static final int TZTOKJAD_15699 = 15699; public static final int YAMA_15700 = 15700; + public static final int DEMON_BUTLER_15710 = 15710; + public static final int DEMON_BUTLER_15711 = 15711; /* This file is automatically generated. Do not edit. */ } diff --git a/runelite-api/src/main/java/net/runelite/api/NullItemID.java b/runelite-api/src/main/java/net/runelite/api/NullItemID.java index e1985921c57..9e003977d38 100644 --- a/runelite-api/src/main/java/net/runelite/api/NullItemID.java +++ b/runelite-api/src/main/java/net/runelite/api/NullItemID.java @@ -16760,16 +16760,11 @@ public final class NullItemID public static final int NULL_33312 = 33312; public static final int NULL_33313 = 33313; public static final int NULL_33315 = 33315; - public static final int NULL_33317 = 33317; public static final int NULL_33319 = 33319; - public static final int NULL_33321 = 33321; - public static final int NULL_33324 = 33324; public static final int NULL_33325 = 33325; public static final int NULL_33327 = 33327; - public static final int NULL_33329 = 33329; public static final int NULL_33331 = 33331; public static final int NULL_33334 = 33334; - public static final int NULL_33336 = 33336; public static final int NULL_33337 = 33337; public static final int NULL_33339 = 33339; public static final int NULL_33341 = 33341; @@ -16831,5 +16826,20 @@ public final class NullItemID public static final int NULL_33430 = 33430; public static final int NULL_33432 = 33432; public static final int NULL_33433 = 33433; + public static final int NULL_33435 = 33435; + public static final int NULL_33436 = 33436; + public static final int NULL_33438 = 33438; + public static final int NULL_33440 = 33440; + public static final int NULL_33442 = 33442; + public static final int NULL_33444 = 33444; + public static final int NULL_33446 = 33446; + public static final int NULL_33448 = 33448; + public static final int NULL_33450 = 33450; + public static final int NULL_33452 = 33452; + public static final int NULL_33453 = 33453; + public static final int NULL_33455 = 33455; + public static final int NULL_33456 = 33456; + public static final int NULL_33458 = 33458; + public static final int NULL_33459 = 33459; /* This file is automatically generated. Do not edit. */ } diff --git a/runelite-api/src/main/java/net/runelite/api/NullNpcID.java b/runelite-api/src/main/java/net/runelite/api/NullNpcID.java index d4c1ac0cf70..830c5bfa0ea 100644 --- a/runelite-api/src/main/java/net/runelite/api/NullNpcID.java +++ b/runelite-api/src/main/java/net/runelite/api/NullNpcID.java @@ -18,6 +18,7 @@ public final class NullNpcID public static final int NULL_224 = 224; public static final int NULL_226 = 226; public static final int NULL_228 = 228; + public static final int NULL_229 = 229; public static final int NULL_230 = 230; public static final int NULL_317 = 317; public static final int NULL_324 = 324; @@ -2561,5 +2562,14 @@ public final class NullNpcID public static final int NULL_15670 = 15670; public static final int NULL_15671 = 15671; public static final int NULL_15676 = 15676; + public static final int NULL_15701 = 15701; + public static final int NULL_15702 = 15702; + public static final int NULL_15703 = 15703; + public static final int NULL_15704 = 15704; + public static final int NULL_15705 = 15705; + public static final int NULL_15706 = 15706; + public static final int NULL_15707 = 15707; + public static final int NULL_15708 = 15708; + public static final int NULL_15709 = 15709; /* This file is automatically generated. Do not edit. */ } diff --git a/runelite-api/src/main/java/net/runelite/api/ObjectID.java b/runelite-api/src/main/java/net/runelite/api/ObjectID.java index 6ded450580f..da790ae2d21 100644 --- a/runelite-api/src/main/java/net/runelite/api/ObjectID.java +++ b/runelite-api/src/main/java/net/runelite/api/ObjectID.java @@ -8037,7 +8037,7 @@ public final class ObjectID public static final int SEWAGE_WATER = 14204; public static final int FOOD_PILE = 14205; public static final int FOOD_PILE_14206 = 14206; - public static final int HERBS_14209 = 14209; + public static final int GUAM_LEAF = 14209; public static final int WALL_14212 = 14212; public static final int WALL_14213 = 14213; public static final int WALL_14216 = 14216; @@ -14094,11 +14094,11 @@ public final class ObjectID public static final int HUTCH_26822 = 26822; public static final int CAGES_26823 = 26823; public static final int CAGE_26824 = 26824; - public static final int HERBS_26825 = 26825; - public static final int HERBS_26826 = 26826; - public static final int HERBS_26827 = 26827; - public static final int HERBS_26828 = 26828; - public static final int HERBS_26829 = 26829; + public static final int GUAM_LEAF_26825 = 26825; + public static final int GUAM_LEAF_26826 = 26826; + public static final int GUAM_LEAF_26827 = 26827; + public static final int GUAM_LEAF_26828 = 26828; + public static final int MARRENTILL = 26829; public static final int CONSECRATED_HOUSE = 26830; public static final int DESECRATED_HOUSE = 26831; public static final int NATURE_HOUSE = 26832; @@ -15632,6 +15632,58 @@ public final class ObjectID public static final int HEROIC_STATUE = 29557; public static final int DEATH = 29559; public static final int FAIRY_RING_29560 = 29560; + public static final int OAK_OUTFIT_STAND = 29561; + public static final int OAK_OUTFIT_STAND_29562 = 29562; + public static final int OAK_OUTFIT_STAND_29563 = 29563; + public static final int MAHOGANY_OUTFIT_STAND = 29564; + public static final int MAHOGANY_OUTFIT_STAND_29565 = 29565; + public static final int MAHOGANY_OUTFIT_STAND_29566 = 29566; + public static final int THRONE_29567 = 29567; + public static final int DISEASED_GUAM_LEAF = 29568; + public static final int DISEASED_GUAM_LEAF_29569 = 29569; + public static final int DISEASED_GUAM_LEAF_29570 = 29570; + public static final int DISEASED_MARRENTILL = 29571; + public static final int DISEASED_MARRENTILL_29572 = 29572; + public static final int DISEASED_MARRENTILL_29573 = 29573; + public static final int DISEASED_TARROMIN = 29574; + public static final int DISEASED_TARROMIN_29575 = 29575; + public static final int DISEASED_TARROMIN_29576 = 29576; + public static final int DISEASED_HARRALANDER = 29577; + public static final int DISEASED_HARRALANDER_29578 = 29578; + public static final int DISEASED_HARRALANDER_29579 = 29579; + public static final int DISEASED_RANARR_WEED = 29580; + public static final int DISEASED_RANARR_WEED_29581 = 29581; + public static final int DISEASED_RANARR_WEED_29582 = 29582; + public static final int DISEASED_IRIT_LEAF = 29583; + public static final int DISEASED_IRIT_LEAF_29584 = 29584; + public static final int DISEASED_IRIT_LEAF_29585 = 29585; + public static final int DISEASED_AVANTOE = 29586; + public static final int DISEASED_AVANTOE_29587 = 29587; + public static final int DISEASED_AVANTOE_29588 = 29588; + public static final int DISEASED_TOADFLAX = 29589; + public static final int DISEASED_TOADFLAX_29590 = 29590; + public static final int DISEASED_TOADFLAX_29591 = 29591; + public static final int DISEASED_KWUARM = 29592; + public static final int DISEASED_KWUARM_29593 = 29593; + public static final int DISEASED_KWUARM_29594 = 29594; + public static final int DISEASED_CADANTINE = 29595; + public static final int DISEASED_CADANTINE_29596 = 29596; + public static final int DISEASED_CADANTINE_29597 = 29597; + public static final int DISEASED_LANTADYME = 29598; + public static final int DISEASED_LANTADYME_29599 = 29599; + public static final int DISEASED_LANTADYME_29600 = 29600; + public static final int DISEASED_DWARF_WEED = 29601; + public static final int DISEASED_DWARF_WEED_29602 = 29602; + public static final int DISEASED_DWARF_WEED_29603 = 29603; + public static final int DISEASED_TORSTOL = 29604; + public static final int DISEASED_TORSTOL_29605 = 29605; + public static final int DISEASED_TORSTOL_29606 = 29606; + public static final int DISEASED_SNAPDRAGON = 29607; + public static final int DISEASED_SNAPDRAGON_29608 = 29608; + public static final int DISEASED_SNAPDRAGON_29609 = 29609; + public static final int DISEASED_HUASCA = 29610; + public static final int DISEASED_HUASCA_29611 = 29611; + public static final int DISEASED_HUASCA_29612 = 29612; public static final int MOUNTED_MAX_CAPE_29625 = 29625; public static final int CRACK_29626 = 29626; public static final int CRACK_29627 = 29627; @@ -19906,70 +19958,70 @@ public final class ObjectID public static final int GIANT_ANVIL_39724 = 39724; public static final int STONE_PILE_39740 = 39740; public static final int STONE_PILE_39741 = 39741; - public static final int HERBS_39748 = 39748; - public static final int HERBS_39749 = 39749; - public static final int HERBS_39750 = 39750; - public static final int HERBS_39751 = 39751; - public static final int HERBS_39752 = 39752; - public static final int HERBS_39753 = 39753; - public static final int HERBS_39754 = 39754; - public static final int HERBS_39755 = 39755; - public static final int HERBS_39756 = 39756; - public static final int HERBS_39757 = 39757; - public static final int HERBS_39758 = 39758; - public static final int HERBS_39759 = 39759; - public static final int HERBS_39760 = 39760; - public static final int HERBS_39761 = 39761; - public static final int HERBS_39762 = 39762; - public static final int HERBS_39763 = 39763; - public static final int HERBS_39764 = 39764; - public static final int HERBS_39765 = 39765; - public static final int HERBS_39766 = 39766; - public static final int HERBS_39767 = 39767; - public static final int HERBS_39768 = 39768; - public static final int HERBS_39769 = 39769; - public static final int HERBS_39770 = 39770; - public static final int HERBS_39771 = 39771; - public static final int HERBS_39772 = 39772; - public static final int HERBS_39773 = 39773; - public static final int HERBS_39774 = 39774; - public static final int HERBS_39775 = 39775; - public static final int HERBS_39776 = 39776; - public static final int HERBS_39777 = 39777; - public static final int HERBS_39778 = 39778; - public static final int HERBS_39779 = 39779; - public static final int HERBS_39780 = 39780; - public static final int HERBS_39781 = 39781; - public static final int HERBS_39782 = 39782; - public static final int HERBS_39783 = 39783; - public static final int HERBS_39784 = 39784; - public static final int HERBS_39785 = 39785; - public static final int HERBS_39786 = 39786; - public static final int HERBS_39787 = 39787; - public static final int HERBS_39788 = 39788; - public static final int HERBS_39789 = 39789; - public static final int HERBS_39790 = 39790; - public static final int HERBS_39791 = 39791; - public static final int HERBS_39792 = 39792; - public static final int HERBS_39793 = 39793; - public static final int HERBS_39794 = 39794; - public static final int HERBS_39795 = 39795; - public static final int HERBS_39796 = 39796; - public static final int HERBS_39797 = 39797; - public static final int HERBS_39798 = 39798; - public static final int HERBS_39799 = 39799; - public static final int HERBS_39800 = 39800; - public static final int HERBS_39801 = 39801; - public static final int HERBS_39802 = 39802; - public static final int HERBS_39803 = 39803; - public static final int HERBS_39804 = 39804; - public static final int HERBS_39805 = 39805; - public static final int HERBS_39806 = 39806; - public static final int HERBS_39807 = 39807; - public static final int HERBS_39808 = 39808; - public static final int HERBS_39809 = 39809; - public static final int HERBS_39810 = 39810; - public static final int HERBS_39811 = 39811; + public static final int MARRENTILL_39748 = 39748; + public static final int MARRENTILL_39749 = 39749; + public static final int MARRENTILL_39750 = 39750; + public static final int MARRENTILL_39751 = 39751; + public static final int TARROMIN = 39752; + public static final int TARROMIN_39753 = 39753; + public static final int TARROMIN_39754 = 39754; + public static final int TARROMIN_39755 = 39755; + public static final int TARROMIN_39756 = 39756; + public static final int HARRALANDER = 39757; + public static final int HARRALANDER_39758 = 39758; + public static final int HARRALANDER_39759 = 39759; + public static final int HARRALANDER_39760 = 39760; + public static final int HARRALANDER_39761 = 39761; + public static final int RANARR_WEED = 39762; + public static final int RANARR_WEED_39763 = 39763; + public static final int RANARR_WEED_39764 = 39764; + public static final int RANARR_WEED_39765 = 39765; + public static final int RANARR_WEED_39766 = 39766; + public static final int IRIT_LEAF = 39767; + public static final int IRIT_LEAF_39768 = 39768; + public static final int IRIT_LEAF_39769 = 39769; + public static final int IRIT_LEAF_39770 = 39770; + public static final int IRIT_LEAF_39771 = 39771; + public static final int AVANTOE = 39772; + public static final int AVANTOE_39773 = 39773; + public static final int AVANTOE_39774 = 39774; + public static final int AVANTOE_39775 = 39775; + public static final int AVANTOE_39776 = 39776; + public static final int TOADFLAX = 39777; + public static final int TOADFLAX_39778 = 39778; + public static final int TOADFLAX_39779 = 39779; + public static final int TOADFLAX_39780 = 39780; + public static final int TOADFLAX_39781 = 39781; + public static final int KWUARM = 39782; + public static final int KWUARM_39783 = 39783; + public static final int KWUARM_39784 = 39784; + public static final int KWUARM_39785 = 39785; + public static final int KWUARM_39786 = 39786; + public static final int CADANTINE = 39787; + public static final int CADANTINE_39788 = 39788; + public static final int CADANTINE_39789 = 39789; + public static final int CADANTINE_39790 = 39790; + public static final int CADANTINE_39791 = 39791; + public static final int LANTADYME = 39792; + public static final int LANTADYME_39793 = 39793; + public static final int LANTADYME_39794 = 39794; + public static final int LANTADYME_39795 = 39795; + public static final int LANTADYME_39796 = 39796; + public static final int DWARF_WEED = 39797; + public static final int DWARF_WEED_39798 = 39798; + public static final int DWARF_WEED_39799 = 39799; + public static final int DWARF_WEED_39800 = 39800; + public static final int DWARF_WEED_39801 = 39801; + public static final int TORSTOL = 39802; + public static final int TORSTOL_39803 = 39803; + public static final int TORSTOL_39804 = 39804; + public static final int TORSTOL_39805 = 39805; + public static final int TORSTOL_39806 = 39806; + public static final int SNAPDRAGON = 39807; + public static final int SNAPDRAGON_39808 = 39808; + public static final int SNAPDRAGON_39809 = 39809; + public static final int SNAPDRAGON_39810 = 39810; + public static final int SNAPDRAGON_39811 = 39811; public static final int HERB_PATCH_39812 = 39812; public static final int HERB_PATCH_39813 = 39813; public static final int HERBS_39814 = 39814; @@ -20787,14 +20839,14 @@ public final class ObjectID public static final int ORNATE_BANNER_STAND_41162 = 41162; public static final int ORNATE_BANNER_STAND_41163 = 41163; public static final int ORNATE_BANNER_STAND_41164 = 41164; - public static final int OAK_OUTFIT_STAND = 41165; + public static final int OAK_OUTFIT_STAND_41165 = 41165; public static final int OAK_OUTFIT_STAND_41166 = 41166; public static final int OAK_OUTFIT_STAND_41167 = 41167; public static final int OAK_OUTFIT_STAND_41168 = 41168; public static final int OAK_OUTFIT_STAND_41169 = 41169; public static final int OAK_OUTFIT_STAND_41170 = 41170; public static final int OAK_OUTFIT_STAND_41171 = 41171; - public static final int MAHOGANY_OUTFIT_STAND = 41172; + public static final int MAHOGANY_OUTFIT_STAND_41172 = 41172; public static final int MAHOGANY_OUTFIT_STAND_41173 = 41173; public static final int MAHOGANY_OUTFIT_STAND_41174 = 41174; public static final int MAHOGANY_OUTFIT_STAND_41175 = 41175; @@ -26267,11 +26319,11 @@ public final class ObjectID public static final int HERBS_55344 = 55344; public static final int HERBS_55345 = 55345; public static final int HERBS_55346 = 55346; - public static final int HERBS_55347 = 55347; - public static final int HERBS_55348 = 55348; - public static final int HERBS_55349 = 55349; - public static final int HERBS_55350 = 55350; - public static final int HERBS_55351 = 55351; + public static final int HUASCA = 55347; + public static final int HUASCA_55348 = 55348; + public static final int HUASCA_55349 = 55349; + public static final int HUASCA_55350 = 55350; + public static final int HUASCA_55351 = 55351; public static final int HOUSE_ADVERTISEMENT_55352 = 55352; public static final int PORTAL_55353 = 55353; public static final int HOPPER_CONTROLS_55402 = 55402; From 8d39eff0fc42c198c00cce861900460627aa365e Mon Sep 17 00:00:00 2001 From: RuneLite Cache-Code Autoupdater Date: Tue, 2 Jun 2026 09:01:39 -0400 Subject: [PATCH 48/51] Update GameVals to 2026-06-02 --- .../net/runelite/api/gameval/AnimationID.java | 7 + .../net/runelite/api/gameval/DBTableID.java | 6 + .../net/runelite/api/gameval/InterfaceID.java | 47 +-- .../java/net/runelite/api/gameval/ItemID.java | 87 +++++- .../java/net/runelite/api/gameval/NpcID.java | 23 +- .../net/runelite/api/gameval/ObjectID.java | 272 +++++++++++++++++- .../net/runelite/api/gameval/ObjectID1.java | 138 ++++----- .../net/runelite/api/gameval/SpotanimID.java | 10 + .../net/runelite/api/gameval/SpriteID.java | 6 + .../net/runelite/api/gameval/VarPlayerID.java | 7 + .../net/runelite/api/gameval/VarbitID.java | 17 ++ 11 files changed, 509 insertions(+), 111 deletions(-) diff --git a/runelite-api/src/main/java/net/runelite/api/gameval/AnimationID.java b/runelite-api/src/main/java/net/runelite/api/gameval/AnimationID.java index fa85c934677..fb0adc899ff 100644 --- a/runelite-api/src/main/java/net/runelite/api/gameval/AnimationID.java +++ b/runelite-api/src/main/java/net/runelite/api/gameval/AnimationID.java @@ -13899,5 +13899,12 @@ public final class AnimationID public static final int SAILING_BOAT_SAIL_KANDARIN_3X8_FULL_OFFSET = 13899; public static final int SAILING_BOAT_SAIL_KANDARIN_3X8_DOWN_TO_FULL_OFFSET = 13900; public static final int SAILING_BOAT_SAIL_KANDARIN_3X8_HALF_TO_FULL_OFFSET = 13901; + public static final int LEAGUE_6_HOME_TELEPORT_SCROLL_READ = 13902; + public static final int CHAIR_SIT_READY_THRONE_8 = 13903; + public static final int CHAIR_EAT_THRONE_8 = 13904; + public static final int SLAYER_TOTS_CHARGE_ORN = 13905; + public static final int SLAYER_TOTS_CHARGE_TOXIC_ORN = 13906; + public static final int SLAYER_TOTS_I_CHARGE_ORN = 13907; + public static final int SLAYER_TOTS_I_CHARGE_TOXIC_ORN = 13908; /* This file is automatically generated. Do not edit. */ } diff --git a/runelite-api/src/main/java/net/runelite/api/gameval/DBTableID.java b/runelite-api/src/main/java/net/runelite/api/gameval/DBTableID.java index 9f53b1831d1..834e2e5431b 100644 --- a/runelite-api/src/main/java/net/runelite/api/gameval/DBTableID.java +++ b/runelite-api/src/main/java/net/runelite/api/gameval/DBTableID.java @@ -7086,6 +7086,9 @@ public static final class Row public static final int VENGEANCE_ANIM_TOGGLE_LIST = 4094; public static final int ALCHEMY_ANIM_TOGGLE_LIST = 4095; public static final int NPC_CONTACT_ANIM_TOGGLE_LIST = 4096; + public static final int RESURRECTION_COSMETIC_TOGGLE_LIST_ZOMBIE = 8114; + public static final int RESURRECTION_COSMETIC_TOGGLE_LIST_SKELETON = 8115; + public static final int RESURRECTION_COSMETIC_TOGGLE_LIST_GHOST = 8116; } } @@ -9222,6 +9225,7 @@ public static final class Row public static final int POH_TROPHY_BLUEFIN = 7126; public static final int POH_TROPHY_MARLIN = 7127; public static final int POH_GARDEN_CENTREPIECE_6 = 9654; + public static final int POH_THRONE_8 = 16324; } } @@ -10327,6 +10331,7 @@ public static final class Row public static final int SLAYER_REWARDS_UNLOCK_GRYPHONS = 9420; public static final int SLAYER_REWARDS_UNLOCK_HELM_HOODED = 9641; public static final int SLAYER_REWARDS_LONGER_GRYPHONS = 9642; + public static final int SLAYER_REWARDS_UNLOCK_HELM_LEAGUE_6 = 16325; } } @@ -22710,6 +22715,7 @@ public static final class Row public static final int SKILL_FEATURE_MAGIC_ANCIENT_LEAGUES_6_HOME_TELEPORT = 16262; public static final int SKILL_FEATURE_MAGIC_LUNAR_LEAGUES_6_HOME_TELEPORT = 16263; public static final int SKILL_FEATURE_MAGIC_ARCEUUS_LEAGUES_6_HOME_TELEPORT = 16264; + public static final int SKILL_FEATURE_FARMING_SULPHUROUS_FERTILISER = 16323; } } diff --git a/runelite-api/src/main/java/net/runelite/api/gameval/InterfaceID.java b/runelite-api/src/main/java/net/runelite/api/gameval/InterfaceID.java index b7f47bee02d..06bfef774a4 100644 --- a/runelite-api/src/main/java/net/runelite/api/gameval/InterfaceID.java +++ b/runelite-api/src/main/java/net/runelite/api/gameval/InterfaceID.java @@ -5365,6 +5365,9 @@ public static final class FarmingTools public static final int COMPOST = 0x007d_0011; public static final int SUPERCOMPOST = 0x007d_0012; public static final int ULTRACOMPOST = 0x007d_0013; + public static final int DEPOSIT_ALL = 0x007d_0014; + public static final int DEPOSITINV = 0x007d_0015; + public static final int DEPOSITINV_GRAPHIC = 0x007d_0016; } public static final class FarmingToolsSide @@ -22334,27 +22337,29 @@ public static final class SeedVault public static final int RIGHT_PANEL = 0x0277_0009; public static final int RIGHT_SCROLL = 0x0277_000a; public static final int LIST = 0x0277_000b; - public static final int FAVES = 0x0277_000c; - public static final int CATEGORY_HEADERS = 0x0277_000d; - public static final int CATEGORY_LINES = 0x0277_000e; - public static final int OBJ_LIST = 0x0277_000f; - public static final int TEXT_LIST = 0x0277_0010; - public static final int BUTTONS = 0x0277_0011; - public static final int BUTTONS_LINE0 = 0x0277_0012; - public static final int X1 = 0x0277_0013; - public static final int X5 = 0x0277_0014; - public static final int X10 = 0x0277_0015; - public static final int XX = 0x0277_0016; - public static final int XALL = 0x0277_0017; - public static final int SEARCH = 0x0277_0018; - public static final int DEPOSIT_ALL = 0x0277_0019; - public static final int DEPOSIT_ALL_GFX = 0x0277_001a; - public static final int TEXT_1 = 0x0277_001b; - public static final int TEXT_5 = 0x0277_001c; - public static final int TEXT_10 = 0x0277_001d; - public static final int TEXT_X = 0x0277_001e; - public static final int TEXT_ALL = 0x0277_001f; - public static final int SEARCH_GFX = 0x0277_0020; + public static final int CONTRACT_SEEDS = 0x0277_000c; + public static final int MAIN_VAULT = 0x0277_000d; + public static final int FAVES = 0x0277_000e; + public static final int CATEGORY_HEADERS = 0x0277_000f; + public static final int CATEGORY_LINES = 0x0277_0010; + public static final int OBJ_LIST = 0x0277_0011; + public static final int TEXT_LIST = 0x0277_0012; + public static final int BUTTONS = 0x0277_0013; + public static final int BUTTONS_LINE0 = 0x0277_0014; + public static final int X1 = 0x0277_0015; + public static final int X5 = 0x0277_0016; + public static final int X10 = 0x0277_0017; + public static final int XX = 0x0277_0018; + public static final int XALL = 0x0277_0019; + public static final int SEARCH = 0x0277_001a; + public static final int DEPOSIT_ALL = 0x0277_001b; + public static final int DEPOSIT_ALL_GFX = 0x0277_001c; + public static final int TEXT_1 = 0x0277_001d; + public static final int TEXT_5 = 0x0277_001e; + public static final int TEXT_10 = 0x0277_001f; + public static final int TEXT_X = 0x0277_0020; + public static final int TEXT_ALL = 0x0277_0021; + public static final int SEARCH_GFX = 0x0277_0022; } public static final class HallowedToolSide diff --git a/runelite-api/src/main/java/net/runelite/api/gameval/ItemID.java b/runelite-api/src/main/java/net/runelite/api/gameval/ItemID.java index c16ac4a14ef..36cb0504e9b 100644 --- a/runelite-api/src/main/java/net/runelite/api/gameval/ItemID.java +++ b/runelite-api/src/main/java/net/runelite/api/gameval/ItemID.java @@ -87225,7 +87225,7 @@ public final class ItemID public static final int TOXIC_TOTS_I_CHARGED_ORN = 33318; /** - * Uncharged toxic trident (e) + * Uncharged toxic trident (e) (o) */ public static final int TOXIC_TOTS_I_UNCHARGED_ORN = 33320; @@ -87250,7 +87250,7 @@ public final class ItemID public static final int TOTS_I_UNCHARGED_ORN = 33328; /** - * Iban's staff + * Iban's staff (o) */ public static final int IBAN_STAFF_ORN = 33330; @@ -87260,7 +87260,7 @@ public final class ItemID public static final int IBAN_STAFF_UPGRADED_ORN = 33332; /** - * Iban's staff (o) + * Iban's staff (b)(o) */ public static final int BROKEN_IBAN_STAFF_ORN = 33333; @@ -87270,12 +87270,12 @@ public final class ItemID public static final int SOULREAPER_AXE_ORN = 33335; /** - * Demonic slayer helmet + * Oathplate slayer helmet */ public static final int LEAGUE_6_SLAYER_HELM1 = 33338; /** - * Demonic slayer helmet + * Radiant slayer helmet */ public static final int LEAGUE_6_SLAYER_HELM2 = 33340; @@ -87320,7 +87320,7 @@ public final class ItemID public static final int LEAGUE_6_BRONZE_TROPHY = 33357; /** - * Impish ritual kit + * Impish ritual scroll */ public static final int LEAGUE_6_RITUAL_KIT = 33359; @@ -87479,6 +87479,62 @@ public final class ItemID */ public static final int MAGIC_ROCK_OF_VENGEANCE_1_USE = 33431; + /** + * Uncharged trident (o) + */ + public static final int TOTS_UNCHARGED_ORN = 33434; + public static final int POH_DUMMY_THRONE_8 = 33436; + + /** + * Sulphurous fertiliser + */ + public static final int BUCKET_SULPHUROUS_FERTILISER = 33437; + + /** + * Oathplate slayer helmet (i) + */ + public static final int LEAGUE_6_SLAYER_HELM1_I = 33439; + + /** + * Oathplate slayer helmet (i) + */ + public static final int SW_LEAGUE_6_SLAYER_HELM1_I = 33441; + + /** + * Oathplate slayer helmet (i) + */ + public static final int PVPA_LEAGUE_6_SLAYER_HELM1_I = 33443; + + /** + * Radiant slayer helmet (i) + */ + public static final int LEAGUE_6_SLAYER_HELM2_I = 33445; + + /** + * Radiant slayer helmet (i) + */ + public static final int SW_LEAGUE_6_SLAYER_HELM2_I = 33447; + + /** + * Radiant slayer helmet (i) + */ + public static final int PVPA_LEAGUE_6_SLAYER_HELM2_I = 33449; + + /** + * Demonic pacts relic hunter (t1) armour set + */ + public static final int SET_LEAGUE_6_RELICHUNTER_T1 = 33451; + + /** + * Demonic pacts relic hunter (t2) armour set + */ + public static final int SET_LEAGUE_6_RELICHUNTER_T2 = 33454; + + /** + * Demonic pacts relic hunter (t3) armour set + */ + public static final int SET_LEAGUE_6_RELICHUNTER_T3 = 33457; + public static final class Cert { public static final int TWPART1 = 7; @@ -92104,11 +92160,6 @@ public static final class Cert public static final int DEMONIC_AXE_ORNAMENT_KIT = 33306; public static final int DEMONIC_STAFF_ORNAMENT_KIT = 33309; public static final int DEMONIC_TRIDENT_ORNAMENT_KIT = 33312; - public static final int TOXIC_TOTS_UNCHARGED_ORN = 33317; - public static final int TOXIC_TOTS_I_UNCHARGED_ORN = 33321; - public static final int TOTS_ORN = 33324; - public static final int TOTS_I_UNCHARGED_ORN = 33329; - public static final int SOULREAPER_AXE_ORN = 33336; public static final int LEAGUE_6_DEMONIC_QUILL = 33343; public static final int LEAGUE_6_RITUAL_KIT = 33360; public static final int LEAGUE_6_THRONE = 33363; @@ -92117,6 +92168,9 @@ public static final class Cert public static final int DEADMAN_ALL_STAR_MISSION_TOTEM = 33425; public static final int MAGIC_ROCK_OF_VENGEANCE_2_USE = 33429; public static final int MAGIC_ROCK_OF_VENGEANCE_1_USE = 33432; + public static final int SET_LEAGUE_6_RELICHUNTER_T1 = 33452; + public static final int SET_LEAGUE_6_RELICHUNTER_T2 = 33455; + public static final int SET_LEAGUE_6_RELICHUNTER_T3 = 33458; } public static final class Placeholder @@ -101670,6 +101724,17 @@ public static final class Placeholder public static final int DEADMAN_ALL_STAR_MISSION_TOTEM = 33426; public static final int MAGIC_ROCK_OF_VENGEANCE_2_USE = 33430; public static final int MAGIC_ROCK_OF_VENGEANCE_1_USE = 33433; + public static final int TOTS_UNCHARGED_ORN = 33435; + public static final int BUCKET_SULPHUROUS_FERTILISER = 33438; + public static final int LEAGUE_6_SLAYER_HELM1_I = 33440; + public static final int SW_LEAGUE_6_SLAYER_HELM1_I = 33442; + public static final int PVPA_LEAGUE_6_SLAYER_HELM1_I = 33444; + public static final int LEAGUE_6_SLAYER_HELM2_I = 33446; + public static final int SW_LEAGUE_6_SLAYER_HELM2_I = 33448; + public static final int PVPA_LEAGUE_6_SLAYER_HELM2_I = 33450; + public static final int SET_LEAGUE_6_RELICHUNTER_T1 = 33453; + public static final int SET_LEAGUE_6_RELICHUNTER_T2 = 33456; + public static final int SET_LEAGUE_6_RELICHUNTER_T3 = 33459; } /* This file is automatically generated. Do not edit. */ } diff --git a/runelite-api/src/main/java/net/runelite/api/gameval/NpcID.java b/runelite-api/src/main/java/net/runelite/api/gameval/NpcID.java index d634a4aeb4b..5e7c3304550 100644 --- a/runelite-api/src/main/java/net/runelite/api/gameval/NpcID.java +++ b/runelite-api/src/main/java/net/runelite/api/gameval/NpcID.java @@ -1109,10 +1109,6 @@ public final class NpcID */ public static final int POH_SERVANT_MAITRE_D_MAN = 227; public static final int POH_SERVANT_MULTI_MAITRE_D_MAN = 228; - - /** - * Demon butler - */ public static final int POH_SERVANT_DEMON = 229; public static final int POH_SERVANT_MULTI_DEMON = 230; @@ -68297,5 +68293,24 @@ public final class NpcID * Yama */ public static final int DEADMAN_ALL_STARS_MISSION_YAMA = 15700; + public static final int THRALL_IMP_MAGIC_LESSER = 15701; + public static final int THRALL_IMP_MAGIC_SUPERIOR = 15702; + public static final int THRALL_IMP_MAGIC_GREATER = 15703; + public static final int THRALL_IMP_RANGED_LESSER = 15704; + public static final int THRALL_IMP_RANGED_SUPERIOR = 15705; + public static final int THRALL_IMP_RANGED_GREATER = 15706; + public static final int THRALL_IMP_MELEE_LESSER = 15707; + public static final int THRALL_IMP_MELEE_SUPERIOR = 15708; + public static final int THRALL_IMP_MELEE_GREATER = 15709; + + /** + * Demon butler + */ + public static final int POH_SERVANT_DEMON_BASE = 15710; + + /** + * Demon butler + */ + public static final int POH_SERVANT_DEMON_LEAGUES_6 = 15711; /* This file is automatically generated. Do not edit. */ } diff --git a/runelite-api/src/main/java/net/runelite/api/gameval/ObjectID.java b/runelite-api/src/main/java/net/runelite/api/gameval/ObjectID.java index 2c69dca512e..8ebba550d0d 100644 --- a/runelite-api/src/main/java/net/runelite/api/gameval/ObjectID.java +++ b/runelite-api/src/main/java/net/runelite/api/gameval/ObjectID.java @@ -46339,7 +46339,7 @@ public final class ObjectID extends ObjectID1 public static final int DUNGEONKIT_BARS02_EDGE01 = 14208; /** - * Herbs + * Guam leaf */ public static final int HERB_GUAM_LEAF_SEED = 14209; public static final int PEST_WALL_RANGE_FIXED = 14210; @@ -83239,27 +83239,27 @@ public final class ObjectID extends ObjectID1 public static final int FEUD_BIRD_CAGE_TABLE_SMALLER = 26824; /** - * Herbs + * Guam leaf */ public static final int HERB_GUAM_LEAF_1 = 26825; /** - * Herbs + * Guam leaf */ public static final int HERB_GUAM_LEAF_2 = 26826; /** - * Herbs + * Guam leaf */ public static final int HERB_GUAM_LEAF_3 = 26827; /** - * Herbs + * Guam leaf */ public static final int HERB_GUAM_LEAF_FULLYGROWN = 26828; /** - * Herbs + * Marrentill */ public static final int HERB_MARRENTILL_SEED = 26829; @@ -92130,6 +92130,266 @@ public final class ObjectID extends ObjectID1 */ public static final int FAIRYRING_HOMEHUB = 29560; + /** + * Oak outfit stand + */ + public static final int POH_LEAGUEHALL_OUTFITSTAND_OAK_LEAGUE_6_T1 = 29561; + + /** + * Oak outfit stand + */ + public static final int POH_LEAGUEHALL_OUTFITSTAND_OAK_LEAGUE_6_T2 = 29562; + + /** + * Oak outfit stand + */ + public static final int POH_LEAGUEHALL_OUTFITSTAND_OAK_LEAGUE_6_T3 = 29563; + + /** + * Mahogany outfit stand + */ + public static final int POH_LEAGUEHALL_OUTFITSTAND_MAHOGANY_LEAGUE_6_T1 = 29564; + + /** + * Mahogany outfit stand + */ + public static final int POH_LEAGUEHALL_OUTFITSTAND_MAHOGANY_LEAGUE_6_T2 = 29565; + + /** + * Mahogany outfit stand + */ + public static final int POH_LEAGUEHALL_OUTFITSTAND_MAHOGANY_LEAGUE_6_T3 = 29566; + + /** + * Throne + */ + public static final int POH_INVISIBLE_THRONE_8 = 29567; + + /** + * Diseased guam leaf + */ + public static final int HERB_GUAM_LEAF_DISEASED_1 = 29568; + + /** + * Diseased guam leaf + */ + public static final int HERB_GUAM_LEAF_DISEASED_2 = 29569; + + /** + * Diseased guam leaf + */ + public static final int HERB_GUAM_LEAF_DISEASED_3 = 29570; + + /** + * Diseased marrentill + */ + public static final int HERB_MARENTILL_DISEASED_1 = 29571; + + /** + * Diseased marrentill + */ + public static final int HERB_MARENTILL_DISEASED_2 = 29572; + + /** + * Diseased marrentill + */ + public static final int HERB_MARENTILL_DISEASED_3 = 29573; + + /** + * Diseased tarromin + */ + public static final int HERB_TARROMIN_DISEASED_1 = 29574; + + /** + * Diseased tarromin + */ + public static final int HERB_TARROMIN_DISEASED_2 = 29575; + + /** + * Diseased tarromin + */ + public static final int HERB_TARROMIN_DISEASED_3 = 29576; + + /** + * Diseased harralander + */ + public static final int HERB_HARRALANDER_DISEASED_1 = 29577; + + /** + * Diseased harralander + */ + public static final int HERB_HARRALANDER_DISEASED_2 = 29578; + + /** + * Diseased harralander + */ + public static final int HERB_HARRALANDER_DISEASED_3 = 29579; + + /** + * Diseased ranarr weed + */ + public static final int HERB_RANARR_WEED_DISEASED_1 = 29580; + + /** + * Diseased ranarr weed + */ + public static final int HERB_RANARR_WEED_DISEASED_2 = 29581; + + /** + * Diseased ranarr weed + */ + public static final int HERB_RANARR_WEED_DISEASED_3 = 29582; + + /** + * Diseased irit leaf + */ + public static final int HERB_IRIT_LEAF_DISEASED_1 = 29583; + + /** + * Diseased irit leaf + */ + public static final int HERB_IRIT_LEAF_DISEASED_2 = 29584; + + /** + * Diseased irit leaf + */ + public static final int HERB_IRIT_LEAF_DISEASED_3 = 29585; + + /** + * Diseased avantoe + */ + public static final int HERB_AVANTOE_DISEASED_1 = 29586; + + /** + * Diseased avantoe + */ + public static final int HERB_AVANTOE_DISEASED_2 = 29587; + + /** + * Diseased avantoe + */ + public static final int HERB_AVANTOE_DISEASED_3 = 29588; + + /** + * Diseased toadflax + */ + public static final int HERB_TOADFLAX_DISEASED_1 = 29589; + + /** + * Diseased toadflax + */ + public static final int HERB_TOADFLAX_DISEASED_2 = 29590; + + /** + * Diseased toadflax + */ + public static final int HERB_TOADFLAX_DISEASED_3 = 29591; + + /** + * Diseased kwuarm + */ + public static final int HERB_KWUARM_DISEASED_1 = 29592; + + /** + * Diseased kwuarm + */ + public static final int HERB_KWUARM_DISEASED_2 = 29593; + + /** + * Diseased kwuarm + */ + public static final int HERB_KWUARM_DISEASED_3 = 29594; + + /** + * Diseased cadantine + */ + public static final int HERB_CADANTINE_DISEASED_1 = 29595; + + /** + * Diseased cadantine + */ + public static final int HERB_CADANTINE_DISEASED_2 = 29596; + + /** + * Diseased cadantine + */ + public static final int HERB_CADANTINE_DISEASED_3 = 29597; + + /** + * Diseased lantadyme + */ + public static final int HERB_LANTADYME_DISEASED_1 = 29598; + + /** + * Diseased lantadyme + */ + public static final int HERB_LANTADYME_DISEASED_2 = 29599; + + /** + * Diseased lantadyme + */ + public static final int HERB_LANTADYME_DISEASED_3 = 29600; + + /** + * Diseased dwarf weed + */ + public static final int HERB_DWARF_WEED_DISEASED_1 = 29601; + + /** + * Diseased dwarf weed + */ + public static final int HERB_DWARF_WEED_DISEASED_2 = 29602; + + /** + * Diseased dwarf weed + */ + public static final int HERB_DWARF_WEED_DISEASED_3 = 29603; + + /** + * Diseased torstol + */ + public static final int HERB_TORSTOL_DISEASED_1 = 29604; + + /** + * Diseased torstol + */ + public static final int HERB_TORSTOL_DISEASED_2 = 29605; + + /** + * Diseased torstol + */ + public static final int HERB_TORSTOL_DISEASED_3 = 29606; + + /** + * Diseased snapdragon + */ + public static final int HERB_SNAPDRAGON_DISEASED_1 = 29607; + + /** + * Diseased snapdragon + */ + public static final int HERB_SNAPDRAGON_DISEASED_2 = 29608; + + /** + * Diseased snapdragon + */ + public static final int HERB_SNAPDRAGON_DISEASED_3 = 29609; + + /** + * Diseased huasca + */ + public static final int HERB_HUASCA_DISEASED_1 = 29610; + + /** + * Diseased huasca + */ + public static final int HERB_HUASCA_DISEASED_2 = 29611; + + /** + * Diseased huasca + */ + public static final int HERB_HUASCA_DISEASED_3 = 29612; + /** * Mounted Max Cape */ diff --git a/runelite-api/src/main/java/net/runelite/api/gameval/ObjectID1.java b/runelite-api/src/main/java/net/runelite/api/gameval/ObjectID1.java index 4efad80ec03..7fe60a75a7f 100644 --- a/runelite-api/src/main/java/net/runelite/api/gameval/ObjectID1.java +++ b/runelite-api/src/main/java/net/runelite/api/gameval/ObjectID1.java @@ -19922,322 +19922,322 @@ class ObjectID1 public static final int GH_ROCKSLIDE_06 = 39747; /** - * Herbs + * Marrentill */ public static final int HERB_MARRENTILL_1 = 39748; /** - * Herbs + * Marrentill */ public static final int HERB_MARRENTILL_2 = 39749; /** - * Herbs + * Marrentill */ public static final int HERB_MARRENTILL_3 = 39750; /** - * Herbs + * Marrentill */ public static final int HERB_MARRENTILL_FULLYGROWN = 39751; /** - * Herbs + * Tarromin */ public static final int HERB_TARROMIN_SEED = 39752; /** - * Herbs + * Tarromin */ public static final int HERB_TARROMIN_1 = 39753; /** - * Herbs + * Tarromin */ public static final int HERB_TARROMIN_2 = 39754; /** - * Herbs + * Tarromin */ public static final int HERB_TARROMIN_3 = 39755; /** - * Herbs + * Tarromin */ public static final int HERB_TARROMIN_FULLYGROWN = 39756; /** - * Herbs + * Harralander */ public static final int HERB_HARRALANDER_SEED = 39757; /** - * Herbs + * Harralander */ public static final int HERB_HARRALANDER_1 = 39758; /** - * Herbs + * Harralander */ public static final int HERB_HARRALANDER_2 = 39759; /** - * Herbs + * Harralander */ public static final int HERB_HARRALANDER_3 = 39760; /** - * Herbs + * Harralander */ public static final int HERB_HARRALANDER_FULLYGROWN = 39761; /** - * Herbs + * Ranarr weed */ public static final int HERB_RANARR_WEED_SEED = 39762; /** - * Herbs + * Ranarr weed */ public static final int HERB_RANARR_WEED_1 = 39763; /** - * Herbs + * Ranarr weed */ public static final int HERB_RANARR_WEED_2 = 39764; /** - * Herbs + * Ranarr weed */ public static final int HERB_RANARR_WEED_3 = 39765; /** - * Herbs + * Ranarr weed */ public static final int HERB_RANARR_WEED_FULLYGROWN = 39766; /** - * Herbs + * Irit leaf */ public static final int HERB_IRIT_LEAF_SEED = 39767; /** - * Herbs + * Irit leaf */ public static final int HERB_IRIT_LEAF_1 = 39768; /** - * Herbs + * Irit leaf */ public static final int HERB_IRIT_LEAF_2 = 39769; /** - * Herbs + * Irit leaf */ public static final int HERB_IRIT_LEAF_3 = 39770; /** - * Herbs + * Irit leaf */ public static final int HERB_IRIT_LEAF_FULLYGROWN = 39771; /** - * Herbs + * Avantoe */ public static final int HERB_AVANTOE_SEED = 39772; /** - * Herbs + * Avantoe */ public static final int HERB_AVANTOE_1 = 39773; /** - * Herbs + * Avantoe */ public static final int HERB_AVANTOE_2 = 39774; /** - * Herbs + * Avantoe */ public static final int HERB_AVANTOE_3 = 39775; /** - * Herbs + * Avantoe */ public static final int HERB_AVANTOE_FULLYGROWN = 39776; /** - * Herbs + * Toadflax */ public static final int HERB_TOADFLAX_SEED = 39777; /** - * Herbs + * Toadflax */ public static final int HERB_TOADFLAX_1 = 39778; /** - * Herbs + * Toadflax */ public static final int HERB_TOADFLAX_2 = 39779; /** - * Herbs + * Toadflax */ public static final int HERB_TOADFLAX_3 = 39780; /** - * Herbs + * Toadflax */ public static final int HERB_TOADFLAX_FULLYGROWN = 39781; /** - * Herbs + * Kwuarm */ public static final int HERB_KWUARM_SEED = 39782; /** - * Herbs + * Kwuarm */ public static final int HERB_KWUARM_1 = 39783; /** - * Herbs + * Kwuarm */ public static final int HERB_KWUARM_2 = 39784; /** - * Herbs + * Kwuarm */ public static final int HERB_KWUARM_3 = 39785; /** - * Herbs + * Kwuarm */ public static final int HERB_KWUARM_FULLYGROWN = 39786; /** - * Herbs + * Cadantine */ public static final int HERB_CADANTINE_SEED = 39787; /** - * Herbs + * Cadantine */ public static final int HERB_CADANTINE_1 = 39788; /** - * Herbs + * Cadantine */ public static final int HERB_CADANTINE_2 = 39789; /** - * Herbs + * Cadantine */ public static final int HERB_CADANTINE_3 = 39790; /** - * Herbs + * Cadantine */ public static final int HERB_CADANTINE_FULLYGROWN = 39791; /** - * Herbs + * Lantadyme */ public static final int HERB_LANTADYME_SEED = 39792; /** - * Herbs + * Lantadyme */ public static final int HERB_LANTADYME_1 = 39793; /** - * Herbs + * Lantadyme */ public static final int HERB_LANTADYME_2 = 39794; /** - * Herbs + * Lantadyme */ public static final int HERB_LANTADYME_3 = 39795; /** - * Herbs + * Lantadyme */ public static final int HERB_LANTADYME_FULLYGROWN = 39796; /** - * Herbs + * Dwarf weed */ public static final int HERB_DWARF_WEED_SEED = 39797; /** - * Herbs + * Dwarf weed */ public static final int HERB_DWARF_WEED_1 = 39798; /** - * Herbs + * Dwarf weed */ public static final int HERB_DWARF_WEED_2 = 39799; /** - * Herbs + * Dwarf weed */ public static final int HERB_DWARF_WEED_3 = 39800; /** - * Herbs + * Dwarf weed */ public static final int HERB_DWARF_WEED_FULLYGROWN = 39801; /** - * Herbs + * Torstol */ public static final int HERB_TORSTOL_SEED = 39802; /** - * Herbs + * Torstol */ public static final int HERB_TORSTOL_1 = 39803; /** - * Herbs + * Torstol */ public static final int HERB_TORSTOL_2 = 39804; /** - * Herbs + * Torstol */ public static final int HERB_TORSTOL_3 = 39805; /** - * Herbs + * Torstol */ public static final int HERB_TORSTOL_FULLYGROWN = 39806; /** - * Herbs + * Snapdragon */ public static final int HERB_SNAPDRAGON_SEED = 39807; /** - * Herbs + * Snapdragon */ public static final int HERB_SNAPDRAGON_1 = 39808; /** - * Herbs + * Snapdragon */ public static final int HERB_SNAPDRAGON_2 = 39809; /** - * Herbs + * Snapdragon */ public static final int HERB_SNAPDRAGON_3 = 39810; /** - * Herbs + * Snapdragon */ public static final int HERB_SNAPDRAGON_FULLYGROWN = 39811; @@ -60917,27 +60917,27 @@ class ObjectID1 public static final int MYARM_REALPATCH_HERB5_HUASCA_ACTIVE = 55346; /** - * Herbs + * Huasca */ public static final int HERB_HUASCA_SEED = 55347; /** - * Herbs + * Huasca */ public static final int HERB_HUASCA_1 = 55348; /** - * Herbs + * Huasca */ public static final int HERB_HUASCA_2 = 55349; /** - * Herbs + * Huasca */ public static final int HERB_HUASCA_3 = 55350; /** - * Herbs + * Huasca */ public static final int HERB_HUASCA_FULLYGROWN = 55351; diff --git a/runelite-api/src/main/java/net/runelite/api/gameval/SpotanimID.java b/runelite-api/src/main/java/net/runelite/api/gameval/SpotanimID.java index eda5325a1e4..c520fca3cfa 100644 --- a/runelite-api/src/main/java/net/runelite/api/gameval/SpotanimID.java +++ b/runelite-api/src/main/java/net/runelite/api/gameval/SpotanimID.java @@ -3786,5 +3786,15 @@ public final class SpotanimID public static final int VFX_SAILING_CANNON01_SMALL01_CHAINSHOT01_ADAMANT_PROJ01 = 3779; public static final int VFX_SAILING_CANNON01_SMALL01_CHAINSHOT01_RUNE_PROJ01 = 3780; public static final int VFX_SAILING_CANNON01_SMALL01_CHAINSHOT01_DRAGON_PROJ01 = 3781; + public static final int LEAGUE_6_AREA_UNLOCK_EMOTE_SPOTANIM_ASGARNIA = 3782; + public static final int LEAGUE_6_AREA_UNLOCK_EMOTE_SPOTANIM_FREMENNIK = 3783; + public static final int LEAGUE_6_AREA_UNLOCK_EMOTE_SPOTANIM_KANDARIN = 3784; + public static final int LEAGUE_6_AREA_UNLOCK_EMOTE_SPOTANIM_KARAMJA = 3785; + public static final int LEAGUE_6_AREA_UNLOCK_EMOTE_SPOTANIM_KHARIDIAN_DESERT = 3786; + public static final int LEAGUE_6_AREA_UNLOCK_EMOTE_SPOTANIM_MORYTANIA = 3787; + public static final int LEAGUE_6_AREA_UNLOCK_EMOTE_SPOTANIM_TIRANNWN = 3788; + public static final int LEAGUE_6_AREA_UNLOCK_EMOTE_SPOTANIM_WILDERNESS = 3789; + public static final int LEAGUE_6_AREA_UNLOCK_EMOTE_SPOTANIM_KOUREND = 3790; + public static final int LEAGUE_6_AREA_UNLOCK_EMOTE_SPOTANIM_VARLAMORE = 3791; /* This file is automatically generated. Do not edit. */ } diff --git a/runelite-api/src/main/java/net/runelite/api/gameval/SpriteID.java b/runelite-api/src/main/java/net/runelite/api/gameval/SpriteID.java index a17e363306c..8467611637d 100644 --- a/runelite-api/src/main/java/net/runelite/api/gameval/SpriteID.java +++ b/runelite-api/src/main/java/net/runelite/api/gameval/SpriteID.java @@ -3946,6 +3946,12 @@ public static final class WorldswitcherFlags public static final int _9 = 4936; public static final int _10 = 4937; public static final int _11 = 4938; + public static final int _12 = 8351; + public static final int _13 = 8352; + public static final int _14 = 8353; + public static final int _15 = 8354; + public static final int _16 = 8355; + public static final int _17 = 8356; public static final int NONE = _0; public static final int USA = _1; diff --git a/runelite-api/src/main/java/net/runelite/api/gameval/VarPlayerID.java b/runelite-api/src/main/java/net/runelite/api/gameval/VarPlayerID.java index 97fd0474995..25b8ef58025 100644 --- a/runelite-api/src/main/java/net/runelite/api/gameval/VarPlayerID.java +++ b/runelite-api/src/main/java/net/runelite/api/gameval/VarPlayerID.java @@ -903,6 +903,7 @@ public final class VarPlayerID public static final int KEBOS_BLESSING_TRACKER = 2088; public static final int MOLCH_BARRIERS = 2090; public static final int KEBOS_FARMGUILD = 2091; + public static final int FARMGUILD_CONTRACT = 2094; public static final int SLAYER_AREA = 2096; public static final int CLUEQUEST_MAIN = 2111; public static final int SEED_VAULT_TEMP = 2194; @@ -2862,5 +2863,11 @@ public final class VarPlayerID public static final int DEADMAN_ALLSTAR_DATA = 5575; public static final int DEADMAN_ALLSTAR_DATA_2 = 5576; public static final int DEADMAN_ALLSTAR_DATA_3 = 5577; + public static final int RESURRECTION_COSMETIC_TOGGLES = 5581; + public static final int LEAGUE_6_REWARDS = 5582; + public static final int AUTO_BANK_LOOT = 5583; + public static final int POH_BUTLER_COSMETICS = 5584; + public static final int PORT_TASKS_BOUNTY_ITEM_COUNTER = 5586; + public static final int SLAYER_REWARDS_UNLOCKS2 = 5587; /* This file is automatically generated. Do not edit. */ } diff --git a/runelite-api/src/main/java/net/runelite/api/gameval/VarbitID.java b/runelite-api/src/main/java/net/runelite/api/gameval/VarbitID.java index 5a837c364cc..d1ae7bbf362 100644 --- a/runelite-api/src/main/java/net/runelite/api/gameval/VarbitID.java +++ b/runelite-api/src/main/java/net/runelite/api/gameval/VarbitID.java @@ -4827,6 +4827,10 @@ public final class VarbitID public static final int FGUILD_BOOK_HERBS = 7956; public static final int FGUILD_BOOK_TREES = 7957; public static final int FGUILD_BOOK_FRUIT = 7958; + public static final int FARMGUILD_CONTRACT_COMPLETE = 7961; + public static final int FARMGUILD_CONTRACT_LEVEL = 7962; + public static final int FARMGUILD_CONTRACT_TYPE = 7963; + public static final int FARMGUILD_CONTRACT_COUNT = 7964; public static final int PLAYER_HAS_HIT_200M_ALL = 7969; public static final int HH_CONSTRUCTED_MEDIUM_EXP10 = 7970; public static final int WINT_TRANSMIT_RESPAWNDELAY = 7980; @@ -8456,6 +8460,14 @@ public final class VarbitID public static final int SPECTATOR_STAT_TRACK_TARGET = 13326; public static final int SPECTATOR_TOTAL_PLAYER = 13327; public static final int SPECTATOR_PLAYERLIST_IS_SEARCHING = 13328; + public static final int RESURRECTION_COSMETIC_TOGGLES_ZOMBIE = 13329; + public static final int RESURRECTION_COSMETIC_TOGGLES_SKELETON = 13330; + public static final int RESURRECTION_COSMETIC_TOGGLES_GHOST = 13331; + public static final int LEAGUE_6_HOME_TELEPORT_UNLOCKED = 13332; + public static final int LEAGUE_6_THRALL_OVERRIDE_UNLOCKED = 13333; + public static final int LEAGUE_6_RED_SKIN_UNLOCKED = 13334; + public static final int LEAGUE_6_BUTLER_UNLOCKED = 13335; + public static final int POH_DEMON_BUTLER_COSMETICS = 13336; public static final int LEAGUE_TASK_FILTER_SKILL = 13340; public static final int LEAGUE_REWARD_POINTS_BUFFER = 13341; public static final int LEAGUE_UNLOCK_POINTS_BUFFER = 13342; @@ -9537,6 +9549,8 @@ public final class VarbitID public static final int CA_TASK_TOA_KILLCOUNT_1 = 14659; public static final int CA_TASK_TOA_KILLCOUNT_2 = 14660; public static final int CA_TASK_TOA_KILLCOUNT_3 = 14661; + public static final int BOUNTY_TASK_ITEM_COUNTER_1 = 14662; + public static final int BOUNTY_TASK_ITEM_COUNTER_2 = 14663; public static final int CA_TASK_TOA_KILLCOUNT_6 = 14664; public static final int CA_TASK_TOA_KILLCOUNT_7 = 14665; public static final int CA_TASK_TOA_KILLCOUNT_8 = 14666; @@ -9668,6 +9682,7 @@ public final class VarbitID public static final int SETTINGS_TEXTFIELD_CSV_MODAL_OPENED = 14816; public static final int OPTION_MASTER_VOLUME_SAVED = 14817; public static final int AB_GROUP_RESET6 = 14818; + public static final int BOUNTY_TASK_ITEM_COUNTER_3 = 14819; public static final int SLAYER_LONGER_REVENANTS = 14822; public static final int SLAYER_TOGGLEOFF_LONGERREVENANTS = 14823; public static final int SLAYER_TOGGLEOFF_FOSSILWYVERNBLOCK = 14824; @@ -9993,11 +10008,13 @@ public final class VarbitID public static final int STAR_LAST_MINED_LOCATION = 15352; public static final int STAR_LAST_MINED_SIZE = 15353; public static final int STAR_TIME_SINCE_LANDED = 15354; + public static final int BOUNTY_TASK_ITEM_COUNTER_4 = 15370; public static final int POH_DECOR_LEAGUES_4_POOL = 15372; public static final int RUNE_POUCH_TYPE_5 = 15373; public static final int RUNE_POUCH_TYPE_6 = 15374; public static final int RUNE_POUCH_QUANTITY_5 = 15375; public static final int RUNE_POUCH_QUANTITY_6 = 15376; + public static final int BOUNTY_TASK_ITEM_COUNTER_5 = 15397; public static final int XMAS24_INVITE_HAIRDRESSER = 15522; public static final int XMAS24_INVITE_SARAH = 15524; public static final int LEAGUE_HASNT_USED_BOOTS_FOR_MOUNT_QUID = 15647; From f6390cf08c3782cb7805240f57b25069f4f4d04b Mon Sep 17 00:00:00 2001 From: RuneLite updater Date: Wed, 3 Jun 2026 10:08:06 +0000 Subject: [PATCH 49/51] Release 1.12.28 --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 0d3120495f8..fe8a2aa6e51 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,6 +28,6 @@ org.gradle.parallel=true org.gradle.caching=false project.build.group=net.runelite -project.build.version=1.12.28-SNAPSHOT +project.build.version=1.12.28 glslang.path= From c411ab80a116e1bc2a10d82579de9b574f67e56a Mon Sep 17 00:00:00 2001 From: RuneLite updater Date: Wed, 3 Jun 2026 10:08:07 +0000 Subject: [PATCH 50/51] Bump for 1.12.29-SNAPSHOT [ci skip] --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index fe8a2aa6e51..78918ff53ec 100644 --- a/gradle.properties +++ b/gradle.properties @@ -28,6 +28,6 @@ org.gradle.parallel=true org.gradle.caching=false project.build.group=net.runelite -project.build.version=1.12.28 +project.build.version=1.12.29-SNAPSHOT glslang.path= From d9023f9361f2b488ea6c52b14be60e5f78d836b2 Mon Sep 17 00:00:00 2001 From: Adam Date: Thu, 4 Jun 2026 18:26:59 -0400 Subject: [PATCH 51/51] worldhopper: fix region filter for new regions --- .../plugins/worldhopper/RegionFilterMode.java | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/RegionFilterMode.java b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/RegionFilterMode.java index 481b959e3d3..d0011405be5 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/RegionFilterMode.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/worldhopper/RegionFilterMode.java @@ -60,20 +60,13 @@ public String toString() static RegionFilterMode of(WorldRegion region) { - switch (region) + for (var rfm : values()) { - case UNITED_STATES_OF_AMERICA: - return UNITED_STATES; - case UNITED_KINGDOM: - return UNITED_KINGDOM; - case AUSTRALIA: - return AUSTRALIA; - case GERMANY: - return GERMANY; - case BRAZIL: - return BRAZIL; - default: - throw new IllegalStateException(); + if (rfm.region == region) + { + return rfm; + } } + throw new IllegalArgumentException(); } }