diff --git a/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabPlugin.java index b75908576f..6233dd29c1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabPlugin.java @@ -28,7 +28,7 @@ @Slf4j public class AmmoniteCrabPlugin extends Plugin { - public final static String version = "1.1.4"; + public final static String version = "1.1.6"; @Inject public AmmoniteCrabScript ammoniteCrabScript; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabScript.java b/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabScript.java index fe02d9dcf8..4706db437b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabScript.java @@ -13,15 +13,12 @@ import net.runelite.client.plugins.microbot.util.grounditem.LootingParameters; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.player.Rs2PlayerModel; import net.runelite.client.plugins.microbot.util.security.Login; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; -import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; -import java.awt.event.KeyEvent; import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -101,11 +98,11 @@ public boolean run(AmmoniteCrabConfig config) { break; case HOP_WORLD: int world = Login.getRandomWorld(true, null); - Microbot.hopToWorld(world); + if (!Microbot.hopToWorld(world)) { + Microbot.log("World hop to " + world + " refused, retrying next loop"); + break; + } timesHopped++; - boolean result = sleepUntil(() -> Rs2Widget.findWidget("Switch World") != null); - Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); - sleepUntil(() -> Microbot.getClient().getGameState() == GameState.HOPPING); sleepUntil(() -> Microbot.getClient().getGameState() == GameState.LOGGED_IN); sleep(1200, 2000); // After successful hop, reset counters and scan the area diff --git a/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperPlugin.java index c4c679e13b..c2a4100c4c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperPlugin.java @@ -36,7 +36,7 @@ ) @Slf4j public class BanksShopperPlugin extends Plugin { - public final static String version = "1.4.2"; + public final static String version = "1.4.3"; @Inject private BanksShopperConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperScript.java b/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperScript.java index 2670596c54..1ee85224b7 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperScript.java @@ -158,8 +158,9 @@ public boolean run(BanksShopperConfig config) { state = ShopperState.SHOPPING; break; case HOPPING: - hopWorld(); - state = ShopperState.SHOPPING; + if (hopWorld()) { + state = ShopperState.SHOPPING; + } break; } } catch (Exception ex) { @@ -298,13 +299,17 @@ private boolean ensureShopOpen() { /** * Hops to a new world */ - private void hopWorld() { + private boolean hopWorld() { System.out.println("Hopping worlds..."); Rs2Random.waitEx(3200, 800); // this sleep is required to avoid the message: please finish what you're doing before using the world switcher. int world = plugin.isUseNextWorld() ? Login.getNextWorld(Rs2Player.isMember()) : Login.getRandomWorld(Rs2Player.isMember()); - sleepUntil(() -> Microbot.hopToWorld(world), 15000); + if (!Microbot.hopToWorld(world)) { + System.out.println("World hop to " + world + " refused, retrying next loop"); + return false; + } System.out.println("Successfully hopped to world: " + world); + return true; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsPlugin.java index 52c38073ac..dafed31b83 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsPlugin.java @@ -27,7 +27,7 @@ ) public class BlueDragonsPlugin extends Plugin { - public static final String version = "1.2.0"; + public static final String version = "1.2.1"; static final String CONFIG = "bluedragons"; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsScript.java b/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsScript.java index d8d09082fb..e06d59f909 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsScript.java @@ -187,7 +187,9 @@ private void handleBanking() { private void handleTravelToDragons() { if (isPlayerAtSafeSpot()) { - currentState = BlueDragonState.COMBAT; + if (hopIfPlayerAtSafeSpot()) { + currentState = BlueDragonState.COMBAT; + } return; } @@ -199,8 +201,7 @@ private void handleTravelToDragons() { sleepUntil(this::isPlayerAtSafeSpot, 10000); } - if (isPlayerAtSafeSpot()) { - hopIfPlayerAtSafeSpot(); + if (isPlayerAtSafeSpot() && hopIfPlayerAtSafeSpot()) { currentState = BlueDragonState.COMBAT; } } @@ -343,17 +344,23 @@ private boolean isPlayerAtSafeSpot() { Microbot.getClient().getLocalPlayer().getWorldLocation())); } - private void hopIfPlayerAtSafeSpot() { + private boolean hopIfPlayerAtSafeSpot() { List players = Rs2Player.getPlayers(it -> it != null).collect(Collectors.toList()); boolean occupied = players.stream().anyMatch(p -> p != null && !p.equals(Microbot.getClient().getLocalPlayer()) && p.getWorldLocation().distanceTo(SAFE_SPOT) <= 1); - if (occupied) { - Microbot.log("Player at safe spot. Hopping worlds."); - Microbot.hopToWorld(findRandomWorld()); - sleep(5000); + if (!occupied) { + return true; } + Microbot.log("Player at safe spot. Hopping worlds."); + int world = findRandomWorld(); + if (!Microbot.hopToWorld(world)) { + Microbot.log("World hop to " + world + " refused, retrying next loop"); + return false; + } + sleep(5000); + return true; } private int findRandomWorld() { diff --git a/src/main/java/net/runelite/client/plugins/microbot/giantmole/GiantMolePlugin.java b/src/main/java/net/runelite/client/plugins/microbot/giantmole/GiantMolePlugin.java index 1e9f60ba60..e64d24583a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/giantmole/GiantMolePlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/giantmole/GiantMolePlugin.java @@ -32,7 +32,7 @@ ) @Slf4j public class GiantMolePlugin extends Plugin { - public static final String version = "0.0.5"; + public static final String version = "0.0.6"; @Inject private GiantMoleConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/giantmole/GiantMoleScript.java b/src/main/java/net/runelite/client/plugins/microbot/giantmole/GiantMoleScript.java index c10bb2ffeb..01308061e4 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/giantmole/GiantMoleScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/giantmole/GiantMoleScript.java @@ -230,7 +230,11 @@ public static boolean isInMoleTunnel() public void hopWorlds() { int randomWorld = Login.getRandomWorld(isMember()); - Microbot.hopToWorld(randomWorld); + if (!Microbot.hopToWorld(randomWorld)) + { + Microbot.log("World hop to " + randomWorld + " refused, retrying next loop"); + return; + } checkedIfWorldOccupied = false; state = GiantMoleState.IDLE; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/hsblackjack/HSBlackJackPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/hsblackjack/HSBlackJackPlugin.java index 056776637a..75e78196a5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/hsblackjack/HSBlackJackPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/hsblackjack/HSBlackJackPlugin.java @@ -27,7 +27,7 @@ @Slf4j public class HSBlackJackPlugin extends Plugin { - static final String version = "1.0.1"; + static final String version = "1.0.2"; @Inject private HSBlackJackConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/hsblackjack/HSBlackJackScript.java b/src/main/java/net/runelite/client/plugins/microbot/hsblackjack/HSBlackJackScript.java index b42f62b8e2..0208df8e60 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/hsblackjack/HSBlackJackScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/hsblackjack/HSBlackJackScript.java @@ -685,10 +685,14 @@ private Rs2NpcModel findReachableTarget(String targetName) { * Hops to a random world - used when the tent stays blocked too long, the * bar's stock is too low, or a lure's NPC never makes it inside in time. */ - private void hopWorld() { + private boolean hopWorld() { sleep(600, 1000); int world = Login.getRandomWorld(Rs2Player.isMember()); - sleepUntil(() -> Microbot.hopToWorld(world), 15000); + if (Microbot.hopToWorld(world)) { + return true; + } + setState("World hop to " + world + " refused - retrying next loop"); + return false; } /** @@ -768,8 +772,9 @@ private void handleCheckTentOccupancy() { if (allInTent.size() != 1 || targetsInTent.size() != 1) { setState("Tent occupied by " + allInTent.size() + " NPC(s), not a clean single target - hopping world"); - hopWorld(); - phase = Phase.TRAVEL_TO_START; + if (hopWorld()) { + phase = Phase.TRAVEL_TO_START; + } return; } @@ -1013,7 +1018,9 @@ private void handleCloseCurtain() { if (waitedFor >= NPC_ENTER_TENT_TIMEOUT_MS) { setState("NPC never entered tent (" + (waitedFor / 1000) + "s) - hopping world, restarting"); - hopWorld(); + if (!hopWorld()) { + return; + } npcEnterTentWaitSince = -1; luredNpc = null; needsLureInsideTent = false; @@ -1062,7 +1069,9 @@ private void handleKnockOut() { if (blockedFor >= ROOM_BLOCKED_HOP_THRESHOLD_MS) { setState("Tent occupied too long (" + (blockedFor / 1000) + "s) - hopping world"); - hopWorld(); + if (!hopWorld()) { + return; + } roomBlockedSince = -1; luredNpc = null; phase = Phase.TRAVEL_TO_START; @@ -1219,8 +1228,9 @@ private void handleRestockFlow() { long stuckFor = System.currentTimeMillis() - restockCurtainStuckSince; if (stuckFor >= CURTAIN_STUCK_TIMEOUT_MS) { setState("Restock: stuck opening curtain for " + (stuckFor / 1000) + "s - hopping world"); - hopWorld(); - restockCurtainStuckSince = -1; + if (hopWorld()) { + restockCurtainStuckSince = -1; + } return; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/microhunter/AutoHunterPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/microhunter/AutoHunterPlugin.java index 5b39219b00..7e420ab3a2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/microhunter/AutoHunterPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/microhunter/AutoHunterPlugin.java @@ -31,7 +31,7 @@ @Slf4j @PluginDependency(HunterPlugin.class) public class AutoHunterPlugin extends Plugin { - public static final String version = "1.5.0"; + public static final String version = "1.5.4"; @Inject private AutoHunterConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/microhunter/scripts/AutoChinScript.java b/src/main/java/net/runelite/client/plugins/microbot/microhunter/scripts/AutoChinScript.java index 5f88d2a016..9cf1ad54c6 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/microhunter/scripts/AutoChinScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/microhunter/scripts/AutoChinScript.java @@ -17,11 +17,15 @@ import net.runelite.client.plugins.microbot.breakhandler.BreakHandlerScript; import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; import net.runelite.client.plugins.microbot.util.antiban.enums.ActivityIntensity; +import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; +import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; +import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; import net.runelite.client.plugins.microbot.microhunter.AutoHunterConfig; import net.runelite.client.plugins.microbot.util.menu.NewMenuEntry; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.player.Rs2PlayerModel; +import net.runelite.client.plugins.microbot.util.shop.Rs2Shop; import net.runelite.client.plugins.microbot.util.tile.Rs2Tile; import net.runelite.http.api.worlds.World; import net.runelite.http.api.worlds.WorldRegion; @@ -84,9 +88,13 @@ private enum Action { private static final long OCCUPANCY_SCAN_MS = 7_000; private static final long PLAYER_PERSISTENCE_MS = 3_500; private static final long WORLD_HOP_TIMEOUT_MS = 15_000; + private static final long REFUSED_HOP_RETRY_MS = 3_000; + private static final int MAX_REFUSED_HOPS_PER_WORLD = 3; + private static final long LOCAL_HOP_BLOCK_LIMIT_MS = 180_000; private static final long REJECTED_WORLD_COOLDOWN_MS = 600_000; private static final long BREAK_RECOVERY_CLEAR_MS = 1_500; private static final int MAX_WORLD_HOP_ATTEMPTS = 5; + private static final String HOP_BLOCKER_DIALOGUE = "dialogue open"; private static final EnumSet UNSAFE_WORLD_TYPES = EnumSet.of( WorldType.PVP, WorldType.BOUNTY, WorldType.PVP_ARENA, WorldType.SKILL_TOTAL, WorldType.QUEST_SPEEDRUNNING, WorldType.HIGH_RISK, WorldType.LAST_MAN_STANDING, @@ -140,6 +148,10 @@ private enum Action { private int hopTargetWorld = -1; private int hopSourceWorld = -1; private long hopStartedAt; + private boolean hopRefused; + private int hopRefusals; + private String hopBlocker; + private long hopBlockedSince; private boolean breakRecoveryRequested; private boolean breakRecoveryComplete; private boolean breakRecoveryLockHeld; @@ -196,6 +208,10 @@ private void resetSession() { hopTargetWorld = -1; hopSourceWorld = -1; hopStartedAt = 0; + hopRefused = false; + hopRefusals = 0; + hopBlocker = null; + hopBlockedSince = 0; breakRecoveryRequested = false; breakRecoveryComplete = false; breakRecoveryLockHeld = false; @@ -307,6 +323,10 @@ private void beginWorldArrival(AutoHunterConfig config, int world) { hopTargetWorld = -1; hopSourceWorld = -1; hopStartedAt = 0; + hopRefused = false; + hopRefusals = 0; + hopBlocker = null; + hopBlockedSince = 0; nearbyPlayerSeenSince = 0; if (!config.avoidOccupiedWorlds()) { worldHopAttempts = 0; @@ -426,14 +446,58 @@ private void requestAustralianWorldHop(String reason) { stopSafely("No eligible Australian world is currently available"); return; } - worldHopAttempts++; hopSourceWorld = Microbot.getClient().getWorld(); hopTargetWorld = target; - hopStartedAt = System.currentTimeMillis(); + hopRefusals = 0; + hopBlocker = null; transition(State.HOPPING_WORLD, "Hopping to Australian world " + target + " (" + reason + ")"); Microbot.log("AutoHunter occupancy: hopping " + hopSourceWorld + " -> " + target - + " attempt=" + worldHopAttempts); - Microbot.hopToWorld(target); + + " attempt=" + (worldHopAttempts + 1)); + issueWorldHop(); + } + + private void issueWorldHop() { + hopStartedAt = System.currentTimeMillis(); + String blocker = localHopBlocker(); + if (blocker == null) { + if (Microbot.hopToWorld(hopTargetWorld)) { + hopRefused = false; + hopBlocker = null; + hopBlockedSince = 0; + worldHopAttempts++; + return; + } + blocker = localHopBlocker(); + } + hopRefused = true; + if (blocker != null) { + if (hopBlockedSince == 0) hopBlockedSince = hopStartedAt; + long blockedFor = hopStartedAt - hopBlockedSince; + if (blockedFor >= LOCAL_HOP_BLOCK_LIMIT_MS) { + stopSafely("World hop blocked locally for " + (blockedFor / 1_000) + "s: " + blocker); + return; + } + if (!blocker.equals(hopBlocker)) { + Microbot.log("AutoHunter occupancy: hop to world " + hopTargetWorld + " waiting (" + blocker + ")"); + } + hopBlocker = blocker; + if (HOP_BLOCKER_DIALOGUE.equals(blocker)) Rs2Dialogue.clickContinue(); + return; + } + hopBlocker = null; + hopBlockedSince = 0; + hopRefusals++; + Microbot.log("AutoHunter occupancy: hop to world " + hopTargetWorld + " refused (" + + hopRefusals + "/" + MAX_REFUSED_HOPS_PER_WORLD + ")"); + } + + private String localHopBlocker() { + if (!Microbot.isLoggedIn()) return "not logged in"; + if (Rs2Player.isInCombat() || Rs2Combat.inCombat()) return "in combat"; + if (Rs2Bank.isOpen() || Rs2Shop.isOpen()) return "interface open"; + if (Rs2Dialogue.isInDialogue()) return HOP_BLOCKER_DIALOGUE; + if (Rs2Player.isAnimating()) return "animating"; + return null; } private Integer selectAustralianWorld() { @@ -460,6 +524,19 @@ private Integer selectAustralianWorld() { private void monitorWorldHop() { int currentWorld = Microbot.getClient().getWorld(); if (hopSourceWorld > 0 && currentWorld != hopSourceWorld) return; + if (hopRefused) { + if (System.currentTimeMillis() - hopStartedAt < REFUSED_HOP_RETRY_MS) return; + if (hopRefusals < MAX_REFUSED_HOPS_PER_WORLD) { + issueWorldHop(); + return; + } + worldHopAttempts++; + rejectedWorlds.put(hopTargetWorld, System.currentTimeMillis() + REJECTED_WORLD_COOLDOWN_MS); + Microbot.log("AutoHunter occupancy: giving up on world " + hopTargetWorld + " after " + + hopRefusals + " refused hops"); + requestAustralianWorldHop("previous hop refused"); + return; + } if (System.currentTimeMillis() - hopStartedAt < WORLD_HOP_TIMEOUT_MS) return; if (hopTargetWorld > 0) { rejectedWorlds.put(hopTargetWorld, System.currentTimeMillis() + REJECTED_WORLD_COOLDOWN_MS); diff --git a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtPlugin.java index e54d0fca9b..8b21d996ef 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtPlugin.java @@ -42,7 +42,7 @@ ) @Slf4j public class MKE_WintertodtPlugin extends Plugin { - static final String version = "2.2.3"; + static final String version = "2.2.4"; // Core plugin components @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java index 11abd36f88..e132b42850 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java @@ -126,6 +126,8 @@ public class MKE_WintertodtScript extends Script { private boolean wasOnBreak = false; private boolean wasOnBreakHandlerBreak = false; private boolean worldChecked = false; // Track if we've already checked/hopped to Wintertodt world + private static final long REFUSED_HOP_RETRY_MS = 5_000; + private long lastRefusedHopAt = 0; // Wintertodt round timer tracking private long roundEndTime = -1; @@ -3902,9 +3904,9 @@ private void logCurrentGearSetup(WintertodtGearManager gearManager) { /** * Ensures the player is on a Wintertodt world, hopping if necessary. * Only hops when in a safe location (not in combat, not interacting, etc.). - * Stops script and logs out on failure. + * A refused hop is retried on a later call. * - * @return true if on Wintertodt world or successfully hopped, false if failed (script should stop) + * @return false only if the world check itself failed (script should stop) */ private boolean ensureWintertodtWorldSafely() { try { @@ -3932,16 +3934,17 @@ private boolean ensureWintertodtWorldSafely() { return true; // Don't fail, just retry later } + if (System.currentTimeMillis() - lastRefusedHopAt < REFUSED_HOP_RETRY_MS) { + return true; + } + // Not on a Wintertodt world, need to hop Microbot.log("Not on a Wintertodt world (current: " + currentWorld + ") - hopping to a Wintertodt world..."); - boolean hopSuccess = hopToWintertodtWorldSafely(wintertodtWorlds); - - if (!hopSuccess) { - // Hop failed - stop script and logout - Microbot.log("CRITICAL: Failed to hop to Wintertodt world - stopping script and logging out"); - stopScriptAndLogout(); - return false; + if (!hopToWintertodtWorldSafely(wintertodtWorlds)) { + lastRefusedHopAt = System.currentTimeMillis(); + Microbot.log("World hop to a Wintertodt world refused - will try again later"); + return true; } worldChecked = true; // Mark as checked after successful hop @@ -3999,30 +4002,6 @@ private boolean isSafeToHopWorlds() { } } - /** - * Stops the script and logs out the player. - */ - private void stopScriptAndLogout() { - try { - Microbot.log("Stopping script due to critical error..."); - - // Stop the script - this.shutdown(); - - // Wait a moment for shutdown to process - sleepGaussian(3000, 500); - - // Logout the player - if (Microbot.isLoggedIn()) { - Microbot.log("Logging out player..."); - Rs2Player.logout(); - } - - } catch (Exception e) { - Microbot.log("Error during script stop and logout: " + e.getMessage()); - } - } - /** * Hops to a random Wintertodt world from the available options. * @@ -4031,105 +4010,13 @@ private void stopScriptAndLogout() { */ private boolean hopToWintertodtWorldSafely(int[] wintertodtWorlds) { try { - // Select a random Wintertodt world int targetWorld = wintertodtWorlds[Rs2Random.between(0, wintertodtWorlds.length - 1)]; - int originalWorld = Rs2Player.getWorld(); - Microbot.log("Attempting to hop to Wintertodt world " + targetWorld + "..."); - - // Perform the world hop (NOTE: Microbot.hopToWorld() has a bug - returns false even on success) - Microbot.hopToWorld(targetWorld); - - sleep(1000); - - // If the world hop failed, try again (This happens basically every time) - if (Rs2Player.getWorld() != targetWorld) { - Microbot.hopToWorld(targetWorld); - } - - // Don't rely on return value - wait and check if world actually changed - Microbot.log("World hop initiated, waiting for completion..."); - - // Wait for world hop to complete (up to 15 seconds) - long hopStartTime = System.currentTimeMillis(); - boolean hopCompleted = sleepUntilTrue(() -> { - int currentWorld = Rs2Player.getWorld(); - // Success if we reach target world or any other Wintertodt world - if (currentWorld == targetWorld && Microbot.isLoggedIn()) { - return true; - } - // Also accept if we ended up on any Wintertodt world - for (int wintertodtWorld : wintertodtWorlds) { - if (currentWorld == wintertodtWorld && Microbot.isLoggedIn()) { - return true; - } - } + if (!Microbot.hopToWorld(targetWorld)) { return false; - }, 100, 15000); - - int finalWorld = Rs2Player.getWorld(); - - if (hopCompleted) { - Microbot.log("Successfully hopped to Wintertodt world " + finalWorld); - return true; - } else if (finalWorld != originalWorld) { - // We hopped somewhere, but not to a Wintertodt world - this is partial success - Microbot.log("Hopped to world " + finalWorld + " but not a Wintertodt world - trying to hop again"); - - // Try once more to a Wintertodt world - int retryWorld = wintertodtWorlds[Rs2Random.between(0, wintertodtWorlds.length - 1)]; - Microbot.log("Attempting second hop to Wintertodt world " + retryWorld + "..."); - Microbot.hopToWorld(retryWorld); - - boolean retryCompleted = sleepUntilTrue(() -> { - int currentWorld = Rs2Player.getWorld(); - for (int wintertodtWorld : wintertodtWorlds) { - if (currentWorld == wintertodtWorld && Microbot.isLoggedIn()) { - return true; - } - } - return false; - }, 100, 15000); - - if (retryCompleted) { - Microbot.log("Successfully hopped to Wintertodt world " + Rs2Player.getWorld() + " on second attempt"); - return true; - } else { - Microbot.log("Second hop attempt failed - still on world " + Rs2Player.getWorld()); - } - } else { - Microbot.log("World hop failed - still on original world " + originalWorld + " after 15 seconds"); } - - // Final attempt with a different world if we have multiple options - if (wintertodtWorlds.length > 1) { - int finalAttemptWorld; - do { - finalAttemptWorld = wintertodtWorlds[Rs2Random.between(0, wintertodtWorlds.length - 1)]; - } while (finalAttemptWorld == targetWorld && wintertodtWorlds.length > 1); - - Microbot.log("Final attempt - trying Wintertodt world " + finalAttemptWorld + "..."); - Microbot.hopToWorld(finalAttemptWorld); - - boolean finalCompleted = sleepUntilTrue(() -> { - int currentWorld = Rs2Player.getWorld(); - for (int wintertodtWorld : wintertodtWorlds) { - if (currentWorld == wintertodtWorld && Microbot.isLoggedIn()) { - return true; - } - } - return false; - }, 100, 15000); - - if (finalCompleted) { - Microbot.log("Successfully hopped to Wintertodt world " + Rs2Player.getWorld() + " on final attempt"); - return true; - } - } - - Microbot.log("CRITICAL: Failed to hop to any Wintertodt world after multiple attempts"); - return false; - + Microbot.log("Successfully hopped to Wintertodt world " + Rs2Player.getWorld()); + return true; } catch (Exception e) { Microbot.log("Error during world hop: " + e.getMessage()); e.printStackTrace(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerScript.java b/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerScript.java index 3cccbcfe37..058841d316 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerScript.java @@ -548,7 +548,9 @@ public void hopWorldsBasedOnTimer(){ return; } } - hopToNewWorld(); + if(!hopToNewWorld()){ + return; + } startTime = currentTime; howLongUntilHop = config.hopInMinutes(); int mixUp = Rs2Random.between(5,10); @@ -892,21 +894,21 @@ public void reJfount(){ } } - public void hopToNewWorld(){ + public boolean hopToNewWorld(){ // Thank you george! World currentWorld = Microbot.getWorldService().getWorlds().findWorld(Microbot.getClient().getWorld()); if(currentWorld == null){ Microbot.log("Couldn't find our world"); - return; + return false; } int newRandomWorld = Login.getRandomWorld(true, currentWorld.getRegion()); if(newRandomWorld == 0){ Microbot.log("Couldn't find a new random world"); - return; + return false; } Microbot.log("We're going to world " + newRandomWorld); @@ -928,7 +930,12 @@ public void hopToNewWorld(){ sleep(500,700); attempts++; } + if(Rs2Player.getWorld() != newRandomWorld){ + Microbot.log("World hop to " + newRandomWorld + " refused, retrying later"); + return false; + } hoppedWorld = true; + return true; } public void equipArrows(){ diff --git a/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabPlugin.java index e1e40ce765..22ac529283 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabPlugin.java @@ -26,7 +26,7 @@ ) @Slf4j public class SandCrabPlugin extends Plugin { - public final static String version = "1.5.4"; + public final static String version = "1.5.6"; @Inject private SandCrabConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabScript.java b/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabScript.java index 5920491f2e..6639fb7089 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabScript.java @@ -10,7 +10,6 @@ import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; 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.math.Rs2Random; import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; @@ -18,9 +17,7 @@ import net.runelite.client.plugins.microbot.util.security.Login; import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; -import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; -import java.awt.event.KeyEvent; import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; @@ -162,15 +159,11 @@ public boolean run(SandCrabConfig config, SandCrabPlugin plugin) { break; case HOP_WORLD: int world = Login.getRandomWorld(true, null); - boolean isHopped = Microbot.hopToWorld(world); - if (isHopped) { - boolean result = sleepUntil(() -> Rs2Widget.findWidget("Switch World") != null); - if (result) { - Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); - sleepUntil(() -> Microbot.getClient().getGameState() == GameState.HOPPING); - sleepUntil(() -> Microbot.getClient().getGameState() == GameState.LOGGED_IN); - } + if (!Microbot.hopToWorld(world)) { + Microbot.log("World hop to " + world + " refused, retrying next loop"); + break; } + sleepUntil(() -> Microbot.getClient().getGameState() == GameState.LOGGED_IN); if (timesHopped > 10) { timesHopped = 0; diff --git a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarPlugin.java index 3853812c87..45d9ed1082 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarPlugin.java @@ -22,6 +22,7 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import javax.inject.Inject; @@ -34,10 +35,13 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.ChatMessageType; import net.runelite.api.GameState; +import net.runelite.api.WorldType; import net.runelite.api.coords.WorldArea; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.ChatMessage; import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.VarbitChanged; +import net.runelite.api.gameval.VarPlayerID; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.ConfigChanged; @@ -49,7 +53,6 @@ import net.runelite.client.plugins.microbot.shootingstar.model.Star; import net.runelite.client.plugins.microbot.shortestpath.ShortestPathPlugin; import net.runelite.client.plugins.microbot.shortestpath.pathfinder.Pathfinder; -import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.task.Schedule; import net.runelite.client.ui.ClientToolbar; @@ -73,10 +76,10 @@ public class ShootingStarPlugin extends Plugin { - static final String version = "1.4.7"; + static final String version = "1.4.10"; @Getter - private final List starList = new ArrayList<>(); + private final CopyOnWriteArrayList starList = new CopyOnWriteArrayList<>(); @Inject private ShootingStarScript shootingStarScript; @@ -114,9 +117,10 @@ ShootingStarConfig provideConfig(ConfigManager configManager) @Getter private boolean displayAsMinutes; @Getter - private boolean hideMembersWorlds; + private volatile boolean hideMembersWorlds; @Getter - private boolean hideF2PWorlds; + private volatile boolean hideF2PWorlds; + private volatile boolean worldFilterResolved; private Set blacklistedLocations = new HashSet<>(); @@ -124,8 +128,10 @@ ShootingStarConfig provideConfig(ConfigManager configManager) protected void startUp() throws AWTException { displayAsMinutes = config.isDisplayAsMinutes(); - hideMembersWorlds = !Rs2Player.isInMemberWorld(); - hideF2PWorlds = Rs2Player.isInMemberWorld(); + Optional membersAccount = Microbot.getClientThread().runOnClientThreadOptional( + () -> Microbot.isLoggedIn() ? isMembersAccount() : null); + worldFilterResolved = membersAccount.isPresent(); + applyWorldFilter(membersAccount.orElse(false)); loadBlacklistedLocations(); fetchStars(); @@ -142,6 +148,7 @@ protected void shutDown() removePanel(); starList.clear(); lastWorld = -1; + worldFilterResolved = false; apiTickCounter.set(0); overlayManager.remove(shootingStarOverlay); } @@ -213,8 +220,22 @@ public void tick() { @Subscribe public void onGameStateChanged(GameStateChanged event) { + if (event.getGameState() == GameState.LOGIN_SCREEN) + { + worldFilterResolved = false; + return; + } + if (event.getGameState() == GameState.LOGGED_IN) { + if (!worldFilterResolved) + { + worldFilterResolved = true; + applyWorldFilter(isMembersAccount()); + updateHiddenStars(); + updatePanelList(true); + } + if (lastWorld == -1) { lastWorld = Microbot.getClient().getWorld(); @@ -233,6 +254,24 @@ public void onGameStateChanged(GameStateChanged event) } } + @Subscribe + public void onVarbitChanged(VarbitChanged event) + { + if (event.getVarpId() != VarPlayerID.ACCOUNT_CREDIT || event.getValue() <= 0) + { + return; + } + + if (!worldFilterResolved || !hideMembersWorlds) + { + return; + } + + applyWorldFilter(true); + updateHiddenStars(); + updatePanelList(true); + } + @Subscribe public void onChatMessage(ChatMessage event) { if (event.getType() != ChatMessageType.GAMEMESSAGE) return; @@ -243,6 +282,22 @@ public void onChatMessage(ChatMessage event) { } } + private void applyWorldFilter(boolean membersAccount) + { + hideMembersWorlds = !membersAccount; + hideF2PWorlds = membersAccount; + } + + private static boolean isMembersAccount() + { + return Microbot.getClient().getVarpValue(VarPlayerID.ACCOUNT_CREDIT) > 0 || isOnMembersWorld(); + } + + static boolean isOnMembersWorld() + { + return Microbot.getClient().getWorldType().contains(WorldType.MEMBERS); + } + public void fetchStars() { List latestStars = shootingStarApiClient.getStarData(config.getProvider()); @@ -264,8 +319,10 @@ public void fetchStars() } // If oldStar not found, add new star into the list - starList.add(star); - fullUpdate = true; + if (starList.addIfAbsent(star)) + { + fullUpdate = true; + } } if (fullUpdate) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarScript.java b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarScript.java index 9694832728..5b8f02c5a2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarScript.java @@ -7,7 +7,6 @@ import java.util.stream.Collectors; import javax.inject.Inject; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.GameState; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ObjectID; import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; @@ -40,6 +39,7 @@ @Slf4j public class ShootingStarScript extends Script { + static final int STAR_ARRIVAL_DISTANCE = 6; public static ShootingStarState state; private final ShootingStarPlugin plugin; private final ShootingStarConfig config; @@ -48,6 +48,7 @@ public class ShootingStarScript extends Script private Star currentStar; private boolean hasEquipment = false; private boolean hasInventory = false; + private Star walkBeforeHopStar; public Instant startTime; @@ -75,6 +76,7 @@ public boolean run() initialPlayerLocation = null; hasEquipment = false; hasInventory = false; + walkBeforeHopStar = null; Rs2Antiban.resetAntibanSettings(); applyAntiBanSettings(); Rs2Antiban.setActivity(Activity.GENERAL_MINING); @@ -167,33 +169,7 @@ public boolean run() state = ShootingStarState.WALKING; break; case WALKING: - toggleLockState(true); - - if (Rs2Player.getWorld() != currentStar.getWorld()) - { - Microbot.hopToWorld(currentStar.getWorld()); - sleepUntil(() -> Microbot.getClient().getGameState() == GameState.LOGGED_IN); - return; - } - - boolean isNearShootingStar = Rs2Player.getWorldLocation().distanceTo(currentStar.getShootingStarLocation().getWorldPoint()) < 6; - - if (!isNearShootingStar) - { - WalkerState walkerState = Rs2Walker.walkWithState(currentStar.getShootingStarLocation().getWorldPoint(), 6); - if (walkerState == WalkerState.UNREACHABLE) - { - log.debug("Walker State is {}, removing star from list", walkerState); - plugin.removeStar(plugin.getSelectedStar()); - plugin.updatePanelList(true); - state = ShootingStarState.WAITING_FOR_STAR; - } - return; - } - - initialPlayerLocation = Rs2Player.getWorldLocation(); - - state = ShootingStarState.MINING; + handleWalking(currentStar); break; case MINING: if (!currentStar.hasRequirements()) @@ -332,6 +308,90 @@ public void shutdown() Rs2Antiban.resetAntibanSettings(); } + void handleWalking(Star star) + { + toggleLockState(true); + + boolean isOnStarWorld = Rs2Player.getWorld() == star.getWorld(); + boolean isNearShootingStar = isNearStar(star); + + if (!isOnStarWorld && (isNearShootingStar || !shouldWalkBeforeHop(star))) + { + hopToStarWorld(star, isNearShootingStar); + return; + } + + if (!isNearShootingStar) + { + WalkerState walkerState = Rs2Walker.walkWithState(star.getShootingStarLocation().getWorldPoint(), STAR_ARRIVAL_DISTANCE); + if (walkerState == WalkerState.UNREACHABLE) + { + log.debug("Walker State is {}, removing star from list", walkerState); + plugin.removeStar(plugin.getSelectedStar()); + plugin.updatePanelList(true); + state = ShootingStarState.WAITING_FOR_STAR; + return; + } + if (walkerState != WalkerState.ARRIVED) + { + return; + } + if (!isOnStarWorld) + { + hopToStarWorld(star, true); + return; + } + if (!isNearStar(star)) + { + log.debug("Walker reported {} but the player is still more than {} tiles from the star, retrying", walkerState, STAR_ARRIVAL_DISTANCE); + return; + } + } + + initialPlayerLocation = Rs2Player.getWorldLocation(); + + state = ShootingStarState.MINING; + } + + private static boolean isNearStar(Star star) + { + return Rs2Player.getWorldLocation().distanceTo(star.getShootingStarLocation().getWorldPoint()) <= STAR_ARRIVAL_DISTANCE; + } + + private boolean shouldWalkBeforeHop(Star star) + { + if (star.isMemberWorld()) + { + return false; + } + return star.equals(walkBeforeHopStar) || isOnMembersWorld(); + } + + private static boolean isOnMembersWorld() + { + return Microbot.getClientThread().runOnClientThreadOptional(ShootingStarPlugin::isOnMembersWorld).orElse(false); + } + + private void hopToStarWorld(Star star, boolean isNearShootingStar) + { + if (isNearShootingStar && !star.isMemberWorld() && Microbot.cantHopWorld) + { + Microbot.cantHopWorld = false; + } + + if (Microbot.hopToWorld(star.getWorld())) + { + walkBeforeHopStar = null; + return; + } + + if (!star.isMemberWorld() && !isNearShootingStar) + { + log.info("Hop to non-members world {} failed, walking to the star before hopping again", star.getWorld()); + walkBeforeHopStar = star; + } + } + private boolean isUsingInventorySetup() { boolean isInventorySetupPluginEnabled = Microbot.isPluginEnabled(MInventorySetupsPlugin.class); diff --git a/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerPlugin.java index 45196fa27b..6fd51890e1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerPlugin.java @@ -42,7 +42,7 @@ @Slf4j public class SlayerPlugin extends Plugin { - static final String version = "1.1.1"; + static final String version = "1.1.3"; @Inject private SlayerConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerScript.java b/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerScript.java index 51fb7654a6..2ef14097a7 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerScript.java @@ -110,9 +110,11 @@ public class SlayerScript extends Script { // Crash detection / world hop tracking private long crashDetectionStartTime = 0; private long lastWorldHopTime = 0; + private long lastHopRefusedAt = 0; private boolean isSearchingForTargets = false; private static final int CRASH_HOP_DELAY_MS = 20000; // 20 seconds before hopping private static final int WORLD_HOP_COOLDOWN_MS = 60000; // 1 minute cooldown between hops + private static final int REFUSED_HOP_RETRY_MS = 10000; // Task completion loot delay private long taskCompletedTime = 0; @@ -2898,7 +2900,11 @@ private void handleCombat() { if (timeSinceStart >= CRASH_HOP_DELAY_MS) { // Check cooldown long timeSinceLastHop = System.currentTimeMillis() - lastWorldHopTime; - if (timeSinceLastHop >= WORLD_HOP_COOLDOWN_MS) { + long timeSinceRefusedHop = System.currentTimeMillis() - lastHopRefusedAt; + if (timeSinceRefusedHop < REFUSED_HOP_RETRY_MS) { + log.debug("Last world hop was refused, retrying in {} seconds", + (REFUSED_HOP_RETRY_MS - timeSinceRefusedHop) / 1000); + } else if (timeSinceLastHop >= WORLD_HOP_COOLDOWN_MS) { log.info("No targets found for {} seconds - hopping worlds (likely crashed)", CRASH_HOP_DELAY_MS / 1000); hopToNewWorld(); @@ -2935,26 +2941,31 @@ private void handleCombat() { */ private void hopToNewWorld() { try { - // Disable prayers before hopping - deactivateProfilePrayer(); - deactivateOffensivePrayers(); + if (Rs2Combat.inCombat()) { + lastHopRefusedAt = System.currentTimeMillis(); + log.info("In combat, delaying world hop"); + return; + } // Get world from config list or random int world = getHopWorld(); if (world <= 0) { + lastHopRefusedAt = System.currentTimeMillis(); log.warn("Could not determine a valid world to hop to"); return; } - log.info("Hopping to world {}", world); - Microbot.hopToWorld(world); + // Disable prayers before hopping + deactivateProfilePrayer(); + deactivateOffensivePrayers(); + log.info("Hopping to world {}", world); - // Wait for switch world confirmation dialog - sleepUntil(() -> Rs2Widget.findWidget("Switch World") != null, 5000); - Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); + if (!Microbot.hopToWorld(world)) { + lastHopRefusedAt = System.currentTimeMillis(); + log.warn("World hop to {} refused, retrying in {} seconds", world, REFUSED_HOP_RETRY_MS / 1000); + return; + } - // Wait for hop to complete - sleepUntil(() -> Microbot.getClient().getGameState() == GameState.HOPPING, 5000); sleepUntil(() -> Microbot.getClient().getGameState() == GameState.LOGGED_IN, 10000); // Brief pause after hopping diff --git a/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingPlugin.java index 229f40dd04..0e8848eb7c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingPlugin.java @@ -34,7 +34,7 @@ ) @Slf4j public class ThievingPlugin extends Plugin { - public static final String version = "2.1.2"; + public static final String version = "2.1.5"; @Inject @Getter diff --git a/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingScript.java b/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingScript.java index 926ab36a17..a2a96560e1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingScript.java @@ -9,7 +9,6 @@ import net.runelite.api.Tile; import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; -import net.runelite.api.GameState; import net.runelite.api.gameval.ItemID; import net.runelite.api.Skill; import net.runelite.api.events.ChatMessage; @@ -89,6 +88,8 @@ ThievingNpc.WEALTHY_CITIZEN, new WealthyCitizenStrategy() private int doorCloseIndex = 0; private long lastAction = Long.MAX_VALUE; private long lastHopAt = 0; + private long escapeHopUntil = 0; + private static final long ESCAPE_HOP_WINDOW_MS = 60_000; protected static int getCloseDoorTime() { return DOOR_TIMER.getRemainingTime(); @@ -249,15 +250,6 @@ private boolean isBeingAttackByNpc() { return getAttackingNpcs(npcs -> npcs.findAny().isPresent(), false); } - private Rs2NpcModel getAttackingNpc() { - final WorldPoint myLoc = Rs2Player.getWorldLocation(); - if (myLoc == null) return null; - return getAttackingNpcs(npcs -> - npcs.min(Comparator.comparingInt(npc -> myLoc.distanceTo(npc.getWorldLocation()))) - .orElse(null), null - ); - } - private int getMostExpensiveGroundItemId() { final int minPrice = config.keepItemsAboveValue(); // takes long of client thread if there are a lot of dropped items @@ -287,13 +279,15 @@ private State getCurrentState() { return applyOverride(State.ESCAPE); } + if (isEscapeHopPending()) { + if (needsToEat() && hasFoodToEat()) return applyOverride(State.EAT); + return applyOverride(State.ESCAPE); + } + if (!hasReqs()) return applyOverride(State.BANK); if (config.useFood()) { - boolean needToEat = Rs2Player.getHealthPercentage() <= config.hitpoints(); - boolean needToDrink = config.food() == ThievingFood.ANCIENT_BREW && !Rs2Player.hasPrayerPoints(); - - if (needToEat || needToDrink) { + if (needsToEat()) { return applyOverride(State.EAT); } @@ -443,6 +437,10 @@ private void loop() { item.click("Take"); return; case ESCAPE: + if (!underAttack && isEscapeHopPending()) { + if (attemptEscapeHop()) returnToStartAfterEscape(); + return; + } WorldPoint escape = null; final String escapeLocationString = config.customEscapeLocation(); if (!escapeLocationString.isBlank()) { @@ -468,16 +466,14 @@ private void loop() { underAttack = false; if (!isRunning()) return; if (shouldHop()) { - hopWorld(); + requestEscapeHop(); } else { log.debug("Skipping world hop (Wealthy Citizen)"); } } - if (walkTo("Walk to start", startingLocation, 3)) { - sleepUntilWithInterrupt(() -> !Rs2Player.isMoving(), 1_200); - } - DOOR_TIMER.set(); + if (isEscapeHopPending() && !attemptEscapeHop()) return; + returnToStartAfterEscape(); return; } else { log.error("Failed to use escape route defaulting to bank escape"); @@ -613,6 +609,7 @@ public boolean run() { lastAction = System.currentTimeMillis(); nextShadowVeil = System.currentTimeMillis()+60_000; underAttack = false; + escapeHopUntil = 0; startupGraceUntil = System.currentTimeMillis() + 4_000; startTime = Instant.now(); mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { @@ -629,6 +626,17 @@ public boolean run() { return true; } + private boolean needsToEat() { + if (!config.useFood()) return false; + if (Rs2Player.getHealthPercentage() <= config.hitpoints()) return true; + return config.food() == ThievingFood.ANCIENT_BREW && !Rs2Player.hasPrayerPoints(); + } + + private boolean hasFoodToEat() { + if (config.food() == ThievingFood.ANCIENT_BREW) return hasAncientBrew(); + return !Rs2Inventory.getInventoryFood().isEmpty(); + } + private boolean hasReqs() { if (System.currentTimeMillis() < startupGraceUntil) return true; boolean hasReqs = true; @@ -1017,18 +1025,48 @@ private void bankAndEquip() { if (underAttack) { underAttack = false; if (shouldHop()) { - hopWorld(); + requestEscapeHop(); } else { log.debug("Skipping world hop (Wealthy Citizen)"); } } - if (walkTo("Return to npc", startingLocation, 3)) { + if (isEscapeHopPending() && !attemptEscapeHop()) return; + returnToStartAfterEscape(); + } + + private void returnToStartAfterEscape() { + if (walkTo("Walk to start", startingLocation, 3)) { sleepUntilWithInterrupt(() -> !Rs2Player.isMoving(), 1_200); } DOOR_TIMER.set(); } + private boolean isEscapeHopPending() { + return escapeHopUntil != 0; + } + + private void requestEscapeHop() { + if (escapeHopUntil == 0) escapeHopUntil = System.currentTimeMillis() + ESCAPE_HOP_WINDOW_MS; + } + + private boolean attemptEscapeHop() { + if (!config.escapeAttacking() || !shouldHop()) { + escapeHopUntil = 0; + return true; + } + if (hopWorld()) { + escapeHopUntil = 0; + return true; + } + if (System.currentTimeMillis() >= escapeHopUntil) { + log.warn("Escape hop did not succeed within {}s, returning without hopping", ESCAPE_HOP_WINDOW_MS / 1000); + escapeHopUntil = 0; + return true; + } + return false; + } + private void dropAllExceptImportant() { final Set keep = getExclusions(); if (config.DoNotDropItemList() != null && !config.DoNotDropItemList().isEmpty()) @@ -1044,46 +1082,24 @@ private void dropAllExceptImportant() { } - private void hopWorld() { + private boolean hopWorld() { final long now = System.currentTimeMillis(); if (now - lastHopAt < 15000) { log.debug("Skipping hop; last hop was {}ms ago", now - lastHopAt); - return; + return false; } lastHopAt = now; - DOOR_TIMER.unset(); - final int maxAttempts = 5; log.debug("Hopping world, please wait"); - for (int attempt = 0; attempt < maxAttempts; attempt++) { - final int currentWorld = Microbot.getClient().getWorld(); - Microbot.hopToWorld(Login.getRandomWorld(true, null)); - - BooleanSupplier attackedInterrupt = () -> { - final Rs2NpcModel attacking = getAttackingNpc(); - if (attacking == null) return false; - final WorldPoint me = Rs2Player.getWorldLocation(); - final WorldPoint npc = attacking.getWorldLocation(); - if (me != null && npc != null && me.distanceTo(npc) <= 2) { - log.warn("Getting attacked while hopping"); - return true; - } - return false; - }; - - sleepUntilWithInterrupt(() -> Microbot.getClient().getGameState() == GameState.HOPPING, attackedInterrupt, 15_000); - sleepUntilWithInterrupt(() -> Microbot.getClient().getGameState() == GameState.LOGGED_IN, attackedInterrupt, 15_000); - boolean changed = sleepUntilWithInterrupt(() -> Microbot.getClient().getWorld() != currentWorld, attackedInterrupt, 15_000); - - if (changed) { - log.debug("Successful hop world"); - thievingNpc = null; // force fresh lookup after hop - return; - } - sleep(250, 350); - log.warn("Hop attempt {}/{} failed, retrying...", attempt + 1, maxAttempts); + final int world = Login.getRandomWorld(true, null); + if (!Microbot.hopToWorld(world)) { + log.warn("World hop to {} refused, retrying later", world); + return false; } - log.error("Failed to hop world"); + DOOR_TIMER.unset(); + log.debug("Successful hop world"); + thievingNpc = null; // force fresh lookup after hop + return true; } private void drinkAncientBrew() { diff --git a/src/test/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperHopResultTest.java b/src/test/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperHopResultTest.java new file mode 100644 index 0000000000..d5a50d91cf --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperHopResultTest.java @@ -0,0 +1,51 @@ +package net.runelite.client.plugins.microbot.banksshopper; + +import net.runelite.api.Client; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.util.math.Rs2Random; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.security.Login; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; + +class BanksShopperHopResultTest { + + @Test + void refusedHopIsAttemptedOnceAndReportedAsNotHopped() throws Exception { + assertEquals(Boolean.FALSE, hopWorld(false)); + } + + @Test + void successfulHopIsReportedAsHopped() throws Exception { + assertEquals(Boolean.TRUE, hopWorld(true)); + } + + private static Object hopWorld(boolean hopResult) throws Exception { + BanksShopperScript script = new BanksShopperScript(mock(BanksShopperPlugin.class)); + Client client = mock(Client.class); + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic login = mockStatic(Login.class); + MockedStatic player = mockStatic(Rs2Player.class); + MockedStatic random = mockStatic(Rs2Random.class)) { + microbot.when(Microbot::getClient).thenReturn(client); + player.when(Rs2Player::isMember).thenReturn(false); + login.when(() -> Login.getRandomWorld(false)).thenReturn(301); + microbot.when(() -> Microbot.hopToWorld(anyInt())).thenReturn(hopResult); + + Method hopWorld = BanksShopperScript.class.getDeclaredMethod("hopWorld"); + hopWorld.setAccessible(true); + Object result = hopWorld.invoke(script); + + microbot.verify(() -> Microbot.hopToWorld(anyInt()), times(1)); + return result; + } + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/giantmole/GiantMoleHopResultTest.java b/src/test/java/net/runelite/client/plugins/microbot/giantmole/GiantMoleHopResultTest.java new file mode 100644 index 0000000000..c1e1119220 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/giantmole/GiantMoleHopResultTest.java @@ -0,0 +1,62 @@ +package net.runelite.client.plugins.microbot.giantmole; + +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.giantmole.enums.GiantMoleState; +import net.runelite.client.plugins.microbot.util.security.Login; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mockStatic; + +class GiantMoleHopResultTest { + + @AfterEach + void resetStatics() { + GiantMoleScript.checkedIfWorldOccupied = false; + GiantMoleScript.isWorldOccupied = true; + GiantMoleScript.state = GiantMoleState.IDLE; + } + + @Test + void refusedHopKeepsOccupiedWorldVerdictSoTheNextLoopRetries() { + GiantMoleScript.checkedIfWorldOccupied = true; + GiantMoleScript.isWorldOccupied = true; + GiantMoleScript.state = GiantMoleState.HOPPING; + + hop(false); + + assertTrue(GiantMoleScript.checkedIfWorldOccupied); + assertEquals(GiantMoleState.HOPPING, GiantMoleScript.state); + } + + @Test + void successfulHopClearsVerdictForTheNewWorld() { + GiantMoleScript.checkedIfWorldOccupied = true; + GiantMoleScript.isWorldOccupied = true; + GiantMoleScript.state = GiantMoleState.HOPPING; + + hop(true); + + assertFalse(GiantMoleScript.checkedIfWorldOccupied); + assertEquals(GiantMoleState.IDLE, GiantMoleScript.state); + } + + private static void hop(boolean hopResult) { + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic login = mockStatic(Login.class); + MockedStatic player = mockStatic(Rs2Player.class)) { + player.when(Rs2Player::isMember).thenReturn(true); + login.when(() -> Login.getRandomWorld(true)).thenReturn(420); + microbot.when(() -> Microbot.hopToWorld(420)).thenReturn(hopResult); + + new GiantMoleScript().hopWorlds(); + + microbot.verify(() -> Microbot.hopToWorld(420)); + } + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/hsblackjack/HSBlackJackHopResultTest.java b/src/test/java/net/runelite/client/plugins/microbot/hsblackjack/HSBlackJackHopResultTest.java new file mode 100644 index 0000000000..2892403331 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/hsblackjack/HSBlackJackHopResultTest.java @@ -0,0 +1,51 @@ +package net.runelite.client.plugins.microbot.hsblackjack; + +import net.runelite.api.Client; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.util.math.Rs2Random; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.security.Login; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; + +class HSBlackJackHopResultTest { + + @Test + void refusedHopIsAttemptedOnceAndReportedAsNotHopped() throws Exception { + assertEquals(Boolean.FALSE, hopWorld(false)); + } + + @Test + void successfulHopIsReportedAsHopped() throws Exception { + assertEquals(Boolean.TRUE, hopWorld(true)); + } + + private static Object hopWorld(boolean hopResult) throws Exception { + HSBlackJackScript script = new HSBlackJackScript(mock(HSBlackJackPlugin.class), mock(HSBlackJackConfig.class)); + Client client = mock(Client.class); + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic login = mockStatic(Login.class); + MockedStatic player = mockStatic(Rs2Player.class); + MockedStatic random = mockStatic(Rs2Random.class)) { + microbot.when(Microbot::getClient).thenReturn(client); + player.when(Rs2Player::isMember).thenReturn(true); + login.when(() -> Login.getRandomWorld(true)).thenReturn(302); + microbot.when(() -> Microbot.hopToWorld(anyInt())).thenReturn(hopResult); + + Method hopWorld = HSBlackJackScript.class.getDeclaredMethod("hopWorld"); + hopWorld.setAccessible(true); + Object result = hopWorld.invoke(script); + + microbot.verify(() -> Microbot.hopToWorld(anyInt()), times(1)); + return result; + } + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/microhunter/scripts/AutoChinHopResultTest.java b/src/test/java/net/runelite/client/plugins/microbot/microhunter/scripts/AutoChinHopResultTest.java new file mode 100644 index 0000000000..d6e16581b5 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/microhunter/scripts/AutoChinHopResultTest.java @@ -0,0 +1,359 @@ +package net.runelite.client.plugins.microbot.microhunter.scripts; + +import net.runelite.api.Client; +import net.runelite.client.game.WorldService; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; +import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; +import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.shop.Rs2Shop; +import net.runelite.http.api.worlds.World; +import net.runelite.http.api.worlds.WorldRegion; +import net.runelite.http.api.worlds.WorldResult; +import net.runelite.http.api.worlds.WorldType; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +class AutoChinHopResultTest { + + private static final int SOURCE_WORLD = 330; + private static final int TARGET_WORLD = 480; + + private MockedStatic combat; + private MockedStatic bank; + private MockedStatic shop; + private MockedStatic dialogue; + + @BeforeEach + void mockLocalHopPreconditions() { + combat = mockStatic(Rs2Combat.class); + bank = mockStatic(Rs2Bank.class); + shop = mockStatic(Rs2Shop.class); + dialogue = mockStatic(Rs2Dialogue.class); + } + + @AfterEach + void closeLocalHopPreconditions() { + combat.close(); + bank.close(); + shop.close(); + dialogue.close(); + } + + @Test + void refusedHopIsNotCountedOrBlacklistedAndIsRetriedForTheSameWorld() throws Exception { + AutoChinScript script = new AutoChinScript(); + Client client = mock(Client.class); + when(client.getWorld()).thenReturn(SOURCE_WORLD); + WorldService worldService = mock(WorldService.class); + WorldResult worldResult = mock(WorldResult.class); + World target = mock(World.class); + when(target.getId()).thenReturn(TARGET_WORLD); + when(target.getRegion()).thenReturn(WorldRegion.AUSTRALIA); + when(target.getTypes()).thenReturn(EnumSet.of(WorldType.MEMBERS)); + when(target.getPlayers()).thenReturn(200); + when(worldResult.getWorlds()).thenReturn(List.of(target)); + when(worldService.getWorlds()).thenReturn(worldResult); + + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic player = mockStatic(Rs2Player.class)) { + microbot.when(Microbot::getClient).thenReturn(client); + microbot.when(Microbot::getWorldService).thenReturn(worldService); + microbot.when(Microbot::isLoggedIn).thenReturn(true); + player.when(Rs2Player::isMember).thenReturn(true); + microbot.when(() -> Microbot.hopToWorld(TARGET_WORLD)).thenReturn(false); + + invoke(script, "requestAustralianWorldHop", "occupied"); + + assertEquals(0, field(script, "worldHopAttempts")); + assertTrue(((Map) field(script, "rejectedWorlds")).isEmpty()); + assertEquals(TARGET_WORLD, field(script, "hopTargetWorld")); + assertEquals("HOPPING_WORLD", field(script, "currentState").toString()); + + invoke(script, "monitorWorldHop"); + microbot.verify(() -> Microbot.hopToWorld(TARGET_WORLD), times(1)); + + setField(script, "hopStartedAt", System.currentTimeMillis() - 3_000); + microbot.when(() -> Microbot.hopToWorld(TARGET_WORLD)).thenReturn(true); + invoke(script, "monitorWorldHop"); + + microbot.verify(() -> Microbot.hopToWorld(TARGET_WORLD), times(2)); + assertEquals(1, field(script, "worldHopAttempts")); + assertTrue(((Map) field(script, "rejectedWorlds")).isEmpty()); + } + } + + @Test + void persistentRefusalBlacklistsTheTargetAndMovesToAnotherWorld() throws Exception { + AutoChinScript script = new AutoChinScript(); + Client client = mock(Client.class); + when(client.getWorld()).thenReturn(SOURCE_WORLD); + WorldService worldService = australianWorlds(TARGET_WORLD, TARGET_WORLD + 1); + + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic player = mockStatic(Rs2Player.class)) { + microbot.when(Microbot::getClient).thenReturn(client); + microbot.when(Microbot::getWorldService).thenReturn(worldService); + microbot.when(Microbot::isLoggedIn).thenReturn(true); + player.when(Rs2Player::isMember).thenReturn(true); + microbot.when(() -> Microbot.hopToWorld(anyInt())).thenReturn(false); + + invoke(script, "requestAustralianWorldHop", "occupied"); + int firstTarget = (int) field(script, "hopTargetWorld"); + + for (int i = 0; i < 3; i++) { + setField(script, "hopStartedAt", System.currentTimeMillis() - 3_000); + invoke(script, "monitorWorldHop"); + } + + microbot.verify(() -> Microbot.hopToWorld(firstTarget), times(3)); + assertTrue(((Map) field(script, "rejectedWorlds")).containsKey(firstTarget)); + assertEquals(1, field(script, "worldHopAttempts")); + int secondTarget = (int) field(script, "hopTargetWorld"); + assertNotEquals(firstTarget, secondTarget); + microbot.verify(() -> Microbot.hopToWorld(secondTarget), times(1)); + assertEquals("HOPPING_WORLD", field(script, "currentState").toString()); + } + } + + @Test + void persistentRefusalOnEveryWorldStopsAtTheAttemptCap() throws Exception { + AutoChinScript script = new AutoChinScript(); + Client client = mock(Client.class); + when(client.getWorld()).thenReturn(SOURCE_WORLD); + WorldService worldService = australianWorlds(480, 481, 482, 483, 484, 485, 486, 487); + + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic player = mockStatic(Rs2Player.class)) { + microbot.when(Microbot::getClient).thenReturn(client); + microbot.when(Microbot::getWorldService).thenReturn(worldService); + microbot.when(Microbot::isLoggedIn).thenReturn(true); + player.when(Rs2Player::isMember).thenReturn(true); + microbot.when(() -> Microbot.hopToWorld(anyInt())).thenReturn(false); + + invoke(script, "requestAustralianWorldHop", "occupied"); + for (int i = 0; i < 100 && !"STOPPED".equals(field(script, "currentState").toString()); i++) { + setField(script, "hopStartedAt", System.currentTimeMillis() - 3_000); + invoke(script, "monitorWorldHop"); + } + + assertEquals("STOPPED", field(script, "currentState").toString()); + microbot.verify(() -> Microbot.hopToWorld(anyInt()), times(15)); + assertEquals(5, ((Map) field(script, "rejectedWorlds")).size()); + } + } + + @Test + void locallyBlockedHopWaitsWithoutBlacklistingOrCountingTheTarget() throws Exception { + AutoChinScript script = new AutoChinScript(); + Client client = mock(Client.class); + when(client.getWorld()).thenReturn(SOURCE_WORLD); + WorldService worldService = australianWorlds(480, 481, 482, 483, 484, 485, 486, 487); + + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic player = mockStatic(Rs2Player.class)) { + microbot.when(Microbot::getClient).thenReturn(client); + microbot.when(Microbot::getWorldService).thenReturn(worldService); + microbot.when(Microbot::isLoggedIn).thenReturn(true); + player.when(Rs2Player::isMember).thenReturn(true); + player.when(Rs2Player::isInCombat).thenReturn(true); + microbot.when(() -> Microbot.hopToWorld(anyInt())).thenReturn(false); + + invoke(script, "requestAustralianWorldHop", "occupied"); + int target = (int) field(script, "hopTargetWorld"); + for (int i = 0; i < 40; i++) { + setField(script, "hopStartedAt", System.currentTimeMillis() - 3_000); + invoke(script, "monitorWorldHop"); + } + + microbot.verify(() -> Microbot.hopToWorld(anyInt()), never()); + assertEquals("HOPPING_WORLD", field(script, "currentState").toString()); + assertEquals(target, field(script, "hopTargetWorld")); + assertEquals(0, field(script, "worldHopAttempts")); + assertEquals(0, field(script, "hopRefusals")); + assertTrue(((Map) field(script, "rejectedWorlds")).isEmpty()); + + player.when(Rs2Player::isInCombat).thenReturn(false); + microbot.when(() -> Microbot.hopToWorld(target)).thenReturn(true); + setField(script, "hopStartedAt", System.currentTimeMillis() - 3_000); + invoke(script, "monitorWorldHop"); + + microbot.verify(() -> Microbot.hopToWorld(target), times(1)); + assertEquals(1, field(script, "worldHopAttempts")); + assertTrue(((Map) field(script, "rejectedWorlds")).isEmpty()); + } + } + + @Test + void refusalCausedByALocalBlockThatAppearedDuringTheHopIsNotCounted() throws Exception { + AutoChinScript script = new AutoChinScript(); + Client client = mock(Client.class); + when(client.getWorld()).thenReturn(SOURCE_WORLD); + WorldService worldService = australianWorlds(TARGET_WORLD); + + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic player = mockStatic(Rs2Player.class)) { + microbot.when(Microbot::getClient).thenReturn(client); + microbot.when(Microbot::getWorldService).thenReturn(worldService); + microbot.when(Microbot::isLoggedIn).thenReturn(true); + player.when(Rs2Player::isMember).thenReturn(true); + dialogue.when(Rs2Dialogue::isInDialogue).thenReturn(false, true); + microbot.when(() -> Microbot.hopToWorld(TARGET_WORLD)).thenReturn(false); + + invoke(script, "requestAustralianWorldHop", "occupied"); + + microbot.verify(() -> Microbot.hopToWorld(TARGET_WORLD), times(1)); + dialogue.verify(Rs2Dialogue::clickContinue, times(1)); + assertEquals(0, field(script, "hopRefusals")); + assertEquals(0, field(script, "worldHopAttempts")); + assertTrue(((Map) field(script, "rejectedWorlds")).isEmpty()); + assertEquals("HOPPING_WORLD", field(script, "currentState").toString()); + } + } + + @Test + void persistentLocalBlockStopsVisiblyAfterTheCapWithoutPenalisingTheTarget() throws Exception { + AutoChinScript script = new AutoChinScript(); + Client client = mock(Client.class); + when(client.getWorld()).thenReturn(SOURCE_WORLD); + WorldService worldService = australianWorlds(480, 481, 482, 483, 484, 485, 486, 487); + + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic player = mockStatic(Rs2Player.class)) { + microbot.when(Microbot::getClient).thenReturn(client); + microbot.when(Microbot::getWorldService).thenReturn(worldService); + microbot.when(Microbot::isLoggedIn).thenReturn(true); + player.when(Rs2Player::isMember).thenReturn(true); + player.when(Rs2Player::isInCombat).thenReturn(true); + microbot.when(() -> Microbot.hopToWorld(anyInt())).thenReturn(false); + + invoke(script, "requestAustralianWorldHop", "occupied"); + int target = (int) field(script, "hopTargetWorld"); + + setField(script, "hopBlockedSince", System.currentTimeMillis() - 170_000); + setField(script, "hopStartedAt", System.currentTimeMillis() - 3_000); + invoke(script, "monitorWorldHop"); + assertEquals("HOPPING_WORLD", field(script, "currentState").toString()); + + setField(script, "hopBlockedSince", System.currentTimeMillis() - 181_000); + setField(script, "hopStartedAt", System.currentTimeMillis() - 3_000); + invoke(script, "monitorWorldHop"); + + assertEquals("STOPPED", field(script, "currentState").toString()); + String reason = script.getStopReason(); + assertTrue(reason.startsWith("World hop blocked locally for 18"), reason); + assertTrue(reason.endsWith("s: in combat"), reason); + microbot.verify(() -> Microbot.hopToWorld(anyInt()), never()); + assertEquals(target, field(script, "hopTargetWorld")); + assertEquals(0, field(script, "worldHopAttempts")); + assertEquals(0, field(script, "hopRefusals")); + assertTrue(((Map) field(script, "rejectedWorlds")).isEmpty()); + } + } + + @Test + void localBlockTimerRestartsWhenTheBlockClears() throws Exception { + AutoChinScript script = new AutoChinScript(); + Client client = mock(Client.class); + when(client.getWorld()).thenReturn(SOURCE_WORLD); + WorldService worldService = australianWorlds(TARGET_WORLD); + + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic player = mockStatic(Rs2Player.class)) { + microbot.when(Microbot::getClient).thenReturn(client); + microbot.when(Microbot::getWorldService).thenReturn(worldService); + microbot.when(Microbot::isLoggedIn).thenReturn(true); + player.when(Rs2Player::isMember).thenReturn(true); + dialogue.when(Rs2Dialogue::isInDialogue).thenReturn(true); + microbot.when(() -> Microbot.hopToWorld(TARGET_WORLD)).thenReturn(false); + + invoke(script, "requestAustralianWorldHop", "occupied"); + assertTrue((long) field(script, "hopBlockedSince") > 0); + + setField(script, "hopBlockedSince", System.currentTimeMillis() - 170_000); + dialogue.when(Rs2Dialogue::isInDialogue).thenReturn(false); + setField(script, "hopStartedAt", System.currentTimeMillis() - 3_000); + invoke(script, "monitorWorldHop"); + assertEquals(0L, field(script, "hopBlockedSince")); + assertEquals(1, field(script, "hopRefusals")); + + dialogue.when(Rs2Dialogue::isInDialogue).thenReturn(true); + for (int i = 0; i < 5; i++) { + setField(script, "hopStartedAt", System.currentTimeMillis() - 3_000); + invoke(script, "monitorWorldHop"); + } + assertEquals("HOPPING_WORLD", field(script, "currentState").toString()); + assertTrue(System.currentTimeMillis() - (long) field(script, "hopBlockedSince") < 10_000); + + setField(script, "hopBlockedSince", System.currentTimeMillis() - 170_000); + dialogue.when(Rs2Dialogue::isInDialogue).thenReturn(false); + microbot.when(() -> Microbot.hopToWorld(TARGET_WORLD)).thenReturn(true); + setField(script, "hopStartedAt", System.currentTimeMillis() - 3_000); + invoke(script, "monitorWorldHop"); + + assertEquals(0L, field(script, "hopBlockedSince")); + assertEquals(1, field(script, "worldHopAttempts")); + assertEquals("HOPPING_WORLD", field(script, "currentState").toString()); + assertTrue(((Map) field(script, "rejectedWorlds")).isEmpty()); + } + } + + private static WorldService australianWorlds(int... ids) { + List worlds = new ArrayList<>(); + for (int id : ids) { + World world = mock(World.class); + when(world.getId()).thenReturn(id); + when(world.getRegion()).thenReturn(WorldRegion.AUSTRALIA); + when(world.getTypes()).thenReturn(EnumSet.of(WorldType.MEMBERS)); + when(world.getPlayers()).thenReturn(200); + worlds.add(world); + } + WorldResult worldResult = mock(WorldResult.class); + when(worldResult.getWorlds()).thenReturn(worlds); + WorldService worldService = mock(WorldService.class); + when(worldService.getWorlds()).thenReturn(worldResult); + return worldService; + } + + private static void invoke(Object target, String name, Object... args) throws Exception { + Class[] types = new Class[args.length]; + for (int i = 0; i < args.length; i++) { + types[i] = args[i].getClass(); + } + Method method = target.getClass().getDeclaredMethod(name, types); + method.setAccessible(true); + method.invoke(target, args); + } + + private static Object field(Object target, String name) throws Exception { + Field field = target.getClass().getDeclaredField(name); + field.setAccessible(true); + return field.get(target); + } + + private static void setField(Object target, String name, Object value) throws Exception { + Field field = target.getClass().getDeclaredField(name); + field.setAccessible(true); + field.set(target, value); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/mke_wintertodt/WintertodtHopResultTest.java b/src/test/java/net/runelite/client/plugins/microbot/mke_wintertodt/WintertodtHopResultTest.java new file mode 100644 index 0000000000..554de2000d --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/mke_wintertodt/WintertodtHopResultTest.java @@ -0,0 +1,72 @@ +package net.runelite.client.plugins.microbot.mke_wintertodt; + +import net.runelite.api.Client; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.breakhandler.BreakHandlerScript; +import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; +import net.runelite.client.plugins.microbot.util.math.Rs2Random; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; + +class WintertodtHopResultTest { + + @Test + void refusedHopKeepsTheScriptRunningAndRetriesAfterACooldown() throws Exception { + MKE_WintertodtScript script = new MKE_WintertodtScript(); + Client client = mock(Client.class); + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic player = mockStatic(Rs2Player.class); + MockedStatic bank = mockStatic(Rs2Bank.class); + MockedStatic breakHandler = mockStatic(BreakHandlerScript.class); + MockedStatic random = mockStatic(Rs2Random.class)) { + microbot.when(Microbot::getClient).thenReturn(client); + microbot.when(Microbot::isLoggedIn).thenReturn(true); + player.when(Rs2Player::getWorld).thenReturn(301); + random.when(() -> Rs2Random.between(anyInt(), anyInt())).thenReturn(0, 1, 2, 3); + microbot.when(() -> Microbot.hopToWorld(anyInt())).thenReturn(false); + + assertTrue(ensureWintertodtWorld(script)); + microbot.verify(() -> Microbot.hopToWorld(anyInt()), times(1)); + assertFalse((Boolean) field(script, "worldChecked")); + + assertTrue(ensureWintertodtWorld(script)); + microbot.verify(() -> Microbot.hopToWorld(anyInt()), times(1)); + + setField(script, "lastRefusedHopAt", System.currentTimeMillis() - 5_000); + microbot.when(() -> Microbot.hopToWorld(anyInt())).thenReturn(true); + assertTrue(ensureWintertodtWorld(script)); + microbot.verify(() -> Microbot.hopToWorld(anyInt()), times(2)); + assertEquals(Boolean.TRUE, field(script, "worldChecked")); + } + } + + private static boolean ensureWintertodtWorld(MKE_WintertodtScript script) throws Exception { + Method method = MKE_WintertodtScript.class.getDeclaredMethod("ensureWintertodtWorldSafely"); + method.setAccessible(true); + return (Boolean) method.invoke(script); + } + + private static Object field(Object target, String name) throws Exception { + Field field = target.getClass().getDeclaredField(name); + field.setAccessible(true); + return field.get(target); + } + + private static void setField(Object target, String name, Object value) throws Exception { + Field field = target.getClass().getDeclaredField(name); + field.setAccessible(true); + field.set(target, value); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarWalkBeforeHopTest.java b/src/test/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarWalkBeforeHopTest.java new file mode 100644 index 0000000000..0fab6a8876 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarWalkBeforeHopTest.java @@ -0,0 +1,313 @@ +package net.runelite.client.plugins.microbot.shootingstar; + +import java.util.EnumSet; +import java.util.Optional; +import java.util.concurrent.Callable; +import net.runelite.api.Client; +import net.runelite.api.WorldType; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.shootingstar.enums.ShootingStarLocation; +import net.runelite.client.plugins.microbot.shootingstar.enums.ShootingStarState; +import net.runelite.client.plugins.microbot.shootingstar.model.Star; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; +import net.runelite.client.plugins.microbot.util.walker.WalkerState; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +class ShootingStarWalkBeforeHopTest +{ + private static final int MEMBERS_WORLD = 302; + private static final int STAR_WORLD = 301; + private static final int OTHER_STAR_WORLD = 308; + private static final ShootingStarLocation LOCATION = ShootingStarLocation.DRAYNOR_VILLAGE; + private static final WorldPoint FAR_AWAY = new WorldPoint(2605, 3092, 0); + private static final WorldPoint AT_STAR = LOCATION.getWorldPoint().dx(2); + + private MockedStatic microbot; + private MockedStatic player; + private MockedStatic walker; + private ShootingStarScript script; + private Client client; + private boolean onClientThread; + + @BeforeEach + void setUp() + { + microbot = mockStatic(Microbot.class); + player = mockStatic(Rs2Player.class); + walker = mockStatic(Rs2Walker.class); + client = mock(Client.class); + ClientThread clientThread = mock(ClientThread.class); + when(clientThread.runOnClientThreadOptional(any())).thenAnswer(invocation -> { + onClientThread = true; + try + { + return Optional.ofNullable(((Callable) invocation.getArgument(0)).call()); + } + finally + { + onClientThread = false; + } + }); + microbot.when(Microbot::getClient).thenReturn(client); + microbot.when(Microbot::getClientThread).thenReturn(clientThread); + walker.when(() -> Rs2Walker.walkWithState(any(WorldPoint.class), anyInt())).thenReturn(WalkerState.MOVING); + player.when(Rs2Player::getWorld).thenReturn(MEMBERS_WORLD); + ShootingStarPlugin plugin = mock(ShootingStarPlugin.class); + script = new ShootingStarScript(plugin, mock(ShootingStarConfig.class)); + ShootingStarScript.state = ShootingStarState.WALKING; + Microbot.cantHopWorld = false; + } + + @AfterEach + void tearDown() + { + walker.close(); + player.close(); + microbot.close(); + ShootingStarScript.state = null; + Microbot.cantHopWorld = false; + } + + @Test + void f2pStarFromMembersWorldWalksToTheStarInsteadOfHopping() + { + standAt(FAR_AWAY, true); + + script.handleWalking(star(false)); + + microbot.verify(() -> Microbot.hopToWorld(anyInt()), never()); + walker.verify(() -> Rs2Walker.walkWithState(LOCATION.getWorldPoint(), 6)); + } + + @Test + void f2pStarHopsOnceThePlayerReachedTheStar() + { + standAt(AT_STAR, true); + microbot.when(() -> Microbot.hopToWorld(STAR_WORLD)).thenReturn(true); + + script.handleWalking(star(false)); + + microbot.verify(() -> Microbot.hopToWorld(STAR_WORLD)); + walker.verify(() -> Rs2Walker.walkWithState(any(WorldPoint.class), anyInt()), never()); + assertEquals(ShootingStarState.WALKING, ShootingStarScript.state); + } + + @Test + void membersStarStillHopsBeforeWalking() + { + standAt(FAR_AWAY, true); + microbot.when(() -> Microbot.hopToWorld(STAR_WORLD)).thenReturn(true); + + script.handleWalking(star(true)); + + microbot.verify(() -> Microbot.hopToWorld(STAR_WORLD)); + walker.verify(() -> Rs2Walker.walkWithState(any(WorldPoint.class), anyInt()), never()); + } + + @Test + void failedHopToF2pWorldMakesTheNextLoopWalkInsteadOfRetryingTheHop() + { + standAt(FAR_AWAY, false); + microbot.when(() -> Microbot.hopToWorld(STAR_WORLD)).thenReturn(false); + Star star = star(false); + + script.handleWalking(star); + script.handleWalking(star); + + microbot.verify(() -> Microbot.hopToWorld(STAR_WORLD), times(1)); + walker.verify(() -> Rs2Walker.walkWithState(LOCATION.getWorldPoint(), 6), times(1)); + } + + @Test + void staleMembersAreaRefusalIsClearedOnceAtTheF2pStar() + { + standAt(AT_STAR, true); + Microbot.cantHopWorld = true; + microbot.when(() -> Microbot.hopToWorld(STAR_WORLD)).thenAnswer(invocation -> !Microbot.cantHopWorld); + + script.handleWalking(star(false)); + + assertFalse(Microbot.cantHopWorld); + microbot.verify(() -> Microbot.hopToWorld(STAR_WORLD)); + } + + @Test + void onTheStarWorldNextToTheStarStartsMining() + { + standAt(AT_STAR, false); + player.when(Rs2Player::getWorld).thenReturn(STAR_WORLD); + + script.handleWalking(star(false)); + + microbot.verify(() -> Microbot.hopToWorld(anyInt()), never()); + assertEquals(ShootingStarState.MINING, ShootingStarScript.state); + } + + @Test + void standingExactlyOnTheWalkerArrivalDistanceHopsInsteadOfWalkingAgain() + { + standAt(LOCATION.getWorldPoint().dx(ShootingStarScript.STAR_ARRIVAL_DISTANCE), true); + walker.when(() -> Rs2Walker.walkWithState(any(WorldPoint.class), anyInt())).thenReturn(WalkerState.ARRIVED); + microbot.when(() -> Microbot.hopToWorld(STAR_WORLD)).thenReturn(true); + + script.handleWalking(star(false)); + + microbot.verify(() -> Microbot.hopToWorld(STAR_WORLD)); + walker.verify(() -> Rs2Walker.walkWithState(any(WorldPoint.class), anyInt()), never()); + } + + @Test + void standingExactlyOnTheWalkerArrivalDistanceOnTheStarWorldStartsMining() + { + standAt(LOCATION.getWorldPoint().dx(ShootingStarScript.STAR_ARRIVAL_DISTANCE), false); + player.when(Rs2Player::getWorld).thenReturn(STAR_WORLD); + walker.when(() -> Rs2Walker.walkWithState(any(WorldPoint.class), anyInt())).thenReturn(WalkerState.ARRIVED); + + script.handleWalking(star(false)); + + assertEquals(ShootingStarState.MINING, ShootingStarScript.state); + } + + @Test + void walkerReportingArrivedOutsideTheRadiusHopsInsteadOfLoopingOnTheWalker() + { + standAt(LOCATION.getWorldPoint().dx(ShootingStarScript.STAR_ARRIVAL_DISTANCE + 1), true); + walker.when(() -> Rs2Walker.walkWithState(any(WorldPoint.class), anyInt())).thenReturn(WalkerState.ARRIVED); + microbot.when(() -> Microbot.hopToWorld(STAR_WORLD)).thenReturn(true); + + script.handleWalking(star(false)); + + walker.verify(() -> Rs2Walker.walkWithState(LOCATION.getWorldPoint(), ShootingStarScript.STAR_ARRIVAL_DISTANCE), times(1)); + microbot.verify(() -> Microbot.hopToWorld(STAR_WORLD)); + } + + @Test + void membersWorldIsReadFromTheClientWorldTypeNotTheHttpWorldList() + { + standAt(FAR_AWAY, true); + player.when(Rs2Player::isInMemberWorld).thenReturn(false); + + script.handleWalking(star(false)); + + microbot.verify(() -> Microbot.hopToWorld(anyInt()), never()); + walker.verify(() -> Rs2Walker.walkWithState(LOCATION.getWorldPoint(), ShootingStarScript.STAR_ARRIVAL_DISTANCE)); + } + + @Test + void f2pWorldTypeHopsEvenWhenTheHttpWorldListSaysMembers() + { + standAt(FAR_AWAY, false); + player.when(Rs2Player::isInMemberWorld).thenReturn(true); + microbot.when(() -> Microbot.hopToWorld(STAR_WORLD)).thenReturn(true); + + script.handleWalking(star(false)); + + microbot.verify(() -> Microbot.hopToWorld(STAR_WORLD)); + walker.verify(() -> Rs2Walker.walkWithState(any(WorldPoint.class), anyInt()), never()); + } + + @Test + void walkerArrivedOnTheStarWorldFarFromTheStarKeepsWalkingInsteadOfMining() + { + standAt(FAR_AWAY, false); + player.when(Rs2Player::getWorld).thenReturn(STAR_WORLD); + walker.when(() -> Rs2Walker.walkWithState(any(WorldPoint.class), anyInt())).thenReturn(WalkerState.ARRIVED); + + script.handleWalking(star(false)); + + assertEquals(ShootingStarState.WALKING, ShootingStarScript.state); + microbot.verify(() -> Microbot.hopToWorld(anyInt()), never()); + } + + @Test + void walkerArrivedOnTheStarWorldNextToTheStarStartsMining() + { + standAt(FAR_AWAY, false); + player.when(Rs2Player::getWorld).thenReturn(STAR_WORLD); + walker.when(() -> Rs2Walker.walkWithState(any(WorldPoint.class), anyInt())).thenAnswer(invocation -> { + player.when(Rs2Player::getWorldLocation).thenReturn(AT_STAR); + return WalkerState.ARRIVED; + }); + + script.handleWalking(star(false)); + + assertEquals(ShootingStarState.MINING, ShootingStarScript.state); + } + + @Test + void walkBeforeHopResetsAfterASuccessfulHop() + { + standAt(FAR_AWAY, false); + microbot.when(() -> Microbot.hopToWorld(STAR_WORLD)).thenReturn(false); + Star star = star(false); + script.handleWalking(star); + + standAt(AT_STAR, false); + microbot.when(() -> Microbot.hopToWorld(STAR_WORLD)).thenReturn(true); + script.handleWalking(star); + + standAt(FAR_AWAY, false); + script.handleWalking(star); + + microbot.verify(() -> Microbot.hopToWorld(STAR_WORLD), times(3)); + walker.verify(() -> Rs2Walker.walkWithState(any(WorldPoint.class), anyInt()), never()); + } + + @Test + void walkBeforeHopFromAFailedHopDoesNotCarryOverToTheNextStar() + { + standAt(FAR_AWAY, false); + microbot.when(() -> Microbot.hopToWorld(anyInt())).thenReturn(false); + script.handleWalking(star(false)); + + Star nextStar = star(false, OTHER_STAR_WORLD); + script.handleWalking(nextStar); + + microbot.verify(() -> Microbot.hopToWorld(OTHER_STAR_WORLD)); + walker.verify(() -> Rs2Walker.walkWithState(any(WorldPoint.class), anyInt()), never()); + } + + private void standAt(WorldPoint location, boolean onMembersWorld) + { + player.when(Rs2Player::getWorldLocation).thenReturn(location); + EnumSet worldTypes = onMembersWorld ? EnumSet.of(WorldType.MEMBERS) : EnumSet.noneOf(WorldType.class); + doAnswer(invocation -> { + if (!onClientThread) + { + throw new AssertionError("world type read off the client thread"); + } + return worldTypes; + }).when(client).getWorldType(); + } + + private static Star star(boolean membersWorld) + { + return star(membersWorld, STAR_WORLD); + } + + private static Star star(boolean membersWorld, int world) + { + Star star = mock(Star.class); + when(star.getWorld()).thenReturn(world); + when(star.isMemberWorld()).thenReturn(membersWorld); + when(star.getShootingStarLocation()).thenReturn(LOCATION); + return star; + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarWorldFilterTest.java b/src/test/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarWorldFilterTest.java new file mode 100644 index 0000000000..cb162af8e8 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarWorldFilterTest.java @@ -0,0 +1,270 @@ +package net.runelite.client.plugins.microbot.shootingstar; + +import java.lang.reflect.Field; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Optional; +import java.util.concurrent.Callable; +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.api.WorldType; +import net.runelite.api.events.GameStateChanged; +import net.runelite.api.events.VarbitChanged; +import net.runelite.api.gameval.VarPlayerID; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.shootingstar.enums.ShootingStarLocation; +import net.runelite.client.plugins.microbot.shootingstar.model.Star; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class ShootingStarWorldFilterTest +{ + private static final int MEMBERS_WORLD = 302; + private static final int F2P_WORLD = 301; + private static final int MEMBERSHIP_DAYS = 30; + + private MockedStatic microbot; + private Client client; + private ShootingStarPlugin plugin; + private boolean onClientThread; + + @BeforeEach + void setUp() throws Exception + { + client = mock(Client.class); + microbot = mockStatic(Microbot.class); + microbot.when(Microbot::getClient).thenReturn(client); + + ShootingStarApiClient apiClient = mock(ShootingStarApiClient.class); + when(apiClient.getStarData(any())).thenReturn(Collections.emptyList()); + + ClientThread clientThread = mock(ClientThread.class); + when(clientThread.runOnClientThreadOptional(any())).thenAnswer(invocation -> { + onClientThread = true; + try + { + return Optional.ofNullable(((Callable) invocation.getArgument(0)).call()); + } + finally + { + onClientThread = false; + } + }); + microbot.when(Microbot::getClientThread).thenReturn(clientThread); + microbot.when(Microbot::getConfigManager).thenReturn(mock(ConfigManager.class)); + + plugin = new ShootingStarPlugin(); + inject("config", mock(ShootingStarConfig.class)); + inject("shootingStarApiClient", apiClient); + inject("shootingStarScript", mock(ShootingStarScript.class)); + inject("panel", mock(ShootingStarPanel.class)); + inject("hideMembersWorlds", true); + inject("hideF2PWorlds", false); + } + + @AfterEach + void tearDown() + { + microbot.close(); + } + + @Test + void firstLoginOnAMembersWorldShowsMembersStarsAndHidesF2pStars() + { + Star membersStar = star(true); + Star f2pStar = star(false); + plugin.getStarList().add(membersStar); + plugin.getStarList().add(f2pStar); + + logInTo(MEMBERS_WORLD, EnumSet.of(WorldType.MEMBERS)); + + assertFalse(plugin.isHideMembersWorlds()); + assertTrue(plugin.isHideF2PWorlds()); + verify(membersStar).setHidden(false); + verify(f2pStar).setHidden(true); + } + + @Test + void hoppingToAnF2pWorldAfterLoginKeepsTheMembersFilter() + { + logInTo(MEMBERS_WORLD, EnumSet.of(WorldType.MEMBERS)); + + fire(GameState.HOPPING); + logInTo(F2P_WORLD, EnumSet.noneOf(WorldType.class)); + + assertFalse(plugin.isHideMembersWorlds()); + assertTrue(plugin.isHideF2PWorlds()); + } + + @Test + void returningToTheLoginScreenWithUnknownMembershipFallsBackToTheWorldType() + { + logInTo(MEMBERS_WORLD, EnumSet.of(WorldType.MEMBERS)); + + fire(GameState.LOGIN_SCREEN); + logInTo(F2P_WORLD, EnumSet.noneOf(WorldType.class)); + + assertTrue(plugin.isHideMembersWorlds()); + assertFalse(plugin.isHideF2PWorlds()); + } + + @Test + void membersAccountRelogOnAnF2pWorldKeepsTheMembersFilter() + { + when(client.getVarpValue(VarPlayerID.ACCOUNT_CREDIT)).thenReturn(MEMBERSHIP_DAYS); + logInTo(MEMBERS_WORLD, EnumSet.of(WorldType.MEMBERS)); + + fire(GameState.HOPPING); + logInTo(F2P_WORLD, EnumSet.noneOf(WorldType.class)); + fire(GameState.LOGIN_SCREEN); + logInTo(F2P_WORLD, EnumSet.noneOf(WorldType.class)); + + assertFalse(plugin.isHideMembersWorlds()); + assertTrue(plugin.isHideF2PWorlds()); + } + + @Test + void membershipArrivingAfterLoginOnAnF2pWorldSwitchesToTheMembersFilter() + { + Star membersStar = star(true); + Star f2pStar = star(false); + plugin.getStarList().add(membersStar); + plugin.getStarList().add(f2pStar); + logInTo(F2P_WORLD, EnumSet.noneOf(WorldType.class)); + + accountCredit(MEMBERSHIP_DAYS); + + assertFalse(plugin.isHideMembersWorlds()); + assertTrue(plugin.isHideF2PWorlds()); + verify(membersStar).setHidden(false); + verify(f2pStar).setHidden(true); + } + + @Test + void accountCreditResetDuringAHopDoesNotDropTheMembersFilter() + { + when(client.getVarpValue(VarPlayerID.ACCOUNT_CREDIT)).thenReturn(MEMBERSHIP_DAYS); + logInTo(MEMBERS_WORLD, EnumSet.of(WorldType.MEMBERS)); + + fire(GameState.HOPPING); + when(client.getWorld()).thenReturn(F2P_WORLD); + when(client.getWorldType()).thenReturn(EnumSet.noneOf(WorldType.class)); + accountCredit(0); + + assertFalse(plugin.isHideMembersWorlds()); + assertTrue(plugin.isHideF2PWorlds()); + } + + @Test + void accountCreditBeforeTheFirstLoginWaitsForTheLogin() + { + accountCredit(MEMBERSHIP_DAYS); + + assertTrue(plugin.isHideMembersWorlds()); + assertFalse(plugin.isHideF2PWorlds()); + } + + @Test + void startUpWhileLoggedInReadsMembershipOnTheClientThread() throws Exception + { + inject("hideMembersWorlds", false); + microbot.when(Microbot::isLoggedIn).thenReturn(true); + when(client.getVarpValue(anyInt())).thenAnswer(invocation -> { + assertTrue(onClientThread, "varp read off the client thread"); + return MEMBERSHIP_DAYS; + }); + when(client.getWorldType()).thenAnswer(invocation -> { + assertTrue(onClientThread, "world type read off the client thread"); + return EnumSet.noneOf(WorldType.class); + }); + + plugin.startUp(); + + assertFalse(plugin.isHideMembersWorlds()); + assertTrue(plugin.isHideF2PWorlds()); + } + + @Test + void startUpWhileLoggedOutWaitsForTheLogin() throws Exception + { + plugin.startUp(); + + assertTrue(plugin.isHideMembersWorlds()); + assertFalse(plugin.isHideF2PWorlds()); + + when(client.getVarpValue(VarPlayerID.ACCOUNT_CREDIT)).thenReturn(MEMBERSHIP_DAYS); + logInTo(F2P_WORLD, EnumSet.noneOf(WorldType.class)); + + assertFalse(plugin.isHideMembersWorlds()); + } + + @Test + void removingAStarWhileTheFilterIsAppliedDoesNotBreakTheIteration() + { + Star first = star(true); + Star second = star(false); + plugin.getStarList().add(first); + plugin.getStarList().add(second); + doAnswer(invocation -> { + plugin.removeStar(second); + return null; + }).when(first).setHidden(anyBoolean()); + + assertDoesNotThrow(plugin::updateHiddenStars); + assertFalse(plugin.getStarList().contains(second)); + } + + private void accountCredit(int days) + { + when(client.getVarpValue(VarPlayerID.ACCOUNT_CREDIT)).thenReturn(days); + VarbitChanged event = new VarbitChanged(); + event.setVarpId(VarPlayerID.ACCOUNT_CREDIT); + event.setValue(days); + plugin.onVarbitChanged(event); + } + + private void logInTo(int world, EnumSet worldTypes) + { + when(client.getWorld()).thenReturn(world); + when(client.getWorldType()).thenReturn(worldTypes); + fire(GameState.LOGGED_IN); + } + + private void fire(GameState gameState) + { + GameStateChanged event = new GameStateChanged(); + event.setGameState(gameState); + plugin.onGameStateChanged(event); + } + + private void inject(String fieldName, Object value) throws Exception + { + Field field = ShootingStarPlugin.class.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(plugin, value); + } + + private static Star star(boolean membersWorld) + { + Star star = mock(Star.class); + when(star.isMemberWorld()).thenReturn(membersWorld); + when(star.getShootingStarLocation()).thenReturn(ShootingStarLocation.DRAYNOR_VILLAGE); + return star; + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/slayer/SlayerHopResultTest.java b/src/test/java/net/runelite/client/plugins/microbot/slayer/SlayerHopResultTest.java new file mode 100644 index 0000000000..9a1cc352cc --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/slayer/SlayerHopResultTest.java @@ -0,0 +1,125 @@ +package net.runelite.client.plugins.microbot.slayer; + +import net.runelite.api.Client; +import net.runelite.api.GameState; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; +import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; +import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; +import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +class SlayerHopResultTest { + + private static final int CURRENT_WORLD = 330; + private static final int TARGET_WORLD = 331; + + @Test + void refusedHopStartsTheRefusalCooldownWithoutCountingAsAHop() throws Exception { + SlayerScript script = scriptWithHopList(); + Client client = client(); + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic combat = mockStatic(Rs2Combat.class); + MockedStatic prayer = mockStatic(Rs2Prayer.class)) { + microbot.when(Microbot::getClient).thenReturn(client); + combat.when(Rs2Combat::inCombat).thenReturn(false); + microbot.when(() -> Microbot.hopToWorld(TARGET_WORLD)).thenReturn(false); + + long before = System.currentTimeMillis(); + invoke(script, "hopToNewWorld"); + + microbot.verify(() -> Microbot.hopToWorld(TARGET_WORLD), times(1)); + assertTrue((long) field(script, "lastHopRefusedAt") >= before); + assertEquals(0L, field(script, "lastWorldHopTime")); + } + } + + @Test + void inCombatSkipsTheHopAndLeavesPrayersAlone() throws Exception { + SlayerScript script = scriptWithHopList(); + Client client = client(); + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic combat = mockStatic(Rs2Combat.class); + MockedStatic prayer = mockStatic(Rs2Prayer.class)) { + microbot.when(Microbot::getClient).thenReturn(client); + combat.when(Rs2Combat::inCombat).thenReturn(true); + + long before = System.currentTimeMillis(); + invoke(script, "hopToNewWorld"); + + microbot.verify(() -> Microbot.hopToWorld(anyInt()), never()); + prayer.verifyNoInteractions(); + assertTrue((long) field(script, "lastHopRefusedAt") >= before); + } + } + + @Test + void landedHopDoesNotWaitForSwitchWorldDialogOrPressSpace() throws Exception { + SlayerScript script = scriptWithHopList(); + Client client = client(); + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic combat = mockStatic(Rs2Combat.class); + MockedStatic prayer = mockStatic(Rs2Prayer.class); + MockedStatic widget = mockStatic(Rs2Widget.class); + MockedStatic keyboard = mockStatic(Rs2Keyboard.class)) { + microbot.when(Microbot::getClient).thenReturn(client); + combat.when(Rs2Combat::inCombat).thenReturn(false); + microbot.when(() -> Microbot.hopToWorld(TARGET_WORLD)).thenReturn(true); + + long before = System.currentTimeMillis(); + invoke(script, "hopToNewWorld"); + + keyboard.verify(() -> Rs2Keyboard.keyPress(anyInt()), never()); + widget.verifyNoInteractions(); + assertTrue((long) field(script, "lastWorldHopTime") >= before); + assertEquals(0L, field(script, "lastHopRefusedAt")); + } + } + + private static SlayerScript scriptWithHopList() throws Exception { + SlayerScript script = new SlayerScript(); + SlayerConfig config = mock(SlayerConfig.class); + when(config.hopWorldList()).thenReturn(String.valueOf(TARGET_WORLD)); + when(config.prayerFlickStyle()).thenReturn(PrayerFlickStyle.OFF); + setField(script, "config", config); + return script; + } + + private static Client client() { + Client client = mock(Client.class); + when(client.getWorld()).thenReturn(CURRENT_WORLD); + when(client.getGameState()).thenReturn(GameState.LOGGED_IN); + return client; + } + + private static void invoke(Object target, String name) throws Exception { + Method method = target.getClass().getDeclaredMethod(name); + method.setAccessible(true); + method.invoke(target); + } + + private static Object field(Object target, String name) throws Exception { + Field field = target.getClass().getDeclaredField(name); + field.setAccessible(true); + return field.get(target); + } + + private static void setField(Object target, String name, Object value) throws Exception { + Field field = target.getClass().getDeclaredField(name); + field.setAccessible(true); + field.set(target, value); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/thieving/ThievingEscapeHopTest.java b/src/test/java/net/runelite/client/plugins/microbot/thieving/ThievingEscapeHopTest.java new file mode 100644 index 0000000000..a40a8c88e0 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/thieving/ThievingEscapeHopTest.java @@ -0,0 +1,168 @@ +package net.runelite.client.plugins.microbot.thieving; + +import net.runelite.api.Client; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.thieving.enums.ThievingFood; +import net.runelite.client.plugins.microbot.thieving.enums.ThievingNpc; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.security.Login; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.List; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +class ThievingEscapeHopTest { + + private static final int TARGET_WORLD = 420; + + @Test + void refusedEscapeHopStaysPendingAndIsRetriedUntilItLands() throws Exception { + ThievingScript script = script(ThievingNpc.VYRES); + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic login = mockStatic(Login.class)) { + login.when(() -> Login.getRandomWorld(anyBoolean(), any())).thenReturn(TARGET_WORLD); + microbot.when(() -> Microbot.hopToWorld(TARGET_WORLD)).thenReturn(false); + + invoke(script, "requestEscapeHop"); + assertFalse(call(script, "attemptEscapeHop")); + assertTrue(call(script, "isEscapeHopPending")); + + assertFalse(call(script, "attemptEscapeHop")); + microbot.verify(() -> Microbot.hopToWorld(anyInt()), times(1)); + assertTrue(call(script, "isEscapeHopPending")); + + setField(script, "lastHopAt", System.currentTimeMillis() - 15_000); + microbot.when(() -> Microbot.hopToWorld(TARGET_WORLD)).thenReturn(true); + assertTrue(call(script, "attemptEscapeHop")); + + microbot.verify(() -> Microbot.hopToWorld(TARGET_WORLD), times(2)); + assertFalse(call(script, "isEscapeHopPending")); + } + } + + @Test + void escapeHopIsDroppedOnceTheRetryWindowRunsOut() throws Exception { + ThievingScript script = script(ThievingNpc.VYRES); + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic login = mockStatic(Login.class)) { + login.when(() -> Login.getRandomWorld(anyBoolean(), any())).thenReturn(TARGET_WORLD); + microbot.when(() -> Microbot.hopToWorld(TARGET_WORLD)).thenReturn(false); + + invoke(script, "requestEscapeHop"); + setField(script, "escapeHopUntil", System.currentTimeMillis() - 1); + + assertTrue(call(script, "attemptEscapeHop")); + assertFalse(call(script, "isEscapeHopPending")); + } + } + + @Test + void escapeHopIsDroppedWhenEscapingIsTurnedOff() throws Exception { + ThievingConfig config = mock(ThievingConfig.class); + when(config.THIEVING_NPC()).thenReturn(ThievingNpc.VYRES); + when(config.escapeAttacking()).thenReturn(false); + ThievingScript script = new ThievingScript(config, mock(ThievingPlugin.class)); + try (MockedStatic microbot = mockStatic(Microbot.class)) { + invoke(script, "requestEscapeHop"); + assertTrue(call(script, "attemptEscapeHop")); + assertFalse(call(script, "isEscapeHopPending")); + microbot.verify(() -> Microbot.hopToWorld(anyInt()), never()); + } + } + + @Test + void lowHitpointsEatBeforeAPendingEscapeHopAndTheHopStaysPending() throws Exception { + ThievingScript script = foodScript(); + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic player = mockStatic(Rs2Player.class); + MockedStatic inventory = mockStatic(Rs2Inventory.class)) { + stubIdleWorld(microbot); + inventory.when(Rs2Inventory::getInventoryFood).thenReturn(List.of(mock(Rs2ItemModel.class))); + player.when(Rs2Player::getHealthPercentage).thenReturn(30.0); + + invoke(script, "requestEscapeHop"); + + assertEquals(State.EAT, invoke(script, "getCurrentState")); + assertTrue(call(script, "isEscapeHopPending")); + + player.when(Rs2Player::getHealthPercentage).thenReturn(80.0); + assertEquals(State.ESCAPE, invoke(script, "getCurrentState")); + assertTrue(call(script, "isEscapeHopPending")); + } + } + + @Test + void pendingEscapeHopKeepsEscapingWhenThereIsNoFoodToEat() throws Exception { + ThievingScript script = foodScript(); + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic player = mockStatic(Rs2Player.class); + MockedStatic inventory = mockStatic(Rs2Inventory.class)) { + stubIdleWorld(microbot); + inventory.when(Rs2Inventory::getInventoryFood).thenReturn(List.of()); + player.when(Rs2Player::getHealthPercentage).thenReturn(30.0); + + invoke(script, "requestEscapeHop"); + + assertEquals(State.ESCAPE, invoke(script, "getCurrentState")); + assertTrue(call(script, "isEscapeHopPending")); + } + } + + private static ThievingScript foodScript() { + ThievingConfig config = mock(ThievingConfig.class); + when(config.THIEVING_NPC()).thenReturn(ThievingNpc.VYRES); + when(config.escapeAttacking()).thenReturn(true); + when(config.useFood()).thenReturn(true); + when(config.food()).thenReturn(ThievingFood.SHARK); + when(config.hitpoints()).thenReturn(50); + return new ThievingScript(config, mock(ThievingPlugin.class)); + } + + private static void stubIdleWorld(MockedStatic microbot) { + ClientThread clientThread = mock(ClientThread.class); + when(clientThread.runOnClientThreadOptional(any())).thenReturn(Optional.empty()); + microbot.when(Microbot::getClientThread).thenReturn(clientThread); + microbot.when(Microbot::getClient).thenReturn(mock(Client.class)); + } + + private static ThievingScript script(ThievingNpc npc) { + ThievingConfig config = mock(ThievingConfig.class); + when(config.THIEVING_NPC()).thenReturn(npc); + when(config.escapeAttacking()).thenReturn(true); + return new ThievingScript(config, mock(ThievingPlugin.class)); + } + + private static Object invoke(Object target, String name) throws Exception { + Method method = target.getClass().getDeclaredMethod(name); + method.setAccessible(true); + return method.invoke(target); + } + + private static boolean call(Object target, String name) throws Exception { + return (boolean) invoke(target, name); + } + + private static void setField(Object target, String name, Object value) throws Exception { + Field field = target.getClass().getDeclaredField(name); + field.setAccessible(true); + field.set(target, value); + } +}