Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
isExternal = PluginConstants.IS_EXTERNAL
)
public class FlipperPlugin extends Plugin {
public static final String version = "1.2.80";
public static final String version = "1.2.81";
private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(FlipperPlugin.class);
@Inject
private Client client;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1482,12 +1482,16 @@ private boolean isSlotDefaultAction(int slotId, SlotActionExecutor.Action action
return readUi(() -> {
if (Microbot.naturalMouse == null || Microbot.targetMenu != null || Microbot.getClient().isMenuOpen()
|| !isSlotActionSwapEnabled() || !sameSlotSuggestion(key) || isOfferScreenOpen()) return false;
net.runelite.api.Point mouse = Microbot.getClient().getMouseCanvasPosition();
Widget slot = Microbot.getClient().getWidget(slotId);
Widget button = slot == null ? null : slot.getChild(2);
if (button == null || button.isHidden() || mouse == null
|| mouse.getX() != point.getX() || mouse.getY() != point.getY()
|| !button.getBounds().contains(point.getX(), point.getY())) return false;
if (button == null || button.isHidden()) return false;
Rectangle bounds = button.getBounds();
// Owned gestures can update PointerState a frame before the client mouse position.
net.runelite.api.Point mouse = Microbot.getClient().getMouseCanvasPosition();
if (!SlotActionExecutor.cursorOnSlotButton(mouse, point, bounds)
&& !SlotActionExecutor.cursorOnSlotButton(PointerState.get(), point, bounds)) {
return false;
}
return SlotActionExecutor.matchesDefaultAction(
Microbot.getClient().getMenu().getMenuEntries(), slotId, action);
});
Expand Down Expand Up @@ -1520,47 +1524,53 @@ private boolean checkAndAbortOrModifyIfNeeded() {
final int slotId = grandExchangeSlotIds[boxId];
final SlotActionExecutor.Action action = abort
? SlotActionExecutor.Action.ABORT : SlotActionExecutor.Action.MODIFY;
SlotActionExecutor.Result result = SlotActionExecutor.execute(config.slotAction(), action, slotId,
new SlotActionExecutor.Ui() {
private BooleanSupplier hoverGuard = () -> true;
private boolean variedHover;
public boolean slotSwapEnabled() { return isSlotActionSwapEnabled(); }
public net.runelite.api.Point actionPoint(int id, SlotActionExecutor.Action a) {
return slotActionPoint(id, a);
}
public boolean hover(net.runelite.api.Point point) {
// Mouse.move dispatches a single jump. Follow a smooth path on this
// script thread; never block the client thread for mouse movement.
if (randomizeMouseSpeed()) {
variedHover = true;
hoverGuard = mouseMovementGuard();
return mouseMotion.move(point, true, hoverGuard);
}
if (Microbot.naturalMouse != null && !Thread.currentThread().isInterrupted()) {
Microbot.naturalMouse.moveTo(point.getX(), point.getY());
}
return true;
}
public boolean awaitDefaultAction(int id, SlotActionExecutor.Action a, net.runelite.api.Point point) {
return hoverGuard.getAsBoolean()
&& waitForUi(() -> !hoverGuard.getAsBoolean() || isSlotDefaultAction(id, a, point, key), 1800)
&& hoverGuard.getAsBoolean();
}
public boolean clickDefaultAction(int id, SlotActionExecutor.Action a, net.runelite.api.Point point) {
if (!hoverGuard.getAsBoolean() || !FlipperScript.this.isRunning() || Thread.currentThread().isInterrupted()
|| !isSlotDefaultAction(id, a, point, key)) return false;
// Reuse the verified point; a rectangle would choose a different point.
return variedHover ? mouseMotion.click(point, true, hoverGuard) : clickTradingMouse(point);
SlotActionExecutor.Ui ui = new SlotActionExecutor.Ui() {
private BooleanSupplier hoverGuard = () -> true;
private boolean variedHover;
public boolean slotSwapEnabled() { return isSlotActionSwapEnabled(); }
public net.runelite.api.Point actionPoint(int id, SlotActionExecutor.Action a) {
return slotActionPoint(id, a);
}
public boolean hover(net.runelite.api.Point point) {
// Mouse.move dispatches a single jump. Follow a smooth path on this
// script thread; never block the client thread for mouse movement.
if (randomizeMouseSpeed()) {
variedHover = true;
hoverGuard = mouseMovementGuard();
return mouseMotion.move(point, true, hoverGuard);
}
public boolean invokeAction(int id, SlotActionExecutor.Action a, net.runelite.api.Point point) {
if (Thread.currentThread().isInterrupted() || !sameSlotSuggestion(key)
|| isOfferScreenOpen() || slotActionPoint(id, a) == null) return false;
return invokeTradingMouse(new NewMenuEntry().option(a.option).target("")
.identifier(a.identifier).type(MenuAction.CC_OP).param0(2).param1(id)
.itemId(-1).forceLeftClick(false),
new Rectangle(point.getX() - 1, point.getY() - 1, 2, 2));
if (Microbot.naturalMouse != null && !Thread.currentThread().isInterrupted()) {
Microbot.naturalMouse.moveTo(point.getX(), point.getY());
}
});
return true;
}
public boolean awaitDefaultAction(int id, SlotActionExecutor.Action a, net.runelite.api.Point point) {
return hoverGuard.getAsBoolean()
&& waitForUi(() -> !hoverGuard.getAsBoolean() || isSlotDefaultAction(id, a, point, key), 1800)
&& hoverGuard.getAsBoolean();
}
public boolean clickDefaultAction(int id, SlotActionExecutor.Action a, net.runelite.api.Point point) {
if (!hoverGuard.getAsBoolean() || !FlipperScript.this.isRunning() || Thread.currentThread().isInterrupted()
|| !isSlotDefaultAction(id, a, point, key)) return false;
// Reuse the verified point; a rectangle would choose a different point.
return variedHover ? mouseMotion.click(point, true, hoverGuard) : clickTradingMouse(point);
}
public boolean invokeAction(int id, SlotActionExecutor.Action a, net.runelite.api.Point point) {
if (Thread.currentThread().isInterrupted() || !sameSlotSuggestion(key)
|| isOfferScreenOpen() || slotActionPoint(id, a) == null) return false;
return invokeTradingMouse(new NewMenuEntry().option(a.option).target("")
.identifier(a.identifier).type(MenuAction.CC_OP).param0(2).param1(id)
.itemId(-1).forceLeftClick(false),
new Rectangle(point.getX() - 1, point.getY() - 1, 2, 2));
}
};
SlotActionExecutor.Result result = SlotActionExecutor.execute(config.slotAction(), action, slotId, ui);
FlipperConfig.SlotAction fallback = slotActionFallback(config.slotAction(), result);
if (fallback != null) {
log.info("Copilot left-click for {} was not ready after hover; using explicit slot menu action.",
action.option);
result = SlotActionExecutor.execute(fallback, action, slotId, ui);
}
lastActionTime = System.currentTimeMillis();
actionCooldown = DEFAULT_ACTION_COOLDOWN;
if (handleSlotActionFailure(result, action, key)) return true;
Expand All @@ -1583,6 +1593,17 @@ && sameSlotSuggestion(key)) {
return true;
}
}
/**
* After a hovered left-click never becomes ready, run the explicit slot menu action once.
* Returns null when no fallback should run.
*/
static FlipperConfig.SlotAction slotActionFallback(FlipperConfig.SlotAction mode,
SlotActionExecutor.Result result) {
return result == SlotActionExecutor.Result.MENU_NOT_READY
&& mode == FlipperConfig.SlotAction.COPILOT_LEFT_CLICK
? FlipperConfig.SlotAction.MENU_OPTION : null;
}

/** Transient failures consume this tick without permanently blocking the suggestion. */
boolean handleSlotActionFailure(SlotActionExecutor.Result result, SlotActionExecutor.Action action, String key) {
if (result == SlotActionExecutor.Result.ACTED) return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package net.runelite.client.plugins.microbot.geflipper;

import java.awt.Rectangle;
import net.runelite.api.MenuAction;
import net.runelite.api.MenuEntry;
import net.runelite.api.Point;

/** Executes supported slot operations without falling back to "View offer". */
final class SlotActionExecutor {
public final class SlotActionExecutor {
enum Action {
ABORT("Abort offer", 2),
MODIFY("Modify offer", 3);
Expand Down Expand Up @@ -59,6 +60,17 @@ static boolean supportsAction(String[] actions, Action action) {
&& action.option.equals(actions[action.identifier - 1]);
}

/**
* Planned click point and live cursor must both lie on the slot button.
* Exact pixel equality is not required: natural mouse often settles nearby after a hover.
*/
public static boolean cursorOnSlotButton(Point mouse, Point planned, Rectangle buttonBounds) {
return mouse != null && planned != null && buttonBounds != null
&& buttonBounds.width >= 2 && buttonBounds.height >= 2
&& buttonBounds.contains(planned.getX(), planned.getY())
&& buttonBounds.contains(mouse.getX(), mouse.getY());
}

/** The last menu entry is the actual left-click operation after Copilot's swap. */
static boolean matchesDefaultAction(MenuEntry[] entries, int slotId, Action action) {
if (entries == null || entries.length == 0) return false;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package net.runelite.client.plugins.microbot.geflipper;

import java.awt.Rectangle;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
Expand Down
Loading