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 6233dd29c1..d23df32335 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.6"; + public final static String version = "1.1.7"; @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 4706db437b..d7da445bcd 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 @@ -46,11 +46,12 @@ public boolean run(AmmoniteCrabConfig config) { switch (ammoniteCrabState) { case FIGHT: - if (!Microbot.getClient().getLocalPlayer().isInteracting() && !Rs2Combat.inCombat()) { + if (!Rs2Player.isInteracting() && !Rs2Combat.inCombat()) { Rs2Combat.enableAutoRetialiate(); } - if (Rs2Player.getWorldLocation().distanceTo(config.crabLocation().getFightLocation()) > 0) { + WorldPoint fightPlayerLocation = Rs2Player.getWorldLocation(); + if (fightPlayerLocation != null && fightPlayerLocation.distanceTo(config.crabLocation().getFightLocation()) > 0) { Rs2Walker.walkFastCanvas(config.crabLocation().getFightLocation()); } @@ -132,7 +133,8 @@ private void preStateChecks(AmmoniteCrabConfig config) { ammoniteCrabState = AmmoniteCrabState.HOP_WORLD; } - if (Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()).distanceTo(config.crabLocation().getWorldhopLocation()) > 10 && (ammoniteCrabState != AmmoniteCrabState.RESET_AGGRO && ammoniteCrabState != AmmoniteCrabState.WALK_BACK && ammoniteCrabState != AmmoniteCrabState.BANK)) { + WorldPoint playerLocation = Rs2Player.getWorldLocation(); + if (playerLocation != null && playerLocation.distanceTo(config.crabLocation().getWorldhopLocation()) > 10 && (ammoniteCrabState != AmmoniteCrabState.RESET_AGGRO && ammoniteCrabState != AmmoniteCrabState.WALK_BACK && ammoniteCrabState != AmmoniteCrabState.BANK)) { ammoniteCrabState = AmmoniteCrabState.WALK_BACK; resetAggro(config); resetAfkTimer(); @@ -178,7 +180,11 @@ private void scanAmmoniteCrabLocation(AmmoniteCrabConfig config) { ammoniteCrabState = AmmoniteCrabState.FIGHT; return; } - if (config.crabLocation().getWorldhopLocation().distanceTo(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation())) > 2) { + WorldPoint playerLocation = Rs2Player.getWorldLocation(); + if (playerLocation == null) { + return; + } + if (config.crabLocation().getWorldhopLocation().distanceTo(playerLocation) > 2) { Rs2Walker.walkTo(config.crabLocation().getWorldhopLocation()); return; } @@ -188,7 +194,7 @@ private void scanAmmoniteCrabLocation(AmmoniteCrabConfig config) { return; } else { Rs2Walker.walkTo(config.crabLocation().getFightLocation(), 0); - if (Rs2Player.getWorldLocation().distanceTo(config.crabLocation().getFightLocation()) <= 3) { + if (isWithin(Rs2Player.getWorldLocation(), config.crabLocation().getFightLocation(), 3)) { resetAfkTimer(); ammoniteCrabState = AmmoniteCrabState.FIGHT; } else { @@ -208,7 +214,7 @@ private void usePotions(AmmoniteCrabConfig config) { private void walkBack(AmmoniteCrabConfig config) { Rs2Walker.walkTo(config.crabLocation().getFightLocation()); - if (Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()).distanceTo(config.crabLocation().getFightLocation()) <= 3) { + if (isWithin(Rs2Player.getWorldLocation(), config.crabLocation().getFightLocation(), 3)) { attackScatteredCrabs(config); resetAfkTimer(); ammoniteCrabState = AmmoniteCrabState.FIGHT; @@ -256,7 +262,7 @@ private boolean isNpcAggressive() { private void resetAggro(AmmoniteCrabConfig config) { Rs2Walker.walkTo(config.crabLocation().getResetLocation()); - if (Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()).distanceTo(config.crabLocation().getResetLocation()) <= 3) { + if (isWithin(Rs2Player.getWorldLocation(), config.crabLocation().getResetLocation(), 3)) { ammoniteCrabState = AmmoniteCrabState.WALK_BACK; } } @@ -273,10 +279,17 @@ private void resetAfkTimer() { } private boolean otherPlayerDetected() { - return otherPlayerDetected(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation())); + return otherPlayerDetected(Rs2Player.getWorldLocation()); } - private boolean otherPlayerDetected(WorldPoint worldPoint) { + static boolean isWithin(WorldPoint location, WorldPoint target, int distance) { + return location != null && location.distanceTo(target) <= distance; + } + + static boolean otherPlayerDetected(WorldPoint worldPoint) { + if (worldPoint == null) { + return false; + } for (Rs2PlayerModel player : Rs2Player.getPlayers(player -> true).collect(Collectors.toList())) { if (player.getWorldLocation().distanceTo(worldPoint) > 3) continue; diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java index 34411b59be..1df5bc78cc 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java @@ -62,7 +62,7 @@ ) @Slf4j public class AIOFighterPlugin extends Plugin { - public static final String version = "2.2.3"; + public static final String version = "2.2.9"; public static boolean needShopping = false; private static final String SET = "Set"; private static final String CENTER_TILE = ColorUtil.wrapWithColorTag("Center Tile", JagexColors.MENU_TARGET); @@ -251,6 +251,10 @@ public static void setSlayerHasTaskWeakness(boolean slayerHasTaskWeakness) { } public static void setCurrentSlayerInventorySetup(InventorySetup currentInventorySetup) { + if (currentInventorySetup == null) { + Microbot.log("Inventory setup not found; keeping current inventory setup"); + return; + } Microbot.log("Setting current inventory setup to: " + currentInventorySetup.getName()); setConfig("currentInventorySetup", currentInventorySetup); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java index 5f4fe45ed8..b78be6187c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java @@ -242,6 +242,17 @@ static boolean isSetupResolvable(Collection setupNames, String setupName .anyMatch(name -> name.equalsIgnoreCase(setupName) || name.equals("default")); } + static InventorySetup findSetupByName(Collection setups, String setupName) { + if (setups == null || setupName == null) { + return null; + } + return setups.stream() + .filter(Objects::nonNull) + .filter(setup -> setupName.equalsIgnoreCase(setup.getName())) + .findFirst() + .orElse(null); + } + /** * Checks if the given setup requires any variant of a specific potion but the player does not have it. */ @@ -323,7 +334,7 @@ public boolean hasSlayerItems() { public boolean hasProtectiveSlayerEquipment() { String needed = Rs2Slayer.getSlayerTaskProtectiveEquipment(); - if ("None".equals(needed)) { + if (!requiresProtectiveEquipment(needed)) { return true; } @@ -349,7 +360,14 @@ public boolean hasProtectiveSlayerEquipment() { return result; } - private boolean isBypassedBySlayerHelmet(String item) { + static boolean requiresProtectiveEquipment(String needed) { + return needed != null && !"None".equals(needed); + } + + static boolean isBypassedBySlayerHelmet(String item) { + if (item == null) { + return false; + } switch (item) { case "Earmuffs": case "Facemask": @@ -472,7 +490,8 @@ public void withdrawUpkeepItems(AIOFighterConfig config) { Rs2Bank.withdrawAndEquip(Rs2Slayer.getSlayerTaskProtectiveEquipment()); sleep(3000); inventorySetupsPlugin.addInventorySetup("cache"); - InventorySetup inventorySetup1 = MInventorySetupsPlugin.getInventorySetups().stream().filter(Objects::nonNull).filter(x -> x.getName().equalsIgnoreCase("cache")).findFirst().orElse(null); + sleepUntil(() -> findSetupByName(MInventorySetupsPlugin.getInventorySetups(), "cache") != null, 3000); + InventorySetup inventorySetup1 = findSetupByName(MInventorySetupsPlugin.getInventorySetups(), "cache"); AIOFighterPlugin.setCurrentSlayerInventorySetup(inventorySetup1); } else { ShopScript.shopItems.add(new ShopItem(Rs2Slayer.getSlayerTaskProtectiveEquipment(), 1, Microbot.getRs2ItemManager().getItemId(Rs2Slayer.getSlayerTaskProtectiveEquipment()), ShopType.SLAYER_SHOP)); diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java index bc045039a7..277f3cb784 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java @@ -44,6 +44,8 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import java.util.function.IntUnaryOperator; +import java.util.function.ToIntFunction; import java.util.stream.Collectors; import static net.runelite.api.gameval.VarbitID.*; @@ -158,12 +160,10 @@ public void run(AIOFighterConfig config) { String name = npc.getName(); return name != null && !npcsToAttack.isEmpty() && npcsToAttack.contains(name.toLowerCase()); }) - .toList() - .stream() - .sorted(Comparator - .comparingInt((Rs2NpcModel npc) -> Objects.equals(npc.getInteracting(), localPlayer) ? 0 : 1) - .thenComparingInt(npc -> rs2PlayerPoint.distanceToPath(npcWorldLocation(npc, instanced)))) - .collect(Collectors.toList()); + .toList(); + attackableNpcs = sortBySnapshotKeys(attackableNpcs, + npc -> Objects.equals(npc.getInteracting(), localPlayer) ? 0 : 1, + npc -> rs2PlayerPoint.distanceToPath(npcWorldLocation(npc, instanced))); filteredAttackableNpcs.set(attackableNpcs); @@ -296,6 +296,20 @@ public void run(AIOFighterConfig config) { } + static List sortBySnapshotKeys(List items, ToIntFunction primary, ToIntFunction secondary) { + List keyed = new ArrayList<>(items.size()); + for (int i = 0; i < items.size(); i++) { + T item = items.get(i); + keyed.add(new int[]{primary.applyAsInt(item), secondary.applyAsInt(item), i}); + } + keyed.sort(Comparator.comparingInt(k -> k[0]).thenComparingInt(k -> k[1])); + List sorted = new ArrayList<>(keyed.size()); + for (int[] k : keyed) { + sorted.add(items.get(k[2])); + } + return sorted; + } + static boolean shouldHoldAttack(boolean inCombat, boolean antibanEnabled, double actionCooldownChance, boolean actionCooldownActive) { if (inCombat) return true; return antibanEnabled && actionCooldownChance > 0 && actionCooldownActive; @@ -310,20 +324,26 @@ private void handleItemOnNpcToKill(AIOFighterConfig config) { .where(n -> n.isDead() && Objects.equals(n.getInteracting(), localPlayer)) .first(); if (npc == null) return; - List lizardVariants = new ArrayList<>(Arrays.asList("Lizard", "Desert Lizard", "Small Lizard")); + int itemId = finishingItemFor(npc.getName(), npc.getHealthRatio(), Microbot::getVarbitValue); + if (itemId == -1) return; // Rs2Inventory.useItemOnNpc only accepts the legacy util.npc.Rs2NpcModel — wrap our underlying NPC at the call site. net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel legacyNpc = new net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel(npc.getNpc()); - if (Microbot.getVarbitValue(SLAYER_AUTOKILL_DESERTLIZARDS) == 0 && lizardVariants.contains(npc.getName()) && npc.getHealthRatio() < 5) { - Rs2Inventory.useItemOnNpc(ItemID.SLAYER_ICY_WATER, legacyNpc); - Rs2Player.waitForAnimation(); - } else if (Microbot.getVarbitValue(SLAYER_AUTOKILL_ROCKSLUGS) == 0 && npc.getName().equalsIgnoreCase("rockslug") && npc.getHealthRatio() < 5) { - Rs2Inventory.useItemOnNpc(ItemID.SLAYER_BAG_OF_SALT, legacyNpc); - Rs2Player.waitForAnimation(); - } else if (Microbot.getVarbitValue(SLAYER_AUTOKILL_GARGOYLES) == 0 && npc.getName().equalsIgnoreCase("gargoyle") && npc.getHealthRatio() < 3) { - Rs2Inventory.useItemOnNpc(ItemID.SLAYER_ROCK_HAMMER, legacyNpc); - Rs2Player.waitForAnimation(); + Rs2Inventory.useItemOnNpc(itemId, legacyNpc); + Rs2Player.waitForAnimation(); + } + + static int finishingItemFor(String name, int healthRatio, IntUnaryOperator varbits) { + if (name == null) return -1; + List lizardVariants = Arrays.asList("Lizard", "Desert Lizard", "Small Lizard"); + if (varbits.applyAsInt(SLAYER_AUTOKILL_DESERTLIZARDS) == 0 && lizardVariants.contains(name) && healthRatio < 5) { + return ItemID.SLAYER_ICY_WATER; + } else if (varbits.applyAsInt(SLAYER_AUTOKILL_ROCKSLUGS) == 0 && name.equalsIgnoreCase("rockslug") && healthRatio < 5) { + return ItemID.SLAYER_BAG_OF_SALT; + } else if (varbits.applyAsInt(SLAYER_AUTOKILL_GARGOYLES) == 0 && name.equalsIgnoreCase("gargoyle") && healthRatio < 3) { + return ItemID.SLAYER_ROCK_HAMMER; } + return -1; } /** diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/SafeSpot.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/SafeSpot.java index ac6380f48b..909bbae950 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/SafeSpot.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/SafeSpot.java @@ -19,7 +19,7 @@ public class SafeSpot extends Script { public boolean run(AIOFighterConfig config) { mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { try { - if (AIOFighterPlugin.getState().equals(State.BANKING) || AIOFighterPlugin.getState().equals(State.WALKING)) return; + if (isBlockedByState(AIOFighterPlugin.getState())) return; if (!Microbot.isLoggedIn() || !super.run() || !config.toggleSafeSpot() || Rs2Player.isMoving()) return; currentSafeSpot = config.safeSpot(); @@ -48,6 +48,10 @@ public boolean run(AIOFighterConfig config) { return true; } +static boolean isBlockedByState(State state) { + return state == State.BANKING || state == State.WALKING; +} + private boolean isDefaultSafeSpot(WorldPoint safeSpot) { return safeSpot.getX() == 0 && safeSpot.getY() == 0; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/skill/AttackStyleScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/skill/AttackStyleScript.java index 61c8e1aa8d..45b6d4f17b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/skill/AttackStyleScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/skill/AttackStyleScript.java @@ -197,14 +197,25 @@ private void updateAttackStyle(int equippedWeaponType, int attackStyleIndex, int } } - private AttackStyle[] getWeaponTypeStyles(int weaponType) { - int weaponStyleEnum = Microbot.getEnum(EnumID.WEAPON_STYLES).getIntValue(weaponType); - int[] weaponStyleStructs = Microbot.getEnum(weaponStyleEnum).getIntVals(); + static AttackStyle[] getWeaponTypeStyles(int weaponType) { + EnumComposition weaponStylesEnum = Microbot.getEnum(EnumID.WEAPON_STYLES); + if (weaponStylesEnum == null) { + return new AttackStyle[0]; + } + EnumComposition weaponStyleEnum = Microbot.getEnum(weaponStylesEnum.getIntValue(weaponType)); + if (weaponStyleEnum == null) { + return new AttackStyle[0]; + } + int[] weaponStyleStructs = weaponStyleEnum.getIntVals(); AttackStyle[] styles = new AttackStyle[weaponStyleStructs.length]; int i = 0; for (int style : weaponStyleStructs) { - String attackStyleName = Microbot.getStructComposition(style).getStringValue(ParamID.ATTACK_STYLE_NAME); + StructComposition struct = Microbot.getStructComposition(style); + String attackStyleName = struct == null ? null : struct.getStringValue(ParamID.ATTACK_STYLE_NAME); + if (attackStyleName == null) { + return new AttackStyle[0]; + } AttackStyle attackStyle = AttackStyle.valueOf(attackStyleName.toUpperCase()); if (attackStyle == AttackStyle.OTHER) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/bankorganizer/BankOrganizerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/bankorganizer/BankOrganizerPlugin.java index ab7c52d416..48e7c88dc6 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/bankorganizer/BankOrganizerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/bankorganizer/BankOrganizerPlugin.java @@ -42,7 +42,7 @@ @Slf4j public class BankOrganizerPlugin extends Plugin { - public static final String version = "1.0.0"; + public static final String version = "1.0.1"; @Inject private BankOrganizerConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/bankorganizer/BankSnapshotReader.java b/src/main/java/net/runelite/client/plugins/microbot/bankorganizer/BankSnapshotReader.java index 22de962226..8b96ae1891 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/bankorganizer/BankSnapshotReader.java +++ b/src/main/java/net/runelite/client/plugins/microbot/bankorganizer/BankSnapshotReader.java @@ -8,6 +8,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.Optional; import javax.inject.Inject; import net.runelite.api.Client; import net.runelite.api.ItemComposition; @@ -44,14 +45,15 @@ final class BankSnapshotReader BankSnapshot read() { return Microbot.getClientThread().runOnClientThreadOptional(this::readOnClientThread) - .orElseThrow(() -> new IllegalStateException("Could not read bank snapshot on the client thread.")); + .orElseThrow(() -> new IllegalStateException("Could not read bank snapshot on the client thread.")) + .orElseThrow(() -> new IllegalStateException("Open your bank before running Bank Organizer.")); } - private BankSnapshot readOnClientThread() + private Optional readOnClientThread() { if (!Rs2Bank.isOpen()) { - throw new IllegalStateException("Open your bank before running Bank Organizer."); + return Optional.empty(); } List bankItems = new ArrayList<>(Rs2Bank.bankItems()); @@ -83,7 +85,7 @@ private BankSnapshot readOnClientThread() isEquipable(composition))); } - return new BankSnapshot(stacks, tabCounts, client.getVarbitValue(Varbits.CURRENT_BANK_TAB)); + return Optional.of(new BankSnapshot(stacks, tabCounts, client.getVarbitValue(Varbits.CURRENT_BANK_TAB))); } private int[] readTabCounts() diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/ClueSolverPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/ClueSolverPlugin.java index 5ae92659d6..f84df3f516 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/ClueSolverPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/ClueSolverPlugin.java @@ -29,7 +29,7 @@ @PluginDependency(ClueScrollPlugin.class) public class ClueSolverPlugin extends Plugin { - final static String version = "1.0.5"; + final static String version = "1.0.7"; @Inject private ClueSolverScript clueSolverScript; diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/AnagramClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/AnagramClueTask.java index 6f97c3ccfb..0cad7036cb 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/AnagramClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/AnagramClueTask.java @@ -183,9 +183,11 @@ private boolean handleDialogue() { Rs2Dialogue.clickContinue(); } - Rs2Keyboard.typeString(answer); - Rs2Keyboard.enter(); - Rs2Dialogue.sleepUntilHasContinue(); + if (answer != null && !answer.isBlank()) { + Rs2Keyboard.typeString(answer); + Rs2Keyboard.enter(); + Rs2Dialogue.sleepUntilHasContinue(); + } if (Rs2Dialogue.hasContinue()) { Rs2Dialogue.clickContinue(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/RequirementHandlerTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/RequirementHandlerTask.java index 9231599ee4..778d709310 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/RequirementHandlerTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/RequirementHandlerTask.java @@ -5,6 +5,7 @@ import net.runelite.api.Client; import net.runelite.api.InventoryID; import net.runelite.api.Item; +import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.GameTick; import net.runelite.api.events.ItemContainerChanged; import net.runelite.client.callback.ClientThread; @@ -15,11 +16,13 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.cluesolver.ClueSolverPlugin; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; +import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; @Slf4j @@ -28,8 +31,10 @@ public class RequirementHandlerTask extends ClueTask { private final EventBus eventBus; private final ExecutorService backgroundExecutor; private Iterator requirementIterator; - private CompletableFuture itemReceivedFuture; - private ItemRequirement currentRequirement; + private volatile CompletableFuture itemReceivedFuture; + private volatile ItemRequirement currentRequirement; + private volatile BankLocation targetBank; + private final AtomicBoolean bankStepRunning = new AtomicBoolean(false); @Inject private ClientThread clientThread; @@ -45,22 +50,49 @@ public RequirementHandlerTask(Client client, List requirements, protected boolean executeTask() { eventBus.register(this); log.info("Checking for missing items."); - checkForMissingItemsAsync(); + runBankStep(this::checkForMissingItems); return true; } - /** - * Retrieves a list of missing items based on the requirements asynchronously on the client thread. - */ - private void checkForMissingItemsAsync() { - Microbot.getClientThread().invokeLater(() -> { - List missingItems = getMissingItems(requirements); - if (missingItems.isEmpty()) { - completeTask(true); - } else { - fetchMissingItemsFromBank(missingItems); - } - }); + private void runBankStep(Runnable step) { + if (!bankStepRunning.compareAndSet(false, true)) { + return; + } + try { + backgroundExecutor.submit(() -> { + try { + step.run(); + } catch (Exception e) { + log.warn("Requirement bank step failed: {}", e.getMessage(), e); + completeTask(false); + } finally { + bankStepRunning.set(false); + } + }); + } catch (RejectedExecutionException e) { + bankStepRunning.set(false); + completeTask(false); + } + } + + private void checkForMissingItems() { + List missingItems = Microbot.getClientThread() + .runOnClientThreadOptional(() -> getMissingItems(requirements)) + .orElse(null); + if (missingItems == null) { + log.warn("Unable to read inventory and equipment for item requirements."); + completeTask(false); + } else if (missingItems.isEmpty()) { + completeTask(true); + } else { + fetchMissingItemsFromBank(missingItems); + } + } + + private String getItemName(ItemRequirement requirement) { + return Microbot.getClientThread() + .runOnClientThreadOptional(() -> requirement.getCollectiveName(client)) + .orElse("N/A"); } private List getMissingItems(List requirements) { @@ -78,7 +110,7 @@ private List getMissingItems(List requirements private void fetchMissingItemsFromBank(List missingItems) { this.requirementIterator = missingItems.iterator(); - log.info("Missing items: {}", missingItems.stream().map(e -> e.getCollectiveName(client)).collect(Collectors.joining(", "))); + log.info("Missing items: {}", missingItems.stream().map(this::getItemName).collect(Collectors.joining(", "))); if (requirementIterator == null || !requirementIterator.hasNext()) { completeTask(true); @@ -96,13 +128,24 @@ private void walkToBank() { // be called from off-client-thread code paths (executeTask flow). Direct // client.getLocalPlayer().getWorldLocation() throws IllegalStateException, which // cascaded into "Failed to open the bank" + walker deadlock. - net.runelite.api.coords.WorldPoint playerLocation = getPlayerLocationSafe(); + WorldPoint playerLocation = getPlayerLocationSafe(); if (playerLocation == null) { - log.debug("Player location unavailable in walkToBank; will retry next tick"); + log.warn("Player location unavailable in walkToBank."); + completeTask(false); return; } - if (playerLocation.distanceTo(Rs2Bank.getNearestBank().getWorldPoint()) > 10) { - Rs2Bank.walkToBankAndUseBank(); + BankLocation nearestBank = Rs2Bank.getNearestBank(); + if (nearestBank == null) { + log.warn("No reachable bank found."); + completeTask(false); + return; + } + targetBank = nearestBank; + if (playerLocation.distanceTo(nearestBank.getWorldPoint()) > 10) { + if (!Rs2Bank.walkToBankAndUseBank()) { + log.warn("Failed to walk to the bank."); + completeTask(false); + } } else { openBank(); } @@ -123,7 +166,7 @@ private void fetchNextItem() { } currentRequirement = requirementIterator.next(); - String itemName = currentRequirement.getCollectiveName(client); + String itemName = getItemName(currentRequirement); if (!Rs2Bank.hasItem(itemName)) { log.warn("Item {} not found in bank.", itemName); @@ -132,8 +175,8 @@ private void fetchNextItem() { return; } - Rs2Bank.withdrawAndEquip(itemName); itemReceivedFuture = new CompletableFuture<>(); + Rs2Bank.withdrawAndEquip(itemName); backgroundExecutor.submit(() -> { try { @@ -158,16 +201,20 @@ private void fetchNextItem() { @Subscribe public void onGameTick(GameTick event) { + if (bankStepRunning.get()) { + return; + } if (Rs2Bank.isOpen() && currentRequirement == null) { - fetchNextItem(); + runBankStep(this::fetchNextItem); return; } // v1.0.4 fix: defensive client-thread-safe read. onGameTick is @Subscribe-annotated so // it normally runs on the client thread, but using the helper is harmless and consistent. - net.runelite.api.coords.WorldPoint playerLocation = getPlayerLocationSafe(); - if (playerLocation == null) return; - if (!Rs2Bank.isOpen() && playerLocation.distanceTo(Rs2Bank.getNearestBank().getWorldPoint()) <= 5) { - openBank(); + WorldPoint playerLocation = getPlayerLocationSafe(); + BankLocation bank = targetBank; + if (playerLocation == null || bank == null) return; + if (!Rs2Bank.isOpen() && playerLocation.distanceTo(bank.getWorldPoint()) <= 5) { + runBankStep(this::openBank); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPlugin.java index 98a3e0aa5e..276d08f405 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPlugin.java @@ -30,7 +30,7 @@ ) @Slf4j public class FarmTreeRunPlugin extends Plugin { - public static final String version = "1.2.2"; + public static final String version = "1.2.5"; @Inject private FarmTreeRunConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java index 1833743fcc..119f417a7e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java @@ -42,7 +42,6 @@ public class FarmTreeRunScript extends Script { public static net.runelite.client.plugins.microbot.farmtreerun.enums.FarmTreeRunState botStatus; public static boolean test = false; public static Integer compostItemId = null; - private List items = new ArrayList<>(); private final FarmTreeRunPlugin plugin; private final FarmTreeRunConfig config; @@ -558,7 +557,7 @@ private static boolean isInterruption(Throwable error) { } private void bank(FarmTreeRunConfig config) { - items.clear(); + List items = new ArrayList<>(); if (Rs2Bank.openBank() || Rs2Bank.walkToBank()) { sleepUntil(Rs2Bank::isOpen, 5000); if (!Rs2Bank.isOpen()) @@ -704,11 +703,11 @@ private void bank(FarmTreeRunConfig config) { } if (config.travelTablets()) { - addAvailableTravelItem(net.runelite.api.ItemID.VARROCK_TELEPORT, 2); - addAvailableTravelItem(net.runelite.api.ItemID.LUMBRIDGE_TELEPORT, 1); - addAvailableTravelItem(net.runelite.api.ItemID.FALADOR_TELEPORT, 2); - addAvailableTravelItem(net.runelite.api.ItemID.CAMELOT_TELEPORT, 1); - if (!config.preferHouseTravel()) addAvailableTravelItem(net.runelite.api.ItemID.TELEPORT_TO_HOUSE, 3); + addAvailableTravelItem(items, net.runelite.api.ItemID.VARROCK_TELEPORT, 2); + addAvailableTravelItem(items, net.runelite.api.ItemID.LUMBRIDGE_TELEPORT, 1); + addAvailableTravelItem(items, net.runelite.api.ItemID.FALADOR_TELEPORT, 2); + addAvailableTravelItem(items, net.runelite.api.ItemID.CAMELOT_TELEPORT, 1); + if (!config.preferHouseTravel()) addAvailableTravelItem(items, net.runelite.api.ItemID.TELEPORT_TO_HOUSE, 3); } else { items.add(new FarmingItem(ItemID.LAW_RUNE, 10)); items.add(new FarmingItem(ItemID.FIRE_RUNE, 30)); @@ -717,31 +716,25 @@ private void bank(FarmTreeRunConfig config) { items.add(new FarmingItem(ItemID.WATER_RUNE, 30)); } if (config.travelKourendBook()) { - addFirstTravelItem(net.runelite.api.ItemID.BOOK_OF_THE_DEAD, net.runelite.api.ItemID.KHAREDSTS_MEMOIRS); + addFirstTravelItem(items, net.runelite.api.ItemID.BOOK_OF_THE_DEAD, net.runelite.api.ItemID.KHAREDSTS_MEMOIRS); } if (config.preferHouseTravel()) { items.add(new FarmingItem(net.runelite.api.ItemID.TELEPORT_TO_HOUSE, 10)); } if (config.travelQuetzal()) { - addFirstTravelItem(net.runelite.api.ItemID.PERFECTED_QUETZAL_WHISTLEI, + addFirstTravelItem(items, net.runelite.api.ItemID.PERFECTED_QUETZAL_WHISTLEI, net.runelite.api.ItemID.PERFECTED_QUETZAL_WHISTLE, net.runelite.api.ItemID.ENHANCED_QUETZAL_WHISTLE, net.runelite.api.ItemID.BASIC_QUETZAL_WHISTLE); } if (config.travelFairyStaff()) { - addFirstTravelItem(net.runelite.api.ItemID.DRAMEN_STAFF, net.runelite.api.ItemID.LUNAR_STAFF); + addFirstTravelItem(items, net.runelite.api.ItemID.DRAMEN_STAFF, net.runelite.api.ItemID.LUNAR_STAFF); } // TODO: Need to handle what happens if a required item does not exist // Merge entries with the same itemId + noted flag so withdrawal doesn't under-count - Map merged = new LinkedHashMap<>(); - for (FarmingItem item : items) { - long key = ((long) item.getItemId() << 1) | (item.isNoted() ? 1 : 0); - merged.merge(key, item, (a, b) -> - new FarmingItem(a.getItemId(), a.getQuantity() + b.getQuantity(), a.isNoted(), a.isOptional())); - } - items = new ArrayList<>(merged.values()); + items = mergeItems(items); // Deposit only what we don't need: keep desired ids and their noted variants Set keepIds = new HashSet<>(); @@ -824,16 +817,26 @@ private void checkIfPlayerHasItem(int itemId, int quantity, boolean optional) { } } - private void addAvailableTravelItem(int id, int quantity) { + static List mergeItems(List items) { + Map merged = new LinkedHashMap<>(); + for (FarmingItem item : items) { + long key = ((long) item.getItemId() << 1) | (item.isNoted() ? 1 : 0); + merged.merge(key, item, (a, b) -> + new FarmingItem(a.getItemId(), a.getQuantity() + b.getQuantity(), a.isNoted(), a.isOptional())); + } + return new ArrayList<>(merged.values()); + } + + private void addAvailableTravelItem(List items, int id, int quantity) { int available = Rs2Inventory.itemQuantity(id) + Rs2Bank.count(id); if (available > 0) items.add(new FarmingItem(id, Math.min(quantity, available), false, true)); } - private void addFirstTravelItem(int... ids) { + private void addFirstTravelItem(List items, int... ids) { for (int id : ids) { if (Rs2Equipment.isWearing(id)) return; if (Rs2Inventory.hasItem(id) || Rs2Bank.hasItem(id)) { - addAvailableTravelItem(id, 1); + addAvailableTravelItem(items, id, 1); return; } } @@ -865,7 +868,11 @@ private boolean handlePatch(FarmTreeRunConfig config, Patch patch) { // Gagex named actions differently, sometimes it's Pick-fruit and sometimes Pick-banana. // Also seen "Chop down" and "Chop-down". - List exactTreeActions = Arrays.stream(Rs2GameObject.findObjectComposition(treePatch.getId()).getActions()).collect(Collectors.toList()); + ObjectComposition treeComposition = Rs2GameObject.findObjectComposition(treePatch.getId()); + if (treeComposition == null || treeComposition.getActions() == null) { + return false; + } + List exactTreeActions = Arrays.stream(treeComposition.getActions()).collect(Collectors.toList()); for (String action : exactTreeActions) { if (action == null) continue; @@ -951,13 +958,13 @@ private void handleNotingFruit(Patch patch) { * @return {@code true} if payment was successful, else {@code false} */ private boolean handlePayment(FarmTreeRunConfig config, Patch patch, PaymentKind action) { - if (isTreePatch(patch) && !isPatchEmpty(patch) && !shouldProtectTree(config) && action != PaymentKind.CLEAR) + if (isTreePatch(patch) && !shouldProtectTree(config) && action != PaymentKind.CLEAR) return true; - if (isFruitTreePatch(patch) && !isPatchEmpty(patch) && !shouldProtectFruitTree(config) && action != PaymentKind.CLEAR) + if (isFruitTreePatch(patch) && !shouldProtectFruitTree(config) && action != PaymentKind.CLEAR) return true; - if (isHardTreePatch(patch) && !isPatchEmpty(patch) && !shouldProtectHardTree(config) && action != PaymentKind.CLEAR) + if (isHardTreePatch(patch) && !shouldProtectHardTree(config) && action != PaymentKind.CLEAR) return true; int paymentId = isHardTreePatch(patch) ? config.selectedHardTree().getPaymentId() @@ -1204,8 +1211,12 @@ private boolean isCompostEnabled(FarmTreeRunConfig config) { } private boolean isPatchEmpty(Patch patch) { - String name = Rs2GameObject.getObjectComposition(patch.getId()).getName().toLowerCase(); - return name.endsWith("patch"); + return isEmptyPatchComposition(Rs2GameObject.getObjectComposition(patch.getId())); + } + + static boolean isEmptyPatchComposition(ObjectComposition composition) { + if (composition == null || composition.getName() == null) return true; + return composition.getName().toLowerCase().endsWith("patch"); } private Integer getLinkedId(int id) { @@ -1271,7 +1282,6 @@ private void handleExoticGardeners() { @Override public void shutdown() { if(isRunning()) { - items.clear(); super.shutdown(); Microbot.stopPlugin(plugin); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/gotr/GotrPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/gotr/GotrPlugin.java index 0a47d3a605..3f38de67f0 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/gotr/GotrPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/gotr/GotrPlugin.java @@ -41,7 +41,7 @@ ) @Slf4j public class GotrPlugin extends Plugin { - public static final String version = "1.5.9"; + public static final String version = "1.5.10"; @Inject private GotrConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/gotr/GotrScript.java b/src/main/java/net/runelite/client/plugins/microbot/gotr/GotrScript.java index cd27633d52..dff9f6b40c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/gotr/GotrScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/gotr/GotrScript.java @@ -678,6 +678,14 @@ public boolean isInHugeMine() { && Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation().getX()) <= hugeMineX; } + static boolean isActiveAltarRenderable(Renderable renderable) { + if (!(renderable instanceof DynamicObject)) { + return false; + } + Animation animation = ((DynamicObject) renderable).getAnimation(); + return animation != null && animation.getId() == 9363; + } + public static boolean isGuardianPortal(GameObject gameObject) { return guardianPortalInfo.containsKey(gameObject.getId()); } @@ -745,10 +753,7 @@ public static List getAvailableAltars() { return false; } - if (((DynamicObject) x.getRenderable()).getAnimation() == null) { - return false; - } - if (((DynamicObject) x.getRenderable()).getAnimation().getId() != 9363) { + if (!isActiveAltarRenderable(x.getRenderable())) { return false; } Microbot.log("Adding " + portalInfo.getName() + " to list of available altars"); diff --git a/src/main/java/net/runelite/client/plugins/microbot/jad/JadPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/jad/JadPlugin.java index a5acdeef74..0ce3b785e9 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/jad/JadPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/jad/JadPlugin.java @@ -32,7 +32,7 @@ ) @Slf4j public class JadPlugin extends Plugin { - static final String version = "1.0.10"; + static final String version = "1.0.11"; @Inject private JadConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/jad/JadScript.java b/src/main/java/net/runelite/client/plugins/microbot/jad/JadScript.java index a67567e2fd..30891e748a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/jad/JadScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/jad/JadScript.java @@ -1,5 +1,6 @@ package net.runelite.client.plugins.microbot.jad; +import net.runelite.api.Actor; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; @@ -41,7 +42,7 @@ public boolean run(JadConfig config) { int currentTick = Microbot.getClient().getTickCount(); List jadNpcs = Microbot.getRs2NpcCache().query() - .where(n -> n.getName() != null && n.getName().toLowerCase().contains("jad")) + .where(n -> nameContains(n, "jad")) .toList(); Set activeJadIndexes = new HashSet<>(); @@ -83,16 +84,17 @@ public boolean run(JadConfig config) { private void handleHealerInteraction() { var healer = Microbot.getRs2NpcCache().query() - .where(n -> n.getName() != null && n.getName().toLowerCase().contains("hurkot") && !n.isInteractingWithPlayer()) + .where(n -> nameContains(n, "hurkot") && !n.isInteractingWithPlayer()) .nearest(); if (healer != null) { healer.click("Attack"); } else { var npc = Rs2Player.getInteracting(); - if (npc == null || npc.getName().contains("hurkot")) { + String npcName = npc == null ? null : npc.getName(); + if (npc == null || (npcName != null && npcName.contains("hurkot"))) { var jad = Microbot.getRs2NpcCache().query() - .where(n -> n.getName() != null && n.getName().toLowerCase().contains("jad")) + .where(n -> nameContains(n, "jad")) .nearest(); if (jad != null) { jad.click("Attack"); @@ -102,6 +104,11 @@ private void handleHealerInteraction() { } + static boolean nameContains(Actor actor, String part) { + String name = actor.getName(); + return name != null && name.toLowerCase().contains(part); + } + @Override public void shutdown() { super.shutdown(); @@ -207,7 +214,7 @@ synchronized void onTrackedProjectileImpact() { boolean isInJadFight() { var jad = Microbot.getRs2NpcCache().query() - .where(n -> n.getName() != null && n.getName().toLowerCase().contains("jad")) + .where(n -> nameContains(n, "jad")) .nearest(); return jad != null; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java index e03170c3ca..fcaf3ff779 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java @@ -26,7 +26,7 @@ ) @Slf4j public class MageTrainingArenaPlugin extends Plugin { - public static final String version = "1.1.8"; + public static final String version = "1.1.9"; @Inject private MageTrainingArenaConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java b/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java index b0b0bbeddb..cb2f03012f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java @@ -6,6 +6,7 @@ import net.runelite.api.gameval.*; import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; +import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.globval.enums.InterfaceTab; @@ -584,10 +585,12 @@ private void buyReward(Rewards reward) { } Rs2Inventory.waitForInventoryChanges(() -> { - var rewardWidgets = Rs2Widget.getWidget(197, 11).getDynamicChildren(); - if (rewardWidgets == null) return; - var widget = Arrays.stream(rewardWidgets).filter(x -> x.getItemId() == reward.getItemId()).findFirst().orElse(null); - Rs2Widget.clickWidgetFast(widget, Arrays.asList(rewardWidgets).indexOf(widget)); + var rewardContainer = Rs2Widget.getWidget(197, 11); + if (rewardContainer == null) return; + var rewardWidgets = rewardContainer.getDynamicChildren(); + var index = findRewardIndex(rewardWidgets, reward.getItemId()); + if (index < 0) return; + Rs2Widget.clickWidgetFast(rewardWidgets[index], index); sleepGaussian(600, 150); Rs2Widget.clickWidget(197, 9); sleepGaussian(600, 150); @@ -605,6 +608,14 @@ private void buyReward(Rewards reward) { } } + static int findRewardIndex(Widget[] rewardWidgets, int itemId) { + if (rewardWidgets == null) return -1; + for (int i = 0; i < rewardWidgets.length; i++) { + if (rewardWidgets[i] != null && rewardWidgets[i].getItemId() == itemId) return i; + } + return -1; + } + public static Rooms getCurrentRoom() { for (var room : Rooms.values()) { if (room == Rooms.TELEKINETIC && Arrays.stream(TelekineticRooms.values()).anyMatch(x -> Rs2Player.getWorldLocation().distanceTo(x.getArea()) == 0) diff --git a/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesPlugin.java index 772d69c1cc..41f53b416c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesPlugin.java @@ -55,7 +55,7 @@ isExternal = PluginConstants.IS_EXTERNAL ) public class MahoganyHomesPlugin extends Plugin { - public static final String version = "0.0.13"; + public static final String version = "0.0.16"; private static final List PLANKS = Arrays.asList(ItemID.PLANK, ItemID.OAK_PLANK, ItemID.TEAK_PLANK, ItemID.MAHOGANY_PLANK); private static final List PLANK_NAMES = Arrays.asList("Plank", "Oak plank", "Teak plank", "Mahogany plank"); private static final Map MAHOGANY_HOMES_REPAIRS = new HashMap<>(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScript.java b/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScript.java index d78aaa70ad..2ee29bf5cb 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScript.java @@ -53,6 +53,7 @@ public boolean run(MahoganyHomesConfig config) { try { if (!Microbot.isLoggedIn()) return; if (!super.run()) return; + if (Rs2Player.getWorldLocation() == null) return; checkPlankSack(); fix(); finish(); @@ -73,10 +74,7 @@ private List getFixableObjects() { List fixableHotspots = Hotspot.getBrokenHotspots(); HotspotObjects hotspotObjects = plugin.getCurrentHome().getHotspotObjects(); - // Precompute the set of IDs - Set ids = fixableHotspots.stream() - .map(hotspot -> hotspotObjects.objects[hotspot.ordinal()].getObjectId()) - .collect(Collectors.toSet()); + Set ids = getFixableObjectIds(fixableHotspots, hotspotObjects); // Filter using the precomputed set return objects.stream() @@ -85,6 +83,13 @@ private List getFixableObjects() { .collect(Collectors.toList()); } + static Set getFixableObjectIds(List hotspots, HotspotObjects hotspotObjects) { + return hotspots.stream() + .filter(hotspot -> hotspot.ordinal() < hotspotObjects.objects.length) + .map(hotspot -> hotspotObjects.objects[hotspot.ordinal()].getObjectId()) + .collect(Collectors.toSet()); + } + // Custom logging methods private void log(String message) { if (plugin.getConfig().logMessages()) { @@ -393,8 +398,12 @@ private void getNewContract() { } return; } - WorldPoint contractLocation = getClosestContractLocation(); - if (contractLocation.distanceTo2D(Rs2Player.getWorldLocation()) > 10) { + WorldPoint playerLocation = Rs2Player.getWorldLocation(); + WorldPoint contractLocation = getClosestContractLocation(playerLocation); + if (contractLocation == null) { + return; + } + if (contractLocation.distanceTo2D(playerLocation) > 10) { log("Walking to contract NPC"); Rs2Walker.walkWithState(contractLocation, 5); @@ -536,9 +545,8 @@ public void handleContractDialogue() { // Bank if we need to private void bank() { Home currentHome = plugin.getCurrentHome(); - if (currentHome != null - && plugin.distanceBetween(currentHome.getArea(), Rs2Player.getWorldLocation()) > 0 - && isMissingItems()) { + if (isAwayFromHome(currentHome, Rs2Player.getWorldLocation()) + && isMissingItems(currentHome)) { ShortestPathPlugin.getPathfinderConfig().setIgnoreTeleportAndItems(true); BankLocation bankLocation = Rs2Bank.getNearestBank(currentHome.getLocation()); ShortestPathPlugin.getPathfinderConfig().setIgnoreTeleportAndItems(false); @@ -577,19 +585,19 @@ && isMissingItems()) { } } else { // Withdraw steel bars first if needed - if (steelBarsNeeded() > steelBarsInInventory()) { - Rs2Bank.withdrawX(ItemID.STEEL_BAR, steelBarsNeeded() - steelBarsInInventory()); + if (steelBarsNeeded(currentHome) > steelBarsInInventory()) { + Rs2Bank.withdrawX(ItemID.STEEL_BAR, steelBarsNeeded(currentHome) - steelBarsInInventory()); Rs2Inventory.waitForInventoryChanges(5000); } // Calculate if we'll have enough space for planks after steel bars int freeSlots = Rs2Inventory.emptySlotCount(); int currentPlanks = planksInInventory() + planksInPlankSack(); - int additionalPlanksNeeded = planksNeeded() - currentPlanks; + int additionalPlanksNeeded = planksNeeded(currentHome) - currentPlanks; if (additionalPlanksNeeded <= 0) { // We already have enough planks - log("Already have sufficient planks: %d/%d", currentPlanks, planksNeeded()); + log("Already have sufficient planks: %d/%d", currentPlanks, planksNeeded(currentHome)); } else if (freeSlots >= additionalPlanksNeeded) { // Withdraw all planks to fill inventory Rs2Bank.withdrawAll(plugin.getConfig().currentTier().getPlankSelection().getPlankId()); @@ -612,25 +620,31 @@ && isMissingItems()) { // Walk to current home private void walkToHome() { Home currentHome = plugin.getCurrentHome(); - if (currentHome != null - && plugin.distanceBetween(currentHome.getArea(), Rs2Player.getWorldLocation()) > 0 - && !isMissingItems()) { - Rs2Walker.walkWithStateUntil(plugin.getCurrentHome().getLocation(), 3, + if (isAwayFromHome(currentHome, Rs2Player.getWorldLocation()) + && !isMissingItems(currentHome)) { + Rs2Walker.walkWithStateUntil(currentHome.getLocation(), 3, () -> readyFurniture() != null); if (readyFurniture() != null) fix(); } } - private boolean isMissingItems() { - return (planksInInventory() + planksInPlankSack()) < planksNeeded() - || steelBarsInInventory() < steelBarsNeeded(); + + boolean isAwayFromHome(Home home, WorldPoint playerLocation) { + return home != null + && playerLocation != null + && plugin.distanceBetween(home.getArea(), playerLocation) > 0; + } + + private boolean isMissingItems(Home home) { + return (planksInInventory() + planksInPlankSack()) < planksNeeded(home) + || steelBarsInInventory() < steelBarsNeeded(home); } - private int planksNeeded() { - return plugin.getCurrentHome().getRequiredPlanks(plugin.getContractTier()); + private int planksNeeded(Home home) { + return home.getRequiredPlanks(plugin.getContractTier()); } - private int steelBarsNeeded() { - return plugin.getCurrentHome().getRequiredSteelBars(plugin.getContractTier()); + private int steelBarsNeeded(Home home) { + return home.getRequiredSteelBars(plugin.getContractTier()); } private int planksInInventory() { @@ -642,7 +656,10 @@ private int steelBarsInInventory() { } // Get closest contract location - private WorldPoint getClosestContractLocation() { + static WorldPoint getClosestContractLocation(WorldPoint playerLocation) { + if (playerLocation == null) { + return null; + } List contractLocations = new ArrayList<>(); contractLocations.add(ContractLocation.MAHOGANY_HOMES_ARDOUGNE.getLocation()); contractLocations.add(ContractLocation.MAHOGANY_HOMES_FALADOR.getLocation()); @@ -650,7 +667,7 @@ private WorldPoint getClosestContractLocation() { contractLocations.add(ContractLocation.MAHOGANY_HOMES_VARROCK.getLocation()); return contractLocations.stream() - .min(Comparator.comparingInt(wp -> wp.distanceTo2D(Rs2Player.getWorldLocation()))) + .min(Comparator.comparingInt(wp -> wp.distanceTo2D(playerLocation))) .orElse(null); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/MoonsOfPerilPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/MoonsOfPerilPlugin.java index 7b791187b1..e76c3d1e7a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/MoonsOfPerilPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/MoonsOfPerilPlugin.java @@ -35,7 +35,7 @@ ) @Slf4j public class MoonsOfPerilPlugin extends Plugin { - static final String version = "1.0.4"; + static final String version = "1.0.5"; @Inject private MoonsOfPerilConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/ResupplyHandler.java b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/ResupplyHandler.java index d10598a152..a151f5e16f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/ResupplyHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/ResupplyHandler.java @@ -17,6 +17,8 @@ public class ResupplyHandler implements BaseHandler { + static final int BREAM_FISHING_SPOT_ID = ObjectID.RIVER_FISHING_SPOT; + static final long FISHING_STALL_TIMEOUT_MS = 60_000; private final int minMoonlightPotions; private final int minCookedBream; @@ -173,6 +175,8 @@ private void fishBream() { Rs2Walker.walkFastCanvas(new WorldPoint(1520, 9689, 0)); + int lastFreeSlots = Rs2Inventory.emptySlotCount(); + long lastProgressAt = System.currentTimeMillis(); while (!Rs2Inventory.isFull()) { // If for any reason we lost the net mid-loop, bail if (!Rs2Inventory.contains(ItemID.BIG_NET)) { @@ -180,9 +184,22 @@ private void fishBream() { return; } + int freeSlots = Rs2Inventory.emptySlotCount(); + long now = System.currentTimeMillis(); + if (freeSlots != lastFreeSlots) { + lastFreeSlots = freeSlots; + lastProgressAt = now; + } else if (isFishingStalled(lastProgressAt, now)) { + Microbot.log("No bream caught in " + FISHING_STALL_TIMEOUT_MS / 1000 + "s; giving up on fishing this resupply."); + return; + } + if (!Rs2Player.isAnimating()) { - Microbot.getRs2TileObjectCache().query().interact(51367, "Fish"); - sleep(3000, 4000); + if (Microbot.getRs2TileObjectCache().query().interact(BREAM_FISHING_SPOT_ID, "Fish")) { + sleep(3000, 4000); + } else if (debugLogging) { + Microbot.log("Bream fishing spot not found."); + } } sleep(300, 500); } @@ -194,6 +211,10 @@ private void fishBream() { // Rs2Inventory.drop(ItemID.BIG_NET); } + static boolean isFishingStalled(long lastProgressAt, long now) { + return now - lastProgressAt >= FISHING_STALL_TIMEOUT_MS; + } + private void cookBream() { if (debugLogging) {Microbot.log("Walking to cooking stove");} Rs2Walker.walkFastCanvas(new WorldPoint(1512, 9693, 0)); diff --git a/src/main/java/net/runelite/client/plugins/microbot/motherloadmine/MotherloadMinePlugin.java b/src/main/java/net/runelite/client/plugins/microbot/motherloadmine/MotherloadMinePlugin.java index d4e6c70244..2c563e8711 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/motherloadmine/MotherloadMinePlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/motherloadmine/MotherloadMinePlugin.java @@ -29,7 +29,7 @@ ) public class MotherloadMinePlugin extends Plugin { - static final String version = "1.9.8"; + static final String version = "1.9.9"; @Inject private MotherloadMineConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/motherloadmine/MotherloadMineScript.java b/src/main/java/net/runelite/client/plugins/microbot/motherloadmine/MotherloadMineScript.java index d817bc5add..828a7af356 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/motherloadmine/MotherloadMineScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/motherloadmine/MotherloadMineScript.java @@ -39,6 +39,7 @@ import net.runelite.client.plugins.microbot.util.antiban.AntibanPlugin; import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; +import net.runelite.client.plugins.microbot.util.antiban.enums.Activity; 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.camera.Rs2Camera; @@ -158,7 +159,7 @@ private void executeTask() case IDLE: break; case MINING: - Rs2Antiban.setActivityIntensity(Rs2Antiban.getActivity().getActivityIntensity()); + Rs2Antiban.setActivityIntensity(miningIntensity(Rs2Antiban.getActivity())); handleMining(); break; case EMPTY_SACK: @@ -180,6 +181,12 @@ private void executeTask() break; } } + + static ActivityIntensity miningIntensity(Activity activity) + { + return (activity != null ? activity : Activity.GENERAL_MINING).getActivityIntensity(); + } + private String[] SPEC_PICKAXES = {"dragon pickaxe", "crystal pickaxe", "infernal pickaxe"}; private void handlePickaxeSpec() { diff --git a/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzPlugin.java index 28f0aa4273..2a4afc7bfc 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzPlugin.java @@ -31,7 +31,7 @@ ) @Slf4j public class NmzPlugin extends Plugin { - final static String version = "2.4.2"; + final static String version = "2.4.4"; @Inject private NmzConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzScript.java b/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzScript.java index 8624f37853..f168cca81b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzScript.java @@ -2,6 +2,7 @@ import lombok.Getter; import lombok.Setter; +import net.runelite.api.Player; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; @@ -146,7 +147,10 @@ public void shutdown() { } public boolean isOutside() { - WorldPoint loc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); + return isNearLobby(Rs2Player.getWorldLocation()); + } + + static boolean isNearLobby(WorldPoint loc) { return loc != null && loc.distanceTo(new WorldPoint(2602, 3116, 0)) < 20; } @@ -236,15 +240,22 @@ public boolean useOrbs() { return orbHasSpawned; } + private static WorldPoint getLocalPlayerWorldLocation() { + return Microbot.getClientThread().invoke(() -> { + Player player = Microbot.getClient().getLocalPlayer(); + return player == null ? null : player.getWorldLocation(); + }); + } + public boolean interactWithObject(int objectId) { Rs2TileObjectModel obj = tileObjectCache.query().withId(objectId).nearest(); if (obj != null) { sleep(1000, 15000); - WorldPoint playerLoc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); + WorldPoint playerLoc = getLocalPlayerWorldLocation(); if (playerLoc != null && playerLoc.distanceTo(obj.getWorldLocation()) >= 15) { Rs2Walker.walkFastLocal(obj.getLocalLocation()); sleepUntil(() -> { - WorldPoint loc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); + WorldPoint loc = getLocalPlayerWorldLocation(); return loc != null && loc.distanceTo(obj.getWorldLocation()) < 15; }, 10000); } @@ -379,7 +390,7 @@ public void consumeEmptyVial() { } sleep(2000, 4000); Widget widget = Rs2Widget.getWidget(129, 6); - if (!Microbot.getClientThread().runOnClientThreadOptional(widget::isHidden).orElse(false)) { + if (widget != null && !Microbot.getClientThread().runOnClientThreadOptional(widget::isHidden).orElse(false)) { Rs2Widget.clickWidget(widget.getId()); sleep(300); Rs2Widget.clickWidget(widget.getId()); @@ -438,14 +449,18 @@ public void handleStore() { for (int i = 0; i < overloadToBuy; i++) { Widget nmzRewardShop = Rs2Widget.getWidget(206, 6); if (nmzRewardShop == null) break; - Rs2Widget.clickWidgetFast(nmzRewardShop.getChild(6), 6, 4); + Widget potionButton = nmzRewardShop.getChild(6); + if (potionButton == null) break; + Rs2Widget.clickWidgetFast(potionButton, 6, 4); sleep(600, 1000); } for (int i = 0; i < absorptionToBuy; i++) { Widget nmzRewardShop = Rs2Widget.getWidget(206, 6); if (nmzRewardShop == null) break; - Rs2Widget.clickWidgetFast(nmzRewardShop.getChild(9), 9, 4); + Widget potionButton = nmzRewardShop.getChild(9); + if (potionButton == null) break; + Rs2Widget.clickWidgetFast(potionButton, 9, 4); sleep(600, 1000); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/nmz/PrayerPotionScript.java b/src/main/java/net/runelite/client/plugins/microbot/nmz/PrayerPotionScript.java index 037b55f042..ee8503c116 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/nmz/PrayerPotionScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/nmz/PrayerPotionScript.java @@ -16,7 +16,7 @@ public boolean run() { try { if (!Microbot.isLoggedIn()) return; if (!super.run()) return; - if ((Microbot.getClient().getBoostedSkillLevel(Skill.PRAYER) * 100) / Microbot.getClient().getRealSkillLevel(Skill.PRAYER) > Rs2Random.between(25, 30)) + if (isPrayerAboveThreshold(Microbot.getClient().getBoostedSkillLevel(Skill.PRAYER), Microbot.getClient().getRealSkillLevel(Skill.PRAYER), Rs2Random.between(25, 30))) return; List potions = Microbot.getClientThread().runOnClientThreadOptional(Rs2Inventory::getPotions).orElse(null); if (potions == null || potions.isEmpty()) { @@ -40,9 +40,10 @@ public boolean run() { public boolean run(NmzConfig config) { mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { try { + if (!Microbot.isLoggedIn()) return; if (!super.run()) return; if (!config.togglePrayerPotions()) return; - if ((Microbot.getClient().getBoostedSkillLevel(Skill.PRAYER) * 100) / Microbot.getClient().getRealSkillLevel(Skill.PRAYER) > Rs2Random.between(25, 30)) + if (isPrayerAboveThreshold(Microbot.getClient().getBoostedSkillLevel(Skill.PRAYER), Microbot.getClient().getRealSkillLevel(Skill.PRAYER), Rs2Random.between(25, 30))) return; List potions = Microbot.getClientThread().runOnClientThreadOptional(Rs2Inventory::getPotions).orElse(null); if (potions == null || potions.isEmpty()) { @@ -62,4 +63,9 @@ public boolean run(NmzConfig config) { }, 0, 600, TimeUnit.MILLISECONDS); return true; } + + static boolean isPrayerAboveThreshold(int boostedLevel, int realLevel, int thresholdPercent) { + if (realLevel <= 0) return true; + return (boostedLevel * 100) / realLevel > thresholdPercent; + } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/playermonitor/PlayerMonitorPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/playermonitor/PlayerMonitorPlugin.java index a433ff89a5..ec63f46ddc 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/playermonitor/PlayerMonitorPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/playermonitor/PlayerMonitorPlugin.java @@ -37,7 +37,7 @@ import java.util.stream.Collectors; @PluginDescriptor( - version = "1.0.4", + version = "1.0.5", name = PluginDescriptor.eXioStorm + "Player Monitor", enabledByDefault = false, minClientVersion = "2.0.0", @@ -215,7 +215,11 @@ protected void shutDown() throws Exception { if (this.flashOverlay != null) { overlayManager.remove(flashOverlay); } - saveMouseClicks(); + try { + saveMouseClicks(); + } catch (IOException e) { + log.warn("Failed to save mouse clicks", e); + } this.mouseManager.unregisterMouseListener((net.runelite.client.input.MouseListener)this.mouseListener); this.mouseListener = null; if (this.overlayOn) { @@ -275,17 +279,16 @@ public static void resetMouseClickCounterListener() { } } public static void saveMouseClicks() throws IOException { - if (!CLICK_TOTAL_FILE.exists()) - try { - if (!CLICK_TOTAL_FILE.createNewFile()) - System.out.println("Failed to create log file"); - } catch (IOException e) { - e.printStackTrace(); - } - FileWriter writer = new FileWriter(CLICK_TOTAL_FILE); - Integer[] totals = { Integer.valueOf(getLeftClickCounter()) }; - writer.write("" + totals[FILE_CLICK_TYPE_INDICES.LEFT.getValue()] + " "); - writer.close(); + writeClickCount(CLICK_TOTAL_FILE, getLeftClickCounter()); + } + static void writeClickCount(File file, int leftClicks) throws IOException { + File dir = file.getParentFile(); + if (dir != null && !dir.isDirectory() && !dir.mkdirs() && !dir.isDirectory()) + throw new IOException("Failed to create directory " + dir); + Integer[] totals = { Integer.valueOf(leftClicks) }; + try (FileWriter writer = new FileWriter(file)) { + writer.write("" + totals[FILE_CLICK_TYPE_INDICES.LEFT.getValue()] + " "); + } } public void loadMouseClicks() throws FileNotFoundException { if (!this.CLICK_TOTAL_DIR.mkdir() && CLICK_TOTAL_FILE.exists()) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java index f3bcaa937b..9ec4864232 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java @@ -91,7 +91,7 @@ ) @Slf4j public class QoLPlugin extends Plugin implements KeyListener { - public static final String version = "1.8.17"; + public static final String version = "1.8.19"; public static final List bankMenuEntries = new LinkedList<>(); public static final List furnaceMenuEntries = new LinkedList<>(); public static final List anvilMenuEntries = new LinkedList<>(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLScript.java b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLScript.java index 418467bd5b..89e5710193 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLScript.java @@ -2,6 +2,7 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.Player; +import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; @@ -15,7 +16,6 @@ import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import java.awt.*; -import java.util.Objects; import java.util.concurrent.TimeUnit; @Slf4j @@ -225,7 +225,7 @@ private void processBankMenuEntry(NewMenuEntry menuEntry) { } } } - Microbot.doInvoke(menuEntry, Objects.requireNonNull(menuEntry.getWidget()).getBounds()); + Microbot.doInvoke(menuEntry, invokeBounds(menuEntry)); //Rs2Random.wait(200, 500); } @@ -267,7 +267,7 @@ private boolean openAnvil() { private void processFurnaceMenuEntry(NewMenuEntry menuEntry) { log.info("Executing action: {} {}", menuEntry.getOption(), menuEntry.getTarget()); - Microbot.doInvoke(menuEntry, Objects.requireNonNull(menuEntry.getWidget()).getBounds()); + Microbot.doInvoke(menuEntry, invokeBounds(menuEntry)); Rs2Random.wait(200, 500); } @@ -286,10 +286,15 @@ private void handleAnvilActions() { private void processAnvilMenuEntry(NewMenuEntry menuEntry) { log.info("Executing action: {} {}", menuEntry.getOption(), menuEntry.getTarget()); - Microbot.doInvoke(menuEntry, Objects.requireNonNull(menuEntry.getWidget()).getBounds()); + Microbot.doInvoke(menuEntry, invokeBounds(menuEntry)); Rs2Random.wait(200, 500); } + static Rectangle invokeBounds(NewMenuEntry menuEntry) { + Widget widget = menuEntry.getWidget(); + return widget != null ? widget.getBounds() : new Rectangle(1, 1); + } + // reset all stored menu entries //Decrepatated use resetMenuEntries method in main class @Deprecated(since = "1.5.8 Use resetMenuEntries method in main class", forRemoval = true) diff --git a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtScript.java b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtScript.java index 66ef7dac7f..536c085853 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtScript.java @@ -5,6 +5,7 @@ import net.runelite.api.events.NpcChanged; import net.runelite.api.events.NpcDespawned; import net.runelite.api.events.NpcSpawned; +import net.runelite.api.gameval.InterfaceID; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.ObjectID; import net.runelite.api.widgets.Widget; @@ -29,6 +30,7 @@ import java.util.regex.Pattern; public class WintertodtScript extends Script { + private static final Pattern DIGITS = Pattern.compile("\\d+"); public static QoLConfig config; public static Rs2TileObjectModel unlitBrazier; public static Rs2TileObjectModel brokenBrazier; @@ -64,11 +66,10 @@ public boolean run(QoLConfig config) { return; } - Widget wintertodtHealthbar = Rs2Widget.getWidget(25952276); + Widget wintertodtHealthbar = Rs2Widget.getWidget(InterfaceID.WintStatus.ENERGY_TITLE); isWintertodtAlive = Rs2Widget.hasWidget("Wintertodt's Energy"); if (wintertodtHealthbar != null && isWintertodtAlive) { - String widgetText = wintertodtHealthbar.getText(); - wintertodtHp = Integer.parseInt(widgetText.split("\\D+")[1]); + wintertodtHp = parseWintertodtHp(wintertodtHealthbar.getText()); } else { wintertodtHp = -1; } @@ -238,11 +239,21 @@ public void onChatMessage(ChatMessage chatMessage) { } } + static int parseWintertodtHp(String widgetText) { + if (widgetText == null) { + return -1; + } + Matcher matcher = DIGITS.matcher(widgetText); + if (matcher.find()) { + return Integer.parseInt(matcher.group()); + } + return -1; + } + public int getWarmthLevel() { String warmthWidgetText = Rs2Widget.getChildWidgetText(396, 20); - Pattern pattern = Pattern.compile("\\d+"); - Matcher matcher = pattern.matcher(warmthWidgetText); + Matcher matcher = DIGITS.matcher(warmthWidgetText); if (matcher.find()) { return Integer.parseInt(matcher.group()); diff --git a/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerPlugin.java index 617f578efa..e4a5af7415 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerPlugin.java @@ -32,7 +32,7 @@ ) @Slf4j public class revKillerPlugin extends Plugin { - public static final String version = "2.0.11"; + public static final String version = "2.0.12"; @Inject private net.runelite.client.plugins.microbot.revkiller.revKillerConfig config; @Provides 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 058841d316..49f83e62d3 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 @@ -1154,7 +1154,7 @@ public void Bankfortrip(){ } if(Rs2Inventory.contains(ItemID.RING_OF_DUELING8)){ if(Rs2Inventory.interact(ItemID.RING_OF_DUELING8, "Wear")){ - sleepUntil(()-> Rs2Equipment.get(EquipmentInventorySlot.RING).getName().contains("dueling"), generateRandomNumber(5000,15000)); + sleepUntil(()-> isWearingDuelingRing(), generateRandomNumber(5000,15000)); } } } @@ -1234,7 +1234,7 @@ public void Bankfortrip(){ } if(Rs2Inventory.contains(selectedArrow)){ if(Rs2Inventory.interact(selectedArrow, "Wield")){ - sleepUntil(()-> Rs2Equipment.get(EquipmentInventorySlot.AMMO).getQuantity() > 50, generateRandomNumber(5000,15000)); + sleepUntil(()-> hasEquippedAmmoAbove(50), generateRandomNumber(5000,15000)); } } } @@ -1534,12 +1534,8 @@ public boolean isPkerAround(){ } - if ((player.getInteracting() != null) && (player.getInteracting().equals(Rs2Player.getLocalPlayer()))) { - Microbot.log(player.getName() + " is attacking us"); - return true; - } - - if((player.getInteracting() != null && player.getInteracting().getName() != null) && player.getInteracting().getName().equals(Rs2Player.getLocalPlayer().getName())){ + Actor interacting = player.getInteracting(); + if (interacting != null && interacting == Microbot.getClient().getLocalPlayer()) { Microbot.log(player.getName() + " is attacking us"); return true; } @@ -1547,4 +1543,14 @@ public boolean isPkerAround(){ } return false; } + + static boolean isWearingDuelingRing() { + Rs2ItemModel ring = Rs2Equipment.get(EquipmentInventorySlot.RING); + return ring != null && ring.getName() != null && ring.getName().contains("dueling"); + } + + static boolean hasEquippedAmmoAbove(int quantity) { + Rs2ItemModel ammo = Rs2Equipment.get(EquipmentInventorySlot.AMMO); + return ammo != null && ammo.getQuantity() > quantity; + } } 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 22ac529283..6219280c0b 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.6"; + public final static String version = "1.5.7"; @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 6639fb7089..3cf5185c26 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 @@ -4,6 +4,7 @@ import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.inventorysetups.InventorySetup; import net.runelite.client.plugins.microbot.sandcrabs.enums.State; import net.runelite.client.plugins.microbot.sandcrabs.models.ScanLocation; import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; @@ -48,6 +49,10 @@ public class SandCrabScript extends Script { List sandCrabLocations = new ArrayList<>(); + static boolean hasInventorySetup(InventorySetup setup) { + return setup != null && setup.getName() != null && !setup.getName().isEmpty(); + } + public boolean run(SandCrabConfig config, SandCrabPlugin plugin) { initialPlayerLocation = null; if (config.threeNpcs()) { @@ -63,19 +68,20 @@ public boolean run(SandCrabConfig config, SandCrabPlugin plugin) { initialPlayerLocation = Rs2Player.getWorldLocation(); } long startTime = System.currentTimeMillis(); - var inventorySetup = new Rs2InventorySetup(config.inventorySetup().getName(), mainScheduledFuture); + Rs2InventorySetup inventorySetup = null; if (!config.DisableInventorySetup()) { - if (inventorySetup == null || config.inventorySetup().getName().isEmpty()) { + if (!hasInventorySetup(config.inventorySetup())) { Microbot.showMessage("Please setup your Inventory Setup for sand crabs! Stopping..."); shutdown(); return; } + inventorySetup = new Rs2InventorySetup(config.inventorySetup().getName(), mainScheduledFuture); } Rs2Combat.enableAutoRetialiate(); - if (!inventorySetup.doesEquipmentMatch() && !config.DisableInventorySetup()) { + if (!config.DisableInventorySetup() && !inventorySetup.doesEquipmentMatch()) { if (Rs2Bank.walkToBankAndUseBank()) { if (inventorySetup.loadEquipment()) { Microbot.log("We're setup for sand crabs!"); diff --git a/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusPlugin.java index b23f8b71e2..7cacaa75e8 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusPlugin.java @@ -32,7 +32,7 @@ ) @Slf4j public class ScurriusPlugin extends Plugin { - public static final String version = "1.0.2"; + public static final String version = "1.0.4"; @Inject private ScurriusConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusScript.java b/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusScript.java index 28619c4ed7..2753c16f8c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusScript.java @@ -59,6 +59,7 @@ public boolean run(ScurriusConfig config) { try { if (!Microbot.isLoggedIn()) return; if (!super.run()) return; + if (Rs2Player.getWorldLocation() == null) return; long startTime = System.currentTimeMillis(); long currentTime = System.currentTimeMillis(); @@ -169,9 +170,7 @@ public boolean run(ScurriusConfig config) { } if (currentTime - lastEatTime > EAT_COOLDOWN_MS) { - int minEat = config.minEatPercent(); - int maxEat = config.maxEatPercent(); - int randomEatThreshold = ThreadLocalRandom.current().nextInt(minEat, maxEat + 1); + int randomEatThreshold = randomThreshold(config.minEatPercent(), config.maxEatPercent()); if (Microbot.getClient().getBoostedSkillLevel(Skill.HITPOINTS) < randomEatThreshold && !Rs2Player.isAnimating()) { Rs2Player.eatAt(randomEatThreshold); @@ -181,9 +180,7 @@ public boolean run(ScurriusConfig config) { } if (currentTime - lastPrayerTime > PRAYER_COOLDOWN_MS) { - int minPrayer = config.minPrayerPercent(); - int maxPrayer = config.maxPrayerPercent(); - int randomPrayerThreshold = ThreadLocalRandom.current().nextInt(minPrayer, maxPrayer + 1); + int randomPrayerThreshold = randomThreshold(config.minPrayerPercent(), config.maxPrayerPercent()); if (Microbot.getClient().getBoostedSkillLevel(Skill.PRAYER) < randomPrayerThreshold && !Rs2Player.isAnimating()) { Rs2Player.drinkPrayerPotionAt(randomPrayerThreshold); @@ -193,8 +190,7 @@ public boolean run(ScurriusConfig config) { } Rs2NpcModel giantRatModel = Microbot.getRs2NpcCache().query() - .where(n -> n.getName() != null && n.getName().toLowerCase().contains("giant rat") - && !n.getNpc().isDead()) + .where(ScurriusScript::isLiveGiantRat) .nearest(); if (giantRatModel != null) { boolean didWeAttackAGiantRat = scurrius != null && config.prioritizeRats() && giantRatModel.click("Attack"); @@ -271,7 +267,22 @@ private String getStateDescription(State state) { } private boolean isInFightRoom() { - return fightRoom.contains(Rs2Player.getWorldLocation()); + return isInArea(fightRoom, Rs2Player.getWorldLocation()); + } + + static boolean isInArea(WorldArea area, WorldPoint location) { + return location != null && area.contains(location); + } + + static boolean isLiveGiantRat(Rs2NpcModel npc) { + String name = npc.getName(); + return name != null && name.toLowerCase().contains("giant rat") && !npc.getNpc().isDead(); + } + + static int randomThreshold(int first, int second) { + int low = Math.min(first, second); + int high = Math.max(first, second); + return ThreadLocalRandom.current().nextInt(low, high + 1); } private WorldPoint findSafeTile(WorldPoint playerLocation, List dangerousWorldPoints) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarOverlay.java index f0b6cb63bb..2a624756a0 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarOverlay.java @@ -15,6 +15,7 @@ import net.runelite.client.plugins.microbot.shootingstar.ShootingStarConfig; import net.runelite.client.plugins.microbot.shootingstar.ShootingStarPlugin; import net.runelite.client.plugins.microbot.shootingstar.ShootingStarScript; +import net.runelite.client.plugins.microbot.shootingstar.enums.ShootingStarState; import net.runelite.client.plugins.microbot.util.misc.TimeUtils; import net.runelite.client.ui.overlay.OverlayPanel; import net.runelite.client.ui.overlay.OverlayPosition; @@ -77,11 +78,12 @@ public Dimension render(Graphics2D graphics) if (!config.isHideDevOverlay()) { - if (ShootingStarScript.state != null) + ShootingStarState state = ShootingStarScript.state; + if (state != null) { panelComponent.getChildren().add(LineComponent.builder() .left("Current State:") - .right(ShootingStarScript.state.name()) + .right(state.name()) .build()); } 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 45d9ed1082..41a2e68d58 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 @@ -76,7 +76,7 @@ public class ShootingStarPlugin extends Plugin { - static final String version = "1.4.10"; + static final String version = "1.4.11"; @Getter private final CopyOnWriteArrayList starList = new CopyOnWriteArrayList<>(); @@ -278,7 +278,7 @@ public void onChatMessage(ChatMessage event) { if (event.getMessage().equalsIgnoreCase("oh dear, you are dead!") && config.shutdownOnDeath()) { Rs2Walker.setTarget(null); - shutDown(); + Microbot.stopPlugin(this); } } @@ -412,16 +412,29 @@ private void removePanel() public void updatePanelList(boolean fullUpdate) { - List stars = new ArrayList<>(starList); - - if (fullUpdate) - { - SwingUtilities.invokeLater(() -> panel.updateList(stars)); - } - else + if (panel == null) { - SwingUtilities.invokeLater(() -> panel.refreshList(stars)); + return; } + + List stars = new ArrayList<>(starList); + + SwingUtilities.invokeLater(() -> { + ShootingStarPanel currentPanel = panel; + if (currentPanel == null) + { + return; + } + + if (fullUpdate) + { + currentPanel.updateList(stars); + } + else + { + currentPanel.refreshList(stars); + } + }); } private void toggleOverlay(boolean hideOverlay) 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 5b8f02c5a2..7eb0415816 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 @@ -23,6 +23,7 @@ import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.antiban.enums.Activity; +import net.runelite.client.plugins.microbot.util.antiban.enums.Category; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; @@ -74,6 +75,9 @@ public boolean run() startTime = Instant.now(); Microbot.enableAutoRunOn = true; initialPlayerLocation = null; + state = null; + currentStar = null; + pickaxe = null; hasEquipment = false; hasInventory = false; walkBeforeHopStar = null; @@ -110,7 +114,7 @@ public boolean run() return; } - if (Rs2Player.isMoving() || Rs2Antiban.getCategory().isBusy()) + if (Rs2Player.isMoving() || isAntibanBusy(Rs2Antiban.getCategory())) { return; } @@ -302,12 +306,14 @@ public boolean run() public void shutdown() { super.shutdown(); - state = null; - currentStar = null; - pickaxe = null; Rs2Antiban.resetAntibanSettings(); } + static boolean isAntibanBusy(Category category) + { + return category != null && category.isBusy(); + } + void handleWalking(Star star) { toggleLockState(true); diff --git a/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaPlugin.java index 6022ed8529..12fd1b2c14 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaPlugin.java @@ -29,7 +29,7 @@ @Slf4j public class SulphurNaguaPlugin extends Plugin { - public static final String version = "2.0.2"; + public static final String version = "2.0.3"; @Inject SulphurNaguaScript sulphurNaguaScript; diff --git a/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaScript.java b/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaScript.java index 29faf582d8..be83b19046 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaScript.java @@ -2,6 +2,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; +import net.runelite.api.Actor; import net.runelite.api.Client; import net.runelite.api.Skill; import net.runelite.api.coords.WorldArea; @@ -236,7 +237,7 @@ private void determineState(SulphurNaguaConfig config) { return; } - boolean isAvailableForAction = !Rs2Player.isInCombat() || Rs2Player.getInteracting() == null || Rs2Player.getInteracting().isDead(); + boolean isAvailableForAction = !Rs2Player.isInCombat() || isMissingOrDead(Rs2Player.getInteracting()); if (!dynamicLootIds.isEmpty() && isAvailableForAction && !Rs2Inventory.isFull() && isStackableLootNearby()) { currentState = SulphurNaguaState.LOOTING; @@ -539,7 +540,7 @@ private void handleFighting(SulphurNaguaConfig config) { var attackingNagua = Microbot.getRs2NpcCache().query() .withName("Sulphur Nagua") .where(n -> !n.isDead()) - .where(n -> n.getInteracting() != null && n.getInteracting().equals(client.getLocalPlayer())) + .where(n -> isInteractingWith(n, client.getLocalPlayer())) .nearest(); boolean attackInitiated = false; @@ -552,7 +553,7 @@ private void handleFighting(SulphurNaguaConfig config) { .withName("Sulphur Nagua") .where(n -> !n.isDead()) .where(n -> combatArea.contains(n.getWorldLocation())) - .where(n -> n.getInteracting() == null || n.getInteracting().equals(client.getLocalPlayer())) + .where(n -> isFreeOrInteractingWith(n, client.getLocalPlayer())) .interact("Attack"); } @@ -719,4 +720,18 @@ private int countFixedItems() { return fixedItemCount; } + + static boolean isMissingOrDead(Actor target) { + return target == null || target.isDead(); + } + + static boolean isInteractingWith(Actor actor, Actor other) { + Actor target = actor.getInteracting(); + return target != null && target.equals(other); + } + + static boolean isFreeOrInteractingWith(Actor actor, Actor other) { + Actor target = actor.getInteracting(); + return target == null || target.equals(other); + } } \ No newline at end of file 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 0e8848eb7c..23e44867a1 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 @@ -14,6 +14,7 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.PluginConstants; import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.player.Rs2PlayerModel; import net.runelite.client.ui.overlay.OverlayManager; import javax.inject.Inject; @@ -34,7 +35,7 @@ ) @Slf4j public class ThievingPlugin extends Plugin { - public static final String version = "2.1.5"; + public static final String version = "2.1.6"; @Inject @Getter @@ -80,7 +81,11 @@ protected void shutDown() { private void setStartXp() { // this should handle changing accounts - final String name = Rs2Player.getLocalPlayer().getName(); + final Rs2PlayerModel localPlayer = Rs2Player.getLocalPlayer(); + if (localPlayer == null) { + return; + } + final String name = localPlayer.getName(); if (startXp == 0 || (name != null && !name.equals(this.name))) { this.name = name; startXp = Microbot.getClient().getSkillExperience(Skill.THIEVING); diff --git a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/StallThievingPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/StallThievingPlugin.java index 477445194d..d2ad7eab1b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/StallThievingPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/StallThievingPlugin.java @@ -25,7 +25,7 @@ @Slf4j public class StallThievingPlugin extends Plugin { - public final static String version = "1.0.0"; + public final static String version = "1.0.1"; @Inject private StallThievingConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/BankInventoryStrategy.java b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/BankInventoryStrategy.java index 1f1ba749a8..418cef5e41 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/BankInventoryStrategy.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/BankInventoryStrategy.java @@ -41,10 +41,15 @@ public void execute(IStallThievingSpot stallThievingSpot) { private int[] getItemIdsToAlwaysDrop() { - if (config.alwaysDrop().isEmpty()) + final String alwaysDrop = config.alwaysDrop(); + if (alwaysDrop == null || alwaysDrop.isBlank()) { return new int[0]; } - return Arrays.stream(config.alwaysDrop().split("\\s*,\\s*")).mapToInt(Integer::parseInt).toArray(); + return Arrays.stream(alwaysDrop.split(",")) + .map(String::trim) + .filter(token -> token.matches("\\d{1,9}")) + .mapToInt(Integer::parseInt) + .toArray(); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/toweroflife_creaturecreation/TowerOfLifeCCPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/toweroflife_creaturecreation/TowerOfLifeCCPlugin.java index 2a98412040..252fefae0e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/toweroflife_creaturecreation/TowerOfLifeCCPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/toweroflife_creaturecreation/TowerOfLifeCCPlugin.java @@ -39,7 +39,7 @@ TowerOfLifeCCConfig provideConfig(ConfigManager configManager) { @Inject TowerOfLifeCCScript towerOfLifeCCScript; - static final String version = "1.1.4"; + static final String version = "1.1.5"; @Override protected void startUp() throws AWTException { diff --git a/src/main/java/net/runelite/client/plugins/microbot/toweroflife_creaturecreation/TowerOfLifeCCScript.java b/src/main/java/net/runelite/client/plugins/microbot/toweroflife_creaturecreation/TowerOfLifeCCScript.java index f0bf31a13d..65d71f12b4 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/toweroflife_creaturecreation/TowerOfLifeCCScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/toweroflife_creaturecreation/TowerOfLifeCCScript.java @@ -405,10 +405,7 @@ void HandleCreature(LootingParameters params, int disposableItem, int secondaryI //Rs2Inventory.useItemOnObject(secondaryItem, altarObjectId); //Rs2Inventory.waitForInventoryChanges(3000); Microbot.getRs2TileObjectCache().query().interact(altarObjectId, "Activate"); - sleepUntil(() -> { summonedCreature = Microbot.getRs2NpcCache().query() - .where(npc -> npc.getNpc() != null - && (npc.getNpc().getInteracting() == null || npc.getNpc().getInteracting() == Microbot.getClient().getLocalPlayer())) - .nearest(); + sleepUntil(() -> { summonedCreature = findSummonedCreature(); return summonedCreature != null; }, 5000); //Microbot.log("Summoned creature"); @@ -437,4 +434,12 @@ else if (!Rs2Combat.inCombat()) } } } + + Rs2NpcModel findSummonedCreature() + { + return Microbot.getRs2NpcCache().query() + .where(npc -> npc.getNpc() != null + && (npc.getNpc().getInteracting() == null || npc.getNpc().getInteracting() == Microbot.getClient().getLocalPlayer())) + .nearestOnClientThread(); + } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/RatPen.java b/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/RatPen.java new file mode 100644 index 0000000000..ebaa147fde --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/RatPen.java @@ -0,0 +1,29 @@ +package net.runelite.client.plugins.microbot.tutorialisland; + +import net.runelite.api.coords.WorldPoint; + +final class RatPen { + static final int GATE_ID = 9719; + static final WorldPoint GATE_OUTSIDE = new WorldPoint(3111, 9518, 0); + static final WorldPoint GATE_INSIDE = new WorldPoint(3110, 9518, 0); + + enum Step { + ATTACK, + OPEN_GATE, + WALK_TO_GATE + } + + private RatPen() { + } + + static Step next(WorldPoint playerLoc, boolean ratReachable) { + if (ratReachable) { + return Step.ATTACK; + } + if (playerLoc != null && playerLoc.getPlane() == GATE_OUTSIDE.getPlane() + && playerLoc.distanceTo2D(GATE_OUTSIDE) <= 1) { + return Step.OPEN_GATE; + } + return Step.WALK_TO_GATE; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandPlugin.java index d99aac9841..7fa7200298 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandPlugin.java @@ -25,7 +25,7 @@ isExternal = PluginConstants.IS_EXTERNAL ) public class TutorialIslandPlugin extends Plugin { - public static final String version = "1.3.16"; + public static final String version = "1.3.17"; private boolean toggleMusic; private boolean toggleRoofs; diff --git a/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java b/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java index a8e567a28d..a22b63e6ac 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java @@ -334,21 +334,26 @@ private boolean walkAndAct(Rs2NpcModel npc, int reach, String action, Runnable a } private boolean walkAndAttackRat() { - // The rat pit gate (id 9719) blocks both Rs2Walker and native pathfinder. - // Explicitly open it if we're standing adjacent; after passing through, attack directly. - WorldPoint playerLoc = Rs2Player.getWorldLocation(); - if (playerLoc != null && playerLoc.getX() == 3111 && playerLoc.getY() >= 9516 && playerLoc.getY() <= 9519) { - if (Microbot.getRs2TileObjectCache().query().withId(9719).interact("Open")) { - sleepUntil(() -> { - WorldPoint p = Rs2Player.getWorldLocation(); - return p != null && p.getY() < 9516; - }, 3000); + Rs2NpcModel rat = Microbot.getRs2NpcCache().query().withName("Giant rat").nearest(); + if (rat == null || rat.getWorldLocation() == null) return false; + switch (RatPen.next(Rs2Player.getWorldLocation(), Rs2Tile.isTileReachable(rat.getWorldLocation()))) { + case ATTACK: + return rat.click("Attack"); + case OPEN_GATE: + if (Microbot.getRs2TileObjectCache().query().withId(RatPen.GATE_ID).interact("Open")) { + sleepUntil(() -> Rs2Tile.isTileReachable(RatPen.GATE_INSIDE), 3000); + } + return false; + default: + Rs2Walker.walkTo(RatPen.GATE_OUTSIDE, 1); + Rs2Player.waitForWalking(); return false; - } } + } + + private boolean attackRatAtRange() { Rs2NpcModel rat = Microbot.getRs2NpcCache().query().withName("Giant rat").nearest(); - if (rat == null || rat.getWorldLocation() == null) return false; - if (!Rs2Walker.canReach(rat.getWorldLocation())) return false; + if (rat == null) return false; return rat.click("Attack"); } @@ -655,7 +660,7 @@ public void CombatGuide() { Rs2Inventory.wield("Bronze arrow"); Rs2Random.waitEx(600, 100); } - walkAndAttackRat(); + attackRatAtRange(); } else if (Microbot.getVarbitPlayerValue(281) == 470) { if (npc == null) return; walkAndTalk(npc); diff --git a/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchKillerOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchKillerOverlay.java index 8e58ec4794..a00a297c40 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchKillerOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchKillerOverlay.java @@ -5,6 +5,7 @@ import net.runelite.api.Perspective; import net.runelite.api.TileObject; import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldArea; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; @@ -55,10 +56,11 @@ public Dimension render(Graphics2D graphics) { renderOutline(graphics, alterStatue); } - if (plugin.fightArea != null && !config.disableFightArea()) { - for (int x = plugin.fightArea.getX(); x < plugin.fightArea.getX() + plugin.fightArea.getWidth(); x++) { - for (int y = plugin.fightArea.getY(); y < plugin.fightArea.getY() + plugin.fightArea.getHeight(); y++) { - WorldPoint worldPoint = new WorldPoint(x, y, plugin.fightArea.getPlane()); + WorldArea fightArea = plugin.fightArea; + if (fightArea != null && !config.disableFightArea()) { + for (int x = fightArea.getX(); x < fightArea.getX() + fightArea.getWidth(); x++) { + for (int y = fightArea.getY(); y < fightArea.getY() + fightArea.getHeight(); y++) { + WorldPoint worldPoint = new WorldPoint(x, y, fightArea.getPlane()); drawTile(graphics, worldPoint, Color.YELLOW); } } @@ -75,11 +77,11 @@ public Dimension render(Graphics2D graphics) { } } - if (!config.disableNPCOutline()) { + if (!config.disableNPCOutline() && fightArea != null) { for (var npcModel : Microbot.getRs2NpcCache().query().withName("Vyrewatch Sentinel").where(n -> !n.isDead()).toList()) { NPC npc = npcModel.getNpc(); if (npc != null && npc.getCanvasTilePoly() != null) { - if (!plugin.fightArea.contains(npc.getWorldLocation())) continue; + if (!fightArea.contains(npc.getWorldLocation())) continue; try { modelOutlineRenderer.drawOutline(npc, 2, Color.ORANGE, 4); diff --git a/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchKillerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchKillerPlugin.java index d50994b3e3..b0d71abbee 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchKillerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchKillerPlugin.java @@ -47,7 +47,7 @@ ) public class PVirewatchKillerPlugin extends Plugin { - public final static String version = "1.0.4"; + public final static String version = "1.0.5"; private PLooter looterScript = new PLooter(); private PAlcher alchScript = new PAlcher(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchScript.java b/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchScript.java index 403ae30d68..6862254bf3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchScript.java @@ -1,6 +1,9 @@ package net.runelite.client.plugins.microbot.virewatch; +import net.runelite.api.Player; import net.runelite.api.Skill; +import net.runelite.api.coords.WorldArea; +import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; @@ -19,13 +22,21 @@ public boolean run(PVirewatchKillerConfig config, PVirewatchKillerPlugin plugin) if (!super.run()) return; Rs2Combat.enableAutoRetialiate(); - if(plugin.fightArea.contains(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()))) { + WorldPoint playerLocation = Microbot.getClientThread().invoke(() -> { + Player localPlayer = Microbot.getClient().getLocalPlayer(); + return localPlayer == null ? null : localPlayer.getWorldLocation(); + }); + WorldArea fightArea = plugin.fightArea; + WorldPoint startingLocation = plugin.startingLocation; + if (!hasFightState(playerLocation, fightArea, startingLocation)) return; + + if(fightArea.contains(playerLocation)) { Microbot.status = "Figthing"; } - if(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()) != plugin.startingLocation) { + if(isAwayFromStart(playerLocation, startingLocation)) { if(plugin.ticksOutOfArea > config.tickToReturn() || plugin.countedTicks > config.tickToReturnCombat()) { - Rs2Walker.walkTo(plugin.startingLocation, 0); + Rs2Walker.walkTo(startingLocation, 0); } } @@ -45,7 +56,7 @@ public boolean run(PVirewatchKillerConfig config, PVirewatchKillerPlugin plugin) if(Rs2Player.isInteracting()) { sleepUntil(() -> Microbot.getClient().getBoostedSkillLevel(Skill.PRAYER) > config.prayAt()); Microbot.status = "WALKING TO STARTING POINT"; - Rs2Walker.walkTo(plugin.startingLocation, 0); + Rs2Walker.walkTo(startingLocation, 0); } } @@ -60,6 +71,14 @@ public boolean run(PVirewatchKillerConfig config, PVirewatchKillerPlugin plugin) return true; } + static boolean hasFightState(WorldPoint playerLocation, WorldArea fightArea, WorldPoint startingLocation) { + return playerLocation != null && fightArea != null && startingLocation != null; + } + + static boolean isAwayFromStart(WorldPoint playerLocation, WorldPoint startingLocation) { + return !playerLocation.equals(startingLocation); + } + @Override public void shutdown() { super.shutdown(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/wildernessagility/WildernessAgilityPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/wildernessagility/WildernessAgilityPlugin.java index 5925a1fd9c..23c81a253a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/wildernessagility/WildernessAgilityPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/wildernessagility/WildernessAgilityPlugin.java @@ -26,7 +26,7 @@ ) public class WildernessAgilityPlugin extends Plugin { - static final String version = "1.6.1"; + static final String version = "1.6.2"; @Inject private OverlayManager overlayManager; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/wildernessagility/WildernessAgilityScript.java b/src/main/java/net/runelite/client/plugins/microbot/wildernessagility/WildernessAgilityScript.java index df689eb559..da6f6aa5f8 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/wildernessagility/WildernessAgilityScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/wildernessagility/WildernessAgilityScript.java @@ -509,7 +509,7 @@ private Rs2TileObjectModel getObstacleObj(int index) { private boolean isInUndergroundPit() { return Microbot.getRs2TileObjectCache().query().withId(UNDERGROUND_OBJECT_ID).nearest() != null; } - private void recoverFromPit() { + void recoverFromPit() { // First check if we're still in the pit using game object detection if (isInUndergroundPit()) { // Immediately refresh ladder object before attempting to interact @@ -521,9 +521,12 @@ private void recoverFromPit() { if (now - lastLadderInteractTime > 2000) { // Refresh ladder object again just before interaction List laddersNow = Microbot.getRs2TileObjectCache().query().withId(17385).toList(); - ladderObj = laddersNow.isEmpty() ? null : laddersNow.get(0); - ladderObj.click("Climb-up"); - lastLadderInteractTime = now; + if (!laddersNow.isEmpty()) { + ladderObj = laddersNow.get(0); + } + if (ladderObj.click("Climb-up")) { + lastLadderInteractTime = now; + } } } return; // Return here to let the next tick handle the state transition diff --git a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingOverlay.java index 2175fe955b..5fcf0192f4 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingOverlay.java @@ -4,6 +4,7 @@ import net.runelite.api.Perspective; import net.runelite.api.Skill; import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.woodcutting.enums.ForestryEvents; import net.runelite.client.plugins.microbot.woodcutting.enums.WoodcuttingTree; @@ -185,8 +186,9 @@ public Dimension render(Graphics2D graphics) { .build()); // Display area boundary - if (config.distanceToStray() < 21) { - LocalPoint lp = LocalPoint.fromWorld(client, AutoWoodcuttingScript.getReturnPoint(config)); + WorldPoint returnPoint = AutoWoodcuttingScript.getReturnPoint(config); + if (config.distanceToStray() < 21 && returnPoint != null) { + LocalPoint lp = LocalPoint.fromWorld(client, returnPoint); if (lp != null) { Polygon poly = Perspective.getCanvasTileAreaPoly(client, lp, config.distanceToStray() * 2); diff --git a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingPlugin.java index 310c8707b4..7ecc604ffe 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingPlugin.java @@ -50,7 +50,7 @@ ) @Slf4j public class AutoWoodcuttingPlugin extends Plugin { - public static final String version = "1.8.6"; + public static final String version = "1.8.7"; @Inject @Getter(AccessLevel.MODULE) public AutoWoodcuttingScript autoWoodcuttingScript; diff --git a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingScript.java b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingScript.java index 1be35c8536..15da87dccb 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingScript.java @@ -495,8 +495,15 @@ private boolean isFlectching() { } private void walkBack(AutoWoodcuttingConfig config) { - Rs2Walker.walkTo(new WorldPoint(getReturnPoint(config).getX() - Rs2Random.between(-1, 1), getReturnPoint(config).getY() - Rs2Random.between(-1, 1), getReturnPoint(config).getPlane())); - sleepUntil(() -> Rs2Player.getWorldLocation().distanceTo(getReturnPoint(config)) <= 4); + WorldPoint target = getReturnPoint(config); + if (target == null) { + return; + } + Rs2Walker.walkTo(new WorldPoint(target.getX() - Rs2Random.between(-1, 1), target.getY() - Rs2Random.between(-1, 1), target.getPlane())); + sleepUntil(() -> { + WorldPoint playerLocation = Rs2Player.getWorldLocation(); + return playerLocation != null && playerLocation.distanceTo(target) <= 4; + }); } /** diff --git a/src/test/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabScriptTest.java b/src/test/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabScriptTest.java new file mode 100644 index 0000000000..6b1d2e8e70 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabScriptTest.java @@ -0,0 +1,36 @@ +package net.runelite.client.plugins.microbot.AmmoniteCrabs; + +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +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.Mockito.mockStatic; +import static org.mockito.Mockito.never; + +class AmmoniteCrabScriptTest { + + private final WorldPoint fightLocation = new WorldPoint(3733, 3846, 0); + + @Test + void nullPlayerLocationIsNotWithinRange() { + assertFalse(AmmoniteCrabScript.isWithin(null, fightLocation, 3)); + } + + @Test + void playerLocationWithinRangeIsDetected() { + assertTrue(AmmoniteCrabScript.isWithin(new WorldPoint(3735, 3846, 0), fightLocation, 3)); + assertFalse(AmmoniteCrabScript.isWithin(new WorldPoint(3740, 3846, 0), fightLocation, 3)); + } + + @Test + void nullLocationDetectsNoOtherPlayerWithoutQueryingPlayers() { + try (MockedStatic player = mockStatic(Rs2Player.class)) { + assertFalse(AmmoniteCrabScript.otherPlayerDetected(null)); + player.verify(() -> Rs2Player.getPlayers(any()), never()); + } + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScriptCacheSetupTest.java b/src/test/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScriptCacheSetupTest.java new file mode 100644 index 0000000000..5df3cea82e --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScriptCacheSetupTest.java @@ -0,0 +1,49 @@ +package net.runelite.client.plugins.microbot.aiofighter.bank; + +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.aiofighter.AIOFighterPlugin; +import net.runelite.client.plugins.microbot.inventorysetups.InventorySetup; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.util.Arrays; +import java.util.Collections; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.when; + +public final class BankerScriptCacheSetupTest { + + private static InventorySetup setup(String name) { + InventorySetup setup = mock(InventorySetup.class); + when(setup.getName()).thenReturn(name); + return setup; + } + + @Test + void findsSetupByNameIgnoringCase() { + InventorySetup cache = setup("Cache"); + assertSame(cache, BankerScript.findSetupByName(Arrays.asList(setup("Melee"), null, setup(null), cache), "cache")); + } + + @Test + void returnsNullWhenSetupNotRegisteredYet() { + assertNull(BankerScript.findSetupByName(Collections.singletonList(setup("Melee")), "cache")); + assertNull(BankerScript.findSetupByName(Collections.emptyList(), "cache")); + assertNull(BankerScript.findSetupByName(null, "cache")); + assertNull(BankerScript.findSetupByName(Collections.singletonList(setup("cache")), null)); + } + + @Test + void settingNullInventorySetupIsIgnored() { + try (MockedStatic microbot = mockStatic(Microbot.class)) { + assertDoesNotThrow(() -> AIOFighterPlugin.setCurrentSlayerInventorySetup(null)); + microbot.verify(Microbot::getConfigManager, never()); + } + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScriptProtectiveEquipmentTest.java b/src/test/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScriptProtectiveEquipmentTest.java new file mode 100644 index 0000000000..ebb05c456d --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScriptProtectiveEquipmentTest.java @@ -0,0 +1,40 @@ +package net.runelite.client.plugins.microbot.aiofighter.bank; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public final class BankerScriptProtectiveEquipmentTest { + + @Test + void noSlayerTaskRequiresNoProtectiveEquipment() { + assertFalse(BankerScript.requiresProtectiveEquipment(null)); + } + + @Test + void taskWithoutProtectiveEquipmentRequiresNone() { + assertFalse(BankerScript.requiresProtectiveEquipment("None")); + } + + @Test + void taskWithProtectiveEquipmentRequiresIt() { + assertTrue(BankerScript.requiresProtectiveEquipment("Earmuffs")); + assertTrue(BankerScript.requiresProtectiveEquipment("Rock hammer")); + } + + @Test + void slayerHelmetBypassHandlesNullItem() { + assertDoesNotThrow(() -> BankerScript.isBypassedBySlayerHelmet(null)); + assertFalse(BankerScript.isBypassedBySlayerHelmet(null)); + } + + @Test + void slayerHelmetBypassesHeadProtection() { + assertTrue(BankerScript.isBypassedBySlayerHelmet("Earmuffs")); + assertTrue(BankerScript.isBypassedBySlayerHelmet("Nose peg")); + assertFalse(BankerScript.isBypassedBySlayerHelmet("Rock hammer")); + assertFalse(BankerScript.isBypassedBySlayerHelmet("Mirror shield")); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScriptTest.java b/src/test/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScriptTest.java index 4b328ed5ae..0e1ce00795 100644 --- a/src/test/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScriptTest.java +++ b/src/test/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScriptTest.java @@ -1,7 +1,19 @@ package net.runelite.client.plugins.microbot.aiofighter.combat; +import net.runelite.api.gameval.ItemID; import org.junit.jupiter.api.Test; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.function.IntUnaryOperator; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -34,4 +46,63 @@ void attacksWhenIdleAndNoCooldown() { assertFalse(AttackNpcScript.shouldHoldAttack(false, true, 1.0, false)); assertFalse(AttackNpcScript.shouldHoldAttack(false, false, 0.0, false)); } + + @Test + void sortsByPrimaryThenSecondaryKey() { + List items = Arrays.asList("far-free", "near-free", "far-mine", "near-mine"); + Map primary = Map.of("far-free", 1, "near-free", 1, "far-mine", 0, "near-mine", 0); + Map secondary = Map.of("far-free", 9, "near-free", 2, "far-mine", 7, "near-mine", 1); + + List sorted = AttackNpcScript.sortBySnapshotKeys(items, primary::get, secondary::get); + + assertEquals(Arrays.asList("near-mine", "far-mine", "near-free", "far-free"), sorted); + } + + @Test + void readsEachKeyOncePerItemSoLiveKeysCannotBreakTheSort() { + List items = IntStream.range(0, 500).boxed().collect(Collectors.toList()); + Random random = new Random(42); + Map primaryReads = new HashMap<>(); + Map secondaryReads = new HashMap<>(); + + List sorted = AttackNpcScript.sortBySnapshotKeys(items, + item -> { + primaryReads.merge(item, 1, Integer::sum); + return random.nextInt(2); + }, + item -> { + secondaryReads.merge(item, 1, Integer::sum); + return random.nextInt(50); + }); + + assertEquals(items.size(), sorted.size()); + assertEquals(new ArrayList<>(items), sorted.stream().sorted().collect(Collectors.toList())); + assertTrue(primaryReads.values().stream().allMatch(count -> count == 1)); + assertTrue(secondaryReads.values().stream().allMatch(count -> count == 1)); + assertEquals(items.size(), primaryReads.size()); + } + + private static final IntUnaryOperator AUTOKILL_OFF = varbit -> 0; + private static final IntUnaryOperator AUTOKILL_ON = varbit -> 1; + + @Test + void skipsNpcWhoseNameIsGoneWhileDespawning() { + assertEquals(-1, AttackNpcScript.finishingItemFor(null, 1, AUTOKILL_OFF)); + assertEquals(-1, AttackNpcScript.finishingItemFor(null, 0, AUTOKILL_ON)); + } + + @Test + void picksFinishingItemForSlayerNpcsNearDeath() { + assertEquals(ItemID.SLAYER_ICY_WATER, AttackNpcScript.finishingItemFor("Desert Lizard", 4, AUTOKILL_OFF)); + assertEquals(ItemID.SLAYER_BAG_OF_SALT, AttackNpcScript.finishingItemFor("Rockslug", 4, AUTOKILL_OFF)); + assertEquals(ItemID.SLAYER_ROCK_HAMMER, AttackNpcScript.finishingItemFor("Gargoyle", 2, AUTOKILL_OFF)); + } + + @Test + void skipsFinishingItemWhenHealthyAutokillEnabledOrOtherNpc() { + assertEquals(-1, AttackNpcScript.finishingItemFor("Rockslug", 5, AUTOKILL_OFF)); + assertEquals(-1, AttackNpcScript.finishingItemFor("Gargoyle", 3, AUTOKILL_OFF)); + assertEquals(-1, AttackNpcScript.finishingItemFor("Rockslug", 1, AUTOKILL_ON)); + assertEquals(-1, AttackNpcScript.finishingItemFor("Goblin", 0, AUTOKILL_OFF)); + } } diff --git a/src/test/java/net/runelite/client/plugins/microbot/aiofighter/combat/SafeSpotTest.java b/src/test/java/net/runelite/client/plugins/microbot/aiofighter/combat/SafeSpotTest.java new file mode 100644 index 0000000000..d8ce1d0353 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/aiofighter/combat/SafeSpotTest.java @@ -0,0 +1,30 @@ +package net.runelite.client.plugins.microbot.aiofighter.combat; + +import net.runelite.client.plugins.microbot.aiofighter.enums.State; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public final class SafeSpotTest { + + @Test + void unsetStateDoesNotBlockSafeSpot() { + assertFalse(SafeSpot.isBlockedByState(null)); + } + + @Test + void bankingAndWalkingBlockSafeSpot() { + assertTrue(SafeSpot.isBlockedByState(State.BANKING)); + assertTrue(SafeSpot.isBlockedByState(State.WALKING)); + } + + @Test + void otherStatesDoNotBlockSafeSpot() { + for (State state : State.values()) { + if (state != State.BANKING && state != State.WALKING) { + assertFalse(SafeSpot.isBlockedByState(state)); + } + } + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/aiofighter/skill/AttackStyleScriptTest.java b/src/test/java/net/runelite/client/plugins/microbot/aiofighter/skill/AttackStyleScriptTest.java index 83b2ee3a07..5d39231428 100644 --- a/src/test/java/net/runelite/client/plugins/microbot/aiofighter/skill/AttackStyleScriptTest.java +++ b/src/test/java/net/runelite/client/plugins/microbot/aiofighter/skill/AttackStyleScriptTest.java @@ -1,16 +1,28 @@ package net.runelite.client.plugins.microbot.aiofighter.skill; +import net.runelite.api.EnumComposition; +import net.runelite.api.EnumID; +import net.runelite.api.ParamID; +import net.runelite.api.StructComposition; import net.runelite.api.widgets.WidgetInfo; +import net.runelite.client.plugins.microbot.Microbot; import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Random; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertSame; 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.when; public final class AttackStyleScriptTest { @@ -33,4 +45,76 @@ void pickComponentReturnsOneOfCandidates() { assertTrue(candidates.contains(AttackStyleScript.pickComponent(candidates, new Random(seed)))); } } + + @Test + void weaponTypeStylesEmptyWhenWeaponStylesEnumMissing() { + try (MockedStatic microbot = mockStatic(Microbot.class)) { + microbot.when(() -> Microbot.getEnum(anyInt())).thenReturn(null); + assertEquals(0, AttackStyleScript.getWeaponTypeStyles(0).length); + } + } + + @Test + void weaponTypeStylesEmptyWhenWeaponEnumMissing() { + EnumComposition weaponStyles = mock(EnumComposition.class); + when(weaponStyles.getIntValue(0)).thenReturn(5000); + try (MockedStatic microbot = mockStatic(Microbot.class)) { + microbot.when(() -> Microbot.getEnum(EnumID.WEAPON_STYLES)).thenReturn(weaponStyles); + microbot.when(() -> Microbot.getEnum(5000)).thenReturn(null); + assertEquals(0, AttackStyleScript.getWeaponTypeStyles(0).length); + } + } + + @Test + void weaponTypeStylesEmptyWhenStructOrNameMissing() { + EnumComposition weaponStyles = mock(EnumComposition.class); + when(weaponStyles.getIntValue(0)).thenReturn(5000); + EnumComposition weaponEnum = mock(EnumComposition.class); + when(weaponEnum.getIntVals()).thenReturn(new int[]{10, 11}); + StructComposition unnamed = mock(StructComposition.class); + try (MockedStatic microbot = mockStatic(Microbot.class)) { + microbot.when(() -> Microbot.getEnum(EnumID.WEAPON_STYLES)).thenReturn(weaponStyles); + microbot.when(() -> Microbot.getEnum(5000)).thenReturn(weaponEnum); + microbot.when(() -> Microbot.getStructComposition(10)).thenReturn(unnamed); + microbot.when(() -> Microbot.getStructComposition(11)).thenReturn(null); + assertEquals(0, AttackStyleScript.getWeaponTypeStyles(0).length); + } + } + + @Test + void weaponTypeStylesEmptyWhenStructMissing() { + EnumComposition weaponStyles = mock(EnumComposition.class); + when(weaponStyles.getIntValue(0)).thenReturn(5000); + EnumComposition weaponEnum = mock(EnumComposition.class); + when(weaponEnum.getIntVals()).thenReturn(new int[]{10, 11}); + StructComposition named = mock(StructComposition.class); + when(named.getStringValue(ParamID.ATTACK_STYLE_NAME)).thenReturn("Accurate"); + try (MockedStatic microbot = mockStatic(Microbot.class)) { + microbot.when(() -> Microbot.getEnum(EnumID.WEAPON_STYLES)).thenReturn(weaponStyles); + microbot.when(() -> Microbot.getEnum(5000)).thenReturn(weaponEnum); + microbot.when(() -> Microbot.getStructComposition(10)).thenReturn(null); + microbot.when(() -> Microbot.getStructComposition(11)).thenReturn(named); + assertEquals(0, AttackStyleScript.getWeaponTypeStyles(0).length); + } + } + + @Test + void weaponTypeStylesResolvedFromStructs() { + EnumComposition weaponStyles = mock(EnumComposition.class); + when(weaponStyles.getIntValue(0)).thenReturn(5000); + EnumComposition weaponEnum = mock(EnumComposition.class); + when(weaponEnum.getIntVals()).thenReturn(new int[]{10, 11}); + StructComposition accurate = mock(StructComposition.class); + when(accurate.getStringValue(ParamID.ATTACK_STYLE_NAME)).thenReturn("Accurate"); + StructComposition aggressive = mock(StructComposition.class); + when(aggressive.getStringValue(ParamID.ATTACK_STYLE_NAME)).thenReturn("Aggressive"); + try (MockedStatic microbot = mockStatic(Microbot.class)) { + microbot.when(() -> Microbot.getEnum(EnumID.WEAPON_STYLES)).thenReturn(weaponStyles); + microbot.when(() -> Microbot.getEnum(5000)).thenReturn(weaponEnum); + microbot.when(() -> Microbot.getStructComposition(10)).thenReturn(accurate); + microbot.when(() -> Microbot.getStructComposition(11)).thenReturn(aggressive); + assertArrayEquals(new AttackStyle[]{AttackStyle.ACCURATE, AttackStyle.AGGRESSIVE}, + AttackStyleScript.getWeaponTypeStyles(0)); + } + } } diff --git a/src/test/java/net/runelite/client/plugins/microbot/bankorganizer/BankSnapshotReaderTest.java b/src/test/java/net/runelite/client/plugins/microbot/bankorganizer/BankSnapshotReaderTest.java new file mode 100644 index 0000000000..250edca99d --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/bankorganizer/BankSnapshotReaderTest.java @@ -0,0 +1,86 @@ +package net.runelite.client.plugins.microbot.bankorganizer; + +import java.util.Collections; +import java.util.Optional; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicReference; +import net.runelite.api.Client; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.game.ItemManager; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; +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.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +class BankSnapshotReaderTest +{ + private MockedStatic microbot; + private MockedStatic bank; + private final AtomicReference clientThreadFailure = new AtomicReference<>(); + private BankSnapshotReader reader; + + @BeforeEach + void setUp() + { + ClientThread clientThread = mock(ClientThread.class); + when(clientThread.runOnClientThreadOptional(any())).thenAnswer(invocation -> { + try + { + return Optional.ofNullable(((Callable) invocation.getArgument(0)).call()); + } + catch (Throwable t) + { + clientThreadFailure.set(t); + return Optional.empty(); + } + }); + + microbot = mockStatic(Microbot.class); + microbot.when(Microbot::getClientThread).thenReturn(clientThread); + bank = mockStatic(Rs2Bank.class); + + reader = new BankSnapshotReader(mock(Client.class), mock(ItemManager.class)); + } + + @AfterEach + void tearDown() + { + bank.close(); + microbot.close(); + } + + @Test + void closedBankFailsOffTheClientThread() + { + bank.when(Rs2Bank::isOpen).thenReturn(false); + + IllegalStateException thrown = assertThrows(IllegalStateException.class, reader::read); + + assertEquals("Open your bank before running Bank Organizer.", thrown.getMessage()); + assertNull(clientThreadFailure.get()); + } + + @Test + void openBankReturnsSnapshot() + { + bank.when(Rs2Bank::isOpen).thenReturn(true); + bank.when(Rs2Bank::bankItems).thenReturn(Collections.emptyList()); + + BankSnapshot snapshot = reader.read(); + + assertNotNull(snapshot); + assertEquals(0, snapshot.stackCount()); + assertNull(clientThreadFailure.get()); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/cluesolver/AnagramClueTaskTest.java b/src/test/java/net/runelite/client/plugins/microbot/cluesolver/AnagramClueTaskTest.java new file mode 100644 index 0000000000..866e84e6c0 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/cluesolver/AnagramClueTaskTest.java @@ -0,0 +1,143 @@ +package net.runelite.client.plugins.microbot.cluesolver; + +import net.runelite.api.Client; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.GameTick; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.eventbus.EventBus; +import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; +import net.runelite.client.plugins.cluescrolls.clues.AnagramClue; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.cluesolver.cluetask.AnagramClueTask; +import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; +import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; +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.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.AbstractExecutorService; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.when; + +class AnagramClueTaskTest { + + private static final WorldPoint LOCATION = new WorldPoint(3222, 3218, 0); + + private MockedStatic microbot; + private MockedStatic dialogue; + private MockedStatic keyboard; + private ClueScrollPlugin clueScrollPlugin; + private AnagramClue clue; + + @BeforeEach + void setUp() { + ClientThread clientThread = mock(ClientThread.class); + when(clientThread.runOnClientThreadOptional(any())).thenReturn(Optional.of(LOCATION)); + microbot = mockStatic(Microbot.class); + microbot.when(Microbot::getClientThread).thenReturn(clientThread); + dialogue = mockStatic(Rs2Dialogue.class); + dialogue.when(Rs2Dialogue::isInDialogue).thenReturn(true); + keyboard = mockStatic(Rs2Keyboard.class); + + clueScrollPlugin = mock(ClueScrollPlugin.class); + clue = mock(AnagramClue.class); + when(clue.getLocation(clueScrollPlugin)).thenReturn(LOCATION); + } + + @AfterEach + void tearDown() { + keyboard.close(); + dialogue.close(); + microbot.close(); + } + + @Test + void anagramWithoutAnswerClicksThroughDialogueWithoutTyping() throws Exception { + when(clue.getAnswer(clueScrollPlugin)).thenReturn(null); + CompletableFuture result = runDialogueTick(); + + keyboard.verify(() -> Rs2Keyboard.typeString(any()), never()); + keyboard.verify(Rs2Keyboard::enter, never()); + assertTrue(result.isDone()); + assertTrue(result.join()); + } + + @Test + void anagramWithBlankAnswerDoesNotType() throws Exception { + when(clue.getAnswer(clueScrollPlugin)).thenReturn(" "); + CompletableFuture result = runDialogueTick(); + + keyboard.verify(() -> Rs2Keyboard.typeString(any()), never()); + assertTrue(result.join()); + } + + @Test + void anagramWithAnswerTypesItAndSubmits() throws Exception { + when(clue.getAnswer(clueScrollPlugin)).thenReturn("6"); + CompletableFuture result = runDialogueTick(); + + keyboard.verify(() -> Rs2Keyboard.typeString("6")); + keyboard.verify(Rs2Keyboard::enter); + assertTrue(result.join()); + } + + private CompletableFuture runDialogueTick() throws Exception { + AnagramClueTask task = new AnagramClueTask(mock(Client.class), clue, clueScrollPlugin, null, + mock(EventBus.class), new DirectExecutor()); + CompletableFuture result = new CompletableFuture<>(); + task.setFuture(result); + setState(task, "HANDLING_DIALOGUE"); + task.onGameTick(new GameTick()); + return result; + } + + @SuppressWarnings({"unchecked", "rawtypes"}) + private static void setState(AnagramClueTask task, String name) throws Exception { + Field field = AnagramClueTask.class.getDeclaredField("state"); + field.setAccessible(true); + field.set(task, Enum.valueOf((Class) field.getType(), name)); + } + + private static final class DirectExecutor extends AbstractExecutorService { + @Override + public void execute(Runnable command) { + command.run(); + } + + @Override + public void shutdown() { + } + + @Override + public List shutdownNow() { + return Collections.emptyList(); + } + + @Override + public boolean isShutdown() { + return false; + } + + @Override + public boolean isTerminated() { + return false; + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) { + return true; + } + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/cluesolver/RequirementHandlerTaskTest.java b/src/test/java/net/runelite/client/plugins/microbot/cluesolver/RequirementHandlerTaskTest.java new file mode 100644 index 0000000000..7d57de30df --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/cluesolver/RequirementHandlerTaskTest.java @@ -0,0 +1,192 @@ +package net.runelite.client.plugins.microbot.cluesolver; + +import net.runelite.api.Client; +import net.runelite.api.Item; +import net.runelite.api.Player; +import net.runelite.api.events.GameTick; +import net.runelite.client.callback.ClientThread; +import net.runelite.client.eventbus.EventBus; +import net.runelite.client.plugins.cluescrolls.clues.item.ItemRequirement; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.cluesolver.cluetask.RequirementHandlerTask; +import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; +import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.AbstractExecutorService; +import java.util.concurrent.Callable; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +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.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.when; + +class RequirementHandlerTaskTest { + + private final AtomicBoolean onClientThread = new AtomicBoolean(false); + private final AtomicInteger bankCallsOnClientThread = new AtomicInteger(); + private MockedStatic microbot; + private MockedStatic bank; + private DeferredExecutor executor; + private CompletableFuture result; + private RequirementHandlerTask task; + + @BeforeEach + void setUp() { + ClientThread clientThread = mock(ClientThread.class); + when(clientThread.runOnClientThreadOptional(any())).thenAnswer(invocation -> { + onClientThread.set(true); + try { + return Optional.ofNullable(((Callable) invocation.getArgument(0)).call()); + } finally { + onClientThread.set(false); + } + }); + microbot = mockStatic(Microbot.class); + microbot.when(Microbot::getClientThread).thenReturn(clientThread); + bank = mockStatic(Rs2Bank.class, invocation -> { + if (onClientThread.get()) { + bankCallsOnClientThread.incrementAndGet(); + } + return invocation.getMethod().getReturnType() == boolean.class ? false : null; + }); + + Player player = mock(Player.class); + when(player.getWorldLocation()).thenReturn(BankLocation.AL_KHARID.getWorldPoint()); + Client client = mock(Client.class); + when(client.getLocalPlayer()).thenReturn(player); + + ItemRequirement spade = mock(ItemRequirement.class); + when(spade.fulfilledBy(any(Item[].class))).thenReturn(false); + when(spade.getCollectiveName(client)).thenReturn("Spade"); + + executor = new DeferredExecutor(); + result = new CompletableFuture<>(); + task = new RequirementHandlerTask(client, Collections.singletonList(spade), mock(EventBus.class), null, null, executor); + task.setFuture(result); + } + + @AfterEach + void tearDown() { + bank.close(); + microbot.close(); + } + + @Test + void executeTaskHandsTheBankWorkToTheExecutor() { + task.run(); + + assertEquals(1, executor.queued.size()); + bank.verifyNoInteractions(); + assertFalse(result.isDone()); + } + + @Test + void nearestBankIsResolvedOutsideTheClientThread() { + bank.when(Rs2Bank::getNearestBank).thenReturn(BankLocation.AL_KHARID); + + task.run(); + executor.runAll(); + + bank.verify(Rs2Bank::getNearestBank); + bank.verify(Rs2Bank::openBank); + assertEquals(0, bankCallsOnClientThread.get()); + assertTrue(result.isDone()); + assertFalse(result.join()); + } + + @Test + void unreachableBankFailsTheTaskWithoutWalking() { + task.run(); + executor.runAll(); + + bank.verify(Rs2Bank::getNearestBank); + bank.verify(Rs2Bank::walkToBankAndUseBank, never()); + bank.verify(Rs2Bank::openBank, never()); + assertTrue(result.isDone()); + assertFalse(result.join()); + } + + @Test + void gameTickRunsBankFetchOnTheExecutorAndDropsOverlappingTicks() { + bank.when(Rs2Bank::isOpen).thenReturn(true); + + task.onGameTick(new GameTick()); + task.onGameTick(new GameTick()); + + assertEquals(1, executor.queued.size()); + bank.verify(Rs2Bank::closeBank, never()); + + executor.runAll(); + + bank.verify(Rs2Bank::closeBank); + assertTrue(result.join()); + + task.onGameTick(new GameTick()); + assertEquals(1, executor.queued.size()); + } + + @Test + void gameTickDoesNotPlanARouteBeforeABankIsChosen() { + bank.when(Rs2Bank::isOpen).thenReturn(false); + + task.onGameTick(new GameTick()); + + bank.verify(Rs2Bank::getNearestBank, never()); + assertEquals(0, executor.queued.size()); + } + + private static final class DeferredExecutor extends AbstractExecutorService { + private final List queued = new ArrayList<>(); + + @Override + public void execute(Runnable command) { + queued.add(command); + } + + void runAll() { + while (!queued.isEmpty()) { + queued.remove(0).run(); + } + } + + @Override + public void shutdown() { + } + + @Override + public List shutdownNow() { + return Collections.emptyList(); + } + + @Override + public boolean isShutdown() { + return false; + } + + @Override + public boolean isTerminated() { + return false; + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) { + return true; + } + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunBankItemsTest.java b/src/test/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunBankItemsTest.java new file mode 100644 index 0000000000..f83ad9cb07 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunBankItemsTest.java @@ -0,0 +1,50 @@ +package net.runelite.client.plugins.microbot.farmtreerun; + +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class FarmTreeRunBankItemsTest { + + @Test + void scriptKeepsNoSharedWithdrawalCollection() { + for (Field field : FarmTreeRunScript.class.getDeclaredFields()) { + if (Modifier.isStatic(field.getModifiers())) continue; + assertFalse(Collection.class.isAssignableFrom(field.getType()), + "FarmTreeRunScript must not share collection state across threads: " + field.getName()); + } + } + + @Test + void mergeSumsSameItemAndNotedFlag() { + List input = new ArrayList<>(); + input.add(new FarmingItem(563, 10)); + input.add(new FarmingItem(554, 30)); + input.add(new FarmingItem(563, 5, false, true)); + input.add(new FarmingItem(995, 100, true)); + input.add(new FarmingItem(995, 50, false)); + + List merged = FarmTreeRunScript.mergeItems(input); + + assertNotSame(input, merged); + assertEquals(5, input.size()); + assertEquals(4, merged.size()); + assertEquals(563, merged.get(0).getItemId()); + assertEquals(15, merged.get(0).getQuantity()); + assertFalse(merged.get(0).isOptional()); + assertEquals(554, merged.get(1).getItemId()); + assertTrue(merged.get(2).isNoted()); + assertEquals(100, merged.get(2).getQuantity()); + assertFalse(merged.get(3).isNoted()); + assertEquals(50, merged.get(3).getQuantity()); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunHandlePatchTest.java b/src/test/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunHandlePatchTest.java new file mode 100644 index 0000000000..59f54c92d9 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunHandlePatchTest.java @@ -0,0 +1,99 @@ +package net.runelite.client.plugins.microbot.farmtreerun; + +import net.runelite.api.GameObject; +import net.runelite.api.ObjectComposition; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.concurrent.ScheduledFuture; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.when; + +class FarmTreeRunHandlePatchTest { + + private static FarmTreeRunScript runningScript(FarmTreeRunConfig config) throws Exception { + FarmTreeRunScript script = new FarmTreeRunScript(mock(FarmTreeRunPlugin.class), config); + ScheduledFuture future = mock(ScheduledFuture.class); + when(future.isDone()).thenReturn(false); + Field field = Script.class.getDeclaredField("mainScheduledFuture"); + field.setAccessible(true); + field.set(script, future); + return script; + } + + @Test + void nullTreeCompositionSkipsPatchInsteadOfThrowing() throws Exception { + FarmTreeRunConfig config = mock(FarmTreeRunConfig.class); + FarmTreeRunScript script = runningScript(config); + GameObject treePatch = mock(GameObject.class); + when(treePatch.getId()).thenReturn(8392); + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic gameObject = mockStatic(Rs2GameObject.class)) { + microbot.when(Microbot::isLoggedIn).thenReturn(true); + gameObject.when(() -> Rs2GameObject.findObjectByImposter(anyInt(), anyString(), eq(false))).thenReturn(treePatch); + gameObject.when(() -> Rs2GameObject.findObjectComposition(anyInt())).thenReturn(null); + + Method handlePatch = FarmTreeRunScript.class.getDeclaredMethod("handlePatch", FarmTreeRunConfig.class, FarmTreeRunScript.Patch.class); + handlePatch.setAccessible(true); + Object result = handlePatch.invoke(script, config, FarmTreeRunScript.Patch.GNOME_STRONGHOLD_TREE_PATCH); + + assertEquals(Boolean.FALSE, result); + } + } + + @Test + void nullTreeActionsSkipsPatchInsteadOfThrowing() throws Exception { + FarmTreeRunConfig config = mock(FarmTreeRunConfig.class); + FarmTreeRunScript script = runningScript(config); + GameObject treePatch = mock(GameObject.class); + when(treePatch.getId()).thenReturn(8392); + ObjectComposition composition = mock(ObjectComposition.class); + when(composition.getActions()).thenReturn(null); + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic gameObject = mockStatic(Rs2GameObject.class)) { + microbot.when(Microbot::isLoggedIn).thenReturn(true); + gameObject.when(() -> Rs2GameObject.findObjectByImposter(anyInt(), anyString(), eq(false))).thenReturn(treePatch); + gameObject.when(() -> Rs2GameObject.findObjectComposition(anyInt())).thenReturn(composition); + + Method handlePatch = FarmTreeRunScript.class.getDeclaredMethod("handlePatch", FarmTreeRunConfig.class, FarmTreeRunScript.Patch.class); + handlePatch.setAccessible(true); + Object result = handlePatch.invoke(script, config, FarmTreeRunScript.Patch.GNOME_STRONGHOLD_TREE_PATCH); + + assertEquals(Boolean.FALSE, result); + } + } + + @Test + void disabledProtectionSkipsPaymentWithoutReadingComposition() throws Exception { + FarmTreeRunConfig config = mock(FarmTreeRunConfig.class); + when(config.protectTrees()).thenReturn(false); + FarmTreeRunScript script = runningScript(config); + try (MockedStatic gameObject = mockStatic(Rs2GameObject.class); + MockedStatic npc = mockStatic(Rs2Npc.class)) { + gameObject.when(() -> Rs2GameObject.getObjectComposition(anyInt())).thenReturn(null); + + Class paymentKind = Class.forName(FarmTreeRunScript.class.getName() + "$PaymentKind"); + Object protect = java.util.Arrays.stream(paymentKind.getEnumConstants()).filter(k -> k.toString().equals("PROTECT")).findFirst().orElseThrow(); + Method handlePayment = FarmTreeRunScript.class.getDeclaredMethod("handlePayment", FarmTreeRunConfig.class, FarmTreeRunScript.Patch.class, paymentKind); + handlePayment.setAccessible(true); + Object result = handlePayment.invoke(script, config, FarmTreeRunScript.Patch.GNOME_STRONGHOLD_TREE_PATCH, protect); + + assertEquals(Boolean.TRUE, result); + gameObject.verify(() -> Rs2GameObject.getObjectComposition(anyInt()), never()); + npc.verify(() -> Rs2Npc.getNearestNpcWithAction(anyString()), never()); + } + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPatchEmptyTest.java b/src/test/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPatchEmptyTest.java new file mode 100644 index 0000000000..71e73d2a36 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPatchEmptyTest.java @@ -0,0 +1,40 @@ +package net.runelite.client.plugins.microbot.farmtreerun; + +import net.runelite.api.ObjectComposition; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class FarmTreeRunPatchEmptyTest { + + private static ObjectComposition named(String name) { + ObjectComposition composition = mock(ObjectComposition.class); + when(composition.getName()).thenReturn(name); + return composition; + } + + @Test + void nullCompositionIsTreatedAsEmptyInsteadOfThrowing() { + assertTrue(FarmTreeRunScript.isEmptyPatchComposition(null)); + } + + @Test + void nullNameIsTreatedAsEmpty() { + assertTrue(FarmTreeRunScript.isEmptyPatchComposition(named(null))); + } + + @Test + void patchNameIsEmpty() { + assertTrue(FarmTreeRunScript.isEmptyPatchComposition(named("Tree patch"))); + assertTrue(FarmTreeRunScript.isEmptyPatchComposition(named("Fruit Tree Patch"))); + } + + @Test + void grownTreeIsNotEmpty() { + assertFalse(FarmTreeRunScript.isEmptyPatchComposition(named("Magic tree"))); + assertFalse(FarmTreeRunScript.isEmptyPatchComposition(named("Papaya tree"))); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/gotr/GotrAltarRenderableTest.java b/src/test/java/net/runelite/client/plugins/microbot/gotr/GotrAltarRenderableTest.java new file mode 100644 index 0000000000..94346759af --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/gotr/GotrAltarRenderableTest.java @@ -0,0 +1,55 @@ +package net.runelite.client.plugins.microbot.gotr; + +import net.runelite.api.Animation; +import net.runelite.api.DynamicObject; +import net.runelite.api.Model; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class GotrAltarRenderableTest +{ + @Test + void plainModelRenderableIsNotActive() + { + assertFalse(GotrScript.isActiveAltarRenderable(mock(Model.class))); + } + + @Test + void nullRenderableIsNotActive() + { + assertFalse(GotrScript.isActiveAltarRenderable(null)); + } + + @Test + void dynamicObjectWithoutAnimationIsNotActive() + { + DynamicObject altar = mock(DynamicObject.class); + when(altar.getAnimation()).thenReturn(null); + assertFalse(GotrScript.isActiveAltarRenderable(altar)); + } + + @Test + void dynamicObjectWithOtherAnimationIsNotActive() + { + assertFalse(GotrScript.isActiveAltarRenderable(animated(9362))); + } + + @Test + void dynamicObjectWithActiveAnimationIsActive() + { + assertTrue(GotrScript.isActiveAltarRenderable(animated(9363))); + } + + private static DynamicObject animated(int animationId) + { + Animation animation = mock(Animation.class); + when(animation.getId()).thenReturn(animationId); + DynamicObject altar = mock(DynamicObject.class); + when(altar.getAnimation()).thenReturn(animation); + return altar; + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/jad/JadNpcNameTest.java b/src/test/java/net/runelite/client/plugins/microbot/jad/JadNpcNameTest.java new file mode 100644 index 0000000000..05d12f955b --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/jad/JadNpcNameTest.java @@ -0,0 +1,41 @@ +package net.runelite.client.plugins.microbot.jad; + +import net.runelite.api.Actor; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class JadNpcNameTest { + + @Test + void nameThatDropsAfterFirstReadIsReadOnce() { + Actor npc = mock(Actor.class); + when(npc.getName()).thenReturn("TzTok-Jad", (String) null); + + assertTrue(JadScript.nameContains(npc, "jad")); + assertFalse(JadScript.nameContains(npc, "jad")); + verify(npc, times(2)).getName(); + } + + @Test + void nullNameDoesNotMatch() { + Actor npc = mock(Actor.class); + + assertFalse(JadScript.nameContains(npc, "jad")); + verify(npc, times(1)).getName(); + } + + @Test + void matchIgnoresCase() { + Actor healer = mock(Actor.class); + when(healer.getName()).thenReturn("Yt-HurKot"); + + assertTrue(JadScript.nameContains(healer, "hurkot")); + assertFalse(JadScript.nameContains(healer, "jad")); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScriptTest.java b/src/test/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScriptTest.java new file mode 100644 index 0000000000..b4e4aa52e8 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScriptTest.java @@ -0,0 +1,34 @@ +package net.runelite.client.plugins.microbot.magetrainingarena; + +import net.runelite.api.widgets.Widget; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class MageTrainingArenaScriptTest { + + private static Widget item(int itemId) { + Widget widget = mock(Widget.class); + when(widget.getItemId()).thenReturn(itemId); + return widget; + } + + @Test + void unloadedRewardListHasNoReward() { + assertEquals(-1, MageTrainingArenaScript.findRewardIndex(null, 6908)); + } + + @Test + void missingRewardIsNotClicked() { + Widget[] widgets = {item(6908), item(6910)}; + assertEquals(-1, MageTrainingArenaScript.findRewardIndex(widgets, 6912)); + } + + @Test + void matchingRewardReturnsItsIndex() { + Widget[] widgets = {item(6908), null, item(6910)}; + assertEquals(2, MageTrainingArenaScript.findRewardIndex(widgets, 6910)); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScriptTest.java b/src/test/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScriptTest.java new file mode 100644 index 0000000000..b2b80988c2 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScriptTest.java @@ -0,0 +1,75 @@ +package net.runelite.client.plugins.microbot.mahoganyhomez; + +import net.runelite.api.coords.WorldPoint; +import org.junit.jupiter.api.Test; + +import java.util.Arrays; +import java.util.Set; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class MahoganyHomesScriptTest { + + @Test + void closestContractLocationIsNullWithoutPlayerLocation() { + assertNull(MahoganyHomesScript.getClosestContractLocation(null)); + } + + @Test + void closestContractLocationPicksNearestOffice() { + assertEquals(ContractLocation.MAHOGANY_HOMES_VARROCK.getLocation(), + MahoganyHomesScript.getClosestContractLocation(new WorldPoint(3213, 3424, 0))); + assertEquals(ContractLocation.MAHOGANY_HOMES_FALADOR.getLocation(), + MahoganyHomesScript.getClosestContractLocation(new WorldPoint(2965, 3380, 0))); + assertEquals(ContractLocation.MAHOGANY_HOMES_ARDOUGNE.getLocation(), + MahoganyHomesScript.getClosestContractLocation(new WorldPoint(2662, 3305, 0))); + assertEquals(ContractLocation.MAHOGANY_HOMES_HOSIDIUS.getLocation(), + MahoganyHomesScript.getClosestContractLocation(new WorldPoint(1760, 3600, 0))); + } + + @Test + void isAwayFromHomeIsFalseWithoutHomeOrPlayerLocation() { + MahoganyHomesScript script = scriptWithPlugin(); + assertFalse(script.isAwayFromHome(null, new WorldPoint(3213, 3424, 0))); + assertFalse(script.isAwayFromHome(Home.JESS, null)); + assertFalse(script.isAwayFromHome(null, null)); + } + + @Test + void isAwayFromHomeComparesPlayerLocationToHomeArea() { + MahoganyHomesScript script = scriptWithPlugin(); + assertTrue(script.isAwayFromHome(Home.JESS, new WorldPoint(3213, 3424, 0))); + assertFalse(script.isAwayFromHome(Home.JESS, new WorldPoint(2615, 3293, 1))); + } + + @Test + void fixableObjectIdsSkipsHotspotsBeyondHomeObjectCount() { + Set ids = MahoganyHomesScript.getFixableObjectIds(Arrays.asList(Hotspot.values()), HotspotObjects.ROSS); + assertEquals(HotspotObjects.ROSS.objects.length, ids.size()); + assertTrue(ids.contains(HotspotObjects.ROSS.objects[6].getObjectId())); + } + + @Test + void fixableObjectIdsSkipsHotspotsBeyondSixObjectHome() { + Set ids = MahoganyHomesScript.getFixableObjectIds( + Arrays.asList(Hotspot.MAHOGANY_HOMES_HOTSPOT_1, Hotspot.MAHOGANY_HOMES_HOTSPOT_7, Hotspot.MAHOGANY_HOMES_HOTSPOT_8), + HotspotObjects.SARAH); + assertEquals(Set.of(HotspotObjects.SARAH.objects[0].getObjectId()), ids); + } + + @Test + void fixableObjectIdsMapsAllHotspotsForEightObjectHome() { + Set ids = MahoganyHomesScript.getFixableObjectIds(Arrays.asList(Hotspot.values()), HotspotObjects.JESS); + assertEquals(8, ids.size()); + assertTrue(ids.contains(HotspotObjects.JESS.objects[7].getObjectId())); + } + + private static MahoganyHomesScript scriptWithPlugin() { + MahoganyHomesScript script = new MahoganyHomesScript(); + script.plugin = new MahoganyHomesPlugin(); + return script; + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/ResupplyHandlerTest.java b/src/test/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/ResupplyHandlerTest.java new file mode 100644 index 0000000000..bee663051e --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/ResupplyHandlerTest.java @@ -0,0 +1,25 @@ +package net.runelite.client.plugins.microbot.moonsofperil.handlers; + +import net.runelite.api.gameval.ObjectID; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ResupplyHandlerTest { + @Test + void fishesTheRiverFishingSpotNotTheRetiredMoonsId() { + assertEquals(ObjectID.RIVER_FISHING_SPOT, ResupplyHandler.BREAM_FISHING_SPOT_ID); + assertNotEquals(ObjectID.SAILING_GANGPLANK_PROXY_WIDE_VIKING, ResupplyHandler.BREAM_FISHING_SPOT_ID); + } + + @Test + void fishingStallsOnlyAfterTimeoutWithoutProgress() { + long start = 1_000_000L; + assertFalse(ResupplyHandler.isFishingStalled(start, start)); + assertFalse(ResupplyHandler.isFishingStalled(start, start + ResupplyHandler.FISHING_STALL_TIMEOUT_MS - 1)); + assertTrue(ResupplyHandler.isFishingStalled(start, start + ResupplyHandler.FISHING_STALL_TIMEOUT_MS)); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/motherloadmine/MotherloadMineScriptTest.java b/src/test/java/net/runelite/client/plugins/microbot/motherloadmine/MotherloadMineScriptTest.java new file mode 100644 index 0000000000..0e2a6a741d --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/motherloadmine/MotherloadMineScriptTest.java @@ -0,0 +1,18 @@ +package net.runelite.client.plugins.microbot.motherloadmine; + +import net.runelite.client.plugins.microbot.util.antiban.enums.Activity; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public final class MotherloadMineScriptTest { + @Test + void missingAntibanActivityFallsBackToGeneralMining() { + assertEquals(Activity.GENERAL_MINING.getActivityIntensity(), MotherloadMineScript.miningIntensity(null)); + } + + @Test + void usesConfiguredAntibanActivityIntensity() { + assertEquals(Activity.GENERAL_COMBAT.getActivityIntensity(), MotherloadMineScript.miningIntensity(Activity.GENERAL_COMBAT)); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/nmz/NmzScriptTest.java b/src/test/java/net/runelite/client/plugins/microbot/nmz/NmzScriptTest.java new file mode 100644 index 0000000000..8ea1adf077 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/nmz/NmzScriptTest.java @@ -0,0 +1,25 @@ +package net.runelite.client.plugins.microbot.nmz; + +import net.runelite.api.coords.WorldPoint; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class NmzScriptTest { + + @Test + void unknownPlayerLocationIsNotOutside() { + assertFalse(NmzScript.isNearLobby(null)); + } + + @Test + void locationNearLobbyIsOutside() { + assertTrue(NmzScript.isNearLobby(new WorldPoint(2609, 3114, 0))); + } + + @Test + void locationInsideDreamIsNotOutside() { + assertFalse(NmzScript.isNearLobby(new WorldPoint(2273, 4694, 0))); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/nmz/PrayerPotionScriptTest.java b/src/test/java/net/runelite/client/plugins/microbot/nmz/PrayerPotionScriptTest.java new file mode 100644 index 0000000000..6d5c41d86b --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/nmz/PrayerPotionScriptTest.java @@ -0,0 +1,26 @@ +package net.runelite.client.plugins.microbot.nmz; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PrayerPotionScriptTest { + + @Test + void zeroRealLevelSkipsDrinkingInsteadOfDividingByZero() { + assertTrue(PrayerPotionScript.isPrayerAboveThreshold(0, 0, 25)); + assertTrue(PrayerPotionScript.isPrayerAboveThreshold(10, 0, 30)); + } + + @Test + void prayerAboveThresholdSkipsDrinking() { + assertTrue(PrayerPotionScript.isPrayerAboveThreshold(50, 70, 30)); + } + + @Test + void prayerAtOrBelowThresholdAllowsDrinking() { + assertFalse(PrayerPotionScript.isPrayerAboveThreshold(21, 70, 30)); + assertFalse(PrayerPotionScript.isPrayerAboveThreshold(0, 70, 25)); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/playermonitor/PlayerMonitorPluginTest.java b/src/test/java/net/runelite/client/plugins/microbot/playermonitor/PlayerMonitorPluginTest.java new file mode 100644 index 0000000000..e29ce1c7f2 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/playermonitor/PlayerMonitorPluginTest.java @@ -0,0 +1,39 @@ +package net.runelite.client.plugins.microbot.playermonitor; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PlayerMonitorPluginTest { + + @TempDir + Path tempDir; + + @Test + void writeClickCountCreatesMissingDirectory() throws IOException { + File file = tempDir.resolve("PlayerMonitor").resolve("click_count.log").toFile(); + + PlayerMonitorPlugin.writeClickCount(file, 42); + + assertTrue(file.isFile()); + assertEquals("42 ", new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8)); + } + + @Test + void writeClickCountOverwritesExistingFile() throws IOException { + File file = tempDir.resolve("click_count.log").toFile(); + Files.write(file.toPath(), "999 ".getBytes(StandardCharsets.UTF_8)); + + PlayerMonitorPlugin.writeClickCount(file, 7); + + assertEquals("7 ", new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8)); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/qualityoflife/QoLScriptInvokeBoundsTest.java b/src/test/java/net/runelite/client/plugins/microbot/qualityoflife/QoLScriptInvokeBoundsTest.java new file mode 100644 index 0000000000..c59def8531 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/qualityoflife/QoLScriptInvokeBoundsTest.java @@ -0,0 +1,31 @@ +package net.runelite.client.plugins.microbot.qualityoflife; + +import java.awt.Rectangle; +import net.runelite.api.MenuAction; +import net.runelite.api.widgets.Widget; +import net.runelite.client.plugins.microbot.util.menu.NewMenuEntry; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class QoLScriptInvokeBoundsTest { + @Test + void recordedEntryWithoutWidgetFallsBackToUnitRectangle() { + NewMenuEntry entry = new NewMenuEntry("Walk here", "", 0, MenuAction.WALK, 0, 0, false); + + assertEquals(new Rectangle(1, 1), QoLScript.invokeBounds(entry)); + } + + @Test + void recordedEntryWithWidgetUsesWidgetBounds() { + Rectangle bounds = new Rectangle(40, 60, 36, 32); + Widget widget = mock(Widget.class); + when(widget.getBounds()).thenReturn(bounds); + NewMenuEntry entry = new NewMenuEntry("Withdraw-1", "Coins", 1, MenuAction.CC_OP, 3, 786445, false); + entry.setWidget(widget); + + assertEquals(bounds, QoLScript.invokeBounds(entry)); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtHpParseTest.java b/src/test/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtHpParseTest.java new file mode 100644 index 0000000000..94537f1d5f --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtHpParseTest.java @@ -0,0 +1,25 @@ +package net.runelite.client.plugins.microbot.qualityoflife.scripts.wintertodt; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; + +class WintertodtHpParseTest { + @Test + void parsesEnergyPercentage() { + assertEquals(45, WintertodtScript.parseWintertodtHp("Wintertodt's Energy: 45%")); + assertEquals(100, WintertodtScript.parseWintertodtHp("Wintertodt's Energy: 100%")); + assertEquals(0, WintertodtScript.parseWintertodtHp("Wintertodt's Energy: 0%")); + } + + @Test + void parsesTextStartingWithDigits() { + assertEquals(72, WintertodtScript.parseWintertodtHp("72%")); + } + + @Test + void returnsMinusOneWhenTextHasNoDigits() { + assertEquals(-1, WintertodtScript.parseWintertodtHp("")); + assertEquals(-1, WintertodtScript.parseWintertodtHp("Wintertodt's Energy")); + assertEquals(-1, WintertodtScript.parseWintertodtHp(null)); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/revkiller/RevKillerScriptSafetyTest.java b/src/test/java/net/runelite/client/plugins/microbot/revkiller/RevKillerScriptSafetyTest.java new file mode 100644 index 0000000000..6d49a43e34 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/revkiller/RevKillerScriptSafetyTest.java @@ -0,0 +1,116 @@ +package net.runelite.client.plugins.microbot.revkiller; + +import net.runelite.api.Actor; +import net.runelite.api.Client; +import net.runelite.api.EquipmentInventorySlot; +import net.runelite.api.Player; +import net.runelite.api.kit.KitType; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; +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.player.Rs2PlayerModel; +import net.runelite.client.plugins.microbot.util.player.Rs2Pvp; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class RevKillerScriptSafetyTest { + + @Test + void emptyRingSlotIsNotADuelingRing() { + try (MockedStatic equipment = mockStatic(Rs2Equipment.class)) { + equipment.when(() -> Rs2Equipment.get(EquipmentInventorySlot.RING)).thenReturn(null); + + assertFalse(revKillerScript.isWearingDuelingRing()); + } + } + + @Test + void equippedDuelingRingIsDetected() { + Rs2ItemModel ring = mock(Rs2ItemModel.class); + when(ring.getName()).thenReturn("Ring of dueling(8)"); + try (MockedStatic equipment = mockStatic(Rs2Equipment.class)) { + equipment.when(() -> Rs2Equipment.get(EquipmentInventorySlot.RING)).thenReturn(ring); + + assertTrue(revKillerScript.isWearingDuelingRing()); + } + } + + @Test + void emptyAmmoSlotHasNoAmmo() { + try (MockedStatic equipment = mockStatic(Rs2Equipment.class)) { + equipment.when(() -> Rs2Equipment.get(EquipmentInventorySlot.AMMO)).thenReturn(null); + + assertFalse(revKillerScript.hasEquippedAmmoAbove(50)); + } + } + + @Test + void ammoQuantityIsComparedAgainstThreshold() { + Rs2ItemModel ammo = mock(Rs2ItemModel.class); + when(ammo.getQuantity()).thenReturn(51); + try (MockedStatic equipment = mockStatic(Rs2Equipment.class)) { + equipment.when(() -> Rs2Equipment.get(EquipmentInventorySlot.AMMO)).thenReturn(ammo); + + assertTrue(revKillerScript.hasEquippedAmmoAbove(50)); + assertFalse(revKillerScript.hasEquippedAmmoAbove(51)); + } + } + + @Test + void playerAttackingUsIsAPkerWithoutReadingRawActorName() { + Player local = mock(Player.class); + Rs2PlayerModel attacker = attackerInteractingWith(local); + + assertTrue(isPkerAround(local, attacker)); + verify(local, never()).getName(); + } + + @Test + void playerAttackingSomeoneElseIsNotAPker() { + Player local = mock(Player.class); + Actor other = mock(Actor.class); + Rs2PlayerModel attacker = attackerInteractingWith(other); + + assertFalse(isPkerAround(local, attacker)); + verify(other, never()).getName(); + } + + private static Rs2PlayerModel attackerInteractingWith(Actor target) { + Rs2PlayerModel attacker = mock(Rs2PlayerModel.class); + when(attacker.getName()).thenReturn("Attacker"); + when(attacker.getInteracting()).thenReturn(target); + return attacker; + } + + private static boolean isPkerAround(Player local, Rs2PlayerModel attacker) { + Client client = mock(Client.class); + when(client.getLocalPlayer()).thenReturn(local); + Map gear = new EnumMap<>(KitType.class); + gear.put(KitType.WEAPON, "Rune scimitar"); + gear.put(KitType.TORSO, "Rune platebody"); + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic pvp = mockStatic(Rs2Pvp.class); + MockedStatic player = mockStatic(Rs2Player.class)) { + microbot.when(Microbot::isLoggedIn).thenReturn(true); + microbot.when(Microbot::getClient).thenReturn(client); + pvp.when(Rs2Pvp::isInWilderness).thenReturn(true); + player.when(Rs2Player::getPlayersInCombatLevelRange).thenReturn(List.of(attacker)); + player.when(() -> Rs2Player.getPlayerEquipmentNames(attacker)).thenReturn(gear); + + return new revKillerScript().isPkerAround(); + } + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabScriptTest.java b/src/test/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabScriptTest.java new file mode 100644 index 0000000000..9cfcf0c83c --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabScriptTest.java @@ -0,0 +1,34 @@ +package net.runelite.client.plugins.microbot.sandcrabs; + +import net.runelite.client.plugins.microbot.inventorysetups.InventorySetup; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class SandCrabScriptTest { + + @Test + void unselectedInventorySetupIsMissing() { + assertFalse(SandCrabScript.hasInventorySetup(null)); + } + + @Test + void setupWithoutNameIsMissing() { + InventorySetup setup = mock(InventorySetup.class); + assertFalse(SandCrabScript.hasInventorySetup(setup)); + + when(setup.getName()).thenReturn(""); + assertFalse(SandCrabScript.hasInventorySetup(setup)); + } + + @Test + void namedSetupIsPresent() { + InventorySetup setup = mock(InventorySetup.class); + when(setup.getName()).thenReturn("Sand crabs"); + + assertTrue(SandCrabScript.hasInventorySetup(setup)); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/scurrius/ScurriusScriptTest.java b/src/test/java/net/runelite/client/plugins/microbot/scurrius/ScurriusScriptTest.java new file mode 100644 index 0000000000..ff5515d902 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/scurrius/ScurriusScriptTest.java @@ -0,0 +1,85 @@ +package net.runelite.client.plugins.microbot.scurrius; + +import net.runelite.api.coords.WorldArea; +import net.runelite.api.NPC; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import org.junit.jupiter.api.Test; + +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.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class ScurriusScriptTest { + + private final WorldArea fightRoom = new WorldArea(new WorldPoint(3290, 9860, 0), 17, 17); + + @Test + void thresholdStaysInRangeWhenMinExceedsMax() { + for (int i = 0; i < 500; i++) { + int threshold = ScurriusScript.randomThreshold(60, 40); + assertTrue(threshold >= 40 && threshold <= 60); + } + } + + @Test + void thresholdStaysInRangeWhenOrdered() { + for (int i = 0; i < 500; i++) { + int threshold = ScurriusScript.randomThreshold(40, 60); + assertTrue(threshold >= 40 && threshold <= 60); + } + } + + @Test + void equalBoundsReturnThatValue() { + assertEquals(50, ScurriusScript.randomThreshold(50, 50)); + } + + @Test + void nullLocationIsNotInFightRoom() { + assertFalse(ScurriusScript.isInArea(fightRoom, null)); + } + + @Test + void locationInsideFightRoomIsDetected() { + assertTrue(ScurriusScript.isInArea(fightRoom, new WorldPoint(3295, 9865, 0))); + assertFalse(ScurriusScript.isInArea(fightRoom, new WorldPoint(3279, 9869, 0))); + } + + @Test + void giantRatNameIsReadOnceSoALaterNullCannotThrow() { + Rs2NpcModel rat = mock(Rs2NpcModel.class); + NPC npc = mock(NPC.class); + when(rat.getName()).thenReturn("Giant rat", (String) null); + when(rat.getNpc()).thenReturn(npc); + when(npc.isDead()).thenReturn(false); + + assertTrue(ScurriusScript.isLiveGiantRat(rat)); + verify(rat).getName(); + } + + @Test + void nullNameIsNotAGiantRat() { + Rs2NpcModel npc = mock(Rs2NpcModel.class); + when(npc.getName()).thenReturn(null); + + assertFalse(ScurriusScript.isLiveGiantRat(npc)); + } + + @Test + void deadOrOtherNpcsAreNotLiveGiantRats() { + Rs2NpcModel deadRat = mock(Rs2NpcModel.class); + NPC deadNpc = mock(NPC.class); + when(deadRat.getName()).thenReturn("Giant rat"); + when(deadRat.getNpc()).thenReturn(deadNpc); + when(deadNpc.isDead()).thenReturn(true); + assertFalse(ScurriusScript.isLiveGiantRat(deadRat)); + + Rs2NpcModel scurrius = mock(Rs2NpcModel.class); + when(scurrius.getName()).thenReturn("Scurrius"); + assertFalse(ScurriusScript.isLiveGiantRat(scurrius)); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarStopRaceTest.java b/src/test/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarStopRaceTest.java new file mode 100644 index 0000000000..7e21c001b4 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarStopRaceTest.java @@ -0,0 +1,135 @@ +package net.runelite.client.plugins.microbot.shootingstar; + +import java.lang.reflect.Field; +import java.util.concurrent.atomic.AtomicReference; +import javax.swing.SwingUtilities; +import net.runelite.client.plugins.microbot.shootingstar.enums.ShootingStarState; +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 static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +class ShootingStarStopRaceTest +{ + private final AtomicReference edtFailure = new AtomicReference<>(); + private Thread.UncaughtExceptionHandler previousHandler; + private ShootingStarPlugin plugin; + + @BeforeEach + void setUp() + { + previousHandler = Thread.getDefaultUncaughtExceptionHandler(); + Thread.setDefaultUncaughtExceptionHandler((thread, throwable) -> edtFailure.set(throwable)); + plugin = new ShootingStarPlugin(); + } + + @AfterEach + void tearDown() + { + Thread.setDefaultUncaughtExceptionHandler(previousHandler); + } + + @Test + void updatePanelListIsNoOpWithoutPanel() throws Exception + { + plugin.updatePanelList(true); + plugin.updatePanelList(false); + flushEdt(); + + assertNull(edtFailure.get()); + } + + @Test + void queuedPanelUpdateSkipsRemovedPanel() throws Exception + { + ShootingStarPanel panel = mock(ShootingStarPanel.class); + setPanel(panel); + + SwingUtilities.invokeAndWait(() -> { + plugin.updatePanelList(true); + plugin.updatePanelList(false); + setPanelUnchecked(null); + }); + flushEdt(); + + assertNull(edtFailure.get()); + verify(panel, never()).updateList(anyList()); + verify(panel, never()).refreshList(anyList()); + } + + @Test + void queuedPanelUpdateReachesLivePanel() throws Exception + { + ShootingStarPanel panel = mock(ShootingStarPanel.class); + setPanel(panel); + + plugin.updatePanelList(true); + plugin.updatePanelList(false); + flushEdt(); + + assertNull(edtFailure.get()); + verify(panel).updateList(anyList()); + verify(panel).refreshList(anyList()); + } + + @Test + void missingAntibanCategoryIsNotBusy() + { + assertFalse(ShootingStarScript.isAntibanBusy(null)); + } + + @Test + void stoppingScriptKeepsLoopStateForInFlightIteration() throws Exception + { + ShootingStarScript script = new ShootingStarScript(plugin, mock(ShootingStarConfig.class)); + Star star = mock(Star.class); + Field currentStar = ShootingStarScript.class.getDeclaredField("currentStar"); + currentStar.setAccessible(true); + currentStar.set(script, star); + ShootingStarScript.state = ShootingStarState.MINING; + + try + { + script.shutdown(); + + assertSame(ShootingStarState.MINING, ShootingStarScript.state); + assertSame(star, currentStar.get(script)); + } + finally + { + ShootingStarScript.state = null; + } + } + + private static void flushEdt() throws Exception + { + SwingUtilities.invokeAndWait(() -> { }); + } + + private void setPanelUnchecked(ShootingStarPanel value) + { + try + { + setPanel(value); + } + catch (ReflectiveOperationException e) + { + throw new IllegalStateException(e); + } + } + + private void setPanel(ShootingStarPanel value) throws ReflectiveOperationException + { + Field field = ShootingStarPlugin.class.getDeclaredField("panel"); + field.setAccessible(true); + field.set(plugin, value); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaInteractingTest.java b/src/test/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaInteractingTest.java new file mode 100644 index 0000000000..a0d2912689 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaInteractingTest.java @@ -0,0 +1,78 @@ +package net.runelite.client.plugins.microbot.sulphurnaguafigther; + +import net.runelite.api.Actor; +import net.runelite.api.Player; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class SulphurNaguaInteractingTest { + + @Test + void missingTargetIsAvailable() { + assertTrue(SulphurNaguaScript.isMissingOrDead(null)); + } + + @Test + void deadTargetIsAvailable() { + Actor target = mock(Actor.class); + when(target.isDead()).thenReturn(true); + + assertTrue(SulphurNaguaScript.isMissingOrDead(target)); + } + + @Test + void liveTargetIsNotAvailable() { + Actor target = mock(Actor.class); + + assertFalse(SulphurNaguaScript.isMissingOrDead(target)); + } + + @Test + void npcWhoseTargetDropsAfterFirstReadIsNotInteractingWithUs() { + Player local = mock(Player.class); + Actor npc = mock(Actor.class); + when(npc.getInteracting()).thenReturn(local, (Actor) null); + + assertTrue(SulphurNaguaScript.isInteractingWith(npc, local)); + assertFalse(SulphurNaguaScript.isInteractingWith(npc, local)); + verify(npc, times(2)).getInteracting(); + } + + @Test + void npcInteractingWithSomeoneElseIsNotInteractingWithUs() { + Player local = mock(Player.class); + Player other = mock(Player.class); + Actor npc = mock(Actor.class); + when(npc.getInteracting()).thenReturn(other); + + assertFalse(SulphurNaguaScript.isInteractingWith(npc, local)); + } + + @Test + void npcWhoseTargetDropsMidCheckIsReadOnce() { + Player local = mock(Player.class); + Player other = mock(Player.class); + Actor npc = mock(Actor.class); + when(npc.getInteracting()).thenReturn(other, (Actor) null); + + assertFalse(SulphurNaguaScript.isFreeOrInteractingWith(npc, local)); + verify(npc, times(1)).getInteracting(); + } + + @Test + void freeNpcOrNpcOnUsIsTargetable() { + Player local = mock(Player.class); + Actor freeNpc = mock(Actor.class); + Actor npcOnUs = mock(Actor.class); + when(npcOnUs.getInteracting()).thenReturn(local); + + assertTrue(SulphurNaguaScript.isFreeOrInteractingWith(freeNpc, local)); + assertTrue(SulphurNaguaScript.isFreeOrInteractingWith(npcOnUs, local)); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/thieving/ThievingPluginXpGainedTest.java b/src/test/java/net/runelite/client/plugins/microbot/thieving/ThievingPluginXpGainedTest.java new file mode 100644 index 0000000000..66b41bccd7 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/thieving/ThievingPluginXpGainedTest.java @@ -0,0 +1,50 @@ +package net.runelite.client.plugins.microbot.thieving; + +import net.runelite.api.Client; +import net.runelite.api.Skill; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.player.Rs2PlayerModel; +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.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +class ThievingPluginXpGainedTest { + + @Test + void xpGainedDoesNotThrowWhenLocalPlayerIsMissing() { + Client client = mock(Client.class); + when(client.getSkillExperience(Skill.THIEVING)).thenReturn(1000); + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic player = mockStatic(Rs2Player.class)) { + microbot.when(Microbot::getClient).thenReturn(client); + player.when(Rs2Player::getLocalPlayer).thenReturn(null); + + ThievingPlugin plugin = new ThievingPlugin(); + assertDoesNotThrow(plugin::xpGained); + } + } + + @Test + void xpGainedKeepsStartXpAcrossMissingLocalPlayer() { + Client client = mock(Client.class); + Rs2PlayerModel localPlayer = mock(Rs2PlayerModel.class); + when(localPlayer.getName()).thenReturn("Thief"); + when(client.getSkillExperience(Skill.THIEVING)).thenReturn(1000, 1000, 1200, 1500); + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic player = mockStatic(Rs2Player.class)) { + microbot.when(Microbot::getClient).thenReturn(client); + player.when(Rs2Player::getLocalPlayer).thenReturn(localPlayer, null, localPlayer); + + ThievingPlugin plugin = new ThievingPlugin(); + assertEquals(0, plugin.xpGained()); + assertEquals(200, plugin.xpGained()); + assertEquals(500, plugin.xpGained()); + } + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/thievingstalls/model/BankInventoryStrategyTest.java b/src/test/java/net/runelite/client/plugins/microbot/thievingstalls/model/BankInventoryStrategyTest.java new file mode 100644 index 0000000000..f76885defa --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/thievingstalls/model/BankInventoryStrategyTest.java @@ -0,0 +1,46 @@ +package net.runelite.client.plugins.microbot.thievingstalls.model; + +import net.runelite.client.plugins.microbot.thievingstalls.StallThievingConfig; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class BankInventoryStrategyTest { + + private void runWithAlwaysDrop(String alwaysDrop, int... expectedIds) { + BotApi botApi = mock(BotApi.class); + DropInventoryStrategy dropInventoryStrategy = mock(DropInventoryStrategy.class); + StallThievingConfig config = mock(StallThievingConfig.class); + IStallThievingSpot spot = mock(IStallThievingSpot.class); + when(config.alwaysDrop()).thenReturn(alwaysDrop); + when(botApi.isInventoryFull()).thenReturn(true, false); + + BankInventoryStrategy strategy = new BankInventoryStrategy(botApi, dropInventoryStrategy, config); + + assertDoesNotThrow(() -> strategy.execute(spot)); + verify(botApi).dropAll(expectedIds); + } + + @Test + void itemNamesAreIgnoredInsteadOfThrowing() { + runWithAlwaysDrop("Bread Chocolate slice "); + } + + @Test + void numericIdsAreKeptAlongsideInvalidEntries() { + runWithAlwaysDrop(" 2309 , Bread,1901, 99999999999 ,", 2309, 1901); + } + + @Test + void defaultIdListIsParsed() { + runWithAlwaysDrop("2309,1901", 2309, 1901); + } + + @Test + void blankConfigDropsNothing() { + runWithAlwaysDrop(" "); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/toweroflife_creaturecreation/TowerOfLifeCCSummonQueryTest.java b/src/test/java/net/runelite/client/plugins/microbot/toweroflife_creaturecreation/TowerOfLifeCCSummonQueryTest.java new file mode 100644 index 0000000000..eaacd436d6 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/toweroflife_creaturecreation/TowerOfLifeCCSummonQueryTest.java @@ -0,0 +1,109 @@ +package net.runelite.client.plugins.microbot.toweroflife_creaturecreation; + +import net.runelite.api.Actor; +import net.runelite.api.Client; +import net.runelite.api.NPC; +import net.runelite.api.Player; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.Rs2NpcCache; +import net.runelite.client.plugins.microbot.api.npc.Rs2NpcQueryable; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Predicate; + +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class TowerOfLifeCCSummonQueryTest { + + private final AtomicBoolean onClientThread = new AtomicBoolean(false); + private final Player localPlayer = mock(Player.class); + + @Test + void summonQueryEvaluatesInteractingOnClientThread() { + Rs2NpcModel idle = npc(null); + try (MockedStatic microbot = mockStatic(Microbot.class)) { + Rs2NpcQueryable query = stubQuery(microbot, List.of(idle)); + + assertSame(idle, new TowerOfLifeCCScript().findSummonedCreature()); + verify(query, never()).nearest(); + } + } + + @Test + void summonQueryAcceptsCreatureTargetingLocalPlayer() { + Rs2NpcModel busy = npc(mock(Actor.class)); + Rs2NpcModel mine = npc(localPlayer); + try (MockedStatic microbot = mockStatic(Microbot.class)) { + stubQuery(microbot, List.of(busy, mine)); + + assertSame(mine, new TowerOfLifeCCScript().findSummonedCreature()); + } + } + + @Test + void summonQueryRejectsCreatureTargetingSomeoneElse() { + Rs2NpcModel busy = npc(mock(Actor.class)); + try (MockedStatic microbot = mockStatic(Microbot.class)) { + stubQuery(microbot, List.of(busy)); + + assertNull(new TowerOfLifeCCScript().findSummonedCreature()); + } + } + + private Rs2NpcModel npc(Actor interacting) { + NPC npc = mock(NPC.class); + when(npc.getInteracting()).thenAnswer(invocation -> { + if (!onClientThread.get()) { + throw new IllegalStateException("must be called on client thread"); + } + return interacting; + }); + Rs2NpcModel model = mock(Rs2NpcModel.class); + when(model.getNpc()).thenReturn(npc); + return model; + } + + @SuppressWarnings("unchecked") + private Rs2NpcQueryable stubQuery(MockedStatic microbot, List npcs) { + Client client = mock(Client.class); + when(client.getLocalPlayer()).thenReturn(localPlayer); + microbot.when(Microbot::getClient).thenReturn(client); + + AtomicReference> filter = new AtomicReference<>(npc -> true); + Rs2NpcQueryable query = mock(Rs2NpcQueryable.class); + when(query.where(any())).thenAnswer(invocation -> { + filter.set(filter.get().and(invocation.getArgument(0))); + return query; + }); + when(query.nearest()).thenAnswer(invocation -> first(npcs, filter.get())); + when(query.nearestOnClientThread()).thenAnswer(invocation -> { + onClientThread.set(true); + try { + return first(npcs, filter.get()); + } finally { + onClientThread.set(false); + } + }); + + Rs2NpcCache cache = mock(Rs2NpcCache.class); + when(cache.query()).thenReturn(query); + microbot.when(Microbot::getRs2NpcCache).thenReturn(cache); + return query; + } + + private static Rs2NpcModel first(List npcs, Predicate filter) { + return npcs.stream().filter(filter).findFirst().orElse(null); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/tutorialisland/RatPenTest.java b/src/test/java/net/runelite/client/plugins/microbot/tutorialisland/RatPenTest.java new file mode 100644 index 0000000000..b5e7b42909 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/tutorialisland/RatPenTest.java @@ -0,0 +1,40 @@ +package net.runelite.client.plugins.microbot.tutorialisland; + +import net.runelite.api.coords.WorldPoint; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +public final class RatPenTest { + @Test + void attacksWhenRatIsReachable() { + assertEquals(RatPen.Step.ATTACK, RatPen.next(new WorldPoint(3111, 9526, 0), true)); + } + + @Test + void walksToGateWhenRatIsUnreachableFromLadder() { + assertEquals(RatPen.Step.WALK_TO_GATE, RatPen.next(new WorldPoint(3111, 9526, 0), false)); + } + + @Test + void walksToGateWhenRatIsUnreachableFromInstructor() { + assertEquals(RatPen.Step.WALK_TO_GATE, RatPen.next(new WorldPoint(3107, 9509, 0), false)); + } + + @Test + void opensGateWhenStandingAtEitherGateTile() { + assertEquals(RatPen.Step.OPEN_GATE, RatPen.next(new WorldPoint(3111, 9518, 0), false)); + assertEquals(RatPen.Step.OPEN_GATE, RatPen.next(new WorldPoint(3111, 9519, 0), false)); + assertEquals(RatPen.Step.OPEN_GATE, RatPen.next(new WorldPoint(3112, 9517, 0), false)); + } + + @Test + void walksToGateWhenPlayerLocationIsUnknown() { + assertEquals(RatPen.Step.WALK_TO_GATE, RatPen.next(null, false)); + } + + @Test + void doesNotOpenGateFromAnotherPlane() { + assertEquals(RatPen.Step.WALK_TO_GATE, RatPen.next(new WorldPoint(3111, 9518, 1), false)); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchScriptTest.java b/src/test/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchScriptTest.java new file mode 100644 index 0000000000..a6357795a9 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchScriptTest.java @@ -0,0 +1,40 @@ +package net.runelite.client.plugins.microbot.virewatch; + +import net.runelite.api.coords.WorldArea; +import net.runelite.api.coords.WorldPoint; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class PVirewatchScriptTest { + + private final WorldPoint start = new WorldPoint(3600, 3360, 0); + private final WorldArea fightArea = new WorldArea(3598, 3358, 5, 5, 0); + + @Test + void fightAreaNotYetSetByFirstGameTickSkipsTheLoop() { + assertFalse(PVirewatchScript.hasFightState(start, null, null)); + assertFalse(PVirewatchScript.hasFightState(start, null, start)); + } + + @Test + void missingLocalPlayerSkipsTheLoop() { + assertFalse(PVirewatchScript.hasFightState(null, fightArea, start)); + } + + @Test + void fightStateIsReadyOncePlayerAreaAndStartAreSet() { + assertTrue(PVirewatchScript.hasFightState(start, fightArea, start)); + } + + @Test + void standingOnAnEqualStartTileIsNotAwayFromStart() { + assertFalse(PVirewatchScript.isAwayFromStart(new WorldPoint(3600, 3360, 0), start)); + } + + @Test + void anotherTileIsAwayFromStart() { + assertTrue(PVirewatchScript.isAwayFromStart(new WorldPoint(3601, 3360, 0), start)); + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/wildernessagility/WildernessAgilityPitRecoveryTest.java b/src/test/java/net/runelite/client/plugins/microbot/wildernessagility/WildernessAgilityPitRecoveryTest.java new file mode 100644 index 0000000000..883957c7c0 --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/wildernessagility/WildernessAgilityPitRecoveryTest.java @@ -0,0 +1,87 @@ +package net.runelite.client.plugins.microbot.wildernessagility; + +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.tileobject.Rs2TileObjectCache; +import net.runelite.client.plugins.microbot.api.tileobject.Rs2TileObjectQueryable; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class WildernessAgilityPitRecoveryTest { + + private static final WorldPoint PIT_TILE = new WorldPoint(3004, 10362, 0); + + @Test + void emptyLadderRequeryFallsBackToFirstLookup() { + Rs2TileObjectModel ladder = ladder(true); + Rs2TileObjectQueryable query = pitQuery(List.of(ladder), Collections.emptyList()); + + runRecovery(query, 1); + + verify(ladder).click("Climb-up"); + } + + @Test + void failedClickRetriesWithoutWaitingForThrottle() { + Rs2TileObjectModel ladder = ladder(false); + Rs2TileObjectQueryable query = pitQuery(List.of(ladder)); + + runRecovery(query, 2); + + verify(ladder, times(2)).click("Climb-up"); + } + + @Test + void successfulClickIsThrottled() { + Rs2TileObjectModel ladder = ladder(true); + Rs2TileObjectQueryable query = pitQuery(List.of(ladder)); + + runRecovery(query, 2); + + verify(ladder, times(1)).click("Climb-up"); + } + + private static Rs2TileObjectModel ladder(boolean clickSucceeds) { + Rs2TileObjectModel ladder = mock(Rs2TileObjectModel.class); + when(ladder.getWorldLocation()).thenReturn(PIT_TILE); + when(ladder.click("Climb-up")).thenReturn(clickSucceeds); + return ladder; + } + + @SafeVarargs + private static Rs2TileObjectQueryable pitQuery(List first, List... rest) { + Rs2TileObjectQueryable query = mock(Rs2TileObjectQueryable.class); + when(query.withId(anyInt())).thenReturn(query); + when(query.nearest()).thenReturn(mock(Rs2TileObjectModel.class)); + when(query.toList()).thenReturn(first, rest); + return query; + } + + private static void runRecovery(Rs2TileObjectQueryable query, int ticks) { + Rs2TileObjectCache cache = mock(Rs2TileObjectCache.class); + when(cache.query()).thenReturn(query); + try (MockedStatic microbot = mockStatic(Microbot.class); + MockedStatic player = mockStatic(Rs2Player.class)) { + microbot.when(Microbot::getRs2TileObjectCache).thenReturn(cache); + player.when(Rs2Player::getWorldLocation).thenReturn(PIT_TILE); + + WildernessAgilityScript script = new WildernessAgilityScript(); + for (int i = 0; i < ticks; i++) { + assertDoesNotThrow(script::recoverFromPit); + } + } + } +} diff --git a/src/test/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingOverlayTest.java b/src/test/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingOverlayTest.java new file mode 100644 index 0000000000..1b55a80e5c --- /dev/null +++ b/src/test/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingOverlayTest.java @@ -0,0 +1,82 @@ +package net.runelite.client.plugins.microbot.woodcutting; + +import net.runelite.api.Client; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.woodcutting.enums.WoodcuttingPrimaryAction; +import net.runelite.client.plugins.microbot.woodcutting.enums.WoodcuttingScriptState; +import net.runelite.client.plugins.microbot.woodcutting.enums.WoodcuttingSecondaryAction; +import net.runelite.client.plugins.microbot.woodcutting.enums.WoodcuttingWalkBack; +import org.junit.jupiter.api.Test; +import org.mockito.MockedStatic; + +import java.awt.Graphics2D; +import java.awt.image.BufferedImage; +import java.lang.reflect.Field; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class AutoWoodcuttingOverlayTest { + + @Test + void renderSkipsAreaWhenReturnPointIsUnavailable() throws Exception { + Client client = mock(Client.class); + when(client.findWorldViewFromWorldPoint(isNull())).thenThrow(new NullPointerException()); + + AutoWoodcuttingConfig config = mock(AutoWoodcuttingConfig.class); + when(config.distanceToStray()).thenReturn(10); + when(config.walkBack()).thenReturn(WoodcuttingWalkBack.INITIAL_LOCATION); + when(config.primaryAction()).thenReturn(WoodcuttingPrimaryAction.BANK); + when(config.secondaryAction()).thenReturn(WoodcuttingSecondaryAction.NONE); + + AutoWoodcuttingPlugin plugin = mock(AutoWoodcuttingPlugin.class); + AutoWoodcuttingScript script = mock(AutoWoodcuttingScript.class); + script.woodcuttingScriptState = WoodcuttingScriptState.WOODCUTTING; + plugin.autoWoodcuttingScript = script; + + Field initialLocation = findField(AutoWoodcuttingScript.class, "initialPlayerLocation"); + Field returnPoint = findField(AutoWoodcuttingScript.class, "returnPoint"); + Object savedInitialLocation = initialLocation.get(null); + Object savedReturnPoint = returnPoint.get(null); + initialLocation.set(null, null); + returnPoint.set(null, null); + + Graphics2D graphics = new BufferedImage(1, 1, BufferedImage.TYPE_INT_ARGB).createGraphics(); + try (MockedStatic microbot = mockStatic(Microbot.class, CALLS_REAL_METHODS); + MockedStatic player = mockStatic(Rs2Player.class)) { + microbot.when(Microbot::isLoggedIn).thenReturn(true); + microbot.when(() -> Microbot.logStackTrace(anyString(), any(Exception.class))).thenAnswer(invocation -> null); + player.when(Rs2Player::getWorldLocation).thenReturn(null); + + new AutoWoodcuttingOverlay(plugin, config, client).render(graphics); + + microbot.verify(() -> Microbot.logStackTrace(anyString(), any(Exception.class)), never()); + verify(client, never()).findWorldViewFromWorldPoint(any(WorldPoint.class)); + } finally { + graphics.dispose(); + initialLocation.set(null, savedInitialLocation); + returnPoint.set(null, savedReturnPoint); + } + } + + private static Field findField(Class type, String name) throws NoSuchFieldException { + for (Class current = type; current != null; current = current.getSuperclass()) { + try { + Field field = current.getDeclaredField(name); + field.setAccessible(true); + return field; + } catch (NoSuchFieldException ignored) { + } + } + throw new NoSuchFieldException(name); + } +}