Skip to content

feat(webwalker): add configurable per-card hotkeys#1748

Merged
chsami merged 2 commits intochsami:developmentfrom
runsonmypc:feat/webwalker-hotkeys
Apr 18, 2026
Merged

feat(webwalker): add configurable per-card hotkeys#1748
chsami merged 2 commits intochsami:developmentfrom
runsonmypc:feat/webwalker-hotkeys

Conversation

@runsonmypc
Copy link
Copy Markdown
Contributor

Summary

  • Adds 10 user-configurable toggle hotkeys to the WebWalker (shortestpath) side panel, one inline button per category card: 8 for the selected target (custom location, bank, deposit box, slayer master, quest, clue, farming, hunter) and 2 for "nearest bank" / "nearest deposit box".
  • Keybinds are stored as hidden `@ConfigItem Keybind` entries, bound via `MicrobotHotkeyButton` on each card; the plugin dispatches them through `HotkeyListener`. `toggleCategory` routes start/stop through the existing panel methods so logs and walker state match the Start/Stop buttons.
  • `MicrobotHotkeyButton` promoted from package-private to public so side-panel code can embed it.
  • Adds a plain `:client:run` gradle task alongside the existing `:client:runDebug` (no JDWP suspend).
  • Hardcoded CTRL+X stop hotkey is preserved unchanged.

Test plan

  • Bind one hotkey per category; press once → walker starts to selected target. Press again → walker stops.
  • Empty selection (e.g. no bank selected): hotkey logs "WebWalker: no X selected in the panel." to chat.
  • Nearest bank / deposit box hotkeys: press once starts walk; press again while walking stops.
  • CTRL+X still stops the walker.
  • Disable and re-enable plugin: hotkeys stop while disabled, resume after re-enable (unregister/register symmetry).

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.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 16, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 200025b4-f06a-43a4-b123-6b52df1cb5b6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This pull request adds hotkey support to the ShortestPath plugin. The changes include: (1) registering a new Gradle JavaExec task for running the client; (2) defining 10 hidden keybind configuration accessors in ShortestPathConfig; (3) extending ShortestPathPanel with hotkey UI controls and helper methods for triggering navigation actions; (4) registering HotkeyListener instances in ShortestPathPlugin that dispatch to the appropriate panel actions; and (5) making MicrobotHotkeyButton public to support the hotkey button creation.

Possibly related PRs

  • Development #1635: Modifies build.gradle.kts to add JavaExec run task configurations, directly overlapping with the Gradle changes in this PR.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title directly and clearly describes the main change: adding configurable hotkeys to the WebWalker side panel, which aligns perfectly with the primary focus of the changeset.
