From b910d53db1f854e221d11f85dbe581e893577589 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 15:24:32 +0000 Subject: [PATCH 01/24] Initial plan From c4754b3580a274a358d3f472a0c689b9d0c83b78 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 15:28:55 +0000 Subject: [PATCH 02/24] fix: update blastoise furnace energy threshold and potion handling Co-authored-by: TannerIsBender <10541121+TannerIsBender@users.noreply.github.com> --- .../BlastoiseFurnaceConfig.java | 17 +++++++- .../BlastoiseFurnacePlugin.java | 2 +- .../BlastoiseFurnaceScript.java | 39 +++++++++++++++---- 3 files changed, 49 insertions(+), 9 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceConfig.java b/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceConfig.java index 2241899088..ce33197df5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceConfig.java @@ -33,6 +33,21 @@ default boolean useStamina() { default Bars getBars() { return Bars.STEEL_BAR; } + + @Range( + min = 0, + max = 10000 + ) + @ConfigItem( + keyName = "runEnergyThreshold", + name = "Run energy threshold", + description = "Drink stamina/energy potions below this run energy value (0-10000)", + position = 2, + section = "bFSettings" + ) + default int runEnergyThreshold() { + return 8100; + } @ConfigSection( name = "Credits", description = "Credits", @@ -50,4 +65,4 @@ default Bars getBars() { default String Credits() { return "Created by: Fishy \n\nUpdated by: Acun, Wassuppzzz"; } -} \ No newline at end of file +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnacePlugin.java b/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnacePlugin.java index 8cc2cfef28..f73c4dc7e5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnacePlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnacePlugin.java @@ -35,7 +35,7 @@ ) @Slf4j public class BlastoiseFurnacePlugin extends Plugin { - final static String version = "1.2.0"; + final static String version = "1.2.1"; @Inject private BlastoiseFurnaceConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java b/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java index 8da2474d9d..c5d215aa65 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java @@ -159,7 +159,7 @@ public boolean run() { Microbot.stopPlugin(plugin); } - if (Microbot.getClient().getEnergy() < 8100) { + if (Microbot.getClient().getEnergy() < config.runEnergyThreshold()) { useStaminaPotions(); } @@ -278,6 +278,7 @@ private void retrieveDoubleCoal() { } if (!Rs2Inventory.interact(coalBag, "Fill")) return; + sleepAfterCoalBagFill(); depositOre(); } @@ -290,8 +291,7 @@ private void retrieveCoalAndPrimary() { } if (!Rs2Inventory.interact(coalBag, "Fill")) return; - - sleep(500, 1200); + sleepAfterCoalBagFill(); Rs2Bank.closeBank(); sleepUntil(() -> !Rs2Bank.isOpen()); depositOre(); @@ -305,8 +305,7 @@ private void retrieveCoalAndGold() { } if (!Rs2Inventory.interact(coalBag, "Fill")) return; - - sleep(500, 1200); + sleepAfterCoalBagFill(); Rs2Bank.closeBank(); sleepUntil(() -> !Rs2Bank.isOpen()); depositOre(); @@ -429,11 +428,27 @@ private void useStaminaPotions() { if (hasEnergyPotion) { String potionName = getLowestDosePotionName(Rs2Potion.getRestoreEnergyPotionsVariants()); if (potionName != null) { - withdrawAndDrink(potionName); + int targetEnergy = 9001; + int currentEnergy = Microbot.getClient().getEnergy(); + int perDoseRestore = getEstimatedEnergyRestorePerDose(potionName); + int dosesNeeded = Math.max(1, (int) Math.ceil((targetEnergy - currentEnergy) / (double) perDoseRestore)); + int maxDoses = getAvailableDosesForVariants(Rs2Potion.getRestoreEnergyPotionsVariants()); + int dosesToDrink = Math.min(dosesNeeded, maxDoses); + for (int i = 0; i < dosesToDrink && Microbot.getClient().getEnergy() < targetEnergy; i++) { + String dosePotionName = getLowestDosePotionName(Rs2Potion.getRestoreEnergyPotionsVariants()); + if (dosePotionName == null) { + break; + } + withdrawAndDrink(dosePotionName); + } } } } + private void sleepAfterCoalBagFill() { + sleep(Rs2Random.between(650, 1450)); + } + private String getLowestDosePotionName(List variants) { return Rs2Bank.getAll(item -> variants.stream().anyMatch(variant -> item.getName().toLowerCase().contains(variant.toLowerCase()))) .min(Comparator.comparingInt(item -> getDoseFromName(item.getName()))) @@ -457,6 +472,17 @@ private String getBaseName(String itemName) { return itemName; } + private int getEstimatedEnergyRestorePerDose(String potionItemName) { + String lowerName = potionItemName.toLowerCase(); + return lowerName.contains("super energy") ? 2000 : 1000; + } + + private int getAvailableDosesForVariants(List variants) { + return Rs2Bank.getAll(item -> variants.stream().anyMatch(variant -> item.getName().toLowerCase().contains(variant.toLowerCase()))) + .mapToInt(item -> item.getQuantity() * getDoseFromName(item.getName())) + .sum(); + } + private void withdrawAndDrink(String potionItemName) { String baseName = getBaseName(potionItemName); boolean withdrewPotion = Rs2Bank.withdrawOne(potionItemName); @@ -701,4 +727,3 @@ public boolean fullCoffer() { return coffer == 1; } } - From 47ecaf5d1ec2a464977e596e2f6151c28362f1b1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 15:33:40 +0000 Subject: [PATCH 03/24] fix: refine blastoise furnace potion target and delay constants Co-authored-by: TannerIsBender <10541121+TannerIsBender@users.noreply.github.com> --- .../blastoisefurnace/BlastoiseFurnaceScript.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java b/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java index c5d215aa65..572ba990d6 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java @@ -42,6 +42,11 @@ public class BlastoiseFurnaceScript extends Script { static final int coalBag = 12019; private static final int MAX_ORE_PER_INTERACTION = 27; private static final int MAX_ORE_PER_HYBRID_INTERACTION = 26; + private static final int TARGET_RUN_ENERGY_LEVEL = 9001; + private static final int ENERGY_RESTORE_PER_DOSE = 1000; + private static final int SUPER_ENERGY_RESTORE_PER_DOSE = 2000; + private static final int COAL_BAG_FILL_DELAY_MIN_MS = 650; + private static final int COAL_BAG_FILL_DELAY_MAX_MS = 1450; public static State state = State.BANKING; static boolean coalBagEmpty; static boolean primaryOreEmpty; @@ -428,8 +433,11 @@ private void useStaminaPotions() { if (hasEnergyPotion) { String potionName = getLowestDosePotionName(Rs2Potion.getRestoreEnergyPotionsVariants()); if (potionName != null) { - int targetEnergy = 9001; + int targetEnergy = TARGET_RUN_ENERGY_LEVEL; int currentEnergy = Microbot.getClient().getEnergy(); + if (currentEnergy >= targetEnergy) { + return; + } int perDoseRestore = getEstimatedEnergyRestorePerDose(potionName); int dosesNeeded = Math.max(1, (int) Math.ceil((targetEnergy - currentEnergy) / (double) perDoseRestore)); int maxDoses = getAvailableDosesForVariants(Rs2Potion.getRestoreEnergyPotionsVariants()); @@ -446,7 +454,7 @@ private void useStaminaPotions() { } private void sleepAfterCoalBagFill() { - sleep(Rs2Random.between(650, 1450)); + sleep(Rs2Random.between(COAL_BAG_FILL_DELAY_MIN_MS, COAL_BAG_FILL_DELAY_MAX_MS)); } private String getLowestDosePotionName(List variants) { @@ -474,7 +482,7 @@ private String getBaseName(String itemName) { private int getEstimatedEnergyRestorePerDose(String potionItemName) { String lowerName = potionItemName.toLowerCase(); - return lowerName.contains("super energy") ? 2000 : 1000; + return lowerName.contains("super energy") ? SUPER_ENERGY_RESTORE_PER_DOSE : ENERGY_RESTORE_PER_DOSE; } private int getAvailableDosesForVariants(List variants) { From 08ee50e9907dd2bd5a469e41ac865594d03f83bb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 23:17:33 +0000 Subject: [PATCH 04/24] fix: drink energy potion doses before re-banking at blast furnace Co-authored-by: TannerIsBender <10541121+TannerIsBender@users.noreply.github.com> --- .../BlastoiseFurnaceScript.java | 61 +++++++++++++------ 1 file changed, 43 insertions(+), 18 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java b/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java index 572ba990d6..2a6558853f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java @@ -442,13 +442,21 @@ private void useStaminaPotions() { int dosesNeeded = Math.max(1, (int) Math.ceil((targetEnergy - currentEnergy) / (double) perDoseRestore)); int maxDoses = getAvailableDosesForVariants(Rs2Potion.getRestoreEnergyPotionsVariants()); int dosesToDrink = Math.min(dosesNeeded, maxDoses); + String currentPotionBaseName = null; for (int i = 0; i < dosesToDrink && Microbot.getClient().getEnergy() < targetEnergy; i++) { - String dosePotionName = getLowestDosePotionName(Rs2Potion.getRestoreEnergyPotionsVariants()); - if (dosePotionName == null) { + if (currentPotionBaseName == null || !Rs2Inventory.hasItem(currentPotionBaseName)) { + String dosePotionName = getLowestDosePotionName(Rs2Potion.getRestoreEnergyPotionsVariants()); + if (dosePotionName == null || !withdrawPotion(dosePotionName)) { + break; + } + Rs2Inventory.waitForInventoryChanges(1800); + currentPotionBaseName = getBaseName(dosePotionName); + } + if (!drinkPotionDose(currentPotionBaseName)) { break; } - withdrawAndDrink(dosePotionName); } + bankPotionRemnants(currentPotionBaseName); } } } @@ -493,25 +501,42 @@ private int getAvailableDosesForVariants(List variants) { private void withdrawAndDrink(String potionItemName) { String baseName = getBaseName(potionItemName); + if (!withdrawPotion(potionItemName)) { + return; + } + Rs2Inventory.waitForInventoryChanges(1800); + if (drinkPotionDose(potionItemName)) { + bankPotionRemnants(baseName); + } + } + + private boolean withdrawPotion(String potionItemName) { boolean withdrewPotion = Rs2Bank.withdrawOne(potionItemName); - if (!withdrewPotion) { - if (Rs2Inventory.isFull()) { - // Wait a full tick for safety - if (!Rs2Inventory.waitForInventoryChanges(600) && Rs2Inventory.isFull()) { - log.debug("Inventory remained full while attempting to withdraw {}", potionItemName); - return; - } - withdrewPotion = Rs2Bank.withdrawOne(potionItemName); - } - if (!withdrewPotion) { - log.debug("Failed to withdraw potion {} from the bank", potionItemName); - return; + if (!withdrewPotion && Rs2Inventory.isFull()) { + // Wait a full tick for safety + if (!Rs2Inventory.waitForInventoryChanges(600) && Rs2Inventory.isFull()) { + log.debug("Inventory remained full while attempting to withdraw {}", potionItemName); + return false; } + withdrewPotion = Rs2Bank.withdrawOne(potionItemName); + } + if (!withdrewPotion) { + log.debug("Failed to withdraw potion {} from the bank", potionItemName); + return false; + } + return true; + } + + private boolean drinkPotionDose(String potionName) { + if (!Rs2Inventory.interact(potionName, "drink")) { + return false; } Rs2Inventory.waitForInventoryChanges(1800); - Rs2Inventory.interact(potionItemName, "drink"); - Rs2Inventory.waitForInventoryChanges(1800); - if (Rs2Inventory.hasItem(baseName)) { + return true; + } + + private void bankPotionRemnants(String baseName) { + if (baseName != null && Rs2Inventory.hasItem(baseName)) { Rs2Bank.depositOne(baseName); Rs2Inventory.waitForInventoryChanges(1800); } From 71d3eefee1b418860ccf8e860d824c8e8d4215f0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 23:18:10 +0000 Subject: [PATCH 05/24] fix: require inventory change when drinking potion doses Co-authored-by: TannerIsBender <10541121+TannerIsBender@users.noreply.github.com> --- .../microbot/blastoisefurnace/BlastoiseFurnaceScript.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java b/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java index 2a6558853f..35708718ce 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java @@ -531,8 +531,7 @@ private boolean drinkPotionDose(String potionName) { if (!Rs2Inventory.interact(potionName, "drink")) { return false; } - Rs2Inventory.waitForInventoryChanges(1800); - return true; + return Rs2Inventory.waitForInventoryChanges(1800); } private void bankPotionRemnants(String baseName) { From c30558955877637f419aa546be68b5d1c28164a5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sun, 22 Feb 2026 23:19:02 +0000 Subject: [PATCH 06/24] fix: track exact potion dose names while drinking energy pots Co-authored-by: TannerIsBender <10541121+TannerIsBender@users.noreply.github.com> --- .../BlastoiseFurnaceScript.java | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java b/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java index 35708718ce..de8179e532 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/blastoisefurnace/BlastoiseFurnaceScript.java @@ -442,21 +442,22 @@ private void useStaminaPotions() { int dosesNeeded = Math.max(1, (int) Math.ceil((targetEnergy - currentEnergy) / (double) perDoseRestore)); int maxDoses = getAvailableDosesForVariants(Rs2Potion.getRestoreEnergyPotionsVariants()); int dosesToDrink = Math.min(dosesNeeded, maxDoses); - String currentPotionBaseName = null; + String currentPotionName = null; for (int i = 0; i < dosesToDrink && Microbot.getClient().getEnergy() < targetEnergy; i++) { - if (currentPotionBaseName == null || !Rs2Inventory.hasItem(currentPotionBaseName)) { + if (currentPotionName == null || !Rs2Inventory.hasItem(currentPotionName)) { String dosePotionName = getLowestDosePotionName(Rs2Potion.getRestoreEnergyPotionsVariants()); if (dosePotionName == null || !withdrawPotion(dosePotionName)) { break; } Rs2Inventory.waitForInventoryChanges(1800); - currentPotionBaseName = getBaseName(dosePotionName); + currentPotionName = dosePotionName; } - if (!drinkPotionDose(currentPotionBaseName)) { + if (!drinkPotionDose(currentPotionName)) { break; } + currentPotionName = getNextPotionDoseName(currentPotionName); } - bankPotionRemnants(currentPotionBaseName); + bankPotionRemnants(currentPotionName == null ? null : getBaseName(currentPotionName)); } } } @@ -499,6 +500,19 @@ private int getAvailableDosesForVariants(List variants) { .sum(); } + private String getNextPotionDoseName(String potionItemName) { + Matcher matcher = ITEM_NAME_SUFFIX_PATTERN.matcher(potionItemName); + if (matcher.find()) { + String baseName = matcher.group(1).trim(); + int currentDose = Integer.parseInt(matcher.group(2)); + if (currentDose > 1) { + return baseName + "(" + (currentDose - 1) + ")"; + } + return baseName; + } + return potionItemName; + } + private void withdrawAndDrink(String potionItemName) { String baseName = getBaseName(potionItemName); if (!withdrawPotion(potionItemName)) { From 97f6abf34dd9926a93939f2c53751d8bacaa27c3 Mon Sep 17 00:00:00 2001 From: Bender Date: Tue, 3 Mar 2026 17:25:29 -0800 Subject: [PATCH 07/24] Motherload mine fixes --- .../motherloadmine/MotherloadMineScript.java | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) 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 ee1341b871..954ab605e8 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 @@ -324,18 +324,19 @@ private void depositHopper() return; } } - - WorldPoint hopperDeposit = (isUpperFloor() && config.upstairsHopperUnlocked()) ? HOPPER_DEPOSIT_UP : HOPPER_DEPOSIT_DOWN; - Rs2TileObjectModel hopper = rs2TileObjectCache.query().where(x -> x.getWorldLocation().equals(hopperDeposit)).withId(ObjectID.MOTHERLODE_HOPPER).first(); - if(isUpperFloor() && !config.upstairsHopperUnlocked()) { ensureLowerFloor(); } - + final int paydirtToDeposit = payDirtCount(); + sleep(800, 1200); + + WorldPoint hopperDeposit = (isUpperFloor() && config.upstairsHopperUnlocked()) ? HOPPER_DEPOSIT_UP : HOPPER_DEPOSIT_DOWN; + Rs2TileObjectModel hopper = rs2TileObjectCache.query().where(x -> x.getWorldLocation().equals(hopperDeposit)).withId(ObjectID.MOTHERLODE_HOPPER).first(); + - if (hopper != null && rs2TileObjectCache.query().interact(hopper.getId())) { + if (hopper != null && hopper.click("Deposit")) { sleepUntil(() -> payDirtCount() != paydirtToDeposit && !Rs2Player.isAnimating(), 10_000); shouldRepairWaterwheel = true; @@ -513,6 +514,11 @@ private boolean walkToMiningSpot() return false; // Wait until we've gone up } + if (miningSpot.isUpstairs() && isUpperFloor()) + { + return true; // if on upper floor lets mine... instead of walking + } + if (miningSpot.isDownstairs() && isUpperFloor()) { goDown(); return false; // Wait until we've gone down From 4e28d254a83b9feabe7dcfa1a8595cc8f6c26336 Mon Sep 17 00:00:00 2001 From: Bender Date: Tue, 3 Mar 2026 20:44:10 -0800 Subject: [PATCH 08/24] Optimize rogues den cooking --- .../plugins/microbot/cooking/scripts/AutoCookingScript.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/cooking/scripts/AutoCookingScript.java b/src/main/java/net/runelite/client/plugins/microbot/cooking/scripts/AutoCookingScript.java index f3d59f6f52..5f563b3c27 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cooking/scripts/AutoCookingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cooking/scripts/AutoCookingScript.java @@ -102,7 +102,11 @@ public boolean run(AutoCookingConfig config) { Rs2Camera.turnTo(cookingObject.getLocalLocation()); return; } - Rs2Inventory.useItemOnObject(cookingItem.getRawItemID(), cookingObject.getId()); + if (location == CookingLocation.ROUGES_DEN) { + Rs2GameObject.interact(cookingObject, "Cook"); + } else { + Rs2Inventory.useItemOnObject(cookingItem.getRawItemID(), cookingObject.getId()); + } boolean productionWidgetOpen = Rs2Widget.isProductionWidgetOpen(); if (!productionWidgetOpen) { From d2adbb93e5660ed5ee4f5738a0c8e659bb277834 Mon Sep 17 00:00:00 2001 From: Bender Date: Fri, 6 Mar 2026 16:53:42 -0800 Subject: [PATCH 09/24] WC --- .../plugins/microbot/woodcutting/AutoWoodcuttingScript.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 628eff7d0b..d34c974eb0 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 @@ -29,7 +29,6 @@ import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import net.runelite.client.plugins.microbot.woodcutting.enums.*; -import net.runelite.client.plugins.microbot.woodcutting.enums.WoodcuttingTreeLocations; import javax.inject.Inject; import java.awt.event.KeyEvent; @@ -405,7 +404,7 @@ private void burnLog(AutoWoodcuttingConfig config) { // prioritize campfire if available Rs2TileObjectModel fire = rs2TileObjectCache.query().where(x -> x.getId() == 49927).nearest(6); // Forester's campfire if (fire == null) { - rs2TileObjectCache.query().where(x -> x.getId() == 26185).nearest(6); + fire = rs2TileObjectCache.query().where(x -> x.getId() == 26185).nearest(6); } if (config.primaryAction() == WoodcuttingPrimaryAction.BURN_CAMPFIRE) { if (fire != null) { @@ -424,10 +423,11 @@ private void burnLog(AutoWoodcuttingConfig config) { Rs2Inventory.useLast(treeType.getLogID()); }, 300, 100); } else if (!isFiremake() && useCampfire) { - Rs2Inventory.useItemOnObject(treeType.getLogID(), fire.getId()); + fire.click("Tend-to"); sleepUntil(() -> (!Rs2Player.isMoving() && Rs2Widget.findWidget("How many would you like to burn?", null, false) != null), 5000); Rs2Random.waitEx(400, 200); Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); + sleep(1000, 1200); sleepUntil(() -> !Rs2Inventory.contains(treeType.getLog()) || !Rs2Player.isAnimating(), 40000); return; From a55926df411cfac31d56c6c7908d2a5c71b2624a Mon Sep 17 00:00:00 2001 From: Bender Date: Sat, 7 Mar 2026 14:46:33 -0800 Subject: [PATCH 10/24] Start to Bankshopper changes --- .../banksshopper/BanksShopperConfig.java | 24 ++++++ .../banksshopper/BanksShopperPlugin.java | 16 +++- .../banksshopper/BanksShopperScript.java | 78 +++++++++++++++++-- 3 files changed, 110 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperConfig.java b/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperConfig.java index d5093f98bc..b84929c10f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperConfig.java @@ -26,6 +26,8 @@ public interface BanksShopperConfig extends Config { String useBank = "useBank"; String logout = "logout"; String useNextWorld = "useNextWorld"; + String blastFurnaceOptimization = "blastFurnaceOptimization"; + String useKeyboardWorldHop = "useKeyboardWorldHop"; @ConfigSection( name = "Action Settings", @@ -106,6 +108,28 @@ default boolean useNextWorld() { return false; } + @ConfigItem( + position = 5, + keyName = blastFurnaceOptimization, + name = "Blast Furnace optimization", + description = "Optimize route at Blast Furnace by banking/interacting directly", + section = actionSection + ) + default boolean blastFurnaceOptimization() { + return false; + } + + @ConfigItem( + position = 6, + keyName = useKeyboardWorldHop, + name = "Use Ctrl+Shift+Right to hop", + description = "Use keyboard shortcut to hop to the next world", + section = actionSection + ) + default boolean useKeyboardWorldHop() { + return false; + } + @ConfigItem( keyName = itemNames, name = "Item Name(s)/ID(s)", diff --git a/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperPlugin.java index 9dee6d3554..c3138a578e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperPlugin.java @@ -36,7 +36,7 @@ ) @Slf4j public class BanksShopperPlugin extends Plugin { - public final static String version = "1.4.0"; + public final static String version = "1.4.2"; @Inject private BanksShopperConfig config; @@ -70,6 +70,10 @@ BanksShopperConfig provideConfig(ConfigManager configManager) { private boolean useLogout; @Getter private boolean useExactNaming; + @Getter + private boolean blastFurnaceOptimization; + @Getter + private boolean useKeyboardWorldHop; @Override protected void startUp() throws AWTException { @@ -81,6 +85,8 @@ protected void startUp() throws AWTException { useLogout = config.logout(); useExactNaming = config.useExactNaming(); useNextWorld = config.useNextWorld(); + blastFurnaceOptimization = config.blastFurnaceOptimization(); + useKeyboardWorldHop = config.useKeyboardWorldHop(); updateItemList(config.itemNames()); if (overlayManager != null) { @@ -140,6 +146,14 @@ public void onConfigChanged(ConfigChanged event) { useNextWorld = config.useNextWorld(); } + if (event.getKey().equals(BanksShopperConfig.blastFurnaceOptimization)) { + blastFurnaceOptimization = config.blastFurnaceOptimization(); + } + + if (event.getKey().equals(BanksShopperConfig.useKeyboardWorldHop)) { + useKeyboardWorldHop = config.useKeyboardWorldHop(); + } + if (event.getKey().equals(BanksShopperConfig.itemNames)) { updateItemList(config.itemNames()); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperScript.java b/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperScript.java index e5600c8e46..9b49eb5236 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperScript.java @@ -1,16 +1,20 @@ package net.runelite.client.plugins.microbot.banksshopper; +import net.runelite.api.GameState; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; 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.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.security.Login; import net.runelite.client.plugins.microbot.util.shop.Rs2Shop; +import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; +import java.awt.event.KeyEvent; import java.util.concurrent.TimeUnit; enum ShopperState { @@ -84,7 +88,9 @@ public boolean run(BanksShopperConfig config) { } if (Rs2Inventory.isFull()){ System.out.println("Inventory is full, stopping buy action to bank."); - Rs2Shop.closeShop(); + if (!plugin.isBlastFurnaceOptimization()) { + Rs2Shop.closeShop(); + } state = ShopperState.BANKING; return; } @@ -128,8 +134,13 @@ public boolean run(BanksShopperConfig config) { } break; case BANKING: - if (!Rs2Bank.bankItemsAndWalkBackToOriginalPosition(plugin.getItemNames(), initialPlayerLocation)) + if (plugin.isBlastFurnaceOptimization()) { + if (!bankItemsWithoutWalkBack()) { + return; + } + } else if (!Rs2Bank.bankItemsAndWalkBackToOriginalPosition(plugin.getItemNames(), initialPlayerLocation)) { return; + } state = ShopperState.SHOPPING; break; case HOPPING: @@ -165,12 +176,28 @@ public void shutdown() { * Hops to a new world */ private void hopWorld() { - System.out.println("Hopping worlds..."); - Rs2Random.waitEx(3200, 800); // this sleep is required to avoid the message: please finish what you're doing before using the world switcher. + Rs2Shop.closeShop(); + sleep(2400, 4800); + int world = Login.getRandomWorld(true, null); + boolean isHopped = Microbot.hopToWorld(world); + if (!isHopped) return; + boolean result = sleepUntil(() -> Rs2Widget.findWidget("Switch World") != null); + if (result) { + Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); + sleepUntil(() -> Microbot.getClient().getGameState() == GameState.HOPPING); + sleepUntil(() -> Microbot.getClient().getGameState() == GameState.LOGGED_IN); + } + } - int world = plugin.isUseNextWorld() ? Login.getNextWorld(Rs2Player.isMember()) : Login.getRandomWorld(Rs2Player.isMember()); - sleepUntil(() -> Microbot.hopToWorld(world), 15000); - System.out.println("Successfully hopped to world: " + world); + private void hopWorldWithKeyboardShortcut() { + Rs2Keyboard.keyHold(KeyEvent.VK_CONTROL); + Rs2Keyboard.keyHold(KeyEvent.VK_SHIFT); + Rs2Keyboard.keyHold(KeyEvent.VK_RIGHT); + sleepGaussian(80, 20); + Rs2Keyboard.keyRelease(KeyEvent.VK_RIGHT); + Rs2Keyboard.keyRelease(KeyEvent.VK_SHIFT); + Rs2Keyboard.keyRelease(KeyEvent.VK_CONTROL); + System.out.println("Sent world hop shortcut: Ctrl+Shift+Right"); } @@ -250,4 +277,41 @@ private boolean processSellAction(int itemID, String quantity) { System.out.println("Item ID" + itemID + " not found in inventory."); return false; } + + private boolean bankItemsWithoutWalkBack() { + boolean openedBank = Rs2Bank.isOpen() || Rs2Bank.openBank(); + + if (!openedBank) { + openedBank = Rs2Bank.walkToBankAndUseBank(); + } + + if (!openedBank || !Rs2Bank.isOpen()) { + return false; + } + + for (String itemName : plugin.getItemNames()) { + if (itemName == null || itemName.length() <= 1) { + continue; + } + + if (itemName.matches("\\d+")) { + Rs2Bank.depositAll(Integer.parseInt(itemName)); + } else { + Rs2Bank.depositAll(itemName); + } + sleepGaussian(120, 40); + } + + if (Rs2Bank.isOpen()) { + if (plugin.isBlastFurnaceOptimization()) { + sleepUntil(() -> Rs2Shop.openShop(plugin.getNpcName(), plugin.isUseExactNaming()), 5000); + return true; + } + + Rs2Bank.closeBank(); + sleepUntil(() -> !Rs2Bank.isOpen(), 2000); + } + + return true; + } } From c84acbc17344b39ddeb5fbfe21bb55acbf6ba03a Mon Sep 17 00:00:00 2001 From: Bender Date: Thu, 12 Mar 2026 13:11:29 -0700 Subject: [PATCH 11/24] GOTR --- .../plugins/microbot/gotr/GotrConfig.java | 23 ++++++++++--- .../plugins/microbot/gotr/GotrScript.java | 32 +++++++++++++------ 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/gotr/GotrConfig.java b/src/main/java/net/runelite/client/plugins/microbot/gotr/GotrConfig.java index 97dbb93599..b4c361220a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/gotr/GotrConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/gotr/GotrConfig.java @@ -4,6 +4,7 @@ import net.runelite.client.config.ConfigGroup; import net.runelite.client.config.ConfigInformation; import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.Range; import net.runelite.client.plugins.microbot.gotr.data.Mode; @ConfigGroup("gotr") @@ -40,11 +41,25 @@ default int maxAmountEssence() { return 20; } + @Range( + min = 0, + max = 5 + ) + @ConfigItem( + keyName = "emptySlotsToKeep", + name = "Empty slots to keep", + description = "How many inventory spaces to keep empty before considering inventory effectively full", + position = 3 + ) + default int emptySlotsToKeep() { + return 0; + } + @ConfigItem( keyName = "shouldDepositRunes", name = "Deposit runes?", description = "Should you deposit runes into the deposit pool?", - position = 3 + position = 4 ) default boolean shouldDepositRunes() { return true; @@ -54,7 +69,7 @@ default boolean shouldDepositRunes() { keyName = "useLunarSpellbook", name = "Use lunar spellbook", description = "Switch to lunar spellbook for NPC Contact spell to repair pouches. Disable if using Cordelia repair with pearls.", - position = 4 + position = 5 ) default boolean useLunarSpellbook() { return true; @@ -64,7 +79,7 @@ default boolean useLunarSpellbook() { keyName = "useInventorySetup", name = "Use inventory setup", description = "Use a specific inventory setup instead of progressive equipment management", - position = 5 + position = 6 ) default boolean useInventorySetup() { return false; @@ -74,7 +89,7 @@ default boolean useInventorySetup() { keyName = "inventorySetupName", name = "Inventory setup name", description = "Name of the inventory setup to use (only if 'Use inventory setup' is enabled)", - position = 6 + position = 7 ) default String inventorySetupName() { return ""; 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 9a97d9c1ab..e62e45932f 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 @@ -230,7 +230,7 @@ public boolean run(GotrConfig config) { private boolean waitingForGameToStart(int timeToStart) { if (isInHugeMine()) return false; - if (getStartTimer() > Rs2Random.randomGaussian(35, Rs2Random.between(1, 5)) || getStartTimer() == -1 || timeToStart > 10) { + if (getStartTimer() > Rs2Random.randomGaussian(25, Rs2Random.between(1, 3)) || getStartTimer() == -1 || timeToStart > 13) { // Only take cells if we don't already have them if (!Rs2Inventory.hasItem("Uncharged cell")) { @@ -359,6 +359,9 @@ private boolean enterAltar() { } private boolean craftGuardianEssences() { + if (!Rs2Inventory.hasItem(GUARDIAN_FRAGMENTS)) { + return false; + } if (Rs2GameObject.interact(ObjectID.WORKBENCH_43754)) { state = GotrState.CRAFT_GUARDIAN_ESSENCE; sleep(Rs2Random.randomGaussian(Rs2Random.between(600, 900), Rs2Random.between(150, 300))); @@ -388,8 +391,14 @@ private boolean fillPouches() { return false; } + private boolean hasConfiguredEmptySlots() { + return Rs2Inventory.emptySlotCount() > config.emptySlotsToKeep(); + } + private boolean isOutOfFragments() { - if ((!Rs2Inventory.hasItem(GUARDIAN_FRAGMENTS) && !Rs2Inventory.isFull()) || (getTimeSincePortal() > 85 && !Rs2Inventory.hasItem(GUARDIAN_ESSENCE))) { + boolean noFragments = !Rs2Inventory.hasItem(GUARDIAN_FRAGMENTS); + boolean noEssence = !Rs2Inventory.hasItem(GUARDIAN_ESSENCE) && !Rs2Inventory.anyPouchFull(); + if ((noFragments && (hasConfiguredEmptySlots() || noEssence)) || (getTimeSincePortal() > 85 && !Rs2Inventory.hasItem(GUARDIAN_ESSENCE))) { shouldMineGuardianRemains = true; if(!Rs2Inventory.hasItem(GUARDIAN_FRAGMENTS)) log("Memorize that we no longer have guardian fragments..."); @@ -405,7 +414,7 @@ private boolean craftRunes() { TileObject rcAltar = findRcAltar(); if (rcAltar != null) { if (Rs2Player.isMoving()) return true; - if (Rs2Inventory.anyPouchFull() && !Rs2Inventory.isFull()) { + if (Rs2Inventory.anyPouchFull() && hasConfiguredEmptySlots()) { //test when back from gym Rs2Inventory.emptyPouches(); Rs2Inventory.waitForInventoryChanges(5000); sleep(Rs2Random.randomGaussian(350, 150)); @@ -724,17 +733,20 @@ public static List getAvailableAltars() { .collect(Collectors.toList()); } - if ((config.Mode() == Mode.BALANCED && elementalPoints < catalyticPoints) || config.Mode() == Mode.ELEMENTAL) { - Microbot.log(elementalPoints < catalyticPoints + if (config.Mode() == Mode.ELEMENTAL || config.Mode() == Mode.CATALYTIC || config.Mode() == Mode.BALANCED) { + boolean preferElemental = config.Mode() == Mode.ELEMENTAL + || (config.Mode() == Mode.BALANCED && elementalPoints <= catalyticPoints); + + Microbot.log(preferElemental ? "We have " + elementalPoints + " elemental points, looking for elemental altar..." - : "We have " + catalyticPoints +" catalytic points, looking for catalytic altar..."); + : "We have " + catalyticPoints + " catalytic points, looking for catalytic altar..."); - Microbot.log("Sorting for BALANCED/ELEMENTAL mode (" + - (elementalPoints < catalyticPoints ? "Elemental priority" : "Catalytic priority") + ")"); + Microbot.log("Sorting for " + config.Mode() + " mode (" + + (preferElemental ? "Elemental priority" : "Catalytic priority") + ")"); return availableAltars.stream() .sorted( - (elementalPoints < catalyticPoints) + preferElemental ? Comparator.comparingInt(TileObject::getId) : Comparator.comparingInt(TileObject::getId).reversed() ) @@ -796,4 +808,4 @@ public static boolean leaveMinigame() { GotrScript.isInMiniGame = !isOutsideBarrier() && isInMainRegion(); return !GotrScript.isInMiniGame;// Successfully left the minigame } -} +} \ No newline at end of file From a5a3320cba8312d00c072031076da6350d6fa8f6 Mon Sep 17 00:00:00 2001 From: Bender Date: Thu, 12 Mar 2026 14:01:59 -0700 Subject: [PATCH 12/24] Bank Shopper --- .../banksshopper/BanksShopperScript.java | 116 +++++++++++------- .../java/net/runelite/client/Microbot.java | 2 + 2 files changed, 77 insertions(+), 41 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperScript.java b/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperScript.java index 9b49eb5236..be88beac3d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperScript.java @@ -41,7 +41,8 @@ public boolean run(BanksShopperConfig config) { mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { try { - if (!super.run() || !Microbot.isLoggedIn() || Rs2AntibanSettings.actionCooldownActive) return; + if (!super.run() || !Microbot.isLoggedIn() || Rs2AntibanSettings.actionCooldownActive) + return; if (initialPlayerLocation == null) { initialPlayerLocation = Rs2Player.getWorldLocation(); @@ -50,7 +51,8 @@ public boolean run(BanksShopperConfig config) { switch (state) { case SHOPPING: boolean missingAllRequiredItems = plugin.getItemNames().stream().noneMatch((itemName) -> { - if (itemName == null || itemName.isEmpty()) return false; + if (itemName == null || itemName.isEmpty()) + return false; if (itemName.matches("\\d+")) { return Rs2Inventory.hasItem(Integer.parseInt(itemName)); } else { @@ -71,22 +73,29 @@ public boolean run(BanksShopperConfig config) { boolean outOfStock = false; if (Rs2Shop.isOpen()) { for (String itemName : plugin.getItemNames()) { - if (!isRunning() || Microbot.pauseAllScripts.get()) break; - if (itemName.length() <= 1) continue; + if (!isRunning() || Microbot.pauseAllScripts.get()) + break; + if (itemName.length() <= 1) + continue; switch (plugin.getSelectedAction()) { case BUY: // Check if name is purely numeric or alphanumeric if (itemName.matches("\\d+")) { - outOfStock = !Rs2Shop.hasMinimumStock(Integer.parseInt(itemName), plugin.getMinStock()); - if (outOfStock) continue; - successfullAction = processBuyAction(Integer.parseInt(itemName), plugin.getSelectedQuantity().toString()); + outOfStock = !Rs2Shop.hasMinimumStock(Integer.parseInt(itemName), + plugin.getMinStock()); + if (outOfStock) + continue; + successfullAction = processBuyAction(Integer.parseInt(itemName), + plugin.getSelectedQuantity().toString()); } else { outOfStock = !Rs2Shop.hasMinimumStock(itemName, plugin.getMinStock()); - if (outOfStock) continue; - successfullAction = processBuyAction(itemName, plugin.getSelectedQuantity().toString()); + if (outOfStock) + continue; + successfullAction = processBuyAction(itemName, + plugin.getSelectedQuantity().toString()); } - if (Rs2Inventory.isFull()){ + if (Rs2Inventory.isFull()) { System.out.println("Inventory is full, stopping buy action to bank."); if (!plugin.isBlastFurnaceOptimization()) { Rs2Shop.closeShop(); @@ -96,22 +105,28 @@ public boolean run(BanksShopperConfig config) { } break; case SELL: - if (Rs2Shop.isFull()) continue; + if (Rs2Shop.isFull()) + continue; // Check if name is purely numeric or alphanumeric if (itemName.matches("\\d+")) { - while(isRunning() && processSellAction(Integer.parseInt(itemName), plugin.getSelectedQuantity().toString())){ + while (isRunning() && processSellAction(Integer.parseInt(itemName), + plugin.getSelectedQuantity().toString())) { sleepGaussian(200, 40); - if (Rs2Shop.hasMinimumStock(Integer.parseInt(itemName), plugin.getMinStock())){ - System.out.println("Stop selling over the minimum stock for item: " + itemName); + if (Rs2Shop.hasMinimumStock(Integer.parseInt(itemName), + plugin.getMinStock())) { + System.out.println("Stop selling over the minimum stock for item: " + + itemName); successfullAction = true; break; } } } else { - while(isRunning() && processSellAction(itemName, plugin.getSelectedQuantity().toString())){ + while (isRunning() && processSellAction(itemName, + plugin.getSelectedQuantity().toString())) { sleepGaussian(200, 40); - if (Rs2Shop.hasMinimumStock(itemName, plugin.getMinStock())){ - System.out.println("Stop selling over the minimum stock for item: " + itemName); + if (Rs2Shop.hasMinimumStock(itemName, plugin.getMinStock())) { + System.out.println("Stop selling over the minimum stock for item: " + + itemName); successfullAction = true; break; } @@ -126,7 +141,7 @@ public boolean run(BanksShopperConfig config) { if (successfullAction) { state = ShopperState.HOPPING; return; - }else if (outOfStock){ + } else if (outOfStock) { System.out.println("Out of stock for all items, hopping worlds..."); state = ShopperState.HOPPING; return; @@ -138,7 +153,8 @@ public boolean run(BanksShopperConfig config) { if (!bankItemsWithoutWalkBack()) { return; } - } else if (!Rs2Bank.bankItemsAndWalkBackToOriginalPosition(plugin.getItemNames(), initialPlayerLocation)) { + } else if (!Rs2Bank.bankItemsAndWalkBackToOriginalPosition(plugin.getItemNames(), + initialPlayerLocation)) { return; } state = ShopperState.SHOPPING; @@ -176,33 +192,45 @@ public void shutdown() { * Hops to a new world */ private void hopWorld() { + System.out.println("[BanksShopper] Starting world hop sequence..."); Rs2Shop.closeShop(); - sleep(2400, 4800); - int world = Login.getRandomWorld(true, null); - boolean isHopped = Microbot.hopToWorld(world); - if (!isHopped) return; - boolean result = sleepUntil(() -> Rs2Widget.findWidget("Switch World") != null); - if (result) { - Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); - sleepUntil(() -> Microbot.getClient().getGameState() == GameState.HOPPING); - sleepUntil(() -> Microbot.getClient().getGameState() == GameState.LOGGED_IN); - } + + int currentWorld = Microbot.getClient().getWorld(); + int targetWorld = currentWorld + 1; + System.out.println("[BanksShopper] Current world: " + currentWorld + ", target world: " + targetWorld); + Microbot.hopToWorld(targetWorld); + System.out.println("[BanksShopper] Hop command sent."); + + // Wait for hop to complete + boolean enteredHoppingState = sleepUntil(() -> Microbot.getClient().getGameState() == GameState.HOPPING, 5000); + System.out.println(enteredHoppingState + ? "[BanksShopper] Client entered HOPPING state." + : "[BanksShopper] Client did not enter HOPPING state within timeout."); + + boolean returnedToLoggedIn = sleepUntil(() -> Microbot.getClient().getGameState() == GameState.LOGGED_IN, 10000); + System.out.println(returnedToLoggedIn + ? "[BanksShopper] Hop completed and client returned to LOGGED_IN state." + : "[BanksShopper] Client did not return to LOGGED_IN state within timeout."); + + // Brief pause after hopping + sleep(1500, 2500); + System.out.println("[BanksShopper] World hop sequence finished."); } private void hopWorldWithKeyboardShortcut() { Rs2Keyboard.keyHold(KeyEvent.VK_CONTROL); Rs2Keyboard.keyHold(KeyEvent.VK_SHIFT); Rs2Keyboard.keyHold(KeyEvent.VK_RIGHT); - sleepGaussian(80, 20); - Rs2Keyboard.keyRelease(KeyEvent.VK_RIGHT); + sleepGaussian(300, 150); Rs2Keyboard.keyRelease(KeyEvent.VK_SHIFT); Rs2Keyboard.keyRelease(KeyEvent.VK_CONTROL); + Rs2Keyboard.keyRelease(KeyEvent.VK_RIGHT); System.out.println("Sent world hop shortcut: Ctrl+Shift+Right"); } - /** * Processes the buy action for the specified item. + * * @param itemName The name of the item to buy. * @param quantity The quantity of the item to buy. * @return true if bought successfully, false otherwise. @@ -215,18 +243,19 @@ private boolean processBuyAction(String itemName, String quantity) { boolean boughtItem = Rs2Shop.buyItem(itemName, quantity); - if (boughtItem){ + if (boughtItem) { Rs2Inventory.waitForInventoryChanges(3000); } - System.out.println(boughtItem ? "Successfully bought " + quantity + " item: " + itemName : "Failed to buy " + quantity + " item ID: " + itemName); + System.out.println(boughtItem ? "Successfully bought " + quantity + " item: " + itemName + : "Failed to buy " + quantity + " item ID: " + itemName); return boughtItem; } - /** * Processes the buy action for the specified item. - * @param itemID The ID of the item to buy. + * + * @param itemID The ID of the item to buy. * @param quantity The quantity of the item to buy. * @return true if bought successfully, false otherwise. */ @@ -238,16 +267,18 @@ private boolean processBuyAction(int itemID, String quantity) { boolean boughtItem = Rs2Shop.buyItem(itemID, quantity); - if (boughtItem){ + if (boughtItem) { Rs2Inventory.waitForInventoryChanges(3000); } - System.out.println(boughtItem ? "Successfully bought " + quantity + " item ID: " + itemID : "Failed to buy " + quantity + " item ID: " + itemID); + System.out.println(boughtItem ? "Successfully bought " + quantity + " item ID: " + itemID + : "Failed to buy " + quantity + " item ID: " + itemID); return boughtItem; } /** * Processes the sell action for the specified item. + * * @param itemName The name of the item to sell. * @param quantity The quantity of the item to sell. * @return true if sold successfully, false otherwise. @@ -255,7 +286,8 @@ private boolean processBuyAction(int itemID, String quantity) { private boolean processSellAction(String itemName, String quantity) { if (Rs2Inventory.hasItem(itemName)) { boolean soldItem = Rs2Inventory.sellItem(itemName, quantity); - System.out.println(soldItem ? "Successfully sold " + quantity + " " + itemName : "Failed to sell " + quantity + " " + itemName); + System.out.println(soldItem ? "Successfully sold " + quantity + " " + itemName + : "Failed to sell " + quantity + " " + itemName); return soldItem; } System.out.println("Item " + itemName + " not found in inventory."); @@ -264,14 +296,16 @@ private boolean processSellAction(String itemName, String quantity) { /** * Processes the sell action for the specified item. - * @param itemID The name of the item to sell. + * + * @param itemID The name of the item to sell. * @param quantity The quantity of the item to sell. * @return true if sold successfully, false otherwise. */ private boolean processSellAction(int itemID, String quantity) { if (Rs2Inventory.hasItem(itemID)) { boolean soldItem = Rs2Inventory.sellItem(itemID, quantity); - System.out.println(soldItem ? "Successfully sold " + quantity + ", item ID:" + itemID : "Failed to sell " + quantity + ", item ID: " + itemID); + System.out.println(soldItem ? "Successfully sold " + quantity + ", item ID:" + itemID + : "Failed to sell " + quantity + ", item ID: " + itemID); return soldItem; } System.out.println("Item ID" + itemID + " not found in inventory."); diff --git a/src/test/java/net/runelite/client/Microbot.java b/src/test/java/net/runelite/client/Microbot.java index 54d5a6b9c6..562dba03f4 100644 --- a/src/test/java/net/runelite/client/Microbot.java +++ b/src/test/java/net/runelite/client/Microbot.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.stream.Collectors; +import net.runelite.client.plugins.microbot.banksshopper.BanksShopperPlugin; import net.runelite.client.plugins.microbot.slayer.SlayerPlugin; public class Microbot @@ -11,6 +12,7 @@ public class Microbot private static final Class[] debugPlugins = { SlayerPlugin.class, + BanksShopperPlugin.class, }; public static void main(String[] args) throws Exception From 001d7a4034b2a6a92335534a3a44d5c834dbc11f Mon Sep 17 00:00:00 2001 From: Bender Date: Fri, 13 Mar 2026 19:49:53 -0700 Subject: [PATCH 13/24] Updates --- .../plugins/microbot/thieving/State.java | 1 + .../microbot/thieving/ThievingConfig.java | 22 ++++++++++++ .../microbot/thieving/ThievingData.java | 11 ++++++ .../microbot/thieving/ThievingPlugin.java | 2 +- .../microbot/thieving/ThievingScript.java | 36 ++++++++++++++++++- .../java/net/runelite/client/Microbot.java | 4 +-- 6 files changed, 72 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/thieving/State.java b/src/main/java/net/runelite/client/plugins/microbot/thieving/State.java index 636e46cc85..4b57d61356 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thieving/State.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thieving/State.java @@ -11,6 +11,7 @@ public enum State { LOOT, EAT(false), DROP(false), + SEED_VAULT(false), SHADOW_VEIL(false), PICKPOCKET, HOP, diff --git a/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingConfig.java b/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingConfig.java index a1e4eaee6c..9c3e76bbfb 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingConfig.java @@ -50,6 +50,17 @@ default boolean ardougneAreaCheck() { return false; } + @ConfigItem( + keyName = "farmingGuildMasterFarmer", + name = "Farming Guild Master Farmer", + description = "When thieving Master Farmers, target only those inside the Farming Guild. If your Farming level is below 65, focuses on the master farmer in the south (around y=3728).", + position = 2, + section = generalSection + ) + default boolean farmingGuildMasterFarmer() { + return false; + } + @ConfigSection( name = "Buffs", description = "Buffs and spell-casting options.", @@ -213,4 +224,15 @@ default int dodgyNecklaceAmount() { default String DoNotDropItemList() { return ""; } + + @ConfigItem( + keyName = "useSeedVault", + name = "Use Seed Vault", + description = "Walk to the Farming Guild seed vault to store seeds instead of dropping items when inventory is full.", + position = 4, + section = coinPouchSection + ) + default boolean useSeedVault() { + return false; + } } \ No newline at end of file diff --git a/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingData.java b/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingData.java index 9861537b5c..aaf99b1633 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingData.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingData.java @@ -11,8 +11,19 @@ public final class ThievingData { public static final WorldPoint NULL_WORLD_POINT = new WorldPoint(-1,-1,-1); public static final WorldPoint OUTSIDE_HALLOWED_BANK = new WorldPoint(3654,3384,0); + /** Farming Guild seed vault object ID */ + public static final int SEED_VAULT_OBJECT_ID = 26206; + /** Approximate tile in front of the Farming Guild seed vault */ + public static final WorldPoint SEED_VAULT_LOCATION = new WorldPoint(1243, 3740, 0); public static final WorldArea ARDOUGNE_AREA = new WorldArea(2649, 3280, 7, 8, 0); + /** Farming Guild area (all tiers) */ + public static final WorldArea FARMING_GUILD_AREA = new WorldArea(1215, 3715, 55, 60, 0); + /** NPC IDs for the Master Farmers in the southern (low-level accessible) section of the Farming Guild */ + public static final Set FARMING_GUILD_SOUTH_NPC_IDS = Set.of(11940, 5731); + /** Farming level required to access the full (upper) section of the Farming Guild */ + public static final int FARMING_GUILD_MID_FARMING_LEVEL = 85; + public static final Set VYRE_SET = Set.of( "vyre noble shoes", "vyre noble legs", 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 95dca2bc69..b641975055 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingPlugin.java @@ -34,7 +34,7 @@ ) @Slf4j public class ThievingPlugin extends Plugin { - public static final String version = "2.1.0"; + public static final String version = "2.1.1"; @Inject @Getter diff --git a/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingScript.java b/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingScript.java index c88e971aa5..5f4a9bcd2c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingScript.java @@ -35,9 +35,13 @@ import net.runelite.client.plugins.microbot.util.security.Login; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.walker.WalkerState; +import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; +import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; +import net.runelite.api.widgets.WidgetID; import net.runelite.client.plugins.skillcalculator.skills.MagicAction; +import java.awt.event.KeyEvent; import java.time.Duration; import java.time.Instant; @@ -181,6 +185,17 @@ private Predicate getThievingNpcFilter() { case ELVES: filter = validateName(ThievingData.ELVES::contains); break; + case MASTER_FARMER: + filter = validateName(name -> name.toLowerCase().contains(finalNpc.getName())); + if (config.farmingGuildMasterFarmer()) { + int farmingLevel = Microbot.getClient().getRealSkillLevel(Skill.FARMING); + if (farmingLevel < ThievingData.FARMING_GUILD_MID_FARMING_LEVEL) { + filter = filter.and(npc -> ThievingData.FARMING_GUILD_SOUTH_NPC_IDS.contains(npc.getId())); + } else { + filter = filter.and(npc -> ThievingData.FARMING_GUILD_AREA.contains(npc.getWorldLocation())); + } + } + break; default: filter = validateName(name -> name.toLowerCase().contains(finalNpc.getName())); break; @@ -301,7 +316,7 @@ private State getCurrentState() { } } - if (Rs2Inventory.isFull()) return applyOverride(State.DROP); + if (Rs2Inventory.isFull()) return applyOverride(config.useSeedVault() ? State.SEED_VAULT : State.DROP); if (isNpcNull(thievingNpc) && (thievingNpc = getThievingNpcCache()) == null && shouldHop()) return applyOverride(State.HOP); @@ -506,6 +521,9 @@ private void loop() { dropAllExceptImportant(); if (Rs2Inventory.isFull()) Rs2Player.eatAt(99); return; + case SEED_VAULT: + useSeedVault(); + return; case HOP: if (shouldHop()) { hopWorld(); @@ -1033,6 +1051,22 @@ private void bankAndEquip() { DOOR_TIMER.set(); } + private void useSeedVault() { + if (!walkTo("Walk to seed vault", ThievingData.SEED_VAULT_LOCATION, 3)) return; + if (!Rs2Widget.isWidgetVisible(WidgetID.SEED_VAULT_GROUP_ID, 0)) { + if (!Rs2GameObject.interact(ThievingData.SEED_VAULT_OBJECT_ID, "Use")) return; + if (!sleepUntil(() -> Rs2Widget.isWidgetVisible(WidgetID.SEED_VAULT_GROUP_ID, 0), 3_000)) { + log.warn("Seed vault did not open"); + return; + } + } + Rs2Widget.clickWidget(WidgetID.SEED_VAULT_GROUP_ID, 25); + Rs2Inventory.waitForInventoryChanges(2_500); + Rs2Keyboard.keyPress(KeyEvent.VK_ESCAPE); + sleepUntil(() -> !Rs2Widget.isWidgetVisible(WidgetID.SEED_VAULT_GROUP_ID, 0), 1_500); + if (startingLocation != null) walkTo("Return to thieving spot", startingLocation, 3); + } + private void dropAllExceptImportant() { final Set keep = getExclusions(); if (config.DoNotDropItemList() != null && !config.DoNotDropItemList().isEmpty()) diff --git a/src/test/java/net/runelite/client/Microbot.java b/src/test/java/net/runelite/client/Microbot.java index 562dba03f4..588619e37e 100644 --- a/src/test/java/net/runelite/client/Microbot.java +++ b/src/test/java/net/runelite/client/Microbot.java @@ -4,15 +4,15 @@ import java.util.List; import java.util.stream.Collectors; -import net.runelite.client.plugins.microbot.banksshopper.BanksShopperPlugin; import net.runelite.client.plugins.microbot.slayer.SlayerPlugin; +import net.runelite.client.plugins.microbot.thieving.ThievingPlugin; public class Microbot { private static final Class[] debugPlugins = { SlayerPlugin.class, - BanksShopperPlugin.class, + ThievingPlugin.class, }; public static void main(String[] args) throws Exception From aa9246ccfcb5a4b51312d11f9cc40414d08b0bdb Mon Sep 17 00:00:00 2001 From: Bender Date: Sat, 14 Mar 2026 10:32:04 -0700 Subject: [PATCH 14/24] Fix Edge Bank for crafting --- .../client/plugins/microbot/crafting/jewelry/JewelryScript.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/crafting/jewelry/JewelryScript.java b/src/main/java/net/runelite/client/plugins/microbot/crafting/jewelry/JewelryScript.java index 2b0d89475b..74ed5bd07e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/crafting/jewelry/JewelryScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/crafting/jewelry/JewelryScript.java @@ -74,7 +74,7 @@ public boolean run() { Rs2Bank.preHover(); break; case BANKING: - boolean isBankOpen = Rs2Bank.isNearBank(15) ? Rs2Bank.openBank() : Rs2Bank.walkToBankAndUseBank(); + boolean isBankOpen = Rs2Bank.isNearBank(21) ? Rs2Bank.openBank() : Rs2Bank.walkToBankAndUseBank(); if (!isBankOpen || !Rs2Bank.isOpen()) return; From 58d1d5fdad5bc27608bfb4c7ed06e637a7f8ac6c Mon Sep 17 00:00:00 2001 From: Bender Date: Sun, 15 Mar 2026 11:51:21 -0700 Subject: [PATCH 15/24] Updates --- .../glassmake/GabulhasGlassMakeScript.java | 2 +- .../microbot/smelting/AutoSmeltingPlugin.java | 2 +- .../microbot/smelting/AutoSmeltingScript.java | 23 ++++++++++--------- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/glassmake/GabulhasGlassMakeScript.java b/src/main/java/net/runelite/client/plugins/microbot/glassmake/GabulhasGlassMakeScript.java index 76d1b11f65..a1d4bf2db8 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/glassmake/GabulhasGlassMakeScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/glassmake/GabulhasGlassMakeScript.java @@ -73,7 +73,7 @@ public void shutdown() { } private void takeBreak() { - if (Rs2Random.nextInt(0, 20, 1, true) == 30) { + if (Rs2Random.nextInt(0, 20, 1, true) == 20) { sleep(1000, 20000); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/smelting/AutoSmeltingPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/smelting/AutoSmeltingPlugin.java index 48180a70e9..c26b3e216f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/smelting/AutoSmeltingPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/smelting/AutoSmeltingPlugin.java @@ -29,7 +29,7 @@ ) @Slf4j public class AutoSmeltingPlugin extends Plugin { - public static final String version = "1.0.3"; + public static final String version = "1.0.4"; @Inject private AutoSmeltingConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/smelting/AutoSmeltingScript.java b/src/main/java/net/runelite/client/plugins/microbot/smelting/AutoSmeltingScript.java index 9747f70b27..463b561b93 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/smelting/AutoSmeltingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/smelting/AutoSmeltingScript.java @@ -13,9 +13,11 @@ import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; +import java.awt.event.KeyEvent; import java.text.MessageFormat; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -59,7 +61,7 @@ public boolean run(AutoSmeltingConfig config) { } Rs2Player.waitForWalking(); sleep(600,1200); - if (!Rs2Player.isInMemberWorld()) { + if (!Rs2Player.isInMemberWorld() || config.SELECTED_BAR_TYPE().getId() == ItemID.MOLTEN_GLASS) { Rs2Bank.depositAll(); } else if (Rs2Player.isMember()) Rs2Bank.depositAllExcept(coalBag); if (config.SELECTED_BAR_TYPE().getId() == ItemID.IRON_BAR && Rs2Bank.hasItem(ItemID.RING_OF_FORGING) && !Rs2Equipment.isWearing(ItemID.RING_OF_FORGING)) { @@ -110,16 +112,14 @@ public boolean run(AutoSmeltingConfig config) { } GameObject oneClickFurnace = Rs2GameObject.findObject("furnace", true, 20, false, initialPlayerLocation); if (oneClickFurnace != null) { - if (Rs2Bank.isOpen()){ - Rs2Bank.closeBank(); - sleepUntil(() -> !Rs2Bank.isOpen(), 1000); - } Rs2GameObject.interact(oneClickFurnace, "smelt"); sleepUntil(Rs2Player::isMoving, 1000); sleepUntil(() -> !Rs2Player.isMoving(), 4000); - Rs2Widget.sleepUntilHasWidgetText("What would you like to smelt?", 270, 5, false, 4000); - Rs2Widget.clickWidget(config.SELECTED_BAR_TYPE().getName()); - Rs2Widget.sleepUntilHasNotWidgetText("What would you like to smelt?", 270, 5, false, 4000); + String widgetText1 = config.SELECTED_BAR_TYPE().getId() == ItemID.MOLTEN_GLASS ? "How many do you wish to make?" : "What would you like to smelt?"; + Rs2Widget.sleepUntilHasWidgetText(widgetText1, 270, 5, false, 5000); + sleep(300, 950); + Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); + Rs2Widget.sleepUntilHasNotWidgetText(widgetText1, 270, 5, false, 5000); Rs2Antiban.actionCooldown(); Rs2Antiban.takeMicroBreakByChance(); return; @@ -137,9 +137,10 @@ public boolean run(AutoSmeltingConfig config) { GameObject furnace = Rs2GameObject.findObject("furnace",true,20,false,initialPlayerLocation); if (furnace != null) { Rs2GameObject.interact(furnace, "smelt"); - Rs2Widget.sleepUntilHasWidgetText("What would you like to smelt?", 270, 5, false, 4000); - Rs2Widget.clickWidget(config.SELECTED_BAR_TYPE().getName()); - Rs2Widget.sleepUntilHasNotWidgetText("What would you like to smelt?", 270, 5, false, 4000); + String widgetText2 = config.SELECTED_BAR_TYPE().getId() == ItemID.MOLTEN_GLASS ? "How many do you wish to make?" : "What would you like to smelt?"; + Rs2Widget.sleepUntilHasWidgetText(widgetText2, 270, 5, false, 5000); + Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); + Rs2Widget.sleepUntilHasNotWidgetText(widgetText2, 270, 5, false, 5000); Rs2Antiban.actionCooldown(); Rs2Antiban.takeMicroBreakByChance(); } From 4c8d1cd7a69cf6993554a1bb123bb68b5654a96b Mon Sep 17 00:00:00 2001 From: Bender Date: Fri, 20 Mar 2026 21:57:51 -0700 Subject: [PATCH 16/24] Master Farmer + Karambwans --- .../karambwans/GabulhasKarambwansScript.java | 5 ++-- .../microbot/thieving/ThievingScript.java | 27 +++++++++++++++++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/karambwans/GabulhasKarambwansScript.java b/src/main/java/net/runelite/client/plugins/microbot/karambwans/GabulhasKarambwansScript.java index 593996a2f5..aaf7e85d2b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/karambwans/GabulhasKarambwansScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/karambwans/GabulhasKarambwansScript.java @@ -148,7 +148,6 @@ private void useBank() { } - Rs2Bank.closeBank(); sleepUntil(() -> !Rs2Bank.isOpen(), 3000); } @@ -176,12 +175,12 @@ private void walkToFish() { } } else { Rs2Walker.walkTo(zanarisRingPoint, 3); - Rs2Player.waitForWalking(); + sleepUntil(() -> Rs2Player.distanceTo(zanarisRingPoint) <= 8, 15000); if (Rs2GameObject.interact(FAIRY_RING_ID, "Last-destination (DKP)")) { waitTillPlayerNextToFishingSpot(); } else { - Rs2Player.waitForWalking(); + sleepUntil(() -> Rs2Player.isMoving(), 5000); } } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingScript.java b/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingScript.java index 5f4a9bcd2c..3ed624b97b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thieving/ThievingScript.java @@ -935,6 +935,13 @@ private void showMessage(String message) { } private void bankAndEquip() { + if (config.useSeedVault() && !Rs2Bank.isOpen() && hasSeedsInInventory()) { + final WorldPoint myLoc = Rs2Player.getWorldLocation(); + if (myLoc != null && myLoc.distanceTo(ThievingData.SEED_VAULT_LOCATION) <= 20) { + log.debug("Depositing seeds to vault before banking"); + depositToSeedVault(); + } + } if (!Rs2Bank.isOpen()) { BankLocation bank; if (config.THIEVING_NPC() == ThievingNpc.VYRES && ThievingData.OUTSIDE_HALLOWED_BANK.distanceTo(Rs2Player.getWorldLocation()) < 20) { @@ -1051,8 +1058,16 @@ private void bankAndEquip() { DOOR_TIMER.set(); } - private void useSeedVault() { - if (!walkTo("Walk to seed vault", ThievingData.SEED_VAULT_LOCATION, 3)) return; + private boolean hasSeedsInInventory() { + return Rs2Inventory.all().stream().anyMatch(item -> { + if (item == null || item.getName() == null) return false; + final String name = item.getName().toLowerCase(); + return name.endsWith(" seed") || name.endsWith(" seeds"); + }); + } + + /** Opens the seed vault and deposits all seeds. Does not walk to the vault or return to starting location. */ + private void depositToSeedVault() { if (!Rs2Widget.isWidgetVisible(WidgetID.SEED_VAULT_GROUP_ID, 0)) { if (!Rs2GameObject.interact(ThievingData.SEED_VAULT_OBJECT_ID, "Use")) return; if (!sleepUntil(() -> Rs2Widget.isWidgetVisible(WidgetID.SEED_VAULT_GROUP_ID, 0), 3_000)) { @@ -1064,6 +1079,14 @@ private void useSeedVault() { Rs2Inventory.waitForInventoryChanges(2_500); Rs2Keyboard.keyPress(KeyEvent.VK_ESCAPE); sleepUntil(() -> !Rs2Widget.isWidgetVisible(WidgetID.SEED_VAULT_GROUP_ID, 0), 1_500); + } + + private void useSeedVault() { + final WorldPoint myLoc = Rs2Player.getWorldLocation(); + if (myLoc == null || myLoc.distanceTo(ThievingData.SEED_VAULT_LOCATION) > 20) { + if (!walkTo("Walk to seed vault", ThievingData.SEED_VAULT_LOCATION, 20)) return; + } + depositToSeedVault(); if (startingLocation != null) walkTo("Return to thieving spot", startingLocation, 3); } From dd0e5d43d961db0fd96a8039e829617773f7d9a4 Mon Sep 17 00:00:00 2001 From: Bender Date: Sun, 22 Mar 2026 17:22:17 -0700 Subject: [PATCH 17/24] Start to pitfall --- .../microbot/PitFallTrapHunter/Axe.java | 40 ++ .../PitFallTrapHunter/FurHandling.java | 18 + .../microbot/PitFallTrapHunter/MeatPouch.java | 20 + .../PitFallTrapHunter/PitFallState.java | 12 + .../PitFallTrapHunterConfig.java | 234 +++++++ .../PitFallTrapHunterOverlay.java | 119 ++++ .../PitFallTrapHunterPlugin.java | 189 ++++++ .../PitFallTrapHunterScript.java | 606 ++++++++++++++++++ .../PitFallTrapHunter/PitFallTrapHunting.java | 83 +++ .../PitFallTrapInventoryHandlerScript.java | 63 ++ .../PitFallTrapHunter/PitLocation.java | 70 ++ .../PitFallTrapHunter/PitOrientation.java | 26 + .../PitFallTrapHunter/TeasingTool.java | 23 + 13 files changed, 1503 insertions(+) create mode 100644 src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/Axe.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/FurHandling.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/MeatPouch.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallState.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapHunterConfig.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapHunterOverlay.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapHunterPlugin.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapHunterScript.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapHunting.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapInventoryHandlerScript.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitLocation.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitOrientation.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/TeasingTool.java diff --git a/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/Axe.java b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/Axe.java new file mode 100644 index 0000000000..c4b4a18ef7 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/Axe.java @@ -0,0 +1,40 @@ +package net.runelite.client.plugins.microbot.PitFallTrapHunter; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import net.runelite.api.Skill; +import net.runelite.api.gameval.ItemID; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; + +@Getter +@AllArgsConstructor +public enum Axe { + + BRONZE_AXE("bronze axe", ItemID.BRONZE_AXE, 1, 1), + BRONZE_FELLING_AXE("bronze felling axe", ItemID.BRONZE_AXE_2H, 1, 1), + IRON_AXE("iron axe", ItemID.IRON_AXE, 1, 1), + IRON_FELLING_AXE("iron felling axe", ItemID.IRON_AXE_2H, 1, 1), + STEEL_AXE("steel felling axe", ItemID.STEEL_AXE_2H, 6, 5), + STEEL_FELLING_AXE("steel axe", ItemID.STEEL_AXE, 6, 5), + BLACK_FELLING_AXE("black felling axe", ItemID.BLACK_AXE_2H, 11, 10), + BLACK_AXE("black axe", ItemID.BLACK_AXE, 11, 10), + MITHRIL_AXE("mithril axe", ItemID.MITHRIL_AXE, 21, 20), + MITHRIL_FELLING_AXE("mithril felling axe", ItemID.MITHRIL_AXE_2H, 21, 20), + ADAMANT_AXE("adamant axe", ItemID.ADAMANT_AXE, 31, 30), + ADAMANT_FELLING_AXE("adamant felling axe", ItemID.ADAMANT_AXE_2H, 31, 30), + RUNE_AXE("rune axe", ItemID.RUNE_AXE, 41, 40), + RUNE_FELLING_AXE("rune felling axe", ItemID.RUNE_AXE_2H, 41, 40), + DRAGON_AXE("dragon axe", ItemID.DRAGON_AXE, 61, 60), + DRAGON_FELLING_AXE("dragon felling axe", ItemID.DRAGON_AXE_2H, 61, 60), + CRYSTAL_AXE("crystal axe", ItemID.CRYSTAL_AXE, 71, 70), + CRYSTAL_FELLING_AXE("crystal felling axe", ItemID.CRYSTAL_AXE_2H, 71, 70); + + private final String itemName; + private final int itemID; + private final int woodcuttingLevel; + private final int attackLevel; + + public boolean hasRequirements(boolean axeInInventory) { + return Rs2Player.getSkillRequirement(Skill.WOODCUTTING, this.woodcuttingLevel) && (Rs2Player.getSkillRequirement(Skill.ATTACK, this.attackLevel) || axeInInventory); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/FurHandling.java b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/FurHandling.java new file mode 100644 index 0000000000..fc94531c53 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/FurHandling.java @@ -0,0 +1,18 @@ +package net.runelite.client.plugins.microbot.PitFallTrapHunter; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum FurHandling { + DROP("Drop"), + BANK("Bank"); + + private final String name; + + @Override + public String toString() { + return name; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/MeatPouch.java b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/MeatPouch.java new file mode 100644 index 0000000000..22785cad06 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/MeatPouch.java @@ -0,0 +1,20 @@ +package net.runelite.client.plugins.microbot.PitFallTrapHunter; + +import lombok.Getter; +import net.runelite.api.gameval.ItemID; + +public enum MeatPouch { + SMALL_MEAT_POUCH(ItemID.HG_MEATPOUCH_SMALL, ItemID.HG_MEATPOUCH_SMALL_OPEN), + LARGE_MEAT_POUCH(ItemID.HG_MEATPOUCH_LARGE, ItemID.HG_MEATPOUCH_LARGE_OPEN); + + @Getter + private final int closedItemID; + + @Getter + private final int openItemID; + + MeatPouch(int closedItemID, int openItemID) { + this.openItemID = openItemID; + this.closedItemID = closedItemID; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallState.java b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallState.java new file mode 100644 index 0000000000..701eb97969 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallState.java @@ -0,0 +1,12 @@ +package net.runelite.client.plugins.microbot.PitFallTrapHunter; + +public enum PitFallState { + IDLE, + WOODCUTTING, + SETTING_TRAP, + TEASING, + JUMPING, + CHECKING, + BANKING, + FLETCHING +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapHunterConfig.java b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapHunterConfig.java new file mode 100644 index 0000000000..f8fdab6d54 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapHunterConfig.java @@ -0,0 +1,234 @@ +package net.runelite.client.plugins.microbot.PitFallTrapHunter; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigInformation; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.ConfigSection; +import net.runelite.client.plugins.microbot.util.misc.Rs2Food; + +@ConfigInformation("" + + "Pitfall Trap Hunter by TaF" + + "

This plugin automates hunting creatures with Pitfall traps.

\n" + + "

Requirements:

\n" + + "
    \n" + + "
  1. Appropriate Hunter level for your chosen creature
  2. \n" + + "
  3. Teasing stick or Hunter's spear (equipped or in inventory)
  4. \n" + + "
  5. Axe in bank or inventory (for chopping logs)
  6. \n" + + "
  7. Knife in bank or inventory
  8. \n" + + "
\n" + + "

Cycle: Set trap on pit → Tease NPC → Jump over spiked pit → Check pit for loot

\n" + + "

Other valuable items:

\n" + + "
    \n" + + "
  1. Kandarin headgear for a chance at extra logs being cut
  2. \n" + + "
  3. Graceful for banking/travel
  4. \n" + + "
  5. Guild hunter outfit for increased catch rates
  6. \n" + + "
  7. Chisel for fletching sunlight antler bolts
  8. \n" + + "
\n" + + "") +@ConfigGroup("PitFallTrapHunter") +public interface PitFallTrapHunterConfig extends Config { + + // ---- Sections ---- + + @ConfigSection( + name = "General", + description = "General hunting settings", + position = 0 + ) + String generalSection = "General"; + + @ConfigSection( + name = "Supplies", + description = "Axe, meat pouch, and food settings", + position = 1 + ) + String suppliesSection = "Supplies"; + + @ConfigSection( + name = "Loot & Fletching", + description = "How to handle antelope fur and antler bolt fletching", + position = 2 + ) + String lootSection = "Loot & Fletching"; + + @ConfigSection( + name = "Timings", + description = "Sleep timings after actions", + position = 3 + ) + String timingsSection = "Timings"; + + // ---- General ---- + + @ConfigItem( + section = generalSection, + position = 0, + keyName = "pitFallTrapHunting", + name = "Creature to hunt", + description = "Select which creature to hunt with pitfall traps" + ) + default PitFallTrapHunting pitFallTrapHunting() { + return PitFallTrapHunting.SUNLIGHT_ANTELOPE; + } + + @ConfigItem( + section = generalSection, + position = 1, + keyName = "xpMode", + name = "XP Mode", + description = "Disables looting and banking to maximize XP per hour" + ) + default boolean xpMode() { + return false; + } + + @ConfigItem( + section = generalSection, + position = 2, + keyName = "showOverlay", + name = "Show Overlay", + description = "Displays overlay with traps and status" + ) + default boolean showOverlay() { + return true; + } + + // ---- Supplies ---- + + @ConfigItem( + section = suppliesSection, + position = 0, + keyName = "axeInInventory", + name = "Use axe in inventory?", + description = "Keep axe in inventory instead of equipping it" + ) + default boolean axeInInventory() { + return true; + } + + @ConfigItem( + section = suppliesSection, + position = 1, + keyName = "UseMeatPouch", + name = "Use meat pouch?", + description = "Do you have a meat pouch?" + ) + default boolean UseMeatPouch() { + return true; + } + + @ConfigItem( + section = suppliesSection, + position = 2, + keyName = "MeatPouch", + name = "Meat pouch", + description = "Which meat pouch should the script use?" + ) + default MeatPouch MeatPouch() { + return MeatPouch.LARGE_MEAT_POUCH; + } + + @ConfigItem( + section = suppliesSection, + position = 3, + keyName = "EatAtBank", + name = "Eat at bank", + description = "Auto eats food at the bank to fill up your hitpoints." + ) + default boolean AutoEat() { + return true; + } + + @ConfigItem( + section = suppliesSection, + position = 4, + keyName = "FoodToEatAtBank", + name = "Food to eat at bank", + description = "What food should we eat at the bank?" + ) + default Rs2Food FoodToEatAtBank() { + return Rs2Food.LOBSTER; + } + + @ConfigItem( + section = suppliesSection, + position = 5, + keyName = "runToBankHP", + name = "Run to bank at HP", + description = "Run to the bank to eat when HP drops to this level or below" + ) + default int runToBankHP() { + return 25; + } + + // ---- Loot & Fletching ---- + + @ConfigItem( + section = lootSection, + position = 0, + keyName = "furHandling", + name = "Antelope fur", + description = "Drop antelope fur or bank it" + ) + default FurHandling furHandling() { + return FurHandling.DROP; + } + + @ConfigItem( + section = lootSection, + position = 1, + keyName = "fletchAntlerBolts", + name = "Fletch sunlight antler bolts", + description = "Use chisel on sunlight antlers to fletch bolts when inventory is nearly full. Requires chisel in inventory." + ) + default boolean fletchAntlerBolts() { + return false; + } + + // ---- Timings ---- + + @ConfigItem( + section = timingsSection, + position = 0, + keyName = "MinSleepAfterCatch", + name = "Min. Sleep After Catch (ms)", + description = "Minimum sleep in ms after checking a caught creature" + ) + default int minSleepAfterCatch() { + return 4500; + } + + @ConfigItem( + section = timingsSection, + position = 1, + keyName = "MaxSleepAfterCatch", + name = "Max. Sleep After Catch (ms)", + description = "Maximum sleep in ms after checking a caught creature" + ) + default int maxSleepAfterCatch() { + return 7000; + } + + @ConfigItem( + section = timingsSection, + position = 2, + keyName = "MinSleepAfterLay", + name = "Min. Sleep After Trap Set (ms)", + description = "Minimum sleep in ms after setting a trap" + ) + default int minSleepAfterLay() { + return 2000; + } + + @ConfigItem( + section = timingsSection, + position = 3, + keyName = "MaxSleepAfterLay", + name = "Max. Sleep After Trap Set (ms)", + description = "Maximum sleep in ms after setting a trap" + ) + default int maxSleepAfterLay() { + return 5000; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapHunterOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapHunterOverlay.java new file mode 100644 index 0000000000..227c352369 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapHunterOverlay.java @@ -0,0 +1,119 @@ +package net.runelite.client.plugins.microbot.PitFallTrapHunter; + +import net.runelite.api.Client; +import net.runelite.api.Skill; +import net.runelite.client.ui.overlay.OverlayPanel; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.components.LineComponent; +import net.runelite.client.ui.overlay.components.TitleComponent; + +import javax.inject.Inject; +import java.awt.*; + +public class PitFallTrapHunterOverlay extends OverlayPanel { + private final Client client; + private final PitFallTrapHunterConfig config; + private final PitFallTrapHunterPlugin plugin; + private final PitFallTrapHunterScript script; + private int startingLevel = 0; + + @Inject + public PitFallTrapHunterOverlay(Client client, PitFallTrapHunterConfig config, PitFallTrapHunterPlugin plugin, PitFallTrapHunterScript script) { + super(plugin); + this.client = client; + this.config = config; + this.plugin = plugin; + this.script = script; + setPosition(OverlayPosition.TOP_LEFT); + setNaughty(); + } + + @Override + public Dimension render(Graphics2D graphics) { + if (!config.showOverlay()) { + return null; + } + + if (startingLevel == 0) { + startingLevel = client.getRealSkillLevel(Skill.HUNTER); + } + + panelComponent.getChildren().clear(); + panelComponent.setPreferredSize(new Dimension(200, 300)); + + panelComponent.getChildren().add(TitleComponent.builder() + .text("Pitfall Trap Hunter by TaF") + .color(Color.GREEN) + .build()); + + panelComponent.getChildren().add(LineComponent.builder().build()); + + panelComponent.getChildren().add(LineComponent.builder() + .left("Running: ") + .right(plugin.getTimeRunning()) + .leftColor(Color.WHITE) + .rightColor(Color.WHITE) + .build()); + + panelComponent.getChildren().add(LineComponent.builder() + .left("Hunter Level:") + .right(startingLevel + "/" + client.getRealSkillLevel(Skill.HUNTER)) + .leftColor(Color.WHITE) + .rightColor(Color.ORANGE) + .build()); + + if (config.pitFallTrapHunting() != null) { + panelComponent.getChildren().add(LineComponent.builder() + .left("Hunting:") + .right(config.pitFallTrapHunting().getName()) + .leftColor(Color.WHITE) + .rightColor(Color.YELLOW) + .build()); + } + + // Current state + panelComponent.getChildren().add(LineComponent.builder() + .left("State:") + .right(script.getState().name()) + .leftColor(Color.WHITE) + .rightColor(Color.CYAN) + .build()); + + // Traps information + int currentTraps = plugin.getTraps().size(); + panelComponent.getChildren().add(LineComponent.builder() + .left("Traps:") + .right(String.valueOf(currentTraps)) + .leftColor(Color.WHITE) + .rightColor(currentTraps > 0 ? Color.GREEN : Color.CYAN) + .build()); + + // Statistics + panelComponent.getChildren().add(LineComponent.builder() + .left("Creatures Caught:") + .right(String.valueOf(PitFallTrapHunterScript.creaturesCaught)) + .leftColor(Color.WHITE) + .rightColor(Color.GREEN) + .build()); + + // Fletching status + if (config.fletchAntlerBolts()) { + panelComponent.getChildren().add(LineComponent.builder() + .left("Bolt Fletching:") + .right("Enabled") + .leftColor(Color.WHITE) + .rightColor(Color.MAGENTA) + .build()); + } + + // Fur handling + panelComponent.getChildren().add(LineComponent.builder() + .left("Fur:") + .right(config.furHandling().getName()) + .leftColor(Color.WHITE) + .rightColor(config.furHandling() == FurHandling.BANK ? Color.YELLOW : Color.GRAY) + .build()); + + return super.render(graphics); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapHunterPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapHunterPlugin.java new file mode 100644 index 0000000000..7d70df12ea --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapHunterPlugin.java @@ -0,0 +1,189 @@ +package net.runelite.client.plugins.microbot.PitFallTrapHunter; + +import com.google.inject.Provides; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.*; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.GameObjectSpawned; +import net.runelite.api.events.GameTick; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.hunter.HunterTrap; +import net.runelite.client.plugins.microbot.PluginConstants; +import net.runelite.client.plugins.microbot.util.misc.TimeUtils; +import net.runelite.client.ui.overlay.OverlayManager; + +import javax.inject.Inject; +import java.time.Instant; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +@Slf4j +@PluginDescriptor( + name = PluginDescriptor.TaFCat + "Pitfall Trap Hunter", + description = "Automates hunting creatures with Pitfall traps (Sunlight Antelope).", + tags = {"hunter", "pitfall", "antelope", "skilling", "xp", "loot", "TaF"}, + version = PitFallTrapHunterPlugin.version, + minClientVersion = "2.1.0", + cardUrl = "", + iconUrl = "", + enabledByDefault = PluginConstants.DEFAULT_ENABLED, + isExternal = PluginConstants.IS_EXTERNAL +) +public class PitFallTrapHunterPlugin extends Plugin { + + public static final String version = "1.0.0"; + + // TODO: Replace these placeholder object IDs with actual values from in-game dev tools + // These are used in onGameObjectSpawned to track trap states + private static final int PIT_UNSET_ID = -1; // Unset pit (collapsed/empty) + private static final int PIT_SET_ID = -1; // Pit after "Trap" interaction (set, waiting) + private static final int PIT_SPIKED_ID = -1; // Spiked pit ready for "Jump" + private static final int PIT_FULL_ANTELOPE_ID = -1; // Pit with caught Sunlight Antelope + private static final int PIT_TRAPPING_ID = -1; // Transition state while creature falls in + + @Getter + private final Map traps = new HashMap<>(); + @Inject + private Client client; + @Inject + private PitFallTrapHunterConfig config; + @Inject + private OverlayManager overlayManager; + @Inject + private PitFallTrapHunterOverlay overlay; + private PitFallTrapHunterScript script; + private PitFallTrapInventoryHandlerScript inventoryHandler; + private Instant scriptStartTime; + + @Provides + PitFallTrapHunterConfig provideConfig(ConfigManager configManager) { + return configManager.getConfig(PitFallTrapHunterConfig.class); + } + + @Override + protected void startUp() throws Exception { + log.info("Pitfall Trap Hunter plugin started!"); + scriptStartTime = Instant.now(); + overlayManager.add(overlay); + script = new PitFallTrapHunterScript(); + script.run(config, this); + inventoryHandler = new PitFallTrapInventoryHandlerScript(); + inventoryHandler.run(config, script); + } + + @Override + protected void shutDown() throws Exception { + log.info("Pitfall Trap Hunter plugin stopped!"); + scriptStartTime = null; + overlayManager.remove(overlay); + if (script != null) { + script.shutdown(); + inventoryHandler.shutdown(); + } + } + + protected String getTimeRunning() { + return scriptStartTime != null ? TimeUtils.getFormattedDurationBetween(scriptStartTime, Instant.now()) : ""; + } + + @Subscribe + public void onGameObjectSpawned(GameObjectSpawned event) { + final GameObject gameObject = event.getGameObject(); + final WorldPoint trapLocation = gameObject.getWorldLocation(); + final HunterTrap myTrap = traps.get(trapLocation); + final Player localPlayer = client.getLocalPlayer(); + final int objectId = gameObject.getId(); + + // Using if-else instead of switch because placeholder IDs are all -1 during development. + // Replace with a proper switch once real object IDs are filled in. + if (objectId == PIT_SET_ID || objectId == PIT_SPIKED_ID) { + // Trap placed — player interacted with "Trap" on pit + if (localPlayer.getWorldLocation().distanceTo(trapLocation) <= 3) { + log.debug("Pitfall trap set by \"{}\" at {}", localPlayer.getName(), trapLocation); + traps.put(trapLocation, new HunterTrap(gameObject)); + } + } else if (objectId == PIT_FULL_ANTELOPE_ID) { + // Creature caught — pit is full + if (myTrap != null) { + myTrap.setState(HunterTrap.State.FULL); + myTrap.resetTimer(); + } + } else if (objectId == PIT_UNSET_ID) { + // Pit collapsed/reset — empty again + if (myTrap != null) { + myTrap.setState(HunterTrap.State.EMPTY); + myTrap.resetTimer(); + } + } else if (objectId == PIT_TRAPPING_ID) { + // Transition — creature falling in + if (myTrap != null) { + myTrap.setState(HunterTrap.State.TRANSITION); + } + } + } + + @Subscribe + public void onChatMessage(ChatMessage event) { + if (event.getType() == ChatMessageType.GAMEMESSAGE) { + String msg = event.getMessage(); + if (msg.equalsIgnoreCase("oh dear, you are dead!")) { + script.hasDied = true; + } + if (msg.contains("You don't have enough inventory space. You need")) { + script.forceBank = true; + } + } + } + + @Subscribe + public void onGameTick(GameTick event) { + Iterator> it = traps.entrySet().iterator(); + Tile[][][] tiles = client.getScene().getTiles(); + Instant expire = Instant.now().minus(HunterTrap.TRAP_TIME.multipliedBy(2)); + + while (it.hasNext()) { + Map.Entry entry = it.next(); + HunterTrap trap = entry.getValue(); + WorldPoint world = entry.getKey(); + LocalPoint local = LocalPoint.fromWorld(client, world); + + if (local == null) { + if (trap.getPlacedOn().isBefore(expire)) { + log.debug("Pitfall trap removed due to timeout, {} left", traps.size()); + it.remove(); + } + continue; + } + + Tile tile = tiles[world.getPlane()][local.getSceneX()][local.getSceneY()]; + GameObject[] objects = tile.getGameObjects(); + + boolean containsAnything = false; + boolean containsUnsetPit = false; + for (GameObject object : objects) { + if (object != null) { + containsAnything = true; + if (object.getId() == PIT_UNSET_ID) { + containsUnsetPit = true; + break; + } + } + } + + if (!containsAnything) { + it.remove(); + log.debug("Pitfall trap removed (no objects), {} left", traps.size()); + } else if (containsUnsetPit) { + it.remove(); + log.debug("Pitfall trap removed (pit collapsed), {} left", traps.size()); + } + } + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapHunterScript.java b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapHunterScript.java new file mode 100644 index 0000000000..362f96c6c4 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapHunterScript.java @@ -0,0 +1,606 @@ +package net.runelite.client.plugins.microbot.PitFallTrapHunter; + +import lombok.Getter; +import lombok.Setter; +import net.runelite.api.GameObject; +import net.runelite.api.Skill; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.gameval.ItemID; +import net.runelite.client.plugins.hunter.HunterTrap; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.Script; +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.bank.Rs2Bank; +import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; +import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; + +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +public class PitFallTrapHunterScript extends Script { + public static int creaturesCaught = 0; + public boolean hasDied = false; + public boolean forceBank = false; + public boolean forceDrop = false; + + @Getter + @Setter + private PitFallState state = PitFallState.IDLE; + + // Track which pit we're currently working with + private PitLocation activePit = null; + + public boolean run(PitFallTrapHunterConfig config, PitFallTrapHunterPlugin plugin) { + Rs2Antiban.resetAntibanSettings(); + applyAntiBanSettings(); + Rs2Antiban.setActivity(Activity.GENERAL_HUNTER); + mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { + try { + if (!super.run()) return; + if (!Microbot.isLoggedIn()) return; + if (!this.isRunning()) return; + + PitFallTrapHunting creature = config.pitFallTrapHunting(); + + // --- Priority checks --- + if (hasDied) { + Microbot.log("We died - Shutting down"); + shutdown(); + return; + } + if (forceBank) { + Microbot.log("Inventory too full - Banking"); + state = PitFallState.BANKING; + } + if (Rs2Player.getBoostedSkillLevel(Skill.HITPOINTS) <= config.runToBankHP()) { + state = PitFallState.BANKING; + } + + // --- Fletching check: when inventory nearly full and antler bolts enabled --- + if (config.fletchAntlerBolts() && Rs2Inventory.count() >= 24 + && Rs2Inventory.hasItem(creature.getAntlerItemId()) + && Rs2Inventory.hasItem(ItemID.CHISEL)) { + state = PitFallState.FLETCHING; + } + + // --- Supply checks --- + if (state != PitFallState.BANKING) { + if ((Rs2Inventory.count("axe") < 1 && !Rs2Equipment.isWearing("axe")) && plugin.getTraps().isEmpty()) { + Microbot.log("No axe found - Banking"); + state = PitFallState.BANKING; + } + if (!Rs2Inventory.hasItem("Knife")) { + Microbot.log("No knife found - Banking"); + state = PitFallState.BANKING; + } + if (!hasTeasingTool()) { + Microbot.log("No teasing stick or hunter's spear found - Banking"); + state = PitFallState.BANKING; + } + if (Rs2Inventory.isFull()) { + Microbot.log("Inventory full - Banking"); + state = PitFallState.BANKING; + } + } + + // --- Walk to area if far away --- + if (!isNearArea(creature) && state != PitFallState.BANKING) { + Microbot.log("Walking to " + creature.getName() + " hunting area"); + Rs2Walker.walkTo(creature.getHuntingPoint()); + return; + } + + // --- State machine --- + switch (state) { + case BANKING: + handleBanking(config); + state = PitFallState.IDLE; + break; + + case FLETCHING: + handleFletching(creature); + state = PitFallState.IDLE; + break; + + case WOODCUTTING: + handleWoodCutting(config, plugin); + // Transition to IDLE once we have logs + if (Rs2Inventory.hasItem("logs")) { + state = PitFallState.IDLE; + } + break; + + case SETTING_TRAP: + handleSettingTrap(creature, config); + break; + + case TEASING: + handleTeasing(creature, plugin); + break; + + case JUMPING: + handleJumping(creature, config); + break; + + case CHECKING: + handleChecking(plugin, config); + break; + + case IDLE: + default: + handleIdle(creature, plugin, config); + break; + } + + } catch (Exception ex) { + System.out.println("PitFallTrapHunter Script Error: " + ex.getMessage()); + ex.printStackTrace(); + } + }, 0, 1000, TimeUnit.MILLISECONDS); + return true; + } + + // ==================== STATE HANDLERS ==================== + + /** + * IDLE: Decide what to do next based on current trap state. + */ + private void handleIdle(PitFallTrapHunting creature, PitFallTrapHunterPlugin plugin, PitFallTrapHunterConfig config) { + // Check for full traps first (highest priority: collect loot) + var fullTrap = plugin.getTraps().entrySet().stream() + .filter(entry -> entry.getValue().getState() == HunterTrap.State.FULL) + .findFirst().orElse(null); + if (fullTrap != null) { + state = PitFallState.CHECKING; + return; + } + + // Check for set/spiked traps that need teasing + var setTrap = plugin.getTraps().entrySet().stream() + .filter(entry -> entry.getValue().getState() == HunterTrap.State.EMPTY) + .findFirst().orElse(null); + if (setTrap != null) { + // Trap is set, find NPC to tease toward it + activePit = findPitForWorldPoint(creature, setTrap.getKey()); + if (activePit != null) { + state = PitFallState.TEASING; + return; + } + } + + // No active traps — need to set one + if (!Rs2Inventory.hasItem("logs")) { + state = PitFallState.WOODCUTTING; + } else { + state = PitFallState.SETTING_TRAP; + } + } + + /** + * SETTING_TRAP: Walk to a pit and interact with "Trap" to set it (uses a log). + */ + private void handleSettingTrap(PitFallTrapHunting creature, PitFallTrapHunterConfig config) { + if (Rs2Player.isAnimating() || Rs2Player.isMoving()) return; + + if (!Rs2Inventory.hasItem("logs")) { + state = PitFallState.WOODCUTTING; + return; + } + + // Pick the nearest unset pit + WorldPoint playerLoc = Rs2Player.getWorldLocation(); + activePit = creature.findNearestPit(playerLoc); + if (activePit == null) { + Microbot.log("No pit locations configured"); + return; + } + + // Walk toward the pit's ground object if not close + if (playerLoc.distanceTo(activePit.getGroundObjectPoint()) > 5) { + Rs2Walker.walkTo(activePit.getGroundObjectPoint()); + return; + } + + // Interact with the pit ground object to set trap + if (Rs2GameObject.interact(creature.getPitObjectId(), "Trap")) { + Microbot.log("Setting pitfall trap"); + sleep(config.minSleepAfterLay(), config.maxSleepAfterLay()); + // After setting, transition to teasing + state = PitFallState.TEASING; + } + } + + /** + * TEASING: Find the nearest 2x2 NPC, determine which side of the pit it's on, + * walk to the NPC's side of the pit, then tease it. + */ + private void handleTeasing(PitFallTrapHunting creature, PitFallTrapHunterPlugin plugin) { + if (Rs2Player.isAnimating() || Rs2Player.isMoving()) return; + + // Find nearest NPC using the queryable API + Rs2NpcModel npc = findNearestNpc(creature.getNpcName()); + if (npc == null) { + Microbot.log("No " + creature.getNpcName() + " found nearby - waiting"); + return; + } + + // Find the best pit for this NPC (closest set pit) + if (activePit == null) { + activePit = creature.findNearestPit(npc.getWorldLocation()); + } + if (activePit == null) { + Microbot.log("No pit location available"); + state = PitFallState.IDLE; + return; + } + + // Determine which side of the pit the NPC is on + WorldPoint npcLocation = npc.getWorldLocation(); + PitLocation.Side npcSide = activePit.getNpcSide(npcLocation); + WorldPoint teasingTile = activePit.getTeasingTile(npcSide); + + Microbot.log("NPC is on " + npcSide + " side of " + activePit.getOrientation() + " pit"); + + // Walk to the teasing tile on the NPC's side if not already there + WorldPoint playerLoc = Rs2Player.getWorldLocation(); + if (playerLoc.distanceTo(teasingTile) > 2) { + Rs2Walker.walkTo(teasingTile); + return; + } + + // Tease the NPC + if (npc.click("Tease")) { + Microbot.log("Teasing " + creature.getNpcName()); + Rs2Player.waitForAnimation(); + state = PitFallState.JUMPING; + } + } + + /** + * JUMPING: Run to the spiked pit ground object and interact with "Jump". + * The player crosses the pit, and the NPC follows and falls in. + */ + private void handleJumping(PitFallTrapHunting creature, PitFallTrapHunterConfig config) { + if (Rs2Player.isAnimating() || Rs2Player.isMoving()) return; + + if (activePit == null) { + state = PitFallState.IDLE; + return; + } + + // Walk toward the pit ground object if not close enough + WorldPoint playerLoc = Rs2Player.getWorldLocation(); + if (playerLoc.distanceTo(activePit.getGroundObjectPoint()) > 3) { + Rs2Walker.walkTo(activePit.getGroundObjectPoint()); + return; + } + + // Jump over the spiked pit + if (Rs2GameObject.interact(creature.getSpikedPitObjectId(), "Jump")) { + Microbot.log("Jumping over spiked pit"); + Rs2Player.waitForAnimation(); + sleep(1500, 2500); + // After jump, NPC should fall in — transition to checking + state = PitFallState.CHECKING; + } + } + + /** + * CHECKING: Check a full pit to collect loot. + */ + private void handleChecking(PitFallTrapHunterPlugin plugin, PitFallTrapHunterConfig config) { + if (Rs2Player.isAnimating() || Rs2Player.isMoving()) return; + + var fullTrap = plugin.getTraps().entrySet().stream() + .filter(entry -> entry.getValue().getState() == HunterTrap.State.FULL) + .findFirst().orElse(null); + + if (fullTrap != null) { + WorldPoint location = fullTrap.getKey(); + var gameObject = Rs2GameObject.getGameObject(location); + if (gameObject != null) { + if (Rs2Inventory.count() > 24) { + forceDrop = true; + Rs2Inventory.waitForInventoryChanges(8000); + } + Rs2GameObject.interact(gameObject, "Check"); + creaturesCaught++; + Microbot.log("Checking pit - total caught: " + creaturesCaught); + sleep(config.minSleepAfterCatch(), config.maxSleepAfterCatch()); + } + } + + // Reset for next cycle + activePit = null; + state = PitFallState.IDLE; + } + + // ==================== SUPPORT METHODS ==================== + + /** + * Find the nearest NPC matching the given name. Sunlight antelopes are 2x2 NPCs. + */ + private Rs2NpcModel findNearestNpc(String npcName) { + return Microbot.getRs2NpcCache().query() + .withName(npcName) + .where(npc -> !npc.isDead()) + .nearestOnClientThread(); + } + + /** + * Find the PitLocation corresponding to a given world point (for trap tracking). + */ + private PitLocation findPitForWorldPoint(PitFallTrapHunting creature, WorldPoint trapPoint) { + return creature.getPitLocations().stream() + .min(Comparator.comparingInt(pit -> pit.distanceTo(trapPoint))) + .orElse(null); + } + + /** + * Check if player has a teasing stick or hunter's spear in inventory or equipment. + */ + private boolean hasTeasingTool() { + for (TeasingTool tool : TeasingTool.values()) { + if (Rs2Inventory.hasItem(tool.getItemId()) || Rs2Equipment.isWearing(tool.getItemName())) { + return true; + } + } + return false; + } + + /** + * Find the first available teasing tool from equipment or inventory. + */ + private TeasingTool findTeasingTool() { + for (TeasingTool tool : TeasingTool.values()) { + if (Rs2Equipment.isWearing(tool.getItemName()) || Rs2Inventory.hasItem(tool.getItemId())) { + return tool; + } + } + return null; + } + + private boolean isNearArea(PitFallTrapHunting creature) { + WorldPoint currentLocation = Rs2Player.getWorldLocation(); + return currentLocation.distanceTo(creature.getHuntingPoint()) <= 50; + } + + // ==================== FLETCHING ==================== + + private void handleFletching(PitFallTrapHunting creature) { + if (!Rs2Inventory.hasItem(creature.getAntlerItemId()) || !Rs2Inventory.hasItem(ItemID.CHISEL)) { + state = PitFallState.IDLE; + return; + } + Microbot.log("Fletching sunlight antler bolts"); + Rs2Inventory.combineClosest(creature.getAntlerItemId(), ItemID.CHISEL); + Rs2Player.waitForAnimation(); + sleep(1200, 2400); + } + + // ==================== WOODCUTTING ==================== + + private void handleWoodCutting(PitFallTrapHunterConfig config, PitFallTrapHunterPlugin plugin) { + if (Rs2Player.isInCombat()) { + Rs2Walker.walkTo(config.pitFallTrapHunting().getHuntingPoint()); + return; + } + + if (Rs2Inventory.count("logs") > 5) { + walkBackToHunterArea(config, plugin); + return; + } + + if (Rs2Player.isMoving() || Rs2Player.isAnimating()) { + return; + } + + GameObject tree = Rs2GameObject.findReachableObject("tree", true, 20, config.pitFallTrapHunting().getHuntingPoint()); + if (tree == null) { + tree = Rs2GameObject.findReachableObject("dead tree", true, 20, config.pitFallTrapHunting().getHuntingPoint()); + } + if (tree != null) { + if (Rs2GameObject.interact(tree, "Chop down")) { + Rs2Player.waitForAnimation(); + Rs2Antiban.actionCooldown(); + } + } else { + Microbot.log("No nearby tree found - Walking back to origin"); + walkBackToHunterArea(config, plugin); + } + } + + private void walkBackToHunterArea(PitFallTrapHunterConfig config, PitFallTrapHunterPlugin plugin) { + if (!plugin.getTraps().isEmpty()) { + var trap = plugin.getTraps().values().stream().findFirst().orElse(null); + if (trap != null) { + Rs2Walker.walkTo(trap.getWorldLocation()); + } else { + Rs2Walker.walkTo(config.pitFallTrapHunting().getHuntingPoint()); + } + } else { + Rs2Walker.walkTo(config.pitFallTrapHunting().getHuntingPoint()); + } + } + + // ==================== BANKING ==================== + + private void handleBanking(PitFallTrapHunterConfig config) { + Rs2Bank.walkToBank(); + Rs2Bank.openBank(); + + PitFallTrapHunting creature = config.pitFallTrapHunting(); + + // Handle meat pouch + if (config.UseMeatPouch()) { + if (!Rs2Inventory.hasItem(config.MeatPouch().getClosedItemID()) && !Rs2Inventory.hasItem(config.MeatPouch().getOpenItemID())) { + Rs2Bank.withdrawOne(config.MeatPouch().getOpenItemID()); + sleep(600, 1200); + Rs2Bank.withdrawOne(config.MeatPouch().getClosedItemID()); + sleep(600, 1200); + } + if (Rs2Inventory.hasItem(config.MeatPouch().getClosedItemID())) { + Rs2Inventory.interact(config.MeatPouch().getClosedItemID(), "Open"); + sleep(400, 600); + } + if (Rs2Inventory.hasItem(config.MeatPouch().getOpenItemID())) { + Rs2Inventory.interact(config.MeatPouch().getOpenItemID(), "Empty"); + sleep(400, 600); + } + } + + // Withdraw knife + if (!Rs2Inventory.hasItem(ItemID.KNIFE)) { + Rs2Bank.withdrawOne(ItemID.KNIFE); + } + + // Withdraw chisel if fletching enabled + if (config.fletchAntlerBolts() && !Rs2Inventory.hasItem(ItemID.CHISEL)) { + Rs2Bank.withdrawOne(ItemID.CHISEL); + sleep(300, 600); + } + + // Ensure we have a teasing tool + if (!hasTeasingTool()) { + for (TeasingTool tool : TeasingTool.values()) { + if (tool.hasRequirements()) { + Rs2Bank.withdrawOne(tool.getItemId()); + sleep(600, 800); + if (Rs2Inventory.hasItem(tool.getItemId())) break; + } + } + } + + // Handle axe + getBestAxe(config); + + // Deposit loot + var loot = creature.getLootId(); + if (loot != null && loot > 0) { + Rs2Bank.depositAll(loot); + } + + // Deposit fur if banking + if (config.furHandling() == FurHandling.BANK && creature.getFurItemId() > 0) { + Rs2Bank.depositAll(creature.getFurItemId()); + } + + // Deposit antler bolts (they're the product, always bank them) + // TODO: deposit antler bolt item ID when known + + sleep(50, 1200); + + // Auto-eat until full + if (config.AutoEat()) { + while (!Rs2Player.isFullHealth()) { + if (!isRunning()) break; + Rs2Bank.withdrawOne(config.FoodToEatAtBank().getId()); + sleep(200, 400); + Rs2Inventory.interact(config.FoodToEatAtBank().getId(), "Eat"); + sleep(200, 400); + } + } + + if (Rs2Inventory.contains(config.FoodToEatAtBank().getId())) { + Rs2Bank.depositAll(config.FoodToEatAtBank().getId()); + } + + if (Rs2Bank.isOpen()) { + Rs2Bank.closeBank(); + sleep(600, 900); + } + hasDied = false; + forceBank = false; + } + + // ==================== AXE MANAGEMENT ==================== + + private void getBestAxe(PitFallTrapHunterConfig config) { + Axe bestAxeInInventory = getBestAxe(Rs2Inventory.items().collect(Collectors.toList()), config); + Axe bestAxeEquipped = getBestAxe(Rs2Equipment.items(), config); + Axe bestAxeInBank = getBestAxe(Rs2Bank.bankItems(), config); + + Axe bestAxeOverall = null; + if (bestAxeInInventory != null && (bestAxeOverall == null || bestAxeInInventory.getWoodcuttingLevel() > bestAxeOverall.getWoodcuttingLevel())) { + bestAxeOverall = bestAxeInInventory; + } + if (bestAxeEquipped != null && (bestAxeOverall == null || bestAxeEquipped.getWoodcuttingLevel() > bestAxeOverall.getWoodcuttingLevel())) { + bestAxeOverall = bestAxeEquipped; + } + if (bestAxeInBank != null && (bestAxeOverall == null || bestAxeInBank.getWoodcuttingLevel() > bestAxeOverall.getWoodcuttingLevel())) { + bestAxeOverall = bestAxeInBank; + } + + if (bestAxeOverall != null) { + if (config.axeInInventory()) { + if (bestAxeOverall == bestAxeEquipped && bestAxeInInventory == null) { + Rs2Equipment.unEquip(bestAxeOverall.getItemID()); + sleep(600, 800); + } else if (bestAxeOverall == bestAxeInBank && bestAxeInInventory == null) { + Rs2Bank.withdrawItem(bestAxeOverall.getItemID()); + sleep(600, 800); + } + } else { + if (bestAxeOverall == bestAxeInBank && bestAxeEquipped == null) { + Rs2Bank.withdrawItem(bestAxeOverall.getItemID()); + sleep(600, 800); + Rs2Inventory.interact(bestAxeOverall.getItemID(), "Wield"); + sleep(600, 800); + } else if (bestAxeOverall == bestAxeInInventory && bestAxeEquipped == null) { + Rs2Inventory.interact(bestAxeOverall.getItemID(), "Wield"); + } + } + } else { + Microbot.log("No suitable axe found in bank, inventory, or equipment"); + shutdown(); + } + } + + private Axe getBestAxe(List items, PitFallTrapHunterConfig config) { + Axe best = null; + for (Axe axe : Axe.values()) { + if (items.stream().noneMatch(i -> i.getName().toLowerCase().contains(axe.getItemName()))) continue; + if (axe.hasRequirements(config.axeInInventory())) { + if (best == null || axe.getWoodcuttingLevel() > best.getWoodcuttingLevel()) { + best = axe; + } + } + } + return best; + } + + // ==================== ANTIBAN ==================== + + private void applyAntiBanSettings() { + Rs2AntibanSettings.antibanEnabled = true; + Rs2AntibanSettings.usePlayStyle = true; + Rs2AntibanSettings.simulateFatigue = true; + Rs2AntibanSettings.simulateAttentionSpan = true; + Rs2AntibanSettings.behavioralVariability = true; + Rs2AntibanSettings.nonLinearIntervals = true; + Rs2AntibanSettings.naturalMouse = true; + Rs2AntibanSettings.simulateMistakes = true; + Rs2AntibanSettings.moveMouseOffScreen = true; + Rs2AntibanSettings.contextualVariability = true; + Rs2AntibanSettings.devDebug = false; + Rs2AntibanSettings.playSchedule = true; + Rs2AntibanSettings.actionCooldownChance = 0.1; + } + + @Override + public void shutdown() { + super.shutdown(); + creaturesCaught = 0; + state = PitFallState.IDLE; + activePit = null; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapHunting.java b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapHunting.java new file mode 100644 index 0000000000..50f7943cbc --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapHunting.java @@ -0,0 +1,83 @@ +package net.runelite.client.plugins.microbot.PitFallTrapHunter; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.runelite.api.coords.WorldPoint; + +import java.util.List; + +@Getter +@RequiredArgsConstructor +public enum PitFallTrapHunting { + + // TODO: Replace placeholder IDs with actual ObjectIDs, NpcIDs, and ItemIDs from in-game dev tools + // Pit locations: center, orientation, and ground object point per pit (up to 3 per creature) + SUNLIGHT_ANTELOPE( + "Sunlight Antelope", + "Sunlight antelope", + -1, // npcId — TODO: fill from game + -1, // pitObjectId — unset pit ground object (interact "Trap") + -1, // spikedPitObjectId — set pit (interact "Jump") + -1, // fullPitObjectId — pit with caught creature (interact "Check") + -1, // emptyPitObjectId — pit after loot collected (collapsed/reset state) + 72, // requiredHunterLevel + new WorldPoint(1600, 3000, 0), // huntingPoint — TODO: fill from game + List.of( + new PitLocation( + new WorldPoint(1600, 3002, 0), // pit 1 center — TODO + PitOrientation.EAST_WEST, + new WorldPoint(1600, 3002, 0) // pit 1 ground object — TODO + ), + new PitLocation( + new WorldPoint(1604, 3002, 0), // pit 2 center — TODO + PitOrientation.EAST_WEST, + new WorldPoint(1604, 3002, 0) // pit 2 ground object — TODO + ), + new PitLocation( + new WorldPoint(1608, 3002, 0), // pit 3 center — TODO + PitOrientation.NORTH_SOUTH, + new WorldPoint(1608, 3002, 0) // pit 3 ground object — TODO + ) + ), + List.of(-1, -1), // itemsToDrop — TODO: fill with big bones ID, fur ID etc. + -1, // furItemId — TODO: antelope fur item ID + -1, // antlerItemId — TODO: sunlight antler item ID + -1 // lootId — raw meat for banking (ItemID.HUNTING_ANTELOPESUN_MEAT) + ); + + private final String name; + private final String npcName; + private final int npcId; + private final int pitObjectId; + private final int spikedPitObjectId; + private final int fullPitObjectId; + private final int emptyPitObjectId; + private final int requiredHunterLevel; + private final WorldPoint huntingPoint; + private final List pitLocations; + private final List itemsToDrop; + private final int furItemId; + private final int antlerItemId; + private final Integer lootId; + + /** + * Find the pit location closest to the given world point. + */ + public PitLocation findNearestPit(WorldPoint point) { + PitLocation nearest = null; + int nearestDist = Integer.MAX_VALUE; + for (PitLocation pit : pitLocations) { + int dist = pit.distanceTo(point); + if (dist < nearestDist) { + nearestDist = dist; + nearest = pit; + } + } + return nearest; + } + + @Override + public String toString() { + return name; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapInventoryHandlerScript.java b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapInventoryHandlerScript.java new file mode 100644 index 0000000000..f48d134436 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitFallTrapInventoryHandlerScript.java @@ -0,0 +1,63 @@ +package net.runelite.client.plugins.microbot.PitFallTrapHunter; + +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.math.Rs2Random; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; + +import java.util.concurrent.TimeUnit; + +public class PitFallTrapInventoryHandlerScript extends Script { + private PitFallTrapHunterConfig config; + private PitFallTrapHunterScript script; + + public boolean run(PitFallTrapHunterConfig config, PitFallTrapHunterScript script) { + this.config = config; + this.script = script; + mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { + try { + if (!super.run()) return; + if (!Microbot.isLoggedIn()) return; + if (!this.isRunning()) return; + if (!script.isRunning()) return; + if (!script.forceDrop) { + if (Rs2Player.isMoving()) return; + if (Rs2Player.isAnimating()) return; + if (Rs2Inventory.count() > Rs2Random.between(8, 28)) { + cleanInventory(); + } + return; + } + cleanInventory(); + } catch (Exception ex) { + System.out.println("PitFallTrapInventoryHandlerScript: " + ex.getMessage()); + ex.printStackTrace(); + } + }, 0, 5000, TimeUnit.MILLISECONDS); + return true; + } + + private void cleanInventory() { + PitFallTrapHunting creature = config.pitFallTrapHunting(); + boolean keepFur = config.furHandling() == FurHandling.BANK; + boolean keepAntlers = config.fletchAntlerBolts(); + + Rs2Inventory.items().forEachOrdered(item -> { + int itemId = item.getId(); + + // Keep fur if banking, otherwise drop + if (keepFur && itemId == creature.getFurItemId()) return; + + // Keep antlers if fletching bolts is enabled + if (keepAntlers && itemId == creature.getAntlerItemId()) return; + + // Drop items in the creature's drop list + if (creature.getItemsToDrop().contains(itemId)) { + Rs2Inventory.interact(item, "Drop"); + sleep(600, 1200); + } + }); + script.forceDrop = false; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitLocation.java b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitLocation.java new file mode 100644 index 0000000000..c08a0df97f --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitLocation.java @@ -0,0 +1,70 @@ +package net.runelite.client.plugins.microbot.PitFallTrapHunter; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.runelite.api.coords.WorldPoint; + +/** + * Represents a single pitfall trap location with orientation-aware positioning. + *

+ * Pits are 8x2 (EAST_WEST) or 2x8 (NORTH_SOUTH). The clickable ground objects + * ("Trap"/"Jump") occupy the middle 4 tiles. + *

+ * For EAST_WEST pits: the NPC is north or south → player jumps across Y axis. + * For NORTH_SOUTH pits: the NPC is east or west → player jumps across X axis. + */ +@Getter +@RequiredArgsConstructor +public class PitLocation { + + public enum Side { + NORTH, SOUTH, EAST, WEST + } + + private final WorldPoint center; + private final PitOrientation orientation; + private final WorldPoint groundObjectPoint; + + /** + * Determines which side of this pit an NPC is on, based on the pit's orientation. + * For EAST_WEST pits (8x2): compares Y coords → NORTH or SOUTH. + * For NORTH_SOUTH pits (2x8): compares X coords → EAST or WEST. + */ + public Side getNpcSide(WorldPoint npcLocation) { + if (orientation == PitOrientation.EAST_WEST) { + // Pit is wide along X, narrow along Y → NPC is north or south + return npcLocation.getY() >= center.getY() ? Side.NORTH : Side.SOUTH; + } else { + // Pit is tall along Y, narrow along X → NPC is east or west + return npcLocation.getX() >= center.getX() ? Side.EAST : Side.WEST; + } + } + + /** + * Returns a WorldPoint on the given side of the pit where the player should + * stand to tease the NPC. The tile is adjacent to the pit edge on the NPC's + * side, roughly centered along the pit's long axis. + */ + public WorldPoint getTeasingTile(Side npcSide) { + int offsetDistance = 2; // tiles away from pit center toward the NPC's side + switch (npcSide) { + case NORTH: + return new WorldPoint(center.getX(), center.getY() + offsetDistance, center.getPlane()); + case SOUTH: + return new WorldPoint(center.getX(), center.getY() - offsetDistance, center.getPlane()); + case EAST: + return new WorldPoint(center.getX() + offsetDistance, center.getY(), center.getPlane()); + case WEST: + return new WorldPoint(center.getX() - offsetDistance, center.getY(), center.getPlane()); + default: + return center; + } + } + + /** + * Returns the distance from this pit's center to the given point. + */ + public int distanceTo(WorldPoint point) { + return center.distanceTo(point); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitOrientation.java b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitOrientation.java new file mode 100644 index 0000000000..5d4b26eb6f --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/PitOrientation.java @@ -0,0 +1,26 @@ +package net.runelite.client.plugins.microbot.PitFallTrapHunter; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +/** + * Orientation of a pitfall trap pit. + *

+ * EAST_WEST = pit is 8 tiles wide along X, 2 tiles tall along Y → player jumps north↔south. + * NORTH_SOUTH = pit is 2 tiles wide along X, 8 tiles tall along Y → player jumps east↔west. + */ +@Getter +@RequiredArgsConstructor +public enum PitOrientation { + EAST_WEST("East-West (8x2)", 8, 2), + NORTH_SOUTH("North-South (2x8)", 2, 8); + + private final String description; + private final int xSize; + private final int ySize; + + @Override + public String toString() { + return description; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/TeasingTool.java b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/TeasingTool.java new file mode 100644 index 0000000000..4db52f4dd0 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/PitFallTrapHunter/TeasingTool.java @@ -0,0 +1,23 @@ +package net.runelite.client.plugins.microbot.PitFallTrapHunter; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.runelite.api.Skill; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; + +@Getter +@RequiredArgsConstructor +public enum TeasingTool { + // TODO: Replace placeholder item IDs with actual values from game data + TEASING_STICK("teasing stick", 10029, 0), // ItemID for teasing stick + HUNTERS_SPEAR("hunter's spear", 29796, 0); // ItemID for hunter's spear — TODO: confirm attack level + + private final String itemName; + private final int itemId; + private final int attackLevel; + + public boolean hasRequirements() { + if (attackLevel <= 0) return true; + return Rs2Player.getSkillRequirement(Skill.ATTACK, attackLevel); + } +} From fa92713df3bc88d446c71c8e4b4df0286ebd540c Mon Sep 17 00:00:00 2001 From: Bender Date: Mon, 23 Mar 2026 21:13:05 -0700 Subject: [PATCH 18/24] Barrows --- .../aiofighter/cannon/CannonScript.java | 63 ++++++++++++++++--- 1 file changed, 56 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/cannon/CannonScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/cannon/CannonScript.java index 248e5c57e7..f84177ec4e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/cannon/CannonScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/cannon/CannonScript.java @@ -1,33 +1,82 @@ package net.runelite.client.plugins.microbot.aiofighter.cannon; +import net.runelite.api.TileObject; +import net.runelite.api.coords.WorldArea; +import net.runelite.client.plugins.cannon.CannonPlugin; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.aiofighter.AIOFighterConfig; import net.runelite.client.plugins.microbot.aiofighter.enums.State; +import net.runelite.client.plugins.microbot.util.Global; import net.runelite.client.plugins.microbot.util.gameobject.Rs2Cannon; +import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.math.Rs2Random; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; import java.util.concurrent.TimeUnit; public class CannonScript extends Script { + + private static final int CANNON_REFILL_AMOUNT = 5; + public boolean run(AIOFighterConfig config) { mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { try { if (!Microbot.isLoggedIn()) return; if (!super.run() || !config.toggleCannon()) return; - - if(config.state().equals(State.BANKING) || config.state().equals(State.WALKING)) + + if (config.state().equals(State.BANKING) || config.state().equals(State.WALKING)) + return; + + if (Rs2Cannon.repair()) return; - - if (Rs2Cannon.repair()) - return; - Rs2Cannon.refill(); - } catch(Exception ex) { + + refill(CANNON_REFILL_AMOUNT); + } catch (Exception ex) { Microbot.logStackTrace(this.getClass().getSimpleName(), ex); } }, 0, 2000, TimeUnit.MILLISECONDS); return true; } + private boolean refill(int cannonRefillAmount) { + if (!Rs2Inventory.hasItemAmount("cannonball", 15, true)) { + return false; + } + + int cannonBallsLeft = Microbot.getClientThread().runOnClientThreadOptional( + () -> Microbot.getClient().getVarpValue(3)).orElse(0); + + if (cannonBallsLeft > cannonRefillAmount) { + return false; + } + + Microbot.status = "Refilling Cannon"; + TileObject cannon = Rs2GameObject.findObject(new Integer[]{6, 43027}); + if (cannon == null) { + return false; + } + + WorldArea cannonLocation = new WorldArea( + cannon.getWorldLocation().getX() - 1, + cannon.getWorldLocation().getY() - 1, + 3, 3, + cannon.getWorldLocation().getPlane()); + + if (!cannonLocation.toWorldPoint().equals(CannonPlugin.getCannonPosition().toWorldPoint())) { + return false; + } + + Microbot.pauseAllScripts.compareAndSet(false, true); + Rs2Inventory.useItemOnObject(2, cannon.getId()); + Rs2Player.waitForWalking(5000); + Global.sleepUntil(() -> Microbot.getClientThread().runOnClientThreadOptional( + () -> Microbot.getClient().getVarpValue(3)).orElse(0) > Rs2Random.between(10, 15)); + Microbot.pauseAllScripts.compareAndSet(true, false); + return true; + } + @Override public void shutdown() { super.shutdown(); From f3a320186ecaf989cefb21f51d0e549012a42304 Mon Sep 17 00:00:00 2001 From: Bender Date: Thu, 26 Mar 2026 20:26:44 -0700 Subject: [PATCH 19/24] Barrows --- .../microbot/barrows/BarrowsConfig.java | 54 +++- .../microbot/barrows/BarrowsScript.java | 252 ++++++++++++------ 2 files changed, 230 insertions(+), 76 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsConfig.java b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsConfig.java index e3a01dad74..277ab77dfa 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsConfig.java @@ -137,7 +137,8 @@ default selectedToBarrowsTPMethod selectedToBarrowsTPMethod() { enum selectedToBarrowsTPMethod { Tablet(ItemID.TELETAB_BARROWS, "Barrows teleport"), - POH(ItemID.POH_TABLET_TELEPORTTOHOUSE, "Teleport to house"); + POH(ItemID.POH_TABLET_TELEPORTTOHOUSE, "Teleport to house"), + Walk(-1, "Walk to Barrows"); private final int id; private final String name; @@ -211,4 +212,55 @@ default boolean shouldPrayAgainstWeakerBrothers() { return true; } + @ConfigItem( + keyName = "rangeAhrim", + name = "Range Ahrim", + description = "Switch to ranged gear when fighting Ahrim", + position = 15 + ) + default boolean rangeAhrim() { + return false; + } + + @ConfigItem( + keyName = "rangeAhrimGear", + name = "Range Gear (Ahrim)", + description = "Comma-separated item names to equip for Ahrim (e.g. Magic shortbow,Leather body)", + position = 16 + ) + default String rangeAhrimGear() { + return ""; + } + + @ConfigItem( + keyName = "mageGearSwapBack", + name = "Mage Gear (swap back)", + description = "Comma-separated item names to re-equip after Ahrim (e.g. Staff of fire,Book of darkness)", + position = 17 + ) + default String mageGearSwapBack() { + return ""; + } + + @ConfigItem( + keyName = "eatAtHealthPercent", + name = "Eat at HP %", + description = "Eat food when health drops below this percentage", + position = 18 + ) + @Range(min = 10, max = 90) + default int eatAtHealthPercent() { + return 70; + } + + @ConfigItem( + keyName = "eatForSpace", + name = "Eat for inventory space", + description = "Eat food before looting chest to free up inventory slots", + position = 19 + ) + default boolean eatForSpace() { + return false; + } + } diff --git a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java index 848f14097e..5e51acf050 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java @@ -162,6 +162,13 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { } } + if(config.selectedToBarrowsTPMethod() == BarrowsConfig.selectedToBarrowsTPMethod.Walk) { + if (!inTunnels && !shouldBank && Rs2Player.getWorldLocation().distanceTo(new WorldPoint(3550, 3285, 0)) > 30) { + walkToBarrows(); + return; + } + } + if(!inTunnels && !shouldBank) { if(!BreakHandlerScript.lockState.get()){ @@ -260,15 +267,6 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { if (Rs2Player.getWorldLocation().getPlane() == 3) { Microbot.log("We're in the mound"); - if(config.shouldPrayAgainstWeakerBrothers()){ - activatePrayer(brother.getWhatToPray()); - } else { - if(!brother.getName().contains("Torag") && !brother.getName().contains("Guthan") && !brother.getName().contains("Verac")){ - activatePrayer(brother.getWhatToPray()); - } - } - - // we're in the mound, prayer is active Rs2TileObjectModel sarc = rs2TileObjectCache.query().withIds(20770,20720,20722,20771,20721,20772).nearest(); Rs2NpcModel currentBrother = null; Microbot.log("Found the Sarcophagus"); @@ -278,6 +276,14 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { if (sarc.click("Search")) { + // Activate prayer after clicking the sarcophagus + if(config.shouldPrayAgainstWeakerBrothers()){ + activatePrayer(brother.getWhatToPray()); + } else { + if(!brother.getName().contains("Torag") && !brother.getName().contains("Guthan") && !brother.getName().contains("Verac")){ + activatePrayer(brother.getWhatToPray()); + } + } sleepUntil(() -> Rs2Player.isMoving(), Rs2Random.between(1000, 3000)); sleepUntil(() -> !Rs2Player.isMoving() || Rs2Player.isInCombat(), Rs2Random.between(3000, 6000)); // the brother could take a second to spawn in. @@ -386,7 +392,7 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { stuckInTunsCheck(); solvePuzzle(); checkForAndFightBrother(config); - eatFood(); + eatFood(config); outOfSupplies(config); gainRP(config); lootChampionScroll(); @@ -403,6 +409,18 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { //chest ID: 20973 stopFutureWalker(); + // Eat food to free inventory space before looting + if(config.eatForSpace()) { + while(Rs2Inventory.getEmptySlots() < 4 && Rs2Inventory.contains(it -> it != null && it.isFood())) { + if(!super.isRunning()) break; + Rs2ItemModel food = Rs2Inventory.get(it -> it != null && it.isFood()); + if(food != null) { + Rs2Inventory.interact(food, "Eat"); + sleep(300, 600); + } + } + } + if(barrowsChest.click("Open")){ @@ -433,7 +451,7 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { } //we looted the chest time to reset - suppliesCheck(config); + suppliesCheck(config, true); if(shouldBank){ Microbot.log("We should bank."); @@ -447,6 +465,18 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { ChestsOpened++; WhoisTun = "Unknown"; inTunnels = false; + } else if(config.selectedToBarrowsTPMethod() == BarrowsConfig.selectedToBarrowsTPMethod.Walk) { + // Walk method - teleport to Ferox Enclave and bank every run + Microbot.log("Teleporting to Ferox Enclave via Ring of Dueling..."); + if(Rs2Equipment.interact(EquipmentInventorySlot.RING, "Ferox Enclave")){ + sleepUntil(() -> Rs2Player.isAnimating(), Rs2Random.between(2000, 4000)); + sleepUntil(() -> !Rs2Player.isAnimating(), Rs2Random.between(6000, 10000)); + sleepUntil(() -> Rs2Player.getWorldLocation().getY() < 9600 || Rs2Player.getWorldLocation().getY() > 9730, Rs2Random.between(6000, 10000)); + } + ChestsOpened++; + WhoisTun = "Unknown"; + inTunnels = false; + shouldBank = true; } else { Rs2Inventory.interact("Teleport to house", "Inside"); sleepUntil(() -> Rs2Player.getWorldLocation().getY() < 9600 || Rs2Player.getWorldLocation().getY() > 9730, Rs2Random.between(6000, 10000)); @@ -468,6 +498,8 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { stopFutureWalker(); //tele out outOfSupplies(config); + //use rejuv pool before banking + reJfount(); //walk to and open the bank Rs2Bank.walkToBankAndUseBank(BankLocation.FEROX_ENCLAVE); BreakHandlerScript.lockState.set(false); @@ -476,21 +508,31 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { int ourFoodsID = ourfood.getId(); String ourfoodsname = ourfood.getName(); - if(Rs2Inventory.isFull() || Rs2Inventory.contains(it->it!=null&&it.getName().contains("'s") || it.getName().contains("Coins"))){ - if(Rs2Inventory.contains(it->it!=null&&it.getName().contains("'s"))){ - Rs2ItemModel piece = Rs2Inventory.get(it->it!=null&&it.getName().contains("'s")); + if(Rs2Inventory.contains(it->it!=null&&it.getName().contains("'s") && !it.getName().contains("Ava's"))){ + Rs2ItemModel piece = Rs2Inventory.get(it->it!=null&&it.getName().contains("'s") && !it.getName().contains("Ava's")); - if(piece!=null){ - barrowsPieces.add(piece.getName()); - if(barrowsPieces.contains("Nothing yet.")){ - barrowsPieces.remove("Nothing yet."); - } + if(piece!=null){ + barrowsPieces.add(piece.getName()); + if(barrowsPieces.contains("Nothing yet.")){ + barrowsPieces.remove("Nothing yet."); } + } + } + java.util.List keepItems = new java.util.ArrayList<>(java.util.Arrays.asList( + neededRune, "Moonlight moth", "Moonlight moth mix (2)", "Teleport to house", "Spade", "Prayer potion(4)", "Prayer potion(3)", "Forgotten brew(4)", "Forgotten brew(3)", "Barrows teleport", + ourfoodsname)); + if(config.rangeAhrim() && config.rangeAhrimGear() != null && !config.rangeAhrimGear().trim().isEmpty()) { + for(String item : config.rangeAhrimGear().split("\\s*,\\s*")) { + if(!item.trim().isEmpty()) keepItems.add(item.trim()); } - Rs2Bank.depositAllExcept(neededRune, "Moonlight moth", "Moonlight moth mix (2)", "Teleport to house", "Spade", "Prayer potion(4)", "Prayer potion(3)", "Forgotten brew(4)", "Forgotten brew(3)", "Barrows teleport", - ourfoodsname); } + if(config.rangeAhrim() && config.mageGearSwapBack() != null && !config.mageGearSwapBack().trim().isEmpty()) { + for(String item : config.mageGearSwapBack().split("\\s*,\\s*")) { + if(!item.trim().isEmpty()) keepItems.add(item.trim()); + } + } + Rs2Bank.depositAllExcept(keepItems.toArray(new String[0])); int howtoBank = Rs2Random.between(0,100); if(!usingPoweredStaffs) { @@ -524,16 +566,16 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { howtoBank = Rs2Random.between(0,100); if(howtoBank<= 60){ - if(Rs2Inventory.count(config.prayerRestoreType().getPrayerRestoreTypeID()) < Rs2Random.between(config.minPrayerPots(),config.targetPrayerPots())){ + if(Rs2Inventory.count(config.prayerRestoreType().getPrayerRestoreTypeID()) < config.targetPrayerPots()){ if(Rs2Bank.getBankItem(config.prayerRestoreType().getPrayerRestoreTypeID())!=null){ if(Rs2Bank.getBankItem(config.prayerRestoreType().getPrayerRestoreTypeID()).getQuantity()>=config.targetPrayerPots()){ - int amt = ((Rs2Random.between(config.minPrayerPots(),config.targetPrayerPots())) - (Rs2Inventory.count(config.prayerRestoreType().getPrayerRestoreTypeID()))); + int amt = config.targetPrayerPots() - Rs2Inventory.count(config.prayerRestoreType().getPrayerRestoreTypeID()); if(amt <= 0){ amt = 1; } - Microbot.log("Withdrawing "+amt); + Microbot.log("Withdrawing "+amt+" prayer pots"); if(Rs2Bank.withdrawX(config.prayerRestoreType().getPrayerRestoreTypeID(), amt)){ - sleepUntil(()-> Rs2Inventory.count(config.prayerRestoreType().getPrayerRestoreTypeID()) > Rs2Random.between(4,8), Rs2Random.between(2000,4000)); + sleepUntil(()-> Rs2Inventory.count(config.prayerRestoreType().getPrayerRestoreTypeID()) >= config.targetPrayerPots(), Rs2Random.between(2000,4000)); } } else { Microbot.log("We're out of "+config.prayerRestoreType().getPrayerRestoreTypeID()+" need at least "+config.targetPrayerPots()+" stopping..."); @@ -565,21 +607,23 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { } } } - howtoBank = Rs2Random.between(0,100); - if(howtoBank<= 40){ - if(Rs2Inventory.get(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID())==null || Rs2Inventory.get(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID()).getQuantity() < Rs2Random.between(config.minBarrowsTeleports(),config.targetBarrowsTeleports())){ - if(Rs2Bank.getBankItem(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID())!=null){ - if(Rs2Bank.getBankItem(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID()).getQuantity()>=config.targetBarrowsTeleports()){ - if(Rs2Bank.withdrawX(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID(), Rs2Random.between(config.minBarrowsTeleports(),config.targetBarrowsTeleports()))){ - sleep(Rs2Random.between(300,750)); + if(config.selectedToBarrowsTPMethod() != BarrowsConfig.selectedToBarrowsTPMethod.Walk) { + howtoBank = Rs2Random.between(0,100); + if(howtoBank<= 40){ + if(Rs2Inventory.get(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID())==null || Rs2Inventory.get(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID()).getQuantity() < Rs2Random.between(config.minBarrowsTeleports(),config.targetBarrowsTeleports())){ + if(Rs2Bank.getBankItem(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID())!=null){ + if(Rs2Bank.getBankItem(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID()).getQuantity()>=config.targetBarrowsTeleports()){ + if(Rs2Bank.withdrawX(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID(), Rs2Random.between(config.minBarrowsTeleports(),config.targetBarrowsTeleports()))){ + sleep(Rs2Random.between(300,750)); + } + } else { + Microbot.log("We're out of "+config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID()+" need at least "+config.targetBarrowsTeleports()+" stopping..."); + super.shutdown(); } } else { Microbot.log("We're out of "+config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID()+" need at least "+config.targetBarrowsTeleports()+" stopping..."); super.shutdown(); } - } else { - Microbot.log("We're out of "+config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID()+" need at least "+config.targetBarrowsTeleports()+" stopping..."); - super.shutdown(); } } } @@ -589,13 +633,13 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { if(Rs2Inventory.count(ourFoodsID) < config.targetFoodAmount()){ if(Rs2Bank.getBankItem(ourFoodsID)!=null){ if(Rs2Bank.getBankItem(ourFoodsID).getQuantity()>=config.targetFoodAmount()){ - int amt = (Rs2Random.between(config.minFood(),config.targetFoodAmount()) - (Rs2Inventory.count(ourFoodsID))); + int amt = config.targetFoodAmount() - Rs2Inventory.count(ourFoodsID); if(amt <= 0){ amt = 1; } - Microbot.log("Withdrawing "+amt); + Microbot.log("Withdrawing "+amt+" food"); if(Rs2Bank.withdrawX(ourFoodsID, amt)){ - sleepUntil(()-> Rs2Inventory.count(ourFoodsID) >= 10, Rs2Random.between(2000,4000)); + sleepUntil(()-> Rs2Inventory.count(ourFoodsID) >= config.targetFoodAmount(), Rs2Random.between(2000,4000)); } } else { Microbot.log("We're out of "+ourfoodsname+" need at least "+config.targetFoodAmount()+" stopping..."); @@ -644,7 +688,7 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { } } - suppliesCheck(config); + suppliesCheck(config, true); if(!shouldBank){ closeBank(); @@ -786,6 +830,7 @@ public void digIntoTheMound(Rs2WorldArea moundArea){ } public void goToTheMound(Rs2WorldArea moundArea){ + int moundAttempts = 0; while (!moundArea.contains(Rs2Player.getWorldLocation())) { checkForWorldMap(); int totalTiles = moundArea.toWorldPointList().size(); @@ -803,33 +848,45 @@ public void goToTheMound(Rs2WorldArea moundArea){ // We're not in the mound yet. randomMoundTile = moundArea.toWorldPointList().get(Rs2Random.between(0,(totalTiles-1))); - if(Rs2Walker.walkTo(randomMoundTile))sleepUntil(()-> !Rs2Player.isMoving(), Rs2Random.between(2000,4000)); + if(Rs2Player.getWorldLocation().distanceTo(randomMoundTile) < Rs2Random.between(5,12)){ + Rs2Walker.walkFastCanvas(randomMoundTile); + Rs2Player.waitForWalking(); + } else { + Rs2Walker.walkTo(randomMoundTile); + Rs2Player.waitForWalking(); + } if (moundArea.contains(Rs2Player.getWorldLocation())) { if(!Rs2Player.isMoving()) break; } else { - Microbot.log("At the mound, but we can't dig yet."); - randomMoundTile = moundArea.toWorldPointList().get(Rs2Random.between(0,(totalTiles-1))); + moundAttempts++; + Microbot.log("At the mound, but we can't dig yet. Attempt: " + moundAttempts); //strange old man body blocking us - net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel strangeOldMan = rs2NpcCache.query().withName("Strange Old Man").nearest(); - if(strangeOldMan !=null){ - if(strangeOldMan.getWorldLocation() != null){ - if(strangeOldMan.getWorldLocation() == randomMoundTile){ - while(strangeOldMan.getWorldLocation() == randomMoundTile){ - if(!super.isRunning()){break;} - randomMoundTile = moundArea.toWorldPointList().get(Rs2Random.between(0,(totalTiles-1))); - sleep(250,500); - } - } + // Pick a new random tile, avoiding the Strange Old Man's tile + randomMoundTile = moundArea.toWorldPointList().get(Rs2Random.between(0,(totalTiles-1))); + if(strangeOldMan != null && strangeOldMan.getWorldLocation() != null){ + int tileRetries = 0; + while(strangeOldMan.getWorldLocation().equals(randomMoundTile) && tileRetries < 10){ + if(!super.isRunning()){break;} + randomMoundTile = moundArea.toWorldPointList().get(Rs2Random.between(0,(totalTiles-1))); + tileRetries++; + sleep(250,500); } } Rs2Walker.walkCanvas(randomMoundTile); - sleepUntil(()-> !Rs2Player.isMoving(), Rs2Random.between(2000,4000)); + Rs2Player.waitForWalking(); + + if(moundAttempts >= 10){ + Microbot.log("Stuck trying to reach mound, using webwalker."); + Rs2Walker.walkTo(moundArea.toWorldPointList().get(Rs2Random.between(0,(totalTiles-1)))); + Rs2Player.waitForWalking(); + moundAttempts = 0; + } } } } @@ -900,7 +957,7 @@ public void gainRP(BarrowsConfig config){ stopFutureWalker(); sleep(750,1500); - eatFood(); + eatFood(config); outOfSupplies(config); antiPatternDropVials(); @@ -940,6 +997,10 @@ public void gainRP(BarrowsConfig config){ } public void suppliesCheck(BarrowsConfig config){ + suppliesCheck(config, false); + } + + public void suppliesCheck(BarrowsConfig config, boolean postLoot){ if(!usingPoweredStaffs) { if (Rs2Inventory.get(neededRune) == null || Rs2Inventory.get(neededRune).getQuantity() <= minRuneAmt) { Microbot.log("We have less than 180 " + neededRune); @@ -971,10 +1032,12 @@ public void suppliesCheck(BarrowsConfig config){ shouldBank = true; return; } - if ((Rs2Inventory.get(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID()) == null)) { - Microbot.log("We don't have a "+config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemName()); - shouldBank = true; - return; + if (config.selectedToBarrowsTPMethod() != BarrowsConfig.selectedToBarrowsTPMethod.Walk) { + if ((Rs2Inventory.get(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID()) == null)) { + Microbot.log("We don't have a "+config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemName()); + shouldBank = true; + return; + } } if (Rs2Inventory.count(it->it!=null&&it.getName().contains("Forgotten brew(")) < minForgottenBrews) { Microbot.log("We forgot our Forgotten brew."); @@ -982,19 +1045,22 @@ public void suppliesCheck(BarrowsConfig config){ return; } - String name = config.prayerRestoreType().getPrayerRestoreTypeName(); - if(name.contains("(")) name = config.prayerRestoreType().getPrayerRestoreTypeName().split("\\(")[0]; - String splitName = name; - if (Rs2Inventory.count(it->it!=null&&it.getName().toLowerCase().contains(splitName.toLowerCase())) < 1) { - Microbot.log("We don't have enough "+splitName); - shouldBank = true; - return; - } + // Only check prayer restores and run energy after looting the chest or during banking + if(postLoot) { + String name = config.prayerRestoreType().getPrayerRestoreTypeName(); + if(name.contains("(")) name = name.split("\\(")[0]; + String splitName = name; + if (Rs2Inventory.count(it->it!=null&&it.getName().toLowerCase().contains(splitName.toLowerCase())) < 1) { + Microbot.log("We don't have enough "+splitName); + shouldBank = true; + return; + } - if(Rs2Player.getRunEnergy() <= 5){ - Microbot.log("We need more run energy "); - shouldBank = true; - return; + if(Rs2Player.getRunEnergy() <= 5){ + Microbot.log("We need more run energy"); + shouldBank = true; + return; + } } shouldBank = false; @@ -1104,6 +1170,18 @@ public void antiPatternDropVials(){ } } } + private void equipGearFromInventory(String gearConfig) { + if (gearConfig == null || gearConfig.trim().isEmpty()) return; + String[] items = gearConfig.split("\\s*,\\s*"); + for (String item : items) { + item = item.trim(); + if (!item.isEmpty() && Rs2Inventory.contains(item)) { + Rs2Inventory.wield(item); + sleep(50, 150); + } + } + } + public void outOfSupplies(BarrowsConfig config){ suppliesCheck(config); // Needed because the walker won't teleport to the enclave while in the tunnels or in a barrow @@ -1117,7 +1195,7 @@ public void outOfSupplies(BarrowsConfig config){ } } public void disablePrayer(){ - if(Rs2Random.between(0,100) >= Rs2Random.between(0,5)) { + if(Rs2Random.between(0,100) >= Rs2Random.between(0,2)) { Rs2Prayer.disableAllPrayers(); sleep(0,750); } @@ -1180,10 +1258,16 @@ public void checkForAndFightBrother(BarrowsConfig config){ if (hintNpcModel() != null && currentBrother !=null) { stopFutureWalker(); - if(hintNpcModel().getName().contains("Ahrim")) neededprayer = Rs2PrayerEnum.PROTECT_MAGIC; + boolean isAhrim = hintNpcModel().getName().contains("Ahrim"); + if(isAhrim) neededprayer = Rs2PrayerEnum.PROTECT_MAGIC; if(hintNpcModel().getName().contains("Karil")) neededprayer = Rs2PrayerEnum.PROTECT_RANGE; + if(isAhrim && config.rangeAhrim()) { + Microbot.log("Switching to range gear for Ahrim..."); + equipGearFromInventory(config.rangeAhrimGear()); + } + while(hintNpcModel() != null){ Microbot.log("Fighting the brother."); @@ -1222,7 +1306,7 @@ public void checkForAndFightBrother(BarrowsConfig config){ sleep(750,1500); drinkPrayerPot(); - eatFood(); + eatFood(config); outOfSupplies(config); antiPatternDropVials(); drinkforgottonbrew(); @@ -1230,12 +1314,20 @@ public void checkForAndFightBrother(BarrowsConfig config){ if(hintNpcModel() == null) { Microbot.log("Breaking out the brother is null."); disablePrayer(); + if(isAhrim && config.rangeAhrim()) { + Microbot.log("Switching back to mage gear..."); + equipGearFromInventory(config.mageGearSwapBack()); + } break; } if(hintNpcModel().isDead()){ Microbot.log("Breaking out the brother is dead."); disablePrayer(); + if(isAhrim && config.rangeAhrim()) { + Microbot.log("Switching back to mage gear..."); + equipGearFromInventory(config.mageGearSwapBack()); + } sleepUntil(()-> hintNpcModel() == null, Rs2Random.between(3000,6000)); break; } @@ -1244,6 +1336,16 @@ public void checkForAndFightBrother(BarrowsConfig config){ } } + public void walkToBarrows() { + WorldPoint barrowsLocation = new WorldPoint(3550, 3283, 0); + if (Rs2Player.getWorldLocation().distanceTo(barrowsLocation) > 10) { + Microbot.log("Walking to Barrows..."); + Rs2Walker.walkTo(barrowsLocation); + sleepUntil(() -> Rs2Player.getWorldLocation().distanceTo(barrowsLocation) <= 15, 120000); + sleepUntil(() -> !Rs2Player.isMoving(), Rs2Random.between(3000, 6000)); + } + } + public void stopFutureWalker(){ if(WalkToTheChestFuture!=null) { Rs2Walker.setTarget(null); @@ -1298,8 +1400,8 @@ public void drinkforgottonbrew() { } } - public void eatFood(){ - if(Rs2Player.getHealthPercentage() <= 60){ + public void eatFood(BarrowsConfig config){ + if(Rs2Player.getHealthPercentage() <= config.eatAtHealthPercent()){ if(Rs2Inventory.contains(it->it!=null&&it.isFood())){ Rs2ItemModel food = Rs2Inventory.get(it->it!=null&&it.isFood()); if(Rs2Inventory.interact(food, "Eat")){ From 3c8f0444b4681f0ecce5fd67a134c1991522e6ed Mon Sep 17 00:00:00 2001 From: Bender Date: Sat, 4 Apr 2026 11:07:24 -0700 Subject: [PATCH 20/24] Start to agents and skills --- .github/agents/plugin-review.agent.md | 115 +++++ .github/agents/script-updater.agent.md | 73 ++++ .github/skills/antiban/SKILL.md | 404 ++++++++++++++++++ .github/skills/build-plugin/SKILL.md | 82 ++++ .github/skills/rs2walker/SKILL.md | 328 ++++++++++++++ .../microbot/barrows/BarrowsPlugin.java | 2 +- .../microbot/barrows/BarrowsScript.java | 153 +++---- 7 files changed, 1074 insertions(+), 83 deletions(-) create mode 100644 .github/agents/plugin-review.agent.md create mode 100644 .github/agents/script-updater.agent.md create mode 100644 .github/skills/antiban/SKILL.md create mode 100644 .github/skills/build-plugin/SKILL.md create mode 100644 .github/skills/rs2walker/SKILL.md diff --git a/.github/agents/plugin-review.agent.md b/.github/agents/plugin-review.agent.md new file mode 100644 index 0000000000..b9a0f19101 --- /dev/null +++ b/.github/agents/plugin-review.agent.md @@ -0,0 +1,115 @@ +--- +description: "Audit a Microbot Hub plugin for best practices, completeness, and common issues. USE FOR: reviewing plugin code quality, checking descriptor fields, finding client thread violations, verifying version consistency, checking documentation, assessing antiban integration." +tools: [read, search] +--- + +You are a Plugin Review specialist for the Microbot Hub project. Your job is to audit plugin code and produce a structured report of findings. You are **read-only** — never edit files. + +## What You Review + +For any plugin under `src/main/java/net/runelite/client/plugins/microbot//`, check the following categories in order. + +### 1. Plugin Descriptor Completeness (Critical) + +Read the `*Plugin.java` file and verify the `@PluginDescriptor` annotation has: + +**Required fields (fail if missing):** +- `name` — Should use a `PluginConstants` prefix (e.g., `PluginConstants.DEFAULT_PREFIX`, `PluginConstants.MOCROSOFT`) +- `version` — Must reference `PluginClassName.version`, NOT a hardcoded string +- `minClientVersion` — Must be present with a valid version string + +**Expected fields (warn if missing):** +- `authors` — Array of author names +- `description` — Brief plugin description +- `tags` — Array of search tags +- `iconUrl` — URL to icon image +- `cardUrl` — URL to card image +- `enabledByDefault` — Must be `PluginConstants.DEFAULT_ENABLED` (never `true`) +- `isExternal` — Must be `PluginConstants.IS_EXTERNAL` (never `false`) + +**Version field check:** +- Verify `static final String version = "X.Y.Z"` exists as a field in the plugin class +- Verify the descriptor's `version =` references it (e.g., `version = MyPlugin.version`) +- Verify the version follows semantic versioning (MAJOR.MINOR.PATCH) + +### 2. Client Thread Safety (High) + +Search the plugin's Script and other non-Plugin/non-Overlay files for these patterns that indicate client thread violations: + +**Unsafe outside `invoke()`:** +- `client.getWidget(` — Must use `Rs2Widget.getWidget()` or wrap in `Microbot.getClientThread().invoke()` +- `client.getVarbitValue(` — Must use `Microbot.getVarbitValue()` or wrap in `invoke()` +- `widget.isHidden()` — Must be on client thread +- `client.getLocalPlayer().getWorldView()` — Must be on client thread + +**Safe patterns (no issue):** +- Inside `@Subscribe` event handlers (these run on client thread) +- Inside `Microbot.getClientThread().invoke()` blocks +- Using utility wrappers: `Rs2Widget`, `Rs2Player`, `Rs2GameObject`, etc. + +### 3. Script Structure (Medium) + +Check the `*Script.java` file for: +- Extends `Script` class +- Has a proper main loop with `mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(...)` +- Uses `sleepUntilTrue()` or `sleep()` for waiting, not busy loops +- Handles exceptions in the main loop with try/catch + +### 4. Config & Injection (Medium) + +Check the `*Config.java` and `*Plugin.java` for: +- Config interface extends `Config` with `@ConfigGroup` annotation +- Plugin has a `@Provides` method for the config +- Uses `@Inject` for dependencies (config, overlays, scripts) +- Overlays registered in `startUp()` and unregistered in `shutDown()` + +### 5. Documentation (Low) + +Check for: +- `src/main/resources/net/runelite/client/plugins/microbot//docs/README.md` exists +- README has meaningful content (not just a title) +- Assets directory exists if `iconUrl`/`cardUrl` are specified + +### 6. Antiban Integration (Info) + +Check if the script has: +- `Rs2Antiban` setup (resetAntibanSettings, template, activity) +- Action cooldowns after interactions +- Gaussian delays instead of flat random +- Mouse/camera randomization +- Break handler integration + +Report whether antiban is integrated, partially integrated, or absent. + +## Output Format + +Produce a report with this structure: + +``` +## Plugin Review: +Version: | Authors: | Min Client: + +### 🔴 Critical Issues +- : + +### 🟡 Warnings +- : + +### 🟢 Good Practices +- + +### 📊 Antiban Status: +-

+ +### Summary + critical, warnings, info items +``` + +## Constraints + +- **DO NOT** edit any files — you are read-only +- **DO NOT** suggest code fixes inline — just report the issues +- **DO NOT** review build configuration or Gradle files +- **ONLY** review plugin source code and resources +- When checking client thread safety, account for `@Subscribe` handlers being safe +- Read the full Plugin, Script, Config, and Overlay files before making judgments diff --git a/.github/agents/script-updater.agent.md b/.github/agents/script-updater.agent.md new file mode 100644 index 0000000000..0f793a4eee --- /dev/null +++ b/.github/agents/script-updater.agent.md @@ -0,0 +1,73 @@ +--- +description: "Modify existing Microbot Hub plugin scripts — apply antiban patterns, fix bugs, add config options, update threading, improve behavior. USE FOR: updating script logic, adding Rs2Antiban integration, fixing client thread violations, adding config settings, refactoring script code." +tools: [read, edit, search, execute] +--- + +You are a Script Updater specialist for the Microbot Hub project. Your job is to modify existing plugin scripts based on user requests while following project conventions. + +## Before Making Changes + +1. **Read the full plugin** — Read all Java files in the plugin's directory before editing +2. **Understand the script's state machine** — Identify the states, transitions, and main loop structure +3. **Check existing antiban** — See if Rs2Antiban is already configured; don't duplicate +4. **Note the current version** — You MUST increment it after any code changes + +## Skills Available + +When performing specific tasks, load these skills for detailed guidance: + +- **Antiban patterns**: Read `.github/skills/antiban/SKILL.md` before adding human-like behavior, Rs2Antiban setup, mouse/camera randomization, Gaussian delays, or break systems +- **Building**: Read `.github/skills/build-plugin/SKILL.md` before compiling. Use `./gradlew build -PpluginList=` to verify changes compile + +## Common Tasks + +### Adding Antiban Integration + +1. Read the antiban skill file first +2. Add `configureAntibanSettings()` method using the appropriate setup template +3. Call it in the script's `run()` method before the main loop +4. Replace `sleep(random)` calls with `sleepGaussian(mean, stddev)` +5. Add `maybeNudgeMouse()` after game object interactions +6. Add `Rs2Antiban.actionCooldown()` gated behind `Rs2AntibanSettings.usePlayStyle` +7. Add camera movement scheduling in the main loop +8. Add overlay integration if the plugin has an overlay + +### Fixing Client Thread Violations + +Replace unsafe direct client calls with thread-safe alternatives: +- `client.getWidget(id)` → `Rs2Widget.getWidget(id)` or wrap in `Microbot.getClientThread().invoke()` +- `client.getVarbitValue(id)` → `Microbot.getVarbitValue(id)` or wrap in `invoke()` +- `client.getLocalPlayer().getWorldView()` → wrap in `invoke()` + +Note: Code inside `@Subscribe` handlers is already on the client thread and does NOT need wrapping. + +### Adding Config Options + +1. Add the `@ConfigItem` to the Config interface with proper section, position, keyName +2. Add a `@ConfigSection` if a new logical group is needed +3. Reference the config value in the script via the injected config instance +4. Use descriptive HTML in `description` for complex settings + +### Updating Script Logic + +- Maintain the existing state machine pattern +- Use `changeState()` for state transitions (don't set `state` directly if a helper exists) +- Use `setLockState()` / unlock patterns if the script uses state locking +- Add proper logging with `Microbot.log()` + +## After Making Changes + +1. **Increment the version** — Update `static final String version` in the Plugin class (bump PATCH for fixes, MINOR for features, MAJOR for breaking changes) +2. **Build the plugin** — Run `./gradlew build -PpluginList=` to verify compilation +3. **Report changes** — Summarize what was modified and the new version number + +## Constraints + +- **DO NOT** create new plugins from scratch — only modify existing ones +- **DO NOT** change the plugin's `name` or `@PluginDescriptor` name field +- **DO NOT** remove existing functionality unless explicitly asked +- **DO NOT** add features, refactor code, or make improvements beyond what was requested +- **DO NOT** modify `PluginConstants.java` unless adding a new author prefix +- **ALWAYS** increment the version after any code change +- **ALWAYS** build after changes to verify compilation +- **ALWAYS** use Gaussian delays (`sleepGaussian`) instead of flat random delays when adding timing diff --git a/.github/skills/antiban/SKILL.md b/.github/skills/antiban/SKILL.md new file mode 100644 index 0000000000..177c1c57fe --- /dev/null +++ b/.github/skills/antiban/SKILL.md @@ -0,0 +1,404 @@ +--- +name: antiban +description: "Antiban integration patterns for Microbot Hub plugin scripts. USE FOR: adding human-like behavior to scripts, configuring Rs2Antiban and Rs2AntibanSettings, implementing mouse/camera randomization, break systems, action cooldowns, Gaussian delays, and natural timing. DO NOT USE FOR: creating new plugins from scratch, build issues, publishing." +--- + +# Antiban Skill + +Guidelines for implementing human-like antiban behavior in Microbot Hub scripts, inspired by the MKE Wintertodt AI plugin's approach. + +## Core Antiban System: Rs2Antiban + +The Microbot client provides `Rs2Antiban` and `Rs2AntibanSettings` as the foundation. Always configure these in your script's initialization method. + +### Setup Template + +```java +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; + +private void configureAntibanSettings() { + // 1. Reset to defaults first + Rs2Antiban.resetAntibanSettings(); + + // 2. Apply a setup template that matches your activity + // Available templates (pick the closest match): + // - applyFiremakingSetup() + // - applyMiningSetup() + // - applyFishingSetup() + // - applyCookingSetup() + // - applyWoodcuttingSetup() + // - applyGeneralBasicSetup() + Rs2Antiban.antibanSetupTemplates.applyFiremakingSetup(); + + // 3. Set the activity type for behavior profiling + Rs2Antiban.setActivity(Activity.GENERAL_FIREMAKING); + + // 4. Override specific settings for your script's needs + Rs2AntibanSettings.takeMicroBreaks = false; // Disable if using custom break system + Rs2AntibanSettings.microBreakChance = 0.0; + Rs2AntibanSettings.actionCooldownChance = 0.15; // 15% chance per action + Rs2AntibanSettings.moveMouseRandomly = true; + Rs2AntibanSettings.moveMouseRandomlyChance = 0.36; + Rs2AntibanSettings.moveMouseOffScreen = true; + Rs2AntibanSettings.moveMouseOffScreenChance = 0.38; + Rs2AntibanSettings.naturalMouse = true; + Rs2AntibanSettings.simulateMistakes = true; + Rs2AntibanSettings.simulateFatigue = true; + + // 5. Set activity intensity (LOW, MODERATE, HIGH) + Rs2Antiban.setActivityIntensity(ActivityIntensity.HIGH); +} +``` + +### Action Cooldown Integration + +After performing an action (clicking an object, using an item), conditionally trigger an action cooldown: + +```java +if (Rs2AntibanSettings.usePlayStyle) { + Rs2Antiban.actionCooldown(); +} +``` + +Check if a cooldown is active before proceeding with logic: + +```java +if (!Rs2AntibanSettings.actionCooldownActive) { + // Safe to perform next action +} +``` + +### Break Handler Integration + +Respect the break handler lock state in your main loop: + +```java +private boolean shouldPauseForBreaks() { + // Check Rs2Antiban cooldown + if (Rs2AntibanSettings.actionCooldownActive) { + return true; + } + // Check break handler + if (BreakHandlerScript.isLockState()) { + return true; + } + return false; +} +``` + +### Overlay Integration + +Show antiban status in your overlay: + +```java +if (config.showAntibanOverlay() + && Rs2AntibanSettings.antibanEnabled + && Rs2Antiban.getActivity() != null) { + Rs2Antiban.renderAntibanOverlayComponents(panelComponent); +} +``` + +--- + +## Human-Like Timing + +### Gaussian Delays (preferred over uniform random) + +Use `sleepGaussian(mean, stddev)` instead of flat `sleep(random)`. Gaussian distributions create a natural bell curve where most delays cluster near the mean with occasional outliers. + +```java +// Standard action delay: most delays around 1600ms, occasionally 1200-2000ms +sleepGaussian(1600, 400); + +// Quick reaction delay: most around 200ms +sleepGaussian(200, 150); + +// Post-action cooldown: centered on 1200ms +sleepGaussian(1200, 300); +``` + +### Variable Base Delays + +Add randomized delays after actions using both a base and a variation: + +```java +int baseDelay = 150; +int variation = 50 + random.nextInt(100); +sleepGaussian(baseDelay, variation); +``` + +--- + +## Mouse Behavior + +### Mouse Nudge After Actions + +After interacting with game objects, occasionally perform a small mouse wobble. This simulates natural hand jitter: + +```java +private void maybeNudgeMouse() { + // Probability: 18% base + Gaussian component (5-40% effective range) + int moveChance = (int) Math.round(18 + Math.abs(random.nextGaussian()) * 10); + moveChance = Math.max(5, Math.min(40, moveChance)); + if (random.nextInt(100) >= moveChance) return; + + Point start = Microbot.getMouse().getMousePosition(); + if (start == null) return; + + // First micro-movement: σ ≈ 20px, capped ±80px + int dx = (int) Math.max(-80, Math.min(80, random.nextGaussian() * 20)); + int dy = (int) Math.max(-80, Math.min(80, random.nextGaussian() * 20)); + Microbot.getMouse().move(start.x + dx, start.y + dy); + + // 50% chance of a follow-up wobble + if (random.nextBoolean()) { + sleepGaussian(30, 15); + int dx2 = (int) Math.max(-30, Math.min(30, random.nextGaussian() * 10)); + int dy2 = (int) Math.max(-30, Math.min(30, random.nextGaussian() * 10)); + Microbot.getMouse().move(start.x + dx + dx2, start.y + dy + dy2); + } +} +``` + +### Mouse Offscreen During Idle + +When the script is idle/waiting, move the mouse offscreen to simulate AFK: + +```java +// Move mouse offscreen with 85% probability of going to a natural edge position +Rs2Antiban.moveMouseOffScreen(85); +``` + +### Hover Before Action + +Before a game round or event starts, hover over the next interactive object slightly early (1-8 seconds before), like a real player anticipating: + +```java +// Calculate random hover time once per round +int hoverBeforeStartTime = Rs2Random.randomGaussian(1000, 8000); +hoverBeforeStartTime = Math.max(500, Math.min(8000, hoverBeforeStartTime)); + +// When time remaining reaches hover threshold +if (timeUntilStart > 0 && timeUntilStart <= hoverBeforeStartTime) { + Rs2GameObject.hoverOverObject(nextObject); +} +``` + +### Random Mouse Movements During Actions + +Periodically move the mouse to simulate checking the screen: + +```java +if (currentTime - lastMouseMovement > 15000 + random.nextInt(10000)) { + if (random.nextInt(100) < 15) { // 15% chance + if (currentBrazier != null && random.nextBoolean()) { + Rs2GameObject.hoverOverObject(currentBrazier); + } + lastMouseMovement = currentTime; + } +} +``` + +--- + +## Camera Behavior + +### Natural Camera Movements + +Simulate camera adjustments using keyboard holds (not instant teleports). Use Gaussian distributions for yaw/pitch deltas so small adjustments are common and large sweeps are rare: + +```java +private void performHumanLikeCameraMovement() { + // Yaw: μ=0°, σ=20° → 68% within ±20°, rarely >±60° + int yawDelta = (int) Math.round(random.nextGaussian() * 20); + yawDelta = Math.max(-75, Math.min(75, yawDelta)); + + int targetYaw = (Rs2Camera.getAngle() + yawDelta + 360) % 360; + + // Hold arrow key proportional to rotation needed + int degreesToMove = Math.abs(Rs2Camera.getAngleTo(targetYaw)); + int pressTime = 80 + (int) (degreesToMove * 2.2); + pressTime = (int) (pressTime * (0.8 + random.nextGaussian() * 0.1)); + + if (Rs2Camera.getAngleTo(targetYaw) > 0) { + Rs2Keyboard.keyHold(KeyEvent.VK_LEFT); + sleepGaussian(pressTime, (int) (pressTime * 0.15)); + Rs2Keyboard.keyRelease(KeyEvent.VK_LEFT); + } else { + Rs2Keyboard.keyHold(KeyEvent.VK_RIGHT); + sleepGaussian(pressTime, (int) (pressTime * 0.15)); + Rs2Keyboard.keyRelease(KeyEvent.VK_RIGHT); + } + + // 42% chance to also adjust pitch + if (random.nextInt(100) < 42) { + float deltaPct = (float) (random.nextGaussian() * 0.10); + float targetPitchPct = Math.max(0.30f, Math.min(0.90f, + Rs2Camera.cameraPitchPercentage() + deltaPct)); + Rs2Camera.adjustPitch(targetPitchPct); + } +} +``` + +### Camera Movement Scheduling + +Use a probability that increases over time since last movement, with a configurable minimum frequency: + +```java +private boolean shouldPerformCameraMovement(long currentTime) { + long cameraMovementMinDelay = config.cameraMovementFrequency() * 1000L; + if (cameraMovementMinDelay <= 0) return false; + + long timeSinceLastMove = currentTime - lastCameraMovement; + if (timeSinceLastMove < cameraMovementMinDelay) return false; + + // Probability increases over time + double baseChance = 0.001; + double timeMultiplier = Math.min(50.0, timeSinceLastMove / (double) cameraMovementMinDelay); + return random.nextDouble() <= baseChance * timeMultiplier; +} +``` + +--- + +## Custom Break System + +For scripts that need more control than the built-in break handler, implement a custom break manager with AFK and logout break types. + +### Break Types + +| Type | Duration | Behavior | +|------|----------|----------| +| AFK (Short) | 1-6 min | Mouse goes offscreen, player stays logged in | +| Logout (Long) | 5-40 min | Player logs out entirely | + +### Key Design Principles + +1. **Location-aware**: Only trigger breaks in safe locations (e.g., bank areas, safe spots) +2. **Configurable intervals**: Min/max break interval, min/max break duration +3. **Break type weighting**: Configurable chance for AFK vs logout (e.g., 60% AFK / 40% logout) +4. **Emergency override**: Always allow eating/healing even during breaks +5. **Reward-safe**: Never interrupt reward collection or banking flows +6. **Graceful timeout**: If a safe spot isn't reached within a time limit, walk to one + +### Config Pattern for Breaks + +```java +@ConfigSection(name = "Break System", position = 5) +String breakSection = "breaks"; + +@ConfigItem(section = breakSection) +default boolean enableCustomBreaks() { return true; } + +@ConfigItem(section = breakSection) +default int minBreakInterval() { return 20; } // minutes + +@ConfigItem(section = breakSection) +default int maxBreakInterval() { return 60; } // minutes + +@ConfigItem(section = breakSection) +default int logoutBreakChance() { return 40; } // % chance for logout vs AFK +``` + +### Config Pattern for Advanced Antiban + +```java +@ConfigSection(name = "Advanced Options", position = 6) +String advancedSection = "advanced"; + +@ConfigItem(section = advancedSection) +default boolean humanizedTiming() { return true; } + +@ConfigItem(section = advancedSection) +default boolean randomMouseMovements() { return true; } + +@ConfigItem(section = advancedSection, + description = "Higher = less frequent. 0 to disable.") +default int cameraMovementFrequency() { return 10; } // seconds + +@ConfigItem(section = advancedSection) +default boolean showAntibanOverlay() { return true; } +``` + +--- + +## Spam Clicking (Round Start Behavior) + +Simulate impatient clicking when a game event is about to begin — a common real-player behavior: + +```java +private boolean spamClickingActive = false; +private long spamClickEndTime = 0; +private long lastSpamClick = 0; + +// Activate spam clicking in a short window before event +private void startSpamClicking(GameObject target) { + spamClickingActive = true; + spamClickEndTime = System.currentTimeMillis() + Rs2Random.between(800, 2500); + spamClickTarget = target; +} + +// Execute during main loop — rapidly click target with variable intervals +private void executeSpamClicking() { + if (!spamClickingActive) return; + if (System.currentTimeMillis() > spamClickEndTime) { + spamClickingActive = false; + return; + } + long now = System.currentTimeMillis(); + if (now - lastSpamClick > 200 + random.nextInt(300)) { + Rs2GameObject.interact(spamClickTarget, "Use"); + lastSpamClick = now; + } +} +``` + +--- + +## Live Action Duration Tracking + +Track real action durations via XP drops and refine timing estimates using an exponential moving average (EMA). This makes the bot adapt to the player's actual performance: + +```java +// Track via @Subscribe or onStatChanged +public static void onStatChanged(StatChanged event) { + long now = System.currentTimeMillis(); + if (event.getSkill() == Skill.WOODCUTTING && lastWoodcuttingXpDropTime > 0) { + long duration = now - lastWoodcuttingXpDropTime; + if (duration > 600 && duration < 10000) { // sanity check + // EMA smoothing: 80% old, 20% new + avgChopMs = (avgChopMs * 0.8) + (duration * 0.2); + } + } + lastWoodcuttingXpDropTime = now; +} +``` + +--- + +## Checklist: Adding Antiban to a Script + +When updating or creating a script, apply these in order: + +1. **Initialize Rs2Antiban** — Call `configureAntibanSettings()` in your `run()` method before the main loop +2. **Gaussian delays** — Replace all `sleep(random)` calls with `sleepGaussian(mean, stddev)` +3. **Action cooldowns** — Add `Rs2Antiban.actionCooldown()` after object interactions (when `usePlayStyle` is enabled) +4. **Mouse nudge** — Call `maybeNudgeMouse()` after clicking game objects +5. **Camera movements** — Schedule `performHumanLikeCameraMovement()` in the main loop with time-based probability +6. **Mouse offscreen** — Move mouse offscreen during idle/waiting periods +7. **Hover pre-action** — Hover over the next target object before the action window opens +8. **Break system** — Implement or integrate breaks (AFK + logout) with safe-location checks +9. **Overlay** — Show antiban status via `Rs2Antiban.renderAntibanOverlayComponents()` +10. **Config options** — Expose humanized timing, mouse movements, camera frequency, and overlay toggle + +## Common Mistakes + +- **Using uniform `random.nextInt()` for delays** — Always prefer Gaussian distributions for natural bell-curve timing +- **Instant camera teleports** — Use keyboard holds (`keyHold`/`keyRelease`) to simulate real camera panning +- **Breaking during critical flows** — Never interrupt banking, reward collection, or combat healing +- **Forgetting to reset antiban on startup** — Always call `Rs2Antiban.resetAntibanSettings()` before applying templates +- **Hard-coded delay values** — Make timing configurable or at least add ±20% Gaussian variation +- **Not checking `actionCooldownActive`** — The script will double-act if you don't gate actions behind this check diff --git a/.github/skills/build-plugin/SKILL.md b/.github/skills/build-plugin/SKILL.md new file mode 100644 index 0000000000..52767735f1 --- /dev/null +++ b/.github/skills/build-plugin/SKILL.md @@ -0,0 +1,82 @@ +--- +name: build-plugin +description: "Build one or more Microbot Hub plugins using Gradle. USE FOR: compiling plugin JARs, building specific plugins by name, building all plugins, troubleshooting build failures. DO NOT USE FOR: creating new plugins, editing plugin code, publishing releases." +--- + +# Build Plugin Skill + +## Overview + +Microbot Hub uses Gradle with dynamic plugin discovery. Each plugin under `src/main/java/net/runelite/client/plugins/microbot//` that contains a `*Plugin.java` file is automatically discovered and gets its own source set, compile task, and shadow JAR. + +## Build Commands + +### Build a single plugin (fastest for iteration) + +```bash +./gradlew build -PpluginList= +``` + +The `` is the Java class name **without** `.java`. Examples: + +```bash +./gradlew build -PpluginList=GotrPlugin +./gradlew build -PpluginList=PestControlPlugin +./gradlew build -PpluginList=AutoMiningPlugin +``` + +### Build multiple specific plugins + +Comma-separate the plugin names (no spaces): + +```bash +./gradlew build -PpluginList=GotrPlugin,PestControlPlugin,AutoMiningPlugin +``` + +### Build all plugins + +```bash +./gradlew clean build +``` + +This scans every directory under the plugins source path and builds all discovered plugins. Takes significantly longer than targeted builds. + +### Other useful tasks + +| Command | Purpose | +|---------|---------| +| `./gradlew generatePluginsJson` | Generate `plugins.json` metadata with SHA256 hashes (requires JDK 11) | +| `./gradlew copyPluginDocs` | Copy plugin `docs/` folders to `public/docs/` | +| `./gradlew test` | Run all tests (test classes can access all plugin source sets) | +| `./gradlew validateJdkVersion` | Verify the correct JDK version is active | + +## Finding the plugin name + +The plugin name used with `-PpluginList` is the Java class name of the main plugin file (the one with `@PluginDescriptor`), without the `.java` extension. To find it: + +```bash +# List all discoverable plugin names +find src/main/java/net/runelite/client/plugins/microbot -name "*Plugin.java" -printf "%f\n" | sed 's/.java$//' +``` + +## Build output + +- Plugin JARs are written to `build/libs/-.jar` +- The version comes from the `static final String version` field in the Plugin class +- JARs are shadow JARs that include any dependencies listed in the plugin's `dependencies.txt` + +## Troubleshooting + +- **JDK version errors**: This project requires JDK 11 (Adoptium). Run `./gradlew validateJdkVersion`. +- **Plugin not found**: Ensure the directory contains a file matching `*Plugin.java` and the class has `@PluginDescriptor`. +- **Dependency issues**: Check `src/main/resources/net/runelite/client/plugins/microbot//dependencies.txt` for correct Maven coordinates. +- **Client JAR missing**: The build auto-downloads the Microbot client. Override with `-PmicrobotClientVersion=` or `-PmicrobotClientPath=/path/to/jar` for offline use. + +## Procedure + +When asked to build a plugin: + +1. Identify the plugin class name (e.g., `GotrPlugin`, `PestControlPlugin`) +2. Run `./gradlew build -PpluginList=` in the terminal +3. Verify the build succeeds and report the result +4. If building multiple plugins, comma-separate them in a single `-PpluginList` argument \ No newline at end of file diff --git a/.github/skills/rs2walker/SKILL.md b/.github/skills/rs2walker/SKILL.md new file mode 100644 index 0000000000..97ca2e794a --- /dev/null +++ b/.github/skills/rs2walker/SKILL.md @@ -0,0 +1,328 @@ +--- +name: rs2walker +description: "Walking, navigation, and tile visibility patterns for Microbot Hub scripts. USE FOR: choosing between walkTo/walkFastCanvas/walkCanvas/walkMiniMap, checking tile visibility with Rs2Camera.isTileOnScreen, ensuring NPCs and game objects are on-screen before interaction, camera adjustments, and canvas vs walker decision logic. DO NOT USE FOR: creating new plugins from scratch, build issues, publishing." +--- + +# Rs2Walker & Tile Visibility Skill + +Guidelines for walking, navigation, and on-screen tile validation in Microbot Hub scripts. + +## Walking Methods Overview + +Microbot provides several walking methods with different behaviors and use cases: + +| Method | How it works | When to use | Distance | +|--------|-------------|-------------|----------| +| `Rs2Walker.walkTo(WorldPoint)` | Full web walker with pathfinding | Long distances, cross-region travel | Any | +| `Rs2Walker.walkTo(WorldPoint, distance)` | Web walker, stops within `distance` tiles | Long distances with tolerance | Any | +| `Rs2Walker.walkFastCanvas(WorldPoint)` | Clicks directly on the game canvas tile | Short distances, tile must be visible | ≤ ~12 tiles | +| `Rs2Walker.walkCanvas(WorldPoint)` | Clicks on canvas, slower than walkFast | Short distances, tile must be visible | ≤ ~12 tiles | +| `Rs2Walker.walkMiniMap(WorldPoint)` | Clicks on minimap to walk | Medium distances, avoids pathing | ~5–15 tiles | +| `Rs2Walker.walkFastLocal(LocalPoint)` | Canvas click using a LocalPoint directly | When you already have a LocalPoint | ≤ ~12 tiles | + +### Key Differences + +- **`walkTo`**: Uses the full web walker with API pathfinding. Reliable over any distance but slower and more CPU-intensive. Best for traveling between areas. Always works regardless of camera angle. +- **`walkFastCanvas`**: Clicks directly on the game canvas at the target tile. Very fast and lightweight, but **the tile must be visible on-screen**. Fails silently if the tile is off-screen or behind camera. +- **`walkCanvas`**: Similar to `walkFastCanvas` but with additional movement handling. Same visibility requirement. +- **`walkMiniMap`**: Clicks on the minimap. Good middle ground—doesn't require the tile to be on-screen but has limited range (~15 tiles). Useful when the target is nearby but camera isn't facing it. + +## Tile Visibility Check: `Rs2Camera.isTileOnScreen` + +Before using canvas-based walking or before interacting with NPCs/game objects, always verify the target is actually visible on-screen. + +### Method Overloads + +`Rs2Camera.isTileOnScreen` accepts: + +- `LocalPoint` — most common, convert from WorldPoint if needed +- `TileObject` — for game objects directly (agility obstacles, furnaces, etc.) + +### Converting WorldPoint to LocalPoint + +```java +import net.runelite.api.coords.LocalPoint; + +LocalPoint localTile = LocalPoint.fromWorld( + Microbot.getClient().getTopLevelWorldView(), + worldPoint +); +// localTile will be null if the world point is not in the loaded scene +``` + +**Important**: Always null-check the result. `LocalPoint.fromWorld()` returns `null` if the world point is outside the currently loaded scene (too far away). + +### Basic Visibility Check Pattern + +```java +import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; +import net.runelite.api.coords.LocalPoint; + +LocalPoint localTile = LocalPoint.fromWorld( + Microbot.getClient().getTopLevelWorldView(), targetWorldPoint +); +if (localTile != null && Rs2Camera.isTileOnScreen(localTile)) { + // Tile is visible — safe to use canvas walking or interact +} else { + // Tile is NOT visible — use walkTo, walkMiniMap, or turnTo first +} +``` + +## Pattern 1: Canvas Walk with Visibility Fallback + +Use this when walking short-to-medium distances. Prefer canvas walking when the tile is visible and close, fall back to the web walker otherwise. + +```java +WorldPoint target = someWorldPoint; +int distance = Rs2Player.getWorldLocation().distanceTo(target); + +if (distance <= 12) { + LocalPoint localTile = LocalPoint.fromWorld( + Microbot.getClient().getTopLevelWorldView(), target + ); + if (localTile != null && Rs2Camera.isTileOnScreen(localTile)) { + Rs2Walker.walkFastCanvas(target); + Rs2Player.waitForWalking(); + } else { + Rs2Walker.walkTo(target); + Rs2Player.waitForWalking(); + } +} else { + Rs2Walker.walkTo(target); + Rs2Player.waitForWalking(); +} +``` + +**Why 12 tiles?** The game canvas renders roughly 12–14 tiles in any direction depending on camera zoom. Using 12 guarantees the tile is within render range. Using a random threshold (e.g., `Rs2Random.between(7,14)`) causes the decision to flip-flop across iterations at boundary distances — always use a fixed threshold. + +### Two-Phase Approach (Long then Short) + +For precise tile targeting (e.g., safe spots), use the web walker for long-range approach, then canvas for exact placement: + +```java +// Phase 1: Long-range approach with web walker +if (Rs2Player.distanceTo(target) > 15) { + Rs2Walker.walkTo(target, 0); + sleepUntil(() -> Rs2Player.distanceTo(target) <= 5, 30000); +} + +// Phase 2: Precise canvas click for exact tile +if (Rs2Player.distanceTo(target) > 0) { + Rs2Walker.walkFastCanvas(target); + sleepUntil(() -> Rs2Player.getWorldLocation().equals(target), 15000); +} +``` + +## Pattern 2: Ensure NPC/Object is On-Screen Before Interaction + +**Always check visibility before interacting with NPCs or game objects.** If the target is not on-screen, turn the camera to face it first. This prevents failed interactions and looks more human. + +### NPCs + +```java +Rs2NpcModel npc = rs2NpcCache.query().withName("Goblin").nearest(); +if (npc != null) { + if (!Rs2Camera.isTileOnScreen(npc.getLocalLocation())) { + Rs2Camera.turnTo(npc); + } + Rs2Npc.interact(npc, "Attack"); +} +``` + +For NPCs, the `Rs2Npc.validateInteractable()` helper combines the visibility check and camera turn in one call: + +```java +import static net.runelite.client.plugins.microbot.util.npc.Rs2Npc.validateInteractable; + +if (!Rs2Camera.isTileOnScreen(fishingspot.getLocalLocation())) { + validateInteractable(fishingspot); +} +Rs2Npc.interact(fishingspot); +``` + +### Game Objects + +```java +TileObject furnace = Rs2GameObject.findObjectById(furnaceId); +if (furnace == null) { + Rs2Walker.walkTo(furnaceLocation); + return; +} + +if (!Rs2Camera.isTileOnScreen(furnace.getLocalLocation())) { + Rs2Camera.turnTo(furnace.getLocalLocation()); + return; // Wait for camera turn to complete before clicking +} + +Rs2GameObject.interact(furnace, "Smelt"); +``` + +### TileObjectModel (Cache API) + +When using `Rs2TileObjectCache`, convert the world location to a LocalPoint: + +```java +Rs2TileObjectModel tree = rs2TileObjectCache.query().withName("Oak").nearest(); +if (tree != null) { + LocalPoint local = LocalPoint.fromWorld( + Microbot.getClient().getTopLevelWorldView(), tree.getWorldLocation() + ); + if (local != null && !Rs2Camera.isTileOnScreen(local)) { + Rs2Camera.turnTo(local); + } + tree.click("Chop down"); +} +``` + +## Pattern 3: Camera Turn for Off-Screen Targets + +When a target is within interaction range but the camera isn't facing it, use `Rs2Camera.turnTo()` instead of walking: + +```java +Rs2Camera.turnTo(LocalPoint localPoint); // Turn to a tile +Rs2Camera.turnTo(Rs2NpcModel npc); // Turn to an NPC +Rs2Camera.turnTo(TileObject gameObject); // Turn to a game object +``` + +After turning the camera, either return from the loop iteration (let the next tick handle interaction) or add a brief sleep: + +```java +if (!Rs2Camera.isTileOnScreen(target.getLocalLocation())) { + Rs2Camera.turnTo(target.getLocalLocation()); + return; // Let the next iteration attempt the interaction +} +``` + +## Pattern 4: GPU Plugin Extended Range + +When the GPU plugin is enabled, the visible range on canvas is extended. Some scripts check for this: + +```java +LocalPoint localPoint = LocalPoint.fromWorld( + Microbot.getClient().getTopLevelWorldView(), targetPoint +); +if (Rs2Camera.isTileOnScreen(localPoint) && Microbot.isPluginEnabled(GpuPlugin.class)) { + Rs2Walker.walkFastLocal(localPoint); +} else { + Rs2Walker.walkTo(targetPoint); +} +``` + +Only use this pattern if your plugin explicitly wants to take advantage of GPU draw distance. For general use, stick to the standard 12-tile threshold which works with or without GPU. + +## Common Mistakes + +### 1. Using canvas walking without visibility check + +**Bad:** +```java +Rs2Walker.walkFastCanvas(target); // May fail silently if off-screen +``` + +**Good:** +```java +LocalPoint local = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), target); +if (local != null && Rs2Camera.isTileOnScreen(local)) { + Rs2Walker.walkFastCanvas(target); +} else { + Rs2Walker.walkTo(target); +} +``` + +### 2. Randomizing the canvas vs walker distance threshold + +**Bad:** +```java +// Threshold flips between canvas and walker randomly each iteration +if (distance < Rs2Random.between(7, 14)) { + Rs2Walker.walkFastCanvas(target); +} else { + Rs2Walker.walkTo(target); +} +``` + +**Good:** +```java +// Deterministic threshold — no oscillation +if (distance <= 12) { + // ... visibility check, then canvas or fallback +} else { + Rs2Walker.walkTo(target); +} +``` + +### 3. Forgetting the null check on LocalPoint.fromWorld() + +**Bad:** +```java +LocalPoint local = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), target); +Rs2Camera.isTileOnScreen(local); // NPE if target is outside loaded scene +``` + +**Good:** +```java +LocalPoint local = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), target); +if (local != null && Rs2Camera.isTileOnScreen(local)) { + // safe +} +``` + +### 4. Interacting with an NPC/object without checking visibility + +**Bad:** +```java +Rs2Npc.interact(npc, "Attack"); // Might fail if NPC is behind camera +``` + +**Good:** +```java +if (!Rs2Camera.isTileOnScreen(npc.getLocalLocation())) { + Rs2Camera.turnTo(npc); +} +Rs2Npc.interact(npc, "Attack"); +``` + +### 5. Using walkTo for very short distances + +**Bad:** +```java +// Target is 3 tiles away — web walker is overkill +Rs2Walker.walkTo(target); +``` + +**Good:** +```java +// Target is 3 tiles away — use canvas for speed +Rs2Walker.walkFastCanvas(target); +``` + +## Required Imports + +```java +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.util.camera.Rs2Camera; +import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +``` + +## Quick Reference: Decision Tree + +``` +Need to walk to a target? +├── Distance > 15 tiles? +│ └── Use Rs2Walker.walkTo() +├── Distance 5-12 tiles? +│ ├── LocalPoint.fromWorld() returns non-null AND isTileOnScreen()? +│ │ └── Use Rs2Walker.walkFastCanvas() +│ └── Otherwise? +│ └── Use Rs2Walker.walkTo() or Rs2Walker.walkMiniMap() +└── Distance < 5 tiles? + └── Use Rs2Walker.walkFastCanvas() (almost always visible) + +Need to interact with NPC/object? +├── Is it on-screen? (Rs2Camera.isTileOnScreen) +│ └── Yes → interact directly +└── No → Rs2Camera.turnTo() first, then interact +``` diff --git a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsPlugin.java index 168940c873..401d8f4517 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsPlugin.java @@ -32,7 +32,7 @@ ) @Slf4j public class BarrowsPlugin extends Plugin { - public static final String version = "2.0.3"; + public static final String version = "2.1.0"; @Inject private BarrowsConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java index fe48a4ef4f..080ef98b94 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java @@ -2,6 +2,7 @@ import com.google.inject.Inject; import net.runelite.api.*; +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.Script; @@ -32,6 +33,7 @@ import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; +import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -94,10 +96,10 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { if (!inventorySetup.doesEquipmentMatch()) { while(!inventorySetup.doesEquipmentMatch()) { if(!super.isRunning()){ break; } - if (Rs2Bank.getNearestBank().getWorldPoint().distanceTo(Rs2Player.getWorldLocation()) > 6) { + if (Rs2Bank.getNearestBank().getWorldPoint().distanceTo(Rs2Player.getWorldLocation()) > 10) { Rs2Bank.walkToBank(); } - if (Rs2Bank.getNearestBank().getWorldPoint().distanceTo(Rs2Player.getWorldLocation()) <= 6) { + if (Rs2Bank.getNearestBank().getWorldPoint().distanceTo(Rs2Player.getWorldLocation()) <= 10) { inventorySetup.loadEquipment(); } } @@ -120,11 +122,13 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { } //powered staffs - if(Rs2Equipment.get(EquipmentInventorySlot.WEAPON).getName().contains("Trident of the") || - Rs2Equipment.get(EquipmentInventorySlot.WEAPON).getName().contains("Tumeken's") || - Rs2Equipment.get(EquipmentInventorySlot.WEAPON).getName().contains("sceptre") || - Rs2Equipment.get(EquipmentInventorySlot.WEAPON).getName().contains("Sanguinesti") || - Rs2Equipment.get(EquipmentInventorySlot.WEAPON).getName().contains("Crystal staff")) { + var equippedWeapon = Rs2Equipment.get(EquipmentInventorySlot.WEAPON); + String weaponName = equippedWeapon != null ? equippedWeapon.getName() : ""; + if(weaponName.contains("Trident of the") || + weaponName.contains("Tumeken's") || + weaponName.contains("sceptre") || + weaponName.contains("Sanguinesti") || + weaponName.contains("Crystal staff")) { usingPoweredStaffs = true; } else { usingPoweredStaffs = false; @@ -393,7 +397,7 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { solvePuzzle(); checkForAndFightBrother(config); eatFood(config); - outOfSupplies(config); + if(outOfSupplies(config)) return; gainRP(config); lootChampionScroll(); @@ -508,8 +512,8 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { int ourFoodsID = ourfood.getId(); String ourfoodsname = ourfood.getName(); - if(Rs2Inventory.contains(it->it!=null&&it.getName().contains("'s") && !it.getName().contains("Ava's"))){ - Rs2ItemModel piece = Rs2Inventory.get(it->it!=null&&it.getName().contains("'s") && !it.getName().contains("Ava's")); + if(Rs2Inventory.contains(it->it!=null&&it.getName().contains("'s") && !it.getName().contains("ava"))){ + Rs2ItemModel piece = Rs2Inventory.get(it->it!=null&&it.getName().contains("'s") && !it.getName().contains("ava")); if(piece!=null){ barrowsPieces.add(piece.getName()); @@ -534,27 +538,24 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { } Rs2Bank.depositAllExcept(keepItems.toArray(new String[0])); - int howtoBank = Rs2Random.between(0,100); if(!usingPoweredStaffs) { - if (howtoBank <= 40) { - if (Rs2Inventory.get(neededRune) == null || Rs2Inventory.get(neededRune).getQuantity() <= config.minRuneAmount()) { - if (Rs2Bank.getBankItem(neededRune) != null) { - if (Rs2Bank.getBankItem(neededRune).getQuantity() > config.minRuneAmount()) { - if (Rs2Bank.withdrawX(neededRune, Rs2Random.between(config.minRuneAmount(), Rs2Bank.getBankItem(neededRune).getQuantity()))) { - String therune = neededRune; - sleepUntil(() -> Rs2Inventory.get(therune).getQuantity() > config.minRuneAmount(), Rs2Random.between(2000, 4000)); - } + if (Rs2Inventory.get(neededRune) == null || Rs2Inventory.get(neededRune).getQuantity() <= config.minRuneAmount()) { + if (Rs2Bank.getBankItem(neededRune) != null) { + if (Rs2Bank.getBankItem(neededRune).getQuantity() > config.minRuneAmount()) { + if (Rs2Bank.withdrawX(neededRune, Rs2Random.between(config.minRuneAmount(), Rs2Bank.getBankItem(neededRune).getQuantity()))) { + String therune = neededRune; + sleepUntil(() -> Rs2Inventory.get(therune).getQuantity() > config.minRuneAmount(), Rs2Random.between(2000, 4000)); } - } else { - if(neededRune.equals("Wrath rune")){ - if(Rs2Bank.hasItem("Blood rune") && Rs2Bank.count("Blood rune") > config.minRuneAmount()){ - neededRune = "Blood rune"; - return; - } + } + } else { + if(neededRune.equals("Wrath rune")){ + if(Rs2Bank.hasItem("Blood rune") && Rs2Bank.count("Blood rune") > config.minRuneAmount()){ + neededRune = "Blood rune"; + return; } - Microbot.log("We're out of " + neededRune + "s. stopping..."); - super.shutdown(); } + Microbot.log("We're out of " + neededRune + "s. stopping..."); + super.shutdown(); } } } else { @@ -564,8 +565,6 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { } } - howtoBank = Rs2Random.between(0,100); - if(howtoBank<= 60){ if(Rs2Inventory.count(config.prayerRestoreType().getPrayerRestoreTypeID()) < config.targetPrayerPots()){ if(Rs2Bank.getBankItem(config.prayerRestoreType().getPrayerRestoreTypeID())!=null){ if(Rs2Bank.getBankItem(config.prayerRestoreType().getPrayerRestoreTypeID()).getQuantity()>=config.targetPrayerPots()){ @@ -583,10 +582,7 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { } } } - } - howtoBank = Rs2Random.between(0,100); - if(howtoBank<= 40){ if(config.minForgottenBrew() > 0) { if (Rs2Inventory.count("Forgotten brew(4)") + Rs2Inventory.count("Forgotten brew(3)") < Rs2Random.between(config.minForgottenBrew(), config.targetForgottenBrew())) { if (Rs2Bank.getBankItem("Forgotten brew(4)") != null) { @@ -606,29 +602,23 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { } } } - } if(config.selectedToBarrowsTPMethod() != BarrowsConfig.selectedToBarrowsTPMethod.Walk) { - howtoBank = Rs2Random.between(0,100); - if(howtoBank<= 40){ - if(Rs2Inventory.get(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID())==null || Rs2Inventory.get(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID()).getQuantity() < Rs2Random.between(config.minBarrowsTeleports(),config.targetBarrowsTeleports())){ - if(Rs2Bank.getBankItem(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID())!=null){ - if(Rs2Bank.getBankItem(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID()).getQuantity()>=config.targetBarrowsTeleports()){ - if(Rs2Bank.withdrawX(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID(), Rs2Random.between(config.minBarrowsTeleports(),config.targetBarrowsTeleports()))){ - sleep(Rs2Random.between(300,750)); - } - } else { - Microbot.log("We're out of "+config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID()+" need at least "+config.targetBarrowsTeleports()+" stopping..."); - super.shutdown(); + if(Rs2Inventory.get(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID())==null || Rs2Inventory.get(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID()).getQuantity() < config.minBarrowsTeleports()){ + if(Rs2Bank.getBankItem(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID())!=null){ + if(Rs2Bank.getBankItem(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID()).getQuantity()>=config.targetBarrowsTeleports()){ + if(Rs2Bank.withdrawX(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID(), Rs2Random.between(config.minBarrowsTeleports(),config.targetBarrowsTeleports()))){ + sleep(Rs2Random.between(300,750)); } } else { Microbot.log("We're out of "+config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID()+" need at least "+config.targetBarrowsTeleports()+" stopping..."); super.shutdown(); } + } else { + Microbot.log("We're out of "+config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID()+" need at least "+config.targetBarrowsTeleports()+" stopping..."); + super.shutdown(); } } } - howtoBank = Rs2Random.between(0,100); - if(howtoBank<= 40){ if(Rs2Inventory.count(ourFoodsID) < config.targetFoodAmount()){ if(Rs2Bank.getBankItem(ourFoodsID)!=null){ @@ -647,10 +637,6 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { } } } - } - - howtoBank = Rs2Random.between(0,100); - if(howtoBank<= 40){ if(!Rs2Inventory.contains("Spade")){ if(Rs2Bank.getBankItem("Spade")!=null){ if(Rs2Bank.getBankItem("Spade").getQuantity()>=1){ @@ -662,28 +648,22 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { } } } - } - howtoBank = Rs2Random.between(0,100); - if(howtoBank <= 40){ - if(Rs2Equipment.get(EquipmentInventorySlot.RING)!=null){ - // we have our ring do nothing - } else { - Microbot.log("Getting the ring of dueling"); - if(Rs2Bank.count(ItemID.RING_OF_DUELING8)>0){ - if(!Rs2Inventory.contains(ItemID.RING_OF_DUELING8)){ - if(Rs2Bank.withdrawX(ItemID.RING_OF_DUELING8, 1)){ - sleepUntil(()-> Rs2Inventory.contains(ItemID.RING_OF_DUELING8), Rs2Random.between(5000,15000)); - } + if(Rs2Equipment.get(EquipmentInventorySlot.RING)==null){ + Microbot.log("Getting the ring of dueling"); + if(Rs2Bank.count(ItemID.RING_OF_DUELING8)>0){ + if(!Rs2Inventory.contains(ItemID.RING_OF_DUELING8)){ + if(Rs2Bank.withdrawX(ItemID.RING_OF_DUELING8, 1)){ + sleepUntil(()-> Rs2Inventory.contains(ItemID.RING_OF_DUELING8), Rs2Random.between(5000,15000)); } - } else { - Microbot.log("Out of rings of dueling"); - super.shutdown(); } - if(Rs2Inventory.contains(ItemID.RING_OF_DUELING8)){ - if(Rs2Inventory.interact(ItemID.RING_OF_DUELING8, "Wear")){ - sleepUntil(()-> Rs2Equipment.get(EquipmentInventorySlot.RING).getName().contains("dueling"), Rs2Random.between(5000,15000)); - } + } else { + Microbot.log("Out of rings of dueling"); + super.shutdown(); + } + if(Rs2Inventory.contains(ItemID.RING_OF_DUELING8)){ + if(Rs2Inventory.interact(ItemID.RING_OF_DUELING8, "Wear")){ + sleepUntil(()-> Rs2Equipment.get(EquipmentInventorySlot.RING) != null && Rs2Equipment.get(EquipmentInventorySlot.RING).getName().contains("dueling"), Rs2Random.between(5000,15000)); } } } @@ -751,15 +731,16 @@ public void handlePOH(BarrowsConfig config){ } Rs2TileObjectModel regularPortal = rs2TileObjectCache.query().withIds(37603,37615,37591).nearest(); if(regularPortal != null){ - while(pohThing != null){ + while(rs2TileObjectCache.query().withId(4525).nearest() != null){ if(!super.isRunning()){break;} if(!Rs2Player.isMoving()){ if(regularPortal.click("Enter")){ sleepUntil(()-> Rs2Player.isMoving(), Rs2Random.between(2000,4000)); sleepUntil(()-> !Rs2Player.isMoving(), Rs2Random.between(10000,15000)); - sleepUntil(()-> rs2TileObjectCache.query().withIds(37603,37615,37591).nearest() == null, Rs2Random.between(10000,15000)); + sleepUntil(()-> rs2TileObjectCache.query().withId(4525).nearest() == null, Rs2Random.between(10000,15000)); } } + if(rs2TileObjectCache.query().withId(4525).nearest() == null) break; } } else { @@ -848,9 +829,15 @@ public void goToTheMound(Rs2WorldArea moundArea){ // We're not in the mound yet. randomMoundTile = moundArea.toWorldPointList().get(Rs2Random.between(0,(totalTiles-1))); - if(Rs2Player.getWorldLocation().distanceTo(randomMoundTile) < Rs2Random.between(7,14)){ - Rs2Walker.walkFastCanvas(randomMoundTile); - Rs2Player.waitForWalking(); + if(Rs2Player.getWorldLocation().distanceTo(randomMoundTile) <= 12){ + LocalPoint localTile = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), randomMoundTile); + if(localTile != null && Rs2Camera.isTileOnScreen(localTile)){ + Rs2Walker.walkFastCanvas(randomMoundTile); + Rs2Player.waitForWalking(); + } else { + Rs2Walker.walkTo(randomMoundTile); + Rs2Player.waitForWalking(); + } } else { Rs2Walker.walkTo(randomMoundTile); Rs2Player.waitForWalking(); @@ -1110,19 +1097,19 @@ public void gettheRune(){ } public void setAutoCast(){ - if(neededRune == "Wrath rune"){ + if("Wrath rune".equals(neededRune)){ if (Rs2Magic.getCurrentAutoCastSpell() != Rs2CombatSpells.WIND_SURGE) { Rs2Combat.setAutoCastSpell(Rs2CombatSpells.WIND_SURGE, false); } } - if(neededRune == "Blood rune"){ + if("Blood rune".equals(neededRune)){ if (Rs2Magic.getCurrentAutoCastSpell() != Rs2CombatSpells.WIND_WAVE) { Rs2Combat.setAutoCastSpell(Rs2CombatSpells.WIND_WAVE, false); } } - if(neededRune == "Death rune"){ + if("Death rune".equals(neededRune)){ if (Rs2Magic.getCurrentAutoCastSpell() != Rs2CombatSpells.WIND_BLAST) { Rs2Combat.setAutoCastSpell(Rs2CombatSpells.WIND_BLAST, false); } @@ -1164,7 +1151,7 @@ public void antiPatternActivatePrayer(){ } public void antiPatternDropVials(){ if(Rs2Random.between(0,100) <= Rs2Random.between(1,25)) { - Rs2ItemModel whatToDrop = Rs2Inventory.get(it->it!=null&&it.getName().contains("Vial")||it.getName().contains("Butterfly jar")); + Rs2ItemModel whatToDrop = Rs2Inventory.get(it->it!=null&&(it.getName().contains("Vial")||it.getName().contains("Butterfly jar"))); if(whatToDrop!=null) { if (Rs2Inventory.contains(whatToDrop.getName())) { if (Rs2Inventory.drop(whatToDrop.getName())) sleep(0, 750); @@ -1184,7 +1171,7 @@ private void equipGearFromInventory(String gearConfig) { } } - public void outOfSupplies(BarrowsConfig config){ + public boolean outOfSupplies(BarrowsConfig config){ suppliesCheck(config); // Needed because the walker won't teleport to the enclave while in the tunnels or in a barrow if(shouldBank && (inTunnels || Rs2Player.getWorldLocation().getPlane() == 3)){ @@ -1193,8 +1180,10 @@ public void outOfSupplies(BarrowsConfig config){ if(inTunnels) inTunnels=false; sleepUntil(() -> Rs2Player.isAnimating(), Rs2Random.between(2000, 4000)); sleepUntil(() -> !Rs2Player.isAnimating(), Rs2Random.between(6000, 10000)); + return true; } } + return shouldBank; } public void disablePrayer(){ if(Rs2Random.between(0,100) >= Rs2Random.between(0,2)) { @@ -1232,8 +1221,8 @@ public void drinkPrayerPot(){ if(!skipThePot) { if (Rs2Player.getBoostedSkillLevel(Skill.PRAYER) <= Rs2Random.between(8, 15)) { - if (Rs2Inventory.contains(it -> it != null && it.getName().contains("Prayer potion") || it.getName().contains("Moonlight moth"))) { - Rs2ItemModel prayerpotion = Rs2Inventory.get(it -> it != null && it.getName().contains("Prayer potion") || it.getName().contains("Moonlight moth")); + if (Rs2Inventory.contains(it -> it != null && (it.getName().contains("Prayer potion") || it.getName().contains("Moonlight moth")))) { + Rs2ItemModel prayerpotion = Rs2Inventory.get(it -> it != null && (it.getName().contains("Prayer potion") || it.getName().contains("Moonlight moth"))); String action = "Drink"; if (prayerpotion.getName().equals("Moonlight moth")) action = "Release"; @@ -1309,7 +1298,7 @@ public void checkForAndFightBrother(BarrowsConfig config){ sleep(750,1500); drinkPrayerPot(); eatFood(config); - outOfSupplies(config); + if(outOfSupplies(config)) break; antiPatternDropVials(); drinkforgottonbrew(); From 95cbfc99d64947dd684c51bf907a37cf44e02aa4 Mon Sep 17 00:00:00 2001 From: Bender Date: Sat, 4 Apr 2026 20:00:26 -0700 Subject: [PATCH 21/24] Barrows + Agents and Skills --- .github/agents/plugin-review.agent.md | 23 ++++- .github/agents/script-updater.agent.md | 9 ++ .github/skills/rs2walker/SKILL.md | 56 +++++++++++- .../microbot/barrows/BarrowsConfig.java | 42 +++++++-- .../microbot/barrows/BarrowsPlugin.java | 2 +- .../microbot/barrows/BarrowsScript.java | 90 ++++++++++--------- .../plugins/microbot/gotr/GotrPlugin.java | 2 +- .../plugins/microbot/gotr/GotrScript.java | 21 +++-- 8 files changed, 180 insertions(+), 65 deletions(-) diff --git a/.github/agents/plugin-review.agent.md b/.github/agents/plugin-review.agent.md index b9a0f19101..fac000de93 100644 --- a/.github/agents/plugin-review.agent.md +++ b/.github/agents/plugin-review.agent.md @@ -5,6 +5,13 @@ tools: [read, search] You are a Plugin Review specialist for the Microbot Hub project. Your job is to audit plugin code and produce a structured report of findings. You are **read-only** — never edit files. +## Skills Available + +Always load these skills for domain-specific review guidance: + +- **Walking & visibility**: Read `.github/skills/rs2walker/SKILL.md` to check for `walkFastCanvas` without visibility checks, inconsistent location APIs, missing `Rs2Camera.isTileOnScreen` before canvas clicks, and walker anti-patterns +- **Antiban patterns**: Read `.github/skills/antiban/SKILL.md` to assess antiban integration quality — Rs2Antiban setup, Gaussian delays, action cooldowns, mouse/camera randomization + ## What You Review For any plugin under `src/main/java/net/runelite/client/plugins/microbot//`, check the following categories in order. @@ -47,7 +54,15 @@ Search the plugin's Script and other non-Plugin/non-Overlay files for these patt - Inside `Microbot.getClientThread().invoke()` blocks - Using utility wrappers: `Rs2Widget`, `Rs2Player`, `Rs2GameObject`, etc. -### 3. Script Structure (Medium) +### 3. Walking & Visibility (High) + +Search the script for walking and interaction patterns using the walker skill as reference: +- `walkFastCanvas` or `walkCanvas` called without a prior `Rs2Camera.isTileOnScreen` check — these fail silently if the tile is off-screen +- Inconsistent location APIs — mixing `Rs2Player.getWorldLocation()` with `Microbot.getClientThread().invoke(() -> client.getLocalPlayer().getWorldLocation())` in similar methods +- Missing camera turns before game object/NPC interactions when the target may be off-screen +- Randomized canvas-vs-walker distance thresholds (e.g., `Rs2Random.between(7,14)`) — should use a fixed threshold like `<= 12` + +### 4. Script Structure (Medium) Check the `*Script.java` file for: - Extends `Script` class @@ -55,7 +70,7 @@ Check the `*Script.java` file for: - Uses `sleepUntilTrue()` or `sleep()` for waiting, not busy loops - Handles exceptions in the main loop with try/catch -### 4. Config & Injection (Medium) +### 5. Config & Injection (Medium) Check the `*Config.java` and `*Plugin.java` for: - Config interface extends `Config` with `@ConfigGroup` annotation @@ -63,14 +78,14 @@ Check the `*Config.java` and `*Plugin.java` for: - Uses `@Inject` for dependencies (config, overlays, scripts) - Overlays registered in `startUp()` and unregistered in `shutDown()` -### 5. Documentation (Low) +### 6. Documentation (Low) Check for: - `src/main/resources/net/runelite/client/plugins/microbot//docs/README.md` exists - README has meaningful content (not just a title) - Assets directory exists if `iconUrl`/`cardUrl` are specified -### 6. Antiban Integration (Info) +### 7. Antiban Integration (Info) Check if the script has: - `Rs2Antiban` setup (resetAntibanSettings, template, activity) diff --git a/.github/agents/script-updater.agent.md b/.github/agents/script-updater.agent.md index 0f793a4eee..2183db2a7d 100644 --- a/.github/agents/script-updater.agent.md +++ b/.github/agents/script-updater.agent.md @@ -17,6 +17,7 @@ You are a Script Updater specialist for the Microbot Hub project. Your job is to When performing specific tasks, load these skills for detailed guidance: - **Antiban patterns**: Read `.github/skills/antiban/SKILL.md` before adding human-like behavior, Rs2Antiban setup, mouse/camera randomization, Gaussian delays, or break systems +- **Walking & visibility**: Read `.github/skills/rs2walker/SKILL.md` before modifying any walking, navigation, or game object interaction code. Always verify `walkFastCanvas` has a visibility check, use consistent location APIs, and follow the canvas-vs-walker decision patterns - **Building**: Read `.github/skills/build-plugin/SKILL.md` before compiling. Use `./gradlew build -PpluginList=` to verify changes compile ## Common Tasks @@ -38,9 +39,17 @@ Replace unsafe direct client calls with thread-safe alternatives: - `client.getWidget(id)` → `Rs2Widget.getWidget(id)` or wrap in `Microbot.getClientThread().invoke()` - `client.getVarbitValue(id)` → `Microbot.getVarbitValue(id)` or wrap in `invoke()` - `client.getLocalPlayer().getWorldView()` → wrap in `invoke()` +- Location checks should use `Rs2Player.getWorldLocation()` consistently — don't mix it with `Microbot.getClientThread().invoke(() -> client.getLocalPlayer().getWorldLocation())` in similar methods Note: Code inside `@Subscribe` handlers is already on the client thread and does NOT need wrapping. +### Fixing Walking & Visibility Issues + +Read the walker skill before modifying walking code. Common fixes: +- `walkFastCanvas` without a visibility check → add `Rs2Camera.isTileOnScreen()` guard or replace with `Rs2Walker.walkTo()` +- Missing camera turn before interacting with off-screen game objects/NPCs → add `Rs2Camera.turnTo()` +- Randomized canvas-vs-walker threshold → use a fixed threshold (`<= 12` tiles) + ### Adding Config Options 1. Add the `@ConfigItem` to the Config interface with proper section, position, keyName diff --git a/.github/skills/rs2walker/SKILL.md b/.github/skills/rs2walker/SKILL.md index 97ca2e794a..9cf71ba2ad 100644 --- a/.github/skills/rs2walker/SKILL.md +++ b/.github/skills/rs2walker/SKILL.md @@ -296,6 +296,23 @@ Rs2Walker.walkTo(target); Rs2Walker.walkFastCanvas(target); ``` +### 6. Using `invoke()` for player location instead of `Rs2Player` + +`Rs2Player.getWorldLocation()` is already thread-safe. Wrapping `client.getLocalPlayer().getWorldLocation()` in `invoke()` is redundant and inconsistent with other location checks that use the utility wrapper. + +**Bad:** +```java +// Redundant invoke — Rs2Player already handles thread safety +int x = Microbot.getClientThread().invoke(() -> + Microbot.getClient().getLocalPlayer().getWorldLocation().getX()); +``` + +**Good:** +```java +// Use the thread-safe utility consistently +int x = Rs2Player.getWorldLocation().getX(); +``` + ## Required Imports ```java @@ -307,6 +324,36 @@ import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.player.Rs2Player; ``` +## Pattern 5: Interact with Game Object if Within Range, Otherwise Walk + +When a game object (chest, sarcophagus, bank booth, etc.) is within 12–14 tiles AND on-screen, interact directly instead of walking next to it first. The game client handles pathing to the object when you click it. If it's within range but off-screen, turn the camera first. If it's farther away, walk closer before interacting. + +```java +Rs2TileObjectModel chest = rs2TileObjectCache.query().withId(CHEST_ID).nearest(); +if (chest != null) { + int distance = chest.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()); + if (distance <= 14) { + // Within interaction range — ensure it's on-screen, then click + LocalPoint chestLocal = LocalPoint.fromWorld( + Microbot.getClient().getTopLevelWorldView(), chest.getWorldLocation() + ); + if (chestLocal != null && !Rs2Camera.isTileOnScreen(chestLocal)) { + Rs2Camera.turnTo(chestLocal); + sleep(300, 600); + } + chest.click("Open"); + sleepUntil(() -> Rs2Player.isMoving(), Rs2Random.between(1000, 3000)); + sleepUntil(() -> !Rs2Player.isMoving(), Rs2Random.between(3000, 6000)); + } else { + // Too far — walk closer first + Rs2Walker.walkTo(chest.getWorldLocation()); + Rs2Player.waitForWalking(); + } +} +``` + +**Why 14 tiles?** The game client allows clicking objects up to ~14 tiles away if they are visible on the canvas. Using `<= 14` as the upper threshold catches cases where the object is renderable but you'd otherwise waste time walking right next to it. Use `<= 12` for more conservative scripts. + ## Quick Reference: Decision Tree ``` @@ -322,7 +369,10 @@ Need to walk to a target? └── Use Rs2Walker.walkFastCanvas() (almost always visible) Need to interact with NPC/object? -├── Is it on-screen? (Rs2Camera.isTileOnScreen) -│ └── Yes → interact directly -└── No → Rs2Camera.turnTo() first, then interact +├── Distance ≤ 14 tiles? +│ ├── Is it on-screen? (Rs2Camera.isTileOnScreen) +│ │ └── Yes → interact directly (game handles pathing) +│ └── No → Rs2Camera.turnTo() first, then interact +└── Distance > 14 tiles? + └── Rs2Walker.walkTo() first, then interact when close ``` diff --git a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsConfig.java b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsConfig.java index 277ab77dfa..050d4595b9 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsConfig.java @@ -181,11 +181,37 @@ default int minBarrowsTeleports() { return 1; } + @ConfigItem( + keyName = "combatSpell", + name = "Combat Spell", + description = "Which wind spell to use (determines which runes to bring)", + position = 12 + ) + default CombatSpell combatSpell() { + return CombatSpell.WIND_SURGE; + } + + enum CombatSpell { + WIND_BLAST("Death rune"), + WIND_WAVE("Blood rune"), + WIND_SURGE("Wrath rune"); + + private final String runeName; + + CombatSpell(String runeName) { + this.runeName = runeName; + } + + public String getRuneName() { + return runeName; + } + } + @ConfigItem( keyName = "minRuneAmount", name = "Min Runes", description = "Minimum amount of runes before banking", - position = 12 + position = 13 ) @Range(min = 50, max = 1000) default int minRuneAmount() { @@ -196,7 +222,7 @@ default int minRuneAmount() { keyName = "shouldGainRP", name = "Aim for 86+% rewards potential", description = "Should we gain additional RP other than the barrows brothers?", - position = 13 + position = 14 ) default boolean shouldGainRP() { return false; @@ -206,7 +232,7 @@ default boolean shouldGainRP() { keyName = "shouldPrayAgainstWeakerBrothers", name = "Pray against Torag, Verac, and Guthans?", description = "Should we Pray against Torag, Verac, and Guthans?", - position = 14 + position = 15 ) default boolean shouldPrayAgainstWeakerBrothers() { return true; @@ -216,7 +242,7 @@ default boolean shouldPrayAgainstWeakerBrothers() { keyName = "rangeAhrim", name = "Range Ahrim", description = "Switch to ranged gear when fighting Ahrim", - position = 15 + position = 16 ) default boolean rangeAhrim() { return false; @@ -226,7 +252,7 @@ default boolean rangeAhrim() { keyName = "rangeAhrimGear", name = "Range Gear (Ahrim)", description = "Comma-separated item names to equip for Ahrim (e.g. Magic shortbow,Leather body)", - position = 16 + position = 17 ) default String rangeAhrimGear() { return ""; @@ -236,7 +262,7 @@ default String rangeAhrimGear() { keyName = "mageGearSwapBack", name = "Mage Gear (swap back)", description = "Comma-separated item names to re-equip after Ahrim (e.g. Staff of fire,Book of darkness)", - position = 17 + position = 18 ) default String mageGearSwapBack() { return ""; @@ -246,7 +272,7 @@ default String mageGearSwapBack() { keyName = "eatAtHealthPercent", name = "Eat at HP %", description = "Eat food when health drops below this percentage", - position = 18 + position = 19 ) @Range(min = 10, max = 90) default int eatAtHealthPercent() { @@ -257,7 +283,7 @@ default int eatAtHealthPercent() { keyName = "eatForSpace", name = "Eat for inventory space", description = "Eat food before looting chest to free up inventory slots", - position = 19 + position = 20 ) default boolean eatForSpace() { return false; diff --git a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsPlugin.java index 401d8f4517..c66008aed5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsPlugin.java @@ -32,7 +32,7 @@ ) @Slf4j public class BarrowsPlugin extends Plugin { - public static final String version = "2.1.0"; + public static final String version = "2.1.3"; @Inject private BarrowsConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java index 080ef98b94..17adad7a0d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java @@ -74,6 +74,8 @@ public class BarrowsScript extends Script { public static List barrowsPieces = new ArrayList<>(); private ScheduledFuture WalkToTheChestFuture; + private BarrowsConfig config; + @Inject Rs2NpcCache rs2NpcCache; @Inject Rs2TileItemCache rs2TileItemCache; @@ -83,6 +85,7 @@ public class BarrowsScript extends Script { public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { + this.config = config; Microbot.enableAutoRunOn = false; mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { try { @@ -132,7 +135,7 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { usingPoweredStaffs = true; } else { usingPoweredStaffs = false; - gettheRune(); + neededRune = config.combatSpell().getRuneName(); minRuneAmt = config.minRuneAmount(); if(!Rs2Magic.getSpellbook().equals(Rs2Spellbook.MODERN)){ swapTheSpellbook(); @@ -150,7 +153,9 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { } } - outOfSupplies(config); + if(!shouldBank) { + outOfSupplies(config); + } if(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID() == ItemID.TELEPORT_TO_HOUSE) { if (!inTunnels && !shouldBank && Rs2Player.getWorldLocation().distanceTo(new WorldPoint(3573, 3296, 0)) > 60) { @@ -169,6 +174,12 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { if(config.selectedToBarrowsTPMethod() == BarrowsConfig.selectedToBarrowsTPMethod.Walk) { if (!inTunnels && !shouldBank && Rs2Player.getWorldLocation().distanceTo(new WorldPoint(3550, 3285, 0)) > 30) { walkToBarrows(); + // Drop rotten food received from the swamp boat + while(Rs2Inventory.contains("Rotten food")) { + if(!super.isRunning()) break; + Rs2Inventory.drop("Rotten food"); + sleep(150, 350); + } return; } } @@ -278,9 +289,16 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { Microbot.log("Searching the Sarcophagus"); if (!super.isRunning()) break; + // Ensure sarcophagus is on-screen before clicking + LocalPoint sarcLocal = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), sarc.getWorldLocation()); + if (sarcLocal != null && !Rs2Camera.isTileOnScreen(sarcLocal)) { + Rs2Camera.turnTo(sarcLocal); + sleep(300, 600); + } if (sarc.click("Search")) { // Activate prayer after clicking the sarcophagus + sleepGaussian(400, 1200); if(config.shouldPrayAgainstWeakerBrothers()){ activatePrayer(brother.getWhatToPray()); } else { @@ -409,10 +427,11 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { Rs2TileObjectModel barrowsChest = rs2TileObjectCache.query().withId(20973).nearest(); if(barrowsChest != null && - (barrowsChest.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) < 5)){ + (barrowsChest.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) <= 6)){ //chest ID: 20973 stopFutureWalker(); + // Eat food to free inventory space before looting if(config.eatForSpace()) { while(Rs2Inventory.getEmptySlots() < 4 && Rs2Inventory.contains(it -> it != null && it.isFood())) { @@ -500,8 +519,6 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { if(!Rs2Bank.isOpen()){ //stop the walker stopFutureWalker(); - //tele out - outOfSupplies(config); //use rejuv pool before banking reJfount(); //walk to and open the bank @@ -512,10 +529,10 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { int ourFoodsID = ourfood.getId(); String ourfoodsname = ourfood.getName(); - if(Rs2Inventory.contains(it->it!=null&&it.getName().contains("'s") && !it.getName().contains("ava"))){ - Rs2ItemModel piece = Rs2Inventory.get(it->it!=null&&it.getName().contains("'s") && !it.getName().contains("ava")); + if(Rs2Inventory.contains(it->it!=null&&it.getName().contains("'s") && !it.getName().contains("Ava"))){ + Rs2ItemModel piece = Rs2Inventory.get(it->it!=null&&it.getName().contains("'s") && !it.getName().contains("Ava")); - if(piece!=null){ + if(piece != null){ barrowsPieces.add(piece.getName()); if(barrowsPieces.contains("Nothing yet.")){ barrowsPieces.remove("Nothing yet."); @@ -536,7 +553,7 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { if(!item.trim().isEmpty()) keepItems.add(item.trim()); } } - Rs2Bank.depositAllExcept(keepItems.toArray(new String[0])); + Rs2Bank.depositAll(); if(!usingPoweredStaffs) { if (Rs2Inventory.get(neededRune) == null || Rs2Inventory.get(neededRune).getQuantity() <= config.minRuneAmount()) { @@ -829,10 +846,14 @@ public void goToTheMound(Rs2WorldArea moundArea){ // We're not in the mound yet. randomMoundTile = moundArea.toWorldPointList().get(Rs2Random.between(0,(totalTiles-1))); - if(Rs2Player.getWorldLocation().distanceTo(randomMoundTile) <= 12){ + if(Rs2Player.getWorldLocation().distanceTo(randomMoundTile) <= Rs2Random.between(10,15)){ LocalPoint localTile = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), randomMoundTile); if(localTile != null && Rs2Camera.isTileOnScreen(localTile)){ - Rs2Walker.walkFastCanvas(randomMoundTile); + if (!Rs2Player.isMoving()) { + Rs2Walker.walkFastCanvas(randomMoundTile); + Rs2Player.waitForWalking(); + } + Rs2Player.waitForWalking(); } else { Rs2Walker.walkTo(randomMoundTile); @@ -1083,36 +1104,23 @@ public void swapTheSpellbook(){ } } - public void gettheRune(){ - if(!neededRune.equals("unknown")) return; - - neededRune = "unknown"; - int magicLvl = Rs2Player.getRealSkillLevel(Skill.MAGIC); - - if(magicLvl >= 41 && magicLvl < 62) neededRune = "Death rune"; - - if(magicLvl >= 62 && magicLvl < 81) neededRune = "Blood rune"; - - if(magicLvl >= 81) neededRune = "Wrath rune"; - } - public void setAutoCast(){ - if("Wrath rune".equals(neededRune)){ - if (Rs2Magic.getCurrentAutoCastSpell() != Rs2CombatSpells.WIND_SURGE) { - Rs2Combat.setAutoCastSpell(Rs2CombatSpells.WIND_SURGE, false); - } - } - - if("Blood rune".equals(neededRune)){ - if (Rs2Magic.getCurrentAutoCastSpell() != Rs2CombatSpells.WIND_WAVE) { - Rs2Combat.setAutoCastSpell(Rs2CombatSpells.WIND_WAVE, false); - } - } - - if("Death rune".equals(neededRune)){ - if (Rs2Magic.getCurrentAutoCastSpell() != Rs2CombatSpells.WIND_BLAST) { - Rs2Combat.setAutoCastSpell(Rs2CombatSpells.WIND_BLAST, false); - } + switch (config.combatSpell()) { + case WIND_SURGE: + if (Rs2Magic.getCurrentAutoCastSpell() != Rs2CombatSpells.WIND_SURGE) { + Rs2Combat.setAutoCastSpell(Rs2CombatSpells.WIND_SURGE, false); + } + break; + case WIND_WAVE: + if (Rs2Magic.getCurrentAutoCastSpell() != Rs2CombatSpells.WIND_WAVE) { + Rs2Combat.setAutoCastSpell(Rs2CombatSpells.WIND_WAVE, false); + } + break; + case WIND_BLAST: + if (Rs2Magic.getCurrentAutoCastSpell() != Rs2CombatSpells.WIND_BLAST) { + Rs2Combat.setAutoCastSpell(Rs2CombatSpells.WIND_BLAST, false); + } + break; } } @@ -1257,6 +1265,7 @@ public void checkForAndFightBrother(BarrowsConfig config){ if(isAhrim && config.rangeAhrim()) { Microbot.log("Switching to range gear for Ahrim..."); equipGearFromInventory(config.rangeAhrimGear()); + sleepGaussian(600, 850); } while(hintNpcModel() != null){ @@ -1353,6 +1362,7 @@ private void walkToChest(){ } Rs2Walker.walkTo(Chest, 2); + sleepGaussian(1200, 2200); } catch (Exception e) { Microbot.log("walkToChest failed: " + e.getMessage()); } 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 161a4c59a8..aea07c3223 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.1"; + public static final String version = "1.5.4"; @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 9ea03f6e9b..7ab05f071d 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 @@ -186,6 +186,10 @@ public boolean run(GotrConfig config) { if (craftGuardianEssences()) return; + // No fragments left but have essence – enter altar instead of getting stuck + if (!Rs2Inventory.hasItem(GUARDIAN_FRAGMENTS) && Rs2Inventory.hasItem(GUARDIAN_ESSENCE)) { + if (enterAltar()) return; + } } else if (Rs2Inventory.hasItem(GUARDIAN_ESSENCE)) { if (leaveLargeMine()) return; if (enterAltar()) return; @@ -217,7 +221,7 @@ public boolean run(GotrConfig config) { long endTime = System.currentTimeMillis(); totalTime = endTime - startTime; - System.out.println("Total time for loop " + totalTime); + log("Total time for loop " + totalTime + "ms"); } catch (Exception ex) { Microbot.log("Something went wrong in the GOTR Script: " + ex.getMessage() + ". If the script is stuck, please contact us on discord with this log."); @@ -320,9 +324,10 @@ private void takeUnchargedCells() { private boolean usePortal() { if (!isInHugeMine() && Microbot.getClient().hasHintArrow() && Rs2Inventory.count() < config.maxAmountEssence()) { if (leaveLargeMine()) return true; - Rs2Walker.walkFastCanvas(Microbot.getClient().getHintArrowPoint()); + WorldPoint portalPoint = Microbot.getClient().getHintArrowPoint(); + Rs2Walker.walkTo(portalPoint); sleepUntil(Rs2Player::isMoving); - Rs2GameObject.interact(Microbot.getClient().getHintArrowPoint()); + Rs2GameObject.interact(portalPoint); log("Found a portal spawn...interacting with it..."); Rs2Player.waitForWalking(); sleepUntil(() -> isInHugeMine()); @@ -618,16 +623,16 @@ public static boolean isOutsideBarrier() { && Rs2Player.getWorldLocation().getRegionID() == 14484; } - public static boolean isInLargeMine() { + public static boolean isInLargeMine() { int largeMineX = 3637; return Rs2Player.getWorldLocation().getRegionID() == 14484 - && Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation().getX()) >= largeMineX; + && Rs2Player.getWorldLocation().getX() >= largeMineX; } - public boolean isInHugeMine() { + public boolean isInHugeMine() { int hugeMineX = 3594; return Rs2Player.getWorldLocation().getRegionID() == 14484 - && Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation().getX()) <= hugeMineX; + && Rs2Player.getWorldLocation().getX() <= hugeMineX; } public static boolean isGuardianPortal(GameObject gameObject) { @@ -640,7 +645,7 @@ public ItemManager getItemManager() { public boolean isInMiniGame() { int parentWidgetId = 48889857; - Widget elementalRuneWidget = Microbot.getClient().getWidget(parentWidgetId); + Widget elementalRuneWidget = Rs2Widget.getWidget(parentWidgetId); return elementalRuneWidget != null; } From 4b1d24e0e6ea64403b1035b0c2edb27638a34790 Mon Sep 17 00:00:00 2001 From: Bender Date: Tue, 7 Apr 2026 09:09:03 -0700 Subject: [PATCH 22/24] Barrows --- .github/skills/banking-equipment/SKILL.md | 186 ++++++++++++++++++ .../microbot/barrows/BarrowsPlugin.java | 2 +- .../microbot/barrows/BarrowsScript.java | 61 +++--- 3 files changed, 214 insertions(+), 35 deletions(-) create mode 100644 .github/skills/banking-equipment/SKILL.md diff --git a/.github/skills/banking-equipment/SKILL.md b/.github/skills/banking-equipment/SKILL.md new file mode 100644 index 0000000000..56b8c7ee78 --- /dev/null +++ b/.github/skills/banking-equipment/SKILL.md @@ -0,0 +1,186 @@ +--- +name: banking-equipment +description: "Banking, equipment management, and inventory setup patterns for Microbot Hub plugin scripts. USE FOR: handling bank deposits/withdrawals, managing charged items (rings, staffs), preserving equipment during banking, using Rs2InventorySetup safely, handling gear swaps. DO NOT USE FOR: creating new plugins from scratch, build issues, publishing." +--- + +# Banking & Equipment Management Skill + +Guidelines for safe banking, equipment preservation, and charged item handling in Microbot Hub scripts. + +## Key Problems & Solutions + +### 1. Charged Items (Ring of Dueling, Amulet of Glory, etc.) + +Each charge level is a **different ItemID** in OSRS. `Ring of dueling(8)` ≠ `Ring of dueling(5)`. + +**NEVER** hardcode a single charge variant (e.g. `ItemID.RING_OF_DUELING8`). Use name-based matching: + +```java +// BAD - only finds fully charged rings +if (Rs2Bank.count(ItemID.RING_OF_DUELING8) > 0) { ... } + +// GOOD - finds any charge level +if (Rs2Bank.hasItem("Ring of dueling")) { ... } +``` + +**Withdrawing charged items:** +```java +// BAD +Rs2Bank.withdrawOne(ItemID.RING_OF_DUELING8); + +// GOOD - partial name match gets highest charge first (bank ordering) +Rs2Bank.withdrawOne("Ring of dueling"); +``` + +**Checking equipped charged items:** +```java +// BAD - misses other charge levels +if (Rs2Equipment.isWearing(ItemID.RING_OF_DUELING8)) { ... } + +// GOOD +Rs2ItemModel ring = Rs2Equipment.get(EquipmentInventorySlot.RING); +if (ring != null && ring.getName().contains("dueling")) { ... } +``` + +**Equipping from inventory:** +```java +Rs2ItemModel ring = Rs2Inventory.get(it -> it != null && it.getName().contains("Ring of dueling")); +if (ring != null) { + Rs2Inventory.interact(ring, "Wear"); + sleepUntil(() -> { + Rs2ItemModel equipped = Rs2Equipment.get(EquipmentInventorySlot.RING); + return equipped != null && equipped.getName().contains("dueling"); + }, Rs2Random.between(3000, 6000)); +} +``` + +### 2. Rs2InventorySetup and doesEquipmentMatch() + +`doesEquipmentMatch()` does exact ID comparison by default. A ring with fewer charges than the setup expects will cause it to return `false`, triggering `loadEquipment()` which calls `depositEquipment()` — stripping ALL worn gear. + +**Always add a retry limit and charged-item tolerance:** + +```java +if (!inventorySetup.doesEquipmentMatch()) { + // Check if the "mismatch" is just a charged item with different charges + boolean chargedItemOk = Rs2Equipment.get(EquipmentInventorySlot.RING) != null + && Rs2Equipment.get(EquipmentInventorySlot.RING).getName().contains("dueling"); + + int retries = 0; + while (!inventorySetup.doesEquipmentMatch() && retries < 3) { + if (!super.isRunning()) break; + inventorySetup.loadEquipment(); + retries++; + } + // Accept near-match if only charged items differ + if (!inventorySetup.doesEquipmentMatch() && chargedItemOk) { + Microbot.log("Equipment close enough (charged item charges differ) - continuing."); + } +} +``` + +### 3. Depositing Safely + +**`Rs2Bank.depositAll()`** deposits inventory only, NOT equipment. Equipment is deposited by `depositEquipment()` or indirectly by `loadEquipment()`. + +**Prefer `depositAllExcept()` when you have items to preserve:** + +```java +// Available overloads: +Rs2Bank.depositAllExcept(String... names); // partial name match +Rs2Bank.depositAllExcept(Integer... ids); // exact item IDs +Rs2Bank.depositAllExcept(Collection names); // collection of names +Rs2Bank.depositAllExcept(Predicate p); // custom logic +``` + +**Build a keep-list for items that should stay in inventory:** +```java +List keepItems = new ArrayList<>(Arrays.asList( + "Spade", "Prayer potion(4)", "Prayer potion(3)", + "Ring of dueling", foodName +)); +// Add conditional items +if (config.useBrews()) { + keepItems.add("Forgotten brew(4)"); + keepItems.add("Forgotten brew(3)"); +} +Rs2Bank.depositAllExcept(keepItems); +``` + +**MKE Wintertodt's per-slot approach (most precise but more complex):** +```java +// Deposit slot-by-slot, preserving specific items +for (int slot = 0; slot < 28; slot++) { + Rs2ItemModel item = Rs2Inventory.get(slot); + if (item != null && !shouldKeep(item)) { + Rs2Bank.depositOne(item.getId()); + } +} +``` + +### 4. Equipment Replacement During Banking + +**Check by name, not null, for charged equipment:** +```java +// BAD - only triggers when slot is completely empty +if (Rs2Equipment.get(EquipmentInventorySlot.RING) == null) { getNewRing(); } + +// GOOD - also catches wrong item type in slot +Rs2ItemModel ring = Rs2Equipment.get(EquipmentInventorySlot.RING); +if (ring == null || !ring.getName().contains("dueling")) { + getNewRing(); +} +``` + +**Pattern for items that degrade/break (e.g. barrows equipment, charged items):** +```java +// Check if equipped item still has charges/is functional +Rs2ItemModel staff = Rs2Equipment.get(EquipmentInventorySlot.WEAPON); +if (staff != null && staff.getName().contains("uncharged")) { + // Need to replace +} +``` + +### 5. Banking Flow Template + +```java +if (shouldBank) { + if (!Rs2Bank.isOpen()) { + Rs2Bank.walkToBankAndUseBank(bankLocation); + } else { + // 1. Save notable loot before depositing + saveLootToTracking(); + + // 2. Deposit inventory (keeping essentials) + Rs2Bank.depositAllExcept(keepItems); + + // 3. Withdraw supplies (check current count first) + if (Rs2Inventory.count(foodId) < targetFood) { + int needed = targetFood - Rs2Inventory.count(foodId); + Rs2Bank.withdrawX(foodId, needed); + } + + // 4. Check/replace equipment (name-based for charged items) + Rs2ItemModel ring = Rs2Equipment.get(EquipmentInventorySlot.RING); + if (ring == null || !ring.getName().contains("dueling")) { + Rs2Bank.withdrawOne("Ring of dueling"); + Rs2ItemModel newRing = Rs2Inventory.get( + it -> it != null && it.getName().contains("Ring of dueling")); + if (newRing != null) Rs2Inventory.interact(newRing, "Wear"); + } + + // 5. Verify supplies before leaving + suppliesCheck(config, true); + } +} +``` + +## Common Pitfalls + +| Pitfall | Fix | +|---------|-----| +| `loadEquipment()` infinite loop on charged items | Add retry limit + charged-item tolerance | +| Hardcoded `ItemID.RING_OF_DUELING8` | Use `"Ring of dueling"` name matching | +| `depositAll()` then re-withdraw everything | Use `depositAllExcept(keepItems)` | +| Building a keepItems list but never using it | Pass it to `depositAllExcept()` | +| Only checking `== null` for equipment slot | Also check item name matches expected type | diff --git a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsPlugin.java index c66008aed5..b9e92b8ce7 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsPlugin.java @@ -32,7 +32,7 @@ ) @Slf4j public class BarrowsPlugin extends Plugin { - public static final String version = "2.1.3"; + public static final String version = "2.1.5"; @Inject private BarrowsConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java index 17adad7a0d..cc03a80acc 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java @@ -297,19 +297,21 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { } if (sarc.click("Search")) { - // Activate prayer after clicking the sarcophagus - sleepGaussian(400, 1200); - if(config.shouldPrayAgainstWeakerBrothers()){ - activatePrayer(brother.getWhatToPray()); - } else { - if(!brother.getName().contains("Torag") && !brother.getName().contains("Guthan") && !brother.getName().contains("Verac")){ - activatePrayer(brother.getWhatToPray()); - } - } sleepUntil(() -> Rs2Player.isMoving(), Rs2Random.between(1000, 3000)); sleepUntil(() -> !Rs2Player.isMoving() || Rs2Player.isInCombat(), Rs2Random.between(3000, 6000)); // the brother could take a second to spawn in. - sleepUntil(() -> hintNpcModel() != null || Rs2Dialogue.isInDialogue(), Rs2Random.between(750, 1500)); + sleepUntil(() -> hintNpcModel() != null || Rs2Dialogue.isInDialogue(), Rs2Random.between(1000, 1500)); + + // Activate prayer only after brother has spawned + // if (hintNpcModel() != null) { + if(config.shouldPrayAgainstWeakerBrothers()){ + activatePrayer(brother.getWhatToPray()); + } else { + if(!brother.getName().contains("Torag") && !brother.getName().contains("Guthan") && !brother.getName().contains("Verac")){ + activatePrayer(brother.getWhatToPray()); + } + } + // } } if(Rs2Dialogue.isInDialogue() && Rs2Dialogue.hasDialogueText("You've found a hidden")){ @@ -427,7 +429,7 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { Rs2TileObjectModel barrowsChest = rs2TileObjectCache.query().withId(20973).nearest(); if(barrowsChest != null && - (barrowsChest.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) <= 6)){ + (barrowsChest.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) < 5) ){ //chest ID: 20973 stopFutureWalker(); @@ -541,8 +543,8 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { } java.util.List keepItems = new java.util.ArrayList<>(java.util.Arrays.asList( - neededRune, "Moonlight moth", "Moonlight moth mix (2)", "Teleport to house", "Spade", "Prayer potion(4)", "Prayer potion(3)", "Forgotten brew(4)", "Forgotten brew(3)", "Barrows teleport", - ourfoodsname)); + neededRune, "Moonlight moth", "Moonlight moth mix (2)", "Teleport to house", "Spade", "Prayer potion(4)", "Prayer potion(3)", "Forgotten brew(4)", "Forgotten brew(3)", "Dramen staff", "Barrows teleport", + "Ring of dueling", ourfoodsname)); if(config.rangeAhrim() && config.rangeAhrimGear() != null && !config.rangeAhrimGear().trim().isEmpty()) { for(String item : config.rangeAhrimGear().split("\\s*,\\s*")) { if(!item.trim().isEmpty()) keepItems.add(item.trim()); @@ -553,7 +555,7 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { if(!item.trim().isEmpty()) keepItems.add(item.trim()); } } - Rs2Bank.depositAll(); + Rs2Bank.depositAllExcept(keepItems); if(!usingPoweredStaffs) { if (Rs2Inventory.get(neededRune) == null || Rs2Inventory.get(neededRune).getQuantity() <= config.minRuneAmount()) { @@ -811,12 +813,6 @@ public void digIntoTheMound(Rs2WorldArea moundArea){ if (!super.isRunning()) break; - //antipattern turn on prayer early - antiPatternEnableWrongPrayer(); - - antiPatternActivatePrayer(); - //antipattern - if (Rs2Inventory.contains("Spade")) { if (Rs2Inventory.interact("Spade", "Dig")) { sleepUntil(() -> Rs2Player.getWorldLocation().getPlane() == 3, Rs2Random.between(3000, 5000)); @@ -835,13 +831,13 @@ public void goToTheMound(Rs2WorldArea moundArea){ WorldPoint randomMoundTile; if (!super.isRunning()) break; - //antipattern turn on prayer early - antiPatternEnableWrongPrayer(); - - antiPatternActivatePrayer(); + // Don't issue new walk commands while already moving or animating + if (Rs2Player.isMoving() || Rs2Player.isAnimating()) { + Rs2Player.waitForWalking(); + continue; + } antiPatternDropVials(); - //antipattern // We're not in the mound yet. randomMoundTile = moundArea.toWorldPointList().get(Rs2Random.between(0,(totalTiles-1))); @@ -849,11 +845,7 @@ public void goToTheMound(Rs2WorldArea moundArea){ if(Rs2Player.getWorldLocation().distanceTo(randomMoundTile) <= Rs2Random.between(10,15)){ LocalPoint localTile = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), randomMoundTile); if(localTile != null && Rs2Camera.isTileOnScreen(localTile)){ - if (!Rs2Player.isMoving()) { - Rs2Walker.walkFastCanvas(randomMoundTile); - Rs2Player.waitForWalking(); - } - + Rs2Walker.walkFastCanvas(randomMoundTile); Rs2Player.waitForWalking(); } else { Rs2Walker.walkTo(randomMoundTile); @@ -886,7 +878,7 @@ public void goToTheMound(Rs2WorldArea moundArea){ } } - if (!Rs2Player.isMoving()) { + if (!Rs2Player.isMoving() && !Rs2Player.isAnimating()) { Rs2Walker.walkCanvas(randomMoundTile); Rs2Player.waitForWalking(); } @@ -1128,6 +1120,7 @@ public void activatePrayer(Rs2PrayerEnum prayer){ if(!Rs2Prayer.isPrayerActive(prayer)){ Microbot.log("Turning on Prayer."); drinkPrayerPot(); + sleepGaussian(150, 350); Rs2Prayer.toggle(prayer); } } @@ -1200,8 +1193,8 @@ public void disablePrayer(){ } } public void reJfount(){ - int rejat = Rs2Random.between(10,30); - int runener = Rs2Random.between(50,65); + int rejat = 45; + int runener = 80; while(Rs2Player.getBoostedSkillLevel(Skill.PRAYER) < rejat || Rs2Player.getRunEnergy() <= runener){ if (!super.isRunning()) break; @@ -1234,7 +1227,7 @@ public void drinkPrayerPot(){ String action = "Drink"; if (prayerpotion.getName().equals("Moonlight moth")) action = "Release"; - if (Rs2Inventory.interact(prayerpotion, action)) sleep(0, 750); + if (Rs2Inventory.interact(prayerpotion, action)) sleep(300, 750); } } } From 79d97986c3fd2f76464bac488cc96240a571f7c8 Mon Sep 17 00:00:00 2001 From: Bender Date: Sun, 12 Apr 2026 13:29:06 -0700 Subject: [PATCH 23/24] Resupply --- .../handlers/ResupplyHandler.java | 63 ++++++++++++++++++- 1 file changed, 60 insertions(+), 3 deletions(-) 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 21856dc944..edbe31f076 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 @@ -103,6 +103,9 @@ private void makeMoonlightPotions(int moonlightPotionsQuantum) { sleepUntil(() -> !Rs2Player.isAnimating(), 10_000); } + /* Decant partial potions to 4-dose */ + decantMoonlightPotions(); + /* Drop leftovers */ while (Rs2Inventory.contains(ItemID.PESTLE_AND_MORTAR)) Rs2Inventory.drop(ItemID.PESTLE_AND_MORTAR); sleep(600); @@ -200,16 +203,70 @@ private void cookBream() { Rs2Walker.walkFastCanvas(new WorldPoint(1512, 9693, 0)); while (Rs2Inventory.contains(ItemID.BREAM_FISH_RAW)) { - if (!Rs2Player.isAnimating()) { + if (!Rs2Player.isAnimating(1200) || !Rs2Player.isInteracting()) { if (Rs2GameObject.interact(ObjectID.PMOON_RANGE, "Cook")) { - sleep(600, 900); + sleepUntil(() -> !Rs2Player.isAnimating() || !Rs2Inventory.contains(ItemID.BREAM_FISH_RAW), 30_000); } } - sleep(900, 1200); + sleep(600); } if (debugLogging) {Microbot.log("Finished cooking bream.");} } + private void decantMoonlightPotions() { + if (debugLogging) { Microbot.log("Decanting moonlight potions to 4-dose"); } + + int[] partialDoseIds = { + ItemID._3DOSEMOONLIGHTPOTION, + ItemID._2DOSEMOONLIGHTPOTION, + ItemID._1DOSEMOONLIGHTPOTION + }; + + boolean combined = true; + while (combined) { + combined = false; + + int partialCount = 0; + for (int id : partialDoseIds) { + partialCount += Rs2Inventory.count(id); + } + if (partialCount < 2) break; + + int firstId = -1; + int secondId = -1; + for (int id : partialDoseIds) { + int count = Rs2Inventory.count(id); + if (count > 0) { + if (firstId == -1) { + firstId = id; + if (count >= 2) { + secondId = id; + break; + } + } else { + secondId = id; + break; + } + } + } + + if (firstId != -1 && secondId != -1) { + if (Rs2Inventory.combine(firstId, secondId)) { + Rs2Inventory.waitForInventoryChanges(2_000); + combined = true; + } + } + sleep(300, 600); + } + + while (Rs2Inventory.contains(ItemID.VIAL_EMPTY)) { + Rs2Inventory.drop(ItemID.VIAL_EMPTY); + sleep(300, 600); + } + + if (debugLogging) { Microbot.log("Decanting complete. 4-dose potions: " + Rs2Inventory.count(ItemID._4DOSEMOONLIGHTPOTION)); } + } + /** * Return int: the total number of Moonlight Potions currently in inventory */ From 8ca58e2e4fc12e13f82978e9b6acf9d20c370e91 Mon Sep 17 00:00:00 2001 From: Bender Date: Fri, 8 May 2026 11:44:33 -0700 Subject: [PATCH 24/24] Changes --- .github/skills/rs2walker/SKILL.md | 126 +++++++++++++++ .../AutoBankStanderScript.java | 2 + .../skills/herblore/HerbloreProcessor.java | 13 +- .../banksshopper/BanksShopperConfig.java | 103 ++++++++++++- .../banksshopper/BanksShopperPlugin.java | 52 +++++++ .../banksshopper/BanksShopperScript.java | 145 ++++++++++++++++-- .../microbot/barrows/BarrowsScript.java | 1 + .../plankrunner/PlankRunnerScript.java | 66 ++++---- .../java/net/runelite/client/Microbot.java | 4 +- 9 files changed, 456 insertions(+), 56 deletions(-) diff --git a/.github/skills/rs2walker/SKILL.md b/.github/skills/rs2walker/SKILL.md index 9cf71ba2ad..d7a1a23242 100644 --- a/.github/skills/rs2walker/SKILL.md +++ b/.github/skills/rs2walker/SKILL.md @@ -376,3 +376,129 @@ Need to interact with NPC/object? └── Distance > 14 tiles? └── Rs2Walker.walkTo() first, then interact when close ``` + +## Pattern 6: Boss Arena Navigation (Moons of Peril Patterns) + +Boss arenas are instanced areas where all movement is short-range (typically <15 tiles). Use `walkFastCanvas` exclusively and never the web walker, since instanced regions have no web graph. + +### Two-Step Approach to Boss Lobby + +Use `walkWithState` for long-range travel to the lobby, then `walkFastCanvas` for precise tile placement: + +```java +// Phase 1: Walk to boss lobby (non-instanced, uses web walker) +Rs2Walker.walkWithState(bossLobbyLocation, 0); +sleep(600); + +// Phase 2: Precise placement on the statue tile (short range, canvas) +if (!Rs2Player.getWorldLocation().equals(bossLobbyLocation)) { + Rs2Walker.walkFastCanvas(bossLobbyLocation); + sleepUntil(() -> Rs2Player.getWorldLocation().equals(bossLobbyLocation)); +} +``` + +### walkFastCanvas with `shiftClick` Parameter + +`Rs2Walker.walkFastCanvas(WorldPoint, boolean shiftClick)` — the second parameter controls whether to shift-click (which forces a walk without queuing actions). Use `true` when you need to interrupt combat or cancel the current action: + +```java +// Normal walk — click without shift (good for non-combat scenarios) +Rs2Walker.walkFastCanvas(safeTile, false); + +// Shift-click walk — interrupts current action (good for dodging during combat) +Rs2Walker.walkFastCanvas(evadeTile, true); +``` + +**When to use shiftClick=true:** +- Dodging boss special attacks mid-combat +- Cancelling an attack animation to move immediately +- Any movement that must override the current action queue + +### Exact Tile Placement in Boss Arenas + +Boss mechanics often require standing on an exact tile. Always use `sleepUntil` with an exact WorldPoint match: + +```java +WorldPoint attackTile = Locations.ATTACK_TILE.getWorldPoint(); +Rs2Walker.walkFastCanvas(attackTile, true); +sleepUntil(() -> Rs2Player.getWorldLocation().equals(attackTile), 3_000); +``` + +### Running a Lap (Sequential Anchor Tiles) + +For mechanics that require running a path (e.g., Eclipse Moon shield dodge), walk through a sequence of anchor tiles: + +```java +WorldPoint[] lap = { + new WorldPoint(1483, 9627, 0), // SW + new WorldPoint(1483, 9637, 0), // NW + new WorldPoint(1493, 9637, 0), // NE + new WorldPoint(1493, 9627, 0) // SE +}; + +for (WorldPoint p : lap) { + Rs2Walker.walkFastCanvas(p, false); + // Eat/drink between tiles if needed + boss.eatIfNeeded(); + boss.drinkIfNeeded(); + // Break early if mechanic ends + if (!isSpecialAttackActive()) return; + sleepUntil(() -> Rs2Player.getWorldLocation().equals(p)); +} +``` + +### Avoiding Dangerous Tiles (Rs2Tile.getDangerousGraphicsObjectTiles) + +Check if the player's current tile is dangerous and move to a safe adjacent tile: + +```java +WorldPoint playerTile = Rs2Player.getWorldLocation(); +if (Rs2Tile.getDangerousGraphicsObjectTiles().containsKey(playerTile)) { + WorldPoint safeTile = Rs2Tile.getSafeTiles(1).get(0); + if (safeTile != null) { + Rs2Walker.walkFastCanvas(safeTile, true); + sleepUntil(() -> Rs2Player.getWorldLocation().equals(safeTile), 600); + } +} +``` + +For custom dangerous objects (not in the standard graphics objects), query the tile object cache: + +```java +Set dangerTiles = Microbot.getRs2TileObjectCache().query() + .withId(DANGEROUS_OBJECT_ID) + .within(distance) + .toList().stream() + .map(o -> o.getWorldLocation()) + .collect(Collectors.toSet()); + +// Build safe candidates within radius +List candidates = new ArrayList<>(); +for (int dx = -1; dx <= 1; dx++) { + for (int dy = -1; dy <= 1; dy++) { + WorldPoint wp = new WorldPoint(centre.getX() + dx, centre.getY() + dy, centre.getPlane()); + if (!dangerTiles.contains(wp)) candidates.add(wp); + } +} +WorldPoint safeTile = candidates.get(ThreadLocalRandom.current().nextInt(candidates.size())); +Rs2Walker.walkFastCanvas(safeTile, true); +``` + +### walkCanvas for NPC Tile Targeting (Clone Parrying) + +When you need to click an NPC's tile directly (e.g., parrying clones), use `Rs2Walker.walkCanvas()` with the NPC's local-to-world location: + +```java +Rs2NpcModel clone = /* find the clone NPC */; +WorldPoint cloneLocalLocation = WorldPoint.fromLocal(Microbot.getClient(), clone.getLocalLocation()); +Rs2Walker.walkCanvas(cloneLocalLocation); +``` + +### Key Boss Arena Rules + +1. **Never use `Rs2Walker.walkTo()` inside instanced boss arenas** — no web graph exists +2. **Always use `walkFastCanvas`** — all boss tiles are within canvas range +3. **Use shiftClick=true for dodge mechanics** — ensures immediate movement +4. **Use `walkWithState` for non-instanced lobby travel** — handles long distances with state tracking +5. **Camera reset before boss fights** — `Rs2Camera.resetPitch()` + `Rs2Camera.resetZoom()` ensures all arena tiles are visible on canvas +6. **sleepUntil with exact tile match** — boss mechanics require precision, use `.equals()` not distance checks diff --git a/src/main/java/net/runelite/client/plugins/microbot/autobankstander/AutoBankStanderScript.java b/src/main/java/net/runelite/client/plugins/microbot/autobankstander/AutoBankStanderScript.java index 3651c596d4..cc914355b6 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/autobankstander/AutoBankStanderScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/autobankstander/AutoBankStanderScript.java @@ -124,6 +124,7 @@ private void handleBanking() { if (processor.hasRequiredItems()) { log.info("Have all required items - switching to processing"); Rs2Bank.closeBank(); // close the bank interface + sleepUntil(() -> !Rs2Bank.isOpen(), 2000); // wait for bank to fully close changeState(AutoBankStanderState.PROCESSING); // switch to processing mode return; } @@ -133,6 +134,7 @@ private void handleBanking() { if (bankingSuccess) { log.info("Banking complete - switching to processing"); Rs2Bank.closeBank(); // close the bank interface + sleepUntil(() -> !Rs2Bank.isOpen(), 2000); // wait for bank to fully close changeState(AutoBankStanderState.PROCESSING); // switch to processing mode } else { log.info("Banking failed - no required items available, shutting down"); diff --git a/src/main/java/net/runelite/client/plugins/microbot/autobankstander/skills/herblore/HerbloreProcessor.java b/src/main/java/net/runelite/client/plugins/microbot/autobankstander/skills/herblore/HerbloreProcessor.java index 53f1f626cc..3cc76e4244 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/autobankstander/skills/herblore/HerbloreProcessor.java +++ b/src/main/java/net/runelite/client/plugins/microbot/autobankstander/skills/herblore/HerbloreProcessor.java @@ -135,6 +135,9 @@ public boolean performBanking() { return false; } + // Reset processing flag from previous cycle + currentlyMakingPotions = false; + log.info("Depositing all items"); Rs2Bank.depositAll(); sleepUntil(() -> Rs2Inventory.isEmpty(), 3000); @@ -347,9 +350,9 @@ private boolean processUnfinishedPotions() { if (Rs2Inventory.hasItem(currentHerbForUnfinished.clean) && Rs2Inventory.hasItem(ItemID.VIAL_WATER)) { log.info("Combining {} with vial of water", currentHerbForUnfinished.name()); - sleep(400, 600); + sleep(400, 550); if (Rs2Inventory.combine(currentHerbForUnfinished.clean, ItemID.VIAL_WATER)) { - sleep(600, 800); + sleep(250, 350); if (withdrawnAmount > 1) { sleepUntil(() -> Rs2Dialogue.hasCombinationDialogue(), 3000); Rs2Keyboard.keyPress('1'); @@ -379,8 +382,9 @@ private boolean processSuperCombat() { if (Rs2Inventory.hasItem(ItemID.TORSTOL) && Rs2Inventory.hasItem(ItemID._4DOSE2ATTACK)) { log.info("Combining torstol with super attack for super combat"); + sleep(400, 550); if (Rs2Inventory.combine(ItemID.TORSTOL, ItemID._4DOSE2ATTACK)) { - sleep(600, 800); + sleep(250, 350); if (withdrawnAmount > 1) { sleepUntil(() -> Rs2Dialogue.hasQuestion("How many do you wish to make?"), 3000); Rs2Keyboard.keyPress('1'); @@ -397,8 +401,9 @@ private boolean processRegularPotion() { if (Rs2Inventory.hasItem(currentPotion.unfinished) && Rs2Inventory.hasItem(currentPotion.secondary)) { log.info("Combining {} unfinished with secondary ingredient", currentPotion.name()); + sleep(400, 550); if (Rs2Inventory.combine(currentPotion.unfinished, currentPotion.secondary)) { - sleep(600, 800); + sleep(250, 350); if (withdrawnAmount > 1) { sleepUntil(() -> Rs2Dialogue.hasQuestion("How many do you wish to make?"), 3000); Rs2Keyboard.keyPress('1'); diff --git a/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperConfig.java b/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperConfig.java index b84929c10f..77427f1628 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperConfig.java @@ -13,6 +13,9 @@ "
  • Hop to the next world for better stock.
  • " + "
  • Specify items to trade and maintain minimum stock.
  • " + "
  • Trade with specific NPCs using exact naming if needed.
  • " + + "
  • Unlimited Stock mode: loops buying until full, then banks (no hopping).
  • " + + "
  • Fast Mode: skips closing bank/shop interfaces for faster cycles.
  • " + + "
  • Set shop coordinates for precise walk-back after banking.
  • " + "" ) public interface BanksShopperConfig extends Config { @@ -28,6 +31,14 @@ public interface BanksShopperConfig extends Config { String useNextWorld = "useNextWorld"; String blastFurnaceOptimization = "blastFurnaceOptimization"; String useKeyboardWorldHop = "useKeyboardWorldHop"; + String unlimitedStock = "unlimitedStock"; + String fastMode = "fastMode"; + String bankName = "bankName"; + String shopX = "shopX"; + String shopY = "shopY"; + String shopZ = "shopZ"; + String useGameObject = "useGameObject"; + String shopAction = "shopAction"; @ConfigSection( name = "Action Settings", @@ -130,6 +141,28 @@ default boolean useKeyboardWorldHop() { return false; } + @ConfigItem( + position = 7, + keyName = unlimitedStock, + name = "Unlimited Stock", + description = "Shop has unlimited stock - keep buying until inventory is full, then bank (no world hopping)", + section = actionSection + ) + default boolean unlimitedStock() { + return false; + } + + @ConfigItem( + position = 8, + keyName = fastMode, + name = "Fast Mode", + description = "Don't close bank/shop interfaces - interact directly with bank object and shop NPC by name", + section = actionSection + ) + default boolean fastMode() { + return false; + } + @ConfigItem( keyName = itemNames, name = "Item Name(s)/ID(s)", @@ -156,8 +189,8 @@ default int minimumStock() { @ConfigItem( keyName = npcName, - name = "NPC Name", - description = "Name of the NPC to trade with", + name = "NPC/Object Name", + description = "Name of the NPC or Game Object to trade with (e.g., Shop keeper, Culinaromancer's chest)", position = 0, section = shopSection ) @@ -176,4 +209,70 @@ default String npcName() { default boolean useExactNaming() { return true; } + + @ConfigItem( + position = 2, + keyName = useGameObject, + name = "Shop is a Game Object", + description = "Enable if the shop is a Game Object instead of an NPC (e.g., Culinaromancer's chest)", + section = shopSection + ) + default boolean useGameObject() { + return false; + } + + @ConfigItem( + keyName = shopAction, + name = "Shop Action", + description = "Action to open the shop (e.g., Trade, Buy-food, Buy-items)", + position = 3, + section = shopSection + ) + default String shopAction() { + return "Trade"; + } + + @ConfigItem( + keyName = bankName, + name = "Bank Object/NPC Name", + description = "Name of the bank booth, chest, or NPC to interact with in Fast Mode (e.g., Bank chest, Bank booth)", + position = 2, + section = shopSection + ) + default String bankName() { + return "Bank chest"; + } + + @ConfigItem( + keyName = shopX, + name = "Shop X", + description = "X coordinate of the shop location (0 to use current position)", + position = 3, + section = shopSection + ) + default int shopX() { + return 0; + } + + @ConfigItem( + keyName = shopY, + name = "Shop Y", + description = "Y coordinate of the shop location (0 to use current position)", + position = 4, + section = shopSection + ) + default int shopY() { + return 0; + } + + @ConfigItem( + keyName = shopZ, + name = "Shop Plane", + description = "Plane/floor of the shop location (usually 0)", + position = 5, + section = shopSection + ) + default int shopZ() { + return 0; + } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperPlugin.java index c3138a578e..7e85025d2a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperPlugin.java @@ -4,6 +4,7 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; import net.runelite.api.ChatMessageType; +import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.ChatMessage; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; @@ -74,6 +75,18 @@ BanksShopperConfig provideConfig(ConfigManager configManager) { private boolean blastFurnaceOptimization; @Getter private boolean useKeyboardWorldHop; + @Getter + private boolean unlimitedStock; + @Getter + private boolean fastMode; + @Getter + private String bankName; + @Getter + private boolean useGameObject; + @Getter + private String shopAction; + @Getter + private WorldPoint shopLocation; @Override protected void startUp() throws AWTException { @@ -87,6 +100,12 @@ protected void startUp() throws AWTException { useNextWorld = config.useNextWorld(); blastFurnaceOptimization = config.blastFurnaceOptimization(); useKeyboardWorldHop = config.useKeyboardWorldHop(); + unlimitedStock = config.unlimitedStock(); + fastMode = config.fastMode(); + bankName = config.bankName(); + useGameObject = config.useGameObject(); + shopAction = config.shopAction(); + updateShopLocation(); updateItemList(config.itemNames()); if (overlayManager != null) { @@ -154,6 +173,32 @@ public void onConfigChanged(ConfigChanged event) { useKeyboardWorldHop = config.useKeyboardWorldHop(); } + if (event.getKey().equals(BanksShopperConfig.unlimitedStock)) { + unlimitedStock = config.unlimitedStock(); + } + + if (event.getKey().equals(BanksShopperConfig.fastMode)) { + fastMode = config.fastMode(); + } + + if (event.getKey().equals(BanksShopperConfig.bankName)) { + bankName = config.bankName(); + } + + if (event.getKey().equals(BanksShopperConfig.useGameObject)) { + useGameObject = config.useGameObject(); + } + + if (event.getKey().equals(BanksShopperConfig.shopAction)) { + shopAction = config.shopAction(); + } + + if (event.getKey().equals(BanksShopperConfig.shopX) + || event.getKey().equals(BanksShopperConfig.shopY) + || event.getKey().equals(BanksShopperConfig.shopZ)) { + updateShopLocation(); + } + if (event.getKey().equals(BanksShopperConfig.itemNames)) { updateItemList(config.itemNames()); } @@ -171,4 +216,11 @@ private void updateItemList(String items) { itemNames = Collections.singletonList(items.trim().toLowerCase()); } } + + private void updateShopLocation() { + int x = config.shopX(); + int y = config.shopY(); + int z = config.shopZ(); + shopLocation = (x > 0 && y > 0) ? new WorldPoint(x, y, z) : null; + } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperScript.java b/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperScript.java index be88beac3d..3e2c0f5e57 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/banksshopper/BanksShopperScript.java @@ -1,17 +1,22 @@ package net.runelite.client.plugins.microbot.banksshopper; import net.runelite.api.GameState; +import net.runelite.api.coords.LocalPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; 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.bank.Rs2Bank; +import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; +import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.math.Rs2Random; +import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.security.Login; import net.runelite.client.plugins.microbot.util.shop.Rs2Shop; +import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import java.awt.event.KeyEvent; @@ -32,6 +37,18 @@ public BanksShopperScript(final BanksShopperPlugin plugin) { this.plugin = plugin; } + /** + * Opens the shop - handles both NPC shops and game object shops (e.g., Culinaromancer's chest). + */ + private boolean openShopInterface() { + if (Rs2Shop.isOpen()) return true; + if (plugin.isUseGameObject()) { + return Rs2GameObject.interact(plugin.getNpcName(), plugin.getShopAction()); + } else { + return Rs2Shop.openShop(plugin.getNpcName(), plugin.isUseExactNaming()); + } + } + public boolean run(BanksShopperConfig config) { Microbot.pauseAllScripts.compareAndSet(true, false); Microbot.enableAutoRunOn = false; @@ -67,7 +84,7 @@ public boolean run(BanksShopperConfig config) { return; } - sleepUntil(() -> Rs2Shop.openShop(plugin.getNpcName(), plugin.isUseExactNaming()), 5000); + sleepUntil(this::openShopInterface, 5000); boolean successfullAction = false; boolean outOfStock = false; @@ -86,18 +103,38 @@ public boolean run(BanksShopperConfig config) { plugin.getMinStock()); if (outOfStock) continue; - successfullAction = processBuyAction(Integer.parseInt(itemName), - plugin.getSelectedQuantity().toString()); + if (plugin.isUnlimitedStock()) { + while (isRunning() && !Rs2Inventory.isFull()) { + if (!processBuyAction(Integer.parseInt(itemName), + plugin.getSelectedQuantity().toString())) + break; + sleepGaussian(200, 40); + } + successfullAction = true; + } else { + successfullAction = processBuyAction(Integer.parseInt(itemName), + plugin.getSelectedQuantity().toString()); + } } else { outOfStock = !Rs2Shop.hasMinimumStock(itemName, plugin.getMinStock()); if (outOfStock) continue; - successfullAction = processBuyAction(itemName, - plugin.getSelectedQuantity().toString()); + if (plugin.isUnlimitedStock()) { + while (isRunning() && !Rs2Inventory.isFull()) { + if (!processBuyAction(itemName, + plugin.getSelectedQuantity().toString())) + break; + sleepGaussian(200, 40); + } + successfullAction = true; + } else { + successfullAction = processBuyAction(itemName, + plugin.getSelectedQuantity().toString()); + } } if (Rs2Inventory.isFull()) { System.out.println("Inventory is full, stopping buy action to bank."); - if (!plugin.isBlastFurnaceOptimization()) { + if (!plugin.isBlastFurnaceOptimization() && !plugin.isFastMode()) { Rs2Shop.closeShop(); } state = ShopperState.BANKING; @@ -137,9 +174,15 @@ public boolean run(BanksShopperConfig config) { System.out.println("Invalid action specified in config."); } } - Rs2Shop.closeShop(); + if (!plugin.isFastMode()) { + Rs2Shop.closeShop(); + } if (successfullAction) { - state = ShopperState.HOPPING; + if (plugin.isUnlimitedStock()) { + state = ShopperState.SHOPPING; + } else { + state = ShopperState.HOPPING; + } return; } else if (outOfStock) { System.out.println("Out of stock for all items, hopping worlds..."); @@ -149,13 +192,21 @@ public boolean run(BanksShopperConfig config) { } break; case BANKING: - if (plugin.isBlastFurnaceOptimization()) { + if (plugin.isFastMode()) { + if (!bankItemsFastMode()) { + return; + } + } else if (plugin.isBlastFurnaceOptimization()) { if (!bankItemsWithoutWalkBack()) { return; } - } else if (!Rs2Bank.bankItemsAndWalkBackToOriginalPosition(plugin.getItemNames(), - initialPlayerLocation)) { - return; + } else { + var walkBackLocation = plugin.getShopLocation() != null + ? plugin.getShopLocation() : initialPlayerLocation; + if (!Rs2Bank.bankItemsAndWalkBackToOriginalPosition(plugin.getItemNames(), + walkBackLocation)) { + return; + } } state = ShopperState.SHOPPING; break; @@ -338,7 +389,7 @@ private boolean bankItemsWithoutWalkBack() { if (Rs2Bank.isOpen()) { if (plugin.isBlastFurnaceOptimization()) { - sleepUntil(() -> Rs2Shop.openShop(plugin.getNpcName(), plugin.isUseExactNaming()), 5000); + sleepUntil(this::openShopInterface, 5000); return true; } @@ -348,4 +399,72 @@ private boolean bankItemsWithoutWalkBack() { return true; } + + /** + * Fast mode banking: don't close shop/bank interfaces, interact directly by name. + * Uses walkFastCanvas for short distances, checks if NPC is on screen before walking. + */ + private boolean bankItemsFastMode() { + String bankObjectName = plugin.getBankName(); + + // Open bank - interact directly by name (Rs2GameObject.interact walks if needed) + if (!Rs2Bank.isOpen()) { + boolean interacted = Rs2GameObject.interact(bankObjectName, "Bank") || + Rs2GameObject.interact(bankObjectName, "Use"); + if (!interacted) { + if (!Rs2Bank.walkToBankAndUseBank()) { + System.out.println("[FastMode] Failed to interact with bank: " + bankObjectName); + return false; + } + } + if (!sleepUntil(Rs2Bank::isOpen, 5000)) { + System.out.println("[FastMode] Bank did not open in time."); + return false; + } + } + + // Deposit all items + Rs2Bank.depositAll(); + sleepGaussian(200, 40); + + // Check if the shop is already interactable from here + if (plugin.isUseGameObject()) { + // For game object shops (e.g., Culinaromancer's chest) - try interacting directly + if (openShopInterface()) { + sleepUntil(Rs2Shop::isOpen, 5000); + return true; + } + } else { + // Check if the shop NPC is already on screen - if so, just click it directly + var shopNpc = Rs2Npc.getNpc(plugin.getNpcName(), plugin.isUseExactNaming()); + if (shopNpc != null && Rs2Camera.isTileOnScreen(shopNpc.getLocalLocation())) { + sleepUntil(this::openShopInterface, 5000); + return true; + } + } + + // NPC not on screen - walk back to shop location using fast canvas if close enough + var walkBackLocation = plugin.getShopLocation() != null + ? plugin.getShopLocation() : initialPlayerLocation; + if (walkBackLocation != null && Rs2Player.getWorldLocation().distanceTo(walkBackLocation) > 1) { + int distance = Rs2Player.getWorldLocation().distanceTo(walkBackLocation); + if (distance <= 12) { + LocalPoint local = LocalPoint.fromWorld( + Microbot.getClient().getTopLevelWorldView(), walkBackLocation); + if (local != null && Rs2Camera.isTileOnScreen(local)) { + Rs2Walker.walkFastCanvas(walkBackLocation); + } else { + Rs2Walker.walkTo(walkBackLocation, 1); + } + } else { + Rs2Walker.walkTo(walkBackLocation, 1); + } + sleepUntil(() -> Rs2Player.getWorldLocation().distanceTo(walkBackLocation) <= 2, 10000); + } + + // Re-open shop + sleepUntil(this::openShopInterface, 5000); + + return true; + } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java index 2d568dc8f2..b0ad1ed676 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/barrows/BarrowsScript.java @@ -1191,6 +1191,7 @@ public boolean outOfSupplies(BarrowsConfig config){ sleepUntil(() -> !Rs2Player.isAnimating(), Rs2Random.between(6000, 10000)); } } + return false; } public void disablePrayer(){ if(Rs2Random.between(0,100) >= Rs2Random.between(0,2)) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/plankrunner/PlankRunnerScript.java b/src/main/java/net/runelite/client/plugins/microbot/plankrunner/PlankRunnerScript.java index 5793a99b4c..d46d40046e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/plankrunner/PlankRunnerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/plankrunner/PlankRunnerScript.java @@ -13,6 +13,7 @@ import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.misc.Rs2Potion; +import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -74,41 +75,35 @@ public boolean run() { Rs2Inventory.waitForInventoryChanges(1800); } - if (plugin.isUseEnergyRestorePotions() && Rs2Player.getRunEnergy() <= plugin.getDrinkAtPercent()) { + while (plugin.isUseEnergyRestorePotions() && Rs2Player.getRunEnergy() <= plugin.getDrinkAtPercent()) { boolean hasStaminaPotion = Rs2Bank.hasItem(Rs2Potion.getStaminaPotion()); boolean hasEnergyRestorePotion = Rs2Bank.hasItem(Rs2Potion.getRestoreEnergyPotionsVariants()); - + + if (!hasStaminaPotion && !hasEnergyRestorePotion) { + Microbot.showMessage("Unable to find Stamina Potion OR Energy Restore Potions"); + shutdown(); + return; + } + if ((Rs2Player.hasStaminaBuffActive() && hasEnergyRestorePotion) || (!hasStaminaPotion && hasEnergyRestorePotion)) { Rs2ItemModel energyRestoreItem = Rs2Bank.bankItems().stream() .filter(rs2Item -> Rs2Potion.getRestoreEnergyPotionsVariants().stream() .anyMatch(variant -> rs2Item.getName().toLowerCase().contains(variant.toLowerCase()))) - .min(Comparator.comparingInt(rs2Item -> getDoseFromName(rs2Item.getName()))) + .max(Comparator.comparingInt(rs2Item -> getDoseFromName(rs2Item.getName()))) .orElse(null); - - if (energyRestoreItem == null) { - Microbot.showMessage("Unable to find Restore Energy Potion but hasItem?"); - shutdown(); - return; - } - + + if (energyRestoreItem == null) break; + withdrawAndDrink(energyRestoreItem.getName()); } else if (hasStaminaPotion) { Rs2ItemModel staminaPotionItem = Rs2Bank.bankItems().stream() .filter(rs2Item -> rs2Item.getName().toLowerCase().contains(Rs2Potion.getStaminaPotion().toLowerCase())) - .min(Comparator.comparingInt(rs2Item -> getDoseFromName(rs2Item.getName()))) + .max(Comparator.comparingInt(rs2Item -> getDoseFromName(rs2Item.getName()))) .orElse(null); - - if (staminaPotionItem == null) { - Microbot.showMessage("Unable to find Stamina Potion but hasItem?"); - shutdown(); - return; - } - + + if (staminaPotionItem == null) break; + withdrawAndDrink(staminaPotionItem.getName()); - } else { - Microbot.showMessage("Unable to find Stamina Potion OR Energy Restore Potions"); - shutdown(); - return; } } @@ -124,25 +119,22 @@ public boolean run() { sleepUntil(() -> !Rs2Bank.isOpen()); break; case RUNNING_TO_SAWMILL: - boolean isNearSawmill = Rs2Walker.getTotalTiles(plugin.getSawmillLocation().getWorldPoint()) < 15; - if (!isNearSawmill) { - Microbot.status = "Running to Sawmill"; - Rs2Walker.walkTo(plugin.getSawmillLocation().getWorldPoint()); - return; - } - Set sawmillNpcs = Set.of(NpcID.POH_SAWMILL_OPP, NpcID.AUBURN_SAWMILL_OPERATOR); var sawmillOperator = Microbot.getRs2NpcCache().query() .where(n -> sawmillNpcs.contains(n.getId())) .nearest(); - if (sawmillOperator == null) { - Microbot.showMessage("Unable to find Sawmill Operator!"); - shutdown(); + if (sawmillOperator != null && Rs2Camera.isTileOnScreen(sawmillOperator.getLocalLocation())) { + Rs2Walker.setTarget(null); + sleepUntil(() -> !Rs2Player.isMoving()); + sawmillOperator.click("Buy-plank"); + } else { + Microbot.status = "Running to Sawmill"; + if (!Rs2Player.isMoving()) { + Rs2Walker.walkFastCanvas(plugin.getSawmillLocation().getWorldPoint()); + } return; } - - sawmillOperator.click("Buy-plank"); Microbot.status = "Buying Planks"; Rs2Dialogue.sleepUntilHasCombinationDialogue(); Rs2Dialogue.clickCombinationOption(plugin.getPlank().getDialogueOption()); @@ -193,8 +185,10 @@ private void withdrawAndDrink(String potionItemName) { String simplifiedPotionName = potionItemName.replaceAll("\\s*\\(\\d+\\)", "").trim(); Rs2Bank.withdrawOne(potionItemName); Rs2Inventory.waitForInventoryChanges(1800); - Rs2Inventory.interact(potionItemName, "drink"); - Rs2Inventory.waitForInventoryChanges(1800); + while (Rs2Player.getRunEnergy() <= plugin.getDrinkAtPercent() && Rs2Inventory.hasItem(simplifiedPotionName)) { + Rs2Inventory.interact(simplifiedPotionName, "drink"); + Rs2Inventory.waitForInventoryChanges(1800); + } if (Rs2Inventory.hasItem(simplifiedPotionName)) { Rs2Bank.depositOne(simplifiedPotionName); Rs2Inventory.waitForInventoryChanges(1800); diff --git a/src/test/java/net/runelite/client/Microbot.java b/src/test/java/net/runelite/client/Microbot.java index 8933287b08..2d330d7076 100644 --- a/src/test/java/net/runelite/client/Microbot.java +++ b/src/test/java/net/runelite/client/Microbot.java @@ -11,6 +11,7 @@ import net.runelite.client.plugins.microbot.autofishing.AutoFishingPlugin; import net.runelite.client.plugins.microbot.example.ExamplePlugin; import net.runelite.client.plugins.microbot.leftclickcast.LeftClickCastPlugin; +import net.runelite.client.plugins.microbot.plankrunner.PlankRunnerPlugin; import net.runelite.client.plugins.microbot.sailing.MSailingPlugin; import net.runelite.client.plugins.microbot.thieving.ThievingPlugin; import net.runelite.client.plugins.microbot.woodcutting.AutoWoodcuttingPlugin; @@ -22,7 +23,8 @@ public class Microbot private static final Class[] debugPlugins = { AIOFighterPlugin.class, AgentServerPlugin.class, - LeftClickCastPlugin.class + LeftClickCastPlugin.class, + PlankRunnerPlugin.class }; public static void main(String[] args) throws Exception