Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 3 additions & 4 deletions .run/Microbot.run.xml
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Microbot" type="Application" factoryName="Application">
<option name="ALTERNATIVE_JRE_PATH" value="11" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
<option name="MAIN_CLASS_NAME" value="net.runelite.client.RuneLite" />
<module name="client.main" />
<module name="runelite.client.main" />
<shortenClasspath name="ARGS_FILE" />
<option name="PROGRAM_PARAMETERS" value="-developer-mode" />
<option name="VM_PARAMETERS" value="-ea" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
</component>
5 changes: 3 additions & 2 deletions .run/Runelite with proxy.run.xml
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Runelite with proxy" type="Application" factoryName="Application">
<option name="MAIN_CLASS_NAME" value="net.runelite.client.RuneLite" />
<module name="client" />
<module name="runelite.client.main" />
<shortenClasspath name="ARGS_FILE" />
<option name="PROGRAM_PARAMETERS" value="-developer-mode -proxy=144.49.99.215:8080 -proxy-type=http"/>
<option name="VM_PARAMETERS" value="-ea" />
<option name="WORKING_DIRECTORY" value="$USER_HOME$/.runelite"/>
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
</component>
7 changes: 3 additions & 4 deletions .run/Runelite.run.xml
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Runelite" type="Application" factoryName="Application" singleton="false">
<option name="ALTERNATIVE_JRE_PATH" value="11" />
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
<option name="MAIN_CLASS_NAME" value="net.runelite.client.RuneLite" />
<module name="client.main" />
<module name="runelite.client.main" />
<shortenClasspath name="ARGS_FILE" />
<option name="PROGRAM_PARAMETERS" value="-developer-mode" />
<option name="VM_PARAMETERS" value="-ea" />
<method v="2">
<option name="Make" enabled="true" />
</method>
</configuration>
</component>
</component>
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import net.runelite.api.Client;
import net.runelite.api.Point;
import net.runelite.client.plugins.microbot.Microbot;
import net.runelite.client.plugins.microbot.util.input.PointerState;
import net.runelite.client.ui.overlay.Overlay;
import net.runelite.client.ui.overlay.OverlayLayer;
import net.runelite.client.ui.overlay.OverlayPosition;
Expand All @@ -26,7 +27,9 @@ public class MicrobotMouseOverlay extends Overlay {
this.client = client;
this.plugin = plugin;
setPosition(OverlayPosition.DYNAMIC);
setLayer(OverlayLayer.ABOVE_WIDGETS);
// Not ABOVE_WIDGETS: that layer is defined as "render under the right-click menu", so the
// crosshair vanished behind an open context menu. A real cursor draws above it.
setLayer(OverlayLayer.ALWAYS_ON_TOP);
setPriority(Overlay.PRIORITY_LOW);
setNaughty();
// Increase the angle
Expand All @@ -51,7 +54,10 @@ public class MicrobotMouseOverlay extends Overlay {

@Override
public Dimension render(Graphics2D g) {
if (plugin.getMouseMovement().isActive()) {
// Nothing to draw while the pointer is off the canvas: the client believes none is there,
// so a crosshair where the user left it is a phantom. Falls through to the branch that
// clears the trail, so returning rebuilds it from wherever the pointer comes back.
if (plugin.getMouseMovement().isActive() && !PointerState.isOutside()) {
if (!Microbot.getMouse().getTimer().isRunning()) {
Microbot.getMouse().getPoints().clear();
Microbot.getMouse().getTimer().start();
Expand Down Expand Up @@ -125,8 +131,9 @@ public Dimension render(Graphics2D g) {

g2d.dispose();
// Mouse position
int x = Microbot.getMouse().getLastMove().getX();
int y = Microbot.getMouse().getLastMove().getY();
Point cursor = PointerState.get();
int x = cursor.getX();
int y = cursor.getY();


// Draw the crosshair centered
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ Config UI uses the custom `MicrobotConfigPanel` (`plugins/microbot/ui`), not Run

Check `docs/entity-guides/README.md` before modifying anything under `util/` — each entity type has documented footguns.

## Waits and multi-action helpers
- **A wait that returned is not a wait that succeeded.** Every `sleepUntil*` ends early on a timeout, an interrupt, or a human taking over. Discarding the result means acting as though the previous game action landed when nothing observed that.
- **Never report success you did not observe.** A helper returning `true` unconditionally after a wait leaves its caller no way to find out.
- **A helper spanning more than one game action exposes a step.** The game is a remote asynchronous server, so no sequence of actions can be made atomic; the only option is re-deriving from observed state, which the 600ms `Script.run()` tick already does. A helper blocking for seconds is reimplementing that loop inside one iteration of it.
- Copy `Rs2Walker`: `walkStep` issues one click and returns a `WalkerState`, with the blocking `walkTo` built on top. The enum matters as much as the split, since `if (walkStep(...))` does not compile while `if (someBooleanHelper(...))` silently discards the answer.

## Scripts
- Extend `Script` (or `StateMachineScript<S>` for 3+ phase scripts — see `statemachine/AGENTS.md`).
- Implement `run(TConfig)`; return `true` to signal successful start.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,4 +230,46 @@ default boolean showCacheInfo() {
default boolean disableTelemetry() {
return false;
}

String keyDisableInputYielding = "disableInputYielding";
@ConfigItem(
keyName = keyDisableInputYielding,
name = "Disable input yielding",
description = "Stop scripts pausing when you use the real mouse or keyboard on the game canvas. " +
"Leave this off unless yielding misfires; with it on, your input and the bot's compete for the cursor.",
position = 7,
section = generalSection
)
default boolean disableInputYielding()
{
return false;
}

String keyInputMotionThresholdPx = "inputMotionThresholdPx";
@ConfigItem(
keyName = keyInputMotionThresholdPx,
name = "Input yield: motion threshold",
description = "How far the real mouse must move, in pixels from where the bot last put it, before scripts yield. " +
"Lower reacts sooner and risks yielding on an accidental nudge.",
position = 8,
section = generalSection
)
default int inputMotionThresholdPx()
{
return 10;
}

String keyInputIdleResumeMs = "inputIdleResumeMs";
@ConfigItem(
keyName = keyInputIdleResumeMs,
name = "Input yield: resume delay (ms)",
description = "How long the real mouse and keyboard must stay quiet before scripts resume. " +
"Never resumes while you are holding a button or key, whatever this is set to.",
position = 9,
section = generalSection
)
default int inputIdleResumeMs()
{
return 1800;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
import net.runelite.api.Point;
import net.runelite.api.coords.LocalPoint;
import net.runelite.api.coords.WorldPoint;
import net.runelite.client.plugins.microbot.util.input.InputArbiter;
import net.runelite.client.plugins.microbot.util.input.InputDiagnostics;
import net.runelite.client.plugins.microbot.util.player.Rs2Player;
import net.runelite.client.plugins.microbot.util.tile.Rs2Tile;
import net.runelite.client.ui.overlay.OverlayPanel;
import net.runelite.client.ui.overlay.OverlayPosition;
import net.runelite.client.ui.overlay.OverlayUtil;
import net.runelite.client.ui.overlay.components.LineComponent;
import net.runelite.client.ui.overlay.components.TitleComponent;

import javax.annotation.Nullable;
import javax.inject.Inject;
Expand All @@ -30,7 +34,29 @@ public class MicrobotOverlay extends OverlayPanel {

@Override
public Dimension render(Graphics2D graphics) {
panelComponent.setPreferredSize(new Dimension(200, 300));
panelComponent.setPreferredSize(new Dimension(InputDiagnostics.isEnabled() ? 260 : 200, 300));
panelComponent.getChildren().clear();

// A silent pause is indistinguishable from a crash. Empty, and so invisible, otherwise.
if (InputArbiter.isHuman()) {
panelComponent.getChildren().add(TitleComponent.builder()
.text("Paused for your input")
.color(Color.YELLOW)
.build());
}

if (InputDiagnostics.isEnabled()) {
panelComponent.getChildren().add(TitleComponent.builder()
.text("input")
.color(Color.WHITE)
.build());
for (Map.Entry<String, String> row : InputDiagnostics.readout().entrySet()) {
panelComponent.getChildren().add(LineComponent.builder()
.left(row.getKey())
.right(row.getValue())
.build());
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

for (Map.Entry<WorldPoint, Integer> dangerousTile : Rs2Tile.getDangerousGraphicsObjectTiles().entrySet()) {
drawTile(graphics, dangerousTile.getKey(), Color.RED, dangerousTile.getValue().toString());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
import net.runelite.client.plugins.microbot.util.huntkit.Rs2HuntKit;
import net.runelite.client.plugins.microbot.util.inventory.Rs2Gembag;
import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory;
import net.runelite.client.plugins.microbot.util.input.CanvasInputListener;
import net.runelite.client.plugins.microbot.util.input.InputArbiter;
import net.runelite.client.plugins.microbot.util.inventory.Rs2RunePouch;
import net.runelite.client.plugins.microbot.util.overlay.GembagOverlay;
import net.runelite.client.plugins.microbot.util.player.Rs2Player;
Expand Down Expand Up @@ -164,6 +166,10 @@ protected void startUp() throws AWTException
);

Microbot.pauseAllScripts.set(false);
InputArbiter.setDisabled(microbotConfig.disableInputYielding());
InputArbiter.setMotionThresholdPx(microbotConfig.inputMotionThresholdPx());
InputArbiter.setIdleResumeMs(microbotConfig.inputIdleResumeMs());
CanvasInputListener.attach();
Microbot.enableAutoRunOn = microbotConfig.enableAutoRunOn();
Microbot.useStaminaPotsIfNeeded = microbotConfig.useStaminaPotsIfNeeded();
Microbot.getBlockingEventManager().start();
Expand Down Expand Up @@ -206,6 +212,7 @@ protected void startUp() throws AWTException

protected void shutDown()
{
CanvasInputListener.detach();
overlayManager.remove(microbotOverlay);
overlayManager.remove(gembagOverlay);
overlayManager.remove(pouchOverlay);
Expand Down Expand Up @@ -490,6 +497,15 @@ public void onConfigChanged(ConfigChanged ev)
case MicrobotConfig.keyUseStaminaPotsIfNeeded:
Microbot.useStaminaPotsIfNeeded = microbotConfig.useStaminaPotsIfNeeded();
break;
case MicrobotConfig.keyDisableInputYielding:
InputArbiter.setDisabled(microbotConfig.disableInputYielding());
break;
case MicrobotConfig.keyInputMotionThresholdPx:
InputArbiter.setMotionThresholdPx(microbotConfig.inputMotionThresholdPx());
break;
case MicrobotConfig.keyInputIdleResumeMs:
InputArbiter.setIdleResumeMs(microbotConfig.inputIdleResumeMs());
break;
case MicrobotConfig.keyEnableGameChatLogging:
case MicrobotConfig.keyGameChatLogPattern:
case MicrobotConfig.keyGameChatLogLevel:
Expand Down Expand Up @@ -607,6 +623,9 @@ public void onOverlayMenuClicked(OverlayMenuClicked overlayMenuClicked)
@Subscribe
public void onGameTick(GameTick event)
{
// Cheap identity check: a stale registration leaves the arbiter deaf with no other symptom.
CanvasInputListener.attach();

// Start Leagues teleport calibration ASAP after login (non-blocking; prompts for consent once).
Rs2LeaguesTransport.tickLeaguesCalibration();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import net.runelite.client.plugins.microbot.util.Global;
import net.runelite.client.plugins.microbot.agentserver.handler.ScriptHeartbeatRegistry;
import net.runelite.client.plugins.microbot.util.antiban.SessionFatigue;
import net.runelite.client.plugins.microbot.util.input.InputArbiter;
import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory;
import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard;
import net.runelite.client.plugins.microbot.util.player.Rs2Player;
import net.runelite.client.plugins.microbot.util.walker.Rs2Walker;
import org.jetbrains.annotations.NotNull;
Expand Down Expand Up @@ -60,6 +62,9 @@ public void shutdown() {
if (Microbot.getClientThread().scheduledFuture != null)
Microbot.getClientThread().scheduledFuture.cancel(true);
initialPlayerLocation = null;
// Backstop for a script stopped between a hold and its release. Fires inconsistently:
// most scripts catch-and-continue without reaching here.
Rs2Keyboard.releaseHeldKeys();
Microbot.pauseAllScripts.set(false);
Rs2Walker.disableTeleports = false;
Microbot.getSpecialAttackConfigs().reset();
Expand Down Expand Up @@ -88,7 +93,14 @@ public boolean run() {
// A blocking event was found & is executing
return false;
}
if (Microbot.pauseAllScripts.get())
// The arbiter keeps its own flag, so a takeover idles every script through the gate that
// already exists, cancelling nothing.
boolean humanOwnsInput = InputArbiter.isHuman();
if (humanOwnsInput) {
// A held key is not gesture-scoped, so InputLoop cannot unwind it.
Rs2Keyboard.releaseHeldKeys();
}
if (Microbot.pauseAllScripts.get() || humanOwnsInput)
return false;
if (Thread.currentThread().isInterrupted())
return false;
Expand Down
Loading