Description check ✅ Passed The description is directly related to the changeset, providing a comprehensive summary of the hotkey implementation, configuration storage, UI updates, and test plan.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPanel.java`:
- Around line 738-774: The async nearest-deposit-box lookup can resume walking
after the user has stopped; mirror the guard used in startWalkingNearestBank
inside startWalkingNearestDepositBox by capturing the same cancellation/token
value before calling
CompletableFuture.supplyAsync(Rs2DepositBox::getNearestDepositBox) and, inside
thenAccept, verify the captured token (and any panel/enabled state check used by
startWalkingNearestBank) still matches the current token/state before calling
startWalking(nearestDepositBox.getWorldPoint()); otherwise skip calling
startWalking to prevent restarting after Stop.

In
`@runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPlugin.java`:
- Around line 964-979: toggleCategory's null-guard is ineffective because
hotkeyPressed binds instance method references (e.g., panel::getBankTarget)
capturing a possibly-null panel and causing NPEs before toggleCategory runs;
change callers to pass unbound suppliers or safe lambdas that read panel at call
time (for example use ShortestPathPanel::getBankTarget or () -> {
ShortestPathPanel p = panel; return p == null ? null : p.getBankTarget(); }) so
the supplier evaluates panel null-check inside toggleCategory, and keep the
existing panel == null check in toggleCategory to short-circuit when
appropriate; update all call sites that currently pass bound references (lines
invoking toggleCategory with panel::...) to use unbound method references or the
safe lambda form.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 91c28d9e-be8f-47cf-96e7-290fe8c11de9

📥 Commits

Reviewing files that changed from the base of the PR and between cfc8b30 and d67b5af.

📒 Files selected for processing (5)
  • runelite-client/build.gradle.kts
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathConfig.java
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPanel.java
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPlugin.java
  • runelite-client/src/main/java/net/runelite/client/plugins/microbot/ui/MicrobotHotkeyButton.java

Comment on lines +738 to +774
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;
});
}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Apr 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Nearest lookups can restart walking after Stop.

These async lookups are fire-and-forget. If the user stops walking, disables the panel, or presses the nearest hotkey a second time before the lookup completes, the old thenAccept(...) still calls startWalking(...) and walking resumes anyway.

Possible fix
+import java.util.concurrent.atomic.AtomicLong;
...
+	private final AtomicLong nearestLookupToken = new AtomicLong();
...
 	void stopWalking()
 	{
+		nearestLookupToken.incrementAndGet();
 		Microbot.log("Web walking stopping..");
 		plugin.getShortestPathScript().setTriggerWalker(null);
 		Rs2Walker.setTarget(null);
 	}
...
 	void startWalkingNearestBank()
 	{
+		long token = nearestLookupToken.incrementAndGet();
 		CompletableFuture.supplyAsync(Rs2Bank::getNearestBank)
 			.thenAccept(nearestBank -> {
+				if (token != nearestLookupToken.get())
+				{
+					return;
+				}
 				if (nearestBank != null)
 				{
 					startWalking(nearestBank.getWorldPoint());
 				}

Apply the same token check to startWalkingNearestDepositBox().

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPanel.java`
around lines 738 - 774, The async nearest-deposit-box lookup can resume walking
after the user has stopped; mirror the guard used in startWalkingNearestBank
inside startWalkingNearestDepositBox by capturing the same cancellation/token
value before calling
CompletableFuture.supplyAsync(Rs2DepositBox::getNearestDepositBox) and, inside
thenAccept, verify the captured token (and any panel/enabled state check used by
startWalkingNearestBank) still matches the current token/state before calling
startWalking(nearestDepositBox.getWorldPoint()); otherwise skip calling
startWalking to prevent restarting after Stop.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should probably be fixed. Walking should stop after pressing stop.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@runsonmypc, agreed — the fix is straightforward. Here's the plan:

  • Add AtomicLong nearestLookupToken field to ShortestPathPanel
  • Increment token in stopWalking() to invalidate any in-flight lookups
  • Capture + check token in both startWalkingNearestBank() and startWalkingNearestDepositBox() before calling startWalking()

🧠 Learnings used
Learnt from: CR
Repo: chsami/Microbot PR: 0
File: runelite-client/src/main/java/net/runelite/client/plugins/microbot/AGENTS.md:0-0
Timestamp: 2026-04-12T01:06:30.165Z
Learning: Applies to runelite-client/src/main/java/net/runelite/client/plugins/microbot/**/*Config*.java : Configure Config UI tweaks and custom controls in `MicrobotConfigPanel` rather than RuneLite's default config panel

Learnt from: CR
Repo: chsami/Microbot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-10T06:02:28.166Z
Learning: Applies to runelite-client/src/**/java/net/runelite/client/plugins/microbot/MicrobotPlugin.java : Preserve the hidden/always-on nature of `MicrobotPlugin` and its config panel wiring

Learnt from: Krulvis
Repo: chsami/Microbot PR: 1506
File: runelite-client/src/main/java/net/runelite/client/plugins/microbot/util/walker/Rs2Walker.java:1412-1423
Timestamp: 2025-09-11T09:51:12.404Z
Learning: In the microbot pathfinding system, global (null-keyed) transports like teleportation spells are automatically added to the player's start worldpoint by PathfinderConfig.refreshTeleports method during pathfinder refresh, eliminating the need to manually union null-keyed transports when iterating through available transports.

