From d67b5afb5bab841b34ab76c9bf0ddfeb9054653a Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Thu, 16 Apr 2026 11:01:05 -0400 Subject: [PATCH 1/2] feat(webwalker): add configurable per-card hotkeys Adds 10 user-configurable hotkeys to the WebWalker side panel, each rendered inline on its category card via MicrobotHotkeyButton: 8 toggles for the selected target (custom location, bank, deposit box, slayer master, quest, clue, farming, hunter) and 2 toggles for "nearest bank" / "nearest deposit box". Keybinds are stored as hidden @ConfigItem entries so they persist without cluttering the settings UI. The plugin wires each one through HotkeyListener; toggleCategory routes start/stop through the existing panel helpers so logs and walker state match the Start/Stop buttons. MicrobotHotkeyButton promoted from package-private to public so the WebWalker panel can embed it. Also adds a plain ':client:run' Gradle task alongside the existing 'runDebug' (no JDWP suspend). Hardcoded CTRL+X stop hotkey is intentionally preserved. --- runelite-client/build.gradle.kts | 12 ++ .../shortestpath/ShortestPathConfig.java | 106 ++++++++++ .../shortestpath/ShortestPathPanel.java | 197 ++++++++++++++---- .../shortestpath/ShortestPathPlugin.java | 124 ++++++++++- .../microbot/ui/MicrobotHotkeyButton.java | 2 +- 5 files changed, 400 insertions(+), 41 deletions(-) diff --git a/runelite-client/build.gradle.kts b/runelite-client/build.gradle.kts index 7eadde61ad5..5e89b1f4ac3 100644 --- a/runelite-client/build.gradle.kts +++ b/runelite-client/build.gradle.kts @@ -52,6 +52,18 @@ plugins { } +tasks.register("run") { + group = "application" + description = "Run RuneLite client" + + classpath = sourceSets.main.get().runtimeClasspath + mainClass.set("net.runelite.client.RuneLite") + + jvmArgs( + "-Dfile.encoding=UTF-8" + ) +} + tasks.register("runDebug") { group = "application" description = "Run RuneLite client with JDWP debug" diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathConfig.java index a9e861240e5..1422b51ff44 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathConfig.java @@ -7,6 +7,112 @@ @ConfigGroup(ShortestPathPlugin.CONFIG_GROUP) @ConfigInformation("Press 'CTRL + X' to stop the webwalker automatically.") public interface ShortestPathConfig extends Config { + /* ------------------------------------------------------------------ + * Hotkeys — stored as config values but bound/displayed inline on + * each side-panel category card (see ShortestPathPanel). Marked + * hidden so they don't clutter the settings UI. + * ------------------------------------------------------------------ */ + + @ConfigItem( + keyName = "customLocationToggleHotkey", + name = "", + description = "", + hidden = true + ) + default Keybind customLocationToggleHotkey() { + return Keybind.NOT_SET; + } + + @ConfigItem( + keyName = "bankToggleHotkey", + name = "", + description = "", + hidden = true + ) + default Keybind bankToggleHotkey() { + return Keybind.NOT_SET; + } + + @ConfigItem( + keyName = "nearestBankHotkey", + name = "", + description = "", + hidden = true + ) + default Keybind nearestBankHotkey() { + return Keybind.NOT_SET; + } + + @ConfigItem( + keyName = "depositBoxToggleHotkey", + name = "", + description = "", + hidden = true + ) + default Keybind depositBoxToggleHotkey() { + return Keybind.NOT_SET; + } + + @ConfigItem( + keyName = "nearestDepositBoxHotkey", + name = "", + description = "", + hidden = true + ) + default Keybind nearestDepositBoxHotkey() { + return Keybind.NOT_SET; + } + + @ConfigItem( + keyName = "slayerMasterToggleHotkey", + name = "", + description = "", + hidden = true + ) + default Keybind slayerMasterToggleHotkey() { + return Keybind.NOT_SET; + } + + @ConfigItem( + keyName = "questToggleHotkey", + name = "", + description = "", + hidden = true + ) + default Keybind questToggleHotkey() { + return Keybind.NOT_SET; + } + + @ConfigItem( + keyName = "clueToggleHotkey", + name = "", + description = "", + hidden = true + ) + default Keybind clueToggleHotkey() { + return Keybind.NOT_SET; + } + + @ConfigItem( + keyName = "farmingToggleHotkey", + name = "", + description = "", + hidden = true + ) + default Keybind farmingToggleHotkey() { + return Keybind.NOT_SET; + } + + @ConfigItem( + keyName = "hunterToggleHotkey", + name = "", + description = "", + hidden = true + ) + default Keybind hunterToggleHotkey() { + return Keybind.NOT_SET; + } + @ConfigSection( name = "Settings", description = "Options for the pathfinding", diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPanel.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPanel.java index 0680c07374c..ec15191077e 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPanel.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPanel.java @@ -22,9 +22,14 @@ import javax.swing.SwingConstants; import javax.swing.border.Border; import javax.swing.border.TitledBorder; +import java.awt.event.FocusAdapter; +import java.awt.event.FocusEvent; import net.runelite.api.coords.WorldPoint; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.config.Keybind; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.shortestpath.components.ComboBoxListRenderer; +import net.runelite.client.plugins.microbot.ui.MicrobotHotkeyButton; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.depositbox.DepositBoxLocation; @@ -62,6 +67,8 @@ public class ShortestPathPanel extends PluginPanel { private final ShortestPathPlugin plugin; + private final ShortestPathConfig config; + private final ConfigManager configManager; private JTextField xField, yField, zField; private JComboBox bankComboBox; @@ -86,10 +93,12 @@ public class ShortestPathPanel extends PluginPanel private javax.swing.Timer clueInfoTimer; @Inject - private ShortestPathPanel(ShortestPathPlugin plugin) + private ShortestPathPanel(ShortestPathPlugin plugin, ShortestPathConfig config, ConfigManager configManager) { super(); this.plugin = plugin; + this.config = config; + this.configManager = configManager; setLayout(new BoxLayout(this, BoxLayout.Y_AXIS)); setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10)); @@ -206,6 +215,9 @@ private JPanel createCustomLocationPanel() panel.add(coordinatesPanel); panel.add(Box.createRigidArea(new Dimension(0, 5))); panel.add(buttonPanel); + panel.add(Box.createRigidArea(new Dimension(0, 5))); + panel.add(createHotkeyRow("customLocationToggleHotkey", config.customLocationToggleHotkey(), + "Toggle hotkey: start walking to the X/Y/Z coordinates entered above; press again to stop.")); return panel; } @@ -240,21 +252,14 @@ private JPanel createBankPanel() startWalking(ge); }); - useNearestBankButton.addActionListener(e -> { - CompletableFuture.supplyAsync(Rs2Bank::getNearestBank) - .thenAccept(nearestBank -> { - if (nearestBank != null) - { - startWalking(nearestBank.getWorldPoint()); - } - }) - .exceptionally(ex -> { - Microbot.log("Error while finding the nearest bank: " + ex.getMessage()); - return null; - }); - }); + useNearestBankButton.addActionListener(e -> startWalkingNearestBank()); - nearestBankPanel.add(useNearestBankButton); + // First grid row: [Go To Nearest Bank] [hotkey] + JPanel nearestBankRow = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 0)); + nearestBankRow.add(useNearestBankButton); + nearestBankRow.add(createHotkeyButton("nearestBankHotkey", config.nearestBankHotkey(), + "Hotkey: walk to the nearest bank from your current location.")); + nearestBankPanel.add(nearestBankRow); nearestBankPanel.add(goToGrandExchangeButton); // Go to GE button buttonPanel.add(startButton); @@ -263,7 +268,10 @@ private JPanel createBankPanel() panel.add(bankComboBox); panel.add(Box.createRigidArea(new Dimension(0, 5))); panel.add(buttonPanel); - panel.add(Box.createRigidArea(new Dimension(0, 2))); + panel.add(Box.createRigidArea(new Dimension(0, 5))); + panel.add(createHotkeyRow("bankToggleHotkey", config.bankToggleHotkey(), + "Toggle hotkey: start walking to the bank selected above; press again to stop.")); + panel.add(Box.createRigidArea(new Dimension(0, 5))); panel.add(nearestBankPanel); return panel; @@ -288,24 +296,14 @@ private JPanel createDepositBoxPanel() startButton.addActionListener(e -> startWalking(getSelectedDepositBox().getWorldPoint())); stopButton.addActionListener(e -> stopWalking()); - JPanel nearestDepositBoxPanel = new JPanel(new FlowLayout(FlowLayout.CENTER)); + JPanel nearestDepositBoxPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 5, 0)); JButton useNearestDepositBoxButton = new JButton("Go To Nearest Deposit Box"); - useNearestDepositBoxButton.addActionListener(e -> { - CompletableFuture.supplyAsync(Rs2DepositBox::getNearestDepositBox) - .thenAccept(nearestDepositBox -> { - if (nearestDepositBox != null) - { - startWalking(nearestDepositBox.getWorldPoint()); - } - }) - .exceptionally(ex -> { - Microbot.log("Error while finding the nearest deposit box: " + ex.getMessage()); - return null; - }); - }); + useNearestDepositBoxButton.addActionListener(e -> startWalkingNearestDepositBox()); nearestDepositBoxPanel.add(useNearestDepositBoxButton); + nearestDepositBoxPanel.add(createHotkeyButton("nearestDepositBoxHotkey", config.nearestDepositBoxHotkey(), + "Hotkey: walk to the nearest deposit box from your current location.")); buttonPanel.add(startButton); buttonPanel.add(stopButton); @@ -313,7 +311,10 @@ private JPanel createDepositBoxPanel() panel.add(depositBoxComboBox); panel.add(Box.createRigidArea(new Dimension(0, 5))); panel.add(buttonPanel); - panel.add(Box.createRigidArea(new Dimension(0, 2))); + panel.add(Box.createRigidArea(new Dimension(0, 5))); + panel.add(createHotkeyRow("depositBoxToggleHotkey", config.depositBoxToggleHotkey(), + "Toggle hotkey: start walking to the deposit box selected above; press again to stop.")); + panel.add(Box.createRigidArea(new Dimension(0, 5))); panel.add(nearestDepositBoxPanel); return panel; @@ -351,7 +352,10 @@ private JPanel createSlayerMasterPanel() panel.add(slayerMasterComboBox); panel.add(Box.createRigidArea(new Dimension(0, 5))); panel.add(buttonPanel); - panel.add(Box.createRigidArea(new Dimension(0, 2))); + panel.add(Box.createRigidArea(new Dimension(0, 5))); + panel.add(createHotkeyRow("slayerMasterToggleHotkey", config.slayerMasterToggleHotkey(), + "Toggle hotkey: start walking to the slayer master selected above; press again to stop.")); + panel.add(Box.createRigidArea(new Dimension(0, 5))); panel.add(turaelSkipPanel); return panel; @@ -419,7 +423,10 @@ else if (qhp.getSelectedQuest() == null) panel.add(questInfoLabel); panel.add(Box.createRigidArea(new Dimension(0, 10))); panel.add(buttonPanel); - panel.add(Box.createRigidArea(new Dimension(0, 2))); + panel.add(Box.createRigidArea(new Dimension(0, 5))); + panel.add(createHotkeyRow("questToggleHotkey", config.questToggleHotkey(), + "Toggle hotkey: start walking to the active QuestHelper step; press again to stop.")); + panel.add(Box.createRigidArea(new Dimension(0, 5))); panel.add(helpPanel); return panel; @@ -484,6 +491,9 @@ private JPanel createFarmingPanel() } panel.add(Box.createRigidArea(new Dimension(0, 5))); panel.add(buttonPanel); + panel.add(Box.createRigidArea(new Dimension(0, 5))); + panel.add(createHotkeyRow("farmingToggleHotkey", config.farmingToggleHotkey(), + "Toggle hotkey: start walking to the farming location selected above; press again to stop.")); return panel; } @@ -552,7 +562,10 @@ private JPanel createHunterCreaturePanel() } panel.add(Box.createRigidArea(new Dimension(0, 5))); panel.add(buttonPanel); - panel.add(Box.createRigidArea(new Dimension(0, 2))); + panel.add(Box.createRigidArea(new Dimension(0, 5))); + panel.add(createHotkeyRow("hunterToggleHotkey", config.hunterToggleHotkey(), + "Toggle hotkey: start walking to the hunting area selected above; press again to stop.")); + panel.add(Box.createRigidArea(new Dimension(0, 5))); panel.add(hunterGuildPanel); return panel; @@ -644,19 +657,122 @@ public WorldPoint getSelectedHuntingArea() } } - private void startWalking(WorldPoint point) + /** + * Creates a standalone hotkey-binding button. Seeds its value from the + * stored config and writes back on focus-lost. The plugin reads the same + * config key via HotkeyListener, so rebinding takes effect immediately. + */ + private MicrobotHotkeyButton createHotkeyButton(String keyName, Keybind initial, String tooltip) + { + MicrobotHotkeyButton button = new MicrobotHotkeyButton(initial == null ? Keybind.NOT_SET : initial, false); + Dimension size = new Dimension(90, 22); + button.setPreferredSize(size); + button.setMinimumSize(size); + button.setMaximumSize(size); + button.setToolTipText(tooltip); + button.addFocusListener(new FocusAdapter() + { + @Override + public void focusLost(FocusEvent e) + { + configManager.setConfiguration(ShortestPathPlugin.CONFIG_GROUP, keyName, button.getValue()); + } + }); + return button; + } + + /** + * Wraps createHotkeyButton in a centered row for cards where the hotkey + * sits on its own line under the Start/Stop row. + */ + private JPanel createHotkeyRow(String keyName, Keybind initial, String tooltip) + { + JPanel row = new JPanel(new FlowLayout(FlowLayout.CENTER, 0, 0)); + row.add(createHotkeyButton(keyName, initial, tooltip)); + return row; + } + + void startWalking(WorldPoint point) { Microbot.log("Web walking starting. Traveling to Custom Location (" + point.getX() + ", " + point.getY() + ", " + point.getPlane() + ")."); plugin.getShortestPathScript().setTriggerWalker(point); } - private void stopWalking() + void stopWalking() { Microbot.log("Web walking stopping.."); plugin.getShortestPathScript().setTriggerWalker(null); Rs2Walker.setTarget(null); } + /* ------------------------------------------------------------------ + * Hotkey integration: per-category "peek target" and "start walking". + * Mirrors each panel's Start button so ShortestPathPlugin's + * HotkeyListeners can trigger the same action without duplicating + * the category-specific selection logic. + * ------------------------------------------------------------------ */ + + /* Enum-unwrap helpers for the three categories whose combo-box items don't + * directly expose a WorldPoint. The other five categories (custom location, + * quest, clue, farming, hunter) already expose WorldPoint-returning getters + * that the plugin uses as method references. */ + + WorldPoint getBankTarget() + { + BankLocation bank = getSelectedBank(); + return bank == null ? null : bank.getWorldPoint(); + } + + WorldPoint getDepositBoxTarget() + { + DepositBoxLocation box = getSelectedDepositBox(); + return box == null ? null : box.getWorldPoint(); + } + + WorldPoint getSlayerMasterTarget() + { + SlayerMasters master = getSelectedSlayerMaster(); + return master == null ? null : master.getWorldPoint(); + } + + void startWalkingNearestBank() + { + CompletableFuture.supplyAsync(Rs2Bank::getNearestBank) + .thenAccept(nearestBank -> { + if (nearestBank != null) + { + startWalking(nearestBank.getWorldPoint()); + } + else + { + Microbot.log("WebWalker: could not find a nearest bank."); + } + }) + .exceptionally(ex -> { + Microbot.log("Error while finding the nearest bank: " + ex.getMessage()); + return null; + }); + } + + void startWalkingNearestDepositBox() + { + CompletableFuture.supplyAsync(Rs2DepositBox::getNearestDepositBox) + .thenAccept(nearestDepositBox -> { + if (nearestDepositBox != null) + { + startWalking(nearestDepositBox.getWorldPoint()); + } + else + { + Microbot.log("WebWalker: could not find a nearest deposit box."); + } + }) + .exceptionally(ex -> { + Microbot.log("Error while finding the nearest deposit box: " + ex.getMessage()); + return null; + }); + } + private QuestHelperPlugin getQuestHelperPlugin() { return (QuestHelperPlugin) Microbot.getPluginManager().getPlugins().stream() @@ -665,7 +781,7 @@ private QuestHelperPlugin getQuestHelperPlugin() .orElse(null); } - private WorldPoint getCurrentQuestLocation() + WorldPoint getCurrentQuestLocation() { QuestHelperPlugin questHelper = getQuestHelperPlugin(); if (questHelper == null || questHelper.getSelectedQuest() == null) @@ -829,7 +945,10 @@ else if (cluePlugin.getClue() == null) panel.add(clueInfoLabel); panel.add(Box.createRigidArea(new Dimension(0, 10))); panel.add(buttonPanel); - panel.add(Box.createRigidArea(new Dimension(0, 2))); + panel.add(Box.createRigidArea(new Dimension(0, 5))); + panel.add(createHotkeyRow("clueToggleHotkey", config.clueToggleHotkey(), + "Toggle hotkey: start walking to the active clue step; press again to stop.")); + panel.add(Box.createRigidArea(new Dimension(0, 5))); panel.add(helpPanel); return panel; @@ -843,7 +962,7 @@ private ClueScrollPlugin getCluePlugin() .orElse(null); } - private WorldPoint getCurrentClueLocation() + WorldPoint getCurrentClueLocation() { ClueScrollPlugin cluePlugin = getCluePlugin(); if (cluePlugin == null) 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 55d33fd59cd..3a9ad088f99 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 @@ -61,6 +61,7 @@ import net.runelite.client.ui.overlay.worldmap.WorldMapPoint; import net.runelite.client.ui.overlay.worldmap.WorldMapPointManager; import net.runelite.client.util.ColorUtil; +import net.runelite.client.util.HotkeyListener; import net.runelite.client.util.ImageUtil; import net.runelite.client.util.Text; @@ -74,6 +75,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; +import java.util.function.Supplier; import java.util.regex.Pattern; @Slf4j @@ -244,10 +246,34 @@ protected void startUp() { overlayManager.add(debugOverlayPanel); } keyManager.registerKeyListener(this); + keyManager.registerKeyListener(customLocationHotkeyListener); + keyManager.registerKeyListener(bankHotkeyListener); + keyManager.registerKeyListener(nearestBankHotkeyListener); + keyManager.registerKeyListener(depositBoxHotkeyListener); + keyManager.registerKeyListener(nearestDepositBoxHotkeyListener); + keyManager.registerKeyListener(slayerMasterHotkeyListener); + keyManager.registerKeyListener(questHotkeyListener); + keyManager.registerKeyListener(clueHotkeyListener); + keyManager.registerKeyListener(farmingHotkeyListener); + keyManager.registerKeyListener(hunterHotkeyListener); } @Override protected void shutDown() { + // Unregister hotkey listeners first so any in-flight keystroke can't + // dereference panel/shortestPathScript after we null/tear them down. + keyManager.unregisterKeyListener(hunterHotkeyListener); + keyManager.unregisterKeyListener(farmingHotkeyListener); + keyManager.unregisterKeyListener(clueHotkeyListener); + keyManager.unregisterKeyListener(questHotkeyListener); + keyManager.unregisterKeyListener(slayerMasterHotkeyListener); + keyManager.unregisterKeyListener(nearestDepositBoxHotkeyListener); + keyManager.unregisterKeyListener(depositBoxHotkeyListener); + keyManager.unregisterKeyListener(nearestBankHotkeyListener); + keyManager.unregisterKeyListener(bankHotkeyListener); + keyManager.unregisterKeyListener(customLocationHotkeyListener); + keyManager.unregisterKeyListener(this); + overlayManager.remove(pathOverlay); overlayManager.remove(pathMinimapOverlay); overlayManager.remove(pathMapOverlay); @@ -267,7 +293,6 @@ protected void shutDown() { shortestPathScript.shutdown(); exit(); - keyManager.unregisterKeyListener(this); } //Method from microbot @@ -936,6 +961,23 @@ private Polygon bufferedImageToPolygon(BufferedImage image) { return polygon; } + private void toggleCategory(String categoryName, Supplier targetSupplier) { + if (panel == null || !Microbot.isLoggedIn()) { + return; + } + WorldPoint target = targetSupplier.get(); + if (target == null) { + Microbot.log("WebWalker: no " + categoryName + " selected in the panel."); + return; + } + WorldPoint current = shortestPathScript.getTriggerWalker(); + if (target.equals(current)) { + panel.stopWalking(); + } else { + panel.startWalking(target); + } + } + @Override public void keyTyped(KeyEvent e) { @@ -961,4 +1003,84 @@ public void keyPressed(KeyEvent e) { public void keyReleased(KeyEvent e) { } + + private final HotkeyListener customLocationHotkeyListener = new HotkeyListener(() -> config.customLocationToggleHotkey()) { + @Override + public void hotkeyPressed() { + toggleCategory("custom location", panel::getCustomLocation); + } + }; + + private final HotkeyListener bankHotkeyListener = new HotkeyListener(() -> config.bankToggleHotkey()) { + @Override + public void hotkeyPressed() { + toggleCategory("bank", panel::getBankTarget); + } + }; + + private final HotkeyListener nearestBankHotkeyListener = new HotkeyListener(() -> config.nearestBankHotkey()) { + @Override + public void hotkeyPressed() { + if (panel == null || !Microbot.isLoggedIn()) return; + if (shortestPathScript.getTriggerWalker() != null) { + panel.stopWalking(); + } else { + panel.startWalkingNearestBank(); + } + } + }; + + private final HotkeyListener depositBoxHotkeyListener = new HotkeyListener(() -> config.depositBoxToggleHotkey()) { + @Override + public void hotkeyPressed() { + toggleCategory("deposit box", panel::getDepositBoxTarget); + } + }; + + private final HotkeyListener nearestDepositBoxHotkeyListener = new HotkeyListener(() -> config.nearestDepositBoxHotkey()) { + @Override + public void hotkeyPressed() { + if (panel == null || !Microbot.isLoggedIn()) return; + if (shortestPathScript.getTriggerWalker() != null) { + panel.stopWalking(); + } else { + panel.startWalkingNearestDepositBox(); + } + } + }; + + private final HotkeyListener slayerMasterHotkeyListener = new HotkeyListener(() -> config.slayerMasterToggleHotkey()) { + @Override + public void hotkeyPressed() { + toggleCategory("slayer master", panel::getSlayerMasterTarget); + } + }; + + private final HotkeyListener questHotkeyListener = new HotkeyListener(() -> config.questToggleHotkey()) { + @Override + public void hotkeyPressed() { + toggleCategory("quest location", panel::getCurrentQuestLocation); + } + }; + + private final HotkeyListener clueHotkeyListener = new HotkeyListener(() -> config.clueToggleHotkey()) { + @Override + public void hotkeyPressed() { + toggleCategory("clue location", panel::getCurrentClueLocation); + } + }; + + private final HotkeyListener farmingHotkeyListener = new HotkeyListener(() -> config.farmingToggleHotkey()) { + @Override + public void hotkeyPressed() { + toggleCategory("farming location", panel::getSelectedFarmingLocation); + } + }; + + private final HotkeyListener hunterHotkeyListener = new HotkeyListener(() -> config.hunterToggleHotkey()) { + @Override + public void hotkeyPressed() { + toggleCategory("hunter area", panel::getSelectedHuntingArea); + } + }; } diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotHotkeyButton.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotHotkeyButton.java index 14699a78c89..e124e0a552f 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotHotkeyButton.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotHotkeyButton.java @@ -35,7 +35,7 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; -class MicrobotHotkeyButton extends JButton +public class MicrobotHotkeyButton extends JButton { @Getter private Keybind value; From 0b300f3a2bab1dbb68849c202f16b2dec7b69ea3 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Thu, 16 Apr 2026 13:19:37 -0400 Subject: [PATCH 2/2] fix(webwalker): close panel-null race in hotkey toggle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bound method refs like `panel::getBankTarget` evaluate the receiver at expression time (JLS 15.13.3), so the null guard inside toggleCategory was unreachable — an NPE would fire at the method-ref expression before the check ran. Switched to unbound refs (`ShortestPathPanel::getX`) and moved dereference behind the null check via a locally-captured panel. --- .../shortestpath/ShortestPathPlugin.java | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 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 3a9ad088f99..e4944c29cd0 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 @@ -75,7 +75,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ThreadFactory; -import java.util.function.Supplier; +import java.util.function.Function; import java.util.regex.Pattern; @Slf4j @@ -961,20 +961,24 @@ private Polygon bufferedImageToPolygon(BufferedImage image) { return polygon; } - private void toggleCategory(String categoryName, Supplier targetSupplier) { - if (panel == null || !Microbot.isLoggedIn()) { + private void toggleCategory(String categoryName, Function targetFn) { + // Capture panel locally so the null check is effective. Call sites + // pass unbound method references (ShortestPathPanel::get...) so panel + // is not dereferenced until after we've confirmed it's non-null. + ShortestPathPanel p = panel; + if (p == null || !Microbot.isLoggedIn()) { return; } - WorldPoint target = targetSupplier.get(); + WorldPoint target = targetFn.apply(p); if (target == null) { Microbot.log("WebWalker: no " + categoryName + " selected in the panel."); return; } WorldPoint current = shortestPathScript.getTriggerWalker(); if (target.equals(current)) { - panel.stopWalking(); + p.stopWalking(); } else { - panel.startWalking(target); + p.startWalking(target); } } @@ -1007,14 +1011,14 @@ public void keyReleased(KeyEvent e) { private final HotkeyListener customLocationHotkeyListener = new HotkeyListener(() -> config.customLocationToggleHotkey()) { @Override public void hotkeyPressed() { - toggleCategory("custom location", panel::getCustomLocation); + toggleCategory("custom location", ShortestPathPanel::getCustomLocation); } }; private final HotkeyListener bankHotkeyListener = new HotkeyListener(() -> config.bankToggleHotkey()) { @Override public void hotkeyPressed() { - toggleCategory("bank", panel::getBankTarget); + toggleCategory("bank", ShortestPathPanel::getBankTarget); } }; @@ -1033,7 +1037,7 @@ public void hotkeyPressed() { private final HotkeyListener depositBoxHotkeyListener = new HotkeyListener(() -> config.depositBoxToggleHotkey()) { @Override public void hotkeyPressed() { - toggleCategory("deposit box", panel::getDepositBoxTarget); + toggleCategory("deposit box", ShortestPathPanel::getDepositBoxTarget); } }; @@ -1052,35 +1056,35 @@ public void hotkeyPressed() { private final HotkeyListener slayerMasterHotkeyListener = new HotkeyListener(() -> config.slayerMasterToggleHotkey()) { @Override public void hotkeyPressed() { - toggleCategory("slayer master", panel::getSlayerMasterTarget); + toggleCategory("slayer master", ShortestPathPanel::getSlayerMasterTarget); } }; private final HotkeyListener questHotkeyListener = new HotkeyListener(() -> config.questToggleHotkey()) { @Override public void hotkeyPressed() { - toggleCategory("quest location", panel::getCurrentQuestLocation); + toggleCategory("quest location", ShortestPathPanel::getCurrentQuestLocation); } }; private final HotkeyListener clueHotkeyListener = new HotkeyListener(() -> config.clueToggleHotkey()) { @Override public void hotkeyPressed() { - toggleCategory("clue location", panel::getCurrentClueLocation); + toggleCategory("clue location", ShortestPathPanel::getCurrentClueLocation); } }; private final HotkeyListener farmingHotkeyListener = new HotkeyListener(() -> config.farmingToggleHotkey()) { @Override public void hotkeyPressed() { - toggleCategory("farming location", panel::getSelectedFarmingLocation); + toggleCategory("farming location", ShortestPathPanel::getSelectedFarmingLocation); } }; private final HotkeyListener hunterHotkeyListener = new HotkeyListener(() -> config.hunterToggleHotkey()) { @Override public void hotkeyPressed() { - toggleCategory("hunter area", panel::getSelectedHuntingArea); + toggleCategory("hunter area", ShortestPathPanel::getSelectedHuntingArea); } }; }