Learnt from: CR
Repo: chsami/Microbot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-10T06:02:28.166Z
Learning: Applies to runelite-client/src/**/java/net/runelite/client/plugins/microbot/util/**/*.java : Always use the settings search bar instead of navigating tabs. Tab indices shift on game updates and the 'Interfaces' tab was removed — hardcoded tab navigation will break

Learnt from: CR
Repo: chsami/Microbot PR: 0
File: runelite-client/src/main/java/net/runelite/client/plugins/microbot/AGENTS.md:0-0
Timestamp: 2026-04-12T01:06:30.165Z
Learning: Applies to runelite-client/src/main/java/net/runelite/client/plugins/microbot/**/*Script.java : Check and respect `Microbot.getBlockingEventManager().shouldBlockAndProcess()` to allow high-priority events to interrupt automation

Learnt from: CR
Repo: chsami/Microbot PR: 0
File: runelite-client/src/main/java/net/runelite/client/plugins/microbot/AGENTS.md:0-0
Timestamp: 2026-04-12T01:06:30.165Z
Learning: Applies to runelite-client/src/main/java/net/runelite/client/plugins/microbot/**/*.java : Always use `new Rs2NpcQueryable().withName(name).nearest()` instead of deprecated `Rs2Npc.getNpc(name)` static methods

Learnt from: CR
Repo: chsami/Microbot PR: 0
File: runelite-client/src/main/java/net/runelite/client/plugins/microbot/AGENTS.md:0-0
Timestamp: 2026-04-12T01:06:30.165Z
Learning: Applies to runelite-client/src/main/java/net/runelite/client/plugins/microbot/**/*Script.java : Always wait for game state changes after interactions using `sleepUntil()` instead of assuming immediate completion

Learnt from: CR
Repo: chsami/Microbot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-10T06:02:28.166Z
Learning: Applies to runelite-client/src/**/java/net/runelite/client/plugins/microbot/**/*.java : Never use static sleeps like `sleep(12000)` to wait for game state. Always use conditional dynamic sleeps: `sleepUntil(BooleanSupplier awaitedCondition)` (optionally with a timeout as a safety net)

Learnt from: CR
Repo: chsami/Microbot PR: 0
File: runelite-client/src/main/java/net/runelite/client/plugins/microbot/AGENTS.md:0-0
Timestamp: 2026-04-12T01:06:30.165Z
Learning: Applies to runelite-client/src/main/java/net/runelite/client/plugins/microbot/**/*Script.java : Integrate with `Rs2Antiban` utilities and respect `Microbot.pauseAllScripts.get()` for break handler compatibility

Learnt from: CR
Repo: chsami/Microbot PR: 0
File: runelite-client/src/main/java/net/runelite/client/plugins/microbot/AGENTS.md:0-0
Timestamp: 2026-04-12T01:06:30.165Z
Learning: Applies to runelite-client/src/main/java/net/runelite/client/plugins/microbot/**/*.java : Never block the client thread with `sleep()` or long-running operations; always use `Microbot.getClientThread().runOnClientThread()` for safe client access

Learnt from: CR
Repo: chsami/Microbot PR: 0
File: AGENTS.md:0-0
Timestamp: 2026-04-10T06:02:28.166Z
Learning: Applies to runelite-client/src/**/java/net/runelite/client/plugins/microbot/util/**/*.java : Do not hardcode cache tab indices or assume widget parent structure; always use the settings search bar and widget click by text when possible

  • 📌 Commit the changes to this branch

Comment on lines +964 to +979
private void toggleCategory(String categoryName, Supplier<WorldPoint> 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);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPlugin.java | sed -n '964,979p'

Repository: chsami/Microbot

Length of output: 756


🏁 Script executed:

cat -n runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPlugin.java | sed -n '1007,1084p'

Repository: chsami/Microbot

Length of output: 3600


🏁 Script executed:

cat -n runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPlugin.java | sed -n '1,100p'

Repository: chsami/Microbot

Length of output: 5196


🏁 Script executed:

grep -n "panel = null" runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPlugin.java

Repository: chsami/Microbot

Length of output: 84


🏁 Script executed:

grep -n "shutdown()" runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPlugin.java

Repository: chsami/Microbot

Length of output: 101


🏁 Script executed:

cat -n runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPlugin.java | sed -n '263,275p'

Repository: chsami/Microbot

Length of output: 1023


🏁 Script executed:

grep -n "panel =" runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPlugin.java | head -20

Repository: chsami/Microbot

Length of output: 350


🏁 Script executed:

grep -n "keyManager.registerKeyListener\|new HotkeyListener" runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPlugin.java | head -30

Repository: chsami/Microbot

Length of output: 2016


🏁 Script executed:

cat -n runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPlugin.java | sed -n '200,310p'

Repository: chsami/Microbot

Length of output: 5423


🏁 Script executed:

grep -n "extends HotkeyListener\|HotkeyListener implements\|class HotkeyListener" runelite-client/src/main/java/net/runelite/client/util/HotkeyListener.java

Repository: chsami/Microbot

Length of output: 121


🏁 Script executed:

cat -n runelite-client/src/main/java/net/runelite/client/util/HotkeyListener.java | head -50

Repository: chsami/Microbot

Length of output: 2370


Remove the ineffective null-guard or pass unbound references.

The panel == null check at line 965 cannot protect bound method references like panel::getBankTarget (lines 1010, 1017, 1036, 1055, 1062, 1069, 1076, 1083): the receiver is captured when the reference is created in hotkeyPressed(), so a null panel causes an NPE before toggleCategory() runs.

While the shutdown code correctly unregisters listeners before nulling panel (lines 263–275), this pattern is fragile and creates inconsistency with other listeners that check panel == null directly (lines 1024, 1043). Passing unbound references lets the null-check actually work:

Suggested fix
-import java.util.function.Supplier;
+import java.util.function.Function;
...
-    private void toggleCategory(String categoryName, Supplier<WorldPoint> targetSupplier) {
-        if (panel == null || !Microbot.isLoggedIn()) {
+    private void toggleCategory(String categoryName, Function<ShortestPathPanel, WorldPoint> targetSupplier) {
+        ShortestPathPanel currentPanel = panel;
+        if (currentPanel == null || !Microbot.isLoggedIn()) {
             return;
         }
-        WorldPoint target = targetSupplier.get();
+        WorldPoint target = targetSupplier.apply(currentPanel);

Then call with unbound references: ShortestPathPanel::getBankTarget.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@runelite-client/src/main/java/net/runelite/client/plugins/microbot/shortestpath/ShortestPathPlugin.java`
around lines 964 - 979, toggleCategory's null-guard is ineffective because
hotkeyPressed binds instance method references (e.g., panel::getBankTarget)
capturing a possibly-null panel and causing NPEs before toggleCategory runs;
change callers to pass unbound suppliers or safe lambdas that read panel at call
time (for example use ShortestPathPanel::getBankTarget or () -> {
ShortestPathPanel p = panel; return p == null ? null : p.getBankTarget(); }) so
the supplier evaluates panel null-check inside toggleCategory, and keep the
existing panel == null check in toggleCategory to short-circuit when
appropriate; update all call sites that currently pass bound references (lines
invoking toggleCategory with panel::...) to use unbound method references or the
safe lambda form.

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.
@chsami
Copy link
Copy Markdown
Owner

chsami commented Apr 17, 2026

we already have a web walker section
image

@runsonmypc
Copy link
Copy Markdown
Contributor Author

we already have a web walker section image

Not sure I follow. This feature was added to the web walker section:
image

@chsami chsami merged commit b51980b into chsami:development Apr 18, 2026
2 checks passed
@runsonmypc runsonmypc deleted the feat/webwalker-hotkeys branch April 18, 2026 21:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants