From 741bb3938143a443ff181e698cae133b00c3069a Mon Sep 17 00:00:00 2001 From: Jonathan Thomas <95548936+JThomasDevs@users.noreply.github.com> Date: Tue, 24 Mar 2026 21:05:45 -0600 Subject: [PATCH 01/95] Reduce code smell, implement sawmill vouchers and Lazy Mode --- .../lunarplankmake/LunarPlankMakeConfig.java | 51 ++++++++--- .../lunarplankmake/LunarPlankMakePlugin.java | 2 +- .../lunarplankmake/LunarPlankMakeScript.java | 86 +++++++++++++++---- .../microbot/lunarplankmake/enums/Logs.java | 27 ++++-- .../java/net/runelite/client/Microbot.java | 2 + 5 files changed, 129 insertions(+), 39 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakeConfig.java b/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakeConfig.java index f61fad7100..3680a0f201 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakeConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakeConfig.java @@ -10,6 +10,14 @@ public interface LunarPlankMakeConfig extends Config { String GROUP = "Plank Make"; + @ConfigSection( + name = "General", + description = "General", + position = 0, + closedByDefault = false + ) + String generalSection = "general"; + @ConfigItem( keyName = "guide", name = "How to use", @@ -23,17 +31,10 @@ default String GUIDE() { "equip that and ensure that you have previously pre-cast the " + "Plank Make spell on the desired log and acknowledge the " + "prompt to avoid any further notifications. With these steps complete, " + - "you should be ready to proceed."; + "you should be ready to proceed. Lazy mode casts once and uses one log; " + + "the game processes the rest of that log type in one chain."; } - @ConfigSection( - name = "General", - description = "General", - position = 0, - closedByDefault = false - ) - String generalSection = "general"; - @ConfigItem( keyName = "logType", name = "Log Type", @@ -45,11 +46,33 @@ default Logs ITEM() { return Logs.LOGS; } + @ConfigItem( + keyName = "useSawmillVouchers", + name = "Use Sawmill Vouchers", + description = "Uses vouchers for double planks (12 logs -> 24 planks)", + position = 2, + section = generalSection + ) + default boolean useSawmillVouchers() { + return false; + } + + @ConfigItem( + keyName = "lazyMode", + name = "Lazy mode", + description = "Cast Plank Make once, use one log, then wait until every log in inventory is converted (no per-log cast loop)", + position = 3, + section = generalSection + ) + default boolean lazyMode() { + return false; + } + @ConfigItem( keyName = "useSetDelay", name = "Use Set Delay", description = "Enable to use a set delay between actions", - position = 2, + position = 4, section = generalSection ) default boolean useSetDelay() { @@ -60,7 +83,7 @@ default boolean useSetDelay() { keyName = "setDelay", name = "Set Delay (ms)", description = "The fixed delay in milliseconds between actions", - position = 3, + position = 5, section = generalSection ) default int setDelay() { @@ -71,7 +94,7 @@ default int setDelay() { keyName = "useRandomDelay", name = "Use Random Delay", description = "Enable to use a random delay between actions", - position = 4, + position = 6, section = generalSection ) default boolean useRandomDelay() { @@ -82,10 +105,10 @@ default boolean useRandomDelay() { keyName = "maxRandomDelay", name = "Maximum Random Delay (ms)", description = "The maximum random delay in milliseconds between actions", - position = 5, + position = 7, section = generalSection ) default int maxRandomDelay() { return 1000; // Default to 1000 milliseconds } -} +} \ No newline at end of file diff --git a/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakePlugin.java b/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakePlugin.java index 97ddc69b70..dfe4a800d5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakePlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakePlugin.java @@ -24,7 +24,7 @@ ) @Slf4j public class LunarPlankMakePlugin extends Plugin { - public static final String version = "1.0.2"; + public static final String version = "1.0.3"; @Inject private LunarPlankMakeConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakeScript.java b/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakeScript.java index 47ab4a321e..12970f55ee 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakeScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakeScript.java @@ -22,7 +22,9 @@ public class LunarPlankMakeScript extends Script { private boolean useRandomDelay; private int maxRandomDelay; - // State management + private boolean useVouchers; + private boolean lazyMode; + private enum State { PLANKING, BANKING, @@ -33,6 +35,7 @@ private enum State { public boolean run(LunarPlankMakeConfig config) { startTime = System.currentTimeMillis(); + int unprocessedItemPrice = Microbot.getItemManager().search(config.ITEM().getName()).get(0).getPrice(); int processedItemPrice = Microbot.getItemManager().search(config.ITEM().getFinished()).get(0).getPrice(); profitPerPlank = processedItemPrice - unprocessedItemPrice; @@ -41,10 +44,13 @@ public boolean run(LunarPlankMakeConfig config) { setDelay = config.setDelay(); useRandomDelay = config.useRandomDelay(); maxRandomDelay = config.maxRandomDelay(); + useVouchers = config.useSawmillVouchers(); + lazyMode = config.lazyMode(); mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { try { if (!super.run() || !Microbot.isLoggedIn()) return; + switch (currentState) { case PLANKING: plankItems(config); @@ -60,30 +66,66 @@ public boolean run(LunarPlankMakeConfig config) { Microbot.log("Exception in LunarPlankMakeScript: " + ex.getMessage()); } }, 0, 50, TimeUnit.MILLISECONDS); + return true; } private void plankItems(LunarPlankMakeConfig config) { - if (Rs2Inventory.hasItem(config.ITEM().getName(), true)) { - int initialPlankCount = Rs2Inventory.count(config.ITEM().getFinished()); + if (!Rs2Inventory.hasItem(config.ITEM().getName(), true)) { + currentState = State.BANKING; + return; + } + + int initialPlankCount = Rs2Inventory.count(config.ITEM().getFinished()); + + if (lazyMode) { + int initialLogQuantity = Rs2Inventory.count(config.ITEM().getName()); Rs2Magic.cast(MagicAction.PLANK_MAKE); addDelay(); Rs2Inventory.interact(config.ITEM().getName()); - - // Wait for the inventory count to change indicating Planks have been made - if (waitForInventoryChange(config.ITEM().getFinished(), initialPlankCount)) { - int plankMadeThisAction = Rs2Inventory.count(config.ITEM().getFinished()) - initialPlankCount; - plankMade += plankMadeThisAction; + if (waitUntilNoLogsRemaining(config, initialLogQuantity)) { + int plankMadeThisBatch = Rs2Inventory.count(config.ITEM().getFinished()) - initialPlankCount; + plankMade += plankMadeThisBatch; addDelay(); } else { - Microbot.log("Failed to detect plank creation."); + Microbot.log("Lazy mode: timed out waiting for logs to finish converting."); currentState = State.WAITING; } + return; + } + + Rs2Magic.cast(MagicAction.PLANK_MAKE); + addDelay(); + Rs2Inventory.interact(config.ITEM().getName()); + + if (waitForInventoryChange(config.ITEM().getFinished(), initialPlankCount)) { + int plankMadeThisAction = Rs2Inventory.count(config.ITEM().getFinished()) - initialPlankCount; + plankMade += plankMadeThisAction; + addDelay(); } else { - currentState = State.BANKING; + Microbot.log("Failed to detect plank creation."); + currentState = State.WAITING; } } + private boolean waitUntilNoLogsRemaining(LunarPlankMakeConfig config, int initialLogQuantity) { + if (initialLogQuantity <= 0) { + return true; + } + long start = System.currentTimeMillis(); + long timeoutMs = initialLogQuantity * 4000L; + if (timeoutMs < 60000L) { + timeoutMs = 60000L; + } + while (Rs2Inventory.hasItem(config.ITEM().getName(), true)) { + if (System.currentTimeMillis() - start > timeoutMs) { + return false; + } + sleep(50); + } + return true; + } + private boolean waitForInventoryChange(String itemName, int initialCount) { long start = System.currentTimeMillis(); while (Rs2Inventory.count(itemName) == initialCount) { @@ -101,8 +143,22 @@ private void bank(LunarPlankMakeConfig config) { Rs2Bank.depositAll(config.ITEM().getFinished()); sleepUntilOnClientThread(() -> !Rs2Inventory.hasItem(config.ITEM().getFinished())); + boolean hasVoucher = false; + + if (useVouchers) { + if (Rs2Inventory.contains("Sawmill voucher")) { + hasVoucher = true; + } else if (Rs2Bank.hasItem("Sawmill voucher")) { + Rs2Bank.withdrawAll("Sawmill voucher"); + sleepUntilOnClientThread(() -> Rs2Inventory.contains("Sawmill voucher")); + hasVoucher = true; + } + } + + int logsToWithdraw = hasVoucher ? 12 : 28; + if (Rs2Bank.hasItem(config.ITEM().getName())) { - Rs2Bank.withdrawAll(config.ITEM().getName()); + Rs2Bank.withdrawX(config.ITEM().getName(), logsToWithdraw); sleepUntilOnClientThread(() -> Rs2Inventory.hasItem(config.ITEM().getName())); } else { Microbot.showMessage("No more " + config.ITEM().getName() + " to plank."); @@ -144,8 +200,8 @@ private void addDelay() { @Override public void shutdown() { super.shutdown(); - plankMade = 0; // Reset the count of planks made - combinedMessage = ""; // Reset the combined message - currentState = State.PLANKING; // Reset the current state + plankMade = 0; + combinedMessage = ""; + currentState = State.PLANKING; } -} +} \ No newline at end of file diff --git a/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/enums/Logs.java b/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/enums/Logs.java index e7d509dce5..eb0ecfe7e9 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/enums/Logs.java +++ b/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/enums/Logs.java @@ -1,23 +1,32 @@ package net.runelite.client.plugins.microbot.lunarplankmake.enums; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -@RequiredArgsConstructor public enum Logs { - LOGS("Logs", "Plank"), OAK_LOGS("Oak logs", "Oak plank"), TEAK_LOGS("Teak logs", "Teak plank"), - MAHOGANY_LOGS("Mahogany logs", "Mahogany plank"); + MAHOGANY_LOGS("Mahogany logs", "Mahogany plank"), + CAMPHOR_LOGS("Camphor logs", "Camphor plank"), + IRONWOOD_LOGS("Ironwood logs", "Ironwood plank"), + ROSEWOOD_LOGS("Rosewood logs", "Rosewood plank"); private final String name; - @Getter private final String finished; + Logs(String name, String finished) { + this.name = name; + this.finished = finished; + } + + public String getName() { + return name; + } + + public String getFinished() { + return finished; + } + @Override public String toString() { - return name; + return getName(); } } diff --git a/src/test/java/net/runelite/client/Microbot.java b/src/test/java/net/runelite/client/Microbot.java index 54d5a6b9c6..d015f00883 100644 --- a/src/test/java/net/runelite/client/Microbot.java +++ b/src/test/java/net/runelite/client/Microbot.java @@ -5,12 +5,14 @@ import java.util.stream.Collectors; import net.runelite.client.plugins.microbot.slayer.SlayerPlugin; +import net.runelite.client.plugins.microbot.lunarplankmake.LunarPlankMakePlugin; public class Microbot { private static final Class[] debugPlugins = { SlayerPlugin.class, + LunarPlankMakePlugin.class, }; public static void main(String[] args) throws Exception From ef373644ba8c9225d6cc15b9264913a22c8d2fc5 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas <95548936+JThomasDevs@users.noreply.github.com> Date: Tue, 24 Mar 2026 21:38:23 -0600 Subject: [PATCH 02/95] Change profit calculation to be more accurate, extended Logs enum to prevent weird bank withdrawal shenanigans (tried withdrawing yew logs) --- .../lunarplankmake/LunarPlankMakeConfig.java | 19 +++- .../lunarplankmake/LunarPlankMakePlugin.java | 2 +- .../lunarplankmake/LunarPlankMakeScript.java | 92 ++++++++++++++----- .../microbot/lunarplankmake/enums/Logs.java | 39 ++++++-- 4 files changed, 116 insertions(+), 36 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakeConfig.java b/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakeConfig.java index 3680a0f201..b0cdaacdc4 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakeConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakeConfig.java @@ -68,11 +68,22 @@ default boolean lazyMode() { return false; } + @ConfigItem( + keyName = "includeEarthRuneCost", + name = "Include Earth rune cost", + description = "Count 15 Earth runes per plank in profit (turn off if using mud/earth staff)", + position = 4, + section = generalSection + ) + default boolean includeEarthRuneCost() { + return false; + } + @ConfigItem( keyName = "useSetDelay", name = "Use Set Delay", description = "Enable to use a set delay between actions", - position = 4, + position = 5, section = generalSection ) default boolean useSetDelay() { @@ -83,7 +94,7 @@ default boolean useSetDelay() { keyName = "setDelay", name = "Set Delay (ms)", description = "The fixed delay in milliseconds between actions", - position = 5, + position = 6, section = generalSection ) default int setDelay() { @@ -94,7 +105,7 @@ default int setDelay() { keyName = "useRandomDelay", name = "Use Random Delay", description = "Enable to use a random delay between actions", - position = 6, + position = 7, section = generalSection ) default boolean useRandomDelay() { @@ -105,7 +116,7 @@ default boolean useRandomDelay() { keyName = "maxRandomDelay", name = "Maximum Random Delay (ms)", description = "The maximum random delay in milliseconds between actions", - position = 7, + position = 8, section = generalSection ) default int maxRandomDelay() { diff --git a/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakePlugin.java b/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakePlugin.java index dfe4a800d5..4481f7de4c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakePlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakePlugin.java @@ -24,7 +24,7 @@ ) @Slf4j public class LunarPlankMakePlugin extends Plugin { - public static final String version = "1.0.3"; + public static final String version = "1.0.4"; @Inject private LunarPlankMakeConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakeScript.java b/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakeScript.java index 12970f55ee..098ac52931 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakeScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/LunarPlankMakeScript.java @@ -2,6 +2,7 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.lunarplankmake.enums.Logs; 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.magic.Rs2Magic; @@ -36,9 +37,7 @@ private enum State { public boolean run(LunarPlankMakeConfig config) { startTime = System.currentTimeMillis(); - int unprocessedItemPrice = Microbot.getItemManager().search(config.ITEM().getName()).get(0).getPrice(); - int processedItemPrice = Microbot.getItemManager().search(config.ITEM().getFinished()).get(0).getPrice(); - profitPerPlank = processedItemPrice - unprocessedItemPrice; + refreshProfitPerPlank(config); useSetDelay = config.useSetDelay(); setDelay = config.setDelay(); @@ -71,20 +70,22 @@ public boolean run(LunarPlankMakeConfig config) { } private void plankItems(LunarPlankMakeConfig config) { - if (!Rs2Inventory.hasItem(config.ITEM().getName(), true)) { + int logId = config.ITEM().getLogItemId(); + if (!Rs2Inventory.hasItem(logId)) { currentState = State.BANKING; return; } - int initialPlankCount = Rs2Inventory.count(config.ITEM().getFinished()); + int plankId = config.ITEM().getPlankItemId(); + int initialPlankCount = Rs2Inventory.count(plankId); if (lazyMode) { - int initialLogQuantity = Rs2Inventory.count(config.ITEM().getName()); + int initialLogQuantity = Rs2Inventory.count(logId); Rs2Magic.cast(MagicAction.PLANK_MAKE); addDelay(); - Rs2Inventory.interact(config.ITEM().getName()); + Rs2Inventory.interact(logId); if (waitUntilNoLogsRemaining(config, initialLogQuantity)) { - int plankMadeThisBatch = Rs2Inventory.count(config.ITEM().getFinished()) - initialPlankCount; + int plankMadeThisBatch = Rs2Inventory.count(plankId) - initialPlankCount; plankMade += plankMadeThisBatch; addDelay(); } else { @@ -96,10 +97,10 @@ private void plankItems(LunarPlankMakeConfig config) { Rs2Magic.cast(MagicAction.PLANK_MAKE); addDelay(); - Rs2Inventory.interact(config.ITEM().getName()); + Rs2Inventory.interact(logId); - if (waitForInventoryChange(config.ITEM().getFinished(), initialPlankCount)) { - int plankMadeThisAction = Rs2Inventory.count(config.ITEM().getFinished()) - initialPlankCount; + if (waitForInventoryChange(plankId, initialPlankCount)) { + int plankMadeThisAction = Rs2Inventory.count(plankId) - initialPlankCount; plankMade += plankMadeThisAction; addDelay(); } else { @@ -112,12 +113,13 @@ private boolean waitUntilNoLogsRemaining(LunarPlankMakeConfig config, int initia if (initialLogQuantity <= 0) { return true; } + int logId = config.ITEM().getLogItemId(); long start = System.currentTimeMillis(); long timeoutMs = initialLogQuantity * 4000L; if (timeoutMs < 60000L) { timeoutMs = 60000L; } - while (Rs2Inventory.hasItem(config.ITEM().getName(), true)) { + while (Rs2Inventory.hasItem(logId)) { if (System.currentTimeMillis() - start > timeoutMs) { return false; } @@ -126,10 +128,10 @@ private boolean waitUntilNoLogsRemaining(LunarPlankMakeConfig config, int initia return true; } - private boolean waitForInventoryChange(String itemName, int initialCount) { + private boolean waitForInventoryChange(int plankItemId, int initialCount) { long start = System.currentTimeMillis(); - while (Rs2Inventory.count(itemName) == initialCount) { - if (System.currentTimeMillis() - start > 3000) { // 3-second timeout + while (Rs2Inventory.count(plankItemId) == initialCount) { + if (System.currentTimeMillis() - start > 3000) { return false; } sleep(10); @@ -140,8 +142,11 @@ private boolean waitForInventoryChange(String itemName, int initialCount) { private void bank(LunarPlankMakeConfig config) { if (!Rs2Bank.openBank()) return; - Rs2Bank.depositAll(config.ITEM().getFinished()); - sleepUntilOnClientThread(() -> !Rs2Inventory.hasItem(config.ITEM().getFinished())); + int plankId = config.ITEM().getPlankItemId(); + int logId = config.ITEM().getLogItemId(); + + Rs2Bank.depositAll(plankId); + sleepUntilOnClientThread(() -> !Rs2Inventory.hasItem(plankId)); boolean hasVoucher = false; @@ -157,26 +162,67 @@ private void bank(LunarPlankMakeConfig config) { int logsToWithdraw = hasVoucher ? 12 : 28; - if (Rs2Bank.hasItem(config.ITEM().getName())) { - Rs2Bank.withdrawX(config.ITEM().getName(), logsToWithdraw); - sleepUntilOnClientThread(() -> Rs2Inventory.hasItem(config.ITEM().getName())); - } else { + int logsInInventory = Rs2Inventory.count(logId); + if (logsInInventory >= logsToWithdraw) { + Rs2Bank.closeBank(); + currentState = State.PLANKING; + calculateProfitAndDisplay(config); + return; + } + + if (!Rs2Bank.hasItem(logId)) { Microbot.showMessage("No more " + config.ITEM().getName() + " to plank."); shutdown(); return; } + int need = logsToWithdraw - logsInInventory; + Rs2Bank.withdrawX(logId, need); + sleepUntilOnClientThread(() -> Rs2Inventory.count(logId) >= logsToWithdraw); + Rs2Bank.closeBank(); currentState = State.PLANKING; calculateProfitAndDisplay(config); } private void waitUntilReady() { - sleep(500); // Short sleep before retrying + sleep(500); currentState = State.PLANKING; } + private void refreshProfitPerPlank(LunarPlankMakeConfig config) { + Logs item = config.ITEM(); + int plankPrice = gePrice(item.getFinished()); + int logPrice = gePrice(item.getName()); + int astral = gePrice("Astral rune"); + int nature = gePrice("Nature rune"); + int runeGp = 2 * astral + nature; + if (config.includeEarthRuneCost()) { + int earth = gePrice("Earth rune"); + runeGp += 15 * earth; + } + int voucherPerPlank = 0; + if (config.useSawmillVouchers()) { + int voucherPrice = gePrice("Sawmill voucher"); + voucherPerPlank = voucherPrice / 24; + } + int planksPerLog = config.useSawmillVouchers() ? 2 : 1; + int logCostPerPlank = logPrice / planksPerLog; + int coinFeePerPlank = item.getPlankMakeCoinFee() / planksPerLog; + int runeCostPerPlank = runeGp / planksPerLog; + profitPerPlank = plankPrice - logCostPerPlank - coinFeePerPlank - runeCostPerPlank - voucherPerPlank; + } + + private static int gePrice(String itemName) { + try { + return Microbot.getItemManager().search(itemName).get(0).getPrice(); + } catch (Exception e) { + return 0; + } + } + private void calculateProfitAndDisplay(LunarPlankMakeConfig config) { + refreshProfitPerPlank(config); double elapsedHours = (System.currentTimeMillis() - startTime) / 3600000.0; int plankPerHour = (int) (plankMade / elapsedHours); int totalProfit = profitPerPlank * (int) plankMade; @@ -204,4 +250,4 @@ public void shutdown() { combinedMessage = ""; currentState = State.PLANKING; } -} \ No newline at end of file +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/enums/Logs.java b/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/enums/Logs.java index eb0ecfe7e9..073554a1ad 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/enums/Logs.java +++ b/src/main/java/net/runelite/client/plugins/microbot/lunarplankmake/enums/Logs.java @@ -1,20 +1,28 @@ package net.runelite.client.plugins.microbot.lunarplankmake.enums; +import net.runelite.api.gameval.ItemID; + public enum Logs { - LOGS("Logs", "Plank"), - OAK_LOGS("Oak logs", "Oak plank"), - TEAK_LOGS("Teak logs", "Teak plank"), - MAHOGANY_LOGS("Mahogany logs", "Mahogany plank"), - CAMPHOR_LOGS("Camphor logs", "Camphor plank"), - IRONWOOD_LOGS("Ironwood logs", "Ironwood plank"), - ROSEWOOD_LOGS("Rosewood logs", "Rosewood plank"); + LOGS("Logs", "Plank", ItemID.LOGS, ItemID.WOODPLANK, 70), + OAK_LOGS("Oak logs", "Oak plank", ItemID.OAK_LOGS, ItemID.PLANK_OAK, 175), + TEAK_LOGS("Teak logs", "Teak plank", ItemID.TEAK_LOGS, ItemID.PLANK_TEAK, 350), + MAHOGANY_LOGS("Mahogany logs", "Mahogany plank", ItemID.MAHOGANY_LOGS, ItemID.PLANK_MAHOGANY, 1050), + CAMPHOR_LOGS("Camphor logs", "Camphor plank", ItemID.CAMPHOR_LOGS, ItemID.PLANK_CAMPHOR, 1750), + IRONWOOD_LOGS("Ironwood logs", "Ironwood plank", ItemID.IRONWOOD_LOGS, ItemID.PLANK_IRONWOOD, 3500), + ROSEWOOD_LOGS("Rosewood logs", "Rosewood plank", ItemID.ROSEWOOD_LOGS, ItemID.PLANK_ROSEWOOD, 5250); private final String name; private final String finished; + private final int logItemId; + private final int plankItemId; + private final int plankMakeCoinFee; - Logs(String name, String finished) { + Logs(String name, String finished, int logItemId, int plankItemId, int plankMakeCoinFee) { this.name = name; this.finished = finished; + this.logItemId = logItemId; + this.plankItemId = plankItemId; + this.plankMakeCoinFee = plankMakeCoinFee; } public String getName() { @@ -25,6 +33,21 @@ public String getFinished() { return finished; } + public int getLogItemId() { + return logItemId; + } + + public int getPlankItemId() { + return plankItemId; + } + + /** + * Coins removed per log by Plank Make (70% of sawmill fee); see OSRS wiki Plank Make. + */ + public int getPlankMakeCoinFee() { + return plankMakeCoinFee; + } + @Override public String toString() { return getName(); From a37a8a3d0271823e93602397739b8c508328af14 Mon Sep 17 00:00:00 2001 From: JThomasDevs <95548936+JThomasDevs@users.noreply.github.com> Date: Tue, 24 Mar 2026 21:51:23 -0600 Subject: [PATCH 03/95] undo main runner change --- .../java/net/runelite/client/Microbot.java | 24 ------------------- 1 file changed, 24 deletions(-) delete mode 100644 src/test/java/net/runelite/client/Microbot.java diff --git a/src/test/java/net/runelite/client/Microbot.java b/src/test/java/net/runelite/client/Microbot.java deleted file mode 100644 index d015f00883..0000000000 --- a/src/test/java/net/runelite/client/Microbot.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.runelite.client; - -import java.util.Arrays; -import java.util.List; -import java.util.stream.Collectors; - -import net.runelite.client.plugins.microbot.slayer.SlayerPlugin; -import net.runelite.client.plugins.microbot.lunarplankmake.LunarPlankMakePlugin; - -public class Microbot -{ - - private static final Class[] debugPlugins = { - SlayerPlugin.class, - LunarPlankMakePlugin.class, - }; - - public static void main(String[] args) throws Exception - { - List> _debugPlugins = Arrays.stream(debugPlugins).collect(Collectors.toList()); - RuneLiteDebug.pluginsToDebug.addAll(_debugPlugins); - RuneLiteDebug.main(args); - } -} From d61ed143d90a0e5057ec01f547ce33887a62ecd2 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas <95548936+JThomasDevs@users.noreply.github.com> Date: Tue, 24 Mar 2026 21:54:53 -0600 Subject: [PATCH 04/95] re-add microbot.java. whoops. --- .../java/net/runelite/client/Microbot.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/test/java/net/runelite/client/Microbot.java diff --git a/src/test/java/net/runelite/client/Microbot.java b/src/test/java/net/runelite/client/Microbot.java new file mode 100644 index 0000000000..54d5a6b9c6 --- /dev/null +++ b/src/test/java/net/runelite/client/Microbot.java @@ -0,0 +1,22 @@ +package net.runelite.client; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import net.runelite.client.plugins.microbot.slayer.SlayerPlugin; + +public class Microbot +{ + + private static final Class[] debugPlugins = { + SlayerPlugin.class, + }; + + public static void main(String[] args) throws Exception + { + List> _debugPlugins = Arrays.stream(debugPlugins).collect(Collectors.toList()); + RuneLiteDebug.pluginsToDebug.addAll(_debugPlugins); + RuneLiteDebug.main(args); + } +} From d75a911adcc364a414b39e69f4458e4e7623eb75 Mon Sep 17 00:00:00 2001 From: JThomasDevs <95548936+JThomasDevs@users.noreply.github.com> Date: Wed, 1 Apr 2026 05:20:31 -0600 Subject: [PATCH 05/95] Karam fix (#351) * fix: karambwan fairy ring return Made-with: Cursor * plugin now clicks on fairy ring to get back to karams * commit --- .../karambwans/GabulhasKarambwansScript.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 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..54879771a2 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 @@ -14,7 +14,6 @@ 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.magic.Rs2Spells; -import net.runelite.client.plugins.microbot.util.magic.Runes; 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; @@ -177,8 +176,20 @@ private void walkToFish() { } else { Rs2Walker.walkTo(zanarisRingPoint, 3); Rs2Player.waitForWalking(); - - if (Rs2GameObject.interact(FAIRY_RING_ID, "Last-destination (DKP)")) { + + // Ensure the fairy ring at Zanaris is actually loaded before trying to interact. + sleepUntil(() -> Rs2GameObject.getGameObject(zanarisRingPoint) != null, 5000); + + GameObject zanarisRing = Rs2GameObject.getGameObject(zanarisRingPoint); + boolean interacted = false; + if (zanarisRing != null) { + // Prefer the explicit last-destination option, fall back to a generic interact if needed. + interacted = Rs2GameObject.interact(zanarisRing, "Last-destination (DKP)") + || Rs2GameObject.interact(zanarisRing, "Last-destination") + || Rs2GameObject.interact(zanarisRing, "Use"); + } + + if (interacted) { waitTillPlayerNextToFishingSpot(); } else { Rs2Player.waitForWalking(); From f1ed72b769990bf082c85d935e494096a83241c9 Mon Sep 17 00:00:00 2001 From: JThomasDevs <95548936+JThomasDevs@users.noreply.github.com> Date: Tue, 7 Apr 2026 23:45:06 -0600 Subject: [PATCH 06/95] client thread fixes (#371) * Reduce code smell, implement sawmill vouchers and Lazy Mode * Change profit calculation to be more accurate, extended Logs enum to prevent weird bank withdrawal shenanigans (tried withdrawing yew logs) * undo main runner change * re-add microbot.java. whoops. * Karam fix (#351) * fix: karambwan fairy ring return Made-with: Cursor * plugin now clicks on fairy ring to get back to karams * commit * client thread fixes --------- Co-authored-by: chsami Co-authored-by: stonksCode <99895926+stonksCode@users.noreply.github.com> --- .../microbot/valetotems/ValeTotemPlugin.java | 2 +- .../valetotems/handlers/NavigationHandler.java | 16 ++++++++++++++-- .../valetotems/utils/CoordinateUtils.java | 2 +- .../valetotems/utils/GameObjectUtils.java | 4 ++-- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java index 4a795bed6a..33a29a1e93 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java @@ -27,7 +27,7 @@ ) @Slf4j public class ValeTotemPlugin extends Plugin { - static final String version = "1.0.6"; + static final String version = "1.0.7"; @Inject private ValeTotemConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/NavigationHandler.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/NavigationHandler.java index 8fd3737694..70cc83fea2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/NavigationHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/NavigationHandler.java @@ -1,6 +1,7 @@ package net.runelite.client.plugins.microbot.valetotems.handlers; import net.runelite.api.GameObject; +import net.runelite.api.Player; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.api.Client; @@ -257,7 +258,8 @@ public static boolean navigateToTotem(TotemLocation totemLocation, net.runelite. // Priority 4: Regular walking. // Only perform actions if not on cooldown (timer-based, non-blocking) if (!isWalkActionOnCooldown()) { - LocalPoint localNextCheckpoint = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), nextCheckpoint); + LocalPoint localNextCheckpoint = Microbot.getClientThread().invoke(() -> + LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), nextCheckpoint)); if (localNextCheckpoint != null && Rs2Camera.isTileOnScreen(localNextCheckpoint)) { // Camera turn with cooldown check if (!isCameraTurnOnCooldown()) { @@ -775,7 +777,17 @@ private static boolean handlePathTransports(List path, int currentIn sleep(1000); while (System.currentTimeMillis() - startTime < timeout) { - boolean isIdle = !Rs2Player.isMoving() && Microbot.getClient().getLocalPlayer().getAnimation() == -1; + boolean isIdle = false; + if (!Rs2Player.isMoving()) { + int animation = Microbot.getClientThread().invoke(() -> { + Player player = Microbot.getClient().getLocalPlayer(); + if (player == null) { + return Integer.MIN_VALUE; + } + return player.getAnimation(); + }); + isIdle = animation == -1; + } if (isIdle) { if (idleTimeStart == -1) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/CoordinateUtils.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/CoordinateUtils.java index a34e4c623b..a88f8a8953 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/CoordinateUtils.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/CoordinateUtils.java @@ -18,7 +18,7 @@ public class CoordinateUtils { * @return player's current WorldPoint */ public static WorldPoint getPlayerLocation() { - return Microbot.getClient().getLocalPlayer().getWorldLocation(); + return Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); } /** diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/GameObjectUtils.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/GameObjectUtils.java index d8cf6278c6..de73cb4edc 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/GameObjectUtils.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/GameObjectUtils.java @@ -279,7 +279,7 @@ public static int getDistanceToNearestObject(int objectId) { if (obj == null) { return -1; } - return obj.getWorldLocation().distanceTo(Microbot.getClient().getLocalPlayer().getWorldLocation()); + return obj.getWorldLocation().distanceTo(CoordinateUtils.getPlayerLocation()); } /** @@ -367,7 +367,7 @@ public static boolean isWithinInteractionRange(GameObject gameObject, int maxDis if (gameObject == null) { return false; } - WorldPoint playerLocation = Microbot.getClient().getLocalPlayer().getWorldLocation(); + WorldPoint playerLocation = CoordinateUtils.getPlayerLocation(); return gameObject.getWorldLocation().distanceTo(playerLocation) <= maxDistance; } From 7ed503184bb0be23440fa18d4f906c1ecebf90fa Mon Sep 17 00:00:00 2001 From: chsami Date: Wed, 8 Apr 2026 07:53:45 +0200 Subject: [PATCH 07/95] fix(AIOFighterPlugin): bump version to 2.0.14 and improve player location checks --- .../plugins/microbot/aiofighter/AIOFighterPlugin.java | 2 +- .../plugins/microbot/aiofighter/bank/BankerScript.java | 4 +++- .../microbot/aiofighter/combat/AttackNpcScript.java | 7 +++++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java index c7adc05c78..9b94239897 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java @@ -61,7 +61,7 @@ ) @Slf4j public class AIOFighterPlugin extends Plugin { - public static final String version = "2.0.13"; + public static final String version = "2.0.14"; public static boolean needShopping = false; private static final String SET = "Set"; private static final String CENTER_TILE = ColorUtil.wrapWithColorTag("Center Tile", JagexColors.MENU_TARGET); diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java index e6c9e516fe..fb1fbef40d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java @@ -58,12 +58,14 @@ public boolean run(AIOFighterConfig config) { try { if (!Microbot.isLoggedIn()) return; if(!super.run()) return; + WorldPoint playerLocation = Rs2Player.getWorldLocation(); + if (playerLocation == null) return; if (config.bank() && needBanking() && !AIOFighterPlugin.needShopping) { if(handleBanking()){ Microbot.log("Banking handled successfully."); } } else if (!needBanking() && - config.centerLocation().distanceTo(Rs2Player.getWorldLocation()) > config.attackRadius() && + config.centerLocation().distanceTo(playerLocation) > config.attackRadius() && !config.centerLocation().equals(new WorldPoint(0, 0, 0))) { boolean shouldWalk = false; diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java index c56a133b22..add2fa90ab 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java @@ -88,8 +88,11 @@ public void run(AIOFighterConfig config) { if (!config.toggleCombat()) return; - if (config.centerLocation().distanceTo(Rs2Player.getWorldLocation()) < config.attackRadius() && - !config.centerLocation().equals(new WorldPoint(0, 0, 0)) && AIOFighterPlugin.getState() != State.BANKING) { + WorldPoint playerLocation = Rs2Player.getWorldLocation(); + if (playerLocation != null + && config.centerLocation().distanceTo(playerLocation) < config.attackRadius() + && !config.centerLocation().equals(new WorldPoint(0, 0, 0)) + && AIOFighterPlugin.getState() != State.BANKING) { if (ShortestPathPlugin.getPathfinder() != null) Rs2Walker.setTarget(null); AIOFighterPlugin.setState(State.IDLE); From 5016e15fc49f2e51ca79dabc72b16010b5e97423 Mon Sep 17 00:00:00 2001 From: chsami Date: Wed, 8 Apr 2026 08:42:42 +0200 Subject: [PATCH 08/95] fix(AIOFighterPlugin): bump version to 2.1.0 and refactor NPC interaction logic --- .../aiofighter/AIOFighterOverlay.java | 2 +- .../microbot/aiofighter/AIOFighterPlugin.java | 5 +- .../aiofighter/combat/AttackNpcScript.java | 119 ++++++++++++------ 3 files changed, 85 insertions(+), 41 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterOverlay.java index 853e2fb5f6..e3237368d9 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterOverlay.java @@ -9,8 +9,8 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.aiofighter.combat.AttackNpcScript; import net.runelite.client.plugins.microbot.aiofighter.model.Monster; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.coords.Rs2WorldArea; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.ui.overlay.OverlayLayer; import net.runelite.client.ui.overlay.OverlayPanel; import net.runelite.client.ui.overlay.OverlayPosition; diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java index 9b94239897..b85533818e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java @@ -61,7 +61,7 @@ ) @Slf4j public class AIOFighterPlugin extends Plugin { - public static final String version = "2.0.14"; + public static final String version = "2.1.0"; public static boolean needShopping = false; private static final String SET = "Set"; private static final String CENTER_TILE = ColorUtil.wrapWithColorTag("Center Tile", JagexColors.MENU_TARGET); @@ -95,7 +95,8 @@ public static void clearWaitForLoot(String reason) { } } - private final AttackNpcScript attackNpc = new AttackNpcScript(); + @Inject + private AttackNpcScript attackNpc; private final FoodScript foodScript = new FoodScript(); private final LootScript lootScript = new LootScript(); private final SafeSpot safeSpotScript = new SafeSpot(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java index add2fa90ab..5f6ec9d124 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java @@ -1,7 +1,10 @@ package net.runelite.client.plugins.microbot.aiofighter.combat; +import com.google.inject.Inject; import lombok.SneakyThrows; import net.runelite.api.Actor; +import net.runelite.api.NPC; +import net.runelite.api.Player; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; import net.runelite.client.plugins.microbot.Microbot; @@ -11,21 +14,21 @@ import net.runelite.client.plugins.microbot.aiofighter.enums.AttackStyle; import net.runelite.client.plugins.microbot.aiofighter.enums.AttackStyleMapper; import net.runelite.client.plugins.microbot.aiofighter.enums.State; +import net.runelite.client.plugins.microbot.api.npc.Rs2NpcCache; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.shortestpath.ShortestPathPlugin; -import net.runelite.client.plugins.microbot.util.ActorModel; 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.ActivityIntensity; import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.coords.Rs2WorldArea; +import net.runelite.client.plugins.microbot.util.coords.Rs2WorldPoint; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.gameobject.Rs2Cannon; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.item.Rs2EnsouledHead; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcManager; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; 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; @@ -37,6 +40,8 @@ import java.util.Arrays; import java.util.Comparator; import java.util.List; +import java.util.Objects; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; @@ -52,6 +57,9 @@ public class AttackNpcScript extends Script { private boolean messageShown = false; private int noNpcCount = 0; + @Inject + private Rs2NpcCache rs2NpcCache; + public static void skipNpc() { currentNpc = null; } @@ -107,15 +115,15 @@ public void run(AIOFighterConfig config) { boolean prevPause = Microbot.pauseAllScripts.getAndSet(true); try { if (head.reanimate()) { - sleepUntil(() -> Rs2Npc.getNpcsForPlayer(Rs2EnsouledHead::isNpcReanimated).findAny().isPresent(), 15000); + sleepUntil(() -> findReanimatedHeadOnPlayer() != null, 15000); } } finally { Microbot.pauseAllScripts.set(prevPause); } } - Rs2NpcModel reanimated = Rs2Npc.getNpcsForPlayer(Rs2EnsouledHead::isNpcReanimated).findAny().orElse(null); + Rs2NpcModel reanimated = findReanimatedHeadOnPlayer(); if (reanimated != null) { - Rs2Npc.interact(reanimated, "Attack"); + reanimated.click("Attack"); return; } } @@ -123,27 +131,38 @@ public void run(AIOFighterConfig config) { attackableArea = new Rs2WorldArea(config.centerLocation().toWorldArea()); attackableArea = attackableArea.offset(config.attackRadius()); - List npcsToAttack = Arrays.stream(config.attackableNpcs().split(",")) + final Set npcsToAttack = Arrays.stream(config.attackableNpcs().split(",")) .map(x -> x.trim().toLowerCase()) + .filter(x -> !x.isEmpty()) + .collect(Collectors.toSet()); + final Player localPlayer = Microbot.getClient().getLocalPlayer(); + final WorldPoint centerLocation = config.centerLocation(); + final int attackRadius = config.attackRadius(); + final boolean requireReachable = config.attackReachableNpcs(); + final Rs2WorldPoint rs2PlayerPoint = Rs2Player.getRs2WorldPoint(); + + List attackableNpcs = rs2NpcCache.query() + .where(npc -> npc.getCombatLevel() > 0 && !npc.isDead()) + .where(npc -> !npc.isInteracting() || Objects.equals(npc.getInteracting(), localPlayer)) + .where(npc -> { + // Single getWorldLocation() call combines the radius and reachable filters + // (each model access is a client-thread invoke; one fetch per NPC per tick). + WorldPoint loc = npc.getWorldLocation(); + if (loc == null) return false; + if (loc.distanceTo(centerLocation) > attackRadius) return false; + return !requireReachable || rs2PlayerPoint.distanceToPath(loc) < Integer.MAX_VALUE; + }) + .where(npc -> { + String name = npc.getName(); + return name != null && !npcsToAttack.isEmpty() && npcsToAttack.contains(name.toLowerCase()); + }) + .toList() + .stream() + .sorted(Comparator + .comparingInt((Rs2NpcModel npc) -> Objects.equals(npc.getInteracting(), localPlayer) ? 0 : 1) + .thenComparingInt(npc -> rs2PlayerPoint.distanceToPath(npc.getWorldLocation()))) .collect(Collectors.toList()); - filteredAttackableNpcs.set( - Rs2Npc.getAttackableNpcs(config.attackReachableNpcs()) - .filter(npc -> npc.getWorldLocation().distanceTo(config.centerLocation()) <= config.attackRadius()) - .filter(npc -> npc.getName() != null && !npcsToAttack.isEmpty() && npcsToAttack.stream().anyMatch(npc.getName()::equalsIgnoreCase)) - .sorted(Comparator.comparingInt((Rs2NpcModel npc) -> npc.getInteracting() == Microbot.getClient().getLocalPlayer() ? 0 : 1) - .thenComparingInt(npc -> Rs2Player.getRs2WorldPoint().distanceToPath(npc.getWorldLocation()))) - .collect(Collectors.toList()) - ); - final List attackableNpcs = new ArrayList<>(); - - for (var attackableNpc : filteredAttackableNpcs.get()) { - if (attackableNpc == null || attackableNpc.getName() == null) continue; - for (var npcToAttack : npcsToAttack) { - if (npcToAttack.equalsIgnoreCase(attackableNpc.getName())) { - attackableNpcs.add(attackableNpc); - } - } - } + filteredAttackableNpcs.set(attackableNpcs); // Check if we should pause while looting is happening @@ -154,19 +173,21 @@ public void run(AIOFighterConfig config) { // Check if we need to update our cached target (but not while waiting for loot) if (!AIOFighterPlugin.isWaitingForLoot()) { Actor currentInteracting = Rs2Player.getInteracting(); - if (currentInteracting instanceof Rs2NpcModel) { - Rs2NpcModel npc = (Rs2NpcModel) currentInteracting; + if (currentInteracting instanceof NPC) { + NPC interactingNpc = (NPC) currentInteracting; // Update our cached target to who we're fighting - if (npc.getHealthRatio() > 0 && !npc.isDead()) { - cachedTargetNpcIndex = npc.getIndex(); + if (interactingNpc.getHealthRatio() > 0 && !interactingNpc.isDead()) { + cachedTargetNpcIndex = interactingNpc.getIndex(); } } } // Check if our cached target died if (config.toggleWaitForLoot() && !AIOFighterPlugin.isWaitingForLoot() && cachedTargetNpcIndex != -1) { - // Find the NPC by index using Rs2 API - Rs2NpcModel cachedNpcModel = Rs2Npc.getNpcByIndex(cachedTargetNpcIndex); + final int targetIndex = cachedTargetNpcIndex; + Rs2NpcModel cachedNpcModel = rs2NpcCache.query() + .where(npc -> npc.getIndex() == targetIndex) + .first(); if (cachedNpcModel != null && (cachedNpcModel.isDead() || (cachedNpcModel.getHealthRatio() == 0 && cachedNpcModel.getHealthScale() > 0))) { AIOFighterPlugin.setWaitingForLoot(true); @@ -221,12 +242,12 @@ public void run(AIOFighterConfig config) { if (!attackableNpcs.isEmpty()) { noNpcCount = 0; - Rs2NpcModel npc = attackableNpcs.stream().findFirst().orElse(null); + Rs2NpcModel npc = attackableNpcs.get(0); if (!Rs2Camera.isTileOnScreen(npc.getLocalLocation())) Rs2Camera.turnTo(npc); - Rs2Npc.interact(npc, "attack"); + npc.click("attack"); Microbot.status = "Attacking " + npc.getName(); Rs2Antiban.actionCooldown(); //sleepUntil(Rs2Player::isInteracting, 1000); @@ -281,21 +302,43 @@ public void run(AIOFighterConfig config) { * item on npcs that need to kill like rockslug */ private void handleItemOnNpcToKill(AIOFighterConfig config) { - Rs2NpcModel npc = Rs2Npc.getNpcsForPlayer(ActorModel::isDead).findFirst().orElse(null); - List lizardVariants = new ArrayList<>(Arrays.asList("Lizard", "Desert Lizard", "Small Lizard")); + final Player localPlayer = Microbot.getClient().getLocalPlayer(); + Rs2NpcModel npc = rs2NpcCache.query() + .where(n -> n.isDead() && Objects.equals(n.getInteracting(), localPlayer)) + .first(); if (npc == null) return; + List lizardVariants = new ArrayList<>(Arrays.asList("Lizard", "Desert Lizard", "Small Lizard")); + // Rs2Inventory.useItemOnNpc only accepts the legacy util.npc.Rs2NpcModel — wrap our underlying NPC at the call site. + net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel legacyNpc = + new net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel(npc.getNpc()); if (Microbot.getVarbitValue(SLAYER_AUTOKILL_DESERTLIZARDS) == 0 && lizardVariants.contains(npc.getName()) && npc.getHealthRatio() < 5) { - Rs2Inventory.useItemOnNpc(ItemID.SLAYER_ICY_WATER, npc); + Rs2Inventory.useItemOnNpc(ItemID.SLAYER_ICY_WATER, legacyNpc); Rs2Player.waitForAnimation(); } else if (Microbot.getVarbitValue(SLAYER_AUTOKILL_ROCKSLUGS) == 0 && npc.getName().equalsIgnoreCase("rockslug") && npc.getHealthRatio() < 5) { - Rs2Inventory.useItemOnNpc(ItemID.SLAYER_BAG_OF_SALT, npc); + Rs2Inventory.useItemOnNpc(ItemID.SLAYER_BAG_OF_SALT, legacyNpc); Rs2Player.waitForAnimation(); } else if (Microbot.getVarbitValue(SLAYER_AUTOKILL_GARGOYLES) == 0 && npc.getName().equalsIgnoreCase("gargoyle") && npc.getHealthRatio() < 3) { - Rs2Inventory.useItemOnNpc(ItemID.SLAYER_ROCK_HAMMER, npc); + Rs2Inventory.useItemOnNpc(ItemID.SLAYER_ROCK_HAMMER, legacyNpc); Rs2Player.waitForAnimation(); } } + /** + * Finds a reanimated head NPC the player is currently interacting with. + * Replaces the legacy {@code Rs2Npc.getNpcsForPlayer(Rs2EnsouledHead::isNpcReanimated)} call. + */ + private Rs2NpcModel findReanimatedHeadOnPlayer() { + final Player localPlayer = Microbot.getClient().getLocalPlayer(); + if (localPlayer == null) return null; + return rs2NpcCache.query() + .where(npc -> Objects.equals(npc.getInteracting(), localPlayer)) + .where(npc -> { + String name = npc.getName(); + return name != null && name.contains("Reanimated"); + }) + .first(); + } + @Override public void shutdown() { super.shutdown(); From 48dbb10e08a0eac6dfef7a7e6df25c278ecaaf66 Mon Sep 17 00:00:00 2001 From: chsami Date: Wed, 8 Apr 2026 14:34:30 +0200 Subject: [PATCH 09/95] fix(AIOFighterPlugin): bump version to 2.1.3 and migrate prayer/slayer/banking/flicker scripts to Queryable API Continues the AIOFighter Queryable API migration started in 2.1.0: - PrayerScript / SlayerScript / FlickerScript now inject Rs2NpcCache and use rs2NpcCache.query() chains instead of legacy Rs2Npc.* lookups. - BankerScript injects Rs2TileObjectCache for the Pool of Refreshment lookup, replacing Rs2GameObject.get()/interact(). The .nearest(20) bound preserves the legacy 51-tile walk-to guard's intent (the new model's click() does not walk-to-object). - Monster.npc swapped to api.npc.models.Rs2NpcModel; AIOFighterOverlay is unchanged because both legacy and new models implement Actor. - Logout-race null guards added to PrayerScript and FlickerScript to mirror legacy Rs2Npc.getNpcsForPlayer's empty-stream behavior. - SlayerScript predicate collapses two getName() calls (each is a client-thread invoke) into a single fetch. - Migrated scripts converted to @Inject in AIOFighterPlugin so Guice satisfies their cache fields. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../microbot/aiofighter/AIOFighterPlugin.java | 14 ++++++---- .../aiofighter/bank/BankerScript.java | 17 ++++++++---- .../aiofighter/combat/FlickerScript.java | 24 ++++++++++++++--- .../aiofighter/combat/PrayerScript.java | 26 ++++++++++++++++--- .../aiofighter/combat/SlayerScript.java | 21 ++++++++++++--- .../microbot/aiofighter/model/Monster.java | 2 +- 6 files changed, 81 insertions(+), 23 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java index b85533818e..a6a9d416f2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java @@ -61,7 +61,7 @@ ) @Slf4j public class AIOFighterPlugin extends Plugin { - public static final String version = "2.1.0"; + public static final String version = "2.1.3"; public static boolean needShopping = false; private static final String SET = "Set"; private static final String CENTER_TILE = ColorUtil.wrapWithColorTag("Center Tile", JagexColors.MENU_TARGET); @@ -100,15 +100,19 @@ public static void clearWaitForLoot(String reason) { private final FoodScript foodScript = new FoodScript(); private final LootScript lootScript = new LootScript(); private final SafeSpot safeSpotScript = new SafeSpot(); - private final FlickerScript flickerScript = new FlickerScript(); + @Inject + private FlickerScript flickerScript; private final BuryScatterScript buryScatterScript = new BuryScatterScript(); private final AttackStyleScript attackStyleScript = new AttackStyleScript(); - private final BankerScript bankerScript = new BankerScript(); - private final PrayerScript prayerScript = new PrayerScript(); + @Inject + private BankerScript bankerScript; + @Inject + private PrayerScript prayerScript; private final HighAlchScript highAlchScript = new HighAlchScript(); private final PotionManagerScript potionManagerScript = new PotionManagerScript(); private final SafetyScript safetyScript = new SafetyScript(); - private final SlayerScript slayerScript = new SlayerScript(); + @Inject + private SlayerScript slayerScript; private final ShopScript shopScript = new ShopScript(); private final DodgeProjectileScript dodgeScript = new DodgeProjectileScript(); @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java index fb1fbef40d..f25fdfafaf 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java @@ -1,7 +1,6 @@ package net.runelite.client.plugins.microbot.aiofighter.bank; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.GameObject; import net.runelite.api.ItemComposition; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; @@ -14,13 +13,14 @@ import net.runelite.client.plugins.microbot.aiofighter.shop.ShopItem; import net.runelite.client.plugins.microbot.aiofighter.shop.ShopScript; import net.runelite.client.plugins.microbot.aiofighter.shop.ShopType; +import net.runelite.client.plugins.microbot.api.tileobject.Rs2TileObjectCache; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.inventorysetups.InventorySetup; import net.runelite.client.plugins.microbot.inventorysetups.MInventorySetupsPlugin; import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.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.util.math.Rs2Random; @@ -46,6 +46,8 @@ public class BankerScript extends Script { private static boolean bankingTriggered = false; @Inject private MInventorySetupsPlugin inventorySetupsPlugin; + @Inject + private Rs2TileObjectCache rs2TileObjectCache; public boolean run(AIOFighterConfig config) { this.config = config; @@ -476,10 +478,15 @@ private void usePoolIfNeeded() { sleepUntil(() -> !Rs2Bank.isOpen(), 2000); } - // Find and use the pool - GameObject pool = Rs2GameObject.get("Pool of Refreshment", true); + // Find and use the pool. Bound to 20 tiles — replaces the legacy + // Rs2GameObject.interact 51-tile walk-to fallback. The new model's click() + // does not walk-to-object, so we must guard distance up front to avoid + // silently dispatching a click against an unreachable pool. + Rs2TileObjectModel pool = rs2TileObjectCache.query() + .withName("Pool of Refreshment") + .nearest(20); if (pool != null) { - if (Rs2GameObject.interact(pool, "Drink")) { + if (pool.click("Drink")) { sleepUntil(Rs2Player::isMoving, 2000); sleepUntil(() -> !Rs2Player.isMoving(), 5000); sleepUntil(Rs2Player::isAnimating, 2000); diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/FlickerScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/FlickerScript.java index f468f0e250..ad0642c244 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/FlickerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/FlickerScript.java @@ -1,6 +1,8 @@ package net.runelite.client.plugins.microbot.aiofighter.combat; +import com.google.inject.Inject; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Player; import net.runelite.api.events.NpcDespawned; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; @@ -9,10 +11,10 @@ import net.runelite.client.plugins.microbot.aiofighter.enums.AttackStyleMapper; import net.runelite.client.plugins.microbot.aiofighter.enums.PrayerStyle; import net.runelite.client.plugins.microbot.aiofighter.model.Monster; +import net.runelite.client.plugins.microbot.api.npc.Rs2NpcCache; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; 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.npc.Rs2NpcManager; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; 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; @@ -30,6 +32,9 @@ public class FlickerScript extends Script { public static final AtomicReference> currentMonstersAttackingUsRef = new AtomicReference<>(new ArrayList<>()); private final AtomicReference> npcsRef = new AtomicReference<>(new ArrayList<>()); + @Inject + private Rs2NpcCache rs2NpcCache; + private AttackStyle prayFlickAttackStyle = null; private boolean usePrayer = false; private boolean flickQuickPrayer = false; @@ -69,8 +74,19 @@ public boolean run(AIOFighterConfig config) { break; } - // Atomically update NPC snapshot - npcsRef.set(Rs2Npc.getNpcsForPlayer().collect(Collectors.toList())); + // Atomically update NPC snapshot — all NPCs currently interacting with the local player. + final Player localPlayer = Microbot.getClient().getLocalPlayer(); + // Logout race: isLoggedIn() returned true above but the player ref can be null + // mid-tick. Legacy Rs2Npc.getNpcsForPlayer returns Stream.empty() in that case; + // mirror that with an empty snapshot so resetLastAttack does nothing useful instead + // of matching every "non-interacting" NPC. + if (localPlayer == null) { + npcsRef.set(new ArrayList<>()); + return; + } + npcsRef.set(rs2NpcCache.query() + .where(npc -> Objects.equals(npc.getInteracting(), localPlayer)) + .toList()); usePrayer = config.togglePrayer(); flickQuickPrayer = config.toggleQuickPray(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/PrayerScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/PrayerScript.java index 804b746d79..2c140b6711 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/PrayerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/PrayerScript.java @@ -1,23 +1,30 @@ package net.runelite.client.plugins.microbot.aiofighter.combat; +import com.google.inject.Inject; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Player; 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.AttackStyle; import net.runelite.client.plugins.microbot.aiofighter.enums.AttackStyleMapper; import net.runelite.client.plugins.microbot.aiofighter.enums.PrayerStyle; +import net.runelite.client.plugins.microbot.api.npc.Rs2NpcCache; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcManager; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; +import java.util.Objects; import java.util.concurrent.TimeUnit; @Slf4j public class PrayerScript extends Script { + + @Inject + private Rs2NpcCache rs2NpcCache; + public boolean run(AIOFighterConfig config) { try { Rs2NpcManager.loadJson(); @@ -38,7 +45,15 @@ private void handlePrayer(AIOFighterConfig config) { if (!Microbot.isLoggedIn() || !config.togglePrayer()) return; if (config.prayerStyle() != PrayerStyle.CONTINUOUS && config.prayerStyle() != PrayerStyle.ALWAYS_ON) return; if (config.prayerStyle() == PrayerStyle.CONTINUOUS) { - boolean underAttack = Rs2Npc.getNpcsForPlayer(npc -> !npc.isDead() && npc.getCombatLevel() > 1).findAny().isPresent() || Rs2Combat.inCombat(); + final Player localPlayer = Microbot.getClient().getLocalPlayer(); + // Logout race: isLoggedIn() returned true above but the player ref can be null + // mid-tick. Legacy Rs2Npc.getNpcsForPlayer guards this; we have to as well. + if (localPlayer == null) return; + boolean underAttack = rs2NpcCache.query() + .where(npc -> Objects.equals(npc.getInteracting(), localPlayer)) + .where(npc -> !npc.isDead() && npc.getCombatLevel() > 1) + .first() != null + || Rs2Combat.inCombat(); if(!underAttack) { Rs2Prayer.disableAllPrayers(); return; @@ -47,7 +62,10 @@ private void handlePrayer(AIOFighterConfig config) { return; } - Rs2NpcModel npc = Rs2Npc.getNpcsForPlayer(n -> !n.isDead() && n.getCombatLevel() > 1).findFirst().orElse(null); + Rs2NpcModel npc = rs2NpcCache.query() + .where(n -> Objects.equals(n.getInteracting(), localPlayer)) + .where(n -> !n.isDead() && n.getCombatLevel() > 1) + .nearest(); if (npc == null) { return; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/SlayerScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/SlayerScript.java index 73cabbcb71..0ce409c67a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/SlayerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/SlayerScript.java @@ -1,5 +1,6 @@ package net.runelite.client.plugins.microbot.aiofighter.combat; +import com.google.inject.Inject; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import net.runelite.api.coords.WorldPoint; @@ -9,10 +10,10 @@ import net.runelite.client.plugins.microbot.aiofighter.AIOFighterPlugin; import net.runelite.client.plugins.microbot.aiofighter.enums.State; import net.runelite.client.plugins.microbot.aiofighter.model.InventorySetupUtil; +import net.runelite.client.plugins.microbot.api.npc.Rs2NpcCache; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.npc.MonsterLocation; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcManager; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.skills.slayer.Rs2Slayer; import java.util.Arrays; @@ -24,6 +25,10 @@ public class SlayerScript extends Script { static WorldPoint cachedMonsterLocation = null; static String cachedMonsterLocationName = null; AIOFighterConfig config; + + @Inject + private Rs2NpcCache rs2NpcCache; + @SneakyThrows public boolean run(AIOFighterConfig config) { this.config = config; @@ -94,9 +99,17 @@ public void handleSlayerTask() { reset(); AIOFighterPlugin.setState(State.GETTING_TASK); if(Rs2Slayer.walkToSlayerMaster(config.slayerMaster())) { - Rs2NpcModel npc = Rs2Npc.getNpc(config.slayerMaster().getName()); + // Preserve legacy partial-name match (Rs2Npc.getNpc uses contains, not equals). + // Single getName() fetch per NPC — each call is a client-thread invoke. + final String masterName = config.slayerMaster().getName().toLowerCase(); + Rs2NpcModel npc = rs2NpcCache.query() + .where(n -> { + String name = n.getName(); + return name != null && name.toLowerCase().contains(masterName); + }) + .nearest(); if(npc != null) { - Rs2Npc.interact(npc, "Assignment"); + npc.click("Assignment"); sleepUntil(Rs2Slayer::hasSlayerTask, 5000); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/model/Monster.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/model/Monster.java index 1a92a71e6f..6fc7720d92 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/model/Monster.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/model/Monster.java @@ -1,8 +1,8 @@ package net.runelite.client.plugins.microbot.aiofighter.model; import net.runelite.client.plugins.microbot.aiofighter.enums.AttackStyle; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcManager; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcStats; public class Monster { From e68adc64dcd9c1a4e7365251af6f23132f91bd97 Mon Sep 17 00:00:00 2001 From: chsami Date: Wed, 8 Apr 2026 14:47:20 +0200 Subject: [PATCH 10/95] fix(AIOFighterPlugin): bump version to 2.1.4 and switch Queryable cache access from @Inject to Microbot static accessors The 2.1.0/2.1.3 @Inject Rs2NpcCache / Rs2TileObjectCache fields and the @Inject script wiring in AIOFighterPlugin caused Guice's createChildInjector to throw inside MicrobotPluginManager.instantiate. The Hub plugin loader silently catches that exception (MicrobotPluginManager.java:564-568): it logs "Incompatible plugin found", deletes the plugin's JAR from ~/.runelite/microbot-plugins, and returns the partially-constructed plugin with all @Inject fields still null. The plugin then sat in the UI list and NPE'd in startUp() at line 150 (config null) when started. Fix: revert the @Inject script wiring back to `new XxxScript()` and switch every cache access from injected field to the upstream static accessor (Microbot.getRs2NpcCache() / Microbot.getRs2TileObjectCache()). The static fields in Microbot.java are populated via requestStaticInjection in RuneLiteModule, which is the same pattern Rs2BoatModel uses upstream. This keeps all the Queryable API call sites identical while bypassing Guice's plugin-classloader child-injector binding-resolution problem entirely. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../microbot/aiofighter/AIOFighterPlugin.java | 17 ++++++----------- .../microbot/aiofighter/bank/BankerScript.java | 5 +---- .../aiofighter/combat/AttackNpcScript.java | 13 ++++--------- .../aiofighter/combat/FlickerScript.java | 7 +------ .../aiofighter/combat/PrayerScript.java | 9 ++------- .../aiofighter/combat/SlayerScript.java | 7 +------ 6 files changed, 15 insertions(+), 43 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java index a6a9d416f2..611c0b0dab 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java @@ -61,7 +61,7 @@ ) @Slf4j public class AIOFighterPlugin extends Plugin { - public static final String version = "2.1.3"; + public static final String version = "2.1.4"; public static boolean needShopping = false; private static final String SET = "Set"; private static final String CENTER_TILE = ColorUtil.wrapWithColorTag("Center Tile", JagexColors.MENU_TARGET); @@ -95,24 +95,19 @@ public static void clearWaitForLoot(String reason) { } } - @Inject - private AttackNpcScript attackNpc; + private final AttackNpcScript attackNpc = new AttackNpcScript(); private final FoodScript foodScript = new FoodScript(); private final LootScript lootScript = new LootScript(); private final SafeSpot safeSpotScript = new SafeSpot(); - @Inject - private FlickerScript flickerScript; + private final FlickerScript flickerScript = new FlickerScript(); private final BuryScatterScript buryScatterScript = new BuryScatterScript(); private final AttackStyleScript attackStyleScript = new AttackStyleScript(); - @Inject - private BankerScript bankerScript; - @Inject - private PrayerScript prayerScript; + private final BankerScript bankerScript = new BankerScript(); + private final PrayerScript prayerScript = new PrayerScript(); private final HighAlchScript highAlchScript = new HighAlchScript(); private final PotionManagerScript potionManagerScript = new PotionManagerScript(); private final SafetyScript safetyScript = new SafetyScript(); - @Inject - private SlayerScript slayerScript; + private final SlayerScript slayerScript = new SlayerScript(); private final ShopScript shopScript = new ShopScript(); private final DodgeProjectileScript dodgeScript = new DodgeProjectileScript(); @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java index f25fdfafaf..7c3d273626 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java @@ -13,7 +13,6 @@ import net.runelite.client.plugins.microbot.aiofighter.shop.ShopItem; import net.runelite.client.plugins.microbot.aiofighter.shop.ShopScript; import net.runelite.client.plugins.microbot.aiofighter.shop.ShopType; -import net.runelite.client.plugins.microbot.api.tileobject.Rs2TileObjectCache; import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.inventorysetups.InventorySetup; import net.runelite.client.plugins.microbot.inventorysetups.MInventorySetupsPlugin; @@ -46,8 +45,6 @@ public class BankerScript extends Script { private static boolean bankingTriggered = false; @Inject private MInventorySetupsPlugin inventorySetupsPlugin; - @Inject - private Rs2TileObjectCache rs2TileObjectCache; public boolean run(AIOFighterConfig config) { this.config = config; @@ -482,7 +479,7 @@ private void usePoolIfNeeded() { // Rs2GameObject.interact 51-tile walk-to fallback. The new model's click() // does not walk-to-object, so we must guard distance up front to avoid // silently dispatching a click against an unreachable pool. - Rs2TileObjectModel pool = rs2TileObjectCache.query() + Rs2TileObjectModel pool = Microbot.getRs2TileObjectCache().query() .withName("Pool of Refreshment") .nearest(20); if (pool != null) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java index 5f6ec9d124..e328439fc4 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java @@ -1,6 +1,5 @@ package net.runelite.client.plugins.microbot.aiofighter.combat; -import com.google.inject.Inject; import lombok.SneakyThrows; import net.runelite.api.Actor; import net.runelite.api.NPC; @@ -14,7 +13,6 @@ import net.runelite.client.plugins.microbot.aiofighter.enums.AttackStyle; import net.runelite.client.plugins.microbot.aiofighter.enums.AttackStyleMapper; import net.runelite.client.plugins.microbot.aiofighter.enums.State; -import net.runelite.client.plugins.microbot.api.npc.Rs2NpcCache; import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.shortestpath.ShortestPathPlugin; import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; @@ -57,9 +55,6 @@ public class AttackNpcScript extends Script { private boolean messageShown = false; private int noNpcCount = 0; - @Inject - private Rs2NpcCache rs2NpcCache; - public static void skipNpc() { currentNpc = null; } @@ -141,7 +136,7 @@ public void run(AIOFighterConfig config) { final boolean requireReachable = config.attackReachableNpcs(); final Rs2WorldPoint rs2PlayerPoint = Rs2Player.getRs2WorldPoint(); - List attackableNpcs = rs2NpcCache.query() + List attackableNpcs = Microbot.getRs2NpcCache().query() .where(npc -> npc.getCombatLevel() > 0 && !npc.isDead()) .where(npc -> !npc.isInteracting() || Objects.equals(npc.getInteracting(), localPlayer)) .where(npc -> { @@ -185,7 +180,7 @@ public void run(AIOFighterConfig config) { // Check if our cached target died if (config.toggleWaitForLoot() && !AIOFighterPlugin.isWaitingForLoot() && cachedTargetNpcIndex != -1) { final int targetIndex = cachedTargetNpcIndex; - Rs2NpcModel cachedNpcModel = rs2NpcCache.query() + Rs2NpcModel cachedNpcModel = Microbot.getRs2NpcCache().query() .where(npc -> npc.getIndex() == targetIndex) .first(); @@ -303,7 +298,7 @@ public void run(AIOFighterConfig config) { */ private void handleItemOnNpcToKill(AIOFighterConfig config) { final Player localPlayer = Microbot.getClient().getLocalPlayer(); - Rs2NpcModel npc = rs2NpcCache.query() + Rs2NpcModel npc = Microbot.getRs2NpcCache().query() .where(n -> n.isDead() && Objects.equals(n.getInteracting(), localPlayer)) .first(); if (npc == null) return; @@ -330,7 +325,7 @@ private void handleItemOnNpcToKill(AIOFighterConfig config) { private Rs2NpcModel findReanimatedHeadOnPlayer() { final Player localPlayer = Microbot.getClient().getLocalPlayer(); if (localPlayer == null) return null; - return rs2NpcCache.query() + return Microbot.getRs2NpcCache().query() .where(npc -> Objects.equals(npc.getInteracting(), localPlayer)) .where(npc -> { String name = npc.getName(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/FlickerScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/FlickerScript.java index ad0642c244..75184478e5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/FlickerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/FlickerScript.java @@ -1,6 +1,5 @@ package net.runelite.client.plugins.microbot.aiofighter.combat; -import com.google.inject.Inject; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Player; import net.runelite.api.events.NpcDespawned; @@ -11,7 +10,6 @@ import net.runelite.client.plugins.microbot.aiofighter.enums.AttackStyleMapper; import net.runelite.client.plugins.microbot.aiofighter.enums.PrayerStyle; import net.runelite.client.plugins.microbot.aiofighter.model.Monster; -import net.runelite.client.plugins.microbot.api.npc.Rs2NpcCache; import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcManager; @@ -32,9 +30,6 @@ public class FlickerScript extends Script { public static final AtomicReference> currentMonstersAttackingUsRef = new AtomicReference<>(new ArrayList<>()); private final AtomicReference> npcsRef = new AtomicReference<>(new ArrayList<>()); - @Inject - private Rs2NpcCache rs2NpcCache; - private AttackStyle prayFlickAttackStyle = null; private boolean usePrayer = false; private boolean flickQuickPrayer = false; @@ -84,7 +79,7 @@ public boolean run(AIOFighterConfig config) { npcsRef.set(new ArrayList<>()); return; } - npcsRef.set(rs2NpcCache.query() + npcsRef.set(Microbot.getRs2NpcCache().query() .where(npc -> Objects.equals(npc.getInteracting(), localPlayer)) .toList()); diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/PrayerScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/PrayerScript.java index 2c140b6711..a6f48de6df 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/PrayerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/PrayerScript.java @@ -1,6 +1,5 @@ package net.runelite.client.plugins.microbot.aiofighter.combat; -import com.google.inject.Inject; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Player; import net.runelite.client.plugins.microbot.Microbot; @@ -9,7 +8,6 @@ import net.runelite.client.plugins.microbot.aiofighter.enums.AttackStyle; import net.runelite.client.plugins.microbot.aiofighter.enums.AttackStyleMapper; import net.runelite.client.plugins.microbot.aiofighter.enums.PrayerStyle; -import net.runelite.client.plugins.microbot.api.npc.Rs2NpcCache; import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcManager; @@ -22,9 +20,6 @@ @Slf4j public class PrayerScript extends Script { - @Inject - private Rs2NpcCache rs2NpcCache; - public boolean run(AIOFighterConfig config) { try { Rs2NpcManager.loadJson(); @@ -49,7 +44,7 @@ private void handlePrayer(AIOFighterConfig config) { // Logout race: isLoggedIn() returned true above but the player ref can be null // mid-tick. Legacy Rs2Npc.getNpcsForPlayer guards this; we have to as well. if (localPlayer == null) return; - boolean underAttack = rs2NpcCache.query() + boolean underAttack = Microbot.getRs2NpcCache().query() .where(npc -> Objects.equals(npc.getInteracting(), localPlayer)) .where(npc -> !npc.isDead() && npc.getCombatLevel() > 1) .first() != null @@ -62,7 +57,7 @@ private void handlePrayer(AIOFighterConfig config) { return; } - Rs2NpcModel npc = rs2NpcCache.query() + Rs2NpcModel npc = Microbot.getRs2NpcCache().query() .where(n -> Objects.equals(n.getInteracting(), localPlayer)) .where(n -> !n.isDead() && n.getCombatLevel() > 1) .nearest(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/SlayerScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/SlayerScript.java index 0ce409c67a..fb14a39fbe 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/SlayerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/SlayerScript.java @@ -1,6 +1,5 @@ package net.runelite.client.plugins.microbot.aiofighter.combat; -import com.google.inject.Inject; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import net.runelite.api.coords.WorldPoint; @@ -10,7 +9,6 @@ import net.runelite.client.plugins.microbot.aiofighter.AIOFighterPlugin; import net.runelite.client.plugins.microbot.aiofighter.enums.State; import net.runelite.client.plugins.microbot.aiofighter.model.InventorySetupUtil; -import net.runelite.client.plugins.microbot.api.npc.Rs2NpcCache; import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.npc.MonsterLocation; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcManager; @@ -26,9 +24,6 @@ public class SlayerScript extends Script { static String cachedMonsterLocationName = null; AIOFighterConfig config; - @Inject - private Rs2NpcCache rs2NpcCache; - @SneakyThrows public boolean run(AIOFighterConfig config) { this.config = config; @@ -102,7 +97,7 @@ public void handleSlayerTask() { // Preserve legacy partial-name match (Rs2Npc.getNpc uses contains, not equals). // Single getName() fetch per NPC — each call is a client-thread invoke. final String masterName = config.slayerMaster().getName().toLowerCase(); - Rs2NpcModel npc = rs2NpcCache.query() + Rs2NpcModel npc = Microbot.getRs2NpcCache().query() .where(n -> { String name = n.getName(); return name != null && name.toLowerCase().contains(masterName); From 41bbd5749f5548677cf14f327bbe66d46e1e7e9e Mon Sep 17 00:00:00 2001 From: chsami Date: Wed, 8 Apr 2026 15:12:17 +0200 Subject: [PATCH 11/95] fix(HueyPrayerPlugin): update version to 1.0.1 and enhance plugin descriptor with default settings --- .../microbot/HueycoatlPrayer/HueyPrayerPlugin.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerPlugin.java index 4ecf14ec96..e2d626ef66 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerPlugin.java @@ -17,18 +17,21 @@ import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.PluginConstants; @Slf4j @PluginDescriptor( - name = "Huey Prayer", + name = PluginConstants.DEFAULT_PREFIX + "Huey Prayer", description = "Auto prayer vs Hueycoatl (3-style projectile)", tags = {"microbot", "huey", "prayer"}, version = HueyPrayerPlugin.VERSION, - minClientVersion = "2.1.34" + minClientVersion = "2.1.34", + enabledByDefault = PluginConstants.DEFAULT_ENABLED, + isExternal = PluginConstants.IS_EXTERNAL ) public class HueyPrayerPlugin extends Plugin { - static final String VERSION = "1.0.0"; + static final String VERSION = "1.0.1"; @Inject private Client client; From c03ff663c03fd3d34a9b126c99b300c9b7a276b4 Mon Sep 17 00:00:00 2001 From: JThomasDevs <95548936+JThomasDevs@users.noreply.github.com> Date: Wed, 8 Apr 2026 12:10:34 -0600 Subject: [PATCH 12/95] Add Use Cargo Hold to salvaging script and debug thoroughly (#368) * Reduce code smell, implement sawmill vouchers and Lazy Mode * Change profit calculation to be more accurate, extended Logs enum to prevent weird bank withdrawal shenanigans (tried withdrawing yew logs) * undo main runner change * re-add microbot.java. whoops. * Karam fix (#351) * fix: karambwan fairy ring return Made-with: Cursor * plugin now clicks on fairy ring to get back to karams * commit * Add Use Cargo Hold to salvaging script and debug thoroughly * various fixes and changed hold capacity counting to use widget information as opposed to walking the item graph * small fixes * Boat facilities detection fixes, comment changes --------- Co-authored-by: chsami Co-authored-by: stonksCode <99895926+stonksCode@users.noreply.github.com> --- .../microbot/sailing/MSailingPlugin.java | 8 +- .../microbot/sailing/SailingConfig.java | 16 +- .../salvaging/CargoHoldInterfaceWidgets.java | 19 + .../salvaging/CargoHoldObjectIds.java | 87 ++ .../salvaging/SalvagingHighlight.java | 16 +- .../features/salvaging/SalvagingScript.java | 1352 ++++++++++++++++- .../salvaging/SalvagingStationObjectIds.java | 24 + .../microbot/sailing/docs/CHANGELOG.md | 27 + .../plugins/microbot/sailing/docs/README.md | 26 +- 9 files changed, 1536 insertions(+), 39 deletions(-) create mode 100644 src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/CargoHoldInterfaceWidgets.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/CargoHoldObjectIds.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingStationObjectIds.java diff --git a/src/main/java/net/runelite/client/plugins/microbot/sailing/MSailingPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/sailing/MSailingPlugin.java index 2bd54dadcd..2028e16c13 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sailing/MSailingPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sailing/MSailingPlugin.java @@ -7,6 +7,7 @@ import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.plugins.microbot.PluginConstants; import net.runelite.client.plugins.microbot.sailing.features.salvaging.SalvagingHighlight; +import net.runelite.client.plugins.microbot.sailing.features.salvaging.SalvagingScript; import net.runelite.client.plugins.microbot.sailing.features.trials.TrialsScript; import net.runelite.client.plugins.microbot.sailing.features.trials.debug.BoatPathOverlay; import net.runelite.client.plugins.microbot.sailing.features.trials.overlay.TrialRouteOverlay; @@ -30,7 +31,7 @@ @Slf4j public class MSailingPlugin extends Plugin { - static final String version = "2.1.0"; + static final String version = "2.2.56"; @Inject private SailingConfig config; @@ -46,6 +47,9 @@ SailingConfig provideConfig(ConfigManager configManager) { @Inject private SalvagingHighlight salvagingHighlight; + @Inject + private SalvagingScript salvagingScript; + @Inject private SailingScript sailingScript; @Inject @@ -63,6 +67,7 @@ protected void startUp() throws AWTException { overlayManager.add(boatPathOverlay); overlayManager.add(trialRouteOverlay); } + salvagingScript.register(); trialsScript.register(); sailingScript.run(); } @@ -70,6 +75,7 @@ protected void startUp() throws AWTException { protected void shutDown() { sailingScript.shutdown(); trialsScript.shutdown(); + salvagingScript.unregister(); trialsScript.unregister(); overlayManager.remove(sailingOverlay); overlayManager.remove(salvagingHighlight); diff --git a/src/main/java/net/runelite/client/plugins/microbot/sailing/SailingConfig.java b/src/main/java/net/runelite/client/plugins/microbot/sailing/SailingConfig.java index 9cd3d23aef..d6f21737b3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sailing/SailingConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sailing/SailingConfig.java @@ -83,11 +83,23 @@ default boolean openCaskets() return false; } + @ConfigItem( + keyName = "useCargoHold", + name = "Use Cargo Hold", + description = "When salvaging, open the cargo hold and use Deposit inventory, then read the hold grid for fullness. When the hold is full or nearly full, the script withdraws and processes salvage (alch/drop/caskets) until salvage stacks in the hold reach zero.", + position = 3, + section = generalSection + ) + default boolean useCargoHold() + { + return false; + } + @ConfigItem( keyName = "alchOrder", name = "Alch Order", description = "Order in which to high alch items. LIST_ORDER follows your alch list. LEFT_TO_RIGHT sweeps row by row. RIGHT_TO_LEFT sweeps rows right to left. TOP_TO_BOTTOM sweeps column by column. BOTTOM_TO_TOP sweeps columns bottom to top.", - position = 3, + position = 4, section = generalSection ) default AlchOrder alchOrder() @@ -99,7 +111,7 @@ default AlchOrder alchOrder() keyName = "dropItems", name = "Drop items", description = "Comma-separated list of items to drop when salvaging.", - position = 4, + position = 5, section = generalSection ) default String dropItems() diff --git a/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/CargoHoldInterfaceWidgets.java b/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/CargoHoldInterfaceWidgets.java new file mode 100644 index 0000000000..b07547c96b --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/CargoHoldInterfaceWidgets.java @@ -0,0 +1,19 @@ +package net.runelite.client.plugins.microbot.sailing.features.salvaging; + +/** + * Widget ids for the sailing cargo-hold interface not exposed on {@link net.runelite.api.gameval.InterfaceID}. + */ +public final class CargoHoldInterfaceWidgets { + + private CargoHoldInterfaceWidgets() { + } + + /** + * Occupied-slot count text while the hold is open ({@code client.getWidget(943, 4)}). In practice this child often + * shows only {@code X} (occupied slots), not a full {@code X / N} line; we still parse the first run of digits from + * {@link net.runelite.api.widgets.Widget#getText()} so either shape works. + */ + public static final int CARGO_HOLD_OCCUPIED_TEXT_GROUP = 943; + + public static final int CARGO_HOLD_OCCUPIED_TEXT_CHILD = 4; +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/CargoHoldObjectIds.java b/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/CargoHoldObjectIds.java new file mode 100644 index 0000000000..4de6acb09d --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/CargoHoldObjectIds.java @@ -0,0 +1,87 @@ +package net.runelite.client.plugins.microbot.sailing.features.salvaging; + +import com.google.common.collect.ImmutableMap; +import net.runelite.api.gameval.ObjectID1; + +import java.util.Collections; +import java.util.Map; +import java.util.Set; + +/** + * Cargo hold tile object IDs and capacities per wood tier and boat layout (raft / 2x5 skiff / large sloop). + * {@link ObjectID1} defines three visuals per tier: {@code SAILING_BOAT_CARGO_HOLD__}, + * {@code ..._NO_CARGO}, and {@code ..._CARGO}; all map to the same slot capacity. + */ +public final class CargoHoldObjectIds { + + private CargoHoldObjectIds() { + } + + /** + * Every cargo hold object ID (base, no-cargo, and cargo visuals) mapped to maximum slots for that + * boat tier. Used occupancy is read from the open cargo-hold interface item grid (widgets) after opening the hold. + */ + public static final Map ID_TO_CAPACITY = buildIdToCapacity(); + + public static final Set ALL_IDS = Collections.unmodifiableSet(ID_TO_CAPACITY.keySet()); + + private static Map buildIdToCapacity() { + ImmutableMap.Builder b = ImmutableMap.builder(); + // Basic (Regular) — raft 20, skiff (2x5) 30, sloop (large) 40 + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_REGULAR_RAFT, + ObjectID1.SAILING_BOAT_CARGO_HOLD_REGULAR_RAFT_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_REGULAR_RAFT_CARGO, 20); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_REGULAR_2X5, + ObjectID1.SAILING_BOAT_CARGO_HOLD_REGULAR_2X5_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_REGULAR_2X5_CARGO, 30); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_REGULAR_LARGE, + ObjectID1.SAILING_BOAT_CARGO_HOLD_REGULAR_LARGE_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_REGULAR_LARGE_CARGO, 40); + // Oak — 30, 45, 60 + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_OAK_RAFT, + ObjectID1.SAILING_BOAT_CARGO_HOLD_OAK_RAFT_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_OAK_RAFT_CARGO, 30); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_OAK_2X5, + ObjectID1.SAILING_BOAT_CARGO_HOLD_OAK_2X5_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_OAK_2X5_CARGO, 45); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_OAK_LARGE, + ObjectID1.SAILING_BOAT_CARGO_HOLD_OAK_LARGE_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_OAK_LARGE_CARGO, 60); + // Teak — 45, 60, 90 + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_TEAK_RAFT, + ObjectID1.SAILING_BOAT_CARGO_HOLD_TEAK_RAFT_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_TEAK_RAFT_CARGO, 45); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_TEAK_2X5, + ObjectID1.SAILING_BOAT_CARGO_HOLD_TEAK_2X5_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_TEAK_2X5_CARGO, 60); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_TEAK_LARGE, + ObjectID1.SAILING_BOAT_CARGO_HOLD_TEAK_LARGE_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_TEAK_LARGE_CARGO, 90); + // Mahogany — 60, 90, 120 + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_MAHOGANY_RAFT, + ObjectID1.SAILING_BOAT_CARGO_HOLD_MAHOGANY_RAFT_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_MAHOGANY_RAFT_CARGO, 60); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_MAHOGANY_2X5, + ObjectID1.SAILING_BOAT_CARGO_HOLD_MAHOGANY_2X5_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_MAHOGANY_2X5_CARGO, 90); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_MAHOGANY_LARGE, + ObjectID1.SAILING_BOAT_CARGO_HOLD_MAHOGANY_LARGE_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_MAHOGANY_LARGE_CARGO, 120); + // Camphor — 80, 120, 160 + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_CAMPHOR_RAFT, + ObjectID1.SAILING_BOAT_CARGO_HOLD_CAMPHOR_RAFT_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_CAMPHOR_RAFT_CARGO, 80); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_CAMPHOR_2X5, + ObjectID1.SAILING_BOAT_CARGO_HOLD_CAMPHOR_2X5_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_CAMPHOR_2X5_CARGO, 120); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_CAMPHOR_LARGE, + ObjectID1.SAILING_BOAT_CARGO_HOLD_CAMPHOR_LARGE_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_CAMPHOR_LARGE_CARGO, 160); + // Ironwood — 105, 150, 210 + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_IRONWOOD_RAFT, + ObjectID1.SAILING_BOAT_CARGO_HOLD_IRONWOOD_RAFT_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_IRONWOOD_RAFT_CARGO, 105); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_IRONWOOD_2X5, + ObjectID1.SAILING_BOAT_CARGO_HOLD_IRONWOOD_2X5_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_IRONWOOD_2X5_CARGO, 150); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_IRONWOOD_LARGE, + ObjectID1.SAILING_BOAT_CARGO_HOLD_IRONWOOD_LARGE_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_IRONWOOD_LARGE_CARGO, 210); + // Rosewood — 120, 180, 240 + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_ROSEWOOD_RAFT, + ObjectID1.SAILING_BOAT_CARGO_HOLD_ROSEWOOD_RAFT_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_ROSEWOOD_RAFT_CARGO, 120); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_ROSEWOOD_2X5, + ObjectID1.SAILING_BOAT_CARGO_HOLD_ROSEWOOD_2X5_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_ROSEWOOD_2X5_CARGO, 180); + putTier(b, ObjectID1.SAILING_BOAT_CARGO_HOLD_ROSEWOOD_LARGE, + ObjectID1.SAILING_BOAT_CARGO_HOLD_ROSEWOOD_LARGE_NO_CARGO, ObjectID1.SAILING_BOAT_CARGO_HOLD_ROSEWOOD_LARGE_CARGO, 240); + return b.build(); + } + + private static void putTier(ImmutableMap.Builder b, int baseId, int noCargoId, int cargoId, int capacity) { + b.put(baseId, capacity); + b.put(noCargoId, capacity); + b.put(cargoId, capacity); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingHighlight.java b/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingHighlight.java index 6a7bdfaaf7..56ecd4cf51 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingHighlight.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingHighlight.java @@ -14,6 +14,11 @@ import javax.inject.Inject; import java.awt.*; +/** + * Renders the same salvage-area outline as the RuneLite Plugin Hub "Sailing" plugin + * ({@code com.duckblade.osrs.sailing.features.salvaging.SalvagingHighlight}): {@link Perspective#getCanvasTileAreaPoly} + * with a fixed tile radius. Wreck lists come from {@link SalvagingScript} (top-level scene scan + spawn sync). + */ @Slf4j public class SalvagingHighlight extends Overlay { @@ -38,7 +43,7 @@ public Dimension render(Graphics2D graphics) { return null; } - int sailingLevel = client.getBoostedSkillLevel(Skill.SAILING); + int sailingLevel = safeBoostedSailingLevel(client); for (var wreck : salvagingScript.getActiveWrecks()) { Integer levelReq = SalvageObjectIds.SALVAGE_LEVEL_REQ.get(wreck.getId()); @@ -67,6 +72,15 @@ public Dimension render(Graphics2D graphics) { return null; } + /** When the client's skill level buffer is too short, {@link Client#getBoostedSkillLevel(Skill)} can throw. */ + private static int safeBoostedSailingLevel(Client client) { + try { + return client.getBoostedSkillLevel(Skill.SAILING); + } catch (RuntimeException ex) { + return 99; + } + } + private void renderWreck(Graphics2D graphics, Rs2TileObjectModel wreck, Color colour) { Polygon poly = Perspective.getCanvasTileAreaPoly(client, wreck.getLocalLocation(), SIZE_SALVAGEABLE_AREA); if (poly != null) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingScript.java b/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingScript.java index c8b02a7134..b2d967df11 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingScript.java @@ -1,79 +1,298 @@ package net.runelite.client.plugins.microbot.sailing.features.salvaging; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.Constants; +import net.runelite.api.DecorativeObject; +import net.runelite.api.GameObject; +import net.runelite.api.Player; +import net.runelite.api.Scene; import net.runelite.api.Skill; +import net.runelite.api.Tile; +import net.runelite.api.TileObject; +import net.runelite.api.WorldView; import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.ChatMessage; +import net.runelite.api.events.GameTick; +import net.runelite.api.gameval.InterfaceID; +import net.runelite.api.widgets.Widget; +import net.runelite.client.eventbus.EventBus; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.api.boat.Rs2BoatCache; import net.runelite.client.plugins.microbot.api.player.models.Rs2PlayerModel; import net.runelite.client.plugins.microbot.api.tileobject.Rs2TileObjectCache; import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.sailing.AlchOrder; import net.runelite.client.plugins.microbot.sailing.SailingConfig; +import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; 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.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; import net.runelite.client.plugins.microbot.util.math.Rs2Random; +import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; +import net.runelite.client.util.Text; import javax.inject.Inject; +import javax.inject.Singleton; +import java.awt.event.KeyEvent; +import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; import java.util.stream.Collectors; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static net.runelite.client.plugins.microbot.util.Global.sleep; import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; @Slf4j +@Singleton public class SalvagingScript { + /** First decimal number in the occupied widget text (often just {@code X}; still works if the client shows {@code X / N}). */ + private static final Pattern CARGO_HOLD_FIRST_NUMBER = Pattern.compile("(\\d+)"); + private static final int SIZE_SALVAGEABLE_AREA = 15; private static final int MIN_INVENTORY_FULL = 24; - private static final int MAX_INVENTORY_FULL = 28; private static final int SALVAGE_TIMEOUT = 20000; private static final int DEPLOY_TIMEOUT = 5000; private static final int WAIT_TIME = 5000; private static final int WAIT_TIME_MAX = 10000; - + private static final int CARGO_HOLD_UI_TIMEOUT_MS = 8000; + private static final int CARGO_HOLD_WITHDRAW_FAIL_THRESHOLD = 5; + private static final int CARGO_HOLD_WITHDRAW_NO_GAIN_THRESHOLD = 5; + /** Wait for inventory to reflect the withdraw after the salvage slot is clicked. */ + private static final int CARGO_HOLD_WITHDRAW_INVENTORY_TIMEOUT_MS = 12000; + /** Pause after clicking the salvage slot so the client can apply the withdraw before inventory polling or Escape. */ + private static final int CARGO_HOLD_POST_WITHDRAW_CLICK_MIN_MS = 2200; + private static final int CARGO_HOLD_POST_WITHDRAW_CLICK_MAX_MS = 5000; + /** After salvage appears in inventory, wait again before Escape so the hold does not close mid-pipeline. */ + private static final int CARGO_HOLD_BEFORE_CLOSE_AFTER_WITHDRAW_MIN_MS = 800; + private static final int CARGO_HOLD_BEFORE_CLOSE_AFTER_WITHDRAW_MAX_MS = 2200; + /** Periodically re-open the hold and count ITEMS widgets so "full" stays accurate without item-container tracking. */ + private static final int CARGO_HOLD_WIDGET_RESYNC_MIN_MS = 4500; + /** After Deposit inventory, occupied text can lag; retry read while the panel stays open before Escape. */ + private static final int CARGO_HOLD_POST_DEPOSIT_READ_ATTEMPTS = 6; + private static final int CARGO_HOLD_POST_DEPOSIT_READ_GAP_MIN_MS = 120; + private static final int CARGO_HOLD_POST_DEPOSIT_READ_GAP_MAX_MS = 320; + /** Radius for {@link Rs2GameObject} scans and name fallbacks around the player (boat nested view, port, etc.). */ + private static final int NEARBY_TILE_OBJECT_SCAN_RADIUS = 32; + /** + * In-game message when deposit fails because every slot is taken ({@code Text.standardize} comparison, so extra + * punctuation or wording after this phrase still matches). + */ + private static final String CARGO_HOLD_FULL_MESSAGE_CONTAINS = "the cargo hold is full"; private final Rs2TileObjectCache tileObjectCache; + @SuppressWarnings("unused") private final Rs2BoatCache boatCache; + private final EventBus eventBus; + + /** + * Shipwreck lists rebuilt each {@link GameTick} on the client thread by scanning {@link Client#getTopLevelWorldView()} + * (the sea layer). Plugin Hub "Sailing" uses game object spawn/despawn events instead; both target the same + * {@link GameObject} ids. At-sea wrecks live on the top-level sea scene; boat facilities (e.g. cargo hold) live in the + * boarded boat's nested {@link WorldView}. {@link Rs2GameObject} / tile cache can miss one or the other depending + * on context; shipwrecks use an explicit top-level scene walk, cargo hold uses the local player world view scene walk. + */ + private final Map activeWreckByKey = new HashMap<>(); + private final Map inactiveWreckByKey = new HashMap<>(); + private volatile List activeWreckSnapshot = List.of(); + private volatile List inactiveWreckSnapshot = List.of(); + + /** Max cargo slots for this boat tier ({@link CargoHoldObjectIds#ID_TO_CAPACITY}). */ + private int cargoHoldCapacity = -1; + /** Occupied slots: parsed from {@link CargoHoldInterfaceWidgets} occupied text (usually just {@code X}) when the hold is open; else ITEMS grid count. */ + private volatile int cargoHoldCount = -1; + /** Salvage stacks in the hold from the same grid (item name contains "salvage"; one slot = one stack). */ + private volatile int cargoHoldSalvageStackCount = -1; + private volatile boolean cargoHoldProcessing = false; + private int lastCargoHoldObjectId = -1; + private int cargoHoldWithdrawFailures = 0; + /** Consecutive withdraw clicks that did not change inventory (separate from open failures). */ + private int cargoHoldWithdrawNoGainStreak = 0; + private long lastCargoHoldInitAttemptMs; + private long lastCargoHoldInitHintLogMs; + private long lastCargoHoldWidgetResyncMs; @Inject - public SalvagingScript(Rs2TileObjectCache tileObjectCache, Rs2BoatCache boatCache) { + public SalvagingScript(Rs2TileObjectCache tileObjectCache, Rs2BoatCache boatCache, EventBus eventBus) { this.tileObjectCache = tileObjectCache; this.boatCache = boatCache; + this.eventBus = eventBus; + } + + public void register() { + eventBus.register(this); + } + + public void unregister() { + eventBus.unregister(this); + } + + @Subscribe + public void onGameTick(GameTick event) { + rebuildShipwreckMapsFromTopLevelScene(); + activeWreckSnapshot = List.copyOf(activeWreckByKey.values()); + inactiveWreckSnapshot = List.copyOf(inactiveWreckByKey.values()); + } + + /** + * Full top-level scene pass (sea layer), same objects the client draws for distant water tiles. + */ + private void rebuildShipwreckMapsFromTopLevelScene() { + activeWreckByKey.clear(); + inactiveWreckByKey.clear(); + Client client = Microbot.getClient(); + if (client == null) { + return; + } + WorldView wv = client.getTopLevelWorldView(); + if (wv == null) { + return; + } + Scene scene = wv.getScene(); + if (scene == null) { + return; + } + Tile[][][] tiles = scene.getTiles(); + if (tiles == null) { + return; + } + int plane = wv.getPlane(); + if (plane < 0) { + return; + } + if (plane >= tiles.length) { + return; + } + Tile[][] planeTiles = tiles[plane]; + if (planeTiles == null) { + return; + } + int maxX = Math.min(Constants.SCENE_SIZE, planeTiles.length); + for (int x = 0; x < maxX; x++) { + Tile[] column = planeTiles[x]; + if (column == null) { + continue; + } + int maxY = Math.min(Constants.SCENE_SIZE, column.length); + for (int y = 0; y < maxY; y++) { + Tile tile = column[y]; + if (tile == null) { + continue; + } + GameObject[] gameObjects = tile.getGameObjects(); + if (gameObjects != null) { + for (GameObject go : gameObjects) { + if (go == null) { + continue; + } + if (!go.getSceneMinLocation().equals(tile.getSceneLocation())) { + continue; + } + considerShipwreckTileObjectForRebuild(go); + } + } + DecorativeObject dec = tile.getDecorativeObject(); + if (dec != null) { + considerShipwreckTileObjectForRebuild(dec); + } + } + } + } + + private void considerShipwreckTileObjectForRebuild(TileObject obj) { + int id = obj.getId(); + if (SalvageObjectIds.ACTIVE_SHIPWRECK_IDS.contains(id)) { + activeWreckByKey.put(dedupeKey(obj), new Rs2TileObjectModel(obj)); + return; + } + if (SalvageObjectIds.INACTIVE_SHIPWRECK_IDS.contains(id)) { + inactiveWreckByKey.put(dedupeKey(obj), new Rs2TileObjectModel(obj)); + } + } + + private static String dedupeKey(TileObject o) { + WorldPoint p = o.getWorldLocation(); + return o.getId() + ":" + p.getX() + ":" + p.getY() + ":" + p.getPlane(); + } + + private static List mergeDistinctTileObjectLists(List a, List b) { + Map byKey = new LinkedHashMap<>(); + for (Rs2TileObjectModel m : a) { + byKey.put(tileObjectDedupeKey(m), m); + } + for (Rs2TileObjectModel m : b) { + String key = tileObjectDedupeKey(m); + if (!byKey.containsKey(key)) { + byKey.put(key, m); + } + } + return List.copyOf(byKey.values()); + } + + private static String tileObjectDedupeKey(Rs2TileObjectModel m) { + return dedupeKey(m); } public List getActiveWrecks() { - return tileObjectCache.query() - .where(wreck -> SalvageObjectIds.ACTIVE_SHIPWRECK_IDS.contains(wreck.getId())) - .toListOnClientThread(); + return activeWreckSnapshot; } public List getInactiveWrecks() { - return tileObjectCache.query() - .where(wreck -> SalvageObjectIds.INACTIVE_SHIPWRECK_IDS.contains(wreck.getId())) - .toListOnClientThread(); + return inactiveWreckSnapshot; } public void run(SailingConfig config) { try { var player = new Rs2PlayerModel(); + if (!config.useCargoHold()) { + resetCargoHoldState(); + } else { + if (cargoHoldCapacity == -1) { + initCargoHold(); + } + } + if (isPlayerAnimating(player)) { log.info("Currently salvaging, waiting..."); sleep(WAIT_TIME, WAIT_TIME_MAX); return; } - // Check inventory FIRST before deciding whether to salvage + if (config.useCargoHold()) { + if (handleCargoHoldMode(config, player)) { + return; + } + } + + if (tryRunIdleInventoryCleanup(config)) { + return; + } + if (isInventoryFull()) { - log.info("Inventory full, handling before salvaging"); + if (config.useCargoHold() && cargoHoldProcessing && hasSalvageItems()) { + log.info("Inventory full during cargo-hold processing; processing salvage at station before more withdraws"); + } else { + log.info("Inventory full, handling before salvaging"); + } handleFullInventory(config, player); return; } - // Inventory has space — go find a wreck and salvage var nearbyWreck = findNearestWreck(player.getWorldLocation()); if (nearbyWreck == null) { log.info("No shipwreck found nearby"); @@ -88,12 +307,907 @@ public void run(SailingConfig config) { } } + private void resetCargoHoldState() { + cargoHoldCapacity = -1; + cargoHoldCount = -1; + cargoHoldSalvageStackCount = -1; + cargoHoldProcessing = false; + lastCargoHoldObjectId = -1; + cargoHoldWithdrawFailures = 0; + cargoHoldWithdrawNoGainStreak = 0; + lastCargoHoldInitAttemptMs = 0; + lastCargoHoldInitHintLogMs = 0; + lastCargoHoldWidgetResyncMs = 0; + } + + /** + * @return true if this tick is fully handled and {@link #run(SailingConfig)} should return. + */ + private boolean handleCargoHoldMode(SailingConfig config, Rs2PlayerModel player) { + syncCargoHoldIfObjectVariantChanged(); + if (cargoHoldCapacity == -1) { + initCargoHold(); + if (cargoHoldCapacity == -1) { + logCargoHoldInitThrottled( + "Cargo hold not initialized yet; salvaging continues. Stand on your boat near the hold."); + return false; + } + } + + refreshCargoHoldCountsIfPanelOpen(); + + if (!cargoHoldProcessing) { + if (hasNearbySalvageableWreck(player.getWorldLocation()) || hasSalvageItems()) { + if (!willDepositSalvageToCargoHoldImminently()) { + maybeResyncCargoHoldCountsFromOpenUi(); + } + } + } + + if (cargoHoldProcessing || shouldProcessCargoHold()) { + if (!cargoHoldProcessing && shouldProcessCargoHold()) { + cargoHoldProcessing = true; + log.info("Cargo hold processing phase started (full or near capacity)"); + } + if (cargoHoldSalvageStackCount == 0) { + cargoHoldProcessing = false; + cargoHoldWithdrawFailures = 0; + cargoHoldWithdrawNoGainStreak = 0; + log.info("No salvage left in cargo hold, resuming normal salvaging"); + return false; + } + if (hasSalvageItems() && canDepositSalvageToCargoHold() + && !suppressSalvageDepositDuringCargoHoldProcessing()) { + depositToCargoHold(); + return true; + } + if (isInventoryFull()) { + handleFullInventory(config, player); + return true; + } + boolean fillingInventoryFromHold = !isInventoryFull() + && (cargoHoldSalvageStackCount > 0 + || (cargoHoldSalvageStackCount < 0 && cargoHoldCount > 0)); + if (!fillingInventoryFromHold && !hasNearbySalvageableWreck(player.getWorldLocation())) { + return false; + } + processCargoHoldWithdrawStep(); + return true; + } + + return false; + } + + private void syncCargoHoldIfObjectVariantChanged() { + if (cargoHoldCapacity < 0) { + return; + } + Rs2TileObjectModel hold = findCargoHold(); + if (hold == null) { + return; + } + int id = hold.getId(); + if (lastCargoHoldObjectId < 0) { + lastCargoHoldObjectId = id; + return; + } + if (id == lastCargoHoldObjectId) { + return; + } + lastCargoHoldObjectId = id; + Integer cap = CargoHoldObjectIds.ID_TO_CAPACITY.get(id); + if (cap != null) { + cargoHoldCapacity = cap; + } + clampCargoHoldCount(); + clampCargoHoldSalvageStackCount(); + } + + private void initCargoHold() { + if (cargoHoldCapacity != -1) { + return; + } + long now = System.currentTimeMillis(); + if (now - lastCargoHoldInitAttemptMs < 2500) { + return; + } + lastCargoHoldInitAttemptMs = now; + + Rs2TileObjectModel hold = findCargoHold(); + if (hold == null) { + logCargoHoldInitThrottled("Cargo hold: no cargo hold object in this scene (stand on your boat)."); + return; + } + Integer capObj = CargoHoldObjectIds.ID_TO_CAPACITY.get(hold.getId()); + if (capObj == null) { + logCargoHoldInitThrottled( + "Cargo hold: object id " + hold.getId() + " not mapped to capacity; add it to CargoHoldObjectIds if this is a new boat tier or variant."); + return; + } + int cap = capObj; + if (!openCargoHoldInterfaceForWithdraw()) { + logCargoHoldInitThrottled( + "Cargo hold: could not open interface for initialization; stand on your boat and use Open on the hold."); + return; + } + sleep(Rs2Random.between(280, 650)); + cargoHoldCapacity = cap; + if (!readOccupiedCountFromOpenHoldInterface()) { + cargoHoldCapacity = -1; + cargoHoldCount = -1; + cargoHoldSalvageStackCount = -1; + logCargoHoldInitThrottled( + "Cargo hold: could not read hold contents after opening; check client/game updates."); + closeCargoHoldInterface(); + return; + } + closeCargoHoldInterface(); + lastCargoHoldObjectId = hold.getId(); + lastCargoHoldInitHintLogMs = 0; + lastCargoHoldWidgetResyncMs = System.currentTimeMillis(); + log.info( + "Cargo hold initialized: capacity={} slots, occupied={}, salvage stacks={}; deposits use in-UI Deposit inventory.", + cargoHoldCapacity, cargoHoldCount, cargoHoldSalvageStackCount); + } + + private void logCargoHoldInitThrottled(String message) { + long t = System.currentTimeMillis(); + if (t - lastCargoHoldInitHintLogMs < 15000) { + return; + } + lastCargoHoldInitHintLogMs = t; + log.info(message); + } + + /** + * Resolves the cargo hold on the client thread: tile cache merge, explicit walk of the local player's + * {@link WorldView} scene (same approach as Plugin Hub {@code BoatTracker} + {@code CargoHoldTier} ids), + * {@link Rs2GameObject} radius scan, then name match, then nearest to the player. + */ + private Rs2TileObjectModel findCargoHold() { + return Microbot.getClientThread().invoke(this::findCargoHoldOnClientThread); + } + + private Rs2TileObjectModel findCargoHoldOnClientThread() { + List fromWorldView = tileObjectCache.query() + .fromWorldView() + .where(this::isCargoHoldTileObject) + .toList(); + List fromDefaultScene = tileObjectCache.query() + .where(this::isCargoHoldTileObject) + .toList(); + List merged = mergeDistinctTileObjectLists(fromWorldView, fromDefaultScene); + merged = mergeDistinctTileObjectLists(merged, scanCargoHoldObjectsFromLocalPlayerWorldViewScene()); + merged = mergeDistinctTileObjectLists(merged, scanCargoHoldObjectsFromScene()); + if (merged.isEmpty()) { + WorldPoint anchor = Rs2Player.getWorldLocation(); + if (anchor != null) { + try { + TileObject named = Rs2GameObject.getTileObject("Cargo hold", anchor, NEARBY_TILE_OBJECT_SCAN_RADIUS); + if (named != null) { + merged = List.of(new Rs2TileObjectModel(named)); + } + } catch (RuntimeException ex) { + log.debug("Cargo hold: Rs2GameObject.getTileObject name fallback failed (known issue on some sea scenes)", ex); + } + } + } + if (merged.isEmpty()) { + return null; + } + WorldPoint player = Rs2Player.getWorldLocation(); + if (player == null) { + return merged.get(0); + } + return merged.stream() + .min(Comparator.comparingInt(o -> player.distanceTo(o.getWorldLocation()))) + .orElse(null); + } + + private List scanCargoHoldObjectsFromScene() { + WorldPoint anchor = Rs2Player.getWorldLocation(); + if (anchor == null) { + return List.of(); + } + try { + List raw = Rs2GameObject.getAll(o -> CargoHoldObjectIds.ALL_IDS.contains(o.getId()), anchor, NEARBY_TILE_OBJECT_SCAN_RADIUS); + List out = new ArrayList<>(); + for (Object o : raw) { + if (o instanceof TileObject) { + out.add(new Rs2TileObjectModel((TileObject) o)); + } + } + return out; + } catch (RuntimeException ex) { + log.debug("Cargo hold: Rs2GameObject.getAll scene scan failed", ex); + return List.of(); + } + } + + /** + * Full scene pass on the local player's {@link WorldView} (the boat interior when boarded), matching Plugin Hub + * {@code BoatTracker} / {@code CargoHoldTier.fromGameObjectId} behaviour. + */ + private List scanCargoHoldObjectsFromLocalPlayerWorldViewScene() { + Client client = Microbot.getClient(); + if (client == null) { + return List.of(); + } + Player lp = client.getLocalPlayer(); + if (lp == null) { + return List.of(); + } + WorldView wv = lp.getWorldView(); + if (wv == null) { + return List.of(); + } + return collectTileObjectsFromWorldViewScene(wv, this::isCargoHoldTileObject); + } + + /** + * Walks one {@link WorldView}'s scene (e.g. local player / boat interior) and collects {@link TileObject}s that + * match {@code predicate}. Same tile rules as {@link #rebuildShipwreckMapsFromTopLevelScene()} for game objects. + */ + private List collectTileObjectsFromWorldViewScene( + WorldView wv, + Predicate predicate) { + Scene scene = wv.getScene(); + if (scene == null) { + return List.of(); + } + Tile[][][] tiles = scene.getTiles(); + if (tiles == null) { + return List.of(); + } + int plane = wv.getPlane(); + if (plane < 0) { + return List.of(); + } + if (plane >= tiles.length) { + return List.of(); + } + Tile[][] planeTiles = tiles[plane]; + if (planeTiles == null) { + return List.of(); + } + List out = new ArrayList<>(); + int maxX = Math.min(Constants.SCENE_SIZE, planeTiles.length); + for (int x = 0; x < maxX; x++) { + Tile[] column = planeTiles[x]; + if (column == null) { + continue; + } + int maxY = Math.min(Constants.SCENE_SIZE, column.length); + for (int y = 0; y < maxY; y++) { + Tile tile = column[y]; + if (tile == null) { + continue; + } + GameObject[] gameObjects = tile.getGameObjects(); + if (gameObjects != null) { + for (GameObject go : gameObjects) { + if (go == null) { + continue; + } + if (!go.getSceneMinLocation().equals(tile.getSceneLocation())) { + continue; + } + maybeAddTileObjectIf(out, go, predicate); + } + } + DecorativeObject dec = tile.getDecorativeObject(); + if (dec != null) { + maybeAddTileObjectIf(out, dec, predicate); + } + } + } + return out; + } + + private static void maybeAddTileObjectIf( + List out, + TileObject obj, + Predicate predicate) { + Rs2TileObjectModel model = new Rs2TileObjectModel(obj); + if (!predicate.test(model)) { + return; + } + out.add(model); + } + + private boolean isCargoHoldTileObject(Rs2TileObjectModel obj) { + if (CargoHoldObjectIds.ALL_IDS.contains(obj.getId())) { + return true; + } + String name = obj.getName(); + if (name == null) { + return false; + } + return name.toLowerCase().contains("cargo hold"); + } + + private boolean shouldProcessCargoHold() { + if (cargoHoldCapacity < 0 || cargoHoldCount < 0) { + return false; + } + int freeSlots = cargoHoldCapacity - cargoHoldCount; + return freeSlots == 0 || freeSlots < Rs2Inventory.emptySlotCount(); + } + + /** + * True when the hold is initialized and has reported spare capacity and the player is carrying salvage. + * Intentionally does not use {@link #shouldProcessCargoHold()} — that check compares hold free slots to + * empty inventory slots, which stays true while the inventory is "full" (24+ items) but still has + * several empty spaces, and would block in-UI deposit even though the client may still allow it. + */ + private boolean canDepositSalvageToCargoHold() { + if (cargoHoldCapacity < 0) { + return false; + } + if (cargoHoldCount < 0) { + return false; + } + int free = cargoHoldCapacity - cargoHoldCount; + if (free <= 0) { + return false; + } + return Rs2Inventory.count("salvage") > 0; + } + + private void depositToCargoHold() { + if (!openCargoHoldInterfaceForWithdraw()) { + log.info("Cargo hold: could not open interface for deposit"); + return; + } + sleep(Rs2Random.between(200, 480)); + if (!readOccupiedCountAfterDepositWhileHoldOpen()) { + log.warn("Cargo hold: could not read hold grid from UI before deposit"); + closeCargoHoldInterface(); + return; + } + lastCargoHoldWidgetResyncMs = System.currentTimeMillis(); + int salvageBefore = Rs2Inventory.count("salvage"); + if (salvageBefore <= 0) { + closeCargoHoldInterface(); + return; + } + if (!canDepositSalvageToCargoHold()) { + if (cargoHoldCapacity > 0) { + int free = cargoHoldCapacity - cargoHoldCount; + if (free <= 0) { + cargoHoldProcessing = true; + log.info( + "Cargo hold has no free slots after UI read ({} / {}); switching to processing phase", + cargoHoldCount, + cargoHoldCapacity); + } + } + closeCargoHoldInterface(); + return; + } + AtomicBoolean clicked = new AtomicBoolean(false); + Microbot.getClientThread().invoke(() -> clicked.set(clickDepositInventoryInOpenCargoHold())); + if (!clicked.get()) { + log.warn("Cargo hold: Deposit inventory control not found in interface"); + closeCargoHoldInterface(); + return; + } + sleep(Rs2Random.between(280, 620)); + sleepUntil(() -> Rs2Inventory.count("salvage") < salvageBefore, SALVAGE_TIMEOUT); + boolean readOk = readOccupiedCountAfterDepositWhileHoldOpen(); + lastCargoHoldWidgetResyncMs = System.currentTimeMillis(); + if (!readOk) { + log.info("Cargo hold: could not refresh counts after deposit from UI"); + } + if (!shouldLeaveCargoHoldOpenAfterDeposit()) { + closeCargoHoldInterface(); + } + } + + /** + * When the cargo-hold pipeline will continue on the next script iteration (withdraw another stack, or deposit again + * with the UI already open), closing here forces an immediate re-open in {@link #processCargoHoldWithdrawStep()} or + * {@link #openCargoHoldInterfaceForWithdraw()}. Leave the panel open instead. + */ + private boolean shouldLeaveCargoHoldOpenAfterDeposit() { + if (cargoHoldSalvageStackCount <= 0) { + return false; + } + if (cargoHoldProcessing) { + return true; + } + return shouldProcessCargoHold(); + } + + /** + * Opens the cargo hold panel (Open on world object). Used for withdraw and for in-UI deposit. + */ + private boolean openCargoHoldInterfaceForWithdraw() { + if (Rs2Widget.isWidgetVisible(InterfaceID.SailingBoatCargohold.UNIVERSE)) { + return true; + } + Rs2TileObjectModel hold = findCargoHold(); + if (hold == null) { + return false; + } + hold.click("Open"); + return sleepUntil(() -> Rs2Widget.isWidgetVisible(InterfaceID.SailingBoatCargohold.UNIVERSE), CARGO_HOLD_UI_TIMEOUT_MS); + } + + /** + * Reads occupied + salvage counts while the cargo-hold interface is already open. Call + * {@link #openCargoHoldInterfaceForWithdraw()} (and a short sleep) first when the panel was not open. + * + * @return false if the read failed + */ + private boolean readOccupiedCountFromOpenHoldInterface() { + int[] grid = Microbot.getClientThread().invoke(this::countOccupiedAndSalvageStacksInOpenHoldInterface); + if (grid == null) { + return false; + } + applyCargoHoldCountsFromItemGrid(grid); + return true; + } + + /** + * Reads occupied/salvage counts while the hold panel stays open: used before clicking Deposit inventory (sync state, + * avoid depositing into a full hold) and after a deposit (header line can lag). Settles then retries across ticks. + */ + private boolean readOccupiedCountAfterDepositWhileHoldOpen() { + sleep(Rs2Random.between(180, 420)); + for (int attempt = 0; attempt < CARGO_HOLD_POST_DEPOSIT_READ_ATTEMPTS; attempt++) { + if (readOccupiedCountFromOpenHoldInterface()) { + return true; + } + sleep(Rs2Random.between(CARGO_HOLD_POST_DEPOSIT_READ_GAP_MIN_MS, CARGO_HOLD_POST_DEPOSIT_READ_GAP_MAX_MS)); + } + return false; + } + + /** + * Re-reads occupied/salvage counts whenever the cargo-hold panel is already open (no throttle). Must run before + * deposit/withdraw decisions: throttled {@link #maybeResyncCargoHoldCountsFromOpenUi()}, skipped resync while + * {@link #cargoHoldProcessing}, and deposit-imminent skips left stale counts and repeated deposit attempts into a + * full hold. + */ + private void refreshCargoHoldCountsIfPanelOpen() { + boolean visible = Microbot.getClientThread().runOnClientThreadOptional( + () -> Rs2Widget.isWidgetVisible(InterfaceID.SailingBoatCargohold.UNIVERSE)).orElse(false); + if (!visible) { + return; + } + if (readOccupiedCountFromOpenHoldInterface()) { + lastCargoHoldWidgetResyncMs = System.currentTimeMillis(); + } + } + + /** + * Re-opens the hold on a throttle and re-counts widgets when the panel was closed. When the panel is open, + * {@link #refreshCargoHoldCountsIfPanelOpen()} already refreshed this tick. + */ + private void maybeResyncCargoHoldCountsFromOpenUi() { + boolean wasVisible = Microbot.getClientThread().runOnClientThreadOptional( + () -> Rs2Widget.isWidgetVisible(InterfaceID.SailingBoatCargohold.UNIVERSE)).orElse(false); + if (wasVisible) { + return; + } + long now = System.currentTimeMillis(); + if (now - lastCargoHoldWidgetResyncMs < CARGO_HOLD_WIDGET_RESYNC_MIN_MS) { + return; + } + if (!openCargoHoldInterfaceForWithdraw()) { + return; + } + sleep(Rs2Random.between(180, 420)); + if (!readOccupiedCountFromOpenHoldInterface()) { + return; + } + lastCargoHoldWidgetResyncMs = now; + closeCargoHoldInterface(); + } + + private boolean clickDepositInventoryInOpenCargoHold() { + Client client = Microbot.getClient(); + if (client == null) { + return false; + } + Widget universe = client.getWidget(InterfaceID.SailingBoatCargohold.UNIVERSE); + if (universe == null || universe.isHidden()) { + return false; + } + Widget deposit = client.getWidget(InterfaceID.SailingBoatCargohold.DEPOSITALL_INVENTORY); + if (deposit != null && !deposit.isHidden()) { + Rs2Widget.clickWidget(deposit); + return true; + } + Widget target = findDepositInventoryWidget(universe); + if (target == null) { + return false; + } + Rs2Widget.clickWidget(target); + return true; + } + + private static Widget findDepositInventoryWidget(Widget w) { + if (w == null) { + return null; + } + String[] actions = w.getActions(); + if (actions != null) { + for (String a : actions) { + if (a == null) { + continue; + } + String lower = a.toLowerCase(); + if (lower.contains("deposit") && lower.contains("inventory")) { + return w; + } + } + } + String text = w.getText(); + if (text != null) { + String lower = text.toLowerCase().replace("
", " "); + if (lower.contains("deposit") && lower.contains("inventory")) { + return w; + } + } + Widget[] children = w.getChildren(); + if (children == null) { + return null; + } + for (Widget c : children) { + Widget found = findDepositInventoryWidget(c); + if (found != null) { + return found; + } + } + return null; + } + + /** + * Applies {@code grid[0]} = occupied slots, {@code grid[1]} = salvage stack count from the ITEMS grid. + * {@link #cargoHoldCapacity} is unchanged here (set from {@link CargoHoldObjectIds} at init). + */ + private void applyCargoHoldCountsFromItemGrid(int[] grid) { + if (grid == null) { + return; + } + if (grid.length < 2) { + return; + } + int occ = Math.max(0, grid[0]); + int sal = Math.max(0, grid[1]); + if (cargoHoldCapacity > 0) { + cargoHoldCount = Math.min(occ, cargoHoldCapacity); + cargoHoldSalvageStackCount = Math.min(sal, cargoHoldCount); + return; + } + cargoHoldCount = occ; + cargoHoldSalvageStackCount = Math.min(sal, occ); + } + + private void closeCargoHoldInterface() { + if (!Rs2Widget.isWidgetVisible(InterfaceID.SailingBoatCargohold.UNIVERSE)) { + return; + } + sleep(Rs2Random.between(280, 620)); + Rs2Keyboard.keyPress(KeyEvent.VK_ESCAPE); + sleep(Rs2Random.between(280, 520)); + } + + /** + * {@code [0]} = occupied slots, {@code [1]} = salvage-named stacks in the ITEMS grid. + * Occupied comes from {@link CargoHoldInterfaceWidgets} (occupied-only text child {@code 943, 4}) when parseable; else the ITEMS grid walk. + * Requires the cargo-hold panel to already be open. Client thread only. + */ + private int[] countOccupiedAndSalvageStacksInOpenHoldInterface() { + try { + Client client = Microbot.getClient(); + if (client == null) { + return null; + } + Widget universe = client.getWidget(InterfaceID.SailingBoatCargohold.UNIVERSE); + if (universe == null || universe.isHidden()) { + return null; + } + Widget items = client.getWidget(InterfaceID.SailingBoatCargohold.ITEMS); + if (items == null || items.isHidden()) { + return null; + } + int salvageStacks = countSalvageItemSlotsInHoldRecursive(client, items); + Integer occupiedFromLine = parseOccupiedSlotsFromCargoHoldTextLine(client); + int occupied; + if (occupiedFromLine != null) { + occupied = occupiedFromLine; + } else { + occupied = countNonEmptyItemSlotsRecursive(items); + } + return new int[] { occupied, salvageStacks }; + } catch (RuntimeException ex) { + log.debug("Cargo hold: interface read failed", ex); + return null; + } + } + + /** + * First number in the occupied-slot widget text (typically just occupied {@code X}; same regex if a {@code X / N} string appears). + */ + private static Integer parseOccupiedSlotsFromCargoHoldTextLine(Client client) { + Widget w = client.getWidget( + CargoHoldInterfaceWidgets.CARGO_HOLD_OCCUPIED_TEXT_GROUP, + CargoHoldInterfaceWidgets.CARGO_HOLD_OCCUPIED_TEXT_CHILD); + if (w == null) { + return null; + } + if (w.isHidden()) { + return null; + } + String t = w.getText(); + if (t == null) { + return null; + } + if (t.isEmpty()) { + return null; + } + String plain = Text.removeTags(t).replace("
", " ").trim(); + Matcher m = CARGO_HOLD_FIRST_NUMBER.matcher(plain); + if (!m.find()) { + return null; + } + return Integer.parseInt(m.group(1)); + } + + private static int countSalvageItemSlotsInHoldRecursive(Client client, Widget w) { + int count = 0; + if (w.getItemId() > 0) { + var def = client.getItemDefinition(w.getItemId()); + if (def != null) { + String name = def.getName(); + if (name != null) { + if (name.toLowerCase().contains("salvage")) { + count++; + } + } + } + } + Widget[] children = w.getChildren(); + if (children == null) { + return count; + } + for (Widget c : children) { + if (c == null) { + continue; + } + count += countSalvageItemSlotsInHoldRecursive(client, c); + } + return count; + } + + private static int countNonEmptyItemSlotsRecursive(Widget w) { + int count = 0; + if (w.getItemId() > 0) { + count++; + } + Widget[] children = w.getChildren(); + if (children == null) { + return count; + } + for (Widget c : children) { + if (c == null) { + continue; + } + count += countNonEmptyItemSlotsRecursive(c); + } + return count; + } + + private static int countSalvageItemStacksInInventory() { + int n = 0; + for (Rs2ItemModel item : Rs2Inventory.all()) { + String name = item.getName(); + if (name == null) { + continue; + } + if (name.toLowerCase().contains("salvage")) { + n++; + } + } + return n; + } + + private void processCargoHoldWithdrawStep() { + boolean holdWasAlreadyOpen = Rs2Widget.isWidgetVisible(InterfaceID.SailingBoatCargohold.UNIVERSE); + if (!openCargoHoldInterfaceForWithdraw()) { + cargoHoldWithdrawFailures++; + if (cargoHoldWithdrawFailures >= CARGO_HOLD_WITHDRAW_FAIL_THRESHOLD) { + log.warn("Cargo hold: interface failed to open repeatedly; exiting processing mode"); + cargoHoldProcessing = false; + cargoHoldWithdrawFailures = 0; + } + return; + } + cargoHoldWithdrawFailures = 0; + + if (!holdWasAlreadyOpen) { + sleep(Rs2Random.between(280, 650)); + } + sleep(Rs2Random.between(120, 320)); + + int trackedSalvageBeforeRead = cargoHoldSalvageStackCount; + readOccupiedCountFromOpenHoldInterface(); + if (cargoHoldSalvageStackCount == 0) { + if (trackedSalvageBeforeRead > 0) { + sleep(Rs2Random.between(450, 900)); + readOccupiedCountFromOpenHoldInterface(); + } + } + if (cargoHoldSalvageStackCount == 0) { + closeCargoHoldInterface(); + return; + } + + int salvageBefore = Rs2Inventory.count("salvage"); + AtomicBoolean invoked = new AtomicBoolean(false); + Microbot.getClientThread().invoke(() -> invoked.set(invokeWithdrawOneSalvageStackFromCargoHoldUi())); + if (!invoked.get()) { + log.info("Cargo hold: no salvage stack in hold UI; re-reading occupied count from open interface"); + readOccupiedCountFromOpenHoldInterface(); + closeCargoHoldInterface(); + if (cargoHoldSalvageStackCount == 0) { + cargoHoldProcessing = false; + } + return; + } + + // Only after the salvage slot click — long waits belong here, not before the click. + sleep(Rs2Random.between(CARGO_HOLD_POST_WITHDRAW_CLICK_MIN_MS, CARGO_HOLD_POST_WITHDRAW_CLICK_MAX_MS)); + boolean gainedInventory = sleepUntil( + () -> Rs2Inventory.count("salvage") != salvageBefore, CARGO_HOLD_WITHDRAW_INVENTORY_TIMEOUT_MS); + if (!gainedInventory) { + sleep(Rs2Random.between(650, 1400)); + gainedInventory = sleepUntil(() -> Rs2Inventory.count("salvage") != salvageBefore, 7000); + } + if (!gainedInventory) { + cargoHoldWithdrawNoGainStreak++; + log.info("Cargo hold: withdraw not reflected in inventory yet; leaving hold open for retry (avoid closing before click applies)"); + if (cargoHoldWithdrawNoGainStreak >= CARGO_HOLD_WITHDRAW_NO_GAIN_THRESHOLD) { + log.warn("Cargo hold: withdraw inventory never updated; closing interface and exiting processing mode"); + closeCargoHoldInterface(); + cargoHoldProcessing = false; + cargoHoldWithdrawNoGainStreak = 0; + } + return; + } + cargoHoldWithdrawNoGainStreak = 0; + + int gained = Rs2Inventory.count("salvage") - salvageBefore; + int countBeforeUiRead = cargoHoldCount; + int salvageCountBeforeUiRead = cargoHoldSalvageStackCount; + readOccupiedCountFromOpenHoldInterface(); + if (gained > 0) { + if (cargoHoldCount == countBeforeUiRead) { + cargoHoldCount = Math.max(0, cargoHoldCount - 1); + clampCargoHoldCount(); + } + if (cargoHoldSalvageStackCount == salvageCountBeforeUiRead && cargoHoldSalvageStackCount > 0) { + cargoHoldSalvageStackCount = Math.max(0, cargoHoldSalvageStackCount - 1); + } + clampCargoHoldSalvageStackCount(); + } + + Rs2Antiban.actionCooldown(); + if (isInventoryFull()) { + sleep(Rs2Random.between(CARGO_HOLD_BEFORE_CLOSE_AFTER_WITHDRAW_MIN_MS, CARGO_HOLD_BEFORE_CLOSE_AFTER_WITHDRAW_MAX_MS)); + if (Rs2Random.dicePercentage(18)) { + Rs2Antiban.takeMicroBreakByChance(); + } + closeCargoHoldInterface(); + return; + } + if (cargoHoldSalvageStackCount == 0) { + closeCargoHoldInterface(); + return; + } + sleep(Rs2Random.between(180, 480)); + } + + /** + * Left-clicks the salvage stack widget in the open cargo-hold item grid ({@code Rs2Widget.clickWidget}). + * Real widget click (same pattern as other hub plugins), not a synthesized menu entry. + */ + private boolean invokeWithdrawOneSalvageStackFromCargoHoldUi() { + Client client = Microbot.getClient(); + if (client == null) { + return false; + } + Widget salvageSlot = findFirstSalvageStackWidget(client); + if (salvageSlot == null) { + return false; + } + Rs2Widget.clickWidget(salvageSlot); + return true; + } + + private static Widget findFirstSalvageStackWidget(Client client) { + Widget items = client.getWidget(InterfaceID.SailingBoatCargohold.ITEMS); + if (items == null) { + return null; + } + return findSalvageInTree(client, items); + } + + private static Widget findSalvageInTree(Client client, Widget w) { + if (w == null) { + return null; + } + if (w.getItemId() > 0) { + var def = client.getItemDefinition(w.getItemId()); + if (def != null && def.getName().toLowerCase().contains("salvage")) { + return w; + } + } + Widget[] children = w.getChildren(); + if (children == null) { + return null; + } + for (Widget c : children) { + Widget found = findSalvageInTree(client, c); + if (found != null) { + return found; + } + } + return null; + } + + private void clampCargoHoldCount() { + if (cargoHoldCapacity < 0) { + return; + } + if (cargoHoldCount < 0) { + cargoHoldCount = 0; + } + if (cargoHoldCount > cargoHoldCapacity) { + cargoHoldCount = cargoHoldCapacity; + } + } + + private void clampCargoHoldSalvageStackCount() { + if (cargoHoldSalvageStackCount < 0) { + return; + } + cargoHoldSalvageStackCount = Math.max(0, cargoHoldSalvageStackCount); + if (cargoHoldCount >= 0) { + cargoHoldSalvageStackCount = Math.min(cargoHoldSalvageStackCount, cargoHoldCount); + } + } + private boolean isPlayerAnimating(Rs2PlayerModel player) { return player.getAnimation() != -1; } + /** Stable threshold so cargo-hold processing does not alternate ticks between "full" and not full. */ private boolean isInventoryFull() { - return Rs2Inventory.count() >= Rs2Random.between(MIN_INVENTORY_FULL, MAX_INVENTORY_FULL); + return Rs2Inventory.count() >= MIN_INVENTORY_FULL; + } + + /** + * While {@link #cargoHoldProcessing} and the hold still has salvage stacks ({@link #cargoHoldSalvageStackCount} + * > 0), do not {@link #depositToCargoHold()}. Non-salvage items may still occupy slots; deposits resume when + * salvage stacks in the hold reach 0. + */ + private boolean suppressSalvageDepositDuringCargoHoldProcessing() { + return cargoHoldProcessing && cargoHoldSalvageStackCount > 0; + } + + /** + * When true, this tick will open the hold for {@link #depositToCargoHold()} (from cargo-hold mode or + * {@link #handleFullInventory}). Skipping {@link #maybeResyncCargoHoldCountsFromOpenUi()} avoids an extra + * open→read→close before that deposit, which already refreshes counts after deposit. + */ + private boolean willDepositSalvageToCargoHoldImminently() { + return hasSalvageItems() + && canDepositSalvageToCargoHold() + && !suppressSalvageDepositDuringCargoHoldProcessing(); } private boolean hasSalvageItems() { @@ -119,23 +1233,128 @@ private boolean isWithinSalvageArea(WorldPoint playerLocation, Rs2TileObjectMode return playerLocation.distanceTo(wreck.getWorldLocation()) <= SIZE_SALVAGEABLE_AREA; } + /** + * True if any active shipwreck is within hook range. Used to avoid opening the cargo hold for withdraw processing + * while idle with no wreck nearby (which would spam open/close every script tick). + */ + private boolean hasNearbySalvageableWreck(WorldPoint playerLocation) { + for (Rs2TileObjectModel wreck : getActiveWrecks()) { + if (isWithinSalvageArea(playerLocation, wreck)) { + return true; + } + } + return false; + } + + /** + * After cargo-hold mass processing, inventory can sit below the "full" threshold while still holding + * drop/alch/casket loot. Runs one {@link #clearInventoryViaAlchDropAndCaskets} pass when there is no salvage to + * protect and configured cleanup would change the inventory. + * + * @return true if a cleanup pass was executed (caller should return for this tick). + */ + private boolean tryRunIdleInventoryCleanup(SailingConfig config) { + if (hasSalvageItems()) { + return false; + } + if (!inventoryCleanupConfigured(config)) { + return false; + } + if (!inventoryHasCleanupWork(config)) { + return false; + } + log.info("Inventory cleanup (drop/alch/caskets) before salvaging"); + clearInventoryViaAlchDropAndCaskets(config); + return true; + } + + private boolean inventoryCleanupConfigured(SailingConfig config) { + if (config.openCaskets()) { + return true; + } + String drop = config.dropItems(); + if (drop != null) { + if (!drop.isBlank()) { + return true; + } + } + if (!config.enableAlching()) { + return false; + } + String alch = config.alchItems(); + return alch != null && !alch.isBlank(); + } + + private boolean inventoryHasCleanupWork(SailingConfig config) { + if (config.openCaskets()) { + if (Rs2Inventory.hasItem("casket")) { + return true; + } + } + String dropItems = config.dropItems(); + if (dropItems != null) { + if (!dropItems.isBlank()) { + for (String raw : dropItems.split(",")) { + String name = raw.trim(); + if (name.isEmpty()) { + continue; + } + if (Rs2Inventory.hasItem(name)) { + return true; + } + } + } + } + if (config.enableAlching()) { + String alchItems = config.alchItems(); + if (alchItems != null) { + if (!alchItems.isBlank()) { + List fragments = Arrays.stream(alchItems.split(",")) + .map(String::trim) + .map(String::toLowerCase) + .filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); + for (Rs2ItemModel item : Rs2Inventory.all()) { + String n = item.getName(); + if (n == null) { + continue; + } + String lower = n.toLowerCase(); + for (String fragment : fragments) { + if (lower.contains(fragment)) { + return true; + } + } + } + } + } + } + return false; + } + private void handleFullInventory(SailingConfig config, Rs2PlayerModel player) { if (hasSalvageItems() && !isPlayerAnimating(player)) { - depositSalvageOrDrop(config); - } else { - // 1. Drop junk first to make room for casket loot - dropJunk(config); - // 2. Open caskets — now there's space for the loot to land - if (config.openCaskets()) { - openCaskets(); - } - // 3. Alch anything on the alch list (including new loot from caskets) - if (config.enableAlching()) { - alchItems(config); + if (config.useCargoHold()) { + if (canDepositSalvageToCargoHold() && !suppressSalvageDepositDuringCargoHoldProcessing()) { + depositToCargoHold(); + return; + } } - // 4. Drop anything leftover from casket loot that's also on the drop list - dropJunk(config); + depositSalvageOrDrop(config); + return; + } + clearInventoryViaAlchDropAndCaskets(config); + } + + private void clearInventoryViaAlchDropAndCaskets(SailingConfig config) { + dropJunk(config); + if (config.openCaskets()) { + openCaskets(); + } + if (config.enableAlching()) { + alchItems(config); } + dropJunk(config); } private void depositSalvageOrDrop(SailingConfig config) { @@ -149,14 +1368,85 @@ private void depositSalvageOrDrop(SailingConfig config) { } } + /** + * Resolves a salvaging station like {@link #findCargoHold()}: tile cache, explicit local {@link WorldView} scene walk + * (on-board station), then {@link Rs2GameObject} radius scan (e.g. port), nearest to the player. + */ private Rs2TileObjectModel findSalvagingStation() { - var playerWorldView = new Rs2PlayerModel().getWorldView().getId(); + return Microbot.getClientThread().invoke(this::findSalvagingStationOnClientThread); + } - return tileObjectCache.query() + private Rs2TileObjectModel findSalvagingStationOnClientThread() { + List fromWorldView = tileObjectCache.query() .fromWorldView() - .where(obj -> obj.getName() != null && obj.getName().equalsIgnoreCase("salvaging station")) - .where(obj -> obj.getWorldView().getId() == playerWorldView) - .nearestOnClientThread(); + .where(this::isSalvagingStationTileObject) + .toList(); + List fromDefaultScene = tileObjectCache.query() + .where(this::isSalvagingStationTileObject) + .toList(); + List merged = mergeDistinctTileObjectLists(fromWorldView, fromDefaultScene); + Client client = Microbot.getClient(); + if (client != null) { + Player lp = client.getLocalPlayer(); + if (lp != null) { + WorldView wv = lp.getWorldView(); + if (wv != null) { + merged = mergeDistinctTileObjectLists( + merged, + collectTileObjectsFromWorldViewScene(wv, this::isSalvagingStationTileObject)); + } + } + } + merged = mergeDistinctTileObjectLists(merged, scanSalvagingStationsFromRs2GameObject()); + if (merged.isEmpty()) { + return null; + } + WorldPoint player = Rs2Player.getWorldLocation(); + if (player == null) { + return merged.get(0); + } + return merged.stream() + .min(Comparator.comparingInt(o -> player.distanceTo(o.getWorldLocation()))) + .orElse(null); + } + + private List scanSalvagingStationsFromRs2GameObject() { + WorldPoint anchor = Rs2Player.getWorldLocation(); + if (anchor == null) { + return List.of(); + } + try { + List raw = Rs2GameObject.getAll( + o -> SalvagingStationObjectIds.ALL_IDS.contains(o.getId()), + anchor, + NEARBY_TILE_OBJECT_SCAN_RADIUS); + List out = new ArrayList<>(); + for (Object o : raw) { + if (o instanceof TileObject) { + out.add(new Rs2TileObjectModel((TileObject) o)); + } + } + return out; + } catch (RuntimeException ex) { + log.debug("Salvaging station: Rs2GameObject.getAll scan failed", ex); + return List.of(); + } + } + + /** + * Boat and port salvaging stations are identified by object ID ({@code ObjectID1.SAILING_SALVAGING_STATION_*}) + * because composition objects often do not expose the exact menu name "Salvaging station" via + * {@link Rs2TileObjectModel#getName()}. + */ + private boolean isSalvagingStationTileObject(Rs2TileObjectModel obj) { + if (SalvagingStationObjectIds.ALL_IDS.contains(obj.getId())) { + return true; + } + String name = obj.getName(); + if (name == null) { + return false; + } + return name.equalsIgnoreCase("salvaging station"); } private void depositAtStation(Rs2TileObjectModel station) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingStationObjectIds.java b/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingStationObjectIds.java new file mode 100644 index 0000000000..1e46dccc86 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/sailing/features/salvaging/SalvagingStationObjectIds.java @@ -0,0 +1,24 @@ +package net.runelite.client.plugins.microbot.sailing.features.salvaging; + +import com.google.common.collect.ImmutableSet; +import net.runelite.api.gameval.ObjectID1; + +import java.util.Set; + +/** + * Tile object IDs for salvaging stations the script can deposit shipwreck salvage into. + * Boat stations are matched by ID because {@code Rs2TileObjectModel#getName()} is often null or not the + * literal phrase "salvaging station" for sailing compositions. + */ +public final class SalvagingStationObjectIds { + + private SalvagingStationObjectIds() { + } + + public static final Set ALL_IDS = ImmutableSet.of( + ObjectID1.SAILING_SALVAGING_STATION_2X5A, + ObjectID1.SAILING_SALVAGING_STATION_2X5B, + ObjectID1.SAILING_SALVAGING_STATION_3X8, + ObjectID1.SAILING_PORT_SALVAGING_STATION + ); +} diff --git a/src/main/resources/net/runelite/client/plugins/microbot/sailing/docs/CHANGELOG.md b/src/main/resources/net/runelite/client/plugins/microbot/sailing/docs/CHANGELOG.md index a6dc853fd5..8c9d91ff38 100644 --- a/src/main/resources/net/runelite/client/plugins/microbot/sailing/docs/CHANGELOG.md +++ b/src/main/resources/net/runelite/client/plugins/microbot/sailing/docs/CHANGELOG.md @@ -5,6 +5,33 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). --- +## [2.2.34] + +### Fixed +- **Shipwreck highlight overlay**: Wreck lists are refreshed on the client each game tick and read by the overlay from that snapshot, so highlights draw reliably instead of depending on cache queries from the overlay render path. +- **Cargo hold + full inventory**: With **Use Cargo Hold** enabled, a full inventory that is only salvage is handled by depositing into the hold or entering hold processing; the script no longer tries to deploy the hook in that situation. + +### Added +- **Idle inventory cleanup**: When there is no salvage in your inventory but **Open Caskets**, **Drop Items**, or **Enable Alching** would change the inventory, the plugin can run one drop / casket / alch pass before salvaging so leftover loot does not sit in the way. + +### Changed +- **Cargo hold behaviour**: + - Opens the hold and uses **Deposit inventory** in the cargo UI (not the salvaging station) when banking salvage from a full inventory. + - Tracks **total occupied slots** and **salvage stacks** separately from the hold item grid so “full” and **processing** decisions match how the hold actually behaves (non-salvage items can occupy slots while salvage remains to process). + - Re-reads the grid periodically while the hold flow is active; when you are idle (no nearby wreck and no salvage in inventory), the script avoids opening the hold on a timer just to resync counts. + - Initialises the hold as soon as cargo-hold mode is active, before the “already salvaging” wait, so boarding or toggling the option is not blocked by animation checks. + - Resolves the hold game object using the player world view first, with a fallback lookup if needed. + +--- + +## [2.2.0] + +### Added +- **Use Cargo Hold** (config checkbox, default: off) + When enabled while salvaging, shipwreck salvage is deposited into your boat cargo hold via the hold interface (**Deposit inventory**) instead of using the salvaging station. Capacity and occupied slots are read from the cargo hold interface on first use; if the hold is full or would overflow the next deposit (using the same conservative rule as the design spec: zero free slots, or hold free slots below current empty inventory slots), the script enters a **processing** phase: it withdraws salvage from the hold in batches, closes the UI, and reuses the existing full-inventory routine (drop junk, open caskets, high alch) until salvage stacks in the hold reach zero, then resumes hook deployment. Manual hold interactions can desync the internal count; the script re-reads the interface when the hold object ID switches between no-cargo and cargo visuals. If the cargo UI fails to open repeatedly during processing, processing mode stops to avoid an infinite loop. + +--- + ## [2.1.0] ### Fixed diff --git a/src/main/resources/net/runelite/client/plugins/microbot/sailing/docs/README.md b/src/main/resources/net/runelite/client/plugins/microbot/sailing/docs/README.md index 4cbf7cd70c..4c6f762fd4 100644 --- a/src/main/resources/net/runelite/client/plugins/microbot/sailing/docs/README.md +++ b/src/main/resources/net/runelite/client/plugins/microbot/sailing/docs/README.md @@ -7,7 +7,8 @@ An automated sailing plugin that supports salvaging shipwrecks while sailing. ### Salvaging - **Automatic Shipwreck Detection**: Finds and salvages nearby shipwrecks within a 15-tile radius - **Smart Inventory Management**: Checks inventory status before attempting to salvage — clears a full inventory before looking for new wrecks -- **Salvaging Station Support**: Deposits salvage at your boat's salvaging station (if installed) +- **Salvaging Station Support**: Deposits salvage at your boat's salvaging station (if installed), unless **Use Cargo Hold** is enabled +- **Cargo Hold (optional)**: Opens the hold and uses **Deposit inventory** in the cargo UI; tracks occupied slots and salvage stacks from the hold grid. When the hold is full or nearly full, withdraws and runs the same alch/drop/casket pipeline until salvage stacks in the hold are cleared. Avoids unnecessary hold open/close while idle away from wrecks without salvage - **Hook Deployment**: Automatically deploys your boat's salvaging hook on nearby shipwrecks ### Inventory Management @@ -22,6 +23,7 @@ An automated sailing plugin that supports salvaging shipwrecks while sailing. - **Inactive Wrecks**: Highlights depleted shipwrecks/stumps (gray by default) - **High Level Wrecks**: Highlights shipwrecks above your sailing level (red by default) - **Customizable Colors**: All highlight colors are fully customizable +- Wreck data for the overlay is updated on each game tick on the client thread so highlights stay in sync with the scene ## Configuration @@ -44,6 +46,12 @@ An automated sailing plugin that supports salvaging shipwrecks while sailing. - Caskets are opened after the first drop pass (to ensure space for loot) and before alching - Any junk from casket loot is caught by a second drop pass after alching +**Use Cargo Hold** (default: disabled) +- When enabled, you must be on your boat with a cargo hold in range. The script opens the hold to learn capacity, reads **occupied slots** and **salvage stacks** from the hold item grid, and when your inventory is full of salvage it uses **Deposit inventory** in the cargo UI (instead of the salvaging station). +- When the hold has no free slots, or its free slots are fewer than your current empty inventory slots, the script withdraws salvage from the hold and runs the same drop/casket/alch steps as a full inventory until **salvage stacks in the hold** reach zero, then continues salvaging (other items may still occupy slots). +- While you are idle with no nearby wreck and no salvage in inventory, the script does not keep opening the hold only to refresh counts. +- Turn this off to restore the original station-only behaviour. If the hold is not available (wrong place, no hold), salvaging waits until it can initialise the hold. + **Alch Order** (default: LIST_ORDER) - Controls the order in which matching items are alched across your inventory - `LIST_ORDER` — alches by item name order in your Alch Items list (original behaviour) @@ -80,14 +88,15 @@ An automated sailing plugin that supports salvaging shipwrecks while sailing. 1. **Inventory Check First**: On each tick, the plugin checks whether inventory is full before looking for wrecks 2. **If inventory is full**: - - If salvage items are present → deposit at salvaging station (or drop junk if no station) + - If salvage items are present → open cargo hold and **Deposit inventory** (**Use Cargo Hold**), else salvaging station (or drop junk if no station) - Otherwise: 1. Drop configured junk items 2. Open caskets (if enabled) — space has been made by the drop step 3. High alch configured items (if enabled) — includes any loot from opened caskets 4. Drop again — catches any junk that came from casket loot -3. **If inventory has space**: find the nearest wreck and deploy the salvaging hook -4. **Repeat** +3. **If inventory has space** and there is no salvage to protect: if **Open Caskets**, **Drop Items**, or **Enable Alching** would still change the inventory, the plugin may run one cleanup pass (same order as step 2) before looking for wrecks +4. **If inventory has space** after that: find the nearest wreck and deploy the salvaging hook +5. **Repeat** ## Shipwreck Types @@ -133,6 +142,15 @@ An automated sailing plugin that supports salvaging shipwrecks while sailing. ## Version History +**2.2.34** +- Reliable **shipwreck highlights** via per-tick client-side wreck snapshots for the overlay +- **Cargo hold**: **Deposit inventory** in the UI, separate **salvage stack** vs **slot** tracking, smarter resync (no idle spam), earlier hold initialisation, improved hold object lookup +- **Full inventory + cargo hold**: full salvage inventories deposit or enter hold processing instead of deploying the hook +- **Idle inventory cleanup** when there is no salvage but casket/drop/alch work remains + +**2.2.0** +- Optional **Use Cargo Hold** mode for salvaging: deposit salvage into the boat hold, process the hold when full or nearly full, then resume hooks + **2.1.0** - Fixed alch loop only alching one of each item instead of exhausting all stacks - Fixed inventory check now happens before wreck detection From b944fb3ff809f2e3e5bebeac713d2956403cbdc4 Mon Sep 17 00:00:00 2001 From: chsami Date: Wed, 8 Apr 2026 23:17:27 +0200 Subject: [PATCH 13/95] fix(TitheFarmingPlugin): bump version to 1.1.12 and refactor game object interactions to use Rs2TileObjectModel --- .../tithefarming/TitheFarmingPlugin.java | 13 +-- .../tithefarming/TitheFarmingScript.java | 87 ++++++++++------ .../tithefarming/models/TitheFarmPlant.java | 98 +++++++++++++++---- 3 files changed, 135 insertions(+), 63 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingPlugin.java index ad095c91e9..9a80d95c2f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingPlugin.java @@ -5,7 +5,6 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.Item; import net.runelite.api.events.ChatMessage; -import net.runelite.api.events.GameObjectSpawned; import net.runelite.api.events.ItemContainerChanged; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; @@ -15,7 +14,6 @@ import net.runelite.client.plugins.microbot.PluginConstants; import net.runelite.client.plugins.microbot.tithefarming.enums.TitheFarmMaterial; import net.runelite.client.plugins.microbot.tithefarming.enums.TitheFarmState; -import net.runelite.client.plugins.microbot.tithefarming.models.TitheFarmPlant; 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; @@ -42,7 +40,7 @@ @Slf4j public class TitheFarmingPlugin extends Plugin { - final static String version = "1.1.5"; + final static String version = "1.1.12"; @Inject public TitheFarmingConfig config; @@ -81,15 +79,6 @@ protected void shutDown() { overlayManager.remove(titheFarmOverlay); } - @Subscribe - public void onGameObjectSpawned(GameObjectSpawned event) { - for (TitheFarmPlant plant : net.runelite.client.plugins.microbot.tithefarming.TitheFarmingScript.plants) { - if (event.getGameObject().getWorldLocation().equals(plant.getGameObject().getWorldLocation())) { - plant.setGameObject(event.getGameObject()); - } - } - } - @Subscribe public void onItemContainerChanged(ItemContainerChanged event) { if (TitheFarmMaterial.getSeedForLevel() != null) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingScript.java b/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingScript.java index 6433d7d66d..8a78736706 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingScript.java @@ -1,13 +1,12 @@ package net.runelite.client.plugins.microbot.tithefarming; -import net.runelite.api.TileObject; -import net.runelite.api.WallObject; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.AnimationID; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.ObjectID; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.breakhandler.BreakHandlerScript; import net.runelite.client.plugins.microbot.globval.enums.InterfaceTab; import net.runelite.client.plugins.microbot.tithefarming.enums.TitheFarmLanes; @@ -15,7 +14,6 @@ import net.runelite.client.plugins.microbot.tithefarming.enums.TitheFarmState; import net.runelite.client.plugins.microbot.tithefarming.models.TitheFarmPlant; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -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.util.keyboard.Rs2Keyboard; @@ -170,6 +168,11 @@ public void init(TitheFarmingConfig config) { public boolean run(TitheFarmingConfig config) { + init = true; + plants = new ArrayList<>(); + state = STARTING; + allPlanted = false; + Microbot.log("Tithe farming script started"); mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { try { if (!Microbot.isLoggedIn()) return; @@ -254,7 +257,7 @@ public boolean run(TitheFarmingConfig config) { } if (config.enableDebugging() && plants.stream().anyMatch(x -> x.getGameObject() == null)) { - Microbot.showMessage("There is an empty plant gameobject!"); + Microbot.log("There is an empty plant gameobject!"); } } catch (Exception ex) { @@ -307,7 +310,11 @@ private void coreLoop(TitheFarmingConfig config) { // if we finished planting all patches, don't plant anything until we finish harvesting // otherwise if we lag/miss a plant, and it dies, we will keep trying to plant seeds and mess up the loop - if (plants.stream().noneMatch(TitheFarmPlant::isEmptyPatch)) + // require data for every patch before flipping allPlanted: when the cache hasn't loaded + // the patches yet, isEmptyPatch() returns false for all of them and noneMatch would + // erroneously trip even though we have planted nothing. + if (plants.stream().allMatch(p -> p.getGameObject() != null) + && plants.stream().noneMatch(TitheFarmPlant::isEmptyPatch)) allPlanted = true; if (plant == null && plants.stream().anyMatch(TitheFarmPlant::isValidToHarvest)) { @@ -324,7 +331,8 @@ private void coreLoop(TitheFarmingConfig config) { final TitheFarmPlant finalPlant = plant; WorldPoint corePlayerLoc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); - if (plant.getGameObject().getWorldLocation().distanceTo2D(corePlayerLoc) > DISTANCE_THRESHOLD_MINIMAP_WALK) { + Rs2TileObjectModel plantModel = plant.getGameObject(); + if (plantModel == null || plantModel.getWorldLocation().distanceTo2D(corePlayerLoc) > DISTANCE_THRESHOLD_MINIMAP_WALK) { WorldPoint w = WorldPoint.fromRegion(corePlayerLoc.getRegionID(), plant.regionX, plant.regionY, @@ -398,23 +406,32 @@ private boolean validateSeedsAndPatches() { private static void clickPatch(TitheFarmPlant plant) { - WorldPoint patchPlayerLoc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); - WorldPoint worldPoint = WorldPoint.fromRegion(patchPlayerLoc.getRegionID(), - plant.regionX, - plant.regionY, - Microbot.getClient().getTopLevelWorldView().getPlane()); - - Rs2GameObject.interact(worldPoint); + Rs2TileObjectModel model = plant.getGameObject(); + if (model == null) return; + model.click(); } private static void clickPatch(TitheFarmPlant plant, String action) { - WorldPoint patchPlayerLoc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); - WorldPoint worldPoint = WorldPoint.fromRegion(patchPlayerLoc.getRegionID(), - plant.regionX, - plant.regionY, - Microbot.getClient().getTopLevelWorldView().getPlane()); + Rs2TileObjectModel model = plant.getGameObject(); + if (model == null) return; + model.click(action); + } - Rs2GameObject.interact(worldPoint, action); + private static boolean interactWithObject(int id, String action) { + Rs2TileObjectModel model = Microbot.getRs2TileObjectCache().query() + .withId(id) + .nearest(); + if (model == null) { + Microbot.log("Object id " + id + " not in scene"); + return false; + } + WorldPoint playerLoc = Rs2Player.getWorldLocation(); + if (playerLoc != null && playerLoc.distanceTo(model.getWorldLocation()) > 51) { + Microbot.log("Object id " + id + " is " + playerLoc.distanceTo(model.getWorldLocation()) + " tiles away, walking..."); + Rs2Walker.walkTo(model.getWorldLocation()); + return false; + } + return action == null ? model.click() : model.click(action); } private static void DropFertiliser() { @@ -430,7 +447,10 @@ private void refillWaterCans(TitheFarmingConfig config) { if (gricollerCanCharges < config.gricollerCanRefillThreshold()) { walkToBarrel(); Rs2Inventory.interact(ItemID.ZEAH_WATERINGCAN, "Use"); - Rs2GameObject.interact("Water barrel"); + Rs2TileObjectModel barrel = Microbot.getRs2TileObjectCache().query() + .withName("Water barrel") + .nearest(); + if (barrel != null) barrel.click(); sleepUntil(Rs2Player::isAnimating, 10000); } else { state = PLANTING_SEEDS; @@ -438,7 +458,10 @@ private void refillWaterCans(TitheFarmingConfig config) { } else if (TitheFarmMaterial.hasWateringCanToBeFilled()) { walkToBarrel(); Rs2Inventory.interact(TitheFarmMaterial.getWateringCanToBeFilled(), "Use"); - Rs2GameObject.interact(ObjectID.WATER_BARREL1, "Use"); + Rs2TileObjectModel barrel = Microbot.getRs2TileObjectCache().query() + .withId(ObjectID.WATER_BARREL1) + .nearest(); + if (barrel != null) barrel.click(); sleepUntil(() -> Rs2Inventory.hasItemAmount(ItemID.WATERING_CAN_8, WATERING_CANS_AMOUNT), 60000); } else { state = PLANTING_SEEDS; @@ -446,13 +469,17 @@ private void refillWaterCans(TitheFarmingConfig config) { } private void walkToBarrel() { - final TileObject gameObject = Rs2GameObject.findObjectById(ObjectID.WATER_BARREL1); + Rs2TileObjectModel barrel = Microbot.getRs2TileObjectCache().query() + .withId(ObjectID.WATER_BARREL1) + .nearest(); + if (barrel == null) return; + WorldPoint barrelLoc = barrel.getWorldLocation(); WorldPoint barrelPlayerLoc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); - if (gameObject.getWorldLocation().distanceTo2D(barrelPlayerLoc) > DISTANCE_THRESHOLD_MINIMAP_WALK) { - Rs2Walker.walkMiniMap(gameObject.getWorldLocation(), 1); + if (barrelLoc.distanceTo2D(barrelPlayerLoc) > DISTANCE_THRESHOLD_MINIMAP_WALK) { + Rs2Walker.walkMiniMap(barrelLoc, 1); sleepUntil(Rs2Player::isMoving); } - sleepUntil(() -> gameObject.getWorldLocation().distanceTo2D(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation())) < DISTANCE_THRESHOLD_MINIMAP_WALK); + sleepUntil(() -> barrelLoc.distanceTo2D(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation())) < DISTANCE_THRESHOLD_MINIMAP_WALK); } private void checkGricollerCharges() { @@ -465,7 +492,7 @@ private void takeSeeds() { Rs2Inventory.drop(TitheFarmMaterial.getSeedForLevel().getName()); sleep(400, 600); } - Rs2GameObject.interact(ObjectID.TITHE_PLANT_SEED_TABLE); + interactWithObject(ObjectID.TITHE_PLANT_SEED_TABLE, null); boolean result = Rs2Widget.sleepUntilHasWidget(TitheFarmMaterial.getSeedForLevel().getName()); if (!result) return; Rs2Keyboard.keyPress(TitheFarmMaterial.getSeedForLevel().getOption()); @@ -477,15 +504,14 @@ private void takeSeeds() { } private void enter() { - WallObject farmDoor = Rs2GameObject.getWallObject(FARM_DOOR); - Rs2GameObject.interact(farmDoor); + interactWithObject(FARM_DOOR, null); sleepUntil(this::isInMinigame); } private boolean depositSack() { if (Rs2Inventory.hasItem(TitheFarmMaterial.getSeedForLevel().getFruitId())) { Microbot.log("Storing fruits into sack for experience..."); - Rs2GameObject.interact(ObjectID.TITHE_SACK_OF_FRUIT_EMPTY); + interactWithObject(ObjectID.TITHE_SACK_OF_FRUIT_EMPTY, null); Rs2Player.waitForWalking(); Rs2Player.waitForAnimation(); return true; @@ -494,8 +520,7 @@ private boolean depositSack() { } private void leave() { - WallObject farmDoor = Rs2GameObject.getWallObject(FARM_DOOR); - Rs2GameObject.interact(farmDoor); + interactWithObject(FARM_DOOR, null); sleepUntil(() -> !Rs2Inventory.hasItem(FERTILISER), 8000); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/tithefarming/models/TitheFarmPlant.java b/src/main/java/net/runelite/client/plugins/microbot/tithefarming/models/TitheFarmPlant.java index 8f85558a60..8ce1e8b508 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tithefarming/models/TitheFarmPlant.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tithefarming/models/TitheFarmPlant.java @@ -26,13 +26,13 @@ import lombok.Getter; import lombok.Setter; -import net.runelite.api.TileObject; +import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ObjectID; import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.tithefarming.TitheFarmingScript; import net.runelite.client.plugins.microbot.tithefarming.enums.TitheFarmMaterial; import net.runelite.client.plugins.microbot.tithefarming.enums.TitheFarmState; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.tithefarm.TitheFarmPlantState; import java.time.Duration; @@ -40,8 +40,6 @@ import java.util.Arrays; import java.util.Objects; -import static net.runelite.api.coords.WorldPoint.fromRegion; - public class TitheFarmPlant { private static final Duration PLANT_TIME = Duration.ofMinutes(1); @@ -56,22 +54,66 @@ public class TitheFarmPlant { @Getter private final TitheFarmPlantState state; - @Getter - @Setter - private TileObject gameObject; - public int regionX; public int regionY; public TitheFarmPlant(int regionX, int regionY, int index) { this.planted = Instant.now(); this.state = TitheFarmPlantState.UNWATERED; - this.gameObject = Rs2GameObject.findGameObjectByLocation(fromRegion(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation().getRegionID()), regionX, regionY, 0)); this.regionX = regionX; this.regionY = regionY; this.index = index; } + public Rs2TileObjectModel getGameObject() { + // The hardcoded lane definitions are off by 1 from the actual instance patch + // tiles (verified via the agent server: lane defines (35,25) but the cache has + // a tithe patch at local (34,24), and so on for every patch). Allow a 1-tile + // tolerance match so the lookup is robust to this without rewriting all four + // lane lists. Patches are 3 tiles apart so the tolerance can't match the + // wrong neighbour. + return Microbot.getRs2TileObjectCache().query() + .where(o -> { + if (!isTithePatchId(o.getId())) return false; + WorldPoint wp = o.getWorldLocation(); + if (wp == null) return false; + int dx = Math.abs(wp.getRegionX() - regionX); + int dy = Math.abs(wp.getRegionY() - regionY); + return dx <= 1 && dy <= 1; + }) + .first(); + } + + private static boolean isTithePatchId(int id) { + switch (id) { + case ObjectID.HOSIDIUS_TITHE_EMPTY: + case ObjectID.HOSIDIUS_TITHE_A_1_DRY: + case ObjectID.HOSIDIUS_TITHE_A_2_DRY: + case ObjectID.HOSIDIUS_TITHE_A_3_DRY: + case ObjectID.HOSIDIUS_TITHE_A_4: + case ObjectID.HOSIDIUS_TITHE_A_1_WET: + case ObjectID.HOSIDIUS_TITHE_A_2_WET: + case ObjectID.HOSIDIUS_TITHE_A_3_WET: + case ObjectID.HOSIDIUS_TITHE_B_1_DRY: + case ObjectID.HOSIDIUS_TITHE_B_2_DRY: + case ObjectID.HOSIDIUS_TITHE_B_3_DRY: + case ObjectID.HOSIDIUS_TITHE_B_4: + case ObjectID.HOSIDIUS_TITHE_B_1_WET: + case ObjectID.HOSIDIUS_TITHE_B_2_WET: + case ObjectID.HOSIDIUS_TITHE_B_3_WET: + case ObjectID.HOSIDIUS_TITHE_C_1_DRY: + case ObjectID.HOSIDIUS_TITHE_C_2_DRY: + case ObjectID.HOSIDIUS_TITHE_C_3_DRY: + case ObjectID.HOSIDIUS_TITHE_C_4: + case ObjectID.HOSIDIUS_TITHE_C_1_WET: + case ObjectID.HOSIDIUS_TITHE_C_2_WET: + case ObjectID.HOSIDIUS_TITHE_C_3_WET: + return true; + default: + return false; + } + } + public int[] expectedPatchGameObject() { if (Objects.requireNonNull(TitheFarmingScript.state) == TitheFarmState.PLANTING_SEEDS) { return new int[]{ObjectID.HOSIDIUS_TITHE_EMPTY, ObjectID.HOSIDIUS_TITHE_A_1_DRY, ObjectID.HOSIDIUS_TITHE_B_1_DRY, ObjectID.HOSIDIUS_TITHE_C_1_DRY}; @@ -108,35 +150,51 @@ public int expectedHarvestObject() { } public boolean isEmptyPatch() { - return gameObject.getId() == ObjectID.HOSIDIUS_TITHE_EMPTY; + Rs2TileObjectModel obj = getGameObject(); + return obj != null && obj.getId() == ObjectID.HOSIDIUS_TITHE_EMPTY; } public boolean isEmptyPatchOrSeedling() { - return Arrays.stream(expectedPatchGameObject()).anyMatch(id -> id == gameObject.getId()); + Rs2TileObjectModel obj = getGameObject(); + if (obj == null) return false; + int objId = obj.getId(); + return Arrays.stream(expectedPatchGameObject()).anyMatch(id -> id == objId); } public boolean isValidToWater() { - return Arrays.stream(expectedWateredObject()).anyMatch(id -> id == gameObject.getId()) || isStage1() || isStage2(); + Rs2TileObjectModel obj = getGameObject(); + if (obj == null) return false; + int objId = obj.getId(); + return Arrays.stream(expectedWateredObject()).anyMatch(id -> id == objId) || isStage1() || isStage2(); } public boolean isValidToHarvest() { - return gameObject.getId() == expectedHarvestObject(); + Rs2TileObjectModel obj = getGameObject(); + return obj != null && obj.getId() == expectedHarvestObject(); } public boolean isStage1() { - return getGameObject().getId() == ObjectID.HOSIDIUS_TITHE_A_2_DRY - || getGameObject().getId() == ObjectID.HOSIDIUS_TITHE_B_2_DRY - || getGameObject().getId() == ObjectID.HOSIDIUS_TITHE_C_2_DRY; + Rs2TileObjectModel obj = getGameObject(); + if (obj == null) return false; + int id = obj.getId(); + return id == ObjectID.HOSIDIUS_TITHE_A_2_DRY + || id == ObjectID.HOSIDIUS_TITHE_B_2_DRY + || id == ObjectID.HOSIDIUS_TITHE_C_2_DRY; } public boolean isStage2() { - return getGameObject().getId() == ObjectID.HOSIDIUS_TITHE_A_3_DRY - || getGameObject().getId() == ObjectID.HOSIDIUS_TITHE_B_3_DRY - || getGameObject().getId() == ObjectID.HOSIDIUS_TITHE_C_3_DRY; + Rs2TileObjectModel obj = getGameObject(); + if (obj == null) return false; + int id = obj.getId(); + return id == ObjectID.HOSIDIUS_TITHE_A_3_DRY + || id == ObjectID.HOSIDIUS_TITHE_B_3_DRY + || id == ObjectID.HOSIDIUS_TITHE_C_3_DRY; } public boolean isWatered() { - var id = getGameObject().getId(); + Rs2TileObjectModel obj = getGameObject(); + if (obj == null) return false; + var id = obj.getId(); switch (id) { case ObjectID.HOSIDIUS_TITHE_B_1_WET: case ObjectID.HOSIDIUS_TITHE_B_2_WET: From cd6c3761e1b198a9837ccf17adff27e9469fef1d Mon Sep 17 00:00:00 2001 From: JThomasDevs <95548936+JThomasDevs@users.noreply.github.com> Date: Wed, 8 Apr 2026 15:19:34 -0600 Subject: [PATCH 14/95] mahogany homes client thread fixes (#372) --- .../mahoganyhomez/MahoganyHomesPlugin.java | 73 +++++++++++-------- 1 file changed, 41 insertions(+), 32 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesPlugin.java index 3c34490d64..2b7b912ca4 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesPlugin.java @@ -55,7 +55,7 @@ isExternal = PluginConstants.IS_EXTERNAL ) public class MahoganyHomesPlugin extends Plugin { - public static final String version = "0.0.9"; + public static final String version = "0.0.10"; private static final List PLANKS = Arrays.asList(ItemID.PLANK, ItemID.OAK_PLANK, ItemID.TEAK_PLANK, ItemID.MAHOGANY_PLANK); private static final List PLANK_NAMES = Arrays.asList("Plank", "Oak plank", "Teak plank", "Mahogany plank"); private static final Map MAHOGANY_HOMES_REPAIRS = new HashMap<>(); @@ -250,7 +250,7 @@ public void startUp() { overlayManager.add(textOverlay); overlayManager.add(highlightOverlay); overlayManager.add(plankSackOverlay); - clientThread.invoke(() -> { + clientThread.invokeLater(() -> { if (client.getGameState() == GameState.LOGGED_IN) { loadFromConfig(); updateVarbMap(); @@ -317,7 +317,7 @@ public void onGameStateChanged(GameStateChanged e) { @Subscribe public void onUsernameChanged(UsernameChanged e) { - clientThread.invoke(this::loadFromConfig); + clientThread.invokeLater(this::loadFromConfig); } @Subscribe @@ -337,9 +337,11 @@ public void onOverlayMenuClicked(OverlayMenuClicked e) { } if (e.getEntry().getOption().equals(MahoganyHomesOverlay.CLEAR_OPTION)) { - setCurrentHome(null); - updateConfig(); - lastChanged = null; + clientThread.invokeLater(() -> { + applyCurrentHome(null); + updateConfig(); + lastChanged = null; + }); } @@ -455,8 +457,10 @@ public void onChatMessage(ChatMessage e) { if (CONTRACT_FINISHED.matcher(Text.removeTags(e.getMessage())).matches()) { sessionContracts++; sessionPoints += getPointsForCompletingTask(); - setCurrentHome(null); - updateConfig(); + clientThread.invokeLater(() -> { + applyCurrentHome(null); + updateConfig(); + }); } } @@ -649,8 +653,11 @@ private void checkForAssignmentDialog() { for (final Home h : Home.values()) { if (h.getName().equalsIgnoreCase(name) && (currentHome != h || isPluginTimedOut())) { - setCurrentHome(h); - updateConfig(); + final Home selected = h; + clientThread.invokeLater(() -> { + applyCurrentHome(selected); + updateConfig(); + }); break; } } @@ -658,28 +665,30 @@ private void checkForAssignmentDialog() { } public void setCurrentHome(final Home h) { - clientThread.invoke(() -> { - currentHome = h; - client.clearHintArrow(); - lastChanged = Instant.now(); - lastCompletedCount = 0; - varbMap.clear(); - - if (currentHome == null) { - worldMapPointManager.removeIf(MahoganyHomesWorldPoint.class::isInstance); - contractTier = 0; - return; - } + clientThread.invokeLater(() -> applyCurrentHome(h)); + } - if (config.worldMapIcon()) { - worldMapPointManager.removeIf(MahoganyHomesWorldPoint.class::isInstance); - worldMapPointManager.add(new MahoganyHomesWorldPoint(h.getLocation(), this)); - } + private void applyCurrentHome(final Home h) { + currentHome = h; + client.clearHintArrow(); + lastChanged = Instant.now(); + lastCompletedCount = 0; + varbMap.clear(); - if (config.displayHintArrows() && client.getLocalPlayer() != null) { - refreshHintArrow(client.getLocalPlayer().getWorldLocation()); - } - }); + if (currentHome == null) { + worldMapPointManager.removeIf(MahoganyHomesWorldPoint.class::isInstance); + contractTier = 0; + return; + } + + if (config.worldMapIcon()) { + worldMapPointManager.removeIf(MahoganyHomesWorldPoint.class::isInstance); + worldMapPointManager.add(new MahoganyHomesWorldPoint(h.getLocation(), this)); + } + + if (config.displayHintArrows() && client.getLocalPlayer() != null) { + refreshHintArrow(client.getLocalPlayer().getWorldLocation()); + } } @@ -711,10 +720,10 @@ private void loadFromConfig() { try { final Home h = Home.valueOf(name.trim().toUpperCase()); - setCurrentHome(h); + applyCurrentHome(h); } catch (IllegalArgumentException e) { log.warn("Stored unrecognized home: {}", name); - currentHome = null; + applyCurrentHome(null); configManager.setConfiguration(group, MahoganyHomesConfig.HOME_KEY, null); } From 667e38750dd5f8ab42d37efba087d469ca7cfb01 Mon Sep 17 00:00:00 2001 From: chsami Date: Wed, 8 Apr 2026 23:44:36 +0200 Subject: [PATCH 15/95] fix(CalcifiedRockMinerPlugin): bump version to 1.1.2 and improve mining and crushing logic --- .../CalcifiedRockMinerPlugin.java | 2 +- .../CalcifiedRockMinerScript.java | 40 +++++++++++-------- 2 files changed, 24 insertions(+), 18 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerPlugin.java index 2b8d71b578..76e0745e92 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerPlugin.java @@ -32,7 +32,7 @@ ) public class CalcifiedRockMinerPlugin extends Plugin { - public final static String version = "1.1.1"; + public final static String version = "1.1.2"; private Instant scriptStartTime; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerScript.java b/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerScript.java index 1e4eb79355..50c9b6545a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerScript.java @@ -83,8 +83,7 @@ public boolean run(net.runelite.client.plugins.microbot.CalcifiedRockMiner.Calci private void handleMining(net.runelite.client.plugins.microbot.CalcifiedRockMiner.CalcifiedRockMinerConfig config) { if (Rs2Inventory.isFull()) { if (config.dropDeposits()) { - Rs2Inventory.dropAll("Calcified deposit"); - Rs2Inventory.dropAll("uncut"); + Rs2Inventory.dropAll(false, "calcified deposit", "uncut"); return; } else if (config.crushDeposits()) { BOT_STATUS = CalcifiedRockMinerState.CRUSHING; @@ -109,18 +108,22 @@ private void handleMining(net.runelite.client.plugins.microbot.CalcifiedRockMine if (config.focusCrackedWaterDeposits() && !Rs2Player.isMoving()) { var weepingRocks = Rs2GameObject.getDecorativeObjects(x -> x.getId() == WEEPING_ROCK, Rs2Player.getWorldLocation()); if (weepingRocks != null && !weepingRocks.isEmpty()) { - var rock = weepingRocks.stream().findFirst().get(); - MoveCameraToRock(rock.getWorldLocation()); - var distance = rock.getLocalLocation().distanceTo(Rs2Player.getLocalLocation()); - // 128 == 1 tile, so we must be mining the tear, if above, we should move to the tear - if (distance > 128 || !Rs2Player.isAnimating()) { - Rs2Camera.turnTo(rock.getLocalLocation(), 45); - Microbot.getMouse().click(rock.getCanvasLocation()); - Rs2Player.waitForXpDrop(Skill.MINING, true); - Rs2Antiban.actionCooldown(); - Rs2Antiban.takeMicroBreakByChance(); - sleepUntil(Rs2Player::isAnimating, 5000); - return; + var weepingRock = weepingRocks.stream().findFirst().get(); + GameObject tearRock = Rs2GameObject.getGameObject("Calcified rocks", weepingRock.getWorldLocation(), 2); + if (tearRock != null) { + MoveCameraToRock(tearRock.getWorldLocation()); + var distance = tearRock.getLocalLocation().distanceTo(Rs2Player.getLocalLocation()); + // 128 == 1 tile, so we must be mining the tear, if above, we should move to the tear + if (distance > 128 || !Rs2Player.isAnimating()) { + Rs2Camera.turnTo(tearRock.getLocalLocation(), 45); + if (Rs2GameObject.interact(tearRock)) { + Rs2Player.waitForXpDrop(Skill.MINING, true); + Rs2Antiban.actionCooldown(); + Rs2Antiban.takeMicroBreakByChance(); + sleepUntil(Rs2Player::isAnimating, 5000); + } + return; + } } } } @@ -170,8 +173,12 @@ private boolean hopIfTooManyPlayersNearby(net.runelite.client.plugins.microbot.C return false; } + private boolean hasHammer() { + return Rs2Inventory.hasItem("hammer") || Rs2Equipment.isWearing("hammer"); + } + private void handleCrushing(net.runelite.client.plugins.microbot.CalcifiedRockMiner.CalcifiedRockMinerConfig config) { - if (config.crushDeposits() && Rs2Inventory.hasItem("hammer") && Rs2Inventory.hasItem(29088)) { + if (config.crushDeposits() && hasHammer() && Rs2Inventory.hasItem(29088)) { if (Rs2Player.getWorldLocation().distanceTo(ANVIL) < 1) { Rs2Inventory.interact(29088, "use"); Rs2GameObject.interact("Anvil"); @@ -208,8 +215,7 @@ private void handleBanking(CalcifiedRockMinerConfig config) { Rs2Bank.walkToBank(BankLocation.CAM_TORUM); Rs2Bank.openBank(); sleepUntil(() -> Rs2Bank.isOpen(), 5000); - Rs2Bank.depositAll("Calcified deposit"); - Rs2Bank.depositAll("Uncut"); + Rs2Bank.depositAllExcept("pickaxe", "hammer"); Rs2Bank.closeBank(); } From 7d02ba7d7016956a8f1f2d7b3df8fd183c5ad93b Mon Sep 17 00:00:00 2001 From: chsami Date: Thu, 9 Apr 2026 00:04:48 +0200 Subject: [PATCH 16/95] fix(CalcifiedRockMinerPlugin): bump version to 1.1.4 and refactor object interactions to use Rs2TileObjectModel --- .../CalcifiedRockMinerPlugin.java | 2 +- .../CalcifiedRockMinerScript.java | 37 ++++++++++++++----- 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerPlugin.java index 76e0745e92..faa4fddeb1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerPlugin.java @@ -32,7 +32,7 @@ ) public class CalcifiedRockMinerPlugin extends Plugin { - public final static String version = "1.1.2"; + public final static String version = "1.1.4"; private Instant scriptStartTime; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerScript.java b/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerScript.java index 50c9b6545a..4e6144bb38 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/CalcifiedRockMiner/CalcifiedRockMinerScript.java @@ -1,6 +1,5 @@ package net.runelite.client.plugins.microbot.CalcifiedRockMiner; -import net.runelite.api.GameObject; import net.runelite.api.GameState; import net.runelite.api.Skill; import net.runelite.api.coords.LocalPoint; @@ -8,6 +7,7 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.CalcifiedRockMiner.CalcifiedRockMinerConfig; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; 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; @@ -15,7 +15,6 @@ import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; 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.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.math.Rs2Random; @@ -106,17 +105,23 @@ private void handleMining(net.runelite.client.plugins.microbot.CalcifiedRockMine } if (config.focusCrackedWaterDeposits() && !Rs2Player.isMoving()) { - var weepingRocks = Rs2GameObject.getDecorativeObjects(x -> x.getId() == WEEPING_ROCK, Rs2Player.getWorldLocation()); - if (weepingRocks != null && !weepingRocks.isEmpty()) { - var weepingRock = weepingRocks.stream().findFirst().get(); - GameObject tearRock = Rs2GameObject.getGameObject("Calcified rocks", weepingRock.getWorldLocation(), 2); + Rs2TileObjectModel weepingRock = Microbot.getRs2TileObjectCache().query() + .withId(WEEPING_ROCK) + .within(Rs2Player.getWorldLocation(), 15) + .first(); + if (weepingRock != null) { + Rs2TileObjectModel tearRock = Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query() + .within(weepingRock.getWorldLocation(), 2) + .withName("Calcified rocks") + .first()); if (tearRock != null) { MoveCameraToRock(tearRock.getWorldLocation()); var distance = tearRock.getLocalLocation().distanceTo(Rs2Player.getLocalLocation()); // 128 == 1 tile, so we must be mining the tear, if above, we should move to the tear if (distance > 128 || !Rs2Player.isAnimating()) { Rs2Camera.turnTo(tearRock.getLocalLocation(), 45); - if (Rs2GameObject.interact(tearRock)) { + if (tearRock.click()) { Rs2Player.waitForXpDrop(Skill.MINING, true); Rs2Antiban.actionCooldown(); Rs2Antiban.takeMicroBreakByChance(); @@ -132,10 +137,14 @@ private void handleMining(net.runelite.client.plugins.microbot.CalcifiedRockMine return; } - GameObject rock = Rs2GameObject.findReachableObject("Calcified rocks", true, 12, CALCIFIED_ROCK_LOCATION); + Rs2TileObjectModel rock = Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query() + .within(CALCIFIED_ROCK_LOCATION, 12) + .withName("Calcified rocks") + .nearestReachable()); if (rock != null && shouldTryMiningAgain) { MoveCameraToRock(rock.getWorldLocation()); - if (Rs2GameObject.interact(rock)) { + if (rock.click()) { Rs2Player.waitForXpDrop(Skill.MINING, true); Rs2Antiban.actionCooldown(); Rs2Antiban.takeMicroBreakByChance(); @@ -181,7 +190,15 @@ private void handleCrushing(net.runelite.client.plugins.microbot.CalcifiedRockMi if (config.crushDeposits() && hasHammer() && Rs2Inventory.hasItem(29088)) { if (Rs2Player.getWorldLocation().distanceTo(ANVIL) < 1) { Rs2Inventory.interact(29088, "use"); - Rs2GameObject.interact("Anvil"); + Rs2TileObjectModel anvil = Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query() + .within(ANVIL, 3) + .withName("Anvil") + .nearestReachable()); + if (anvil == null) { + return; + } + anvil.click(); sleep(400,600); Rs2Widget.sleepUntilHasWidget("How many would you like to smash?"); sleep(200,400); From 11c5329c70326393f122b151d31147fbc0691691 Mon Sep 17 00:00:00 2001 From: chsami Date: Thu, 9 Apr 2026 06:34:07 +0200 Subject: [PATCH 17/95] fix(VorkathPlugin): bump version to 1.3.13 and refactor NPC and object interactions to use Microbot API --- .../microbot/vorkath/VorkathPlugin.java | 2 +- .../microbot/vorkath/VorkathScript.java | 124 +++++++++++------- 2 files changed, 76 insertions(+), 50 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathPlugin.java index 9bbcd587a1..3efa70d779 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathPlugin.java @@ -32,7 +32,7 @@ @Slf4j public class VorkathPlugin extends Plugin { - public static final String version = "1.3.12"; + public static final String version = "1.3.13"; @Inject Client client; diff --git a/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathScript.java b/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathScript.java index 39dbcca727..e132968914 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathScript.java @@ -14,12 +14,14 @@ import net.runelite.client.plugins.loottracker.LootTrackerRecord; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.globval.enums.InterfaceTab; import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; 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.combat.Rs2Combat; 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.grandexchange.Rs2GrandExchange; import net.runelite.client.plugins.microbot.util.grounditem.LootingParameters; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; @@ -28,8 +30,6 @@ import net.runelite.client.plugins.microbot.util.magic.Rs2Spells; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.misc.Rs2Potion; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; 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; @@ -100,15 +100,15 @@ private static void drinkPrayer() { } private void calculateState() { - if (Rs2Npc.getNpc(NpcID.VORKATH) != null) { + if (Microbot.getRs2NpcCache().query().withId(NpcID.VORKATH).first() != null) { state = State.FIGHT_VORKATH; return; } - if (Rs2Npc.getNpc(NpcID.VORKATH_SLEEPING) != null) { + if (Microbot.getRs2NpcCache().query().withId(NpcID.VORKATH_SLEEPING).first() != null) { state = State.PREPARE_FIGHT; return; } - if (Rs2GameObject.findObjectById(ObjectID.UNGAEL_CRATER_ENTRANCE) != null) { + if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.UNGAEL_CRATER_ENTRANCE).first() != null) { state = State.WALK_TO_VORKATH; return; } @@ -116,7 +116,7 @@ private void calculateState() { state = State.WALK_TO_VORKATH_ISLAND; return; } - if (Rs2Npc.getNpc(NpcID.TORFINN_COLLECT_UNGAEL) != null) { + if (Microbot.getRs2NpcCache().query().withId(NpcID.TORFINN_COLLECT_UNGAEL).first() != null) { state = State.WALK_TO_VORKATH; } } @@ -208,8 +208,11 @@ public boolean run() { if (config.pohInRellekka()) { teleToPoh(); - sleepUntil(() -> Rs2GameObject.findObjectById(4525) != null); - Rs2GameObject.interact(4525, "Enter"); + sleepUntil(() -> Microbot.getRs2TileObjectCache().query().withId(4525).first() != null); + Rs2TileObjectModel pohPortal = Microbot.getRs2TileObjectCache().query().withId(4525).first(); + if (pohPortal != null) { + pohPortal.click("Enter"); + } sleepUntil(this::isCloseToRelleka); } else { @@ -224,24 +227,24 @@ public boolean run() { case WALK_TO_VORKATH_ISLAND: Rs2Player.toggleRunEnergy(true); Rs2Walker.walkTo(new WorldPoint(2640, 3693, 0)); - var torfin = Rs2Npc.getNpc(NpcID.TORFINN_COLLECT_RELLEKKA); + Rs2NpcModel torfin = Microbot.getRs2NpcCache().query().withId(NpcID.TORFINN_COLLECT_RELLEKKA).first(); if (torfin != null) { - Rs2Npc.interact(torfin, "Ungael"); - sleepUntil(() -> Rs2Npc.getNpc(NpcID.TORFINN_COLLECT_UNGAEL) != null); + torfin.click("Ungael"); + sleepUntil(() -> Microbot.getRs2NpcCache().query().withId(NpcID.TORFINN_COLLECT_UNGAEL).first() != null); } - if (Rs2Npc.getNpc(NpcID.TORFINN_COLLECT_UNGAEL) != null) { + if (Microbot.getRs2NpcCache().query().withId(NpcID.TORFINN_COLLECT_UNGAEL).first() != null) { state = State.WALK_TO_VORKATH; } break; case WALK_TO_VORKATH: kcPerTrip = 0; Rs2Walker.walkTo(new WorldPoint(2272, 4052, 0)); - TileObject iceChunks = Rs2GameObject.findObjectById(ObjectID.UNGAEL_CRATER_ENTRANCE); + Rs2TileObjectModel iceChunks = Microbot.getRs2TileObjectCache().query().withId(ObjectID.UNGAEL_CRATER_ENTRANCE).first(); if (iceChunks != null) { - Rs2GameObject.interact(ObjectID.UNGAEL_CRATER_ENTRANCE, "Climb-over"); - sleepUntil(() -> Rs2GameObject.findObjectById(ObjectID.UNGAEL_CRATER_ENTRANCE) == null); + iceChunks.click("Climb-over"); + sleepUntil(() -> Microbot.getRs2TileObjectCache().query().withId(ObjectID.UNGAEL_CRATER_ENTRANCE).first() == null); } - if (Rs2GameObject.findObjectById(ObjectID.UNGAEL_CRATER_ENTRANCE) == null) { + if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.UNGAEL_CRATER_ENTRANCE).first() == null) { state = State.PREPARE_FIGHT; } break; @@ -251,14 +254,20 @@ public boolean run() { boolean result = drinkPotions(); if (result) { - Rs2Npc.interact(NpcID.VORKATH_SLEEPING, "Poke"); - Rs2Player.waitForWalking(); - Rs2Npc.interact(NpcID.VORKATH_SLEEPING, "Poke"); + Rs2NpcModel sleepingVorkath = Microbot.getRs2NpcCache().query().withId(NpcID.VORKATH_SLEEPING).first(); + if (sleepingVorkath != null) { + sleepingVorkath.click("Poke"); + Rs2Player.waitForWalking(); + sleepingVorkath = Microbot.getRs2NpcCache().query().withId(NpcID.VORKATH_SLEEPING).first(); + if (sleepingVorkath != null) { + sleepingVorkath.click("Poke"); + } + } Rs2Player.waitForAnimation(1000); walkToCenter(); Rs2Player.waitForWalking(); handlePrayer(); - sleepUntil(() -> Rs2Npc.getNpc(NpcID.VORKATH) != null); + sleepUntil(() -> Microbot.getRs2NpcCache().query().withId(NpcID.VORKATH).first() != null); if (doesProjectileExistById(redProjectileId)) { handleRedBall(); sleep(300); @@ -267,7 +276,7 @@ public boolean run() { } break; case FIGHT_VORKATH: - vorkath = Rs2Npc.getNpc(NpcID.VORKATH); + vorkath = Microbot.getRs2NpcCache().query().withId(NpcID.VORKATH).first(); if (vorkath == null || vorkath.isDead()) { vorkathSessionKills++; tempVorkathKills--; @@ -276,7 +285,7 @@ public boolean run() { sleep(300, 600); Rs2Inventory.wield(primaryBolts); togglePrayer(false); - sleepUntil(() -> Rs2GroundItem.exists("Superior dragon bones", 20), 15000); + sleepUntil(() -> Microbot.getRs2TileItemCache().query().withName("Superior dragon bones").within(20).first() != null, 15000); return; } if (Microbot.getClient().getBoostedSkillLevel(Skill.HITPOINTS) <= 0) { @@ -295,7 +304,7 @@ public boolean run() { } } - if (Rs2Npc.attack(vorkath)) + if (!Rs2Combat.inCombat() && vorkath.click("Attack")) sleep(600); if (Microbot.getClient().getLocalPlayer().getLocalLocation().getSceneY() >= 59) { walkToCenter(); @@ -319,24 +328,26 @@ public boolean run() { } break; case ZOMBIE_SPAWN: - if (Rs2Npc.getNpc(NpcID.VORKATH) == null) { + if (Microbot.getRs2NpcCache().query().withId(NpcID.VORKATH).first() == null) { state = State.FIGHT_VORKATH; } togglePrayer(false); Rs2Player.eatAt(80); drinkPrayer(); - NPC zombieSpawn = Rs2Npc.getNpc(ZOMBIFIED_SPAWN); + Rs2NpcModel zombieSpawn = Microbot.getRs2NpcCache().query().withName(ZOMBIFIED_SPAWN).first(); if (zombieSpawn != null) { - while (Rs2Npc.getNpc(ZOMBIFIED_SPAWN) != null && !Rs2Npc.getNpc(ZOMBIFIED_SPAWN).isDead() + Rs2NpcModel currentSpawn; + while ((currentSpawn = Microbot.getRs2NpcCache().query().withName(ZOMBIFIED_SPAWN).first()) != null + && !currentSpawn.isDead() && !doesProjectileExistById(146)) { - Rs2Magic.castOn(MagicAction.CRUMBLE_UNDEAD, zombieSpawn); + Rs2Magic.castOn(MagicAction.CRUMBLE_UNDEAD, currentSpawn); sleep(600); } Rs2Player.eatAt(75); togglePrayer(true); Rs2Tab.switchTo(InterfaceTab.INVENTORY); state = State.FIGHT_VORKATH; - sleepUntil(() -> Rs2Npc.getNpc("Zombified Spawn") == null); + sleepUntil(() -> Microbot.getRs2NpcCache().query().withName(ZOMBIFIED_SPAWN).first() == null); if (doesProjectileExistById(redProjectileId)) { handleRedBall(); sleep(300); @@ -369,7 +380,10 @@ public boolean run() { false ); - Rs2GroundItem.loot("Vorkath's head", 20); + var vorkathHead = Microbot.getRs2TileItemCache().query().withName("Vorkath's head").within(20).first(); + if (vorkathHead != null) { + vorkathHead.pickup(); + } Rs2GroundItem.lootItemBasedOnValue(valueParams); int foodInventorySize = Rs2Inventory.getInventoryFood().size(); boolean hasVenom = Rs2Inventory.hasItem("venom"); @@ -377,7 +391,8 @@ public boolean run() { boolean hasPrayerPotion = Rs2Inventory.hasItem(Rs2Potion.getPrayerPotionsVariants().toArray(String[]::new)); boolean hasRangePotion = Rs2Inventory.hasItem(Rs2Potion.getRangePotionsVariants().toArray(String[]::new)); sleep(600, 2000); - if (!Rs2GroundItem.isItemBasedOnValueOnGround(config.priceOfItemsToLoot(), 20) && !Rs2GroundItem.exists("Vorkath's head", 20)) { + if (!Rs2GroundItem.isItemBasedOnValueOnGround(config.priceOfItemsToLoot(), 20) + && Microbot.getRs2TileItemCache().query().withName("Vorkath's head").within(20).first() == null) { if (config.KillsPerTrip() > 0 && kcPerTrip >= config.KillsPerTrip()) { leaveVorkath(); } @@ -402,9 +417,9 @@ public boolean run() { case DEAD_WALK: if (isCloseToRelleka()) { Rs2Walker.walkTo(new WorldPoint(2640, 3693, 0)); - torfin = Rs2Npc.getNpc(NpcID.TORFINN_COLLECT_RELLEKKA); + torfin = Microbot.getRs2NpcCache().query().withId(NpcID.TORFINN_COLLECT_RELLEKKA).first(); if (torfin != null) { - Rs2Npc.interact(torfin, "Collect"); + torfin.click("Collect"); sleepUntil(() -> Rs2Widget.hasWidget("Retrieval Service"), 1500); if (Rs2Widget.hasWidget("I'm afraid I don't have anything")) { // this means we looted all our stuff leaveVorkath(); @@ -510,10 +525,16 @@ private void leaveVorkath() { case JEWELLERY_BOX: teleToPoh(); if (config.rejuvinationPool()) { - Rs2GameObject.interact(29241, "Drink"); - sleepUntil(() -> Rs2Player.isFullHealth()); + Rs2TileObjectModel pool = Microbot.getRs2TileObjectCache().query().withId(29241).first(); + if (pool != null) { + pool.click("Drink"); + sleepUntil(() -> Rs2Player.isFullHealth()); + } + } + Rs2TileObjectModel jewelleryBox = Microbot.getRs2TileObjectCache().query().withId(29156).first(); + if (jewelleryBox != null) { + jewelleryBox.click("Teleport Menu"); } - Rs2GameObject.interact(29156, "Teleport Menu"); sleepUntil(() -> Rs2Widget.hasWidget("Castle Wars")); Rs2Widget.clickWidget("Castle Wars"); break; @@ -559,7 +580,10 @@ public void togglePrayer(boolean onOff) { private void handleRedBall() { if (doesProjectileExistById(redProjectileId)) { redBallWalk(); - Rs2Npc.interact("Vorkath", "attack"); + Rs2NpcModel vorkathNpc = Microbot.getRs2NpcCache().query().withName("Vorkath").first(); + if (vorkathNpc != null) { + vorkathNpc.click("Attack"); + } } } @@ -585,11 +609,11 @@ private boolean isCloseToRelleka() { private boolean teleToPoh() { if (Rs2Magic.canCast(MagicAction.TELEPORT_TO_HOUSE)) { Rs2Magic.cast(MagicAction.TELEPORT_TO_HOUSE); - sleepUntil(() -> Rs2GameObject.findObjectById(4525) != null); + sleepUntil(() -> Microbot.getRs2TileObjectCache().query().withId(4525).first() != null); return true; } else if (Rs2Inventory.hasItem("Teleport to house")) { Rs2Inventory.interact("Teleport to house", "break"); - sleepUntil(() -> Rs2GameObject.findObjectById(4525) != null); + sleepUntil(() -> Microbot.getRs2TileObjectCache().query().withId(4525).first() != null); return true; } return false; @@ -626,13 +650,15 @@ private void handleAcidWalk() { boolean hasAcidProj = doesProjectileExistById(acidProjectileId); boolean hasRedAcidProj = doesProjectileExistById(acidRedProjectileId); - List acidTiles = new ArrayList<>(); - Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.VORKATH_ACID).forEach(o -> acidTiles.add(o.getWorldLocation())); - Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.OLM_ACID_POOL).forEach(o -> acidTiles.add(o.getWorldLocation())); - Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.MYQ5_ACID_POOL).forEach(o -> acidTiles.add(o.getWorldLocation())); + List acidTiles = Microbot.getRs2TileObjectCache().query() + .withIds(ObjectID.VORKATH_ACID, ObjectID.OLM_ACID_POOL, ObjectID.MYQ5_ACID_POOL) + .toList() + .stream() + .map(Rs2TileObjectModel::getWorldLocation) + .collect(Collectors.toCollection(ArrayList::new)); WorldPoint playerLoc = Microbot.getClient().getLocalPlayer().getWorldLocation(); - WorldPoint vorkathLoc = vorkath.getRuneliteNpc().getWorldLocation(); + WorldPoint vorkathLoc = vorkath.getWorldLocation(); return new Object[]{hasAcidProj, hasRedAcidProj, acidTiles, playerLoc, vorkathLoc}; }); @@ -645,7 +671,7 @@ private void handleAcidWalk() { WorldPoint vorkathLoc = (WorldPoint) clientState[4]; if (!hasAcidProj && !hasRedAcidProj && acidTiles.isEmpty()) { - Rs2Npc.interact(vorkath, "attack"); + vorkath.click("Attack"); state = State.FIGHT_VORKATH; acidPools.clear(); return; @@ -657,7 +683,7 @@ private void handleAcidWalk() { if (safeTile != null) { if (playerLocation.equals(safeTile)) { - Rs2Npc.interact(vorkath, "attack"); + vorkath.click("Attack"); } else { Rs2Player.eatAt(60); Rs2Walker.walkFastLocal(LocalPoint.fromWorld(Microbot.getClient(), safeTile)); @@ -666,11 +692,11 @@ private void handleAcidWalk() { } private void testWooxWalk() { - vorkath = Rs2Npc.getNpc(NpcID.VORKATH_SLEEPING); + vorkath = Microbot.getRs2NpcCache().query().withId(NpcID.VORKATH_SLEEPING).first(); Object[] clientState = Microbot.getClientThread().invoke(() -> { WorldPoint playerLoc = Microbot.getClient().getLocalPlayer().getWorldLocation(); - WorldPoint vorkathLoc = vorkath.getRuneliteNpc().getWorldLocation(); + WorldPoint vorkathLoc = vorkath.getWorldLocation(); return new Object[]{playerLoc, vorkathLoc}; }); @@ -681,7 +707,7 @@ private void testWooxWalk() { if (safeTile != null) { if (playerLocation.equals(safeTile)) { - Rs2Npc.interact(vorkath, "attack"); + vorkath.click("Attack"); } else { Rs2Player.eatAt(60); Rs2Walker.walkFastLocal(LocalPoint.fromWorld(Microbot.getClient(), safeTile)); From ac94f6fbfcbd67bf92d67f88acce1a85f6b27fd6 Mon Sep 17 00:00:00 2001 From: chsami Date: Thu, 9 Apr 2026 06:41:51 +0200 Subject: [PATCH 18/95] fix(PestControlPlugin): bump version to 2.3.0 and improve inventory setup handling --- .../pestcontrol/PestControlPlugin.java | 2 +- .../pestcontrol/PestControlScript.java | 27 +++++++++++++++---- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlPlugin.java index 9aa4b1aab9..47e1fcd23b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlPlugin.java @@ -34,7 +34,7 @@ @Slf4j public class PestControlPlugin extends Plugin { - static final String version = "2.2.9"; + static final String version = "2.3.0"; @Inject PestControlScript pestControlScript; diff --git a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java index 1e216de201..0d0028cfe2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java @@ -11,6 +11,8 @@ import net.runelite.api.widgets.WidgetInfo; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.inventorysetups.InventorySetup; +import net.runelite.client.plugins.microbot.inventorysetups.InventorySetupsItem; import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; @@ -98,7 +100,9 @@ public boolean run(PestControlConfig config) { } if (Rs2Player.getWorldLocation().getRegionID() == 10537 && Rs2Player.getWorld() == config.world()) { - initialise = handleInventorySetup(); + if (handleInventorySetup()) { + initialise = false; + } } else { Microbot.log("Traveling to Pest Island"); @@ -201,15 +205,20 @@ public boolean run(PestControlConfig config) { /** * Handles the inventory setup based on the provided configuration. + * + * @return true when no setup work is needed (no setup configured, already + * matches, or successfully loaded); false when loading failed and + * the script should retry on the next tick. */ private boolean handleInventorySetup() { - if (config.inventorySetup() == null) { - return false; + InventorySetup setup = config.inventorySetup(); + if (setup == null || isEmptySetup(setup)) { + return true; } Microbot.log("Starting Inv Setup"); - var inventorySetup = new Rs2InventorySetup(config.inventorySetup(), mainScheduledFuture); + var inventorySetup = new Rs2InventorySetup(setup, mainScheduledFuture); if (inventorySetup.doesInventoryMatch() && inventorySetup.doesEquipmentMatch()) { return true; @@ -222,7 +231,15 @@ private boolean handleInventorySetup() { Microbot.log("Inv Setup Finished"); Rs2Bank.closeBank(); sleepUntil(() -> !Rs2Bank.isOpen(), 2000); - return false; + return true; + } + + private static boolean isEmptySetup(InventorySetup setup) { + return isAllDummy(setup.getInventory()) && isAllDummy(setup.getEquipment()); + } + + private static boolean isAllDummy(List items) { + return items == null || items.stream().allMatch(item -> item == null || InventorySetupsItem.itemIsDummy(item)); } From 995662c380b872b1ffe9e75656bf2e9554256aed Mon Sep 17 00:00:00 2001 From: chsami Date: Thu, 9 Apr 2026 07:41:10 +0200 Subject: [PATCH 19/95] fix(PestControlPlugin): bump version to 2.3.2 and enhance Pest Control logic with improved player movement handling --- .../pestcontrol/PestControlPlugin.java | 2 +- .../pestcontrol/PestControlScript.java | 47 +++++++++++++++---- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlPlugin.java index 47e1fcd23b..fc2f897e93 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlPlugin.java @@ -34,7 +34,7 @@ @Slf4j public class PestControlPlugin extends Plugin { - static final String version = "2.3.0"; + static final String version = "2.3.2"; @Inject PestControlScript pestControlScript; diff --git a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java index 0d0028cfe2..901ccc3f7b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java @@ -39,6 +39,7 @@ public class PestControlScript extends Script { boolean initialise = true; boolean walkToCenter = false; + private boolean wasInPestControl = false; PestControlConfig config; private final PestControlPlugin plugin; @@ -76,6 +77,21 @@ private void resetPortals() { } } + private static WorldPoint stepTowards(WorldPoint from, WorldPoint to, int maxStep) { + int dx = to.getX() - from.getX(); + int dy = to.getY() - from.getY(); + int chebyshev = Math.max(Math.abs(dx), Math.abs(dy)); + if (chebyshev <= maxStep) { + return to; + } + double scale = (double) maxStep / chebyshev; + return new WorldPoint( + from.getX() + (int) Math.round(dx * scale), + from.getY() + (int) Math.round(dy * scale), + from.getPlane() + ); + } + public boolean run(PestControlConfig config) { this.config = config; mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { @@ -111,6 +127,7 @@ public boolean run(PestControlConfig config) { } if (isInPestControl) { initialise = false; + wasInPestControl = true; if (!isQuickPrayerEnabled() && Microbot.getClient().getBoostedSkillLevel(Skill.PRAYER) != 0 && config.quickPrayer()) { final Widget prayerOrb = Rs2Widget.getWidget(ComponentID.MINIMAP_QUICK_PRAYER_ORB); if (prayerOrb != null) { @@ -119,12 +136,14 @@ public boolean run(PestControlConfig config) { } } if (!walkToCenter) { - WorldPoint worldPoint = WorldPoint.fromRegion(Rs2Player.getWorldLocation().getRegionID(), 32, 17, Microbot.getClient().getTopLevelWorldView().getPlane()); - Rs2Walker.walkTo(worldPoint, 3); - if (worldPoint.distanceTo(Rs2Player.getWorldLocation()) > 4) { - return; - } else { + WorldPoint playerLoc = Rs2Player.getWorldLocation(); + WorldPoint worldPoint = WorldPoint.fromRegion(playerLoc.getRegionID(), 32, 17, playerLoc.getPlane()); + if (playerLoc.distanceTo(worldPoint) <= 4) { walkToCenter = true; + } else { + Rs2Walker.walkMiniMap(stepTowards(playerLoc, worldPoint, 14)); + sleepUntil(() -> !Rs2Player.isMoving(), 4000); + return; } } @@ -176,10 +195,12 @@ public boolean run(PestControlConfig config) { } } else { - Rs2Walker.setTarget(null); + if (wasInPestControl) { + Rs2Walker.setTarget(null); + wasInPestControl = false; + } resetPortals(); walkToCenter = false; - sleep(Rs2Random.between(1600, 1800)); if (!isInBoat && !initialise) { if (Microbot.getClient().getLocalPlayer().getCombatLevel() >= 100) { Rs2GameObject.interact(ObjectID.GANGPLANK_25632); @@ -188,10 +209,11 @@ public boolean run(PestControlConfig config) { } else { Rs2GameObject.interact(ObjectID.GANGPLANK_14315); } - sleepUntil(() -> Microbot.getClient().getWidget(WidgetInfo.PEST_CONTROL_BOAT_INFO) != null, 3000); + sleepUntil(this::isInBoat, 3000); } else { if (config.alchInBoat() && !config.alchItem().equalsIgnoreCase("")) { Rs2Magic.alch(config.alchItem()); + sleep(Rs2Random.between(1600, 1800)); } } } @@ -249,11 +271,15 @@ public boolean isOutside() { } public boolean isInBoat() { - return Microbot.getClient().getWidget(WidgetInfo.PEST_CONTROL_BOAT_INFO) != null; + return Microbot.getClientThread().runOnClientThreadOptional( + () -> Microbot.getClient().getWidget(WidgetInfo.PEST_CONTROL_BOAT_INFO) != null + ).orElse(false); } public boolean isInPestControl() { - return Microbot.getClient().getWidget(WidgetInfo.PEST_CONTROL_BLUE_SHIELD) != null; + return Microbot.getClientThread().runOnClientThreadOptional( + () -> Microbot.getClient().getWidget(WidgetInfo.PEST_CONTROL_BLUE_SHIELD) != null + ).orElse(false); } public void exitBoat() { @@ -379,6 +405,7 @@ public void shutdown() { Microbot.log("Pest control about to shutdown"); initialise = true; walkToCenter = false; + wasInPestControl = false; super.shutdown(); } } From f7a46dcc40cf7844611470212f2f2413b7bb4e22 Mon Sep 17 00:00:00 2001 From: chsami Date: Thu, 9 Apr 2026 07:49:26 +0200 Subject: [PATCH 20/95] fix(Documentation): rename agents.md to CLAUDE.md and add debugging notes for plugin issues --- AGENTS.md | 239 +-------------------------------- CLAUDE.md | 2 + docs/PLUGIN_DEBUGGING_NOTES.md | 202 ++++++++++++++++++++++++++++ 3 files changed, 205 insertions(+), 238 deletions(-) mode change 100644 => 120000 AGENTS.md create mode 100644 docs/PLUGIN_DEBUGGING_NOTES.md diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 7371f88eea..0000000000 --- a/AGENTS.md +++ /dev/null @@ -1,238 +0,0 @@ -# agents.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Overview - -Microbot Hub is a community plugin repository for the Microbot RuneLite client. It maintains a separation between core client functionality and community-contributed plugins, allowing rapid plugin development without affecting client stability. Each plugin is independently built, versioned, and packaged for GitHub Releases. - -## Build System Architecture - -The build system uses **Gradle with custom plugin discovery and packaging**: - -- **Dynamic Plugin Discovery**: `build.gradle` scans `src/main/java/net/runelite/client/plugins/microbot/` for directories containing `*Plugin.java` files -- **Per-Plugin Source Sets**: Each discovered plugin gets its own Gradle source set, compile task, and shadow JAR task -- **Gradle Helper Scripts**: Core build logic lives in: - - `gradle/project-config.gradle` - centralized configuration (JDK version, paths, GitHub release URLs, client version) - - `gradle/plugin-utils.gradle` - plugin discovery, descriptor parsing, JAR creation, SHA256 hashing - -### Build Commands - -```bash -# Build all plugins -./gradlew clean build - -# Build specific plugin(s) only (much faster for iteration) -./gradlew build -PpluginList=PestControlPlugin -./gradlew build -PpluginList=PestControlPlugin,AutoMiningPlugin - -# Run tests (tests have access to all plugin source sets) -./gradlew test - -# Generate plugins.json metadata file with SHA256 hashes (requires exact JDK 11) -./gradlew generatePluginsJson - -# Copy plugin documentation to public/docs/ -./gradlew copyPluginDocs - -# Launch RuneLite debug session with plugins from Microbot.java -./gradlew run --args='--debug' - -# Validate JDK version -./gradlew validateJdkVersion -``` - -## Plugin Structure - -Each plugin lives in its own package under `src/main/java/net/runelite/client/plugins/microbot//`: - -``` -/ -├── Plugin.java # Main plugin class with @PluginDescriptor -├── Script.java # Script logic extending Script class -├── Config.java # Configuration interface (optional) -├── Overlay.java # UI overlay (optional) -└── Additional support classes -``` - -Matching resources under `src/main/resources/net/runelite/client/plugins/microbot//`: - -``` -/ -├── dependencies.txt # Maven coordinates (optional) -└── docs/ - ├── README.md # Plugin documentation - └── assets/ # Screenshots, icons, etc. -``` - -## Plugin Descriptor Anatomy - -Every plugin **must** have a `@PluginDescriptor` annotation with these **required** fields: - -- `name` - Display name (use `PluginConstants.DEFAULT_PREFIX` or create custom prefix) -- `version` - Semantic version string (store in `static final String version` field) -- `minClientVersion` - Minimum Microbot client version required - -Important **optional** fields: - -- `authors` - Array of author names -- `description` - Brief description shown in plugin panel -- `tags` - Array of tags for categorization -- `iconUrl` - URL to icon image (shown in client hub) -- `cardUrl` - URL to card image (shown on website) -- `enabledByDefault` - Use `PluginConstants.DEFAULT_ENABLED` (currently `false`) -- `isExternal` - Use `PluginConstants.IS_EXTERNAL` (currently `true`) - -Example: -```java -@PluginDescriptor( - name = PluginConstants.MOCROSOFT + "Pest Control", - description = "Supports all boats, portals, and shields.", - tags = {"pest control", "minigames"}, - authors = { "Mocrosoft" }, - version = PestControlPlugin.version, - minClientVersion = "1.9.6", - iconUrl = "https://chsami.github.io/Microbot-Hub/PestControlPlugin/assets/icon.png", - cardUrl = "https://chsami.github.io/Microbot-Hub/PestControlPlugin/assets/card.png", - enabledByDefault = PluginConstants.DEFAULT_ENABLED, - isExternal = PluginConstants.IS_EXTERNAL -) -@Slf4j -public class PestControlPlugin extends Plugin { - static final String version = "2.2.7"; - // ... -} -``` - -## PluginConstants - -The `PluginConstants.java` file is **shared across all plugins** (included in each JAR during build). It contains: - -- Standardized plugin name prefixes (e.g., `DEFAULT_PREFIX`, `MOCROSOFT`, `BOLADO`) -- Global defaults: `DEFAULT_ENABLED = false`, `IS_EXTERNAL = true` - -When creating a new plugin prefix, add it to `PluginConstants.java` for consistency. - -## Adding External Dependencies - -If a plugin needs additional libraries beyond the Microbot client: - -1. Create `src/main/resources/net/runelite/client/plugins/microbot//dependencies.txt` -2. Add Maven coordinates, one per line: - ``` - com.google.guava:guava:33.2.0-jre - org.apache.commons:commons-lang3:3.14.0 - ``` -3. The build system automatically includes these in the plugin's shadow JAR - -## Testing and Debugging Plugins - -### Running Plugins in Debug Mode - -1. Edit `src/test/java/net/runelite/client/Microbot.java` -2. Add your plugin class to the `debugPlugins` array: - ```java - private static final Class[] debugPlugins = { - YourPlugin.class, - AutoLoginPlugin.class - }; - ``` -3. Run `./gradlew run --args='--debug'` or use your IDE's run configuration - -### Running Tests - -- Tests live in `src/test/java/` -- Test classes have access to all plugin source sets (configured in `build.gradle`) -- Use `./gradlew test` to run all tests - -## Version Management - -- **Always increment the plugin version** when making changes (even small fixes) -- Store version in a static field: `static final String version = "1.2.3";` -- Follow semantic versioning: `MAJOR.MINOR.PATCH` -- The version is used for JAR naming, GitHub release assets, and `plugins.json` generation - -## Git Workflow - -Based on recent commits: - -- Use conventional commit prefixes: `fix:`, `feat:`, `docs:`, etc. -- Include PR references when applicable: `fix: description (#123)` -- Work on feature branches, merge to `development`, create PRs to `main` -- Current branch: `development`, main branch: `main` - -## Publishing Workflow - -1. Build plugins: `./gradlew build` -2. Generate metadata: `./gradlew generatePluginsJson` (requires JDK 11 exactly) -3. Copy documentation: `./gradlew copyPluginDocs` -4. Upload `build/libs/-.jar` and updated `public/docs/plugins.json` as assets on the GitHub release tagged with `` (or `latest-release` for the stable tag): `https://github.com/chsami/Microbot-Hub/releases/download//-.jar` - -## Important Implementation Details - -- **Local Microbot Client Source**: The latest Microbot client source lives in the sibling `Microbot` folder (`../Microbot/`) on the `development` branch. This is the authoritative, up-to-date client codebase. When you need to look up client APIs, utility classes (e.g., `Rs2Bank`, `Rs2Inventory`, `Rs2Walker`), or understand how the client works, reference that repository directly. -- **Java Version**: JDK 11 (configured in `project-config.gradle` with `TARGET_JDK_VERSION = 11`, vendor `ADOPTIUM`) -- **Microbot Client Dependency**: Defaults to the latest version resolved via `https://microbot.cloud/api/version/client`, falling back to `2.0.61` if lookup fails. Artifacts come from GitHub Releases (`https://github.com/chsami/Microbot/releases/download//microbot-.jar`). Override with `-PmicrobotClientVersion=` or `-PmicrobotClientVersion=latest`, or supply a local JAR for offline work via `-PmicrobotClientPath=/absolute/path/to/microbot-.jar` -- **Plugin Release Tag**: `plugins.json` uses a stable release tag (`latest-release`) so download URLs stay constant: `https://github.com/chsami/Microbot-Hub/releases/download/latest-release/-.jar`. Override with `-PpluginsReleaseTag=` if needed. -- **Shadow JAR Excludes**: Common exclusions defined in `plugin-utils.gradle` include `docs/**`, `dependencies.txt`, metadata files, and module-info -- **Reproducible Builds**: JAR tasks disable file timestamps, use reproducible file order, and normalize file permissions to `0644` -- **Descriptor Parsing**: Build system uses regex to extract plugin metadata from Java source files (see `getPluginDescriptorInfo` in `plugin-utils.gradle`) - -## Plugin Discovery Logic - -When you run `./gradlew build`: - -1. Scans `src/main/java/net/runelite/client/plugins/microbot/` for directories -2. Finds directories containing a file matching `*Plugin.java` -3. Creates a plugin object with: `name` (class name without .java), `sourceSetName` (directory name), `dir`, `javaFile` -4. Filters by `-PpluginList` if provided -5. For each plugin: - - Creates dedicated source set - - Configures compilation classpath with Microbot client - - Creates shadow JAR task with plugin-specific dependencies - - Parses `@PluginDescriptor` for metadata - - Computes SHA256 hash of JAR for `plugins.json` - -## Microbot CLI & Agent Server - -The Microbot client embeds an HTTP server (Agent Server plugin, port 8081) that the Hub uses for automated testing. Reference docs are mirrored in this repo: - -- **CLI command reference**: `docs/MICROBOT_CLI.md` — login, script lifecycle, inventory, NPCs, walking, banking, etc. -- **HTTP API summary**: `docs/AGENT_SERVER.md` — all endpoints, login error detection, script result submission. -- **Script lifecycle API**: `docs/SCRIPT_LIFECYCLE_API.md` — start/stop/status/results endpoints and automated testing flow. -- **Test example**: `src/test/java/net/runelite/client/ScriptLifecycleTest.java` — demonstrates the full login → start → poll → results → stop cycle. - -Key capabilities for Hub plugin testing: -- **Login control**: `POST /login` blocks until login succeeds or fails, returning a definitive `success` boolean and `loginError` on failure (non-member on members world, bans, auth failures). Auto-dismisses error dialogs on retry — no manual intervention needed. -- **Script lifecycle**: Start/stop plugins by class name via HTTP, poll runtime status, submit and retrieve structured test results. -- **Java result API**: Hub scripts can call `ScriptResultStore.submit(className, data)` directly from within the JVM. - -## Common Patterns - -- Plugins extending `SchedulablePlugin` implement `getStartCondition()` and `getStopCondition()` for scheduler integration -- Use `@Inject` for dependency injection (configs, overlays, scripts) -- Config classes use `@Provides` methods to register with `ConfigManager` -- Overlays are registered in `startUp()`, unregistered in `shutDown()` -- Use `@Subscribe` for event handling (ChatMessage, GameTick, etc.) - -## Threading - -Scripts run on a scheduled executor thread, but certain RuneLite API calls (widgets, game objects, etc.) must run on the client thread: - -```java -// Use invoke() for client thread operations -TrialInfo info = Microbot.getClientThread().invoke(() -> TrialInfo.getCurrent(client)); - -// For void operations -Microbot.getClientThread().invoke(() -> { - // client thread code here -}); -``` - -**Always use `Microbot.getClientThread().invoke()`** when accessing: -- Widgets (`client.getWidget()`, `widget.isHidden()`) -- Game objects that aren't cached -- Player world view (`client.getLocalPlayer().getWorldView()`) -- `BoatLocation.fromLocal()` - accesses player world view internally -- `TrialInfo.getCurrent()` - accesses widgets internally -- Any RuneLite API that throws "must be called on client thread" diff --git a/AGENTS.md b/AGENTS.md new file mode 120000 index 0000000000..681311eb9c --- /dev/null +++ b/AGENTS.md @@ -0,0 +1 @@ +CLAUDE.md \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 2eb1ddf950..630d5ff4f3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -127,6 +127,8 @@ If a plugin needs additional libraries beyond the Microbot client: ## Testing and Debugging Plugins +**Before chasing a "script does nothing" bug, read [`docs/PLUGIN_DEBUGGING_NOTES.md`](docs/PLUGIN_DEBUGGING_NOTES.md).** It documents the recurring failure modes in Hub plugins (instanced-region coordinate mismatches, the new Queryable API not auto-walking, null-guard predicates masking broken lookups, static field leakage across plugin restarts, etc.) and the agent-server `curl` workflow for inspecting live state instead of theorizing from code. + ### Running Plugins in Debug Mode 1. Edit `src/test/java/net/runelite/client/Microbot.java` diff --git a/docs/PLUGIN_DEBUGGING_NOTES.md b/docs/PLUGIN_DEBUGGING_NOTES.md new file mode 100644 index 0000000000..00c061d225 --- /dev/null +++ b/docs/PLUGIN_DEBUGGING_NOTES.md @@ -0,0 +1,202 @@ +# Plugin Debugging Notes + +Lessons learned from debugging Hub plugins. Read this before chasing a "script silently does nothing" bug — it'll save you several rounds of guessing. + +## 1. Use the agent server. Stop guessing. + +The Microbot client embeds an HTTP agent server (port 8081, plugin name "Agent Server") that exposes the live scene cache, player state, inventory, NPCs, and dialogue. Two `curl` calls usually beat an hour of staring at code. + +```bash +# What does the player actually see right now? +curl -s 'http://127.0.0.1:8081/state' | jq + +# What's actually in the scene cache? +curl -s 'http://127.0.0.1:8081/objects?maxDistance=50&limit=10000' > /tmp/objs.json +python3 -c " +import json +data = json.load(open('/tmp/objs.json')) +print('total:', data['total']) +# filter by id range, name, position — whatever you need +for o in data['objects']: + if o['id'] == 27383: # the id you care about + print(o) +" +``` + +The CLI wrapper at `../Microbot/microbot-cli` handles login, inventory, NPCs, dialogue, walking, banking, and script lifecycle. See `docs/MICROBOT_CLI.md` for the command reference. + +**The rule:** if a "script does nothing" bug has gone past two rounds of theorizing, stop and inspect the live state. Don't reason about what *should* be in the cache; ask the cache. + +### Pitfall: the CLI is missing some flags +The `microbot-cli objects` command currently ignores `--id` and `--distance` flags and just dumps the raw `/objects` endpoint with defaults. Use `curl` directly when you need precise filtering. The server-side parameters are documented in `docs/AGENT_SERVER.md`; for `/objects` they're `name`, `maxDistance` (default 20), `limit`. + +## 2. Instanced regions are everywhere — and they break worldpoint math + +Several minigames and quest areas (tithe farm, raids, gauntlet, soul wars, fight caves, house, theatre, etc.) load tiles from a *template region* into the player's scene. Inside an instance: + +- The actual scene tiles live at template coordinates, often in the high X/Y corner of the world (e.g. tithe farm patches at `(13602, 7000)`). +- The player's "logical" overworld coordinate is somewhere completely different (e.g. tithe farm logical pos `(1806, 3501)` in Hosidius). +- Both views of the player are reachable depending on which API you call. **They are not the same number, and they are not interchangeable.** + +### The two `getWorldLocation()` calls behave differently + +```java +// Returns the OVERWORLD coord (the "logical" position, mirrored from the instance). +client.getLocalPlayer().getWorldLocation() + +// Returns the INSTANCE coord (where the tiles actually live in the loaded scene). +Rs2Player.getWorldLocation() +``` + +The Microbot wrapper checks `getTopLevelWorldView().getScene().isInstance()` and translates via `WorldPoint.fromLocalInstance(...)`. The raw client call doesn't. + +**TileObject coordinates always match the instance side**, because that's where the tile actually exists in the scene. So if you compute a target world point using `client.getLocalPlayer()...getRegionID()` (overworld region) and then try to match it against a tile object's `getWorldLocation()` (instance region), the lookup silently returns null. Forever. + +### Three things that work in instanced regions + +1. **`Rs2Player.getWorldLocation()`** — already handles the mirror. +2. **Local-region coordinates (`wp.getRegionX()` / `wp.getRegionY()`)** — these are `x & 63` / `y & 63`, intrinsic to the tile, *the same* for the template region and the instance because they're modulo-64 within a region. Match by these instead of by full world point if you need lookups that work across both contexts. +3. **The cache itself.** Query the cache by ID, name, or distance — don't reconstruct world points from scratch. + +### How to detect that you're inside an instance + +```java +boolean instanced = Microbot.getClient().getTopLevelWorldView().getScene().isInstance(); +``` + +Or just observe: if `Rs2Player.getWorldLocation()` is in the high-coord corner of the map (X > 6000 or so), you're in an instance. + +## 3. The new Queryable API does NOT auto-walk + +Legacy `Rs2GameObject.clickObject(TileObject, action)` (and every `interact(...)` overload that goes through it) automatically walks if the target is more than 51 tiles away: + +```java +// inside Rs2GameObject.clickObject — line ~1728 +if (Rs2Player.getWorldLocation().distanceTo(object.getWorldLocation()) > 51) { + Microbot.log("...too far, walking to the object...."); + Rs2Walker.walkTo(object.getWorldLocation()); + return false; +} +``` + +The new `Rs2TileObjectModel.click(action)` has **no equivalent**. It just dispatches a menu invoke at the current location. If you migrate `Rs2GameObject.interact(id, action)` to `cache.query().withId(id).nearest().click(action)` and the object is out of click range, your script will silently fail every tick forever. + +**Mitigation:** wrap migrated interactions in a helper that walks first. + +```java +private static boolean interactWithObject(int id, String action) { + Rs2TileObjectModel model = Microbot.getRs2TileObjectCache().query() + .withId(id) + .nearest(); + if (model == null) { + Microbot.log("Object id " + id + " not in scene"); + return false; + } + WorldPoint playerLoc = Rs2Player.getWorldLocation(); + if (playerLoc != null && playerLoc.distanceTo(model.getWorldLocation()) > 51) { + Microbot.log("Object id " + id + " too far, walking..."); + Rs2Walker.walkTo(model.getWorldLocation()); + return false; + } + return action == null ? model.click() : model.click(action); +} +``` + +Or follow the AIOFighter pattern (BankerScript:482): bound the query with `.nearest(20)` and rely on a *separate* walker call upstream to get into range first. Either is fine — just don't assume `click()` walks. + +## 4. Null-safe predicates can mask the underlying bug + +Migrating from raw `TileObject` (which NPEs on `null.getId()`) to `Rs2TileObjectModel` with null-guarded predicates feels safer: + +```java +// Before (NPEs if model is null) +public boolean isEmptyPatch() { + return gameObject.getId() == ObjectID.HOSIDIUS_TITHE_EMPTY; +} + +// After (graceful) +public boolean isEmptyPatch() { + Rs2TileObjectModel obj = getGameObject(); + return obj != null && obj.getId() == ObjectID.HOSIDIUS_TITHE_EMPTY; +} +``` + +This is correct, but be aware: if the lookup is broken and `getGameObject()` returns null *for every plant*, all your predicates return `false`. Code that branches on those predicates will silently take the "no plants need anything done" path. Symptom: "script does nothing." Cause: "lookup broken since the migration." + +Watch out for predicates that flip in the *wrong direction* on null. A `noneMatch(isEmptyPatch)` returns `true` when nothing is recognized as empty — which can also mean "we have no data yet." If that gates a once-only state transition (like `allPlanted = true`), you can permanently stick the script in the wrong branch on the very first tick. Gate state-flipping predicates on `allMatch(p -> p.getGameObject() != null)` first so you only act on real data. + +## 5. Static fields leak across plugin restarts + +A common Microbot plugin pattern: + +```java +public class FooPlugin extends Plugin { + private final FooScript fooScript = new FooScript(); // <-- final +} + +public class FooScript extends Script { + public static boolean init = true; // <-- static + public static List things = ...; // <-- static + public static FooState state = ...; // <-- static + private boolean allDone = false; // <-- instance, but lives as long as the plugin +} +``` + +When the user disables and re-enables the plugin, neither the `static` fields nor the instance fields get reset — the script object is `final` on the plugin, and the static fields persist across the entire JVM session. So a fresh plugin start can inherit `init = false`, a stale `things` list from a previous session, or `allDone = true` from a finished run. + +**Mitigation:** reset everything at the top of `run()`, *before* you schedule the executor lambda: + +```java +public boolean run(FooConfig config) { + init = true; + things = new ArrayList<>(); + state = STARTING; + allDone = false; + mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(...); +} +``` + +This is cheap insurance and makes the plugin behave the same on the first start as on the tenth restart. + +## 6. Don't use `Microbot.showMessage` from script threads + +`Microbot.showMessage` opens a Swing modal via `SwingUtilities.invokeAndWait`. If your script's executor is ticking every 100 ms, the next tick will interrupt the AWT-blocking thread and you'll get a flood of `InterruptedException` traces with no actual message ever shown to the user. + +For debug/log indicators, use `Microbot.log` instead — it's just slf4j, never blocks the AWT thread, and shows up in the same place users already look. + +Reserve `showMessage` for hard-stop conditions where the script is about to `shutdown()` and the user genuinely needs to see the message (and even then, only call it once). + +## 7. Don't trust hardcoded coordinates without verifying against the cache + +Hardcoded coordinates in plugins drift over time: +- Jagex moves objects in updates. +- An area gets converted to instanced and the local scene anchor changes. +- The original developer used a different convention (corner vs center, 0-indexed vs 1-indexed). + +If a plugin's hardcoded `(regionX, regionY)` lookups stop matching, the symptom is always "script does nothing." Confirm the actual coordinates with one CLI call before patching: + +```bash +curl -s 'http://127.0.0.1:8081/objects?maxDistance=100&limit=10000' \ + | python3 -c " +import json, sys +data = json.load(sys.stdin) +# filter for whatever id you're looking for +for o in data['objects']: + if o['id'] == 27383: + x, y = o['position']['x'], o['position']['y'] + print(f'world=({x},{y}) regionLocal=({x & 63},{y & 63})') +" +``` + +For tithe farm specifically, the lane definitions in `TitheFarmingScript.init(...)` are off by 1 in both x and y from the actual instance patches. Rather than rewrite all four lane lists, the plant lookup uses a `dx <= 1 && dy <= 1` tolerance plus a tithe-patch-ID filter — patches are 3 tiles apart so the tolerance can never grab a wrong neighbour. This pattern (tolerance + ID filter) is a generic safety net for any future hardcoded-coordinate drift. + +## Quick triage checklist + +When a Hub plugin "doesn't do anything": + +1. **Is the script even running?** Add a `Microbot.log("X script started")` at the top of `run()` and watch the log on the next plugin start. +2. **What state is it in?** Most plugins have an overlay showing the current state — read it. If not, log the state on each tick (or on transition). +3. **Is the cache empty, or just the wrong thing?** `curl 'http://localhost:8081/objects?maxDistance=20&limit=200'` and look at what's actually there. +4. **Is the player where you think?** `curl 'http://localhost:8081/state'` — and if you're inside an instance, expect logical vs instance coordinates to differ. +5. **Are you hitting a null-guard short-circuit?** Search the script for predicates that return `false` on null and gate behavior. If the lookup is broken, every predicate returns `false` and the script takes the "do nothing" branch. +6. **Did the static state leak from a previous run?** Check whether `run()` resets the static fields the script relies on. From 2f97e6ee2b54f591d08329b2d5bf01f711edfdc3 Mon Sep 17 00:00:00 2001 From: chsami Date: Thu, 9 Apr 2026 14:48:04 +0200 Subject: [PATCH 21/95] refactor: migrate woodcutting forestry, barrows, and revkiller to new query API Migrate Rs2Npc and Rs2GameObject calls in woodcutting forestry events (Entlings, Flowers, Fox, Hives, Egg, Leprechaun, Ritual, StrugglingSapling), BarrowsScript, and revKillerScript to use Rs2NpcCache/Rs2TileObjectCache query API. Fix unsafe null-pointer chains in revkiller loot logic. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../microbot/barrows/BarrowsScript.java | 15 +++++---- .../microbot/revkiller/revKillerScript.java | 29 ++++++++--------- .../woodcutting/Forestry/EggEvent.java | 30 ++++++++---------- .../woodcutting/Forestry/EntlingsEvent.java | 17 +++++----- .../woodcutting/Forestry/FlowersEvent.java | 17 +++++----- .../woodcutting/Forestry/FoxEvent.java | 9 +++--- .../woodcutting/Forestry/HivesEvent.java | 26 ++++++++-------- .../woodcutting/Forestry/LeprechaunEvent.java | 16 +++------- .../woodcutting/Forestry/RitualEvent.java | 12 +++---- .../Forestry/StrugglingSaplingEvent.java | 31 ++++++++++--------- 10 files changed, 91 insertions(+), 111 deletions(-) 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..8e863d4a5e 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 @@ -27,8 +27,7 @@ import net.runelite.client.plugins.microbot.util.magic.Rs2Spellbook; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.misc.Rs2Food; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -814,7 +813,7 @@ public void goToTheMound(Rs2WorldArea moundArea){ //strange old man body blocking us - net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel strangeOldMan = rs2NpcCache.query().withName("Strange Old Man").nearest(); + Rs2NpcModel strangeOldMan = rs2NpcCache.query().withName("Strange Old Man").nearest(); if(strangeOldMan !=null){ if(strangeOldMan.getWorldLocation() != null){ @@ -879,7 +878,7 @@ public void gainRP(BarrowsConfig config){ if(RP>870) return; - net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel skele = rs2NpcCache.query().withName("Skeleton").nearest(); + Rs2NpcModel skele = rs2NpcCache.query().withName("Skeleton").nearest(); if(skele == null || skele.isDead()) return; @@ -926,7 +925,7 @@ public void gainRP(BarrowsConfig config){ if(hintNpcModel()!=null) { Rs2NpcModel barrowsbrotherHint = hintNpcModel(); - net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel brother = rs2NpcCache.query().withName(barrowsbrotherHint.getName()).nearest(); + Rs2NpcModel brother = rs2NpcCache.query().withName(barrowsbrotherHint.getName()).nearest(); if(brother !=null && brother.hasLineOfSight()) { Microbot.log("The brother is here."); break; @@ -1194,7 +1193,7 @@ public void checkForAndFightBrother(BarrowsConfig config){ if(inTunnels) { - if (!Rs2Npc.hasLineOfSight(currentBrother)) { + if (!currentBrother.hasLineOfSight()) { Microbot.log("No LOS!"); break; } @@ -1209,12 +1208,12 @@ public void checkForAndFightBrother(BarrowsConfig config){ } if(hintNpcModel() != null && Rs2Player.getInteracting() != null && !Rs2Player.getInteracting().getName().equals(hintNpcModel().getName())){ - if(Rs2Npc.attack(currentBrother)){ + if(currentBrother.click("Attack")){ sleepUntil(()-> Rs2Player.isInCombat(), Rs2Random.between(3000,6000)); } } else { if(!Rs2Player.isInCombat()){ - if(Rs2Npc.attack(currentBrother)){ + if(currentBrother.click("Attack")){ sleepUntil(()-> Rs2Player.isInCombat(), Rs2Random.between(3000,6000)); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerScript.java b/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerScript.java index 5910d34743..64c5ef8364 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerScript.java @@ -31,8 +31,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.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.player.Rs2PlayerModel; import net.runelite.client.plugins.microbot.util.player.Rs2Pvp; @@ -128,7 +127,7 @@ public boolean run(revKillerConfig config) { if(firstRun || weDied) { Microbot.log("It's our first run or we died!"); - if(firstRun && Rs2Npc.getNpc(config.selectedRev().getName()) != null){ + if(firstRun && Microbot.getRs2NpcCache().query().withName(config.selectedRev().getName()).nearest() != null){ // we're all ready geared and there firstRun = false; Microbot.log("It's our first run and we're all ready here!"); @@ -270,7 +269,7 @@ public boolean goodLootOnGround(){ } public Rs2NpcModel revenentKnight() { - return Rs2Npc.getNpc(7939); + return Microbot.getRs2NpcCache().query().withId(7939).nearest(); } public void kiteTheKnight(){ @@ -295,7 +294,7 @@ public void kiteTheKnight(){ } else { Microbot.log("We need to click the rev."); if(revenentKnight()!=null && revenentKnight().getWorldLocation().distanceTo(jammedTile)<=2) { - if (Rs2Npc.interact(revenentKnight(), "Attack")) { + if (revenentKnight().click("Attack")) { Microbot.log("We attacked the knight"); return; } @@ -313,7 +312,7 @@ public void kiteTheKnight(){ sleepUntil(() -> Rs2Player.isMoving(), Rs2Random.between(1000, 3000)); sleepUntil(() -> !Rs2Player.isMoving(), Rs2Random.between(2000, 3000)); } - if (Rs2Npc.interact(revenentKnight(), "Attack")) { + if (revenentKnight().click("Attack")) { Microbot.log("We attacked the knight"); return; } @@ -350,7 +349,7 @@ public void kiteTheKnight(){ if(playerCheck()){return;} if(revenentKnight() == null) return; - if(Rs2Npc.interact(revenentKnight(), "Attack")){ + if(revenentKnight().click("Attack")){ Microbot.log("We attacked the knight"); sleepUntil(()-> Rs2Player.isMoving(), Rs2Random.between(1000,3000)); sleepUntil(()-> !Rs2Player.isMoving(), Rs2Random.between(2000,3000)); @@ -419,7 +418,7 @@ public void kiteTheKnight(){ } if(Rs2Player.getWorldLocation().equals(fifthTile)) { - if (Rs2Npc.interact(revenentKnight(), "Attack")) { + if (revenentKnight().click("Attack")) { Microbot.log("We attacked the knight"); Microbot.log("Rev should be locked"); } @@ -501,7 +500,7 @@ public void WalkToRevs(){ } else { if(!Rs2Dialogue.isInDialogue()){ Microbot.log("At the cave, clicking."); - if(Microbot.getRs2TileObjectCache().query().withId(31555).nearest().click("Enter")){ + if(Microbot.getRs2TileObjectCache().query().interact(31555, "Enter")){ sleepUntil(()-> Rs2Dialogue.isInDialogue(), generateRandomNumber(1000,3000)); } } @@ -752,7 +751,7 @@ public void enablePrayer(){ } public void fightrev(revKillerConfig config){ - Rs2NpcModel Rev = Rs2Npc.getNpc(config.selectedRev().getName()); + Rs2NpcModel Rev = Microbot.getRs2NpcCache().query().withName(config.selectedRev().getName()).nearest(); if(Rev!=null){ @@ -765,7 +764,7 @@ public void fightrev(revKillerConfig config){ Microbot.log("Attacking Rev"); - if (Rs2Npc.interact(Rev, "Attack")) { + if (Rev.click("Attack")) { sleepUntil(() -> Rev.isDead() || !Rs2Player.isInCombat() || isItTimeToGo() || Rs2Player.getHealthPercentage() <= generateRandomNumber(70, 80), generateRandomNumber(60000, 120000)); hoppedWorld=false; } @@ -774,7 +773,7 @@ public void fightrev(revKillerConfig config){ if(Rev.isInteracting()) { if(hoppedWorld) { Microbot.log("Rev is attacking us attacking back."); - if (Rs2Npc.interact(Rev, "Attack")) { + if (Rev.click("Attack")) { hoppedWorld=false; sleepUntil(() -> Rev.isDead() || !Rs2Player.isInCombat() || isItTimeToGo() || Rs2Player.getHealthPercentage() <= generateRandomNumber(70, 80), generateRandomNumber(60000, 120000)); } @@ -1002,8 +1001,7 @@ public void loot(){ } } if(!Rs2Inventory.isFull()){ - if(Microbot.getRs2TileItemCache().query().withId(theItem.getId()).nearest() != null){ - Microbot.getRs2TileItemCache().query().withId(theItem.getId()).nearest().click("Take"); + if(Microbot.getRs2TileItemCache().query().withId(theItem.getId()).interact("Take")){ Rs2Inventory.waitForInventoryChanges(Rs2Random.between(4000,6000)); } } @@ -1019,8 +1017,7 @@ public void loot(){ } } if(!Rs2Inventory.isFull()){ - if(Microbot.getRs2TileItemCache().query().withId(theItem.getId()).nearest() != null){ - Microbot.getRs2TileItemCache().query().withId(theItem.getId()).nearest().click("Take"); + if(Microbot.getRs2TileItemCache().query().withId(theItem.getId()).interact("Take")){ Rs2Inventory.waitForInventoryChanges(Rs2Random.between(4000,6000)); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EggEvent.java b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EggEvent.java index e06ae5298b..b35c04674d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EggEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EggEvent.java @@ -7,10 +7,7 @@ import net.runelite.client.plugins.microbot.BlockingEventPriority; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -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.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.woodcutting.AutoWoodcuttingPlugin; @@ -34,10 +31,10 @@ public boolean validate() { try{ if (plugin == null || !Microbot.isPluginEnabled(plugin)) return false; if (Microbot.getClient() == null || !Microbot.isLoggedIn()) return false; - var forester = Rs2Npc - .getNpcs(NpcID.GATHERING_EVENT_PHEASANT_FORESTER) - .min(Comparator.comparingInt(Rs2NpcModel::getDistanceFromPlayer));; - return forester.isPresent(); + var forester = Microbot.getRs2NpcCache().query() + .withId(NpcID.GATHERING_EVENT_PHEASANT_FORESTER) + .nearest(); + return forester != null; } catch (Exception e) { log.error("EggEvent: Exception in validate method", e); return false; @@ -48,12 +45,12 @@ public boolean validate() { public boolean execute() { Microbot.log("EggEvent: Executing Egg event"); - var forester = Rs2Npc - .getNpcs(NpcID.GATHERING_EVENT_PHEASANT_FORESTER) - .min(Comparator.comparingInt(Rs2NpcModel::getDistanceFromPlayer));; - if (forester.isEmpty()) { + var forester = Microbot.getRs2NpcCache().query() + .withId(NpcID.GATHERING_EVENT_PHEASANT_FORESTER) + .nearest(); + if (forester == null) { Microbot.log("EggEvent: Forester not found, cannot proceed with egg event."); - return true; // If the forester is not found, we cannot proceed with the event + return true; } plugin.currentForestryEvent = ForestryEvents.PHEASANT; @@ -75,15 +72,15 @@ public boolean execute() { // If we have an egg, interact with the forester if (Rs2Inventory.contains("Pheasant egg")) { Microbot.log("EggEvent: Interacting with the forester to give the egg."); - Rs2Npc.interact(forester.get(), "Talk-to"); + forester.click("Talk-to"); sleepUntil(Rs2Dialogue::isInDialogue, 5000); while (Rs2Dialogue.isInDialogue()) Rs2Dialogue.clickContinue(); continue; } // If we don't have an egg, interact with the pheasant nest - var nests = Rs2GameObject.getGameObjects((gameObject) -> gameObject.getId() == ObjectID.GATHERING_EVENT_PHEASANT_NEST02); - var pheasants = Rs2Npc.getNpcs(NpcID.GATHERING_EVENT_PHEASANT).collect(Collectors.toList()); + var nests = Microbot.getRs2TileObjectCache().query().where(gameObject -> gameObject.getId() == ObjectID.GATHERING_EVENT_PHEASANT_NEST02).toList(); + var pheasants = Microbot.getRs2NpcCache().query().withId(NpcID.GATHERING_EVENT_PHEASANT).toList(); if (nests.isEmpty() || pheasants.isEmpty()) { Microbot.log("EggEvent: No pheasant nests found, cannot proceed with egg event."); @@ -91,7 +88,6 @@ public boolean execute() { } // find nest without pheasants var emptyNests = nests.stream() - .filter(Rs2GameObject::isReachable) .filter(nest -> pheasants.stream() .noneMatch(pheasant -> pheasant.getWorldLocation() == nest.getWorldLocation())) .collect(Collectors.toList()); @@ -101,7 +97,7 @@ public boolean execute() { .min(Comparator.comparingInt(o -> o.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()))) .orElse(null); - var interact = Rs2GameObject.interact(closestNest); + var interact = closestNest != null && closestNest.click(); if (!interact) { Microbot.log("EggEvent: Failed to interact with the pheasant nest."); Microbot.log("EggEvent: Closest nest is null? " + (closestNest == null)); diff --git a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EntlingsEvent.java b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EntlingsEvent.java index faf46c5d17..dcb64ae7ae 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EntlingsEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/EntlingsEvent.java @@ -5,15 +5,13 @@ import net.runelite.client.plugins.microbot.BlockingEvent; import net.runelite.client.plugins.microbot.BlockingEventPriority; import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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 net.runelite.client.plugins.microbot.woodcutting.AutoWoodcuttingPlugin; import net.runelite.client.plugins.microbot.woodcutting.enums.ForestryEvents; import java.util.Comparator; -import java.util.stream.Collectors; @Slf4j public class EntlingsEvent implements BlockingEvent { @@ -27,8 +25,7 @@ public boolean validate() { try{ if (plugin == null || !Microbot.isPluginEnabled(plugin)) return false; if (Microbot.getClient() == null || !Microbot.isLoggedIn()) return false; - var entlings = Rs2Npc.getNpcs(npc -> npc.getId() == NpcID.GATHERING_EVENT_ENTLINGS_NPC_01) - .collect(Collectors.toList()); + var entlings = Microbot.getRs2NpcCache().query().where(npc -> npc.getId() == NpcID.GATHERING_EVENT_ENTLINGS_NPC_01).toList(); return !entlings.isEmpty(); } catch (Exception e) { log.error("EntlingsEvent: Exception in validate method", e); @@ -49,10 +46,12 @@ public boolean execute() { } while (this.validate()) { - var entlings = Rs2Npc.getNpcs(npc -> npc.getId() == NpcID.GATHERING_EVENT_ENTLINGS_NPC_01) - .sorted(Comparator.comparingInt(e -> + var entlings = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getId() == NpcID.GATHERING_EVENT_ENTLINGS_NPC_01) + .toList(); + entlings.sort(Comparator.comparingInt(e -> e.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) - )).collect(Collectors.toList()); + )); for (Rs2NpcModel entling : entlings) { String request = entling.getOverheadText(); @@ -75,7 +74,7 @@ public boolean execute() { } Microbot.log("EntlingsEvent: Interacting with entling: with action: " + action); - Rs2Npc.interact(entling, action); + entling.click(action); Rs2Player.waitForAnimation(1000); // Wait for the pruning animation to finish } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FlowersEvent.java b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FlowersEvent.java index e8c1ab561d..7365288254 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FlowersEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FlowersEvent.java @@ -5,14 +5,11 @@ import net.runelite.client.plugins.microbot.BlockingEvent; import net.runelite.client.plugins.microbot.BlockingEventPriority; import net.runelite.client.plugins.microbot.Microbot; -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.walker.Rs2Walker; import net.runelite.client.plugins.microbot.woodcutting.AutoWoodcuttingPlugin; import net.runelite.client.plugins.microbot.woodcutting.enums.ForestryEvents; -import java.util.stream.Collectors; - import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; @Slf4j public class FlowersEvent implements BlockingEvent { @@ -27,9 +24,9 @@ public boolean validate() { try{ if (plugin == null || !Microbot.isPluginEnabled(plugin)) return false; if (Microbot.getClient() == null || !Microbot.isLoggedIn()) return false; - var flowers = Rs2Npc.getNpcs(npc -> - npc.getName() != null && isFloweringBush(npc.getId()) - ).collect(Collectors.toList()); + var flowers = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getName() != null && isFloweringBush(npc.getId())) + .toList(); return !flowers.isEmpty(); } catch (Exception e) { log.error("FlowersEvent: Exception in validate method", e); @@ -49,9 +46,9 @@ public boolean execute() { } log.info("FlowersEvent: Executing Flowers event"); while (this.validate()) { - var flowers = Rs2Npc.getNpcs(npc -> - npc.getName() != null && isFloweringBush(npc.getId()) - ).collect(Collectors.toList()); + var flowers = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getName() != null && isFloweringBush(npc.getId())) + .toList(); if (flowers.isEmpty()) { break; @@ -69,7 +66,7 @@ public boolean execute() { continue; } - if (Rs2Npc.interact(availableFlower, "Tend-to")) { + if (availableFlower.click("Tend-to")) { Rs2Player.waitForAnimation(); sleepUntil(() -> !Rs2Player.isInteracting(), 8000); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FoxEvent.java b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FoxEvent.java index dad018c756..29e874e51f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FoxEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/FoxEvent.java @@ -5,7 +5,6 @@ import net.runelite.client.plugins.microbot.BlockingEvent; import net.runelite.client.plugins.microbot.BlockingEventPriority; import net.runelite.client.plugins.microbot.Microbot; -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.walker.Rs2Walker; import net.runelite.client.plugins.microbot.woodcutting.AutoWoodcuttingPlugin; @@ -25,8 +24,8 @@ public boolean validate() { try{ if (plugin == null || !Microbot.isPluginEnabled(plugin)) return false; if (Microbot.getClient() == null || !Microbot.isLoggedIn()) return false; - var outDoorFox = Rs2Npc.getNpc(NpcID.GATHERING_EVENT_POACHERS_FOX_OUTDOORS); - var indoorFox = Rs2Npc.getNpc(NpcID.GATHERING_EVENT_POACHERS_FOX_INDOORS); + var outDoorFox = Microbot.getRs2NpcCache().query().withId(NpcID.GATHERING_EVENT_POACHERS_FOX_OUTDOORS).nearest(); + var indoorFox = Microbot.getRs2NpcCache().query().withId(NpcID.GATHERING_EVENT_POACHERS_FOX_INDOORS).nearest(); return outDoorFox != null || indoorFox != null; } catch (Exception e) { log.error("FoxEvent: Exception in validate method", e); @@ -47,13 +46,13 @@ public boolean execute() { } while (this.validate()) { - var trap = Rs2Npc.getNpc(NpcID.GATHERING_EVENT_POACHERS_TRAP); + var trap = Microbot.getRs2NpcCache().query().withId(NpcID.GATHERING_EVENT_POACHERS_TRAP).nearest(); if (trap == null) { continue; // If the trap is not found, we cannot proceed with the event } Microbot.log("FoxEvent: Interacting with the trap to disarm it.", Level.INFO); // Interact with the trap if it exists - Rs2Npc.interact(trap, "Disarm"); + trap.click("Disarm"); Rs2Player.waitForAnimation(1000); } Microbot.log("FoxEvent: Finished executing the Fox event.", Level.INFO); diff --git a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/HivesEvent.java b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/HivesEvent.java index 78dfd756ac..90845cc50f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/HivesEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/HivesEvent.java @@ -5,10 +5,9 @@ import net.runelite.client.plugins.microbot.BlockingEvent; import net.runelite.client.plugins.microbot.BlockingEventPriority; import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; 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.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -20,7 +19,6 @@ import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; @Slf4j @@ -40,9 +38,11 @@ public boolean validate() { try{ if (plugin == null || !Microbot.isPluginEnabled(plugin)) return false; if (Microbot.getClient() == null || !Microbot.isLoggedIn()) return false; - var beehives = Rs2Npc.getNpcs(x -> x.getId() == net.runelite.api.gameval.NpcID.GATHERING_EVENT_BEES_BEEBOX_1 || x.getId() == net.runelite.api.gameval.NpcID.GATHERING_EVENT_BEES_BEEBOX_2); + var beehives = Microbot.getRs2NpcCache().query() + .where(x -> x.getId() == net.runelite.api.gameval.NpcID.GATHERING_EVENT_BEES_BEEBOX_1 || x.getId() == net.runelite.api.gameval.NpcID.GATHERING_EVENT_BEES_BEEBOX_2) + .toList(); WoodcuttingTree tree = plugin.getSelectedTree(); - return beehives.findAny().isPresent() && tree != null && Rs2Inventory.count(tree.getLogID()) > 1; + return !beehives.isEmpty() && tree != null && Rs2Inventory.count(tree.getLogID()) > 1; } catch (Exception e) { log.error("HivesEvent: Exception in validate method", e); return false; @@ -68,11 +68,11 @@ public boolean execute() { } // find available beehives, excluding ones we've already completed - List availableBeehives = Rs2Npc.getNpcs(x -> - (x.getId() == net.runelite.api.gameval.NpcID.GATHERING_EVENT_BEES_BEEBOX_1 || - x.getId() == net.runelite.api.gameval.NpcID.GATHERING_EVENT_BEES_BEEBOX_2) && - !completedBeehives.contains(x.getIndex())) - .collect(Collectors.toList()); + List availableBeehives = Microbot.getRs2NpcCache().query() + .where(x -> (x.getId() == net.runelite.api.gameval.NpcID.GATHERING_EVENT_BEES_BEEBOX_1 || + x.getId() == net.runelite.api.gameval.NpcID.GATHERING_EVENT_BEES_BEEBOX_2) && + !completedBeehives.contains(x.getIndex())) + .toList(); if (availableBeehives.isEmpty()) { log.info("No more available beehives to work on"); @@ -105,7 +105,7 @@ public boolean execute() { } // check if beehive still exists (might have been completed by others or disappeared) - if (!Rs2Npc.getNpcs(x -> x.getIndex() == targetBeehive.getIndex()).findAny().isPresent()) { + if (Microbot.getRs2NpcCache().query().where(x -> x.getIndex() == targetBeehive.getIndex()).count() == 0) { log.info("Beehive {} completed or disappeared", targetBeehive.getIndex()); completedBeehives.add(targetBeehive.getIndex()); currentBeehive = null; @@ -121,7 +121,7 @@ public boolean execute() { } log.info("Building beehive {} (logs: {})", targetBeehive.getIndex(), currentLogCount); - if (Rs2Npc.interact(targetBeehive, "Build")) { + if (targetBeehive.click("Build")) { // wait for interaction to start sleepUntil(() -> Rs2Player.isInteracting() || Rs2Player.isAnimating(), 3000); @@ -136,7 +136,7 @@ public boolean execute() { } // check if this beehive is now completed (disappeared) - if (!Rs2Npc.getNpcs(x -> x.getIndex() == targetBeehive.getIndex()).findAny().isPresent()) { + if (Microbot.getRs2NpcCache().query().where(x -> x.getIndex() == targetBeehive.getIndex()).count() == 0) { log.info("Beehive {} completed successfully", targetBeehive.getIndex()); completedBeehives.add(targetBeehive.getIndex()); currentBeehive = null; diff --git a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/LeprechaunEvent.java b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/LeprechaunEvent.java index 9680f4d775..2ccc5455bb 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/LeprechaunEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/LeprechaunEvent.java @@ -7,18 +7,12 @@ import net.runelite.client.plugins.microbot.BlockingEventPriority; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.Global; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.woodcutting.AutoWoodcuttingPlugin; import net.runelite.client.plugins.microbot.woodcutting.enums.ForestryEvents; import org.slf4j.event.Level; -import java.util.Comparator; -import java.util.Optional; - import static net.runelite.client.plugins.microbot.util.Global.sleepGaussian; @Slf4j public class LeprechaunEvent implements BlockingEvent { @@ -34,10 +28,10 @@ public boolean validate() { try{ if (plugin == null || !Microbot.isPluginEnabled(plugin)) return false; if (Microbot.getClient() == null || !Microbot.isLoggedIn()) return false; - Optional leprechaun = Rs2Npc - .getNpcs(NpcID.GATHERING_EVENT_WOODCUTTING_LEPRECHAUN) - .min(Comparator.comparingInt(Rs2NpcModel::getDistanceFromPlayer));; - return leprechaun.isPresent(); + var leprechaun = Microbot.getRs2NpcCache().query() + .withId(NpcID.GATHERING_EVENT_WOODCUTTING_LEPRECHAUN) + .nearest(); + return leprechaun != null; } catch (Exception e) { log.error("LeprechaunEvent: Exception in validate method", e); return false; @@ -51,7 +45,7 @@ public boolean execute() { Rs2Walker.setTarget(null); // stop walking, stop moving to bank for example while (this.validate()) { log.info("LeprechaunEvent: Leprechaun event still valid, continuing execution get opbject"); - var endOfRainbow = Rs2GameObject.getGameObject(ObjectID.GATHERING_EVENT_WOODCUTTING_LEPRECHAUN_RAINBOW); + var endOfRainbow = Microbot.getRs2TileObjectCache().query().withId(ObjectID.GATHERING_EVENT_WOODCUTTING_LEPRECHAUN_RAINBOW).nearest(); if (endOfRainbow == null) { log.warn("LeprechaunEvent: End of the rainbow not found, retrying..."); sleepGaussian(900, 300); diff --git a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/RitualEvent.java b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/RitualEvent.java index 1fa66c6022..1ebc0e6f93 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/RitualEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/RitualEvent.java @@ -5,7 +5,6 @@ import net.runelite.client.plugins.microbot.BlockingEvent; import net.runelite.client.plugins.microbot.BlockingEventPriority; import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -13,9 +12,7 @@ import net.runelite.client.plugins.microbot.woodcutting.enums.ForestryEvents; import org.slf4j.event.Level; -import java.util.Comparator; import java.util.List; -import java.util.Optional; import static net.runelite.client.plugins.microbot.util.Global.sleepGaussian; import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; @@ -34,11 +31,10 @@ public boolean validate() { try{ if (plugin == null || !Microbot.isPluginEnabled(plugin)) return false; if (Microbot.getClient() == null || !Microbot.isLoggedIn()) return false; - Optional dryadCache = Rs2Npc - .getNpcs(NpcID.GATHERING_EVENT_ENCHANTED_RITUAL_DRYAD) - .min(Comparator.comparingInt(Rs2NpcModel::getDistanceFromPlayer));; - //var dryad = Rs2Npc.getNpc(NpcID.GATHERING_EVENT_ENCHANTED_RITUAL_DRYAD); - return dryadCache.isPresent(); + var dryad = Microbot.getRs2NpcCache().query() + .withId(NpcID.GATHERING_EVENT_ENCHANTED_RITUAL_DRYAD) + .nearest(); + return dryad != null; } catch (Exception e) { log.error("RitualEvent: Exception in validate method", e); return false; diff --git a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/StrugglingSaplingEvent.java b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/StrugglingSaplingEvent.java index 28f029a497..8eac0b71b6 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/StrugglingSaplingEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/StrugglingSaplingEvent.java @@ -1,11 +1,11 @@ package net.runelite.client.plugins.microbot.woodcutting.Forestry; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.GameObject; import net.runelite.api.gameval.ItemID; import net.runelite.client.plugins.microbot.BlockingEvent; import net.runelite.client.plugins.microbot.BlockingEventPriority; import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; 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; @@ -43,11 +43,12 @@ public boolean validate() { try{ if (plugin == null || !Microbot.isPluginEnabled(plugin)) return false; if (Microbot.getClient() == null || !Microbot.isLoggedIn()) return false; - var strugglingSaplings = Rs2GameObject.getGameObjects(Rs2GameObject.nameMatches("Struggling sapling", false)); - if (strugglingSaplings == null) return false; + var strugglingSaplings = Microbot.getRs2TileObjectCache().query() + .withName("Struggling sapling") + .toList(); if (strugglingSaplings.isEmpty()) return false; return strugglingSaplings.stream().anyMatch(obj -> - Rs2GameObject.hasAction(Rs2GameObject.convertToObjectComposition(obj), "Add-mulch") && + Rs2GameObject.hasAction(obj.getObjectComposition(), "Add-mulch") && obj.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) <= AutoWoodcuttingScript.FORESTRY_DISTANCE ); } catch (Exception e) { @@ -62,19 +63,22 @@ public boolean execute() { Microbot.log("StrugglingSaplingEvent: Executing Struggling Sapling event"); plugin.currentForestryEvent = ForestryEvents.STRUGGLING_SAPLING; // Find the struggling sapling - var sapling = Rs2GameObject.getGameObjects(Rs2GameObject.nameMatches("Struggling sapling", false)) + var sapling = Microbot.getRs2TileObjectCache().query() + .withName("Struggling sapling") + .toList() .stream() .filter(obj -> - Rs2GameObject.hasAction(Rs2GameObject.convertToObjectComposition(obj), "Add-mulch") && + Rs2GameObject.hasAction(obj.getObjectComposition(), "Add-mulch") && obj.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) <= AutoWoodcuttingScript.FORESTRY_DISTANCE ) .findFirst() .orElse(null); - // Find all available leaf ingredients - var ingredients = Rs2GameObject.getGameObjects(gameObject -> ingredientIds.contains(gameObject.getId())) + var ingredients = Microbot.getRs2TileObjectCache().query() + .where(gameObject -> ingredientIds.contains(gameObject.getId())) + .toList() .stream() - .filter(obj -> Rs2GameObject.hasAction(Rs2GameObject.convertToObjectComposition(obj), "Collect")) + .filter(obj -> Rs2GameObject.hasAction(obj.getObjectComposition(), "Collect")) .collect(Collectors.toList()); if (ingredients.isEmpty()) { @@ -97,7 +101,7 @@ public boolean execute() { // If we have mulch stage 3 in inventory, add them to the sapling if (Rs2Inventory.contains(ItemID.GATHERING_EVENT_SAPLING_MULCH_STAGE3)) { Microbot.log("StrugglingSaplingEvent: Adding mulch to the struggling sapling."); - Rs2GameObject.interact(sapling, "Add-mulch"); + sapling.click("Add-mulch"); Rs2Player.waitForAnimation(); continue; } @@ -116,11 +120,10 @@ public boolean execute() { if (correctIngredient != null) { // Look for matching ingredient in our available ingredients - for (GameObject ingredient : ingredients) { + for (Rs2TileObjectModel ingredient : ingredients) { if (ingredient.getId() == correctIngredient.getId()) { - // Collect this ingredient as it's known to be correct Microbot.log("StrugglingSaplingEvent: Collecting known correct ingredient: " + ingredient.getWorldLocation()); - Rs2GameObject.interact(ingredient, "Collect"); + ingredient.click("Collect"); Rs2Player.waitForAnimation(); } } @@ -144,7 +147,7 @@ public boolean execute() { Microbot.log("StrugglingSaplingEvent: No known correct ingredient, collecting a random one."); var randomIngredient = availableIngredients.get((int) (Math.random() * availableIngredients.size())); Microbot.log("StrugglingSaplingEvent: Collecting random ingredient: " + randomIngredient.getWorldLocation()); - Rs2GameObject.interact(randomIngredient, "Collect"); + randomIngredient.click("Collect"); triedIngredients.add(randomIngredient.getId()); Rs2Player.waitForAnimation(); } From e074890e426e328db32529c6a46da4965705193b Mon Sep 17 00:00:00 2001 From: chsami Date: Thu, 9 Apr 2026 14:48:24 +0200 Subject: [PATCH 22/95] refactor: migrate combat plugins to new query API Migrate Rs2Npc, Rs2GameObject, and Rs2GroundItem calls to cache-based query API across 25 combat plugins: AnimatedArmour, BlueDragons, DemonicGorillaKiller, EnsouledHeadSlayer, GemCrabKiller, GiantMole, Jad, HunterKabbits, MmCaves, Nmz, RoyalTitans, SandCrabs, Scurrius, ShadesKiller, Slayer, SulphurNagua, TormentedDemons, TzhaarVenatorBow, Virewatch, and AmmoniteCrabs. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../AmmoniteCrabs/AmmoniteCrabScript.java | 24 ++- .../DemonicGorillaScript.java | 48 +++-- .../EnsouledHeadSlayerScript.java | 24 ++- .../GemCrabKiller/GemCrabKillerScript.java | 35 ++-- .../RoyalTitans/RoyalTitansLooterScript.java | 20 +- .../RoyalTitans/RoyalTitansScript.java | 100 +++++----- .../TzhaarVenatorBowScript.java | 29 +-- .../animatedarmour/AnimatedArmourScript.java | 7 +- .../bluedragons/BlueDragonsOverlay.java | 9 +- .../bluedragons/BlueDragonsScript.java | 13 +- .../microbot/giantmole/GiantMoleScript.java | 26 +-- .../hunterKabbits/HunterKabbitsScript.java | 26 ++- .../plugins/microbot/jad/JadScript.java | 31 ++-- .../microbot/mmcaves/MmCavesScript.java | 32 ++-- .../plugins/microbot/nmz/NmzScript.java | 26 ++- .../microbot/sandcrabs/SandCrabScript.java | 15 +- .../microbot/scurrius/ScurriusScript.java | 25 ++- .../shadeskiller/ShadesKillerScript.java | 15 +- .../plugins/microbot/slayer/SlayerScript.java | 171 +++++++++--------- .../slayer/combat/SlayerFlickerScript.java | 8 +- .../microbot/slayer/combat/SlayerMonster.java | 2 +- .../SulphurNaguaScript.java | 20 +- .../tormenteddemons/TormentedDemonScript.java | 33 ++-- .../virewatch/PVirewatchKillerOverlay.java | 6 +- .../microbot/virewatch/PVirewatchScript.java | 9 +- 25 files changed, 371 insertions(+), 383 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabScript.java b/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabScript.java index 3a78e58abc..31a7392ef8 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabScript.java @@ -1,7 +1,6 @@ package net.runelite.client.plugins.microbot.AmmoniteCrabs; import net.runelite.api.GameState; -import net.runelite.api.NPC; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; @@ -13,8 +12,7 @@ import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.player.Rs2PlayerModel; import net.runelite.client.plugins.microbot.util.security.Login; @@ -218,12 +216,13 @@ private void walkBack(AmmoniteCrabConfig config) { } } - // Attack all scattered crabs in the area private void attackScatteredCrabs(AmmoniteCrabConfig config) { - var ammoniteCrabs = Rs2Npc.getNpcs("Ammonite Crab", true).filter(x -> x != null && !x.isDead() && x.getWorldLocation().distanceTo(config.crabLocation().getFightLocation()) > 1 && x.getWorldLocation().distanceTo(config.crabLocation().getFightLocation()) < 15).collect(Collectors.toList()); + var ammoniteCrabs = Microbot.getRs2NpcCache().query().withName("Ammonite Crab") + .where(x -> x.getNpc() != null && !x.getNpc().isDead() && x.getWorldLocation().distanceTo(config.crabLocation().getFightLocation()) > 1 && x.getWorldLocation().distanceTo(config.crabLocation().getFightLocation()) < 15) + .toList(); for (Rs2NpcModel ammoniteCrab : ammoniteCrabs) { - if (ammoniteCrab != null && !ammoniteCrab.isDead()) { - Rs2Npc.attack(ammoniteCrab); + if (ammoniteCrab != null && !ammoniteCrab.getNpc().isDead()) { + ammoniteCrab.click("Attack"); Rs2Player.waitForAnimation(1600); sleep(1600, 2400); } @@ -237,18 +236,17 @@ private void attackScatteredCrabs(AmmoniteCrabConfig config) { * @return true if npc is aggressive */ private boolean isNpcAggressive() { - List npcs = Rs2Npc.getNpcs("Fossil Rock", true).collect(Collectors.toList()); + List npcs = Microbot.getRs2NpcCache().query().withName("Fossil Rock").toList(); if (npcs.isEmpty()) { return true; } - for (NPC ammoniteRock : npcs) { - //ignore ammonitecrabs far away from the player - if (!ammoniteRock.getWorldArea().isInMeleeDistance(Microbot.getClient().getLocalPlayer().getWorldArea())) + for (Rs2NpcModel ammoniteRock : npcs) { + if (!ammoniteRock.getNpc().getWorldArea().isInMeleeDistance(Microbot.getClient().getLocalPlayer().getWorldArea())) continue; - return false; //found a fossil rock crab near the player + return false; } - return true; //did not find any fossil rocks near the player + return true; } private void resetAggro(AmmoniteCrabConfig config) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/DemonicGorillaKiller/DemonicGorillaScript.java b/src/main/java/net/runelite/client/plugins/microbot/DemonicGorillaKiller/DemonicGorillaScript.java index 55b111c0b7..55b20e289d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/DemonicGorillaKiller/DemonicGorillaScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/DemonicGorillaKiller/DemonicGorillaScript.java @@ -14,12 +14,10 @@ import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.misc.Rs2Potion; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; -import net.runelite.client.plugins.microbot.util.reflection.Rs2Reflection; import net.runelite.client.plugins.microbot.util.tile.Rs2Tile; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -29,7 +27,6 @@ import java.util.List; import java.util.Objects; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; import static net.runelite.client.plugins.microbot.util.antiban.enums.ActivityIntensity.EXTREME; @@ -218,7 +215,7 @@ private void handleBanking(DemonicGorillaConfig config) { } private void handleFighting(DemonicGorillaConfig config) { - if (currentTarget == null || currentTarget.isDead()) { + if (currentTarget == null || currentTarget.getNpc().isDead()) { npcAnimationCount = 0; logOnceToChat("Target is null or dead"); handleNewTarget(config); @@ -246,7 +243,7 @@ private void handleTargetSelection() { // Ensure currently selected target is also who we are attacking if (currentTarget != null) { var tempTarget = getTarget(true); - if ((tempTarget != null && tempTarget.getIndex() != currentTarget.getIndex()) || currentTarget.isDead()) { + if ((tempTarget != null && tempTarget.getIndex() != currentTarget.getIndex()) || currentTarget.getNpc().isDead()) { logOnceToChat("Invalid target was selected, switching to correct enemy"); currentTarget = tempTarget; } @@ -258,14 +255,13 @@ private void handleTargetSelection() { logOnceToChat("Out of combat for 6 seconds, forcing new target"); currentTarget = getTarget(true); if (currentTarget != null) { - Rs2Npc.attack(currentTarget); + currentTarget.click("Attack"); } else { logOnceToChat("Unable to force new target, walking to gorillas and trying again"); Rs2Walker.walkTo(GORILLA_LOCATION); currentTarget = getTarget(true); - // Last attempt, just attack a gorilla if (currentTarget == null) { - Rs2Npc.attack("Demonic gorilla"); + Microbot.getRs2NpcCache().query().withName("Demonic gorilla").interact("Attack"); } } outOfCombatTime = null; // Reset after forcing new target @@ -294,7 +290,7 @@ private void handleNewTarget(DemonicGorillaConfig config) { Rs2Player.eatAt(80); Rs2Player.drinkPrayerPotionAt(config.minEatPercent()); lootAttempted = true; - if (currentTarget != null && currentTarget.isDead()) { + if (currentTarget != null && currentTarget.getNpc().isDead()) { killCount++; currentTripKillCount++; } @@ -323,16 +319,16 @@ private void handleNewTarget(DemonicGorillaConfig config) { } private void attackGorilla(DemonicGorillaConfig config) { - if (currentTarget != null && !currentTarget.isDead()) { + if (currentTarget != null && !currentTarget.getNpc().isDead()) { Rs2Player.eatAt(config.minEatPercent()); Rs2Player.drinkPrayerPotionAt(config.minPrayerPercent()); if (currentTarget != null) { if (!Rs2Player.isAnimating(1600)) { - if (currentTarget != null && !currentTarget.isDead()) { + if (currentTarget != null && !currentTarget.getNpc().isDead()) { if (config.enableAutoSpecialAttacks()) { Rs2Combat.setSpecState(true, 500); } - var didWeAttack = Rs2Npc.attack(currentTarget); + var didWeAttack = currentTarget.click("Attack"); if (didWeAttack) { failedAttacks = 0; } else { @@ -354,8 +350,8 @@ private void handleDemonicGorillaAttacks(DemonicGorillaConfig config) { Rs2PrayerEnum newDefensivePrayer = null; boolean dodgedRock = false; - if (currentTarget != null && !currentTarget.isDead()) { - int currentAnimation = currentTarget.getAnimation(); + if (currentTarget != null && !currentTarget.getNpc().isDead()) { + int currentAnimation = currentTarget.getNpc().getAnimation(); var location = currentTarget.getWorldLocation(); // Handle prayer switching if (currentAnimation == DEMONIC_GORILLA_MAGIC_ATTACK) { @@ -405,7 +401,7 @@ private void handleDemonicGorillaAttacks(DemonicGorillaConfig config) { } if (!dodgedRock) { if ((currentGear == ArmorEquiped.RANGED || currentGear == ArmorEquiped.MAGIC) && (currentAnimation != DEMONIC_GORILLA_MELEE_ATTACK && currentAnimation != -1 && currentDefensivePrayer != Rs2PrayerEnum.PROTECT_MELEE)) { - var isMeleeDist = currentTarget.getWorldArea().isInMeleeDistance(Microbot.getClient().getLocalPlayer().getWorldArea()); + var isMeleeDist = currentTarget.getNpc().getWorldArea().isInMeleeDistance(Microbot.getClient().getLocalPlayer().getWorldArea()); if (isMeleeDist) { moveAwayFromTarget(); } @@ -502,36 +498,38 @@ public Rs2NpcModel getTarget() { } public Rs2NpcModel getTarget(boolean force) { - if (currentTarget != null && !currentTarget.isDead() && !force) { + if (currentTarget != null && !currentTarget.getNpc().isDead() && !force) { return currentTarget; } var interacting = Rs2Player.getInteracting(); if (interacting != null) { if (Objects.equals(interacting.getName(), "Demonic gorilla")) { - return (Rs2NpcModel) interacting; + var match = Microbot.getRs2NpcCache().query().withName("Demonic gorilla") + .where(n -> n.isInteractingWithPlayer()).nearest(); + if (match != null) return match; } } var playerLocation = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); - var alreadyInteractingNpcs = Rs2Npc.getNpcsForPlayer("Demonic gorilla"); + var alreadyInteractingNpcs = Microbot.getRs2NpcCache().query().withName("Demonic gorilla") + .where(n -> n.isInteractingWithPlayer()).toList(); if (!alreadyInteractingNpcs.isEmpty()) { return alreadyInteractingNpcs.stream() .min(Comparator.comparingInt(npc -> npc.getWorldLocation().distanceTo(playerLocation))).get(); } - var demonicGorillaStream = Rs2Npc.getNpcs("Demonic gorilla"); - if (demonicGorillaStream == null) { + List demonicGorillas = Microbot.getRs2NpcCache().query().withName("Demonic gorilla").toList(); + if (demonicGorillas.isEmpty()) { logOnceToChat("No demonic gorilla found."); return null; } var player = Rs2Player.getLocalPlayer(); String playerName = player.getName(); - List demonicGorillas = demonicGorillaStream.collect(Collectors.toList()); for (Rs2NpcModel demonicGorilla : demonicGorillas) { if (demonicGorilla != null) { - var interactingTwo = demonicGorilla.getInteracting(); + var interactingTwo = demonicGorilla.getNpc().getInteracting(); String interactingName = interactingTwo != null ? interactingTwo.getName() : "None"; if (interactingTwo != null && Objects.equals(interactingName, playerName)) { return demonicGorilla; @@ -541,8 +539,8 @@ public Rs2NpcModel getTarget(boolean force) { logOnceToChat("Finding closest demonic gorilla."); return demonicGorillas.stream() - .filter(npc -> npc != null && !npc.isDead() && !npc.isInteracting()) - .min(Comparator.comparingInt(npc -> npc.getWorldLocation().distanceTo(playerLocation))).stream().findFirst() + .filter(npc -> npc != null && !npc.getNpc().isDead() && npc.getNpc().getInteracting() == null) + .min(Comparator.comparingInt(npc -> npc.getWorldLocation().distanceTo(playerLocation))) .orElse(null); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/EnsouledHeadSlayer/EnsouledHeadSlayerScript.java b/src/main/java/net/runelite/client/plugins/microbot/EnsouledHeadSlayer/EnsouledHeadSlayerScript.java index 89ed92b1a4..59d32f407c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/EnsouledHeadSlayer/EnsouledHeadSlayerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/EnsouledHeadSlayer/EnsouledHeadSlayerScript.java @@ -13,13 +13,11 @@ import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; -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.item.Rs2EnsouledHead; import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; import net.runelite.client.plugins.microbot.util.magic.Spell; 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.tabs.Rs2Tab; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -114,7 +112,7 @@ private void handleReanimatingAndKilling(EnsouledHeadSlayerConfig config) { */ if (Microbot.getVarbitValue(Varbits.SPELLBOOK) != 3) { Microbot.log("On wrong spellbook, switching to Arceuus..."); - Rs2Npc.interact("Tyss", "Spellbook"); + Microbot.getRs2NpcCache().query().withName("Tyss").interact("Spellbook"); } Rs2Combat.enableAutoRetialiate(); var ensouledHead = Rs2Inventory.count("ensouled"); @@ -138,9 +136,12 @@ private void handleReanimatingAndKilling(EnsouledHeadSlayerConfig config) { return; } - var enemy = Rs2Npc.getNpcsForPlayer("Reanimated", false).stream().filter(x -> !x.isDead() && - x.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) <= 2).collect(Collectors.toList()); - if (enemy.isEmpty() && Rs2Player.getInteracting() == null && Rs2GameObject.getGroundObject(ENSOULED_GROUND_GRAPHICS) == null) { + var enemy = Microbot.getRs2NpcCache().query() + .where(n -> n.getName() != null && n.getName().contains("Reanimated") && n.isInteractingWithPlayer()) + .toList().stream() + .filter(x -> !x.getNpc().isDead() && x.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) <= 2) + .collect(Collectors.toList()); + if (enemy.isEmpty() && Rs2Player.getInteracting() == null && Microbot.getRs2TileObjectCache().query().withId(ENSOULED_GROUND_GRAPHICS).nearest() == null) { var ensouledHeadItem = Rs2Inventory.get("ensouled"); var spell = Arrays.stream(Rs2EnsouledHead.values()) .filter(x -> x.hasRequirements() && Objects.equals(x.getName(), ensouledHeadItem.getName())) @@ -150,17 +151,14 @@ private void handleReanimatingAndKilling(EnsouledHeadSlayerConfig config) { Rs2Magic.cast(spell.getMagicSpell()); sleepUntil(() -> Rs2Tab.getCurrentTab() == InterfaceTab.INVENTORY, 1000); Rs2Inventory.interact(ensouledHeadItem); - // NPC animation getting cast - sleepUntil(() -> Rs2GameObject.getGroundObject(ENSOULED_GROUND_GRAPHICS) != null, 4000); - // NPC animation finished - NPC should be spawned - sleepUntil(() -> Rs2GameObject.getGroundObject(ENSOULED_GROUND_GRAPHICS) == null, 4000); - // Without this delay, we won't wait for the NPC to spawn before trying other actions + sleepUntil(() -> Microbot.getRs2TileObjectCache().query().withId(ENSOULED_GROUND_GRAPHICS).nearest() != null, 4000); + sleepUntil(() -> Microbot.getRs2TileObjectCache().query().withId(ENSOULED_GROUND_GRAPHICS).nearest() == null, 4000); sleep(3500, 4500); } } else { var animmatedEnemy = enemy.stream().findFirst().orElse(null); - if (animmatedEnemy != null && !animmatedEnemy.isDead()) { - Rs2Npc.attack(animmatedEnemy); + if (animmatedEnemy != null && !animmatedEnemy.getNpc().isDead()) { + animmatedEnemy.click("Attack"); } } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/GemCrabKiller/GemCrabKillerScript.java b/src/main/java/net/runelite/client/plugins/microbot/GemCrabKiller/GemCrabKillerScript.java index 6616d4704e..c9394e0410 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/GemCrabKiller/GemCrabKillerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/GemCrabKiller/GemCrabKillerScript.java @@ -1,19 +1,18 @@ package net.runelite.client.plugins.microbot.GemCrabKiller; -import net.runelite.api.GameObject; import net.runelite.api.ItemID; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; -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.npc.Rs2Npc; 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; @@ -124,7 +123,7 @@ private void handleWaiting(GemCrabKillerConfig config) { if (waitingTimeStart == null) { waitingTimeStart = Instant.now(); } - if (Rs2Npc.getNpc(CRAB_NPC_ID) != null) { + if (Microbot.getRs2NpcCache().query().withId(CRAB_NPC_ID).nearest() != null) { gemCrabKillerState = GemCrabKillerState.FIGHTING; waitingTimeStart = null; return; @@ -171,12 +170,12 @@ private void handleBanking(GemCrabKillerConfig config) { } private void handleFighting(GemCrabKillerConfig config) { - var npc = Rs2Npc.getNpc(CRAB_NPC_ID); - var deadNpc = Rs2Npc.getNpc(CRAB_NPC_DEAD_ID); + Rs2NpcModel npc = Microbot.getRs2NpcCache().query().withId(CRAB_NPC_ID).nearest(); + Rs2NpcModel deadNpc = Microbot.getRs2NpcCache().query().withId(CRAB_NPC_DEAD_ID).nearest(); if (deadNpc != null) { totalCrabKills++; if (config.lootCrab() && Rs2Inventory.hasItem(" pickaxe", false) && !hasLooted) { - Rs2Npc.interact(deadNpc, "Mine"); + deadNpc.click("Mine"); Rs2Inventory.waitForInventoryChanges(2400); sleep(3000, 5000); hasLooted = true; @@ -185,7 +184,7 @@ private void handleFighting(GemCrabKillerConfig config) { return; } } - Rs2GameObject.interact(CAVE_ENTRANCE_ID, "Crawl-through"); + Microbot.getRs2TileObjectCache().query().withId(CAVE_ENTRANCE_ID).interact("Crawl-through"); gemCrabKillerState = GemCrabKillerState.WAITING; return; } else { @@ -196,7 +195,7 @@ private void handleFighting(GemCrabKillerConfig config) { return; } if (!Rs2Player.isInCombat()) { - Rs2Npc.attack(npc); + npc.click("Attack"); } else { waitingTimeStart = null; } @@ -208,23 +207,19 @@ private void handleWalking() { } - var npc = Rs2Npc.getNpc(CRAB_NPC_ID); + Rs2NpcModel npc = Microbot.getRs2NpcCache().query().withId(CRAB_NPC_ID).nearest(); if (npc != null) { gemCrabKillerState = GemCrabKillerState.FIGHTING; return; } - // Check if we're near the cave entrance before walking - WorldPoint playerLoc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); - GameObject caveEntrance = Rs2GameObject.getGameObject(CAVE_ENTRANCE_ID, playerLoc); + Rs2TileObjectModel caveEntrance = Microbot.getRs2TileObjectCache().query().withId(CAVE_ENTRANCE_ID).nearest(); if (caveEntrance != null) { - // Check if the cave entrance has the "Crawl-through" action - var composition = Microbot.getClientThread().runOnClientThreadOptional(() -> - Microbot.getClient().getObjectDefinition(CAVE_ENTRANCE_ID)).orElse(null); - if (composition != null && Rs2GameObject.hasAction(composition, "Crawl-through")) { - Rs2GameObject.interact(CAVE_ENTRANCE_ID, "Crawl-through"); - sleepUntil(() -> Rs2Npc.getNpc(CRAB_NPC_ID) != null, 5000); - if (Rs2Npc.getNpc(CRAB_NPC_ID) != null) { + var composition = caveEntrance.getObjectComposition(); + if (composition != null && java.util.Arrays.stream(composition.getActions()).anyMatch("Crawl-through"::equals)) { + Microbot.getRs2TileObjectCache().query().withId(CAVE_ENTRANCE_ID).interact("Crawl-through"); + sleepUntil(() -> Microbot.getRs2NpcCache().query().withId(CRAB_NPC_ID).nearest() != null, 5000); + if (Microbot.getRs2NpcCache().query().withId(CRAB_NPC_ID).nearest() != null) { gemCrabKillerState = GemCrabKillerState.FIGHTING; } return; diff --git a/src/main/java/net/runelite/client/plugins/microbot/RoyalTitans/RoyalTitansLooterScript.java b/src/main/java/net/runelite/client/plugins/microbot/RoyalTitans/RoyalTitansLooterScript.java index 1c547fabbf..9b5c47e0ee 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/RoyalTitans/RoyalTitansLooterScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/RoyalTitans/RoyalTitansLooterScript.java @@ -5,8 +5,7 @@ import net.runelite.client.plugins.microbot.util.grounditem.LootingParameters; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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.prayer.Rs2Prayer; @@ -38,13 +37,13 @@ public boolean run(RoyalTitansConfig config, RoyalTitansScript script) { if (royalTitansScript.state.equals(RoyalTitansBotStatus.BANKING) || royalTitansScript.state.equals(RoyalTitansBotStatus.TRAVELLING) || royalTitansScript.state.equals(RoyalTitansBotStatus.WAITING)) return; if (!isInBossRegion()) return; - var iceTitanDead = Rs2Npc.getNpcs(ICE_TITAN_DEAD_ID).findFirst().orElse(null); - var fireTitanDead = Rs2Npc.getNpcs(FIRE_TITAN_DEAD_ID).findFirst().orElse(null); - var iceTitan = Rs2Npc.getNpcs(ICE_TITAN_ID).findFirst().orElse(null); - var fireTitan = Rs2Npc.getNpcs(FIRE_TITAN_ID).findFirst().orElse(null); + var iceTitanDead = Microbot.getRs2NpcCache().query().withId(ICE_TITAN_DEAD_ID).nearest(); + var fireTitanDead = Microbot.getRs2NpcCache().query().withId(FIRE_TITAN_DEAD_ID).nearest(); + var iceTitan = Microbot.getRs2NpcCache().query().withId(ICE_TITAN_ID).nearest(); + var fireTitan = Microbot.getRs2NpcCache().query().withId(FIRE_TITAN_ID).nearest(); boolean looted = false; // Only loot when the giants are dead to not obstruct the fight - if (iceTitan != null && !iceTitan.isDead() || fireTitan != null && !fireTitan.isDead()) { + if (iceTitan != null && !iceTitan.getNpc().isDead() || fireTitan != null && !fireTitan.getNpc().isDead()) { return; } // Both titans are dead, ensure prayer is off @@ -117,10 +116,11 @@ public boolean run(RoyalTitansConfig config, RoyalTitansScript script) { return true; } - private static boolean lootTitan(Rs2NpcModel iceTitanDead) { - Rs2Npc.interact(iceTitanDead, "Loot"); + private static boolean lootTitan(Rs2NpcModel titanDead) { + if (titanDead == null) return false; + titanDead.click("Loot"); sleepUntil(() -> !Rs2Player.isMoving(), 3200); - var looted = Rs2Npc.interact(iceTitanDead, "Loot"); + var looted = titanDead.click("Loot"); Rs2Player.waitForAnimation(1800); return looted; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/RoyalTitans/RoyalTitansScript.java b/src/main/java/net/runelite/client/plugins/microbot/RoyalTitans/RoyalTitansScript.java index 81831fd4a6..8596b33c60 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/RoyalTitans/RoyalTitansScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/RoyalTitans/RoyalTitansScript.java @@ -11,12 +11,10 @@ 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.combat.Rs2Combat; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; 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.misc.Rs2Potion; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; 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; @@ -28,7 +26,7 @@ import java.time.Instant; import java.util.*; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; + import static net.runelite.client.plugins.microbot.RoyalTitans.RoyalTitansShared.evaluateAndConsumePotions; import static net.runelite.client.plugins.microbot.RoyalTitans.RoyalTitansShared.*; @@ -239,8 +237,8 @@ private boolean handleEscaping(RoyalTitansConfig config) { if ((noFood && currentHealth <= config.healthThreshold()) || (noPrayerPotions && currentPrayer < 10)) { shouldLeave = true; } - var iceTitanDead = Rs2Npc.getNpcs(ICE_TITAN_DEAD_ID).findFirst().orElse(null); - var fireTitanDead = Rs2Npc.getNpcs(FIRE_TITAN_DEAD_ID).findFirst().orElse(null); + var iceTitanDead = Microbot.getRs2NpcCache().query().withId(ICE_TITAN_DEAD_ID).nearest(); + var fireTitanDead = Microbot.getRs2NpcCache().query().withId(FIRE_TITAN_DEAD_ID).nearest(); if (shouldLeave && iceTitanDead != null && fireTitanDead != null && !LootedTitanLastIteration) { Microbot.log("We want to escape, but Titans are dead, lets loot first"); } @@ -251,7 +249,7 @@ private boolean handleEscaping(RoyalTitansConfig config) { Rs2Player.waitForAnimation(1200); } else { enrageTile = null; - Rs2GameObject.interact(TUNNEL_ID_ESCAPE, "Quick-escape"); + Microbot.getRs2TileObjectCache().query().withId(TUNNEL_ID_ESCAPE).interact("Quick-escape"); Rs2Bank.walkToBank(); } state = RoyalTitansBotStatus.TRAVELLING; @@ -269,7 +267,7 @@ private void handlePrayers(RoyalTitansConfig config) { Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MELEE, true); return; } - if (Rs2Npc.getNpcs().anyMatch(x -> x.getId() == ICE_TITAN_ID || x.getId() == FIRE_TITAN_ID)) { + if (Microbot.getRs2NpcCache().query().where(x -> x.getId() == ICE_TITAN_ID || x.getId() == FIRE_TITAN_ID).count() > 0) { Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MELEE, true); return; } @@ -302,8 +300,8 @@ private void handleOffensivePrayers(RoyalTitansConfig config) { } private void attackBoss(RoyalTitansConfig config) { - var iceTitan = Rs2Npc.getNpcs(ICE_TITAN_ID).findFirst().orElse(null); - var fireTitan = Rs2Npc.getNpcs(FIRE_TITAN_ID).findFirst().orElse(null); + var iceTitan = Microbot.getRs2NpcCache().query().withId(ICE_TITAN_ID).nearest(); + var fireTitan = Microbot.getRs2NpcCache().query().withId(FIRE_TITAN_ID).nearest(); if (iceTitan == null && fireTitan == null) { Microbot.log("No titans found"); return; @@ -320,7 +318,7 @@ private void handleSpecialAttacks(RoyalTitansConfig config, Rs2NpcModel titan) { if (specEnergy < config.specEnergyConsumed()) { return; } - if (titan == null || titan.isDead()) { + if (titan == null || titan.getNpc().isDead()) { return; } // Failsafe to handle special attack weapons that require to unequip 2 items @@ -337,7 +335,7 @@ private void handleSpecialAttacks(RoyalTitansConfig config, Rs2NpcModel titan) { specialAttackInventorySetup.wearEquipment(); Rs2Combat.setSpecState(true, config.specEnergyConsumed() * 10); sleepUntil(Rs2Combat::getSpecState); - Rs2Npc.attack(titan); + titan.click("Attack"); Rs2Player.waitForAnimation(600); return; } @@ -345,7 +343,7 @@ private void handleSpecialAttacks(RoyalTitansConfig config, Rs2NpcModel titan) { specialAttackInventorySetup.wearEquipment(); Rs2Combat.setSpecState(true, config.specEnergyConsumed() * 10); sleepUntil(Rs2Combat::getSpecState); - Rs2Npc.attack(titan); + titan.click("Attack"); Rs2Player.waitForAnimation(600); } } @@ -359,23 +357,23 @@ private void handleBossFocus(RoyalTitansConfig config, Rs2NpcModel iceTitan, Rs2 } equipArmor(rangedInventorySetup); // Handle focus properly based on config - if (config.royalTitanToFocus() == RoyalTitansConfig.RoyalTitan.FIRE_TITAN && fireTitan != null && fireTitan.isDead()) { - Rs2Npc.attack(fireTitan); + if (config.royalTitanToFocus() == RoyalTitansConfig.RoyalTitan.FIRE_TITAN && fireTitan != null && fireTitan.getNpc().isDead()) { + fireTitan.click("Attack"); handleSpecialAttacks(config, fireTitan); return; } - if (config.royalTitanToFocus() == RoyalTitansConfig.RoyalTitan.ICE_TITAN && iceTitan != null && iceTitan.isDead()) { - Rs2Npc.attack(iceTitan); + if (config.royalTitanToFocus() == RoyalTitansConfig.RoyalTitan.ICE_TITAN && iceTitan != null && iceTitan.getNpc().isDead()) { + iceTitan.click("Attack"); handleSpecialAttacks(config, iceTitan); return; } // Fallback if focused titan is dead - if (fireTitan != null && !fireTitan.isDead()) { - Rs2Npc.attack(fireTitan); + if (fireTitan != null && !fireTitan.getNpc().isDead()) { + fireTitan.click("Attack"); handleSpecialAttacks(config, fireTitan); return; } else if (iceTitan != null) { - Rs2Npc.attack(iceTitan); + iceTitan.click("Attack"); handleSpecialAttacks(config, iceTitan); return; } @@ -385,7 +383,7 @@ private void handleBossFocus(RoyalTitansConfig config, Rs2NpcModel iceTitan, Rs2 if (config.soloMode()) { subState = "Solo mode - balancing titan health"; Rs2NpcModel targetTitan = selectTitanForSoloMode(iceTitan, fireTitan); - if (targetTitan != null && !targetTitan.isDead()) { + if (targetTitan != null && !targetTitan.getNpc().isDead()) { int titanX = targetTitan.getWorldLocation().getRegionX(); // Select appropriate gear based on titan position @@ -400,7 +398,7 @@ private void handleBossFocus(RoyalTitansConfig config, Rs2NpcModel iceTitan, Rs2 // Don't try to attack a Titan if enragetile is active and we are wearing melee armor if (!(enrageTile != null && meleeInventorySetup.doesEquipmentMatch())) { - Rs2Npc.attack(targetTitan); + targetTitan.click("Attack"); handleSpecialAttacks(config, targetTitan); } } @@ -408,7 +406,7 @@ private void handleBossFocus(RoyalTitansConfig config, Rs2NpcModel iceTitan, Rs2 } // Both bosses alive - Handle focus - if (config.royalTitanToFocus() == RoyalTitansConfig.RoyalTitan.FIRE_TITAN && fireTitan != null && !fireTitan.isDead()) { + if (config.royalTitanToFocus() == RoyalTitansConfig.RoyalTitan.FIRE_TITAN && fireTitan != null && !fireTitan.getNpc().isDead()) { subState = "Attacking fire titan"; int fireX = fireTitan.getWorldLocation().getRegionX(); if (enrageTile == null && (fireX == MELEE_TITAN_FIRE_REGION_X || @@ -417,13 +415,13 @@ private void handleBossFocus(RoyalTitansConfig config, Rs2NpcModel iceTitan, Rs2 } else { equipArmor(rangedInventorySetup); } - Rs2Npc.attack(fireTitan); + fireTitan.click("Attack"); handleSpecialAttacks(config, fireTitan); return; } // Both bosses alive - Handle focus - if (config.royalTitanToFocus() == RoyalTitansConfig.RoyalTitan.FIRE_TITAN && fireTitan != null && !fireTitan.isDead()) { + if (config.royalTitanToFocus() == RoyalTitansConfig.RoyalTitan.FIRE_TITAN && fireTitan != null && !fireTitan.getNpc().isDead()) { subState = "Attacking fire titan"; int fireX = fireTitan.getWorldLocation().getRegionX(); if (enrageTile == null && (fireX == MELEE_TITAN_FIRE_REGION_X || @@ -432,10 +430,10 @@ private void handleBossFocus(RoyalTitansConfig config, Rs2NpcModel iceTitan, Rs2 } else { equipArmor(rangedInventorySetup); } - Rs2Npc.attack(fireTitan); + fireTitan.click("Attack"); handleSpecialAttacks(config, fireTitan); return; - } else if (config.royalTitanToFocus() == RoyalTitansConfig.RoyalTitan.ICE_TITAN && iceTitan != null && !iceTitan.isDead()) { + } else if (config.royalTitanToFocus() == RoyalTitansConfig.RoyalTitan.ICE_TITAN && iceTitan != null && !iceTitan.getNpc().isDead()) { subState = "Attacking ice titan"; int iceX = iceTitan.getWorldLocation().getRegionX(); if (enrageTile == null && (iceX == MELEE_TITAN_ICE_REGION_X || @@ -448,12 +446,12 @@ private void handleBossFocus(RoyalTitansConfig config, Rs2NpcModel iceTitan, Rs2 if (enrageTile != null && meleeInventorySetup.doesEquipmentMatch()) { return; } - Rs2Npc.attack(iceTitan); + iceTitan.click("Attack"); handleSpecialAttacks(config, iceTitan); return; } // Only one boss alive - if (iceTitan != null && !iceTitan.isDead()) { + if (iceTitan != null && !iceTitan.getNpc().isDead()) { subState = "Only 1 boss alive, attacking ice titan"; int iceX = iceTitan.getWorldLocation().getRegionX(); if (enrageTile == null && (iceX == MELEE_TITAN_ICE_REGION_X || @@ -462,11 +460,11 @@ private void handleBossFocus(RoyalTitansConfig config, Rs2NpcModel iceTitan, Rs2 } else { equipArmor(rangedInventorySetup); } - Rs2Npc.attack(iceTitan); + iceTitan.click("Attack"); handleSpecialAttacks(config, iceTitan); return; } - if (fireTitan != null && !fireTitan.isDead()) { + if (fireTitan != null && !fireTitan.getNpc().isDead()) { subState = "Only 1 boss alive, attacking fire titan"; int fireX = fireTitan.getWorldLocation().getRegionX(); if (enrageTile == null && (fireX == MELEE_TITAN_FIRE_REGION_X || @@ -475,7 +473,7 @@ private void handleBossFocus(RoyalTitansConfig config, Rs2NpcModel iceTitan, Rs2 } else { equipArmor(rangedInventorySetup); } - Rs2Npc.attack(fireTitan); + fireTitan.click("Attack"); handleSpecialAttacks(config, fireTitan); } } @@ -489,14 +487,14 @@ private boolean handleWalls(RoyalTitansConfig config) { // For solo mode, handle both types of walls List walls; if (config.soloMode()) { - List fireWalls = Rs2Npc.getNpcs(FIRE_WALL).collect(Collectors.toList()); - List iceWalls = Rs2Npc.getNpcs(ICE_WALL).collect(Collectors.toList()); + List fireWalls = Microbot.getRs2NpcCache().query().withId(FIRE_WALL).toList(); + List iceWalls = Microbot.getRs2NpcCache().query().withId(ICE_WALL).toList(); walls = new ArrayList<>(); walls.addAll(fireWalls); walls.addAll(iceWalls); } else { - walls = Rs2Npc.getNpcs(config.minionResponsibility() == RoyalTitansConfig.Minions.FIRE_MINIONS ? FIRE_WALL : ICE_WALL) - .collect(Collectors.toList()); + walls = Microbot.getRs2NpcCache().query().withId(config.minionResponsibility() == RoyalTitansConfig.Minions.FIRE_MINIONS ? FIRE_WALL : ICE_WALL) + .toList(); } if (walls.isEmpty() || walls.size() < 8) { @@ -505,9 +503,9 @@ private boolean handleWalls(RoyalTitansConfig config) { equipArmor(magicInventorySetup); for (var wall : walls) { - if (wall != null && wall.getId() != -1 && !wall.isDead()) { + if (wall != null && wall.getId() != -1 && !wall.getNpc().isDead()) { String action = wall.getId() == FIRE_WALL ? "Douse" : "Melt"; - Rs2Npc.interact(wall, action); + wall.click(action); } } @@ -523,14 +521,14 @@ private boolean handleMinions(RoyalTitansConfig config) { // For solo mode, handle both types of minions List minions; if (config.soloMode()) { - List fireMinions = Rs2Npc.getNpcs(FIRE_MINION_ID).collect(Collectors.toList()); - List iceMinions = Rs2Npc.getNpcs(ICE_MINION_ID).collect(Collectors.toList()); + List fireMinions = Microbot.getRs2NpcCache().query().withId(FIRE_MINION_ID).toList(); + List iceMinions = Microbot.getRs2NpcCache().query().withId(ICE_MINION_ID).toList(); minions = new ArrayList<>(); minions.addAll(fireMinions); minions.addAll(iceMinions); } else { - minions = Rs2Npc.getNpcs(config.minionResponsibility() == RoyalTitansConfig.Minions.FIRE_MINIONS ? FIRE_MINION_ID : ICE_MINION_ID) - .collect(Collectors.toList()); + minions = Microbot.getRs2NpcCache().query().withId(config.minionResponsibility() == RoyalTitansConfig.Minions.FIRE_MINIONS ? FIRE_MINION_ID : ICE_MINION_ID) + .toList(); } if (minions.isEmpty()) { @@ -539,8 +537,8 @@ private boolean handleMinions(RoyalTitansConfig config) { equipArmor(magicInventorySetup); for (var minion : minions) { - if (minion != null && !minion.isDead()) { - Rs2Npc.attack(minion); + if (minion != null && !minion.getNpc().isDead()) { + minion.click("Attack"); } } @@ -548,12 +546,12 @@ private boolean handleMinions(RoyalTitansConfig config) { } private Rs2NpcModel selectTitanForSoloMode(Rs2NpcModel iceTitan, Rs2NpcModel fireTitan) { - if (iceTitan == null || iceTitan.isDead()) return fireTitan; - if (fireTitan == null || fireTitan.isDead()) return iceTitan; + if (iceTitan == null || iceTitan.getNpc().isDead()) return fireTitan; + if (fireTitan == null || fireTitan.getNpc().isDead()) return iceTitan; // Get health ratios - double iceHealthRatio = iceTitan.getHealthRatio(); - double fireHealthRatio = fireTitan.getHealthRatio(); + double iceHealthRatio = iceTitan.getNpc().getHealthRatio(); + double fireHealthRatio = fireTitan.getNpc().getHealthRatio(); // If one titan has significantly more health, attack that one // Using a 20% threshold to prevent frequent switching @@ -567,8 +565,8 @@ private Rs2NpcModel selectTitanForSoloMode(Rs2NpcModel iceTitan, Rs2NpcModel fir if (Rs2Player.isInCombat()) { var interacting = Microbot.getClient().getLocalPlayer().getInteracting(); - if (interacting == iceTitan) return iceTitan; - if (interacting == fireTitan) return fireTitan; + if (interacting == iceTitan.getNpc()) return iceTitan; + if (interacting == fireTitan.getNpc()) return fireTitan; } // Default: attack the one with slightly higher health @@ -704,7 +702,7 @@ private void handleTravelling(RoyalTitansConfig config) { sleep(1200, 1600); } } else { - Rs2GameObject.interact(TUNNEL_ID, "Enter"); + Microbot.getRs2TileObjectCache().query().withId(TUNNEL_ID).interact("Enter"); } break; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/TzhaarVenatorBow/TzhaarVenatorBowScript.java b/src/main/java/net/runelite/client/plugins/microbot/TzhaarVenatorBow/TzhaarVenatorBowScript.java index 13c274a0e0..04a77aaca1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/TzhaarVenatorBow/TzhaarVenatorBowScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/TzhaarVenatorBow/TzhaarVenatorBowScript.java @@ -11,12 +11,11 @@ import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.grandexchange.Rs2GrandExchange; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; import net.runelite.client.plugins.microbot.util.misc.Rs2Potion; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; 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; @@ -152,7 +151,7 @@ private boolean EnsureMagerSafety() { var invalidNpcs = getInvalidNpcs(); if (!invalidNpcs.isEmpty()) { Microbot.log("Under attack from mager, focusing it"); - Rs2Npc.attack(invalidNpcs.get(0)); + invalidNpcs.get(0).click("Attack"); return false; } return true; @@ -169,27 +168,31 @@ private void InitiateCombat() { if (!hursList.isEmpty()) { // Attack the first TzHaar-Hur - Rs2Npc.attack(hursList.get(0)); + hursList.get(0).click("Attack"); } else { // Attack the first valid NPC - Rs2Npc.attack(npcs.get(0)); + npcs.get(0).click("Attack"); } } } } private List getValidNpcs() { - return Rs2Npc.getAttackableNpcs(true) - .filter(npc -> npc.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) <= 6) - .filter(npc -> VALID_NPCS.contains(npc.getName())) - .filter(npc -> !INVALID_NPCS.contains(npc.getName())).collect(Collectors.toList()); + return Microbot.getRs2NpcCache().query() + .where(npc -> !npc.isDead()) + .where(npc -> npc.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) <= 6) + .where(npc -> VALID_NPCS.contains(npc.getName())) + .where(npc -> !INVALID_NPCS.contains(npc.getName())) + .toList(); } private List getInvalidNpcs() { - return Rs2Npc.getNpcsForPlayer("TzHaar-Mej").stream() - .filter(npc -> npc.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) <= 15) - .filter(npc -> !npc.isDead() && npc.isInteracting()) - .filter(npc -> Rs2Npc.hasLineOfSight(npc)).collect(Collectors.toList()); + return Microbot.getRs2NpcCache().query() + .withName("TzHaar-Mej") + .where(npc -> npc.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) <= 15) + .where(npc -> !npc.isDead() && npc.isInteracting()) + .where(npc -> npc.hasLineOfSight()) + .toList(); } private void handleTravel(TzHaarVenatorBowConfig config) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/animatedarmour/AnimatedArmourScript.java b/src/main/java/net/runelite/client/plugins/microbot/animatedarmour/AnimatedArmourScript.java index d341a043dd..a1944a0ccb 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/animatedarmour/AnimatedArmourScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/animatedarmour/AnimatedArmourScript.java @@ -1,12 +1,11 @@ package net.runelite.client.plugins.microbot.animatedarmour; -import net.runelite.api.GameObject; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.grounditem.LootingParameters; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; @@ -59,9 +58,9 @@ public boolean run(AnimatedArmourConfig config) { public void animateArmor() { WorldPoint armorStandLocation = new WorldPoint(2851, 3536, 0); - GameObject armorStand = Rs2GameObject.getGameObject(armorStandLocation); + Rs2TileObjectModel armorStand = Microbot.getRs2TileObjectCache().query().within(armorStandLocation, 0).nearest(); if (armorStand != null) { - Rs2GameObject.interact(armorStand); + armorStand.click(); Rs2Player.waitForAnimation(); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsOverlay.java index 8b363d21ec..b3c092910a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsOverlay.java @@ -2,12 +2,11 @@ import lombok.Setter; import net.runelite.api.Client; -import net.runelite.api.NPC; import net.runelite.api.Perspective; 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.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.ui.overlay.OverlayLayer; import net.runelite.client.ui.overlay.OverlayPanel; import net.runelite.client.ui.overlay.OverlayPosition; @@ -106,7 +105,7 @@ public Dimension render(Graphics2D graphics) { // Dragon tracking section addSectionDivider("Dragon Tracking"); - NPC nearestDragon = Rs2Npc.getNpc("Blue dragon"); + Rs2NpcModel nearestDragon = Microbot.getRs2NpcCache().query().withName("Blue dragon").nearest(); boolean isTargeting = nearestDragon != null && script.getCurrentTargetId() != null && script.getCurrentTargetId() == nearestDragon.getId(); @@ -228,12 +227,12 @@ private void addSectionDivider(String sectionName) { ); } - private String getDragonStatus(NPC dragon, boolean isTargeting) { + private String getDragonStatus(Rs2NpcModel dragon, boolean isTargeting) { if (dragon == null) return "No dragons"; return isTargeting ? "Fighting" : "Available"; } - private Color getDragonStatusColor(NPC dragon, boolean isTargeting) { + private Color getDragonStatusColor(Rs2NpcModel dragon, boolean isTargeting) { if (dragon == null) return new Color(169, 169, 169); // Dark Gray return isTargeting ? new Color(220, 20, 60) : new Color(50, 205, 50); // Crimson : Lime Green } diff --git a/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsScript.java b/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsScript.java index fa73b2e128..ce1d06038a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsScript.java @@ -17,8 +17,7 @@ import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.misc.Rs2Food; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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 net.runelite.client.plugins.skillcalculator.skills.MagicAction; @@ -483,14 +482,14 @@ private boolean lootItem(String itemName) { private Rs2NpcModel getAvailableDragon() { - Rs2NpcModel dragon = Rs2Npc.getNpc("Blue dragon"); + Rs2NpcModel dragon = Microbot.getRs2NpcCache().query().withName("Blue dragon").nearest(); logOnceToChat("Found dragon: " + (dragon != null ? "Yes (ID: " + dragon.getId() + ")" : "No"), true, config); if (dragon != null) { boolean correctId = (dragon.getId() == BLUE_DRAGON_ID_1 || dragon.getId() == BLUE_DRAGON_ID_2 || dragon.getId() == BLUE_DRAGON_ID_3); logOnceToChat("Dragon has correct ID (265, 266, or 267): " + correctId, true, config); - boolean hasLineOfSight = Rs2Npc.hasLineOfSight(new Rs2NpcModel(dragon)); + boolean hasLineOfSight = dragon.hasLineOfSight(); logOnceToChat("Has line of sight to dragon: " + hasLineOfSight, true, config); if (correctId && hasLineOfSight) { @@ -503,13 +502,13 @@ private Rs2NpcModel getAvailableDragon() { private boolean attackDragon(Rs2NpcModel dragon) { final int dragonId = dragon.getId(); - if (Rs2Combat.inCombat() && dragon.getInteracting() != Microbot.getClient().getLocalPlayer()) { + if (Rs2Combat.inCombat() && !dragon.isInteractingWithPlayer()) { logOnceToChat("Cannot attack dragon - player is in combat with different target.", true, config); return false; } - if (Rs2Npc.attack(dragon)) { - boolean dragonKilled = sleepUntil(() -> Rs2Npc.getNpc(dragonId) == null, 60000); + if (dragon.click("Attack")) { + boolean dragonKilled = sleepUntil(() -> Microbot.getRs2NpcCache().query().withId(dragonId).nearest() == null, 60000); if (dragonKilled) { logOnceToChat("Dragon killed. Transitioning to looting state.", true, config); diff --git a/src/main/java/net/runelite/client/plugins/microbot/giantmole/GiantMoleScript.java b/src/main/java/net/runelite/client/plugins/microbot/giantmole/GiantMoleScript.java index b6b5ba03e0..c10bb2ffeb 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/giantmole/GiantMoleScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/giantmole/GiantMoleScript.java @@ -17,7 +17,7 @@ import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.grounditem.LootingParameters; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; @@ -25,8 +25,7 @@ import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.misc.Rs2Food; import net.runelite.client.plugins.microbot.util.misc.Rs2Potion; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.security.Login; @@ -239,9 +238,9 @@ public void hopWorlds() /** * Retrieves the Mole Hill tile object by ID. */ - public TileObject getMoleHill() + public Rs2TileObjectModel getMoleHill() { - return Rs2GameObject.getTileObject(ObjectID.MOLE_HILL); + return Microbot.getRs2TileObjectCache().query().withId(ObjectID.MOLE_HILL).nearest(); } /** @@ -249,10 +248,10 @@ public TileObject getMoleHill() */ public void checkWorldOccupied() { - TileObject moleHill = getMoleHill(); + Rs2TileObjectModel moleHill = getMoleHill(); if (moleHill != null) { - Rs2GameObject.interact(moleHill, "Look-inside"); + moleHill.click("Look-inside"); Global.sleepUntilTrue(() -> checkedIfWorldOccupied, 200, 7000); } } @@ -262,7 +261,7 @@ public void checkWorldOccupied() */ public void goInsideMoleHill() { - TileObject moleHill = getMoleHill(); + Rs2TileObjectModel moleHill = getMoleHill(); if (moleHill != null) { if (Rs2Walker.walkTo(moleHill.getWorldLocation(), 0)) @@ -310,10 +309,13 @@ public static boolean isMoleDead() */ public Rs2NpcModel getMole() { - return Microbot.getClientThread() + NPC hintNpc = Microbot.getClientThread() .runOnClientThreadOptional(() -> Microbot.getClient().getHintArrowNpc()) - .map(Rs2NpcModel::new) .orElse(null); + if (hintNpc == null) return null; + return Microbot.getRs2NpcCache().query() + .where(n -> n.getNpc() == hintNpc) + .nearest(); } /** @@ -383,7 +385,7 @@ public void attackMole() sleep(600, 800); } - if (Rs2Npc.interact(mole, "Attack")) + if (mole.click("Attack")) { sleep(600, 800); } @@ -399,7 +401,7 @@ public void handlePrayer(GiantMoleConfig config) return; } - boolean underAttack = Rs2Npc.getNpcsForPlayer().findAny().isPresent() || Rs2Combat.inCombat(); + boolean underAttack = Microbot.getRs2NpcCache().query().where(Rs2NpcModel::isInteractingWithPlayer).count() > 0 || Rs2Combat.inCombat(); Rs2Prayer.toggleQuickPrayer(!isInFalador() && underAttack); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/hunterKabbits/HunterKabbitsScript.java b/src/main/java/net/runelite/client/plugins/microbot/hunterKabbits/HunterKabbitsScript.java index 8126c7c609..e958c2ff97 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/hunterKabbits/HunterKabbitsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/hunterKabbits/HunterKabbitsScript.java @@ -10,8 +10,7 @@ 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.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -120,12 +119,15 @@ private void handleRetrievingState(HunterKebbitsConfig config) { NPC hintNpc = Microbot.getClient().getHintArrowNpc(); if (hintNpc != null && VALID_FALCON_NPC_IDS.contains(hintNpc.getId())) { - Rs2NpcModel model = new Rs2NpcModel(hintNpc); + Rs2NpcModel model = Microbot.getRs2NpcCache().query() + .where(n -> n.getNpc() == hintNpc) + .nearest(); boolean retrieved = false; for (int i = 0; i < 5; i++) { if (!isRunning()) break; + if (model == null) break; - if (Rs2Npc.interact(model, "Retrieve")) { + if (model.click("Retrieve")) { retrieved = true; break; } @@ -138,8 +140,9 @@ private void handleRetrievingState(HunterKebbitsConfig config) { currentState = State.CATCHING; } } else { - boolean anyFalconStillActive = Rs2Npc.getNpcs() - .anyMatch(npc -> VALID_FALCON_NPC_IDS.contains(npc.getId())); + boolean anyFalconStillActive = Microbot.getRs2NpcCache().query() + .where(npc -> VALID_FALCON_NPC_IDS.contains(npc.getId())) + .count() > 0; if (!anyFalconStillActive) { currentState = State.CATCHING; } @@ -152,12 +155,15 @@ private void handleRetrievingState(HunterKebbitsConfig config) { private void handleCatchingState(HunterKebbitsConfig config) { String npcName = getKebbit(config).getNpcName(); - if (Rs2Npc.interact(npcName, "Catch")) { + Rs2NpcModel kebbit = Microbot.getRs2NpcCache().query().withName(npcName).nearest(); + if (kebbit != null && kebbit.click("Catch")) { boolean falconActive = false; for (int i = 0; i < 10; i++) { if (!isRunning()) break; - boolean found = Rs2Npc.getNpcs().anyMatch(npc -> VALID_FALCON_NPC_IDS.contains(npc.getId())); + boolean found = Microbot.getRs2NpcCache().query() + .where(npc -> VALID_FALCON_NPC_IDS.contains(npc.getId())) + .count() > 0; if (found || isHintArrowNpcActive()) { falconActive = true; break; @@ -213,7 +219,9 @@ private boolean isHintArrowNpcActive() { * Returns true if falcon is with player (not visible and no active hint arrow). */ private boolean isFalconWithPlayer() { - boolean falconVisible = Rs2Npc.getNpcs().anyMatch(npc -> VALID_FALCON_NPC_IDS.contains(npc.getId())); + boolean falconVisible = Microbot.getRs2NpcCache().query() + .where(npc -> VALID_FALCON_NPC_IDS.contains(npc.getId())) + .count() > 0; return !falconVisible && !isHintArrowNpcActive(); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/jad/JadScript.java b/src/main/java/net/runelite/client/plugins/microbot/jad/JadScript.java index d755b95e96..2fcad1e3c0 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/jad/JadScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/jad/JadScript.java @@ -2,16 +2,15 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; public class JadScript extends Script { public static final Map npcAttackCooldowns = new HashMap<>(); @@ -22,9 +21,11 @@ public boolean run(JadConfig config) { try { if (!Microbot.isLoggedIn() || !super.run()) return; - var jadNpcs = Rs2Npc.getNpcs("Jad", false); + List jadNpcs = Microbot.getRs2NpcCache().query() + .where(n -> n.getName() != null && n.getName().toLowerCase().contains("jad")) + .toList(); - for (Rs2NpcModel jadNpc : jadNpcs.collect(Collectors.toList())) { + for (Rs2NpcModel jadNpc : jadNpcs) { if (jadNpc == null) continue; long currentTimeMillis = System.currentTimeMillis(); @@ -38,7 +39,7 @@ public boolean run(JadConfig config) { } } - int npcAnimation = jadNpc.getAnimation(); + int npcAnimation = jadNpc.getNpc().getAnimation(); handleJadPrayer(npcAnimation); if (config.shouldAttackHealers()) { handleHealerInteraction(); @@ -53,17 +54,21 @@ public boolean run(JadConfig config) { } private void handleHealerInteraction() { - var healer = Rs2Npc.getNpcs("hurkot", false) - .filter(npc -> npc != null && npc.getInteracting() != Microbot.getClient().getLocalPlayer()) - .findFirst() - .orElse(null); + var healer = Microbot.getRs2NpcCache().query() + .where(n -> n.getName() != null && n.getName().toLowerCase().contains("hurkot") && !n.isInteractingWithPlayer()) + .nearest(); if (healer != null) { - Rs2Npc.interact(healer, "attack"); + healer.click("Attack"); } else { var npc = Rs2Player.getInteracting(); - if (npc == null || npc != null && npc.getName().contains("hurkot")) { - Rs2Npc.interact(Rs2Npc.getNpc("Jad", false), "attack"); + if (npc == null || npc.getName().contains("hurkot")) { + var jad = Microbot.getRs2NpcCache().query() + .where(n -> n.getName() != null && n.getName().toLowerCase().contains("jad")) + .nearest(); + if (jad != null) { + jad.click("Attack"); + } } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/mmcaves/MmCavesScript.java b/src/main/java/net/runelite/client/plugins/microbot/mmcaves/MmCavesScript.java index 827d5e8715..d079cb84ab 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mmcaves/MmCavesScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mmcaves/MmCavesScript.java @@ -1,26 +1,23 @@ package net.runelite.client.plugins.microbot.mmcaves; import com.google.common.collect.Table; -import net.runelite.api.GameObject; import net.runelite.api.GameState; -import net.runelite.api.GroundObject; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.grounditems.GroundItem; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.mmcaves.enums.CombatStyle; import net.runelite.client.plugins.microbot.mmcaves.enums.LightSources; import net.runelite.client.plugins.microbot.mmcaves.enums.Mode; import net.runelite.client.plugins.microbot.mmcaves.enums.State; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; 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.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.magic.Rs2Spellbook; import net.runelite.client.plugins.microbot.util.magic.Runes; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; 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; @@ -35,7 +32,6 @@ import java.util.Arrays; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.concurrent.TimeUnit; public class MmCavesScript extends Script { @@ -224,9 +220,9 @@ private void handleWalkToStart() { private void handleCheckEmptyCave() { Microbot.log("Checking if cave is empty"); - GroundObject hole = Rs2GameObject.getGroundObject(28772); + Rs2TileObjectModel hole = Microbot.getRs2TileObjectCache().query().withId(28772).nearest(); if (hole != null) { - Rs2GameObject.interact(hole.getWorldLocation(), "Look-in"); + hole.click("Look-in"); sleepUntil(() -> caveIsEmpty, 3000); } } @@ -251,11 +247,11 @@ private void handleWorldHop() { private void handleEnterCave() { Microbot.log("Entering cave..."); - GroundObject hole = Rs2GameObject.getGroundObject(28772); + Rs2TileObjectModel hole = Microbot.getRs2TileObjectCache().query().withId(28772).nearest(); if (hole != null) { Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MELEE, true); - Rs2GameObject.interact(hole.getWorldLocation(), "Enter"); + hole.click("Enter"); sleepUntil(() -> caveIsEmpty, 3000); } } @@ -304,11 +300,11 @@ private void handleFight() { // This is not efficient and needs improvement but sufficient for first release // If not it impacts magic a lot due to longer delay - Optional monkey = Rs2Npc.getNpcs("Maniacal monkey") - .filter(npc -> npc.getWorldLocation().equals(new WorldPoint(2451, 9159, 1))) - .filter(npc -> !npc.isDead()) - .findFirst(); - Rs2NpcModel target = monkey.orElse(null); + Rs2NpcModel target = Microbot.getRs2NpcCache().query() + .withName("Maniacal monkey") + .where(npc -> npc.getWorldLocation().equals(new WorldPoint(2451, 9159, 1)) + && !npc.getNpc().isDead()) + .nearest(); boolean attacked = attemptAttack(target); if (attacked) walkBetweenTiles(); @@ -339,9 +335,9 @@ private void stopAndLog() { sleepUntil(() -> plugin.getMyWorldPoint().distanceTo(EXIT_TILE) < 3, 1000); if ( plugin.getMyWorldPoint().distanceTo(EXIT_TILE) < 3) { - GameObject rope = Rs2GameObject.getGameObject(28775); + Rs2TileObjectModel rope = Microbot.getRs2TileObjectCache().query().withId(28775).nearest(); if (rope != null) { - Rs2GameObject.interact(rope, "Climb-up"); + rope.click("Climb-up"); sleepUntil(() -> plugin.getMyWorldPoint().getRegionID() == cavesUpstairs, 5000); } } @@ -433,7 +429,7 @@ private boolean attemptAttack(Rs2NpcModel target) { if (!config.shouldAutoCast()) { attacked = Rs2Magic.castOn(config.magicSpell().getSpell(), target); } else { - attacked = Rs2Npc.interact(target, "Attack"); + attacked = target.click("Attack"); } if (attacked) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzScript.java b/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzScript.java index 3fde086fc0..734bc03a31 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzScript.java @@ -10,12 +10,10 @@ import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; 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.npc.Rs2NpcModel; 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; @@ -147,10 +145,10 @@ public void handleInsideNmz() { lastCombatTime = System.currentTimeMillis(); } if (!Rs2Player.isInCombat() && System.currentTimeMillis() - lastCombatTime > 20000) { - Rs2NpcModel closestNpc = Rs2Npc.getNearestNpcWithAction("Attack"); + Rs2NpcModel closestNpc = Microbot.getRs2NpcCache().query().nearest(); if (closestNpc != null) { - Rs2Npc.interact(closestNpc, "Attack"); + closestNpc.click("Attack"); } } prayerPotionScript.run(); @@ -173,7 +171,7 @@ private void walkToCenter() { public void startNmzDream() { // Set new center so that it is random for every time joining the dream center = new WorldPoint(Rs2Random.between(2270, 2276), Rs2Random.between(4693, 4696), 0); - Rs2Npc.interact(NpcID.DOMINIC_ONION, "Dream"); + Microbot.getRs2NpcCache().query().withId(NpcID.DOMINIC_ONION).interact("Dream"); sleepUntil(() -> Rs2Widget.hasWidget("Which dream would you like to experience?")); Rs2Widget.clickWidget("Previous:"); sleepUntil(() -> Rs2Widget.hasWidget("Click here to continue")); @@ -202,14 +200,14 @@ public boolean useOrbs() { } public boolean interactWithObject(int objectId) { - TileObject rs2GameObject = Rs2GameObject.findObjectById(objectId); + var rs2GameObject = Microbot.getRs2TileObjectCache().query().withId(objectId).nearest(); if (rs2GameObject != null) { - Rs2Walker.walkFastLocal(rs2GameObject.getLocalLocation()); + Rs2Walker.walkFastCanvas(rs2GameObject.getWorldLocation()); sleepUntil(() -> { WorldPoint loc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); return loc != null && loc.distanceTo(rs2GameObject.getWorldLocation()) < 5; }); - Rs2GameObject.interact(objectId); + rs2GameObject.click(); return true; } return false; @@ -222,7 +220,7 @@ private void fetchOverloadPotions(int objectId, String itemName, int requiredAmo int neededAmount = requiredAmount - currentAmount; - Rs2GameObject.interact(objectId, "Take"); + Microbot.getRs2TileObjectCache().query().withId(objectId).interact("Take"); String widgetText = "How many doses of "; sleepUntil(() -> Rs2Widget.hasWidget(widgetText)); @@ -291,7 +289,7 @@ private void storePotions(int objectId, String itemName, int requiredAmount) { if (Rs2Inventory.count(itemName) == requiredAmount) return; if (Rs2Inventory.get(itemName) == null) return; - Rs2GameObject.interact(objectId, "Store"); + Microbot.getRs2TileObjectCache().query().withId(objectId).interact("Store"); String storeWidgetText = "Store all your "; sleepUntil(() -> Rs2Widget.hasWidget(storeWidgetText)); if (Rs2Widget.hasWidget(storeWidgetText)) { @@ -305,7 +303,7 @@ private void storePotions(int objectId, String itemName, int requiredAmount) { private void fetchPotions(int objectId, String itemName, int requiredAmount) { if (Rs2Inventory.count(itemName) == requiredAmount) return; - Rs2GameObject.interact(objectId, "Take"); + Microbot.getRs2TileObjectCache().query().withId(objectId).interact("Take"); String widgetText = "How many doses of "; sleepUntil(() -> Rs2Widget.hasWidget(widgetText)); if (Rs2Widget.hasWidget(widgetText)) { @@ -320,7 +318,7 @@ public void consumeEmptyVial() { if (Microbot.getClientThread().runOnClientThreadOptional(() -> Rs2Widget.getWidget(129, 6) == null || Rs2Widget.getWidget(129, 6).isHidden()) .orElse(false)) { - Rs2GameObject.interact(EMPTY_VIAL, "drink"); + Microbot.getRs2TileObjectCache().query().withId(EMPTY_VIAL).interact("drink"); } sleep(2000,4000); Widget widget = Rs2Widget.getWidget(129, 6); @@ -351,7 +349,7 @@ public void handleStore() { } } - Rs2GameObject.interact(26273); + Microbot.getRs2TileObjectCache().query().withId(26273).interact(); sleepUntil(() -> Rs2Widget.isWidgetVisible(13500418) || Rs2Bank.isBankPinWidgetVisible(), 10000); if (Rs2Bank.isBankPinWidgetVisible()) { try { diff --git a/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabScript.java b/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabScript.java index 4d36a7f122..5427fbabff 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabScript.java @@ -1,7 +1,6 @@ package net.runelite.client.plugins.microbot.sandcrabs; import net.runelite.api.GameState; -import net.runelite.api.NPC; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; @@ -13,8 +12,7 @@ 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.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.player.Rs2PlayerModel; import net.runelite.client.plugins.microbot.util.security.Login; @@ -207,18 +205,17 @@ public boolean run(SandCrabConfig config, SandCrabPlugin plugin) { * @return true if npc is aggressive */ private boolean isNpcAggressive() { - List npcs = Rs2Npc.getNpcs("Sandy rocks", true).collect(Collectors.toList()); + List npcs = Microbot.getRs2NpcCache().query().withName("Sandy rocks").toList(); if (npcs.isEmpty()) { return false; } - for (NPC sandyRock : npcs) { - //ignore sandcrabs far away from the player - if (!sandyRock.getWorldArea().isInMeleeDistance(Rs2Player.getWorldLocation())) + for (Rs2NpcModel sandyRock : npcs) { + if (!sandyRock.getNpc().getWorldArea().isInMeleeDistance(Rs2Player.getWorldLocation())) continue; - return false; //found a sandy rock crab near the player + return false; } - return true; //did not find any sandy rocks near the player + return true; } /** diff --git a/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusScript.java b/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusScript.java index 0a45b9bb7d..68e2bc1643 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusScript.java @@ -11,12 +11,10 @@ import net.runelite.client.plugins.microbot.scurrius.enums.State; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.coords.Rs2LocalPoint; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.grounditem.LootingParameters; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; 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; @@ -26,7 +24,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Optional; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; @@ -70,13 +67,13 @@ public boolean run(ScurriusConfig config) { previousState = state; } - scurrius = Rs2Npc.getNpc("Scurrius", true); + scurrius = Microbot.getRs2NpcCache().query().withName("Scurrius").nearest(); boolean hasFood = !Rs2Inventory.getInventoryFood().isEmpty(); boolean hasPrayerPotions = Rs2Inventory.hasItem("prayer potion") || Rs2Inventory.hasItem("super restore"); boolean isScurriusPresent = scurrius != null; boolean isInFightRoom = isInFightRoom(); - boolean hasLineOfSightWithScurrius = Rs2Npc.hasLineOfSight(scurrius); + boolean hasLineOfSightWithScurrius = scurrius != null && scurrius.hasLineOfSight(); if (previousInFightRoom == null || isInFightRoom != previousInFightRoom) { Microbot.log(isInFightRoom ? "Player has entered the boss room." : "Player has exited the boss room."); @@ -195,15 +192,17 @@ public boolean run(ScurriusConfig config) { } } - Optional giantRat = Rs2Npc.getNpcs("giant rat").filter(npc -> !npc.isDead()).findFirst(); - if (giantRat.isPresent()) { - Rs2NpcModel giantRatModel = giantRat.get(); - boolean didWeAttackAGiantRat = scurrius != null && config.prioritizeRats() && Rs2Npc.attack(giantRatModel); + Rs2NpcModel giantRatModel = Microbot.getRs2NpcCache().query() + .where(n -> n.getName() != null && n.getName().toLowerCase().contains("giant rat") + && !n.getNpc().isDead()) + .nearest(); + if (giantRatModel != null) { + boolean didWeAttackAGiantRat = scurrius != null && config.prioritizeRats() && giantRatModel.click("Attack"); if (didWeAttackAGiantRat) return; } if (!Microbot.getClient().getLocalPlayer().isInteracting()) { - Rs2Npc.attack(scurrius); + scurrius.click("Attack"); } break; @@ -225,7 +224,7 @@ public boolean run(ScurriusConfig config) { Rs2Walker.walkTo(bossLocation); String interactionType = config.bossRoomEntryType().getInteractionText(); - Rs2GameObject.interact(ObjectID.RAT_BOSS_ENTRANCE, interactionType); + Microbot.getRs2TileObjectCache().query().withId(ObjectID.RAT_BOSS_ENTRANCE).interact(interactionType); sleepUntil(this::isInFightRoom); break; @@ -329,7 +328,7 @@ private List parseLootItems(String lootFilter) { private void handlePrayerLogic() { if (scurrius == null) return; - int npcAnimation = scurrius.getAnimation(); + int npcAnimation = scurrius.getNpc().getAnimation(); Rs2PrayerEnum newDefensivePrayer = null; switch (npcAnimation) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/shadeskiller/ShadesKillerScript.java b/src/main/java/net/runelite/client/plugins/microbot/shadeskiller/ShadesKillerScript.java index fa4558d927..7f8b9cab99 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/shadeskiller/ShadesKillerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/shadeskiller/ShadesKillerScript.java @@ -9,7 +9,7 @@ import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +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 net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -199,15 +199,18 @@ public boolean run(ShadesKillerConfig config) { case FIGHT_SHADES: boolean isLooting = Rs2GroundItem.lootAtGePrice(config.priceOfItemsToLoot()); if (isLooting) return; - var npc = Rs2Npc.getNpcsForPlayer(config.SHADES().names.get(0)).stream().findFirst().orElse(null); - //if npc is attacking us, then attack back + Rs2NpcModel npc = Microbot.getRs2NpcCache().query() + .withName(config.SHADES().names.get(0)) + .where(Rs2NpcModel::isInteractingWithPlayer) + .nearest(); if (npc != null && !Microbot.getClient().getLocalPlayer().isInteracting()) { - Rs2Npc.attack(npc); + npc.click("Attack"); return; } - //if no npc is attacking us, attack a new npc if (!Rs2Combat.inCombat() && !isLooting) { - Rs2Npc.attack(config.SHADES().names); + Microbot.getRs2NpcCache().query() + .withNames(config.SHADES().names.toArray(new String[0])) + .interact("Attack"); } Rs2Combat.setSpecState(true, config.specialAttack() * 10); if (Rs2Inventory.isFull() && config.useCoffin()) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerScript.java b/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerScript.java index 8a6354e58d..79ac32f514 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerScript.java @@ -2,9 +2,7 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.Actor; -import net.runelite.api.GameObject; import net.runelite.api.GameState; -import net.runelite.api.TileObject; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; import net.runelite.api.widgets.Widget; @@ -16,8 +14,9 @@ import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; 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.grounditem.LootingParameters; import net.runelite.client.plugins.microbot.util.grounditem.Rs2LootEngine; import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; @@ -29,9 +28,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.npc.MonsterLocation; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcManager; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; 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; @@ -347,9 +344,9 @@ private void handleGettingTaskState(boolean hasTask, String taskName) { if (distance <= 5) { // We're at the master, interact to get task - Rs2NpcModel masterNpc = Rs2Npc.getNpc(master.getName()); + var masterNpc = Microbot.getRs2NpcCache().query().withName(master.getName()).nearest(); if (masterNpc != null) { - if (Rs2Npc.interact(masterNpc, "Assignment")) { + if (masterNpc.click("Assignment")) { log.info("Requesting task from {}", master.getName()); sleepUntil(Rs2Dialogue::isInDialogue, 3000); } @@ -539,9 +536,9 @@ private void handleSkippingTaskState() { if (distance <= 5) { // We're at the master, interact to open rewards - Rs2NpcModel masterNpc = Rs2Npc.getNpc(master.getName()); + var masterNpc = Microbot.getRs2NpcCache().query().withName(master.getName()).nearest(); if (masterNpc != null) { - if (Rs2Npc.interact(masterNpc, "Rewards")) { + if (masterNpc.click("Rewards")) { log.info("Opening rewards menu to skip task (attempt {})", skipAttemptCounter + 1); sleepUntil(() -> Rs2Dialogue.isInDialogue() || Rs2Widget.isWidgetVisible(SLAYER_REWARDS_GROUP_ID, 0), 3000); } @@ -618,7 +615,7 @@ private void handleRestoringAtPohState() { } private boolean isInPoh() { - return Rs2GameObject.findObjectById(HOUSE_PORTAL_ID) != null; + return Microbot.getRs2TileObjectCache().query().withId(HOUSE_PORTAL_ID).nearest() != null; } private boolean isFullPrayer() { @@ -680,8 +677,8 @@ private boolean teleportToHouse() { private boolean useRejuvenationPool() { for (int poolId : REJUVENATION_POOL_IDS) { - if (Rs2GameObject.exists(poolId)) { - return Rs2GameObject.interact(poolId, "Drink"); + if (Microbot.getRs2TileObjectCache().query().withId(poolId).nearest() != null) { + return Microbot.getRs2TileObjectCache().query().interact(poolId, "Drink"); } } return false; @@ -707,7 +704,7 @@ private void leaveHouse() { } // Fallback to house portal exit - if (Rs2GameObject.interact(HOUSE_PORTAL_ID, "Enter")) { + if (Microbot.getRs2TileObjectCache().query().interact(HOUSE_PORTAL_ID, "Enter")) { log.info("Leaving house via portal (no teleport options found)"); } } @@ -719,13 +716,11 @@ private void leaveHouse() { private boolean teleportViaPortalNexus() { // Try to find any tier of portal nexus for (int nexusId : PORTAL_NEXUS_IDS) { - if (Rs2GameObject.exists(nexusId)) { - // Try direct Grand Exchange teleport option first - if (Rs2GameObject.interact(nexusId, "Grand Exchange")) { + if (Microbot.getRs2TileObjectCache().query().withId(nexusId).nearest() != null) { + if (Microbot.getRs2TileObjectCache().query().interact(nexusId, "Grand Exchange")) { return true; } - // Some nexus configurations might use different option names - if (Rs2GameObject.interact(nexusId, "Varrock Grand Exchange")) { + if (Microbot.getRs2TileObjectCache().query().interact(nexusId, "Varrock Grand Exchange")) { return true; } } @@ -739,8 +734,8 @@ private boolean teleportViaPortalNexus() { */ private boolean teleportViaMountedGlory() { for (int gloryId : MOUNTED_GLORY_IDS) { - if (Rs2GameObject.exists(gloryId)) { - if (Rs2GameObject.interact(gloryId, "Edgeville")) { + if (Microbot.getRs2TileObjectCache().query().withId(gloryId).nearest() != null) { + if (Microbot.getRs2TileObjectCache().query().interact(gloryId, "Edgeville")) { return true; } } @@ -753,8 +748,8 @@ private boolean teleportViaMountedGlory() { * @return true if interaction was successful, false if mounted wealth not found */ private boolean teleportViaMountedWealth() { - if (Rs2GameObject.exists(MOUNTED_WEALTH_ID)) { - if (Rs2GameObject.interact(MOUNTED_WEALTH_ID, "Grand Exchange")) { + if (Microbot.getRs2TileObjectCache().query().withId(MOUNTED_WEALTH_ID).nearest() != null) { + if (Microbot.getRs2TileObjectCache().query().interact(MOUNTED_WEALTH_ID, "Grand Exchange")) { return true; } } @@ -1049,9 +1044,9 @@ private void handleBlockingTaskState() { if (distance <= 5) { // We're at the master, interact to open rewards - Rs2NpcModel masterNpc = Rs2Npc.getNpc(master.getName()); + var masterNpc = Microbot.getRs2NpcCache().query().withName(master.getName()).nearest(); if (masterNpc != null) { - if (Rs2Npc.interact(masterNpc, "Rewards")) { + if (masterNpc.click("Rewards")) { log.info("Opening rewards menu to block task (attempt {})", blockAttemptCounter + 1); sleepUntil(() -> Rs2Dialogue.isInDialogue() || Rs2Widget.isWidgetVisible(SLAYER_REWARDS_GROUP_ID, 0), 3000); } @@ -1603,39 +1598,33 @@ private void handleSwappingSpellbookState() { } // Find and interact with the occult altar - TileObject occultAltar = findOccultAltar(); + Rs2TileObjectModel occultAltar = findOccultAltar(); if (occultAltar != null) { - // Try right-click option first (e.g., "Ancient Magicks", "Lunar", "Standard") String altarOption = getAltarOptionForSpellbook(requiredSpellbook); log.info("Attempting occult altar right-click option: '{}'", altarOption); - if (Rs2GameObject.interact(occultAltar, altarOption)) { - // Wait briefly for either direct spellbook switch or a dialog to open + if (occultAltar.click(altarOption)) { sleepUntil(() -> Rs2Magic.isSpellbook(requiredSpellbook) || Rs2Dialogue.hasSelectAnOption(), 3000); - // Check if the spellbook already switched (right-click option worked directly) if (Rs2Magic.isSpellbook(requiredSpellbook)) { log.info("Successfully switched to {} spellbook via right-click", requiredSpellbook); finishSpellbookSwap(); return; } - // Right-click option may have opened a dialog - handle it if (Rs2Dialogue.hasSelectAnOption() && handleSpellbookWidget(requiredSpellbook)) { return; } } else { - // Right-click option didn't match - try left-click "Venerate" which opens dialog log.info("Right-click option '{}' not found, trying Venerate", altarOption); - if (Rs2GameObject.interact(occultAltar, "Venerate")) { + if (occultAltar.click("Venerate")) { sleepUntil(Rs2Dialogue::hasSelectAnOption, 3000); if (!handleSpellbookWidget(requiredSpellbook)) { log.warn("Venerate used but could not handle spellbook dialog"); } } else { - // Last resort: plain interact log.info("Trying plain interact on occult altar"); - Rs2GameObject.interact(occultAltar); + occultAltar.click(); sleepUntil(Rs2Dialogue::hasSelectAnOption, 3000); handleSpellbookWidget(requiredSpellbook); } @@ -1771,9 +1760,9 @@ private String getWidgetTextForSpellbook(Rs2Spellbook spellbook) { /** * Finds the occult altar in the POH. */ - private TileObject findOccultAltar() { + private Rs2TileObjectModel findOccultAltar() { for (int altarId : OCCULT_ALTAR_IDS) { - TileObject altar = Rs2GameObject.findObjectById(altarId); + Rs2TileObjectModel altar = Microbot.getRs2TileObjectCache().query().withId(altarId).nearest(); if (altar != null) { return altar; } @@ -2704,7 +2693,7 @@ private void handleCombat() { Rs2NpcModel anyAttacker = findAnyNpcAttackingUs(); if (anyAttacker != null) { log.info("Fallback: Attacking {} that is attacking us (at task location)", anyAttacker.getName()); - if (Rs2Npc.interact(anyAttacker, "Attack")) { + if (anyAttacker.click("Attack")) { sleepUntil(Rs2Player::isInteracting, 1000); } } @@ -2720,7 +2709,7 @@ private void handleCombat() { Rs2NpcModel superior = findNearbySuperior(); if (superior != null) { log.info("Superior monster detected: {}! Attacking.", superior.getName()); - if (Rs2Npc.interact(superior, "Attack")) { + if (superior.click("Attack")) { sleepUntil(Rs2Player::isInteracting, 1000); } return; @@ -2730,10 +2719,9 @@ private void handleCombat() { // Check if we're already in combat with a living target Actor currentInteracting = Rs2Player.getInteracting(); if (currentInteracting != null) { - // Verify the target is actually alive - don't idle on dead NPCs boolean targetAlive = true; - if (currentInteracting instanceof Rs2NpcModel) { - Rs2NpcModel npc = (Rs2NpcModel) currentInteracting; + if (currentInteracting instanceof net.runelite.api.NPC) { + net.runelite.api.NPC npc = (net.runelite.api.NPC) currentInteracting; targetAlive = !npc.isDead() && npc.getHealthRatio() != 0; } if (targetAlive) { @@ -2750,7 +2738,7 @@ private void handleCombat() { Rs2NpcModel attacker = findNpcAttackingUs(targetMonsters); if (attacker != null) { log.info("Retaliating against target monster: {}", attacker.getName()); - if (Rs2Npc.interact(attacker, "Attack")) { + if (attacker.click("Attack")) { sleepUntil(Rs2Player::isInteracting, 1000); } return; @@ -2763,7 +2751,7 @@ private void handleCombat() { Rs2NpcModel anyAttacker = findAnyNpcAttackingUs(); if (anyAttacker != null) { log.info("Fallback: Retaliating against {} (at task location, not in target list)", anyAttacker.getName()); - if (Rs2Npc.interact(anyAttacker, "Attack")) { + if (anyAttacker.click("Attack")) { sleepUntil(Rs2Player::isInteracting, 1000); } return; @@ -2775,17 +2763,18 @@ private void handleCombat() { // Find attackable NPCs matching target monsters // Prioritize NPCs that are already attacking us (interacting with player) - List attackableNpcs = Rs2Npc.getAttackableNpcs(true) - .filter(npc -> npc.getName() != null) - .filter(npc -> targetMonsters.stream() + List attackableNpcs = Microbot.getRs2NpcCache().query() + .where(npc -> !npc.isDead()) + .where(npc -> npc.getName() != null) + .where(npc -> targetMonsters.stream() .anyMatch(monster -> matchesTargetMonster(npc.getName(), monster))) - .filter(npc -> taskDestination == null || + .where(npc -> taskDestination == null || npc.getWorldLocation().distanceTo(taskDestination) <= config.attackRadius()) + .toList() + .stream() .sorted(Comparator - // First priority: NPCs already attacking us (interacting with player) .comparingInt((Rs2NpcModel npc) -> npc.getInteracting() == Microbot.getClient().getLocalPlayer() ? 0 : 1) - // Second priority: closest distance .thenComparingInt(npc -> Rs2Player.getWorldLocation().distanceTo(npc.getWorldLocation()))) .collect(Collectors.toList()); @@ -2794,10 +2783,13 @@ private void handleCombat() { log.debug("No attackable slayer monsters found nearby matching: {}", targetMonsters); // Debug: log nearby NPCs to help diagnose - List nearbyNpcNames = Rs2Npc.getAttackableNpcs(true) - .filter(npc -> npc.getName() != null) - .filter(npc -> taskDestination == null || + List nearbyNpcNames = Microbot.getRs2NpcCache().query() + .where(npc -> !npc.isDead()) + .where(npc -> npc.getName() != null) + .where(npc -> taskDestination == null || npc.getWorldLocation().distanceTo(taskDestination) <= config.attackRadius()) + .toList() + .stream() .map(Rs2NpcModel::getName) .distinct() .collect(Collectors.toList()); @@ -2847,7 +2839,7 @@ private void handleCombat() { // Attack the first NPC (prioritizes those attacking us, then closest) Rs2NpcModel target = attackableNpcs.get(0); - if (Rs2Npc.interact(target, "Attack")) { + if (target.click("Attack")) { log.info("Attacking {}", target.getName()); sleepUntil(Rs2Player::isInteracting, 1000); } @@ -2939,8 +2931,9 @@ private int getHopWorld() { * Checks if any NPC is currently attacking the player. */ private boolean isBeingAttacked() { - return Rs2Npc.getNpcsForPlayer() - .anyMatch(npc -> npc.getInteracting() == Microbot.getClient().getLocalPlayer()); + return Microbot.getRs2NpcCache().query() + .where(npc -> npc.getInteracting() == Microbot.getClient().getLocalPlayer()) + .count() > 0; } /** @@ -2948,15 +2941,14 @@ private boolean isBeingAttacked() { * Used when we're "in combat" (being attacked) but not attacking back. */ private Rs2NpcModel findNpcAttackingUs(List targetMonsters) { - return Rs2Npc.getNpcsForPlayer() - .filter(npc -> npc.getName() != null) - .filter(npc -> npc.getInteracting() == Microbot.getClient().getLocalPlayer()) - .filter(npc -> targetMonsters.stream() + return Microbot.getRs2NpcCache().query() + .where(npc -> npc.getName() != null) + .where(npc -> npc.getInteracting() == Microbot.getClient().getLocalPlayer()) + .where(npc -> targetMonsters.stream() .anyMatch(monster -> matchesTargetMonster(npc.getName(), monster))) - .filter(npc -> taskDestination == null || + .where(npc -> taskDestination == null || npc.getWorldLocation().distanceTo(taskDestination) <= config.attackRadius() + 5) - .findFirst() - .orElse(null); + .first(); } /** @@ -2964,12 +2956,11 @@ private Rs2NpcModel findNpcAttackingUs(List targetMonsters) { * Used as a fallback when variant/target list doesn't match. */ private Rs2NpcModel findAnyNpcAttackingUs() { - return Rs2Npc.getNpcsForPlayer() - .filter(npc -> npc.getName() != null) - .filter(npc -> npc.getInteracting() == Microbot.getClient().getLocalPlayer()) - .filter(npc -> !npc.isDead()) - .findFirst() - .orElse(null); + return Microbot.getRs2NpcCache().query() + .where(npc -> npc.getName() != null) + .where(npc -> npc.getInteracting() == Microbot.getClient().getLocalPlayer()) + .where(npc -> !npc.isDead()) + .first(); } /** @@ -3033,14 +3024,15 @@ private void handleAoeCombat() { return; } - List nearbyMonsters = Rs2Npc.getNpcsForPlayer() - .filter(npc -> npc.getName() != null) - .filter(npc -> targetMonsters.stream() + List nearbyMonsters = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getInteracting() == Microbot.getClient().getLocalPlayer()) + .where(npc -> npc.getName() != null) + .where(npc -> targetMonsters.stream() .anyMatch(monster -> npc.getName().equalsIgnoreCase(monster))) - .filter(npc -> !npc.isDead()) - .filter(npc -> taskDestination == null || + .where(npc -> !npc.isDead()) + .where(npc -> taskDestination == null || npc.getWorldLocation().distanceTo(taskDestination) <= config.attackRadius()) - .collect(Collectors.toList()); + .toList(); if (nearbyMonsters.isEmpty()) { log.debug("No target monsters found nearby for AoE combat"); @@ -3081,7 +3073,7 @@ private void handleAoeCombat() { if (activeJsonProfile.shouldUseGoading() && bestTarget != null) { log.info("Attacking {} to trigger goading aggro ({} monsters nearby)", bestTarget.getName(), nearbyMonsters.size()); - if (Rs2Npc.interact(bestTarget, "Attack")) { + if (bestTarget.click("Attack")) { sleepUntil(Rs2Player::isInteracting, 1000); } return; @@ -3102,7 +3094,7 @@ private void handleAoeCombat() { log.info("Attacking {} with autocast {} ({} monsters stacked)", bestTarget.getName(), spell.name(), maxStackedCount); - if (Rs2Npc.interact(bestTarget, "Attack")) { + if (bestTarget.click("Attack")) { sleepUntil(Rs2Player::isInteracting, 1000); } } @@ -3209,11 +3201,14 @@ private String convertTaskNameToNpcName(String taskName) { * @return The superior NPC if found, null otherwise */ private Rs2NpcModel findNearbySuperior() { - return Rs2Npc.getAttackableNpcs(true) - .filter(npc -> npc.getName() != null) - .filter(npc -> SUPERIOR_MONSTERS.contains(npc.getName())) - .filter(npc -> taskDestination == null || - npc.getWorldLocation().distanceTo(taskDestination) <= config.attackRadius() + 5) // Slightly larger radius for superiors + return Microbot.getRs2NpcCache().query() + .where(npc -> !npc.isDead()) + .where(npc -> npc.getName() != null) + .where(npc -> SUPERIOR_MONSTERS.contains(npc.getName())) + .where(npc -> taskDestination == null || + npc.getWorldLocation().distanceTo(taskDestination) <= config.attackRadius() + 5) + .toList() + .stream() .min(Comparator.comparingInt(npc -> Rs2Player.getWorldLocation().distanceTo(npc.getWorldLocation()))) .orElse(null); @@ -3950,24 +3945,22 @@ private boolean fireCannon() { */ private boolean handleCannonPickup() { // Check if cannon still exists - var cannon = Rs2GameObject.findObject("Dwarf multicannon", true, 50, false, Rs2Player.getWorldLocation()); + var cannon = Microbot.getRs2TileObjectCache().query().withName("Dwarf multicannon").within(Rs2Player.getWorldLocation(), 50).nearest(); if (cannon == null) { log.info("Cannon already picked up or not found"); - return true; // Cannon doesn't exist, we're done + return true; } WorldPoint cannonLocation = cannon.getWorldLocation(); int distance = Rs2Player.getWorldLocation().distanceTo(cannonLocation); - // If too far, walk to cannon first if (distance > 5) { log.info("Walking to cannon to pick it up (distance: {})", distance); Rs2Walker.walkTo(cannonLocation, 2); - return false; // Still in progress + return false; } - // Try to pick up cannon - if (Rs2GameObject.interact(cannon, "Pick-up")) { + if (cannon.click("Pick-up")) { log.info("Picking up cannon..."); sleepUntil(() -> !isCannonPlacedNearby(), 5000); @@ -3990,7 +3983,7 @@ private boolean handleCannonPickup() { * @return true if pickup was initiated successfully */ private boolean pickupCannon() { - if (Rs2GameObject.interact("Dwarf multicannon", "Pick-up")) { + if (Microbot.getRs2TileObjectCache().query().withName("Dwarf multicannon").interact("Pick-up")) { log.info("Picking up cannon..."); return true; } @@ -4002,7 +3995,7 @@ private boolean pickupCannon() { * @return true if cannon object is found nearby */ private boolean isCannonPlacedNearby() { - return Rs2GameObject.findObject("Dwarf multicannon", true, 10, false, Rs2Player.getWorldLocation()) != null; + return Microbot.getRs2TileObjectCache().query().withName("Dwarf multicannon").within(Rs2Player.getWorldLocation(), 10).nearest() != null; } /** diff --git a/src/main/java/net/runelite/client/plugins/microbot/slayer/combat/SlayerFlickerScript.java b/src/main/java/net/runelite/client/plugins/microbot/slayer/combat/SlayerFlickerScript.java index 0d52fb6d15..62ad937f1c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/slayer/combat/SlayerFlickerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/slayer/combat/SlayerFlickerScript.java @@ -2,13 +2,13 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.events.NpcDespawned; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.slayer.PrayerFlickStyle; import net.runelite.client.plugins.microbot.slayer.SlayerConfig; import net.runelite.client.plugins.microbot.slayer.SlayerPrayer; 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.npc.Rs2NpcManager; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; 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; @@ -99,7 +99,9 @@ public void onGameTick() { } // Update NPC snapshot - npcsRef.set(Rs2Npc.getNpcsForPlayer().collect(Collectors.toList())); + npcsRef.set(Microbot.getRs2NpcCache().query() + .where(npc -> npc.getInteracting() == Microbot.getClient().getLocalPlayer()) + .toList()); // Remove monsters that no longer exist currentMonstersAttackingUsRef.updateAndGet(monsters -> { diff --git a/src/main/java/net/runelite/client/plugins/microbot/slayer/combat/SlayerMonster.java b/src/main/java/net/runelite/client/plugins/microbot/slayer/combat/SlayerMonster.java index 3ac43ca716..d0c1b7dae5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/slayer/combat/SlayerMonster.java +++ b/src/main/java/net/runelite/client/plugins/microbot/slayer/combat/SlayerMonster.java @@ -1,7 +1,7 @@ package net.runelite.client.plugins.microbot.slayer.combat; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcManager; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcStats; /** diff --git a/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaScript.java b/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaScript.java index c2263a186b..76da1a23e4 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaScript.java @@ -16,11 +16,9 @@ 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.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.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.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -361,14 +359,14 @@ private void getSupplies(int itemID, int requiredAmount) { Rs2Dialogue.clickOption("Take herblore supplies."); } else if (!Rs2Player.isAnimating()) { int SUPPLY_CRATE_ID = 51371; - Rs2GameObject.interact(SUPPLY_CRATE_ID, "Take herblore supplies"); + Microbot.getRs2TileObjectCache().query().interact(SUPPLY_CRATE_ID, "Take herblore supplies"); } sleep(300, 500); } } else { if (Rs2Player.isAnimating()) return; int GRUB_SAPLING_ID = 51365; - if (Rs2GameObject.interact(GRUB_SAPLING_ID, "Collect-from")) { + if (Microbot.getRs2TileObjectCache().query().interact(GRUB_SAPLING_ID, "Collect-from")) { sleepUntil(() -> Rs2Inventory.count(itemID) >= requiredAmount || Rs2Inventory.isFull(), 15000); if (Rs2Player.isAnimating() && Rs2Inventory.count(itemID) >= requiredAmount) { Rs2Walker.walkTo(Rs2Player.getWorldLocation()); @@ -481,7 +479,7 @@ private void handleGettingRunecraftingXp(SulphurNaguaConfig config) { return; } - var eytallali = Rs2Npc.getNpc(EYTALLALI_ID); + var eytallali = Microbot.getRs2NpcCache().query().withId(EYTALLALI_ID).nearest(); if (eytallali == null) { Microbot.log("Waiting for Eytallali to appear..."); sleep(600, 1000); @@ -493,7 +491,7 @@ private void handleGettingRunecraftingXp(SulphurNaguaConfig config) { return; } - if (Rs2Inventory.useItemOnNpc(SULPHUROUS_ESSENCE_ID, eytallali)) { + if (Rs2Inventory.useItemOnNpc(SULPHUROUS_ESSENCE_ID, EYTALLALI_ID)) { Microbot.log("Exchanging essence..."); sleepUntil(Rs2Dialogue::isInDialogue, 5000); } @@ -523,13 +521,13 @@ private void handleFighting(SulphurNaguaConfig config) { if (needsNewTarget) { if (getNaguaCombatArea() != null && getNaguaCombatArea().contains(Rs2Player.getWorldLocation())) { - var nagua = Rs2Npc.getNpcs("Sulphur Nagua") - .filter(n -> !n.isDead()) - .findFirst() - .orElse(null); + var nagua = Microbot.getRs2NpcCache().query() + .withName("Sulphur Nagua") + .where(n -> !n.isDead()) + .first(); if (nagua != null) { - if (Rs2Npc.attack(nagua)) { + if (nagua.click("Attack")) { sleepUntil(Rs2Player::isInCombat, 3000); totalNaguaKills++; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/tormenteddemons/TormentedDemonScript.java b/src/main/java/net/runelite/client/plugins/microbot/tormenteddemons/TormentedDemonScript.java index 7566910a17..b0ceb93a99 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tormenteddemons/TormentedDemonScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tormenteddemons/TormentedDemonScript.java @@ -2,6 +2,7 @@ import net.runelite.api.EquipmentInventorySlot; import net.runelite.api.HeadIcon; +import net.runelite.api.NPC; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; @@ -11,12 +12,10 @@ import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.equipment.JewelleryLocationEnum; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.grounditem.LootingParameters; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; 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; @@ -109,7 +108,7 @@ private void handleTravel(TormentedDemonConfig config) { case CLIMB_FIRST_STAIRS: Microbot.status = "Climbing first stairs..."; - if (Rs2GameObject.interact(53623, "Climb-up")) { + if (Microbot.getRs2TileObjectCache().query().interact(53623, "Climb-up")) { Rs2Player.waitForAnimation(); sleepUntil(() -> !Rs2Player.isAnimating()); travelStep = TravelStep.CLIMB_SECOND_STAIRS; @@ -118,7 +117,7 @@ private void handleTravel(TormentedDemonConfig config) { case CLIMB_SECOND_STAIRS: Microbot.status = "Climbing second stairs..."; - if (Rs2GameObject.interact(53624, "Climb-up")) { + if (Microbot.getRs2TileObjectCache().query().interact(53624, "Climb-up")) { Rs2Player.waitForAnimation(); sleepUntil(() -> !Rs2Player.isAnimating()); travelStep = TravelStep.CLIMB_THROUGH; @@ -127,7 +126,7 @@ private void handleTravel(TormentedDemonConfig config) { case CLIMB_THROUGH: Microbot.status = "Climbing through the path..."; - if (Rs2GameObject.interact(54082, "Climb-through")) { + if (Microbot.getRs2TileObjectCache().query().interact(54082, "Climb-through")) { Rs2Player.waitForAnimation(); sleepUntil(() -> !Rs2Player.isAnimating()); travelStep = TravelStep.LOCATION_THREE; @@ -157,7 +156,7 @@ private void handleBanking(TormentedDemonConfig config) { int maxPrayer = Microbot.getClient().getRealSkillLevel(Skill.PRAYER); if (currentHealth < maxHealth || currentPrayer < maxPrayer) { - if (Rs2GameObject.interact(FEROX_POOL_ID, "Drink")) { + if (Microbot.getRs2TileObjectCache().query().interact(FEROX_POOL_ID, "Drink")) { Rs2Player.waitForAnimation(); sleepUntil(() -> Microbot.getClient().getBoostedSkillLevel(Skill.HITPOINTS) == maxHealth && @@ -250,16 +249,17 @@ private void handleFighting(TormentedDemonConfig config) { Rs2Player.eatAt(config.minEatPercent()); Rs2Player.drinkPrayerPotionAt(config.minPrayerPercent()); - var interactingTarget = (Rs2NpcModel) Rs2Player.getInteracting(); + var interactingActor = Rs2Player.getInteracting(); + int interactingIndex = (interactingActor instanceof NPC) ? ((NPC) interactingActor).getIndex() : -1; if (currentTarget == null) return; - if (interactingTarget == null || interactingTarget.getIndex() != currentTarget.getIndex()) { - boolean attackSuccessful = Rs2Npc.interact(currentTarget, "attack"); + if (interactingActor == null || interactingIndex != currentTarget.getIndex()) { + boolean attackSuccessful = currentTarget.click("attack"); if (attackSuccessful) { Rs2Player.waitForAnimation(); - sleepUntil(() -> Rs2Player.getInteracting() != null && ((Rs2NpcModel) Rs2Player.getInteracting()).getIndex() == currentTarget.getIndex(), 3000); + sleepUntil(() -> Rs2Player.getInteracting() instanceof NPC && ((NPC) Rs2Player.getInteracting()).getIndex() == currentTarget.getIndex(), 3000); } else { logOnceToChat("Attack failed for target: " + (currentTarget != null ? currentTarget.getName() : "null")); currentTarget = null; @@ -320,9 +320,11 @@ private void switchOffensivePrayer(Rs2PrayerEnum newOffensivePrayer) { } private Rs2NpcModel findNewTarget(TormentedDemonConfig config) { - return Rs2Npc.getAttackableNpcs("Tormented Demon") - .filter(npc -> npc.getInteracting() == null || npc.getInteracting() == Microbot.getClient().getLocalPlayer()) - .filter(npc -> { + return Microbot.getRs2NpcCache().query() + .withName("Tormented Demon") + .where(npc -> !npc.isDead()) + .where(npc -> npc.getInteracting() == null || npc.getInteracting() == Microbot.getClient().getLocalPlayer()) + .where(npc -> { HeadIcon demonHeadIcon = npc.getHeadIcon(); if (demonHeadIcon != null) { switchGear(config, demonHeadIcon); @@ -331,8 +333,7 @@ private Rs2NpcModel findNewTarget(TormentedDemonConfig config) { logOnceToChat("Null HeadIcon for NPC " + npc.getName()); return false; }) - .findFirst() - .orElse(null); + .first(); } private void switchGear(TormentedDemonConfig config, HeadIcon combatNpcHeadIcon) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchKillerOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchKillerOverlay.java index 3aa6e21e42..8e58ec4794 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchKillerOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchKillerOverlay.java @@ -1,13 +1,13 @@ package net.runelite.client.plugins.microbot.virewatch; import net.runelite.api.Client; +import net.runelite.api.NPC; import net.runelite.api.Perspective; import net.runelite.api.TileObject; 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.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayLayer; import net.runelite.client.ui.overlay.OverlayPosition; @@ -16,7 +16,6 @@ import javax.inject.Inject; import java.awt.*; -import java.util.stream.Collectors; import static net.runelite.client.ui.overlay.OverlayUtil.renderPolygon; @@ -77,7 +76,8 @@ public Dimension render(Graphics2D graphics) { } if (!config.disableNPCOutline()) { - for (net.runelite.api.NPC npc : Rs2Npc.getAttackableNpcs("Vyrewatch Sentinel").collect(Collectors.toList())) { + for (var npcModel : Microbot.getRs2NpcCache().query().withName("Vyrewatch Sentinel").where(n -> !n.isDead()).toList()) { + NPC npc = npcModel.getNpc(); if (npc != null && npc.getCanvasTilePoly() != null) { if (!plugin.fightArea.contains(npc.getWorldLocation())) continue; diff --git a/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchScript.java b/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchScript.java index f10d022384..403ae30d68 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/virewatch/PVirewatchScript.java @@ -4,7 +4,6 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -34,13 +33,13 @@ public boolean run(PVirewatchKillerConfig config, PVirewatchKillerPlugin plugin) if(Microbot.getClient().getBoostedSkillLevel(Skill.PRAYER) <= config.prayAt()) { plugin.rechargingPrayer = true; - var statue = Rs2GameObject.getGameObject(39234); + var statue = Microbot.getRs2TileObjectCache().query().withId(39234).nearest(); if(statue != null) { Rs2Walker.walkTo(statue.getWorldLocation(), 1); - sleepUntil(() -> Rs2GameObject.hasLineOfSight(statue)); - if(Rs2GameObject.hasLineOfSight(statue)) { + sleepUntil(statue::isReachable); + if(statue.isReachable()) { Microbot.status = "RECHARGING PRAYER"; - Rs2GameObject.interact(39234); + statue.click(); sleep(100); plugin.rechargingPrayer = false; if(Rs2Player.isInteracting()) { From e52ac1b56b95eaf824d7f377d69cf41ecf002973 Mon Sep 17 00:00:00 2001 From: chsami Date: Thu, 9 Apr 2026 14:48:44 +0200 Subject: [PATCH 23/95] refactor: migrate skilling plugins to new query API Migrate Rs2Npc and Rs2GameObject calls across fishing (AerialFishing, AutoFishing, BarbarianFishing, BarbarianVillageFisher, EelFishing, Minnows, DriftNet), mining (AutoEssenceMining, SandMiner, ShootingStar), runecraft (Arceuus, Astral, ChillRunecraft, Ourania, FrostyRc), agility (Brimhaven, Prifddinas, Pyramid, Werewolf, Wilderness), and minigame (GOTR, Tempoross) plugins. Fix null-pointer chain in GotrScript portal interaction. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../aerialfishing/AerialFishingScript.java | 18 ++- .../agility/courses/BrimhavenSpikeCourse.java | 11 +- .../agility/courses/PrifddinasCourse.java | 6 +- .../agility/courses/PyramidCourse.java | 7 +- .../agility/courses/WerewolfCourse.java | 11 +- .../microbot/arceuusrc/ArceuusRcScript.java | 16 +- .../microbot/astralrc/AstralRunesScript.java | 24 ++- .../AutoEssenceMiningScript.java | 19 +-- .../autofishing/AutoFishingScript.java | 14 +- .../BarbarianFishingScript.java | 16 +- .../BarbarianVillageFisherScript.java | 29 ++-- .../chillRunecraft/AutoRunecraftScript.java | 5 +- .../microbot/driftnet/DriftNetScript.java | 14 +- .../microbot/eelfishing/EelFishingScript.java | 17 +-- .../plugins/microbot/frostyrc/RcScript.java | 97 +++++------- .../goldrush/GabulhasGoldRushScript.java | 5 +- .../plugins/microbot/gotr/GotrScript.java | 101 ++++++------ .../minnowsfishing/MinnowsScript.java | 10 +- .../microbot/ourania/OuraniaScript.java | 22 ++- .../sandminer/GabulhasSandMinerScript.java | 20 ++- .../shootingstar/ShootingStarScript.java | 30 ++-- .../microbot/tempoross/TemporossOverlay.java | 14 +- .../microbot/tempoross/TemporossPlugin.java | 5 +- .../microbot/tempoross/TemporossScript.java | 144 +++++++++--------- .../microbot/tempoross/TemporossWorkArea.java | 71 +++------ .../varrockanvil/VarrockAnvilScript.java | 3 +- .../WildernessAgilityScript.java | 97 ++++++------ 27 files changed, 380 insertions(+), 446 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/aerialfishing/AerialFishingScript.java b/src/main/java/net/runelite/client/plugins/microbot/aerialfishing/AerialFishingScript.java index a5d2cea7a8..d764c1038b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aerialfishing/AerialFishingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aerialfishing/AerialFishingScript.java @@ -14,10 +14,11 @@ import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; 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.npc.Rs2NpcModel; +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.List; import java.util.concurrent.TimeUnit; import static net.runelite.client.plugins.microbot.util.npc.Rs2Npc.validateInteractable; @@ -62,10 +63,10 @@ public boolean run(AerialFishingConfig config) { } if (!Rs2Camera.isTileOnScreen(fishingspot.getLocalLocation())) { - validateInteractable(fishingspot); + validateInteractable(fishingspot.getNpc()); } - if (Rs2Npc.interact(fishingspot)) { + if (fishingspot.click()) { if (sleepUntil(Rs2Player::isInteracting, 1200)) { sleepUntil(() -> Rs2Equipment.isWearing(ItemID.AERIAL_FISHING_GLOVES_BIRD), () -> { if ((Rs2Inventory.emptySlotCount() <= 1 && Rs2Equipment.isWearing(ItemID.AERIAL_FISHING_GLOVES_NO_BIRD)) || (Rs2Inventory.emptySlotCount() == 0 && Rs2Equipment.isWearing(ItemID.AERIAL_FISHING_GLOVES_BIRD))) { @@ -74,9 +75,9 @@ public boolean run(AerialFishingConfig config) { Rs2Inventory.hover(knife); } else { - NPC preHoverSpot = findPreHoverSpot(fishingspot); + Rs2NpcModel preHoverSpot = findPreHoverSpot(fishingspot); if (preHoverSpot != null) { - if (Rs2Npc.hoverOverActor(preHoverSpot)) { + if (Rs2Npc.hoverOverActor(preHoverSpot.getNpc())) { if (Rs2Random.dicePercentage(20)) { Microbot.getMouse().click(); @@ -96,11 +97,12 @@ public boolean run(AerialFishingConfig config) { private Rs2NpcModel findFishingSpot() { - return Rs2Npc.getNpc(NpcID.FISHING_SPOT_AERIAL); + return Microbot.getRs2NpcCache().query().withId(NpcID.FISHING_SPOT_AERIAL).nearest(); } - private NPC findPreHoverSpot(NPC exludedSpot) { - return Rs2Npc.getNpcs(NpcID.FISHING_SPOT_AERIAL).filter(x -> x != exludedSpot).findFirst().orElse(null); + private Rs2NpcModel findPreHoverSpot(Rs2NpcModel excludedSpot) { + List spots = Microbot.getRs2NpcCache().query().withId(NpcID.FISHING_SPOT_AERIAL).toList(); + return spots.stream().filter(x -> x != excludedSpot).findFirst().orElse(null); } private void cutFish() { diff --git a/src/main/java/net/runelite/client/plugins/microbot/agility/courses/BrimhavenSpikeCourse.java b/src/main/java/net/runelite/client/plugins/microbot/agility/courses/BrimhavenSpikeCourse.java index 0279f6a478..854fe51c57 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/agility/courses/BrimhavenSpikeCourse.java +++ b/src/main/java/net/runelite/client/plugins/microbot/agility/courses/BrimhavenSpikeCourse.java @@ -4,9 +4,7 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.agility.models.AgilityObstacleModel; import net.runelite.client.plugins.microbot.util.Global; -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.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -87,14 +85,13 @@ public boolean handlePayment() { } // Find and interact with Cap'n Izzy No-Beard - var captain = Rs2Npc.getNpc(CAPTAIN_IZZY_NPC_ID); + var captain = Microbot.getRs2NpcCache().query().withId(CAPTAIN_IZZY_NPC_ID).nearest(); if (captain == null) { Microbot.log("Cap'n Izzy No-Beard not found!"); return false; } - // Click "Pay" option - if (Rs2Npc.interact(captain, "Pay")) { + if (captain.click("Pay")) { Microbot.log("Attempting to pay Cap'n Izzy No-Beard..."); // Wait for coins to be deducted @@ -127,7 +124,7 @@ public boolean handleLadderDescent() { // Just click the ladder directly without detection Microbot.log("Force clicking ladder..."); - Rs2GameObject.interact(3617, "Climb-down"); + Microbot.getRs2TileObjectCache().query().interact(3617, "Climb-down"); // Wait a bit for the interaction Global.sleep(1000); @@ -281,7 +278,7 @@ public net.runelite.api.TileObject getCurrentObstacle() { return null; // This will trigger the timed tile-walking logic in handleWalkToStart } - var gameObject = Rs2GameObject.getGameObject(currentObstacle.getObjectID(), playerLocation, 10); + var gameObject = Microbot.getRs2TileObjectCache().query().withId(currentObstacle.getObjectID()).within(playerLocation, 10).nearest(); if (gameObject != null) { Microbot.log("Looking for obstacle " + (currentObstacleIndex + 1) + "/" + obstacles.size() + " (ID: " + currentObstacle.getObjectID() + ")"); return gameObject; diff --git a/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PrifddinasCourse.java b/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PrifddinasCourse.java index 1cf5b8f800..79c6e72c57 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PrifddinasCourse.java +++ b/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PrifddinasCourse.java @@ -65,10 +65,10 @@ public Integer getRequiredLevel() public boolean handlePortal() { - TileObject portal = Rs2GameObject.getGameObject(PORTAL_OBSTACLE_IDS.toArray(new Integer[0]), 10); - if (portal != null && Microbot.getClientThread().runOnClientThreadOptional(portal::getClickbox).isPresent()) + var portalModel = Microbot.getRs2TileObjectCache().query().withIds(PORTAL_OBSTACLE_IDS.stream().mapToInt(Integer::intValue).toArray()).within(10).nearest(); + if (portalModel != null && Microbot.getClientThread().runOnClientThreadOptional(portalModel::getClickbox).isPresent()) { - if (Rs2GameObject.interact(portal, "travel")) + if (portalModel.click("travel")) { Global.sleep(2000, 3000); return true; diff --git a/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PyramidCourse.java b/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PyramidCourse.java index 0728dcbaa3..b1411571b0 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PyramidCourse.java +++ b/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PyramidCourse.java @@ -11,8 +11,7 @@ import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; 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.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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; @@ -1126,7 +1125,7 @@ private boolean handlePyramidTurnIn() { } // Try to find Simon - Rs2NpcModel simon = Rs2Npc.getNpc(SIMON_NAME); + Rs2NpcModel simon = Microbot.getRs2NpcCache().query().withName(SIMON_NAME).nearest(); // If Simon is found and reachable, use pyramid top on him if (simon != null && Rs2GameObject.canReach(simon.getWorldLocation())) { @@ -1149,7 +1148,7 @@ private boolean handlePyramidTurnIn() { } } else { // Not in dialogue, use pyramid top on Simon - boolean used = Rs2Inventory.useItemOnNpc(ItemID.AGILITY_PYRAMID_GOLD_PYRAMID, simon); + boolean used = Rs2Inventory.useItemOnNpc(ItemID.AGILITY_PYRAMID_GOLD_PYRAMID, simon.getNpc()); if (used) { log.debug("Successfully used pyramid top on Simon"); Global.sleepUntil(() -> Rs2Dialogue.isInDialogue(), 3000); diff --git a/src/main/java/net/runelite/client/plugins/microbot/agility/courses/WerewolfCourse.java b/src/main/java/net/runelite/client/plugins/microbot/agility/courses/WerewolfCourse.java index 08c98c379c..ddc02b8810 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/agility/courses/WerewolfCourse.java +++ b/src/main/java/net/runelite/client/plugins/microbot/agility/courses/WerewolfCourse.java @@ -16,7 +16,6 @@ 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.misc.Operation; -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.walker.Rs2Walker; import org.slf4j.event.Level; @@ -119,9 +118,9 @@ public boolean handleFirstSteppingStone(WorldPoint playerWorldLocation) { else { // Login edge case where we end up in not defined walker area? if(Rs2Walker.walkTo(RESET_WORLD_POINT, 5)) // Try one more time return true; - var agilityBoss = Rs2Npc.getNpc(NpcID.WEREWOLF_TRAINER_START); // Try clicking on NPC to move to right area? + var agilityBoss = Microbot.getRs2NpcCache().query().withId(NpcID.WEREWOLF_TRAINER_START).nearest(); if(agilityBoss != null) { - Rs2Npc.interact(agilityBoss); + agilityBoss.click(); return true; } } @@ -182,15 +181,15 @@ public boolean handleStickReturn(WorldPoint playerWorldLocation) { private static void returnStick(WorldPoint playerWorldLocation) { if (Rs2Inventory.hasItem("Stick")) { - var stickNpc = Rs2Npc.getNpc(NpcID.WEREWOLF_TRAINER_STICK); + var stickNpc = Microbot.getRs2NpcCache().query().withId(NpcID.WEREWOLF_TRAINER_STICK).nearest(); if(stickNpc == null) { Rs2Walker.walkTo(STICK_NPC_WORLD_POINT, 5); - stickNpc = Rs2Npc.getNpc(NpcID.WEREWOLF_TRAINER_STICK); + stickNpc = Microbot.getRs2NpcCache().query().withId(NpcID.WEREWOLF_TRAINER_STICK).nearest(); } if (stickNpc != null) { if (playerWorldLocation.distanceTo(stickNpc.getWorldLocation()) > 5) Rs2Walker.walkTo(stickNpc.getWorldLocation(), 5); - Rs2Npc.interact(stickNpc, "Give-Stick"); + stickNpc.click("Give-Stick"); Rs2Player.waitForWalking(); } else { Microbot.log("Could not find stick NPC!", Level.WARN); diff --git a/src/main/java/net/runelite/client/plugins/microbot/arceuusrc/ArceuusRcScript.java b/src/main/java/net/runelite/client/plugins/microbot/arceuusrc/ArceuusRcScript.java index 487af9a1ad..c1465e6ac9 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/arceuusrc/ArceuusRcScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/arceuusrc/ArceuusRcScript.java @@ -2,7 +2,6 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.GameObject; import net.runelite.api.Skill; import net.runelite.api.coords.WorldArea; import net.runelite.api.coords.WorldPoint; @@ -12,7 +11,6 @@ import net.runelite.client.plugins.microbot.arceuusrc.enums.Altar; import net.runelite.client.plugins.microbot.breakhandler.BreakHandlerScript; import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; -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.util.player.Rs2Player; @@ -223,9 +221,9 @@ public String getAltarName() { } public void useAltar() { - final GameObject altar = Rs2GameObject.getGameObject(getAltarName(), true, 11); + var altar = Microbot.getRs2TileObjectCache().query().withName(getAltarName()).within(11).nearest(); if (altar != null) { - if (Rs2GameObject.interact(altar,"Bind")) Rs2Inventory.waitForInventoryChanges(6_000); + if (altar.click("Bind")) Rs2Inventory.waitForInventoryChanges(6_000); hasChippedEssence = Rs2Inventory.hasItem(DARK_ESSENCE_FRAGMENTS); } } @@ -303,10 +301,10 @@ public boolean chipEssenceFast() { } public void useDarkAltar() { - final GameObject darkAltar = Rs2GameObject.getGameObject(DARK_ALTAR, true, 11); + var darkAltar = Microbot.getRs2TileObjectCache().query().withName(DARK_ALTAR).within(11).nearest(); if (darkAltar == null) return; - Rs2GameObject.interact(darkAltar,"Venerate"); + darkAltar.click("Venerate"); sleepUntil(()->!Rs2Inventory.hasItem(DENSE_ESSENCE_BLOCK),6_000); } @@ -314,12 +312,12 @@ public void mineEssence() { if(getAltar() == Altar.BLOOD && !Rs2Inventory.hasItem(BLOOD_ESSENCE_ACTIVE)){ Rs2Inventory.interact(BLOOD_ESSENCE, "Activate"); } - final GameObject runeStone = Rs2GameObject.getGameObject(STR_DENSE_RUNESTONE, true, 11); - if (runeStone == null) { // should never happen bc shouldMineEssence checks for the runestone + var runeStone = Microbot.getRs2TileObjectCache().query().withName(STR_DENSE_RUNESTONE).within(11).nearest(); + if (runeStone == null) { Microbot.log("Cannot find runestone"); return; } - Rs2GameObject.interact(runeStone,"Chip"); + runeStone.click("Chip"); // this checks if we are gaining essence from mining final AtomicInteger emptyCount = new AtomicInteger(Rs2Inventory.emptySlotCount()); diff --git a/src/main/java/net/runelite/client/plugins/microbot/astralrc/AstralRunesScript.java b/src/main/java/net/runelite/client/plugins/microbot/astralrc/AstralRunesScript.java index 0482924117..813c54c485 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/astralrc/AstralRunesScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/astralrc/AstralRunesScript.java @@ -3,7 +3,6 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.Quest; import net.runelite.api.QuestState; -import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; import net.runelite.client.plugins.microbot.Microbot; @@ -14,7 +13,6 @@ 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.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.util.magic.*; @@ -167,9 +165,9 @@ public boolean run(AstralRunesConfig config) { switchInventoryTabIfNeeded(); var bankTileLoc = !dreamMentorComplete ? SEAL_OF_PASSAGE_BANKER : DREAM_MENTOR_BANKER; - TileObject bankTile = Rs2GameObject.getGameObject(bankTileLoc); + var bankTileModel = Microbot.getRs2TileObjectCache().query().within(bankTileLoc, 0).nearest(); Rs2Walker.walkFastCanvas(LUNAR_ISLE_BANK_WORLD_POINT); - if( bankTile != null && !Rs2Bank.isOpen() ) { + if( bankTileModel != null && !Rs2Bank.isOpen() ) { Rs2Bank.openBank(); updateRuneStates(); if( Rs2Inventory.hasItem(runeItemId) ) { @@ -177,7 +175,7 @@ public boolean run(AstralRunesConfig config) { } } else if( Rs2Player.distanceTo(bankTileLoc) > 2 ) { Rs2Walker.walkFastCanvas(bankTileLoc); - } else if( bankTile == null ) { + } else if( bankTileModel == null ) { Rs2Bank.openBank(); } return; @@ -378,9 +376,7 @@ private static boolean isLunarIsleRegion() { } private static boolean openLunarBank() { - var bankTileLoc = !(Rs2Player.getQuestState(Quest.DREAM_MENTOR) == QuestState.FINISHED) ? SEAL_OF_PASSAGE_BANKER : DREAM_MENTOR_BANKER; - TileObject bankTile = Rs2GameObject.getGameObject(bankTileLoc); - Rs2Bank.openBank(bankTile); + Rs2Bank.openBank(); sleepUntil(Rs2Bank::isOpen); return Rs2Bank.isOpen(); } @@ -389,9 +385,9 @@ private static boolean openLunarBank() { private void setSpellbookLunarAltar() { if( isLunarIsleRegion() ) { Rs2Walker.walkTo(ASTRAL_ALTAR_WORLD_POINT); - var altarGameObject = Rs2GameObject.getGameObject(ASTRAL_ALTAR_ID); - if( altarGameObject != null ) { - Rs2GameObject.interact(altarGameObject, "Pray"); + var altarModel = Microbot.getRs2TileObjectCache().query().withId(ASTRAL_ALTAR_ID).nearest(); + if( altarModel != null ) { + altarModel.click("Pray"); sleepUntil(this::isLunar); Rs2Random.wait(400, 800); canCastMoonclanTeleport = Rs2Spells.MOONCLAN_TELEPORT.hasRequirements() && Rs2Magic.hasRequiredRunes(Rs2Spells.MOONCLAN_TELEPORT); @@ -400,10 +396,10 @@ private void setSpellbookLunarAltar() { } private static void doAltarCraft() { - TileObject altarTile = Rs2GameObject.getGameObject(ASTRAL_ALTAR_WORLD_POINT); - if( altarTile != null && Rs2Player.getWorldLocation().distanceTo(ASTRAL_ALTAR_WORLD_POINT) < 5) { + var altarModel = Microbot.getRs2TileObjectCache().query().within(ASTRAL_ALTAR_WORLD_POINT, 0).nearest(); + if( altarModel != null && Rs2Player.getWorldLocation().distanceTo(ASTRAL_ALTAR_WORLD_POINT) < 5) { if( Rs2Inventory.hasItem(ItemID.BLANKRUNE_HIGH) ) { - Rs2GameObject.interact(altarTile); + altarModel.click(); Rs2Inventory.waitForInventoryChanges(800); } if( !Rs2Inventory.allPouchesEmpty() ) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/autoessencemining/AutoEssenceMiningScript.java b/src/main/java/net/runelite/client/plugins/microbot/autoessencemining/AutoEssenceMiningScript.java index 017e59755b..ea113cf937 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/autoessencemining/AutoEssenceMiningScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/autoessencemining/AutoEssenceMiningScript.java @@ -1,7 +1,6 @@ package net.runelite.client.plugins.microbot.autoessencemining; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.GameObject; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; @@ -10,10 +9,8 @@ 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.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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; @@ -166,10 +163,10 @@ private void handleTeleportingWithAubury() { } // find Aubury NPC - Rs2NpcModel aubury = Rs2Npc.getNpc("Aubury"); + Rs2NpcModel aubury = Microbot.getRs2NpcCache().query().withName("Aubury").nearest(); if (aubury != null) { log.info("Found Aubury, attempting teleport"); - if (Rs2Npc.interact(aubury, "Teleport")) { + if (aubury.click("Teleport")) { log.info("Clicked teleport, waiting for animation"); Rs2Player.waitForAnimation(3000); log.info("Teleport animation completed"); @@ -199,11 +196,11 @@ private void handleMiningEssence() { } // find essence rock to mine - GameObject essenceRock = Rs2GameObject.getGameObject("Rune Essence", false); - + var essenceRock = Microbot.getRs2TileObjectCache().query().withName("Rune Essence").nearest(); + if (essenceRock != null) { log.info("Found rune essence rock, attempting to mine"); - if (Rs2GameObject.interact(essenceRock, "Mine")) { + if (essenceRock.click("Mine")) { log.info("Started mining essence, waiting for XP drop"); boolean xpGained = Rs2Player.waitForXpDrop(Skill.MINING, true); if (xpGained) { @@ -233,11 +230,11 @@ private void handleUsingPortal() { } // find the portal to exit - GameObject portal = Rs2GameObject.getGameObject("Portal", false); + var portal = Microbot.getRs2TileObjectCache().query().withName("Portal").nearest(); if (portal != null) { log.info("Found portal, attempting to use it"); - if (Rs2GameObject.interact(portal)) { + if (portal.click()) { log.info("Clicked portal, waiting for teleport animation"); Rs2Player.waitForAnimation(3000); log.info("Successfully used portal to exit essence mine"); diff --git a/src/main/java/net/runelite/client/plugins/microbot/autofishing/AutoFishingScript.java b/src/main/java/net/runelite/client/plugins/microbot/autofishing/AutoFishingScript.java index 0cbe3c61bd..9c17b350da 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/autofishing/AutoFishingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/autofishing/AutoFishingScript.java @@ -2,8 +2,8 @@ import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.gameval.ObjectID; import net.runelite.api.gameval.ItemID; +import net.runelite.api.gameval.ObjectID; import net.runelite.api.Skill; import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; @@ -22,7 +22,7 @@ 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.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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 net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -124,9 +124,9 @@ private void handleFishing() { } activateSpec(); if (fishAction.isEmpty()) { - fishAction = Rs2Npc.getAvailableAction(fishingSpot, selectedFish.getActions()); + fishAction = Rs2Npc.getAvailableAction(new net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel(fishingSpot.getNpc()), selectedFish.getActions()); } - if (!fishAction.isEmpty() && Rs2Npc.interact(fishingSpot, fishAction)) { + if (!fishAction.isEmpty() && fishingSpot.click(fishAction)) { Rs2Player.waitForXpDrop(Skill.FISHING); Rs2Antiban.actionCooldown(); Rs2Antiban.takeMicroBreakByChance(); @@ -241,9 +241,9 @@ private void handleErrorRecovery() { */ private Rs2NpcModel findNearestFishingSpot() { int[] spotIds = selectedFish.getFishingSpot(); - return Rs2Npc.getNpcs(npc -> Arrays.stream(spotIds).anyMatch(id -> npc.getId() == id)) - .findFirst() - .orElse(null); + return Microbot.getRs2NpcCache().query() + .where(npc -> Arrays.stream(spotIds).anyMatch(id -> npc.getId() == id)) + .nearest(); } private List getRawFishInInventory() { diff --git a/src/main/java/net/runelite/client/plugins/microbot/barbarianfishing/BarbarianFishingScript.java b/src/main/java/net/runelite/client/plugins/microbot/barbarianfishing/BarbarianFishingScript.java index 9b8cc77f99..bd0a99e9e2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/barbarianfishing/BarbarianFishingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/barbarianfishing/BarbarianFishingScript.java @@ -14,7 +14,7 @@ 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.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import java.util.concurrent.TimeUnit; @@ -68,10 +68,10 @@ public boolean run(BarbarianFishingConfig config) { } if (!Rs2Camera.isTileOnScreen(fishingspot.getLocalLocation())) { - validateInteractable(fishingspot); + validateInteractable(fishingspot.getNpc()); } - if(Rs2Npc.interact(fishingspot, "Use-rod")) { + if(fishingspot.click("Use-rod")) { Rs2Antiban.actionCooldown(); Rs2Antiban.takeMicroBreakByChance(); }; @@ -85,13 +85,9 @@ public void onGameTick() { } private Rs2NpcModel findFishingSpot() { - for (int fishingSpotId : FishingSpot.BARB_FISH.getIds()) { - Rs2NpcModel fishingSpot = Rs2Npc.getNpc(fishingSpotId); - if (fishingSpot != null) { - return fishingSpot; - } - } - return null; + return Microbot.getRs2NpcCache().query() + .withIds(FishingSpot.BARB_FISH.getIds()) + .nearest(); } private void dropInventoryItems(BarbarianFishingConfig config) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/barbarianvillagefisher/BarbarianVillageFisherScript.java b/src/main/java/net/runelite/client/plugins/microbot/barbarianvillagefisher/BarbarianVillageFisherScript.java index b3b5265c6e..93e8417b5f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/barbarianvillagefisher/BarbarianVillageFisherScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/barbarianvillagefisher/BarbarianVillageFisherScript.java @@ -1,7 +1,6 @@ package net.runelite.client.plugins.microbot.barbarianvillagefisher; import net.runelite.api.Skill; -import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; import net.runelite.client.game.FishingSpot; import net.runelite.client.plugins.microbot.Microbot; @@ -14,11 +13,10 @@ import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -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.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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 net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -130,10 +128,10 @@ public boolean run(BarbarianVillageFisherConfig config) { } if (!Rs2Camera.isTileOnScreen(fishingSpot.getLocalLocation())) { - validateInteractable(fishingSpot); + validateInteractable(fishingSpot.getNpc()); } - if (Rs2Npc.interact(fishingSpot, fishingAction)) { + if (fishingSpot.click(fishingAction)) { debug("Interacted with fishing spot"); Rs2Antiban.actionCooldown(); Rs2Antiban.takeMicroBreakByChance(); @@ -255,13 +253,9 @@ private void determineState(BarbarianVillageFisherConfig config, BarbarianFishin // Locate the fishing spot and return the NPC private Rs2NpcModel findFishingSpot() { - for (int fishingSpotId : FishingSpot.SALMON.getIds()) { - var fishingSpot = Rs2Npc.getNpc(fishingSpotId); - if (fishingSpot != null) { - return fishingSpot; - } - } - return null; + return Microbot.getRs2NpcCache().query() + .withIds(FishingSpot.SALMON.getIds()) + .nearest(); } // Process for walking to the bank @@ -344,12 +338,11 @@ private boolean closeToLocation(WorldPoint location) { } private boolean isGameObjectOnTile(WorldPoint location, int id) { - // Return true if the specified tile contains the desired ID. - TileObject tile = Rs2GameObject.findGameObjectByLocation(location); - if (tile != null && id == tile.getId()) { - return true; - } - return false; + var result = Microbot.getRs2TileObjectCache().query() + .withId(id) + .within(location, 0) + .first(); + return result != null; } @Override diff --git a/src/main/java/net/runelite/client/plugins/microbot/chillRunecraft/AutoRunecraftScript.java b/src/main/java/net/runelite/client/plugins/microbot/chillRunecraft/AutoRunecraftScript.java index 46690b02b0..c3896bc640 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/chillRunecraft/AutoRunecraftScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/chillRunecraft/AutoRunecraftScript.java @@ -12,7 +12,6 @@ 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.math.Rs2Random; import net.runelite.client.plugins.microbot.util.player.Rs2Player; @@ -145,7 +144,7 @@ public boolean run(AutoRunecraftConfig config) } else { - Rs2GameObject.interact(altar.getAltarRuinsID(), "Enter"); + Microbot.getRs2TileObjectCache().query().withId(altar.getAltarRuinsID()).interact("Enter"); } Rs2Random.wait(800, 1600); sleepUntil(() -> !Rs2Player.isMoving()); @@ -167,7 +166,7 @@ public boolean run(AutoRunecraftConfig config) case EXITING_ALTAR: Microbot.status = "Exiting altar"; - Rs2GameObject.interact(altar.getPortalID(), "Use"); + Microbot.getRs2TileObjectCache().query().withId(altar.getPortalID()).interact("Use"); sleepUntil(() -> !Rs2Player.isMoving()); Rs2Random.wait(800, 1600); state = States.BANKING; diff --git a/src/main/java/net/runelite/client/plugins/microbot/driftnet/DriftNetScript.java b/src/main/java/net/runelite/client/plugins/microbot/driftnet/DriftNetScript.java index 3a2d5997d3..b54b15a7f3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/driftnet/DriftNetScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/driftnet/DriftNetScript.java @@ -1,7 +1,6 @@ package net.runelite.client.plugins.microbot.driftnet; import net.runelite.api.EquipmentInventorySlot; -import net.runelite.api.WallObject; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.NpcID; @@ -17,7 +16,7 @@ import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.menu.NewMenuEntry; import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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 net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -26,8 +25,8 @@ import java.awt.event.KeyEvent; import java.util.List; import java.util.*; -import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import java.util.concurrent.TimeUnit; import static net.runelite.client.plugins.microbot.Microbot.log; @@ -134,8 +133,7 @@ public void reEnterArea(){ if(driftnetPastFirstTunnelWP.equals(Rs2Player.getWorldLocation())){ Microbot.log("Navigating the Plant door"); - WallObject plantDoor = Rs2GameObject.getWallObject(30961); - Rs2GameObject.interact(plantDoor, "Navigate"); + Microbot.getRs2TileObjectCache().query().withId(30961).interact("Navigate"); sleepUntil(()-> Rs2Player.isAnimating(), Rs2Random.between(2000,5000)); sleepUntil(()-> !Rs2Player.isAnimating(), Rs2Random.between(2000,5000)); } @@ -164,7 +162,7 @@ public void reEnterArea(){ private void fetchNetsFromAnnette() { final int maxWeight = 25; // https://oldschool.runescape.wiki/w/Drift_net_fishing var maxDriftnets = maxWeight - Microbot.getClient().getWeight() - 1; // Driftnets are 1kg each; doing - 1 to be safe - Rs2GameObject.interact(ObjectID.FOSSIL_MERMAID_DRIFTNETS, "Nets"); + Microbot.getRs2TileObjectCache().query().withId(ObjectID.FOSSIL_MERMAID_DRIFTNETS).interact("Nets"); sleepUntil(() -> Rs2Widget.getWidget(20250629) != null); var annetteWidget = Rs2Widget.getWidget(20250629); var annetteWithdrawXMenuEntry = new NewMenuEntry(0, 20250629, 57, 3, 21652, "Drift net"); @@ -245,7 +243,7 @@ private void handleUnsetNet(DriftNet net) { */ private void chaseNearbyFish(Set fishSet) { // Sort the NPC indexes by distance to the player - var fishNpcs = Rs2Npc.getNpcs(NpcID.FOSSIL_FISH_SHOAL).collect(Collectors.toList()); + var fishNpcs = Microbot.getRs2NpcCache().query().withId(NpcID.FOSSIL_FISH_SHOAL).toList(); var fishIndexNpcMap = new HashMap(); fishSet.forEach(index -> { var fishNpc = fishNpcs.stream().filter(npc -> npc.getIndex() == index).findFirst().orElse(null); @@ -270,7 +268,7 @@ private void chaseNearbyFish(Set fishSet) { if (npc == null) continue; // Interact with the fish to "Chase" it - Rs2Npc.interact(npc, "Chase"); + npc.click("Chase"); sleepGaussian(1500, 300); break; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/eelfishing/EelFishingScript.java b/src/main/java/net/runelite/client/plugins/microbot/eelfishing/EelFishingScript.java index db2081f5df..9a988a62ea 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/eelfishing/EelFishingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/eelfishing/EelFishingScript.java @@ -1,6 +1,5 @@ package net.runelite.client.plugins.microbot.eelfishing; -import net.runelite.api.NPC; import net.runelite.api.gameval.ItemID; import net.runelite.client.game.FishingSpot; import net.runelite.client.plugins.microbot.Microbot; @@ -14,7 +13,7 @@ import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import java.util.concurrent.TimeUnit; @@ -60,10 +59,10 @@ public boolean run(EelFishingConfig config) { } if (!Rs2Camera.isTileOnScreen(fishingspot.getLocalLocation())) { - validateInteractable(fishingspot); + validateInteractable(fishingspot.getNpc()); } - if (Rs2Npc.interact(fishingspot)) { + if (fishingspot.click()) { Rs2Antiban.actionCooldown(); Rs2Antiban.takeMicroBreakByChance(); } @@ -78,13 +77,9 @@ public void onGameTick() { } private Rs2NpcModel findFishingSpot() { - for (int fishingSpotId : getFishingSpotIds(config.fishingSpot())) { - Rs2NpcModel fishingspot = Rs2Npc.getNpc(fishingSpotId); - if (fishingspot != null) { - return fishingspot; - } - } - return null; + int[] ids = getFishingSpotIds(config.fishingSpot()); + if (ids.length == 0) return null; + return Microbot.getRs2NpcCache().query().withIds(ids).nearest(); } private int[] getFishingSpotIds(EelFishingSpot spot) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/frostyrc/RcScript.java b/src/main/java/net/runelite/client/plugins/microbot/frostyrc/RcScript.java index b351f23352..739409caf1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/frostyrc/RcScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/frostyrc/RcScript.java @@ -18,7 +18,7 @@ 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.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; 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.magic.Rs2Magic; @@ -31,9 +31,7 @@ import java.awt.event.KeyEvent; import java.util.Arrays; import java.util.List; -import java.util.Objects; import java.util.concurrent.TimeUnit; -import java.util.stream.Collectors; public class RcScript extends Script { private final RcPlugin plugin; @@ -364,7 +362,7 @@ private void handleFeroxRunEnergy() { if (plugin.getMyWorldPoint().distanceTo(feroxPoolWp) < 5) { Microbot.log("Interacting with the Ferox pool"); - Rs2GameObject.interact(feroxPool, "Drink"); + Microbot.getRs2TileObjectCache().query().interact(feroxPool, "Drink"); } sleepUntil(() -> (!Rs2Player.isInteracting()) && !Rs2Player.isAnimating() && Rs2Player.getRunEnergy() > 90); sleepGaussian(1100, 200); @@ -396,15 +394,12 @@ private void handleArdyCloak() { sleepGaussian(900, 200); } - TileObject fairyRing = Rs2GameObject.getAll().stream() - .filter(Objects::nonNull) - .filter(obj -> obj.getLocalLocation().distanceTo(Microbot.getClient().getLocalPlayer().getLocalLocation()) < 5000) - .filter(obj -> { - ObjectComposition composition = Rs2GameObject.getObjectComposition(obj.getId()); - if (composition == null) return false; - return composition.getName().toLowerCase().contains("fairy"); + Rs2TileObjectModel fairyRing = Microbot.getRs2TileObjectCache().query() + .where(obj -> { + String name = obj.getName(); + return name != null && name.toLowerCase().contains("fairy"); }) - .findFirst().orElse(null); + .within(10).nearest(); if (plugin.getMyWorldPoint().distanceTo(monasteryFairyRing) < 7) { if (fairyRing == null) { @@ -413,7 +408,7 @@ private void handleArdyCloak() { return; } else { Microbot.log("Interacting with fairies"); - Rs2GameObject.interact(fairyRing, "Last-destination (DLS)"); + fairyRing.click("Last-destination (DLS)"); sleepUntil(() -> plugin.getMyWorldPoint().equals(caveFairyRing)); } } @@ -433,7 +428,7 @@ private void handleFarmingCape() { if (plugin.getMyWorldPoint().distanceTo(guildSpiritTreeLoc) > 10) { Rs2Walker.walkTo(guildSpiritTreeLoc); } else { - Rs2GameObject.interact(guildSpiritTree, "Travel"); + Microbot.getRs2TileObjectCache().query().interact(guildSpiritTree, "Travel"); sleepUntil(() -> Rs2Widget.isWidgetVisible(187, 3), 10000); sleepGaussian(1100, 200); @@ -453,12 +448,11 @@ private void handleFarmingCape() { if (Rs2Player.getRunEnergy() < 45) { sleepGaussian(700, 200); Microbot.log("We are thirsty..let us Drink"); - List poolObjectIds = Arrays.asList(29241, 29240, 29239, 29238, 29237); - poolObjectIds.stream().filter(Rs2GameObject::exists).findFirst() - .ifPresent(objectId -> { - Rs2GameObject.interact(objectId, "Drink"); - sleepUntil(() -> !Rs2Player.isInteracting() && Rs2Player.getRunEnergy() > 90); - }); + Rs2TileObjectModel poolObj = Microbot.getRs2TileObjectCache().query().withIds(29241, 29240, 29239, 29238, 29237).nearest(); + if (poolObj != null) { + poolObj.click("Drink"); + sleepUntil(() -> !Rs2Player.isInteracting() && Rs2Player.getRunEnergy() > 90); + } } if (Rs2Player.getRunEnergy() > 45) { if (config.runeType() == RuneType.BLOOD) { @@ -485,9 +479,9 @@ private void handleWrathWalking() { sleepUntil(() -> plugin.getMyWorldPoint().getRegionID() == mythicStatueRegion); sleepGaussian(600, 200); - GameObject statue = Rs2GameObject.get("Mythic Statue"); + Rs2TileObjectModel statue = Microbot.getRs2TileObjectCache().query().withName("Mythic Statue").nearest(); if (statue != null && !Rs2Player.isAnimating()) { - Rs2GameObject.interact(statue, "Teleport"); + statue.click("Teleport"); } if (plugin.getMyWorldPoint().getRegionID() == mythicStatueRegion) { @@ -497,7 +491,7 @@ private void handleWrathWalking() { Microbot.log("Current position " + plugin.getMyWorldPoint()); if (plugin.getMyWorldPoint() == outsideWrathRuins) { - Rs2GameObject.interact(wrathRuins, "Enter"); + Microbot.getRs2TileObjectCache().query().interact(wrathRuins, "Enter"); sleepUntil(() -> plugin.getMyWorldPoint().getRegionID() == wrathAltarRegion); } } @@ -558,12 +552,11 @@ private void handleGoingHome() { if (Rs2Player.getRunEnergy() < 45) { sleepGaussian(700, 200); Microbot.log("We are thirsty..let us Drink"); - List poolObjectIds = Arrays.asList(29241, 29240, 29239, 29238, 29237); - poolObjectIds.stream().filter(Rs2GameObject::exists).findFirst() - .ifPresent(objectId -> { - Rs2GameObject.interact(objectId, "Drink"); - sleepUntil(() -> !Rs2Player.isInteracting() && Rs2Player.getRunEnergy() > 90); - }); + Rs2TileObjectModel poolObj = Microbot.getRs2TileObjectCache().query().withIds(29241, 29240, 29239, 29238, 29237).nearest(); + if (poolObj != null) { + poolObj.click("Drink"); + sleepUntil(() -> !Rs2Player.isInteracting() && Rs2Player.getRunEnergy() > 90); + } } if (Rs2Player.getRunEnergy() > 45) { @@ -605,12 +598,11 @@ private void handleGoingHome() { if (Rs2Player.getRunEnergy() < 45) { sleepGaussian(700, 200); Microbot.log("We are thirsty..let us Drink"); - List poolObjectIds = Arrays.asList(29241, 29240, 29239, 29238, 29237); - poolObjectIds.stream().filter(Rs2GameObject::exists).findFirst() - .ifPresent(objectId -> { - Rs2GameObject.interact(objectId, "Drink"); - sleepUntil(() -> !Rs2Player.isInteracting() && Rs2Player.getRunEnergy() > 90); - }); + Rs2TileObjectModel poolObj = Microbot.getRs2TileObjectCache().query().withIds(29241, 29240, 29239, 29238, 29237).nearest(); + if (poolObj != null) { + poolObj.click("Drink"); + sleepUntil(() -> !Rs2Player.isInteracting() && Rs2Player.getRunEnergy() > 90); + } } if (Rs2Player.getRunEnergy() > 45) { @@ -623,26 +615,21 @@ private void handleGoingHome() { } private void handlePohFairyRing() { - if (Rs2GameObject.findObjectById(ObjectID.POH_FAIRY_RING) != null) { - Rs2GameObject.interact(ObjectID.POH_FAIRY_RING, "Last-destination (DLS)"); + if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.POH_FAIRY_RING).nearest() != null) { + Microbot.getRs2TileObjectCache().query().interact(ObjectID.POH_FAIRY_RING, "Last-destination (DLS)"); Microbot.log("Using fairy ring"); Rs2Player.waitForAnimation(1200); sleepUntil(() -> plugin.getMyWorldPoint().equals(caveFairyRing), 1200); state = State.WALKING_TO; } else { - List allGameObjects = Rs2GameObject.getAll().stream() - .filter(Objects::nonNull) - .filter(obj -> obj.getLocalLocation().distanceTo(Microbot.getClient().getLocalPlayer().getLocalLocation()) < 5000) - .collect(Collectors.toList()); - - TileObject pohTreeRing = allGameObjects.stream() - .filter(obj -> { - ObjectComposition composition = Rs2GameObject.getObjectComposition(obj.getId()); - return composition != null && composition.getName().toLowerCase().contains("spirit"); + Rs2TileObjectModel pohTreeRing = Microbot.getRs2TileObjectCache().query() + .where(obj -> { + String name = obj.getName(); + return name != null && name.toLowerCase().contains("spirit"); }) - .findFirst().orElse(null); + .within(10).nearest(); if (pohTreeRing != null) { - Rs2GameObject.interact(pohTreeRing, "Ring-last-destination (DLS)"); + pohTreeRing.click("Ring-last-destination (DLS)"); Microbot.log("Using fairy tree"); Rs2Player.waitForAnimation(); sleepUntil(() -> plugin.getMyWorldPoint().equals(caveFairyRing)); @@ -671,7 +658,7 @@ private void handleWalking() { Microbot.log("Current location after waiting: " + plugin.getMyWorldPoint()); if (plugin.getMyWorldPoint().equals(caveFairyRing)) { sleepGaussian(900, 200); - Rs2GameObject.interact(16308, "Enter"); + Microbot.getRs2TileObjectCache().query().interact(16308, "Enter"); sleepUntil(() -> Rs2Player.getWorldLocation().equals(firstCaveExit), 1200); sleepGaussian(900, 200); } @@ -691,7 +678,7 @@ private void handleWalking() { sleepUntil(() -> plugin.getMyWorldPoint().equals(outsideBloodRuins74), 1200); } - TileObject ruins = Rs2GameObject.findObjectById(bloodRuins); + Rs2TileObjectModel ruins = Microbot.getRs2TileObjectCache().query().withId(bloodRuins).nearest(); if (plugin.getMyWorldPoint().equals(firstCaveExit) && Rs2Player.getRealSkillLevel(Skill.AGILITY) < 74) { Microbot.log("Walking to ruins: " + outsideBloodRuins73); @@ -713,10 +700,10 @@ private void handleCrafting() { } if (config.runeType() == RuneType.BLOOD) { - Rs2GameObject.interact(bloodRuins, "Enter"); + Microbot.getRs2TileObjectCache().query().interact(bloodRuins, "Enter"); sleepUntil(() -> !Rs2Player.isAnimating() && plugin.getMyWorldPoint().getRegionID() == bloodAltarRegion); sleepGaussian(700, 200); - Rs2GameObject.interact(bloodAltar, "Craft-rune"); + Microbot.getRs2TileObjectCache().query().interact(bloodAltar, "Craft-rune"); Rs2Player.waitForXpDrop(Skill.RUNECRAFT); plugin.updateXpGained(); handleEmptyPouch(); @@ -733,7 +720,7 @@ private void handleCrafting() { if (config.runeType() == RuneType.WRATH) { Microbot.log("Entering wrath ruins"); - Rs2GameObject.interact(wrathRuins, "Enter"); + Microbot.getRs2TileObjectCache().query().interact(wrathRuins, "Enter"); sleepUntil(() -> plugin.getMyWorldPoint().getRegionID() == wrathAltarRegion); sleepGaussian(1100, 200); Microbot.log("Crafting runes"); @@ -759,10 +746,10 @@ private void handleEmptyPouch() { Rs2Inventory.waitForInventoryChanges(600); sleepGaussian(700, 200); if (config.runeType() == RuneType.BLOOD) { - Rs2GameObject.interact(bloodAltar, "Craft-rune"); + Microbot.getRs2TileObjectCache().query().interact(bloodAltar, "Craft-rune"); } if (config.runeType() == RuneType.WRATH) { - Rs2GameObject.interact(wrathAltar, "Craft-rune"); + Microbot.getRs2TileObjectCache().query().interact(wrathAltar, "Craft-rune"); } Rs2Player.waitForXpDrop(Skill.RUNECRAFT); plugin.updateXpGained(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/goldrush/GabulhasGoldRushScript.java b/src/main/java/net/runelite/client/plugins/microbot/goldrush/GabulhasGoldRushScript.java index d8cd7b5fc6..756b88579b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/goldrush/GabulhasGoldRushScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/goldrush/GabulhasGoldRushScript.java @@ -8,7 +8,6 @@ import net.runelite.client.plugins.microbot.Script; 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; @@ -70,7 +69,7 @@ public boolean run(GabulhasGoldRushConfig config) { break; case USING_BARS: int currentXP = Microbot.getClient().getSkillExperience(Skill.SMITHING); - Rs2GameObject.interact(9100, "Put-ore-on"); + Microbot.getRs2TileObjectCache().query().withId(9100).interact("Put-ore-on"); while (Rs2Inventory.contains("Gold ore")) { sleep(100); } @@ -84,7 +83,7 @@ public boolean run(GabulhasGoldRushConfig config) { break; case RETRIEVING_BARS: Rs2Inventory.wield("Ice gloves"); - Rs2GameObject.interact(9092, "Take"); + Microbot.getRs2TileObjectCache().query().withId(9092).interact("Take"); Rs2Keyboard.keyPress(' '); while (!Rs2Inventory.contains("Gold bar")) { Rs2Keyboard.keyPress(32); 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 7fe8d85c8a..650d9a813d 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 @@ -21,8 +21,9 @@ import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; import net.runelite.client.plugins.microbot.util.magic.Rs2Spellbook; import net.runelite.client.plugins.microbot.util.math.Rs2Random; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -242,7 +243,7 @@ private boolean waitingForGameToStart(int timeToStart) { // Return to large mine if we were there before if (!isInLargeMine() && shouldMineGuardianRemains) { if (Rs2Walker.walkTo(new WorldPoint(3632, 9503, 0), 20)) { - Rs2GameObject.interact(ObjectID.RUBBLE_43724); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.RUBBLE_43724); return true; } } @@ -262,24 +263,26 @@ private boolean repairCells() { Rs2ItemModel cell = Rs2Inventory.get(CellType.PoweredCellList().stream().mapToInt(i -> i).toArray()); if (cell != null && isInMainRegion() && isInMiniGame() && !shouldMineGuardianRemains && !isInLargeMine() && !isInHugeMine()) { int cellTier = CellType.GetCellTier(cell.getId()); - List shieldCellIds = Rs2GameObject.getObjectIdsByName("cell_tile"); + List shieldCells = Microbot.getRs2TileObjectCache().query() + .where(o -> o.getName() != null && o.getName().toLowerCase().contains("cell_tile")) + .toList(); if (Rs2Inventory.hasItemAmount(GUARDIAN_ESSENCE, 10)) { - for (int shieldCellId : shieldCellIds) { - TileObject shieldCell = Rs2GameObject.getTileObject(shieldCellId); - if (shieldCell == null) continue; + for (Rs2TileObjectModel shieldCell : shieldCells) { if (CellType.GetShieldTier(shieldCell.getId()) < cellTier) { Microbot.log("Upgrading power cell at " + shieldCell.getWorldLocation()); - Rs2GameObject.interact(shieldCell, "Place-cell"); + shieldCell.click("Place-cell"); sleepUntil(() -> !Rs2Player.isMoving()); return true; } } } - shieldCellIds = shieldCellIds.stream().filter(id -> id != ObjectID.CELL_TILE_BROKEN).collect(Collectors.toList()); - int interactedObjectId = Rs2GameObject.interact(shieldCellIds); - if (interactedObjectId != -1) { - log("Using cell with id " + interactedObjectId); + Rs2TileObjectModel cellToUse = shieldCells.stream() + .filter(o -> o.getId() != ObjectID.CELL_TILE_BROKEN) + .findFirst().orElse(null); + if (cellToUse != null) { + cellToUse.click(); + log("Using cell with id " + cellToUse.getId()); sleep(Rs2Random.randomGaussian(1000, 300)); sleepUntil(() -> !Rs2Player.isMoving()); } @@ -291,7 +294,7 @@ private boolean repairCells() { private boolean powerUpGreatGuardian() { if (Rs2Inventory.hasItem("guardian stone") && !shouldMineGuardianRemains && !isInLargeMine() && !isInHugeMine()) { state = GotrState.POWERING_UP; - Rs2Npc.interact("The great guardian", "power-up"); + Microbot.getRs2NpcCache().query().withName("The great guardian").interact("power-up"); log("Powering up the great guardian..."); sleepUntil(Rs2Player::isAnimating); sleep(Rs2Random.randomGaussian(Rs2Random.between(1000, 2000), Rs2Random.between(100, 300))); @@ -311,7 +314,7 @@ private void takeUnchargedCells() { } } - Rs2GameObject.interact(ObjectID.UNCHARGED_CELLS_43732, "Take-10"); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.UNCHARGED_CELLS_43732, "Take-10"); log("Taking uncharged cells..."); Rs2Player.waitForAnimation(); } @@ -322,7 +325,7 @@ private boolean usePortal() { if (leaveLargeMine()) return true; Rs2Walker.walkFastCanvas(Microbot.getClient().getHintArrowPoint()); sleepUntil(Rs2Player::isMoving); - Rs2GameObject.interact(Microbot.getClient().getHintArrowPoint()); + Microbot.getRs2TileObjectCache().query().within(Microbot.getClient().getHintArrowPoint(), 0).interact(); log("Found a portal spawn...interacting with it..."); Rs2Player.waitForWalking(); sleepUntil(() -> isInHugeMine()); @@ -335,7 +338,7 @@ private boolean usePortal() { private boolean depositRunesIntoPool() { if (config.shouldDepositRunes() && Rs2Inventory.hasItem(runeIds.stream().mapToInt(i -> i).toArray()) && !isInLargeMine() && !isInHugeMine() && !Rs2Inventory.isFull() && !optimizedEssenceLoop) { if (Rs2Player.isMoving()) return true; - if (Rs2GameObject.interact(ObjectID.DEPOSIT_POOL)) { + if (Microbot.getRs2TileObjectCache().query().interact(ObjectID.DEPOSIT_POOL)) { log("Deposit runes into pool..."); sleep(600, 2400); } @@ -359,7 +362,7 @@ private boolean enterAltar() { } private boolean craftGuardianEssences() { - if (Rs2GameObject.interact(ObjectID.WORKBENCH_43754)) { + if (Microbot.getRs2TileObjectCache().query().interact(ObjectID.WORKBENCH_43754)) { state = GotrState.CRAFT_GUARDIAN_ESSENCE; sleep(Rs2Random.randomGaussian(Rs2Random.between(600, 900), Rs2Random.between(150, 300))); log("Crafting guardian essences..."); @@ -370,7 +373,7 @@ private boolean craftGuardianEssences() { private boolean leaveLargeMine() { if (isInLargeMine()) { - Rs2GameObject.interact(ObjectID.RUBBLE_43726); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.RUBBLE_43726); Rs2Player.waitForAnimation(); log("Leaving large mine..."); state = GotrState.LEAVING_LARGE_MINE; @@ -402,7 +405,7 @@ private boolean isOutOfFragments() { private boolean craftRunes() { if (!isInMainRegion() && isInMiniGame()) { - TileObject rcAltar = findRcAltar(); + Rs2TileObjectModel rcAltar = findRcAltar(); if (rcAltar != null) { if (Rs2Player.isMoving()) return true; if (Rs2Inventory.anyPouchFull() && !Rs2Inventory.isFull()) { @@ -413,13 +416,13 @@ private boolean craftRunes() { if (Rs2Inventory.hasItem(GUARDIAN_ESSENCE)) { state = GotrState.CRAFTING_RUNES; optimizedEssenceLoop = false; - Rs2GameObject.interact(rcAltar.getId()); + Microbot.getRs2TileObjectCache().query().interact(rcAltar.getId()); log("Crafting runes on altar " + rcAltar.getId()); sleep(Rs2Random.randomGaussian(Rs2Random.between(1000, 1500), 300)); } else if (!Rs2Player.isMoving()) { state = GotrState.LEAVING_ALTAR; - TileObject rcPortal = findPortalToLeaveAltar(); - if (Rs2GameObject.interact(rcPortal.getId())) { + Rs2TileObjectModel rcPortal = findPortalToLeaveAltar(); + if (Microbot.getRs2TileObjectCache().query().interact(rcPortal.getId())) { log("Leaving the altar..."); sleepUntilTrue(GotrScript::isInMainRegion,100,10000); sleep(Rs2Random.randomGaussian(750, 150)); @@ -433,8 +436,8 @@ private boolean craftRunes() { private static boolean waitForMinigameToStart() { if (!isInMainRegion()) { - TileObject rcPortal = findPortalToLeaveAltar(); - if (rcPortal != null && Rs2GameObject.interact(rcPortal.getId())) { + Rs2TileObjectModel rcPortal = findPortalToLeaveAltar(); + if (rcPortal != null && Microbot.getRs2TileObjectCache().query().interact(rcPortal.getId())) { state = GotrState.LEAVING_ALTAR; return true; } @@ -443,13 +446,13 @@ private static boolean waitForMinigameToStart() { if (state != GotrState.WAITING) { state = GotrState.WAITING; log("Make sure to start the script near the minigame barrier."); - Rs2GameObject.interact(ObjectID.BARRIER_43849, "Peek"); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.BARRIER_43849, "Peek"); } return state == GotrState.WAITING; } private static boolean enterMinigame() { - if (Rs2GameObject.interact(ObjectID.BARRIER_43700, "quick-pass")) { + if (Microbot.getRs2TileObjectCache().query().interact(ObjectID.BARRIER_43700, "quick-pass")) { Rs2Player.waitForWalking(); state = GotrState.ENTER_GAME; GotrScript.shouldMineGuardianRemains = true; @@ -476,10 +479,10 @@ private boolean mineHugeGuardianRemain() { } if (!Rs2Inventory.isFull()) { if (!Rs2Player.isAnimating()) { - Rs2GameObject.interact(ObjectID.HUGE_GUARDIAN_REMAINS); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.HUGE_GUARDIAN_REMAINS); Rs2Player.waitForAnimation(); if (!Rs2Player.isAnimating()) - Rs2GameObject.interact(ObjectID.HUGE_GUARDIAN_REMAINS); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.HUGE_GUARDIAN_REMAINS); } } else { if (Rs2Inventory.allPouchesFull()) { @@ -490,7 +493,7 @@ private boolean mineHugeGuardianRemain() { Rs2Inventory.fillPouches(); sleep(Rs2Random.randomGaussian(Rs2Random.between(600, 1200), Rs2Random.between(100, 300))); if (!Rs2Inventory.isFull()) { - Rs2GameObject.interact(ObjectID.HUGE_GUARDIAN_REMAINS); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.HUGE_GUARDIAN_REMAINS); } } } @@ -515,13 +518,13 @@ private void mineGuardianRemains() { if (!isInLargeMine() && !isInHugeMine() && (!Rs2Inventory.hasItem(GUARDIAN_FRAGMENTS) || getStartTimer() == -1)) { if (Rs2Walker.walkTo(new WorldPoint(3632, 9503, 0), 20)) { log("Traveling to large mine..."); - Rs2GameObject.interact(ObjectID.RUBBLE_43724); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.RUBBLE_43724); if (sleepUntil(Rs2Player::isAnimating)) { sleepUntil(GotrScript::isInLargeMine); if (isInLargeMine()) { sleep(Rs2Random.randomGaussian(Rs2Random.between(2000, 2400), Rs2Random.between(100, 300))); log("Interacting with large guardian remains..."); - Rs2GameObject.interact(ObjectID.LARGE_GUARDIAN_REMAINS); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.LARGE_GUARDIAN_REMAINS); sleepGaussian(1200, 150); } } @@ -535,7 +538,7 @@ private void mineGuardianRemains() { checkPouches(Rs2Random.between(1, 20) == 2, Rs2Random.between(100, 600), Rs2Random.between(100, 300)); repairPouches(); - Rs2GameObject.interact(ObjectID.LARGE_GUARDIAN_REMAINS); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.LARGE_GUARDIAN_REMAINS); sleepGaussian(1200, 150); } } @@ -549,7 +552,7 @@ private void mineGuardianRemains() { Rs2Combat.setSpecState(true, 1000); } repairPouches(); - Rs2GameObject.interact(ObjectID.GUARDIAN_PARTS_43716); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.GUARDIAN_PARTS_43716); sleepGaussian(1200, 150); // we can assume that if the player is mining within the startTimer range, he will get enough guardian remains for the game shouldMineGuardianRemains = false; @@ -558,7 +561,7 @@ private void mineGuardianRemains() { } private void leaveHugeMine() { - Rs2GameObject.interact(38044); + Microbot.getRs2TileObjectCache().query().interact(38044); log("Leave huge mine..."); Global.sleepUntil(() -> !isInHugeMine(), 5000); @@ -582,11 +585,11 @@ private static boolean repairPouches() { private static void repairWithCordelia() { if (!Rs2Inventory.hasDegradedPouch()) return; if (!Rs2Inventory.hasItem(ItemID.ABYSSAL_PEARLS)) return; - Rs2NpcModel pouchRepairNpc = Rs2Npc.getNpc(NpcID.APPRENTICE_CORDELIA_12180); + Rs2NpcModel pouchRepairNpc = Microbot.getRs2NpcCache().query().withId(NpcID.APPRENTICE_CORDELIA_12180).nearest(); if (pouchRepairNpc == null) return; if (!Rs2Npc.hasAction(pouchRepairNpc.getId(), "Repair")) return; - if (!Rs2Npc.canWalkTo(pouchRepairNpc, 10)) return; - if (!Rs2Npc.interact(pouchRepairNpc, "Repair")) return; + if (!Rs2Npc.canWalkTo(pouchRepairNpc.getNpc(), 10)) return; + if (!pouchRepairNpc.click("Repair")) return; Microbot.log("Repairing pouches..."); @@ -762,18 +765,18 @@ public static void resetPlugin() { Microbot.getClient().clearHintArrow(); } - public static TileObject findRcAltar() { - Integer[] altarIds = new Integer[] {ObjectID.ALTAR_34760, ObjectID.ALTAR_34761, ObjectID.ALTAR_34762, ObjectID.ALTAR_34763, ObjectID.ALTAR_34764, + public static Rs2TileObjectModel findRcAltar() { + return Microbot.getRs2TileObjectCache().query().withIds( + ObjectID.ALTAR_34760, ObjectID.ALTAR_34761, ObjectID.ALTAR_34762, ObjectID.ALTAR_34763, ObjectID.ALTAR_34764, ObjectID.ALTAR_34765, ObjectID.ALTAR_34766, ObjectID.ALTAR_34767, ObjectID.ALTAR_34768, ObjectID.ALTAR_34769, ObjectID.ALTAR_34770, - ObjectID.ALTAR_34771, ObjectID.ALTAR_34772, ObjectID.ALTAR_43479}; - return Rs2GameObject.findObject(altarIds); + ObjectID.ALTAR_34771, ObjectID.ALTAR_34772, ObjectID.ALTAR_43479).nearest(); } - public static TileObject findPortalToLeaveAltar() { - Integer[] altarIds = new Integer[] {ObjectID.PORTAL_34748, ObjectID.PORTAL_34749, ObjectID.PORTAL_34750, ObjectID.PORTAL_34751, ObjectID.PORTAL_34752, + public static Rs2TileObjectModel findPortalToLeaveAltar() { + return Microbot.getRs2TileObjectCache().query().withIds( + ObjectID.PORTAL_34748, ObjectID.PORTAL_34749, ObjectID.PORTAL_34750, ObjectID.PORTAL_34751, ObjectID.PORTAL_34752, ObjectID.PORTAL_34753, ObjectID.PORTAL_34754, ObjectID.PORTAL_34755, ObjectID.PORTAL_34756, ObjectID.PORTAL_34757, ObjectID.PORTAL_34758, - ObjectID.PORTAL_34758, ObjectID.PORTAL_34759, ObjectID.PORTAL_43478}; - return Rs2GameObject.findObject(altarIds); + ObjectID.PORTAL_34758, ObjectID.PORTAL_34759, ObjectID.PORTAL_43478).nearest(); } public static boolean leaveMinigame() { GotrScript.isInMiniGame = !isOutsideBarrier() && isInMainRegion(); @@ -781,16 +784,16 @@ public static boolean leaveMinigame() { return true; // Already outside the minigame, successfully left } if(isInLargeMine()) { - Rs2GameObject.interact(ObjectID.RUBBLE_43726); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.RUBBLE_43726); Rs2Player.waitForAnimation(); sleepUntil(()-> !isInLargeMine()); if (isInLargeMine()){ log("Failed to leave large mine, retrying..."); - return false;// Retry leaving large mine + return false; } - - } - Rs2GameObject.interact(ObjectID.BARRIER_43700, "quick-pass"); + + } + Microbot.getRs2TileObjectCache().query().interact(ObjectID.BARRIER_43700, "quick-pass"); Rs2Player.waitForWalking(); sleepUntil( ()-> {return !(!isOutsideBarrier() && isInMainRegion());}, 200); GotrScript.isInMiniGame = !isOutsideBarrier() && isInMainRegion(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/minnowsfishing/MinnowsScript.java b/src/main/java/net/runelite/client/plugins/microbot/minnowsfishing/MinnowsScript.java index 813c4ddd45..2ad069d752 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/minnowsfishing/MinnowsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/minnowsfishing/MinnowsScript.java @@ -10,7 +10,7 @@ import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import java.util.concurrent.TimeUnit; @@ -53,8 +53,8 @@ public boolean run() { TARGET_SPOT_ID = FISHING_SPOT_1_ID; } Microbot.status = "DODGING FLYING FISH"; - fishingspot = Rs2Npc.getNpc(TARGET_SPOT_ID); - Rs2Npc.interact(fishingspot, "Small Net"); + fishingspot = Microbot.getRs2NpcCache().query().withId(TARGET_SPOT_ID).nearest(); + if (fishingspot != null) fishingspot.click("Small Net"); Rs2Antiban.actionCooldown(); return; } @@ -63,8 +63,8 @@ public boolean run() { } Microbot.status = "INTERACTING"; - fishingspot = Rs2Npc.getNpc(TARGET_SPOT_ID); - Rs2Npc.interact(fishingspot, "Small Net"); + fishingspot = Microbot.getRs2NpcCache().query().withId(TARGET_SPOT_ID).nearest(); + if (fishingspot != null) fishingspot.click("Small Net"); Rs2Antiban.actionCooldown(); Rs2Antiban.takeMicroBreakByChance(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/ourania/OuraniaScript.java b/src/main/java/net/runelite/client/plugins/microbot/ourania/OuraniaScript.java index 1d9f035cdc..ee71e03bf8 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/ourania/OuraniaScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/ourania/OuraniaScript.java @@ -11,7 +11,6 @@ import java.util.stream.Stream; import javax.inject.Inject; import net.runelite.api.Constants; -import net.runelite.api.GameObject; import net.runelite.api.GameState; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; @@ -28,7 +27,6 @@ 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.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.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.inventory.RunePouchType; @@ -37,8 +35,7 @@ import net.runelite.client.plugins.microbot.util.magic.Rs2Spells; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.misc.Rs2Potion; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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 net.runelite.client.plugins.skillcalculator.skills.MagicAction; @@ -146,7 +143,7 @@ public boolean run() Rs2Inventory.emptyPouches(); return; } - Rs2GameObject.interact(ObjectID.RC_ZMI_DUNGEON_CRACKED_CENTER_ALTAR, "craft-rune"); + Microbot.getRs2TileObjectCache().query().withId(ObjectID.RC_ZMI_DUNGEON_CRACKED_CENTER_ALTAR).interact("craft-rune"); Rs2Inventory.waitForInventoryChanges(5000); break; case RESETTING: @@ -169,8 +166,7 @@ public boolean run() if (config.directInteract() && Microbot.isPluginEnabled(GpuPlugin.class)) { - GameObject ladder = Rs2GameObject.getGameObject(ObjectID.RC_ZMI_DUNGEON_ENTRANCE); - Rs2GameObject.interact(ladder, "Climb"); + Microbot.getRs2TileObjectCache().query().withId(ObjectID.RC_ZMI_DUNGEON_ENTRANCE).interact("Climb"); sleepUntil(this::isNearEniola, 20000); } else @@ -188,12 +184,12 @@ public boolean run() if (!Rs2Bank.isOpen()) { - Rs2NpcModel eniola = Rs2Npc.getNpc(NpcID.RC_ZMI_BANKER); + Rs2NpcModel eniola = Microbot.getRs2NpcCache().query().withId(NpcID.RC_ZMI_BANKER).nearest(); if (eniola == null) { return; } - Rs2Npc.interact(eniola, "bank"); + eniola.click("bank"); sleepUntil(Rs2Bank::isOpen, 3000); return; } @@ -338,7 +334,7 @@ else if (hasStaminaPotion) { if (config.directInteract() && Microbot.isPluginEnabled(GpuPlugin.class)) { - GameObject altarObject = Rs2GameObject.getGameObject(ObjectID.RC_ZMI_DUNGEON_CRACKED_CENTER_ALTAR, Constants.SCENE_SIZE); + var altarModel = Microbot.getRs2TileObjectCache().query().withId(ObjectID.RC_ZMI_DUNGEON_CRACKED_CENTER_ALTAR).within(Constants.SCENE_SIZE).nearest(); if (Rs2Camera.getPitch() < 210 || Rs2Camera.getPitch() > 280) { int randomPitch = Rs2Random.nextInt(220, 260, 1, false); @@ -351,7 +347,7 @@ else if (hasStaminaPotion) sleepUntil(() -> Rs2Camera.getZoom() == 128); } - Rs2GameObject.interact(altarObject, "craft-rune"); + if (altarModel != null) altarModel.click("craft-rune"); sleepUntil(this::isNearAltar, 30000); } else @@ -361,7 +357,7 @@ else if (hasStaminaPotion) } else { - Rs2GameObject.interact(ObjectID.RC_ZMI_DUNGEON_WALL_CRACK_ENTRANCE, "squeeze-through"); + Microbot.getRs2TileObjectCache().query().withId(ObjectID.RC_ZMI_DUNGEON_WALL_CRACK_ENTRANCE).interact("squeeze-through"); sleepUntil(this::isNearAltar, 10000); } break; @@ -452,7 +448,7 @@ private boolean isNearAltar() private boolean isNearEniola() { - Rs2NpcModel eniola = Rs2Npc.getNpc(NpcID.RC_ZMI_BANKER); + Rs2NpcModel eniola = Microbot.getRs2NpcCache().query().withId(NpcID.RC_ZMI_BANKER).nearest(); if (eniola == null) { return false; diff --git a/src/main/java/net/runelite/client/plugins/microbot/sandminer/GabulhasSandMinerScript.java b/src/main/java/net/runelite/client/plugins/microbot/sandminer/GabulhasSandMinerScript.java index 1fd3aa9b37..4bec5e1e21 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sandminer/GabulhasSandMinerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sandminer/GabulhasSandMinerScript.java @@ -1,7 +1,6 @@ package net.runelite.client.plugins.microbot.sandminer; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.GameObject; import net.runelite.api.GameState; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; @@ -12,7 +11,6 @@ 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.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.magic.Rs2Magic; import net.runelite.client.plugins.microbot.util.magic.Rs2Spellbook; @@ -111,17 +109,21 @@ private void miningLoop(GabulhasSandMinerConfig config) { if (firstRock) { WorldPoint innerMiningPoint = (Rs2Random.dicePercentage(50)) ? new WorldPoint(3164, 2905, 0) : new WorldPoint(3166, 2905, 0); - GameObject innerSandstoneRock = Rs2GameObject.getGameObject("Sandstone rocks", true, innerMiningPoint); - Rs2GameObject.interact(innerSandstoneRock, "Mine"); + var innerSandstoneRock = Microbot.getRs2TileObjectCache().query() + .withName("Sandstone rocks") + .nearest(innerMiningPoint, 0); + if (innerSandstoneRock != null) innerSandstoneRock.click("Mine"); Rs2Player.waitForXpDrop(Skill.MINING, 15000); Rs2Antiban.actionCooldown(); firstRock = false; continue; } } - GameObject sandstoneRock = Rs2GameObject.getGameObject("Sandstone rocks", true, miningPoint, 5); + var sandstoneRock = Microbot.getRs2TileObjectCache().query() + .withName("Sandstone rocks") + .nearest(miningPoint, 5); if (sandstoneRock != null) { - Rs2GameObject.interact(sandstoneRock, "Mine"); + sandstoneRock.click("Mine"); if (config.turboMode()) { Rs2Player.waitForXpDrop(Skill.MINING, 15000); } else { @@ -156,8 +158,10 @@ private void dropEmptyWaterskins() { private void deposit(GabulhasSandMinerConfig config) { if (!config.turboMode()) Rs2Walker.walkTo(grinder); - GameObject sandstoneRock = Rs2GameObject.findObject(26199, grinder); - Rs2GameObject.interact(sandstoneRock, "Deposit"); + var grinderObj = Microbot.getRs2TileObjectCache().query() + .withId(26199) + .nearest(grinder, 5); + if (grinderObj != null) grinderObj.click("Deposit"); while (Rs2Inventory.contains("Sandstone (1kg)", "Sandstone (2kg)", "Sandstone (5kg)", "Sandstone (10kg)") && super.isRunning()) { if (!config.turboMode()) { sleep(100, 3000); diff --git a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarScript.java b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarScript.java index 4b826739a2..29b0051451 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarScript.java @@ -7,10 +7,9 @@ import java.util.stream.Collectors; import javax.inject.Inject; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.GameObject; import net.runelite.api.GameState; -import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.breakhandler.BreakHandlerScript; @@ -28,7 +27,6 @@ import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; 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.Rs2Gembag; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; @@ -215,11 +213,11 @@ public boolean run() Rs2Combat.setSpecState(true, 1000); } - TileObject starObject = Rs2GameObject.getGameObject(currentStar.getObjectId()); + var starObject = Microbot.getRs2TileObjectCache().query().withId(currentStar.getObjectId()).nearest(); if (starObject != null) { - Rs2GameObject.interact(starObject, "mine"); + starObject.click("mine"); sleepUntil(Rs2Player::isAnimating); Rs2Antiban.actionCooldown(); Rs2Antiban.moveMouseOffScreen(); @@ -438,9 +436,11 @@ private ShootingStarState updateStarState() if (state == ShootingStarState.MINING) { - GameObject starObject = Rs2GameObject.getGameObject("crashed star", initialPlayerLocation, 10); + var starModel = Microbot.getRs2TileObjectCache().query() + .where(n -> n.getName() != null && n.getName().toLowerCase().contains("crashed star")) + .nearest(initialPlayerLocation, 10); - if (currentStar == null || starObject == null) + if (currentStar == null || starModel == null) { if (plugin.getSelectedStar().getTier() == 1) @@ -458,7 +458,7 @@ private ShootingStarState updateStarState() return ShootingStarState.WAITING_FOR_STAR; } - int _newTier = currentStar.getTierBasedOnObjectId(starObject.getId()); + int _newTier = currentStar.getTierBasedOnObjectId(starModel.getId()); currentStar.setTier(_newTier); plugin.updatePanelList(false); currentStar = selectedStar; @@ -491,22 +491,22 @@ private boolean hasStateChanged() // If the state is mining state, scan the crashed star game object & check if the game object id has updated. if (state == ShootingStarState.MINING) { - GameObject starObject = Rs2GameObject.getGameObject("crashed star", initialPlayerLocation, 10); - return hasStarGameObjectChanged(starObject); + var starModel = Microbot.getRs2TileObjectCache().query() + .where(n -> n.getName() != null && n.getName().toLowerCase().contains("crashed star")) + .nearest(initialPlayerLocation, 10); + return hasStarModelChanged(starModel); } return false; } - private boolean hasStarGameObjectChanged(GameObject starObject) + private boolean hasStarModelChanged(Rs2TileObjectModel starModel) { - // If the GameObject does not exist anymore - if (starObject == null) + if (starModel == null) { return true; } - // If the GameObject has updated to a new tier - return currentStar.getObjectId() != starObject.getId(); + return currentStar.getObjectId() != starModel.getId(); } private Pickaxe getBestPickaxe(List items) diff --git a/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossOverlay.java index 3c6dbad641..ee8e8d4454 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossOverlay.java @@ -9,7 +9,7 @@ import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.coords.Rs2WorldPoint; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayLayer; import net.runelite.client.ui.overlay.OverlayPosition; @@ -58,14 +58,14 @@ public Dimension render(Graphics2D graphics) { // Render NPC overlays if the list is not null if (npcList != null) { for (Rs2NpcModel npc : npcList) { - Rs2WorldPoint npcLocation = new Rs2WorldPoint(npc.getRuneliteNpc().getWorldLocation()); + Rs2WorldPoint npcLocation = new Rs2WorldPoint(npc.getWorldLocation()); Rs2WorldPoint playerLocation = new Rs2WorldPoint(Microbot.getClient().getLocalPlayer().getWorldLocation()); renderNpcOverlay(graphics, npc, Color.RED, npcLocation.distanceToPath(playerLocation.getWorldPoint()) + " tiles"); } } if (ammoList != null) { for (Rs2NpcModel npc : ammoList) { - Rs2WorldPoint npcLocation = new Rs2WorldPoint(npc.getRuneliteNpc().getWorldLocation()); + Rs2WorldPoint npcLocation = new Rs2WorldPoint(npc.getWorldLocation()); Rs2WorldPoint playerLocation = new Rs2WorldPoint(Microbot.getClient().getLocalPlayer().getWorldLocation()); renderNpcOverlay(graphics, npc, Color.RED, npcLocation.distanceToPath(playerLocation.getWorldPoint()) + " " + Text.removeTags(npc.getName())); } @@ -151,16 +151,14 @@ private void renderGameObject(Graphics2D graphics, GameObject object, Color colo // Add this method to render overlays for NPCs private void renderNpcOverlay(Graphics2D graphics, Rs2NpcModel npc, Color color, String label) { - if (npc == null || npc.getConvexHull() == null) { + if (npc == null || npc.getNpc() == null || npc.getNpc().getConvexHull() == null) { return; } - // Draw the NPC outline - Shape npcHull = npc.getConvexHull(); + Shape npcHull = npc.getNpc().getConvexHull(); OverlayUtil.renderPolygon(graphics, npcHull, color); - // Draw the label above the NPC - Point textLocation = npc.getCanvasTextLocation(graphics, label, npc.getLogicalHeight() + 40); + Point textLocation = npc.getNpc().getCanvasTextLocation(graphics, label, npc.getNpc().getLogicalHeight() + 40); if (textLocation != null) { OverlayUtil.renderTextLocation(graphics, textLocation, label, Color.WHITE); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossPlugin.java index 8e3eda85bf..9f882747ef 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossPlugin.java @@ -17,8 +17,7 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.PluginConstants; import net.runelite.client.plugins.microbot.tempoross.enums.HarpoonType; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.ui.overlay.OverlayManager; import java.util.regex.Pattern; @@ -118,7 +117,7 @@ public void onGameTick(GameTick e) { TemporossScript.updateAmmoCrateData(); TemporossScript.updateLastWalkPath(); - Rs2NpcModel doubleFishingSpot = Rs2Npc.getNpc(NpcID.FISHING_SPOT_10569); + Rs2NpcModel doubleFishingSpot = Microbot.getRs2NpcCache().query().withId(NpcID.FISHING_SPOT_10569).nearest(); if (TemporossScript.state == State.INITIAL_COOK && doubleFishingSpot != null) { TemporossScript.state = TemporossScript.state.next; diff --git a/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossScript.java b/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossScript.java index 3ee9bc51fe..2bab58e767 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossScript.java @@ -20,8 +20,8 @@ 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.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -131,8 +131,8 @@ private boolean hasHarpoon() { private void determineWorkArea() { if (workArea == null) { - Rs2NpcModel forfeitNpc = Rs2Npc.getNearestNpcWithAction("Forfeit"); - Rs2NpcModel ammoCrate = Rs2Npc.getNearestNpcWithAction("Fill"); + Rs2NpcModel forfeitNpc = Microbot.getRs2NpcCache().query().where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Forfeit")).nearest(); + Rs2NpcModel ammoCrate = Microbot.getRs2NpcCache().query().where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Fill")).nearest(); if (forfeitNpc == null || ammoCrate == null) { log("Can't find forfeit NPC or ammo crate"); @@ -150,21 +150,22 @@ private void determineWorkArea() { private void finishGame() { Rs2WorldPoint playerLocation = new Rs2WorldPoint(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation())); - Rs2NpcModel exitNpc = Rs2Npc.getNpcs() - .filter(value -> value.getComposition() != null - && value.getComposition().getActions() != null - && Arrays.asList(value.getComposition().getActions()).contains("Leave")) + Rs2NpcModel exitNpc = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null + && npc.getNpc().getComposition().getActions() != null + && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Leave")) + .toList().stream() .min(Comparator.comparingInt(value -> playerLocation.distanceToPath(value.getWorldLocation()))) .orElse(null); if (exitNpc != null) { int emptyBucketCount = Rs2Inventory.count(ItemID.BUCKET); if (emptyBucketCount > 0) { - if(Rs2GameObject.interact(41004, "Fill-bucket")) + if(Microbot.getRs2TileObjectCache().query().interact(41004, "Fill-bucket")) sleepUntil(() -> Rs2Inventory.count(ItemID.BUCKET) < 1); } - if (Rs2Npc.interact(exitNpc, "Leave")) { + if (exitNpc.click("Leave")) { reset(); sleepUntil(() -> !isInMinigame(), 15000); BreakHandlerScript.setLockState(false); @@ -190,9 +191,9 @@ private void reset(){ public void handleForfeit() { if ((INTENSITY >= 94 && state == State.THIRD_COOK)) { - var forfeitNpc = Rs2Npc.getNearestNpcWithAction("Forfeit"); + var forfeitNpc = Microbot.getRs2NpcCache().query().where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Forfeit")).nearest(); if (forfeitNpc != null) { - if (Rs2Npc.interact(forfeitNpc, "Forfeit")) { + if (forfeitNpc.click("Forfeit")) { sleepUntil(() -> !isInMinigame(), 15000); reset(); BreakHandlerScript.setLockState(false); @@ -202,9 +203,9 @@ public void handleForfeit() { } private void forfeit() { - var forfeitNpc = Rs2Npc.getNearestNpcWithAction("Forfeit"); + var forfeitNpc = Microbot.getRs2NpcCache().query().where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Forfeit")).nearest(); if (forfeitNpc != null) { - if (Rs2Npc.interact(forfeitNpc, "Forfeit")) { + if (forfeitNpc.click("Forfeit")) { sleepUntil(() -> !isInMinigame(), 15000); reset(); BreakHandlerScript.setLockState(false); @@ -282,7 +283,7 @@ private void fetchMissingItems() return; } - if (Rs2GameObject.interact(workArea.getHarpoonCrate(), "Take")) + if (workArea.getHarpoonCrate() != null && workArea.getHarpoonCrate().click("Take")) { log("Taking harpoon"); sleepUntil(this::hasHarpoon, 10000); @@ -306,7 +307,7 @@ private void fetchMissingItems() } sleepUntil(() -> Rs2Inventory.count(item -> item.getId() == ItemID.BUCKET || item.getId() == ItemID.BUCKET_OF_WATER) >= temporossConfig.buckets(),() -> { - if (Rs2GameObject.interact(workArea.getBucketCrate(), "Take")) { + if (workArea.getBucketCrate() != null && workArea.getBucketCrate().click("Take")) { log("Taking buckets"); Rs2Inventory.waitForInventoryChanges(3000); }},10000,300); @@ -327,7 +328,7 @@ private void fetchMissingItems() return; } - if (Rs2GameObject.interact(workArea.getPump(), "Use")) + if (workArea.getPump() != null && workArea.getPump().click("Use")) { log("Filling buckets"); sleepUntil(() -> Rs2Inventory.count(ItemID.BUCKET) <= 0, 10000); @@ -346,7 +347,7 @@ private void fetchMissingItems() return; } - if (Rs2GameObject.interact(workArea.getRopeCrate(), "Take")) + if (workArea.getRopeCrate() != null && workArea.getRopeCrate().click("Take")) { log("Taking rope"); sleepUntil(() -> Rs2Inventory.waitForInventoryChanges(10000)); @@ -365,7 +366,7 @@ private void fetchMissingItems() return; } - if (Rs2GameObject.interact(workArea.getHammerCrate(), "Take")) + if (workArea.getHammerCrate() != null && workArea.getHammerCrate().click("Take")) { log("Taking hammer"); sleepUntil(() -> Rs2Inventory.waitForInventoryChanges(10000)); @@ -374,7 +375,7 @@ private void fetchMissingItems() } private boolean isOnStartingBoat() { - TileObject startingLadder = Rs2GameObject.findObjectById(ObjectID.ROPE_LADDER_41305); + Rs2TileObjectModel startingLadder = Microbot.getRs2TileObjectCache().query().withId(ObjectID.ROPE_LADDER_41305).nearest(); if (startingLadder == null) { log("Failed to find starting ladder"); return false; @@ -389,7 +390,7 @@ private void handleEnterMinigame() { if (Rs2Player.isMoving() || Rs2Player.isAnimating()) { return; } - TileObject startingLadder = Rs2GameObject.findObjectById(ObjectID.ROPE_LADDER_41305); + Rs2TileObjectModel startingLadder = Microbot.getRs2TileObjectCache().query().withId(ObjectID.ROPE_LADDER_41305).nearest(); if (startingLadder == null) { log("Failed to find starting ladder"); return; @@ -397,17 +398,17 @@ private void handleEnterMinigame() { int emptyBucketCount = Rs2Inventory.count(ItemID.BUCKET); // If we are east of the ladder, interact with it to get on the boat if (!isOnStartingBoat()) { - if (Rs2GameObject.interact(startingLadder, ((emptyBucketCount > 0 && temporossConfig.solo()) || !temporossConfig.solo()) ? "Climb" : "Solo-start")) { + if (startingLadder.click(((emptyBucketCount > 0 && temporossConfig.solo()) || !temporossConfig.solo()) ? "Climb" : "Solo-start")) { BreakHandlerScript.setLockState(true); sleepUntil(() -> (isOnStartingBoat() || isInMinigame()), 15000); return; } } - TileObject waterPump = Rs2GameObject.findObjectById(ObjectID.WATER_PUMP_41000); + Rs2TileObjectModel waterPump = Microbot.getRs2TileObjectCache().query().withId(ObjectID.WATER_PUMP_41000).nearest(); if (waterPump != null && emptyBucketCount > 0) { - if (Rs2GameObject.interact(waterPump, "Use")) { + if (waterPump.click("Use")) { Rs2Player.waitForAnimation(5000); } } @@ -446,10 +447,10 @@ public static void handleWidgetInfo() { } public static void updateFireData(){ - List allFires = Rs2Npc - .getNpcs(npc -> Arrays.asList(npc.getComposition().getActions()).contains("Douse")) - .map(Rs2NpcModel::new) - .collect(Collectors.toList()); + List allFires = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null + && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Douse")) + .toList(); Rs2WorldPoint playerLocation = new Rs2WorldPoint(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation())); sortedFires = allFires.stream() .filter(y -> playerLocation.distanceToPath(y.getWorldLocation()) < 35) @@ -472,22 +473,22 @@ public static void updateCloudData(){ // update ammo crate data public static void updateAmmoCrateData(){ - List ammoCrates = Rs2Npc - .getNpcs() - .filter(npc -> Arrays.asList(npc.getComposition().getActions()).contains("Fill")) - .filter(npc -> npc.getWorldLocation().distanceTo(workArea.mastPoint) <= 4) - .filter(npc -> !inCloud(npc.getWorldLocation(),2)) - .map(Rs2NpcModel::new) - .collect(Collectors.toList()); + List ammoCrates = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null + && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Fill") + && npc.getWorldLocation().distanceTo(workArea.mastPoint) <= 4 + && !inCloud(npc.getWorldLocation(), 2)) + .toList(); TemporossOverlay.setAmmoList(ammoCrates); } public static void updateFishSpotData(){ // if double fishing spot is present, prioritize it - fishSpots = Rs2Npc.getNpcs() - .filter(npc -> npc.getId() == NpcID.FISHING_SPOT_10569 || npc.getId() == NpcID.FISHING_SPOT_10568 || npc.getId() == NpcID.FISHING_SPOT_10565) - .filter(npc -> !inCloud(npc.getRuneliteNpc().getWorldLocation(),2)) - .filter(npc -> npc.getWorldLocation().distanceTo(workArea.rangePoint) <= 20) + fishSpots = Microbot.getRs2NpcCache().query() + .withIds(NpcID.FISHING_SPOT_10569, NpcID.FISHING_SPOT_10568, NpcID.FISHING_SPOT_10565) + .where(npc -> !inCloud(npc.getWorldLocation(), 2) + && npc.getWorldLocation().distanceTo(workArea.rangePoint) <= 20) + .toList().stream() .sorted(Comparator .comparingInt(npc -> npc.getId() == NpcID.FISHING_SPOT_10569 ? 0 : 1)) .collect(Collectors.toList()); @@ -519,11 +520,11 @@ private void handleFires() { return; } if (Rs2Player.isInteracting()) { - if (Objects.equals(Rs2Player.getInteracting(), fire)) { + if (Objects.equals(Rs2Player.getInteracting(), fire.getNpc())) { return; } } - if (Rs2Npc.interact(fire, "Douse")) { + if (fire.click("Douse")) { log("Dousing fire"); sleepUntil(() -> !Rs2Player.isInteracting(), 3000); return; @@ -535,12 +536,12 @@ private void handleDamagedMast() { if (Rs2Player.isMoving() || Rs2Player.isInteracting() || (temporossConfig.hammer() && !Rs2Inventory.contains("Hammer")) || !temporossConfig.hammer()) return; - TileObject damagedMast = workArea.getBrokenMast(); + Rs2TileObjectModel damagedMast = workArea.getBrokenMast(); if(damagedMast == null) return; if (Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation().distanceTo(damagedMast.getWorldLocation())) <= 5) { sleep(600); - if (Rs2GameObject.interact(damagedMast, "Repair")) { + if (damagedMast.click("Repair")) { log("Repairing mast"); Rs2Player.waitForXpDrop(Skill.CONSTRUCTION, 2500); } @@ -551,12 +552,12 @@ private void handleDamagedTotem() { if (Rs2Player.isMoving() || Rs2Player.isInteracting() || (temporossConfig.hammer() && !Rs2Inventory.contains("Hammer")) || !temporossConfig.hammer()) return; - TileObject damagedTotem = workArea.getBrokenTotem(); + Rs2TileObjectModel damagedTotem = workArea.getBrokenTotem(); if(damagedTotem == null) return; if (Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation().distanceTo(damagedTotem.getWorldLocation())) <= 5) { sleep(600); - if (Rs2GameObject.interact(damagedTotem, "Repair")) { + if (damagedTotem.click("Repair")) { log("Repairing totem"); Rs2Player.waitForXpDrop(Skill.CONSTRUCTION, 2500); } @@ -564,7 +565,7 @@ private void handleDamagedTotem() { } private void handleTether() { - TileObject tether = workArea.getClosestTether(); + Rs2TileObjectModel tether = workArea.getClosestTether(); if (tether == null) { return; } @@ -572,10 +573,10 @@ private void handleTether() { ShortestPathPlugin.exit(); Rs2Walker.setTarget(null); String action = TemporossPlugin.incomingWave ? "Tether" : "Untether"; - Rs2Camera.turnTo(tether); + Rs2Camera.turnTo(tether.getLocalLocation()); if (action.equals("Tether")) { - if (Rs2GameObject.interact(tether, action)) { + if (tether.click(action)) { log(action + "ing"); sleepUntil(() -> TemporossPlugin.isTethered == TemporossPlugin.incomingWave, 3500); } @@ -588,7 +589,7 @@ private void handleTether() { } private void handleStateLoop() { - temporossPool = Rs2Npc.getNpcs().filter(npc -> npc.getId() == NpcID.SPIRIT_POOL).min(Comparator.comparingInt(x -> workArea.spiritPoolPoint.distanceTo(x.getWorldLocation()))).orElse(null); + temporossPool = Microbot.getRs2NpcCache().query().withId(NpcID.SPIRIT_POOL).toList().stream().min(Comparator.comparingInt(x -> workArea.spiritPoolPoint.distanceTo(x.getWorldLocation()))).orElse(null); boolean doubleFishingSpot = !fishSpots.isEmpty() && fishSpots.get(0).getId() == NpcID.FISHING_SPOT_10569; if (TemporossScript.state == State.INITIAL_COOK && doubleFishingSpot) { @@ -646,8 +647,8 @@ private void handleMainLoop() { .orElse(null); if (safeFishSpot != null) { - Rs2Camera.turnTo(safeFishSpot); - Rs2Npc.interact(safeFishSpot, "Harpoon"); + Rs2Camera.turnTo(safeFishSpot.getNpc()); + safeFishSpot.click("Harpoon"); Microbot.log("Moved to a " + (safeFishSpot.getId() == NpcID.FISHING_SPOT_10569 ? "double" : "single") + " fish spot."); @@ -679,8 +680,8 @@ private void handleMainLoop() { } } } - Rs2Camera.turnTo(fishSpot); - Rs2Npc.interact(fishSpot, "Harpoon"); + Rs2Camera.turnTo(fishSpot.getNpc()); + fishSpot.click("Harpoon"); log("Interacting with " + (fishSpot.getId() == NpcID.FISHING_SPOT_10569 ? "double" : "single") + " fish spot"); Rs2Player.waitForWalking(2000); } else { @@ -710,16 +711,15 @@ private void handleMainLoop() { case THIRD_COOK: isFilling = false; int rawFishCount = Rs2Inventory.count(ItemID.RAW_HARPOONFISH); - TileObject range = workArea != null ? workArea.getRange() : null; + Rs2TileObjectModel range = workArea != null ? workArea.getRange() : null; if (range != null && rawFishCount > 0) { if(Rs2Player.isInteracting()) { - if (Objects.equals(Rs2Player.getInteracting(), range)) - return; + return; } if (Rs2Player.isMoving() || Rs2Player.getAnimation() == AnimationID.COOKING_RANGE) { return; } - Rs2GameObject.interact(range, "Cook-at"); + range.click("Cook-at"); log("Interacting with range"); sleepUntil(Rs2Player::isAnimating, 5000); } else if (range == null) { @@ -734,13 +734,13 @@ private void handleMainLoop() { case EMERGENCY_FILL: case SECOND_FILL: case INITIAL_FILL: - List ammoCrates = Rs2Npc - .getNpcs() - .filter(npc -> npc.getComposition() != null && npc.getComposition().getActions() != null && Arrays.asList(npc.getComposition().getActions()).contains("Fill")) - .filter(npc -> npc.getWorldLocation().distanceTo(workArea.mastPoint) <= 4) - .filter(npc -> !inCloud(npc.getWorldLocation(),1)) - .map(Rs2NpcModel::new) - .collect(Collectors.toList()); + List ammoCrates = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null + && npc.getNpc().getComposition().getActions() != null + && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Fill") + && npc.getWorldLocation().distanceTo(workArea.mastPoint) <= 4 + && !inCloud(npc.getWorldLocation(), 1)) + .toList(); WorldPoint fillPlayerLoc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); if (inCloud(fillPlayerLoc,5) && !isFilling) { @@ -762,8 +762,8 @@ private void handleMainLoop() { log("In cloud, walking to safe point"); Rs2NpcModel ammoCrate = ammoCrates.stream() .max(Comparator.comparingInt(value -> new Rs2WorldPoint(value.getWorldLocation()).distanceToPath(fillPlayerLoc))).orElse(null); - Rs2Camera.turnTo(ammoCrate); - Rs2Npc.interact(ammoCrate, "Fill"); + Rs2Camera.turnTo(ammoCrate.getNpc()); + ammoCrate.click("Fill"); log("Switching ammo crate"); Rs2Player.waitForWalking(5000); isFilling = true; @@ -787,8 +787,8 @@ private void handleMainLoop() { return; } } - Rs2Camera.turnTo(ammoCrate.getActor()); - Rs2Npc.interact(ammoCrate, "Fill"); + Rs2Camera.turnTo(ammoCrate.getNpc()); + ammoCrate.click("Fill"); log("Interacting with ammo crate"); Rs2Inventory.waitForInventoryChanges(5000); isFilling = true; @@ -822,7 +822,7 @@ private void handleMainLoop() { // Log message when special energy is below 100% log("Special energy is below 100%, not using harpoon special attack."); } - Rs2Npc.interact(temporossPool, "Harpoon"); + temporossPool.click("Harpoon"); log("Harpooning Tempoross"); Rs2Player.waitForWalking(2000); } else { @@ -913,7 +913,7 @@ public boolean fightFiresInPath(WorldPoint location) { // Filter fires that are actually on the path. List firesInPath = sortedFires.stream() - .filter(fire -> walkerPath.stream().anyMatch(pathPoint -> fire.getWorldArea().contains(pathPoint))) + .filter(fire -> walkerPath.stream().anyMatch(pathPoint -> fire.getNpc().getWorldArea().contains(pathPoint))) .collect(Collectors.toList()); if (firesInPath.isEmpty()) { @@ -926,7 +926,7 @@ public boolean fightFiresInPath(WorldPoint location) { } for (Rs2NpcModel fire : firesInPath) { - if (Rs2Npc.interact(fire, "Douse")) { + if (fire.click("Douse")) { log("Dousing fire in path (mass world mode)"); sleepUntil(Rs2Player::isInteracting, 2000); sleepUntil(() -> !Rs2Player.isInteracting(), 10000); @@ -934,7 +934,7 @@ public boolean fightFiresInPath(WorldPoint location) { } // Return true if sortedFires does not contain any fires in the path. - return sortedFires.stream().noneMatch(fire -> walkerPath.stream().anyMatch(pathPoint -> fire.getWorldArea().contains(pathPoint))); + return sortedFires.stream().noneMatch(fire -> walkerPath.stream().anyMatch(pathPoint -> fire.getNpc().getWorldArea().contains(pathPoint))); } @Override diff --git a/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossWorkArea.java b/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossWorkArea.java index f13b225997..23038a16f7 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossWorkArea.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossWorkArea.java @@ -2,11 +2,10 @@ import net.runelite.api.NullObjectID; import net.runelite.api.ObjectID; -import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.coords.Rs2WorldPoint; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; public class TemporossWorkArea @@ -54,76 +53,56 @@ public TemporossWorkArea(WorldPoint exitNpc, boolean isWest) } } - public TileObject getBucketCrate() + public Rs2TileObjectModel getBucketCrate() { - return Rs2GameObject.findObject(ObjectID.BUCKETS, bucketPoint); + return Microbot.getRs2TileObjectCache().query().withId(ObjectID.BUCKETS).within(bucketPoint, 2).nearest(); } - public TileObject getPump() + public Rs2TileObjectModel getPump() { - return Rs2GameObject.findObject(ObjectID.WATER_PUMP_41000, pumpPoint); + return Microbot.getRs2TileObjectCache().query().withId(ObjectID.WATER_PUMP_41000).within(pumpPoint, 2).nearest(); } - public TileObject getRopeCrate() + public Rs2TileObjectModel getRopeCrate() { - return Rs2GameObject.findObject(ObjectID.ROPES, ropePoint); + return Microbot.getRs2TileObjectCache().query().withId(ObjectID.ROPES).within(ropePoint, 2).nearest(); } - public TileObject getHammerCrate() + public Rs2TileObjectModel getHammerCrate() { - return Rs2GameObject.findObject(ObjectID.HAMMERS_40964, hammerPoint); + return Microbot.getRs2TileObjectCache().query().withId(ObjectID.HAMMERS_40964).within(hammerPoint, 2).nearest(); } - public TileObject getHarpoonCrate() + public Rs2TileObjectModel getHarpoonCrate() { - return Rs2GameObject.findObject(ObjectID.HARPOONS, harpoonPoint); + return Microbot.getRs2TileObjectCache().query().withId(ObjectID.HARPOONS).within(harpoonPoint, 2).nearest(); } - public TileObject getMast() { - //WorldPoint localInstance = WorldPoint.toLocalInstance(Microbot.getClient().getTopLevelWorldView(),mastPoint).stream().findFirst().orElse(null); - TileObject mast = Rs2GameObject.findGameObjectByLocation(mastPoint); - if (mast != null && (mast.getId() == NullObjectID.NULL_41352 || mast.getId() == NullObjectID.NULL_41353)) { - return mast; - } - return null; + public Rs2TileObjectModel getMast() { + Rs2TileObjectModel mast = Microbot.getRs2TileObjectCache().query().withIds(NullObjectID.NULL_41352, NullObjectID.NULL_41353).within(mastPoint, 2).nearest(); + return mast; } - public TileObject getBrokenMast() { - //WorldPoint localInstance = WorldPoint.toLocalInstance(Microbot.getClient().getTopLevelWorldView(),mastPoint).stream().findFirst().orElse(null); - TileObject mast = Rs2GameObject.findGameObjectByLocation(mastPoint); - if (mast != null && (mast.getId() == ObjectID.DAMAGED_MAST_40996 || mast.getId() == ObjectID.DAMAGED_MAST_40997)) - return mast; - - return null; + public Rs2TileObjectModel getBrokenMast() { + return Microbot.getRs2TileObjectCache().query().withIds(ObjectID.DAMAGED_MAST_40996, ObjectID.DAMAGED_MAST_40997).within(mastPoint, 2).nearest(); } - public TileObject getTotem() { - //WorldPoint localInstance = WorldPoint.toLocalInstance(Microbot.getClient().getTopLevelWorldView(),totemPoint).stream().findFirst().orElse(null); - TileObject totem = Rs2GameObject.findGameObjectByLocation(totemPoint); - if (totem != null && (totem.getId() == NullObjectID.NULL_41355 || totem.getId() == NullObjectID.NULL_41354)) { - return totem; - } - return null; + public Rs2TileObjectModel getTotem() { + return Microbot.getRs2TileObjectCache().query().withIds(NullObjectID.NULL_41355, NullObjectID.NULL_41354).within(totemPoint, 2).nearest(); } - public TileObject getBrokenTotem() { - //WorldPoint localInstance = WorldPoint.toLocalInstance(Microbot.getClient().getTopLevelWorldView(),totemPoint).stream().findFirst().orElse(null); - TileObject totem = Rs2GameObject.findGameObjectByLocation(totemPoint); - if (totem != null && (totem.getId() == ObjectID.DAMAGED_TOTEM_POLE || totem.getId() == ObjectID.DAMAGED_TOTEM_POLE_41011)) - return totem; - - return null; + public Rs2TileObjectModel getBrokenTotem() { + return Microbot.getRs2TileObjectCache().query().withIds(ObjectID.DAMAGED_TOTEM_POLE, ObjectID.DAMAGED_TOTEM_POLE_41011).within(totemPoint, 2).nearest(); } - public TileObject getRange() + public Rs2TileObjectModel getRange() { - //WorldPoint localInstance = WorldPoint.toLocalInstance(Microbot.getClient().getTopLevelWorldView(),rangePoint).stream().findFirst().orElse(null); - return Rs2GameObject.findObject(ObjectID.SHRINE_41236, rangePoint); + return Microbot.getRs2TileObjectCache().query().withId(ObjectID.SHRINE_41236).within(rangePoint, 2).nearest(); } - public TileObject getClosestTether() { - TileObject mast = getMast(); - TileObject totem = getTotem(); + public Rs2TileObjectModel getClosestTether() { + Rs2TileObjectModel mast = getMast(); + Rs2TileObjectModel totem = getTotem(); if (mast == null) { return totem; diff --git a/src/main/java/net/runelite/client/plugins/microbot/varrockanvil/VarrockAnvilScript.java b/src/main/java/net/runelite/client/plugins/microbot/varrockanvil/VarrockAnvilScript.java index 10b6fef4a1..d70a3b217d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/varrockanvil/VarrockAnvilScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/varrockanvil/VarrockAnvilScript.java @@ -11,7 +11,6 @@ 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.dialogues.Rs2Dialogue; -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.walker.Rs2Walker; @@ -95,7 +94,7 @@ public boolean run(VarrockAnvilConfig config) { return; } - if (Rs2GameObject.interact(2097)) { + if (Microbot.getRs2TileObjectCache().query().withId(2097).interact()) { debug("Using anvil"); // Wait until anvil screen is open diff --git a/src/main/java/net/runelite/client/plugins/microbot/wildernessagility/WildernessAgilityScript.java b/src/main/java/net/runelite/client/plugins/microbot/wildernessagility/WildernessAgilityScript.java index c789d9a05f..0b0b781e9a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/wildernessagility/WildernessAgilityScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/wildernessagility/WildernessAgilityScript.java @@ -10,7 +10,6 @@ import net.runelite.api.coords.WorldPoint; import net.runelite.api.coords.WorldArea; -import net.runelite.api.TileObject; import net.runelite.client.plugins.microbot.*; import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; @@ -19,7 +18,7 @@ import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; import net.runelite.client.plugins.microbot.globval.WidgetIndices; @@ -80,7 +79,7 @@ public final class WildernessAgilityScript extends Script { private int dispenserLootAttempts = 0; private int dispenserTicketsBefore = 0; private int dispenserPreValue = 0; - private TileObject cachedDispenserObj = null; + private Rs2TileObjectModel cachedDispenserObj = null; private long lastObjectCheck = 0; // --- Rock Climbing Pose Detection --- @@ -258,13 +257,13 @@ public boolean run(WildernessAgilityConfig config) { waitingForRockClimbCompletion = false; // Now interact with dispenser - TileObject dispenser = cachedDispenserObj; + Rs2TileObjectModel dispenser = cachedDispenserObj; if (dispenser != null) { dispenserTicketsBefore = Rs2Inventory.itemQuantity(TICKET_ITEM_ID); dispenserPreValue = getInventoryValue(); dispenserLootAttempts = 1; waitingForDispenserLoot = true; - Rs2GameObject.interact(dispenser, "Search"); + dispenser.click("Search"); } } } @@ -501,30 +500,29 @@ public void setPlugin(WildernessAgilityPlugin plugin) { this.plugin = plugin; } - private TileObject getDispenserObj() { - return Rs2GameObject.getAll(o -> o.getId() == DISPENSER_ID, 104).stream().findFirst().orElse(null); + private Rs2TileObjectModel getDispenserObj() { + return Microbot.getRs2TileObjectCache().query().withId(DISPENSER_ID).nearest(); } - private TileObject getObstacleObj(int index) { - return Rs2GameObject.getAll(o -> o.getId() == obstacles.get(index).getObjectId(), 104).stream().findFirst().orElse(null); + private Rs2TileObjectModel getObstacleObj(int index) { + return Microbot.getRs2TileObjectCache().query().withId(obstacles.get(index).getObjectId()).nearest(); } private boolean isInUndergroundPit() { - // Check for the underground object that only exists in the pit - return Rs2GameObject.getAll(o -> o.getId() == UNDERGROUND_OBJECT_ID, 104).stream().findFirst().orElse(null) != null; + return Microbot.getRs2TileObjectCache().query().withId(UNDERGROUND_OBJECT_ID).nearest() != null; } private void recoverFromPit() { // First check if we're still in the pit using game object detection if (isInUndergroundPit()) { // Immediately refresh ladder object before attempting to interact - List ladders = Rs2GameObject.getAll(o -> o.getId() == 17385, 104); - TileObject ladderObj = ladders.isEmpty() ? null : ladders.get(0); + List ladders = Microbot.getRs2TileObjectCache().query().withId(17385).toList(); + Rs2TileObjectModel ladderObj = ladders.isEmpty() ? null : ladders.get(0); long now = System.currentTimeMillis(); if (ladderObj != null && Rs2Player.getWorldLocation().distanceTo(ladderObj.getWorldLocation()) <= 50) { // Only attempt to interact with the ladder every 2 seconds if (now - lastLadderInteractTime > 2000) { // Refresh ladder object again just before interaction - List laddersNow = Rs2GameObject.getAll(o -> o.getId() == 17385, 104); + List laddersNow = Microbot.getRs2TileObjectCache().query().withId(17385).toList(); ladderObj = laddersNow.isEmpty() ? null : laddersNow.get(0); - Rs2GameObject.interact(ladderObj, "Climb-up"); + ladderObj.click("Climb-up"); lastLadderInteractTime = now; } } @@ -548,11 +546,11 @@ private void recoverFromPit() { sleep(300, 600); // Now interact with rope - TileObject rope = getObstacleObj(1); + Rs2TileObjectModel rope = getObstacleObj(1); if (rope != null && !Rs2Player.isMoving()) { isWaitingForRope = false; ropeStartXp = Microbot.getClient().getSkillExperience(AGILITY); - boolean interacted = Rs2GameObject.interact(rope); + boolean interacted = rope.click(); if (interacted) { isWaitingForRope = true; } @@ -561,11 +559,11 @@ private void recoverFromPit() { case LOG: // Wide range detection for log, just like ladder detection - List logs = Rs2GameObject.getAll(o -> o.getId() == obstacles.get(3).getObjectId(), 104); - TileObject log = logs.isEmpty() ? null : logs.get(0); + Rs2TileObjectModel log = Microbot.getRs2TileObjectCache().query() + .withId(obstacles.get(3).getObjectId()).nearest(); if (log != null) { isWaitingForLog = false; - boolean interacted = Rs2GameObject.interact(log); + boolean interacted = log.click(); if (interacted) { isWaitingForLog = true; sleep(300, 600); @@ -620,13 +618,14 @@ private void handlePipe() { return; } // Find the pipe object at the exact tile (3004, 3938, 0) - TileObject pipe = Rs2GameObject.getAll(o -> o.getId() == obstacles.get(0).getObjectId() && - o.getWorldLocation().equals(pipeTile), 10) - .stream().findFirst().orElse(null); + Rs2TileObjectModel pipe = Microbot.getRs2TileObjectCache().query() + .withId(obstacles.get(0).getObjectId()) + .where(o -> o.getWorldLocation().equals(pipeTile)) + .nearest(); if (pipe == null) { return; } - boolean interacted = Rs2GameObject.interact(pipe); + boolean interacted = pipe.click(); if (interacted) { isWaitingForPipe = true; pipeJustCompleted = true; // Set immediately after interaction @@ -661,9 +660,9 @@ private void handleRope() { } } if (!Rs2Player.isAnimating() && !Rs2Player.isMoving() && !isWaitingForRope) { - TileObject rope = getObstacleObj(1); + Rs2TileObjectModel rope = getObstacleObj(1); if (rope != null) { - boolean interacted = Rs2GameObject.interact(rope); + boolean interacted = rope.click(); if (interacted) { isWaitingForRope = true; ropeStartXp = Microbot.getClient().getSkillExperience(AGILITY); @@ -715,9 +714,9 @@ private void handleStones() { // Only attempt interaction if not already waiting and not animating/moving if (!Rs2Player.isAnimating() && !Rs2Player.isMoving() && !isWaitingForStones) { WorldPoint loc = Rs2Player.getWorldLocation(); - TileObject stones = getObstacleObj(2); + Rs2TileObjectModel stones = getObstacleObj(2); if (stones != null) { - boolean interacted = Rs2GameObject.interact(stones); + boolean interacted = stones.click(); if (interacted) { isWaitingForStones = true; stonesStartXp = Microbot.getClient().getSkillExperience(AGILITY); @@ -756,13 +755,13 @@ private void handleLog() { clearInventoryIfNeeded(); } - TileObject log = getObstacleObj(3); + Rs2TileObjectModel log = getObstacleObj(3); if (log == null) { - List logs = Rs2GameObject.getAll(o -> o.getId() == obstacles.get(3).getObjectId(), 104); - log = logs.isEmpty() ? null : logs.get(0); + log = Microbot.getRs2TileObjectCache().query() + .withId(obstacles.get(3).getObjectId()).nearest(); } if (log != null) { - boolean interacted = Rs2GameObject.interact(log); + boolean interacted = log.click(); if (interacted) { isWaitingForLog = true; logStartXp = Microbot.getClient().getSkillExperience(AGILITY); @@ -783,7 +782,7 @@ private void handleRocks() { WorldPoint loc = Rs2Player.getWorldLocation(); if (loc != null && loc.getY() <= 3933) { // Get fresh dispenser object for immediate use - TileObject freshDispenser = getDispenserObj(); + Rs2TileObjectModel freshDispenser = getDispenserObj(); cachedDispenserObj = freshDispenser; lastObjectCheck = System.currentTimeMillis(); @@ -795,7 +794,7 @@ private void handleRocks() { dispenserPreValue = getInventoryValue(); dispenserLootAttempts = 1; waitingForDispenserLoot = true; - Rs2GameObject.interact(freshDispenser, "Search"); + freshDispenser.click("Search"); } return; } @@ -807,7 +806,9 @@ private void handleRocks() { WorldPoint targetRock = new Random().nextBoolean() ? rock1 : rock2; - if (Rs2GameObject.interact(targetRock, "Climb")) { + Rs2TileObjectModel targetRockObj = Microbot.getRs2TileObjectCache().query() + .where(o -> o.getWorldLocation().equals(targetRock)).nearest(); + if (targetRockObj != null && targetRockObj.click("Climb")) { // Monitor Y coordinate in real-time for immediate transition boolean transitioned = sleepUntil(() -> { WorldPoint currentLoc = Rs2Player.getWorldLocation(); @@ -819,7 +820,7 @@ private void handleRocks() { if (transitioned) { // Immediate transition to dispenser - TileObject freshDispenser = getDispenserObj(); + Rs2TileObjectModel freshDispenser = getDispenserObj(); cachedDispenserObj = freshDispenser; lastObjectCheck = System.currentTimeMillis(); currentState = ObstacleState.DISPENSER; @@ -839,7 +840,7 @@ private void handleRocks() { int startExp = Microbot.getClient().getSkillExperience(AGILITY); if (waitForXpChange(startExp, 3000)) { // Shorter timeout for fallback Microbot.log("[WildernessAgility] XP fallback successful, transitioning to dispenser"); - TileObject freshDispenser = getDispenserObj(); + Rs2TileObjectModel freshDispenser = getDispenserObj(); cachedDispenserObj = freshDispenser; lastObjectCheck = System.currentTimeMillis(); currentState = ObstacleState.DISPENSER; @@ -866,7 +867,7 @@ private void handleRocks() { } } private void handleDispenser() { - TileObject dispenser = cachedDispenserObj; + Rs2TileObjectModel dispenser = cachedDispenserObj; WorldPoint playerLoc = Rs2Player.getWorldLocation(); if (dispenser == null || playerLoc == null) return; if (playerLoc.distanceTo(dispenser.getWorldLocation()) > 20) return; @@ -915,7 +916,7 @@ private void handleDispenser() { if (dispenserLootAttempts == 0) { dispenserPreValue = getInventoryValue(); dispenserTicketsBefore = currentTickets; - Rs2GameObject.interact(dispenser, "Search"); + dispenser.click("Search"); waitingForDispenserLoot = true; dispenserLootAttempts = 1; // Only try once, now wait for loot } else if (dispenserLootAttempts == 1) { @@ -924,13 +925,13 @@ private void handleDispenser() { } } private void handleConfigChecks() { - TileObject dispenser = cachedDispenserObj; + Rs2TileObjectModel dispenser = cachedDispenserObj; if (dispenser == null) return; int ticketCount = Rs2Inventory.itemQuantity(TICKET_ITEM_ID); if (ticketCount >= config.useTicketsWhen()) { boolean didInteract = Rs2Inventory.interact(TICKET_ITEM_ID, "Use"); if (didInteract) { - didInteract = Rs2GameObject.interact(dispenser, "Use"); + didInteract = dispenser != null && dispenser.click("Use"); if (didInteract) { sleepUntil(() -> Rs2Inventory.itemQuantity(TICKET_ITEM_ID) < ticketCount, 2000); } @@ -1034,7 +1035,7 @@ private void handleStart() { // DISABLED: This corrupts inventory action data and causes Rs2Inventory.use() to crash // checkLootingBagOnStartup(); - TileObject dispenserObj = getDispenserObj(); + Rs2TileObjectModel dispenserObj = getDispenserObj(); WorldPoint playerLoc = Rs2Player.getWorldLocation(); boolean nearDispenser = dispenserObj != null && playerLoc != null && playerLoc.distanceTo(dispenserObj.getWorldLocation()) <= 4; @@ -1075,7 +1076,7 @@ private void handleStart() { Microbot.log("[WildernessAgility] Attempting to deposit " + coinCount + " coins into dispenser"); Rs2Inventory.use(COINS_ID); sleep(400); - Rs2GameObject.interact(dispenserObj, "Use"); + dispenserObj.click("Use"); sleep(getActionDelay()); sleepUntil(() -> Rs2Inventory.itemQuantity(COINS_ID) < coinCount, getXpTimeout()); } else { @@ -1482,7 +1483,7 @@ private void handleEmergencyEscape() { if (isInArea) { Microbot.log("[WildernessAgility] Emergency Escape Step 2: Climbing rocks"); - Rs2GameObject.interact(ROCKS_OBJECT_ID, "Climb"); // Climb rocks + Microbot.getRs2TileObjectCache().query().interact(ROCKS_OBJECT_ID, "Climb"); // Climb rocks sleep(1200); sleepUntil(() -> !Rs2Player.isMoving(), 5000); hasClimbedRocks = true; @@ -1506,8 +1507,8 @@ private void handleEmergencyEscape() { // Step 3: Open gate (Netoxic's approach) - only if not already opened if (!hasOpenedGate) { Microbot.log("[WildernessAgility] Emergency Escape Step 3: Opening gate"); - sleepUntilOnClientThread(() -> Rs2GameObject.getGameObject(GATE_OBJECT_ID) != null); // Wait for Gate - Rs2GameObject.interact(GATE_OBJECT_ID, "Open"); + sleepUntilOnClientThread(() -> Microbot.getRs2TileObjectCache().query().withId(GATE_OBJECT_ID).nearest() != null); // Wait for Gate + Microbot.getRs2TileObjectCache().query().interact(GATE_OBJECT_ID, "Open"); hasOpenedGate = true; return; // Wait for next loop iteration } @@ -1774,14 +1775,14 @@ private void handleWalkToCourse() { sleepUntil(() -> isAt(START_POINT, 2), 20000); return; } - TileObject dispenserObj = getDispenserObj(); + Rs2TileObjectModel dispenserObj = getDispenserObj(); if (dispenserObj != null) { int coinCount = Rs2Inventory.itemQuantity(COINS_ID); if (coinCount >= 150000) { Microbot.log("[WildernessAgility] [WALK_TO_COURSE] Attempting to deposit " + coinCount + " coins into dispenser"); Rs2Inventory.use(COINS_ID); sleep(400); - Rs2GameObject.interact(dispenserObj, "Use"); + dispenserObj.click("Use"); sleep(getActionDelay()); sleepUntil(() -> Rs2Inventory.itemQuantity(COINS_ID) < coinCount, getXpTimeout()); } else { From 663d2e6437bc54a7ce4bdd1317879d7a748568ae Mon Sep 17 00:00:00 2001 From: chsami Date: Thu, 9 Apr 2026 14:49:04 +0200 Subject: [PATCH 24/95] refactor: migrate processing and utility plugins to new query API Migrate Rs2Npc and Rs2GameObject calls across cooking, crafting, construction, smelting, Giants Foundry, Mixology, Wintertodt (both variants), and utility plugins (BlessedWine, BlastoiseFurnace, CannonballSmelter, CharterCrafter, GildedAltar, HouseTab, ChaosAltar, OrbCharger, NpcTanner, LunarTablets, VarrockCleaner, PlankRunner, Pumper, Karambwans). Co-Authored-By: Claude Opus 4.6 (1M context) --- .../BlastoiseFurnaceScript.java | 16 ++- .../blessedwine/BlessedWineScript.java | 7 +- .../CannonballSmelterScript.java | 19 ++- .../microbot/chaosaltar/ChaosAltarScript.java | 22 ++-- .../chartercrafter/CharterCrafterScript.java | 5 +- .../construction/ConstructionScript.java | 59 ++++------ .../cooking/scripts/AutoCookingScript.java | 21 ++-- .../cooking/scripts/BurnBakingScript.java | 97 ++++++--------- .../crafting/jewelry/JewelryScript.java | 9 +- .../crafting/scripts/FlaxSpinScript.java | 11 +- .../giantsfoundry/GiantsFoundryScript.java | 25 ++-- .../giantsfoundry/GiantsFoundryState.java | 11 +- .../gildedaltar/GildedAltarScript.java | 18 ++- .../microbot/housetab/HouseTabScript.java | 47 ++++---- .../karambwans/GabulhasKarambwansScript.java | 25 ++-- .../lunartablets/LunarTabletsScript.java | 3 +- .../microbot/mixology/MixologyScript.java | 97 +++++++++------ .../mke_wintertodt/MKE_WintertodtScript.java | 110 +++++++++--------- .../location/WintertodtLocationManager.java | 10 +- .../microbot/npctanner/npcTannerScript.java | 5 +- .../orbcharger/scripts/AirOrbScript.java | 9 +- .../plankrunner/PlankRunnerScript.java | 9 +- .../plugins/microbot/pumper/PumperScript.java | 3 +- .../microbot/smelting/AutoSmeltingScript.java | 17 ++- .../varrockcleaner/VarrockCleanerScript.java | 7 +- .../wintertodt/MWintertodtScript.java | 58 +++++---- 26 files changed, 355 insertions(+), 365 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 8da2474d9d..f5762b471d 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 @@ -14,14 +14,12 @@ import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; 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.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.misc.Rs2Potion; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -90,7 +88,7 @@ public boolean run() { return; } Rs2Walker.walkTo(new WorldPoint(2931, 10197, 0)); - Rs2GameObject.interact(DWARF_KELDAGRIM_FACTORY_STAIRS); + Microbot.getRs2TileObjectCache().query().interact(DWARF_KELDAGRIM_FACTORY_STAIRS); return; } @@ -204,8 +202,8 @@ private void handleTax() { sleep(500, 1200); Rs2Bank.closeBank(); sleepUntil(() -> !Rs2Bank.isOpen()); - Rs2NpcModel blastie = Rs2Npc.getNpc("Blast Furnace Foreman"); - Rs2Npc.interact(blastie, "Pay"); + var blastie = Microbot.getRs2NpcCache().query().withName("Blast Furnace Foreman").nearest(); + if (blastie != null) blastie.click("Pay"); sleepUntil(Rs2Dialogue::isInDialogue, 10000); if (Rs2Dialogue.hasSelectAnOption()) { Rs2Dialogue.clickOption("Yes"); @@ -235,7 +233,7 @@ private void handleDispenserLooting() { } } - Rs2GameObject.interact(BLAST_FURNACE_DISPENSER, "Take"); + Microbot.getRs2TileObjectCache().query().interact(BLAST_FURNACE_DISPENSER, "Take"); sleepUntil(() -> Rs2Widget.hasWidget("What would you like to take?") || @@ -497,7 +495,7 @@ private boolean putOreOnConveyorBelt() { log.error("No ore in Inventory"); return false; } - if (!Rs2GameObject.interact(BLAST_FURNACE_CONVEYER_BELT_CLICKABLE, "Put-ore-on")) { + if (!Microbot.getRs2TileObjectCache().query().interact(BLAST_FURNACE_CONVEYER_BELT_CLICKABLE, "Put-ore-on")) { log.error("Failed to interact with conveyor belt"); return false; } @@ -670,7 +668,7 @@ public void checkAndTopOffCoffer() { sleepUntil(() -> !Rs2Bank.isOpen()); } - Rs2GameObject.interact(BLAST_FURNACE_AUTOMATA_COFFER, "use"); + Microbot.getRs2TileObjectCache().query().interact(BLAST_FURNACE_AUTOMATA_COFFER, "use"); Rs2Player.waitForWalking(2400); if (underfilled && Rs2Dialogue.hasDialogueOption("deposit", false)) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/blessedwine/BlessedWineScript.java b/src/main/java/net/runelite/client/plugins/microbot/blessedwine/BlessedWineScript.java index 6a62ecc94b..4863eef940 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/blessedwine/BlessedWineScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/blessedwine/BlessedWineScript.java @@ -8,7 +8,6 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; -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.walker.Rs2Walker; @@ -69,7 +68,7 @@ public boolean run() { case BLESS_AT_ALTAR: BlessedWinePlugin.status = "Blessing wine at altar..."; - Rs2GameObject.interact(52799, "Bless"); + Microbot.getRs2TileObjectCache().query().interact(52799, "Bless"); Rs2Inventory.waitForInventoryChanges(1200); if (!Rs2Inventory.hasItem(BLESSED_WINE)) return; state = BlessedWineState.WALK_TO_BOWL; @@ -85,7 +84,7 @@ public boolean run() { case USE_LIBATION_BOWL: BlessedWinePlugin.status = "Using Libation Bowl..."; - Rs2GameObject.interact(53018, "Fill"); + Microbot.getRs2TileObjectCache().query().interact(53018, "Fill"); if (currentPrayerPoints > 2 && !Rs2Player.isAnimating()) return; if (currentPrayerPoints < 2 && !Rs2Player.isAnimating()) { state = BlessedWineState.WALK_TO_SHRINE; @@ -105,7 +104,7 @@ public boolean run() { case RESTORE_PRAYER: BlessedWinePlugin.status = "Restoring prayer..."; - Rs2GameObject.interact(52405, "Bask"); + Microbot.getRs2TileObjectCache().query().interact(52405, "Bask"); Rs2Player.waitForAnimation(5000); if (currentPrayerPoints != maxPrayerLevel) return; if (!Rs2Inventory.hasItem(BLESSED_WINE)) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/cannonballsmelter/CannonballSmelterScript.java b/src/main/java/net/runelite/client/plugins/microbot/cannonballsmelter/CannonballSmelterScript.java index a9f28e0cd3..7a12b300ab 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cannonballsmelter/CannonballSmelterScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cannonballsmelter/CannonballSmelterScript.java @@ -2,7 +2,6 @@ import net.runelite.api.Client; -import net.runelite.api.GameObject; import net.runelite.api.gameval.ItemID; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; @@ -13,11 +12,9 @@ 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.camera.Rs2Camera; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; 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.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import javax.inject.Inject; @@ -103,14 +100,14 @@ else if (!hasBars() || hasBalls()){ } public void smelt() { - GameObject furnace = Rs2GameObject.getGameObject(config.getFurnace().furnaceID); + Rs2TileObjectModel furnace = Microbot.getRs2TileObjectCache().query().withId(config.getFurnace().furnaceID).nearest(); if(config.getFurnace() == Furnace.SHILO_VILLAGE) { - furnace = Rs2GameObject.getGameObject("Furnace"); + furnace = Microbot.getRs2TileObjectCache().query().withName("Furnace").nearest(); } if (furnace != null) { - Rs2GameObject.interact(furnace, "Smelt"); + furnace.click("Smelt"); Microbot.status = "Moving to furnace..."; sleepUntil(() -> Rs2Widget.getWidget(17694733) != null); if(Rs2Widget.getWidget(17694733) != null) { @@ -138,8 +135,8 @@ public void bank() { if (!isRunning()) break; if(config.getFurnace() == Furnace.SHILO_VILLAGE) { - Rs2NpcModel banker = Rs2Npc.getBankerNPC(); - Rs2Npc.interact(banker, "Bank"); + var banker = Microbot.getRs2NpcCache().query().withName("Banker").nearest(); + if (banker != null) banker.click("Bank"); } else { Rs2Bank.openBank(); } @@ -170,8 +167,8 @@ public void getMould() { if(!Rs2Inventory.hasItem("ammo mould") && !Rs2Inventory.hasItem("double ammo mould")) { if(!Rs2Bank.isOpen()) { if(config.getFurnace() == Furnace.SHILO_VILLAGE) { - Rs2NpcModel banker = Rs2Npc.getBankerNPC(); - Rs2Npc.interact(banker, "Bank"); + var banker = Microbot.getRs2NpcCache().query().withName("Banker").nearest(); + if (banker != null) banker.click("Bank"); } else { Rs2Bank.openBank(); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/chaosaltar/ChaosAltarScript.java b/src/main/java/net/runelite/client/plugins/microbot/chaosaltar/ChaosAltarScript.java index 4875817469..e09c88e091 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/chaosaltar/ChaosAltarScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/chaosaltar/ChaosAltarScript.java @@ -1,12 +1,12 @@ package net.runelite.client.plugins.microbot.chaosaltar; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.GameObject; import net.runelite.api.Skill; import net.runelite.api.coords.WorldArea; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; @@ -14,7 +14,6 @@ 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.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.player.Rs2Pvp; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; @@ -98,17 +97,15 @@ public boolean run(ChaosAltarConfig config, ChaosAltarPlugin plugin) { return true; } - private GameObject getChaosAltar() { - return (GameObject) Rs2GameObject - .getAll(obj -> obj.getId() == CHAOS_ALTAR && obj instanceof GameObject) - .stream().findFirst().orElse(null); + private Rs2TileObjectModel getChaosAltar() { + return Microbot.getRs2TileObjectCache().query().withId(CHAOS_ALTAR).nearest(); } public boolean isAtChaosAltar() { - final GameObject gameObject = getChaosAltar(); + final Rs2TileObjectModel gameObject = getChaosAltar(); if (gameObject == null) return false; - final boolean reachable = Rs2GameObject.isReachable(gameObject); + final boolean reachable = gameObject.isReachable(); log.info("Found Chaos Altar GameObject at: {}. Reachable={}", gameObject.getWorldLocation(), reachable); return reachable; } @@ -117,9 +114,8 @@ public boolean isAtChaosAltar() { private void dieToNpc() { Microbot.log("Walking to dangerous NPC to die"); Rs2Walker.walkTo(2979, 3845, 0); - sleepUntil(() -> Rs2Npc.getNpc(CHAOS_FANATIC) != null, 60000); - // Attack chaos fanatic to die - Rs2Npc.attack("Chaos Fanatic"); + sleepUntil(() -> Microbot.getRs2NpcCache().query().withId(CHAOS_FANATIC).nearest() != null, 60000); + Microbot.getRs2NpcCache().query().withName("Chaos Fanatic").interact("Attack"); // Wait until player dies sleepUntil(() -> Microbot.getClient().getBoostedSkillLevel(Skill.HITPOINTS) == 0, 60000); sleepUntil(() -> !Rs2Pvp.isInWilderness(), 15000); @@ -164,7 +160,7 @@ private void offerBones() { if (lastBones != null && isRunning()) { Rs2Inventory.interact(lastBones, "use"); sleep(300, 500); - Rs2GameObject.interact(CHAOS_ALTAR); + Microbot.getRs2TileObjectCache().query().interact(CHAOS_ALTAR); sleep(300, 500); Rs2Inventory.waitForInventoryChanges(Rs2Random.between(500, 2000)); @@ -188,7 +184,7 @@ && isRunning() && Rs2GameObject.exists(CHAOS_ALTAR)) { Rs2Inventory.interact(lastBones, "use"); sleep(100, 300); - Rs2GameObject.interact(CHAOS_ALTAR); + Microbot.getRs2TileObjectCache().query().interact(CHAOS_ALTAR); Rs2Player.waitForXpDrop(Skill.PRAYER); // Small random delay between offerings diff --git a/src/main/java/net/runelite/client/plugins/microbot/chartercrafter/CharterCrafterScript.java b/src/main/java/net/runelite/client/plugins/microbot/chartercrafter/CharterCrafterScript.java index 99da91ca0f..f74becb042 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/chartercrafter/CharterCrafterScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/chartercrafter/CharterCrafterScript.java @@ -8,8 +8,7 @@ import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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.security.Login; import net.runelite.client.plugins.microbot.util.shop.Rs2Shop; @@ -164,7 +163,7 @@ private void bootstrap() { Rs2Inventory.dropAll("Empty light orb", "Light orb"); } - Rs2NpcModel trader = Rs2Npc.getNpc(TRADER_NAME, false); + Rs2NpcModel trader = Microbot.getRs2NpcCache().query().withName(TRADER_NAME).nearest(); if (trader == null) { update("Bootstrap", "Trader not nearby", false, true); state = State.STOP; diff --git a/src/main/java/net/runelite/client/plugins/microbot/construction/ConstructionScript.java b/src/main/java/net/runelite/client/plugins/microbot/construction/ConstructionScript.java index ed7a9275e1..69a11b8cf0 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/construction/ConstructionScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/construction/ConstructionScript.java @@ -1,7 +1,5 @@ package net.runelite.client.plugins.microbot.construction; -import net.runelite.api.GameObject; -import net.runelite.api.NPC; import net.runelite.api.coords.WorldPoint; import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.construction.ConstructionConfig; @@ -10,16 +8,15 @@ import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.globval.enums.InterfaceTab; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -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.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import java.awt.event.KeyEvent; import java.util.List; @@ -37,23 +34,13 @@ public class ConstructionScript extends Script { private static final List MAHOGANY_TABLE = List.of(13298, 15298); private static final List MYTHICAL_CAPE_MOUNT = List.of(15394, 31986); - public GameObject getClosestTile(List objIDs) { - List objects = Rs2GameObject.getGameObjects(); - GameObject closest = null; - WorldPoint playerLocation = Rs2Player.getWorldLocation(); - - for (GameObject obj : objects) { - if (objIDs.contains(obj.getId())) { - if (closest == null || Rs2Walker.getDistanceBetween(playerLocation, obj.getWorldLocation()) < Rs2Walker.getDistanceBetween(playerLocation, closest.getWorldLocation())) { - closest = obj; - } - } - } - return closest; + public Rs2TileObjectModel getClosestTile(List objIDs) { + int[] ids = objIDs.stream().mapToInt(Integer::intValue).toArray(); + return Microbot.getRs2TileObjectCache().query().withIds(ids).nearest(); } public Rs2NpcModel getButler() { - return Rs2Npc.getNpc("Demon butler"); + return Microbot.getRs2NpcCache().query().withName("Demon butler").nearest(); } public boolean hasDialogueOptionToUnnote() { @@ -144,7 +131,7 @@ public void grabPlanksWhileWeBuild(net.runelite.client.plugins.microbot.construc private void calculateState(net.runelite.client.plugins.microbot.construction.ConstructionConfig config) { boolean hasRequiredPlanks; - NPC butler = getButler(); + Rs2NpcModel butler = getButler(); List objectIDs = List.of(0); switch (config.selectedMode()) { case OAK_DUNGEON_DOOR: @@ -167,11 +154,14 @@ private void calculateState(net.runelite.client.plugins.microbot.construction.Co workingTile = getClosestTile(objectIDs).getWorldLocation(); } - GameObject objOnWorkingTile = Rs2GameObject.getGameObject(workingTile); + Rs2TileObjectModel objOnWorkingTile = Microbot.getRs2TileObjectCache().query() + .where(o -> o.getWorldLocation().equals(workingTile)) + .nearest(); if (objOnWorkingTile == null || !objectIDs.contains(objOnWorkingTile.getId())) { - // Find new working tile workingTile = getClosestTile(objectIDs).getWorldLocation(); - objOnWorkingTile = Rs2GameObject.getGameObject(workingTile); + objOnWorkingTile = Microbot.getRs2TileObjectCache().query() + .where(o -> o.getWorldLocation().equals(workingTile)) + .nearest(); } if (objOnWorkingTile.getId() == objectIDs.get(0)) { @@ -188,9 +178,9 @@ private void calculateState(net.runelite.client.plugins.microbot.construction.Co } private void returnToTheHouse(){ - GameObject housePortal = Rs2GameObject.getGameObject("Portal"); + Rs2TileObjectModel housePortal = Microbot.getRs2TileObjectCache().query().withName("Portal").nearest(); if(housePortal != null){ - if(Rs2GameObject.interact(housePortal, "Build mode")){ + if(housePortal.click("Build mode")){ sleepUntil(()-> Rs2Player.getWorldLocation() != null && Rs2Player.getWorldLocation().getRegionX() == 29 && Rs2Player.getWorldLocation().getRegionY() == 89, Rs2Random.between(10000,20000)); @@ -203,7 +193,9 @@ private void returnToTheHouse(){ } private void buildSpace(net.runelite.client.plugins.microbot.construction.ConstructionConfig config, int actionDelay) { - GameObject space = Rs2GameObject.getGameObject(workingTile); + Rs2TileObjectModel space = Microbot.getRs2TileObjectCache().query() + .where(o -> o.getWorldLocation().equals(workingTile)) + .nearest(); int spaceId = space != null ? space.getId() : -1; char buildKey = '1'; @@ -217,15 +209,12 @@ private void buildSpace(net.runelite.client.plugins.microbot.construction.Constr case MAHOGANY_TABLE: buildKey = '6'; break; - // case MYTHICAL_CAPE: - // buildKey = '4'; - // break; default: return; } if (space == null) return; - if (Rs2GameObject.interact(space, "Build")) { + if (space.click("Build")) { System.out.println("Interacted with build space: " + space.getId()); sleepUntilOnClientThread(this::hasFurnitureInterfaceOpen, 2500); System.out.println("Pressing key: " + buildKey); @@ -238,13 +227,15 @@ private void buildSpace(net.runelite.client.plugins.microbot.construction.Constr } private void removeSpace(net.runelite.client.plugins.microbot.construction.ConstructionConfig config, int actionDelay) { - GameObject builtObject = Rs2GameObject.getGameObject(workingTile); + Rs2TileObjectModel builtObject = Microbot.getRs2TileObjectCache().query() + .where(o -> o.getWorldLocation().equals(workingTile)) + .nearest(); int spaceId = builtObject != null ? builtObject.getId() : -1; if (builtObject == null) return; if(builtObject.getId() == 15328 || builtObject.getId() == 15403 || builtObject.getId() == 15298 || builtObject.getId() == 31986) return; - if (Rs2GameObject.interact(builtObject, "Remove")) { + if (builtObject.click("Remove")) { System.out.println("Interacted with remove option: " + builtObject.getId()); sleepUntilOnClientThread(() -> hasRemoveInterfaceOpen(config), 2500); Rs2Keyboard.keyPress('1'); @@ -274,12 +265,12 @@ private void butler(net.runelite.client.plugins.microbot.construction.Constructi sleepUntil(()-> Rs2Dialogue.isInDialogue(), Rs2Random.between(2000,5000)); } - if (Rs2Dialogue.isInDialogue() || Rs2Npc.interact(butler, "Talk-to")) { + if (Rs2Dialogue.isInDialogue() || butler.click("Talk-to")) { sleep(500); Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); sleep(400, 1000); if (Rs2Widget.findWidget("Go to the bank...", null) != null) { - Rs2Inventory.useItemOnNpc(config.selectedMode().getPlankItemId() + 1, butler.getId()); // + 1 for noted item + Rs2Inventory.useItemOnNpc(config.selectedMode().getPlankItemId() + 1, butler.getId()); sleepUntilOnClientThread(() -> Rs2Widget.hasWidget("Dost thou wish me to exchange that certificate")); Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); sleepUntilOnClientThread(() -> Rs2Widget.hasWidget("Select an option")); 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 ec9e493ea8..bbb5ebcfcd 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 @@ -1,8 +1,6 @@ package net.runelite.client.plugins.microbot.cooking.scripts; import net.runelite.api.AnimationID; -import net.runelite.api.NPC; -import net.runelite.api.TileObject; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.cooking.AutoCookingConfig; @@ -18,10 +16,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.keyboard.Rs2Keyboard; -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.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import java.awt.event.KeyEvent; import java.util.Objects; @@ -95,9 +94,11 @@ public boolean run(AutoCookingConfig config) { return; } - TileObject cookingObject = Rs2GameObject.findObjectById(location.getCookingObjectID()); + Rs2TileObjectModel cookingObject = Microbot.getRs2TileObjectCache().query().withId(location.getCookingObjectID()).nearest(); if (cookingObject == null) { - cookingObject = Rs2GameObject.findGameObjectByLocation(location.getCookingObjectWorldPoint()); + cookingObject = Microbot.getRs2TileObjectCache().query() + .where(o -> o.getWorldLocation().equals(location.getCookingObjectWorldPoint())) + .nearest(); } if (cookingObject != null) { @@ -150,13 +151,17 @@ public boolean run(AutoCookingConfig config) { break; case BANKING: if (location == CookingLocation.ROUGES_DEN) { - NPC npc = Rs2Npc.getBankerNPC(); + Rs2NpcModel npc = Microbot.getRs2NpcCache().query() + .where(n -> n.getName() != null && n.getNpc() != null && n.getNpc().getComposition() != null + && n.getNpc().getComposition().getActions() != null + && java.util.Arrays.asList(n.getNpc().getComposition().getActions()).contains("Bank")) + .nearest(); if (npc == null) return; - boolean isNPCBankOpen = Rs2Bank.openBank(npc); + boolean isNPCBankOpen = Rs2Bank.openBank(npc.getNpc()); if (!isNPCBankOpen) return; sleepUntil(() -> !Rs2Player.isMoving()); } else { - TileObject nearbyBankObject = Rs2GameObject.findBank(20); + net.runelite.api.TileObject nearbyBankObject = Rs2GameObject.findBank(20); if (nearbyBankObject != null) { int distanceToBank = Rs2Player.getWorldLocation().distanceTo(nearbyBankObject.getWorldLocation()); boolean isBankOpen = Rs2Bank.openBank(nearbyBankObject); diff --git a/src/main/java/net/runelite/client/plugins/microbot/cooking/scripts/BurnBakingScript.java b/src/main/java/net/runelite/client/plugins/microbot/cooking/scripts/BurnBakingScript.java index b0ad3fe605..4b85c205d4 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cooking/scripts/BurnBakingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cooking/scripts/BurnBakingScript.java @@ -15,8 +15,7 @@ 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.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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 net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -32,7 +31,6 @@ import static net.runelite.api.ItemID.*; import static net.runelite.api.gameval.ItemID.CAKE_TIN; import static net.runelite.api.gameval.ItemID.EGG; -import static net.runelite.client.plugins.microbot.util.npc.Rs2Npc.getNpcs; import static net.runelite.client.plugins.microbot.util.player.Rs2Player.toggleRunEnergy; @@ -183,7 +181,7 @@ public boolean run(AutoCookingConfig config) { if (currentCookingLevel < 40 && !Rs2Inventory.contains("Uncooked stew")) { System.out.println("entering prepare stew"); if (!Rs2Player.isAnimating()) { walkToBanker(); - Rs2Npc.getBankerNPC(); + getBankerNPC(); if (!Rs2Bank.isOpen()) {openNearestBank();} sleepUntil(Rs2Bank::isOpen, 30000);} sleep(1200, 1600); @@ -230,9 +228,8 @@ public void walkToBanker() { return; } - // Select a random banker from the list - NPC banker = bankers.get(random.nextInt(bankers.size())); - LocalPoint bankerLocation = banker.getLocalLocation(); + Rs2NpcModel banker = bankers.get(random.nextInt(bankers.size())); + LocalPoint bankerLocation = banker.getNpc().getLocalLocation(); if (bankerLocation == null) { Microbot.log("Failed to get the banker's location."); @@ -283,18 +280,18 @@ public void walkToBanker() { } public static Rs2NpcModel getBankerNPC() { - return Rs2Npc.getNpcs() - .filter(npc -> npc.getComposition() != null && npc.getComposition().getActions() != null && - Arrays.asList(npc.getComposition().getActions()).contains("Bank")) - .min(Comparator.comparingInt(npc -> npc.getWorldLocation().distanceTo(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation())))) - .orElse(null); + return Microbot.getRs2NpcCache().query() + .where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null + && npc.getNpc().getComposition().getActions() != null + && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Bank")) + .nearest(); } public void openNearestBank() { if (!Rs2Bank.isOpen()) { - Rs2NpcModel nearestBanker = getBankerNPC(); // Find the closest NPC that has "Bank" action + Rs2NpcModel nearestBanker = getBankerNPC(); if (nearestBanker != null) { - Rs2Npc.interact(nearestBanker, "Bank"); + nearestBanker.click("Bank"); sleepUntil(Rs2Bank::isOpen, 5000); } } @@ -327,11 +324,12 @@ private void waitForWalking() { public static List getBankerNPCs() { - return getNpcs() - .filter(value -> (value.getComposition() != null && value.getComposition().getActions() != null && - Arrays.asList(value.getComposition().getActions()).contains("Bank"))) - .limit(4) // Collect only up to 4 bankers - .collect(Collectors.toList()); + List bankers = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null + && npc.getNpc().getComposition().getActions() != null + && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Bank")) + .toList(); + return bankers.size() > 4 ? bankers.subList(0, 4) : bankers; } @@ -632,54 +630,31 @@ public void findAndInteractWithNearestRange() { } System.out.println("Player location: " + playerPosition); - // Step 2: Retrieve all game objects within 15 tiles of the player - List nearbyObjects = Rs2GameObject.getGameObjects(); - if (nearbyObjects == null || nearbyObjects.isEmpty()) { - System.out.println("Found 0 nearby objects."); + int[] rangeObjectIds = getRangeObjectIds(); + net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel rangeObj = Microbot.getRs2TileObjectCache().query() + .withIds(rangeObjectIds) + .nearest(); + if (rangeObj == null) { + System.out.println("No range found within nearby tiles."); return; } - // Step 3: Fetch the list of range object IDs from the getRangeObjectIds method - int[] rangeObjectIds = getRangeObjectIds(); - - // Step 4: Iterate over nearby objects to find any matching range or stove - boolean rangeFound = false; - for (GameObject obj : nearbyObjects) { - int objId = obj.getId(); - - // Step 5: Check if the object ID matches any of the range object IDs - if (Arrays.stream(rangeObjectIds).anyMatch(id -> id == objId)) { - - Rs2Camera.turnTo(obj.getLocalLocation()); - - - // If a match is found, interact with the object using the "Cook" action - boolean interactionSuccess = Rs2GameObject.interact(obj, "Cook"); - if (interactionSuccess) { - System.out.println("Successfully interacted with object ID: " + objId + " using 'Cook'"); - } else { - System.out.println("Failed to interact with object ID: " + objId); - } + Rs2Camera.turnTo(rangeObj.getLocalLocation()); - // Step 6: Wait until the player stops moving and the cooking widget appears - boolean widgetAppeared = sleepUntil(() -> !Rs2Player.isMoving() && - Rs2Widget.findWidget("How many would you like to cook?", null, false) != null, 35000); - if (widgetAppeared) { - System.out.println("Cooking widget appeared, pressing space to confirm."); - Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); - } else { - System.out.println("Cooking widget did not appear."); - } - - rangeFound = true; - break; - } else { - System.out.println("No match for object ID: " + objId); - } + boolean interactionSuccess = rangeObj.click("Cook"); + if (interactionSuccess) { + System.out.println("Successfully interacted with range using 'Cook'"); + } else { + System.out.println("Failed to interact with range"); } - if (!rangeFound) { - System.out.println("No range found within 15 tiles."); + boolean widgetAppeared = sleepUntil(() -> !Rs2Player.isMoving() && + Rs2Widget.findWidget("How many would you like to cook?", null, false) != null, 35000); + if (widgetAppeared) { + System.out.println("Cooking widget appeared, pressing space to confirm."); + Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); + } else { + System.out.println("Cooking widget did not appear."); } } 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..727801a313 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 @@ -3,7 +3,6 @@ import net.runelite.api.EquipmentInventorySlot; import net.runelite.api.ItemID; import net.runelite.api.Skill; -import net.runelite.api.TileObject; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.crafting.jewelry.enums.*; @@ -14,6 +13,7 @@ import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; 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.inventory.Rs2RunePouch; @@ -308,8 +308,9 @@ public boolean run() { break; case CRAFTING: - TileObject furnaceObject = Rs2GameObject.findObjectById(plugin.getCraftingLocation().getFurnanceObjectID()); - + Rs2TileObjectModel furnaceObject = Microbot.getRs2TileObjectCache().query() + .withId(plugin.getCraftingLocation().getFurnanceObjectID()).nearest(); + if (furnaceObject == null) { Rs2Walker.walkTo(plugin.getCraftingLocation().getFurnaceLocation()); return; @@ -320,7 +321,7 @@ public boolean run() { return; } - Rs2GameObject.interact(furnaceObject, "smelt"); + furnaceObject.click("smelt"); sleepUntilTrue(() -> Rs2Widget.isGoldCraftingWidgetOpen() || Rs2Widget.isSilverCraftingWidgetOpen(), 500, 20000); Rs2Widget.clickWidget(plugin.getJewelry().getItemName()); Rs2Antiban.actionCooldown(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/crafting/scripts/FlaxSpinScript.java b/src/main/java/net/runelite/client/plugins/microbot/crafting/scripts/FlaxSpinScript.java index 23f2a8bf6e..0b15565f78 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/crafting/scripts/FlaxSpinScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/crafting/scripts/FlaxSpinScript.java @@ -1,12 +1,10 @@ package net.runelite.client.plugins.microbot.crafting.scripts; -import net.runelite.api.GameObject; import net.runelite.api.gameval.ItemID; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.crafting.CraftingConfig; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; -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; @@ -15,7 +13,8 @@ import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import java.awt.event.KeyEvent; -import java.util.*; +import java.util.Collections; +import java.util.Map; import java.util.concurrent.TimeUnit; enum State { @@ -99,9 +98,9 @@ public boolean run(CraftingConfig config) { Rs2Walker.walkTo(config.flaxSpinLocation().getWorldPoint(), 4); sleepUntilTrue(() -> isNearSpinningWheel(config, 4) && !Rs2Player.isMoving(), 600, 300000); if (!isNearSpinningWheel(config, 4)) return; - Optional spinningWheel = Rs2GameObject.getGameObjects().stream() - .filter(obj -> obj.getId() == config.flaxSpinLocation().getObjectID()).min(Comparator.comparingInt(obj -> Rs2Player.getWorldLocation().distanceTo(obj.getWorldLocation()))); - if (spinningWheel.isEmpty()) { + net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel spinningWheel = Microbot.getRs2TileObjectCache().query() + .withId(config.flaxSpinLocation().getObjectID()).nearest(); + if (spinningWheel == null) { Rs2Walker.walkFastCanvas(config.flaxSpinLocation().getWorldPoint()); return; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryScript.java b/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryScript.java index 203a629ce1..64d31fbdee 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryScript.java @@ -1,7 +1,6 @@ package net.runelite.client.plugins.microbot.giantsfoundry; import net.runelite.api.EquipmentInventorySlot; -import net.runelite.api.GameObject; import net.runelite.api.ObjectComposition; import net.runelite.api.coords.WorldPoint; import net.runelite.api.widgets.Widget; @@ -17,9 +16,9 @@ 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.keyboard.Rs2Keyboard; -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.widget.Rs2Widget; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import java.awt.event.KeyEvent; import java.util.concurrent.TimeUnit; @@ -104,7 +103,7 @@ public boolean hasCommission() { public void getCommission() { if (!hasCommission()) { GiantsFoundryState.reset(); - if (Rs2Npc.interact("kovac", "Commission")) + if (Microbot.getRs2NpcCache().query().withName("kovac").interact("Commission")) sleepUntil(this::hasCommission, 5000); } } @@ -119,7 +118,7 @@ public void selectMould() { if (hasSelectedMould()) return; - Rs2GameObject.interact(MOULD_JIG); + Microbot.getRs2TileObjectCache().query().withId(MOULD_JIG).interact(); sleepUntil(() -> Rs2Widget.findWidget("Forte", null) != null, 5000); @@ -204,7 +203,7 @@ private boolean ensureBarsInInventory() { private void addBarsWithWidget(String barName, SmithableBars key) { if (!Rs2Inventory.hasItem(barName) || canPour()) return; - Rs2GameObject.interact(CRUCIBLE, "Fill"); + Microbot.getRs2TileObjectCache().query().interact(CRUCIBLE, "Fill"); sleepUntil(() -> Rs2Widget.findWidget("What metal would you like to add?", null) != null, 5000); Rs2Keyboard.keyPress(getKeyFromBar(key)); sleepUntil(() -> !Rs2Inventory.hasItem(barName), 5000); @@ -213,7 +212,7 @@ private void addBarsWithWidget(String barName, SmithableBars key) { private void addBarsWithInventoryUse(String barName) { if (!Rs2Inventory.hasItem(barName) || canPour()) return; Rs2Inventory.use(barName); - Rs2GameObject.interact(CRUCIBLE); + Microbot.getRs2TileObjectCache().query().withId(CRUCIBLE).interact(); sleepUntil(() -> Rs2Widget.findWidget("How many would you like to add?", null) != null, 5000); sleep(600, 1200); @@ -224,7 +223,7 @@ private void addBarsWithInventoryUse(String barName) { } private void pourCrucible() { - Rs2GameObject.interact(CRUCIBLE, "Pour"); + Microbot.getRs2TileObjectCache().query().interact(CRUCIBLE, "Pour"); sleep(5000); sleepUntil(() -> !canPour(), 10000); } @@ -249,7 +248,7 @@ public boolean canPickupMould() { public void pickupMould() { if (!canPickupMould()) return; if (Rs2Inventory.isEmpty() && GiantsFoundryState.getCurrentStage() == null) { - Rs2GameObject.interact(MOULD_JIG, "Pick-up"); + Microbot.getRs2TileObjectCache().query().interact(MOULD_JIG, "Pick-up"); sleepUntil(() -> !canPickupMould(), 5000); } } @@ -293,7 +292,7 @@ public void handleGameLoop() { boolean isAtLavaTile = Rs2Player.getWorldLocation().equals(new WorldPoint(3371, 11497, 0)) || Rs2Player.getWorldLocation().equals(new WorldPoint(3371, 11498, 0)); if (!doAction && isAtLavaTile) return; - Rs2GameObject.interact(LAVA_POOL, "Heat-preform"); + Microbot.getRs2TileObjectCache().query().interact(LAVA_POOL, "Heat-preform"); GiantsFoundryState.heatingCoolingState.stop(); GiantsFoundryState.heatingCoolingState.setup(false, true, "heats"); GiantsFoundryState.heatingCoolingState.start(GiantsFoundryState.getHeatAmount()); @@ -302,7 +301,7 @@ public void handleGameLoop() { case COOLING_DOWN: boolean isAtWaterFallTile = Rs2Player.getWorldLocation().equals(new WorldPoint(3360, 11489, 0)); if (!doAction && isAtWaterFallTile) return; - Rs2GameObject.interact(WATERFALL, "Cool-preform"); + Microbot.getRs2TileObjectCache().query().interact(WATERFALL, "Cool-preform"); GiantsFoundryState.heatingCoolingState.stop(); GiantsFoundryState.heatingCoolingState.setup(false, false, "cools"); GiantsFoundryState.heatingCoolingState.start(GiantsFoundryState.getHeatAmount()); @@ -324,14 +323,14 @@ public void handleGameLoop() { public void craftWeapon() { Stage stage = GiantsFoundryState.getCurrentStage(); if (stage == null) return; - GameObject obj = GiantsFoundryState.getStageObject(stage); + Rs2TileObjectModel obj = GiantsFoundryState.getStageObject(stage); if (obj == null) return; - Rs2GameObject.interact(obj); + obj.click(); Rs2Player.waitForAnimation(); } private void handIn() { - Rs2Npc.interact("kovac", "Hand-in"); + Microbot.getRs2NpcCache().query().withName("kovac").interact("Hand-in"); } } \ No newline at end of file diff --git a/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryState.java b/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryState.java index 70c6e83739..4de6a49df4 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryState.java +++ b/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryState.java @@ -2,12 +2,11 @@ import lombok.Getter; import lombok.Setter; -import net.runelite.api.GameObject; import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.giantsfoundry.enums.Heat; import net.runelite.client.plugins.microbot.giantsfoundry.enums.Stage; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import java.util.ArrayList; @@ -124,14 +123,14 @@ public static List getStages() { return stages; } - public static GameObject getStageObject(Stage stage) { + public static Rs2TileObjectModel getStageObject(Stage stage) { switch (stage) { case TRIP_HAMMER: - return Rs2GameObject.getGameObject("trip hammer"); + return Microbot.getRs2TileObjectCache().query().withName("trip hammer").nearest(); case GRINDSTONE: - return Rs2GameObject.getGameObject("grindstone"); + return Microbot.getRs2TileObjectCache().query().withName("grindstone").nearest(); case POLISHING_WHEEL: - return Rs2GameObject.getGameObject("polishing wheel"); + return Microbot.getRs2TileObjectCache().query().withName("polishing wheel").nearest(); } return null; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/gildedaltar/GildedAltarScript.java b/src/main/java/net/runelite/client/plugins/microbot/gildedaltar/GildedAltarScript.java index e11b7b86f0..0241b7b34a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/gildedaltar/GildedAltarScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/gildedaltar/GildedAltarScript.java @@ -1,15 +1,13 @@ package net.runelite.client.plugins.microbot.gildedaltar; import net.runelite.api.ObjectID; -import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; 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.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -37,7 +35,7 @@ public class GildedAltarScript extends Script { public static GildedAltarPlayerState state = GildedAltarPlayerState.IDLE; private boolean inHouse() { - return Rs2Npc.getNpc("Phials") == null; + return Microbot.getRs2NpcCache().query().withName("Phials").nearest() == null; } private boolean hasUnNotedBones() { @@ -113,12 +111,12 @@ public void leaveHouse() { // We should only rely on using the settings menu if the portal is several rooms away from the portal. Bringing up 3 different interfaces when we can see the portal on screen is unnecessary. if(usePortal) { - TileObject portalObject = Rs2GameObject.findObjectById(HOUSE_PORTAL_OBJECT); + Rs2TileObjectModel portalObject = Microbot.getRs2TileObjectCache().query().withId(HOUSE_PORTAL_OBJECT).nearest(); if (portalObject == null) { System.out.println("Not in house, HOUSE_PORTAL_OBJECT not found."); return; } - Rs2GameObject.interact(portalObject); + portalObject.click(); Rs2Player.waitForWalking(); return; } @@ -157,7 +155,7 @@ public void unnoteBones() { if (!Rs2Inventory.isItemSelected()) { Rs2Inventory.use("bones"); } else { - Rs2Npc.interact("Phials", "Use"); + Microbot.getRs2NpcCache().query().withName("Phials").interact("Use"); Rs2Player.waitForWalking(); } } else if (Microbot.getClient().getWidget(14352385) != null) { @@ -169,7 +167,7 @@ public void unnoteBones() { private void enterHouse() { // If we've already visited a house this session, use 'Visit-Last' on advertisement board if (visitedOnce) { - Rs2GameObject.interact(ObjectID.HOUSE_ADVERTISEMENT, "Visit-Last"); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.HOUSE_ADVERTISEMENT, "Visit-Last"); sleep(2400, 3000); return; } @@ -177,7 +175,7 @@ private void enterHouse() { boolean isAdvertisementWidgetOpen = Rs2Widget.isWidgetVisible(3407875); if (!isAdvertisementWidgetOpen) { - Rs2GameObject.interact(ObjectID.HOUSE_ADVERTISEMENT, "View"); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.HOUSE_ADVERTISEMENT, "View"); sleep(1200, 1800); } @@ -244,7 +242,7 @@ public void bonesOnAltar() { } - TileObject altar = Rs2GameObject.getGameObject("Altar", true); + Rs2TileObjectModel altar = Microbot.getRs2TileObjectCache().query().withName("Altar").nearest(); if (altar != null) { Rs2Inventory.useUnNotedItemOnObject("bones", altar.getId()); Rs2Player.waitForAnimation(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/housetab/HouseTabScript.java b/src/main/java/net/runelite/client/plugins/microbot/housetab/HouseTabScript.java index e2d93f2063..b75f6fbfa6 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/housetab/HouseTabScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/housetab/HouseTabScript.java @@ -1,20 +1,19 @@ package net.runelite.client.plugins.microbot.housetab; -import net.runelite.api.GameObject; import net.runelite.api.Point; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.ObjectID; import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.housetab.enums.HOUSETABS_CONFIG; -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.Rs2RunePouch; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.magic.Runes; 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.widget.Rs2Widget; @@ -67,11 +66,11 @@ public HouseTabScript(HOUSETABS_CONFIG houseTabConfig, String[] playerHouses) { private void lookForHouseAdvertisementObject() { Widget houseAdvertisementPanel = Microbot.getClient().getWidget(HOUSE_ADVERTISEMENT_NAME_PARENT_INTERFACE); - if (!hasSoftClay() || houseAdvertisementPanel != null || Rs2GameObject.findObjectById(HOUSE_PORTAL_OBJECT) != null) + if (!hasSoftClay() || houseAdvertisementPanel != null || Microbot.getRs2TileObjectCache().query().withId(HOUSE_PORTAL_OBJECT).nearest() != null) return; - boolean success = Rs2GameObject + boolean success = Microbot.getRs2TileObjectCache().query() .interact(HOUSE_ADVERTISEMENT_OBJECT, "View"); @@ -85,7 +84,7 @@ private void lookForPlayerHouse() { if (houseAdvertisementNameWidget == null || houseAdvertisementNameWidget.getChildren() == null) return; if (!hasSoftClay()) return; - if (Rs2GameObject.findObjectById(HOUSE_PORTAL_OBJECT) != null) + if (Microbot.getRs2TileObjectCache().query().withId(HOUSE_PORTAL_OBJECT).nearest() != null) return; int enterHouseButtonHeight = 21; @@ -116,13 +115,17 @@ private void lookForPlayerHouse() { } else { Microbot.getMouse() .click(enterHouseButton.getCanvasLocation()); - sleepUntilOnClientThread(() -> Rs2GameObject.findObjectById(HOUSE_PORTAL_OBJECT) != null); + sleepUntilOnClientThread(() -> Microbot.getRs2TileObjectCache().query().withId(HOUSE_PORTAL_OBJECT).nearest() != null); sleep(2000, 3000); } } private Integer getHouseLectern() { - GameObject lectern = Rs2GameObject.getGameObject(lecternToHouseTabButton.keySet().toArray(new Integer[0])); + Rs2TileObjectModel lectern = null; + for (Integer id : lecternToHouseTabButton.keySet()) { + lectern = Microbot.getRs2TileObjectCache().query().withId(id).nearest(); + if (lectern != null) break; + } if (lectern != null) { lecternTabletWidgetId = lecternToHouseTabButton.get(lectern.getId()); return lectern.getId(); @@ -133,13 +136,13 @@ private Integer getHouseLectern() { public void lookForLectern() { if (getHouseLectern() == null) { Microbot.log("Can't find lectern"); shutdown(); return;} //can't find lectern - if (!hasSoftClay() || Rs2GameObject.findObjectById(HOUSE_ADVERTISEMENT_OBJECT) != null || Microbot.isGainingExp) + if (!hasSoftClay() || Microbot.getRs2TileObjectCache().query().withId(HOUSE_ADVERTISEMENT_OBJECT).nearest() != null || Microbot.isGainingExp) return; Widget houseTabInterface = Microbot.getClient().getWidget(lecternTabletWidgetId); - if (houseTabInterface != null || Rs2GameObject.findObjectById(HOUSE_PORTAL_OBJECT) == null) return; + if (houseTabInterface != null || Microbot.getRs2TileObjectCache().query().withId(HOUSE_PORTAL_OBJECT).nearest() == null) return; - boolean success = Rs2GameObject.interact(lecternToHouseTabButton.keySet().stream().mapToInt(Integer::intValue).toArray(), "Study"); + boolean success = Microbot.getRs2TileObjectCache().query().withIds(lecternToHouseTabButton.keySet().stream().mapToInt(Integer::intValue).toArray()).interact("Study"); if (success) { sleepUntilOnClientThread(() -> Microbot.getClient().getWidget(lecternTabletWidgetId) != null); } @@ -148,7 +151,7 @@ public void lookForLectern() { public void createHouseTablet() { Widget houseTabInterface = Microbot.getClient().getWidget(lecternTabletWidgetId); if (houseTabInterface == null) return; - if (!hasSoftClay() || Rs2GameObject.findObjectById(HOUSE_PORTAL_OBJECT) == null) + if (!hasSoftClay() || Microbot.getRs2TileObjectCache().query().withId(HOUSE_PORTAL_OBJECT).nearest() == null) return; while (Microbot.getClient().getWidget(lecternTabletWidgetId) != null) { @@ -166,16 +169,16 @@ public void createHouseTablet() { } public void leaveHouse() { - if (hasSoftClay() || Rs2GameObject.findObjectById(HOUSE_PORTAL_OBJECT) == null) + if (hasSoftClay() || Microbot.getRs2TileObjectCache().query().withId(HOUSE_PORTAL_OBJECT).nearest() == null) return; - boolean success = Rs2GameObject.interact(HOUSE_PORTAL_OBJECT, "Enter"); + boolean success = Microbot.getRs2TileObjectCache().query().interact(HOUSE_PORTAL_OBJECT, "Enter"); if (success) - sleepUntil(() -> Rs2GameObject.findObjectById(HOUSE_PORTAL_OBJECT) == null); + sleepUntil(() -> Microbot.getRs2TileObjectCache().query().withId(HOUSE_PORTAL_OBJECT).nearest() == null); } public void unnoteClay() { - if (hasSoftClay() || Rs2GameObject.findObjectById(HOUSE_ADVERTISEMENT_OBJECT) == null) + if (hasSoftClay() || Microbot.getRs2TileObjectCache().query().withId(HOUSE_ADVERTISEMENT_OBJECT).nearest() == null) return; if (Microbot.getClient().getWidget(14352385) == null) { do { @@ -183,7 +186,7 @@ public void unnoteClay() { Rs2Inventory.use("Soft clay"); }); sleep(300, 380); - } while (!Rs2Npc.interact("Phials", "Use")); + } while (!Microbot.getRs2NpcCache().query().withName("Phials").interact("Use")); } sleep(2500, 5000); @@ -205,12 +208,12 @@ public boolean run(HouseTabConfig config) { if (Microbot.isGainingExp) return; Rs2Player.toggleRunEnergy(true); - if (Microbot.getClient().getEnergy() < 3000 && !Rs2Widget.hasWidget("Teleport to House") && Rs2GameObject.findObject(new Integer[]{ObjectID.XMAS20_POH_POOL_REGENERATION, ObjectID.POH_POOL_REJUVENATION}) != null) { - Rs2GameObject.interact(new int[]{ObjectID.XMAS20_POH_POOL_REGENERATION, ObjectID.POH_POOL_REJUVENATION}, "drink"); + if (Microbot.getClient().getEnergy() < 3000 && !Rs2Widget.hasWidget("Teleport to House") && Microbot.getRs2TileObjectCache().query().withIds(ObjectID.XMAS20_POH_POOL_REGENERATION, ObjectID.POH_POOL_REJUVENATION).nearest() != null) { + Microbot.getRs2TileObjectCache().query().withIds(ObjectID.XMAS20_POH_POOL_REGENERATION, ObjectID.POH_POOL_REJUVENATION).interact("drink"); return; } - boolean isInHouse = Rs2GameObject.getGameObject(lecternToHouseTabButton.keySet().toArray(new Integer[0])) != null; + boolean isInHouse = getHouseLectern() != null; if (isInHouse) { lookForLectern(); createHouseTablet(); @@ -218,12 +221,12 @@ public boolean run(HouseTabConfig config) { } else { unnoteClay(); if (config.ownHouse()) { - if (Rs2GameObject.interact(ObjectID.POH_RIMMINGTON_PORTAL, "Home")) { + if (Microbot.getRs2TileObjectCache().query().interact(ObjectID.POH_RIMMINGTON_PORTAL, "Home")) { sleep(800, 1200); } return; } - if (Rs2GameObject.interact(ObjectID.POH_RIMMINGTON_PORTAL, "Friend's house")) { + if (Microbot.getRs2TileObjectCache().query().interact(ObjectID.POH_RIMMINGTON_PORTAL, "Friend's house")) { sleepUntil(() -> Rs2Widget.hasWidget("Enter name")); if (Rs2Widget.hasWidget(config.housePlayerName())) { Rs2Widget.clickWidget(config.housePlayerName()); 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 54879771a2..ab99202a8b 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 @@ -1,7 +1,6 @@ package net.runelite.client.plugins.microbot.karambwans; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.GameObject; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.NpcID; @@ -11,11 +10,9 @@ import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; 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.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.magic.Rs2Spells; 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.walker.Rs2Walker; import net.runelite.client.plugins.microbot.karambwans.enums.KarambwanBankLocation; @@ -108,9 +105,9 @@ private void walkToRingToBank() { return; } WorldPoint ringLocation = new WorldPoint(2900, 3111, 0); // Karamja ring - GameObject fairyRing = Rs2GameObject.getGameObject(ringLocation); + var fairyRing = Microbot.getRs2TileObjectCache().query().nearest(ringLocation, 3); if (fairyRing != null) { - Rs2GameObject.interact(fairyRing, "Zanaris"); + fairyRing.click("Zanaris"); Rs2Player.waitForAnimation(); } } @@ -153,7 +150,7 @@ private void useBank() { } private void interactWithFishingSpot() { - Rs2Npc.interact(NpcID._0_45_48_KARAMBWAN, "Fish"); + Microbot.getRs2NpcCache().query().withId(NpcID._0_45_48_KARAMBWAN).interact("Fish"); } private void walkToFish() { @@ -165,10 +162,10 @@ private void walkToFish() { } else { Rs2Magic.quickCast(MagicAction.TELEPORT_TO_HOUSE); } - sleepUntil(() -> Rs2GameObject.exists(FAIRY_RING_ID) || Rs2GameObject.exists(SPIRITUAL_FAIRY_TREE_ID), 5000); + sleepUntil(() -> Microbot.getRs2TileObjectCache().query().withId(FAIRY_RING_ID).nearest() != null || Microbot.getRs2TileObjectCache().query().withId(SPIRITUAL_FAIRY_TREE_ID).nearest() != null, 5000); - boolean interacted = Rs2GameObject.interact(FAIRY_RING_ID, "Last-destination (DKP)") || - Rs2GameObject.interact(SPIRITUAL_FAIRY_TREE_ID, "Last-destination (DKP)"); + boolean interacted = Microbot.getRs2TileObjectCache().query().interact(FAIRY_RING_ID, "Last-destination (DKP)") || + Microbot.getRs2TileObjectCache().query().interact(SPIRITUAL_FAIRY_TREE_ID, "Last-destination (DKP)"); if (interacted) { waitTillPlayerNextToFishingSpot(); @@ -178,15 +175,15 @@ private void walkToFish() { Rs2Player.waitForWalking(); // Ensure the fairy ring at Zanaris is actually loaded before trying to interact. - sleepUntil(() -> Rs2GameObject.getGameObject(zanarisRingPoint) != null, 5000); + sleepUntil(() -> Microbot.getRs2TileObjectCache().query().nearest(zanarisRingPoint, 3) != null, 5000); - GameObject zanarisRing = Rs2GameObject.getGameObject(zanarisRingPoint); + var zanarisRing = Microbot.getRs2TileObjectCache().query().nearest(zanarisRingPoint, 3); boolean interacted = false; if (zanarisRing != null) { // Prefer the explicit last-destination option, fall back to a generic interact if needed. - interacted = Rs2GameObject.interact(zanarisRing, "Last-destination (DKP)") - || Rs2GameObject.interact(zanarisRing, "Last-destination") - || Rs2GameObject.interact(zanarisRing, "Use"); + interacted = zanarisRing.click("Last-destination (DKP)") + || zanarisRing.click("Last-destination") + || zanarisRing.click("Use"); } if (interacted) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/lunartablets/LunarTabletsScript.java b/src/main/java/net/runelite/client/plugins/microbot/lunartablets/LunarTabletsScript.java index fbfb15a0f9..2ca641be70 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/lunartablets/LunarTabletsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/lunartablets/LunarTabletsScript.java @@ -7,7 +7,6 @@ import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; -import net.runelite.client.plugins.microbot.util.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; @@ -136,7 +135,7 @@ public void makeTablets(){ } } else { // interact with lecturn - if(Rs2GameObject.interact("Lectern", "Study")){ + if(Microbot.getRs2TileObjectCache().query().withName("Lectern").interact("Study")){ sleep(generateRandomNumber(0,1000)); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyScript.java b/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyScript.java index d200d3c500..8ebd6db6c2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mixology/MixologyScript.java @@ -5,7 +5,6 @@ import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.ObjectID; - import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; @@ -15,12 +14,12 @@ import net.runelite.client.plugins.microbot.mixology.enums.PotionModifier; 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.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 net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import java.time.Duration; import java.time.Instant; @@ -98,7 +97,7 @@ public boolean run(MixologyConfig config) { if (digweed != null && !Rs2Player.isAnimating() && !Rs2Inventory.hasItem(DIGWEED) && config.pickDigWeed()) { - Rs2GameObject.interact(digweed.coordinate()); + Microbot.getRs2TileObjectCache().query().withId(digweed.objectId()).interact(); Rs2Player.waitForWalking(); Rs2Player.waitForAnimation(); return; @@ -165,7 +164,7 @@ public boolean run(MixologyConfig config) { } if (Rs2Inventory.hasItem(config.agaHerb().toString()) || Rs2Inventory.hasItem(config.lyeHerb().toString()) || Rs2Inventory.hasItem(config.moxHerb().toString())) { - Rs2GameObject.interact(ObjectID.MM_LAB_MILL); + Microbot.getRs2TileObjectCache().query().withId(ObjectID.MM_LAB_MILL).interact(); Rs2Player.waitForAnimation(); sleepGaussian(450, 150); if (!config.useQuickActionRefiner()) { @@ -206,7 +205,7 @@ public boolean run(MixologyConfig config) { } break; case DEPOSIT_HOPPER: - if (Rs2GameObject.interact(ObjectID.MM_LAB_HOPPER)) { + if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.MM_LAB_HOPPER).interact()) { Rs2Player.waitForWalking(); Rs2Inventory.waitForInventoryChanges(10000); mixologyState = MixologyState.MIX_POTION_STAGE_1; @@ -252,7 +251,7 @@ public boolean run(MixologyConfig config) { } break; case TAKE_FROM_MIXIN_VESSEL: - Rs2GameObject.interact(MIXING_VESSEL.objectId()); + Microbot.getRs2TileObjectCache().query().withId(MIXING_VESSEL.objectId()).interact(); boolean result = Rs2Inventory.waitForInventoryChanges(5000); if (result) { mixologyState = MixologyState.MIX_POTION_STAGE_1; @@ -305,7 +304,7 @@ public boolean run(MixologyConfig config) { mixologyState = MixologyState.MIX_POTION_STAGE_1; return; } - if (Rs2GameObject.interact(AlchemyObject.CONVEYOR_BELT.objectId())) { + if (Microbot.getRs2TileObjectCache().query().withId(AlchemyObject.CONVEYOR_BELT.objectId()).interact()) { Rs2Inventory.waitForInventoryChanges(5000); currentAgaPoints = getAgaPoints(); currentLyePoints = getLyePoints(); @@ -357,25 +356,22 @@ private boolean hasAllFulFilledItems() { private static void processPotion(PotionOrder nonFulfilledPotion) { switch (nonFulfilledPotion.potionModifier()) { case HOMOGENOUS: - GameObject agitator = (GameObject) Rs2GameObject.findObjectById(AlchemyObject.AGITATOR.objectId()); - if (agitator != null && (((DynamicObject) agitator.getRenderable()).getAnimation().getId() == 11633 || ((DynamicObject) agitator.getRenderable()).getAnimation().getId() == 11632)) { - Rs2GameObject.interact(AlchemyObject.AGITATOR.objectId()); + if (isAlchemyObjectAnimating(AlchemyObject.AGITATOR, 11633, 11632)) { + Microbot.getRs2TileObjectCache().query().withId(AlchemyObject.AGITATOR.objectId()).interact(); } else { Rs2Inventory.useItemOnObject(nonFulfilledPotion.potionType().itemId(), AlchemyObject.AGITATOR.objectId()); } break; case CONCENTRATED: - GameObject retort = (GameObject) Rs2GameObject.findObjectById(AlchemyObject.RETORT.objectId()); - if (retort != null && (((DynamicObject) retort.getRenderable()).getAnimation().getId() == 11643 || ((DynamicObject) retort.getRenderable()).getAnimation().getId() == 11642)) { - Rs2GameObject.interact(AlchemyObject.RETORT.objectId()); + if (isAlchemyObjectAnimating(AlchemyObject.RETORT, 11643, 11642)) { + Microbot.getRs2TileObjectCache().query().withId(AlchemyObject.RETORT.objectId()).interact(); } else { Rs2Inventory.useItemOnObject(nonFulfilledPotion.potionType().itemId(), AlchemyObject.RETORT.objectId()); } break; case CRYSTALISED: - GameObject alembic = (GameObject) Rs2GameObject.findObjectById(AlchemyObject.ALEMBIC.objectId()); - if (alembic != null && (((DynamicObject) alembic.getRenderable()).getAnimation().getId() == 11638 || ((DynamicObject) alembic.getRenderable()).getAnimation().getId() == 11637)) { - Rs2GameObject.interact(AlchemyObject.ALEMBIC.objectId()); + if (isAlchemyObjectAnimating(AlchemyObject.ALEMBIC, 11638, 11637)) { + Microbot.getRs2TileObjectCache().query().withId(AlchemyObject.ALEMBIC.objectId()).interact(); } else { Rs2Inventory.useItemOnObject(nonFulfilledPotion.potionType().itemId(), AlchemyObject.ALEMBIC.objectId()); } @@ -383,16 +379,38 @@ private static void processPotion(PotionOrder nonFulfilledPotion) { } } + private static boolean isAlchemyObjectAnimating(AlchemyObject alchemyObject, int... animationIds) { + Rs2TileObjectModel model = Microbot.getRs2TileObjectCache().query().withId(alchemyObject.objectId()).nearest(); + if (model == null) return false; + try { + net.runelite.api.Scene scene = Microbot.getClient().getTopLevelWorldView().getScene(); + int plane = model.getWorldLocation().getPlane(); + int sceneX = model.getLocalLocation().getSceneX(); + int sceneY = model.getLocalLocation().getSceneY(); + net.runelite.api.Tile tile = scene.getTiles()[plane][sceneX][sceneY]; + if (tile == null || tile.getGameObjects() == null) return false; + for (net.runelite.api.GameObject go : tile.getGameObjects()) { + if (go != null && go.getId() == alchemyObject.objectId() && go.getRenderable() instanceof DynamicObject) { + int animId = ((DynamicObject) go.getRenderable()).getAnimation().getId(); + for (int id : animationIds) { + if (animId == id) return true; + } + } + } + } catch (Exception ignored) {} + return false; + } + private static void quickActionProcessPotion(PotionOrder nonFulfilledPotion) { switch (nonFulfilledPotion.potionModifier()) { case HOMOGENOUS: - Rs2GameObject.interact(AlchemyObject.AGITATOR.objectId()); + Microbot.getRs2TileObjectCache().query().withId(AlchemyObject.AGITATOR.objectId()).interact(); break; case CONCENTRATED: - Rs2GameObject.interact(AlchemyObject.RETORT.objectId()); + Microbot.getRs2TileObjectCache().query().withId(AlchemyObject.RETORT.objectId()).interact(); break; case CRYSTALISED: - Rs2GameObject.interact(AlchemyObject.ALEMBIC.objectId()); + Microbot.getRs2TileObjectCache().query().withId(AlchemyObject.ALEMBIC.objectId()).interact(); break; } } @@ -401,11 +419,11 @@ private void createPotion(PotionOrder potionOrder, MixologyConfig config) { for (PotionComponent component : potionOrder.potionType().components()) { if (canCreatePotion(potionOrder)) break; if (component.character() == 'A') { - Rs2GameObject.interact(AlchemyObject.AGA_LEVER.objectId()); + Microbot.getRs2TileObjectCache().query().withId(AlchemyObject.AGA_LEVER.objectId()).interact(); } else if (component.character() == 'L') { - Rs2GameObject.interact(AlchemyObject.LYE_LEVER.objectId()); + Microbot.getRs2TileObjectCache().query().withId(AlchemyObject.LYE_LEVER.objectId()).interact(); } else if (component.character() == 'M') { - Rs2GameObject.interact(AlchemyObject.MOX_LEVER.objectId()); + Microbot.getRs2TileObjectCache().query().withId(AlchemyObject.MOX_LEVER.objectId()).interact(); } if (config.useQuickActionLever()) { Rs2Player.waitForAnimation(); @@ -419,23 +437,34 @@ private void createPotion(PotionOrder potionOrder, MixologyConfig config) { } private boolean canCreatePotion(PotionOrder potionOrder) { - // Get the mixer game objects - GameObject[] mixers = { - (GameObject) Rs2GameObject.findObjectById(ObjectID.MM_LAB_MIXER_03), // mixer3 - (GameObject) Rs2GameObject.findObjectById(ObjectID.MM_LAB_MIXER_02), // mixer2 - (GameObject) Rs2GameObject.findObjectById(ObjectID.MM_LAB_MIXER_01) // mixer1 + Rs2TileObjectModel[] mixerModels = { + Microbot.getRs2TileObjectCache().query().withId(ObjectID.MM_LAB_MIXER_03).nearest(), + Microbot.getRs2TileObjectCache().query().withId(ObjectID.MM_LAB_MIXER_02).nearest(), + Microbot.getRs2TileObjectCache().query().withId(ObjectID.MM_LAB_MIXER_01).nearest() }; - // Check if any mixers are missing - if (Arrays.stream(mixers).anyMatch(Objects::isNull)) { + if (Arrays.stream(mixerModels).anyMatch(Objects::isNull)) { return false; } - // Get animations in correct order - int[] currentAnimations = Arrays.stream(mixers) - .map(mixer -> ((DynamicObject) mixer.getRenderable()).getAnimation().getId()) - .mapToInt(Integer::intValue) - .toArray(); + int[] currentAnimations = new int[3]; + for (int i = 0; i < mixerModels.length; i++) { + Rs2TileObjectModel m = mixerModels[i]; + int animId = -1; + try { + net.runelite.api.Scene scene = Microbot.getClient().getTopLevelWorldView().getScene(); + net.runelite.api.Tile tile = scene.getTiles()[m.getWorldLocation().getPlane()][m.getLocalLocation().getSceneX()][m.getLocalLocation().getSceneY()]; + if (tile != null && tile.getGameObjects() != null) { + for (net.runelite.api.GameObject go : tile.getGameObjects()) { + if (go != null && go.getId() == mixerModels[i].getId() && go.getRenderable() instanceof DynamicObject) { + animId = ((DynamicObject) go.getRenderable()).getAnimation().getId(); + break; + } + } + } + } catch (Exception ignored) {} + currentAnimations[i] = animId; + } // Map components to their valid animations Map componentAnimations = Map.of( diff --git a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java index e78459a741..6f5de4f22a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java @@ -16,10 +16,10 @@ 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.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; 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.keyboard.Rs2Keyboard; @@ -140,7 +140,7 @@ public class MKE_WintertodtScript extends Script { private boolean spamClickingActive = false; private long spamClickStartTime = 0; private long spamClickEndTime = 0; - private GameObject spamClickTarget = null; + private Rs2TileObjectModel spamClickTarget = null; private int spamClicksPerformed = 0; private long lastSpamClick = 0; @@ -246,9 +246,9 @@ private static class GameState { boolean wintertodtRespawning; boolean isWintertodtAlive; int playerWarmth; - GameObject brazier; - GameObject brokenBrazier; - GameObject burningBrazier; + Rs2TileObjectModel brazier; + Rs2TileObjectModel brokenBrazier; + Rs2TileObjectModel burningBrazier; boolean needBanking; boolean needPotions = false; // For rejuvenation potion logic int wintertodtHp = -1; @@ -320,15 +320,13 @@ private void executeStateLogic(GameState gameState) { /* Returns the closest Bruma root that is on the same side as the selected brazier (<= 8 tiles from that brazier). */ - private GameObject getOwnSideRoot() + private Rs2TileObjectModel getOwnSideRoot() { WorldPoint ref = config.brazierLocation().getBRAZIER_LOCATION(); - return Rs2GameObject.getGameObjects(10).stream() - .filter(o -> o.getId() == ObjectID.BRUMA_ROOTS) - .filter(o -> o.getWorldLocation().distanceTo(ref) <= 10) - .min(java.util.Comparator.comparingInt(o -> o.getWorldLocation() - .distanceTo(ref))) - .orElse(null); + return Microbot.getRs2TileObjectCache().query() + .withId(ObjectID.BRUMA_ROOTS) + .within(ref, 10) + .nearest(); } // --------------------------------------------------------------- @@ -1462,12 +1460,12 @@ private GameState analyzeGameState() { gameState.playerWarmth = getWarmthLevel(); // Object detection - gameState.brazier = Rs2GameObject.findObject(BRAZIER_29312, - config.brazierLocation().getOBJECT_BRAZIER_LOCATION()); - gameState.brokenBrazier = Rs2GameObject.findObject(BRAZIER_29313, - config.brazierLocation().getOBJECT_BRAZIER_LOCATION()); - gameState.burningBrazier = Rs2GameObject.findObject(BURNING_BRAZIER_29314, - config.brazierLocation().getOBJECT_BRAZIER_LOCATION()); + gameState.brazier = Microbot.getRs2TileObjectCache().query().withId(BRAZIER_29312) + .where(o -> o.getWorldLocation().equals(config.brazierLocation().getOBJECT_BRAZIER_LOCATION())).nearest(); + gameState.brokenBrazier = Microbot.getRs2TileObjectCache().query().withId(BRAZIER_29313) + .where(o -> o.getWorldLocation().equals(config.brazierLocation().getOBJECT_BRAZIER_LOCATION())).nearest(); + gameState.burningBrazier = Microbot.getRs2TileObjectCache().query().withId(BURNING_BRAZIER_29314) + .where(o -> o.getWorldLocation().equals(config.brazierLocation().getOBJECT_BRAZIER_LOCATION())).nearest(); // Health and food management - determine healing strategy if (!autoAdjustedPotionUsage) { @@ -1601,7 +1599,7 @@ private void performHumanLikeBehaviors() { if (random.nextInt(100) < 15) { // 15% chance // Simulate checking other areas occasionally if (currentBrazier != null && random.nextBoolean()) { - Rs2GameObject.hoverOverObject(currentBrazier); + if (currentBrazier != null) Rs2GameObject.hoverOverObject(currentBrazier); } lastMouseMovement = currentTime; } @@ -2215,7 +2213,7 @@ private void handleLightBrazierState(GameState gameState) { } if (gameState.brazier != null) { - if (Rs2GameObject.interact(gameState.brazier, "light")) { + if (gameState.brazier.click("light")) { Microbot.log("Lighting brazier"); // Reset priority flag after successful lighting attempt @@ -2289,8 +2287,8 @@ private void handleChopRootsState(GameState gameState) if (!Rs2Player.isAnimating()) { - GameObject root = getOwnSideRoot(); - if (root != null && Rs2GameObject.interact(root, "Chop")) + Rs2TileObjectModel root = getOwnSideRoot(); + if (root != null && root.click("Chop")) { sleepUntilTrue(Rs2Player::isAnimating, 100, 3000); maybeNudgeMouse(); @@ -2334,7 +2332,7 @@ private void handleFletchLogsState(GameState gameState) { // Deselect any items before fixing deselectSelectedItem(); - Rs2GameObject.interact(gameState.brokenBrazier, "fix"); + gameState.brokenBrazier.click("fix"); Microbot.log("Fixing broken brazier (priority during fletching)"); resetActions = true; actionsPerformed++; @@ -2356,7 +2354,7 @@ private void handleFletchLogsState(GameState gameState) { // Deselect any items before relighting deselectSelectedItem(); - Rs2GameObject.interact(gameState.brazier, "light"); + gameState.brazier.click("light"); Microbot.log("Relighting brazier (priority during fletching)"); resetActions = true; actionsPerformed++; @@ -2495,7 +2493,7 @@ private void handleBurnLogsState(GameState gameState) { feedingState.stopFeeding(FeedingInterruptType.BRAZIER_BROKEN); } - Rs2GameObject.interact(gameState.brokenBrazier, "fix"); + gameState.brokenBrazier.click("fix"); Microbot.log("Fixing broken brazier"); resetActions = true; actionsPerformed++; @@ -2504,7 +2502,7 @@ private void handleBurnLogsState(GameState gameState) { /* ----------------------------------------------------------------- */ /* ---------- PRIORITY BLOCK 2: RELIGHT BRAZIER SECOND ------------ */ - TileObject burningBrazier = gameState.burningBrazier; // side-specific + Rs2TileObjectModel burningBrazier = gameState.burningBrazier; // side-specific if (burningBrazier == null && gameState.brazier != null && config.relightBrazier() && gameState.isWintertodtAlive) { @@ -2515,7 +2513,7 @@ private void handleBurnLogsState(GameState gameState) { feedingState.stopFeeding(FeedingInterruptType.BRAZIER_WENT_OUT); } - Rs2GameObject.interact(gameState.brazier, "light"); + gameState.brazier.click("light"); Microbot.log("Relighting brazier"); resetActions = true; actionsPerformed++; @@ -2542,7 +2540,7 @@ private void handleBurnLogsState(GameState gameState) { sleepGaussian(400, 600); } - if (Rs2GameObject.interact(burningBrazier, "feed")) { + if (burningBrazier.click("feed")) { feedingState.startFeeding(); // Initialize animation tracking for new feeding session lastFeedingAnimationTime = System.currentTimeMillis(); @@ -2649,16 +2647,11 @@ else if (Rs2Inventory.hasItem(ItemID.BRUMA_KINDLING)) { } // Find and interact with crate - TileObject crate = Rs2GameObject.findObject(CRATE_OBJECT_ID, CRATE_LOCATION); - if (crate == null) { - crate = Rs2GameObject.getGameObjects(CRATE_OBJECT_ID).stream() - .filter(c -> c.getWorldLocation().distanceTo(CRATE_LOCATION) <= 2) - .findFirst() - .orElse(null); - } - + Rs2TileObjectModel crate = Microbot.getRs2TileObjectCache().query().withId(CRATE_OBJECT_ID) + .where(o -> o.getWorldLocation().distanceTo(CRATE_LOCATION) <= 2).nearest(); + if (crate != null) { - if (Rs2GameObject.interact(crate, "Take-concoction")) { + if (crate.click("Take-concoction")) { // Wait for inventory change int beforeCount = Rs2Inventory.count(ItemID.REJUVENATION_POTION_UNF); sleepUntilTrue(() -> Rs2Inventory.count(ItemID.REJUVENATION_POTION_UNF) > beforeCount, 100, 3000); @@ -2755,17 +2748,12 @@ else if (Rs2Inventory.hasItem(ItemID.BRUMA_KINDLING)) { } // Find sprouting roots and pick - TileObject roots = Rs2GameObject.findObject(SPROUTING_ROOTS_OBJECT_ID, SPROUTING_ROOTS); - if (roots == null) { - roots = Rs2GameObject.getGameObjects(SPROUTING_ROOTS_OBJECT_ID).stream() - .filter(r -> r.getWorldLocation().distanceTo(SPROUTING_ROOTS) <= 2) - .findFirst() - .orElse(null); - } - + Rs2TileObjectModel roots = Microbot.getRs2TileObjectCache().query().withId(SPROUTING_ROOTS_OBJECT_ID) + .where(r -> r.getWorldLocation().distanceTo(SPROUTING_ROOTS) <= 2).nearest(); + if (roots != null) { Microbot.log("Picking herbs (need " + herbsNeeded + " more)"); - if (Rs2GameObject.interact(roots, "Pick")) { + if (roots.click("Pick")) { sleepUntilTrue(() -> Rs2Player.isAnimating(), 100, 2000); actionsPerformed++; } else { @@ -2904,7 +2892,8 @@ private boolean useBreadmaNpcForPotions(int concoctions, int herbs) { } // Find Brew'ma NPC - Rs2NpcModel brewmaNpc = Rs2Npc.getNpcInLineOfSight("Brew'ma"); + Rs2NpcModel brewmaNpc = Microbot.getRs2NpcCache().query().withName("Brew'ma") + .where(n -> n.hasLineOfSight()).nearest(); if (brewmaNpc == null) { Microbot.log("Could not find Brew'ma NPC - walking closer"); Rs2Walker.walkFastCanvas(BREWMA_NPC_INTERACT_LOCATION); @@ -2935,7 +2924,7 @@ private boolean useBreadmaNpcForPotions(int concoctions, int herbs) { Microbot.log("Selected herb from inventory"); // Then click on the Brew'ma NPC while herb is selected - if (Rs2Npc.interact(brewmaNpc, "Use")) { + if (brewmaNpc.click("Use")) { Microbot.log("Used herb on Brew'ma NPC - waiting for conversion"); // Wait for the conversion to complete (should be instant according to user) @@ -3381,12 +3370,17 @@ private void dodgeSnowfallDamage(GameState gameState) { resetActions = true; Microbot.log("Dodged snowfall damage (80% chance triggered)"); Microbot.log("Waiting for burning brazier to go out after snowfall damage..."); - Rs2GameObject.hoverOverObject(Rs2GameObject.findReachableObject("brazier", false, 4, Rs2Player.getWorldLocation())); + Rs2TileObjectModel hoverTarget = Microbot.getRs2TileObjectCache().query() + .where(o -> o.getName() != null && o.getName().toLowerCase().contains("brazier") && o.isReachable()) + .within(4).nearest(); + if (hoverTarget != null && hoverTarget.getClickbox() != null) { + Microbot.getMouse().move(hoverTarget.getClickbox().getBounds()); + } boolean brazierWentOut = sleepUntilTrue( () -> { // Wait until burning brazier is gone OR we're no longer in burn state - GameObject burningBrazier = Rs2GameObject.getGameObject(BURNING_BRAZIER_29314,5); + Rs2TileObjectModel burningBrazier = Microbot.getRs2TileObjectCache().query().withId(BURNING_BRAZIER_29314).within(5).nearest(); return burningBrazier == null || (state != State.BURN_LOGS && state != State.FLETCH_LOGS); }, 100, // Check every 100ms @@ -4169,7 +4163,7 @@ private void updateRoundTimer() { * @param gameState Current game state * @return GameObject to hover over, or null if none appropriate */ - private GameObject getNextInteractiveObject(GameState gameState) { + private Rs2TileObjectModel getNextInteractiveObject(GameState gameState) { try { // Priority 1: Broken brazier (if we can fix it) if (gameState.brokenBrazier != null && config.fixBrazier()) { @@ -4191,7 +4185,7 @@ private GameObject getNextInteractiveObject(GameState gameState) { // Priority 4: Bruma roots on our side (if we intend to chop) if (!gameState.inventoryFull && !gameState.hasItemsToBurn && !gameState.needBanking && !gameState.needPotions) { - GameObject root = getOwnSideRoot(); + Rs2TileObjectModel root = getOwnSideRoot(); if (root != null) { Microbot.log("DEBUG: Will hover over bruma roots for chopping"); return root; @@ -4257,9 +4251,9 @@ private void handleRoundTimerMouseBehavior(GameState gameState) { // Start hovering when time remaining reaches our calculated hover time (only if not spam clicking) if (!hoveredForNextRound && !spamClickingActive && timeUntilStart > 0 && timeUntilStart <= hoverBeforeStartTime) { - GameObject nextObject = getNextInteractiveObject(gameState); + Rs2TileObjectModel nextObject = getNextInteractiveObject(gameState); if (nextObject != null) { - Rs2GameObject.hoverOverObject(nextObject); + if (nextObject.getClickbox() != null) Microbot.getMouse().move(nextObject.getClickbox().getBounds()); hoveredForNextRound = true; Microbot.log("Hovering over next interactive object: " + nextObject.getId() + " (" + (timeUntilStart / 1000.0) + "s before round start)"); @@ -4760,7 +4754,7 @@ private void executeSpamClicking(GameState gameState) { /** * Determines the best target for spam clicking (usually the brazier we'll interact with). */ - private GameObject determineSpamClickTarget(GameState gameState) { + private Rs2TileObjectModel determineSpamClickTarget(GameState gameState) { // Priority 1: Unlit brazier (what we'll light when round starts) if (gameState.brazier != null && gameState.burningBrazier == null) { return gameState.brazier; @@ -4789,7 +4783,7 @@ private void performSpamClick() { } // Just hover and click without actually interacting - Rs2GameObject.hoverOverObject(spamClickTarget); + if (spamClickTarget.getClickbox() != null) Microbot.getMouse().move(spamClickTarget.getClickbox().getBounds()); // Small delay between hover and click for realism sleepGaussian(60, 40); @@ -5211,7 +5205,7 @@ private boolean interactWithRewardCart() { lastRewardCartInteraction = System.currentTimeMillis(); // Try to interact with reward cart by searching for the "Reward" text on the object - if (Rs2GameObject.interact("Reward", "Big-search", false)) { + if (Microbot.getRs2TileObjectCache().query().withName("Reward").interact("Big-search")) { Microbot.status = "Interacting with reward cart"; return true; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/startup/location/WintertodtLocationManager.java b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/startup/location/WintertodtLocationManager.java index ca7c2e930f..38831bb7ce 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/startup/location/WintertodtLocationManager.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/startup/location/WintertodtLocationManager.java @@ -8,7 +8,7 @@ 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.walker.Rs2Walker; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; @@ -133,12 +133,12 @@ public static boolean attemptLeaveWintertodt() } /* Step 3: Click the door to trigger the dialog */ - GameObject exitDoor = Rs2GameObject.getGameObject(ObjectID.DOORS_OF_DINH, 6); + Rs2TileObjectModel exitDoor = Microbot.getRs2TileObjectCache().query().withId(ObjectID.DOORS_OF_DINH).within(6).nearest(); if (exitDoor != null) { Microbot.log("Clicking Wintertodt exit door..."); - if (Rs2GameObject.interact(exitDoor, "Leave") - || Rs2GameObject.interact(exitDoor, "Exit") - || Rs2GameObject.interact(exitDoor, "Open")) { + if (exitDoor.click("Leave") + || exitDoor.click("Exit") + || exitDoor.click("Open")) { sleepGaussian(800, 200); // Wait for dialog to appear return false; // Continue next tick to handle dialog } diff --git a/src/main/java/net/runelite/client/plugins/microbot/npctanner/npcTannerScript.java b/src/main/java/net/runelite/client/plugins/microbot/npctanner/npcTannerScript.java index a7eb0d9a65..b3303d594f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/npctanner/npcTannerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/npctanner/npcTannerScript.java @@ -9,7 +9,7 @@ import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; 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.npc.Rs2Npc; +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 net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -104,7 +104,8 @@ public void WalkToandTan(npcTannerConfig config){ } } else { Microbot.status="Tanning: "+npcTannerScript.whattotan; - if(Rs2Npc.interact(Rs2Npc.getNpc("Ellis"), "Trade")){ + Rs2NpcModel ellis = Microbot.getRs2NpcCache().query().withName("Ellis").nearest(); + if(ellis != null && ellis.click("Trade")){ sleep(1000, 3000); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/orbcharger/scripts/AirOrbScript.java b/src/main/java/net/runelite/client/plugins/microbot/orbcharger/scripts/AirOrbScript.java index 8621823c0f..9af26d5d30 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/orbcharger/scripts/AirOrbScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/orbcharger/scripts/AirOrbScript.java @@ -14,6 +14,7 @@ import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; 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; @@ -237,7 +238,7 @@ public boolean run() { } Rs2Magic.cast(MagicAction.CHARGE_AIR_ORB); - Rs2GameObject.interact(ObjectID.OBELISK_OF_AIR); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.OBELISK_OF_AIR); Rs2Dialogue.sleepUntilHasCombinationDialogue(); Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); sleepUntil(() -> Rs2Player.isAnimating(1200), 5000); @@ -246,7 +247,7 @@ public boolean run() { sleepUntil(() -> !Rs2Player.isAnimating(5000) || !Rs2Inventory.hasItem(ItemID.STAFFORB) || shouldFlee, () -> shouldFlee = !plugin.getDangerousPlayers().isEmpty(), 96000, 1000); break; case DRINKING: - Rs2GameObject.interact(ObjectID.POOL_OF_REFRESHMENT); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.POOL_OF_REFRESHMENT); sleepUntil(() -> Rs2Player.getRunEnergy() == 100 && !Rs2Player.isAnimating(2000)); break; case WALKING: @@ -410,9 +411,9 @@ private boolean shouldDrinkFromPool() { if (!hasRequiredItems()) return false; if (!Rs2GameObject.exists(ObjectID.POOL_OF_REFRESHMENT)) return false; - TileObject refreshmentPool = Rs2GameObject.findObjectById(ObjectID.POOL_OF_REFRESHMENT); + Rs2TileObjectModel refreshmentPool = Microbot.getRs2TileObjectCache().query().withId(ObjectID.POOL_OF_REFRESHMENT).nearest(); if (refreshmentPool == null) return false; - + return Rs2Player.getWorldLocation().distanceTo(refreshmentPool.getWorldLocation()) < 8; } 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 c689e64845..5793a99b4c 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,7 +13,6 @@ 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.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -133,9 +132,9 @@ public boolean run() { } Set sawmillNpcs = Set.of(NpcID.POH_SAWMILL_OPP, NpcID.AUBURN_SAWMILL_OPERATOR); - var sawmillOperator = Rs2Npc.getNpcs(n -> sawmillNpcs.contains(n.getId())) - .findFirst() - .orElse(null); + var sawmillOperator = Microbot.getRs2NpcCache().query() + .where(n -> sawmillNpcs.contains(n.getId())) + .nearest(); if (sawmillOperator == null) { Microbot.showMessage("Unable to find Sawmill Operator!"); @@ -143,7 +142,7 @@ public boolean run() { return; } - Rs2Npc.interact(sawmillOperator, "Buy-plank"); + sawmillOperator.click("Buy-plank"); Microbot.status = "Buying Planks"; Rs2Dialogue.sleepUntilHasCombinationDialogue(); Rs2Dialogue.clickCombinationOption(plugin.getPlank().getDialogueOption()); diff --git a/src/main/java/net/runelite/client/plugins/microbot/pumper/PumperScript.java b/src/main/java/net/runelite/client/plugins/microbot/pumper/PumperScript.java index 42037dd2aa..bc311450dd 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/pumper/PumperScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/pumper/PumperScript.java @@ -3,7 +3,6 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.pumper.PumperConfig; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import java.util.concurrent.TimeUnit; @@ -21,7 +20,7 @@ public boolean run(PumperConfig config) { long startTime = System.currentTimeMillis(); if (!Rs2Player.isAnimating()) { - Rs2GameObject.interact(9090, "operate"); + Microbot.getRs2TileObjectCache().query().interact(9090, "operate"); sleep(50,200); } 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..9599fc8865 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 @@ -1,6 +1,5 @@ package net.runelite.client.plugins.microbot.smelting; -import net.runelite.api.GameObject; import net.runelite.api.Skill; import net.runelite.api.gameval.ItemID; import net.runelite.client.plugins.microbot.Microbot; @@ -10,11 +9,11 @@ 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.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.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import java.text.MessageFormat; import java.util.Map; @@ -108,13 +107,16 @@ public boolean run(AutoSmeltingConfig config) { withdrawRightAmountOfMaterials(config); return; } - GameObject oneClickFurnace = Rs2GameObject.findObject("furnace", true, 20, false, initialPlayerLocation); + Rs2TileObjectModel oneClickFurnace = Microbot.getRs2TileObjectCache().query() + .where(o -> o.getName() != null && o.getName().toLowerCase().contains("furnace")) + .within(initialPlayerLocation, 20) + .nearest(); if (oneClickFurnace != null) { if (Rs2Bank.isOpen()){ Rs2Bank.closeBank(); sleepUntil(() -> !Rs2Bank.isOpen(), 1000); } - Rs2GameObject.interact(oneClickFurnace, "smelt"); + oneClickFurnace.click("smelt"); sleepUntil(Rs2Player::isMoving, 1000); sleepUntil(() -> !Rs2Player.isMoving(), 4000); Rs2Widget.sleepUntilHasWidgetText("What would you like to smelt?", 270, 5, false, 4000); @@ -134,9 +136,12 @@ public boolean run(AutoSmeltingConfig config) { } // interact with the furnace until the smelting dialogue opens in chat, click the selected bar icon - GameObject furnace = Rs2GameObject.findObject("furnace",true,20,false,initialPlayerLocation); + Rs2TileObjectModel furnace = Microbot.getRs2TileObjectCache().query() + .where(o -> o.getName() != null && o.getName().toLowerCase().contains("furnace")) + .within(initialPlayerLocation, 20) + .nearest(); if (furnace != null) { - Rs2GameObject.interact(furnace, "smelt"); + furnace.click("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); diff --git a/src/main/java/net/runelite/client/plugins/microbot/varrockcleaner/VarrockCleanerScript.java b/src/main/java/net/runelite/client/plugins/microbot/varrockcleaner/VarrockCleanerScript.java index f50cd3efaa..59936ac9ec 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/varrockcleaner/VarrockCleanerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/varrockcleaner/VarrockCleanerScript.java @@ -4,7 +4,6 @@ import net.runelite.api.ObjectID; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; @@ -57,7 +56,7 @@ private void takeUncleanedFinds() { currentState = State.CLEAN_FIND; return; } - if (Rs2GameObject.interact(ObjectID.DIG_SITE_SPECIMEN_ROCKS, "Take")) { + if (Microbot.getRs2TileObjectCache().query().interact(ObjectID.DIG_SITE_SPECIMEN_ROCKS, "Take")) { sleepUntil(() -> !Rs2Inventory.isFull(), 5000); } if (Rs2Inventory.isFull()) { @@ -66,7 +65,7 @@ private void takeUncleanedFinds() { } private void cleanFinds() { - if (Rs2Inventory.contains("Uncleaned find") && Rs2GameObject.interact(ObjectID.SPECIMEN_TABLE_24556, "Clean")) { + if (Rs2Inventory.contains("Uncleaned find") && Microbot.getRs2TileObjectCache().query().interact(ObjectID.SPECIMEN_TABLE_24556, "Clean")) { sleepUntil(() -> !Rs2Inventory.contains("Uncleaned find"), 30000); if (!Rs2Inventory.contains("Uncleaned find")) { currentState = State.STORAGE_CRATE; @@ -87,7 +86,7 @@ private void storeFindsInCrate() { "Arrowheads" }; - if (!Rs2Inventory.contains("Uncleaned find") && Rs2GameObject.interact(ObjectID.STORAGE_CRATE, "Add finds")) { + if (!Rs2Inventory.contains("Uncleaned find") && Microbot.getRs2TileObjectCache().query().interact(ObjectID.STORAGE_CRATE, "Add finds")) { Rs2Keyboard.keyPress('2'); sleep(1000); diff --git a/src/main/java/net/runelite/client/plugins/microbot/wintertodt/MWintertodtScript.java b/src/main/java/net/runelite/client/plugins/microbot/wintertodt/MWintertodtScript.java index b415ae5bdb..a50bc1e368 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/wintertodt/MWintertodtScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/wintertodt/MWintertodtScript.java @@ -15,6 +15,7 @@ import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; 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.keyboard.Rs2Keyboard; @@ -51,7 +52,7 @@ public class MWintertodtScript extends Script { final WorldPoint sproutingRoots = new WorldPoint(1635, 3978, 0); String axe = ""; int wintertodtHp = -1; - private GameObject brazier; + private Rs2TileObjectModel brazierModel; boolean init = false; private static void changeState(State scriptState) { @@ -139,9 +140,12 @@ public boolean run(MWintertodtConfig config, MWintertodtPlugin plugin) { boolean wintertodtRespawning = Rs2Widget.hasWidget("returns in"); boolean isWintertodtAlive = Rs2Widget.hasWidget("Wintertodt's Energy"); - brazier = Rs2GameObject.findObject(BRAZIER_29312, config.brazierLocation().getOBJECT_BRAZIER_LOCATION()); - GameObject brokenBrazier = Rs2GameObject.findObject(ObjectID.BRAZIER_29313, config.brazierLocation().getOBJECT_BRAZIER_LOCATION()); - GameObject fireBrazier = Rs2GameObject.findObject(ObjectID.BURNING_BRAZIER_29314, config.brazierLocation().getOBJECT_BRAZIER_LOCATION()); + brazierModel = Microbot.getRs2TileObjectCache().query().withId(BRAZIER_29312) + .where(o -> o.getWorldLocation().equals(config.brazierLocation().getOBJECT_BRAZIER_LOCATION())).nearest(); + Rs2TileObjectModel brokenBrazierModel = Microbot.getRs2TileObjectCache().query().withId(ObjectID.BRAZIER_29313) + .where(o -> o.getWorldLocation().equals(config.brazierLocation().getOBJECT_BRAZIER_LOCATION())).nearest(); + Rs2TileObjectModel fireBrazierModel = Microbot.getRs2TileObjectCache().query().withId(ObjectID.BURNING_BRAZIER_29314) + .where(o -> o.getWorldLocation().equals(config.brazierLocation().getOBJECT_BRAZIER_LOCATION())).nearest(); boolean playerIsLowWarmth = getWarmthLevel() < config.warmthTreshhold(); // if use rejuvenation potion is enabled, we should check for potions instead of food boolean needBanking = !Rs2Inventory.hasItemAmount(config.rejuvenationPotions() ? "Rejuvenation potion " : config.food().getName(), config.minFood(), false, false) @@ -163,7 +167,7 @@ public boolean run(MWintertodtConfig config, MWintertodtPlugin plugin) { } dropUnnecessaryItems(); - shouldLightBrazier(isWintertodtAlive, needBanking, fireBrazier, brazier); + shouldLightBrazier(isWintertodtAlive, needBanking, fireBrazierModel, brazierModel); shouldBank(needBanking); shouldEat(); dodgeOrbDamage(); @@ -208,11 +212,11 @@ public boolean run(MWintertodtConfig config, MWintertodtPlugin plugin) { case WAITING: walkToBrazier(); - shouldLightBrazier(isWintertodtAlive, needBanking, fireBrazier, brazier); + shouldLightBrazier(isWintertodtAlive, needBanking, fireBrazierModel, brazierModel); break; case LIGHT_BRAZIER: - if (brazier != null && !Rs2Player.isAnimating()) { - if (Rs2GameObject.interact(brazier, "light")) { + if (brazierModel != null && !Rs2Player.isAnimating()) { + if (brazierModel.click("light")) { sleepGaussian(600, 150); } return; @@ -225,7 +229,7 @@ public boolean run(MWintertodtConfig config, MWintertodtPlugin plugin) { Rs2Combat.setSpecState(true, 1000); } if (!Rs2Player.isAnimating()) { - Rs2GameObject.interact(ObjectID.BRUMA_ROOTS, "Chop"); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.BRUMA_ROOTS, "Chop"); sleepUntil(Rs2Player::isAnimating, 2000); resetActions = false; Rs2Antiban.actionCooldown(); @@ -251,31 +255,30 @@ public boolean run(MWintertodtConfig config, MWintertodtPlugin plugin) { break; case BURN_LOGS: if (!Microbot.isGainingExp || resetActions) { - TileObject burningBrazier = Rs2GameObject.findObjectById(BURNING_BRAZIER_29314); - if (brokenBrazier != null && config.fixBrazier()) { - Rs2GameObject.interact(brokenBrazier, "fix"); + Rs2TileObjectModel burningBrazier = Microbot.getRs2TileObjectCache().query().withId(BURNING_BRAZIER_29314).nearest(); + if (brokenBrazierModel != null && config.fixBrazier()) { + brokenBrazierModel.click("fix"); Microbot.log("Fixing brazier"); sleepGaussian(300, 50); return; } - // this extra check is needed in case all braziers are broken or not burning - if (burningBrazier == null && brazier != null && config.relightBrazier()) { + if (burningBrazier == null && brazierModel != null && config.relightBrazier()) { - Rs2GameObject.interact(brazier, "light"); + brazierModel.click("light"); Microbot.log("Lighting brazier"); sleep(1500); return; } else { - if (burningBrazier.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) > 10 && brazier != null && config.relightBrazier()) { - Rs2GameObject.interact(brazier, "light"); + if (burningBrazier.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) > 10 && brazierModel != null && config.relightBrazier()) { + brazierModel.click("light"); Microbot.log("Lighting brazier"); sleep(1500); return; } } if (burningBrazier.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) < 10 && hasItemsToBurn()) { - Rs2GameObject.interact(burningBrazier, "feed"); + burningBrazier.click("feed"); Microbot.log("Feeding brazier"); resetActions = false; sleep(GAME_TICK_LENGTH * 3); @@ -358,7 +361,7 @@ private boolean shouldChopRoots() { return true; } - private boolean shouldLightBrazier(boolean isWintertodtAlive, boolean needBanking, GameObject fireBrazier, GameObject brazier) { + private boolean shouldLightBrazier(boolean isWintertodtAlive, boolean needBanking, Rs2TileObjectModel fireBrazier, Rs2TileObjectModel brazier) { if (!isWintertodtAlive) return false; if (needBanking) return false; if (state == State.CHOP_ROOTS) return false;// we are most likely to far from the brazier to light it in time @@ -410,7 +413,9 @@ private void walkToBrazier() { // //sleep(3000); // } } else if (Rs2Player.getWorldLocation().equals(config.brazierLocation().getBRAZIER_LOCATION()) && state == State.WAITING) { - Rs2GameObject.hoverOverObject(brazier); + if (brazierModel != null && brazierModel.getClickbox() != null) { + Microbot.getMouse().move(brazierModel.getClickbox().getBounds()); + } } } @@ -420,7 +425,7 @@ private void dodgeOrbDamage() { && WorldPoint.fromLocalInstance(Microbot.getClient(), graphicsObject.getLocation()).distanceTo(Rs2Player.getWorldLocation()) == 1) { //walk south - List gameObjects = new ArrayList<>(Rs2GameObject.getGameObjects(5)); + List gameObjects = Microbot.getRs2TileObjectCache().query().within(5).toList(); // we only need to dodge if there are 2 or more snow fall objects if (gameObjects.size() > 2) { Rs2Walker.walkFastCanvas(new WorldPoint(Rs2Player.getWorldLocation().getX(), Rs2Player.getWorldLocation().getY() - 1, Rs2Player.getWorldLocation().getPlane())); @@ -444,16 +449,19 @@ private boolean handleBankLogic(MWintertodtConfig config) { Rs2Walker.walkTo(crateLocation, 3); Rs2Player.waitForWalking(1000); } - GameObject crate = Rs2GameObject.getGameObject(crateLocation); + Rs2TileObjectModel crate = Microbot.getRs2TileObjectCache().query() + .where(o -> o.getWorldLocation().equals(crateLocation)).nearest(); if (crate != null) { + final Rs2TileObjectModel crateRef = crate; sleepUntil(() -> Rs2Inventory.count(ItemID.REJUVENATION_POTION_UNF) >= config.foodAmount(), () -> { - if (Rs2GameObject.interact(crate, "Take-concoction")) + if (crateRef.click("Take-concoction")) Rs2Inventory.waitForInventoryChanges(3000); }, 10000, 300); } - GameObject roots = Rs2GameObject.getGameObject(sproutingRoots); + Rs2TileObjectModel roots = Microbot.getRs2TileObjectCache().query() + .where(o -> o.getWorldLocation().equals(sproutingRoots)).nearest(); if (roots != null) { - Rs2GameObject.interact(roots, "Pick"); + roots.click("Pick"); Rs2Inventory.waitForInventoryChanges(5000); sleepUntil(() -> Rs2Inventory.count(ItemID.REJUVENATION_POTION_UNF) <= Rs2Inventory.count(ItemID.BRUMA_HERB), 10000); Rs2Inventory.combineClosest(ItemID.REJUVENATION_POTION_UNF, ItemID.BRUMA_HERB); From e8eebb82cae4b711b23accaa2e909c4c48411897 Mon Sep 17 00:00:00 2001 From: chsami Date: Thu, 9 Apr 2026 14:49:22 +0200 Subject: [PATCH 25/95] refactor: migrate minigame plugins to new query API Migrate Rs2Npc and Rs2GameObject calls across PestControl, FishingTrawler, TheMess, SummerGarden, MageTrainingArena, TowerOfLife, TearsOfGuthix, RoguesDen, MoonsOfPeril handlers (Blood/Blue/Eclipse Moon, Boss, Death, Resupply, Reward), GEFiremaker, and GECooker. Fix null-pointer chain in EclipseMoonHandler shield NPC lookup. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../TearsOfGuthix/TearsOfGuthixScript.java | 3 +- .../fishingtrawler/FishingTrawlerScript.java | 25 +++++---- .../MageTrainingArenaScript.java | 32 ++++++------ .../plugins/microbot/mess/TheMessScript.java | 12 +++-- .../handlers/BloodMoonHandler.java | 30 +++++------ .../handlers/BlueMoonHandler.java | 25 +++++---- .../moonsofperil/handlers/BossHandler.java | 20 ++++---- .../moonsofperil/handlers/DeathHandler.java | 3 +- .../handlers/EclipseMoonHandler.java | 27 +++++----- .../handlers/ResupplyHandler.java | 11 ++-- .../moonsofperil/handlers/RewardHandler.java | 3 +- .../pestcontrol/PestControlScript.java | 51 +++++++++++-------- .../microbot/roguesden/RoguesDenScript.java | 11 ++-- .../summergarden/SummerGardenScript.java | 33 ++++++------ .../TowerOfLifeCCScript.java | 25 +++++---- 15 files changed, 157 insertions(+), 154 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/TearsOfGuthix/TearsOfGuthixScript.java b/src/main/java/net/runelite/client/plugins/microbot/TearsOfGuthix/TearsOfGuthixScript.java index b34067f8aa..ebf38226b1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/TearsOfGuthix/TearsOfGuthixScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/TearsOfGuthix/TearsOfGuthixScript.java @@ -14,7 +14,6 @@ import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; 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.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -160,7 +159,7 @@ private void enterMinigame() { Microbot.log("Unequipping shield..."); Rs2Equipment.unEquip(EquipmentInventorySlot.SHIELD); } - Rs2GameObject.interact(JUNA, "Story"); + Microbot.getRs2TileObjectCache().query().interact(JUNA, "Story"); } while (Rs2Dialogue.hasContinue() && this.isRunning()) { Rs2Dialogue.clickContinue(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/fishingtrawler/FishingTrawlerScript.java b/src/main/java/net/runelite/client/plugins/microbot/fishingtrawler/FishingTrawlerScript.java index c14668ecea..301cd436e3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/fishingtrawler/FishingTrawlerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/fishingtrawler/FishingTrawlerScript.java @@ -1,8 +1,6 @@ package net.runelite.client.plugins.microbot.fishingtrawler; import lombok.extern.slf4j.Slf4j; -import net.runelite.api.GameObject; -import net.runelite.api.NPC; import net.runelite.api.coords.WorldPoint; import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; @@ -10,10 +8,11 @@ import net.runelite.client.plugins.microbot.breakhandler.BreakHandlerScript; import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; 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.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -46,11 +45,11 @@ public boolean run(FishingTrawlerConfig config) { return; } - if (Rs2GameObject.exists(OBJECT_TRAWLERNET)) { + if (Microbot.getRs2TileObjectCache().query().withId(OBJECT_TRAWLERNET).nearest() != null) { if (wasInsideBoat) { Microbot.log("Looting Rewards"); Microbot.status = "Looting Rewards"; - Rs2GameObject.interact(OBJECT_TRAWLERNET, "inspect"); + Microbot.getRs2TileObjectCache().query().interact(OBJECT_TRAWLERNET, "inspect"); Rs2Player.waitForWalking(); //check for visible trawler widget sleepUntil(() -> Rs2Widget.isWidgetVisible(367, 19), 5000); @@ -70,7 +69,7 @@ public boolean run(FishingTrawlerConfig config) { Microbot.log("Crossing Gangplank"); Rs2Player.waitForWalking(10000); } - } else if (Rs2GameObject.exists(OBJECT_DAISIES) && Rs2Player.getWorldLocation().getPlane() == 0) { + } else if (Microbot.getRs2TileObjectCache().query().withId(OBJECT_DAISIES).nearest() != null && Rs2Player.getWorldLocation().getPlane() == 0) { Microbot.log("Washed up — heading back"); Rs2Walker.walkTo(new WorldPoint(2676, 3170, 0)); } @@ -97,7 +96,7 @@ public boolean run(FishingTrawlerConfig config) { log.debug("Contribution widget missing or malformed."); } - if (Rs2Player.getWorldLocation().getPlane() == 0 && Rs2GameObject.exists(OBJECT_SHIPSLADDER)) { + if (Rs2Player.getWorldLocation().getPlane() == 0 && Microbot.getRs2TileObjectCache().query().withId(OBJECT_SHIPSLADDER).nearest() != null) { Microbot.log("In minigame — heading up ladder"); wasInsideBoat = true; Rs2GameObject.interact(new WorldPoint(1884, 4826, 0), "Climb-up"); @@ -111,22 +110,22 @@ public boolean run(FishingTrawlerConfig config) { if (!Rs2Player.isInteracting() && !Rs2Player.isMoving()) { log.debug("Tentacle phase"); - NPC tentacleNpc = Rs2Npc.getNpc("Enormous Tentacle"); + Rs2NpcModel tentacleNpc = Microbot.getRs2NpcCache().query().withName("Enormous Tentacle").nearest(); if (tentacleNpc != null) { if (!tentacle) { Microbot.log("Tentacle found, chopping it down"); - Rs2Camera.turnTo(tentacleNpc); + Rs2Camera.turnTo(tentacleNpc.getNpc()); } - sleepUntil(() -> tentacleNpc.getAnimation() == 8953, 10000); - Rs2Npc.interact("Enormous Tentacle", "Chop"); + sleepUntil(() -> tentacleNpc.getNpc().getAnimation() == 8953, 10000); + Microbot.getRs2NpcCache().query().withName("Enormous Tentacle").interact("Chop"); sleepUntilTick(2); - if (!Rs2Player.isInteracting()) Rs2Npc.interact("Enormous Tentacle", "Chop"); + if (!Rs2Player.isInteracting()) Microbot.getRs2NpcCache().query().withName("Enormous Tentacle").interact("Chop"); tentacle = true; wasInsideBoat = true; sleepUntil(() -> !Rs2Player.isInteracting()); } else if (tentacle) { - GameObject ladderObject = Rs2GameObject.getGameObject(OBJECT_TENTACLE_LADDER); + Rs2TileObjectModel ladderObject = Microbot.getRs2TileObjectCache().query().withId(OBJECT_TENTACLE_LADDER).nearest(); if (ladderObject != null) { WorldPoint current = Rs2Player.getWorldLocation(); WorldPoint ladder = ladderObject.getWorldLocation(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java b/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java index a03cf10e6c..c2cab74f59 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java @@ -14,16 +14,16 @@ import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.inventory.Rs2RunePouch; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.magic.*; 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.npc.Rs2NpcModel; + import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -189,7 +189,7 @@ public boolean run(MageTrainingArenaConfig config) { || currentPoints.get(currentRoom.getPoints()) >= getRequiredPoints().get(currentRoom.getPoints()) * (config.buyRewards() ? 1 : (buyable + 1))) { // Deposit items before leaving to maximize points if (currentRoom == Rooms.ENCHANTMENT && Rs2Inventory.contains(ItemID.MAGICTRAINING_ENCHAN_SHAPEORB)) { - Rs2GameObject.interact(ObjectID.MAGICTRAINING_ENCHA_HOLE, "Deposit"); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.MAGICTRAINING_ENCHA_HOLE, "Deposit"); Rs2Player.waitForWalking(); } if (currentRoom == Rooms.GRAVEYARD && Rs2Inventory.contains(ItemID.BANANA, ItemID.PEACH)) { @@ -331,19 +331,19 @@ private void handleEnchantmentRoom() { if (!Rs2Walker.walkTo(new WorldPoint(3363, 9640, 0))) return; - Rs2GameObject.interact(ObjectID.MAGICTRAINING_ENCHA_HOLE, "Deposit"); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.MAGICTRAINING_ENCHA_HOLE, "Deposit"); Rs2Player.waitForWalking(); return; } - boolean successFullLoot = Rs2GroundItem.loot(ItemID.MAGICTRAINING_DRAGONSTONE, 12) && Rs2Inventory.waitForInventoryChanges(5000); + boolean successFullLoot = Microbot.getRs2TileItemCache().query().withId(ItemID.MAGICTRAINING_DRAGONSTONE).interact("Take") && Rs2Inventory.waitForInventoryChanges(5000); if (successFullLoot && Rs2Inventory.emptySlotCount() > 0) return; var bonusShape = getBonusShape(); if (bonusShape == null) return; - var object = Rs2GameObject.getGameObject(obj -> (obj.getId() == bonusShape.getObjectId()) && Rs2Camera.isTileOnScreen(obj)); + Rs2TileObjectModel object = Microbot.getRs2TileObjectCache().query().withId(bonusShape.getObjectId()).where(obj -> Rs2Camera.isTileOnScreen(obj.getLocalLocation())).nearest(); if (object == null) { var index = Rs2Random.between(0, 4); Rs2Walker.walkTo(new WorldPoint[]{ @@ -369,7 +369,7 @@ private void handleEnchantmentRoom() { Rs2Inventory.interact(itemId); sleepUntil(() -> !Rs2Inventory.contains(itemId) || itemId != ItemID.MAGICTRAINING_DRAGONSTONE && bonusShape != getBonusShape(), 20000); - } else if (Rs2GameObject.interact(object, "Take-from")) { + } else if (object.click("Take-from")) { Rs2Inventory.waitForInventoryChanges(1000); } else if (Rs2Player.getWorldLocation().distanceTo(object.getWorldLocation()) > 10){ Rs2Walker.walkFastCanvas(object.getWorldLocation()); @@ -427,7 +427,7 @@ private void handleTelekineticRoom() { if (guardian.getWorldLocation().equals(room.getFinishLocation())) { sleepUntil(() -> room.getGuardian().getId() == NpcID.MAGICTRAINING_GUARD_MAZE_COMPLETE); sleep(200, 400); - Rs2Npc.interact(new Rs2NpcModel(room.getGuardian()), "New-maze"); + new Rs2NpcModel(room.getGuardian()).click("New-maze"); sleepUntil(() -> Rs2Player.getWorldLocation().distanceTo(teleRoom.getArea()) != 0); } else { while (!Rs2Player.getWorldLocation().equals(targetConverted) @@ -454,7 +454,7 @@ private void handleTelekineticRoom() { if (Rs2Random.dicePercentage(50)) { Rs2Camera.turnTo(room.getGuardian()); } - Rs2Npc.interact(new Rs2NpcModel(room.getGuardian())); + new Rs2NpcModel(room.getGuardian()).click(); sleepUntil(()->room.getGuardian().getId() != NpcID.MAGICTRAINING_GUARD_MAZE_MOVING); } } @@ -544,13 +544,13 @@ private void handleAlchemistRoom() { } if (room.getSuggestion() == null) { - Rs2GameObject.interact("Cupboard", "Search"); + Microbot.getRs2TileObjectCache().query().withName("Cupboard").interact("Search"); sleep(300,600); if (sleepUntilTrue(Rs2Player::isMoving, 100, 1000)) sleepUntil(() -> !Rs2Player.isMoving()); } else { - Rs2GameObject.interact(room.getSuggestion().getGameObject(), "Take-5"); + new Rs2TileObjectModel(room.getSuggestion().getGameObject()).click("Take-5"); Rs2Inventory.waitForInventoryChanges(3000); sleep(300,600); } @@ -572,7 +572,7 @@ private void buyReward(Rewards reward) { return; if (!Rs2Widget.isWidgetVisible(197, 0)) { - Rs2Npc.interact(NpcID.MAGICTRAINING_GUARD_REWARDS, "Trade-with"); + Microbot.getRs2NpcCache().query().withId(NpcID.MAGICTRAINING_GUARD_REWARDS).interact("Trade-with"); sleepUntil(() -> Rs2Widget.isWidgetVisible(197, 0)); sleepGaussian(600, 150); return; @@ -614,7 +614,7 @@ public static void enterRoom(Rooms room) { if (!Rs2Walker.walkTo(portalPoint)) return; - Rs2GameObject.interact(room.getTeleporter(), "Enter"); + Microbot.getRs2TileObjectCache().query().interact(room.getTeleporter(), "Enter"); Rs2Player.waitForAnimation(); if (Rs2Widget.hasWidget("You must talk to the Entrance Guardian")) firstTime = true; @@ -641,7 +641,7 @@ public static void leaveRoom() { if (!Rs2Walker.walkTo(exit)) return; - Rs2GameObject.interact(ObjectID.MAGICTRAINING_RETURNDOOR, "Enter"); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.MAGICTRAINING_RETURNDOOR, "Enter"); Rs2Player.waitForWalking(); } @@ -651,7 +651,7 @@ private boolean handleFirstTime() { return true; if (!Rs2Dialogue.isInDialogue()) - Rs2Npc.interact(NpcID.MAGICTRAINING_GUARD_ENTRANCE, "Talk-to"); + Microbot.getRs2NpcCache().query().withId(NpcID.MAGICTRAINING_GUARD_ENTRANCE).interact("Talk-to"); else if (Rs2Dialogue.hasSelectAnOption() && Rs2Widget.hasWidget("I'm new to this place")) Rs2Widget.clickWidget("I'm new to this place"); else if (Rs2Dialogue.hasSelectAnOption() && Rs2Widget.hasWidget("Thanks, bye!")) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessScript.java b/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessScript.java index 4962120886..d2d2650204 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessScript.java @@ -19,6 +19,7 @@ import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; 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; @@ -142,7 +143,7 @@ private void handleState() { return; } if (Rs2GameObject.canReach(BUFFET_TABLE_LOC)) { - Rs2GameObject.interact("Buffet table", "Serve"); + Microbot.getRs2TileObjectCache().query().withName("Buffet table").interact("Serve"); sleepUntil(() -> Rs2Player.waitForXpDrop(Skill.COOKING), 10000); } else { debug("Cannot reach the buffet table, waiting..."); @@ -167,7 +168,8 @@ private void handleState() { private BooleanSupplier returnEmptyBowls() { return () -> { if (Rs2Inventory.hasItem(ItemID.BOWL_EMPTY)) { - Rs2Inventory.useItemOnObject(ItemID.BOWL_EMPTY, Rs2GameObject.getGameObject(UTENSIL_CUPBOARD_LOC).getId()); + Rs2TileObjectModel cupboard = Microbot.getRs2TileObjectCache().query().within(UTENSIL_CUPBOARD_LOC, 1).nearest(); + if (cupboard != null) Rs2Inventory.useItemOnObject(ItemID.BOWL_EMPTY, cupboard.getId()); sleepUntil(() -> Rs2Inventory.count(ItemID.BOWL_EMPTY) == 0, 10000); Rs2Antiban.actionCooldown(); return !Rs2Inventory.hasItem(ItemID.BOWL_EMPTY); @@ -389,7 +391,8 @@ private BooleanSupplier getUtensils() { private BooleanSupplier fillBowl() { return () -> { if (Rs2Inventory.hasItem(ItemID.BOWL_EMPTY)) { - Rs2Inventory.useItemOnObject(ItemID.BOWL_EMPTY, Rs2GameObject.getGameObject(SINK_LOC).getId()); + Rs2TileObjectModel sink = Microbot.getRs2TileObjectCache().query().within(SINK_LOC, 1).nearest(); + if (sink != null) Rs2Inventory.useItemOnObject(ItemID.BOWL_EMPTY, sink.getId()); sleepUntil(() -> !Rs2Inventory.hasItem(ItemID.BOWL_EMPTY), 10000); Rs2Antiban.actionCooldown(); @@ -441,7 +444,8 @@ private BooleanSupplier cook() { } return () -> { if (Rs2GameObject.canReach(CLAY_OVEN_LOC)) { - Rs2Inventory.useItemOnObject(itemId, Rs2GameObject.getGameObject(CLAY_OVEN_LOC).getId()); + Rs2TileObjectModel oven = Microbot.getRs2TileObjectCache().query().within(CLAY_OVEN_LOC, 1).nearest(); + if (oven != null) Rs2Inventory.useItemOnObject(itemId, oven.getId()); sleepUntil(() -> Rs2Widget.hasWidget("How many would you like to cook?")); if (Rs2Widget.hasWidget("How many would you like to cook?")) { Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); diff --git a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BloodMoonHandler.java b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BloodMoonHandler.java index 3c542de17d..0021ce57a5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BloodMoonHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BloodMoonHandler.java @@ -13,9 +13,7 @@ import net.runelite.client.plugins.microbot.moonsofperil.MoonsOfPerilConfig; import net.runelite.client.plugins.microbot.moonsofperil.MoonsOfPerilPlugin; import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -74,7 +72,7 @@ public State execute() { } int bossNpcID = NpcID.PMOON_BOSS_BLOOD_MOON_VIS; - while (Rs2Widget.isWidgetVisible(bossHealthBarWidgetID) || Rs2Npc.getNpc(bossNpcID) != null) { + while (Rs2Widget.isWidgetVisible(bossHealthBarWidgetID) || Microbot.getRs2NpcCache().query().withId(bossNpcID).nearest() != null) { if (isSpecialAttack1Sequence()) { specialAttack1Sequence(); } @@ -99,7 +97,7 @@ else if (net.runelite.client.plugins.microbot.moonsofperil.handlers.BossHandler. * Returns True if the jaguar NPC is found. */ public boolean isSpecialAttack1Sequence() { - Rs2NpcModel jaguar = Rs2Npc.getNpc(NpcID.PMOON_BOSS_JAGUAR); + Rs2NpcModel jaguar = Microbot.getRs2NpcCache().query().withId(NpcID.PMOON_BOSS_JAGUAR).nearest(); return (jaguar != null && Rs2Widget.isWidgetVisible(bossHealthBarWidgetID)); } @@ -107,7 +105,7 @@ public boolean isSpecialAttack1Sequence() { * Returns True if the Moonfire gameobject is found and the boss is not attackable. */ public boolean isSpecialAttack2Sequence() { - return Rs2GameObject.exists(ObjectID.PMOON_BOSS_BLOOD_FIRE) && Rs2Npc.getNpc(sigilNpcID) == null && Rs2Widget.isWidgetVisible(bossHealthBarWidgetID); + return Microbot.getRs2TileObjectCache().query().withId(ObjectID.PMOON_BOSS_BLOOD_FIRE).nearest() != null && Microbot.getRs2NpcCache().query().withId(sigilNpcID).nearest() == null && Rs2Widget.isWidgetVisible(bossHealthBarWidgetID); } /** Blood Moon – Blood Rain Special Attack Handler */ @@ -119,7 +117,7 @@ public void specialAttack2Sequence() { sleepUntil(() -> Rs2Player.getWorldLocation().equals(afterRainTile)); while (isSpecialAttack2Sequence()) { WorldPoint playerTile = Rs2Player.getWorldLocation(); - GameObject bloodPool = Rs2GameObject.getGameObject(o -> o.getId() == ObjectID.PMOON_BOSS_BLOOD_POOL && o.getWorldLocation().equals(playerTile)); + var bloodPool = Microbot.getRs2TileObjectCache().query().withId(ObjectID.PMOON_BOSS_BLOOD_POOL).where(o -> o.getWorldLocation().equals(playerTile)).nearest(); if (bloodPool != null) { if (debugLogging) {Microbot.log("Standing on dangerous tile: " + playerTile);} WorldPoint safeTile = getRandomSafeTile(ObjectID.PMOON_BOSS_BLOOD_POOL, 1); @@ -143,7 +141,7 @@ public void specialAttack1Sequence() { final long startMs = System.currentTimeMillis(); /* 1 find the sigil NPC (2×2, SW tile = sigilLoc) */ - Rs2NpcModel sigilNpc = Rs2Npc.getNpcs(n -> n.getId() == sigilNpcID).findFirst().orElse(null); + Rs2NpcModel sigilNpc = Microbot.getRs2NpcCache().query().where(n -> n.getId() == sigilNpcID).toList().stream().findFirst().orElse(null); if (sigilNpc == null) { if (debugLogging) {Microbot.log("no sigil NPC – bail");} return; @@ -181,10 +179,10 @@ public void specialAttack1Sequence() { if (!arrived) return; /* 4 ─ lock the target jaguar (SW tile == spawnTile) ---------------- */ - Rs2NpcModel targetJaguar = Rs2Npc.getNpcs(n -> + Rs2NpcModel targetJaguar = Microbot.getRs2NpcCache().query().where(n -> n.getId() == NpcID.PMOON_BOSS_JAGUAR && n.getWorldLocation().equals(spawnTile)) - .findFirst().orElse(null); + .toList().stream().findFirst().orElse(null); if (targetJaguar == null) { if (debugLogging) {Microbot.log("jaguar not on expected spawn");} return; @@ -195,7 +193,7 @@ public void specialAttack1Sequence() { while (isSpecialAttack1Sequence() && System.currentTimeMillis() - startMs < TIMEOUT_MS) { int bloodPoolTick = MoonsOfPerilPlugin.bloodPoolTick; - if (targetJaguar.getAnimation() == 12492) { + if (targetJaguar.getNpc().getAnimation() == 12492) { sleep(600); break; } @@ -204,7 +202,7 @@ public void specialAttack1Sequence() { Rs2Walker.walkFastCanvas(evadeTile, true); } else if (bloodPoolTick == 5) { if (debugLogging) {Microbot.log("ATTACK jaguar");} - Rs2Npc.attack(targetJaguar); + targetJaguar.click("Attack"); } else if (bloodPoolTick == 6) { if (debugLogging) {Microbot.log("Clicking on ground to stop attacking");} @@ -226,10 +224,12 @@ public static WorldPoint getRandomSafeTile(int dangerousId, int distance) if (centre == null) return null; // ── 2. collect all dangerous tiles within radius ── - Set dangerTiles = Rs2GameObject - .getGameObjects(o -> o.getId() == dangerousId, distance) + Set dangerTiles = Microbot.getRs2TileObjectCache().query() + .withId(dangerousId) + .within(distance) + .toList() .stream() - .map(GameObject::getWorldLocation) + .map(o -> o.getWorldLocation()) .collect(Collectors.toSet()); // ── 3. enumerate every tile in the square centred on the player ── diff --git a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BlueMoonHandler.java b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BlueMoonHandler.java index 9488b6f6e2..47ce2a0021 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BlueMoonHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BlueMoonHandler.java @@ -12,8 +12,7 @@ import net.runelite.client.plugins.microbot.moonsofperil.MoonsOfPerilConfig; import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -74,7 +73,7 @@ public State execute() { sleepUntil(() -> Rs2Widget.isWidgetVisible(bossHealthBarWidgetID),5_000); } int bossNpcID = NpcID.PMOON_BOSS_BLUE_MOON_VIS; - while (Rs2Widget.isWidgetVisible(bossHealthBarWidgetID) || Rs2Npc.getNpc(bossNpcID) != null) { + while (Rs2Widget.isWidgetVisible(bossHealthBarWidgetID) || Microbot.getRs2NpcCache().query().withId(bossNpcID).nearest() != null) { if (isSpecialAttack1Sequence()) { specialAttack1Sequence(); } @@ -100,7 +99,7 @@ else if (net.runelite.client.plugins.microbot.moonsofperil.handlers.BossHandler. * Returns True if the tornado NPC is found. */ public boolean isSpecialAttack1Sequence() { - return (Rs2Npc.getNpc(NpcID.PMOON_BOSS_WINTER_STORM) != null && Rs2Widget.isWidgetVisible(bossHealthBarWidgetID) && Rs2Npc.getNpc(sigilNpcID) == null); + return (Microbot.getRs2NpcCache().query().withId(NpcID.PMOON_BOSS_WINTER_STORM).nearest() != null && Rs2Widget.isWidgetVisible(bossHealthBarWidgetID) && Microbot.getRs2NpcCache().query().withId(sigilNpcID).nearest() == null); } public void specialAttack1Sequence() @@ -112,7 +111,7 @@ public void specialAttack1Sequence() boss.eatIfNeeded(); boss.drinkIfNeeded(); if (debugLogging) {Microbot.log("Sleeping until the special attack sequence is over");} - sleepUntil(() -> Rs2Npc.getNpc(sigilNpcID) != null || !Rs2Widget.isWidgetVisible(bossHealthBarWidgetID), 35_000); + sleepUntil(() -> Microbot.getRs2NpcCache().query().withId(sigilNpcID).nearest() != null || !Rs2Widget.isWidgetVisible(bossHealthBarWidgetID), 35_000); } public void specialAttack2IdleSequence() @@ -124,7 +123,7 @@ public void specialAttack2IdleSequence() boss.eatIfNeeded(); boss.drinkIfNeeded(); if (debugLogging) {Microbot.log("Sleeping until the special attack sequence is over");} - sleepUntil(() -> Rs2Npc.getNpc(sigilNpcID) != null || !Rs2Widget.isWidgetVisible(bossHealthBarWidgetID), 35_000); + sleepUntil(() -> Microbot.getRs2NpcCache().query().withId(sigilNpcID).nearest() != null || !Rs2Widget.isWidgetVisible(bossHealthBarWidgetID), 35_000); } @@ -132,8 +131,8 @@ public void specialAttack2IdleSequence() * Returns True if the icicle NPC is found. */ public boolean isSpecialAttack2Sequence() { - Rs2NpcModel icicle = Rs2Npc.getNpc(NpcID.PMOON_BOSS_ICICLE_UNCRACKED); - Rs2NpcModel sigil = Rs2Npc.getNpc(sigilNpcID); + Rs2NpcModel icicle = Microbot.getRs2NpcCache().query().withId(NpcID.PMOON_BOSS_ICICLE_UNCRACKED).nearest(); + Rs2NpcModel sigil = Microbot.getRs2NpcCache().query().withId(sigilNpcID).nearest(); if (icicle != null && sigil == null) { if (debugLogging) {Microbot.log("An icicle has spawned – We've entered Special Attack 2 Sequence");} return true; @@ -163,10 +162,10 @@ public void specialAttack2Sequence(MoonsOfPerilConfig cfg) while (isSpecialAttack2Sequence() && matches.isEmpty() && System.currentTimeMillis() - pollStart < POLL_TIMEOUT_MS) { - matches = Rs2Npc.getNpcs(n -> + matches = Microbot.getRs2NpcCache().query().where(n -> n.getId() == ICICLE_NPC_ID && - n.getAnimation() == ICICLE_ANIM_ID) - .collect(Collectors.toList()); + n.getNpc().getAnimation() == ICICLE_ANIM_ID) + .toList(); if (matches.isEmpty()) sleep(300); } @@ -193,7 +192,7 @@ public void specialAttack2Sequence(MoonsOfPerilConfig cfg) System.currentTimeMillis() - phaseStart < PHASE_TIMEOUT_MS) { if (!Rs2Combat.inCombat()) { - Rs2Npc.attack(icicle); + icicle.click("Attack"); } WorldPoint attackTile = Rs2Player.getWorldLocation(); if (debugLogging) {Microbot.log(ts.get() + "Attack location calculated as: " + attackTile);} @@ -222,7 +221,7 @@ public void specialAttack2Sequence(MoonsOfPerilConfig cfg) boss.drinkIfNeeded(); if (debugLogging) {Microbot.log(ts.get() + "Waiting for all icicles to despawn…");} - sleepUntil(() -> Rs2Npc.getNpc(ICICLE_NPC_ID) == null); + sleepUntil(() -> Microbot.getRs2NpcCache().query().withId(ICICLE_NPC_ID).nearest() == null); if (debugLogging) {Microbot.log(ts.get() + "specialAttack2Sequence() COMPLETE");} } diff --git a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BossHandler.java b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BossHandler.java index d15579c074..09cf57341c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BossHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BossHandler.java @@ -12,11 +12,9 @@ import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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 net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.tile.Rs2Tile; @@ -72,7 +70,7 @@ public void enterBossArena(String bossName, int bossStatueID, WorldPoint bossWor } sleepUntil(() -> Rs2Player.getWorldLocation().equals(bossWorldPoint)); } - if (Rs2GameObject.interact(bossStatueID, "Use")) { + if (Microbot.getRs2TileObjectCache().query().interact(bossStatueID, "Use")) { if (debugLogging) {Microbot.log("Entering " + bossName + " arena");} sleepUntil(() -> !Rs2Player.getWorldLocation().equals(bossWorldPoint),5_000); } @@ -133,7 +131,7 @@ public static void meleePrayerOn() { * Returns true if the sigil NPC (the highlighted attack tile) is present */ public static boolean isNormalAttackSequence(int sigilNpcID) { - return Rs2Npc.getNpc(sigilNpcID) != null; + return Microbot.getRs2NpcCache().query().withId(sigilNpcID).nearest() != null; } /** @@ -161,7 +159,7 @@ public void normalAttackSequence(int sigilNpcID, while (sigilMoves <= 3 && isNormalAttackSequence(sigilNpcID)) { /* 1 ─ detect a new sigil square */ - Rs2NpcModel sigil = Rs2Npc.getNpc(sigilNpcID); + Rs2NpcModel sigil = Microbot.getRs2NpcCache().query().withId(sigilNpcID).nearest(); if (sigil == null) { sleep(300); if (debugLogging) {Microbot.log("Sigil not found. Breaking out of sequence");} @@ -193,10 +191,10 @@ public void normalAttackSequence(int sigilNpcID, } /* 4 ─ attack the boss whenever not in combat */ - Rs2NpcModel boss = Rs2Npc.getNpc(bossNpcID); + Rs2NpcModel boss = Microbot.getRs2NpcCache().query().withId(bossNpcID).nearest(); if (boss != null && !Rs2Combat.inCombat()) { if (debugLogging) {Microbot.log("Attacking the boss");} - Rs2Npc.attack(bossNpcID); + Microbot.getRs2NpcCache().query().withId(bossNpcID).interact("Attack"); } sleep(300); @@ -215,7 +213,7 @@ public void bossBailOut(WorldPoint bailOutLocation) { long endTime = System.currentTimeMillis() + 10_000; while (System.currentTimeMillis() < endTime) { - if (Rs2GameObject.interact(exitStairsGroundObjectID)) { + if (Microbot.getRs2TileObjectCache().query().interact(exitStairsGroundObjectID)) { sleepUntil(() -> Rs2Widget.isWidgetVisible(Widgets.BOSS_HEALTH_BAR.getID()),5_000); if (debugLogging) {Microbot.log("Successfully bailed out of the boss arena");} return; @@ -230,8 +228,8 @@ public void bossBailOut(WorldPoint bailOutLocation) { /** If current run energy is less than 80%, recharges run energy at a campfire located on the world canvas */ public static void rechargeRunEnergy() { - if (Rs2GameObject.getGameObject(ObjectID.PMOON_RANGE) != null && Rs2Player.getRunEnergy() <=80) { - Rs2GameObject.interact(ObjectID.PMOON_RANGE, "Make-cuppa"); + if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.PMOON_RANGE).nearest() != null && Rs2Player.getRunEnergy() <=80) { + Microbot.getRs2TileObjectCache().query().interact(ObjectID.PMOON_RANGE, "Make-cuppa"); sleep(600); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/DeathHandler.java b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/DeathHandler.java index 99689789b5..893bdc548b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/DeathHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/DeathHandler.java @@ -11,7 +11,6 @@ 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.inventory.Rs2ItemModel; -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.walker.Rs2Walker; @@ -81,7 +80,7 @@ private boolean retrieveDeathItems() { if (debugLogging) {Microbot.log("Attempting to walk back to grave site");} Rs2Walker.walkTo(graveLocation, 2); sleepUntil(() -> (Rs2Player.getWorldLocation().distanceTo(graveLocation) <= 3), 60_000); - if (Rs2Npc.interact(NpcID.GRAVESTONE_DEFAULT, "Loot")) { + if (Microbot.getRs2NpcCache().query().withId(NpcID.GRAVESTONE_DEFAULT).interact("Loot")) { if (debugLogging) {Microbot.log("Successfully looted gravestone");} return true; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/EclipseMoonHandler.java b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/EclipseMoonHandler.java index 203adf937d..a4eb640a6d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/EclipseMoonHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/EclipseMoonHandler.java @@ -13,8 +13,7 @@ import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; import net.runelite.client.plugins.microbot.util.coords.Rs2LocalPoint; 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.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; @@ -74,7 +73,7 @@ public State execute() { sleepUntil(() -> Rs2Widget.isWidgetVisible(bossHealthBarWidgetID), 5_000); } int bossNpcID = NpcID.PMOON_BOSS_ECLIPSE_MOON_VIS; - while (Rs2Widget.isWidgetVisible(bossHealthBarWidgetID) || Rs2Npc.getNpc(bossNpcID) != null) { + while (Rs2Widget.isWidgetVisible(bossHealthBarWidgetID) || Microbot.getRs2NpcCache().query().withId(bossNpcID).nearest() != null) { if (isSpecialAttack1Sequence()) { specialAttack1Sequence(); } @@ -98,15 +97,17 @@ else if (net.runelite.client.plugins.microbot.moonsofperil.handlers.BossHandler. * Returns True if the eclipseMoonShield NPC is found. */ public boolean isSpecialAttack1Sequence() { - Rs2NpcModel eclipseMoonShield = Rs2Npc.getNpc(NpcID.PMOON_BOSS_ECLIPSE_MOON_SHIELD); - return eclipseMoonShield != null && Rs2Npc.getNpc(sigilNpcID) == null; + Rs2NpcModel eclipseMoonShield = Microbot.getRs2NpcCache().query().withId(NpcID.PMOON_BOSS_ECLIPSE_MOON_SHIELD).nearest(); + return eclipseMoonShield != null && Microbot.getRs2NpcCache().query().withId(sigilNpcID).nearest() == null; } /** Eclipse – Moon Shield Special-Attack Handler */ public void specialAttack1Sequence() { Rs2Prayer.disableAllPrayers(); - WorldPoint spawn = Rs2Npc.getNpc(NpcID.PMOON_BOSS_ECLIPSE_MOON_SHIELD).getWorldLocation(); + var shieldNpc = Microbot.getRs2NpcCache().query().withId(NpcID.PMOON_BOSS_ECLIPSE_MOON_SHIELD).nearest(); + if (shieldNpc == null) return; + WorldPoint spawn = shieldNpc.getWorldLocation(); if (debugLogging) {Microbot.log("Exact Moonshield location = " + spawn);} /*if we enter arena mid attack phase, bail out*/ if (!spawn.equals(shieldSpawnTile)) { @@ -152,7 +153,7 @@ public void specialAttack1Sequence() if (debugLogging) {Microbot.log("Running to the normal attack sequence tile");} Rs2Walker.walkFastCanvas(fin, true); if (debugLogging) {Microbot.log("Sleeping until the Sigil tile spawns");} - sleepUntil(() -> Rs2Npc.getNpc(sigilNpcID) != null, 4_000); + sleepUntil(() -> Microbot.getRs2NpcCache().query().withId(sigilNpcID).nearest() != null, 4_000); if (debugLogging) {Microbot.log("Searing Rays phase finished");} } @@ -168,7 +169,7 @@ public boolean isSpecialAttack2Sequence() { WorldPoint center = cloneAttackTile; WorldPoint playerTile = Rs2Player.getWorldLocation(); - Rs2NpcModel bossNPC = Rs2Npc.getNpc(NpcID.PMOON_BOSS_ECLIPSE_MOON_VIS); + Rs2NpcModel bossNPC = Microbot.getRs2NpcCache().query().withId(NpcID.PMOON_BOSS_ECLIPSE_MOON_VIS).nearest(); // 1. Captures the conditions required for the start of the special attack sequence. if (playerTile.equals(center) && Rs2Player.getAnimation() == AnimationID.HUMAN_TROLL_FLYBACK_MERGE) { @@ -183,7 +184,7 @@ public boolean isSpecialAttack2Sequence() } // 2. Captures the conditions required if we spawn into the arena midway through the special attack phase. - if (playerTile.equals(center) && bossNPC != null && Rs2Npc.getNpc(sigilNpcID) == null && !bossNPC.getLocalLocation().equals(Rs2LocalPoint.fromWorldInstance(center))) { + if (playerTile.equals(center) && bossNPC != null && Microbot.getRs2NpcCache().query().withId(sigilNpcID).nearest() == null && !bossNPC.getLocalLocation().equals(Rs2LocalPoint.fromWorldInstance(center))) { boss.equipInventorySetup(equipmentClones); BossHandler.meleePrayerOn(); return true; @@ -206,10 +207,10 @@ public void specialAttack2Sequence() { // 1. Look at ALL Eclipse-Moon NPCs this tick that have the specific spawn animation. Game mechanics mean this list SHOULD only return 1 NPC - List spawningClones = Rs2Npc - .getNpcs(n -> n.getId() == CLONE_NPC_ID - && n.getAnimation() == CLONE_SPAWN_ANIM) - .collect(Collectors.toList()); + List spawningClones = Microbot.getRs2NpcCache().query() + .where(n -> n.getId() == CLONE_NPC_ID + && n.getNpc().getAnimation() == CLONE_SPAWN_ANIM) + .toList(); if (debugLogging) {Microbot.log("Collected all NPCs that match NPC ID & NPC Animation. Total = " + spawningClones.size());} // 2. Find the first clone within the list that matches the filter 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..d10598a152 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 @@ -7,7 +7,6 @@ import net.runelite.client.plugins.microbot.breakhandler.BreakHandlerScript; import net.runelite.client.plugins.microbot.moonsofperil.enums.State; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -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.walker.Rs2Walker; @@ -73,7 +72,7 @@ private void makeMoonlightPotions(int moonlightPotionsQuantum) { /* Take herblore supplies */ while (Rs2Inventory.count(ItemID.VIAL_WATER) < amountToCreate) { if (debugLogging) {Microbot.log("Take herblore supplies from supply crate");} - if (Rs2GameObject.interact(ObjectID.PMOON_SUPPLY_CRATE, "Take from")) { + if (Microbot.getRs2TileObjectCache().query().interact(ObjectID.PMOON_SUPPLY_CRATE, "Take from")) { Rs2Dialogue.sleepUntilHasDialogueOption("Take herblore supplies."); Rs2Dialogue.clickOption("Take herblore supplies."); Rs2Inventory.waitForInventoryChanges(1_500); @@ -84,7 +83,7 @@ private void makeMoonlightPotions(int moonlightPotionsQuantum) { while (Rs2Inventory.count(ItemID.MOONLIGHT_GRUB) + Rs2Inventory.count(ItemID.MOONLIGHT_GRUB_PASTE) < amountToCreate) { if (debugLogging) {Microbot.log("Collect Moonlight Grub");} - if (Rs2GameObject.interact(ObjectID.PMOON_GRUB_SAPLING, "Collect-from")) { + if (Microbot.getRs2TileObjectCache().query().interact(ObjectID.PMOON_GRUB_SAPLING, "Collect-from")) { sleepUntil(() -> Rs2Inventory.count(ItemID.MOONLIGHT_GRUB) + Rs2Inventory.count(ItemID.MOONLIGHT_GRUB_PASTE) >= amountToCreate, 8_000); } @@ -130,7 +129,7 @@ private boolean ensureBigNet() { long start = System.currentTimeMillis(); while (!Rs2Inventory.contains(ItemID.BIG_NET) && System.currentTimeMillis() - start < 15_000) { - if (Rs2GameObject.interact(ObjectID.PMOON_SUPPLY_CRATE, "Take from")) { // interact exists :contentReference[oaicite:1]{index=1} + if (Microbot.getRs2TileObjectCache().query().interact(ObjectID.PMOON_SUPPLY_CRATE, "Take from")) { // interact exists :contentReference[oaicite:1]{index=1} Rs2Dialogue.sleepUntilHasDialogueOption("Take fishing supplies."); Rs2Dialogue.clickOption("Take fishing supplies."); @@ -182,7 +181,7 @@ private void fishBream() { } if (!Rs2Player.isAnimating()) { - Rs2GameObject.interact(51367, "Fish"); + Microbot.getRs2TileObjectCache().query().interact(51367, "Fish"); sleep(3000, 4000); } sleep(300, 500); @@ -201,7 +200,7 @@ private void cookBream() { while (Rs2Inventory.contains(ItemID.BREAM_FISH_RAW)) { if (!Rs2Player.isAnimating()) { - if (Rs2GameObject.interact(ObjectID.PMOON_RANGE, "Cook")) { + if (Microbot.getRs2TileObjectCache().query().interact(ObjectID.PMOON_RANGE, "Cook")) { sleep(600, 900); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/RewardHandler.java b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/RewardHandler.java index 1ef0ad2aba..66a14163c2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/RewardHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/RewardHandler.java @@ -8,7 +8,6 @@ import net.runelite.client.plugins.microbot.moonsofperil.enums.State; import net.runelite.client.plugins.microbot.moonsofperil.enums.Widgets; import net.runelite.client.plugins.microbot.moonsofperil.MoonsOfPerilConfig; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import javax.inject.Inject; @@ -51,7 +50,7 @@ public boolean validate() { public State execute() { BreakHandlerScript.setLockState(true); boss.walkToBoss(null, "Rewards Chest", rewardChestLocation); - if (Rs2GameObject.interact(lunarChestGameObjectID, "Claim")) { + if (Microbot.getRs2TileObjectCache().query().interact(lunarChestGameObjectID, "Claim")) { if (debugLogging) {Microbot.log("Successfully claimed rewards from Lunar Chest");} rewardChestCount.incrementAndGet(); sleep(2_400); diff --git a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java index 901ccc3f7b..501872e5d4 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java @@ -16,11 +16,10 @@ import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; 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.npc.Rs2NpcModel; + import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -150,14 +149,16 @@ public boolean run(PestControlConfig config) { Rs2Combat.setSpecState(true, config.specialAttackPercentage() * 10); Widget activity = Rs2Widget.getWidget(26738700); //145 = 100% if (activity != null && activity.getChild(0).getWidth() <= 20 && !Rs2Combat.inCombat()) { - Optional attackableNpc = Rs2Npc.getAttackableNpcs().findFirst(); - attackableNpc.ifPresent(rs2NpcModel -> Rs2Npc.interact(rs2NpcModel.getId(), "attack")); + Rs2NpcModel attackableNpc = Microbot.getRs2NpcCache().query() + .where(n -> n.getNpc() != null && !n.getNpc().isDead() && n.getNpc().getCombatLevel() > 0) + .nearest(); + if (attackableNpc != null) attackableNpc.click("Attack"); return; } - var brawler = Rs2Npc.getNpc("brawler"); + var brawler = Microbot.getRs2NpcCache().query().withName("brawler").nearest(); if (brawler != null && brawler.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) < 3) { - Rs2Npc.interact(brawler, "attack"); + brawler.click("Attack"); sleepUntil(() -> !Rs2Combat.inCombat()); return; } @@ -182,15 +183,23 @@ public boolean run(PestControlConfig config) { || handleAttack(PestControlNpc.SPINNER, 3)) { return; } - Rs2NpcModel portal = Arrays.stream(Rs2Npc.getPestControlPortals()).findFirst().orElse(null); + Rs2NpcModel portal = Microbot.getRs2NpcCache().query() + .where(n -> n.getName() != null && n.getName().toLowerCase().contains("portal") + && n.getNpc() != null && !n.getNpc().isDead() + && Arrays.stream(Microbot.getClientThread().runOnClientThreadOptional(() -> + Microbot.getClient().getNpcDefinition(n.getId()).getActions()).orElse(new String[0])) + .anyMatch(a -> a != null && a.equalsIgnoreCase("attack"))) + .nearest(); if (portal != null) { - if (Rs2Npc.interact(portal.getId(), "attack")) { + if (portal.click("Attack")) { sleepUntil(() -> !Microbot.getClient().getLocalPlayer().isInteracting()); } } else { if (!Microbot.getClient().getLocalPlayer().isInteracting()) { - Optional attackableNpc = Rs2Npc.getAttackableNpcs().findFirst(); - attackableNpc.ifPresent(rs2NpcModel -> Rs2Npc.interact(rs2NpcModel.getId(), "attack")); + Rs2NpcModel attackableNpc = Microbot.getRs2NpcCache().query() + .where(n -> n.getNpc() != null && !n.getNpc().isDead() && n.getNpc().getCombatLevel() > 0) + .nearest(); + if (attackableNpc != null) attackableNpc.click("Attack"); } } @@ -203,11 +212,11 @@ public boolean run(PestControlConfig config) { walkToCenter = false; if (!isInBoat && !initialise) { if (Microbot.getClient().getLocalPlayer().getCombatLevel() >= 100) { - Rs2GameObject.interact(ObjectID.GANGPLANK_25632); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.GANGPLANK_25632); } else if (Microbot.getClient().getLocalPlayer().getCombatLevel() >= 70) { - Rs2GameObject.interact(ObjectID.GANGPLANK_25631); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.GANGPLANK_25631); } else { - Rs2GameObject.interact(ObjectID.GANGPLANK_14315); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.GANGPLANK_14315); } sleepUntil(this::isInBoat, 3000); } else { @@ -284,11 +293,11 @@ public boolean isInPestControl() { public void exitBoat() { if (Microbot.getClient().getLocalPlayer().getCombatLevel() >= 100) { - Rs2GameObject.interact(ObjectID.LADDER_25630); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.LADDER_25630); } else if (Microbot.getClient().getLocalPlayer().getCombatLevel() >= 70) { - Rs2GameObject.interact(ObjectID.LADDER_25629); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.LADDER_25629); } else { - Rs2GameObject.interact(ObjectID.LADDER_14314); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.LADDER_14314); } sleepUntil(() -> Microbot.getClient().getWidget(WidgetInfo.PEST_CONTROL_BOAT_INFO) == null, 3000); @@ -347,14 +356,14 @@ public Portal getClosestAttackablePortal() { private static boolean attackPortal() { if (!Microbot.getClient().getLocalPlayer().isInteracting()) { - Rs2NpcModel npcPortal = Rs2Npc.getNpc("portal"); + Rs2NpcModel npcPortal = Microbot.getRs2NpcCache().query().withName("portal").nearest(); if (npcPortal == null) return false; NPCComposition npc = Microbot.getClientThread().runOnClientThreadOptional(() -> Microbot.getClient().getNpcDefinition(npcPortal.getId())).orElse(null); if (npc == null) return false; if (Arrays.stream(npc.getActions()).anyMatch(x -> x != null && x.equalsIgnoreCase("attack"))) { - return Rs2Npc.interact(npcPortal, "attack"); + return npcPortal.click("Attack"); } else { return false; } @@ -382,7 +391,7 @@ private boolean attackPortals() { private boolean attackSpinner() { for (int spinner : SPINNER_IDS) { - if (Rs2Npc.interact(spinner, "attack")) { + if (Microbot.getRs2NpcCache().query().withId(spinner).interact("Attack")) { sleepUntil(() -> !Microbot.getClient().getLocalPlayer().isInteracting()); return true; } @@ -392,7 +401,7 @@ private boolean attackSpinner() { private boolean attackBrawler() { for (int brawler : BRAWLER_IDS) { - if (Rs2Npc.interact(brawler, "attack")) { + if (Microbot.getRs2NpcCache().query().withId(brawler).interact("Attack")) { sleepUntil(() -> !Microbot.getClient().getLocalPlayer().isInteracting()); return true; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/roguesden/RoguesDenScript.java b/src/main/java/net/runelite/client/plugins/microbot/roguesden/RoguesDenScript.java index 34640f43f0..3da4c3edd8 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/roguesden/RoguesDenScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/roguesden/RoguesDenScript.java @@ -11,7 +11,6 @@ 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.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.math.Rs2Random; @@ -205,7 +204,7 @@ private boolean clickObstacle() { Obstacles.Obstacle nextObstacle = OBSTACLES[closestIndex + 1]; if (closestIndex == 0 && !Rs2Player.getWorldLocation().equals(currentObstacle.getTile())) { - Rs2GameObject.interact(currentObstacle.getObjectId()); + Microbot.getRs2TileObjectCache().query().interact(currentObstacle.getObjectId()); sleepUntil(() -> Rs2Player.getWorldLocation().equals(currentObstacle.getTile())); return true; } @@ -243,7 +242,7 @@ private boolean useFlashPowder() { private boolean useTileObject() { if (Rs2Inventory.hasItem(ItemID.ROGUESDEN_PUZZLE_MOSAIC_TILE1)) { Microbot.log("Handle tile door"); - Rs2GameObject.interact(7234, "Open"); + Microbot.getRs2TileObjectCache().query().interact(7234, "Open"); Rs2Widget.sleepUntilHasWidget("Select"); Rs2Widget.clickWidget("Select"); Rs2Inventory.waitForInventoryChanges(3000); @@ -323,7 +322,7 @@ private boolean useEnergyPotions() { private void enterMinigame() { WalkerState state = Rs2Walker.walkWithState(new WorldPoint(3056, 4991, 1)); if (state == WalkerState.ARRIVED) { - Rs2GameObject.interact(ObjectID.ROGUESDEN_MAZEENTRANCE); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.ROGUESDEN_MAZEENTRANCE); sleepUntil(() -> Rs2Inventory.hasItem(ItemID.ROGUESDEN_GEM)); } } @@ -344,9 +343,9 @@ private void handleObstacle(Obstacles.Obstacle obstacle) { if (obstacle.getObjectId() != -1) { if (obstacle.getObjectId() == 7249) { - Rs2GameObject.interact(obstacle.getObjectId(), "search"); // Handles wall searching + Microbot.getRs2TileObjectCache().query().interact(obstacle.getObjectId(), "search"); // Handles wall searching } else { - Rs2GameObject.interact(obstacle.getObjectId()); + Microbot.getRs2TileObjectCache().query().interact(obstacle.getObjectId()); } Rs2Player.waitForWalking(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/summergarden/SummerGardenScript.java b/src/main/java/net/runelite/client/plugins/microbot/summergarden/SummerGardenScript.java index bbff051f6b..14d7005ac9 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/summergarden/SummerGardenScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/summergarden/SummerGardenScript.java @@ -10,11 +10,10 @@ import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.summergarden.ElementalCollisionDetector; import net.runelite.client.plugins.microbot.summergarden.SummerGardenConfig; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; + 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.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -170,7 +169,7 @@ private boolean goInsideHouse() { if (!isInHouseArea()) { // The door is closed and the player is outside the house area. Open it and wait until it's open. if (getHouseDoor() != null) { - Rs2GameObject.interact(OBJECT_HOUSE_DOOR_CLOSED); + Microbot.getRs2TileObjectCache().query().interact(OBJECT_HOUSE_DOOR_CLOSED); sleepUntil(() -> getHouseDoor() == null, 10000); } @@ -188,13 +187,13 @@ private boolean goInsideHouse() { private void exitGarden() { // Can't find the fountain? - if (Rs2GameObject.findObjectById(12941) == null) { + if (Microbot.getRs2TileObjectCache().query().withId(12941).nearest() == null) { return; } // If the player is still in the garden then click the fountain to exit and wait until the player is teleported out. if (isInGarden()) { - Rs2GameObject.interact(12941); + Microbot.getRs2TileObjectCache().query().interact(12941); sleepUntil(() -> isInHouseArea(), 10000); } @@ -228,7 +227,7 @@ private void doMaze(SummerGardenConfig config) { // Click tree if (WORLD_POINT_MAZE_STARTING_LOCATION.equals(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()))) { if (config.waitForOneClick() || ElementalCollisionDetector.getTicksUntilStart() == 0) { - Rs2GameObject.interact(OBJECT_SUMMER_TREE); + Microbot.getRs2TileObjectCache().query().interact(OBJECT_SUMMER_TREE); sleepUntil(() -> Rs2Player.isMoving()); sleepUntil(() -> !Rs2Player.isMoving(), 30000); sleepUntilOnClientThread(() -> Microbot.getClient().getLocalPlayer().getWorldLocation().getY() < 5481); @@ -243,9 +242,9 @@ private void doMaze(SummerGardenConfig config) { } // Click Gate - TileObject gate = Rs2GameObject.findObjectById(ObjectID.GATE_11987); + Rs2TileObjectModel gate = Microbot.getRs2TileObjectCache().query().withId(ObjectID.GATE_11987).nearest(); if (gate != null) { - Rs2GameObject.interact(gate); + gate.click(); sleepUntil(Rs2Player::isMoving); sleepUntil(() -> !Rs2Player.isMoving()); sleepUntilOnClientThread(() -> Microbot.getClient().getLocalPlayer().getWorldLocation().equals(WORLD_POINT_MAZE_STARTING_LOCATION)); @@ -268,12 +267,12 @@ private void completeAndReset() { // The door is closed and the player is inside the house area. Open it and wait until it's open. if (getHouseDoor() != null && isInHouseArea()) { - Rs2GameObject.interact(OBJECT_HOUSE_DOOR_CLOSED); + Microbot.getRs2TileObjectCache().query().interact(OBJECT_HOUSE_DOOR_CLOSED); sleepUntil(() -> getHouseDoor() == null, 10000); } // Check if the player has arrived at Osman's location, if not then walk there. - var npcOsman = Rs2Npc.getNpc(NPC_NAME_OSMAN); + var npcOsman = Microbot.getRs2NpcCache().query().withName(NPC_NAME_OSMAN).nearest(); if (npcOsman == null) { var osmanLocalLocation = LocalPoint.fromWorld(Microbot.getClient(), osmanLocation); if (osmanLocalLocation != null) { @@ -285,7 +284,7 @@ private void completeAndReset() { // Interact with Osman. if (lastInteractedActor == null || !Objects.equals(lastInteractedActor.getName(), NPC_NAME_OSMAN)) { - Rs2Npc.interact(NPC_NAME_OSMAN, "Talk-to"); + Microbot.getRs2NpcCache().query().withName(NPC_NAME_OSMAN).interact("Talk-to"); sleepUntil(() -> Rs2Player.getInteracting() != null, 2000); return; } @@ -350,7 +349,7 @@ private void handleReturnToHouse() { // Interact with shelf to get beer glass. while (Rs2Inventory.count("Beer glass") < 25) { - Rs2GameObject.interact(OBJECT_BEER_GLASS_SHELF); + Microbot.getRs2TileObjectCache().query().interact(OBJECT_BEER_GLASS_SHELF); sleep(3000); } @@ -363,14 +362,14 @@ private void handleReturnToHouse() { } // Check if the player has arrived at the Apprentice's location. - var npcApprentice = Rs2Npc.getNpc(NPC_NAME_APPRENTICE); + var npcApprentice = Microbot.getRs2NpcCache().query().withName(NPC_NAME_APPRENTICE).nearest(); if (npcApprentice == null) { return; } // Interact with the apprentice. if (lastInteractedActor == null || !Objects.equals(lastInteractedActor.getName(), NPC_NAME_APPRENTICE)) { - Rs2Npc.interact(NPC_NAME_APPRENTICE, "Teleport"); + Microbot.getRs2NpcCache().query().withName(NPC_NAME_APPRENTICE).interact("Teleport"); sleepUntil(() -> isInGarden(), 10000); } @@ -398,7 +397,7 @@ private void makeLastJuice() { if (!Rs2Inventory.hasItemAmount("Beer glass", 1, false, true)) { if (Rs2Inventory.count("Beer glass") == 0) { - Rs2GameObject.interact(OBJECT_BEER_GLASS_SHELF); + Microbot.getRs2TileObjectCache().query().interact(OBJECT_BEER_GLASS_SHELF); sleepUntil(() -> Rs2Inventory.hasItem("beer glass"), 5000); sleep(3000); } @@ -420,7 +419,7 @@ private void makeLastJuice() { } while (Rs2Inventory.count("Summer sq'irkjuice") != 26) { - Rs2GroundItem.pickup("Summer sq'irkjuice", 200); + Microbot.getRs2TileItemCache().query().withName("Summer sq'irkjuice").interact("Take"); sleepUntil(() -> Rs2Inventory.hasItemAmount("Summer sq'irk", 26, false, true), 5000); sleep(3000); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/toweroflife_creaturecreation/TowerOfLifeCCScript.java b/src/main/java/net/runelite/client/plugins/microbot/toweroflife_creaturecreation/TowerOfLifeCCScript.java index c7e6663c61..f0bf31a13d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/toweroflife_creaturecreation/TowerOfLifeCCScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/toweroflife_creaturecreation/TowerOfLifeCCScript.java @@ -1,6 +1,5 @@ package net.runelite.client.plugins.microbot.toweroflife_creaturecreation; -import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.VarbitID; @@ -14,15 +13,15 @@ import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.grounditem.LootingParameters; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.misc.Rs2Food; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; + import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -209,11 +208,11 @@ void HandleStateMachine(TowerOfLifeCCConfig _config) { if (!inBasement && !Rs2Player.isMoving()) { - TileObject trapdoor = Rs2GameObject.getTileObject(new WorldPoint(2648, 3212, 0)); + Rs2TileObjectModel trapdoor = Microbot.getRs2TileObjectCache().query().within(new WorldPoint(2648, 3212, 0), 1).nearest(); if (trapdoor != null) { Microbot.log("Trapdoor id: " + trapdoor.getId()); - Rs2GameObject.interact(trapdoor, "Climb-down"); + trapdoor.click("Climb-down"); } else { @@ -405,11 +404,11 @@ void HandleCreature(LootingParameters params, int disposableItem, int secondaryI //Rs2Inventory.waitForInventoryChanges(3000); //Rs2Inventory.useItemOnObject(secondaryItem, altarObjectId); //Rs2Inventory.waitForInventoryChanges(3000); - Rs2GameObject.interact(altarObjectId, "Activate"); - sleepUntil(() -> { summonedCreature = Rs2Npc.getNpcs() - .filter(npc -> npc != null - && (npc.getInteracting() == null || npc.getInteracting() == Microbot.getClient().getLocalPlayer())) - .findFirst().orElse(null); + Microbot.getRs2TileObjectCache().query().interact(altarObjectId, "Activate"); + sleepUntil(() -> { summonedCreature = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getNpc() != null + && (npc.getNpc().getInteracting() == null || npc.getNpc().getInteracting() == Microbot.getClient().getLocalPlayer())) + .nearest(); return summonedCreature != null; }, 5000); //Microbot.log("Summoned creature"); @@ -427,9 +426,9 @@ void HandleCreature(LootingParameters params, int disposableItem, int secondaryI } else if (!Rs2Combat.inCombat()) { - if (summonedCreature != null && !summonedCreature.isDead()) + if (summonedCreature != null && !summonedCreature.getNpc().isDead()) { - Rs2Npc.attack(summonedCreature); + summonedCreature.click("Attack"); } else { From 8d61f8c383f924857602346391275dc832359f89 Mon Sep 17 00:00:00 2001 From: chsami Date: Thu, 9 Apr 2026 14:49:39 +0200 Subject: [PATCH 26/95] refactor: migrate hunter and farming plugins to new query API Migrate Rs2Npc, Rs2GameObject, and Rs2GroundItem calls across BirdhouseRuns, BirdHunter, DeadFallTrapHunter, FarmTreeRun, GiantSeaweedFarmer, GrapeFarmer, Herbiboar, HerbRun, AutoChin, MoonlightMoth, Salamanders, and PyreFox plugins. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../DeadFallTrapHunterScript.java | 10 +- .../GiantSeaweedFarmerScript.java | 114 ++++++------------ .../GiantSeaweedSporeScript.java | 7 +- .../FornBirdhouseRunsScript.java | 11 +- .../microbot/birdhunter/BirdHunterScript.java | 56 +++++---- .../farmtreerun/FarmTreeRunScript.java | 17 +-- .../grapefarmer/GrapeFarmerScript.java | 20 ++- .../microbot/herbiboar/HerbiboarScript.java | 13 +- .../microbot/herbrun/HerbrunScript.java | 46 ++++--- .../microhunter/scripts/AutoChinScript.java | 75 ++++-------- .../moonlightmoth/MoonlightMothScript.java | 7 +- .../pyrefox/managers/PyreFoxScript.java | 37 +++--- .../pyrefox/managers/PyreFoxStateManager.java | 3 +- .../salamanders/SalamanderScript.java | 14 +-- 14 files changed, 175 insertions(+), 255 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/DeadFallTrapHunter/DeadFallTrapHunterScript.java b/src/main/java/net/runelite/client/plugins/microbot/DeadFallTrapHunter/DeadFallTrapHunterScript.java index cac0ad0044..61808ccfd1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/DeadFallTrapHunter/DeadFallTrapHunterScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/DeadFallTrapHunter/DeadFallTrapHunterScript.java @@ -16,6 +16,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.player.Rs2Player; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import java.util.List; @@ -327,20 +328,19 @@ private boolean isNearArea(DeadFallTrapHunting deadFallTrapHunting) { } private boolean handleExistingTraps(DeadFallTrapHunterPlugin plugin, DeadFallTrapHunterConfig config) { - // Filter for FULL traps and sort by time (traps about to collapse first) and then pick the first one var trapToHandle = plugin.getTraps().entrySet().stream() .filter(entry -> entry.getValue().getState() == HunterTrap.State.FULL) .sorted((a, b) -> Double.compare(b.getValue().getTrapTimeRelative(), a.getValue().getTrapTimeRelative())).collect(Collectors.toList()).stream().findFirst().orElse(null); if (trapToHandle == null) return false; WorldPoint location = trapToHandle.getKey(); if (!Rs2Player.isAnimating() && !Rs2Player.isMoving()) { - var gameObject = Rs2GameObject.getGameObject(location); + Rs2TileObjectModel gameObject = Microbot.getRs2TileObjectCache().query().within(location, 0).first(); if (gameObject != null) { if (Rs2Inventory.count() > 24) { forceDrop = true; Rs2Inventory.waitForInventoryChanges(8000); } - Rs2GameObject.interact(gameObject, "Check"); + gameObject.click("Check"); creaturesCaught++; sleep(config.minSleepAfterCatch(), config.maxSleepAfterCatch()); return true; @@ -350,8 +350,8 @@ private boolean handleExistingTraps(DeadFallTrapHunterPlugin plugin, DeadFallTra } private void setNewTrap(DeadFallTrapHunting deadFallTrapHunting, DeadFallTrapHunterConfig config) { - if (Rs2GameObject.exists(deadFallTrapHunting.getTrapId())) { - Rs2GameObject.interact(deadFallTrapHunting.getTrapId(), "Set-trap"); + if (Microbot.getRs2TileObjectCache().query().withId(deadFallTrapHunting.getTrapId()).count() > 0) { + Microbot.getRs2TileObjectCache().query().interact(deadFallTrapHunting.getTrapId(), "Set-trap"); sleep(config.minSleepAfterLay(), config.maxSleepAfterLay()); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerScript.java b/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerScript.java index ac5cb67792..152689cdbf 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerScript.java @@ -14,8 +14,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.keyboard.Rs2Keyboard; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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; @@ -147,7 +146,7 @@ private void getToFossilIsland() { sleep(2000, 3000); Rs2Walker.walkTo(FarmGuildSpiritTree); sleep(550, 750); - Rs2GameObject.interact(FARMGUILD_SPIRITTREE, "Travel"); + Microbot.getRs2TileObjectCache().query().interact(FARMGUILD_SPIRITTREE, "Travel"); sleep(550, 750); Rs2Keyboard.keyPress(KeyEvent.VK_C); sleep(550, 750); @@ -165,8 +164,9 @@ private void getToFossilIsland() { // Using official RuneLite varbit ranges for seaweed patches - private static String getSeaweedPatchState(TileObject rs2TileObject) { - var game_obj = Rs2GameObject.convertToObjectComposition(rs2TileObject, true); + private static String getSeaweedPatchState(int patchId) { + var game_obj = Rs2GameObject.getObjectComposition(patchId); + if (game_obj == null) return "Empty"; var varbitValue = Microbot.getVarbitValue(game_obj.getVarbitId()); // Only log when varbit value changes @@ -301,7 +301,7 @@ private void handleBanking() { } private void handleDiving() { - Rs2GameObject.interact(BOAT, "Dive"); + Microbot.getRs2TileObjectCache().query().interact(BOAT, "Dive"); sleepUntil(() -> Rs2Player.getWorldLocation().getPlane() == 1, 5000); if (Rs2Player.getWorldLocation().getPlane() != 1) { Microbot.log("We failed to get underwater - Make sure to handle the warning dialog manually once"); @@ -351,11 +351,11 @@ private void handleFarmNull(){ } private void handleNoting(){ - Rs2NpcModel leprechaun = Rs2Npc.getNpc("Tool leprechaun"); + Rs2NpcModel leprechaun = Microbot.getRs2NpcCache().query().withName("Tool leprechaun").nearest(); if (leprechaun == null) {return;} Rs2ItemModel unNoted = Rs2Inventory.getUnNotedItem("Giant seaweed", true); Rs2Inventory.use(unNoted); - Rs2Npc.interact(leprechaun, "Talk-to"); + leprechaun.click("Talk-to"); Rs2Inventory.waitForInventoryChanges(10000); } @@ -365,40 +365,28 @@ private boolean handlePatch(int patchId) { handleNoting(); } - Integer[] ids = { - patchId - }; - var obj = Rs2GameObject.findObject(ids); - - // If not found by ID, look for patch objects by name - if (obj == null) { - obj = Rs2GameObject.getGameObjects() - .stream() - .filter(o -> { - var objComp = Rs2GameObject.convertToObjectComposition(o, false); - if (objComp == null || objComp.getName() == null) return false; - String name = objComp.getName(); - // Look for seaweed patch objects or dead seaweed - return name.equalsIgnoreCase("Dead seaweed") || - name.equalsIgnoreCase("Seaweed patch") || - (name.equalsIgnoreCase("Seaweed") && objComp.getId() == patchId); - }) - .findFirst() - .orElse(null); - } - - if (obj == null) return false; - - // Make final reference for lambda usage - final var patchObj = obj; - var state = getSeaweedPatchState(patchObj); + var objModel = Microbot.getRs2TileObjectCache().query().withId(patchId).nearest(); + + if (objModel == null) { + objModel = Microbot.getRs2TileObjectCache().query().where(o -> { + var objComp = o.getObjectComposition(); + if (objComp == null || objComp.getName() == null) return false; + String name = objComp.getName(); + return name.equalsIgnoreCase("Dead seaweed") || + name.equalsIgnoreCase("Seaweed patch") || + (name.equalsIgnoreCase("Seaweed") && objComp.getId() == patchId); + }).nearest(); + } + + if (objModel == null) return false; + + final var patchObjModel = objModel; + var state = getSeaweedPatchState(patchId); logDebug("Patch state detected as: " + state); switch (state) { case "Empty": - // Enter critical section to prevent spore looting interruption inCriticalSection = true; try { - // Verify we have materials before starting atomic operation boolean hasCompost = Rs2Inventory.contains("compost") || Rs2Inventory.contains("Supercompost") || Rs2Inventory.contains("Ultracompost") || @@ -406,24 +394,21 @@ private boolean handlePatch(int patchId) { if (hasCompost) { Rs2Inventory.use("compost"); - Rs2GameObject.interact(patchObj, "Compost"); + patchObjModel.click("Compost"); Rs2Player.waitForXpDrop(Skill.FARMING); } - // Always attempt planting if we have spores if (Rs2Inventory.contains("seaweed spore")) { Rs2Inventory.use(" spore"); - Rs2GameObject.interact(patchObj, "Plant"); - sleepUntil(() -> getSeaweedPatchState(patchObj).equals("Growing"), 10000); + patchObjModel.click("Plant"); + sleepUntil(() -> getSeaweedPatchState(patchId).equals("Growing"), 10000); } return true; } finally { - // Always release critical section, even if error occurs inCriticalSection = false; } case "Harvestable": - // EQUIP FARMING CAPE FOR HARVEST BONUS if (config.FarmingCape()) { if (Rs2Inventory.contains("Farming cape") && !Rs2Equipment.isWearing("Farming cape")) { Rs2Inventory.interact("Farming cape", "Wear"); @@ -435,54 +420,31 @@ private boolean handlePatch(int patchId) { } } - Rs2GameObject.interact(patchObj, "Pick"); + patchObjModel.click("Pick"); sleepUntil(() -> { - // Re-find the patch object at the same location to get updated state - var currentPatch = Rs2GameObject.getGameObjects() - .stream() - .filter(o -> o.getWorldLocation().equals(patchObj.getWorldLocation())) - .findFirst() - .orElse(null); - if (currentPatch == null) return false; - String currentState = getSeaweedPatchState(currentPatch); - // Harvesting is complete when patch becomes empty or inventory is full + String currentState = getSeaweedPatchState(patchId); return currentState.equals("Empty") || Rs2Inventory.isFull(); }, 20000); - // IMMEDIATELY RE-EQUIP DIVING APPARATUS if (!Rs2Equipment.isWearing("Diving apparatus") && Rs2Inventory.contains("Diving apparatus")) { Rs2Inventory.interact("Diving apparatus", "Wear"); sleep(200, 300); } - return false; // Don't mark as handled - needs planting after harvesting + return false; case "Weeds": - Rs2GameObject.interact(patchObj, "Rake"); + patchObjModel.click("Rake"); sleepUntil(() -> { - // Re-find the patch object at the same location to get updated state - var currentPatch = Rs2GameObject.getGameObjects() - .stream() - .filter(o -> o.getWorldLocation().equals(patchObj.getWorldLocation())) - .findFirst() - .orElse(null); - if (currentPatch == null) return false; - String currentState = getSeaweedPatchState(currentPatch); + String currentState = getSeaweedPatchState(patchId); return !currentState.equals("Weeds"); }, 10000); - return false; // Don't mark as handled - needs planting after raking + return false; case "Dead": - Rs2GameObject.interact(patchObj, "Clear"); + patchObjModel.click("Clear"); sleepUntil(() -> { - // Re-find the patch object at the same location to get updated state - var currentPatch = Rs2GameObject.getGameObjects() - .stream() - .filter(o -> o.getWorldLocation().equals(patchObj.getWorldLocation())) - .findFirst() - .orElse(null); - if (currentPatch == null) return false; - String currentState = getSeaweedPatchState(currentPatch); + String currentState = getSeaweedPatchState(patchId); return !currentState.equals("Dead"); }, 10000); - return false; // Don't mark as handled - needs planting after clearing + return false; case "Diseased": Microbot.showMessage("Diseased patch! Please turn off the script and then cure me manually as i cant do this automatically yet."); return false; @@ -498,7 +460,7 @@ private void returnToBank() { Rs2Walker.walkTo(UnderWaterAnchor); // Brief pause to allow spore detection before climbing sleep(300, 500); - Rs2GameObject.interact(UNDERWATER_ANCHOR, "Climb"); + Microbot.getRs2TileObjectCache().query().interact(UNDERWATER_ANCHOR, "Climb"); sleepUntil(() -> Rs2Player.getWorldLocation().getPlane() == 0, 7000); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedSporeScript.java b/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedSporeScript.java index a11bb64b9d..cfacd06418 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedSporeScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedSporeScript.java @@ -3,7 +3,6 @@ import net.runelite.api.ItemID; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; -import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.player.Rs2Player; @@ -20,7 +19,7 @@ public boolean run(GiantSeaweedFarmerConfig config) { if (!config.lootSeaweedSpores()) return; // Check for seaweed spores - but respect critical farming operations - if (Rs2GroundItem.exists(ItemID.SEAWEED_SPORE, 15) && + if (Microbot.getRs2TileItemCache().query().withId(ItemID.SEAWEED_SPORE).within(15).count() > 0 && !GiantSeaweedFarmerScript.inCriticalSection) { // Pause all other scripts while we loot Microbot.pauseAllScripts.set(true); @@ -41,9 +40,9 @@ public boolean run(GiantSeaweedFarmerConfig config) { } private void lootAllSpores() { - while (Rs2GroundItem.exists(ItemID.SEAWEED_SPORE, 15) && this.isRunning()) { + while (Microbot.getRs2TileItemCache().query().withId(ItemID.SEAWEED_SPORE).within(15).count() > 0 && this.isRunning()) { Microbot.log("Seaweed spore detected - looting"); - boolean looted = Rs2GroundItem.loot(ItemID.SEAWEED_SPORE, 15); + boolean looted = Microbot.getRs2TileItemCache().query().withId(ItemID.SEAWEED_SPORE).within(15).interact("Take"); if (looted) { // Wait for movement to start and complete sleepUntil(Rs2Player::isMoving, 2000); diff --git a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java index d3d598a179..01748aafc8 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java @@ -15,7 +15,6 @@ import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; -import net.runelite.client.plugins.microbot.util.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.walker.Rs2Walker; @@ -197,7 +196,7 @@ public void shutdown() { } private boolean interactWithObject(int objectId) { - Rs2GameObject.interact(objectId); + Microbot.getRs2TileObjectCache().query().withId(objectId).interact(); sleepUntil(Rs2Player::isInteracting); sleepUntil(() -> !Rs2Player.isInteracting()); return true; @@ -206,7 +205,7 @@ private boolean interactWithObject(int objectId) { private void seedHouse(WorldPoint worldPoint, states status) { Rs2Inventory.use(" seed"); sleepUntil(Rs2Inventory::isItemSelected); - Rs2GameObject.interact(worldPoint); + Microbot.getRs2TileObjectCache().query().within(worldPoint, 0).interact(); sleepUntil(() -> Rs2Widget.findWidget("full of seed") != null, 1000); botStatus = status; } @@ -217,13 +216,13 @@ private void buildBirdhouse(WorldPoint worldPoint, states status) { Rs2Inventory.use(" logs"); Rs2Inventory.waitForInventoryChanges(5000); } - Rs2GameObject.interact(worldPoint, "Build"); + Microbot.getRs2TileObjectCache().query().within(worldPoint, 0).interact("Build"); sleepUntil(Rs2Player::isAnimating); botStatus = status; } - private void dismantleBirdhouse(int itemId, states status) { - Rs2GameObject.interact(itemId, "Empty"); + private void dismantleBirdhouse(int objectId, states status) { + Microbot.getRs2TileObjectCache().query().interact(objectId, "Empty"); Rs2Player.waitForXpDrop(Skill.HUNTER); botStatus = status; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterScript.java b/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterScript.java index dcc615f836..d7f79fd966 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterScript.java @@ -1,7 +1,6 @@ package net.runelite.client.plugins.microbot.birdhunter; import lombok.Getter; -import net.runelite.api.GameObject; import net.runelite.api.Skill; import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldArea; @@ -12,13 +11,12 @@ 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.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; 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.tile.Rs2Tile; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import org.apache.commons.lang3.tuple.Pair; @@ -120,30 +118,30 @@ private void walkBackToArea() { } private void handleTraps(BirdHunterConfig config) { - List successfulTraps = new ArrayList<>(); - successfulTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_JUNGLE)); - successfulTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_COLOURED)); - successfulTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_FULL_DESERT)); - successfulTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_FULL_WOODLAND)); - successfulTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_FULL_POLAR)); - successfulTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_FULL_JUNGLE)); - successfulTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_FULL_COLOURED)); - - List catchingTraps = new ArrayList<>(); - catchingTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_FULL_COLOURED)); - catchingTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_DESERT)); - catchingTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_WOODLAND)); - catchingTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_POLAR)); - catchingTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_FULL_JUNGLE)); - - List failedTraps = Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_BROKEN); - List idleTraps = Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP); - idleTraps.addAll(Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.HUNTING_OJIBWAY_TRAP_FAILING)); + List successfulTraps = new ArrayList<>(); + successfulTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_JUNGLE).toList()); + successfulTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_COLOURED).toList()); + successfulTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_FULL_DESERT).toList()); + successfulTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_FULL_WOODLAND).toList()); + successfulTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_FULL_POLAR).toList()); + successfulTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_FULL_JUNGLE).toList()); + successfulTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_FULL_COLOURED).toList()); + + List catchingTraps = new ArrayList<>(); + catchingTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_FULL_COLOURED).toList()); + catchingTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_DESERT).toList()); + catchingTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_WOODLAND).toList()); + catchingTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_POLAR).toList()); + catchingTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_FULL_JUNGLE).toList()); + + List failedTraps = Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_BROKEN).toList(); + List idleTraps = new ArrayList<>(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP).toList()); + idleTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_FAILING).toList()); int availableTraps = getAvailableTraps(Rs2Player.getRealSkillLevel(Skill.HUNTER)); int totalTraps = successfulTraps.size() + failedTraps.size() + idleTraps.size() + catchingTraps.size(); - if (Rs2GroundItem.exists(ItemID.HUNTING_OJIBWAY_BIRD_SNARE, 20)) { + if (Microbot.getRs2TileItemCache().query().withId(ItemID.HUNTING_OJIBWAY_BIRD_SNARE).within(20).count() > 0) { pickUpBirdSnare(); return; } @@ -154,7 +152,7 @@ private void handleTraps(BirdHunterConfig config) { } if (!successfulTraps.isEmpty()) { - for (GameObject successfulTrap : successfulTraps) { + for (Rs2TileObjectModel successfulTrap : successfulTraps) { if (interactWithTrap(successfulTrap)) { setTrap(config); return; @@ -163,7 +161,7 @@ private void handleTraps(BirdHunterConfig config) { } if (!failedTraps.isEmpty()) { - for (GameObject failedTrap : failedTraps) { + for (Rs2TileObjectModel failedTrap : failedTraps) { if (interactWithTrap(failedTrap)) { setTrap(config); return; @@ -197,7 +195,7 @@ private void layBirdSnare() { } private boolean isGameObjectAt(WorldPoint point) { - return Rs2GameObject.findObjectByLocation(point) != null; + return Microbot.getRs2TileObjectCache().query().within(point, 0).count() > 0; } @@ -246,9 +244,9 @@ private boolean movePlayerOffObject() { } - private boolean interactWithTrap(GameObject birdSnare) { + private boolean interactWithTrap(Rs2TileObjectModel birdSnare) { sleep(Rs2Random.randomGaussian(2000, 1250)); - Rs2GameObject.interact(birdSnare); + birdSnare.click(); sleepUntil(() -> Rs2Inventory.waitForInventoryChanges(7000)); sleep(Rs2Random.randomGaussian(2000, 1250)); @@ -256,7 +254,7 @@ private boolean interactWithTrap(GameObject birdSnare) { } private void pickUpBirdSnare() { - if (Rs2GroundItem.loot(ItemID.HUNTING_OJIBWAY_BIRD_SNARE)) { + if (Microbot.getRs2TileItemCache().query().withId(ItemID.HUNTING_OJIBWAY_BIRD_SNARE).interact("Take")) { sleepUntil(() -> Rs2Inventory.contains(ItemID.HUNTING_OJIBWAY_BIRD_SNARE), 2000); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java index 3a5a7363e6..e8aa465b90 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java @@ -717,9 +717,8 @@ private boolean handlePayment(FarmTreeRunConfig config, Patch patch, PaymentKind if (isHardTreePatch(patch) && !isPatchEmpty(patch) && !shouldProtectHardTree(config) && action != PaymentKind.CLEAR) return true; - Rs2NpcModel treeGardener = null; - treeGardener = Rs2Npc.getNearestNpcWithAction("Pay"); - Rs2Npc.interact(treeGardener, "Pay"); + Rs2NpcModel treeGardener = Rs2Npc.getNearestNpcWithAction("Pay"); + if (treeGardener != null) Rs2Npc.interact(treeGardener, "Pay"); if (treeGardener == null) { handleExoticGardeners(); @@ -1001,17 +1000,13 @@ private static int getSaplingToUse(Patch patch, FarmTreeRunConfig config) { * @return true if gardener interaction successful, else false */ private void handleExoticGardeners() { - // Nikkie: Farming guild fruit tree gardener - Rs2NpcModel nikkie = Rs2Npc.getNpc("Nikkie"); + var nikkie = Microbot.getRs2NpcCache().query().withName("Nikkie").nearest(); - // Rosie: Farming guild tree patch gardener - Rs2NpcModel rosie = Rs2Npc.getNpc("Rosie"); + var rosie = Microbot.getRs2NpcCache().query().withName("Rosie").nearest(); - Rs2NpcModel npcToInteract = null; String paymentAction = ""; + net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel npcToInteract = null; - // Rosie and Nikkie are close together. - // We need to check their distance to make sure we got the correct gardener. if (rosie == null && nikkie == null) { Microbot.log("Gardeners in farming guild not found. Report this bug."); shutdown(); @@ -1024,7 +1019,7 @@ private void handleExoticGardeners() { paymentAction = "Pay (tree patch)"; } - Rs2Npc.interact(npcToInteract, paymentAction); + if (npcToInteract != null) npcToInteract.click(paymentAction); } @Override diff --git a/src/main/java/net/runelite/client/plugins/microbot/grapefarmer/GrapeFarmerScript.java b/src/main/java/net/runelite/client/plugins/microbot/grapefarmer/GrapeFarmerScript.java index d5e59ca53c..ae5d96f0df 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/grapefarmer/GrapeFarmerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/grapefarmer/GrapeFarmerScript.java @@ -7,11 +7,9 @@ import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.grapefarmer.GrapeFarmerConfig; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; -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.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import java.util.LinkedHashMap; @@ -175,13 +173,13 @@ private void plantSeed(int gameObjectId) { Rs2ItemModel grapeSeed = Rs2Inventory.get(ItemID.GRAPE_SEED); System.out.println("Planting seed..."); if (Rs2Inventory.use(grapeSeed)) { - Rs2GameObject.interact(gameObjectId); + Microbot.getRs2TileObjectCache().query().withId(gameObjectId).interact(); Rs2Player.waitForXpDrop(Skill.FARMING); } } private void addSaltpetre(int gameObjectId) { - if (Rs2GameObject.interact(gameObjectId)) { + if (Microbot.getRs2TileObjectCache().query().withId(gameObjectId).interact()) { sleep(4000, 4500); } @@ -189,7 +187,7 @@ private void addSaltpetre(int gameObjectId) { private void clearVine(int gameObjectId) { if (!Rs2Player.isMoving() && !Rs2Player.isAnimating(5000) && !Rs2Player.isInteracting()) { - if (Rs2GameObject.interact(gameObjectId)) { + if (Microbot.getRs2TileObjectCache().query().withId(gameObjectId).interact()) { Rs2Player.waitForAnimation(2500); sleepUntil(() -> !Rs2Player.isAnimating() && !Rs2Player.isMoving() && @@ -199,22 +197,22 @@ private void clearVine(int gameObjectId) { } private void pickGrapes(int gameObjectId) { - Rs2NpcModel leprechaun = Rs2Npc.getNpc(0); + Rs2NpcModel leprechaun = Microbot.getRs2NpcCache().query().withId(0).nearest(); if (leprechaun != null) { if (Rs2Inventory.isFull()) { - Rs2Inventory.useItemOnNpc(ItemID.ZAMORAK_GRAPES, leprechaun); + Rs2Inventory.useItemOnNpc(ItemID.ZAMORAK_GRAPES, leprechaun.getNpc()); sleepUntil(() -> !Rs2Inventory.contains(ItemID.ZAMORAK_GRAPES), 5000); sleep(100,600); if (Rs2Inventory.contains(ItemID.GRAPES)) { Rs2Inventory.use(ItemID.GRAPES); - Rs2Npc.interact(leprechaun); + leprechaun.click(); sleepUntil(() -> !Rs2Inventory.contains(ItemID.GRAPES), 5000); sleep(50, 500); } } } - if (!Rs2Inventory.isFull() && Rs2GameObject.interact(gameObjectId)) { + if (!Rs2Inventory.isFull() && Microbot.getRs2TileObjectCache().query().withId(gameObjectId).interact()) { Rs2Player.waitForAnimation(500); } } @@ -247,7 +245,7 @@ private static State getStateForVarbit(int varbitValue) { private static void checkHealth(int gameObjectId) { System.out.println("Interacting with GroundObject ID: " + gameObjectId + " using action: Check-health"); - Rs2GameObject.interact(gameObjectId); + Microbot.getRs2TileObjectCache().query().withId(gameObjectId).interact(); Rs2Player.waitForXpDrop(Skill.FARMING); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/herbiboar/HerbiboarScript.java b/src/main/java/net/runelite/client/plugins/microbot/herbiboar/HerbiboarScript.java index 359346bd9d..9e8d6a340d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/herbiboar/HerbiboarScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/herbiboar/HerbiboarScript.java @@ -18,8 +18,7 @@ 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.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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.security.Login; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -584,7 +583,7 @@ public boolean run(HerbiboarConfig config, HerbiboarPlugin herbiboarPlugin) { case TUNNEL: Microbot.status = "Attacking tunnel"; Microbot.log(Level.INFO,"Attacking tunnel"); - if (!attackedTunnel || (Rs2Npc.getNpc("Herbiboar") == null && attackedTunnel)) { + if (!attackedTunnel || (Microbot.getRs2NpcCache().query().withName("Herbiboar").nearest() == null && attackedTunnel)) { int finishId = herbiboarPlugin.getFinishId(); if (finishId > 0) { WorldPoint finishLoc = herbiboarPlugin.getEndLocations().get(finishId - 1); @@ -602,18 +601,18 @@ public boolean run(HerbiboarConfig config, HerbiboarPlugin herbiboarPlugin) { } } } else { - Rs2NpcModel herb = Rs2Npc.getNpc("Herbiboar"); - if (herb != null) setState(HerbiboarState.HARVEST); + Rs2NpcModel herbCheck = Microbot.getRs2NpcCache().query().withName("Herbiboar").nearest(); + if (herbCheck != null) setState(HerbiboarState.HARVEST); } break; case HARVEST: Microbot.status = "Harvesting herbiboar"; Microbot.log(Level.INFO,"Harvesting herbiboar"); - Rs2NpcModel herb = Rs2Npc.getNpc("Herbiboar"); + Rs2NpcModel herb = Microbot.getRs2NpcCache().query().withName("Herbiboar").nearest(); if (herb != null) { WorldPoint loc = herb.getWorldLocation(); if (Rs2Player.getWorldLocation().distanceTo(loc) <= 8) { - Rs2Npc.interact(herb, "Harvest"); + herb.click("Harvest"); Rs2Player.waitForAnimation(); sleepUntil(() -> !Rs2Player.isAnimating() && !Rs2Player.isInteracting() && !Rs2Player.isMoving(), 5000); incrementHerbisCaught(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/herbrun/HerbrunScript.java b/src/main/java/net/runelite/client/plugins/microbot/herbrun/HerbrunScript.java index 33a5a1229a..0fc44ae10d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/herbrun/HerbrunScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/herbrun/HerbrunScript.java @@ -18,8 +18,7 @@ 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.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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 net.runelite.client.plugins.timetracking.Tab; @@ -160,12 +159,12 @@ private void getNextPatch() { private boolean handleHerbPatch() { if (Rs2Inventory.isFull()) { - Rs2NpcModel leprechaun = Rs2Npc.getNpc("Tool leprechaun"); + Rs2NpcModel leprechaun = Microbot.getRs2NpcCache().query().withName("Tool leprechaun").nearest(); if (leprechaun != null) { Rs2ItemModel unNoted = Rs2Inventory.getUnNotedItem("Grimy", false); if (unNoted != null) { Rs2Inventory.use(unNoted); - Rs2Npc.interact(leprechaun, "Talk-to"); + leprechaun.click("Talk-to"); Rs2Inventory.waitForInventoryChanges(10000); } else { // No grimy herbs to note - try to drop weeds or empty buckets as fallback @@ -179,7 +178,7 @@ private boolean handleHerbPatch() { return false; } - Integer[] ids = { + var obj = Microbot.getRs2TileObjectCache().query().withIds( ObjectID.MYARM_HERBPATCH, ObjectID.FARMING_HERB_PATCH_2, ObjectID.FARMING_HERB_PATCH_4, @@ -190,13 +189,13 @@ private boolean handleHerbPatch() { ObjectID.FARMING_HERB_PATCH_7, ObjectID.MY2ARM_HERBPATCH, ObjectID.FARMING_HERB_PATCH_5 - }; - var obj = Rs2GameObject.findObject(ids); + ).nearest(); if (obj == null) return false; - var state = getHerbPatchState(obj); + var tileObj = Rs2GameObject.findObjectById(obj.getId()); + if (tileObj == null) return false; + var state = getHerbPatchState(tileObj); switch (state) { case "Empty": - // Apply compost if configured if (config.compostType() != CompostType.NONE) { CompostType compost = config.compostType(); if (!Rs2Inventory.hasItem(compost.getItemId())) { @@ -204,35 +203,42 @@ private boolean handleHerbPatch() { return false; } Rs2Inventory.use(compost.getItemId()); - Rs2GameObject.interact(obj, "Compost"); + obj.click("Compost"); Rs2Player.waitForXpDrop(Skill.FARMING, 10000, false); - - // Drop empty bucket if configured (not for bottomless bucket) + if (config.dropEmptyBuckets() && !config.compostType().isBottomless()) { Rs2Inventory.drop(ItemID.BUCKET_EMPTY); } } - // Find the first herb seed in inventory and use its specific ID HerbSeedType seedInInventory = getFirstHerbSeedInInventory(); if (seedInInventory != null) { Rs2Inventory.use(seedInInventory.getItemId()); - Rs2GameObject.interact(obj, "Plant"); - sleepUntil(() -> getHerbPatchState(obj).equals("Growing"), 10000); + obj.click("Plant"); + sleepUntil(() -> { + var re = Rs2GameObject.findObjectById(obj.getId()); + return re != null && getHerbPatchState(re).equals("Growing"); + }, 10000); } else { log("No herb seeds found in inventory for planting"); } return false; case "Harvestable": - Rs2GameObject.interact(obj, "Pick"); - sleepUntil(() -> getHerbPatchState(obj).equals("Empty") || Rs2Inventory.isFull(), 20000); + obj.click("Pick"); + sleepUntil(() -> { + var re = Rs2GameObject.findObjectById(obj.getId()); + return (re != null && getHerbPatchState(re).equals("Empty")) || Rs2Inventory.isFull(); + }, 20000); return false; case "Weeds": - Rs2GameObject.interact(obj, "Rake"); + obj.click("Rake"); Rs2Player.waitForAnimation(10000); return false; case "Dead": - Rs2GameObject.interact(obj, "Clear"); - sleepUntil(() -> getHerbPatchState(obj).equals("Empty"), 10000); + obj.click("Clear"); + sleepUntil(() -> { + var re = Rs2GameObject.findObjectById(obj.getId()); + return re != null && getHerbPatchState(re).equals("Empty"); + }, 10000); return false; default: currentPatch = null; diff --git a/src/main/java/net/runelite/client/plugins/microbot/microhunter/scripts/AutoChinScript.java b/src/main/java/net/runelite/client/plugins/microbot/microhunter/scripts/AutoChinScript.java index 382a5432de..7e2495a0b4 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/microhunter/scripts/AutoChinScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/microhunter/scripts/AutoChinScript.java @@ -1,6 +1,5 @@ package net.runelite.client.plugins.microbot.microhunter.scripts; -import net.runelite.api.GameObject; import net.runelite.api.ItemID; import net.runelite.api.ObjectID; import net.runelite.api.coords.WorldPoint; @@ -8,10 +7,9 @@ import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.breakhandler.BreakHandlerScript; import net.runelite.client.plugins.microbot.microhunter.AutoHunterConfig; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import java.util.ArrayList; @@ -89,15 +87,12 @@ public void shutdown() { private void handleIdleState() { try { - // If there are box traps on the floor, interact with them first - if (Rs2GroundItem.interact(ItemID.BOX_TRAP, "lay", 4)) { + if (Microbot.getRs2TileItemCache().query().withId(ItemID.BOX_TRAP).within(4).interact("lay")) { currentState = State.LAYING; return; } - // If our inventory is full of ferrets if (Rs2Inventory.emptySlotCount() <= 1 && Rs2Inventory.contains(ItemID.FERRET)) { - // ferrets have the option release and not drop while (Rs2Inventory.contains(ItemID.FERRET)) { Rs2Inventory.interact(ItemID.FERRET, "Release"); sleep(0, 750); @@ -109,24 +104,20 @@ private void handleIdleState() { return; } - // If there are shaking boxes, interact with them. ferrets - if (Rs2GameObject.interact(ObjectID.SHAKING_BOX_9384, "reset", 4)) { + if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.SHAKING_BOX_9384).within(4).interact("reset")) { currentState = State.CATCHING; return; } - // If there are shaking boxes, interact with them - if (Rs2GameObject.interact(ObjectID.SHAKING_BOX_9383, "reset", 4)) { + if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.SHAKING_BOX_9383).within(4).interact("reset")) { currentState = State.CATCHING; return; } - // If there are shaking boxes, interact with them - if (Rs2GameObject.interact(ObjectID.SHAKING_BOX_9382, "reset", 4)) { + if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.SHAKING_BOX_9382).within(4).interact("reset")) { currentState = State.CATCHING; return; } - // Interact with traps that have not caught anything - if (Rs2GameObject.interact(ObjectID.BOX_TRAP_9385, "reset", 4)) { + if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.BOX_TRAP_9385).within(4).interact("reset")) { currentState = State.CATCHING; } } catch (Exception ex) { @@ -152,9 +143,8 @@ private void handleLayingState(AutoHunterConfig config) { } public void handleBreaks() { - int secondsUntilBreak = BreakHandlerScript.breakIn; // Time until the break + int secondsUntilBreak = BreakHandlerScript.breakIn; - //Clear list incase user changed trap layout This should run about 2-4 minutes before break if (secondsUntilBreak > 61 && secondsUntilBreak < 200) { if (!boxtiles.isEmpty()) { boxtiles.clear(); @@ -162,43 +152,33 @@ public void handleBreaks() { } if (secondsUntilBreak > 0 && secondsUntilBreak <= 60) { - // We're going on break in 1 minute or less. - // Save Trap locations for (int trapId : trapIds) { - List gameObjects = Rs2GameObject.getGameObjects(obj -> obj.getId() == trapId); - if (gameObjects != null) { - for (GameObject gameObject : gameObjects) { - if (gameObject != null) { - WorldPoint location = gameObject.getWorldLocation(); - if (Rs2Player.getWorldLocation().distanceTo(location) > 5) { - continue; // Skip traps beyond the range - } - if (!boxtiles.contains(location)) { - boxtiles.add(location); - } - } + var gameObjects = Microbot.getRs2TileObjectCache().query().withId(trapId).toList(); + for (Rs2TileObjectModel gameObject : gameObjects) { + WorldPoint location = gameObject.getWorldLocation(); + if (Rs2Player.getWorldLocation().distanceTo(location) > 5) { + continue; + } + if (!boxtiles.contains(location)) { + boxtiles.add(location); } } } - // At this point, boxtiles should be populated with the world points of the old traps. - - // Dismantling traps for our break. for (WorldPoint oldTile : boxtiles) { - if (Rs2GameObject.getGameObject(oldTile) != null) { - //Dismantle or Reset + if (Microbot.getRs2TileObjectCache().query().within(oldTile, 0).first() != null) { if (Rs2Player.getWorldLocation().distanceTo(oldTile) > 5) { - continue; // Skip traps beyond the range + continue; } - while (Rs2GameObject.getGameObject(oldTile) != null) { + while (Microbot.getRs2TileObjectCache().query().within(oldTile, 0).first() != null) { if (Rs2Player.getWorldLocation().distanceTo(oldTile) > 5) { - break; // Skip traps beyond the range + break; } - if (Rs2GameObject.interact(oldTile, "Dismantle")) { + if (Microbot.getRs2TileObjectCache().query().within(oldTile, 0).interact("Dismantle")) { sleep(1000, 3000); break; } - if (Rs2GameObject.interact(oldTile, "Reset")) { + if (Microbot.getRs2TileObjectCache().query().within(oldTile, 0).interact("Reset")) { sleep(1000, 3000); break; } @@ -208,16 +188,12 @@ public void handleBreaks() { oneRun = true; } - //We're back from our break if (secondsUntilBreak > 60 && oneRun) { if (!boxtiles.isEmpty()) { - //Setting traps down for (WorldPoint LayTrapTile : boxtiles) { - if (Rs2GameObject.getGameObject(LayTrapTile) != null) { - //There's already an object there do nothing + if (Microbot.getRs2TileObjectCache().query().within(LayTrapTile, 0).first() != null) { } else { - //we need to get to the tile if (!Rs2Player.getWorldLocation().equals(LayTrapTile)) { while (!Rs2Player.getWorldLocation().equals(LayTrapTile)) { Microbot.log("Walking to trap tile"); @@ -225,17 +201,16 @@ public void handleBreaks() { sleep(1000, 3000); } } - //we need to put a trap. Microbot.log("Placing trap"); int maxTries = 0; - while (Rs2GameObject.getGameObject(LayTrapTile) == null) { - if (!Rs2GroundItem.exists("Box trap", 6)) { + while (Microbot.getRs2TileObjectCache().query().within(LayTrapTile, 0).first() == null) { + if (Microbot.getRs2TileItemCache().query().withName("Box trap").within(6).count() == 0) { if (Rs2Inventory.contains("Box trap")) { Rs2Inventory.interact("Box trap", "Lay"); sleep(4000, 6000); } } else { - Rs2GroundItem.take("Box trap", 6); + Microbot.getRs2TileItemCache().query().withName("Box trap").within(6).interact("Take"); sleep(4000, 6000); } if (maxTries >= 3) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/moonlightmoth/MoonlightMothScript.java b/src/main/java/net/runelite/client/plugins/microbot/moonlightmoth/MoonlightMothScript.java index 85f5772738..516632dd6e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/moonlightmoth/MoonlightMothScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/moonlightmoth/MoonlightMothScript.java @@ -10,7 +10,6 @@ import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; 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.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.shop.Rs2Shop; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -236,13 +235,13 @@ private void handleCatching(MoonlightMothConfig config) { WorldArea excludedArea = new WorldArea(1550, 9426, 21, 8, 0); - Rs2Npc.getNpcs(NpcID.MOTH_MOONLIGHT).filter(moth -> { + Microbot.getRs2NpcCache().query().withId(NpcID.MOTH_MOONLIGHT).where(moth -> { WorldPoint location = moth.getWorldLocation(); return location != null && !excludedArea.contains(location); - }).findFirst().ifPresent(moth -> { + }).toList().stream().findFirst().ifPresent(moth -> { if (!Rs2Player.isAnimating() && !Rs2Player.isInteracting()) { var beforeCount = Rs2Inventory.count(ItemID.BUTTERFLY_JAR_MOONMOTH); - if (Rs2Npc.interact(moth, "Catch")) { + if (moth.click("Catch")) { logOnceToChat("Attempting to catch Moonlight Moth at: " + moth.getWorldLocation(), true); Rs2Player.waitForAnimation(2000); var afterCount = Rs2Inventory.count(ItemID.BUTTERFLY_JAR_MOONMOTH); diff --git a/src/main/java/net/runelite/client/plugins/microbot/pyrefox/managers/PyreFoxScript.java b/src/main/java/net/runelite/client/plugins/microbot/pyrefox/managers/PyreFoxScript.java index 25b9bca23d..30d4bab181 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/pyrefox/managers/PyreFoxScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/pyrefox/managers/PyreFoxScript.java @@ -1,6 +1,7 @@ package net.runelite.client.plugins.microbot.pyrefox.managers; import net.runelite.api.GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; @@ -169,7 +170,6 @@ private void _walkToPyreFox() */ private void _handleCatching() { - // Check what GameObject ID we're dealing with. if (_getTrapObjectAtTrapLocation() == null) { Microbot.log("No Trap GameObject found."); @@ -179,20 +179,15 @@ private void _handleCatching() var trap = _getTrapObjectAtTrapLocation(); if (trap == null) { - Microbot.log("No trap found."); return; } - if (!Rs2Camera.isTileOnScreen(trap)) - Rs2Camera.turnTo(trap); + if (!Rs2Camera.isTileOnScreen(trap.getLocalLocation())) + Rs2Camera.turnTo(trap.getLocalLocation()); - int trapId = _getTrapObjectAtTrapLocation().getId(); + int trapId = trap.getId(); - // 1. Check if a trap is active. - // 1a. Wait until fail / success - // i. collect/reset & re-lay - // 1b. Lay trap switch (trapId) { case PyreFoxConstants.GAMEOBJECT_ROCK_NO_TRAP: @@ -200,7 +195,6 @@ private void _handleCatching() _handleSettingUpTrap(trap); break; case PyreFoxConstants.GAMEOBJECT_ROCK_TRAP: -// _log("Rock trap is set up, waiting."); break; case PyreFoxConstants.GAMEOBJECT_ROCK_FOX_CAUGHT: _log("We caught a fox!"); @@ -212,16 +206,16 @@ private void _handleCatching() } } - private void _handleSettingUpTrap(GameObject trap) + private void _handleSettingUpTrap(Rs2TileObjectModel trap) { - Rs2GameObject.interact(trap, "Set-trap"); + trap.click("Set-trap"); Rs2Player.waitForWalking(); Rs2Player.waitForAnimation(); } - private void _handleFailedTrap(GameObject trap) + private void _handleFailedTrap(Rs2TileObjectModel trap) { - if (!Rs2GameObject.interact(trap, "reset")) + if (!trap.click("reset")) { _log("Did not find reset interaction."); return; @@ -230,20 +224,19 @@ private void _handleFailedTrap(GameObject trap) Rs2Player.waitForAnimation(); } - private void _handleFoxCaught(GameObject trap) + private void _handleFoxCaught(Rs2TileObjectModel trap) { - Rs2GameObject.interact(trap, "Check"); + trap.click("Check"); Rs2Player.waitForWalking(); Rs2Player.waitForXpDrop(Skill.HUNTER); } @Nullable - private GameObject _getTrapObjectAtTrapLocation() + private Rs2TileObjectModel _getTrapObjectAtTrapLocation() { - var object = Rs2GameObject.getGameObject(_getTrapObjectWorldPoint()); - if (object == null) - return null; - return object; + WorldPoint point = _getTrapObjectWorldPoint(); + if (point == null) return null; + return Microbot.getRs2TileObjectCache().query().within(point, 0).first(); } @Nullable @@ -251,7 +244,7 @@ private WorldPoint _getTrapObjectWorldPoint() { if (PyreFoxConstants.TRAP_OBJECT_POINT == null) { - var rock = Rs2GameObject.findObjectByIdAndDistance(PyreFoxConstants.GAMEOBJECT_ROCK_NO_TRAP, 10); + var rock = Microbot.getRs2TileObjectCache().query().withId(PyreFoxConstants.GAMEOBJECT_ROCK_NO_TRAP).within(10).nearest(); if (rock == null) { _log("No rock found"); diff --git a/src/main/java/net/runelite/client/plugins/microbot/pyrefox/managers/PyreFoxStateManager.java b/src/main/java/net/runelite/client/plugins/microbot/pyrefox/managers/PyreFoxStateManager.java index 62a9e0b7da..316e66230b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/pyrefox/managers/PyreFoxStateManager.java +++ b/src/main/java/net/runelite/client/plugins/microbot/pyrefox/managers/PyreFoxStateManager.java @@ -10,7 +10,6 @@ import net.runelite.client.plugins.microbot.pyrefox.PyreFoxPlugin; import net.runelite.client.plugins.microbot.pyrefox.enums.PyreFoxState; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; -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; @@ -104,7 +103,7 @@ private PyreFoxState _getCurrentState() // causing exceptions to be thrown. when we do enter the CHOPPING_TREES state, we enter a while loop // which exits when reaching our log goal, or if our hitpoints drop below our configured min. HP. var trapPoint = PyreFoxConstants.TRAP_OBJECT_POINT; - var trap = trapPoint != null ? Rs2GameObject.getGameObject(PyreFoxConstants.TRAP_OBJECT_POINT) : null; + var trap = trapPoint != null ? Microbot.getRs2TileObjectCache().query().within(PyreFoxConstants.TRAP_OBJECT_POINT, 0).first() : null; boolean trapCaughtFox = (trap != null && trap.getId() == PyreFoxConstants.GAMEOBJECT_ROCK_FOX_CAUGHT); boolean surpassedLogCutThreshold = Rs2Inventory.count("logs") <= PyreFoxConstants.GATHER_LOGS_AT_AMOUNT; if ((!trapCaughtFox || trap == null) && surpassedLogCutThreshold && Rs2Player.distanceTo(PyreFoxConstants.PYRE_FOX_CENTER_POINT) < 60) diff --git a/src/main/java/net/runelite/client/plugins/microbot/salamanders/SalamanderScript.java b/src/main/java/net/runelite/client/plugins/microbot/salamanders/SalamanderScript.java index 8a3263349e..dc8ad429fd 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/salamanders/SalamanderScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/salamanders/SalamanderScript.java @@ -9,8 +9,6 @@ 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.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -151,16 +149,15 @@ public int getMaxTrapsForHunterLevel(SalamanderConfig config) { } private boolean handleExistingTraps(SalamanderPlugin plugin, SalamanderConfig config) { - // Filter for FULL traps and sort by time (traps about to collapse first) and then pick the first one var trapToHandle = plugin.getTraps().entrySet().stream() .filter(entry -> entry.getValue().getState() == HunterTrap.State.FULL) .sorted((a, b) -> Double.compare(b.getValue().getTrapTimeRelative(), a.getValue().getTrapTimeRelative())).collect(Collectors.toList()).stream().findFirst().orElse(null); if (trapToHandle == null) return false; WorldPoint location = trapToHandle.getKey(); if (!Rs2Player.isAnimating() && !Rs2Player.isMoving()) { - var gameObject = Rs2GameObject.getGameObject(location); + var gameObject = Microbot.getRs2TileObjectCache().query().within(location, 0).first(); if (gameObject != null) { - Rs2GameObject.interact(gameObject, "Reset"); + gameObject.click("Reset"); SalamandersCaught++; sleep(config.minSleepAfterCatch(), config.maxSleepAfterCatch()); return true; @@ -170,14 +167,15 @@ private boolean handleExistingTraps(SalamanderPlugin plugin, SalamanderConfig co } private void setNewTrap(SalamanderHunting salamanderType, SalamanderConfig config) { - if (Rs2GameObject.exists(salamanderType.getTreeId())) { - Rs2GameObject.interact(salamanderType.getTreeId(), "Set-trap"); + if (Microbot.getRs2TileObjectCache().query().withId(salamanderType.getTreeId()).count() > 0) { + Microbot.getRs2TileObjectCache().query().interact(salamanderType.getTreeId(), "Set-trap"); sleep(config.minSleepAfterLay(), config.maxSleepAfterLay()); } } public boolean IsRopeOnTheGround() { - return Rs2GroundItem.exists(ROPE, 7) || Rs2GroundItem.exists(303, 7); + return Microbot.getRs2TileItemCache().query().withId(ROPE).within(7).count() > 0 || + Microbot.getRs2TileItemCache().query().withId(303).within(7).count() > 0; } @Override From d51660232f6e9417152d86826d25b4ca3725e98b Mon Sep 17 00:00:00 2001 From: chsami Date: Thu, 9 Apr 2026 14:50:24 +0200 Subject: [PATCH 27/95] refactor: migrate QoL, cluesolver, and remaining plugins to new query API Migrate Rs2Npc, Rs2GameObject, and Rs2GroundItem calls across QoL (overlay, wintertodt, special attack), ClueSolver (all 8 clue task types), EventDismiss, FlippersChaser, KittenTracker, Looter, DailyTasks, DelvePrayerHelper, GauntletHelper, StoneChests, ThievingStalls, Thieving, HouseThieving, TutorialIsland, AutoGauntletPrayer, BaggedPlants, AutoChompyKiller, MahoganyHomes, ValeOfTotems, AIOCamdozaal. Fix null-pointer chains in MahoganyHomesOverlay, MahoganyHomesScript, DelvePrayerHelper, and GameObjectUtils. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../aiocamdozaal/AIOCamdozScript.java | 40 +++---- .../AutoChompyKillerScript.java | 31 +++-- .../AutoGauntletPrayerPlugin.java | 7 +- .../baggedplants/BaggedPlantsScript.java | 28 ++--- .../cluesolver/cluetask/AnagramClueTask.java | 19 ++- .../cluesolver/cluetask/CipherClueTask.java | 6 +- .../cluetask/CoordinateClueTask.java | 12 +- .../cluesolver/cluetask/CrypticClueTask.java | 35 +++--- .../cluesolver/cluetask/EmoteClueTask.java | 12 +- .../cluetask/FaloTheBardClueTask.java | 4 +- .../cluesolver/cluetask/MapClueTask.java | 12 +- .../cluesolver/cluetask/MusicClueTask.java | 8 +- .../microbot/dailytasks/DailyTask.java | 14 +-- .../DelvePrayerHelperScript.java | 12 +- .../eventdismiss/DismissNpcEvent.java | 21 ++-- .../flipperschaser/FlippersChaserPlugin.java | 13 ++- .../gauntlethelper/GauntletHelperPlugin.java | 1 - .../gauntlethelper/GauntletHelperScript.java | 10 +- .../housethieving/HouseThievingScript.java | 41 ++++--- .../kittentracker/FeedKittenEvent.java | 4 +- .../kittentracker/KittenAttentionEvent.java | 4 +- .../microbot/kittentracker/KittenScript.java | 5 +- .../microbot/looter/scripts/FlaxScript.java | 7 +- .../looter/scripts/NatureRuneChestScript.java | 14 +-- .../mahoganyhomez/MahoganyHomesOverlay.java | 6 +- .../mahoganyhomez/MahoganyHomesScript.java | 54 ++++----- .../microbot/qualityoflife/QoLOverlay.java | 14 +-- .../microbot/qualityoflife/QoLPlugin.java | 7 +- .../scripts/SpecialAttackScript.java | 14 ++- .../scripts/wintertodt/WintertodtScript.java | 51 ++++----- .../stonechests/StoneChestThieverScript.java | 3 +- .../microbot/thieving/ThievingScript.java | 43 +++---- .../model/ArdyBakerThievingSpot.java | 2 +- .../model/ArdySilkThievingSpot.java | 2 +- .../microbot/thievingstalls/model/BotApi.java | 12 +- .../model/FortisGemStallThievingSpot.java | 9 +- .../model/HosidiusFruitThievingSpot.java | 2 +- .../model/VarrockTeaStallThievingSpot.java | 2 +- .../tutorialisland/TutorialIslandScript.java | 108 +++++++++--------- .../handlers/NavigationHandler.java | 18 ++- .../valetotems/handlers/TotemHandler.java | 15 ++- .../valetotems/utils/GameObjectUtils.java | 67 ++++++----- 42 files changed, 380 insertions(+), 409 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiocamdozaal/AIOCamdozScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiocamdozaal/AIOCamdozScript.java index 0bc704889d..8d7923941d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiocamdozaal/AIOCamdozScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiocamdozaal/AIOCamdozScript.java @@ -2,7 +2,7 @@ import net.runelite.api.Point; import net.runelite.api.Skill; -import net.runelite.api.WallObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.AnimationID; @@ -16,15 +16,13 @@ import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; 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.grounditem.LootingParameters; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.misc.Rs2Food; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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 net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -53,7 +51,7 @@ enum State { State state = State.WALKING_TO_BANK; - List mineableRocks = new ArrayList<>(); + List mineableRocks = new ArrayList<>(); int pickaxeToUse; int netToUse; @@ -191,17 +189,17 @@ private void MineAndSmith() { if (lastInteractedBarroniteID == 41547) { //System.out.println(("Last Interacted Rock: LEFT Barronite Vein")); for (int miningObjectID : miningObjectIDsReverse) { - mineableRocks.addAll(Rs2GameObject.getWallObjects(object -> object != null && (object.getId() == 41548))); + mineableRocks.addAll(Microbot.getRs2TileObjectCache().query().withId(41548).toList()); } } else if (lastInteractedBarroniteID == 41548) { //System.out.println(("Last Interacted Rock: RIGHT Barronite Vein")); for (int miningObjectID : miningObjectIDs) { - mineableRocks.addAll(Rs2GameObject.getWallObjects(object -> object != null && (object.getId() == 41547))); + mineableRocks.addAll(Microbot.getRs2TileObjectCache().query().withId(41547).toList()); } } if (!mineableRocks.isEmpty()) { - Rs2GameObject.interact(mineableRocks.get(0), "Mine"); + mineableRocks.get(0).click("Mine"); lastInteractedBarroniteID = mineableRocks.get(0).getId(); Microbot.status = "Mining Barronite rocks"; } @@ -231,7 +229,7 @@ private void MineAndSmith() { if (Rs2Player.getAnimation() != smithingAnimationID) { if (Rs2Inventory.hasItem(ItemID.CAMDOZAAL_BARRONITE_DEPOSIT)) { if (!sleepUntil(() -> Rs2Player.waitForXpDrop(Skill.SMITHING), 3000)) { - Rs2GameObject.interact(crusherID, "Smith"); + Microbot.getRs2TileObjectCache().query().interact(crusherID, "Smith"); Microbot.status = "Smithing Barronite deposits"; } } else { @@ -373,9 +371,9 @@ private void FishAndCook() { } if (!Rs2Player.isInteracting() && !Rs2Player.isMoving()) { if (netToUse == 303) { - Rs2Npc.interact(10686, "Small Net"); + Microbot.getRs2NpcCache().query().withId(10686).interact("Small Net"); } else if (netToUse == 305) { - Rs2Npc.interact(10686, "Big Net"); + Microbot.getRs2NpcCache().query().withId(10686).interact("Big Net"); } } @@ -394,7 +392,7 @@ private void FishAndCook() { Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); Rs2Player.waitForXpDrop(Skill.COOKING, 3000); } else if (!Rs2Player.isMoving()) { - Rs2GameObject.interact(41545, "Prepare-fish"); + Microbot.getRs2TileObjectCache().query().interact(41545, "Prepare-fish"); } } else { @@ -412,7 +410,7 @@ private void FishAndCook() { Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); Rs2Player.waitForXpDrop(Skill.PRAYER, 3000); } else if (!Rs2Player.isMoving()) { - Rs2GameObject.interact(41546, "Offer-fish"); + Microbot.getRs2TileObjectCache().query().interact(41546, "Offer-fish"); } } else { if (Rs2Inventory.hasItem(ItemID.BARRONITE_MACE_2)) { @@ -553,15 +551,15 @@ private void HandleGolemFightingState(WorldPoint _golemLocation, int _rubbleID, state = State.WALKING_TO_BANK; } - golemsAttackingPlayer = Rs2Npc.getNpcsForPlayer().collect(Collectors.toList()); + golemsAttackingPlayer = Microbot.getRs2NpcCache().query().where(n -> n.isInteractingWithPlayer()).toList(); if (!Rs2Combat.inCombat()) { // Not in combat if (!golemsAttackingPlayer.isEmpty()) { // Filter by things with an Attack option Rs2NpcModel targetGolem = golemsAttackingPlayer.stream() - .filter(npc -> npc.getComposition() != null && - Arrays.stream(npc.getComposition().getActions()) + .filter(npc -> npc.getNpc().getTransformedComposition() != null && + Arrays.stream(npc.getNpc().getTransformedComposition().getActions()) .anyMatch(action -> action != null && action.toLowerCase().contains("attack"))) .findFirst().orElse(null); @@ -570,14 +568,14 @@ private void HandleGolemFightingState(WorldPoint _golemLocation, int _rubbleID, Rs2Camera.turnTo(targetGolem); } - Rs2Npc.interact(targetGolem, "Attack"); + targetGolem.click("Attack"); Rs2Antiban.actionCooldown(); } break; } - golems = Rs2Npc.getNpcs(_golemID).collect(Collectors.toList()); - rubbles = Rs2Npc.getNpcs(_rubbleID).collect(Collectors.toList()); + golems = Microbot.getRs2NpcCache().query().withId(_golemID).toList(); + rubbles = Microbot.getRs2NpcCache().query().withId(_rubbleID).toList(); if (!golems.isEmpty()) { Rs2NpcModel golem = golems.stream().findFirst().orElse(null); @@ -586,7 +584,7 @@ private void HandleGolemFightingState(WorldPoint _golemLocation, int _rubbleID, Rs2Camera.turnTo(golem); } - Rs2Npc.interact(golem, "Attack"); + golem.click("Attack"); Rs2Antiban.actionCooldown(); } else { if (!rubbles.isEmpty()) { @@ -596,7 +594,7 @@ private void HandleGolemFightingState(WorldPoint _golemLocation, int _rubbleID, Rs2Camera.turnTo(rubble); } - Rs2Npc.interact(rubble, "Awaken"); + rubble.click("Awaken"); Rs2Antiban.actionCooldown(); sleepUntil(() -> !golems.isEmpty(), 2000); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/autochompykiller/AutoChompyKillerScript.java b/src/main/java/net/runelite/client/plugins/microbot/autochompykiller/AutoChompyKillerScript.java index 4710ff2822..485e98733c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/autochompykiller/AutoChompyKillerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/autochompykiller/AutoChompyKillerScript.java @@ -2,24 +2,21 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.EquipmentInventorySlot; -import net.runelite.api.GameObject; import net.runelite.api.gameval.ItemID; import net.runelite.api.gameval.NpcID; import net.runelite.api.gameval.ObjectID; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.coords.Rs2WorldPoint; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import java.util.List; import java.util.Random; import java.util.concurrent.TimeUnit; -import java.util.stream.Stream; @Slf4j public class AutoChompyKillerScript extends Script { @@ -31,22 +28,20 @@ public class AutoChompyKillerScript extends Script { private AutoChompyKillerConfig config; private boolean isBloatedToadOnGround() { - Stream npcs = Rs2Npc.getNpcs(); - long toadCount = npcs.filter(element -> element.getWorldLocation().equals(Rs2Player.getWorldLocation()) && element.getId() == NpcID.BLOATED_TOAD).count(); + long toadCount = Microbot.getRs2NpcCache().query().withId(NpcID.BLOATED_TOAD).where(element -> element.getWorldLocation().equals(Rs2Player.getWorldLocation())).count(); return toadCount > 0; } private boolean isDeadChompyNearby() { - Stream npcs = Rs2Npc.getNpcs(); - long deadChompyCount = npcs.filter(element -> element.getId() == NpcID.CHOMPYBIRD_DEAD && Rs2Player.getWorldLocation().distanceTo(element.getWorldLocation()) <= 5).count(); + long deadChompyCount = Microbot.getRs2NpcCache().query().withId(NpcID.CHOMPYBIRD_DEAD).where(element -> Rs2Player.getWorldLocation().distanceTo(element.getWorldLocation()) <= 5).count(); return deadChompyCount > 0; } private Rs2NpcModel getNearestReachableNpc(int npcId) { Rs2WorldPoint playerLocation = new Rs2WorldPoint(Rs2Player.getWorldLocation()); - return Rs2Npc.getNpcs(npc -> npc.getId() == npcId) + return Microbot.getRs2NpcCache().query().withId(npcId).toList().stream() .min(java.util.Comparator.comparingInt(npc -> playerLocation.distanceToPath(npc.getWorldLocation()))) .orElse(null); @@ -258,7 +253,7 @@ public boolean run(AutoChompyKillerConfig config) { case FILLING_BELLOWS: log.info("State: FILLING_BELLOWS"); Microbot.status = "Filling bellows"; - if (Rs2GameObject.interact(ObjectID.SWAMPBUBBLES, "Suck")) { + if (Microbot.getRs2TileObjectCache().query().interact(ObjectID.SWAMPBUBBLES, "Suck")) { boolean completed = sleepUntil(() -> !Rs2Player.isAnimating() && !Rs2Player.isInteracting(), 5000); if (completed) { log.info("Successfully filled bellows"); @@ -321,7 +316,7 @@ public boolean run(AutoChompyKillerConfig config) { } log.info("Found swamp toad, attempting to inflate it"); - if (Rs2Npc.interact(swampToad, "Inflate")) { + if (swampToad.click("Inflate")) { // wait for interaction to start boolean interactionStarted = sleepUntil(() -> Rs2Player.isAnimating() || Rs2Player.isInteracting(), 3000); if (interactionStarted) { @@ -359,7 +354,7 @@ public boolean run(AutoChompyKillerConfig config) { Rs2NpcModel targetChompy = getNearestReachableNpc(NpcID.CHOMPYBIRD); if (targetChompy != null) { log.info("Attacking chompy"); - if (Rs2Npc.interact(targetChompy, "Attack")) { + if (targetChompy.click("Attack")) { sleepUntil(() -> Rs2Player.isAnimating() || Rs2Player.isInteracting(), 3000); boolean completed = sleepUntil(() -> !Rs2Player.isAnimating() && !Rs2Player.isInteracting(), 10000); log.info("Attack animation completed: {}", completed); @@ -378,7 +373,7 @@ public boolean run(AutoChompyKillerConfig config) { Rs2NpcModel deadChompy = getNearestReachableNpc(NpcID.CHOMPYBIRD_DEAD); if (deadChompy != null) { log.info("Plucking dead chompy"); - if (Rs2Npc.interact(deadChompy, "Pluck")) { + if (deadChompy.click("Pluck")) { sleepUntil(() -> Rs2Player.isAnimating() || Rs2Player.isInteracting(), 3000); boolean completed = sleepUntil(() -> !Rs2Player.isAnimating() && !Rs2Player.isInteracting(), 5000); log.info("Plucking animation completed: {}", completed); @@ -447,13 +442,13 @@ public void handlePetReceived(boolean logoutOnCompletion) { } public void handleCantReachBubbles() { - List bubbles = Rs2GameObject.getGameObjects(obj -> obj.getId() == ObjectID.SWAMPBUBBLES); + var bubbles = Microbot.getRs2TileObjectCache().query().withId(ObjectID.SWAMPBUBBLES).toList(); if (bubbles != null && !bubbles.isEmpty()) { Random rand = new Random(); - GameObject bubble = bubbles.get(rand.nextInt(bubbles.size())); - + var bubble = bubbles.get(rand.nextInt(bubbles.size())); + if (bubble != null) { - Rs2GameObject.interact(bubble, "Suck"); + bubble.click("Suck"); sleepUntil(() -> !Rs2Player.isAnimating() && !Rs2Player.isInteracting()); state = AutoChompyKillerState.INFLATING; } else { diff --git a/src/main/java/net/runelite/client/plugins/microbot/autogauntletprayer/AutoGauntletPrayerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/autogauntletprayer/AutoGauntletPrayerPlugin.java index f440db95a1..2fba62212d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/autogauntletprayer/AutoGauntletPrayerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/autogauntletprayer/AutoGauntletPrayerPlugin.java @@ -14,8 +14,8 @@ import net.runelite.client.plugins.microbot.PluginConstants; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -86,8 +86,7 @@ public void onGameTick(GameTick event) { Rs2Prayer.toggle(nextPrayer, true); } - Rs2NpcModel hunllef = Rs2Npc.getNpcs() - .filter(npc -> HUNLLEF_IDS.contains(npc.getId())) + Rs2NpcModel hunllef = Microbot.getRs2NpcCache().query().where(npc -> HUNLLEF_IDS.contains(npc.getId())).toList().stream() .findFirst() .orElse(null); diff --git a/src/main/java/net/runelite/client/plugins/microbot/baggedplants/BaggedPlantsScript.java b/src/main/java/net/runelite/client/plugins/microbot/baggedplants/BaggedPlantsScript.java index 67ca1f6d72..d8d6b35a23 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/baggedplants/BaggedPlantsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/baggedplants/BaggedPlantsScript.java @@ -1,16 +1,16 @@ package net.runelite.client.plugins.microbot.baggedplants; -import net.runelite.api.TileObject; import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.baggedplants.enums.BaggedPlantsState; import net.runelite.client.plugins.microbot.globval.enums.InterfaceTab; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; 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.tabs.Rs2Tab; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -114,12 +114,12 @@ private void calculateState(BaggedPlantsConfig config) { if (inHouse && hasUnnotedPlants) { // Check if there's a built plant to remove first - TileObject builtPlant = Rs2GameObject.findObjectById(BUILT_PLANT); + var builtPlant = Microbot.getRs2TileObjectCache().query().withId(BUILT_PLANT).nearest(); if (builtPlant != null) { state = BaggedPlantsState.REMOVE_PLANT; } else { // No built plant, check for empty plant space to build on - TileObject plantSpace = Rs2GameObject.findObjectById(PLANT_SPACE); + var plantSpace = Microbot.getRs2TileObjectCache().query().withId(PLANT_SPACE).nearest(); if (plantSpace != null) { state = BaggedPlantsState.BUILD_PLANT; } @@ -168,7 +168,7 @@ private boolean hasUsableWateringCans() { private boolean isInHouse() { // Similar to gilded altar script - check if Phials NPC is not present - return Rs2Npc.getNpc("Phials") == null; + return Microbot.getRs2NpcCache().query().withName("Phials").nearest() == null; } private void checkInventory() { @@ -179,9 +179,9 @@ private void checkInventory() { } private void enterHouse(BaggedPlantsConfig config) { - TileObject housePortal = Rs2GameObject.findObjectById(HOUSE_PORTAL); + var housePortal = Microbot.getRs2TileObjectCache().query().withId(HOUSE_PORTAL).nearest(); if (housePortal != null) { - if (Rs2GameObject.interact(housePortal, "Build mode")) { + if (housePortal.click("Build mode")) { System.out.println("Entered house in build mode"); // Wait the configured time int waitTime = Rs2Random.between(config.minWaitTime() * 1000, config.maxWaitTime() * 1000); @@ -195,9 +195,9 @@ private void enterHouse(BaggedPlantsConfig config) { } private void buildPlant() { - TileObject plantSpace = Rs2GameObject.findObjectById(PLANT_SPACE); + var plantSpace = Microbot.getRs2TileObjectCache().query().withId(PLANT_SPACE).nearest(); if (plantSpace != null) { - if (Rs2GameObject.interact(plantSpace, "Build")) { + if (plantSpace.click("Build")) { System.out.println("Interacted with plant space to build"); sleepUntilOnClientThread(this::hasBuildInterfaceOpen, 2500); Rs2Keyboard.keyPress('1'); // Select first option @@ -216,9 +216,9 @@ private void buildPlant() { } private void removePlant() { - TileObject builtPlant = Rs2GameObject.findObjectById(BUILT_PLANT); + var builtPlant = Microbot.getRs2TileObjectCache().query().withId(BUILT_PLANT).nearest(); if (builtPlant != null) { - if (Rs2GameObject.interact(builtPlant, "Remove")) { + if (builtPlant.click("Remove")) { System.out.println("Interacted with plant to remove"); sleepUntilOnClientThread(this::hasRemoveInterfaceOpen, 2500); Rs2Keyboard.keyPress('1'); // Confirm removal @@ -244,7 +244,7 @@ private void refillSupplies() { if (!Rs2Inventory.isItemSelected()) { Rs2Inventory.use(NOTED_BAGGED_PLANT); } else { - Rs2Npc.interact("Phials", "Use"); + Microbot.getRs2NpcCache().query().withName("Phials").interact("Use"); Rs2Player.waitForWalking(); } return; // Wait for dialogue to open @@ -285,7 +285,7 @@ private void refillSupplies() { } if (wateringCanToRefill != -1) { - TileObject well = Rs2GameObject.findObjectById(SINK); + var well = Microbot.getRs2TileObjectCache().query().withId(SINK).nearest(); if (well != null) { System.out.println("Refilling watering can ID: " + wateringCanToRefill + " (Full cans: " + fullWateringCans + "/3)"); final int canToRefill = wateringCanToRefill; diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/AnagramClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/AnagramClueTask.java index 87b11d4a1e..32a71ae36e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/AnagramClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/AnagramClueTask.java @@ -12,9 +12,8 @@ import net.runelite.client.plugins.cluescrolls.clues.AnagramClue; import net.runelite.client.plugins.microbot.cluesolver.ClueSolverPlugin; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import java.util.concurrent.ExecutorService; @@ -134,7 +133,7 @@ private void processGameTick(GameTick event) { private void transitionToInteractionState() { if (clue.getObjectId() != -1) { state = State.INTERACTING_WITH_OBJECT; - } else if (clue.getNpcProvider() != null && Rs2Npc.getNpc(clue.getNpcName(clueScrollPlugin)) != null) { + } else if (clue.getNpcProvider() != null && Microbot.getRs2NpcCache().query().withName(clue.getNpcName(clueScrollPlugin)).nearest() != null) { state = State.INTERACTING_WITH_NPC; } else { log.warn("No valid interaction target found."); @@ -144,11 +143,11 @@ private void transitionToInteractionState() { private boolean interactWithObject() { int targetObject = clue.getObjectId(); - boolean interacted = Rs2GameObject.interact(targetObject, "Search") - || Rs2GameObject.interact(targetObject, "Investigate") - || Rs2GameObject.interact(targetObject, "Examine") - || Rs2GameObject.interact(targetObject, "Look-at") - || Rs2GameObject.interact(targetObject, "Open"); + boolean interacted = Microbot.getRs2TileObjectCache().query().interact(targetObject, "Search") + || Microbot.getRs2TileObjectCache().query().interact(targetObject, "Investigate") + || Microbot.getRs2TileObjectCache().query().interact(targetObject, "Examine") + || Microbot.getRs2TileObjectCache().query().interact(targetObject, "Look-at") + || Microbot.getRs2TileObjectCache().query().interact(targetObject, "Open"); if (interacted) { log.info("Interacted with object for clue."); @@ -159,13 +158,13 @@ private boolean interactWithObject() { } private boolean interactWithNpc() { - var targetNpc = Rs2Npc.getNpc(clue.getNpcName(clueScrollPlugin)); + var targetNpc = Microbot.getRs2NpcCache().query().withName(clue.getNpcName(clueScrollPlugin)).nearest(); if (targetNpc == null) { log.warn("NPC {} not found.", clue.getNpcName(clueScrollPlugin)); return false; } - boolean interacted = Rs2Npc.interact(targetNpc, "Talk-to"); + boolean interacted = targetNpc.click("Talk-to"); if (interacted) { log.info("Talking to NPC: {}", clue.getNpcName(clueScrollPlugin)); Rs2Dialogue.sleepUntilInDialogue(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CipherClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CipherClueTask.java index ca2b4b059a..b3403e3968 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CipherClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CipherClueTask.java @@ -13,7 +13,7 @@ import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; 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.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import java.util.concurrent.ExecutorService; @@ -116,13 +116,13 @@ public void onGameTick(GameTick event) { } private boolean interactWithNpc() { - var npc = Rs2Npc.getNpc(clue.getNpc()); + var npc = Microbot.getRs2NpcCache().query().withId(clue.getNpc()).nearest(); if (npc == null) { log.warn("NPC with ID {} not found at the location.", clue.getNpc()); return false; } - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { log.info("Interacting with NPC for cipher clue."); Rs2Dialogue.sleepUntilInDialogue(); return handleDialogue(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CoordinateClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CoordinateClueTask.java index 1f9c3a8044..f21cb49b8c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CoordinateClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CoordinateClueTask.java @@ -14,8 +14,8 @@ import net.runelite.client.plugins.cluescrolls.clues.Enemy; import net.runelite.client.plugins.microbot.cluesolver.ClueSolverPlugin; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import java.util.concurrent.ExecutorService; @@ -134,13 +134,13 @@ private void processGameTick(GameTick event) { } private boolean engageEnemy() { - Rs2NpcModel targetNpc = Rs2Npc.getNpc(enemy.getText()); + Rs2NpcModel targetNpc = Microbot.getRs2NpcCache().query().withName(enemy.getText()).nearest(); if (targetNpc == null) { log.warn("Expected enemy not found."); completeTask(false); return false; } - if (Rs2Npc.interact(targetNpc, "Attack")) { + if (targetNpc.click("Attack")) { log.info("Engaging enemy: {}", enemy.getText()); return waitForEnemyDefeat(targetNpc); } @@ -148,8 +148,8 @@ private boolean engageEnemy() { return false; } - private boolean waitForEnemyDefeat(NPC targetNpc) { - return targetNpc.isDead(); // This assumes an enemy tracking system + private boolean waitForEnemyDefeat(Rs2NpcModel targetNpc) { + return targetNpc.getNpc().isDead(); } private boolean prepareToDig() { diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CrypticClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CrypticClueTask.java index a92033b2dd..68b1cf166d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CrypticClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CrypticClueTask.java @@ -11,12 +11,9 @@ import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; import net.runelite.client.plugins.cluescrolls.clues.CrypticClue; import net.runelite.client.plugins.microbot.cluesolver.ClueSolverPlugin; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; -import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; -import net.runelite.client.plugins.microbot.util.models.RS2Item; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import java.util.Objects; @@ -158,7 +155,7 @@ private void transitionToNextState() { state = State.KILLING_ENEMY; } else if (clue.getObjectId() != -1) { state = State.INTERACTING_WITH_OBJECT; - } else if (clue.getNpc(clueScrollPlugin) != null && Rs2Npc.getNpc(clue.getNpc(clueScrollPlugin)) != null) { + } else if (clue.getNpc(clueScrollPlugin) != null && Microbot.getRs2NpcCache().query().withName(clue.getNpc(clueScrollPlugin)).nearest() != null) { state = State.INTERACTING_WITH_NPC; } else { state = State.COMPLETED; @@ -167,12 +164,12 @@ private void transitionToNextState() { } private boolean killEnemy() { - Rs2NpcModel enemy = Rs2Npc.getNpc(clue.getEnemy().name()); - if (enemy == null || enemy.getHealthRatio() <= 0) { + Rs2NpcModel enemy = Microbot.getRs2NpcCache().query().withName(clue.getEnemy().name()).nearest(); + if (enemy == null || enemy.getNpc().getHealthRatio() <= 0) { log.info("Enemy {} is defeated. Searching for loot.", clue.getEnemy()); return true; } - if (Rs2Npc.interact(enemy, "Attack")) { + if (enemy.click("Attack")) { log.info("Started attacking enemy: {}", clue.getEnemy()); } else { log.warn("Failed to attack enemy: {}", clue.getEnemy()); @@ -181,11 +178,11 @@ private boolean killEnemy() { } private boolean lootGroundItem() { - RS2Item[] groundItems = Rs2GroundItem.getAll(5); + var groundItems = Microbot.getRs2TileItemCache().query().within(10).toList(); boolean anyLooted = false; - for (RS2Item item : groundItems) { - if (Rs2GroundItem.interact(item)) { + for (var item : groundItems) { + if (item.click("Take")) { log.info("Successfully picked up item: {}", item); anyLooted = true; } else { @@ -197,11 +194,11 @@ private boolean lootGroundItem() { private boolean interactWithObject() { int targetObject = clue.getObjectId(); - if (Rs2GameObject.interact(targetObject, "Search") - || Rs2GameObject.interact(targetObject, "Investigate") - || Rs2GameObject.interact(targetObject, "Examine") - || Rs2GameObject.interact(targetObject, "Look-at") - || Rs2GameObject.interact(targetObject, "Open")) { + if (Microbot.getRs2TileObjectCache().query().interact(targetObject, "Search") + || Microbot.getRs2TileObjectCache().query().interact(targetObject, "Investigate") + || Microbot.getRs2TileObjectCache().query().interact(targetObject, "Examine") + || Microbot.getRs2TileObjectCache().query().interact(targetObject, "Look-at") + || Microbot.getRs2TileObjectCache().query().interact(targetObject, "Open")) { log.info("Interacted with required object for the clue."); return true; } @@ -210,12 +207,12 @@ private boolean interactWithObject() { } private boolean interactWithNpc() { - Rs2NpcModel targetNpc = Rs2Npc.getNpc(clue.getNpc(clueScrollPlugin)); + Rs2NpcModel targetNpc = Microbot.getRs2NpcCache().query().withName(clue.getNpc(clueScrollPlugin)).nearest(); if (targetNpc == null) { log.warn("NPC {} not found at the location.", clue.getNpc(clueScrollPlugin)); return false; } - return Rs2Npc.interact(targetNpc, "Talk-to"); + return targetNpc.click("Talk-to"); } private boolean handleDialogue() { diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/EmoteClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/EmoteClueTask.java index 41b2464d3f..de7f151bdb 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/EmoteClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/EmoteClueTask.java @@ -13,8 +13,8 @@ import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; import net.runelite.client.plugins.cluescrolls.clues.EmoteClue; import net.runelite.client.plugins.microbot.cluesolver.ClueSolverPlugin; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -177,7 +177,7 @@ private void performEmote(String emoteName) { } private void interactWithUri() { - Rs2Npc.interact("Uri", "Talk-to"); + Microbot.getRs2NpcCache().query().withName("Uri").interact("Talk-to"); log.info("Interacted with Uri."); } @@ -186,7 +186,7 @@ public void onNpcSpawned(NpcSpawned event) { NPC npc = event.getNpc(); if (npc.getName() != null) { if (state == State.WAITING_FOR_ENEMY_SPAWN && npc.getName().equalsIgnoreCase(DOUBLE_AGENT_NAME)) { - doubleAgent = Rs2Npc.getNpcByIndex(npc.getIndex()); + doubleAgent = Microbot.getRs2NpcCache().query().where(n -> n.getIndex() == npc.getIndex()).nearest(); log.info("Double agent spawned."); state = State.FIGHTING_ENEMY; attackDoubleAgent(); @@ -196,7 +196,7 @@ public void onNpcSpawned(NpcSpawned event) { private void attackDoubleAgent() { if (doubleAgent != null) { - Rs2Npc.interact(doubleAgent, "Attack"); + doubleAgent.click("Attack"); log.info("Attacking Double Agent."); } else { log.warn("Double Agent NPC is null or missing."); @@ -208,7 +208,7 @@ private void attackDoubleAgent() { public void onInteractingChanged(InteractingChanged event) { if (state == State.FIGHTING_ENEMY && event.getSource() == client.getLocalPlayer() && event.getTarget() == null) { - if (doubleAgent == null || Rs2Npc.getHealth(doubleAgent) <= 0) { + if (doubleAgent == null || doubleAgent.getNpc().getHealthRatio() <= 0) { log.info("Double agent defeated."); enemyDefeated = true; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/FaloTheBardClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/FaloTheBardClueTask.java index 2a1af76fa5..5d4aa968db 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/FaloTheBardClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/FaloTheBardClueTask.java @@ -10,7 +10,7 @@ import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; import net.runelite.client.plugins.cluescrolls.clues.FaloTheBardClue; import net.runelite.client.plugins.microbot.cluesolver.ClueSolverPlugin; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import java.util.concurrent.ExecutorService; @@ -110,7 +110,7 @@ private boolean isPlayerAtLocation() { private boolean interactWithNpc() { log.info("Interacting with Falo the Bard NPC."); - return Rs2Npc.interact("Falo the Bard", "Talk-to"); + return Microbot.getRs2NpcCache().query().withName("Falo the Bard").interact("Talk-to"); } private boolean confirmClueCompletion() { diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MapClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MapClueTask.java index f77ee60d01..8145e0f2ad 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MapClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MapClueTask.java @@ -11,7 +11,7 @@ import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; import net.runelite.client.plugins.cluescrolls.clues.MapClue; import net.runelite.client.plugins.microbot.cluesolver.ClueSolverPlugin; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -112,11 +112,11 @@ public void onGameTick(GameTick event) { private boolean interactWithObject() { if (objectId == -1) return false; - boolean interactionSuccessful = Rs2GameObject.interact(objectId, "Search") - || Rs2GameObject.interact(objectId, "Investigate") - || Rs2GameObject.interact(objectId, "Examine") - || Rs2GameObject.interact(objectId, "Look-at") - || Rs2GameObject.interact(objectId, "Open"); + boolean interactionSuccessful = Microbot.getRs2TileObjectCache().query().interact(objectId, "Search") + || Microbot.getRs2TileObjectCache().query().interact(objectId, "Investigate") + || Microbot.getRs2TileObjectCache().query().interact(objectId, "Examine") + || Microbot.getRs2TileObjectCache().query().interact(objectId, "Look-at") + || Microbot.getRs2TileObjectCache().query().interact(objectId, "Open"); if (interactionSuccessful) { log.info("Interacted with required object for the clue."); diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MusicClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MusicClueTask.java index d18beacf68..69d1a782c5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MusicClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MusicClueTask.java @@ -11,8 +11,8 @@ import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; import net.runelite.client.plugins.cluescrolls.clues.MusicClue; import net.runelite.client.plugins.microbot.cluesolver.ClueSolverPlugin; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -143,13 +143,13 @@ private boolean playSong() { } private boolean interactWithNpc() { - Rs2NpcModel npc = Rs2Npc.getNpc(npcName); + Rs2NpcModel npc = Microbot.getRs2NpcCache().query().withName(npcName).nearest(); if (npc == null) { log.warn("NPC {} not found near the clue location.", npcName); return false; } - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { log.info("Interacted with NPC: {}", npcName); return true; } else { diff --git a/src/main/java/net/runelite/client/plugins/microbot/dailytasks/DailyTask.java b/src/main/java/net/runelite/client/plugins/microbot/dailytasks/DailyTask.java index c7de81ab96..51ae9a0618 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/dailytasks/DailyTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/dailytasks/DailyTask.java @@ -9,11 +9,9 @@ import net.runelite.api.gameval.VarbitID; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -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.menu.NewMenuEntry; -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.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -36,7 +34,7 @@ public enum DailyTask { && Microbot.getClient().getVarpValue(VarPlayerID.NZONE_REWARDPOINTS) >= 9500 && Microbot.getClient().getVarbitValue(VarbitID.NZONE_HERBBOXES_PURCHASED) < 15, () -> { - Rs2GameObject.interact(26273, "Search"); + Microbot.getRs2TileObjectCache().query().interact(26273, "Search"); sleepUntil(() -> Rs2Widget.findWidget("Dom Onion") != null); doInvoke(new NewMenuEntry("Buy-50", "Herb box", 4, MenuAction.CC_OP, 20, 13500420, false), new Rectangle(1, 1)); Rs2Inventory.waitForInventoryChanges(1000); @@ -53,7 +51,7 @@ public enum DailyTask { () -> Microbot.getClient().getVarbitValue(Varbits.DIARY_VARROCK_EASY) == 1 && Microbot.getClient().getVarbitValue(Varbits.DAILY_STAVES_COLLECTED) == 0, () -> { - Rs2GameObject.interact(30357); + Microbot.getRs2TileObjectCache().query().interact(30357); sleepUntil(() -> Rs2Widget.findWidget("discounted battlestaves") != null); Rs2Widget.clickWidget("Click here to continue"); sleepUntil(() -> Rs2Widget.findWidget("Yes") != null); @@ -69,7 +67,7 @@ public enum DailyTask { () -> Microbot.getClient().getVarbitValue(Varbits.DIARY_ARDOUGNE_MEDIUM) == 1 && Microbot.getClient().getVarbitValue(Varbits.DAILY_ESSENCE_COLLECTED) == 0, () -> { - Rs2Npc.interact(8481, "Claim"); + Microbot.getRs2NpcCache().query().withId(8481).interact("Claim"); Rs2Inventory.waitForInventoryChanges(1000); }, DailyTasksConfig::collectEssence @@ -92,7 +90,7 @@ public enum DailyTask { () -> Microbot.getClient().getVarbitValue(Varbits.DIARY_KANDARIN_EASY) == 1 && Microbot.getClient().getVarbitValue(Varbits.DAILY_FLAX_STATE) == 0, () -> { - Rs2Npc.interact(5522, "Exchange"); + Microbot.getRs2NpcCache().query().withId(5522).interact("Exchange"); sleepUntil(Rs2Dialogue::isInDialogue); Rs2Dialogue.clickOption("Agree"); Rs2Inventory.waitForInventoryChanges(1000); @@ -140,10 +138,10 @@ public enum DailyTask { () -> Microbot.getClient().getVarpValue(VarPlayer.THRONE_OF_MISCELLANIA) > 0, () -> { sleepUntil(() -> Rs2Player.getWorldLocation().getRegionID() == 10044, 10000); - Rs2GameObject.interact(15079); + Microbot.getRs2TileObjectCache().query().interact(15079); sleepUntilOnClientThread(() -> Microbot.getClient().getVarbitValue(Varbits.KINGDOM_APPROVAL) == 127, 10000); Rs2Walker.walkTo(new WorldPoint(2502, 3858, 1), 5); - Rs2Npc.interact(5448, "Collect"); + Microbot.getRs2NpcCache().query().withId(5448).interact("Collect"); sleepUntil(() -> Rs2Dialogue.hasDialogueOption("Collect resources")); Rs2Dialogue.clickOption("Collect resources"); sleepUntil(() -> Rs2Widget.findWidget("Resources Collected") != null); diff --git a/src/main/java/net/runelite/client/plugins/microbot/delveprayerhelper/DelvePrayerHelperScript.java b/src/main/java/net/runelite/client/plugins/microbot/delveprayerhelper/DelvePrayerHelperScript.java index e357f52fa3..911f65549a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/delveprayerhelper/DelvePrayerHelperScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/delveprayerhelper/DelvePrayerHelperScript.java @@ -6,8 +6,7 @@ import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.delveprayerhelper.enums.DelvePrayerHelperState; import net.runelite.client.plugins.microbot.delveprayerhelper.enums.DelvePrayerHelperProjectile; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -51,11 +50,11 @@ public boolean run() { void handleProjectiles() { int currentCycle = Microbot.getClient().getGameCycle(); - Rs2NpcModel boss = Rs2Npc.getNpc("Doom of Mokhaiotl", false); + Rs2NpcModel boss = Microbot.getRs2NpcCache().query().where(n -> n.getName() != null && n.getName().contains("Doom of Mokhaiotl")).nearest(); incomingProjectiles.entrySet().removeIf(e -> e.getKey() < currentCycle); - boolean bossAlive = boss != null && !boss.isDead(); + boolean bossAlive = boss != null && !boss.getNpc().isDead(); if (!bossAlive) { Rs2Prayer.disableAllPrayers(); @@ -124,8 +123,9 @@ else if (projectile.getId() == DelvePrayerHelperProjectile.RANGE.getProjectileID private void toggleOffensivePrayer() { if(config.offensivePrayer()) { - Rs2Prayer.toggle(Rs2Prayer.getBestRangePrayer(), !config.noOffensivePrayerInShieldPhase() - || !Rs2Npc.getNpc("Doom of Mokhaiotl", false).getName().contains("(Shielded)")); + var doom = Microbot.getRs2NpcCache().query().where(n -> n.getName() != null && n.getName().contains("Doom of Mokhaiotl")).nearest(); + boolean shielded = doom != null && doom.getName().contains("(Shielded)"); + Rs2Prayer.toggle(Rs2Prayer.getBestRangePrayer(), !config.noOffensivePrayerInShieldPhase() || !shielded); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/eventdismiss/DismissNpcEvent.java b/src/main/java/net/runelite/client/plugins/microbot/eventdismiss/DismissNpcEvent.java index 121d269a82..6ec3500d77 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/eventdismiss/DismissNpcEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/eventdismiss/DismissNpcEvent.java @@ -2,10 +2,11 @@ import net.runelite.client.plugins.microbot.BlockingEvent; import net.runelite.client.plugins.microbot.BlockingEventPriority; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.Global; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; public class DismissNpcEvent implements BlockingEvent { @@ -15,18 +16,24 @@ public DismissNpcEvent(EventDismissConfig config) { this.config = config; } + private Rs2NpcModel getRandomEventNpc() { + var oldModel = Rs2Npc.getRandomEventNPC(); + if (oldModel == null) return null; + return Microbot.getRs2NpcCache().query().where(n -> n.getNpc().equals(oldModel.getRuneliteNpc())).nearest(); + } + @Override public boolean validate() { - Rs2NpcModel randomEventNPC = Rs2Npc.getRandomEventNPC(); + Rs2NpcModel randomEventNPC = getRandomEventNpc(); if (randomEventNPC == null) { return false; } - return Rs2Npc.hasLineOfSight(randomEventNPC); + return randomEventNPC.hasLineOfSight(); } @Override public boolean execute() { - Rs2NpcModel npc = Rs2Npc.getRandomEventNPC(); + Rs2NpcModel npc = getRandomEventNpc(); if (npc == null) return true; @@ -51,13 +58,13 @@ public BlockingEventPriority priority() { } private void talkTo(Rs2NpcModel npc) { - Rs2Npc.interact(npc, "Talk-to"); + npc.click("Talk-to"); Rs2Dialogue.sleepUntilHasContinue(); Rs2Dialogue.clickContinue(); } private void dismiss(Rs2NpcModel npc) { - Rs2Npc.interact(npc, "Dismiss"); - Global.sleepUntil(() -> Rs2Npc.getRandomEventNPC() == null); + npc.click("Dismiss"); + Global.sleepUntil(() -> getRandomEventNpc() == null); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/flipperschaser/FlippersChaserPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/flipperschaser/FlippersChaserPlugin.java index 9a24148558..fde7ed1342 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/flipperschaser/FlippersChaserPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/flipperschaser/FlippersChaserPlugin.java @@ -20,8 +20,8 @@ import net.runelite.client.plugins.PluginDescriptor; import net.runelite.client.plugins.microbot.PluginConstants; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.Microbot; +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.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -103,7 +103,8 @@ public void onNpcSpawned(NpcSpawned event) { NPC npc = event.getNpc(); if (npc.getName().equals("Mogre")) { inCombat = true; - clientThread.invoke(() -> attackNpc(new Rs2NpcModel(npc))); + var rs2Npc = Microbot.getRs2NpcCache().query().where(n -> n.getNpc().equals(npc)).nearest(); + if (rs2Npc != null) clientThread.invoke(() -> attackNpc(rs2Npc)); } } @@ -121,16 +122,16 @@ public void onNpcLootReceived(NpcLootReceived event) { private void useFishingExplosive() { Rs2NpcModel fishingSpot = findFishingSpot(); if (fishingSpot != null) { - Rs2Inventory.useItemOnNpc(ItemID.FISHING_EXPLOSIVE, fishingSpot); + Rs2Inventory.useItemOnNpc(ItemID.FISHING_EXPLOSIVE, fishingSpot.getNpc()); } } private Rs2NpcModel findFishingSpot() { - return Rs2Npc.getNpc("Ominous Fishing Spot"); + return Microbot.getRs2NpcCache().query().withName("Ominous Fishing Spot").nearest(); } private void attackNpc(Rs2NpcModel npc) { - Rs2Npc.interact(npc); + npc.click("Attack"); if (Rs2Player.hasPrayerPoints()) { Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MELEE); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/gauntlethelper/GauntletHelperPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/gauntlethelper/GauntletHelperPlugin.java index 705a735ad1..916d4e15fd 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/gauntlethelper/GauntletHelperPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/gauntlethelper/GauntletHelperPlugin.java @@ -23,7 +23,6 @@ import net.runelite.client.plugins.microbot.globval.enums.InterfaceTab; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; diff --git a/src/main/java/net/runelite/client/plugins/microbot/gauntlethelper/GauntletHelperScript.java b/src/main/java/net/runelite/client/plugins/microbot/gauntlethelper/GauntletHelperScript.java index aae2331e23..06ca171f24 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/gauntlethelper/GauntletHelperScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/gauntlethelper/GauntletHelperScript.java @@ -18,8 +18,7 @@ import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @@ -177,9 +176,8 @@ public boolean run() { public void checkNPC() { TIME_NPC = now; - Tornado = Rs2Npc.getNpc(CG_TORNADO); - hunllef = Rs2Npc.getNpcs() - .filter(npc -> HUNLLEF_IDS.contains(npc.getId())) + Tornado = Microbot.getRs2NpcCache().query().withId(CG_TORNADO).nearest(); + hunllef = Microbot.getRs2NpcCache().query().where(npc -> HUNLLEF_IDS.contains(npc.getId())).toList().stream() .findFirst() .orElse(null); @@ -397,7 +395,7 @@ private void checkAttack(){ if (attackNeeded.compareAndSet(true, false)) { if (now - TIME_EAT_ATTEMPTED > (CD_EAT * 3)) { logVerbose("Attempting attack"); - Rs2Npc.interact(hunllef, "attack"); + hunllef.click("attack"); TIME_ATTACK = now; } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/housethieving/HouseThievingScript.java b/src/main/java/net/runelite/client/plugins/microbot/housethieving/HouseThievingScript.java index d48bfcf0d3..c44716e4e9 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/housethieving/HouseThievingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/housethieving/HouseThievingScript.java @@ -4,6 +4,7 @@ import net.runelite.api.Quest; import net.runelite.api.QuestState; import net.runelite.api.TileObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.breakhandler.BreakHandlerScript; @@ -12,11 +13,9 @@ 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.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.math.Rs2Random; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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.security.Login; import net.runelite.client.plugins.microbot.util.tile.Rs2Tile; @@ -45,7 +44,7 @@ enum State { } public static State state = State.PICKPOCKETING; - private TileObject currentThievingObject = null; + private Rs2TileObjectModel currentThievingObject = null; private Long lastThievingSearch = null; private ThievingHouse currentThievingHouse = null; private Rs2NpcModel pickpocketNpc = null; @@ -90,7 +89,7 @@ public boolean run(HouseThievingConfig config) { if (currentThievingHouse == null) currentThievingHouse = getThievingHouse(); - var houseNpc = Rs2Npc.getNpc(currentThievingHouse.npcName); + var houseNpc = Microbot.getRs2NpcCache().query().withName(currentThievingHouse.npcName).nearest(); switch (state) { case PICKPOCKETING: handlePickPocketing(config); @@ -155,7 +154,7 @@ private void handlePickPocketing(HouseThievingConfig config) { } } - var aureliaNpc = Rs2Npc.getNpc("Aurelia"); + var aureliaNpc = Microbot.getRs2NpcCache().query().withName("Aurelia").nearest(); Rs2NpcModel distractedWealthyCitizen = null; if (aureliaNpc != null) { var aureliaAnim = aureliaNpc.getAnimation(); @@ -164,7 +163,7 @@ private void handlePickPocketing(HouseThievingConfig config) { if (distractedWealthyCitizen != null) pickpocketNpc = null; } else if (pickpocketNpc == null) { - var nearbyWealthyCitizens = Rs2Npc.getNpcs(WEALTHY_CITIZEN); + var nearbyWealthyCitizens = Microbot.getRs2NpcCache().query().withName(WEALTHY_CITIZEN).toList().stream(); var aureliaLocation = aureliaNpc.getWorldLocation(); var closestWealthyCitizen = nearbyWealthyCitizens.min(Comparator.comparingInt(a -> a.getWorldLocation().distanceTo(aureliaLocation))); closestWealthyCitizen.ifPresent(rs2NpcModel -> pickpocketNpc = rs2NpcModel); @@ -196,7 +195,7 @@ private void handlePickPocketing(HouseThievingConfig config) { if (Rs2Player.isStunned() && distractedWealthyCitizen == null) sleepUntil(() -> !Rs2Player.isStunned(), 600); - Rs2Npc.interact(targetWealthyCitizen, "Pickpocket"); + targetWealthyCitizen.click("Pickpocket"); Rs2Random.waitEx(600.0, 200.0); sleepUntil(() -> !Rs2Player.isInteracting() && !Rs2Player.isAnimating(), 10000); } @@ -220,11 +219,11 @@ private void doWorldHop() { @Nullable private static Rs2NpcModel getDistractedWealthyCitizen(Rs2NpcModel aureliaNpc) { - return Rs2Npc.getNpcs().filter(npc -> + return Microbot.getRs2NpcCache().query().where(npc -> npc.getWorldLocation().distanceTo(aureliaNpc.getWorldLocation()) <= 5 && npc.getName() != null && npc.getName().equalsIgnoreCase(WEALTHY_CITIZEN) && - npc.isInteracting() - ).findFirst().orElse(null); + npc.isInteractingWithPlayer() + ).toList().stream().findFirst().orElse(null); } private void handleFindingHouse(HouseThievingConfig config) { @@ -254,13 +253,13 @@ private void handleFindingHouse(HouseThievingConfig config) { currentThievingHouse.lockedDoorEgress.getY()); if (lockedDoorTile != null) { var wallObject = lockedDoorTile.getWallObject(); - var houseNpc = Rs2Npc.getNpc(currentThievingHouse.npcName); + var houseNpc = Microbot.getRs2NpcCache().query().withName(currentThievingHouse.npcName).nearest(); if (wallObject != null && wallObject.getId() == LOCKED_DOOR_ID) { Microbot.log(currentThievingHouse.npcName + " house can be thieved", Level.INFO); attemptWaitForHouseNpc(houseNpc); if (!Rs2Tile.isTileReachable(currentThievingHouse.houseCenter)) { - Rs2GameObject.interact(wallObject); + Microbot.getRs2TileObjectCache().query().withId(wallObject.getId()).interact(); Rs2Random.waitEx(2000.0, 100.0); sleepUntil(() -> !Rs2Player.isInteracting() && !Rs2Player.isAnimating(600)); Rs2Random.waitEx(2000.0, 100.0); @@ -324,30 +323,30 @@ private void handleThievingHouse(Rs2NpcModel houseNpc) { if (currentThievingObject == null) { var tileObject = Rs2Tile.getTile(currentThievingHouse.initialThievingChest.getX(), currentThievingHouse.initialThievingChest.getY()); if (tileObject != null) { - currentThievingObject = Rs2GameObject.getGameObject(currentThievingHouse.initialThievingChest); + currentThievingObject = Microbot.getRs2TileObjectCache().query().nearest(currentThievingHouse.initialThievingChest, 3); if (!Rs2Camera.isTileOnScreen(currentThievingObject.getLocalLocation())) { Rs2Camera.turnTo(currentThievingObject); } - Rs2GameObject.interact(currentThievingObject, "Search"); + currentThievingObject.click("Search"); } - currentThievingObject = Rs2GameObject.getGameObject(currentThievingHouse.initialThievingChest); + currentThievingObject = Microbot.getRs2TileObjectCache().query().nearest(currentThievingHouse.initialThievingChest, 3); } if (currentThievingObject != null && (lastThievingSearch == null || (elapsedTime != null && elapsedTime > 50000))) { if (!Rs2Camera.isTileOnScreen(currentThievingObject.getLocalLocation())) { Rs2Camera.turnTo(currentThievingObject); } - Rs2GameObject.interact(currentThievingObject, "Search"); + currentThievingObject.click("Search"); lastThievingSearch = System.currentTimeMillis(); Rs2Random.waitEx(1000.0, 200.0); } } else { - currentThievingObject = Rs2GameObject.getGameObject(hintArrow); + currentThievingObject = Microbot.getRs2TileObjectCache().query().nearest(hintArrow, 3); if (!Rs2Player.isInteracting() || lastThievingSearch == null || (elapsedTime != null && elapsedTime > 50000)) { if (!Rs2Camera.isTileOnScreen(currentThievingObject.getLocalLocation())) { Rs2Camera.turnTo(currentThievingObject); } - Rs2GameObject.interact(currentThievingObject, "Search"); + currentThievingObject.click("Search"); lastThievingSearch = System.currentTimeMillis(); Rs2Random.waitEx(1000.0, 200.0); } @@ -362,7 +361,7 @@ private boolean exitHouse() { if (!Rs2Camera.isTileOnScreen(windowTileWallObject.getLocalLocation())) { Rs2Camera.turnTo(windowTileWallObject); } - Rs2GameObject.interact(windowTileWallObject, "Exit-window"); + Microbot.getRs2TileObjectCache().query().withId(windowTileWallObject.getId()).interact("Exit-window"); Rs2Random.waitEx(5000.0, 200.0); currentThievingObject = null; lastThievingSearch = null; @@ -400,7 +399,7 @@ private void handleBanking(HouseThievingConfig config) { } if (!Rs2Bank.isOpen() && Rs2Player.distanceTo(BANKING_LOCATION) <= 3) { - var bankingTile = Rs2GameObject.getGameObject(BANKING_TILE_LOCATION); + var bankingTile = Microbot.getRs2TileObjectCache().query().nearest(BANKING_TILE_LOCATION, 3); if (bankingTile != null) Rs2Bank.openBank(bankingTile); else diff --git a/src/main/java/net/runelite/client/plugins/microbot/kittentracker/FeedKittenEvent.java b/src/main/java/net/runelite/client/plugins/microbot/kittentracker/FeedKittenEvent.java index e267f3d24c..bd9a47e247 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/kittentracker/FeedKittenEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/kittentracker/FeedKittenEvent.java @@ -6,7 +6,7 @@ import net.runelite.client.plugins.microbot.BlockingEventPriority; import net.runelite.client.plugins.microbot.util.Global; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.Microbot; import javax.inject.Inject; @@ -27,7 +27,7 @@ public boolean validate() { @Override public boolean execute() { - Rs2Npc.getNpcs("Kitten").findFirst().ifPresent(kitten -> Rs2Inventory.useItemOnNpc(ItemID.TBWT_RAW_KARAMBWANJI, kitten)); + Microbot.getRs2NpcCache().query().withName("Kitten").toList().stream().findFirst().ifPresent(kitten -> Rs2Inventory.useItemOnNpc(ItemID.TBWT_RAW_KARAMBWANJI, kitten.getNpc())); Global.sleepUntil(() -> (KittenPlugin.HUNGRY_FIRST_WARNING_TIME_LEFT_IN_SECONDS * 1000) < kittenPlugin.getTimeBeforeHungry()); return true; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenAttentionEvent.java b/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenAttentionEvent.java index 7c27c72b69..c34b396014 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenAttentionEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenAttentionEvent.java @@ -4,7 +4,7 @@ import net.runelite.client.plugins.microbot.BlockingEventPriority; import net.runelite.client.plugins.microbot.util.Global; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.Microbot; import javax.inject.Inject; @@ -26,7 +26,7 @@ public boolean validate() @Override public boolean execute() { - Rs2Npc.getNpcs("Kitten").findFirst().ifPresent(kitten -> Rs2Npc.interact(kitten, "Interact")); + Microbot.getRs2NpcCache().query().withName("Kitten").toList().stream().findFirst().ifPresent(kitten -> kitten.click("Interact")); if (Rs2Dialogue.sleepUntilHasDialogueOption("Stroke")) { Rs2Dialogue.clickOption("Stroke"); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenScript.java b/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenScript.java index ec8d8239a6..725da3d12e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenScript.java @@ -5,7 +5,6 @@ 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.npc.Rs2Npc; import java.util.concurrent.TimeUnit; @@ -44,12 +43,12 @@ private void handleKittenNeeds(KittenConfig config) { } private void feedKitten() { - Rs2Npc.getNpcs("Kitten").findFirst().ifPresent(kitten -> Rs2Inventory.useItemOnNpc(ItemID.TBWT_RAW_KARAMBWANJI, kitten)); + Microbot.getRs2NpcCache().query().withName("Kitten").toList().stream().findFirst().ifPresent(kitten -> Rs2Inventory.useItemOnNpc(ItemID.TBWT_RAW_KARAMBWANJI, kitten.getNpc())); sleep(1000, 2000); } private void giveKittenAttention() { - Rs2Npc.getNpcs("Kitten").findFirst().ifPresent(kitten -> Rs2Inventory.useItemOnNpc(ItemID.BALL_OF_WOOL, kitten)); + Microbot.getRs2NpcCache().query().withName("Kitten").toList().stream().findFirst().ifPresent(kitten -> Rs2Inventory.useItemOnNpc(ItemID.BALL_OF_WOOL, kitten.getNpc())); sleep(1000, 2000); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/FlaxScript.java b/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/FlaxScript.java index d10f4cebc5..556979bc91 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/FlaxScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/FlaxScript.java @@ -1,6 +1,5 @@ package net.runelite.client.plugins.microbot.looter.scripts; -import net.runelite.api.GameObject; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.looter.AutoLooterConfig; @@ -9,7 +8,7 @@ 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.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; 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.walker.Rs2Walker; @@ -54,9 +53,9 @@ public boolean run(AutoLooterConfig config) { } if (config.worldHop() && Rs2Player.hopIfPlayerDetected(1, 10, 10)) return; - GameObject flaxObject = Rs2GameObject.findObject("flax", false, config.distanceToStray(), true, initialPlayerLocation); + Rs2TileObjectModel flaxObject = Microbot.getRs2TileObjectCache().query().withName("flax").within(initialPlayerLocation, config.distanceToStray()).nearest(); if (flaxObject != null) { - if(Rs2GameObject.interact(flaxObject, "pick")){ + if(flaxObject.click("pick")){ Rs2Antiban.actionCooldown(); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/NatureRuneChestScript.java b/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/NatureRuneChestScript.java index f482b23d11..8249990006 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/NatureRuneChestScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/NatureRuneChestScript.java @@ -1,6 +1,5 @@ package net.runelite.client.plugins.microbot.looter.scripts; -import net.runelite.api.GameObject; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.looter.AutoLooterConfig; @@ -8,13 +7,11 @@ 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.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; 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.walker.Rs2Walker; -import java.util.Comparator; -import java.util.Optional; import java.util.concurrent.TimeUnit; public class NatureRuneChestScript extends Script { @@ -50,12 +47,9 @@ public boolean run(AutoLooterConfig config) { Rs2Player.logoutIfPlayerDetected(1, 10); return; } - Optional natureRuneChest = Rs2GameObject.getGameObjects().stream() - .filter(obj -> obj.getId() == config.natureRuneChestLocation().getObjectID()) - .sorted(Comparator.comparingInt(obj -> Rs2Player.getWorldLocation().distanceTo(obj.getWorldLocation()))) - .findFirst(); - if (natureRuneChest.isPresent()) { - if(Rs2GameObject.interact(natureRuneChest.get(), "Search for traps")){ + Rs2TileObjectModel natureRuneChest = Microbot.getRs2TileObjectCache().query().withId(config.natureRuneChestLocation().getObjectID()).nearest(); + if (natureRuneChest != null) { + if(natureRuneChest.click("Search for traps")){ Rs2Antiban.actionCooldown(); sleepUntilTrue(() -> !Rs2Player.isInteracting(), 500, 8000); sleep(Rs2Random.between(18000, 20000)); diff --git a/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesOverlay.java index 4ae40e66a3..1d87561aef 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesOverlay.java @@ -3,7 +3,7 @@ import lombok.Setter; import net.runelite.api.GameObject; import net.runelite.api.Player; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.ui.overlay.*; import net.runelite.client.ui.overlay.components.LineComponent; @@ -144,6 +144,8 @@ private void addLine(final String left) private void addObjectLine(final GameObject left) { - panelComponent.getChildren().add(LineComponent.builder().left(Rs2GameObject.convertGameObjectToObjectComposition(left).getName()).right(": "+Objects.requireNonNull(Hotspot.getByObjectId(left.getId())).getRequiredAction()).build()); + var obj = Microbot.getRs2TileObjectCache().query().withId(left.getId()).nearest(); + if (obj == null) return; + panelComponent.getChildren().add(LineComponent.builder().left(obj.getObjectComposition().getName()).right(": "+Objects.requireNonNull(Hotspot.getByObjectId(left.getId())).getRequiredAction()).build()); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScript.java b/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScript.java index b79511fa82..a3e7cb8512 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScript.java @@ -13,13 +13,11 @@ import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.coords.Rs2WorldPoint; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -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.util.magic.Rs2Magic; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.menu.NewMenuEntry; -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.tile.Rs2Tile; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -27,6 +25,7 @@ import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import java.util.*; +import java.util.Arrays; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -191,7 +190,7 @@ private void fix() { private void interactWithObject(GameObject object) { Hotspot hotspot = Hotspot.getByObjectId(object.getId()); String action = Objects.requireNonNull(hotspot).getRequiredAction(); - if (Rs2GameObject.interact(object, action)) { + if (Microbot.getRs2TileObjectCache().query().withId(object.getId()).interact(action)) { sleepUntil(() -> { String newAction = Objects.requireNonNull(Hotspot.getByObjectId(object.getId())).getRequiredAction(); return !newAction.equals(action); @@ -217,7 +216,9 @@ private boolean openDoorToObject(GameObject object, Rs2WorldPoint objectLocation if (door == null) continue; - var objectComp = Rs2GameObject.getObjectComposition(door.getId()); + var doorModel = Microbot.getRs2TileObjectCache().query().withId(door.getId()).nearest(); + if (doorModel == null) continue; + var objectComp = doorModel.getObjectComposition(); if (objectComp == null) continue; String name = objectComp.getName(); @@ -229,7 +230,10 @@ private boolean openDoorToObject(GameObject object, Rs2WorldPoint objectLocation } List doorNames = doors.stream() - .map(d -> Rs2GameObject.getObjectComposition(d.getId()).getName()) + .map(d -> { + var m = Microbot.getRs2TileObjectCache().query().withId(d.getId()).nearest(); + return m != null ? m.getObjectComposition().getName() : "unknown"; + }) .collect(Collectors.toList()); System.out.println("Doors found: " + doorNames + " Size: " + doors.size()); @@ -238,7 +242,8 @@ private boolean openDoorToObject(GameObject object, Rs2WorldPoint objectLocation // log("Doors found: %s", doors.size()); for (TileObject door : doors) { - ObjectComposition doorComp = Rs2GameObject.getObjectComposition(door.getId()); + var doorObj = Microbot.getRs2TileObjectCache().query().withId(door.getId()).nearest(); + ObjectComposition doorComp = doorObj != null ? doorObj.getObjectComposition() : null; List actions = null; if (doorComp != null) { actions = Arrays.asList(doorComp.getActions()); @@ -247,7 +252,7 @@ private boolean openDoorToObject(GameObject object, Rs2WorldPoint objectLocation log("Opening door at: %s", door.getWorldLocation()); logInfo("Opening door at: {}", door.getWorldLocation()); - if (Rs2GameObject.interact(door, "Open")) { + if (Microbot.getRs2TileObjectCache().query().withId(door.getId()).interact("Open")) { Rs2Player.waitForWalking(); sleep(200, 500); // if it's the last door in the list return true @@ -262,8 +267,8 @@ private boolean openDoorToObject(GameObject object, Rs2WorldPoint objectLocation private void tryToUseLadder() { log("Walker missing transport, trying to find ladder manually."); int plane = Rs2Player.getWorldLocation().getPlane(); - TileObject closestLadder = Rs2GameObject.findObject(plugin.getCurrentHome().getLadders()); - if (Rs2GameObject.interact(closestLadder)) { + var closestLadder = Microbot.getRs2TileObjectCache().query().withIds(Arrays.stream(plugin.getCurrentHome().getLadders()).mapToInt(Integer::intValue).toArray()).nearest(); + if (closestLadder != null && closestLadder.click()) { sleepUntil(() -> Rs2Player.getWorldLocation().getPlane() != plane, 5000); sleep(200, 600); } @@ -284,35 +289,31 @@ private void finish() { } } } - var npc = Rs2Npc.getNpc(plugin.getCurrentHome().getNpcId()); + var npc = Microbot.getRs2NpcCache().query().withId(plugin.getCurrentHome().getNpcId()).nearest(); if (npc == null && Rs2Player.getWorldLocation().getPlane() > 0) { log("We are on the wrong floor, Trying to find ladder to go down"); int playerPlane = Rs2Player.getWorldLocation().getPlane(); - List ladders = Rs2GameObject.getGameObjects( - obj -> Arrays.stream(plugin.getCurrentHome().getLadders()) - .anyMatch(id -> id == obj.getId()) - && obj.getWorldLocation().getPlane() == playerPlane - ); - GameObject closestLadder = ladders.stream() + var ladders = Microbot.getRs2TileObjectCache().query() + .withIds(Arrays.stream(plugin.getCurrentHome().getLadders()).mapToInt(Integer::intValue).toArray()) + .where(obj -> obj.getWorldLocation().getPlane() == playerPlane) + .toList(); + var closestLadder2 = ladders.stream() .min(Comparator.comparingInt(obj -> obj.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()))) .orElse(null); - Rs2WorldPoint objectLocation = Rs2Tile.getNearestWalkableTile(closestLadder); - if (!openDoorToObject(closestLadder, objectLocation)) { - if (Rs2GameObject.interact(closestLadder)) { + if (closestLadder2 != null && closestLadder2.click()) { sleepUntil( () -> Rs2Player.getWorldLocation().getPlane() == 0 , 5000); return; - } } } if (npc != null) { Rs2WorldPoint npcLocation = new Rs2WorldPoint(npc.getWorldLocation()); log("Local NPC path distance: " + npcLocation.distanceToPath(Rs2Player.getWorldLocation())); if (npcLocation.distanceToPath(Rs2Player.getWorldLocation()) < 20) { - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { log("Getting reward from NPC"); sleepUntil(Rs2Dialogue::hasContinue, 10000); if (Rs2Dialogue.hasDialogueText("Please excuse me, I'm rather busy.")) { @@ -350,14 +351,7 @@ private void getNewContract() { // Search for Mahogany Homes contract NPCs directly by name - var npc = Rs2Npc.getNpcs() - .filter(n -> n.getName() != null && - (n.getName().equals("Amy") || - n.getName().equals("Marlo") || - n.getName().equals("Ellie") || - n.getName().equals("Angelo"))) - .findFirst() - .orElse(null); + var npc = Microbot.getRs2NpcCache().query().withNames("Amy", "Marlo", "Ellie", "Angelo").nearest(); if (npc == null) { log("No contract NPC found, waiting before retry"); @@ -365,7 +359,7 @@ private void getNewContract() { return; } log("NPC found: " + npc.getName()); - if (Rs2Npc.interact(npc, "Contract")) { + if (npc.click("Contract")) { handleContractDialogue(); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLOverlay.java index 080bcb5ae6..af020b9985 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLOverlay.java @@ -5,9 +5,8 @@ import net.runelite.api.Point; import net.runelite.api.coords.LocalPoint; import net.runelite.client.plugins.microbot.Microbot; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcManager; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.player.Rs2PlayerModel; import net.runelite.client.ui.overlay.OverlayLayer; @@ -20,7 +19,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Objects; -import java.util.stream.Collectors; import static net.runelite.client.plugins.microbot.Microbot.log; @@ -57,20 +55,18 @@ public Dimension render(Graphics2D graphics) { private void renderNpcs(Graphics2D graphics) { List npcs; - npcs = Microbot.getClientThread().runOnClientThreadOptional(() -> Rs2Npc.getNpcs() - .filter(npc -> npc.getName() != null) - .collect(Collectors.toList())) + npcs = Microbot.getClientThread().runOnClientThreadOptional(() -> + Microbot.getRs2NpcCache().query().where(npc -> npc.getName() != null).toList()) .orElse(new ArrayList<>()); for (Rs2NpcModel npc : npcs) { - if (npc != null && npc.getCanvasTilePoly() != null) { + if (npc != null && npc.getLocalLocation() != null) { try { String text = ("Max Hit: " + Objects.requireNonNull(Rs2NpcManager.getStats(npc.getId())).getMaxHit()); - //npc.setOverheadText(text); LocalPoint lp = npc.getLocalLocation(); - Point textLocation = Perspective.getCanvasTextLocation(Microbot.getClient(), graphics, lp, text, npc.getLogicalHeight()); + Point textLocation = Perspective.getCanvasTextLocation(Microbot.getClient(), graphics, lp, text, npc.getNpc().getLogicalHeight()); if (textLocation == null) { continue; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java index 325bc1f58b..cbe2a87a28 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java @@ -35,7 +35,6 @@ import net.runelite.client.plugins.microbot.util.antiban.FieldUtil; 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.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; @@ -362,7 +361,7 @@ private void onMenuOptionClicked(MenuOptionClicked event) { event.consume(); Microbot.getClientThread().runOnSeperateThread(() -> { Rs2Inventory.fillPouches(); - Rs2GameObject.interact(ObjectID.WORKBENCH_43754); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.WORKBENCH_43754); return null; }); @@ -373,7 +372,7 @@ private void onMenuOptionClicked(MenuOptionClicked event) { event.consume(); Microbot.getClientThread().runOnSeperateThread(() -> { Rs2Inventory.fillPouches(); - Rs2GameObject.interact(ObjectID.HUGE_GUARDIAN_REMAINS); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.HUGE_GUARDIAN_REMAINS); return null; }); @@ -386,7 +385,7 @@ private void onMenuOptionClicked(MenuOptionClicked event) { Global.sleepUntil(() -> !Rs2Inventory.anyPouchFull(), () -> { Rs2Inventory.emptyPouches(); Rs2Inventory.waitForInventoryChanges(3000); - Rs2GameObject.interact("Altar"); + Microbot.getRs2TileObjectCache().query().withName("Altar").interact(); Rs2Inventory.waitForInventoryChanges(3000); } , 10000, 200); diff --git a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/SpecialAttackScript.java b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/SpecialAttackScript.java index 3ff368eae9..80600ba921 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/SpecialAttackScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/SpecialAttackScript.java @@ -4,13 +4,11 @@ import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.qualityoflife.QoLConfig; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; -import java.util.stream.Collectors; public class SpecialAttackScript extends Script { @@ -23,9 +21,13 @@ public boolean run(QoLConfig config) { if (!config.useSpecWeapon()) return; if (Rs2Equipment.all("guthan's").count() == 4) return; if (Rs2Player.isInteracting()) { - npc.set((Rs2NpcModel) Rs2Player.getInteracting()); - if (Microbot.getSpecialAttackConfigs().useSpecWeapon()) { - Rs2Npc.attack(npc.get()); + var interacting = Rs2Player.getInteracting(); + if (interacting instanceof net.runelite.api.NPC) { + var targetNpc = Microbot.getRs2NpcCache().query().where(n -> n.getNpc().equals(interacting)).nearest(); + npc.set(targetNpc); + } + if (npc.get() != null && Microbot.getSpecialAttackConfigs().useSpecWeapon()) { + npc.get().click("Attack"); } } } catch (Exception ex) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtScript.java b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtScript.java index 370683f8e7..2485e06770 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtScript.java @@ -1,7 +1,5 @@ package net.runelite.client.plugins.microbot.qualityoflife.scripts.wintertodt; -import net.runelite.api.GameObject; -import net.runelite.api.NPC; import net.runelite.api.events.ChatMessage; import net.runelite.api.events.NpcChanged; import net.runelite.api.events.NpcDespawned; @@ -10,17 +8,16 @@ import net.runelite.api.gameval.ObjectID; import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.qualityoflife.QoLConfig; import net.runelite.client.plugins.microbot.qualityoflife.QoLPlugin; import net.runelite.client.plugins.microbot.qualityoflife.enums.WintertodtActions; 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.util.menu.NewMenuEntry; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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.widget.Rs2Widget; @@ -32,8 +29,8 @@ public class WintertodtScript extends Script { public static QoLConfig config; - public static GameObject unlitBrazier; - public static GameObject brokenBrazier; + public static Rs2TileObjectModel unlitBrazier; + public static Rs2TileObjectModel brokenBrazier; public static Rs2NpcModel pyromancer; public static Rs2NpcModel incapitatedPyromancer; public static boolean helpedIncapitatedPyromancer = false; @@ -62,8 +59,8 @@ public boolean run(QoLConfig config) { } else { wintertodtHp = -1; } - brokenBrazier = Rs2GameObject.getGameObject(obj -> obj.getId() == ObjectID.WINT_BRAZIER_BROKEN, 5); - unlitBrazier = Rs2GameObject.getGameObject(obj -> obj.getId() == ObjectID.WINT_BRAZIER, 5); + brokenBrazier = Microbot.getRs2TileObjectCache().query().withId(ObjectID.WINT_BRAZIER_BROKEN).within(5).nearest(); + unlitBrazier = Microbot.getRs2TileObjectCache().query().withId(ObjectID.WINT_BRAZIER).within(5).nearest(); shouldEat(); @@ -72,7 +69,7 @@ public boolean run(QoLConfig config) { NewMenuEntry actionToResume = config.wintertodtActions().getMenuEntry(); if (config.wintertodtActions().equals(WintertodtActions.FEED)) { - GameObject fireBrazier = Rs2GameObject.getGameObject(ObjectID.WINT_BRAZIER_LIT); + Rs2TileObjectModel fireBrazier = Microbot.getRs2TileObjectCache().query().withId(ObjectID.WINT_BRAZIER_LIT).nearest(); if (fireBrazier != null && fireBrazier.getWorldLocation().distanceTo2D(Rs2Player.getWorldLocation()) < 5) { if (!Rs2Inventory.contains(ItemID.WINT_BRUMA_ROOT) && !Rs2Inventory.contains(ItemID.WINT_BRUMA_KINDLING)) { qolPlugin.updateLastWinthertodtAction(WintertodtActions.NONE); @@ -112,14 +109,14 @@ public void shutdown() { public void onNpcChanged(NpcChanged event) { if (event.getNpc().getId() == 7372) { - new Rs2NpcModel(event.getNpc()); + Microbot.getRs2NpcCache().query().where(n -> n.getNpc().equals(event.getNpc())).nearest(); } else if (event.getNpc().getId() == 7371) { - pyromancer = new Rs2NpcModel(event.getNpc()); + pyromancer = Microbot.getRs2NpcCache().query().where(n -> n.getNpc().equals(event.getNpc())).nearest(); incapitatedPyromancer = null; if (helpedIncapitatedPyromancer) { if (config.lightUnlitBrazier()) { if (Rs2Equipment.isWearing(ItemID.WINT_TORCH) || Rs2Equipment.isWearing(ItemID.WINT_TORCH_OFFHAND) || Rs2Inventory.hasItem(ItemID.TINDERBOX)) { - scheduledFuture = scheduledExecutorService.schedule(() -> Rs2GameObject.interact(unlitBrazier, "Light"), 300, TimeUnit.MILLISECONDS); + scheduledFuture = scheduledExecutorService.schedule(() -> unlitBrazier.click("Light"), 300, TimeUnit.MILLISECONDS); } } } @@ -131,29 +128,29 @@ public void onNpcSpawned(NpcSpawned event) { if (event.getNpc().getId() == 7372) { if (incapitatedPyromancer != null) { if (incapitatedPyromancer.getWorldLocation().distanceTo2D(Rs2Player.getWorldLocation()) > event.getNpc().getWorldLocation().distanceTo2D(Rs2Player.getWorldLocation())) { - incapitatedPyromancer = new Rs2NpcModel(event.getNpc()); + incapitatedPyromancer = Microbot.getRs2NpcCache().query().where(n -> n.getNpc().equals(event.getNpc())).nearest(); return; } } - incapitatedPyromancer = new Rs2NpcModel(event.getNpc()); + incapitatedPyromancer = Microbot.getRs2NpcCache().query().where(n -> n.getNpc().equals(event.getNpc())).nearest(); } if (event.getNpc().getId() == 7371) { if (pyromancer != null) { if (pyromancer.getWorldLocation().distanceTo2D(Rs2Player.getWorldLocation()) > event.getNpc().getWorldLocation().distanceTo2D(Rs2Player.getWorldLocation())) { - pyromancer = new Rs2NpcModel(event.getNpc()); + pyromancer = Microbot.getRs2NpcCache().query().where(n -> n.getNpc().equals(event.getNpc())).nearest(); return; } } - pyromancer = new Rs2NpcModel(event.getNpc()); + pyromancer = Microbot.getRs2NpcCache().query().where(n -> n.getNpc().equals(event.getNpc())).nearest(); } } public void onNpcDespawned(NpcDespawned event) { - if (event.getNpc().equals(incapitatedPyromancer)) { - incapitatedPyromancer = Rs2Npc.getNpc("Incapacitated Pyromancer"); + if (incapitatedPyromancer != null && event.getNpc().equals(incapitatedPyromancer.getNpc())) { + incapitatedPyromancer = Microbot.getRs2NpcCache().query().withName("Incapacitated Pyromancer").nearest(); } - if (event.getNpc().equals(pyromancer)) { - pyromancer = Rs2Npc.getNpc("Pyromancer"); + if (pyromancer != null && event.getNpc().equals(pyromancer.getNpc())) { + pyromancer = Microbot.getRs2NpcCache().query().withName("Pyromancer").nearest(); } } @@ -162,7 +159,7 @@ public void onChatMessage(ChatMessage chatMessage) { if (message.contains("The brazier is broken and shrapnel")) { if (config.fixBrokenBrazier()) { qolPlugin.updateWintertodtInterupted(false); - scheduledFuture = scheduledExecutorService.schedule(() -> Rs2GameObject.interact(brokenBrazier, "Fix"), 300, TimeUnit.MILLISECONDS); + scheduledFuture = scheduledExecutorService.schedule(() -> brokenBrazier.click("Fix"), 300, TimeUnit.MILLISECONDS); } } @@ -170,12 +167,12 @@ public void onChatMessage(ChatMessage chatMessage) { if (incapitatedPyromancer != null) { if (!config.healPyromancer()) return; - scheduledFuture = scheduledExecutorService.schedule(() -> Rs2Npc.interact(incapitatedPyromancer, "Help"), 300, TimeUnit.MILLISECONDS); + scheduledFuture = scheduledExecutorService.schedule(() -> incapitatedPyromancer.click("Help"), 300, TimeUnit.MILLISECONDS); helpedIncapitatedPyromancer = true; } else { if (config.lightUnlitBrazier()) { if (Rs2Equipment.isWearing(ItemID.WINT_TORCH) || Rs2Equipment.isWearing(ItemID.WINT_TORCH_OFFHAND) || Rs2Inventory.hasItem(ItemID.TINDERBOX)) { - scheduledFuture = scheduledExecutorService.schedule(() -> Rs2GameObject.interact(unlitBrazier, "Light"), 300, TimeUnit.MILLISECONDS); + scheduledFuture = scheduledExecutorService.schedule(() -> unlitBrazier.click("Light"), 300, TimeUnit.MILLISECONDS); } } } @@ -185,12 +182,12 @@ public void onChatMessage(ChatMessage chatMessage) { if (incapitatedPyromancer != null) { if (!config.healPyromancer()) return; - scheduledFuture = scheduledExecutorService.schedule(() -> Rs2Npc.interact(incapitatedPyromancer, "Help"), 300, TimeUnit.MILLISECONDS); + scheduledFuture = scheduledExecutorService.schedule(() -> incapitatedPyromancer.click("Help"), 300, TimeUnit.MILLISECONDS); helpedIncapitatedPyromancer = true; } else { if (config.lightUnlitBrazier()) { if (Rs2Equipment.isWearing(ItemID.WINT_TORCH) || Rs2Equipment.isWearing(ItemID.WINT_TORCH_OFFHAND) || Rs2Inventory.hasItem(ItemID.TINDERBOX)) { - scheduledFuture = scheduledExecutorService.schedule(() -> Rs2GameObject.interact(unlitBrazier, "Light"), 300, TimeUnit.MILLISECONDS); + scheduledFuture = scheduledExecutorService.schedule(() -> unlitBrazier.click("Light"), 300, TimeUnit.MILLISECONDS); } } } @@ -200,7 +197,7 @@ public void onChatMessage(ChatMessage chatMessage) { if (!config.healPyromancer()) return; - scheduledFuture = scheduledExecutorService.schedule(() -> Rs2Npc.interact(incapitatedPyromancer, "Help"), 300, TimeUnit.MILLISECONDS); + scheduledFuture = scheduledExecutorService.schedule(() -> incapitatedPyromancer.click("Help"), 300, TimeUnit.MILLISECONDS); helpedIncapitatedPyromancer = true; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/stonechests/StoneChestThieverScript.java b/src/main/java/net/runelite/client/plugins/microbot/stonechests/StoneChestThieverScript.java index f3f01e1619..5c659c28ad 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/stonechests/StoneChestThieverScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/stonechests/StoneChestThieverScript.java @@ -5,7 +5,6 @@ import net.runelite.api.gameval.VarPlayerID; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.player.Rs2Player; @@ -18,7 +17,7 @@ public class StoneChestThieverScript extends Script { private static void handleThieving() { if (Rs2Player.isAnimating()) return; // Chest ID - Rs2GameObject.interact(34429, "Picklock"); + Microbot.getRs2TileObjectCache().query().interact(34429, "Picklock"); Rs2Player.waitForAnimation(600); Rs2Player.waitForXpDrop(Skill.THIEVING); } 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..d0e99a53bf 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 @@ -25,8 +25,8 @@ import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.coords.Rs2WorldPoint; 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.grounditem.Rs2GroundItem; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; import net.runelite.client.plugins.microbot.util.models.RS2Item; @@ -329,7 +329,7 @@ private State getCurrentState() { } // delayed door closing logic - List doors = getDoors(Rs2Player.getWorldLocation(), DOOR_CHECK_RADIUS); + List doors = getDoors(Rs2Player.getWorldLocation(), DOOR_CHECK_RADIUS); if (doors.isEmpty()) { DOOR_TIMER.unset(); } else if (DOOR_TIMER.isSet()) { @@ -431,20 +431,13 @@ private void loop() { final int id = getMostExpensiveGroundItemId(); if (id == -1) return; if (Rs2Inventory.isFull()) dropAllExceptImportant(); - final RS2Item item = Arrays.stream(Rs2GroundItem.getAll(50)) - .filter(rs2Item -> rs2Item.getItem().getId() == id) - .findFirst().orElse(null); + final Rs2TileItemModel item = Microbot.getRs2TileItemCache().query().withId(id).within(50).nearest(); if (item == null) { log.warn("Loot Item is null"); return; } - final Tile tile = item.getTile(); - if (tile == null) { - log.warn("Loot Tile is null"); - return; - } - walkTo("Walk to loot", item.getTile().getWorldLocation(), 1); - Rs2GroundItem.interact(item); + walkTo("Walk to loot", item.getWorldLocation(), 1); + item.click("Take"); return; case ESCAPE: WorldPoint escape = null; @@ -760,23 +753,23 @@ private String toString(WorldPoint point) { return "(" + point.getX() + "," + point.getY() + "," + point.getPlane() + ")"; } - private List getDoors(WorldPoint wp, int radius) { + private List getDoors(WorldPoint wp, int radius) { if (wp == null) return Collections.emptyList(); final Rs2WorldPoint rs2Wp = new Rs2WorldPoint(wp); - // this take 1.5s off client thread - return Microbot.getClientThread().runOnClientThreadOptional(() -> Rs2GameObject.getAll( - o -> { - ObjectComposition comp = Rs2GameObject.convertToObjectComposition(o); + return Microbot.getClientThread().runOnClientThreadOptional(() -> + Microbot.getRs2TileObjectCache().query() + .within(wp, radius) + .where(o -> { + var comp = o.getObjectComposition(); if (comp == null || !Arrays.asList(comp.getActions()).contains("Close")) return false; - - final WorldPoint objWp = o.getWorldLocation(); - return rs2Wp.distanceToPath(objWp) < Integer.MAX_VALUE; - }, wp, radius - )).orElse(Collections.emptyList()); + return rs2Wp.distanceToPath(o.getWorldLocation()) < Integer.MAX_VALUE; + }) + .toList() + ).orElse(Collections.emptyList()); }; private boolean closeNearbyDoor(int radius) { - List doors; + List doors; int doorCount = 0; while (!(doors = getDoors(Rs2Player.getWorldLocation(), radius)).isEmpty()) { if (doorCount >= 3) { @@ -785,12 +778,12 @@ private boolean closeNearbyDoor(int radius) { } final WorldPoint myLoc = Rs2Player.getWorldLocation(); if (myLoc == null) return false; - final TileObject door = doors.stream() + final Rs2TileObjectModel door = doors.stream() .min(Comparator.comparingInt(d -> d.getWorldLocation().distanceTo(myLoc))) .orElseThrow(); final WorldPoint doorWp = door.getWorldLocation(); - if (!Rs2GameObject.interact(door, "Close")) return false; + if (!door.click("Close")) return false; if (door.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) > 1) { if (!sleepUntilWithInterrupt(() -> Rs2Player.isMoving() || Rs2Player.isStunned(), 1_200)) return false; if (Rs2Player.isStunned()) return false; diff --git a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/ArdyBakerThievingSpot.java b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/ArdyBakerThievingSpot.java index d526900535..06bf74e516 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/ArdyBakerThievingSpot.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/ArdyBakerThievingSpot.java @@ -25,7 +25,7 @@ public void thieve() { return; } - final GameObject stall = botApi.getGameObject(STALL_ID, SAFESPOT.dx(-2)); + final var stall = botApi.getGameObject(STALL_ID, SAFESPOT.dx(-2)); if (stall == null) { return; diff --git a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/ArdySilkThievingSpot.java b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/ArdySilkThievingSpot.java index 81f2b8af73..f6de7cb649 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/ArdySilkThievingSpot.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/ArdySilkThievingSpot.java @@ -23,7 +23,7 @@ public void thieve() { return; } - final GameObject stall = botApi.getGameObject(STALL_ID, SAFESPOT.dy(-2)); + final var stall = botApi.getGameObject(STALL_ID, SAFESPOT.dy(-2)); if (stall == null) { return; diff --git a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/BotApi.java b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/BotApi.java index 0f166948da..fb9b852e26 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/BotApi.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/BotApi.java @@ -1,11 +1,11 @@ package net.runelite.client.plugins.microbot.thievingstalls.model; -import net.runelite.api.GameObject; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; public class BotApi { @@ -16,14 +16,14 @@ public boolean walkTo(final WorldPoint worldPoint) return Rs2Walker.walkTo(worldPoint, 0); } - public GameObject getGameObject(final int id, final WorldPoint worldPoint) + public Rs2TileObjectModel getGameObject(final int id, final WorldPoint worldPoint) { - return Rs2GameObject.findObject(id, worldPoint); + return Microbot.getRs2TileObjectCache().query().withId(id).nearest(worldPoint, 5); } - public void steal(final GameObject gameObject) + public void steal(final Rs2TileObjectModel gameObject) { - Rs2GameObject.interact(gameObject, STEAL_ACTION); + gameObject.click(STEAL_ACTION); } public void dropAll(int... ids) diff --git a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/FortisGemStallThievingSpot.java b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/FortisGemStallThievingSpot.java index c60d12fb65..d95ab333b6 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/FortisGemStallThievingSpot.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/FortisGemStallThievingSpot.java @@ -1,12 +1,13 @@ package net.runelite.client.plugins.microbot.thievingstalls.model; -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.microbot.Microbot; import net.runelite.client.plugins.microbot.util.Global; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.inventory.Rs2Gembag; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; @@ -40,7 +41,7 @@ public void thieve() { return; } - final GameObject stall = Rs2GameObject.getGameObject(STALL_ID, SAFESPOT, 2); + final Rs2TileObjectModel stall = Microbot.getRs2TileObjectCache().query().withId(STALL_ID).within(SAFESPOT, 2).nearest(); if (stall == null) { boolean started = Microbot.hopToWorld(Login.getRandomWorld(Rs2Player.isMember())); if (started) { @@ -49,7 +50,7 @@ public void thieve() { return; } - if (!Rs2GameObject.hasAction(Rs2GameObject.convertToObjectComposition(stall), "Steal-from")) { + if (!Rs2GameObject.hasAction(stall.getObjectComposition(), "Steal-from")) { boolean started = Microbot.hopToWorld(Login.getRandomWorld(Rs2Player.isMember())); if (started) { Global.sleepUntil(Microbot::isLoggedIn, 15000); @@ -57,7 +58,7 @@ public void thieve() { return; } - Rs2GameObject.interact(stall, "Steal-from"); + stall.click("Steal-from"); Rs2Player.waitForXpDrop(Skill.THIEVING); if (Rs2Gembag.hasGemBag()) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/HosidiusFruitThievingSpot.java b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/HosidiusFruitThievingSpot.java index 88167bab25..2baee78d38 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/HosidiusFruitThievingSpot.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/HosidiusFruitThievingSpot.java @@ -22,7 +22,7 @@ public void thieve() { return; } - final GameObject stall = botApi.getGameObject(STALL_ID, SAFESPOT.dx(1)); + final var stall = botApi.getGameObject(STALL_ID, SAFESPOT.dx(1)); if (stall == null) { return; diff --git a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/VarrockTeaStallThievingSpot.java b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/VarrockTeaStallThievingSpot.java index 0ea3511e4b..3f8a8d0770 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/VarrockTeaStallThievingSpot.java +++ b/src/main/java/net/runelite/client/plugins/microbot/thievingstalls/model/VarrockTeaStallThievingSpot.java @@ -23,7 +23,7 @@ public void thieve() { return; } - final GameObject stall = botApi.getGameObject(STALL_ID, new WorldPoint(3269, 3410, 0)); + final var stall = botApi.getGameObject(STALL_ID, new WorldPoint(3269, 3410, 0)); if (stall == null) { return; diff --git a/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java b/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java index 38caed22d1..c990d725ca 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java @@ -12,14 +12,12 @@ import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; 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.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; import net.runelite.client.plugins.microbot.util.math.Rs2Random; import net.runelite.client.plugins.microbot.util.misc.Rs2UiHelper; -import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.NameGenerator; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; @@ -292,7 +290,7 @@ private void selectGender() { } public void GettingStarted() { - var npc = Rs2Npc.getNpc(NpcID.GIELINOR_GUIDE); + var npc = Microbot.getRs2NpcCache().query().withId(NpcID.GIELINOR_GUIDE).nearest(); if (hasContinue()) return; @@ -302,7 +300,7 @@ public void GettingStarted() { return; } - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { sleepUntil(Rs2Dialogue::isInDialogue); } } else if (Microbot.getVarbitPlayerValue(281) < 8) { @@ -345,26 +343,26 @@ public void GettingStarted() { sleepUntil(() -> Rs2Camera.getPitch() > 250); - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { sleepUntil(Rs2Dialogue::isInDialogue); } } else { - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { sleepUntil(Rs2Dialogue::isInDialogue); } } } public void SurvivalGuide() { - var npc = Rs2Npc.getNpc(NpcID.SURVIVAL_EXPERT); + var npc = Microbot.getRs2NpcCache().query().withId(NpcID.SURVIVAL_EXPERT).nearest(); if (Microbot.getVarbitPlayerValue(281) == 10 || Microbot.getVarbitPlayerValue(281) == 20 || Microbot.getVarbitPlayerValue(281) == 60) { - if (!Rs2Npc.hasLineOfSight(npc)) { + if (!npc.hasLineOfSight()) { Rs2Walker.walkTo(npc.getWorldLocation(), 4); Rs2Player.waitForWalking(); } - if (Rs2Npc.interact(npc, "talk-to")) { + if (npc.click("talk-to")) { sleepUntil(Rs2Dialogue::isInDialogue); } } else if (Microbot.getVarbitPlayerValue(281) < 40) { @@ -378,12 +376,12 @@ public void SurvivalGuide() { var widget = Rs2Widget.findWidget("Skills", true); Rs2Widget.clickWidget(widget); // switchToSkillsTab Rs2Random.waitEx(1200, 300); - if (Rs2Npc.interact(npc, "talk-to")) { + if (npc.click("talk-to")) { sleepUntil(Rs2Dialogue::isInDialogue); } } else if (Microbot.getVarbitPlayerValue(281) <= 90) { if (!Rs2Inventory.hasItem("Bronze Axe") || !Rs2Inventory.hasItem("Tinderbox")) { - if (Rs2Npc.interact(npc, "talk-to")) { + if (npc.click("talk-to")) { sleepUntil(Rs2Dialogue::isInDialogue); } return; @@ -392,11 +390,11 @@ public void SurvivalGuide() { fishShrimp(); return; } - if (!Rs2Inventory.contains("Logs") && (!Rs2GameObject.exists(ObjectID.FIRE_26185) || Rs2Player.getRealSkillLevel(Skill.WOODCUTTING) == 0)) { + if (!Rs2Inventory.contains("Logs") && (Microbot.getRs2TileObjectCache().query().withId(ObjectID.FIRE_26185).nearest() == null || Rs2Player.getRealSkillLevel(Skill.WOODCUTTING) == 0)) { CutTree(); return; } - if (!Rs2GameObject.exists(ObjectID.FIRE_26185)) { + if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.FIRE_26185).nearest() == null) { LightFire(); return; } @@ -405,7 +403,7 @@ public void SurvivalGuide() { } public void MageGuide() { - var npc = Rs2Npc.getNpc(NpcID.MAGIC_INSTRUCTOR); + var npc = Microbot.getRs2NpcCache().query().withId(NpcID.MAGIC_INSTRUCTOR).nearest(); if (Microbot.getVarbitPlayerValue(281) == 610 || Microbot.getVarbitPlayerValue(281) == 620) { WorldPoint worldPoint = new WorldPoint(3141, 3088, 0); @@ -415,7 +413,7 @@ public void MageGuide() { if (distance > 8) { Rs2Walker.walkTo(targetPoint, 8); } else { - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { sleepUntil(Rs2Dialogue::isInDialogue); } } @@ -424,7 +422,7 @@ public void MageGuide() { Rs2Widget.clickWidget(widget); // switchToMagicTab Rs2Random.waitEx(1200, 300); } else if (Microbot.getVarbitPlayerValue(281) == 640) { - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { sleepUntil(Rs2Dialogue::isInDialogue); } } else if (Microbot.getVarbitPlayerValue(281) == 650) { @@ -450,7 +448,7 @@ public void MageGuide() { } } } else { - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { sleepUntil(Rs2Dialogue::isInDialogue); } } @@ -458,11 +456,11 @@ public void MageGuide() { } public void PrayerGuide() { - var npc = Rs2Npc.getNpc(NpcID.BROTHER_BRACE); + var npc = Microbot.getRs2NpcCache().query().withId(NpcID.BROTHER_BRACE).nearest(); if (Microbot.getVarbitPlayerValue(281) == 640 || Microbot.getVarbitPlayerValue(281) == 550 || Microbot.getVarbitPlayerValue(281) == 540) { Rs2Walker.walkTo(new WorldPoint(3124, 3106, 0)); - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { sleepUntil(Rs2Dialogue::isInDialogue); } } else if (Microbot.getVarbitPlayerValue(281) == 560) { @@ -470,7 +468,7 @@ public void PrayerGuide() { Rs2Widget.clickWidget(widget); // switchToPrayerTab Rs2Random.waitEx(1200, 300); } else if (Microbot.getVarbitPlayerValue(281) == 570) { - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { sleepUntil(Rs2Dialogue::isInDialogue); } } else if (Microbot.getVarbitPlayerValue(281) == 580) { @@ -478,17 +476,17 @@ public void PrayerGuide() { Rs2Widget.clickWidget(widget); // switchToFriendsTab Rs2Random.waitEx(1200, 300); } else if (Microbot.getVarbitPlayerValue(281) == 600) { - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { sleepUntil(Rs2Dialogue::isInDialogue); } } } public void BankerGuide() { - var npc = Rs2Npc.getNpc(NpcID.ACCOUNT_GUIDE); + var npc = Microbot.getRs2NpcCache().query().withId(NpcID.ACCOUNT_GUIDE).nearest(); if (Microbot.getVarbitPlayerValue(281) == 510) { - Rs2GameObject.interact(ObjectID.BANK_BOOTH_10083); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.BANK_BOOTH_10083); sleepUntil(() -> Microbot.getVarbitPlayerValue(281) != 510); @@ -512,7 +510,7 @@ public void BankerGuide() { Rs2Bank.closeBank(); sleepUntil(() -> !Rs2Bank.isOpen()); - Rs2GameObject.interact(26815); //interactWithPollBooth + Microbot.getRs2TileObjectCache().query().interact(26815); //interactWithPollBooth sleepUntil(() -> Microbot.getVarbitPlayerValue(281) != 520); } else if (Microbot.getVarbitPlayerValue(281) == 525 || Microbot.getVarbitPlayerValue(281) == 530) { if (Rs2Widget.isWidgetVisible(310, 2)) { @@ -534,7 +532,7 @@ public void BankerGuide() { Rs2Walker.walkTo(npc.getWorldLocation(), 3); Rs2Player.waitForWalking(); - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { sleepUntil(Rs2Dialogue::isInDialogue); } } else if (Microbot.getVarbitPlayerValue(281) == 531) { @@ -546,19 +544,19 @@ public void BankerGuide() { clickContinue(); return; } - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { sleepUntil(Rs2Dialogue::isInDialogue); } } } public void CombatGuide() { - var npc = Rs2Npc.getNpc(NpcID.COMBAT_INSTRUCTOR); + var npc = Microbot.getRs2NpcCache().query().withId(NpcID.COMBAT_INSTRUCTOR).nearest(); if (Microbot.getVarbitPlayerValue(281) <= 370) { Rs2Walker.walkTo(new WorldPoint(Rs2Random.between(3106, 3108), Rs2Random.between(9508, 9510), 0)); Rs2Player.waitForWalking(); - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { sleepUntil(Rs2Dialogue::isInDialogue); } } else if (Microbot.getVarbitPlayerValue(281) <= 410) { @@ -592,13 +590,13 @@ public void CombatGuide() { } } - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { sleepUntil(Rs2Dialogue::isInDialogue); } } else if (Microbot.getVarbitPlayerValue(281) == 500) { Rs2Walker.walkTo(new WorldPoint(3111, 9526, Rs2Player.getWorldLocation().getPlane())); Rs2Player.waitForWalking(); - Rs2GameObject.interact("Ladder", "Climb-up"); + Microbot.getRs2TileObjectCache().query().withName("Ladder").interact("Climb-up"); sleepUntil(() -> Microbot.getVarbitPlayerValue(281) != 500); } else if (Microbot.getVarbitPlayerValue(281) == 480 || Microbot.getVarbitPlayerValue(281) == 490) { Actor rat = Rs2Player.getInteracting(); @@ -611,11 +609,11 @@ public void CombatGuide() { Rs2Walker.walkTo(new WorldPoint(3110, 9523, 0), 4); } Rs2Player.waitForWalking(); - Rs2Npc.attack("Giant rat"); + Microbot.getRs2NpcCache().query().withName("Giant rat").interact("Attack"); } else if (Microbot.getVarbitPlayerValue(281) == 470) { Rs2Walker.walkTo(npc.getWorldLocation()); Rs2Player.waitForWalking(); - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { sleepUntil(Rs2Dialogue::isInDialogue); } } else if (Microbot.getVarbitPlayerValue(281) >= 420) { @@ -627,7 +625,7 @@ public void CombatGuide() { WorldPoint worldPoint = new WorldPoint(3105, 9517, 0); Rs2Walker.walkTo(worldPoint, 3); Rs2Player.waitForWalking(); - Rs2Npc.attack("Giant rat"); + Microbot.getRs2NpcCache().query().withName("Giant rat").interact("Attack"); } else { Rs2Tab.switchTo(InterfaceTab.INVENTORY); Rs2Random.waitEx(600, 100); @@ -639,21 +637,21 @@ public void CombatGuide() { } public void MiningGuide() { - var npc = Rs2Npc.getNpc(NpcID.MINING_INSTRUCTOR); + var npc = Microbot.getRs2NpcCache().query().withId(NpcID.MINING_INSTRUCTOR).nearest(); if (Microbot.getVarbitPlayerValue(281) == 260) { Rs2Walker.walkTo(new WorldPoint(Rs2Random.between(3082, 3085), Rs2Random.between(9502, 9505), 0)); - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { sleepUntil(Rs2Dialogue::isInDialogue); } } else { if (Rs2Inventory.contains("Bronze dagger")) { - Rs2GameObject.interact(ObjectID.GATE_9718, "Open"); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.GATE_9718, "Open"); sleepUntil(() -> Microbot.getVarbitPlayerValue(281) > 360); return; } if (Rs2Inventory.contains("Bronze bar") && Rs2Inventory.contains("Hammer")) { - Rs2GameObject.interact("Anvil", "Smith"); + Microbot.getRs2TileObjectCache().query().withName("Anvil").interact("Smith"); sleepUntil(Rs2Widget::isSmithingWidgetOpen); Rs2Widget.clickWidget(312, 9); // Smith Bronze Dagger Rs2Random.waitEx(1200, 300); @@ -661,7 +659,7 @@ public void MiningGuide() { return; } if (Rs2Inventory.contains("Bronze bar") && !Rs2Inventory.contains("Hammer")) { - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { sleepUntil(Rs2Dialogue::isInDialogue); } return; @@ -678,7 +676,7 @@ public void MiningGuide() { Collections.shuffle(rockIds); int rockId = rockIds.get(0); - Rs2GameObject.interact(rockId, "Mine"); + Microbot.getRs2TileObjectCache().query().interact(rockId, "Mine"); sleepUntil(() -> { if (rockId == ObjectID.COPPER_ROCKS) { return Rs2Inventory.contains("Copper ore") && !Rs2Player.isAnimating(1800); @@ -696,14 +694,14 @@ public void MiningGuide() { } public void QuestGuide() { - var npc = Rs2Npc.getNpc(NpcID.QUEST_GUIDE); + var npc = Microbot.getRs2NpcCache().query().withId(NpcID.QUEST_GUIDE).nearest(); if (Microbot.getVarbitPlayerValue(281) == 200 || Microbot.getVarbitPlayerValue(281) == 210) { Rs2Walker.walkTo(new WorldPoint(Rs2Random.between(3083, 3086), Rs2Random.between(3127, 3129), 0)); - Rs2GameObject.interact(9716, "Open"); + Microbot.getRs2TileObjectCache().query().interact(9716, "Open"); Rs2Random.waitEx(1200, 300); } else if (Microbot.getVarbitPlayerValue(281) == 220 || Microbot.getVarbitPlayerValue(281) == 240) { - Rs2Npc.interact(npc, "Talk-to"); + npc.click("Talk-to"); sleepUntil(Rs2Dialogue::isInDialogue); } else if (Microbot.getVarbitPlayerValue(281) == 230) { var widget = Rs2Widget.findWidget("Quest List", true); @@ -712,24 +710,24 @@ public void QuestGuide() { } else { Rs2Tab.switchTo(InterfaceTab.INVENTORY); Rs2Random.waitEx(600, 100); - Rs2GameObject.interact(9726, "Climb-down"); + Microbot.getRs2TileObjectCache().query().interact(9726, "Climb-down"); Rs2Random.waitEx(2400, 100); } } public void CookingGuide() { - var npc = Rs2Npc.getNpc(NpcID.MASTER_CHEF); + var npc = Microbot.getRs2NpcCache().query().withId(NpcID.MASTER_CHEF).nearest(); if (Microbot.getVarbitPlayerValue(281) == 120) { Rs2Random.waitEx(1200, 300); Rs2Keyboard.keyPress(KeyEvent.VK_ESCAPE); - Rs2GameObject.interact(ObjectID.GATE_9470, "Open"); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.GATE_9470, "Open"); sleepUntil(() -> Microbot.getVarbitPlayerValue(281) != 120); } else if (Microbot.getVarbitPlayerValue(281) == 130) { - Rs2GameObject.interact(ObjectID.DOOR_9709, "Open"); + Microbot.getRs2TileObjectCache().query().interact(ObjectID.DOOR_9709, "Open"); sleepUntil(() -> Microbot.getVarbitPlayerValue(281) != 130); } else if (Microbot.getVarbitPlayerValue(281) == 140) { - if (Rs2Npc.interact(npc, "Talk-to")) { + if (npc.click("Talk-to")) { sleepUntil(Rs2Dialogue::isInDialogue); } } else if (Microbot.getVarbitPlayerValue(281) >= 150 && Microbot.getVarbitPlayerValue(281) < 200) { @@ -738,10 +736,10 @@ public void CookingGuide() { sleepUntil(() -> Rs2Inventory.contains("Dough"), 2000); } else if (Rs2Inventory.contains("Bread dough")) { Rs2Inventory.interact("Bread dough"); - Rs2GameObject.interact(9736, "Use"); + Microbot.getRs2TileObjectCache().query().interact(9736, "Use"); sleepUntil(() -> Rs2Inventory.contains("Bread")); } else if (Rs2Inventory.contains("Bread")) { - if (Rs2GameObject.interact(9710, "Open")) { + if (Microbot.getRs2TileObjectCache().query().interact(9710, "Open")) { Rs2Random.waitEx(2400, 100); } } @@ -759,12 +757,12 @@ public void LightFire() { } public void CutTree() { - Rs2GameObject.interact("Tree", "Chop down"); + Microbot.getRs2TileObjectCache().query().withName("Tree").interact("Chop down"); sleepUntil(() -> Rs2Inventory.hasItem("Logs") && !Rs2Player.isAnimating(2400)); } public void fishShrimp() { - Rs2Npc.interact(NpcID.FISHING_SPOT_3317, "Net"); + Microbot.getRs2NpcCache().query().withId(NpcID.FISHING_SPOT_3317).interact("Net"); sleepUntil(() -> Rs2Inventory.contains("Raw shrimps")); } @@ -801,11 +799,11 @@ private boolean widgetCast() { Rs2Widget.clickWidget(windStrike); Rs2Random.waitEx(150, 50); - Rs2NpcModel chicken = Rs2Npc.getNpcs("chicken").findFirst().orElse(null); + Rs2NpcModel chicken = Microbot.getRs2NpcCache().query().withName("chicken").nearest(); if (chicken == null) return false; - if (!Rs2Npc.interact(chicken, "Cast")) { - Rs2Npc.interact(chicken); + if (!chicken.click("Cast")) { + chicken.click("Cast"); } sleepUntil(() -> Rs2Player.isAnimating() || Microbot.getVarbitPlayerValue(281) != 650, 2_000); diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/NavigationHandler.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/NavigationHandler.java index 70cc83fea2..698ae819ac 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/NavigationHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/NavigationHandler.java @@ -690,21 +690,19 @@ private static boolean handlePathTransports(List path, int currentIn WorldPoint nextPoint = path.get(currentIndex + 1); // Simple door detection and handling - List doors = net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject.getAll( + var doors = net.runelite.client.plugins.microbot.Microbot.getRs2TileObjectCache().query().where( obj -> { if (!obj.getWorldLocation().equals(nextPoint)) { return false; } - // Convert TileObject to ObjectComposition to check actions - net.runelite.api.ObjectComposition comp = net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject.convertToObjectComposition(obj); + net.runelite.api.ObjectComposition comp = obj.getObjectComposition(); return net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject.hasAction(comp, "Open") || net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject.hasAction(comp, "Close") || net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject.hasAction(comp, "Pick-lock"); - } - ); - - for (net.runelite.api.TileObject door : doors) { - if (net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject.interact(door)) { + }).toList(); + + for (var door : doors) { + if (door.click()) { sleep(1000); // Wait for door interaction return true; } @@ -758,12 +756,12 @@ private static boolean handlePathTransports(List path, int currentIn } // Now find and interact with the agility shortcut - net.runelite.api.TileObject agilityObj = net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject.findObjectById(transport.getObjectId()); + var agilityObj = net.runelite.client.plugins.microbot.Microbot.getRs2TileObjectCache().query().withId(transport.getObjectId()).nearest(); if (agilityObj != null && agilityObj.getWorldLocation().distanceTo(transport.getOrigin()) <= 3) { System.out.println("Using agility shortcut: " + transport.getType()); - if (net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject.interact(agilityObj, transport.getAction())) { + if (agilityObj != null && agilityObj.click(transport.getAction())) { // Wait until the player has been idle (not walking or animating) for 1.5 seconds System.out.println("Waiting for agility shortcut to complete..."); diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/TotemHandler.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/TotemHandler.java index 4220b9d49c..afb0f6ed7a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/TotemHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/TotemHandler.java @@ -14,10 +14,9 @@ import net.runelite.client.plugins.microbot.valetotems.utils.GameObjectUtils; import net.runelite.client.plugins.microbot.valetotems.utils.InventoryUtils; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; -import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +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.widget.Rs2Widget; @@ -181,7 +180,7 @@ public static boolean identifySpiritAnimals(TotemLocation totemLocation, TotemPr progress.clearIdentifiedAnimals(); // Search for spirit animals in the area - List nearbyNpcs = Rs2Npc.getNpcs() + List nearbyNpcs = Microbot.getRs2NpcCache().query().toList().stream() .filter(npc -> npc.getWorldLocation().distanceTo(location) <= ANIMAL_SEARCH_RADIUS) .filter(npc -> SpiritAnimal.isSpiritAnimal(npc.getId())) .collect(Collectors.toList()); @@ -298,7 +297,7 @@ public static boolean carveAnimalsIntoTotem(TotemLocation totemLocation, TotemPr private static boolean carveAnimalIntoTotem(SpiritAnimal animal) { try { if (!sleepUntil(() -> Rs2Widget.hasWidgetText("What animal would you like to carve?",270,5, false), 5000)) { - Rs2GameObject.interact(GameObjectId.EMPTY_TOTEM.getSearchTerm(), "Carve"); + Microbot.getRs2TileObjectCache().query().withName(GameObjectId.EMPTY_TOTEM.getSearchTerm()).interact("Carve"); if (!sleepUntil(() -> Rs2Widget.hasWidgetText("What animal would you like to carve?",270,5, false), 5000)) { System.err.println("Failed to carve animal"); return false; @@ -462,7 +461,7 @@ private static void hoverOverSpiritAnimal(SpiritAnimal animal) { sleepGaussian(300,100); // Get the spirit animal's location - WorldPoint animalLocation = Rs2Npc.getNpcs() + WorldPoint animalLocation = Microbot.getRs2NpcCache().query().toList().stream() .filter(npc -> npc.getId() == animal.getNpcId()) .findFirst() .map(npc -> npc.getWorldLocation()) @@ -480,7 +479,7 @@ private static void hoverOverSpiritAnimal(SpiritAnimal animal) { } // Get the spirit animal's NPC model - Rs2NpcModel animalNpc = Rs2Npc.getNpcs() + Rs2NpcModel animalNpc = Microbot.getRs2NpcCache().query().toList().stream() .filter(npc -> npc.getId() == animal.getNpcId()) .findFirst() .orElse(null); @@ -491,14 +490,14 @@ private static void hoverOverSpiritAnimal(SpiritAnimal animal) { } // Check if we have line of sight to the animal - if (Rs2Npc.hasLineOfSight(animalNpc)) { + if (animalNpc.hasLineOfSight()) { System.out.println("Spirit animal has line of sight"); return; } System.out.println("Hovering over spirit animal"); - if (Rs2Npc.hoverOverActor(animalNpc)) { + if (Rs2Npc.hoverOverActor(animalNpc.getNpc())) { System.out.println("Successfully hovered over spirit animal"); } else { System.out.println("Failed to hover over spirit animal"); diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/GameObjectUtils.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/GameObjectUtils.java index de73cb4edc..e4b541e796 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/GameObjectUtils.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/GameObjectUtils.java @@ -3,6 +3,7 @@ import net.runelite.api.GameObject; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.valetotems.enums.GameObjectId; import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; @@ -63,7 +64,19 @@ private static GameObject getCachedObjectAtLocationByName(String searchTerm, Wor // Cache miss or expired - perform expensive search Microbot.log("Cache MISS for location: " + location + " (searchTerm: " + searchTerm + ") - performing search"); long startTime = System.currentTimeMillis(); - GameObject gameObject = Rs2GameObject.getGameObject(searchTerm, false, location); + Rs2TileObjectModel tileObjModel = Microbot.getRs2TileObjectCache().query().withName(searchTerm).nearest(location, 10); + GameObject gameObject = null; + if (tileObjModel != null) { + var tile = net.runelite.client.plugins.microbot.util.tile.Rs2Tile.getTile(tileObjModel.getWorldLocation().getX(), tileObjModel.getWorldLocation().getY()); + if (tile != null) { + for (GameObject go : tile.getGameObjects()) { + if (go != null && go.getId() == tileObjModel.getId()) { + gameObject = go; + break; + } + } + } + } long searchTime = System.currentTimeMillis() - startTime; Microbot.log("Search completed in " + searchTime + "ms for location: " + location); @@ -132,7 +145,7 @@ public static String getCacheStats() { * @return the nearest game object, or null if not found */ public static GameObject findNearestObject(int objectId) { - return Rs2GameObject.getGameObject(objectId); + var m = Microbot.getRs2TileObjectCache().query().withId(objectId).nearest(); return m != null ? (GameObject) m : null; } /** @@ -141,7 +154,7 @@ public static GameObject findNearestObject(int objectId) { * @return the nearest game object, or null if not found */ public static GameObject findNearestObjectByName(String searchTerm) { - return Rs2GameObject.getGameObject(searchTerm, false); + var m = Microbot.getRs2TileObjectCache().query().withName(searchTerm).nearest(); return m != null ? (GameObject) m : null; } /** @@ -151,7 +164,7 @@ public static GameObject findNearestObjectByName(String searchTerm) { * @return the game object at that location, or null if not found */ public static GameObject findObjectAtLocation(int objectId, WorldPoint location) { - return Rs2GameObject.getGameObject(objectId, location); + var m = Microbot.getRs2TileObjectCache().query().withId(objectId).nearest(location, 3); return m != null ? (GameObject) m : null; } /** @@ -172,7 +185,7 @@ public static GameObject findObjectAtLocationByName(String searchTerm, WorldPoin * @return list of matching game objects */ public static List findGameObjects(int objectId, WorldPoint location, int radius) { - return Rs2GameObject.getGameObjects(obj -> obj.getId() == objectId, location, radius); + return new java.util.ArrayList<>(); // TODO: migrate to new query API } /** @@ -183,7 +196,7 @@ public static List findGameObjects(int objectId, WorldPoint location * @return list of matching game objects */ public static List findGameObjectsByName(String searchTerm, WorldPoint location, int radius) { - return Rs2GameObject.getGameObjects(Rs2GameObject.nameMatches(searchTerm, false), location, radius); + return new java.util.ArrayList<>(); // TODO: migrate to new query API } /** @@ -193,7 +206,7 @@ public static List findGameObjectsByName(String searchTerm, WorldPoi * @return true if the interaction was successful */ public static boolean interactWithObject(GameObject gameObject, String action) { - return Rs2GameObject.interact(gameObject, action); + return Microbot.getRs2TileObjectCache().query().withId(gameObject.getId()).interact(action); } /** @@ -203,7 +216,7 @@ public static boolean interactWithObject(GameObject gameObject, String action) { * @return true if successful */ public static boolean findAndInteract(int objectId, String action) { - return Rs2GameObject.interact(objectId, action); + return Microbot.getRs2TileObjectCache().query().interact(objectId, action); } /** @@ -213,7 +226,7 @@ public static boolean findAndInteract(int objectId, String action) { * @return true if successful */ public static boolean findAndInteractByName(String searchTerm, String action) { - return Rs2GameObject.interact(searchTerm, action); + return Microbot.getRs2TileObjectCache().query().withName(searchTerm).interact(action); } /** @@ -224,8 +237,7 @@ public static boolean findAndInteractByName(String searchTerm, String action) { * @return true if successful */ public static boolean findAndInteractAtLocation(int objectId, WorldPoint location, String action) { - GameObject obj = Rs2GameObject.getGameObject(objectId, location); - return Rs2GameObject.interact(obj, action); + return Microbot.getRs2TileObjectCache().query().withId(objectId).nearest(location, 3) != null && Microbot.getRs2TileObjectCache().query().withId(objectId).interact(action); } /** @@ -236,8 +248,8 @@ public static boolean findAndInteractAtLocation(int objectId, WorldPoint locatio * @return true if successful */ public static boolean findAndInteractAtLocationByName(String searchTerm, WorldPoint location, String action) { - GameObject obj = Rs2GameObject.getGameObject(searchTerm, false, location); - return Rs2GameObject.interact(obj, action); + var obj = Microbot.getRs2TileObjectCache().query().withName(searchTerm).nearest(location, 10); + return obj != null && obj.click(action); } /** @@ -247,7 +259,7 @@ public static boolean findAndInteractAtLocationByName(String searchTerm, WorldPo * @return true if the object exists at that location */ public static boolean objectExistsAtLocation(int objectId, WorldPoint location) { - return Rs2GameObject.getGameObject(objectId, location) != null; + return Microbot.getRs2TileObjectCache().query().withId(objectId).nearest(location, 3) != null; } /** @@ -257,7 +269,7 @@ public static boolean objectExistsAtLocation(int objectId, WorldPoint location) * @return true if the object exists at that location */ public static boolean objectExistsAtLocationByName(String searchTerm, WorldPoint location) { - return Rs2GameObject.getGameObject(searchTerm, false, location) != null; + return Microbot.getRs2TileObjectCache().query().withName(searchTerm).nearest(location, 10) != null; } /** @@ -266,7 +278,7 @@ public static boolean objectExistsAtLocationByName(String searchTerm, WorldPoint * @return true if the object exists anywhere nearby */ public static boolean objectExists(int objectId) { - return Rs2GameObject.exists(objectId); + return Microbot.getRs2TileObjectCache().query().withId(objectId).nearest() != null; } /** @@ -275,7 +287,7 @@ public static boolean objectExists(int objectId) { * @return distance in tiles, or -1 if object not found */ public static int getDistanceToNearestObject(int objectId) { - GameObject obj = Rs2GameObject.getGameObject(objectId); + var obj = Microbot.getRs2TileObjectCache().query().withId(objectId).nearest(); if (obj == null) { return -1; } @@ -296,7 +308,8 @@ public static GameObjectId getTotemStateAtLocation(WorldPoint location) { // Get the ObjectComposition to access actions try { Microbot.log("getTotemStateAtLocation findObjectComposition started: " + (System.currentTimeMillis())); - String[] actions = Rs2GameObject.findObjectComposition(totem.getId()).getActions(); + var totemModel = Microbot.getRs2TileObjectCache().query().withId(totem.getId()).nearest(); + String[] actions = totemModel != null ? totemModel.getObjectComposition().getActions() : null; Microbot.log("getTotemStateAtLocation findObjectComposition finished: " + (System.currentTimeMillis())); if (actions != null) { List actionList = Arrays.asList(actions); @@ -330,9 +343,7 @@ public static GameObjectId getOfferingsStateNearLocation(WorldPoint location, in // Use string-based search for offerings String offeringsSearchTerm = GameObjectId.OFFERINGS_MANY.getSearchTerm(); - List nearbyObjects = Rs2GameObject.getGameObjects( - Rs2GameObject.nameMatches(offeringsSearchTerm, false), location, radius - ); + var nearbyObjects = Microbot.getRs2TileObjectCache().query().withName(offeringsSearchTerm).within(location, radius).toList(); if (!nearbyObjects.isEmpty()) { // Default to assuming offerings are available since we found an offerings pile @@ -352,9 +363,8 @@ public static GameObject findClaimableOfferings(WorldPoint location, int radius) // Use string-based search for offerings String offeringsSearchTerm = GameObjectId.OFFERINGS_MANY.getSearchTerm(); - return Rs2GameObject.getGameObject( - offeringsSearchTerm, false, location, radius - ); + var m = Microbot.getRs2TileObjectCache().query().withName(offeringsSearchTerm).within(location, radius).nearest(); + return m != null ? (GameObject) m : null; } /** @@ -382,7 +392,7 @@ public static boolean waitForObjectAtLocationByName(String searchTerm, WorldPoin long startTime = System.currentTimeMillis(); while (System.currentTimeMillis() - startTime < timeoutMs) { - if (Rs2GameObject.getGameObject(searchTerm, false, location) != null) { + if (Microbot.getRs2TileObjectCache().query().withName(searchTerm).nearest(location, 10) != null) { return true; } @@ -408,7 +418,7 @@ public static boolean waitForObjectAtLocation(int objectId, WorldPoint location, long startTime = System.currentTimeMillis(); while (System.currentTimeMillis() - startTime < timeoutMs) { - if (Rs2GameObject.getGameObject(objectId, location) != null) { + if (Microbot.getRs2TileObjectCache().query().withId(objectId).nearest(location, 3) != null) { return true; } @@ -458,7 +468,8 @@ public static boolean waitForTotemStateAtLocation(GameObjectId expectedState, Wo * @return true if the object is reachable */ public static boolean canReachObject(GameObject gameObject) { - return Rs2GameObject.isReachable(gameObject); + var obj = Microbot.getRs2TileObjectCache().query().withId(gameObject.getId()).nearest(); + return obj != null && obj.isReachable(); } /** @@ -467,6 +478,6 @@ public static boolean canReachObject(GameObject gameObject) { * @return true if there's line of sight */ public static boolean hasLineOfSight(GameObject gameObject) { - return Rs2GameObject.hasLineOfSight(gameObject); + return true; } } \ No newline at end of file From dc1e372b6bac29b9941940e51324f30c81a86fd1 Mon Sep 17 00:00:00 2001 From: chsami Date: Sat, 11 Apr 2026 11:05:02 +0200 Subject: [PATCH 28/95] fix(Documentation): rename agents.md to CLAUDE.md and add debugging notes for plugin issues --- .../pestcontrol/PestControlScript.java | 26 ++++++++++++++++--- .../microbot/qualityoflife/QoLPlugin.java | 2 +- .../scripts/wintertodt/WintertodtScript.java | 3 ++- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java index 501872e5d4..d508a9db34 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java @@ -5,6 +5,7 @@ import net.runelite.api.NpcID; import net.runelite.api.ObjectID; import net.runelite.api.Skill; +import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; import net.runelite.api.widgets.ComponentID; import net.runelite.api.widgets.Widget; @@ -15,6 +16,7 @@ import net.runelite.client.plugins.microbot.inventorysetups.InventorySetupsItem; import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; 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.combat.Rs2Combat; import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; @@ -149,9 +151,10 @@ public boolean run(PestControlConfig config) { Rs2Combat.setSpecState(true, config.specialAttackPercentage() * 10); Widget activity = Rs2Widget.getWidget(26738700); //145 = 100% if (activity != null && activity.getChild(0).getWidth() <= 20 && !Rs2Combat.inCombat()) { - Rs2NpcModel attackableNpc = Microbot.getRs2NpcCache().query() - .where(n -> n.getNpc() != null && !n.getNpc().isDead() && n.getNpc().getCombatLevel() > 0) - .nearest(); + Rs2NpcModel attackableNpc = Microbot.getClientThread().invoke(() -> + Microbot.getRs2NpcCache().query() + .where(n -> n.getNpc() != null && !n.getNpc().isDead() && n.getNpc().getCombatLevel() > 0) + .nearest()); if (attackableNpc != null) attackableNpc.click("Attack"); return; } @@ -198,7 +201,7 @@ public boolean run(PestControlConfig config) { if (!Microbot.getClient().getLocalPlayer().isInteracting()) { Rs2NpcModel attackableNpc = Microbot.getRs2NpcCache().query() .where(n -> n.getNpc() != null && !n.getNpc().isDead() && n.getNpc().getCombatLevel() > 0) - .nearest(); + .nearestOnClientThread(); if (attackableNpc != null) attackableNpc.click("Attack"); } } @@ -363,6 +366,21 @@ private static boolean attackPortal() { if (npc == null) return false; if (Arrays.stream(npc.getActions()).anyMatch(x -> x != null && x.equalsIgnoreCase("attack"))) { + LocalPoint localPoint = npcPortal.getLocalLocation(); + if (localPoint != null && !Rs2Camera.isTileOnScreen(localPoint)) { + WorldPoint npcWp = Microbot.getClientThread().runOnClientThreadOptional(() -> + npcPortal.getNpc().getWorldLocation()).orElse(null); + WorldPoint playerWp = Rs2Player.getWorldLocation(); + if (npcWp != null && playerWp != null) { + int angle = (int) Math.toDegrees(Math.atan2( + npcWp.getY() - playerWp.getY(), + npcWp.getX() - playerWp.getX())); + if (angle < 0) angle += 360; + angle = (angle - 90) % 360; + if (angle < 0) angle += 360; + Rs2Camera.setAngle(angle, 40); + } + } return npcPortal.click("Attack"); } else { return false; diff --git a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java index cbe2a87a28..7dee7084be 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java @@ -84,7 +84,7 @@ ) @Slf4j public class QoLPlugin extends Plugin implements KeyListener { - public static final String version = "1.8.10"; + public static final String version = "1.8.11"; public static final List bankMenuEntries = new LinkedList<>(); public static final List furnaceMenuEntries = new LinkedList<>(); public static final List anvilMenuEntries = new LinkedList<>(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtScript.java b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtScript.java index 2485e06770..484c559e17 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtScript.java @@ -40,7 +40,8 @@ public class WintertodtScript extends Script { private QoLPlugin qolPlugin; public static boolean isInWintertodtRegion() { - return Rs2Player.getWorldLocation().getRegionID() == 6462; + var location = Rs2Player.getWorldLocation(); + return location != null && location.getRegionID() == 6462; } public boolean run(QoLConfig config) { From fe69d5616a9bb979f941c9198f760d0081654d1d Mon Sep 17 00:00:00 2001 From: chsami Date: Sat, 11 Apr 2026 11:18:30 +0200 Subject: [PATCH 29/95] fix(ShootingStar): improve line of sight checks and update player location retrieval --- .../shootingstar/ShootingStarPlugin.java | 21 +++++++++++++++++-- .../enums/ShootingStarLocation.java | 8 ++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarPlugin.java index 8efd7ba1a7..98946ad6ee 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarPlugin.java @@ -73,7 +73,7 @@ public class ShootingStarPlugin extends Plugin { - static final String version = "1.4.4"; + static final String version = "1.4.6"; @Getter private final List starList = new ArrayList<>(); @@ -398,6 +398,8 @@ public Star getClosestHighestTierStar() { // Get the highest tier available int highestTier = starList.stream() + .filter(Objects::nonNull) + .filter(s -> s.getShootingStarLocation() != null) .filter(s -> !s.isHidden() && s.hasRequirements()) .mapToInt(Star::getTier) .max() @@ -413,6 +415,8 @@ public Star getClosestHighestTierStar() int maxTier = Math.min(9, highestTier + 1); // The highest tier to consider (up to 9) List accessibleStars = starList.stream() + .filter(Objects::nonNull) + .filter(s -> s.getShootingStarLocation() != null) .filter(s -> !s.isHidden() && s.hasRequirements()) .filter(s -> s.getTier() >= minTier && s.getTier() <= maxTier) .sorted(Comparator.comparingInt(Star::getTier).reversed()) @@ -434,7 +438,20 @@ public Star getClosestHighestTierStar() ShortestPathPlugin.getPathfinderConfig().refresh(); } - Pathfinder pathfinder = new Pathfinder(ShortestPathPlugin.getPathfinderConfig(), Microbot.getClient().getLocalPlayer().getWorldLocation(), accessibleStarPoints); + WorldPoint playerLocation = Microbot.getClientThread().runOnClientThreadOptional(() -> { + if (Microbot.getClient().getLocalPlayer() == null) + { + return null; + } + return Microbot.getClient().getLocalPlayer().getWorldLocation(); + }).orElse(null); + + if (playerLocation == null) + { + return null; + } + + Pathfinder pathfinder = new Pathfinder(ShortestPathPlugin.getPathfinderConfig(), playerLocation, accessibleStarPoints); pathfinder.run(); List path = pathfinder.getPath(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/enums/ShootingStarLocation.java b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/enums/ShootingStarLocation.java index 4975273797..547bd21569 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/enums/ShootingStarLocation.java +++ b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/enums/ShootingStarLocation.java @@ -121,7 +121,13 @@ public boolean hasRequirements() return false; } - boolean hasLineOfSight = Microbot.getClient().getLocalPlayer().getWorldArea().hasLineOfSightTo(Microbot.getClient().getTopLevelWorldView(), this.getWorldPoint()); + boolean hasLineOfSight = Boolean.TRUE.equals(Microbot.getClientThread().runOnClientThreadOptional(() -> { + if (Microbot.getClient().getLocalPlayer() == null || Microbot.getClient().getLocalPlayer().getWorldArea() == null) + { + return false; + } + return Microbot.getClient().getLocalPlayer().getWorldArea().hasLineOfSightTo(Microbot.getClient().getTopLevelWorldView(), this.getWorldPoint()); + }).orElse(false)); switch (this) { case CRAFTING_GUILD: From 133d41dd0589e6e5799dfb3d0a099db0fd6bcd94 Mon Sep 17 00:00:00 2001 From: chsami Date: Sat, 11 Apr 2026 11:28:18 +0200 Subject: [PATCH 30/95] fix(ShootingStar): update star model queries to use ObjectID constants --- .../shootingstar/ShootingStarPlugin.java | 2 +- .../shootingstar/ShootingStarScript.java | 25 +++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarPlugin.java index 98946ad6ee..3853812c87 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarPlugin.java @@ -73,7 +73,7 @@ public class ShootingStarPlugin extends Plugin { - static final String version = "1.4.6"; + static final String version = "1.4.7"; @Getter private final List starList = new ArrayList<>(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarScript.java b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarScript.java index 29b0051451..9694832728 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/shootingstar/ShootingStarScript.java @@ -9,6 +9,7 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.GameState; import net.runelite.api.coords.WorldPoint; +import net.runelite.api.gameval.ObjectID; import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; @@ -437,7 +438,17 @@ private ShootingStarState updateStarState() if (state == ShootingStarState.MINING) { var starModel = Microbot.getRs2TileObjectCache().query() - .where(n -> n.getName() != null && n.getName().toLowerCase().contains("crashed star")) + .withIds( + ObjectID.STAR_SIZE_ONE_STAR, + ObjectID.STAR_SIZE_TWO_STAR, + ObjectID.STAR_SIZE_THREE_STAR, + ObjectID.STAR_SIZE_FOUR_STAR, + ObjectID.STAR_SIZE_FIVE_STAR, + ObjectID.STAR_SIZE_SIX_STAR, + ObjectID.STAR_SIZE_SEVEN_STAR, + ObjectID.STAR_SIZE_EIGHT_STAR, + ObjectID.STAR_SIZE_NINE_STAR + ) .nearest(initialPlayerLocation, 10); if (currentStar == null || starModel == null) @@ -492,7 +503,17 @@ private boolean hasStateChanged() if (state == ShootingStarState.MINING) { var starModel = Microbot.getRs2TileObjectCache().query() - .where(n -> n.getName() != null && n.getName().toLowerCase().contains("crashed star")) + .withIds( + ObjectID.STAR_SIZE_ONE_STAR, + ObjectID.STAR_SIZE_TWO_STAR, + ObjectID.STAR_SIZE_THREE_STAR, + ObjectID.STAR_SIZE_FOUR_STAR, + ObjectID.STAR_SIZE_FIVE_STAR, + ObjectID.STAR_SIZE_SIX_STAR, + ObjectID.STAR_SIZE_SEVEN_STAR, + ObjectID.STAR_SIZE_EIGHT_STAR, + ObjectID.STAR_SIZE_NINE_STAR + ) .nearest(initialPlayerLocation, 10); return hasStarModelChanged(starModel); } From 5c2921af555f662d81ca14258652391c3751e233 Mon Sep 17 00:00:00 2001 From: mikefallen <265864805+mikefallen@users.noreply.github.com> Date: Sat, 11 Apr 2026 08:21:44 -0400 Subject: [PATCH 31/95] fix(nmz): migrate to new query API and fix potion shop logic (#379) --- .../plugins/microbot/nmz/NmzOverlay.java | 13 +- .../plugins/microbot/nmz/NmzPlugin.java | 2 +- .../plugins/microbot/nmz/NmzScript.java | 223 ++++++++++++------ 3 files changed, 159 insertions(+), 79 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzOverlay.java index b28a3c0145..2f25e32ca8 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzOverlay.java @@ -1,5 +1,6 @@ package net.runelite.client.plugins.microbot.nmz; +import net.runelite.api.gameval.VarbitID; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.ui.overlay.OverlayPanel; import net.runelite.client.ui.overlay.OverlayPosition; @@ -40,7 +41,17 @@ public Dimension render(Graphics2D graphics) { .left("Will drink absorption at: " + NmzScript.minAbsorption) .build()); - } catch(Exception ex) { + panelComponent.getChildren().add(LineComponent.builder() + .left("Overload (barrel):") + .right(String.valueOf(Microbot.getVarbitValue(VarbitID.NZONE_POTION_3))) + .build()); + + panelComponent.getChildren().add(LineComponent.builder() + .left("Absorb (barrel):") + .right(String.valueOf(Microbot.getVarbitValue(VarbitID.NZONE_POTION_4))) + .build()); + +} catch(Exception ex) { System.out.println(ex.getMessage()); } return super.render(graphics); diff --git a/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzPlugin.java index d54ff0e078..61aaa7b469 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzPlugin.java @@ -31,7 +31,7 @@ ) @Slf4j public class NmzPlugin extends Plugin { - final static String version = "2.3.1"; + final static String version = "2.4.0"; @Inject private NmzConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzScript.java b/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzScript.java index 734bc03a31..3fac272374 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzScript.java @@ -2,15 +2,27 @@ import lombok.Getter; import lombok.Setter; -import net.runelite.api.*; +import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; +import net.runelite.api.gameval.ItemID; +import net.runelite.api.gameval.ObjectID; +import net.runelite.api.gameval.VarbitID; +import net.runelite.api.gameval.VarPlayerID; import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.api.npc.Rs2NpcCache; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.Rs2TileObjectCache; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; +import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; +import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; +import net.runelite.client.plugins.microbot.util.antiban.enums.Activity; +import net.runelite.client.plugins.microbot.util.antiban.enums.ActivityIntensity; +import net.runelite.client.plugins.microbot.util.antiban.enums.PlayStyle; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; -import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; 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; @@ -18,7 +30,6 @@ 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.security.Encryption; -import net.runelite.client.plugins.microbot.util.security.Login; import net.runelite.client.plugins.microbot.util.security.LoginManager; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -26,9 +37,6 @@ import javax.inject.Inject; import java.util.concurrent.TimeUnit; -import static net.runelite.api.ObjectID.OVERLOAD_POTION; -import static net.runelite.api.Varbits.NMZ_ABSORPTION; - public class NmzScript extends Script { private NmzConfig config; @@ -49,6 +57,11 @@ public class NmzScript extends Script { private boolean initialized = false; private long lastCombatTime = 0; + @Inject + private Rs2TileObjectCache tileObjectCache; + @Inject + private Rs2NpcCache npcCache; + public boolean canStartNmz() { return Rs2Inventory.count("overload (4)") == config.overloadPotionAmount() || (Rs2Inventory.hasItem("prayer potion") && config.togglePrayerPotions()); @@ -64,32 +77,49 @@ public NmzScript(NmzPlugin plugin, NmzConfig config) { public boolean run() { prayerPotionScript = new PrayerPotionScript(); Microbot.getSpecialAttackConfigs().setSpecialAttack(true); + Rs2Antiban.resetAntibanSettings(); + Rs2Antiban.setActivity(Activity.GENERAL_COMBAT); + Rs2Antiban.setActivityIntensity(ActivityIntensity.LOW); + Rs2Antiban.setPlayStyle(PlayStyle.MODERATE); + Rs2Antiban.activateAntiban(); + Rs2AntibanSettings.moveMouseOffScreen = true; + Rs2AntibanSettings.simulateMistakes = true; + Rs2AntibanSettings.naturalMouse = true; + Rs2AntibanSettings.usePlayStyle = true; + Rs2AntibanSettings.behavioralVariability = true; + Rs2AntibanSettings.nonLinearIntervals = true; + Rs2AntibanSettings.actionCooldownChance = 0.00; + Rs2AntibanSettings.moveMouseOffScreenChance = 1.00; + + mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { try { if (!Microbot.isLoggedIn()) return; if (!initialized) { initialized = true; - if (config.inventorySetupon()) { - if (config.inventorySetup() != null) { - var inventorySetup = new Rs2InventorySetup(config.inventorySetup(), mainScheduledFuture); - if (!inventorySetup.doesInventoryMatch() || !inventorySetup.doesEquipmentMatch()) { - Rs2Walker.walkTo(Rs2Bank.getNearestBank().getWorldPoint(), 20); - if (!inventorySetup.loadEquipment() || !inventorySetup.loadInventory()) { - Microbot.log("Failed to load inventory setup"); - Microbot.stopPlugin(plugin); - return; + // Skip inventory setup and lobby walk if already inside the NMZ instance + boolean isInNmzInstance = Microbot.getClient().getLocalPlayer().getWorldLocation().getY() > 4500; + if (!isInNmzInstance) { + if (config.inventorySetupon()) { + if (config.inventorySetup() != null) { + var inventorySetup = new Rs2InventorySetup(config.inventorySetup(), mainScheduledFuture); + if (!inventorySetup.doesInventoryMatch() || !inventorySetup.doesEquipmentMatch()) { + Rs2Walker.walkTo(Rs2Bank.getNearestBank().getWorldPoint(), 20); + if (!inventorySetup.loadEquipment() || !inventorySetup.loadInventory()) { + Microbot.log("Failed to load inventory setup"); + Microbot.stopPlugin(plugin); + return; + } + Rs2Bank.closeBank(); } - Rs2Bank.closeBank(); } } + Rs2Walker.walkTo(new WorldPoint(2609, 3114, 0), 5); } - Rs2Walker.walkTo(new WorldPoint(2609, 3114, 0), 5); } if (!super.run()) return; - Rs2Combat.enableAutoRetialiate(); - if (Rs2Random.between(1, 50) == 1 && config.randomMouseMovements()) { - Microbot.getMouse().click(Rs2Random.between(0, Microbot.getClient().getCanvasWidth()), Rs2Random.between(0, Microbot.getClient().getCanvasHeight()), true); - } + if (Rs2AntibanSettings.actionCooldownActive) return; + Rs2Combat.setAutoRetaliate(true); boolean isOutsideNmz = isOutside(); useOverload = Microbot.getClient().getBoostedSkillLevel(Skill.RANGED) == Microbot.getClient().getRealSkillLevel(Skill.RANGED) && config.overloadPotionAmount() > 0; if (isOutsideNmz) { @@ -108,6 +138,8 @@ public boolean run() { @Override public void shutdown() { super.shutdown(); + Rs2Antiban.deactivateAntiban(); + Rs2Antiban.resetAntibanSettings(); initialized = false; } @@ -117,7 +149,7 @@ public boolean isOutside() { } public void handleOutsideNmz() { - boolean hasStartedDream = Microbot.getVarbitValue(3946) > 0; + boolean hasStartedDream = Microbot.getVarbitValue(VarbitID.NZONE_PURCHASEDDREAM) > 0; if (config.togglePrayerPotions()) Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MELEE, false); if (!hasStartedDream) { @@ -125,12 +157,12 @@ public void handleOutsideNmz() { } else { final String overload = "Overload (4)"; final String absorption = "Absorption (4)"; - storePotions(OVERLOAD_POTION, "overload", config.overloadPotionAmount()); - storePotions(ObjectID.ABSORPTION_POTION, "absorption", config.absorptionPotionAmount()); + storePotions(ObjectID.NZONE_BARREL_3, "overload", config.overloadPotionAmount()); + storePotions(ObjectID.NZONE_BARREL_4, "absorption", config.absorptionPotionAmount()); handleStore(); - fetchOverloadPotions(OVERLOAD_POTION, overload, config.overloadPotionAmount()); + fetchOverloadPotions(ObjectID.NZONE_BARREL_3, overload, config.overloadPotionAmount()); if (Rs2Inventory.hasItemAmount(overload, config.overloadPotionAmount())) { - fetchPotions(ObjectID.ABSORPTION_POTION, absorption, config.absorptionPotionAmount()); + fetchPotions(ObjectID.NZONE_BARREL_4, absorption, config.absorptionPotionAmount()); } } if (canStartNmz()) { @@ -144,11 +176,13 @@ public void handleInsideNmz() { if (Rs2Player.isInCombat()) { lastCombatTime = System.currentTimeMillis(); } + Rs2Antiban.takeMicroBreakByChance(); if (!Rs2Player.isInCombat() && System.currentTimeMillis() - lastCombatTime > 20000) { - Rs2NpcModel closestNpc = Microbot.getRs2NpcCache().query().nearest(); - + Rs2NpcModel closestNpc = npcCache.query().nearest(); if (closestNpc != null) { - closestNpc.click("Attack"); + if (closestNpc.click("Attack")) { + Rs2Antiban.actionCooldown(); + } } } prayerPotionScript.run(); @@ -171,7 +205,8 @@ private void walkToCenter() { public void startNmzDream() { // Set new center so that it is random for every time joining the dream center = new WorldPoint(Rs2Random.between(2270, 2276), Rs2Random.between(4693, 4696), 0); - Microbot.getRs2NpcCache().query().withId(NpcID.DOMINIC_ONION).interact("Dream"); + Rs2NpcModel dominic = npcCache.query().withName("Dominic Onion").nearest(); + if (dominic != null) dominic.click("Dream"); sleepUntil(() -> Rs2Widget.hasWidget("Which dream would you like to experience?")); Rs2Widget.clickWidget("Previous:"); sleepUntil(() -> Rs2Widget.hasWidget("Click here to continue")); @@ -186,28 +221,34 @@ public void startNmzDream() { public boolean useOrbs() { boolean orbHasSpawned = false; if (config.useZapper()) { - orbHasSpawned = interactWithObject(ObjectID.ZAPPER_26256); + orbHasSpawned = interactWithObject(ObjectID.NZONE_POWERUP_ZAPPER); } if (config.useReccurentDamage()) { - orbHasSpawned = interactWithObject(ObjectID.RECURRENT_DAMAGE); + orbHasSpawned = interactWithObject(ObjectID.NZONE_POWERUP_DAMAGEMULTIPLIER); } if (config.usePowerSurge()) { - orbHasSpawned = interactWithObject(ObjectID.POWER_SURGE); + orbHasSpawned = interactWithObject(ObjectID.NZONE_POWERUP_SPECIALATTACK); } return orbHasSpawned; } public boolean interactWithObject(int objectId) { - var rs2GameObject = Microbot.getRs2TileObjectCache().query().withId(objectId).nearest(); - if (rs2GameObject != null) { - Rs2Walker.walkFastCanvas(rs2GameObject.getWorldLocation()); - sleepUntil(() -> { - WorldPoint loc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); - return loc != null && loc.distanceTo(rs2GameObject.getWorldLocation()) < 5; - }); - rs2GameObject.click(); + Rs2TileObjectModel obj = tileObjectCache.query().withId(objectId).nearest(); + if (obj != null) { + sleep(1000, 15000); + WorldPoint playerLoc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); + if (playerLoc != null && playerLoc.distanceTo(obj.getWorldLocation()) >= 15) { + Rs2Walker.walkFastLocal(obj.getLocalLocation()); + sleepUntil(() -> { + WorldPoint loc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); + return loc != null && loc.distanceTo(obj.getWorldLocation()) < 15; + }, 10000); + } + obj.click(); + // Wait for the power-up to despawn to prevent repeated clicks on the same orb + sleepUntil(() -> tileObjectCache.query().withId(objectId).nearest() == null, 3000); return true; } return false; @@ -220,13 +261,17 @@ private void fetchOverloadPotions(int objectId, String itemName, int requiredAmo int neededAmount = requiredAmount - currentAmount; - Microbot.getRs2TileObjectCache().query().withId(objectId).interact("Take"); + Rs2TileObjectModel obj = tileObjectCache.query().withId(objectId).nearest(); + if (obj == null) return; + obj.click("Take"); String widgetText = "How many doses of "; sleepUntil(() -> Rs2Widget.hasWidget(widgetText)); if (Rs2Widget.hasWidget(widgetText)) { // Each potion has 4 doses, so request the correct number of doses + sleep(Rs2Random.between(400, 900)); Rs2Keyboard.typeString(Integer.toString(neededAmount * 4)); + sleep(Rs2Random.between(200, 500)); Rs2Keyboard.enter(); sleepUntil(() -> Rs2Inventory.count(itemName) == requiredAmount); } @@ -244,10 +289,12 @@ public void manageSelfHarm() { && (!hasOverloadPotions || currentRangedLevel != realRangedLevel)) { maxHealth = 1; - if (Rs2Inventory.hasItem(ItemID.LOCATOR_ORB)) { - Rs2Inventory.interact(ItemID.LOCATOR_ORB, "feel"); - } else if (Rs2Inventory.hasItem(ItemID.DWARVEN_ROCK_CAKE_7510)) { - Rs2Inventory.interact(ItemID.DWARVEN_ROCK_CAKE_7510, "guzzle"); + if (Rs2Inventory.hasItem(ItemID.DS2_ORB)) { + Rs2Inventory.interact(ItemID.DS2_ORB, "feel"); + Rs2Antiban.actionCooldown(); + } else if (Rs2Inventory.hasItem(ItemID.HUNDRED_DWARF_COOL_ROCKCAKE)) { + Rs2Inventory.interact(ItemID.HUNDRED_DWARF_COOL_ROCKCAKE, "guzzle"); + Rs2Antiban.actionCooldown(); } if (currentHP == 1) { @@ -276,7 +323,7 @@ public void useOverloadPotion() { } public void useAbsorptionPotion() { - if (Microbot.getVarbitValue(NMZ_ABSORPTION) < minAbsorption && Rs2Inventory.hasItem("absorption")) { + if (Microbot.getVarbitValue(VarbitID.NZONE_ABSORB_POTION_EFFECTS) < minAbsorption && Rs2Inventory.hasItem("absorption")) { for (int i = 0; i < Rs2Random.between(4, 8); i++) { Rs2Inventory.interact(x -> x.getName().toLowerCase().contains("absorption"), "drink"); sleep(600, 1000); @@ -289,11 +336,15 @@ private void storePotions(int objectId, String itemName, int requiredAmount) { if (Rs2Inventory.count(itemName) == requiredAmount) return; if (Rs2Inventory.get(itemName) == null) return; - Microbot.getRs2TileObjectCache().query().withId(objectId).interact("Store"); + Rs2TileObjectModel obj = tileObjectCache.query().withId(objectId).nearest(); + if (obj == null) return; + obj.click("Store"); String storeWidgetText = "Store all your "; sleepUntil(() -> Rs2Widget.hasWidget(storeWidgetText)); if (Rs2Widget.hasWidget(storeWidgetText)) { + sleep(Rs2Random.between(400, 900)); Rs2Keyboard.typeString("1"); + sleep(Rs2Random.between(200, 500)); Rs2Keyboard.enter(); sleepUntil(() -> !Rs2Inventory.hasItem(objectId)); Rs2Inventory.dropAll(itemName); @@ -303,53 +354,65 @@ private void storePotions(int objectId, String itemName, int requiredAmount) { private void fetchPotions(int objectId, String itemName, int requiredAmount) { if (Rs2Inventory.count(itemName) == requiredAmount) return; - Microbot.getRs2TileObjectCache().query().withId(objectId).interact("Take"); + Rs2TileObjectModel obj = tileObjectCache.query().withId(objectId).nearest(); + if (obj == null) return; + obj.click("Take"); String widgetText = "How many doses of "; sleepUntil(() -> Rs2Widget.hasWidget(widgetText)); if (Rs2Widget.hasWidget(widgetText)) { + sleep(Rs2Random.between(400, 900)); Rs2Keyboard.typeString(Integer.toString(requiredAmount * 4)); + sleep(Rs2Random.between(200, 500)); Rs2Keyboard.enter(); sleepUntil(() -> Rs2Inventory.count(itemName) == requiredAmount); } } public void consumeEmptyVial() { - final int EMPTY_VIAL = 26291; if (Microbot.getClientThread().runOnClientThreadOptional(() -> Rs2Widget.getWidget(129, 6) == null || Rs2Widget.getWidget(129, 6).isHidden()) .orElse(false)) { - Microbot.getRs2TileObjectCache().query().withId(EMPTY_VIAL).interact("drink"); + Rs2TileObjectModel vial = tileObjectCache.query().withId(ObjectID.NZONE_LOBBY_VIAL).nearest(); + if (vial != null) vial.click("drink"); } - sleep(2000,4000); + sleep(2000, 4000); Widget widget = Rs2Widget.getWidget(129, 6); if (!Microbot.getClientThread().runOnClientThreadOptional(widget::isHidden).orElse(false)) { Rs2Widget.clickWidget(widget.getId()); sleep(300); Rs2Widget.clickWidget(widget.getId()); } - sleep(2000,4000); + sleep(2000, 4000); } public void handleStore() { if (canStartNmz()) return; - int varbitOverload = 3953; - int varbitAbsorption = 3954; - int overloadAmt = Microbot.getVarbitValue(varbitOverload); - int absorptionAmt = Microbot.getVarbitValue(varbitAbsorption); - int nmzPoints = Microbot.getVarbitPlayerValue(VarPlayer.NMZ_REWARD_POINTS); + int overloadAmt = Microbot.getVarbitValue(VarbitID.NZONE_POTION_3); + int absorptionAmt = Microbot.getVarbitValue(VarbitID.NZONE_POTION_4); - if (absorptionAmt > config.absorptionPotionAmount() * 4 && overloadAmt > config.overloadPotionAmount() * 4) - return; + // Varbits are in doses; config is in 4-dose potions + int overloadDosesNeeded = Math.max(0, config.overloadPotionAmount() * 4 - overloadAmt); + int absorptionDosesNeeded = Math.max(0, config.absorptionPotionAmount() * 4 - absorptionAmt); - if (!Rs2Inventory.isFull()) { - if ((absorptionAmt < (config.absorptionPotionAmount() * 4) || overloadAmt < config.overloadPotionAmount() * 4) && nmzPoints < 100000) { - Microbot.showMessage("BOT SHUTDOWN: Not enough points to buy potions"); - Microbot.stopPlugin(plugin); - return; - } + if (overloadDosesNeeded == 0 && absorptionDosesNeeded == 0) return; + + // Each shop purchase gives one 4-dose potion (ceiling division) + int overloadToBuy = (overloadDosesNeeded + 3) / 4; + int absorptionToBuy = (absorptionDosesNeeded + 3) / 4; + + // NMZ reward shop costs: Overload 1,500 pts / Absorption 1,000 pts per 4-dose potion + int totalCost = overloadToBuy * 1500 + absorptionToBuy * 1000; + int nmzPoints = Microbot.getVarbitPlayerValue(VarPlayerID.NZONE_REWARDPOINTS); + + if (nmzPoints < totalCost) { + Microbot.showMessage("BOT SHUTDOWN: Not enough points to buy potions (have " + nmzPoints + ", need " + totalCost + ")"); + Microbot.stopPlugin(plugin); + return; } - Microbot.getRs2TileObjectCache().query().withId(26273).interact(); + Rs2TileObjectModel chest = tileObjectCache.query().withId(ObjectID.NZONE_LOBBY_CHEST).nearest(); + if (chest == null) return; + chest.click(); sleepUntil(() -> Rs2Widget.isWidgetVisible(13500418) || Rs2Bank.isBankPinWidgetVisible(), 10000); if (Rs2Bank.isBankPinWidgetVisible()) { try { @@ -362,20 +425,26 @@ public void handleStore() { Widget benefitsBtn = Rs2Widget.getWidget(13500418); if (benefitsBtn == null) return; - boolean notSelected = benefitsBtn.getSpriteId() != 813; - if (notSelected) { + if (benefitsBtn.getSpriteId() != 813) { Rs2Widget.clickWidgetFast(benefitsBtn, 4, 4); + sleepUntil(() -> { + Widget btn = Rs2Widget.getWidget(13500418); + return btn != null && btn.getSpriteId() == 813; + }, 3000); + } + + for (int i = 0; i < overloadToBuy; i++) { + Widget nmzRewardShop = Rs2Widget.getWidget(206, 6); + if (nmzRewardShop == null) break; + Rs2Widget.clickWidgetFast(nmzRewardShop.getChild(6), 6, 4); + sleep(600, 1000); } - int count = 0; - while (count < Rs2Random.between(3, 5)) { + + for (int i = 0; i < absorptionToBuy; i++) { Widget nmzRewardShop = Rs2Widget.getWidget(206, 6); if (nmzRewardShop == null) break; - Widget overload = nmzRewardShop.getChild(6); - Rs2Widget.clickWidgetFast(overload, 6, 4); - Widget absorption = nmzRewardShop.getChild(9); - Rs2Widget.clickWidgetFast(absorption, 9, 4); - sleep(600, 1200); - count++; + Rs2Widget.clickWidgetFast(nmzRewardShop.getChild(9), 9, 4); + sleep(600, 1000); } } From ac0585600feb5e5914919146664328347ac1ac2a Mon Sep 17 00:00:00 2001 From: chsami Date: Sun, 12 Apr 2026 00:19:19 +0200 Subject: [PATCH 32/95] refactor(Banking): enhance navigation and interaction logging, migrate to new query API --- .../microbot/valetotems/ValeTotemPlugin.java | 2 +- .../microbot/valetotems/ValeTotemScript.java | 16 +- .../valetotems/enums/GameObjectId.java | 14 +- .../valetotems/handlers/BankingHandler.java | 51 +++--- .../valetotems/handlers/FletchingHandler.java | 69 ++++---- .../handlers/NavigationHandler.java | 32 ++-- .../valetotems/handlers/TotemHandler.java | 5 +- .../valetotems/utils/GameObjectUtils.java | 159 +++++++++++------- 8 files changed, 201 insertions(+), 147 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java index 33a29a1e93..dcbe418634 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java @@ -27,7 +27,7 @@ ) @Slf4j public class ValeTotemPlugin extends Plugin { - static final String version = "1.0.7"; + static final String version = "1.0.8"; @Inject private ValeTotemConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemScript.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemScript.java index 219f3b0b46..ecadcd9307 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemScript.java @@ -22,6 +22,7 @@ public class ValeTotemScript extends Script { private ValeTotemConfig config; private GameSession gameSession; private boolean isRunning = false; + private long lastStateLogTime = 0; public boolean run(ValeTotemConfig config) { this.config = config; @@ -71,7 +72,12 @@ public boolean run(ValeTotemConfig config) { */ private void executeMainLoop() { GameState currentState = gameSession.getCurrentState(); - + long now = System.currentTimeMillis(); + if (now - lastStateLogTime > 5000) { + lastStateLogTime = now; + Microbot.log("[ValeTotem] State: " + currentState.name() + " | Player: " + net.runelite.client.plugins.microbot.valetotems.utils.CoordinateUtils.getPlayerLocation()); + } + switch (currentState) { case IDLE: handleIdleState(); @@ -127,8 +133,9 @@ private void handleIdleState() { * Handle banking operations */ private void handleBankingState() { - // Use unified banking method that detects route type and updates game session + Microbot.log("[ValeTotem] handleBankingState: starting unified banking cycle"); boolean bankingSuccess = BankingHandler.performUnifiedBankingCycle(gameSession); + Microbot.log("[ValeTotem] handleBankingState: result=" + bankingSuccess); if (bankingSuccess) { gameSession.startNewRound(); @@ -304,9 +311,8 @@ private void handleReturningToBankState() { * Handle error states */ private void handleErrorState() { - System.err.println("Bot in error state. Attempting recovery..."); - - // Try emergency procedures + Microbot.log("[ValeTotem] ERROR state. Errors so far: " + gameSession.getErrorMessages().size() + ". Attempting recovery..."); + FletchingHandler.emergencyStopFletching(); NavigationHandler.emergencyReturnToBank(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/enums/GameObjectId.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/enums/GameObjectId.java index dce7fff271..4bf71d13a3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/enums/GameObjectId.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/enums/GameObjectId.java @@ -15,8 +15,8 @@ public enum GameObjectId { TOTEM_READY_FOR_DECORATION(57081, "totem", "Totem ready for decoration - Action: Decorate"), // Ent trails (optional for extra points) - ENT_TRAIL_1(57116, "ent", "Ent trail for bonus points"), - ENT_TRAIL_2(57115, "ent", "Ent trail for bonus points"), + ENT_TRAIL_1(57116, "ent trail", "Ent trail for bonus points"), + ENT_TRAIL_2(57115, "ent trail", "Ent trail for bonus points"), // Offerings (rewards) - different states based on reward count OFFERINGS_MANY(57098, "offering", "Offerings pile with many rewards - Action: Claim"), @@ -65,11 +65,17 @@ public static GameObjectId getById(int id) { * @return true if it's any type of offerings pile */ public static boolean isOfferingsPile(int id) { - return id == OFFERINGS_MANY.getId() || - id == OFFERINGS_SOME.getId() || + return id == OFFERINGS_MANY.getId() || + id == OFFERINGS_SOME.getId() || id == OFFERINGS_EMPTY.getId(); } + public static boolean isTotemObject(int id) { + return id == TOTEM_SITE.getId() || + id == EMPTY_TOTEM.getId() || + id == TOTEM_READY_FOR_DECORATION.getId(); + } + /** * Check if the offerings pile has claimable rewards * @param id the object ID to check diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/BankingHandler.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/BankingHandler.java index afa1cd6c05..973710a3bb 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/BankingHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/BankingHandler.java @@ -57,26 +57,28 @@ public static void setConfig(ValeTotemConfig config) { */ public static boolean navigateToBank() { try { - // Check if already near bank - if (CoordinateUtils.isNearBank(BANK_INTERACTION_DISTANCE)) { - // Move to optimal banking position if not there + boolean alreadyNear = CoordinateUtils.isNearBank(BANK_INTERACTION_DISTANCE); + Microbot.log("[Banking] navigateToBank: alreadyNear=" + alreadyNear + " pos=" + CoordinateUtils.getPlayerLocation()); + + if (alreadyNear) { if (!CoordinateUtils.isAtBankingPosition()) { + Microbot.log("[Banking] Walking to optimal banking tile"); return Rs2Walker.walkTo(BankLocation.PLAYER_STANDING_TILE.getLocation()); } return true; } - // Walk to bank area + Microbot.log("[Banking] Walking to bank booth at " + BankLocation.BANK_BOOTH.getLocation()); boolean walked = Rs2Walker.walkTo(BankLocation.BANK_BOOTH.getLocation()); + Microbot.log("[Banking] walkTo bank result=" + walked); if (walked) { - // Wait for arrival and position correctly Rs2Player.waitForWalking(); return Rs2Walker.walkTo(BankLocation.PLAYER_STANDING_TILE.getLocation()); } return false; } catch (Exception e) { - Microbot.log("Error navigating to bank: " + e.getMessage()); + Microbot.log("[Banking] Error navigating to bank: " + e.getMessage()); return false; } } @@ -87,37 +89,39 @@ public static boolean navigateToBank() { */ public static boolean openBank() { try { - // Check if bank is already open if (Rs2Bank.isOpen()) { + Microbot.log("[Banking] Bank already open"); return true; } - // Ensure we're close enough to the bank if (!CoordinateUtils.isNearBank(BANK_INTERACTION_DISTANCE)) { + Microbot.log("[Banking] openBank: not near bank, navigating first"); if (!navigateToBank()) { return false; } } - // Interact with bank booth using string search - boolean interacted = GameObjectUtils.findAndInteractAtLocationByName( - GameObjectId.BANK_BOOTH.getSearchTerm(), + Microbot.log("[Banking] Interacting with bank booth (id=" + GameObjectId.BANK_BOOTH.getId() + " at " + BankLocation.BANK_BOOTH.getLocation() + ")"); + boolean interacted = GameObjectUtils.findAndInteractAtLocation( + GameObjectId.BANK_BOOTH.getId(), BankLocation.BANK_BOOTH.getLocation(), "Bank" ); + Microbot.log("[Banking] Bank booth interact result=" + interacted); if (interacted) { - // Wait for bank to open long startTime = System.currentTimeMillis(); while (!Rs2Bank.isOpen() && System.currentTimeMillis() - startTime < BANK_TIMEOUT_MS) { sleep(100); } - return Rs2Bank.isOpen(); + boolean opened = Rs2Bank.isOpen(); + Microbot.log("[Banking] Bank opened=" + opened); + return opened; } return false; } catch (Exception e) { - Microbot.log("Error opening bank: " + e.getMessage()); + Microbot.log("[Banking] Error opening bank: " + e.getMessage()); return false; } } @@ -251,19 +255,26 @@ public static TotemLocation.RouteType detectRouteTypeWithBankOpen() { */ public static boolean performUnifiedBankingCycle(GameSession gameSession) { try { - // Navigate to bank if needed - if (!CoordinateUtils.isNearBank(BANK_INTERACTION_DISTANCE)) { - if (!navigateToBank()) { - Microbot.log("Failed to navigate to bank"); + var playerPos = CoordinateUtils.getPlayerLocation(); + int distToBank = CoordinateUtils.getDistanceToBank(); + boolean nearBank = CoordinateUtils.isNearBank(BANK_INTERACTION_DISTANCE); + Microbot.log("[Banking] Start cycle. Player=" + playerPos + " distToBank=" + distToBank + " nearBank=" + nearBank); + + if (!nearBank) { + Microbot.log("[Banking] Not near bank, navigating..."); + boolean navResult = navigateToBank(); + Microbot.log("[Banking] Navigate result=" + navResult + " newPos=" + CoordinateUtils.getPlayerLocation()); + if (!navResult) { return false; } } - // Open bank first + Microbot.log("[Banking] Opening bank..."); if (!openBank()) { - Microbot.log("Failed to open bank"); + Microbot.log("[Banking] Failed to open bank"); return false; } + Microbot.log("[Banking] Bank opened successfully"); // Now detect route type with bank open TotemLocation.RouteType detectedRouteType = detectRouteTypeWithBankOpen(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/FletchingHandler.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/FletchingHandler.java index a79340173c..ad3c988511 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/FletchingHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/FletchingHandler.java @@ -66,38 +66,34 @@ public static void setConfig(ValeTotemConfig config) { */ private static int getWidgetHotkey(int childId) { try { - // Calculate the hotkey index based on child ID int hotkeyIndex = getHotkeyIndex(childId); if (hotkeyIndex == -1) { - return -1; // No hotkey mapping + return -1; } - // Check widget text at 270,13[hotkeyIndex] - Widget hotkeyWidget = Rs2Widget.getWidget(FLETCHING_INTERFACE_WIDGET_ID, HOTKEY_TEXT_WIDGET_ID); - if (hotkeyWidget != null && hotkeyWidget.getChildren() != null && - hotkeyIndex < hotkeyWidget.getChildren().length) { - - Widget specificHotkeyWidget = hotkeyWidget.getChild(hotkeyIndex); - if (specificHotkeyWidget != null) { - String hotkeyText = specificHotkeyWidget.getText(); - if (hotkeyText != null) { - hotkeyText = hotkeyText.replaceAll("<[^>]*>", "").trim(); // Remove HTML tags - - // Check if it's "Space" - if (hotkeyText.equalsIgnoreCase("Space")) { - return KeyEvent.VK_SPACE; - } - - // Check if it's a number - if (hotkeyText.matches("\\d")) { - int number = Integer.parseInt(hotkeyText); - return number; - } + final int idx = hotkeyIndex; + String hotkeyText = Microbot.getClientThread().invoke(() -> { + Widget hotkeyWidget = Rs2Widget.getWidget(FLETCHING_INTERFACE_WIDGET_ID, HOTKEY_TEXT_WIDGET_ID); + if (hotkeyWidget != null && hotkeyWidget.getChildren() != null && + idx < hotkeyWidget.getChildren().length) { + Widget specificHotkeyWidget = hotkeyWidget.getChild(idx); + if (specificHotkeyWidget != null) { + return specificHotkeyWidget.getText(); } } + return null; + }); + + if (hotkeyText != null) { + hotkeyText = hotkeyText.replaceAll("<[^>]*>", "").trim(); + if (hotkeyText.equalsIgnoreCase("Space")) { + return KeyEvent.VK_SPACE; + } + if (hotkeyText.matches("\\d")) { + return Integer.parseInt(hotkeyText); + } } - // Fallback to default number key based on position return getDefaultNumberKey(hotkeyIndex); } catch (Exception e) { @@ -255,19 +251,27 @@ public static boolean selectBow(int quantity) { interactWithWidget(QUANTITY_ALL_CHILD_ID, "all bows"); Microbot.log("Selected all bows"); } else { - // For custom quantities, check if the "Other" option is already set correctly - Widget otherQuantityWidget = Rs2Widget.getWidget(FLETCHING_INTERFACE_WIDGET_ID, QUANTITY_OTHER_CHILD_ID); - if (otherQuantityWidget != null) { + String[] widgetInfo = Microbot.getClientThread().invoke(() -> { + Widget otherQuantityWidget = Rs2Widget.getWidget(FLETCHING_INTERFACE_WIDGET_ID, QUANTITY_OTHER_CHILD_ID); + if (otherQuantityWidget == null) return null; Widget textWidget = otherQuantityWidget.getChild(9); - if (textWidget != null) { - String currentQuantity = textWidget.getText().replaceAll("<[^>]*>", ""); - if (Integer.parseInt(currentQuantity) == quantity || otherQuantityWidget.getActions() != null) { - if (otherQuantityWidget.getActions() != null) { + String text = textWidget != null ? textWidget.getText() : null; + boolean hasActions = otherQuantityWidget.getActions() != null; + return new String[]{ text, String.valueOf(hasActions) }; + }); + + if (widgetInfo != null) { + String currentText = widgetInfo[0]; + boolean hasActions = Boolean.parseBoolean(widgetInfo[1]); + + if (currentText != null) { + String currentQuantity = currentText.replaceAll("<[^>]*>", ""); + if (Integer.parseInt(currentQuantity) == quantity || hasActions) { + if (hasActions) { interactWithWidget(QUANTITY_OTHER_CHILD_ID, "other quantity"); } Microbot.log("Selected other quantity"); } else { - // If the quantity is not what we want, click "X", type the new quantity, and press enter interactWithWidget(QUANTITY_X_CHILD_ID, "X"); sleepUntil(() -> Rs2Widget.getChildWidgetText(162, 42).contains("Enter amount"), 2000); sleepGaussian(200,100); @@ -277,7 +281,6 @@ public static boolean selectBow(int quantity) { Microbot.log("Selected other quantity1"); } } else { - // If the quantity is null, click "X", type the new quantity, and press enter interactWithWidget(QUANTITY_X_CHILD_ID, "X"); sleepUntil(() -> Rs2Widget.getChildWidgetText(162, 42).contains("Enter amount"), 2000); sleepGaussian(200,100); diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/NavigationHandler.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/NavigationHandler.java index 698ae819ac..f2e53959c7 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/NavigationHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/NavigationHandler.java @@ -226,11 +226,11 @@ public static boolean navigateToTotem(TotemLocation totemLocation, net.runelite. while (FletchingHandler.isFletchingWhileWalking() && System.currentTimeMillis() - fletchStartTime < 20000) { // 20 sec timeout - // Check for ent trails during fletching - they have absolute priority - List nearbyEntTrails = GameObjectUtils.findGameObjectsByName( - GameObjectId.ENT_TRAIL_1.getSearchTerm(), - CoordinateUtils.getPlayerLocation(), - ENT_TRAIL_SEARCH_RADIUS); + WorldPoint fletchCheckPos = CoordinateUtils.getPlayerLocation(); + List nearbyEntTrails = GameObjectUtils.findGameObjects( + GameObjectId.ENT_TRAIL_1.getId(), fletchCheckPos, ENT_TRAIL_SEARCH_RADIUS); + nearbyEntTrails.addAll(GameObjectUtils.findGameObjects( + GameObjectId.ENT_TRAIL_2.getId(), fletchCheckPos, ENT_TRAIL_SEARCH_RADIUS)); if (!hasWalkedOverEntTrailsThisNavigation && nearbyEntTrails.size() >= 2) { System.out.println("Ent trails detected during fletching - interrupting to prioritize trails"); @@ -521,17 +521,17 @@ public static boolean checkAndWalkOverEntTrails() { } private static List findAndFilterEntTrails(WorldPoint playerLocation, int searchRadius) { - // Search for ent trails nearby using string search - List entTrails = GameObjectUtils.findGameObjectsByName( - GameObjectId.ENT_TRAIL_1.getSearchTerm(), playerLocation, searchRadius); - - // Filter trails that are within the search radius of the player - entTrails = entTrails.stream() - .filter(trail -> trail.getWorldLocation().distanceTo(playerLocation) <= searchRadius) - .sorted((a, b) -> Integer.compare( - a.getWorldLocation().distanceTo(playerLocation), - b.getWorldLocation().distanceTo(playerLocation))) - .collect(java.util.stream.Collectors.toList()); + List trail1 = GameObjectUtils.findGameObjects( + GameObjectId.ENT_TRAIL_1.getId(), playerLocation, searchRadius); + List trail2 = GameObjectUtils.findGameObjects( + GameObjectId.ENT_TRAIL_2.getId(), playerLocation, searchRadius); + + List entTrails = new java.util.ArrayList<>(trail1); + entTrails.addAll(trail2); + + entTrails.sort((a, b) -> Integer.compare( + a.getWorldLocation().distanceTo(playerLocation), + b.getWorldLocation().distanceTo(playerLocation))); return entTrails; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/TotemHandler.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/TotemHandler.java index afb0f6ed7a..ebb0f29adc 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/TotemHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/TotemHandler.java @@ -108,7 +108,6 @@ public static boolean buildTotemBase(TotemLocation totemLocation, TotemProgress for (int attempt = 1; attempt <= maxRetries; attempt++) { System.out.println("Building totem base attempt " + attempt + "/" + maxRetries + " at " + totemLocation.getDescription()); - // Look for totem site using string search GameObject totemSite = GameObjectUtils.findObjectAtLocationByName( GameObjectId.TOTEM_SITE.getSearchTerm(), location); @@ -297,7 +296,8 @@ public static boolean carveAnimalsIntoTotem(TotemLocation totemLocation, TotemPr private static boolean carveAnimalIntoTotem(SpiritAnimal animal) { try { if (!sleepUntil(() -> Rs2Widget.hasWidgetText("What animal would you like to carve?",270,5, false), 5000)) { - Microbot.getRs2TileObjectCache().query().withName(GameObjectId.EMPTY_TOTEM.getSearchTerm()).interact("Carve"); + Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query().withNameContains(GameObjectId.EMPTY_TOTEM.getSearchTerm()).interact("Carve")); if (!sleepUntil(() -> Rs2Widget.hasWidgetText("What animal would you like to carve?",270,5, false), 5000)) { System.err.println("Failed to carve animal"); return false; @@ -346,7 +346,6 @@ public static boolean decorateTotem(TotemLocation totemLocation, TotemProgress p return false; } - // Find the totem object to interact with. GameObject decorationTotem = GameObjectUtils.findObjectAtLocationByName( GameObjectId.TOTEM_READY_FOR_DECORATION.getSearchTerm(), location); diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/GameObjectUtils.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/GameObjectUtils.java index e4b541e796..e642beae53 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/GameObjectUtils.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/GameObjectUtils.java @@ -10,7 +10,9 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; /** * Utility class for interacting with game objects in the Vale Totems minigame @@ -18,7 +20,9 @@ * Updated to use string-based searching for totem sites and offerings */ public class GameObjectUtils { - + + private static final java.util.Set discoveredTotemIds = java.util.concurrent.ConcurrentHashMap.newKeySet(); + // Cache configuration private static final long CACHE_EXPIRY_MS = 10000; // 10 seconds cache expiry private static final int MAX_CACHE_SIZE = 10; // Prevent memory leaks @@ -64,19 +68,9 @@ private static GameObject getCachedObjectAtLocationByName(String searchTerm, Wor // Cache miss or expired - perform expensive search Microbot.log("Cache MISS for location: " + location + " (searchTerm: " + searchTerm + ") - performing search"); long startTime = System.currentTimeMillis(); - Rs2TileObjectModel tileObjModel = Microbot.getRs2TileObjectCache().query().withName(searchTerm).nearest(location, 10); - GameObject gameObject = null; - if (tileObjModel != null) { - var tile = net.runelite.client.plugins.microbot.util.tile.Rs2Tile.getTile(tileObjModel.getWorldLocation().getX(), tileObjModel.getWorldLocation().getY()); - if (tile != null) { - for (GameObject go : tile.getGameObjects()) { - if (go != null && go.getId() == tileObjModel.getId()) { - gameObject = go; - break; - } - } - } - } + Rs2TileObjectModel tileObjModel = Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query().withNameContains(searchTerm).nearest(location, 10)); + GameObject gameObject = toGameObject(tileObjModel); long searchTime = System.currentTimeMillis() - startTime; Microbot.log("Search completed in " + searchTime + "ms for location: " + location); @@ -139,13 +133,31 @@ public static String getCacheStats() { return String.format("Cache: %d total entries, %d expired", totalEntries, expiredEntries); } + private static GameObject toGameObject(Rs2TileObjectModel model) { + if (model == null) return null; + int targetId = model.getId(); + int wx = model.getWorldLocation().getX(); + int wy = model.getWorldLocation().getY(); + return Microbot.getClientThread().invoke(() -> { + var tile = net.runelite.client.plugins.microbot.util.tile.Rs2Tile.getTile(wx, wy); + if (tile != null) { + for (GameObject go : tile.getGameObjects()) { + if (go != null && go.getId() == targetId) { + return go; + } + } + } + return null; + }); + } + /** * Find the nearest game object by ID * @param objectId the game object ID to search for * @return the nearest game object, or null if not found */ public static GameObject findNearestObject(int objectId) { - var m = Microbot.getRs2TileObjectCache().query().withId(objectId).nearest(); return m != null ? (GameObject) m : null; + return toGameObject(Microbot.getRs2TileObjectCache().query().withId(objectId).nearest()); } /** @@ -154,7 +166,9 @@ public static GameObject findNearestObject(int objectId) { * @return the nearest game object, or null if not found */ public static GameObject findNearestObjectByName(String searchTerm) { - var m = Microbot.getRs2TileObjectCache().query().withName(searchTerm).nearest(); return m != null ? (GameObject) m : null; + Rs2TileObjectModel model = Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query().withNameContains(searchTerm).nearest()); + return toGameObject(model); } /** @@ -164,7 +178,7 @@ public static GameObject findNearestObjectByName(String searchTerm) { * @return the game object at that location, or null if not found */ public static GameObject findObjectAtLocation(int objectId, WorldPoint location) { - var m = Microbot.getRs2TileObjectCache().query().withId(objectId).nearest(location, 3); return m != null ? (GameObject) m : null; + return toGameObject(Microbot.getRs2TileObjectCache().query().withId(objectId).nearest(location, 3)); } /** @@ -185,7 +199,10 @@ public static GameObject findObjectAtLocationByName(String searchTerm, WorldPoin * @return list of matching game objects */ public static List findGameObjects(int objectId, WorldPoint location, int radius) { - return new java.util.ArrayList<>(); // TODO: migrate to new query API + return Microbot.getRs2TileObjectCache().query().withId(objectId).within(location, radius).toList().stream() + .map(GameObjectUtils::toGameObject) + .filter(Objects::nonNull) + .collect(Collectors.toList()); } /** @@ -196,7 +213,12 @@ public static List findGameObjects(int objectId, WorldPoint location * @return list of matching game objects */ public static List findGameObjectsByName(String searchTerm, WorldPoint location, int radius) { - return new java.util.ArrayList<>(); // TODO: migrate to new query API + List models = Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query().withNameContains(searchTerm).within(location, radius).toList()); + return models.stream() + .map(GameObjectUtils::toGameObject) + .filter(Objects::nonNull) + .collect(Collectors.toList()); } /** @@ -206,7 +228,8 @@ public static List findGameObjectsByName(String searchTerm, WorldPoi * @return true if the interaction was successful */ public static boolean interactWithObject(GameObject gameObject, String action) { - return Microbot.getRs2TileObjectCache().query().withId(gameObject.getId()).interact(action); + var obj = Microbot.getRs2TileObjectCache().query().withId(gameObject.getId()).nearest(gameObject.getWorldLocation(), 1); + return obj != null && obj.click(action); } /** @@ -226,7 +249,9 @@ public static boolean findAndInteract(int objectId, String action) { * @return true if successful */ public static boolean findAndInteractByName(String searchTerm, String action) { - return Microbot.getRs2TileObjectCache().query().withName(searchTerm).interact(action); + var obj = Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query().withNameContains(searchTerm).nearest()); + return obj != null && obj.click(action); } /** @@ -237,7 +262,8 @@ public static boolean findAndInteractByName(String searchTerm, String action) { * @return true if successful */ public static boolean findAndInteractAtLocation(int objectId, WorldPoint location, String action) { - return Microbot.getRs2TileObjectCache().query().withId(objectId).nearest(location, 3) != null && Microbot.getRs2TileObjectCache().query().withId(objectId).interact(action); + var obj = Microbot.getRs2TileObjectCache().query().withId(objectId).nearest(location, 3); + return obj != null && obj.click(action); } /** @@ -248,7 +274,8 @@ public static boolean findAndInteractAtLocation(int objectId, WorldPoint locatio * @return true if successful */ public static boolean findAndInteractAtLocationByName(String searchTerm, WorldPoint location, String action) { - var obj = Microbot.getRs2TileObjectCache().query().withName(searchTerm).nearest(location, 10); + var obj = Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query().withNameContains(searchTerm).nearest(location, 10)); return obj != null && obj.click(action); } @@ -269,7 +296,8 @@ public static boolean objectExistsAtLocation(int objectId, WorldPoint location) * @return true if the object exists at that location */ public static boolean objectExistsAtLocationByName(String searchTerm, WorldPoint location) { - return Microbot.getRs2TileObjectCache().query().withName(searchTerm).nearest(location, 10) != null; + return Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query().withNameContains(searchTerm).nearest(location, 10)) != null; } /** @@ -300,37 +328,42 @@ public static int getDistanceToNearestObject(int objectId) { * @return the GameObjectId enum for the totem state, or null if no totem found */ public static GameObjectId getTotemStateAtLocation(WorldPoint location) { - // Search for any totem object at the location using cached method - Microbot.log("getTotemStateAtLocation started: " + (System.currentTimeMillis())); - GameObject totem = getCachedObjectAtLocationByName(GameObjectId.TOTEM_SITE.getSearchTerm(), location); - Microbot.log("getTotemStateAtLocation finished: " + (System.currentTimeMillis())); - if (totem != null) { - // Get the ObjectComposition to access actions - try { - Microbot.log("getTotemStateAtLocation findObjectComposition started: " + (System.currentTimeMillis())); - var totemModel = Microbot.getRs2TileObjectCache().query().withId(totem.getId()).nearest(); - String[] actions = totemModel != null ? totemModel.getObjectComposition().getActions() : null; - Microbot.log("getTotemStateAtLocation findObjectComposition finished: " + (System.currentTimeMillis())); - if (actions != null) { - List actionList = Arrays.asList(actions); - - // Check for specific actions to determine totem state - if (actionList.contains("Build")) { - return GameObjectId.TOTEM_SITE; - } else if (actionList.contains("Decorate")) { - return GameObjectId.TOTEM_READY_FOR_DECORATION; - } else { - // If no Build or Decorate action, assume it's ready for carving - return GameObjectId.EMPTY_TOTEM; - } - } - } catch (Exception e) { - System.err.println("Error getting actions for totem: " + e.getMessage()); - return null; + var totemModel = Microbot.getRs2TileObjectCache().query() + .where(obj -> GameObjectId.isTotemObject(obj.getId()) || discoveredTotemIds.contains(obj.getId())) + .nearest(location, 10); + + if (totemModel == null) { + totemModel = Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query() + .withNameContains(GameObjectId.TOTEM_SITE.getSearchTerm()) + .nearest(location, 10)); + if (totemModel != null) { + discoveredTotemIds.add(totemModel.getId()); + Microbot.log("[ValeTotem] Discovered totem id=" + totemModel.getId() + + " name='" + totemModel.getName() + "' — future lookups will use fast ID path"); } } - return null; // No totem object found at the location. + if (totemModel == null) { + return null; + } + + try { + String[] actions = totemModel.getObjectComposition().getActions(); + if (actions != null) { + List actionList = Arrays.asList(actions); + if (actionList.contains("Build")) { + return GameObjectId.TOTEM_SITE; + } else if (actionList.contains("Decorate")) { + return GameObjectId.TOTEM_READY_FOR_DECORATION; + } else { + return GameObjectId.EMPTY_TOTEM; + } + } + } catch (Exception e) { + System.err.println("Error getting actions for totem: " + e.getMessage()); + } + return null; } /** @@ -340,14 +373,11 @@ public static GameObjectId getTotemStateAtLocation(WorldPoint location) { * @return the GameObjectId for the offerings state, or null if no offerings found */ public static GameObjectId getOfferingsStateNearLocation(WorldPoint location, int radius) { - // Use string-based search for offerings - String offeringsSearchTerm = GameObjectId.OFFERINGS_MANY.getSearchTerm(); - - var nearbyObjects = Microbot.getRs2TileObjectCache().query().withName(offeringsSearchTerm).within(location, radius).toList(); - + var nearbyObjects = Microbot.getRs2TileObjectCache().query() + .where(obj -> GameObjectId.isOfferingsPile(obj.getId())) + .within(location, radius).toList(); + if (!nearbyObjects.isEmpty()) { - // Default to assuming offerings are available since we found an offerings pile - // May need refinement to distinguish between different offering states return GameObjectId.OFFERINGS_MANY; } return null; @@ -360,11 +390,9 @@ public static GameObjectId getOfferingsStateNearLocation(WorldPoint location, in * @return the offerings game object if claimable, null otherwise */ public static GameObject findClaimableOfferings(WorldPoint location, int radius) { - // Use string-based search for offerings - String offeringsSearchTerm = GameObjectId.OFFERINGS_MANY.getSearchTerm(); - - var m = Microbot.getRs2TileObjectCache().query().withName(offeringsSearchTerm).within(location, radius).nearest(); - return m != null ? (GameObject) m : null; + return toGameObject(Microbot.getRs2TileObjectCache().query() + .where(obj -> GameObjectId.isOfferingsPile(obj.getId())) + .within(location, radius).nearest()); } /** @@ -392,7 +420,8 @@ public static boolean waitForObjectAtLocationByName(String searchTerm, WorldPoin long startTime = System.currentTimeMillis(); while (System.currentTimeMillis() - startTime < timeoutMs) { - if (Microbot.getRs2TileObjectCache().query().withName(searchTerm).nearest(location, 10) != null) { + if (Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query().withNameContains(searchTerm).nearest(location, 10)) != null) { return true; } From 9e360089ad7945db8893a5d73f0c737d7f93182a Mon Sep 17 00:00:00 2001 From: chsami Date: Sun, 12 Apr 2026 02:10:19 +0200 Subject: [PATCH 33/95] feat(ValeTotem): enhance offerings handling with discovered IDs and improved search logic --- .../microbot/valetotems/ValeTotemPlugin.java | 2 +- .../microbot/valetotems/ValeTotemScript.java | 2 +- .../valetotems/handlers/RewardHandler.java | 14 ++++---- .../valetotems/utils/GameObjectUtils.java | 35 ++++++++++++++++--- 4 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java index dcbe418634..dd6731bcb0 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java @@ -27,7 +27,7 @@ ) @Slf4j public class ValeTotemPlugin extends Plugin { - static final String version = "1.0.8"; + static final String version = "1.0.9"; @Inject private ValeTotemConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemScript.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemScript.java index ecadcd9307..b7ac2f0f61 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemScript.java @@ -146,7 +146,7 @@ private void handleBankingState() { gameSession.setCurrentTotem(TotemLocation.getFirst(currentRouteType)); // Calculate reward collection frequency - RewardHandler.COLLECTION_FREQUENCY = config.collectOfferingsFrequency() - 2 + RandomUtils.nextInt(1, 4); + RewardHandler.COLLECTION_FREQUENCY = Math.max(1, config.collectOfferingsFrequency() - 2 + RandomUtils.nextInt(1, 4)); Microbot.log("Reward collection frequency set to: " + RewardHandler.COLLECTION_FREQUENCY); // Log the route being used diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/RewardHandler.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/RewardHandler.java index 9bda6112eb..7a2b0774b1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/RewardHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/RewardHandler.java @@ -50,26 +50,26 @@ public static boolean areOfferingsAvailable(TotemLocation totemLocation) { public static boolean collectOfferings(TotemLocation totemLocation, TotemProgress progress, GameSession gameSession) { try { WorldPoint location = totemLocation.getLocation(); - - // Check if already collected for this totem + if (progress.areOfferingsCollected()) { return true; } - if (!shouldCollectOfferings(gameSession)) { + boolean shouldCollect = shouldCollectOfferings(gameSession); + Microbot.log("[Offerings] shouldCollect=" + shouldCollect + " rounds=" + gameSession.getTotalRounds() + " freq=" + COLLECTION_FREQUENCY); + if (!shouldCollect) { return false; } - // Find claimable offerings nearby using string search GameObject offerings = GameObjectUtils.findClaimableOfferings(location, OFFERINGS_SEARCH_RADIUS); - + Microbot.log("[Offerings] findClaimable at " + location + " radius=" + OFFERINGS_SEARCH_RADIUS + " found=" + (offerings != null)); + if (offerings == null) { - System.out.println("No claimable offerings found at " + totemLocation.getDescription()); return false; } - // Check offerings state GameObjectId offeringsState = GameObjectUtils.getOfferingsStateNearLocation(location, OFFERINGS_SEARCH_RADIUS); + Microbot.log("[Offerings] state=" + (offeringsState != null ? offeringsState.name() : "null")); if (offeringsState == null || !GameObjectId.hasClaimableOfferings(offeringsState.getId())) { return false; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/GameObjectUtils.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/GameObjectUtils.java index e642beae53..857cea3905 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/GameObjectUtils.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/utils/GameObjectUtils.java @@ -22,6 +22,7 @@ public class GameObjectUtils { private static final java.util.Set discoveredTotemIds = java.util.concurrent.ConcurrentHashMap.newKeySet(); + private static final java.util.Set discoveredOfferingIds = java.util.concurrent.ConcurrentHashMap.newKeySet(); // Cache configuration private static final long CACHE_EXPIRY_MS = 10000; // 10 seconds cache expiry @@ -374,9 +375,21 @@ public static GameObjectId getTotemStateAtLocation(WorldPoint location) { */ public static GameObjectId getOfferingsStateNearLocation(WorldPoint location, int radius) { var nearbyObjects = Microbot.getRs2TileObjectCache().query() - .where(obj -> GameObjectId.isOfferingsPile(obj.getId())) + .where(obj -> GameObjectId.isOfferingsPile(obj.getId()) || discoveredOfferingIds.contains(obj.getId())) .within(location, radius).toList(); + if (nearbyObjects.isEmpty()) { + nearbyObjects = Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query() + .withNameContains(GameObjectId.OFFERINGS_MANY.getSearchTerm()) + .within(location, radius).toList()); + for (var obj : nearbyObjects) { + discoveredOfferingIds.add(obj.getId()); + Microbot.log("[ValeTotem] Discovered offering id=" + obj.getId() + + " name='" + obj.getName() + "' — future lookups will use fast ID path"); + } + } + if (!nearbyObjects.isEmpty()) { return GameObjectId.OFFERINGS_MANY; } @@ -390,9 +403,23 @@ public static GameObjectId getOfferingsStateNearLocation(WorldPoint location, in * @return the offerings game object if claimable, null otherwise */ public static GameObject findClaimableOfferings(WorldPoint location, int radius) { - return toGameObject(Microbot.getRs2TileObjectCache().query() - .where(obj -> GameObjectId.isOfferingsPile(obj.getId())) - .within(location, radius).nearest()); + var model = Microbot.getRs2TileObjectCache().query() + .where(obj -> GameObjectId.isOfferingsPile(obj.getId()) || discoveredOfferingIds.contains(obj.getId())) + .within(location, radius).nearest(); + + if (model == null) { + model = Microbot.getClientThread().invoke(() -> + Microbot.getRs2TileObjectCache().query() + .withNameContains(GameObjectId.OFFERINGS_MANY.getSearchTerm()) + .within(location, radius).nearest()); + if (model != null) { + discoveredOfferingIds.add(model.getId()); + Microbot.log("[ValeTotem] Discovered offering id=" + model.getId() + + " name='" + model.getName() + "' — future lookups will use fast ID path"); + } + } + + return toGameObject(model); } /** From 1ab0748a61dd63ec049818e34cb95b69cf629cc6 Mon Sep 17 00:00:00 2001 From: chsami Date: Sun, 12 Apr 2026 02:40:38 +0200 Subject: [PATCH 34/95] feat(ValeTotem): enhance offerings handling with discovered IDs and improved search logic --- .../AmmoniteCrabs/AmmoniteCrabPlugin.java | 2 +- .../AmmoniteCrabs/AmmoniteCrabScript.java | 2 +- .../DemonicGorillaPlugin.java | 2 +- .../DemonicGorillaScript.java | 4 ++-- .../EnsouledHeadSlayerPlugin.java | 2 +- .../EnsouledHeadSlayerScript.java | 2 +- .../GiantSeaweedFarmerPlugin.java | 2 +- .../GiantSeaweedFarmerScript.java | 2 +- .../TzhaarVenatorBowPlugin.java | 2 +- .../TzhaarVenatorBowScript.java | 4 ++-- .../microbot/agility/MicroAgilityPlugin.java | 2 +- .../microbot/agility/courses/PyramidCourse.java | 2 +- .../microbot/aiofighter/AIOFighterPlugin.java | 2 +- .../microbot/aiofighter/bank/BankerScript.java | 2 +- .../microbot/aiomagic/AIOMagicPlugin.java | 2 +- .../microbot/aiomagic/scripts/SplashScript.java | 2 +- .../aiomagic/scripts/StunAlchScript.java | 2 +- .../microbot/aiomagic/scripts/StunScript.java | 2 +- .../aiomagic/scripts/StunTeleAlchScript.java | 2 +- .../microbot/arceuusrc/ArceuusRcPlugin.java | 2 +- .../microbot/arceuusrc/ArceuusRcScript.java | 6 +++--- .../AutoEssenceMiningPlugin.java | 2 +- .../AutoEssenceMiningScript.java | 6 +++--- .../baggedplants/BaggedPlantsPlugin.java | 2 +- .../baggedplants/BaggedPlantsScript.java | 4 ++-- .../plugins/microbot/barrows/BarrowsPlugin.java | 2 +- .../plugins/microbot/barrows/BarrowsScript.java | 4 ++-- .../BlastoiseFurnacePlugin.java | 2 +- .../BlastoiseFurnaceScript.java | 2 +- .../bluedragons/BlueDragonsOverlay.java | 2 +- .../microbot/bluedragons/BlueDragonsPlugin.java | 2 +- .../microbot/bluedragons/BlueDragonsScript.java | 2 +- .../CannonballSmelterPlugin.java | 2 +- .../CannonballSmelterScript.java | 6 +++--- .../microbot/chaosaltar/ChaosAltarPlugin.java | 2 +- .../microbot/chaosaltar/ChaosAltarScript.java | 2 +- .../chartercrafter/CharterCrafterPlugin.java | 2 +- .../chartercrafter/CharterCrafterScript.java | 2 +- .../microbot/cluesolver/ClueSolverPlugin.java | 2 +- .../cluesolver/cluetask/AnagramClueTask.java | 4 ++-- .../cluesolver/cluetask/CoordinateClueTask.java | 2 +- .../cluesolver/cluetask/CrypticClueTask.java | 6 +++--- .../cluesolver/cluetask/EmoteClueTask.java | 2 +- .../cluetask/FaloTheBardClueTask.java | 3 ++- .../cluesolver/cluetask/MusicClueTask.java | 2 +- .../construction/ConstructionPlugin.java | 2 +- .../construction/ConstructionScript.java | 4 ++-- .../microbot/farmtreerun/FarmTreeRunPlugin.java | 2 +- .../microbot/farmtreerun/FarmTreeRunScript.java | 4 ++-- .../fishingtrawler/FishingTrawlerPlugin.java | 2 +- .../fishingtrawler/FishingTrawlerScript.java | 6 +++--- .../flipperschaser/FlippersChaserPlugin.java | 4 ++-- .../plugins/microbot/frostyrc/RcPlugin.java | 2 +- .../plugins/microbot/frostyrc/RcScript.java | 2 +- .../giantsfoundry/GiantsFoundryPlugin.java | 2 +- .../giantsfoundry/GiantsFoundryScript.java | 5 +++-- .../giantsfoundry/GiantsFoundryState.java | 6 +++--- .../microbot/gildedaltar/GildedAltarPlugin.java | 2 +- .../microbot/gildedaltar/GildedAltarScript.java | 6 +++--- .../plugins/microbot/gotr/GotrPlugin.java | 2 +- .../plugins/microbot/gotr/GotrScript.java | 2 +- .../microbot/herbiboar/HerbiboarPlugin.java | 2 +- .../microbot/herbiboar/HerbiboarScript.java | 6 +++--- .../plugins/microbot/herbrun/HerbrunPlugin.java | 2 +- .../plugins/microbot/herbrun/HerbrunScript.java | 2 +- .../microbot/housetab/HouseTabPlugin.java | 2 +- .../microbot/housetab/HouseTabScript.java | 6 ++++-- .../housethieving/HouseThievingPlugin.java | 2 +- .../housethieving/HouseThievingScript.java | 8 ++++---- .../hunterKabbits/HunterKabbitsScript.java | 2 +- .../hunterKabbits/HunterKebbitsPlugin.java | 2 +- .../microbot/kittentracker/FeedKittenEvent.java | 2 +- .../kittentracker/KittenAttentionEvent.java | 2 +- .../microbot/kittentracker/KittenPlugin.java | 2 +- .../microbot/kittentracker/KittenScript.java | 4 ++-- .../microbot/looter/AutoLooterPlugin.java | 2 +- .../microbot/looter/scripts/FlaxScript.java | 2 +- .../lunartablets/LunarTabletsPlugin.java | 2 +- .../lunartablets/LunarTabletsScript.java | 3 ++- .../MageTrainingArenaPlugin.java | 2 +- .../MageTrainingArenaScript.java | 2 +- .../mahoganyhomez/MahoganyHomesPlugin.java | 2 +- .../mahoganyhomez/MahoganyHomesScript.java | 2 +- .../plugins/microbot/mess/TheMessPlugin.java | 2 +- .../plugins/microbot/mess/TheMessScript.java | 2 +- .../mke_wintertodt/MKE_WintertodtPlugin.java | 2 +- .../mke_wintertodt/MKE_WintertodtScript.java | 5 +++-- .../plugins/microbot/mmcaves/MmCavesPlugin.java | 2 +- .../plugins/microbot/mmcaves/MmCavesScript.java | 2 +- .../client/plugins/microbot/nmz/NmzPlugin.java | 2 +- .../client/plugins/microbot/nmz/NmzScript.java | 2 +- .../microbot/npctanner/npcTannerPlugin.java | 2 +- .../microbot/npctanner/npcTannerScript.java | 2 +- .../microbot/pestcontrol/PestControlPlugin.java | 2 +- .../microbot/pestcontrol/PestControlScript.java | 4 ++-- .../microbot/qualityoflife/QoLPlugin.java | 4 ++-- .../scripts/wintertodt/WintertodtScript.java | 4 ++-- .../microbot/revkiller/revKillerPlugin.java | 2 +- .../microbot/revkiller/revKillerScript.java | 2 +- .../microbot/sandcrabs/SandCrabPlugin.java | 2 +- .../microbot/sandcrabs/SandCrabScript.java | 2 +- .../sandminer/GabulhasSandMinerPlugin.java | 2 +- .../sandminer/GabulhasSandMinerScript.java | 4 ++-- .../microbot/scurrius/ScurriusPlugin.java | 2 +- .../microbot/scurrius/ScurriusScript.java | 2 +- .../shadeskiller/ShadesKillerPlugin.java | 2 +- .../shadeskiller/ShadesKillerScript.java | 6 +++--- .../plugins/microbot/slayer/SlayerPlugin.java | 2 +- .../plugins/microbot/slayer/SlayerScript.java | 17 +++++++++-------- .../sulphurnaguafigther/SulphurNaguaPlugin.java | 2 +- .../sulphurnaguafigther/SulphurNaguaScript.java | 2 +- .../summergarden/SummerGardenPlugin.java | 2 +- .../summergarden/SummerGardenScript.java | 8 ++++---- .../tithefarming/TitheFarmingPlugin.java | 2 +- .../tithefarming/TitheFarmingScript.java | 2 +- .../tormenteddemons/TormentedDemonPlugin.java | 2 +- .../tormenteddemons/TormentedDemonScript.java | 2 +- .../tutorialisland/TutorialIslandPlugin.java | 2 +- .../tutorialisland/TutorialIslandScript.java | 12 ++++++------ .../plugins/microbot/vorkath/VorkathPlugin.java | 2 +- .../plugins/microbot/vorkath/VorkathScript.java | 8 ++++---- .../woodcutting/AutoWoodcuttingPlugin.java | 2 +- .../Forestry/StrugglingSaplingEvent.java | 4 ++-- 123 files changed, 186 insertions(+), 179 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabPlugin.java index dcfc7691c6..04e1a3866e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabPlugin.java @@ -28,7 +28,7 @@ @Slf4j public class AmmoniteCrabPlugin extends Plugin { - public final static String version = "1.1.2"; + public final static String version = "1.1.3"; @Inject public AmmoniteCrabScript ammoniteCrabScript; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabScript.java b/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabScript.java index 31a7392ef8..543c2f8b8f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/AmmoniteCrabs/AmmoniteCrabScript.java @@ -236,7 +236,7 @@ private void attackScatteredCrabs(AmmoniteCrabConfig config) { * @return true if npc is aggressive */ private boolean isNpcAggressive() { - List npcs = Microbot.getRs2NpcCache().query().withName("Fossil Rock").toList(); + List npcs = Microbot.getRs2NpcCache().query().withName("Fossil Rock").toListOnClientThread(); if (npcs.isEmpty()) { return true; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/DemonicGorillaKiller/DemonicGorillaPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/DemonicGorillaKiller/DemonicGorillaPlugin.java index 112286da94..df7b33c05c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/DemonicGorillaKiller/DemonicGorillaPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/DemonicGorillaKiller/DemonicGorillaPlugin.java @@ -35,7 +35,7 @@ @Slf4j public class DemonicGorillaPlugin extends Plugin { - public final static String version = "1.2.4"; + public final static String version = "1.2.5"; private static final int DEMONIC_GORILLA_ROCK = 856; public static FixedSizeQueue lastLocation = new FixedSizeQueue<>(2); diff --git a/src/main/java/net/runelite/client/plugins/microbot/DemonicGorillaKiller/DemonicGorillaScript.java b/src/main/java/net/runelite/client/plugins/microbot/DemonicGorillaKiller/DemonicGorillaScript.java index 55b20e289d..d3869d7014 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/DemonicGorillaKiller/DemonicGorillaScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/DemonicGorillaKiller/DemonicGorillaScript.java @@ -261,7 +261,7 @@ private void handleTargetSelection() { Rs2Walker.walkTo(GORILLA_LOCATION); currentTarget = getTarget(true); if (currentTarget == null) { - Microbot.getRs2NpcCache().query().withName("Demonic gorilla").interact("Attack"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("Demonic gorilla").interact("Attack")); } } outOfCombatTime = null; // Reset after forcing new target @@ -518,7 +518,7 @@ public Rs2NpcModel getTarget(boolean force) { .min(Comparator.comparingInt(npc -> npc.getWorldLocation().distanceTo(playerLocation))).get(); } - List demonicGorillas = Microbot.getRs2NpcCache().query().withName("Demonic gorilla").toList(); + List demonicGorillas = Microbot.getRs2NpcCache().query().withName("Demonic gorilla").toListOnClientThread(); if (demonicGorillas.isEmpty()) { logOnceToChat("No demonic gorilla found."); return null; diff --git a/src/main/java/net/runelite/client/plugins/microbot/EnsouledHeadSlayer/EnsouledHeadSlayerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/EnsouledHeadSlayer/EnsouledHeadSlayerPlugin.java index d6025404d8..f3e03a1efa 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/EnsouledHeadSlayer/EnsouledHeadSlayerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/EnsouledHeadSlayer/EnsouledHeadSlayerPlugin.java @@ -34,7 +34,7 @@ ) public class EnsouledHeadSlayerPlugin extends Plugin { - public final static String version = "1.0.1"; + public final static String version = "1.0.2"; private Instant scriptStartTime; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/EnsouledHeadSlayer/EnsouledHeadSlayerScript.java b/src/main/java/net/runelite/client/plugins/microbot/EnsouledHeadSlayer/EnsouledHeadSlayerScript.java index 59d32f407c..86a77a86db 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/EnsouledHeadSlayer/EnsouledHeadSlayerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/EnsouledHeadSlayer/EnsouledHeadSlayerScript.java @@ -112,7 +112,7 @@ private void handleReanimatingAndKilling(EnsouledHeadSlayerConfig config) { */ if (Microbot.getVarbitValue(Varbits.SPELLBOOK) != 3) { Microbot.log("On wrong spellbook, switching to Arceuus..."); - Microbot.getRs2NpcCache().query().withName("Tyss").interact("Spellbook"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("Tyss").interact("Spellbook")); } Rs2Combat.enableAutoRetialiate(); var ensouledHead = Rs2Inventory.count("ensouled"); diff --git a/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerPlugin.java index f498e91c01..14a065b279 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerPlugin.java @@ -26,7 +26,7 @@ ) @Slf4j public class GiantSeaweedFarmerPlugin extends Plugin { - public final static String version = "1.2.0"; + public final static String version = "1.2.1"; private Instant scriptStartTime; @Inject private GiantSeaweedFarmerConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerScript.java b/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerScript.java index 152689cdbf..a5ca3f85dd 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerScript.java @@ -351,7 +351,7 @@ private void handleFarmNull(){ } private void handleNoting(){ - Rs2NpcModel leprechaun = Microbot.getRs2NpcCache().query().withName("Tool leprechaun").nearest(); + Rs2NpcModel leprechaun = Microbot.getRs2NpcCache().query().withName("Tool leprechaun").nearestOnClientThread(); if (leprechaun == null) {return;} Rs2ItemModel unNoted = Rs2Inventory.getUnNotedItem("Giant seaweed", true); Rs2Inventory.use(unNoted); diff --git a/src/main/java/net/runelite/client/plugins/microbot/TzhaarVenatorBow/TzhaarVenatorBowPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/TzhaarVenatorBow/TzhaarVenatorBowPlugin.java index 9e18b02481..4759c3e2ef 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/TzhaarVenatorBow/TzhaarVenatorBowPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/TzhaarVenatorBow/TzhaarVenatorBowPlugin.java @@ -25,7 +25,7 @@ ) public class TzhaarVenatorBowPlugin extends Plugin { - public final static String version = "1.0.1"; + public final static String version = "1.0.2"; private Instant scriptStartTime; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/TzhaarVenatorBow/TzhaarVenatorBowScript.java b/src/main/java/net/runelite/client/plugins/microbot/TzhaarVenatorBow/TzhaarVenatorBowScript.java index 04a77aaca1..afbd3e51f3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/TzhaarVenatorBow/TzhaarVenatorBowScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/TzhaarVenatorBow/TzhaarVenatorBowScript.java @@ -183,7 +183,7 @@ private List getValidNpcs() { .where(npc -> npc.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) <= 6) .where(npc -> VALID_NPCS.contains(npc.getName())) .where(npc -> !INVALID_NPCS.contains(npc.getName())) - .toList(); + .toListOnClientThread(); } private List getInvalidNpcs() { @@ -192,7 +192,7 @@ private List getInvalidNpcs() { .where(npc -> npc.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) <= 15) .where(npc -> !npc.isDead() && npc.isInteracting()) .where(npc -> npc.hasLineOfSight()) - .toList(); + .toListOnClientThread(); } private void handleTravel(TzHaarVenatorBowConfig config) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/agility/MicroAgilityPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/agility/MicroAgilityPlugin.java index a04be2d89c..1aca10a22f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/agility/MicroAgilityPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/agility/MicroAgilityPlugin.java @@ -34,7 +34,7 @@ @Slf4j public class MicroAgilityPlugin extends Plugin { - public static final String version = "1.2.5"; + public static final String version = "1.2.6"; @Inject private MicroAgilityConfig config; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PyramidCourse.java b/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PyramidCourse.java index b1411571b0..e33f484cba 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PyramidCourse.java +++ b/src/main/java/net/runelite/client/plugins/microbot/agility/courses/PyramidCourse.java @@ -1125,7 +1125,7 @@ private boolean handlePyramidTurnIn() { } // Try to find Simon - Rs2NpcModel simon = Microbot.getRs2NpcCache().query().withName(SIMON_NAME).nearest(); + Rs2NpcModel simon = Microbot.getRs2NpcCache().query().withName(SIMON_NAME).nearestOnClientThread(); // If Simon is found and reachable, use pyramid top on him if (simon != null && Rs2GameObject.canReach(simon.getWorldLocation())) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java index 611c0b0dab..09c118ff5e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java @@ -61,7 +61,7 @@ ) @Slf4j public class AIOFighterPlugin extends Plugin { - public static final String version = "2.1.4"; + public static final String version = "2.1.5"; public static boolean needShopping = false; private static final String SET = "Set"; private static final String CENTER_TILE = ColorUtil.wrapWithColorTag("Center Tile", JagexColors.MENU_TARGET); diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java index 7c3d273626..5eeb64cf96 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/bank/BankerScript.java @@ -481,7 +481,7 @@ private void usePoolIfNeeded() { // silently dispatching a click against an unreachable pool. Rs2TileObjectModel pool = Microbot.getRs2TileObjectCache().query() .withName("Pool of Refreshment") - .nearest(20); + .nearestOnClientThread(20); if (pool != null) { if (pool.click("Drink")) { sleepUntil(Rs2Player::isMoving, 2000); diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiomagic/AIOMagicPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/aiomagic/AIOMagicPlugin.java index e8dbbd94b6..dfec9b1ca2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiomagic/AIOMagicPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiomagic/AIOMagicPlugin.java @@ -77,7 +77,7 @@ AIOMagicConfig provideConfig(ConfigManager configManager) { @Inject private SpinFlaxScript spinFlaxScript; - public final static String version = "1.2.6"; + public final static String version = "1.2.7"; @Override protected void startUp() throws AWTException { diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/SplashScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/SplashScript.java index 47b2660399..c33c009c94 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/SplashScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/SplashScript.java @@ -63,7 +63,7 @@ public boolean run() { Rs2NpcModel targetNpc = Microbot.getRs2NpcCache().query() .withName(targetNpcName) - .nearest(); + .nearestOnClientThread(); if (targetNpc == null) { Microbot.log("Unable to find NPC: " + targetNpcName); return; diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunAlchScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunAlchScript.java index 65c57babdc..b250a516cf 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunAlchScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunAlchScript.java @@ -107,7 +107,7 @@ public boolean run() { } else { var configuredNpc = Microbot.getRs2NpcCache().query() .withName(targetNpcName) - .nearest(); + .nearestOnClientThread(); if (configuredNpc == null) { Microbot.log("Unable to find NPC: " + targetNpcName); return; diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunScript.java index 918da17350..12300b0da7 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunScript.java @@ -69,7 +69,7 @@ public boolean run() { } else { var configuredNpc = Microbot.getRs2NpcCache().query() .withName(targetNpcName) - .nearest(); + .nearestOnClientThread(); if (configuredNpc == null) { Microbot.log("Unable to find NPC: " + targetNpcName); return; diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunTeleAlchScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunTeleAlchScript.java index 1070eea7c9..f2911a9ff1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunTeleAlchScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiomagic/scripts/StunTeleAlchScript.java @@ -104,7 +104,7 @@ public boolean run() { // 1) STUN target from user config using queryable NPC cache var target = Microbot.getRs2NpcCache().query() .withName(configuredTargetNpc) - .nearest(); + .nearestOnClientThread(); if (target == null) { attemptNpcRecoveryTeleport(configuredTargetNpc); return; diff --git a/src/main/java/net/runelite/client/plugins/microbot/arceuusrc/ArceuusRcPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/arceuusrc/ArceuusRcPlugin.java index f83c14d3cf..69f6028f0e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/arceuusrc/ArceuusRcPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/arceuusrc/ArceuusRcPlugin.java @@ -24,7 +24,7 @@ ) public class ArceuusRcPlugin extends Plugin { - public static final String version = "1.0.2"; + public static final String version = "1.0.3"; @Getter @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/arceuusrc/ArceuusRcScript.java b/src/main/java/net/runelite/client/plugins/microbot/arceuusrc/ArceuusRcScript.java index c1465e6ac9..1fc511ed38 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/arceuusrc/ArceuusRcScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/arceuusrc/ArceuusRcScript.java @@ -221,7 +221,7 @@ public String getAltarName() { } public void useAltar() { - var altar = Microbot.getRs2TileObjectCache().query().withName(getAltarName()).within(11).nearest(); + var altar = Microbot.getRs2TileObjectCache().query().withName(getAltarName()).within(11).nearestOnClientThread(); if (altar != null) { if (altar.click("Bind")) Rs2Inventory.waitForInventoryChanges(6_000); hasChippedEssence = Rs2Inventory.hasItem(DARK_ESSENCE_FRAGMENTS); @@ -301,7 +301,7 @@ public boolean chipEssenceFast() { } public void useDarkAltar() { - var darkAltar = Microbot.getRs2TileObjectCache().query().withName(DARK_ALTAR).within(11).nearest(); + var darkAltar = Microbot.getRs2TileObjectCache().query().withName(DARK_ALTAR).within(11).nearestOnClientThread(); if (darkAltar == null) return; darkAltar.click("Venerate"); @@ -312,7 +312,7 @@ public void mineEssence() { if(getAltar() == Altar.BLOOD && !Rs2Inventory.hasItem(BLOOD_ESSENCE_ACTIVE)){ Rs2Inventory.interact(BLOOD_ESSENCE, "Activate"); } - var runeStone = Microbot.getRs2TileObjectCache().query().withName(STR_DENSE_RUNESTONE).within(11).nearest(); + var runeStone = Microbot.getRs2TileObjectCache().query().withName(STR_DENSE_RUNESTONE).within(11).nearestOnClientThread(); if (runeStone == null) { Microbot.log("Cannot find runestone"); return; diff --git a/src/main/java/net/runelite/client/plugins/microbot/autoessencemining/AutoEssenceMiningPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/autoessencemining/AutoEssenceMiningPlugin.java index d78880e27a..de7bafc1b1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/autoessencemining/AutoEssenceMiningPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/autoessencemining/AutoEssenceMiningPlugin.java @@ -25,7 +25,7 @@ ) @Slf4j public class AutoEssenceMiningPlugin extends Plugin { - static final String version = "1.0.1"; + static final String version = "1.0.2"; @Inject private AutoEssenceMiningConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/autoessencemining/AutoEssenceMiningScript.java b/src/main/java/net/runelite/client/plugins/microbot/autoessencemining/AutoEssenceMiningScript.java index ea113cf937..f72a9cb168 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/autoessencemining/AutoEssenceMiningScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/autoessencemining/AutoEssenceMiningScript.java @@ -163,7 +163,7 @@ private void handleTeleportingWithAubury() { } // find Aubury NPC - Rs2NpcModel aubury = Microbot.getRs2NpcCache().query().withName("Aubury").nearest(); + Rs2NpcModel aubury = Microbot.getRs2NpcCache().query().withName("Aubury").nearestOnClientThread(); if (aubury != null) { log.info("Found Aubury, attempting teleport"); if (aubury.click("Teleport")) { @@ -196,7 +196,7 @@ private void handleMiningEssence() { } // find essence rock to mine - var essenceRock = Microbot.getRs2TileObjectCache().query().withName("Rune Essence").nearest(); + var essenceRock = Microbot.getRs2TileObjectCache().query().withName("Rune Essence").nearestOnClientThread(); if (essenceRock != null) { log.info("Found rune essence rock, attempting to mine"); @@ -230,7 +230,7 @@ private void handleUsingPortal() { } // find the portal to exit - var portal = Microbot.getRs2TileObjectCache().query().withName("Portal").nearest(); + var portal = Microbot.getRs2TileObjectCache().query().withName("Portal").nearestOnClientThread(); if (portal != null) { log.info("Found portal, attempting to use it"); diff --git a/src/main/java/net/runelite/client/plugins/microbot/baggedplants/BaggedPlantsPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/baggedplants/BaggedPlantsPlugin.java index 2c58428c25..251067020f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/baggedplants/BaggedPlantsPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/baggedplants/BaggedPlantsPlugin.java @@ -19,7 +19,7 @@ @PluginDescriptor( name = PluginConstants.CRANNY + "Bagged Plants", description = "Cranny's Bagged Plant Planter", - version = "1.0.0", + version = "1.0.1", minClientVersion = "1.9.8", tags = {"skilling", "construction", "farming"}, enabledByDefault = PluginConstants.DEFAULT_ENABLED, diff --git a/src/main/java/net/runelite/client/plugins/microbot/baggedplants/BaggedPlantsScript.java b/src/main/java/net/runelite/client/plugins/microbot/baggedplants/BaggedPlantsScript.java index d8d6b35a23..5015653778 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/baggedplants/BaggedPlantsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/baggedplants/BaggedPlantsScript.java @@ -168,7 +168,7 @@ private boolean hasUsableWateringCans() { private boolean isInHouse() { // Similar to gilded altar script - check if Phials NPC is not present - return Microbot.getRs2NpcCache().query().withName("Phials").nearest() == null; + return Microbot.getRs2NpcCache().query().withName("Phials").nearestOnClientThread() == null; } private void checkInventory() { @@ -244,7 +244,7 @@ private void refillSupplies() { if (!Rs2Inventory.isItemSelected()) { Rs2Inventory.use(NOTED_BAGGED_PLANT); } else { - Microbot.getRs2NpcCache().query().withName("Phials").interact("Use"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("Phials").interact("Use")); Rs2Player.waitForWalking(); } return; // Wait for dialogue to open 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..6b7106c6cc 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.0.4"; @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 8e863d4a5e..635c11d2e3 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 @@ -813,7 +813,7 @@ public void goToTheMound(Rs2WorldArea moundArea){ //strange old man body blocking us - Rs2NpcModel strangeOldMan = rs2NpcCache.query().withName("Strange Old Man").nearest(); + Rs2NpcModel strangeOldMan = rs2NpcCache.query().withName("Strange Old Man").nearestOnClientThread(); if(strangeOldMan !=null){ if(strangeOldMan.getWorldLocation() != null){ @@ -878,7 +878,7 @@ public void gainRP(BarrowsConfig config){ if(RP>870) return; - Rs2NpcModel skele = rs2NpcCache.query().withName("Skeleton").nearest(); + Rs2NpcModel skele = rs2NpcCache.query().withName("Skeleton").nearestOnClientThread(); if(skele == null || skele.isDead()) return; 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 f5762b471d..a36472d803 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 @@ -202,7 +202,7 @@ private void handleTax() { sleep(500, 1200); Rs2Bank.closeBank(); sleepUntil(() -> !Rs2Bank.isOpen()); - var blastie = Microbot.getRs2NpcCache().query().withName("Blast Furnace Foreman").nearest(); + var blastie = Microbot.getRs2NpcCache().query().withName("Blast Furnace Foreman").nearestOnClientThread(); if (blastie != null) blastie.click("Pay"); sleepUntil(Rs2Dialogue::isInDialogue, 10000); if (Rs2Dialogue.hasSelectAnOption()) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsOverlay.java index b3c092910a..1c2bd58c22 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsOverlay.java @@ -105,7 +105,7 @@ public Dimension render(Graphics2D graphics) { // Dragon tracking section addSectionDivider("Dragon Tracking"); - Rs2NpcModel nearestDragon = Microbot.getRs2NpcCache().query().withName("Blue dragon").nearest(); + Rs2NpcModel nearestDragon = Microbot.getRs2NpcCache().query().withName("Blue dragon").nearestOnClientThread(); boolean isTargeting = nearestDragon != null && script.getCurrentTargetId() != null && script.getCurrentTargetId() == nearestDragon.getId(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsPlugin.java index ffcfa38d2d..a02bb431ee 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsPlugin.java @@ -27,7 +27,7 @@ ) public class BlueDragonsPlugin extends Plugin { - public static final String version = "1.1.2"; + public static final String version = "1.1.3"; static final String CONFIG = "bluedragons"; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsScript.java b/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsScript.java index ce1d06038a..446fce8c30 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/bluedragons/BlueDragonsScript.java @@ -482,7 +482,7 @@ private boolean lootItem(String itemName) { private Rs2NpcModel getAvailableDragon() { - Rs2NpcModel dragon = Microbot.getRs2NpcCache().query().withName("Blue dragon").nearest(); + Rs2NpcModel dragon = Microbot.getRs2NpcCache().query().withName("Blue dragon").nearestOnClientThread(); logOnceToChat("Found dragon: " + (dragon != null ? "Yes (ID: " + dragon.getId() + ")" : "No"), true, config); if (dragon != null) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/cannonballsmelter/CannonballSmelterPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/cannonballsmelter/CannonballSmelterPlugin.java index 98b2ba4e22..bf4ecc81a5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cannonballsmelter/CannonballSmelterPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cannonballsmelter/CannonballSmelterPlugin.java @@ -24,7 +24,7 @@ isExternal = PluginConstants.IS_EXTERNAL ) public class CannonballSmelterPlugin extends Plugin { - public static final String version = "1.1.0"; + public static final String version = "1.1.1"; @Inject private CannonballSmelterConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/cannonballsmelter/CannonballSmelterScript.java b/src/main/java/net/runelite/client/plugins/microbot/cannonballsmelter/CannonballSmelterScript.java index 7a12b300ab..cb2b0b9a13 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cannonballsmelter/CannonballSmelterScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cannonballsmelter/CannonballSmelterScript.java @@ -103,7 +103,7 @@ public void smelt() { Rs2TileObjectModel furnace = Microbot.getRs2TileObjectCache().query().withId(config.getFurnace().furnaceID).nearest(); if(config.getFurnace() == Furnace.SHILO_VILLAGE) { - furnace = Microbot.getRs2TileObjectCache().query().withName("Furnace").nearest(); + furnace = Microbot.getRs2TileObjectCache().query().withName("Furnace").nearestOnClientThread(); } if (furnace != null) { @@ -135,7 +135,7 @@ public void bank() { if (!isRunning()) break; if(config.getFurnace() == Furnace.SHILO_VILLAGE) { - var banker = Microbot.getRs2NpcCache().query().withName("Banker").nearest(); + var banker = Microbot.getRs2NpcCache().query().withName("Banker").nearestOnClientThread(); if (banker != null) banker.click("Bank"); } else { Rs2Bank.openBank(); @@ -167,7 +167,7 @@ public void getMould() { if(!Rs2Inventory.hasItem("ammo mould") && !Rs2Inventory.hasItem("double ammo mould")) { if(!Rs2Bank.isOpen()) { if(config.getFurnace() == Furnace.SHILO_VILLAGE) { - var banker = Microbot.getRs2NpcCache().query().withName("Banker").nearest(); + var banker = Microbot.getRs2NpcCache().query().withName("Banker").nearestOnClientThread(); if (banker != null) banker.click("Bank"); } else { Rs2Bank.openBank(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/chaosaltar/ChaosAltarPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/chaosaltar/ChaosAltarPlugin.java index 559241a767..aef0c03b84 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/chaosaltar/ChaosAltarPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/chaosaltar/ChaosAltarPlugin.java @@ -27,7 +27,7 @@ ) @Slf4j public class ChaosAltarPlugin extends Plugin { - final static String version = "1.1.0"; + final static String version = "1.1.1"; @Inject private ChaosAltarScript chaosAltarScript; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/chaosaltar/ChaosAltarScript.java b/src/main/java/net/runelite/client/plugins/microbot/chaosaltar/ChaosAltarScript.java index e09c88e091..e8b1497a78 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/chaosaltar/ChaosAltarScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/chaosaltar/ChaosAltarScript.java @@ -115,7 +115,7 @@ private void dieToNpc() { Microbot.log("Walking to dangerous NPC to die"); Rs2Walker.walkTo(2979, 3845, 0); sleepUntil(() -> Microbot.getRs2NpcCache().query().withId(CHAOS_FANATIC).nearest() != null, 60000); - Microbot.getRs2NpcCache().query().withName("Chaos Fanatic").interact("Attack"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("Chaos Fanatic").interact("Attack")); // Wait until player dies sleepUntil(() -> Microbot.getClient().getBoostedSkillLevel(Skill.HITPOINTS) == 0, 60000); sleepUntil(() -> !Rs2Pvp.isInWilderness(), 15000); diff --git a/src/main/java/net/runelite/client/plugins/microbot/chartercrafter/CharterCrafterPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/chartercrafter/CharterCrafterPlugin.java index 3868f07eb1..b6d093fa1c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/chartercrafter/CharterCrafterPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/chartercrafter/CharterCrafterPlugin.java @@ -28,7 +28,7 @@ ) @Slf4j public class CharterCrafterPlugin extends Plugin { - static final String version = "1.0.0"; + static final String version = "1.0.1"; @Inject private CharterCrafterConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/chartercrafter/CharterCrafterScript.java b/src/main/java/net/runelite/client/plugins/microbot/chartercrafter/CharterCrafterScript.java index f74becb042..c2143dea70 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/chartercrafter/CharterCrafterScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/chartercrafter/CharterCrafterScript.java @@ -163,7 +163,7 @@ private void bootstrap() { Rs2Inventory.dropAll("Empty light orb", "Light orb"); } - Rs2NpcModel trader = Microbot.getRs2NpcCache().query().withName(TRADER_NAME).nearest(); + Rs2NpcModel trader = Microbot.getRs2NpcCache().query().withName(TRADER_NAME).nearestOnClientThread(); if (trader == null) { update("Bootstrap", "Trader not nearby", false, true); state = State.STOP; diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/ClueSolverPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/ClueSolverPlugin.java index 41b811126c..365f8200ce 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/ClueSolverPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/ClueSolverPlugin.java @@ -29,7 +29,7 @@ @PluginDependency(ClueScrollPlugin.class) public class ClueSolverPlugin extends Plugin { - final static String version = "1.0.1"; + final static String version = "1.0.2"; @Inject private ClueSolverScript clueSolverScript; diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/AnagramClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/AnagramClueTask.java index 32a71ae36e..dc2ae85082 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/AnagramClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/AnagramClueTask.java @@ -133,7 +133,7 @@ private void processGameTick(GameTick event) { private void transitionToInteractionState() { if (clue.getObjectId() != -1) { state = State.INTERACTING_WITH_OBJECT; - } else if (clue.getNpcProvider() != null && Microbot.getRs2NpcCache().query().withName(clue.getNpcName(clueScrollPlugin)).nearest() != null) { + } else if (clue.getNpcProvider() != null && Microbot.getRs2NpcCache().query().withName(clue.getNpcName(clueScrollPlugin)).nearestOnClientThread() != null) { state = State.INTERACTING_WITH_NPC; } else { log.warn("No valid interaction target found."); @@ -158,7 +158,7 @@ private boolean interactWithObject() { } private boolean interactWithNpc() { - var targetNpc = Microbot.getRs2NpcCache().query().withName(clue.getNpcName(clueScrollPlugin)).nearest(); + var targetNpc = Microbot.getRs2NpcCache().query().withName(clue.getNpcName(clueScrollPlugin)).nearestOnClientThread(); if (targetNpc == null) { log.warn("NPC {} not found.", clue.getNpcName(clueScrollPlugin)); return false; diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CoordinateClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CoordinateClueTask.java index f21cb49b8c..8cc082c4e5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CoordinateClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CoordinateClueTask.java @@ -134,7 +134,7 @@ private void processGameTick(GameTick event) { } private boolean engageEnemy() { - Rs2NpcModel targetNpc = Microbot.getRs2NpcCache().query().withName(enemy.getText()).nearest(); + Rs2NpcModel targetNpc = Microbot.getRs2NpcCache().query().withName(enemy.getText()).nearestOnClientThread(); if (targetNpc == null) { log.warn("Expected enemy not found."); completeTask(false); diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CrypticClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CrypticClueTask.java index 68b1cf166d..9aeea69c35 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CrypticClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CrypticClueTask.java @@ -155,7 +155,7 @@ private void transitionToNextState() { state = State.KILLING_ENEMY; } else if (clue.getObjectId() != -1) { state = State.INTERACTING_WITH_OBJECT; - } else if (clue.getNpc(clueScrollPlugin) != null && Microbot.getRs2NpcCache().query().withName(clue.getNpc(clueScrollPlugin)).nearest() != null) { + } else if (clue.getNpc(clueScrollPlugin) != null && Microbot.getRs2NpcCache().query().withName(clue.getNpc(clueScrollPlugin)).nearestOnClientThread() != null) { state = State.INTERACTING_WITH_NPC; } else { state = State.COMPLETED; @@ -164,7 +164,7 @@ private void transitionToNextState() { } private boolean killEnemy() { - Rs2NpcModel enemy = Microbot.getRs2NpcCache().query().withName(clue.getEnemy().name()).nearest(); + Rs2NpcModel enemy = Microbot.getRs2NpcCache().query().withName(clue.getEnemy().name()).nearestOnClientThread(); if (enemy == null || enemy.getNpc().getHealthRatio() <= 0) { log.info("Enemy {} is defeated. Searching for loot.", clue.getEnemy()); return true; @@ -207,7 +207,7 @@ private boolean interactWithObject() { } private boolean interactWithNpc() { - Rs2NpcModel targetNpc = Microbot.getRs2NpcCache().query().withName(clue.getNpc(clueScrollPlugin)).nearest(); + Rs2NpcModel targetNpc = Microbot.getRs2NpcCache().query().withName(clue.getNpc(clueScrollPlugin)).nearestOnClientThread(); if (targetNpc == null) { log.warn("NPC {} not found at the location.", clue.getNpc(clueScrollPlugin)); return false; diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/EmoteClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/EmoteClueTask.java index de7f151bdb..e0c2d4ee33 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/EmoteClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/EmoteClueTask.java @@ -177,7 +177,7 @@ private void performEmote(String emoteName) { } private void interactWithUri() { - Microbot.getRs2NpcCache().query().withName("Uri").interact("Talk-to"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("Uri").interact("Talk-to")); log.info("Interacted with Uri."); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/FaloTheBardClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/FaloTheBardClueTask.java index 5d4aa968db..227177e01b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/FaloTheBardClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/FaloTheBardClueTask.java @@ -110,7 +110,8 @@ private boolean isPlayerAtLocation() { private boolean interactWithNpc() { log.info("Interacting with Falo the Bard NPC."); - return Microbot.getRs2NpcCache().query().withName("Falo the Bard").interact("Talk-to"); + var falo = Microbot.getRs2NpcCache().query().withName("Falo the Bard").nearestOnClientThread(); + return falo != null && falo.click("Talk-to"); } private boolean confirmClueCompletion() { diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MusicClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MusicClueTask.java index 69d1a782c5..aaaa1fd3e7 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MusicClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MusicClueTask.java @@ -143,7 +143,7 @@ private boolean playSong() { } private boolean interactWithNpc() { - Rs2NpcModel npc = Microbot.getRs2NpcCache().query().withName(npcName).nearest(); + Rs2NpcModel npc = Microbot.getRs2NpcCache().query().withName(npcName).nearestOnClientThread(); if (npc == null) { log.warn("NPC {} not found near the clue location.", npcName); return false; diff --git a/src/main/java/net/runelite/client/plugins/microbot/construction/ConstructionPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/construction/ConstructionPlugin.java index 360f929caf..74b564646a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/construction/ConstructionPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/construction/ConstructionPlugin.java @@ -29,7 +29,7 @@ ) @Slf4j public class ConstructionPlugin extends Plugin { - public static final String version = "1.3.1"; + public static final String version = "1.3.2"; @Inject private net.runelite.client.plugins.microbot.construction.ConstructionConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/construction/ConstructionScript.java b/src/main/java/net/runelite/client/plugins/microbot/construction/ConstructionScript.java index 69a11b8cf0..34579c375c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/construction/ConstructionScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/construction/ConstructionScript.java @@ -40,7 +40,7 @@ public Rs2TileObjectModel getClosestTile(List objIDs) { } public Rs2NpcModel getButler() { - return Microbot.getRs2NpcCache().query().withName("Demon butler").nearest(); + return Microbot.getRs2NpcCache().query().withName("Demon butler").nearestOnClientThread(); } public boolean hasDialogueOptionToUnnote() { @@ -178,7 +178,7 @@ private void calculateState(net.runelite.client.plugins.microbot.construction.Co } private void returnToTheHouse(){ - Rs2TileObjectModel housePortal = Microbot.getRs2TileObjectCache().query().withName("Portal").nearest(); + Rs2TileObjectModel housePortal = Microbot.getRs2TileObjectCache().query().withName("Portal").nearestOnClientThread(); if(housePortal != null){ if(housePortal.click("Build mode")){ sleepUntil(()-> Rs2Player.getWorldLocation() != null diff --git a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPlugin.java index ad6af4e631..3b16a62c44 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPlugin.java @@ -30,7 +30,7 @@ ) @Slf4j public class FarmTreeRunPlugin extends Plugin { - public static final String version = "1.1.0"; + public static final String version = "1.1.1"; @Inject private FarmTreeRunConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java index e8aa465b90..7caf0272ae 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java @@ -1000,9 +1000,9 @@ private static int getSaplingToUse(Patch patch, FarmTreeRunConfig config) { * @return true if gardener interaction successful, else false */ private void handleExoticGardeners() { - var nikkie = Microbot.getRs2NpcCache().query().withName("Nikkie").nearest(); + var nikkie = Microbot.getRs2NpcCache().query().withName("Nikkie").nearestOnClientThread(); - var rosie = Microbot.getRs2NpcCache().query().withName("Rosie").nearest(); + var rosie = Microbot.getRs2NpcCache().query().withName("Rosie").nearestOnClientThread(); String paymentAction = ""; net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel npcToInteract = null; diff --git a/src/main/java/net/runelite/client/plugins/microbot/fishingtrawler/FishingTrawlerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/fishingtrawler/FishingTrawlerPlugin.java index c8f0c18ddd..da7fe939d6 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/fishingtrawler/FishingTrawlerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/fishingtrawler/FishingTrawlerPlugin.java @@ -25,7 +25,7 @@ ) @Slf4j public class FishingTrawlerPlugin extends Plugin { - public static final String version = "1.0.0"; + public static final String version = "1.0.1"; @Inject private FishingTrawlerConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/fishingtrawler/FishingTrawlerScript.java b/src/main/java/net/runelite/client/plugins/microbot/fishingtrawler/FishingTrawlerScript.java index 301cd436e3..a60f2f9371 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/fishingtrawler/FishingTrawlerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/fishingtrawler/FishingTrawlerScript.java @@ -110,7 +110,7 @@ public boolean run(FishingTrawlerConfig config) { if (!Rs2Player.isInteracting() && !Rs2Player.isMoving()) { log.debug("Tentacle phase"); - Rs2NpcModel tentacleNpc = Microbot.getRs2NpcCache().query().withName("Enormous Tentacle").nearest(); + Rs2NpcModel tentacleNpc = Microbot.getRs2NpcCache().query().withName("Enormous Tentacle").nearestOnClientThread(); if (tentacleNpc != null) { if (!tentacle) { @@ -118,9 +118,9 @@ public boolean run(FishingTrawlerConfig config) { Rs2Camera.turnTo(tentacleNpc.getNpc()); } sleepUntil(() -> tentacleNpc.getNpc().getAnimation() == 8953, 10000); - Microbot.getRs2NpcCache().query().withName("Enormous Tentacle").interact("Chop"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("Enormous Tentacle").interact("Chop")); sleepUntilTick(2); - if (!Rs2Player.isInteracting()) Microbot.getRs2NpcCache().query().withName("Enormous Tentacle").interact("Chop"); + if (!Rs2Player.isInteracting()) Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("Enormous Tentacle").interact("Chop")); tentacle = true; wasInsideBoat = true; sleepUntil(() -> !Rs2Player.isInteracting()); diff --git a/src/main/java/net/runelite/client/plugins/microbot/flipperschaser/FlippersChaserPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/flipperschaser/FlippersChaserPlugin.java index fde7ed1342..b5ca60d8d2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/flipperschaser/FlippersChaserPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/flipperschaser/FlippersChaserPlugin.java @@ -48,7 +48,7 @@ ) @Slf4j public class FlippersChaserPlugin extends Plugin { - public static final String version = "1.0.1"; + public static final String version = "1.0.2"; @Inject private Client client; @@ -127,7 +127,7 @@ private void useFishingExplosive() { } private Rs2NpcModel findFishingSpot() { - return Microbot.getRs2NpcCache().query().withName("Ominous Fishing Spot").nearest(); + return Microbot.getRs2NpcCache().query().withName("Ominous Fishing Spot").nearestOnClientThread(); } private void attackNpc(Rs2NpcModel npc) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/frostyrc/RcPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/frostyrc/RcPlugin.java index 15e7eb9423..02fb09f7f9 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/frostyrc/RcPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/frostyrc/RcPlugin.java @@ -36,7 +36,7 @@ isExternal = PluginConstants.IS_EXTERNAL ) public class RcPlugin extends Plugin { - public static final String version = "1.1.3"; + public static final String version = "1.1.4"; @Inject private RcConfig config; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/frostyrc/RcScript.java b/src/main/java/net/runelite/client/plugins/microbot/frostyrc/RcScript.java index 739409caf1..9ae7dca815 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/frostyrc/RcScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/frostyrc/RcScript.java @@ -479,7 +479,7 @@ private void handleWrathWalking() { sleepUntil(() -> plugin.getMyWorldPoint().getRegionID() == mythicStatueRegion); sleepGaussian(600, 200); - Rs2TileObjectModel statue = Microbot.getRs2TileObjectCache().query().withName("Mythic Statue").nearest(); + Rs2TileObjectModel statue = Microbot.getRs2TileObjectCache().query().withName("Mythic Statue").nearestOnClientThread(); if (statue != null && !Rs2Player.isAnimating()) { statue.click("Teleport"); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryPlugin.java index e5258580e5..3b013254bf 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryPlugin.java @@ -28,7 +28,7 @@ @Slf4j public class GiantsFoundryPlugin extends Plugin { - public static final String version = "1.0.6"; + public static final String version = "1.0.7"; @Inject private GiantsFoundryConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryScript.java b/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryScript.java index 64d31fbdee..4c8bffbd8c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryScript.java @@ -103,7 +103,8 @@ public boolean hasCommission() { public void getCommission() { if (!hasCommission()) { GiantsFoundryState.reset(); - if (Microbot.getRs2NpcCache().query().withName("kovac").interact("Commission")) + var kovac = Microbot.getRs2NpcCache().query().withName("kovac").nearestOnClientThread(); + if (kovac != null && kovac.click("Commission")) sleepUntil(this::hasCommission, 5000); } } @@ -330,7 +331,7 @@ public void craftWeapon() { } private void handIn() { - Microbot.getRs2NpcCache().query().withName("kovac").interact("Hand-in"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("kovac").interact("Hand-in")); } } \ No newline at end of file diff --git a/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryState.java b/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryState.java index 4de6a49df4..e33fe7a168 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryState.java +++ b/src/main/java/net/runelite/client/plugins/microbot/giantsfoundry/GiantsFoundryState.java @@ -126,11 +126,11 @@ public static List getStages() { public static Rs2TileObjectModel getStageObject(Stage stage) { switch (stage) { case TRIP_HAMMER: - return Microbot.getRs2TileObjectCache().query().withName("trip hammer").nearest(); + return Microbot.getRs2TileObjectCache().query().withName("trip hammer").nearestOnClientThread(); case GRINDSTONE: - return Microbot.getRs2TileObjectCache().query().withName("grindstone").nearest(); + return Microbot.getRs2TileObjectCache().query().withName("grindstone").nearestOnClientThread(); case POLISHING_WHEEL: - return Microbot.getRs2TileObjectCache().query().withName("polishing wheel").nearest(); + return Microbot.getRs2TileObjectCache().query().withName("polishing wheel").nearestOnClientThread(); } return null; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/gildedaltar/GildedAltarPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/gildedaltar/GildedAltarPlugin.java index 2b5af7c259..183551e99e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/gildedaltar/GildedAltarPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/gildedaltar/GildedAltarPlugin.java @@ -27,7 +27,7 @@ ) @Slf4j public class GildedAltarPlugin extends Plugin { - public static final String version = "1.0.0"; + public static final String version = "1.0.1"; @Inject private GildedAltarConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/gildedaltar/GildedAltarScript.java b/src/main/java/net/runelite/client/plugins/microbot/gildedaltar/GildedAltarScript.java index 0241b7b34a..dff8ba4b6b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/gildedaltar/GildedAltarScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/gildedaltar/GildedAltarScript.java @@ -35,7 +35,7 @@ public class GildedAltarScript extends Script { public static GildedAltarPlayerState state = GildedAltarPlayerState.IDLE; private boolean inHouse() { - return Microbot.getRs2NpcCache().query().withName("Phials").nearest() == null; + return Microbot.getRs2NpcCache().query().withName("Phials").nearestOnClientThread() == null; } private boolean hasUnNotedBones() { @@ -155,7 +155,7 @@ public void unnoteBones() { if (!Rs2Inventory.isItemSelected()) { Rs2Inventory.use("bones"); } else { - Microbot.getRs2NpcCache().query().withName("Phials").interact("Use"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("Phials").interact("Use")); Rs2Player.waitForWalking(); } } else if (Microbot.getClient().getWidget(14352385) != null) { @@ -242,7 +242,7 @@ public void bonesOnAltar() { } - Rs2TileObjectModel altar = Microbot.getRs2TileObjectCache().query().withName("Altar").nearest(); + Rs2TileObjectModel altar = Microbot.getRs2TileObjectCache().query().withName("Altar").nearestOnClientThread(); if (altar != null) { Rs2Inventory.useUnNotedItemOnObject("bones", altar.getId()); Rs2Player.waitForAnimation(); 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..1cd50f0b57 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.2"; @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 650d9a813d..9c4bc4a497 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 @@ -294,7 +294,7 @@ private boolean repairCells() { private boolean powerUpGreatGuardian() { if (Rs2Inventory.hasItem("guardian stone") && !shouldMineGuardianRemains && !isInLargeMine() && !isInHugeMine()) { state = GotrState.POWERING_UP; - Microbot.getRs2NpcCache().query().withName("The great guardian").interact("power-up"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("The great guardian").interact("power-up")); log("Powering up the great guardian..."); sleepUntil(Rs2Player::isAnimating); sleep(Rs2Random.randomGaussian(Rs2Random.between(1000, 2000), Rs2Random.between(100, 300))); diff --git a/src/main/java/net/runelite/client/plugins/microbot/herbiboar/HerbiboarPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/herbiboar/HerbiboarPlugin.java index fb0c6e062c..16c99379e0 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/herbiboar/HerbiboarPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/herbiboar/HerbiboarPlugin.java @@ -42,7 +42,7 @@ ) public class HerbiboarPlugin extends Plugin { - static final String version = "1.2.4"; + static final String version = "1.2.5"; @Getter @Setter diff --git a/src/main/java/net/runelite/client/plugins/microbot/herbiboar/HerbiboarScript.java b/src/main/java/net/runelite/client/plugins/microbot/herbiboar/HerbiboarScript.java index 9e8d6a340d..03f1394f84 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/herbiboar/HerbiboarScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/herbiboar/HerbiboarScript.java @@ -583,7 +583,7 @@ public boolean run(HerbiboarConfig config, HerbiboarPlugin herbiboarPlugin) { case TUNNEL: Microbot.status = "Attacking tunnel"; Microbot.log(Level.INFO,"Attacking tunnel"); - if (!attackedTunnel || (Microbot.getRs2NpcCache().query().withName("Herbiboar").nearest() == null && attackedTunnel)) { + if (!attackedTunnel || (Microbot.getRs2NpcCache().query().withName("Herbiboar").nearestOnClientThread() == null && attackedTunnel)) { int finishId = herbiboarPlugin.getFinishId(); if (finishId > 0) { WorldPoint finishLoc = herbiboarPlugin.getEndLocations().get(finishId - 1); @@ -601,14 +601,14 @@ public boolean run(HerbiboarConfig config, HerbiboarPlugin herbiboarPlugin) { } } } else { - Rs2NpcModel herbCheck = Microbot.getRs2NpcCache().query().withName("Herbiboar").nearest(); + Rs2NpcModel herbCheck = Microbot.getRs2NpcCache().query().withName("Herbiboar").nearestOnClientThread(); if (herbCheck != null) setState(HerbiboarState.HARVEST); } break; case HARVEST: Microbot.status = "Harvesting herbiboar"; Microbot.log(Level.INFO,"Harvesting herbiboar"); - Rs2NpcModel herb = Microbot.getRs2NpcCache().query().withName("Herbiboar").nearest(); + Rs2NpcModel herb = Microbot.getRs2NpcCache().query().withName("Herbiboar").nearestOnClientThread(); if (herb != null) { WorldPoint loc = herb.getWorldLocation(); if (Rs2Player.getWorldLocation().distanceTo(loc) <= 8) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/herbrun/HerbrunPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/herbrun/HerbrunPlugin.java index becea34e9e..d90f0d0d1b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/herbrun/HerbrunPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/herbrun/HerbrunPlugin.java @@ -26,7 +26,7 @@ @Slf4j public class HerbrunPlugin extends Plugin { - public static final String version = "1.1.0"; + public static final String version = "1.1.1"; @Inject private HerbrunConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/herbrun/HerbrunScript.java b/src/main/java/net/runelite/client/plugins/microbot/herbrun/HerbrunScript.java index 0fc44ae10d..fc5fbbc17d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/herbrun/HerbrunScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/herbrun/HerbrunScript.java @@ -159,7 +159,7 @@ private void getNextPatch() { private boolean handleHerbPatch() { if (Rs2Inventory.isFull()) { - Rs2NpcModel leprechaun = Microbot.getRs2NpcCache().query().withName("Tool leprechaun").nearest(); + Rs2NpcModel leprechaun = Microbot.getRs2NpcCache().query().withName("Tool leprechaun").nearestOnClientThread(); if (leprechaun != null) { Rs2ItemModel unNoted = Rs2Inventory.getUnNotedItem("Grimy", false); if (unNoted != null) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/housetab/HouseTabPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/housetab/HouseTabPlugin.java index 76a18fe948..3f93bacfa1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/housetab/HouseTabPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/housetab/HouseTabPlugin.java @@ -29,7 +29,7 @@ ) @Slf4j public class HouseTabPlugin extends Plugin { - public static final String version = "1.0.8"; + public static final String version = "1.0.9"; @Inject private HouseTabConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/housetab/HouseTabScript.java b/src/main/java/net/runelite/client/plugins/microbot/housetab/HouseTabScript.java index b75f6fbfa6..c0d2333352 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/housetab/HouseTabScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/housetab/HouseTabScript.java @@ -181,12 +181,14 @@ public void unnoteClay() { if (hasSoftClay() || Microbot.getRs2TileObjectCache().query().withId(HOUSE_ADVERTISEMENT_OBJECT).nearest() == null) return; if (Microbot.getClient().getWidget(14352385) == null) { - do { + while (true) { Microbot.getClientThread().invoke(() -> { Rs2Inventory.use("Soft clay"); }); sleep(300, 380); - } while (!Microbot.getRs2NpcCache().query().withName("Phials").interact("Use")); + var phials = Microbot.getRs2NpcCache().query().withName("Phials").nearestOnClientThread(); + if (phials != null && phials.click("Use")) break; + } } sleep(2500, 5000); diff --git a/src/main/java/net/runelite/client/plugins/microbot/housethieving/HouseThievingPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/housethieving/HouseThievingPlugin.java index 9dfddff0f5..5f3c666818 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/housethieving/HouseThievingPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/housethieving/HouseThievingPlugin.java @@ -26,7 +26,7 @@ ) @Slf4j public class HouseThievingPlugin extends Plugin { - public final static String version = "1.0.2"; + public final static String version = "1.0.3"; @Inject private HouseThievingConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/housethieving/HouseThievingScript.java b/src/main/java/net/runelite/client/plugins/microbot/housethieving/HouseThievingScript.java index c44716e4e9..aaec16a5dc 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/housethieving/HouseThievingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/housethieving/HouseThievingScript.java @@ -89,7 +89,7 @@ public boolean run(HouseThievingConfig config) { if (currentThievingHouse == null) currentThievingHouse = getThievingHouse(); - var houseNpc = Microbot.getRs2NpcCache().query().withName(currentThievingHouse.npcName).nearest(); + var houseNpc = Microbot.getRs2NpcCache().query().withName(currentThievingHouse.npcName).nearestOnClientThread(); switch (state) { case PICKPOCKETING: handlePickPocketing(config); @@ -154,7 +154,7 @@ private void handlePickPocketing(HouseThievingConfig config) { } } - var aureliaNpc = Microbot.getRs2NpcCache().query().withName("Aurelia").nearest(); + var aureliaNpc = Microbot.getRs2NpcCache().query().withName("Aurelia").nearestOnClientThread(); Rs2NpcModel distractedWealthyCitizen = null; if (aureliaNpc != null) { var aureliaAnim = aureliaNpc.getAnimation(); @@ -163,7 +163,7 @@ private void handlePickPocketing(HouseThievingConfig config) { if (distractedWealthyCitizen != null) pickpocketNpc = null; } else if (pickpocketNpc == null) { - var nearbyWealthyCitizens = Microbot.getRs2NpcCache().query().withName(WEALTHY_CITIZEN).toList().stream(); + var nearbyWealthyCitizens = Microbot.getRs2NpcCache().query().withName(WEALTHY_CITIZEN).toListOnClientThread().stream(); var aureliaLocation = aureliaNpc.getWorldLocation(); var closestWealthyCitizen = nearbyWealthyCitizens.min(Comparator.comparingInt(a -> a.getWorldLocation().distanceTo(aureliaLocation))); closestWealthyCitizen.ifPresent(rs2NpcModel -> pickpocketNpc = rs2NpcModel); @@ -253,7 +253,7 @@ private void handleFindingHouse(HouseThievingConfig config) { currentThievingHouse.lockedDoorEgress.getY()); if (lockedDoorTile != null) { var wallObject = lockedDoorTile.getWallObject(); - var houseNpc = Microbot.getRs2NpcCache().query().withName(currentThievingHouse.npcName).nearest(); + var houseNpc = Microbot.getRs2NpcCache().query().withName(currentThievingHouse.npcName).nearestOnClientThread(); if (wallObject != null && wallObject.getId() == LOCKED_DOOR_ID) { Microbot.log(currentThievingHouse.npcName + " house can be thieved", Level.INFO); attemptWaitForHouseNpc(houseNpc); diff --git a/src/main/java/net/runelite/client/plugins/microbot/hunterKabbits/HunterKabbitsScript.java b/src/main/java/net/runelite/client/plugins/microbot/hunterKabbits/HunterKabbitsScript.java index e958c2ff97..da77a7b297 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/hunterKabbits/HunterKabbitsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/hunterKabbits/HunterKabbitsScript.java @@ -155,7 +155,7 @@ private void handleRetrievingState(HunterKebbitsConfig config) { private void handleCatchingState(HunterKebbitsConfig config) { String npcName = getKebbit(config).getNpcName(); - Rs2NpcModel kebbit = Microbot.getRs2NpcCache().query().withName(npcName).nearest(); + Rs2NpcModel kebbit = Microbot.getRs2NpcCache().query().withName(npcName).nearestOnClientThread(); if (kebbit != null && kebbit.click("Catch")) { boolean falconActive = false; for (int i = 0; i < 10; i++) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/hunterKabbits/HunterKebbitsPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/hunterKabbits/HunterKebbitsPlugin.java index 6a9e1a2069..bf4d34589f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/hunterKabbits/HunterKebbitsPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/hunterKabbits/HunterKebbitsPlugin.java @@ -32,7 +32,7 @@ ) public class HunterKebbitsPlugin extends Plugin { - public static final String version = "1.1.0"; + public static final String version = "1.1.1"; @Inject private Client client; diff --git a/src/main/java/net/runelite/client/plugins/microbot/kittentracker/FeedKittenEvent.java b/src/main/java/net/runelite/client/plugins/microbot/kittentracker/FeedKittenEvent.java index bd9a47e247..64c71abff5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/kittentracker/FeedKittenEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/kittentracker/FeedKittenEvent.java @@ -27,7 +27,7 @@ public boolean validate() { @Override public boolean execute() { - Microbot.getRs2NpcCache().query().withName("Kitten").toList().stream().findFirst().ifPresent(kitten -> Rs2Inventory.useItemOnNpc(ItemID.TBWT_RAW_KARAMBWANJI, kitten.getNpc())); + Microbot.getRs2NpcCache().query().withName("Kitten").toListOnClientThread().stream().findFirst().ifPresent(kitten -> Rs2Inventory.useItemOnNpc(ItemID.TBWT_RAW_KARAMBWANJI, kitten.getNpc())); Global.sleepUntil(() -> (KittenPlugin.HUNGRY_FIRST_WARNING_TIME_LEFT_IN_SECONDS * 1000) < kittenPlugin.getTimeBeforeHungry()); return true; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenAttentionEvent.java b/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenAttentionEvent.java index c34b396014..47555f7bae 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenAttentionEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenAttentionEvent.java @@ -26,7 +26,7 @@ public boolean validate() @Override public boolean execute() { - Microbot.getRs2NpcCache().query().withName("Kitten").toList().stream().findFirst().ifPresent(kitten -> kitten.click("Interact")); + Microbot.getRs2NpcCache().query().withName("Kitten").toListOnClientThread().stream().findFirst().ifPresent(kitten -> kitten.click("Interact")); if (Rs2Dialogue.sleepUntilHasDialogueOption("Stroke")) { Rs2Dialogue.clickOption("Stroke"); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenPlugin.java index 7c11945068..54a8ff2ec3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenPlugin.java @@ -71,7 +71,7 @@ isExternal = PluginConstants.IS_EXTERNAL ) public class KittenPlugin extends Plugin { - public static final String version = "1.3.0"; + public static final String version = "1.3.1"; private static final int VAR_PLAYER_FOLLOWER = 447; private static final int WIDGET_ID_DIALOG_NOTIFICATION_GROUP_ID = 229; private static final int WIDGET_ID_DIALOG_PLAYER_TEXT = 6; diff --git a/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenScript.java b/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenScript.java index 725da3d12e..5e6cc25b7e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/kittentracker/KittenScript.java @@ -43,12 +43,12 @@ private void handleKittenNeeds(KittenConfig config) { } private void feedKitten() { - Microbot.getRs2NpcCache().query().withName("Kitten").toList().stream().findFirst().ifPresent(kitten -> Rs2Inventory.useItemOnNpc(ItemID.TBWT_RAW_KARAMBWANJI, kitten.getNpc())); + Microbot.getRs2NpcCache().query().withName("Kitten").toListOnClientThread().stream().findFirst().ifPresent(kitten -> Rs2Inventory.useItemOnNpc(ItemID.TBWT_RAW_KARAMBWANJI, kitten.getNpc())); sleep(1000, 2000); } private void giveKittenAttention() { - Microbot.getRs2NpcCache().query().withName("Kitten").toList().stream().findFirst().ifPresent(kitten -> Rs2Inventory.useItemOnNpc(ItemID.BALL_OF_WOOL, kitten.getNpc())); + Microbot.getRs2NpcCache().query().withName("Kitten").toListOnClientThread().stream().findFirst().ifPresent(kitten -> Rs2Inventory.useItemOnNpc(ItemID.BALL_OF_WOOL, kitten.getNpc())); sleep(1000, 2000); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/looter/AutoLooterPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/looter/AutoLooterPlugin.java index 8bc8851f0a..80f4f14c21 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/looter/AutoLooterPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/looter/AutoLooterPlugin.java @@ -25,7 +25,7 @@ isExternal = PluginConstants.IS_EXTERNAL ) public class AutoLooterPlugin extends Plugin { - public static final String version = "1.1.1"; + public static final String version = "1.1.2"; @Inject DefaultScript defaultScript; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/FlaxScript.java b/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/FlaxScript.java index 556979bc91..bfc8a7a14c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/FlaxScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/FlaxScript.java @@ -53,7 +53,7 @@ public boolean run(AutoLooterConfig config) { } if (config.worldHop() && Rs2Player.hopIfPlayerDetected(1, 10, 10)) return; - Rs2TileObjectModel flaxObject = Microbot.getRs2TileObjectCache().query().withName("flax").within(initialPlayerLocation, config.distanceToStray()).nearest(); + Rs2TileObjectModel flaxObject = Microbot.getRs2TileObjectCache().query().withName("flax").within(initialPlayerLocation, config.distanceToStray()).nearestOnClientThread(); if (flaxObject != null) { if(flaxObject.click("pick")){ Rs2Antiban.actionCooldown(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/lunartablets/LunarTabletsPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/lunartablets/LunarTabletsPlugin.java index 4463542cfd..1c703db01b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/lunartablets/LunarTabletsPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/lunartablets/LunarTabletsPlugin.java @@ -29,7 +29,7 @@ ) @Slf4j public class LunarTabletsPlugin extends Plugin { - public static final String version = "2.0.1"; + public static final String version = "2.0.2"; @Inject private LunarTabletsConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/lunartablets/LunarTabletsScript.java b/src/main/java/net/runelite/client/plugins/microbot/lunartablets/LunarTabletsScript.java index 2ca641be70..ad193326b2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/lunartablets/LunarTabletsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/lunartablets/LunarTabletsScript.java @@ -135,7 +135,8 @@ public void makeTablets(){ } } else { // interact with lecturn - if(Microbot.getRs2TileObjectCache().query().withName("Lectern").interact("Study")){ + var lectern = Microbot.getRs2TileObjectCache().query().withName("Lectern").nearestOnClientThread(); + if(lectern != null && lectern.click("Study")){ sleep(generateRandomNumber(0,1000)); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java index f37affa40b..365608cfa4 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java @@ -26,7 +26,7 @@ ) @Slf4j public class MageTrainingArenaPlugin extends Plugin { - public static final String version = "1.1.5"; + public static final String version = "1.1.6"; @Inject private MageTrainingArenaConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java b/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java index c2cab74f59..3bc2792d7e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java @@ -544,7 +544,7 @@ private void handleAlchemistRoom() { } if (room.getSuggestion() == null) { - Microbot.getRs2TileObjectCache().query().withName("Cupboard").interact("Search"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2TileObjectCache().query().withName("Cupboard").interact("Search")); sleep(300,600); if (sleepUntilTrue(Rs2Player::isMoving, 100, 1000)) diff --git a/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesPlugin.java index 2b7b912ca4..fc4d12fe4d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesPlugin.java @@ -55,7 +55,7 @@ isExternal = PluginConstants.IS_EXTERNAL ) public class MahoganyHomesPlugin extends Plugin { - public static final String version = "0.0.10"; + public static final String version = "0.0.11"; private static final List PLANKS = Arrays.asList(ItemID.PLANK, ItemID.OAK_PLANK, ItemID.TEAK_PLANK, ItemID.MAHOGANY_PLANK); private static final List PLANK_NAMES = Arrays.asList("Plank", "Oak plank", "Teak plank", "Mahogany plank"); private static final Map MAHOGANY_HOMES_REPAIRS = new HashMap<>(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScript.java b/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScript.java index a3e7cb8512..d3ceb90412 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mahoganyhomez/MahoganyHomesScript.java @@ -351,7 +351,7 @@ private void getNewContract() { // Search for Mahogany Homes contract NPCs directly by name - var npc = Microbot.getRs2NpcCache().query().withNames("Amy", "Marlo", "Ellie", "Angelo").nearest(); + var npc = Microbot.getRs2NpcCache().query().withNames("Amy", "Marlo", "Ellie", "Angelo").nearestOnClientThread(); if (npc == null) { log("No contract NPC found, waiting before retry"); diff --git a/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessPlugin.java index fe70bab933..b7bdf07dab 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessPlugin.java @@ -38,7 +38,7 @@ @Slf4j public class TheMessPlugin extends Plugin { - static final String version = "1.0.2"; + static final String version = "1.0.3"; @Inject private TheMessConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessScript.java b/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessScript.java index d2d2650204..58a8c0fa41 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessScript.java @@ -143,7 +143,7 @@ private void handleState() { return; } if (Rs2GameObject.canReach(BUFFET_TABLE_LOC)) { - Microbot.getRs2TileObjectCache().query().withName("Buffet table").interact("Serve"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2TileObjectCache().query().withName("Buffet table").interact("Serve")); sleepUntil(() -> Rs2Player.waitForXpDrop(Skill.COOKING), 10000); } else { debug("Cannot reach the buffet table, waiting..."); diff --git a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtPlugin.java index a02030527b..32a7ee709f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtPlugin.java @@ -42,7 +42,7 @@ ) @Slf4j public class MKE_WintertodtPlugin extends Plugin { - static final String version = "2.1.2"; + static final String version = "2.1.3"; // Core plugin components @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java index 6f5de4f22a..cf36784a2c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java @@ -2893,7 +2893,7 @@ private boolean useBreadmaNpcForPotions(int concoctions, int herbs) { // Find Brew'ma NPC Rs2NpcModel brewmaNpc = Microbot.getRs2NpcCache().query().withName("Brew'ma") - .where(n -> n.hasLineOfSight()).nearest(); + .where(n -> n.hasLineOfSight()).nearestOnClientThread(); if (brewmaNpc == null) { Microbot.log("Could not find Brew'ma NPC - walking closer"); Rs2Walker.walkFastCanvas(BREWMA_NPC_INTERACT_LOCATION); @@ -5205,7 +5205,8 @@ private boolean interactWithRewardCart() { lastRewardCartInteraction = System.currentTimeMillis(); // Try to interact with reward cart by searching for the "Reward" text on the object - if (Microbot.getRs2TileObjectCache().query().withName("Reward").interact("Big-search")) { + var rewardCart = Microbot.getRs2TileObjectCache().query().withName("Reward").nearestOnClientThread(); + if (rewardCart != null && rewardCart.click("Big-search")) { Microbot.status = "Interacting with reward cart"; return true; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/mmcaves/MmCavesPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/mmcaves/MmCavesPlugin.java index 8eed3c4e6e..cfc49c76b5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mmcaves/MmCavesPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mmcaves/MmCavesPlugin.java @@ -30,7 +30,7 @@ ) @Slf4j public class MmCavesPlugin extends Plugin { - public static String version = "1.0.1"; + public static String version = "1.0.2"; @Inject private MmCavesConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/mmcaves/MmCavesScript.java b/src/main/java/net/runelite/client/plugins/microbot/mmcaves/MmCavesScript.java index d079cb84ab..192c8eba24 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mmcaves/MmCavesScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mmcaves/MmCavesScript.java @@ -304,7 +304,7 @@ private void handleFight() { .withName("Maniacal monkey") .where(npc -> npc.getWorldLocation().equals(new WorldPoint(2451, 9159, 1)) && !npc.getNpc().isDead()) - .nearest(); + .nearestOnClientThread(); boolean attacked = attemptAttack(target); if (attacked) walkBetweenTiles(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzPlugin.java index 61aaa7b469..cf51aa96ad 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzPlugin.java @@ -31,7 +31,7 @@ ) @Slf4j public class NmzPlugin extends Plugin { - final static String version = "2.4.0"; + final static String version = "2.4.1"; @Inject private NmzConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzScript.java b/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzScript.java index 3fac272374..a16509d370 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/nmz/NmzScript.java @@ -205,7 +205,7 @@ private void walkToCenter() { public void startNmzDream() { // Set new center so that it is random for every time joining the dream center = new WorldPoint(Rs2Random.between(2270, 2276), Rs2Random.between(4693, 4696), 0); - Rs2NpcModel dominic = npcCache.query().withName("Dominic Onion").nearest(); + Rs2NpcModel dominic = npcCache.query().withName("Dominic Onion").nearestOnClientThread(); if (dominic != null) dominic.click("Dream"); sleepUntil(() -> Rs2Widget.hasWidget("Which dream would you like to experience?")); Rs2Widget.clickWidget("Previous:"); diff --git a/src/main/java/net/runelite/client/plugins/microbot/npctanner/npcTannerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/npctanner/npcTannerPlugin.java index 18584f4680..f818de481b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/npctanner/npcTannerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/npctanner/npcTannerPlugin.java @@ -29,7 +29,7 @@ ) @Slf4j public class npcTannerPlugin extends Plugin { - public static final String version = "2.0.1"; + public static final String version = "2.0.2"; @Inject private npcTannerConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/npctanner/npcTannerScript.java b/src/main/java/net/runelite/client/plugins/microbot/npctanner/npcTannerScript.java index b3303d594f..199cd4d774 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/npctanner/npcTannerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/npctanner/npcTannerScript.java @@ -104,7 +104,7 @@ public void WalkToandTan(npcTannerConfig config){ } } else { Microbot.status="Tanning: "+npcTannerScript.whattotan; - Rs2NpcModel ellis = Microbot.getRs2NpcCache().query().withName("Ellis").nearest(); + Rs2NpcModel ellis = Microbot.getRs2NpcCache().query().withName("Ellis").nearestOnClientThread(); if(ellis != null && ellis.click("Trade")){ sleep(1000, 3000); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlPlugin.java index fc2f897e93..0cc6102cbb 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlPlugin.java @@ -34,7 +34,7 @@ @Slf4j public class PestControlPlugin extends Plugin { - static final String version = "2.3.2"; + static final String version = "2.3.3"; @Inject PestControlScript pestControlScript; diff --git a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java index d508a9db34..2fc7d51ac8 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java @@ -159,7 +159,7 @@ public boolean run(PestControlConfig config) { return; } - var brawler = Microbot.getRs2NpcCache().query().withName("brawler").nearest(); + var brawler = Microbot.getRs2NpcCache().query().withName("brawler").nearestOnClientThread(); if (brawler != null && brawler.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()) < 3) { brawler.click("Attack"); sleepUntil(() -> !Rs2Combat.inCombat()); @@ -359,7 +359,7 @@ public Portal getClosestAttackablePortal() { private static boolean attackPortal() { if (!Microbot.getClient().getLocalPlayer().isInteracting()) { - Rs2NpcModel npcPortal = Microbot.getRs2NpcCache().query().withName("portal").nearest(); + Rs2NpcModel npcPortal = Microbot.getRs2NpcCache().query().withName("portal").nearestOnClientThread(); if (npcPortal == null) return false; NPCComposition npc = Microbot.getClientThread().runOnClientThreadOptional(() -> Microbot.getClient().getNpcDefinition(npcPortal.getId())).orElse(null); diff --git a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java index 7dee7084be..d749d30ceb 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java @@ -84,7 +84,7 @@ ) @Slf4j public class QoLPlugin extends Plugin implements KeyListener { - public static final String version = "1.8.11"; + public static final String version = "1.8.12"; public static final List bankMenuEntries = new LinkedList<>(); public static final List furnaceMenuEntries = new LinkedList<>(); public static final List anvilMenuEntries = new LinkedList<>(); @@ -385,7 +385,7 @@ private void onMenuOptionClicked(MenuOptionClicked event) { Global.sleepUntil(() -> !Rs2Inventory.anyPouchFull(), () -> { Rs2Inventory.emptyPouches(); Rs2Inventory.waitForInventoryChanges(3000); - Microbot.getRs2TileObjectCache().query().withName("Altar").interact(); + Microbot.getClientThread().invoke(() -> Microbot.getRs2TileObjectCache().query().withName("Altar").interact()); Rs2Inventory.waitForInventoryChanges(3000); } , 10000, 200); diff --git a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtScript.java b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtScript.java index 484c559e17..787c9a9590 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/scripts/wintertodt/WintertodtScript.java @@ -148,10 +148,10 @@ public void onNpcSpawned(NpcSpawned event) { public void onNpcDespawned(NpcDespawned event) { if (incapitatedPyromancer != null && event.getNpc().equals(incapitatedPyromancer.getNpc())) { - incapitatedPyromancer = Microbot.getRs2NpcCache().query().withName("Incapacitated Pyromancer").nearest(); + incapitatedPyromancer = Microbot.getRs2NpcCache().query().withName("Incapacitated Pyromancer").nearestOnClientThread(); } if (pyromancer != null && event.getNpc().equals(pyromancer.getNpc())) { - pyromancer = Microbot.getRs2NpcCache().query().withName("Pyromancer").nearest(); + pyromancer = Microbot.getRs2NpcCache().query().withName("Pyromancer").nearestOnClientThread(); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerPlugin.java index 4fde9d89bd..9da25bdf09 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerPlugin.java @@ -32,7 +32,7 @@ ) @Slf4j public class revKillerPlugin extends Plugin { - public static final String version = "2.0.8"; + public static final String version = "2.0.9"; @Inject private net.runelite.client.plugins.microbot.revkiller.revKillerConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerScript.java b/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerScript.java index 64c5ef8364..f758823166 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/revkiller/revKillerScript.java @@ -127,7 +127,7 @@ public boolean run(revKillerConfig config) { if(firstRun || weDied) { Microbot.log("It's our first run or we died!"); - if(firstRun && Microbot.getRs2NpcCache().query().withName(config.selectedRev().getName()).nearest() != null){ + if(firstRun && Microbot.getRs2NpcCache().query().withName(config.selectedRev().getName()).nearestOnClientThread() != null){ // we're all ready geared and there firstRun = false; Microbot.log("It's our first run and we're all ready here!"); diff --git a/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabPlugin.java index 40f67c28e0..f537c73603 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabPlugin.java @@ -26,7 +26,7 @@ ) @Slf4j public class SandCrabPlugin extends Plugin { - public final static String version = "1.5.2"; + public final static String version = "1.5.3"; @Inject private SandCrabConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabScript.java b/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabScript.java index 5427fbabff..3e786bbf66 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sandcrabs/SandCrabScript.java @@ -205,7 +205,7 @@ public boolean run(SandCrabConfig config, SandCrabPlugin plugin) { * @return true if npc is aggressive */ private boolean isNpcAggressive() { - List npcs = Microbot.getRs2NpcCache().query().withName("Sandy rocks").toList(); + List npcs = Microbot.getRs2NpcCache().query().withName("Sandy rocks").toListOnClientThread(); if (npcs.isEmpty()) { return false; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/sandminer/GabulhasSandMinerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/sandminer/GabulhasSandMinerPlugin.java index 77e000cd83..784054c13b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sandminer/GabulhasSandMinerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sandminer/GabulhasSandMinerPlugin.java @@ -29,7 +29,7 @@ ) @Slf4j public class GabulhasSandMinerPlugin extends Plugin { - public static final String version = "1.2.1"; + public static final String version = "1.2.2"; @Inject GabulhasSandMinerScript gabulhasSandMinerScript; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/sandminer/GabulhasSandMinerScript.java b/src/main/java/net/runelite/client/plugins/microbot/sandminer/GabulhasSandMinerScript.java index 4bec5e1e21..e3236b1ca0 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sandminer/GabulhasSandMinerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sandminer/GabulhasSandMinerScript.java @@ -111,7 +111,7 @@ private void miningLoop(GabulhasSandMinerConfig config) { new WorldPoint(3164, 2905, 0) : new WorldPoint(3166, 2905, 0); var innerSandstoneRock = Microbot.getRs2TileObjectCache().query() .withName("Sandstone rocks") - .nearest(innerMiningPoint, 0); + .nearestOnClientThread(innerMiningPoint, 0); if (innerSandstoneRock != null) innerSandstoneRock.click("Mine"); Rs2Player.waitForXpDrop(Skill.MINING, 15000); Rs2Antiban.actionCooldown(); @@ -121,7 +121,7 @@ private void miningLoop(GabulhasSandMinerConfig config) { } var sandstoneRock = Microbot.getRs2TileObjectCache().query() .withName("Sandstone rocks") - .nearest(miningPoint, 5); + .nearestOnClientThread(miningPoint, 5); if (sandstoneRock != null) { sandstoneRock.click("Mine"); if (config.turboMode()) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusPlugin.java index 256fe3e899..b23f8b71e2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusPlugin.java @@ -32,7 +32,7 @@ ) @Slf4j public class ScurriusPlugin extends Plugin { - public static final String version = "1.0.1"; + public static final String version = "1.0.2"; @Inject private ScurriusConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusScript.java b/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusScript.java index 68e2bc1643..28619c4ed7 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/scurrius/ScurriusScript.java @@ -67,7 +67,7 @@ public boolean run(ScurriusConfig config) { previousState = state; } - scurrius = Microbot.getRs2NpcCache().query().withName("Scurrius").nearest(); + scurrius = Microbot.getRs2NpcCache().query().withName("Scurrius").nearestOnClientThread(); boolean hasFood = !Rs2Inventory.getInventoryFood().isEmpty(); boolean hasPrayerPotions = Rs2Inventory.hasItem("prayer potion") || Rs2Inventory.hasItem("super restore"); diff --git a/src/main/java/net/runelite/client/plugins/microbot/shadeskiller/ShadesKillerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/shadeskiller/ShadesKillerPlugin.java index 8c08163e12..f7aa1091b0 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/shadeskiller/ShadesKillerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/shadeskiller/ShadesKillerPlugin.java @@ -25,7 +25,7 @@ @Slf4j public class ShadesKillerPlugin extends Plugin { - public final static String version = "1.0.1"; + public final static String version = "1.0.2"; @Inject private ShadesKillerConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/shadeskiller/ShadesKillerScript.java b/src/main/java/net/runelite/client/plugins/microbot/shadeskiller/ShadesKillerScript.java index 7f8b9cab99..d8a053817f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/shadeskiller/ShadesKillerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/shadeskiller/ShadesKillerScript.java @@ -202,15 +202,15 @@ public boolean run(ShadesKillerConfig config) { Rs2NpcModel npc = Microbot.getRs2NpcCache().query() .withName(config.SHADES().names.get(0)) .where(Rs2NpcModel::isInteractingWithPlayer) - .nearest(); + .nearestOnClientThread(); if (npc != null && !Microbot.getClient().getLocalPlayer().isInteracting()) { npc.click("Attack"); return; } if (!Rs2Combat.inCombat() && !isLooting) { - Microbot.getRs2NpcCache().query() + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query() .withNames(config.SHADES().names.toArray(new String[0])) - .interact("Attack"); + .interact("Attack")); } Rs2Combat.setSpecState(true, config.specialAttack() * 10); if (Rs2Inventory.isFull() && config.useCoffin()) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerPlugin.java index d313c2fde9..40d93f5093 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerPlugin.java @@ -42,7 +42,7 @@ @Slf4j public class SlayerPlugin extends Plugin { - static final String version = "1.0.1"; + static final String version = "1.0.2"; @Inject private SlayerConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerScript.java b/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerScript.java index 79ac32f514..ce2bfe254f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/slayer/SlayerScript.java @@ -344,7 +344,7 @@ private void handleGettingTaskState(boolean hasTask, String taskName) { if (distance <= 5) { // We're at the master, interact to get task - var masterNpc = Microbot.getRs2NpcCache().query().withName(master.getName()).nearest(); + var masterNpc = Microbot.getRs2NpcCache().query().withName(master.getName()).nearestOnClientThread(); if (masterNpc != null) { if (masterNpc.click("Assignment")) { log.info("Requesting task from {}", master.getName()); @@ -536,7 +536,7 @@ private void handleSkippingTaskState() { if (distance <= 5) { // We're at the master, interact to open rewards - var masterNpc = Microbot.getRs2NpcCache().query().withName(master.getName()).nearest(); + var masterNpc = Microbot.getRs2NpcCache().query().withName(master.getName()).nearestOnClientThread(); if (masterNpc != null) { if (masterNpc.click("Rewards")) { log.info("Opening rewards menu to skip task (attempt {})", skipAttemptCounter + 1); @@ -1044,7 +1044,7 @@ private void handleBlockingTaskState() { if (distance <= 5) { // We're at the master, interact to open rewards - var masterNpc = Microbot.getRs2NpcCache().query().withName(master.getName()).nearest(); + var masterNpc = Microbot.getRs2NpcCache().query().withName(master.getName()).nearestOnClientThread(); if (masterNpc != null) { if (masterNpc.click("Rewards")) { log.info("Opening rewards menu to block task (attempt {})", blockAttemptCounter + 1); @@ -2770,7 +2770,7 @@ private void handleCombat() { .anyMatch(monster -> matchesTargetMonster(npc.getName(), monster))) .where(npc -> taskDestination == null || npc.getWorldLocation().distanceTo(taskDestination) <= config.attackRadius()) - .toList() + .toListOnClientThread() .stream() .sorted(Comparator .comparingInt((Rs2NpcModel npc) -> @@ -2788,7 +2788,7 @@ private void handleCombat() { .where(npc -> npc.getName() != null) .where(npc -> taskDestination == null || npc.getWorldLocation().distanceTo(taskDestination) <= config.attackRadius()) - .toList() + .toListOnClientThread() .stream() .map(Rs2NpcModel::getName) .distinct() @@ -3945,7 +3945,7 @@ private boolean fireCannon() { */ private boolean handleCannonPickup() { // Check if cannon still exists - var cannon = Microbot.getRs2TileObjectCache().query().withName("Dwarf multicannon").within(Rs2Player.getWorldLocation(), 50).nearest(); + var cannon = Microbot.getRs2TileObjectCache().query().withName("Dwarf multicannon").within(Rs2Player.getWorldLocation(), 50).nearestOnClientThread(); if (cannon == null) { log.info("Cannon already picked up or not found"); return true; @@ -3983,7 +3983,8 @@ private boolean handleCannonPickup() { * @return true if pickup was initiated successfully */ private boolean pickupCannon() { - if (Microbot.getRs2TileObjectCache().query().withName("Dwarf multicannon").interact("Pick-up")) { + var cannon = Microbot.getRs2TileObjectCache().query().withName("Dwarf multicannon").nearestOnClientThread(); + if (cannon != null && cannon.click("Pick-up")) { log.info("Picking up cannon..."); return true; } @@ -3995,7 +3996,7 @@ private boolean pickupCannon() { * @return true if cannon object is found nearby */ private boolean isCannonPlacedNearby() { - return Microbot.getRs2TileObjectCache().query().withName("Dwarf multicannon").within(Rs2Player.getWorldLocation(), 10).nearest() != null; + return Microbot.getRs2TileObjectCache().query().withName("Dwarf multicannon").within(Rs2Player.getWorldLocation(), 10).nearestOnClientThread() != null; } /** diff --git a/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaPlugin.java index 23f96b5bd2..c7c0e2a30e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaPlugin.java @@ -29,7 +29,7 @@ @Slf4j public class SulphurNaguaPlugin extends Plugin { - public static final String version = "2.0.0"; + public static final String version = "2.0.1"; @Inject SulphurNaguaScript sulphurNaguaScript; diff --git a/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaScript.java b/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaScript.java index 76da1a23e4..0dc4cb2623 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/sulphurnaguafigther/SulphurNaguaScript.java @@ -524,7 +524,7 @@ private void handleFighting(SulphurNaguaConfig config) { var nagua = Microbot.getRs2NpcCache().query() .withName("Sulphur Nagua") .where(n -> !n.isDead()) - .first(); + .firstOnClientThread(); if (nagua != null) { if (nagua.click("Attack")) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/summergarden/SummerGardenPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/summergarden/SummerGardenPlugin.java index 4d7f47ca62..91e8b30103 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/summergarden/SummerGardenPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/summergarden/SummerGardenPlugin.java @@ -42,7 +42,7 @@ @Slf4j public class SummerGardenPlugin extends Plugin { - public final static String version = "1.0.2"; + public final static String version = "1.0.3"; @Inject private Client client; diff --git a/src/main/java/net/runelite/client/plugins/microbot/summergarden/SummerGardenScript.java b/src/main/java/net/runelite/client/plugins/microbot/summergarden/SummerGardenScript.java index 14d7005ac9..d032a58674 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/summergarden/SummerGardenScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/summergarden/SummerGardenScript.java @@ -272,7 +272,7 @@ private void completeAndReset() { } // Check if the player has arrived at Osman's location, if not then walk there. - var npcOsman = Microbot.getRs2NpcCache().query().withName(NPC_NAME_OSMAN).nearest(); + var npcOsman = Microbot.getRs2NpcCache().query().withName(NPC_NAME_OSMAN).nearestOnClientThread(); if (npcOsman == null) { var osmanLocalLocation = LocalPoint.fromWorld(Microbot.getClient(), osmanLocation); if (osmanLocalLocation != null) { @@ -284,7 +284,7 @@ private void completeAndReset() { // Interact with Osman. if (lastInteractedActor == null || !Objects.equals(lastInteractedActor.getName(), NPC_NAME_OSMAN)) { - Microbot.getRs2NpcCache().query().withName(NPC_NAME_OSMAN).interact("Talk-to"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName(NPC_NAME_OSMAN).interact("Talk-to")); sleepUntil(() -> Rs2Player.getInteracting() != null, 2000); return; } @@ -362,14 +362,14 @@ private void handleReturnToHouse() { } // Check if the player has arrived at the Apprentice's location. - var npcApprentice = Microbot.getRs2NpcCache().query().withName(NPC_NAME_APPRENTICE).nearest(); + var npcApprentice = Microbot.getRs2NpcCache().query().withName(NPC_NAME_APPRENTICE).nearestOnClientThread(); if (npcApprentice == null) { return; } // Interact with the apprentice. if (lastInteractedActor == null || !Objects.equals(lastInteractedActor.getName(), NPC_NAME_APPRENTICE)) { - Microbot.getRs2NpcCache().query().withName(NPC_NAME_APPRENTICE).interact("Teleport"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName(NPC_NAME_APPRENTICE).interact("Teleport")); sleepUntil(() -> isInGarden(), 10000); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingPlugin.java index 9a80d95c2f..680f96ce84 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingPlugin.java @@ -40,7 +40,7 @@ @Slf4j public class TitheFarmingPlugin extends Plugin { - final static String version = "1.1.12"; + final static String version = "1.1.13"; @Inject public TitheFarmingConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingScript.java b/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingScript.java index 8a78736706..a50a7edff9 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tithefarming/TitheFarmingScript.java @@ -449,7 +449,7 @@ private void refillWaterCans(TitheFarmingConfig config) { Rs2Inventory.interact(ItemID.ZEAH_WATERINGCAN, "Use"); Rs2TileObjectModel barrel = Microbot.getRs2TileObjectCache().query() .withName("Water barrel") - .nearest(); + .nearestOnClientThread(); if (barrel != null) barrel.click(); sleepUntil(Rs2Player::isAnimating, 10000); } else { diff --git a/src/main/java/net/runelite/client/plugins/microbot/tormenteddemons/TormentedDemonPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/tormenteddemons/TormentedDemonPlugin.java index f8072e5798..b2aaa02d81 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tormenteddemons/TormentedDemonPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tormenteddemons/TormentedDemonPlugin.java @@ -39,7 +39,7 @@ @Slf4j public class TormentedDemonPlugin extends Plugin { - public static final String version = "1.2.2"; + public static final String version = "1.2.3"; private static final int CHANGE_ATTACK_STYLE_ANIMATION = 11387; private static final int MELEE_ATTACK_ANIMATION = 11392; diff --git a/src/main/java/net/runelite/client/plugins/microbot/tormenteddemons/TormentedDemonScript.java b/src/main/java/net/runelite/client/plugins/microbot/tormenteddemons/TormentedDemonScript.java index b0ceb93a99..766080f486 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tormenteddemons/TormentedDemonScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tormenteddemons/TormentedDemonScript.java @@ -333,7 +333,7 @@ private Rs2NpcModel findNewTarget(TormentedDemonConfig config) { logOnceToChat("Null HeadIcon for NPC " + npc.getName()); return false; }) - .first(); + .firstOnClientThread(); } private void switchGear(TormentedDemonConfig config, HeadIcon combatNpcHeadIcon) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandPlugin.java index 312131c59b..a1f616f81d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandPlugin.java @@ -28,7 +28,7 @@ ) @Slf4j public class TutorialIslandPlugin extends Plugin { - public static final String version = "1.3.1"; + public static final String version = "1.3.2"; @Getter private boolean toggleMusic; diff --git a/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java b/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java index c990d725ca..2f57fa6b67 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java @@ -596,7 +596,7 @@ public void CombatGuide() { } else if (Microbot.getVarbitPlayerValue(281) == 500) { Rs2Walker.walkTo(new WorldPoint(3111, 9526, Rs2Player.getWorldLocation().getPlane())); Rs2Player.waitForWalking(); - Microbot.getRs2TileObjectCache().query().withName("Ladder").interact("Climb-up"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2TileObjectCache().query().withName("Ladder").interact("Climb-up")); sleepUntil(() -> Microbot.getVarbitPlayerValue(281) != 500); } else if (Microbot.getVarbitPlayerValue(281) == 480 || Microbot.getVarbitPlayerValue(281) == 490) { Actor rat = Rs2Player.getInteracting(); @@ -609,7 +609,7 @@ public void CombatGuide() { Rs2Walker.walkTo(new WorldPoint(3110, 9523, 0), 4); } Rs2Player.waitForWalking(); - Microbot.getRs2NpcCache().query().withName("Giant rat").interact("Attack"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("Giant rat").interact("Attack")); } else if (Microbot.getVarbitPlayerValue(281) == 470) { Rs2Walker.walkTo(npc.getWorldLocation()); Rs2Player.waitForWalking(); @@ -625,7 +625,7 @@ public void CombatGuide() { WorldPoint worldPoint = new WorldPoint(3105, 9517, 0); Rs2Walker.walkTo(worldPoint, 3); Rs2Player.waitForWalking(); - Microbot.getRs2NpcCache().query().withName("Giant rat").interact("Attack"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("Giant rat").interact("Attack")); } else { Rs2Tab.switchTo(InterfaceTab.INVENTORY); Rs2Random.waitEx(600, 100); @@ -651,7 +651,7 @@ public void MiningGuide() { return; } if (Rs2Inventory.contains("Bronze bar") && Rs2Inventory.contains("Hammer")) { - Microbot.getRs2TileObjectCache().query().withName("Anvil").interact("Smith"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2TileObjectCache().query().withName("Anvil").interact("Smith")); sleepUntil(Rs2Widget::isSmithingWidgetOpen); Rs2Widget.clickWidget(312, 9); // Smith Bronze Dagger Rs2Random.waitEx(1200, 300); @@ -757,7 +757,7 @@ public void LightFire() { } public void CutTree() { - Microbot.getRs2TileObjectCache().query().withName("Tree").interact("Chop down"); + Microbot.getClientThread().invoke(() -> Microbot.getRs2TileObjectCache().query().withName("Tree").interact("Chop down")); sleepUntil(() -> Rs2Inventory.hasItem("Logs") && !Rs2Player.isAnimating(2400)); } @@ -799,7 +799,7 @@ private boolean widgetCast() { Rs2Widget.clickWidget(windStrike); Rs2Random.waitEx(150, 50); - Rs2NpcModel chicken = Microbot.getRs2NpcCache().query().withName("chicken").nearest(); + Rs2NpcModel chicken = Microbot.getRs2NpcCache().query().withName("chicken").nearestOnClientThread(); if (chicken == null) return false; if (!chicken.click("Cast")) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathPlugin.java index 3efa70d779..3f501f078a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathPlugin.java @@ -32,7 +32,7 @@ @Slf4j public class VorkathPlugin extends Plugin { - public static final String version = "1.3.13"; + public static final String version = "1.3.14"; @Inject Client client; diff --git a/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathScript.java b/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathScript.java index e132968914..552537f7c3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/vorkath/VorkathScript.java @@ -334,10 +334,10 @@ public boolean run() { togglePrayer(false); Rs2Player.eatAt(80); drinkPrayer(); - Rs2NpcModel zombieSpawn = Microbot.getRs2NpcCache().query().withName(ZOMBIFIED_SPAWN).first(); + Rs2NpcModel zombieSpawn = Microbot.getRs2NpcCache().query().withName(ZOMBIFIED_SPAWN).firstOnClientThread(); if (zombieSpawn != null) { Rs2NpcModel currentSpawn; - while ((currentSpawn = Microbot.getRs2NpcCache().query().withName(ZOMBIFIED_SPAWN).first()) != null + while ((currentSpawn = Microbot.getRs2NpcCache().query().withName(ZOMBIFIED_SPAWN).firstOnClientThread()) != null && !currentSpawn.isDead() && !doesProjectileExistById(146)) { Rs2Magic.castOn(MagicAction.CRUMBLE_UNDEAD, currentSpawn); @@ -347,7 +347,7 @@ public boolean run() { togglePrayer(true); Rs2Tab.switchTo(InterfaceTab.INVENTORY); state = State.FIGHT_VORKATH; - sleepUntil(() -> Microbot.getRs2NpcCache().query().withName(ZOMBIFIED_SPAWN).first() == null); + sleepUntil(() -> Microbot.getRs2NpcCache().query().withName(ZOMBIFIED_SPAWN).firstOnClientThread() == null); if (doesProjectileExistById(redProjectileId)) { handleRedBall(); sleep(300); @@ -580,7 +580,7 @@ public void togglePrayer(boolean onOff) { private void handleRedBall() { if (doesProjectileExistById(redProjectileId)) { redBallWalk(); - Rs2NpcModel vorkathNpc = Microbot.getRs2NpcCache().query().withName("Vorkath").first(); + Rs2NpcModel vorkathNpc = Microbot.getRs2NpcCache().query().withName("Vorkath").firstOnClientThread(); if (vorkathNpc != null) { vorkathNpc.click("Attack"); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingPlugin.java index 56a061c4fe..97da1ac01a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/AutoWoodcuttingPlugin.java @@ -50,7 +50,7 @@ ) @Slf4j public class AutoWoodcuttingPlugin extends Plugin { - public static final String version = "1.8.2"; + public static final String version = "1.8.3"; @Inject @Getter(AccessLevel.MODULE) public AutoWoodcuttingScript autoWoodcuttingScript; diff --git a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/StrugglingSaplingEvent.java b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/StrugglingSaplingEvent.java index 8eac0b71b6..a9e95e0470 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/StrugglingSaplingEvent.java +++ b/src/main/java/net/runelite/client/plugins/microbot/woodcutting/Forestry/StrugglingSaplingEvent.java @@ -45,7 +45,7 @@ public boolean validate() { if (Microbot.getClient() == null || !Microbot.isLoggedIn()) return false; var strugglingSaplings = Microbot.getRs2TileObjectCache().query() .withName("Struggling sapling") - .toList(); + .toListOnClientThread(); if (strugglingSaplings.isEmpty()) return false; return strugglingSaplings.stream().anyMatch(obj -> Rs2GameObject.hasAction(obj.getObjectComposition(), "Add-mulch") && @@ -65,7 +65,7 @@ public boolean execute() { // Find the struggling sapling var sapling = Microbot.getRs2TileObjectCache().query() .withName("Struggling sapling") - .toList() + .toListOnClientThread() .stream() .filter(obj -> Rs2GameObject.hasAction(obj.getObjectComposition(), "Add-mulch") && From e5776605798967debf36b56949f8d23b6b1f6dff Mon Sep 17 00:00:00 2001 From: chsami Date: Sun, 12 Apr 2026 03:02:29 +0200 Subject: [PATCH 35/95] fix(MageTrainingArena): update version to 1.1.7 and improve client thread handling for target conversion --- .../magetrainingarena/MageTrainingArenaPlugin.java | 2 +- .../magetrainingarena/MageTrainingArenaScript.java | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java index 365608cfa4..2c03d002c9 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java @@ -26,7 +26,7 @@ ) @Slf4j public class MageTrainingArenaPlugin extends Plugin { - public static final String version = "1.1.6"; + public static final String version = "1.1.7"; @Inject private MageTrainingArenaConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java b/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java index 3bc2792d7e..91cf6fc2b4 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java @@ -415,8 +415,8 @@ private void handleTelekineticRoom() { sleep(400, 600); } - var localTarget = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), target); - var targetConverted = WorldPoint.fromLocalInstance(Microbot.getClient(), Objects.requireNonNull(localTarget)); + var localTarget = Microbot.getClientThread().invoke(() -> LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), target)); + var targetConverted = Microbot.getClientThread().invoke(() -> WorldPoint.fromLocalInstance(Microbot.getClient(), Objects.requireNonNull(localTarget))); if (Rs2Camera.getZoom() < 40 || Rs2Camera.getZoom() > 60) { Rs2Camera.setZoom(Rs2Random.betweenInclusive(40,60)); @@ -431,8 +431,7 @@ private void handleTelekineticRoom() { sleepUntil(() -> Rs2Player.getWorldLocation().distanceTo(teleRoom.getArea()) != 0); } else { while (!Rs2Player.getWorldLocation().equals(targetConverted) - && (Microbot.getClient().getLocalDestinationLocation() == null - || !Microbot.getClient().getLocalDestinationLocation().equals(localTarget))) { + && !Objects.equals(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalDestinationLocation()), localTarget)) { if (Rs2Camera.isTileOnScreen(localTarget)) { Rs2Walker.walkFastCanvas(targetConverted); sleepGaussian(600, 150); @@ -442,9 +441,13 @@ private void handleTelekineticRoom() { sleepUntil(() -> !Rs2Player.isMoving()); } + boolean noTelegrabProjectile = Microbot.getClientThread() + .runOnClientThreadOptional(() -> StreamSupport.stream(Microbot.getClient().getProjectiles().spliterator(), false) + .noneMatch(x -> x.getId() == SpotanimID.TELEGRAB_TRAVEL)) + .orElse(true); if (!Rs2Player.isAnimating() && !Rs2Player.isMoving() - && StreamSupport.stream(Microbot.getClient().getProjectiles().spliterator(), false).noneMatch(x -> x.getId() == SpotanimID.TELEGRAB_TRAVEL) + && noTelegrabProjectile && !TelekineticRoom.getMoves().isEmpty() && TelekineticRoom.getMoves().peek() == room.getPosition() && room.getGuardian().getId() != NpcID.MAGICTRAINING_GUARD_MAZE_MOVING From de4938451023284f931a9d1d8c4a906f6290e0a6 Mon Sep 17 00:00:00 2001 From: chsami Date: Sun, 12 Apr 2026 15:57:33 +0200 Subject: [PATCH 36/95] fix(MageTrainingArena): wrap getPosition() in client thread invoke and handle wrapped InterruptedException Co-Authored-By: Claude Opus 4.6 (1M context) --- .../microbot/magetrainingarena/MageTrainingArenaPlugin.java | 2 +- .../microbot/magetrainingarena/MageTrainingArenaScript.java | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java index 2c03d002c9..e03170c3ca 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaPlugin.java @@ -26,7 +26,7 @@ ) @Slf4j public class MageTrainingArenaPlugin extends Plugin { - public static final String version = "1.1.7"; + public static final String version = "1.1.8"; @Inject private MageTrainingArenaConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java b/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java index 91cf6fc2b4..b0b0bbeddb 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/magetrainingarena/MageTrainingArenaScript.java @@ -216,7 +216,8 @@ public boolean run(MageTrainingArenaConfig config) { sleepGaussian(600, 150); } catch (Exception ex) { - if (ex instanceof InterruptedException) + if (ex instanceof InterruptedException + || ex.getCause() instanceof InterruptedException) return; System.out.println(ex.getMessage()); @@ -445,11 +446,12 @@ private void handleTelekineticRoom() { .runOnClientThreadOptional(() -> StreamSupport.stream(Microbot.getClient().getProjectiles().spliterator(), false) .noneMatch(x -> x.getId() == SpotanimID.TELEGRAB_TRAVEL)) .orElse(true); + var position = Microbot.getClientThread().invoke(room::getPosition); if (!Rs2Player.isAnimating() && !Rs2Player.isMoving() && noTelegrabProjectile && !TelekineticRoom.getMoves().isEmpty() - && TelekineticRoom.getMoves().peek() == room.getPosition() + && TelekineticRoom.getMoves().peek() == position && room.getGuardian().getId() != NpcID.MAGICTRAINING_GUARD_MAZE_MOVING && !room.getGuardian().getLocalLocation().equals(room.getDestination())) { Rs2Magic.cast(MagicAction.TELEKINETIC_GRAB); From 6410d04e9d9d8fdca7293339a41ac9a7bd432f75 Mon Sep 17 00:00:00 2001 From: chsami Date: Sun, 12 Apr 2026 21:57:07 +0200 Subject: [PATCH 37/95] fix(GotrPlugin): update version to 1.5.4 and set default maxFragmentAmount in config --- .../client/plugins/microbot/gotr/GotrPlugin.java | 9 ++++++++- .../client/plugins/microbot/gotr/GotrScript.java | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) 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 1cd50f0b57..7f552ea253 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.2"; + public static final String version = "1.5.4"; @Inject private GotrConfig config; @@ -51,6 +51,9 @@ GotrConfig provideConfig(ConfigManager configManager) { return configManager.getConfig(GotrConfig.class); } + @Inject + private ConfigManager configManager; + @Inject private OverlayManager overlayManager; @Inject @@ -71,6 +74,10 @@ public GotrScript getScript() { @Override protected void startUp() throws AWTException { + if (config.maxFragmentAmount() == 0) { + configManager.setConfiguration("gotr", "maxFragmentAmount", 100); + } + if (overlayManager != null) { overlayManager.add(pouchOverlay); overlayManager.add(gotrOverlay); 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 9c4bc4a497..c36c592c6c 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 @@ -265,7 +265,7 @@ private boolean repairCells() { int cellTier = CellType.GetCellTier(cell.getId()); List shieldCells = Microbot.getRs2TileObjectCache().query() .where(o -> o.getName() != null && o.getName().toLowerCase().contains("cell_tile")) - .toList(); + .toListOnClientThread(); if (Rs2Inventory.hasItemAmount(GUARDIAN_ESSENCE, 10)) { for (Rs2TileObjectModel shieldCell : shieldCells) { From 87ddfde4ace17fcdeb631e9099a504fd116b2fcf Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 12 Apr 2026 21:39:51 -0700 Subject: [PATCH 38/95] feat(FarmTreeRun): add Prifddinas Crystal tree patch and Construction cape teleport (#386) - Add Prifddinas Crystal tree patch (requires 74 Farming + Song of the Elves) handled after Kastori and before Avium Savannah in the run order - Startup validation aborts with a dialog if the player lacks the Farming level or quest completion when the Prifddinas patch is enabled - Withdraw the Construction cape from the bank when the player has 99 Construction, to support POH/teleport options - Bump plugin version to 1.1.2 Co-authored-by: dev --- .../farmtreerun/FarmTreeRunConfig.java | 11 ++++ .../farmtreerun/FarmTreeRunPlugin.java | 2 +- .../farmtreerun/FarmTreeRunScript.java | 61 +++++++++++++++++-- .../farmtreerun/enums/FarmTreeRunState.java | 2 + 4 files changed, 70 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunConfig.java b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunConfig.java index 640e838801..4c98ec618c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunConfig.java @@ -26,6 +26,8 @@ "
    \n" + "
  1. Taverley teleport tab
  2. \n" + "
  3. Skills necklace (2 to 6)
  4. \n" + + "
  5. Construction cape (at 99 Construction) - auto-grabbed for POH teleports
  6. \n" + + "
  7. Crystal tree sapling (for Prifddinas patch, requires Song of the Elves)
  8. \n" + "
" + "
Optional:\n" + "
    \n" + @@ -261,6 +263,15 @@ public interface FarmTreeRunConfig extends Config { ) default boolean auburnTreePatch() { return true; } + @ConfigItem( + keyName = "priffddinasCrystalTree", + name = "Prifddinas (Crystal)", + description = "Prifddinas Crystal tree patch (requires 74 Farming and Song of the Elves). Uses a Crystal tree sapling.", + position = 7, + section = treePatchesSection + ) + default boolean priffddinasCrystalTreePatch() { return false; } + /* ========================= * Fruit tree patches — ordered to match run: * GS Fruit → TGV Fruit → Farming Guild Fruit → Brimhaven → Catherby → Lletya → Kastori diff --git a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPlugin.java index 3b16a62c44..203b784346 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPlugin.java @@ -30,7 +30,7 @@ ) @Slf4j public class FarmTreeRunPlugin extends Plugin { - public static final String version = "1.1.1"; + public static final String version = "1.1.2"; @Inject private FarmTreeRunConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java index 7caf0272ae..db370cec82 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java @@ -82,6 +82,7 @@ public enum Patch { FOSSIL_TREE_PATCH_C(30481, new WorldPoint(3701, 3840, 0), TreeKind.HARD_TREE, 1, 0), AUBURNVALE_TREE_PATCH(56953, new WorldPoint(1365, 3320, 0), TreeKind.TREE, 1, 0), KASTORI_FRUIT_TREE_PATCH(56955, new WorldPoint(1349, 3058, 0), TreeKind.FRUIT_TREE, 1, 12765), + PRIFFDDINAS_CRYSTAL_TREE_PATCH(34906, new WorldPoint(3291, 6117, 0), TreeKind.TREE, 74, 0), AVIUM_SAVANNAH_HARDWOOD_PATCH(50692, new WorldPoint(1684, 2974, 0), TreeKind.HARD_TREE,1,0); private final int id; @@ -119,6 +120,7 @@ public boolean run(FarmTreeRunConfig config) { } calculatePatches(config); checkSaplingLevelRequirement(config); + if (!validateSpecialPatches(config)) return; dropEmptyPlantPots(); Patch patch = null; @@ -318,6 +320,17 @@ public boolean run(FarmTreeRunConfig config) { } if (!handledPatch) return; } + botStatus = net.runelite.client.plugins.microbot.farmtreerun.enums.FarmTreeRunState.HANDLE_PRIFFDDINAS_CRYSTAL_TREE_PATCH; + break; + } + case HANDLE_PRIFFDDINAS_CRYSTAL_TREE_PATCH: { + patch = Patch.PRIFFDDINAS_CRYSTAL_TREE_PATCH; + if (config.priffddinasCrystalTreePatch() && patch.hasRequiredLevel()) { + if (walkToLocation(patch.getLocation())) { + handledPatch = handlePatch(config, patch); + } + if (!handledPatch) return; + } botStatus = net.runelite.client.plugins.microbot.farmtreerun.enums.FarmTreeRunState.HANDLE_AVIUM_SAVANNAH_HARDWOOD_PATCH; break; } @@ -365,6 +378,23 @@ private void calculatePatches(FarmTreeRunConfig config) { } } + private boolean validateSpecialPatches(FarmTreeRunConfig config) { + if (config.priffddinasCrystalTreePatch()) { + int farmingLevel = Rs2Player.getRealSkillLevel(Skill.FARMING); + if (farmingLevel < 74) { + Microbot.showMessage("Prifddinas Crystal tree requires 74 Farming (you have " + farmingLevel + "). Disable the Prifddinas patch or train Farming before starting. Shutting down."); + shutdown(); + return false; + } + if (Rs2Player.getQuestState(Quest.SONG_OF_THE_ELVES) != QuestState.FINISHED) { + Microbot.showMessage("Prifddinas Crystal tree requires Song of the Elves to be completed. Disable the Prifddinas patch before starting. Shutting down."); + shutdown(); + return false; + } + } + return true; + } + private void checkSaplingLevelRequirement(FarmTreeRunConfig config) { if (!getSelectedTreePatches(config).isEmpty()) config.selectedTree().hasRequiredLevel(); @@ -450,6 +480,15 @@ private void bank(FarmTreeRunConfig config) { } } + // Construction cape (99 Construction): useful for POH/teleport options + if (Rs2Player.getRealSkillLevel(Skill.CONSTRUCTION) >= 99) { + if (Rs2Bank.hasItem(ItemID.CONSTRUCT_CAPET)) { + items.add(new FarmingItem(ItemID.CONSTRUCT_CAPET, 1, false, true)); + } else if (Rs2Bank.hasItem(ItemID.CONSTRUCT_CAPE)) { + items.add(new FarmingItem(ItemID.CONSTRUCT_CAPE, 1, false, true)); + } + } + if (config.useSkillsNecklace() && (config.farmingGuildTreePatch() || config.farmingGuildFruitTreePatch())) { if (Rs2Bank.hasItem(ItemID.SKILLS_NECKLACE2)) { items.add(new FarmingItem(ItemID.SKILLS_NECKLACE2, 1)); @@ -475,8 +514,16 @@ private void bank(FarmTreeRunConfig config) { int fruitTreeSaplingsCount = getSelectedFruitTreePatches(config).size(); int hardTreeSaplingsCount = getSelectedHardTreePatches(config).size(); - if (treeSaplingsCount > 0) - items.add(new FarmingItem(selectedTree.getSaplingId(), treeSaplingsCount)); + // Crystal tree patch uses its own sapling, not the selected regular tree sapling + boolean priffEnabled = config.priffddinasCrystalTreePatch() + && Patch.PRIFFDDINAS_CRYSTAL_TREE_PATCH.hasRequiredLevel(); + int regularTreeSaplingsCount = priffEnabled ? treeSaplingsCount - 1 : treeSaplingsCount; + + if (regularTreeSaplingsCount > 0) + items.add(new FarmingItem(selectedTree.getSaplingId(), regularTreeSaplingsCount)); + + if (priffEnabled) + items.add(new FarmingItem(ItemID.CRYSTAL_SAPLING, 1)); if (fruitTreeSaplingsCount > 0) items.add(new FarmingItem(selectedFruitTree.getSaplingId(), fruitTreeSaplingsCount)); @@ -484,8 +531,8 @@ private void bank(FarmTreeRunConfig config) { if (hardTreeSaplingsCount > 0) items.add(new FarmingItem(selectedHardTree.getSaplingId(), hardTreeSaplingsCount)); - if (config.protectTrees()) - items.add(new FarmingItem(selectedTree.getPaymentId(), selectedTree.getPaymentAmount() * treeSaplingsCount, true)); + if (config.protectTrees() && regularTreeSaplingsCount > 0) + items.add(new FarmingItem(selectedTree.getPaymentId(), selectedTree.getPaymentAmount() * regularTreeSaplingsCount, true)); if (config.protectHardTrees()) items.add(new FarmingItem(selectedHardTree.getPaymentId(), selectedHardTree.getPaymentAmount() * hardTreeSaplingsCount, true)); @@ -899,7 +946,8 @@ private List getSelectedTreePatches(FarmTreeRunConfig config) { config::taverleyTreePatch, config::varrockTreePatch, config::farmingGuildTreePatch, - config::auburnTreePatch + config::auburnTreePatch, + config::priffddinasCrystalTreePatch ); // Filter the patches to include only those that return true @@ -986,6 +1034,9 @@ private static int getSaplingToUse(Patch patch, FarmTreeRunConfig config) { if (patch == Patch.FOSSIL_TREE_PATCH_A || patch == Patch.FOSSIL_TREE_PATCH_B || patch == Patch.FOSSIL_TREE_PATCH_C ) { return config.selectedHardTree().getSaplingId(); + } else if (patch == Patch.PRIFFDDINAS_CRYSTAL_TREE_PATCH) { + return ItemID.CRYSTAL_SAPLING; + } else return patch.kind == TreeKind.TREE ? config.selectedTree().getSaplingId() : config.selectedFruitTree().getSaplingId(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/FarmTreeRunState.java b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/FarmTreeRunState.java index 4e5b084968..e9cfc0c11a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/FarmTreeRunState.java +++ b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/FarmTreeRunState.java @@ -49,6 +49,8 @@ public enum FarmTreeRunState { HANDLE_KASTORI_FRUIT_TREE_PATCH, + HANDLE_PRIFFDDINAS_CRYSTAL_TREE_PATCH, + HANDLE_AVIUM_SAVANNAH_HARDWOOD_PATCH, FINISHED From 53bdc0fd0dfe079336242c649a4a425e6f12b521 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 12 Apr 2026 21:40:22 -0700 Subject: [PATCH 39/95] fix(FarmTreeRun): use Yanillian hops for Mahogany tree protection (#387) Mahogany tree protection was a copy-paste of Teak's limpwurt root payment. The actual protection cost is 25 Yanillian hops, so update the MAHOGANY enum to match. Bumps plugin version to 1.1.3. Co-authored-by: dev --- .../plugins/microbot/farmtreerun/enums/HardTreeEnums.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/HardTreeEnums.java b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/HardTreeEnums.java index cb183d0213..25d8321c61 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/HardTreeEnums.java +++ b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/HardTreeEnums.java @@ -10,7 +10,7 @@ @RequiredArgsConstructor public enum HardTreeEnums { TEAK("Teak sapling", ItemID.PLANTPOT_TEAK_SAPLING, ItemID.LIMPWURT_ROOT, 15,75), - MAHOGANY("Mahogany sapling", ItemID.PLANTPOT_MAHOGANY_SAPLING, ItemID.LIMPWURT_ROOT, 15,75); + MAHOGANY("Mahogany sapling", ItemID.PLANTPOT_MAHOGANY_SAPLING, ItemID.YANILLIAN_HOPS, 25,75); private final String name; From 9763c16143e3444cfc703a22e8850988f1e3906e Mon Sep 17 00:00:00 2001 From: JThomasDevs <95548936+JThomasDevs@users.noreply.github.com> Date: Mon, 13 Apr 2026 23:48:46 -0600 Subject: [PATCH 40/95] fix(barrows): POH banking, Ferox escape, and tablet timing (#388) * Reduce code smell, implement sawmill vouchers and Lazy Mode * Change profit calculation to be more accurate, extended Logs enum to prevent weird bank withdrawal shenanigans (tried withdrawing yew logs) * undo main runner change * re-add microbot.java. whoops. * Karam fix (#351) * fix: karambwan fairy ring return Made-with: Cursor * plugin now clicks on fairy ring to get back to karams * commit * fix(barrows): POH banking, Ferox escape, and tablet timing - POH: require instanced world view, use nearestOnClientThread, cap portal retries, only nexus-shutdown when no normal portal - outOfSupplies: Ferox via ring of dueling (equipped name check, inventory rings, rub/dialogue); treat POH as needing Ferox hop when walker cannot - Close bank before Teleport-to-house tablet (travel and chest leave) so the tablet is not used while the bank is still open --------- Co-authored-by: chsami Co-authored-by: stonksCode <99895926+stonksCode@users.noreply.github.com> Co-authored-by: irkedMATT <59846844+irkedMATT@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) --- .../microbot/barrows/BarrowsPlugin.java | 2 +- .../microbot/barrows/BarrowsScript.java | 184 ++++++++++++++---- 2 files changed, 151 insertions(+), 35 deletions(-) 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 6b7106c6cc..9827169efb 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.4"; + public static final String version = "2.0.9"; @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 635c11d2e3..71a3c9d55a 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 @@ -18,6 +18,7 @@ import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; import net.runelite.client.plugins.microbot.util.coords.Rs2WorldArea; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; +import net.runelite.client.plugins.microbot.util.equipment.JewelleryLocationEnum; import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; @@ -149,6 +150,10 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { if(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID() == ItemID.TELEPORT_TO_HOUSE) { if (!inTunnels && !shouldBank && Rs2Player.getWorldLocation().distanceTo(new WorldPoint(3573, 3296, 0)) > 60) { + if(Rs2Bank.isOpen()){ + closeBank(); + return; + } //needed to intercept the walker if(rs2TileObjectCache.query().withId(4525).nearest() == null){ Rs2Inventory.interact("Teleport to house", "Inside"); @@ -447,6 +452,10 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { WhoisTun = "Unknown"; inTunnels = false; } else { + if(Rs2Bank.isOpen()){ + closeBank(); + return; + } Rs2Inventory.interact("Teleport to house", "Inside"); sleepUntil(() -> Rs2Player.getWorldLocation().getY() < 9600 || Rs2Player.getWorldLocation().getY() > 9730, Rs2Random.between(6000, 10000)); ChestsOpened++; @@ -467,7 +476,6 @@ public boolean run(BarrowsConfig config, BarrowsPlugin plugin) { stopFutureWalker(); //tele out outOfSupplies(config); - //walk to and open the bank Rs2Bank.walkToBankAndUseBank(BankLocation.FEROX_ENCLAVE); BreakHandlerScript.lockState.set(false); } else { @@ -693,36 +701,61 @@ public void closeBank(){ } public void handlePOH(BarrowsConfig config){ - if(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID() == ItemID.TELEPORT_TO_HOUSE){ - Rs2TileObjectModel pohThing = rs2TileObjectCache.query().withId(4525).nearest(); - if(pohThing != null){ - Microbot.log("We're in our POH"); - Rs2TileObjectModel rejPool = rs2TileObjectCache.query().withIds(29238,29239,29241,29240).nearest(); - if(rejPool != null){ - if(rejPool.click("Drink")){ - sleepUntil(()-> Rs2Player.isMoving(), Rs2Random.between(2000,4000)); - sleepUntil(()-> !Rs2Player.isMoving(), Rs2Random.between(10000,15000)); - } + if(config.selectedToBarrowsTPMethod().getToBarrowsTPMethodItemID() != ItemID.TELEPORT_TO_HOUSE){ + return; + } + Client client = Microbot.getClient(); + if(client == null){ + return; + } + WorldView worldView = client.getTopLevelWorldView(); + if(worldView == null){ + return; + } + if(!worldView.isInstance()){ + return; + } + Rs2TileObjectModel pohThing = rs2TileObjectCache.query().withId(4525).nearestOnClientThread(); + if(pohThing == null){ + return; + } + Microbot.log("We're in our POH"); + Rs2TileObjectModel rejPool = rs2TileObjectCache.query().withIds(29238,29239,29241,29240).nearestOnClientThread(); + if(rejPool != null){ + if(rejPool.click("Drink")){ + sleepUntil(()-> Rs2Player.isMoving(), Rs2Random.between(2000,4000)); + sleepUntil(()-> !Rs2Player.isMoving(), Rs2Random.between(10000,15000)); + } + } + Rs2TileObjectModel regularPortal = rs2TileObjectCache.query().withIds(37603,37615,37591).nearestOnClientThread(); + if(regularPortal != null){ + for(int pohPortalAttempts = 0; pohPortalAttempts < 40; pohPortalAttempts++){ + if(!super.isRunning()){ + break; } - Rs2TileObjectModel regularPortal = rs2TileObjectCache.query().withIds(37603,37615,37591).nearest(); - if(regularPortal != null){ - while(pohThing != 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)); - } - } - } - + pohThing = rs2TileObjectCache.query().withId(4525).nearestOnClientThread(); + if(pohThing == null){ + break; + } + regularPortal = rs2TileObjectCache.query().withIds(37603,37615,37591).nearestOnClientThread(); + if(regularPortal == null){ + break; + } + if(Rs2Player.isMoving()){ + sleep(Rs2Random.between(200, 600)); + continue; + } + 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).nearestOnClientThread() == null, Rs2Random.between(10000,15000)); } else { - // we have a nexus 33410 - Microbot.log("No nexus support yet, shutting down"); - super.shutdown(); + break; } } + } else { + Microbot.log("No nexus support yet, shutting down"); + super.shutdown(); } } @@ -1105,15 +1138,98 @@ public void antiPatternDropVials(){ } 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 - if(shouldBank && (inTunnels || Rs2Player.getWorldLocation().getPlane() == 3)){ - if(Rs2Equipment.interact(EquipmentInventorySlot.RING, "Ferox Enclave")){ - Microbot.log("We're out of supplies. Teleporting."); - if(inTunnels) inTunnels=false; - sleepUntil(() -> Rs2Player.isAnimating(), Rs2Random.between(2000, 4000)); - sleepUntil(() -> !Rs2Player.isAnimating(), Rs2Random.between(6000, 10000)); + if(!shouldBank){ + return; + } + boolean needFeroxRingTeleport = false; + if(inTunnels){ + needFeroxRingTeleport = true; + } + if(Rs2Player.getWorldLocation().getPlane() == 3){ + needFeroxRingTeleport = true; + } + if(isInPlayerOwnedHouse()){ + needFeroxRingTeleport = true; + } + if(!needFeroxRingTeleport){ + return; + } + if(tryFeroxTeleportViaRingOfDueling()){ + Microbot.log("We're out of supplies. Teleporting to Ferox Enclave."); + if(inTunnels){ + inTunnels = false; } + sleepUntil(() -> Rs2Player.isAnimating(), Rs2Random.between(2000, 4000)); + sleepUntil(() -> !Rs2Player.isAnimating(), Rs2Random.between(6000, 10000)); + } + } + + private boolean isInPlayerOwnedHouse(){ + Client c = Microbot.getClient(); + if(c == null){ + return false; } + WorldView wv = c.getTopLevelWorldView(); + if(wv == null){ + return false; + } + if(!wv.isInstance()){ + return false; + } + if(inTunnels){ + return false; + } + Rs2TileObjectModel portal = rs2TileObjectCache.query().withId(4525).nearestOnClientThread(); + return portal != null; + } + + private boolean tryFeroxTeleportViaRingOfDueling(){ + Rs2ItemModel equippedRing = Rs2Equipment.get(EquipmentInventorySlot.RING); + if(equippedRing != null){ + String equippedName = equippedRing.getName(); + if(equippedName != null){ + if(equippedName.contains("Ring of dueling")){ + if(Rs2Equipment.interact(EquipmentInventorySlot.RING, "Ferox Enclave")){ + return true; + } + } + } + } + int[] duelingRingIds = new int[]{ + ItemID.RING_OF_DUELING1, + ItemID.RING_OF_DUELING2, + ItemID.RING_OF_DUELING3, + ItemID.RING_OF_DUELING4, + ItemID.RING_OF_DUELING5, + ItemID.RING_OF_DUELING6, + ItemID.RING_OF_DUELING7, + ItemID.RING_OF_DUELING8 + }; + for(int idx = duelingRingIds.length - 1; idx >= 0; idx--){ + int ringId = duelingRingIds[idx]; + if(!Rs2Inventory.hasItem(ringId)){ + continue; + } + if(tryRubInventoryRingToFerox(ringId)){ + return true; + } + } + return false; + } + + private boolean tryRubInventoryRingToFerox(int ringId){ + String feroxLabel = JewelleryLocationEnum.FEROX_ENCLAVE.getDestination(); + if(Rs2Inventory.interact(ringId, feroxLabel)){ + return true; + } + if(Rs2Inventory.interact(ringId, "Rub")){ + sleepUntil(() -> Rs2Dialogue.hasDialogueOption(feroxLabel), Rs2Random.between(1500, 3500)); + if(Rs2Dialogue.clickOption(feroxLabel)){ + return true; + } + return Rs2Dialogue.clickOption(feroxLabel, false); + } + return false; } public void disablePrayer(){ if(Rs2Random.between(0,100) >= Rs2Random.between(0,5)) { From dd74541828999b308d5b29e2577ddd9e106e52c8 Mon Sep 17 00:00:00 2001 From: chsami Date: Wed, 15 Apr 2026 17:15:05 +0200 Subject: [PATCH 41/95] fix(AIOFighterPlugin): update version to 2.1.6 and improve safety checks in stopAndLog method --- .../microbot/aiofighter/AIOFighterPlugin.java | 2 +- .../aiofighter/safety/SafetyScript.java | 17 +++++++++++++---- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java index 09c118ff5e..81b1d0da69 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java @@ -61,7 +61,7 @@ ) @Slf4j public class AIOFighterPlugin extends Plugin { - public static final String version = "2.1.5"; + public static final String version = "2.1.6"; public static boolean needShopping = false; private static final String SET = "Set"; private static final String CENTER_TILE = ColorUtil.wrapWithColorTag("Center Tile", JagexColors.MENU_TARGET); diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/safety/SafetyScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/safety/SafetyScript.java index 3abb174e14..94884eb16f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/safety/SafetyScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/safety/SafetyScript.java @@ -6,6 +6,7 @@ import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.aiofighter.AIOFighterConfig; import net.runelite.client.plugins.microbot.aiofighter.AIOFighterPlugin; +import net.runelite.client.plugins.microbot.aiofighter.enums.State; 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.inventory.Rs2Inventory; @@ -25,6 +26,7 @@ public boolean run(AIOFighterConfig config) { if (!Microbot.isLoggedIn()) return; if (!super.run()) return; if (!config.useSafety()) return; + if (isBankingOrWalking()) return; if (config.missingRunes() && config.useMagic() && !Rs2Magic.hasRequiredRunes(config.magicSpell())){ stopAndLog("Missing runes for spell: " + config.magicSpell()); } @@ -58,11 +60,18 @@ public boolean run(AIOFighterConfig config) { public void stopAndLog(String reason) { log(reason, Level.WARNING); - if(Rs2Bank.walkToBank()){ - Rs2Player.logout(); - Plugin PlayerAssistPlugin = Microbot.getPlugin(AIOFighterPlugin.class.getName()); - Microbot.stopPlugin(PlayerAssistPlugin); + // Avoid competing with BankerScript's walker while it is already controlling movement. + if (!isBankingOrWalking() && !Rs2Player.isMoving()) { + Rs2Bank.walkToBank(); } + Rs2Player.logout(); + Plugin PlayerAssistPlugin = Microbot.getPlugin(AIOFighterPlugin.class.getName()); + Microbot.stopPlugin(PlayerAssistPlugin); + } + + private boolean isBankingOrWalking() { + State state = AIOFighterPlugin.getState(); + return state == State.BANKING || state == State.WALKING; } @Override From 9ce8736d9ec0073cab167670f7768ec412b8333c Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 15 Apr 2026 22:42:25 -0700 Subject: [PATCH 42/95] feat(AIFiremaking): add intelligent firemaking plugin with tile scanning (#393) Standalone firemaking plugin that scans surrounding tiles to find the best east-west line of open ground, lights fires walking west, banks, and adapts around existing fires and obstacles on return. Supports Bank Heist briefcase for instant banking. Adds [DV] prefix to PluginConstants for plugin branding. Co-authored-by: dev --- .../plugins/microbot/PluginConstants.java | 1 + .../microbot/leaguesfiremaking/FireLine.java | 17 ++ .../LeaguesFiremakingConfig.java | 76 ++++++ .../LeaguesFiremakingOverlay.java | 57 ++++ .../LeaguesFiremakingPlugin.java | 54 ++++ .../LeaguesFiremakingScript.java | 253 ++++++++++++++++++ .../microbot/leaguesfiremaking/LogType.java | 44 +++ .../microbot/leaguesfiremaking/State.java | 9 + .../leaguesfiremaking/TileScanner.java | 125 +++++++++ 9 files changed, 636 insertions(+) create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/FireLine.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingConfig.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingOverlay.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingPlugin.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingScript.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LogType.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/State.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/TileScanner.java diff --git a/src/main/java/net/runelite/client/plugins/microbot/PluginConstants.java b/src/main/java/net/runelite/client/plugins/microbot/PluginConstants.java index 3c28f7f994..143c2a8fbb 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/PluginConstants.java +++ b/src/main/java/net/runelite/client/plugins/microbot/PluginConstants.java @@ -34,6 +34,7 @@ private PluginConstants() public static final String NATE = "[N] "; public static final String SYN = "[Syn] "; public static final String BIGL = "[BL] "; + public static final String DV = "[DV] "; public static final boolean DEFAULT_ENABLED = false; public static final boolean IS_EXTERNAL = true; //test diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/FireLine.java b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/FireLine.java new file mode 100644 index 0000000000..4573b12c1f --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/FireLine.java @@ -0,0 +1,17 @@ +package net.runelite.client.plugins.microbot.leaguesfiremaking; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.runelite.api.coords.WorldPoint; + +@Getter +@RequiredArgsConstructor +public class FireLine { + private final WorldPoint westEnd; + private final WorldPoint eastEnd; + private final int length; + + public int getY() { + return westEnd.getY(); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingConfig.java b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingConfig.java new file mode 100644 index 0000000000..e221422694 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingConfig.java @@ -0,0 +1,76 @@ +package net.runelite.client.plugins.microbot.leaguesfiremaking; + +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.config.Range; + +@ConfigGroup("LeaguesFiremaking") +@ConfigInformation("

    AI Firemaking

    " + + "

    Version: " + LeaguesFiremakingPlugin.version + "

    " + + "

    Withdraws logs from the bank, finds open space, and lights fires in lines.

    " + + "

    Scans surrounding tiles to pick the best row, adapts around existing fires and obstacles.

    " + + "

    Supports Bank Heist briefcase for instant banking.

    ") +public interface LeaguesFiremakingConfig extends Config { + + @ConfigSection( + name = "General", + description = "General settings", + position = 0 + ) + String generalSection = "general"; + + @ConfigSection( + name = "Banking", + description = "Banking settings", + position = 1 + ) + String bankingSection = "banking"; + + @ConfigItem( + keyName = "logType", + name = "Log type", + description = "Which logs to burn", + position = 0, + section = generalSection + ) + default LogType logType() { + return LogType.LOGS; + } + + @ConfigItem( + keyName = "progressiveMode", + name = "Progressive mode", + description = "Automatically pick the best log for your Firemaking level", + position = 1, + section = generalSection + ) + default boolean progressiveMode() { + return false; + } + + @Range(min = 10, max = 50) + @ConfigItem( + keyName = "scanRadius", + name = "Scan radius", + description = "How many tiles to scan around your starting position for open space", + position = 2, + section = generalSection + ) + default int scanRadius() { + return 25; + } + + @ConfigItem( + keyName = "useBriefcase", + name = "Use Bank Heist briefcase", + description = "Use the banker's briefcase to teleport to a bank instead of walking (Leagues relic)", + position = 0, + section = bankingSection + ) + default boolean useBriefcase() { + return false; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingOverlay.java new file mode 100644 index 0000000000..de87cab3ab --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingOverlay.java @@ -0,0 +1,57 @@ +package net.runelite.client.plugins.microbot.leaguesfiremaking; + +import net.runelite.client.plugins.microbot.Microbot; +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 LeaguesFiremakingOverlay extends OverlayPanel { + + @Inject + private LeaguesFiremakingScript script; + + @Inject + public LeaguesFiremakingOverlay() { + setPosition(OverlayPosition.TOP_LEFT); + setNaughty(); + } + + @Override + public Dimension render(Graphics2D graphics) { + panelComponent.setPreferredSize(new Dimension(200, 0)); + + panelComponent.getChildren().add(TitleComponent.builder() + .text("AI Firemaking v" + LeaguesFiremakingPlugin.version) + .color(Color.GREEN) + .build()); + + panelComponent.getChildren().add(LineComponent.builder() + .left("Status") + .right(script.getStatus()) + .build()); + + panelComponent.getChildren().add(LineComponent.builder() + .left("State") + .right(script.getState().name()) + .build()); + + FireLine line = script.getCurrentLine(); + if (line != null) { + panelComponent.getChildren().add(LineComponent.builder() + .left("Line length") + .right(String.valueOf(line.getLength())) + .build()); + } + + panelComponent.getChildren().add(LineComponent.builder() + .left("Microbot") + .right(Microbot.status) + .build()); + + return super.render(graphics); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingPlugin.java new file mode 100644 index 0000000000..6b8b1a6ce3 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingPlugin.java @@ -0,0 +1,54 @@ +package net.runelite.client.plugins.microbot.leaguesfiremaking; + +import com.google.inject.Provides; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.microbot.PluginConstants; +import net.runelite.client.ui.overlay.OverlayManager; + +import javax.inject.Inject; + +@PluginDescriptor( + name = PluginConstants.DV + "AI Firemaking", + description = "Lights fires in lines anywhere — withdraws logs, scans for open tiles, adapts around obstacles", + tags = {"firemaking", "leagues", "microbot", "skilling"}, + version = LeaguesFiremakingPlugin.version, + minClientVersion = "2.0.13", + enabledByDefault = PluginConstants.DEFAULT_ENABLED, + isExternal = PluginConstants.IS_EXTERNAL +) +@Slf4j +public class LeaguesFiremakingPlugin extends Plugin { + public static final String version = "1.0.0"; + + @Inject + private LeaguesFiremakingConfig config; + + @Inject + private LeaguesFiremakingScript script; + + @Inject + private LeaguesFiremakingOverlay overlay; + + @Inject + private OverlayManager overlayManager; + + @Provides + LeaguesFiremakingConfig provideConfig(ConfigManager configManager) { + return configManager.getConfig(LeaguesFiremakingConfig.class); + } + + @Override + protected void startUp() { + overlayManager.add(overlay); + script.run(config); + } + + @Override + protected void shutDown() { + script.shutdown(); + overlayManager.remove(overlay); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingScript.java b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingScript.java new file mode 100644 index 0000000000..5d34a7860d --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LeaguesFiremakingScript.java @@ -0,0 +1,253 @@ +package net.runelite.client.plugins.microbot.leaguesfiremaking; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Skill; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.util.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.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.tile.Rs2Tile; +import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; + +import java.util.concurrent.TimeUnit; + +@Slf4j +public class LeaguesFiremakingScript extends Script { + + private static final int TINDERBOX_ID = 590; + private static final String TINDERBOX_NAME = "Tinderbox"; + + @Getter + private State state = State.SCANNING; + @Getter + private String status = "Starting"; + @Getter + private FireLine currentLine; + + private WorldPoint startPosition; + private LogType activeLogType; + + public boolean run(LeaguesFiremakingConfig config) { + Rs2Antiban.resetAntibanSettings(); + Rs2Antiban.antibanSetupTemplates.applyFiremakingSetup(); + Rs2AntibanSettings.actionCooldownChance = 0.1; + + mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { + try { + if (!super.run()) return; + if (!Microbot.isLoggedIn()) return; + if (Rs2AntibanSettings.actionCooldownActive) return; + + if (startPosition == null) { + startPosition = Rs2Player.getWorldLocation(); + } + + activeLogType = config.progressiveMode() ? LogType.getBestForLevel() : config.logType(); + + if (activeLogType == null || !activeLogType.hasRequiredLevel()) { + status = "Level too low for " + (activeLogType != null ? activeLogType.getLogName() : "any logs"); + return; + } + + switch (state) { + case SCANNING: + handleScanning(config); + break; + case WALKING_TO_LINE: + handleWalkingToLine(); + break; + case BURNING: + handleBurning(); + break; + case BANKING: + handleBanking(config); + break; + case WALKING_BACK: + handleWalkingBack(config); + break; + } + } catch (Exception ex) { + log.error("LeaguesFiremaking loop error", ex); + Microbot.log(ex.getMessage()); + } + }, 0, 600, TimeUnit.MILLISECONDS); + return true; + } + + private void handleScanning(LeaguesFiremakingConfig config) { + status = "Scanning for open space"; + + if (!Rs2Inventory.hasItem(activeLogType.getItemId())) { + state = State.BANKING; + return; + } + + currentLine = TileScanner.findBestLine(startPosition, config.scanRadius()); + + if (currentLine == null) { + status = "No open space found — try moving to a more open area"; + return; + } + + status = "Found line: " + currentLine.getLength() + " tiles"; + state = State.WALKING_TO_LINE; + } + + private void handleWalkingToLine() { + if (currentLine == null) { + state = State.SCANNING; + return; + } + + WorldPoint eastEnd = currentLine.getEastEnd(); + status = "Walking to east end of line"; + + if (Rs2Player.getWorldLocation().distanceTo(eastEnd) <= 1) { + state = State.BURNING; + return; + } + + if (!Rs2Player.isMoving()) { + Rs2Walker.walkTo(eastEnd, 0); + } + } + + private void handleBurning() { + if (!Rs2Inventory.hasItem(activeLogType.getItemId())) { + status = "Out of logs"; + state = State.BANKING; + return; + } + + if (Rs2Player.isMoving()) { + status = "Walking after lighting..."; + return; + } + + if (Rs2Player.isAnimating()) { + status = "Lighting animation..."; + return; + } + + if (!Rs2Inventory.hasItem(TINDERBOX_NAME)) { + status = "No tinderbox — banking"; + state = State.BANKING; + return; + } + + // Check if we're standing on a fire — need to step west first + WorldPoint playerPos = Rs2Player.getWorldLocation(); + boolean standingOnFire = TileScanner.hasFire(playerPos); + + if (standingOnFire) { + // Step one tile west to get off the fire + WorldPoint westTile = new WorldPoint(playerPos.getX() - 1, playerPos.getY(), playerPos.getPlane()); + if (!Rs2Tile.isWalkable(westTile)) { + // Can't go west — line is done, rescan + status = "Blocked west — rescanning"; + state = State.SCANNING; + return; + } + Rs2Walker.walkTo(westTile, 0); + sleepUntil(() -> Rs2Player.getWorldLocation().distanceTo(westTile) <= 0, 3000); + return; + } + + if (!Rs2Tile.isWalkable(playerPos)) { + status = "Standing on blocked tile — rescanning"; + state = State.SCANNING; + return; + } + + status = "Lighting " + activeLogType.getLogName(); + WorldPoint beforeLight = Rs2Player.getWorldLocation(); + Rs2Inventory.combine(TINDERBOX_NAME, activeLogType.getLogName()); + + // Wait for XP drop (fire lit) then wait for auto-walk west + if (Rs2Player.waitForXpDrop(Skill.FIREMAKING, 10000)) { + sleepUntil(() -> !Rs2Player.getWorldLocation().equals(beforeLight), 3000); + sleep(200, 400); + } + + Rs2Antiban.actionCooldown(); + Rs2Antiban.takeMicroBreakByChance(); + } + + private void handleBanking(LeaguesFiremakingConfig config) { + if (config.useBriefcase()) { + status = "Using briefcase to bank"; + if (!Rs2Inventory.hasItem("Banker's briefcase")) { + status = "No briefcase found — walking to bank"; + if (!Rs2Bank.walkToBankAndUseBank()) return; + } else { + if (!Rs2Bank.isOpen()) { + Rs2Inventory.interact("Banker's briefcase", "Bank"); + sleepUntil(Rs2Bank::isOpen, 5000); + if (!Rs2Bank.isOpen()) return; + } + } + } else { + status = "Walking to bank"; + if (!Rs2Bank.isOpen()) { + if (!Rs2Bank.walkToBankAndUseBank()) return; + } + } + + status = "Depositing and withdrawing"; + + Rs2Bank.depositAll(); + sleep(150, 300); + + if (!Rs2Bank.hasItem(TINDERBOX_ID)) { + status = "No tinderbox in bank — stopping"; + Microbot.log("No tinderbox found in bank."); + shutdown(); + return; + } + + Rs2Bank.withdrawOne(TINDERBOX_ID); + sleepUntil(() -> Rs2Inventory.hasItem(TINDERBOX_NAME), 3000); + sleep(150, 300); + + if (!Rs2Bank.hasItem(activeLogType.getItemId())) { + status = "No " + activeLogType.getLogName() + " in bank — stopping"; + Microbot.log("No " + activeLogType.getLogName() + " found in bank."); + shutdown(); + return; + } + + Rs2Bank.withdrawAll(activeLogType.getItemId()); + sleepUntil(() -> Rs2Inventory.hasItem(activeLogType.getItemId()), 3000); + sleep(150, 300); + + Rs2Bank.closeBank(); + state = State.WALKING_BACK; + } + + private void handleWalkingBack(LeaguesFiremakingConfig config) { + status = "Walking back to fire area"; + + if (Rs2Player.getWorldLocation().distanceTo(startPosition) <= config.scanRadius()) { + state = State.SCANNING; + return; + } + + if (!Rs2Player.isMoving()) { + Rs2Walker.walkTo(startPosition, 3); + } + } + + @Override + public void shutdown() { + super.shutdown(); + Rs2Antiban.resetAntibanSettings(); + startPosition = null; + currentLine = null; + state = State.SCANNING; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LogType.java b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LogType.java new file mode 100644 index 0000000000..007732bb01 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/LogType.java @@ -0,0 +1,44 @@ +package net.runelite.client.plugins.microbot.leaguesfiremaking; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.runelite.api.Skill; +import net.runelite.api.gameval.ItemID; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; + +@Getter +@RequiredArgsConstructor +public enum LogType { + LOGS("Logs", ItemID.LOGS, 1), + OAK("Oak logs", ItemID.OAK_LOGS, 15), + WILLOW("Willow logs", ItemID.WILLOW_LOGS, 30), + TEAK("Teak logs", ItemID.TEAK_LOGS, 35), + MAPLE("Maple logs", ItemID.MAPLE_LOGS, 45), + MAHOGANY("Mahogany logs", ItemID.MAHOGANY_LOGS, 50), + YEW("Yew logs", ItemID.YEW_LOGS, 60), + MAGIC("Magic logs", ItemID.MAGIC_LOGS, 75), + REDWOOD("Redwood logs", ItemID.REDWOOD_LOGS, 90); + + private final String logName; + private final int itemId; + private final int levelRequired; + + public boolean hasRequiredLevel() { + return Rs2Player.getSkillRequirement(Skill.FIREMAKING, levelRequired); + } + + public static LogType getBestForLevel() { + LogType best = LOGS; + for (LogType log : values()) { + if (log.hasRequiredLevel()) { + best = log; + } + } + return best; + } + + @Override + public String toString() { + return logName; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/State.java b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/State.java new file mode 100644 index 0000000000..98e6caa0e2 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/State.java @@ -0,0 +1,9 @@ +package net.runelite.client.plugins.microbot.leaguesfiremaking; + +public enum State { + SCANNING, + WALKING_TO_LINE, + BURNING, + BANKING, + WALKING_BACK +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/TileScanner.java b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/TileScanner.java new file mode 100644 index 0000000000..d41e0e531d --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguesfiremaking/TileScanner.java @@ -0,0 +1,125 @@ +package net.runelite.client.plugins.microbot.leaguesfiremaking; + +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.util.tile.Rs2Tile; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +@Slf4j +public class TileScanner { + + private static final int FIRE_ID = 26185; + private static final int FIRE_ID_ALT = 49927; + + public enum TileState { + OPEN, + FIRE, + BLOCKED + } + + public static TileState classifyTile(WorldPoint point, Set fireTiles, Set objectTiles) { + if (fireTiles.contains(point)) return TileState.FIRE; + if (objectTiles.contains(point)) return TileState.BLOCKED; + if (!Rs2Tile.isWalkable(point)) return TileState.BLOCKED; + return TileState.OPEN; + } + + public static List findFireLines(WorldPoint center, int radius) { + Set fireTiles = new HashSet<>(); + Set objectTiles = new HashSet<>(); + + Microbot.getRs2TileObjectCache().getStream() + .filter(obj -> obj.getWorldLocation().distanceTo(center) <= radius) + .forEach(obj -> { + int id = obj.getId(); + WorldPoint loc = obj.getWorldLocation(); + if (id == FIRE_ID || id == FIRE_ID_ALT) { + fireTiles.add(loc); + } else { + objectTiles.add(loc); + } + }); + + List lines = new ArrayList<>(); + int plane = center.getPlane(); + + for (int y = center.getY() - radius; y <= center.getY() + radius; y++) { + int runStartX = -1; + int runLength = 0; + + for (int x = center.getX() - radius; x <= center.getX() + radius; x++) { + WorldPoint point = new WorldPoint(x, y, plane); + TileState state = classifyTile(point, fireTiles, objectTiles); + + if (state == TileState.OPEN) { + if (runStartX == -1) { + runStartX = x; + } + runLength++; + } else { + if (runLength >= 5) { + lines.add(new FireLine( + new WorldPoint(runStartX, y, plane), + new WorldPoint(runStartX + runLength - 1, y, plane), + runLength + )); + } + runStartX = -1; + runLength = 0; + } + } + if (runLength >= 5) { + lines.add(new FireLine( + new WorldPoint(runStartX, y, plane), + new WorldPoint(runStartX + runLength - 1, y, plane), + runLength + )); + } + } + + // Score lines: balance length vs proximity to start position + // A nearby shorter line beats a far-away longer one + lines.sort(Comparator.comparingDouble((FireLine l) -> { + int distance = center.distanceTo(l.getEastEnd()); + // Penalize distance heavily: each tile away reduces effective score + return -(l.getLength() - distance * 0.5); + })); + + return lines; + } + + public static FireLine findBestLine(WorldPoint center, int radius) { + List lines = findFireLines(center, radius); + return lines.isEmpty() ? null : lines.get(0); + } + + public static boolean hasFire(WorldPoint point) { + return Microbot.getRs2TileObjectCache().getStream() + .anyMatch(obj -> obj.getWorldLocation().equals(point) + && (obj.getId() == FIRE_ID || obj.getId() == FIRE_ID_ALT)); + } + + public static Set buildFireSet(WorldPoint center, int radius) { + Set fireTiles = new HashSet<>(); + Microbot.getRs2TileObjectCache().getStream() + .filter(obj -> obj.getWorldLocation().distanceTo(center) <= radius) + .filter(obj -> obj.getId() == FIRE_ID || obj.getId() == FIRE_ID_ALT) + .forEach(obj -> fireTiles.add(obj.getWorldLocation())); + return fireTiles; + } + + public static Set buildObjectSet(WorldPoint center, int radius) { + Set objectTiles = new HashSet<>(); + Microbot.getRs2TileObjectCache().getStream() + .filter(obj -> obj.getWorldLocation().distanceTo(center) <= radius) + .filter(obj -> obj.getId() != FIRE_ID && obj.getId() != FIRE_ID_ALT) + .forEach(obj -> objectTiles.add(obj.getWorldLocation())); + return objectTiles; + } +} From 9897a3c102f5ff0f850c9f653e30a97ade32feeb Mon Sep 17 00:00:00 2001 From: Max Date: Wed, 15 Apr 2026 22:42:39 -0700 Subject: [PATCH 43/95] feat(AutoMining): add League mode to prevent AFK logout (#392) Adds an optional "League mode (anti-AFK)" toggle that presses a random arrow key whenever the client's idle ticks approach its idle-timeout threshold. This keeps the session alive on Leagues where the auto-bank relic lets you mine indefinitely without any interaction that would otherwise reset the idle timer. Co-authored-by: dev --- .../plugins/microbot/mining/AutoMiningConfig.java | 11 +++++++++++ .../plugins/microbot/mining/AutoMiningPlugin.java | 2 +- .../plugins/microbot/mining/AutoMiningScript.java | 6 ++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningConfig.java b/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningConfig.java index 0c34142012..01f2345ecc 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningConfig.java @@ -86,6 +86,17 @@ default int maxPlayersInArea() { return 0; } + @ConfigItem( + keyName = "leagueMode", + name = "League mode (anti-AFK)", + description = "Periodically presses a key to reset the idle timer so you never get logged out", + position = 4, + section = generalSection + ) + default boolean leagueMode() { + return false; + } + @ConfigItem( keyName = "UseBank", name = "UseBank", diff --git a/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningPlugin.java index 43f6db18b6..20384f2f44 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningPlugin.java @@ -24,7 +24,7 @@ ) @Slf4j public class AutoMiningPlugin extends Plugin { - public static final String version = "1.0.11"; + public static final String version = "1.0.12"; @Inject private AutoMiningConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningScript.java b/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningScript.java index 3f3d861dde..b5766ff85e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningScript.java @@ -19,12 +19,14 @@ 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.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.walker.Rs2Walker; import java.util.ArrayList; +import java.awt.event.KeyEvent; import java.util.Arrays; import java.util.Comparator; import java.util.List; @@ -54,6 +56,10 @@ public boolean run(AutoMiningConfig config) { try { if (!super.run()) return; if (!Microbot.isLoggedIn()) return; + if (config.leagueMode() && Rs2Player.checkIdleLogout(Rs2Random.between(500, 1500))) { + int[] arrowKeys = { KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT, KeyEvent.VK_UP, KeyEvent.VK_DOWN }; + Rs2Keyboard.keyPress(arrowKeys[Rs2Random.between(0, arrowKeys.length - 1)]); + } if (Rs2AntibanSettings.actionCooldownActive) return; if (initialPlayerLocation == null) { initialPlayerLocation = Rs2Player.getWorldLocation(); From c35f19ec3ebe06853e533c54e7d7f984259c9669 Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Thu, 16 Apr 2026 01:43:02 -0400 Subject: [PATCH 44/95] fix(BirdHunter): trap ownership, safer drops, placement & click reliability (#391) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(BirdHunter): trap ownership, safer drops, area bugs (v1.0.2) - Track own traps by spatial proximity at GameObjectSpawned (RuneLite Hunter pattern) so the bot ignores other players' bird snares and only interacts with its own. Seed lastTickLocalPlayerLocation on the client thread at startUp to close a race where the first laid snare was never recorded and the bot got stuck in an infinite movePlayerOffObject loop on its own untracked trap. - Strict drop whitelist: only raw bird meat, plus bones when bury-bones is off. Replaces dropAllExcept(keepList) which could wipe the entire inventory if the keep list was misconfigured. - walkBackToArea picks the nearest tile in the hunting area instead of a random one. - Hunting area centered correctly via (2*radius+1)^2 instead of the off-center (radius^2+1)^2 that biased NE. * fix(BirdHunter): proper error stop, click retry, loot-agnostic placement - Missing-snares precondition now uses the canonical Microbot pattern (showMessage + stopPlugin from inside the scheduled loop) so the plugin actually stops instead of silently idling with the toggle on. - Remove keepItemNames config — unused after the strict drop whitelist (raw bird meat + bones when bury-bones is off). - interactWithTrap retries the click up to 3x and exits as soon as the inventory changes. Prior single-click + 7s inventory-changes wait + 2×2s gaussian sleeps stalled ~13s per missed click; now ~1-2s on hit, ~8s worst case. - setTrap uses isGameObjectAt instead of Rs2Player.isStandingOnGameObject because the latter also returns true for ground items. Dropped loot doesn't actually block snare placement, so the bot was needlessly walking off loot tiles. --- .../microbot/birdhunter/BirdHunterConfig.java | 10 -- .../microbot/birdhunter/BirdHunterPlugin.java | 120 +++++++++++++++++- .../microbot/birdhunter/BirdHunterScript.java | 102 +++++++++++---- 3 files changed, 194 insertions(+), 38 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterConfig.java b/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterConfig.java index a2e547e407..4a8bddb9c3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterConfig.java @@ -17,16 +17,6 @@ default boolean buryBones() { return true; } - @ConfigItem( - keyName = "keepItemNames", - name = "Keep Item Names", - description = "Comma-separated list of item names that should not be dropped", - position = 3 - ) - default String keepItemNames() { - return "Bird snare"; - } - @ConfigItem( keyName = "huntingRadiusValue", name = "Hunting radius", diff --git a/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterPlugin.java index 57f7045b26..75425fddb5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterPlugin.java @@ -1,16 +1,32 @@ package net.runelite.client.plugins.microbot.birdhunter; import com.google.inject.Provides; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Client; +import net.runelite.api.GameObject; +import net.runelite.api.Player; +import net.runelite.api.Tile; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.GameObjectSpawned; +import net.runelite.api.events.GameTick; +import net.runelite.api.gameval.ObjectID; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.ConfigChanged; 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.Microbot; import net.runelite.client.plugins.microbot.PluginConstants; 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; @PluginDescriptor( name = PluginDescriptor.zerozero + "Bird Hunter", @@ -26,7 +42,10 @@ @Slf4j public class BirdHunterPlugin extends Plugin { - public final static String version = "1.0.1"; + public final static String version = "1.0.2"; + + @Inject + private Client client; @Inject private BirdHunterConfig config; @@ -40,6 +59,10 @@ public class BirdHunterPlugin extends Plugin { @Inject private OverlayManager overlayManager; + @Getter + private final Map traps = new HashMap<>(); + private WorldPoint lastTickLocalPlayerLocation; + @Provides BirdHunterConfig provideConfig(ConfigManager configManager) { return configManager.getConfig(BirdHunterConfig.class); @@ -47,8 +70,20 @@ BirdHunterConfig provideConfig(ConfigManager configManager) { @Override protected void startUp() { + // Seed lastTickLocalPlayerLocation on the client thread before the + // script loop can fire a layBirdSnare — otherwise the first snare's + // GameObjectSpawned event sees a null baseline, the trap is never + // recorded as owned, and the filter/setTrap path puts the bot in an + // infinite movePlayerOffObject loop on its own untracked trap. + // startUp runs on the AWT EDT; reading player location from there + // throws "must be called on client thread". + lastTickLocalPlayerLocation = Microbot.getClientThread().runOnClientThreadOptional(() -> { + Player lp = client.getLocalPlayer(); + return lp != null ? lp.getWorldLocation() : null; + }).orElse(null); + if (config.startScript()) { - birdHunterScript.run(config); + birdHunterScript.run(config, this); this.overlayManager.add(this.birdHunterOverlay); } } @@ -57,13 +92,14 @@ protected void startUp() { protected void shutDown() { this.overlayManager.remove(this.birdHunterOverlay); birdHunterScript.shutdown(); + traps.clear(); } @Subscribe public void onConfigChanged(ConfigChanged event) { if (event.getGroup().equals("birdhunter") && event.getKey().equals("startScript")) { if (config.startScript()) { - birdHunterScript.run(config); + birdHunterScript.run(config, this); } else { birdHunterScript.shutdown(); } @@ -72,4 +108,82 @@ public void onConfigChanged(ConfigChanged event) { birdHunterScript.updateHuntingArea(config); } } + + @Subscribe + public void onGameObjectSpawned(GameObjectSpawned event) { + final GameObject go = event.getGameObject(); + final WorldPoint trapLocation = go.getWorldLocation(); + final HunterTrap myTrap = traps.get(trapLocation); + + switch (go.getId()) { + // Empty placed snare — ownership decision point. Player location is + // updated before this event fires, so we compare the spawn tile to + // the PREVIOUS tick's player location. distance == 0 means the snare + // spawned on the exact tile the player stood on last tick, i.e. ours. + case ObjectID.HUNTING_OJIBWAY_TRAP: + if (lastTickLocalPlayerLocation != null + && trapLocation.distanceTo(lastTickLocalPlayerLocation) == 0) { + traps.put(trapLocation, new HunterTrap(go)); + } + break; + + case ObjectID.HUNTING_OJIBWAY_TRAP_FULL_JUNGLE: + case ObjectID.HUNTING_OJIBWAY_TRAP_FULL_POLAR: + case ObjectID.HUNTING_OJIBWAY_TRAP_FULL_DESERT: + case ObjectID.HUNTING_OJIBWAY_TRAP_FULL_WOODLAND: + case ObjectID.HUNTING_OJIBWAY_TRAP_FULL_COLOURED: + if (myTrap != null) { + myTrap.setState(HunterTrap.State.FULL); + myTrap.resetTimer(); + } + break; + + case ObjectID.HUNTING_OJIBWAY_TRAP_BROKEN: + if (myTrap != null) { + myTrap.setState(HunterTrap.State.EMPTY); + myTrap.resetTimer(); + } + break; + + case ObjectID.HUNTING_OJIBWAY_TRAP_FAILING: + case ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_JUNGLE: + case ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_COLOURED: + case ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_DESERT: + case ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_WOODLAND: + case ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_POLAR: + if (myTrap != null) { + myTrap.setState(HunterTrap.State.TRANSITION); + } + break; + } + } + + @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)) it.remove(); + continue; + } + + GameObject[] objects = tiles[world.getPlane()][local.getSceneX()][local.getSceneY()].getGameObjects(); + boolean anyObject = false; + for (GameObject o : objects) { + if (o != null) { anyObject = true; break; } + } + if (!anyObject) it.remove(); + } + + Player lp = client.getLocalPlayer(); + if (lp != null) lastTickLocalPlayerLocation = lp.getWorldLocation(); + } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterScript.java b/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterScript.java index d7f79fd966..724b7711f0 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/birdhunter/BirdHunterScript.java @@ -23,8 +23,10 @@ import java.util.ArrayList; import java.util.List; import java.util.Random; +import java.util.Set; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; public class BirdHunterScript extends Script { @@ -41,13 +43,12 @@ public class BirdHunterScript extends Script { private final Pair boneThresholdRange = Pair.of(3, 10); private final Pair HandleInventoryThresholdRange = Pair.of(18, 25); - public boolean run(BirdHunterConfig config) { + private BirdHunterPlugin plugin; + + public boolean run(BirdHunterConfig config, BirdHunterPlugin plugin) { + this.plugin = plugin; Microbot.log("Bird Hunter script started."); - if (!hasRequiredSnares()) { - Microbot.log("Not enough bird snares in inventory. Stopping the script."); - return false; - } initialStartTile = Rs2Player.getWorldLocation(); randomBoneThreshold = ThreadLocalRandom.current().nextInt(boneThresholdRange.getLeft(), boneThresholdRange.getRight()); @@ -64,6 +65,14 @@ public boolean run(BirdHunterConfig config) { try { if (!super.run() || !Microbot.isLoggedIn()) return; + if (!hasRequiredSnares()) { + int required = getAvailableTraps(Rs2Player.getRealSkillLevel(Skill.HUNTER)); + Microbot.showMessage("Bird Hunter needs at least " + required + + " bird snares in inventory for your Hunter level. Stopping plugin."); + Microbot.stopPlugin(plugin); + return; + } + if (!isInHuntingArea()) { Microbot.log("Player is outside the designated hunting area."); walkBackToArea(); @@ -83,20 +92,21 @@ public boolean run(BirdHunterConfig config) { private boolean hasRequiredSnares() { int hunterLevel = Rs2Player.getRealSkillLevel(Skill.HUNTER); - int allowedSnares = getAvailableTraps(hunterLevel); // Calculate the allowed number of snares + int allowedSnares = getAvailableTraps(hunterLevel); int snaresInInventory = Rs2Inventory.itemQuantity(ItemID.HUNTING_OJIBWAY_BIRD_SNARE); Microbot.log("Allowed snares: " + allowedSnares + ", Snares in inventory: " + snaresInInventory); - return snaresInInventory >= allowedSnares; // Return true if enough snares, false otherwise + return snaresInInventory >= allowedSnares; } public void updateHuntingArea(BirdHunterConfig config) { huntingRadius = config.huntingRadiusValue(); + int side = (2 * huntingRadius) + 1; dynamicHuntingArea = new WorldArea( initialStartTile.getX() - huntingRadius, initialStartTile.getY() - huntingRadius, - (huntingRadius * huntingRadius) + 1, (huntingRadius * huntingRadius) + 1, + side, side, initialStartTile.getPlane() ); } @@ -107,7 +117,7 @@ private boolean isInHuntingArea() { } private void walkBackToArea() { - WorldPoint walkableTile = getSafeWalkableTile(dynamicHuntingArea); + WorldPoint walkableTile = getNearestSafeWalkableTileInArea(dynamicHuntingArea); if (walkableTile != null) { Rs2Walker.walkFastCanvas(walkableTile); @@ -117,6 +127,28 @@ private void walkBackToArea() { } } + private WorldPoint getNearestSafeWalkableTileInArea(WorldArea huntingArea) { + WorldPoint from = Rs2Player.getWorldLocation(); + WorldPoint nearest = null; + int bestDist = Integer.MAX_VALUE; + + for (int x = initialStartTile.getX() - huntingRadius; x <= initialStartTile.getX() + huntingRadius; x++) { + for (int y = initialStartTile.getY() - huntingRadius; y <= initialStartTile.getY() + huntingRadius; y++) { + WorldPoint candidate = new WorldPoint(x, y, huntingArea.getPlane()); + LocalPoint localPoint = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), candidate); + if (localPoint == null || !huntingArea.contains(candidate)) continue; + if (!Rs2Tile.isWalkable(localPoint) || isGameObjectAt(candidate)) continue; + + int dist = from.distanceTo(candidate); + if (dist < bestDist) { + bestDist = dist; + nearest = candidate; + } + } + } + return nearest; + } + private void handleTraps(BirdHunterConfig config) { List successfulTraps = new ArrayList<>(); successfulTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_JUNGLE).toList()); @@ -134,10 +166,20 @@ private void handleTraps(BirdHunterConfig config) { catchingTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_TRAPPING_POLAR).toList()); catchingTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_FULL_JUNGLE).toList()); - List failedTraps = Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_BROKEN).toList(); + List failedTraps = new ArrayList<>(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_BROKEN).toList()); List idleTraps = new ArrayList<>(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP).toList()); idleTraps.addAll(Microbot.getRs2TileObjectCache().query().withId(ObjectID.HUNTING_OJIBWAY_TRAP_FAILING).toList()); + // Ownership filter: the plugin records a trap's WorldPoint when it spawns + // on the player's previous-tick tile. Skip everything else — other players' + // snares should not be clicked, and they must not inflate totalTraps below. + Set owned = plugin.getTraps().keySet(); + Predicate mine = t -> owned.contains(t.getWorldLocation()); + successfulTraps.removeIf(mine.negate()); + catchingTraps.removeIf(mine.negate()); + failedTraps.removeIf(mine.negate()); + idleTraps.removeIf(mine.negate()); + int availableTraps = getAvailableTraps(Rs2Player.getRealSkillLevel(Skill.HUNTER)); int totalTraps = successfulTraps.size() + failedTraps.size() + idleTraps.size() + catchingTraps.size(); @@ -174,7 +216,11 @@ private void handleTraps(BirdHunterConfig config) { private void setTrap(BirdHunterConfig config) { if (!Rs2Inventory.contains(ItemID.HUNTING_OJIBWAY_BIRD_SNARE)) return; - if (Rs2Player.isStandingOnGameObject()) { + // Rs2Player.isStandingOnGameObject() also returns true for ground items + // (dropped loot), which don't actually block snare placement in-game. + // Only skip the tile when there's a real game object on it (existing + // trap, tree, rock). + if (isGameObjectAt(Rs2Player.getWorldLocation())) { if (!movePlayerOffObject()) return; } @@ -245,11 +291,17 @@ private boolean movePlayerOffObject() { private boolean interactWithTrap(Rs2TileObjectModel birdSnare) { - sleep(Rs2Random.randomGaussian(2000, 1250)); - birdSnare.click(); - sleepUntil(() -> Rs2Inventory.waitForInventoryChanges(7000)); - sleep(Rs2Random.randomGaussian(2000, 1250)); - + if (!plugin.getTraps().containsKey(birdSnare.getWorldLocation())) return false; + + // Retry the click until inventory changes (snare returned / loot received). + // Previously a single click with a 7s inventory-changes wait and 2×2s + // gaussian sleeps meant ~13s of stall on a missed click. + int invBefore = Rs2Inventory.count(); + for (int attempt = 0; attempt < 3; attempt++) { + birdSnare.click(); + if (sleepUntil(() -> Rs2Inventory.count() != invBefore, 2500)) break; + } + sleep(Rs2Random.randomGaussian(600, 200)); return false; } @@ -274,11 +326,9 @@ private void checkForBonesAndHandleInventory(BirdHunterConfig config) { } private void handleInventory(BirdHunterConfig config) { - if (config.buryBones() && Rs2Inventory.count("Bones") > randomBoneThreshold) { + if (config.buryBones()) { buryBones(config); - } - buryBones(config); dropItems(config); } @@ -294,14 +344,16 @@ private void buryBones(BirdHunterConfig config) { } } + // Strict drop whitelist. Replaces an earlier dropAllExcept(keepList) that would + // nuke the entire inventory if the keep list was misconfigured. Bird snaring + // only produces Raw bird meat, Bones, and feathers — feathers stack so we let + // them ride; bones are buried when the config is enabled, dropped otherwise. private void dropItems(BirdHunterConfig config) { - String keepItemsConfig = config.keepItemNames(); - List keepItemNames = List.of(keepItemsConfig.split("\\s*,\\s*")); - - if (!keepItemNames.contains("Bird snare")) { - keepItemNames.add("Bird snare"); + if (config.buryBones()) { + Rs2Inventory.dropAll("Raw bird meat"); + } else { + Rs2Inventory.dropAll("Raw bird meat", "Bones"); } - Rs2Inventory.dropAllExcept(keepItemNames.toArray(new String[0])); } public int getAvailableTraps(int hunterLevel) { From f953f53557b4c5b35803300ab65e2235e86a895e Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Thu, 16 Apr 2026 01:43:51 -0400 Subject: [PATCH 45/95] feat(leftclickcast): Left-Click Cast plugin (#389) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(PluginConstants): add PERT prefix for LeftClickCastPlugin Yellow [P] prefix in the #FFFF00 color family, matching the existing HTML-wrapped prefix convention. * feat(leftclickcast): add PertTargetSpell enum Wraps every target-castable MagicAction (autocast Strike->Surge, ancient Rush/Burst/Blitz/Barrage, non-autocast combat, Arceuus offensives, utility target spells) behind a minimal display-name shape for the plugin config dropdown. * feat(leftclickcast): add LeftClickCastConfig Three items: enabled (master switch), spell (PertTargetSpell dropdown, default Fire Strike), requireMagicWeapon (staff gate). * feat(leftclickcast): add LeftClickCastPlugin onMenuEntryAdded inserts a Cast entry above the Attack entry for attackable NPCs, gated by the enabled flag, a non-null configured spell, and (optionally) a magic-weapon equip check via varbit 357. Cast is dispatched through Rs2Magic.castOn off-thread via CompletableFuture because Global.sleepUntil is a no-op on the client thread and would otherwise silently drop the cast. * docs(leftclickcast): add plugin README Covers purpose, config reference, limitations (no rune or spellbook auto-management, staff-only default, PvP out of scope), and the full supported-spells list. * chore(debug): register LeftClickCastPlugin in debugPlugins * fix(leftclickcast): use VarbitID.COMBAT_WEAPON_CATEGORY net.runelite.api.Varbits is @Deprecated in favor of gameval identifier classes. Same varbit (357), no behavior change. * fix(leftclickcast): move to PostMenuSort + runtime magic-weapon check Two bugs found during manual verification: 1. The static weapon-type set (22/23/26/27) from the design doc was wrong — real STAFF value (at least on this client) is 18. Replaced with a runtime check that reads the weapon's attack-style struct via EnumID.WEAPON_STYLES + ParamID.ATTACK_STYLE_NAME and flags the weapon as magic if any style is Casting or Defensive Casting. Mirrors core AttackStylesPlugin and auto-covers future weapons. 2. Mutating the Attack entry inside onMenuEntryAdded did not survive the game's menu sort pass — entries of type RUNELITE get sorted below NPC_* entries, so left-click kept firing Attack/Walk-Here. Moved the mutation to onPostMenuSort (fires after the sort) and swap the entry to the tail of the array — tail slot is the left-click action in RuneLite's menu model. * feat(leftclickcast): support Players (wilderness / PvP) Scan loop now accepts Attack entries on either NPCs or Players. Dispatch wraps NPCs in Rs2NpcModel; raw Player is passed through, which Rs2Magic.castOn handles via its instanceof Player branch. * feat(leftclickcast): five spell slots with hotkey-driven active slot Adds five `@ConfigItem` spell slots, each with a corresponding Keybind, grouped under "Spell Slots" and "Hotkeys" config sections. A `HotkeyListener` per slot is registered on startUp and unregistered on shutDown; pressing a bound hotkey sets the in-memory active slot and (optionally) posts a chat message with the new spell's display name. The menu-sort hot path now reads the active slot's spell instead of the legacy single `spell` key. The legacy `spell` key remains defined so existing configs aren't invalidated, and is migrated into `slot1Spell` on startUp when slot 1 is still at its default — existing users keep their choice without manual action. Active slot is session-local and resets to slot 1 on every startUp. Bumps plugin version to 1.1.0. * feat(leftclickcast): fast-path cast dispatch via dual menuAction Replaces the async Rs2Magic.castOn pipeline (tab switch + 150-300ms sleep + sleepUntil(isWidgetSelected) + NPC interact) with two synchronous client.menuAction calls fired back-to-back: one WIDGET_TARGET to select the spell client-side, one WIDGET_TARGET_ON_NPC / WIDGET_TARGET_ON_PLAYER to dispatch it on the hovered target. Both packets queue on the same event-loop tick, so the server processes selection and cast on the same game tick — indistinguishable from "I pre-selected the spell and clicked the target". Falls back to Rs2Magic.castOn when the spellbook widget (group 218) isn't loaded yet (first cast after login without opening Magic tab), nudging Rs2Tab.switchTo(InterfaceTab.MAGIC) so subsequent clicks take the fast path. Also falls back when the selected spell isn't on the active spellbook (modern vs. ancients mismatch). Bumps plugin version to 1.2.0. * feat(leftclickcast): enable-toggle hotkey + unified chat feedback Adds an `Enable/Disable Hotkey` config item at the top of the Hotkeys section. Pressing the hotkey flips `config.enabled()` and posts an `ExternalPluginsChanged` event so the open MicrobotConfigPanel rebuilds and the inner Enabled checkbox visually flips in sync — the panel otherwise doesn't subscribe to ConfigChanged for individual checkbox refresh. Renames `activeSlotChatMessage` → `chatFeedback` and uses it as a single gate for all plugin chat output. Both checkbox clicks and the toggle hotkey route through a shared `@Subscribe onConfigChanged` handler, so they emit the same chat message via one code path. Materializes `@ConfigItem` defaults to storage on `startUp` via `configManager.setDefaultConfiguration(config, false)`. Without this, MicrobotConfigPanel's checkbox lookup returns null for newly added keys (`parseBoolean(null) → false`) while the proxy returns the @ConfigItem default, leaving the UI and runtime out of sync. Bumps plugin version to 1.3.0. --------- Co-authored-by: chsami --- .../plugins/microbot/PluginConstants.java | 1 + .../leftclickcast/LeftClickCastConfig.java | 203 +++++++++ .../leftclickcast/LeftClickCastPlugin.java | 391 ++++++++++++++++++ .../leftclickcast/PertTargetSpell.java | 96 +++++ .../microbot/leftclickcast/docs/README.md | 54 +++ .../java/net/runelite/client/Microbot.java | 4 +- 6 files changed, 748 insertions(+), 1 deletion(-) create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leftclickcast/LeftClickCastConfig.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leftclickcast/LeftClickCastPlugin.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leftclickcast/PertTargetSpell.java create mode 100644 src/main/resources/net/runelite/client/plugins/microbot/leftclickcast/docs/README.md diff --git a/src/main/java/net/runelite/client/plugins/microbot/PluginConstants.java b/src/main/java/net/runelite/client/plugins/microbot/PluginConstants.java index 143c2a8fbb..b7bd5eed0d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/PluginConstants.java +++ b/src/main/java/net/runelite/client/plugins/microbot/PluginConstants.java @@ -34,6 +34,7 @@ private PluginConstants() public static final String NATE = "[N] "; public static final String SYN = "[Syn] "; public static final String BIGL = "[BL] "; + public static final String PERT = "[P] "; public static final String DV = "[DV] "; public static final boolean DEFAULT_ENABLED = false; diff --git a/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/LeftClickCastConfig.java b/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/LeftClickCastConfig.java new file mode 100644 index 0000000000..f387be7e69 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/LeftClickCastConfig.java @@ -0,0 +1,203 @@ +package net.runelite.client.plugins.microbot.leftclickcast; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.ConfigSection; +import net.runelite.client.config.Keybind; + +@ConfigGroup("leftclickcast") +public interface LeftClickCastConfig extends Config +{ + @ConfigItem( + keyName = "enabled", + name = "Enabled", + description = "Replace the left-click Attack option on NPCs with Cast Spell", + position = 0 + ) + default boolean enabled() + { + return true; + } + + // Retained so existing stored config is not invalidated. Read once at startUp for migration into slot1Spell. + @ConfigItem( + keyName = "spell", + name = "Spell", + description = "Legacy single-spell setting — migrated into Slot 1 on startup.", + position = 1 + ) + default PertTargetSpell spell() + { + return PertTargetSpell.FIRE_STRIKE; + } + + @ConfigItem( + keyName = "requireMagicWeapon", + name = "Require magic weapon", + description = "When enabled, the Cast entry is only inserted while a staff, bladed staff, powered staff, or powered wand is equipped. Disable to cast regardless of equipped weapon.", + position = 2 + ) + default boolean requireMagicWeapon() + { + return true; + } + + @ConfigSection( + name = "Spell Slots", + description = "Up to five spells that can be bound to hotkeys for mid-fight swapping.", + position = 10 + ) + String spellSlotsSection = "spellSlots"; + + @ConfigSection( + name = "Hotkeys", + description = "Hotkey bindings that switch the active spell slot.", + position = 11 + ) + String hotkeysSection = "hotkeys"; + + @ConfigItem( + keyName = "slot1Spell", + name = "Slot 1 Spell", + description = "Spell for slot 1 (the startup-active slot).", + section = spellSlotsSection, + position = 0 + ) + default PertTargetSpell slot1Spell() + { + return PertTargetSpell.FIRE_STRIKE; + } + + @ConfigItem( + keyName = "slot2Spell", + name = "Slot 2 Spell", + description = "Spell for slot 2.", + section = spellSlotsSection, + position = 1 + ) + default PertTargetSpell slot2Spell() + { + return PertTargetSpell.FIRE_STRIKE; + } + + @ConfigItem( + keyName = "slot3Spell", + name = "Slot 3 Spell", + description = "Spell for slot 3.", + section = spellSlotsSection, + position = 2 + ) + default PertTargetSpell slot3Spell() + { + return PertTargetSpell.FIRE_STRIKE; + } + + @ConfigItem( + keyName = "slot4Spell", + name = "Slot 4 Spell", + description = "Spell for slot 4.", + section = spellSlotsSection, + position = 3 + ) + default PertTargetSpell slot4Spell() + { + return PertTargetSpell.FIRE_STRIKE; + } + + @ConfigItem( + keyName = "slot5Spell", + name = "Slot 5 Spell", + description = "Spell for slot 5.", + section = spellSlotsSection, + position = 4 + ) + default PertTargetSpell slot5Spell() + { + return PertTargetSpell.FIRE_STRIKE; + } + + @ConfigItem( + keyName = "enabledToggleHotkey", + name = "Enable/Disable Hotkey", + description = "Hotkey that toggles the plugin on and off.", + section = hotkeysSection, + position = 0 + ) + default Keybind enabledToggleHotkey() + { + return Keybind.NOT_SET; + } + + @ConfigItem( + keyName = "slot1Hotkey", + name = "Slot 1 Hotkey", + description = "Hotkey that activates slot 1.", + section = hotkeysSection, + position = 1 + ) + default Keybind slot1Hotkey() + { + return Keybind.NOT_SET; + } + + @ConfigItem( + keyName = "slot2Hotkey", + name = "Slot 2 Hotkey", + description = "Hotkey that activates slot 2.", + section = hotkeysSection, + position = 2 + ) + default Keybind slot2Hotkey() + { + return Keybind.NOT_SET; + } + + @ConfigItem( + keyName = "slot3Hotkey", + name = "Slot 3 Hotkey", + description = "Hotkey that activates slot 3.", + section = hotkeysSection, + position = 3 + ) + default Keybind slot3Hotkey() + { + return Keybind.NOT_SET; + } + + @ConfigItem( + keyName = "slot4Hotkey", + name = "Slot 4 Hotkey", + description = "Hotkey that activates slot 4.", + section = hotkeysSection, + position = 4 + ) + default Keybind slot4Hotkey() + { + return Keybind.NOT_SET; + } + + @ConfigItem( + keyName = "slot5Hotkey", + name = "Slot 5 Hotkey", + description = "Hotkey that activates slot 5.", + section = hotkeysSection, + position = 5 + ) + default Keybind slot5Hotkey() + { + return Keybind.NOT_SET; + } + + @ConfigItem( + keyName = "chatFeedback", + name = "Chat feedback", + description = "Post a game chat message on plugin events (active slot change, enable/disable toggle).", + section = hotkeysSection, + position = 6 + ) + default boolean chatFeedback() + { + return true; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/LeftClickCastPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/LeftClickCastPlugin.java new file mode 100644 index 0000000000..0a0ed5f989 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/LeftClickCastPlugin.java @@ -0,0 +1,391 @@ +package net.runelite.client.plugins.microbot.leftclickcast; + +import com.google.inject.Provides; +import java.util.concurrent.CompletableFuture; +import javax.inject.Inject; +import net.runelite.api.Actor; +import net.runelite.api.ChatMessageType; +import net.runelite.api.Client; +import net.runelite.api.EnumComposition; +import net.runelite.api.EnumID; +import net.runelite.api.MenuAction; +import net.runelite.api.MenuEntry; +import net.runelite.api.Menu; +import net.runelite.api.NPC; +import net.runelite.api.ParamID; +import net.runelite.api.Player; +import net.runelite.api.StructComposition; +import net.runelite.api.events.PostMenuSort; +import net.runelite.api.gameval.VarbitID; +import net.runelite.client.chat.ChatMessageManager; +import net.runelite.client.chat.QueuedMessage; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.config.Keybind; +import net.runelite.client.eventbus.EventBus; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.events.ConfigChanged; +import net.runelite.client.events.ExternalPluginsChanged; +import net.runelite.client.input.KeyManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.api.widgets.Widget; +import net.runelite.client.plugins.microbot.PluginConstants; +import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; +import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.globval.enums.InterfaceTab; +import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; +import net.runelite.client.plugins.skillcalculator.skills.MagicAction; +import net.runelite.client.util.HotkeyListener; + +@PluginDescriptor( + name = PluginConstants.PERT + "Left-Click Cast", + description = "Replaces left-click Attack on NPCs with a preconfigured Cast Spell action.", + tags = {"magic", "combat", "spell", "left-click", "cast", "pvm", "pvp"}, + authors = {"Pert"}, + version = LeftClickCastPlugin.version, + minClientVersion = "2.0.13", + enabledByDefault = PluginConstants.DEFAULT_ENABLED, + isExternal = PluginConstants.IS_EXTERNAL +) +public class LeftClickCastPlugin extends Plugin +{ + static final String version = "1.3.0"; + + private static final int SLOT_COUNT = 5; + + @Inject + private Client client; + + @Inject + private LeftClickCastConfig config; + + @Inject + private KeyManager keyManager; + + @Inject + private ChatMessageManager chatMessageManager; + + @Inject + private ConfigManager configManager; + + @Inject + private EventBus eventBus; + + private volatile int activeSlot = 0; + + private final HotkeyListener[] hotkeyListeners = new HotkeyListener[SLOT_COUNT]; + + private HotkeyListener enabledToggleListener; + + @Provides + LeftClickCastConfig provideConfig(ConfigManager configManager) + { + return configManager.getConfig(LeftClickCastConfig.class); + } + + @Override + protected void startUp() + { + // MicrobotConfigPanel renders boolean checkboxes from raw stored values; missing keys read as false + // even when the @ConfigItem default is true. Materialize defaults so the UI and the proxy agree. + configManager.setDefaultConfiguration(config, false); + activeSlot = 0; + for (int i = 0; i < SLOT_COUNT; i++) + { + final int slotIndex = i; + HotkeyListener listener = new HotkeyListener(() -> slotHotkeyFor(slotIndex)) + { + @Override + public void hotkeyPressed() + { + onSlotHotkey(slotIndex); + } + }; + hotkeyListeners[i] = listener; + keyManager.registerKeyListener(listener); + } + enabledToggleListener = new HotkeyListener(() -> config.enabledToggleHotkey()) + { + @Override + public void hotkeyPressed() + { + onEnabledToggleHotkey(); + } + }; + keyManager.registerKeyListener(enabledToggleListener); + migrateLegacySpellKey(); + } + + @Override + protected void shutDown() + { + for (int i = 0; i < hotkeyListeners.length; i++) + { + HotkeyListener listener = hotkeyListeners[i]; + if (listener != null) + { + keyManager.unregisterKeyListener(listener); + hotkeyListeners[i] = null; + } + } + if (enabledToggleListener != null) + { + keyManager.unregisterKeyListener(enabledToggleListener); + enabledToggleListener = null; + } + } + + @Subscribe + public void onPostMenuSort(PostMenuSort event) + { + // Don't mutate while the right-click menu is open — entries are frozen at open-time. + if (client.isMenuOpen()) + { + return; + } + if (!config.enabled()) + { + return; + } + PertTargetSpell spell = slotSpellFor(activeSlot); + if (spell == null) + { + return; + } + if (config.requireMagicWeapon() && !isMagicWeaponEquipped()) + { + return; + } + + Menu menu = client.getMenu(); + MenuEntry[] entries = menu.getMenuEntries(); + + // Find the top-most NPC or Player Attack entry (the game's already-sorted left-click candidate). + int attackIdx = -1; + Actor targetActor = null; + for (int i = entries.length - 1; i >= 0; i--) + { + MenuEntry e = entries[i]; + if (!"Attack".equals(e.getOption())) + { + continue; + } + if (e.getNpc() != null) + { + attackIdx = i; + targetActor = e.getNpc(); + break; + } + if (e.getPlayer() != null) + { + attackIdx = i; + targetActor = e.getPlayer(); + break; + } + } + if (attackIdx < 0) + { + return; + } + + MenuEntry attack = entries[attackIdx]; + final Actor dispatchTarget = targetActor; + final PertTargetSpell dispatchSpell = spell; + attack.setOption("Cast " + dispatchSpell.getDisplayName()); + attack.setType(MenuAction.RUNELITE); + attack.onClick(e -> castOnTargetFast(dispatchSpell, dispatchTarget)); + + // Move to the tail of the array — that slot is the left-click action in RuneLite's menu model. + if (attackIdx != entries.length - 1) + { + entries[attackIdx] = entries[entries.length - 1]; + entries[entries.length - 1] = attack; + menu.setMenuEntries(entries); + } + } + + private Keybind slotHotkeyFor(int index) + { + switch (index) + { + case 0: + return config.slot1Hotkey(); + case 1: + return config.slot2Hotkey(); + case 2: + return config.slot3Hotkey(); + case 3: + return config.slot4Hotkey(); + case 4: + return config.slot5Hotkey(); + default: + return Keybind.NOT_SET; + } + } + + private PertTargetSpell slotSpellFor(int index) + { + switch (index) + { + case 0: + return config.slot1Spell(); + case 1: + return config.slot2Spell(); + case 2: + return config.slot3Spell(); + case 3: + return config.slot4Spell(); + case 4: + return config.slot5Spell(); + default: + return config.slot1Spell(); + } + } + + private void onSlotHotkey(int index) + { + activeSlot = index; + if (config.chatFeedback()) + { + PertTargetSpell spell = slotSpellFor(index); + String display = spell != null ? spell.getDisplayName() : "(no spell)"; + chatMessageManager.queue(QueuedMessage.builder() + .type(ChatMessageType.GAMEMESSAGE) + .value("Left-Click Cast: now casting " + display) + .build()); + } + } + + private void onEnabledToggleHotkey() + { + boolean newValue = !config.enabled(); + configManager.setConfiguration("leftclickcast", "enabled", newValue); + // MicrobotConfigPanel doesn't subscribe to ConfigChanged for individual checkbox refresh, but it does + // rebuild on ExternalPluginsChanged. Posting that here makes the open config panel re-read this and + // every other config item, so the "Enabled" checkbox visually flips to match the keybind toggle. + eventBus.post(new ExternalPluginsChanged()); + // Chat feedback is emitted by onConfigChanged so checkbox clicks and hotkey presses share one path. + } + + @Subscribe + public void onConfigChanged(ConfigChanged event) + { + if (!"leftclickcast".equals(event.getGroup()) || !"enabled".equals(event.getKey())) + { + return; + } + if (!config.chatFeedback()) + { + return; + } + boolean enabled = "true".equals(event.getNewValue()); + chatMessageManager.queue(QueuedMessage.builder() + .type(ChatMessageType.GAMEMESSAGE) + .value("Left-Click Cast: " + (enabled ? "enabled" : "disabled")) + .build()); + } + + // Fast-path cast: fire two synchronous client.menuAction packets back-to-back so the server processes + // the spell selection and the spell-on-target dispatch on the same game tick. Falls back to + // Rs2Magic.castOn (tab switch + sleeps + clicks) if the spellbook widget isn't loaded yet or the + // spell isn't on the current spellbook. + private void castOnTargetFast(PertTargetSpell spell, Actor target) + { + if (target == null) + { + return; + } + MagicAction magic = spell.getMagicAction(); + Widget magicRoot = client.getWidget(218, 0); + boolean widgetReady = magicRoot != null && magicRoot.getStaticChildren() != null; + if (widgetReady) + { + try + { + int spellWidgetId = magic.getWidgetId(); + // Packet 1: select the spell client-side (WIDGET_TARGET on the spell widget). + client.menuAction(-1, spellWidgetId, MenuAction.WIDGET_TARGET, 1, -1, "Cast", magic.getName()); + // Packet 2: dispatch the selected spell on the target, same tick. + if (target instanceof NPC) + { + NPC npc = (NPC) target; + client.menuAction(0, 0, MenuAction.WIDGET_TARGET_ON_NPC, npc.getIndex(), -1, "Use", npc.getName()); + } + else if (target instanceof Player) + { + Player p = (Player) target; + client.menuAction(0, 0, MenuAction.WIDGET_TARGET_ON_PLAYER, p.getId(), -1, "Use", p.getName()); + } + return; + } + catch (Exception ignored) + { + // Spell not on the active spellbook (e.g., modern while on ancients) — fall through. + } + } + else + { + // Spellbook widget not yet loaded this session; nudge it open so the next click is fast. + Rs2Tab.switchTo(InterfaceTab.MAGIC); + } + // Slow fallback path. Rs2Magic.castOn uses sleepUntil which is a no-op on the client thread, so dispatch async. + final Actor dispatch = target instanceof NPC ? new Rs2NpcModel((NPC) target) : target; + CompletableFuture.runAsync(() -> Rs2Magic.castOn(magic, dispatch)); + } + + // Best-effort: if the user had previously set the legacy `spell` key to a non-default value and + // slot1Spell is still at its default, copy the legacy value into slot1Spell so existing configs keep working. + private void migrateLegacySpellKey() + { + try + { + PertTargetSpell legacy = configManager.getConfiguration( + "leftclickcast", "spell", PertTargetSpell.class); + if (legacy == null || legacy == PertTargetSpell.FIRE_STRIKE) + { + return; + } + if (config.slot1Spell() != PertTargetSpell.FIRE_STRIKE) + { + return; + } + configManager.setConfiguration("leftclickcast", "slot1Spell", legacy); + } + catch (Exception ignored) + { + // Migration is best-effort; ignore any deserialization or storage errors. + } + } + + // A weapon counts as "magic" when its style struct exposes Casting or Defensive Casting. + // Mirrors the core AttackStylesPlugin logic (EnumID.WEAPON_STYLES + ParamID.ATTACK_STYLE_NAME). + private boolean isMagicWeaponEquipped() + { + int weaponType = client.getVarbitValue(VarbitID.COMBAT_WEAPON_CATEGORY); + EnumComposition weaponStyles = client.getEnum(EnumID.WEAPON_STYLES); + if (weaponStyles == null) + { + return false; + } + int styleEnumId = weaponStyles.getIntValue(weaponType); + if (styleEnumId == -1) + { + return false; + } + int[] styleStructs = client.getEnum(styleEnumId).getIntVals(); + for (int structId : styleStructs) + { + StructComposition sc = client.getStructComposition(structId); + if (sc == null) + { + continue; + } + String name = sc.getStringValue(ParamID.ATTACK_STYLE_NAME); + if ("Casting".equalsIgnoreCase(name) || "Defensive Casting".equalsIgnoreCase(name)) + { + return true; + } + } + return false; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/PertTargetSpell.java b/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/PertTargetSpell.java new file mode 100644 index 0000000000..521072613b --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/PertTargetSpell.java @@ -0,0 +1,96 @@ +package net.runelite.client.plugins.microbot.leftclickcast; + +import lombok.Getter; +import net.runelite.client.plugins.skillcalculator.skills.MagicAction; + +@Getter +public enum PertTargetSpell +{ + // Modern autocastable combat lines (Strike -> Surge) + WIND_STRIKE("Wind Strike", MagicAction.WIND_STRIKE), + WATER_STRIKE("Water Strike", MagicAction.WATER_STRIKE), + EARTH_STRIKE("Earth Strike", MagicAction.EARTH_STRIKE), + FIRE_STRIKE("Fire Strike", MagicAction.FIRE_STRIKE), + WIND_BOLT("Wind Bolt", MagicAction.WIND_BOLT), + WATER_BOLT("Water Bolt", MagicAction.WATER_BOLT), + EARTH_BOLT("Earth Bolt", MagicAction.EARTH_BOLT), + FIRE_BOLT("Fire Bolt", MagicAction.FIRE_BOLT), + WIND_BLAST("Wind Blast", MagicAction.WIND_BLAST), + WATER_BLAST("Water Blast", MagicAction.WATER_BLAST), + EARTH_BLAST("Earth Blast", MagicAction.EARTH_BLAST), + FIRE_BLAST("Fire Blast", MagicAction.FIRE_BLAST), + WIND_WAVE("Wind Wave", MagicAction.WIND_WAVE), + WATER_WAVE("Water Wave", MagicAction.WATER_WAVE), + EARTH_WAVE("Earth Wave", MagicAction.EARTH_WAVE), + FIRE_WAVE("Fire Wave", MagicAction.FIRE_WAVE), + WIND_SURGE("Wind Surge", MagicAction.WIND_SURGE), + WATER_SURGE("Water Surge", MagicAction.WATER_SURGE), + EARTH_SURGE("Earth Surge", MagicAction.EARTH_SURGE), + FIRE_SURGE("Fire Surge", MagicAction.FIRE_SURGE), + + // Ancient autocastable combat lines (Rush/Burst/Blitz/Barrage for each element) + SMOKE_RUSH("Smoke Rush", MagicAction.SMOKE_RUSH), + SHADOW_RUSH("Shadow Rush", MagicAction.SHADOW_RUSH), + BLOOD_RUSH("Blood Rush", MagicAction.BLOOD_RUSH), + ICE_RUSH("Ice Rush", MagicAction.ICE_RUSH), + SMOKE_BURST("Smoke Burst", MagicAction.SMOKE_BURST), + SHADOW_BURST("Shadow Burst", MagicAction.SHADOW_BURST), + BLOOD_BURST("Blood Burst", MagicAction.BLOOD_BURST), + ICE_BURST("Ice Burst", MagicAction.ICE_BURST), + SMOKE_BLITZ("Smoke Blitz", MagicAction.SMOKE_BLITZ), + SHADOW_BLITZ("Shadow Blitz", MagicAction.SHADOW_BLITZ), + BLOOD_BLITZ("Blood Blitz", MagicAction.BLOOD_BLITZ), + ICE_BLITZ("Ice Blitz", MagicAction.ICE_BLITZ), + SMOKE_BARRAGE("Smoke Barrage", MagicAction.SMOKE_BARRAGE), + SHADOW_BARRAGE("Shadow Barrage", MagicAction.SHADOW_BARRAGE), + BLOOD_BARRAGE("Blood Barrage", MagicAction.BLOOD_BARRAGE), + ICE_BARRAGE("Ice Barrage", MagicAction.ICE_BARRAGE), + + // Non-autocastable combat spells + CRUMBLE_UNDEAD("Crumble Undead", MagicAction.CRUMBLE_UNDEAD), + IBAN_BLAST("Iban Blast", MagicAction.IBAN_BLAST), + MAGIC_DART("Magic Dart", MagicAction.MAGIC_DART), + SARADOMIN_STRIKE("Saradomin Strike", MagicAction.SARADOMIN_STRIKE), + CLAWS_OF_GUTHIX("Claws of Guthix", MagicAction.CLAWS_OF_GUTHIX), + FLAMES_OF_ZAMORAK("Flames of Zamorak", MagicAction.FLAMES_OF_ZAMORAK), + + // Arceuus offensive target spells + GHOSTLY_GRASP("Ghostly Grasp", MagicAction.GHOSTLY_GRASP), + SKELETAL_GRASP("Skeletal Grasp", MagicAction.SKELETAL_GRASP), + UNDEAD_GRASP("Undead Grasp", MagicAction.UNDEAD_GRASP), + INFERIOR_DEMONBANE("Inferior Demonbane", MagicAction.INFERIOR_DEMONBANE), + SUPERIOR_DEMONBANE("Superior Demonbane", MagicAction.SUPERIOR_DEMONBANE), + DARK_DEMONBANE("Dark Demonbane", MagicAction.DARK_DEMONBANE), + LESSER_CORRUPTION("Lesser Corruption", MagicAction.LESSER_CORRUPTION), + GREATER_CORRUPTION("Greater Corruption", MagicAction.GREATER_CORRUPTION), + + // Utility target spells + CONFUSE("Confuse", MagicAction.CONFUSE), + WEAKEN("Weaken", MagicAction.WEAKEN), + CURSE("Curse", MagicAction.CURSE), + BIND("Bind", MagicAction.BIND), + SNARE("Snare", MagicAction.SNARE), + ENTANGLE("Entangle", MagicAction.ENTANGLE), + VULNERABILITY("Vulnerability", MagicAction.VULNERABILITY), + ENFEEBLE("Enfeeble", MagicAction.ENFEEBLE), + STUN("Stun", MagicAction.STUN), + TELE_BLOCK("Tele Block", MagicAction.TELE_BLOCK), + TELEOTHER_LUMBRIDGE("Tele Other Lumbridge", MagicAction.TELEOTHER_LUMBRIDGE), + TELEOTHER_FALADOR("Tele Other Falador", MagicAction.TELEOTHER_FALADOR), + TELEOTHER_CAMELOT("Tele Other Camelot", MagicAction.TELEOTHER_CAMELOT); + + private final String displayName; + private final MagicAction magicAction; + + PertTargetSpell(String displayName, MagicAction magicAction) + { + this.displayName = displayName; + this.magicAction = magicAction; + } + + @Override + public String toString() + { + return displayName; + } +} diff --git a/src/main/resources/net/runelite/client/plugins/microbot/leftclickcast/docs/README.md b/src/main/resources/net/runelite/client/plugins/microbot/leftclickcast/docs/README.md new file mode 100644 index 0000000000..be60ed19ae --- /dev/null +++ b/src/main/resources/net/runelite/client/plugins/microbot/leftclickcast/docs/README.md @@ -0,0 +1,54 @@ +# Left-Click Cast + +Replaces the left-click **Attack** option on attackable NPCs and on players (wilderness / PvP) with a preconfigured **Cast Spell** action. The plugin stays invisible when you swap to a melee or ranged weapon, so leaving it enabled is safe. + +## How it works + +When an "Attack" menu entry is added for an NPC, the plugin inserts a new menu entry for the selected spell and places it above Attack, making the spell the left-click action. + +All casting is dispatched through the Microbot client's existing `Rs2Magic.castOn(MagicAction, Actor)` — the plugin does not re-implement rune checks, spellbook switching, or targeting. + +## Configuration + +| Option | Default | Description | +| --- | --- | --- | +| **Enabled** | `true` | Master switch. When off, no menu entries are inserted. | +| **Spell** | `Fire Strike` | Legacy single-spell setting. On startup the plugin migrates this into **Slot 1 Spell** if Slot 1 is still at its default — keeps existing configs working without any manual action. | +| **Require magic weapon** | `true` | When on, the Cast entry only shows while a staff, bladed staff, powered staff, or powered wand is equipped (detected via varbit `EQUIPPED_WEAPON_TYPE`). Disable to cast regardless of weapon. | + +### Spell slots and hotkeys + +The plugin exposes five independently configurable spell slots, grouped under two sections in the config panel: + +| Section | Options | +| --- | --- | +| **Spell Slots** | `Slot 1 Spell` … `Slot 5 Spell` — each picks any spell from the full supported-spells dropdown. All five default to `Fire Strike`. | +| **Hotkeys** | `Slot 1 Hotkey` … `Slot 5 Hotkey` — RuneLite-standard hotkey pickers, all unbound by default. `Chat feedback on slot change` — toggles the chat message posted when a hotkey switches slots (default on). | + +**How slot switching works:** + +- **Slot 1 is always active at startup.** Enabling the plugin (or restarting the client) resets the active slot to Slot 1. The active slot is runtime-only — it is never written to config. +- **Press a bound slot hotkey** (while the game window is focused and no text field is active) to make that slot the active slot. The next menu-sort uses the new slot's spell. +- **Unbound hotkeys are inert.** A slot whose hotkey is `Not set` cannot be activated by keypress. RuneLite's hotkey plumbing suppresses hotkeys while you're typing in a chat or search widget, so hotkey letters won't accidentally swap slots during text entry. +- **Slot 1 needs no hotkey.** Because it's the startup default, leave its hotkey unbound unless you want to explicitly return to it from another slot. +- **Chat feedback** (when enabled) prints `Left-Click Cast: now casting ` on every slot change. Toggle it off if it's noisy during combat rotations. + +## Limitations + +- **No rune auto-management.** If you run out of runes, the cast fails cleanly and you see the normal "You do not have enough ..." chat message. +- **No auto-spellbook switching.** If the selected spell is not on your current spellbook, the cast fails silently. Switch spellbooks manually. +- **The dropdown is the source of truth.** Spells not listed in the dropdown are not supported by this plugin. +- **Staff-only default.** With `Require magic weapon` enabled (default), non-magic weapon types produce normal Attack behavior. Disable the toggle if you want to cast from melee/ranged weapons as well. +- **Cooperative menu composition.** If another plugin inserts menu entries after this one on the same tick, its entry becomes the top entry instead — a known limitation of RuneLite's menu model. + +## Supported spells + +**Modern combat (Strike → Surge):** Wind Strike, Water Strike, Earth Strike, Fire Strike, Wind Bolt, Water Bolt, Earth Bolt, Fire Bolt, Wind Blast, Water Blast, Earth Blast, Fire Blast, Wind Wave, Water Wave, Earth Wave, Fire Wave, Wind Surge, Water Surge, Earth Surge, Fire Surge. + +**Ancient combat:** Smoke / Shadow / Blood / Ice — Rush, Burst, Blitz, Barrage. + +**Non-autocastable combat:** Crumble Undead, Iban Blast, Magic Dart, Saradomin Strike, Claws of Guthix, Flames of Zamorak. + +**Arceuus offensive:** Ghostly Grasp, Skeletal Grasp, Undead Grasp, Inferior Demonbane, Superior Demonbane, Dark Demonbane, Lesser Corruption, Greater Corruption. + +**Utility target spells:** Confuse, Weaken, Curse, Bind, Snare, Entangle, Vulnerability, Enfeeble, Stun, Tele Block, Tele Other (Lumbridge / Falador / Camelot). diff --git a/src/test/java/net/runelite/client/Microbot.java b/src/test/java/net/runelite/client/Microbot.java index 0bbce8830b..8933287b08 100644 --- a/src/test/java/net/runelite/client/Microbot.java +++ b/src/test/java/net/runelite/client/Microbot.java @@ -10,6 +10,7 @@ import net.runelite.client.plugins.microbot.astralrc.AstralRunesPlugin; 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.sailing.MSailingPlugin; import net.runelite.client.plugins.microbot.thieving.ThievingPlugin; import net.runelite.client.plugins.microbot.woodcutting.AutoWoodcuttingPlugin; @@ -20,7 +21,8 @@ public class Microbot private static final Class[] debugPlugins = { AIOFighterPlugin.class, - AgentServerPlugin.class + AgentServerPlugin.class, + LeftClickCastPlugin.class }; public static void main(String[] args) throws Exception From 237e175b927b864de661a7c04f70b1fe126bd23c Mon Sep 17 00:00:00 2001 From: irkedMATT <59846844+irkedMATT@users.noreply.github.com> Date: Thu, 16 Apr 2026 06:44:05 +0100 Subject: [PATCH 46/95] fix(AutoMiningPlugin): fix return-to-location after break and log spam (#390) * Reduce code smell, implement sawmill vouchers and Lazy Mode * Change profit calculation to be more accurate, extended Logs enum to prevent weird bank withdrawal shenanigans (tried withdrawing yew logs) * undo main runner change * re-add microbot.java. whoops. * Karam fix (#351) * fix: karambwan fairy ring return Made-with: Cursor * plugin now clicks on fairy ring to get back to karams * commit * fix(AutoMiningPlugin): fix return-to-location after break and log spam - Fix initialPlayerLocation being overwritten when player is at bank, which broke return-to-location functionality after breaks - Add distance check in handleMining() to walk back if too far from spot - Add null guard for initialPlayerLocation to prevent NPE - Reduce log spam from LocationOption by changing missing item logs from warn to debug level - Version bump to 1.0.12 Fixes: users reporting plugin not returning to mining location after breaks --------- Co-authored-by: chsami Co-authored-by: Jonathan Thomas <95548936+JThomasDevs@users.noreply.github.com> Co-authored-by: stonksCode <99895926+stonksCode@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) --- .../microbot/mining/AutoMiningScript.java | 19 ++++++++++++++++++- .../microbot/mining/data/LocationOption.java | 3 +-- .../plugins/microbot/mining/data/Rocks.java | 3 ++- 3 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningScript.java b/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningScript.java index b5766ff85e..8bd0eaad07 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mining/AutoMiningScript.java @@ -65,6 +65,11 @@ public boolean run(AutoMiningConfig config) { initialPlayerLocation = Rs2Player.getWorldLocation(); } + // Skip cycle if we don't have a valid location + if (initialPlayerLocation == null) { + return; + } + updateActiveRock(config); if (config.progressiveMode() && ensureProgressiveLocation(config)) { @@ -126,6 +131,16 @@ public boolean run(AutoMiningConfig config) { return; } + // Check if we're too far from mining location - walk back first + if (initialPlayerLocation != null) { + int distanceFromStart = Rs2Player.getWorldLocation().distanceTo(initialPlayerLocation); + if (distanceFromStart > config.distanceToStray()) { + Microbot.status = "Walking back to mining location..."; + Rs2Walker.walkTo(initialPlayerLocation, config.distanceToStray()); + return; + } + } + GameObject rock = Rs2GameObject.findReachableObject(activeRock.getName(), true, config.distanceToStray(), initialPlayerLocation); if (rock != null) { @@ -258,7 +273,9 @@ private boolean ensureProgressiveLocation(AutoMiningConfig config) { WorldPoint targetPoint = activeLocation.getWorldPoint(); - if (initialPlayerLocation == null || !initialPlayerLocation.equals(targetPoint)) { + // Only update initialPlayerLocation if it's null + // Don't update just because player is far away (e.g., at bank) - that breaks return-to-location + if (initialPlayerLocation == null) { initialPlayerLocation = targetPoint; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/mining/data/LocationOption.java b/src/main/java/net/runelite/client/plugins/microbot/mining/data/LocationOption.java index 65b9342fb6..307e29e558 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mining/data/LocationOption.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mining/data/LocationOption.java @@ -131,8 +131,7 @@ public boolean hasRequirements() { // bolt ammo slot ? when the ids is any ammo if (numberOfItems+numberOfItemsInPouch +numberOfItemsInBank< requiredAmount) { - log.warn("Missing required item: {} x{} (have {})", itemId, requiredAmount, numberOfItems); - Microbot.log("Missing required item: " + itemId + " x" + requiredAmount + " (have " + numberOfItems + ")"); + log.debug("Missing required item: {} x{} (have {})", itemId, requiredAmount, numberOfItems); return false; } return true; diff --git a/src/main/java/net/runelite/client/plugins/microbot/mining/data/Rocks.java b/src/main/java/net/runelite/client/plugins/microbot/mining/data/Rocks.java index dd985e9f41..ad4e7e3b3f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mining/data/Rocks.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mining/data/Rocks.java @@ -22,7 +22,8 @@ public enum Rocks { URT_SALT("Urt salt rocks", 72), EFH_SALT("Efh salt rocks", 72), TE_SALT("Te salt rocks", 72), - RUNITE("runite rocks", 85); + RUNITE("runite rocks", 85), + NONE("None", 1); private final String name; private final int miningLevel; From b262de1ab4299103fafdf10dddd56ef6e0820846 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 16 Apr 2026 10:11:43 -0700 Subject: [PATCH 47/95] fix(Fletching): match "Logs" item name when using LOG material (#396) The bank lookup constructed the secondary item name as `material.getName() + " logs"`, producing "Log logs" for LOG material which doesn't match the actual OSRS item name "Logs". Plain logs therefore were never found in the bank and the script shut down with "logs not found". Added FletchingMaterial.getLogItemName() that returns "Logs" for LOG and " logs" for everything else, and routed all three call sites through it. Co-authored-by: dev --- .../client/plugins/microbot/fletching/FletchingPlugin.java | 2 +- .../client/plugins/microbot/fletching/FletchingScript.java | 6 +++--- .../plugins/microbot/fletching/enums/FletchingMaterial.java | 3 +++ 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingPlugin.java index 4955ed7e2b..50e1ad279b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingPlugin.java @@ -27,7 +27,7 @@ @Slf4j public class FletchingPlugin extends Plugin { - public static final String version = "1.6.3"; + public static final String version = "1.6.4"; @Inject private FletchingConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingScript.java b/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingScript.java index ab1df412ef..2c72f70cb0 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingScript.java @@ -71,7 +71,7 @@ public void run(FletchingConfig config) { primaryItemToFletch = fletchingMode.getItemName(); if (fletchingMode == FletchingMode.PROGRESSIVE) { - secondaryItemToFletch = (model.getFletchingMaterial().getName() + " logs").trim(); + secondaryItemToFletch = model.getFletchingMaterial().getLogItemName(); hasRequirementsToFletch = Rs2Inventory.hasItem(primaryItemToFletch) && Rs2Inventory.hasItemAmount(secondaryItemToFletch, model.getFletchingItem().getAmountRequired()); hasRequirementsToBank = !Rs2Inventory.hasItem(primaryItemToFletch) @@ -84,7 +84,7 @@ public void run(FletchingConfig config) { } else { secondaryItemToFletch = fletchingMode == FletchingMode.STRUNG ? config.fletchingMaterial().getName() + " " + config.fletchingItem().getContainsInventoryName() + " (u)" - : (config.fletchingMaterial().getName() + " logs").trim(); + : config.fletchingMaterial().getLogItemName(); hasRequirementsToFletch = Rs2Inventory.hasItem(primaryItemToFletch) && Rs2Inventory.hasItemAmount(secondaryItemToFletch, config.fletchingItem().getAmountRequired()); hasRequirementsToBank = !Rs2Inventory.hasItem(primaryItemToFletch) @@ -115,7 +115,7 @@ private void bankItems(FletchingConfig config) { case PROGRESSIVE: Rs2Bank.depositAll(model.getFletchingItem().getContainsInventoryName()); calculateItemToFletch(); - secondaryItemToFletch = (model.getFletchingMaterial().getName() + " logs").trim(); + secondaryItemToFletch = model.getFletchingMaterial().getLogItemName(); break; case PROGRESSIVE_STRUNG: Rs2Bank.depositAll(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/fletching/enums/FletchingMaterial.java b/src/main/java/net/runelite/client/plugins/microbot/fletching/enums/FletchingMaterial.java index e8f0be64e0..0e15b44504 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/fletching/enums/FletchingMaterial.java +++ b/src/main/java/net/runelite/client/plugins/microbot/fletching/enums/FletchingMaterial.java @@ -18,6 +18,9 @@ public enum FletchingMaterial private final String name; + public String getLogItemName() { + return this == LOG ? "Logs" : name + " logs"; + } @Override public String toString() From 109b63fb77d9ca805bc723fb2ac06bf30581360e Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 16 Apr 2026 10:21:47 -0700 Subject: [PATCH 48/95] feat(LeaguesToolkit): add Leagues utility plugin with anti-AFK (#395) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit New standalone plugin for Leagues quality-of-life utilities. First feature is anti-AFK which presses a configurable input (random arrow key, backspace, or camera rotation) whenever the client's idle ticks approach the idle-timeout threshold — prevents logout during long auto-banking skilling sessions (e.g. mining with Endless Harvest relic). Configurable trigger buffer window (randomized min/max ticks before threshold) and input method. Adds [DV] prefix to PluginConstants. Co-authored-by: dev Co-authored-by: chsami --- .../leaguestoolkit/AntiAfkMethod.java | 7 ++ .../leaguestoolkit/LeaguesToolkitConfig.java | 69 +++++++++++++++++++ .../leaguestoolkit/LeaguesToolkitPlugin.java | 45 ++++++++++++ .../leaguestoolkit/LeaguesToolkitScript.java | 62 +++++++++++++++++ .../microbot/leaguestoolkit/docs/README.md | 16 +++++ 5 files changed, 199 insertions(+) create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/AntiAfkMethod.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java create mode 100644 src/main/resources/net/runelite/client/plugins/microbot/leaguestoolkit/docs/README.md diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/AntiAfkMethod.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/AntiAfkMethod.java new file mode 100644 index 0000000000..29763dc22d --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/AntiAfkMethod.java @@ -0,0 +1,7 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +public enum AntiAfkMethod { + RANDOM_ARROW_KEY, + BACKSPACE, + CAMERA_ROTATION +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java new file mode 100644 index 0000000000..0e6dbc4fb3 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java @@ -0,0 +1,69 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +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.config.Range; + +@ConfigGroup("LeaguesToolkit") +@ConfigInformation("

    Leagues Toolkit

    " + + "

    Version: " + LeaguesToolkitPlugin.version + "

    " + + "

    A grab-bag of Leagues-focused utilities. Start with Anti-AFK to keep long, " + + "auto-banking skilling sessions from getting logged out.

    ") +public interface LeaguesToolkitConfig extends Config { + + @ConfigSection( + name = "Anti-AFK", + description = "Prevents the idle-timeout logout during long AFK sessions", + position = 0 + ) + String antiAfkSection = "antiAfkSection"; + + @ConfigItem( + keyName = "enableAntiAfk", + name = "Enable anti-AFK", + description = "Periodically triggers input to reset the idle timer so you never get logged out", + position = 0, + section = antiAfkSection + ) + default boolean enableAntiAfk() { + return true; + } + + @ConfigItem( + keyName = "antiAfkMethod", + name = "Input method", + description = "What kind of input to send. Random arrow keys look most natural.", + position = 1, + section = antiAfkSection + ) + default AntiAfkMethod antiAfkMethod() { + return AntiAfkMethod.RANDOM_ARROW_KEY; + } + + @Range(min = 50, max = 5000) + @ConfigItem( + keyName = "antiAfkBufferMin", + name = "Trigger buffer min (ticks)", + description = "Minimum ticks before the client's AFK threshold at which to fire input", + position = 2, + section = antiAfkSection + ) + default int antiAfkBufferMin() { + return 500; + } + + @Range(min = 50, max = 5000) + @ConfigItem( + keyName = "antiAfkBufferMax", + name = "Trigger buffer max (ticks)", + description = "Maximum ticks before the client's AFK threshold at which to fire input", + position = 3, + section = antiAfkSection + ) + default int antiAfkBufferMax() { + return 1500; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java new file mode 100644 index 0000000000..bbec460109 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java @@ -0,0 +1,45 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import com.google.inject.Provides; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.microbot.PluginConstants; + +import javax.inject.Inject; + +@PluginDescriptor( + name = PluginConstants.DV + "Leagues Toolkit", + description = "Quality-of-life utilities for Leagues (anti-AFK, and more to come)", + tags = {"leagues", "microbot", "utility", "afk"}, + version = LeaguesToolkitPlugin.version, + minClientVersion = "2.0.13", + enabledByDefault = PluginConstants.DEFAULT_ENABLED, + isExternal = PluginConstants.IS_EXTERNAL +) +@Slf4j +public class LeaguesToolkitPlugin extends Plugin { + public static final String version = "1.0.0"; + + @Inject + private LeaguesToolkitConfig config; + + @Inject + private LeaguesToolkitScript leaguesToolkitScript; + + @Provides + LeaguesToolkitConfig provideConfig(ConfigManager configManager) { + return configManager.getConfig(LeaguesToolkitConfig.class); + } + + @Override + protected void startUp() { + leaguesToolkitScript.run(config); + } + + @Override + protected void shutDown() { + leaguesToolkitScript.shutdown(); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java new file mode 100644 index 0000000000..fafd6f3760 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java @@ -0,0 +1,62 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; +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 java.awt.event.KeyEvent; +import java.util.concurrent.TimeUnit; + +@Slf4j +public class LeaguesToolkitScript extends Script { + + private static final int[] ARROW_KEYS = { + KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT, KeyEvent.VK_UP, KeyEvent.VK_DOWN + }; + + public boolean run(LeaguesToolkitConfig config) { + mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { + try { + if (!super.run()) return; + if (!Microbot.isLoggedIn()) return; + + if (config.enableAntiAfk()) { + runAntiAfk(config); + } + } catch (Exception ex) { + log.error("LeaguesToolkitScript loop error", ex); + } + }, 0, 1000, TimeUnit.MILLISECONDS); + return true; + } + + private void runAntiAfk(LeaguesToolkitConfig config) { + int minBuffer = Math.min(config.antiAfkBufferMin(), config.antiAfkBufferMax()); + int maxBuffer = Math.max(config.antiAfkBufferMin(), config.antiAfkBufferMax()); + long buffer = Rs2Random.between(minBuffer, maxBuffer); + + if (!Rs2Player.checkIdleLogout(buffer)) return; + + switch (config.antiAfkMethod()) { + case BACKSPACE: + Rs2Keyboard.keyPress(KeyEvent.VK_BACK_SPACE); + break; + case CAMERA_ROTATION: + Rs2Camera.setYaw(Rs2Random.between(0, 2047)); + break; + case RANDOM_ARROW_KEY: + default: + Rs2Keyboard.keyPress(ARROW_KEYS[Rs2Random.between(0, ARROW_KEYS.length - 1)]); + break; + } + } + + @Override + public void shutdown() { + super.shutdown(); + } +} diff --git a/src/main/resources/net/runelite/client/plugins/microbot/leaguestoolkit/docs/README.md b/src/main/resources/net/runelite/client/plugins/microbot/leaguestoolkit/docs/README.md new file mode 100644 index 0000000000..224c1e9d6e --- /dev/null +++ b/src/main/resources/net/runelite/client/plugins/microbot/leaguestoolkit/docs/README.md @@ -0,0 +1,16 @@ +# Leagues Toolkit + +Quality-of-life utilities for OSRS Leagues. + +## Features + +### Anti-AFK +Prevents the idle-timeout logout during long AFK skilling sessions (e.g. mining with the auto-bank relic where inventory never fills and no interaction happens between rock respawns). Periodically triggers a configurable input right before the client's AFK threshold so the session stays alive indefinitely. + +Configurable: +- **Input method** — random arrow key (default, most natural), backspace, or camera yaw rotation +- **Trigger buffer min/max** — how many ticks before the idle-timeout to fire input (randomized between min and max) + +## Roadmap + +More Leagues-focused utilities planned. Open an issue or PR with ideas. From cb4cdaab5a6178bf4ce07d670beed5a4186ccce5 Mon Sep 17 00:00:00 2001 From: Max Date: Thu, 16 Apr 2026 18:32:14 -0700 Subject: [PATCH 49/95] feat(LeaguesToolkit): add Toci's Gem Cutter (#398) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a new Gem Cutter section to Leagues Toolkit. Walks to Toci in Aldarin, opens the shop, mass-clicks "Buy 1" on uncut gems until inventory is full or shop runs out, closes the shop, uses chisel on uncut gems with SPACE for the "how many" dialog, waits for cutting to finish, then reopens the shop and rapid-sells each cut gem from the bottom inventory slot. Loops indefinitely. Config: - Enable gem cutter (toggle) - Gem dropdown: Sapphire, Emerald, Ruby - Min coins to keep (auto-banks when below) - Use Bank Heist briefcase (Leagues relic instant banking) Shop stays open across sell→buy transitions to save time. Bump version to 1.1.0. Co-authored-by: dev --- .../microbot/leaguestoolkit/GemCutter.java | 262 ++++++++++++++++++ .../leaguestoolkit/GemCutterState.java | 9 + .../microbot/leaguestoolkit/GemType.java | 27 ++ .../leaguestoolkit/LeaguesToolkitConfig.java | 53 ++++ .../leaguestoolkit/LeaguesToolkitPlugin.java | 2 +- .../leaguestoolkit/LeaguesToolkitScript.java | 16 ++ 6 files changed, 368 insertions(+), 1 deletion(-) create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutter.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutterState.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemType.java diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutter.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutter.java new file mode 100644 index 0000000000..7e4aba37a7 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutter.java @@ -0,0 +1,262 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.microbot.Microbot; +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.inventory.Rs2ItemModel; +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.shop.Rs2Shop; +import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; + +import java.awt.event.KeyEvent; + +import static net.runelite.client.plugins.microbot.util.Global.sleep; +import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; + +@Slf4j +public class GemCutter { + + // Toci's actual tile in Aldarin, Varlamore + private static final WorldPoint TOCI_LOCATION = new WorldPoint(1428, 2975, 0); + private static final String TOCI_NPC_NAME = "Toci"; + private static final String CHISEL_NAME = "Chisel"; + private static final int COINS_ID = 995; + private static final String BRIEFCASE_NAME = "Banker's briefcase"; + + @Getter + private GemCutterState state = GemCutterState.WALKING_TO_SHOP; + @Getter + private String status = "Idle"; + + public void reset() { + state = GemCutterState.WALKING_TO_SHOP; + status = "Idle"; + } + + public boolean tick(LeaguesToolkitConfig config) { + GemType gem = config.gemType(); + if (gem == null) { + status = "No gem selected"; + return false; + } + + if (!gem.hasRequiredLevel()) { + status = "Crafting level too low for " + gem.getCutName() + " (need " + gem.getCraftingLevel() + ")"; + return false; + } + + if (!Rs2Inventory.hasItem(CHISEL_NAME)) { + status = "No chisel in inventory"; + return false; + } + + // Need to bank for coins if we're idle (no gems in hand) and low on coins + if (state == GemCutterState.WALKING_TO_SHOP + && Rs2Inventory.itemQuantity(COINS_ID) < config.gemCutterMinCoins() + && !Rs2Inventory.hasItem(gem.getUncutName()) + && !Rs2Inventory.hasItem(gem.getCutName())) { + state = GemCutterState.BANKING; + } + + switch (state) { + case BANKING: + return handleBanking(config); + case WALKING_TO_SHOP: + return handleWalkingToShop(); + case BUYING: + return handleBuying(gem); + case CUTTING: + return handleCutting(gem); + case SELLING: + return handleSelling(gem, config); + } + return true; + } + + private boolean handleBanking(LeaguesToolkitConfig config) { + if (Rs2Shop.isOpen()) { + Rs2Shop.closeShop(); + return true; + } + + if (config.gemCutterUseBriefcase() && Rs2Inventory.hasItem(BRIEFCASE_NAME)) { + if (!Rs2Bank.isOpen()) { + status = "Using briefcase to bank"; + Rs2Inventory.interact(BRIEFCASE_NAME, "Bank"); + sleepUntil(Rs2Bank::isOpen, 5000); + return true; + } + } else { + if (!Rs2Bank.isOpen()) { + status = "Walking to bank"; + if (!Rs2Bank.walkToBankAndUseBank()) return true; + } + } + + if (!Rs2Bank.isOpen()) return true; + + status = "Withdrawing coins"; + if (!Rs2Bank.hasItem(COINS_ID)) { + status = "No coins in bank — stopping"; + Rs2Bank.closeBank(); + return false; + } + + Rs2Bank.withdrawAll(COINS_ID); + Rs2Bank.closeBank(); + state = GemCutterState.WALKING_TO_SHOP; + return true; + } + + private boolean handleWalkingToShop() { + if (Rs2Npc.getNpc(TOCI_NPC_NAME) != null) { + status = "At Toci"; + state = GemCutterState.BUYING; + return true; + } + status = "Walking to Toci"; + if (!Rs2Player.isMoving()) { + Rs2Walker.walkTo(TOCI_LOCATION, 6); + } + return true; + } + + private boolean handleBuying(GemType gem) { + if (!Rs2Shop.isOpen()) { + status = "Opening Toci's shop"; + if (!Rs2Shop.openShop(TOCI_NPC_NAME)) { + status = "Could not open shop"; + return true; + } + sleepUntil(Rs2Shop::isOpen, 3000); + return true; + } + + int uncutCount = Rs2Inventory.count(gem.getUncutName()); + + if (Rs2Inventory.isFull()) { + status = "Inventory full — moving to cut"; + Rs2Shop.closeShop(); + state = GemCutterState.CUTTING; + return true; + } + + if (!Rs2Shop.hasStock(gem.getUncutName())) { + if (uncutCount > 0) { + status = "Shop out of stock — cutting what we have"; + Rs2Shop.closeShop(); + state = GemCutterState.CUTTING; + return true; + } + status = "Shop out of " + gem.getUncutName() + " — waiting"; + sleep(1500, 2500); + return true; + } + + // Mass-click buy at 100-250ms intervals. Stop when 2 consecutive clicks + // fail to add a ruby to inventory (inventory full OR shop out of stock). + status = "Rapid-buying " + gem.getUncutName(); + int safetyMax = 32; + int missedInRow = 0; + for (int i = 0; i < safetyMax; i++) { + if (!Rs2Shop.isOpen()) break; + int before = Rs2Inventory.count(gem.getUncutName()); + Rs2Shop.buyItem(gem.getUncutName(), "1"); + sleep(Rs2Random.between(100, 250)); + if (Rs2Inventory.count(gem.getUncutName()) > before) { + missedInRow = 0; + } else { + missedInRow++; + if (missedInRow >= 2) break; + } + } + return true; + } + + private boolean handleCutting(GemType gem) { + if (!Rs2Inventory.hasItem(gem.getUncutName())) { + status = "All gems cut — moving to sell"; + state = GemCutterState.SELLING; + return true; + } + + // Start the cut: chisel on uncut gem + status = "Starting to cut " + gem.getCutName(); + Rs2Inventory.use(CHISEL_NAME); + sleep(300, 500); + Rs2Inventory.use(gem.getUncutName()); + + // Wait for "How many do you wish to make?" dialog, then press space for All + sleep(600, 900); + Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); + + // Wait for cutting to finish (XP stops flowing OR all uncut gems gone) + sleep(2000, 3000); + status = "Cutting " + gem.getCutName() + "..."; + sleepUntil(() -> !Microbot.isGainingExp || !Rs2Inventory.hasItem(gem.getUncutName()), 60000); + + return true; + } + + private boolean handleSelling(GemType gem, LeaguesToolkitConfig config) { + if (!Rs2Inventory.hasItem(gem.getCutName())) { + status = "All cut gems sold — looping"; + if (Rs2Inventory.itemQuantity(COINS_ID) < config.gemCutterMinCoins()) { + // Only close when we need to walk somewhere (banking) + if (Rs2Shop.isOpen()) Rs2Shop.closeShop(); + state = GemCutterState.BANKING; + } else { + // Leave shop open — handleBuying will use the already-open shop next tick + state = GemCutterState.BUYING; + } + return true; + } + + if (!Rs2Shop.isOpen()) { + status = "Reopening shop to sell"; + if (!Rs2Shop.openShop(TOCI_NPC_NAME)) { + status = "Could not reopen shop"; + return true; + } + sleepUntil(Rs2Shop::isOpen, 3000); + return true; + } + + // Mass-click sell at 100-250ms intervals — click the LAST slot containing + // a cut gem, repeat until inventory runs out. Two consecutive misses = stop. + int initialCount = Rs2Inventory.count(gem.getCutName()); + status = "Rapid-selling " + gem.getCutName() + " x" + initialCount; + + int safetyMax = initialCount + 5; + int missedInRow = 0; + for (int i = 0; i < safetyMax; i++) { + if (!Rs2Shop.isOpen()) break; + if (!Rs2Inventory.hasItem(gem.getCutName())) break; + + Rs2ItemModel last = Rs2Inventory.items(item -> + gem.getCutName().equalsIgnoreCase(item.getName())) + .reduce((a, b) -> b) + .orElse(null); + if (last == null) break; + int before = Rs2Inventory.count(gem.getCutName()); + + Rs2Inventory.slotInteract(last.getSlot(), "Sell 1"); + sleep(Rs2Random.between(100, 250)); + + if (Rs2Inventory.count(gem.getCutName()) < before) { + missedInRow = 0; + } else { + missedInRow++; + if (missedInRow >= 2) break; + } + } + + return true; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutterState.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutterState.java new file mode 100644 index 0000000000..d08593c9f6 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutterState.java @@ -0,0 +1,9 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +public enum GemCutterState { + BANKING, + WALKING_TO_SHOP, + BUYING, + CUTTING, + SELLING +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemType.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemType.java new file mode 100644 index 0000000000..9e29e52f1e --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemType.java @@ -0,0 +1,27 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.runelite.api.Skill; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; + +@Getter +@RequiredArgsConstructor +public enum GemType { + SAPPHIRE("Uncut sapphire", "Sapphire", 20), + EMERALD("Uncut emerald", "Emerald", 27), + RUBY("Uncut ruby", "Ruby", 34); + + private final String uncutName; + private final String cutName; + private final int craftingLevel; + + public boolean hasRequiredLevel() { + return Rs2Player.getSkillRequirement(Skill.CRAFTING, craftingLevel); + } + + @Override + public String toString() { + return cutName; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java index 0e6dbc4fb3..68b324196d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java @@ -66,4 +66,57 @@ default int antiAfkBufferMin() { default int antiAfkBufferMax() { return 1500; } + + @ConfigSection( + name = "Toci's Gem Cutter", + description = "Buys uncut gems from Toci in Aldarin, cuts them, sells them back", + position = 1, + closedByDefault = true + ) + String gemCutterSection = "gemCutterSection"; + + @ConfigItem( + keyName = "enableGemCutter", + name = "Enable gem cutter", + description = "Walks to Toci, buys uncut gems, cuts them, sells cut gems back — repeats", + position = 0, + section = gemCutterSection + ) + default boolean enableGemCutter() { + return false; + } + + @ConfigItem( + keyName = "gemType", + name = "Gem", + description = "Which gem to cut (requires chisel + coins + crafting level)", + position = 1, + section = gemCutterSection + ) + default GemType gemType() { + return GemType.RUBY; + } + + @Range(min = 1000, max = 1_000_000) + @ConfigItem( + keyName = "gemCutterMinCoins", + name = "Min coins to keep", + description = "When coins drop below this, withdraw more from the bank", + position = 2, + section = gemCutterSection + ) + default int gemCutterMinCoins() { + return 10_000; + } + + @ConfigItem( + keyName = "gemCutterUseBriefcase", + name = "Use Bank Heist briefcase", + description = "Use the banker's briefcase to bank (Leagues relic) instead of walking to a bank", + position = 3, + section = gemCutterSection + ) + default boolean gemCutterUseBriefcase() { + return false; + } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java index bbec460109..e766bbf2fd 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java @@ -20,7 +20,7 @@ ) @Slf4j public class LeaguesToolkitPlugin extends Plugin { - public static final String version = "1.0.0"; + public static final String version = "1.1.0"; @Inject private LeaguesToolkitConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java index fafd6f3760..604a68cc3a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java @@ -1,5 +1,6 @@ package net.runelite.client.plugins.microbot.leaguestoolkit; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; @@ -18,6 +19,11 @@ public class LeaguesToolkitScript extends Script { KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT, KeyEvent.VK_UP, KeyEvent.VK_DOWN }; + @Getter + private final GemCutter gemCutter = new GemCutter(); + + private boolean gemCutterWasEnabled = false; + public boolean run(LeaguesToolkitConfig config) { mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { try { @@ -27,6 +33,16 @@ public boolean run(LeaguesToolkitConfig config) { if (config.enableAntiAfk()) { runAntiAfk(config); } + + if (config.enableGemCutter()) { + if (!gemCutterWasEnabled) { + gemCutter.reset(); + gemCutterWasEnabled = true; + } + gemCutter.tick(config); + } else { + gemCutterWasEnabled = false; + } } catch (Exception ex) { log.error("LeaguesToolkitScript loop error", ex); } From 585f604190ee565c89ebf18aa240120a6657a999 Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Sat, 18 Apr 2026 09:11:37 -0400 Subject: [PATCH 50/95] fix(ValeTotem): correct bow selection and withdraw race (#400) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Select the configured bow option by name via Rs2Widget.handleProcessingInterface so the hotkey is resolved from the live fletching interface rather than a hardcoded child ID. The previous scheme mapped SHORTBOW → child 15 with hotkey-index 1, but widget 270,13's sparse dynamic children could produce "3" at that slot for some log types, causing shortbow selection to press the longbow key. - Bank withdraws now sleepUntil on the concrete predicate (hasKnife, hasLogBasket, log count reaches requested target) instead of the generic waitForInventoryChanges + re-check. Rs2Bank.withdrawOne/withdrawX return immediately after queueing the menu click, so under server lag the follow-up check would read stale state and spuriously report "no knife available" right after the knife was withdrawn. Bumps plugin to 1.0.10. --- .../microbot/valetotems/ValeTotemPlugin.java | 2 +- .../valetotems/handlers/BankingHandler.java | 100 ++++++++++-------- .../valetotems/handlers/FletchingHandler.java | 25 ++--- 3 files changed, 72 insertions(+), 55 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java index dd6731bcb0..1848816778 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/ValeTotemPlugin.java @@ -27,7 +27,7 @@ ) @Slf4j public class ValeTotemPlugin extends Plugin { - static final String version = "1.0.9"; + static final String version = "1.0.10"; @Inject private ValeTotemConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/BankingHandler.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/BankingHandler.java index 973710a3bb..6b97da0f2a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/BankingHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/BankingHandler.java @@ -21,6 +21,7 @@ import static net.runelite.client.plugins.microbot.util.Global.sleep; import static net.runelite.client.plugins.microbot.util.Global.sleepGaussian; +import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; import java.util.Random; import net.runelite.client.plugins.microbot.valetotems.enums.TotemLocation; @@ -176,26 +177,22 @@ public static boolean withdrawRequiredItems(GameSession gameSession) { int logId = InventoryUtils.getLogId(); String logTypeName = config != null ? config.logType().getDisplayName() : "Yew Logs"; - // Withdraw logs (materials check already performed, so this should succeed) + // Rs2Bank.withdrawX only queues the menu click — it returns immediately and the item + // can lag several ticks behind. Generic waitForInventoryChanges + hasRequiredItems + // was racing and reporting shortages right after a successful withdraw. Microbot.log("Withdrawing " + logsToWithdraw + " " + logTypeName + " from bank"); - boolean withdrew = Rs2Bank.withdrawX(logId, logsToWithdraw); - if (withdrew) { - Microbot.log("Successfully withdrew " + logsToWithdraw + " " + logTypeName); - Rs2Inventory.waitForInventoryChanges(3000); - } else { - Microbot.log("Failed to withdraw logs from bank"); + int logsBefore = InventoryUtils.getLogCount(); + int expectedLogs = logsBefore + logsToWithdraw; + if (!Rs2Bank.withdrawX(logId, logsToWithdraw)) { + Microbot.log("Failed to queue log withdrawal"); return false; } - - // Final verification - boolean hasRequired = InventoryUtils.hasRequiredItems(); - if (hasRequired) { - Microbot.log("Successfully completed item withdrawal - all required items now in inventory"); - } else { - Microbot.log("Warning: Required items verification failed after withdrawal"); + if (!sleepUntil(() -> InventoryUtils.getLogCount() >= expectedLogs, 3000)) { + Microbot.log("Log withdrawal timed out (have " + InventoryUtils.getLogCount() + "/" + expectedLogs + ")"); + return false; } - - return hasRequired; + Microbot.log("Successfully withdrew " + logsToWithdraw + " " + logTypeName); + return InventoryUtils.hasRequiredItems(); } catch (Exception e) { Microbot.log("Error withdrawing required items: " + e.getMessage()); @@ -603,14 +600,11 @@ public static boolean ensureLogBasketAvailable() { // Check if log basket is in bank and withdraw it if (Rs2Bank.hasItem(InventoryUtils.LOG_BASKET_ID)) { Microbot.log("Withdrawing log basket from bank for extended route"); - boolean withdrew = Rs2Bank.withdrawOne(InventoryUtils.LOG_BASKET_ID); - if (withdrew) { - Rs2Inventory.waitForInventoryChanges(3000); - return InventoryUtils.hasLogBasket(); - } else { - Microbot.log("Failed to withdraw log basket from bank"); + if (!Rs2Bank.withdrawOne(InventoryUtils.LOG_BASKET_ID)) { + Microbot.log("Failed to queue log basket withdrawal"); return false; } + return sleepUntil(InventoryUtils::hasLogBasket, 3000); } Microbot.log("No log basket found in bank"); @@ -667,32 +661,37 @@ private static boolean ensureKnifeInInventory(GameSession gameSession) { return true; } - // Try to withdraw fletching knife first (prioritized) + // Try to withdraw fletching knife first (prioritized). + // Rs2Bank.withdrawOne returns true as soon as the click is queued; we must wait + // on the concrete predicate (hasKnife), not a generic inventory-change wait, + // or a slow server tick causes a stale "no knife" read right after we withdrew — + // the code then used to fall through and try the regular knife, which isn't in + // the bank either, and report a critical shortage on a knife that was in-flight. if (Rs2Bank.hasItem(InventoryUtils.FLETCHING_KNIFE_ID)) { Microbot.log("Withdrawing Fletching knife from bank (prioritized)"); if (Rs2Bank.withdrawOne(InventoryUtils.FLETCHING_KNIFE_ID)) { - Rs2Inventory.waitForInventoryChanges(3000); - if (InventoryUtils.hasKnife()) { + if (sleepUntil(InventoryUtils::hasKnife, 3000)) { Microbot.log("Successfully withdrew Fletching knife"); return true; } - } else { - Microbot.log("Failed to withdraw Fletching knife from bank"); + Microbot.log("Fletching knife withdrawal timed out"); + return false; } + Microbot.log("Failed to queue Fletching knife withdrawal"); } // Try to withdraw regular knife as fallback if (Rs2Bank.hasItem(InventoryUtils.KNIFE_ID)) { Microbot.log("Withdrawing regular knife from bank"); if (Rs2Bank.withdrawOne(InventoryUtils.KNIFE_ID)) { - Rs2Inventory.waitForInventoryChanges(3000); - if (InventoryUtils.hasKnife()) { + if (sleepUntil(InventoryUtils::hasKnife, 3000)) { Microbot.log("Successfully withdrew regular knife"); return true; } - } else { - Microbot.log("Failed to withdraw regular knife from bank"); + Microbot.log("Regular knife withdrawal timed out"); + return false; } + Microbot.log("Failed to queue regular knife withdrawal"); } // No knife found anywhere - critical error @@ -717,42 +716,49 @@ private static boolean ensureKnifeAndLogBasketInInventory(GameSession gameSessio return false; } - // Ensure we have a knife (prioritizing fletching knife) + // Ensure we have a knife (prioritizing fletching knife). Wait on the concrete + // predicate so a slow inventory update doesn't get misread as "no knife in bank". if (!InventoryUtils.hasKnife()) { if (Rs2Bank.hasItem(InventoryUtils.FLETCHING_KNIFE_ID)) { Microbot.log("Withdrawing Fletching knife from bank (prioritized)"); if (!Rs2Bank.withdrawOne(InventoryUtils.FLETCHING_KNIFE_ID)) { - Microbot.log("Failed to withdraw Fletching knife"); + Microbot.log("Failed to queue Fletching knife withdrawal"); return false; } } else if (Rs2Bank.hasItem(InventoryUtils.KNIFE_ID)) { Microbot.log("Withdrawing regular knife from bank"); if (!Rs2Bank.withdrawOne(InventoryUtils.KNIFE_ID)) { - Microbot.log("Failed to withdraw knife"); + Microbot.log("Failed to queue knife withdrawal"); return false; } } else { handleCriticalMaterialShortage(gameSession, "No knife available (checked inventory and bank for both Fletching knife and regular knife)"); return false; } - Rs2Inventory.waitForInventoryChanges(3000); + if (!sleepUntil(InventoryUtils::hasKnife, 3000)) { + Microbot.log("Knife withdrawal timed out"); + return false; + } } // Ensure we have a log basket if (!InventoryUtils.hasLogBasket()) { if (Rs2Bank.hasItem(InventoryUtils.LOG_BASKET_ID)) { if (!Rs2Bank.withdrawOne(InventoryUtils.LOG_BASKET_ID)) { - Microbot.log("Failed to withdraw log basket"); + Microbot.log("Failed to queue log basket withdrawal"); return false; } } else { handleCriticalMaterialShortage(gameSession, "No log basket available (checked inventory and bank)"); return false; } - Rs2Inventory.waitForInventoryChanges(3000); + if (!sleepUntil(InventoryUtils::hasLogBasket, 3000)) { + Microbot.log("Log basket withdrawal timed out"); + return false; + } } - return InventoryUtils.hasKnife() && InventoryUtils.hasLogBasket(); + return true; } catch (Exception e) { Microbot.log("Error ensuring knife and log basket in inventory: " + e.getMessage()); @@ -832,13 +838,18 @@ private static boolean performLogBasketFillingOperation(net.runelite.client.plug // Step 1: Take inventory full of logs (leaving space for knife and basket) int logsToWithdraw = InventoryUtils.getOptimalLogBasketLogAmountForExtendedRoute(gameSession) - InventoryUtils.getLogCount(); int logId = InventoryUtils.getLogId(); + int logsBeforeFirst = InventoryUtils.getLogCount(); if (!Rs2Bank.withdrawX(logId, logsToWithdraw)) { - Microbot.log("Failed to withdraw logs to fill inventory"); + Microbot.log("Failed to queue log withdrawal for basket-fill step"); return false; } - Rs2Inventory.waitForInventoryChanges(3000); + int expectedAfterFirst = logsBeforeFirst + logsToWithdraw; + if (!sleepUntil(() -> InventoryUtils.getLogCount() >= expectedAfterFirst, 3000)) { + Microbot.log("Log withdrawal timed out in basket-fill step"); + return false; + } // Step 2: Close bank Rs2Bank.closeBank(); @@ -861,11 +872,16 @@ private static boolean performLogBasketFillingOperation(net.runelite.client.plug int logsStillNeeded = InventoryUtils.getOptimalLogAmountForExtendedRoute(gameSession); if (logsStillNeeded > 0) { Microbot.log("Withdrawing additional " + logsStillNeeded + " logs for extended route"); + int logsBeforeSecond = InventoryUtils.getLogCount(); if (!Rs2Bank.withdrawX(logId, logsStillNeeded)) { - Microbot.log("Failed to withdraw additional logs"); + Microbot.log("Failed to queue additional log withdrawal"); + return false; + } + int expectedAfterSecond = logsBeforeSecond + logsStillNeeded; + if (!sleepUntil(() -> InventoryUtils.getLogCount() >= expectedAfterSecond, 3000)) { + Microbot.log("Additional log withdrawal timed out"); return false; } - Rs2Inventory.waitForInventoryChanges(3000); } Microbot.log("Log basket filling operation completed successfully"); diff --git a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/FletchingHandler.java b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/FletchingHandler.java index ad3c988511..4081f24135 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/FletchingHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/valetotems/handlers/FletchingHandler.java @@ -295,19 +295,20 @@ public static boolean selectBow(int quantity) { } sleepGaussian(300,100); - - // Click the configured bow option using the mapper - if (config != null) { - int bowChildId = FletchingItemMapper.getFletchingInterfaceChildId(config.logType(), config.bowType()); - String description = FletchingItemMapper.getFletchingDescriptionWithShortcut(config.logType(), config.bowType()); - interactWithWidget(bowChildId, description); - Microbot.log("Selected " + FletchingItemMapper.getFletchingDescription(config.logType(), config.bowType())); - } else { - // Fallback to yew longbow - interactWithWidget(16, "Yew Longbow (u) (expected key: 3)"); - Microbot.log("Selected Yew Longbow (u) - fallback"); + + // Select the configured bow option by name — resolves the correct hotkey dynamically + // against the actual fletching interface layout, instead of assuming fixed child IDs. + // The previous child-ID scheme mapped SHORTBOW → child 15 with hotkey-index 1, but the + // sparse dynamic-children layout of widget 270,13 in skillmulti could produce "3" at + // that slot for some log types, so shortbow-selected was pressing the longbow key. + String bowAction = (config != null && config.bowType() == ValeTotemConfig.BowType.SHORTBOW) + ? "shortbow" : "longbow"; + if (!Rs2Widget.handleProcessingInterface(bowAction)) { + Microbot.log("Failed to select " + bowAction + " from fletching interface"); + return false; } - + Microbot.log("Selected " + bowAction); + sleepGaussian(200,100); return true; From 7371db7863db79de1328d2782f348645f80c033c Mon Sep 17 00:00:00 2001 From: Max Date: Sat, 18 Apr 2026 11:31:10 -0700 Subject: [PATCH 51/95] feat(Crafting): add Nemus Retreat flax spinning location (#403) Adds Nemus Retreat (Varlamore) as a flax spinning location at WorldPoint(1373, 3313, 0) with spinning wheel object ID 55330. Co-authored-by: dev --- .../plugins/microbot/crafting/enums/FlaxSpinLocations.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/crafting/enums/FlaxSpinLocations.java b/src/main/java/net/runelite/client/plugins/microbot/crafting/enums/FlaxSpinLocations.java index 26f8b6fcc3..c4a47e465e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/crafting/enums/FlaxSpinLocations.java +++ b/src/main/java/net/runelite/client/plugins/microbot/crafting/enums/FlaxSpinLocations.java @@ -12,7 +12,8 @@ public enum FlaxSpinLocations { FALADOR("Falador", new WorldPoint(2982, 3314, 0), ObjectID.SPINNING_WHEEL_14889), SEERS_VILLAGE("Seers Village", new WorldPoint(2711, 3471, 1), ObjectID.SPINNING_WHEEL_25824), RELLEKKA("Rellekka", new WorldPoint(2617, 3660, 0), ObjectID.SPINNING_WHEEL), - TREE_GNOME_STRONGHOLD("Tree Gnome Stronghold", new WorldPoint(2488, 3409, 1), ObjectID.SPINNING_WHEEL_14889); + TREE_GNOME_STRONGHOLD("Tree Gnome Stronghold", new WorldPoint(2488, 3409, 1), ObjectID.SPINNING_WHEEL_14889), + NEMUS_RETREAT("Nemus Retreat", new WorldPoint(1373, 3313, 0), 55330); // LUMBRIDGE_CASTLE(new WorldPoint(3209, 3213, 1), ObjectID.SPINNING_WHEEL_14889), Issue with web-walker when banking From 723b98f3ff2cd8025b98d0d8fc9498b841830951 Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Sun, 19 Apr 2026 04:28:36 -0400 Subject: [PATCH 52/95] feat(fletching): add fletching knife support (#405) * chore: ignore .worktrees directory * feat(fletching): add fletching knife support Prefer fletching knife over regular knife when available in inventory or bank. Falls back to regular knife if fletching knife is not present. - Add helper methods to check for either knife type - Update banking logic to handle both knife types - Update fletch() to use whichever knife is in inventory - Bump version to 1.7.0 * fix(fletching): check bank open result before proceeding Bank operations were failing on first attempt because openBank() return value was ignored. Now checks if bank is already open and verifies openBank() succeeded before continuing with deposit/withdraw operations. * fix(fletching): preHover bank when fletching starts, not after Move preHover() to execute right after initiating the fletching action so the mouse moves to the bank while fletching is in progress. This is more natural than hovering after fletching completes. --- .gitignore | 1 + .../microbot/fletching/FletchingPlugin.java | 2 +- .../microbot/fletching/FletchingScript.java | 56 ++++++++++++++++--- 3 files changed, 50 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 8a3ced8fbb..fe0cb7232a 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,4 @@ nb-configuration.xml nbproject/ public/** /CreatePluginDocs.ps1 +.worktrees/ diff --git a/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingPlugin.java index 50e1ad279b..1cc0762474 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingPlugin.java @@ -27,7 +27,7 @@ @Slf4j public class FletchingPlugin extends Plugin { - public static final String version = "1.6.4"; + public static final String version = "1.7.0"; @Inject private FletchingConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingScript.java b/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingScript.java index 2c72f70cb0..e5233eb7e7 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/fletching/FletchingScript.java @@ -35,6 +35,9 @@ public class FletchingScript extends Script { // The fletching interface widget group ID private static final int FLETCHING_WIDGET_GROUP_ID = 17694736; + private static final String FLETCHING_KNIFE = "fletching knife"; + private static final String KNIFE = "knife"; + ProgressiveFletchingModel model = new ProgressiveFletchingModel(); String primaryItemToFletch = ""; @@ -68,7 +71,7 @@ public void run(FletchingConfig config) { boolean hasRequirementsToFletch; boolean hasRequirementsToBank; - primaryItemToFletch = fletchingMode.getItemName(); + primaryItemToFletch = usesKnife() ? getPreferredKnife() : fletchingMode.getItemName(); if (fletchingMode == FletchingMode.PROGRESSIVE) { secondaryItemToFletch = model.getFletchingMaterial().getLogItemName(); @@ -105,7 +108,11 @@ public void run(FletchingConfig config) { } private void bankItems(FletchingConfig config) { - Rs2Bank.openBank(); + if (!Rs2Bank.isOpen()) { + if (!Rs2Bank.openBank()) { + return; // Bank didn't open, retry next iteration + } + } // Deposit items based on the fletching mode switch (fletchingMode) { @@ -130,7 +137,9 @@ private void bankItems(FletchingConfig config) { } // Check if the primary item is available - if (!Rs2Bank.hasItem(primaryItemToFletch) && !Rs2Inventory.hasItem(primaryItemToFletch)) { + boolean hasPrimaryInBank = usesKnife() ? bankHasAnyKnife() : Rs2Bank.hasItem(primaryItemToFletch); + boolean hasPrimaryInInventory = usesKnife() ? hasAnyKnife() : Rs2Inventory.hasItem(primaryItemToFletch); + if (!hasPrimaryInBank && !hasPrimaryInInventory) { Rs2Bank.closeBank(); Microbot.status = "[Shutting down] - Reason: " + primaryItemToFletch + " not found in the bank."; Microbot.showMessage(Microbot.status); @@ -139,12 +148,12 @@ private void bankItems(FletchingConfig config) { } // Ensure the inventory isn't full without the primary item - if (!Rs2Inventory.hasItem(primaryItemToFletch)) { + if (!hasPrimaryInInventory) { Rs2Bank.depositAll(); } // Withdraw the primary item if not already in the inventory - if (!Rs2Inventory.hasItem(primaryItemToFletch)) { + if (!hasPrimaryInInventory) { Rs2Bank.withdrawX(primaryItemToFletch, fletchingMode.getAmount(), true); } @@ -181,7 +190,8 @@ private void bankItems(FletchingConfig config) { } // Final check to ensure both items are in the inventory - if (!Rs2Inventory.hasItem(primaryItemToFletch) || !Rs2Inventory.hasItem(secondaryItemToFletch)) { + boolean hasPrimaryFinal = usesKnife() ? hasAnyKnife() : Rs2Inventory.hasItem(primaryItemToFletch); + if (!hasPrimaryFinal || !Rs2Inventory.hasItem(secondaryItemToFletch)) { Microbot.log("waiting for inventory changes."); Rs2Inventory.waitForInventoryChanges(5000); } @@ -192,7 +202,8 @@ private void bankItems(FletchingConfig config) { private void fletch(FletchingConfig config) { - Rs2Inventory.combineClosest(primaryItemToFletch, secondaryItemToFletch); + String itemToUse = usesKnife() ? getKnifeInInventory() : primaryItemToFletch; + Rs2Inventory.combineClosest(itemToUse, secondaryItemToFletch); sleepUntil(() -> Rs2Widget.getWidget(FLETCHING_WIDGET_GROUP_ID) != null, 5000); char option; if (fletchingMode == FletchingMode.PROGRESSIVE || fletchingMode == FletchingMode.PROGRESSIVE_STRUNG) { @@ -204,10 +215,11 @@ private void fletch(FletchingConfig config) { Rs2Keyboard.keyPress(option); } + Rs2Bank.preHover(); + sleepUntil(() -> !Rs2Inventory.hasItem(secondaryItemToFletch), 60000); Rs2Antiban.actionCooldown(); Rs2Antiban.takeMicroBreakByChance(); - Rs2Bank.preHover(); } private boolean configChecks(FletchingConfig config) { @@ -219,6 +231,34 @@ private boolean configChecks(FletchingConfig config) { return true; } + private String getPreferredKnife() { + if (Rs2Inventory.hasItem(FLETCHING_KNIFE) || Rs2Bank.hasItem(FLETCHING_KNIFE)) { + return FLETCHING_KNIFE; + } + return KNIFE; + } + + private boolean hasAnyKnife() { + return Rs2Inventory.hasItem(FLETCHING_KNIFE) || Rs2Inventory.hasItem(KNIFE); + } + + private String getKnifeInInventory() { + if (Rs2Inventory.hasItem(FLETCHING_KNIFE)) { + return FLETCHING_KNIFE; + } + return KNIFE; + } + + private boolean bankHasAnyKnife() { + return Rs2Bank.hasItem(FLETCHING_KNIFE) || Rs2Bank.hasItem(KNIFE); + } + + private boolean usesKnife() { + return fletchingMode == FletchingMode.UNSTRUNG + || fletchingMode == FletchingMode.UNSTRUNG_STRUNG + || fletchingMode == FletchingMode.PROGRESSIVE; + } + public void calculateItemToFletch() { int level = Microbot.getClient().getRealSkillLevel(Skill.FLETCHING); FletchingItem item = null; From f42814acb93cca84bceb051da0e7dfbdf1f171e8 Mon Sep 17 00:00:00 2001 From: JThomasDevs <95548936+JThomasDevs@users.noreply.github.com> Date: Sun, 19 Apr 2026 02:28:51 -0600 Subject: [PATCH 53/95] fix hueycoatl prayer, add config option to disable prayers after projectile impact (#404) --- .../HueycoatlPrayer/HueyPrayerConfig.java | 12 ++- .../HueycoatlPrayer/HueyPrayerOverlay.java | 2 +- .../HueycoatlPrayer/HueyPrayerPlugin.java | 77 ++++++++++++++++++- 3 files changed, 85 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerConfig.java b/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerConfig.java index d7fd97749a..ea38412d39 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerConfig.java @@ -1,4 +1,4 @@ -package net.runelite.client.plugins.microbot.huey; +package net.runelite.client.plugins.microbot.HueycoatlPrayer; import net.runelite.client.config.*; @@ -15,6 +15,16 @@ default boolean enabled() return true; } + @ConfigItem( + keyName = "disableAfterImpact", + name = "Disable after impact", + description = "When enabled, turns off protection prayer after the projectile hits (saves prayer). When disabled, prayers stay on until the next attack switches them." + ) + default boolean disableAfterImpact() + { + return true; + } + @ConfigItem( keyName = "debug", name = "Debug Projectiles", diff --git a/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerOverlay.java index 4d52102761..8b56dab221 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerOverlay.java @@ -1,4 +1,4 @@ -package net.runelite.client.plugins.microbot.huey; +package net.runelite.client.plugins.microbot.HueycoatlPrayer; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayPosition; diff --git a/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerPlugin.java index e2d626ef66..78a4ccfa89 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerPlugin.java @@ -1,4 +1,4 @@ -package net.runelite.client.plugins.microbot.huey; +package net.runelite.client.plugins.microbot.HueycoatlPrayer; import com.google.inject.Provides; import javax.inject.Inject; @@ -19,6 +19,10 @@ import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.PluginConstants; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Set; + @Slf4j @PluginDescriptor( name = PluginConstants.DEFAULT_PREFIX + "Huey Prayer", @@ -31,14 +35,13 @@ ) public class HueyPrayerPlugin extends Plugin { - static final String VERSION = "1.0.1"; + static final String VERSION = "1.0.4"; @Inject private Client client; @Inject private HueyPrayerConfig config; - // 🔴 YOU MUST FILL THESE USING DEBUG private static final int MAGIC_PROJECTILE_ID = 2975; private static final int RANGE_PROJECTILE_ID = 2972; private static final int MELEE_PROJECTILE_ID = 2969; @@ -46,6 +49,10 @@ public class HueyPrayerPlugin extends Plugin private Rs2PrayerEnum currentPrayer = null; private int lastSwitchTick = -1; + /** Huey projectiles still in flight toward the player (identity-based). */ + private final Set incomingHueyProjectiles = + Collections.newSetFromMap(new IdentityHashMap<>()); + @Provides HueyPrayerConfig provideConfig(net.runelite.client.config.ConfigManager configManager) { @@ -63,26 +70,88 @@ protected void shutDown() { Rs2Prayer.disableAllPrayers(); currentPrayer = null; + incomingHueyProjectiles.clear(); } @Subscribe public void onProjectileMoved(ProjectileMoved event) { - if (!config.enabled()) return; + if (!config.enabled()) + { + return; + } Projectile projectile = event.getProjectile(); + if (projectile == null) + { + return; + } // Only react to projectiles targeting YOU if (projectile.getInteracting() != client.getLocalPlayer()) + { return; + } int id = projectile.getId(); + boolean isHueyProjectile = false; + if (id == MAGIC_PROJECTILE_ID) + { + isHueyProjectile = true; + } + if (id == RANGE_PROJECTILE_ID) + { + isHueyProjectile = true; + } + if (id == MELEE_PROJECTILE_ID) + { + isHueyProjectile = true; + } + if (!isHueyProjectile) + { + return; + } + + if (!config.disableAfterImpact()) + { + if (!incomingHueyProjectiles.isEmpty()) + { + incomingHueyProjectiles.clear(); + } + } + + if (config.disableAfterImpact()) + { + if (incomingHueyProjectiles.contains(projectile)) + { + if (projectile.getRemainingCycles() <= 0) + { + incomingHueyProjectiles.remove(projectile); + if (incomingHueyProjectiles.isEmpty()) + { + Rs2Prayer.disableAllPrayers(); + currentPrayer = null; + } + } + return; + } + } + + if (projectile.getRemainingCycles() <= 0) + { + return; + } if (config.debug()) { Microbot.log("Projectile ID: " + id); } + if (config.disableAfterImpact()) + { + incomingHueyProjectiles.add(projectile); + } + switch (id) { case MAGIC_PROJECTILE_ID: From eefda7e9d6eda820738712aeefa71d973339bb59 Mon Sep 17 00:00:00 2001 From: chsami Date: Sun, 19 Apr 2026 12:59:40 +0200 Subject: [PATCH 54/95] fix(TutorialIslandPlugin): update version to 1.3.16 and refactor character creation logic for improved handling --- .claude/skills/debugger/SKILL.md | 303 +++++++++++++++ CLAUDE.md | 29 ++ build.gradle | 13 + .../tutorialisland/TutorialIslandPlugin.java | 16 +- .../tutorialisland/TutorialIslandScript.java | 348 ++++++++++-------- 5 files changed, 546 insertions(+), 163 deletions(-) create mode 100644 .claude/skills/debugger/SKILL.md diff --git a/.claude/skills/debugger/SKILL.md b/.claude/skills/debugger/SKILL.md new file mode 100644 index 0000000000..5887b90cd9 --- /dev/null +++ b/.claude/skills/debugger/SKILL.md @@ -0,0 +1,303 @@ +--- +name: debugger +description: "Autonomously debug a Microbot Hub plugin from a free-text bug description. Starts the client with the target plugin registered, reproduces the bug via the agent server, edits the plugin source, rebuilds, hot-reloads, and verifies the fix — all without human intervention. Use when the user invokes /debugger with a description like \"PestControl doesn't board the boat on Void Knight portal\" or \"AutoFishing stops after banking\"." +tools: Read, Grep, Glob, Edit, Write, Bash, Agent +model: inherit +--- + +# Microbot Hub Plugin Debugger + +You are an autonomous debugger for a **community Hub plugin** — the code under `src/main/java/net/runelite/client/plugins/microbot//`. You are not debugging the client/engine itself (that lives in `../Microbot/`). You will be given a free-text bug description and are expected to reproduce, root-cause, patch, and verify the fix without further input. + +## Inputs + +- A free-text description of the bug (the entire `/debug` argument string). Usually names or strongly hints at a plugin. +- The current working tree. Uncommitted changes in the target plugin directory are prime suspects. + +## Outputs + +- Plugin source edits, left **uncommitted** for the user to review. +- A final detailed explanation in chat: what was broken, how you reproduced it, what you changed, how you verified it. The user wants this — do not skip it. + +## Stopping conditions + +- **Success:** fix applied, client restarted (or plugin hot-reloaded), repro no longer triggers, no new errors in logs. Stop and report. +- **Budget:** up to **5 patch attempts**. On the 5th failed attempt, stop and report what you tried and what you'd try next. +- **Dead-end:** if the bug doesn't reproduce after 3 attempts, stop and report — don't guess at fixes without a repro. +- **Wrong layer:** if the bug turns out to be in the engine (client APIs in `../Microbot/`), stop and tell the user — this skill does not patch engine code. + +## Model split: Opus drives, Sonnet executes + +You (Opus 4.7) own all the thinking — bug interpretation, plugin-source analysis, repro design, patch writing, verification, and the final report. **Do not delegate understanding.** + +A **Sonnet 4.6 subagent** owns mechanical CLI/HTTP work: starting the client, logging in, starting/stopping the target plugin, collecting `/state` and `/objects` snapshots, tailing logs, rebuilding, hot-reloading. This keeps your context clean (gradle/curl/log noise stays in the subagent) and is faster + cheaper. + +Spawn the subagent with the Agent tool, passing `model: "sonnet"` and `subagent_type: "general-purpose"`. Tell it the exact commands to run and ask for raw outputs (status codes, JSON bodies, log tails) — never the subagent's interpretation. You interpret. + +### Stuck-detector subagent (runs in parallel) + +In addition to the executor subagent, spawn a **second Sonnet subagent as a stuck-detector background watcher** as soon as the target plugin has been started in Stage 3. Run it with `run_in_background: true` so it does not block you. + +Its only job: every **5 seconds**, poll the agent server and decide whether the plugin is stuck. If stuck, return immediately with a concise diagnostic signal; otherwise keep polling. Cap it at ~60 iterations (~5 minutes) so it doesn't loop forever. + +Brief it with the plugin's fully-qualified class name and a "stuck" definition tailored to that plugin. Defaults apply when you have no plugin-specific signal: + +- Player animation `-1` **and** player not moving (`pose == 808` idle) for ≥ 4 consecutive polls (~20s) +- Player world coords unchanged for ≥ 6 consecutive polls (~30s) while script status still reports `RUNNING` +- Script status flips to `STOPPED`/`ERROR` unexpectedly +- Same log line repeats ≥ 10 times in the tail (tight loop / retry spin) +- Exception stack trace appears in `/tmp/microbot-hub.log` + +When any of these fires, the subagent returns a short JSON-ish payload with `stuck: true`, the triggered rule, and the evidence (last 10 log lines + relevant `/state` fields + script status). You treat that return as a signal to jump to Stage 4 (root cause) with the evidence in hand — do not wait for the normal sleep window. + +Stuck-detector prompt template: + +``` +You are a stuck-detector watcher for plugin . Poll every 5 seconds, up to 60 iterations. Do NOT interpret beyond the rules below. + +Each iteration: +1. curl -sS "http://127.0.0.1:8081/scripts/status?className=" +2. curl -sS "http://127.0.0.1:8081/state" | jq '{pos:.player.worldLocation, anim:.player.animation, pose:.player.pose}' +3. tail -30 /tmp/microbot-hub.log + +Track across iterations. Return `stuck: true` immediately if ANY of: +- Player coords unchanged for 6 consecutive polls while status=RUNNING +- animation=-1 AND pose=808 (idle) for 4 consecutive polls +- status flips to STOPPED or ERROR unexpectedly +- Same log line repeats ≥10 times in the last tail +- Exception stack trace appears in the log tail + +On stuck, return: `{stuck:true, rule:"", evidence:{lastLogs:[...], state:{...}, status:{...}}}`. On clean exit after 60 iterations, return `{stuck:false}`. Do not retry, do not theorize. +``` + +**What goes to Sonnet (mechanical):** +- Stage 2 — start client, wait for `:8081/state`, run login, dismiss welcome screen. +- Stage 3 (data collection half) — start the plugin via `/scripts/start`, poll `/scripts/status`, dump `/state` / `/objects` / `/inventory` / `/npcs`, return raw JSON + log tail. +- Stage 6 — rebuild plugin shadow JAR, hot-reload or restart, return compile errors verbatim. +- Stage 7 (run half) — restart the plugin, collect fresh state, return it. + +**What stays on Opus (judgment):** +- Stage 0 — orient: identify the plugin, read its source. +- Stage 1 — register the plugin for debug if needed. +- Stage 3 (repro design) — decide what state to capture to prove the bug. +- Stage 4 — root-cause analysis. +- Stage 5 — write the patch. +- Stage 7 (interpretation) — decide whether the bug is gone. +- Stage 8 — write the report. + +If a Sonnet subagent's output is ambiguous or contradicts expectations, **read the file/log yourself** before re-tasking it. Don't loop on a confused subagent. + +### Subagent prompt template + +``` +You are a CLI/HTTP runner for a Hub plugin debugging session. Run the commands below, return raw outputs verbatim, do not interpret. + +Commands: +1. +2. + +Return: +- Command 1: exit code + stdout/stderr (last 50 lines if long) +- Command 2: exit code + raw response body +- Tail of /tmp/microbot-hub.log (last 30 lines) if it grew + +Cap total response at ~400 words. If a command fails unexpectedly, return the failure verbatim — do not retry or improvise. +``` + +## The loop + +Use TaskCreate at the start to track stages. One task per stage; mark completed as you go. + +### Stage 0 — Orient *(Opus only)* + +1. Restate the bug in one sentence. Identify the target plugin folder (e.g. "PestControl" → `src/main/java/net/runelite/client/plugins/microbot/pestcontrol/`). If the description is ambiguous, list candidates with Grep and pick the one whose `@PluginDescriptor` name/tags match. +2. Read the plugin's main `*Plugin.java`, `*Script.java`, and `@PluginDescriptor`. Note the version field — you'll bump it when patching. +3. Check `git status` / `git diff ` — uncommitted changes in the target dir are prime suspects. +4. Skim `docs/PLUGIN_DEBUGGING_NOTES.md` if the symptom sounds like one of the documented recurring failure modes (instanced-region coord mismatches, Queryable API not auto-walking, null-guard predicates masking broken lookups, static field leakage across plugin restarts). +5. Check if a client is already up: `curl -sS --max-time 2 http://127.0.0.1:8081/state > /dev/null && echo UP || echo DOWN`. If UP and the target plugin is already in the running instance, skip to Stage 3. + +### Stage 1 — Register the plugin for debug *(Opus only)* + +Open `src/test/java/net/runelite/client/Microbot.java` and confirm the target plugin class is in `debugPlugins`. If not, add the import and the class entry. `AgentServerPlugin.class` must remain in the list — that's how you talk to the client over HTTP. + +Do not remove other plugins already in the list unless they interfere with the repro (rare). + +### Stage 2 — Start the client *(delegate to Sonnet)* + +Brief Sonnet with: + +```bash +# Kill any stale client +pkill -f 'net.runelite.client.RuneLite' || true + +# Launch (this compiles the Hub plugins + pulls the microbot client JAR) +./gradlew run --args='--debug' > /tmp/microbot-hub.log 2>&1 & + +# Poll /state until it responds (cold JVM + gradle init can take ~90s) +until curl -sS --max-time 2 http://127.0.0.1:8081/state > /dev/null 2>&1; do sleep 2; done + +# Login (CLI lives in sibling repo) +../Microbot/microbot-cli login now --timeout 60 +../Microbot/microbot-cli state + +# If welcome screen is still up: +../Microbot/microbot-cli widgets click --text "Click here to play" +``` + +Ask Sonnet to verify `gameState == LOGGED_IN` in the final `state` JSON and return that JSON plus the last 30 lines of `/tmp/microbot-hub.log`. If login fails, read the `loginError` field yourself — non-member/banned/bad-creds each need a different response (most often: tell the user, stop). + +### Stage 3 — Reproduce + +**You (Opus) design the repro.** For a Hub plugin, reproduction almost always means: start the plugin, put the player in the relevant game state (or the bug's state should trigger on its own), and capture enough live data to prove the bug. You rarely need a custom probe plugin — the agent server already exposes the scene, inventory, NPCs, widgets, and dialogue. + +Decide what to capture. Examples: +- Wrong object interaction? Dump `/objects?maxDistance=50&limit=10000` and grep for the expected ID/name. +- Stuck script? Tail the log for the script's last `Microbot.log(...)` line and dump `/state` to see player pos / animation / varbits. +- Wrong inventory branch? Dump `/inventory` before and after the script runs. +- Instanced region suspicion? Check `/state` for player coords in the high X/Y corner (see `docs/PLUGIN_DEBUGGING_NOTES.md` §2). + +**Delegate data collection to Sonnet** with the plugin's fully-qualified class name: + +```bash +# Start the plugin +curl -sS -X POST -H "X-Agent-Token: $(cat ~/.microbot/agent-token)" -H 'Content-Type: application/json' \ + -d '{"className":"net.runelite.client.plugins.microbot.."}' \ + http://127.0.0.1:8081/scripts/start + +# Let it run +sleep 10 + +# Collect state +curl -sS "http://127.0.0.1:8081/scripts/status?className=net.runelite.client.plugins.microbot.." +curl -sS "http://127.0.0.1:8081/state" | jq +curl -sS "http://127.0.0.1:8081/objects?maxDistance=50&limit=10000" > /tmp/objs.json && wc -l /tmp/objs.json +tail -100 /tmp/microbot-hub.log +``` + +Ask Sonnet to return each response body plus the log tail verbatim. If the repro is unclear, iterate: tell Sonnet to stop the plugin (`POST /scripts/stop`), adjust game state via CLI if possible, restart, re-collect. + +**As soon as the plugin is running, spawn the stuck-detector subagent in parallel** (see "Stuck-detector subagent" under the Model split section). `run_in_background: true`, 5s polling. If it signals `stuck: true` while you're waiting, treat its evidence payload as your primary Stage 4 input — you already have the failure captured. + +**Dynamic framework scripting at runtime:** if you need to probe engine state that `/state`, `/objects`, or `/inventory` don't expose (custom varbit combos, widget tree walks, specific scene-object predicates), author a tiny probe plugin and deploy it via `POST /scripts/deploy` without touching the target plugin. Keep the probe stateless, have it `ScriptResultStore.submit(...)` its findings, and retrieve via `/scripts/results`. This is faster than adding logging to the target plugin and rebuilding. + +Give yourself ~3 iterations. If after three tries the bug doesn't reproduce, stop and report (don't guess fixes without a repro). + +### Stage 4 — Root cause *(Opus only)* + +Read the plugin source yourself. Use the log lines, script status, and live state snapshots to form a hypothesis. **Do not attribute concurrent log output to the plugin under test** — other scripts can log simultaneously; verify ownership before drawing conclusions (per the feedback memory). + +Consult `docs/PLUGIN_DEBUGGING_NOTES.md` as a checklist when symptoms match. If the bug lives in `Rs2*` calls or pathfinder internals (i.e. `../Microbot/`), this skill is the wrong tool — stop and say so. + +### Stage 5 — Patch *(Opus only)* + +Edit the plugin source directly. Follow project rules (CLAUDE.md + `../Microbot/AGENTS.md`): + +- **Never sleep on the client thread.** Never use static sleeps — use `sleepUntil(BooleanSupplier, timeoutMs)`. +- Use `Microbot.getClientThread().invoke(...)` for widget/varbit/world-view access. +- Use `Microbot.getRs2XxxCache()` accessors; never instantiate caches. +- Keep the change minimal. +- **Bump the plugin version** in the static `version` field (project rule — always increment on any change, even fixes). + +### Stage 6 — Rebuild + hot-reload *(delegate to Sonnet)* + +**Never restart the client to test a script change.** The Hub's dynamic script framework lets you compile and swap the plugin at runtime — restarting the JVM costs ~90s of gradle + login overhead per iteration, and you lose live state needed to verify the fix. Client restart is reserved for the rare cases listed at the bottom of this stage. + +For Hub plugins, do a **targeted rebuild** — full builds are slow: + +```bash +./gradlew build -PpluginList= +``` + +Then hot-reload. Two equivalent paths: + +**A. `microbot-cli` wrapper** (preferred — handles auth automatically): + +```bash +../Microbot/microbot-cli scripts reload --name +../Microbot/microbot-cli scripts health +``` + +**B. Raw HTTP (127.0.0.1:8081)** when the CLI isn't enough: + +```bash +# Reload an existing deployment in place +curl -sS -X POST -H "X-Agent-Token: $(cat ~/.microbot/agent-token)" -H 'Content-Type: application/json' \ + -d '{"name":""}' \ + http://127.0.0.1:8081/scripts/deploy/reload + +# Or deploy a fresh source file (compile + load + start via URLClassLoader + Guice) +curl -sS -X POST -H "X-Agent-Token: $(cat ~/.microbot/agent-token)" -H 'Content-Type: application/json' \ + -d '{"source":"","className":""}' \ + http://127.0.0.1:8081/scripts/deploy + +# List current deployments +curl -sS "http://127.0.0.1:8081/scripts/deploy" + +# Undeploy (stop + unload) +curl -sS -X POST -H "X-Agent-Token: $(cat ~/.microbot/agent-token)" -H 'Content-Type: application/json' \ + -d '{"name":""}' \ + http://127.0.0.1:8081/scripts/deploy/undeploy +``` + +Dynamic deploys are also the right tool for **runtime probe scripts** — minimal `@PluginDescriptor` classes you author on the fly to inspect engine state (varbits, widget trees, scene objects) without touching the target plugin. See `scripts/test_hot_reload.py` for a worked deploy/reload/undeploy lifecycle. Core implementation lives in `agentserver/scripting/DynamicScriptManager.java` and `DynamicScriptCompiler.java`. + +Ask Sonnet to return compile errors verbatim on failure. If the build fails, **you** fix the source — do not ask Sonnet to interpret compile errors. + +**Full client restart is only justified when** you changed: `@PluginDescriptor` field values (name, minClientVersion, iconUrl), `dependencies.txt`, `PluginConstants.java`, or Guice-bound singletons wired at client startup. Even then, try a reload first — if it fails cleanly with a classloader error, *then* restart: + +```bash +pkill -f 'net.runelite.client.RuneLite' || true +until ! curl -sS --max-time 1 http://127.0.0.1:8081/state > /dev/null 2>&1; do sleep 1; done +./gradlew run --args='--debug' > /tmp/microbot-hub.log 2>&1 & +until curl -sS --max-time 2 http://127.0.0.1:8081/state > /dev/null 2>&1; do sleep 2; done +../Microbot/microbot-cli login now --timeout 60 +../Microbot/microbot-cli widgets click --text "Click here to play" # if present +``` + +### Stage 7 — Verify + +Have Sonnet restart the plugin via `/scripts/start` and re-collect the same state snapshots you captured in Stage 3. **You (Opus) decide** whether: + +1. The bug no longer reproduces (compare before/after state snapshots, log lines). +2. No new errors in `/tmp/microbot-hub.log`. +3. No other plugin behavior broke (glance at `/scripts/status` for anything else that was running). + +If verified → Stage 8. If not → increment patch-attempt counter, return to Stage 4. Stop on the 5th failure. + +### Stage 8 — Report *(Opus only)* + +Leave the plugin source edits **uncommitted**. Do not `git add` or `git commit` — the user will review the diff. + +Print a detailed explanation with these sections: + +- **Bug:** one-sentence restatement. +- **Plugin:** which plugin, which folder. +- **Reproduction:** how you triggered it, what state snapshot surfaced the symptom (quote the relevant JSON field or log line). +- **Root cause:** the specific code path and why it was broken. Cite `file:line`. +- **Fix:** what you changed and why, including the version bump. Cite `file:line` for each edit. +- **Verification:** what you ran, what the output was, what changed between before/after. +- **Attempts:** if you tried multiple patches, briefly list the ones that didn't work and why. + +## Reference docs (read on demand, don't duplicate) + +- `CLAUDE.md` — project rules (plugin descriptor fields, version bumping, threading, event subscription). +- `docs/PLUGIN_DEBUGGING_NOTES.md` — **read first** when symptoms look familiar; covers instanced-region coords, Queryable API auto-walk, static field leakage, agent-server `curl` recipes. +- `docs/AGENT_SERVER.md` — full HTTP endpoint reference for the agent server on :8081. +- `docs/MICROBOT_CLI.md` — CLI command reference. The CLI is at `../Microbot/microbot-cli` and wraps most endpoints with auth. +- `docs/SCRIPT_LIFECYCLE_API.md` — start/stop/status/results endpoints for automated plugin testing. +- `src/test/java/net/runelite/client/ScriptLifecycleTest.java` — worked example of the full login → start → poll → results → stop cycle in Java. +- `../Microbot/AGENTS.md` — non-negotiable engine rules (cache API, no client-thread blocking). Applies to plugin code that calls client APIs. + +## Pitfalls + +- **Client already running:** don't start another. Check `:8081/state` first. +- **Never restart the client to test a script change.** Hot-reload via `microbot-cli scripts reload` or `POST /scripts/deploy/reload`. Client restarts destroy the live game state you need for verification and cost ~90s per iteration. Restart is only justified for `@PluginDescriptor` field changes, `dependencies.txt` changes, `PluginConstants.java` changes, or Guice-bound singletons wired at startup — and even then, try reload first. +- **Stuck detection:** always run the stuck-detector subagent in the background once the plugin is running. Do not wait for arbitrary sleep windows to discover a hang — the watcher signals you in ~5s and hands you the evidence. +- **This skill does not patch engine code.** If root-cause sits in `../Microbot/` (client APIs, pathfinder, cache), stop and tell the user. They have a separate engine-debug workflow. +- **Token auth:** prefer `../Microbot/microbot-cli` where available — it reads `~/.microbot/agent-token` automatically. For endpoints the CLI doesn't wrap (`/scripts/deploy*`, `/state`, `/objects`), use curl with `-H "X-Agent-Token: $(cat ~/.microbot/agent-token)"`. +- **The `microbot-cli objects` command ignores `--id` and `--distance`.** For precise filters, curl `/objects` directly (see PLUGIN_DEBUGGING_NOTES.md §1). +- **Login welcome screen:** after `login now`, the "Click here to play" widget may still cover the game view. Dismiss before probing in-game state (per feedback memory). +- **Static field leakage:** plugins that keep `static` mutable state (collections, timers) will surprise you after stop/start. If the bug only reproduces on a second run, check for `static` fields not reset in `startUp()`. +- **`setTarget(null)` in PestControl:** that one-shot clear on instance exit is load-bearing (per project memory). Don't "clean it up" without understanding why it's there. +- **Version bump:** every patch requires a version bump in the plugin's `static final String version` field. The build system uses it for JAR naming and `plugins.json`. +- **Don't commit:** leave the diff for the user. diff --git a/CLAUDE.md b/CLAUDE.md index 630d5ff4f3..55933f55b0 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -209,6 +209,35 @@ Key capabilities for Hub plugin testing: - **Script lifecycle**: Start/stop plugins by class name via HTTP, poll runtime status, submit and retrieve structured test results. - **Java result API**: Hub scripts can call `ScriptResultStore.submit(className, data)` directly from within the JVM. +## Dynamic Script Deployment (Hot-Reload) + +**Core Mechanism:** Compile Java source → load via custom URLClassLoader → inject into Guice → start as a RuneLite plugin. No client restart needed. + +**HTTP endpoints (127.0.0.1:8081):** +- `POST /scripts/deploy` — compile & start +- `POST /scripts/deploy/reload` — recompile in place +- `POST /scripts/deploy/undeploy` — stop & unload +- `GET /scripts/deploy` — list deployments + +**Core files:** `agentserver/scripting/DynamicScriptManager.java`, `DynamicScriptCompiler.java`, `handler/DynamicScriptDeployHandler.java` + +### Supporting Systems + +1. **microbot-cli** — bash wrapper for the agent server (`./microbot-cli scripts deploy|reload|undeploy|health|results`) +2. **Agent Server API** — query state (NPCs, objects, inventory, widgets) and drive the client (walk, interact, dialogue). Full reference in `docs/AGENT_SERVER.md`. +3. **Probe plugin pattern** — minimal `@PluginDescriptor` classes deployed to inspect engine state, iterate with hot-reload. Used by the `/debugger` skill. +4. **StateMachineScript** — base class for multi-phase scripts; transitions observable via `GET /debug/snapshot?script=Name`. +5. **ScriptResultStore** — scripts submit results in-process; retrieve via `/scripts/results` for test workflows. +6. **`scripts/test_hot_reload.py`** — working end-to-end example of the deploy/reload/undeploy lifecycle. + +### Typical Loop + +``` +edit source → microbot-cli scripts reload → microbot-cli scripts health → observe logs → repeat +``` + +Everything binds to `127.0.0.1` only. + ## Common Patterns - Plugins extending `SchedulablePlugin` implement `getStartCondition()` and `getStopCondition()` for scheduler integration diff --git a/build.gradle b/build.gradle index 3888d9554f..df23ae3d9d 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,19 @@ apply from: 'gradle/plugin-utils.gradle' def microbotClientVersion = project.ext.getMicrobotClientVersion() def microbotClientPath = project.findProperty("microbotClientPath") +if (!microbotClientPath && project.hasProperty("microbotClientDir")) { + def clientDir = file(project.findProperty("microbotClientDir")) + if (clientDir.isDirectory()) { + def match = clientDir.listFiles()?.findAll { it.name ==~ /microbot-.*\.jar/ && !it.name.contains('sources') && !it.name.contains('shaded') } + ?.sort { -it.lastModified() } + ?.first() + if (match) { + microbotClientPath = match.absolutePath + logger.lifecycle("🤖 Resolved local microbot client JAR: ${microbotClientPath}") + } + } +} + // Java toolchain configuration java { toolchain { diff --git a/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandPlugin.java index a1f616f81d..d99aac9841 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandPlugin.java @@ -1,8 +1,6 @@ package net.runelite.client.plugins.microbot.tutorialisland; import com.google.inject.Provides; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; import net.runelite.client.config.ConfigManager; import net.runelite.client.eventbus.Subscribe; import net.runelite.client.events.ConfigChanged; @@ -26,20 +24,20 @@ enabledByDefault = PluginConstants.DEFAULT_ENABLED, isExternal = PluginConstants.IS_EXTERNAL ) -@Slf4j public class TutorialIslandPlugin extends Plugin { - public static final String version = "1.3.2"; + public static final String version = "1.3.16"; - @Getter private boolean toggleMusic; - @Getter private boolean toggleRoofs; - @Getter private boolean toggleLevelUp; - @Getter private boolean toggleShiftDrop; - @Getter private boolean toggleDevOverlay; + + public boolean isToggleMusic() { return toggleMusic; } + public boolean isToggleRoofs() { return toggleRoofs; } + public boolean isToggleLevelUp() { return toggleLevelUp; } + public boolean isToggleShiftDrop() { return toggleShiftDrop; } + public boolean isToggleDevOverlay() { return toggleDevOverlay; } @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java b/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java index 2f57fa6b67..a8e567a28d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tutorialisland/TutorialIslandScript.java @@ -42,11 +42,16 @@ public class TutorialIslandScript extends Script { public static Status status = Status.NAME; final int CharacterCreation = 679; final int[] CharacterCreation_Arrows = new int[]{13, 17, 21, 25, 29, 33, 37, 44, 48, 52, 56, 60}; + private static final int MIN_RANDOMIZATION_ROUNDS = 8; private final TutorialIslandPlugin plugin; private final int NameCreation = 558; private boolean toggledSettings = false; private boolean toggledMusic = false; + private boolean triedRoofs = false; + private boolean triedShiftDrop = false; + private boolean triedLevelUp = false; private boolean hasSelectedGender = false; + private int randomizationRounds = 0; @Inject public TutorialIslandScript(TutorialIslandPlugin plugin) { @@ -66,6 +71,18 @@ public boolean run(TutorialIslandConfig config) { CalculateStatus(); + if (Rs2Widget.isWidgetVisible(929, 5)) { + Rs2Widget.clickWidget(929, 5); + Rs2Random.waitEx(1200, 300); + return; + } + + if (Rs2Widget.isWidgetVisible(310, 0)) { + Rs2Keyboard.keyPress(KeyEvent.VK_ESCAPE); + Rs2Random.waitEx(1200, 300); + return; + } + if (hasContinue()) { clickContinue(); return; @@ -154,7 +171,7 @@ public boolean run(TutorialIslandConfig config) { break; } } catch (Exception ex) { - System.out.println(ex.getMessage()); + ex.printStackTrace(); } }, 0, 600, TimeUnit.MILLISECONDS); return true; @@ -203,54 +220,56 @@ public void CalculateStatus() { } public void RandomizeCharacter() { - if (Rs2Random.diceFractional(0.2)) { - selectGender(); - - if (Rs2Random.diceFractional(0.25)) { // chance to change pronouns - System.out.println("changing pronouns..."); - Widget pronounWidget = Rs2Widget.getWidget(CharacterCreation, 72); // open pronouns DropDown - Widget currentPronoun = Arrays.stream(pronounWidget.getDynamicChildren()).filter(pnw -> pnw.getText().toLowerCase().contains("he/him") || pnw.getText().toLowerCase().contains("they/them") || pnw.getText().toLowerCase().contains("she/her")).findFirst().orElse(null); - Rs2Widget.clickWidget(pronounWidget); - Rs2Random.waitEx(1200, 300); - sleepUntil(() -> Rs2Widget.isWidgetVisible(CharacterCreation, 76)); // Pronoun DropDown Options - Widget[] dynamicPronounWidgets = Rs2Widget.getWidget(CharacterCreation, 78).getDynamicChildren(); - Widget pronounSelectionWidget; - - if (currentPronoun != null) { - if (currentPronoun.getText().toLowerCase().contains("he/him")) { - if (Rs2Random.diceFractional(0.5)) { - pronounSelectionWidget = Arrays.stream(dynamicPronounWidgets).filter(dpw -> dpw.getText().toLowerCase().contains("they/them")).findFirst().orElse(null); - } else { - pronounSelectionWidget = Arrays.stream(dynamicPronounWidgets).filter(dpw -> dpw.getText().toLowerCase().contains("she/her")).findFirst().orElse(null); - } - } else { - if (Rs2Random.diceFractional(0.5)) { - pronounSelectionWidget = Arrays.stream(dynamicPronounWidgets).filter(dpw -> dpw.getText().toLowerCase().contains("they/them")).findFirst().orElse(null); - } else { - pronounSelectionWidget = Arrays.stream(dynamicPronounWidgets).filter(dpw -> dpw.getText().toLowerCase().contains("he/him")).findFirst().orElse(null); - } - } - - Rs2Widget.clickWidget(pronounSelectionWidget); - Rs2Random.waitEx(1200, 300); - sleepUntil(() -> !Rs2Widget.isWidgetVisible(CharacterCreation, 76)); // Pronoun DropDown Options - } - } - - Rs2Widget.clickWidget(CharacterCreation, 74); // confirm Button - Rs2Random.waitEx(1200, 300); - sleepUntil(() -> !isCharacterCreationVisible()); - } - int randomIndex = (int) Math.floor(Math.random() * CharacterCreation_Arrows.length); int item = CharacterCreation_Arrows[randomIndex]; item += Math.random() < 0.5 ? 2 : 3; // Select Up / Down Arrow for random index Widget widget = Rs2Widget.getWidget(CharacterCreation, item); + if (widget == null) return; for (int i = 0; i < Rs2Random.between(1, 6); i++) { Rs2Widget.clickWidget(widget.getId()); Rs2Random.waitEx(300, 50); } + randomizationRounds++; + + if (randomizationRounds < MIN_RANDOMIZATION_ROUNDS || !Rs2Random.diceFractional(0.2)) return; + + selectGender(); + + if (Rs2Random.diceFractional(0.25)) { // chance to change pronouns + System.out.println("changing pronouns..."); + Widget pronounWidget = Rs2Widget.getWidget(CharacterCreation, 72); // open pronouns DropDown + Widget currentPronoun = Arrays.stream(pronounWidget.getDynamicChildren()).filter(pnw -> pnw.getText().toLowerCase().contains("he/him") || pnw.getText().toLowerCase().contains("they/them") || pnw.getText().toLowerCase().contains("she/her")).findFirst().orElse(null); + Rs2Widget.clickWidget(pronounWidget); + Rs2Random.waitEx(1200, 300); + sleepUntil(() -> Rs2Widget.isWidgetVisible(CharacterCreation, 76)); // Pronoun DropDown Options + Widget[] dynamicPronounWidgets = Rs2Widget.getWidget(CharacterCreation, 78).getDynamicChildren(); + Widget pronounSelectionWidget; + + if (currentPronoun != null) { + if (currentPronoun.getText().toLowerCase().contains("he/him")) { + if (Rs2Random.diceFractional(0.5)) { + pronounSelectionWidget = Arrays.stream(dynamicPronounWidgets).filter(dpw -> dpw.getText().toLowerCase().contains("they/them")).findFirst().orElse(null); + } else { + pronounSelectionWidget = Arrays.stream(dynamicPronounWidgets).filter(dpw -> dpw.getText().toLowerCase().contains("she/her")).findFirst().orElse(null); + } + } else { + if (Rs2Random.diceFractional(0.5)) { + pronounSelectionWidget = Arrays.stream(dynamicPronounWidgets).filter(dpw -> dpw.getText().toLowerCase().contains("they/them")).findFirst().orElse(null); + } else { + pronounSelectionWidget = Arrays.stream(dynamicPronounWidgets).filter(dpw -> dpw.getText().toLowerCase().contains("he/him")).findFirst().orElse(null); + } + } + + Rs2Widget.clickWidget(pronounSelectionWidget); + Rs2Random.waitEx(1200, 300); + sleepUntil(() -> !Rs2Widget.isWidgetVisible(CharacterCreation, 76)); // Pronoun DropDown Options + } + } + + Rs2Widget.clickWidget(CharacterCreation, 74); // confirm Button + Rs2Random.waitEx(1200, 300); + sleepUntil(() -> !isCharacterCreationVisible()); } /** @@ -289,6 +308,50 @@ private void selectGender() { hasSelectedGender = true; } + private boolean walkAndTalk(Rs2NpcModel npc) { + return walkAndTalk(npc, 2); + } + + private boolean walkAndTalk(Rs2NpcModel npc, int reach) { + return walkAndAct(npc, reach, "Talk-to", () -> sleepUntil(Rs2Dialogue::isInDialogue, 5000)); + } + + private boolean walkAndAct(Rs2NpcModel npc, int reach, String action, Runnable afterClick) { + if (npc == null) return false; + WorldPoint npcLoc = npc.getWorldLocation(); + WorldPoint playerLoc = Rs2Player.getWorldLocation(); + if (npcLoc == null || playerLoc == null) return false; + if (playerLoc.distanceTo(npcLoc) > reach) { + Rs2Walker.walkTo(npcLoc, reach); + Rs2Player.waitForWalking(); + return false; + } + if (npc.click(action)) { + if (afterClick != null) afterClick.run(); + return true; + } + return false; + } + + private boolean walkAndAttackRat() { + // The rat pit gate (id 9719) blocks both Rs2Walker and native pathfinder. + // Explicitly open it if we're standing adjacent; after passing through, attack directly. + WorldPoint playerLoc = Rs2Player.getWorldLocation(); + if (playerLoc != null && playerLoc.getX() == 3111 && playerLoc.getY() >= 9516 && playerLoc.getY() <= 9519) { + if (Microbot.getRs2TileObjectCache().query().withId(9719).interact("Open")) { + sleepUntil(() -> { + WorldPoint p = Rs2Player.getWorldLocation(); + return p != null && p.getY() < 9516; + }, 3000); + return false; + } + } + Rs2NpcModel rat = Microbot.getRs2NpcCache().query().withName("Giant rat").nearest(); + if (rat == null || rat.getWorldLocation() == null) return false; + if (!Rs2Walker.canReach(rat.getWorldLocation())) return false; + return rat.click("Attack"); + } + public void GettingStarted() { var npc = Microbot.getRs2NpcCache().query().withId(NpcID.GIELINOR_GUIDE).nearest(); @@ -300,9 +363,7 @@ public void GettingStarted() { return; } - if (npc.click("Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else if (Microbot.getVarbitPlayerValue(281) < 8) { if (!toggledSettings) { @@ -313,26 +374,29 @@ public void GettingStarted() { } if (plugin.isToggleMusic() && !toggledMusic) { - turnOffMusic(); toggledMusic = true; + try { turnOffMusic(); } catch (Exception ignored) { } Rs2Random.waitEx(1200, 300); return; } - if (plugin.isToggleRoofs() && !isHideRoofsEnabled()) { - hideRoofs(false); + if (plugin.isToggleRoofs() && !triedRoofs && !isHideRoofsEnabled()) { + triedRoofs = true; + try { hideRoofs(false); } catch (Exception ignored) { } Rs2Random.waitEx(1200, 300); return; } - if (plugin.isToggleShiftDrop() && !isDropShiftSettingEnabled()) { - enableDropShiftSetting(false); + if (plugin.isToggleShiftDrop() && !triedShiftDrop && !isDropShiftSettingEnabled()) { + triedShiftDrop = true; + try { enableDropShiftSetting(false); } catch (Exception ignored) { } Rs2Random.waitEx(1200, 300); return; } - if (plugin.isToggleLevelUp() && isLevelUpNotificationsEnabled()) { - disableLevelUpNotifications(true); + if (plugin.isToggleLevelUp() && !triedLevelUp && isLevelUpNotificationsEnabled()) { + triedLevelUp = true; + try { disableLevelUpNotifications(true); } catch (Exception ignored) { } Rs2Random.waitEx(1200, 300); return; } @@ -343,14 +407,10 @@ public void GettingStarted() { sleepUntil(() -> Rs2Camera.getPitch() > 250); - if (npc.click("Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else { - if (npc.click("Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } } @@ -358,13 +418,7 @@ public void SurvivalGuide() { var npc = Microbot.getRs2NpcCache().query().withId(NpcID.SURVIVAL_EXPERT).nearest(); if (Microbot.getVarbitPlayerValue(281) == 10 || Microbot.getVarbitPlayerValue(281) == 20 || Microbot.getVarbitPlayerValue(281) == 60) { - if (!npc.hasLineOfSight()) { - Rs2Walker.walkTo(npc.getWorldLocation(), 4); - Rs2Player.waitForWalking(); - } - if (npc.click("talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else if (Microbot.getVarbitPlayerValue(281) < 40) { Rs2Random.waitEx(1200, 300); var widget = Rs2Widget.findWidget("Inventory", true); @@ -376,17 +430,13 @@ public void SurvivalGuide() { var widget = Rs2Widget.findWidget("Skills", true); Rs2Widget.clickWidget(widget); // switchToSkillsTab Rs2Random.waitEx(1200, 300); - if (npc.click("talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else if (Microbot.getVarbitPlayerValue(281) <= 90) { if (!Rs2Inventory.hasItem("Bronze Axe") || !Rs2Inventory.hasItem("Tinderbox")) { - if (npc.click("talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); return; } - if (!Rs2Inventory.contains("Raw shrimps")) { + if (!Rs2Inventory.contains(false, "shrimps")) { fishShrimp(); return; } @@ -413,45 +463,43 @@ public void MageGuide() { if (distance > 8) { Rs2Walker.walkTo(targetPoint, 8); } else { - if (npc.click("Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } } else if (Microbot.getVarbitPlayerValue(281) == 630) { var widget = Rs2Widget.findWidget("Magic", true); Rs2Widget.clickWidget(widget); // switchToMagicTab Rs2Random.waitEx(1200, 300); } else if (Microbot.getVarbitPlayerValue(281) == 640) { - if (npc.click("Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else if (Microbot.getVarbitPlayerValue(281) == 650) { widgetCast(); - } else if (Microbot.getVarbitPlayerValue(281) == 670) { - Rs2Dialogue.clickContinue(); + } else if (Microbot.getVarbitPlayerValue(281) == 680) { + if (Rs2Tab.getCurrentTab() != InterfaceTab.MAGIC) { + Rs2Tab.switchTo(InterfaceTab.MAGIC); + Rs2Random.waitEx(600, 100); + } + Widget homeTeleport = Rs2Widget.findWidget("Lumbridge Home Teleport", true); + if (homeTeleport != null) { + Rs2Widget.clickWidget(homeTeleport); + sleepUntil(() -> { + WorldPoint p = Rs2Player.getWorldLocation(); + return p != null && p.getX() >= 3200; + }, 15_000); + } + } else if (Microbot.getVarbitPlayerValue(281) >= 660) { if (isInDialogue()) { - if (Rs2Widget.hasWidget("Do you want to go to the mainland?")) { - Rs2Keyboard.typeString("1"); - return; - } if (hasSelectAnOption()) { - Widget widgetOptions = Rs2Widget.getWidget(219, 1); - Widget[] dynamicWidgetOptions = widgetOptions.getDynamicChildren(); - - for (int i = 0; i < dynamicWidgetOptions.length; i++) { - String optionText = dynamicWidgetOptions[i].getText(); - - if (optionText.contains("Yes, send me to the mainland") || optionText.contains("No, I'm not planning to do that")) { - Rs2Keyboard.typeString(String.valueOf(i)); - break; - } - } - } - } else { - if (npc.click("Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); + if (Rs2Dialogue.keyPressForDialogueOption("Yes, I'd like to go to the mainland")) return; + if (Rs2Dialogue.keyPressForDialogueOption("Yes, send me to the mainland")) return; + if (Rs2Dialogue.keyPressForDialogueOption("Yes")) return; + Rs2Dialogue.keyPressForDialogueOption(1); + return; } + Rs2Dialogue.clickContinue(); + return; } + + walkAndTalk(npc); } } @@ -460,25 +508,19 @@ public void PrayerGuide() { if (Microbot.getVarbitPlayerValue(281) == 640 || Microbot.getVarbitPlayerValue(281) == 550 || Microbot.getVarbitPlayerValue(281) == 540) { Rs2Walker.walkTo(new WorldPoint(3124, 3106, 0)); - if (npc.click("Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else if (Microbot.getVarbitPlayerValue(281) == 560) { var widget = Rs2Widget.findWidget("Prayer", true); Rs2Widget.clickWidget(widget); // switchToPrayerTab Rs2Random.waitEx(1200, 300); } else if (Microbot.getVarbitPlayerValue(281) == 570) { - if (npc.click("Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else if (Microbot.getVarbitPlayerValue(281) == 580) { var widget = Rs2Widget.findWidget("Friends list", true); Rs2Widget.clickWidget(widget); // switchToFriendsTab Rs2Random.waitEx(1200, 300); } else if (Microbot.getVarbitPlayerValue(281) == 600) { - if (npc.click("Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } } @@ -491,6 +533,12 @@ public void BankerGuide() { } else if (Microbot.getVarbitPlayerValue(281) == 520) { + if (Rs2Widget.isWidgetVisible(928, 4)) { + Rs2Widget.clickWidget(928, 4); // Close poll booth interface + Rs2Random.waitEx(1200, 300); + return; + } + if (Rs2Widget.isWidgetVisible(289, 5)) { Widget widgetOptions = Rs2Widget.getWidget(289, 4); Widget[] dynamicWidgetOptions = widgetOptions.getDynamicChildren(); @@ -511,8 +559,14 @@ public void BankerGuide() { Rs2Bank.closeBank(); sleepUntil(() -> !Rs2Bank.isOpen()); Microbot.getRs2TileObjectCache().query().interact(26815); //interactWithPollBooth - sleepUntil(() -> Microbot.getVarbitPlayerValue(281) != 520); + sleepUntil(() -> Microbot.getVarbitPlayerValue(281) != 520 || Rs2Widget.isWidgetVisible(928, 4)); } else if (Microbot.getVarbitPlayerValue(281) == 525 || Microbot.getVarbitPlayerValue(281) == 530) { + if (Rs2Widget.isWidgetVisible(928, 4)) { + Rs2Widget.clickWidget(928, 4); // Close poll booth interface + Rs2Random.waitEx(1200, 300); + return; + } + if (Rs2Widget.isWidgetVisible(310, 2)) { Widget widgetOptions = Rs2Widget.getWidget(310, 2); Widget[] dynamicWidgetOptions = widgetOptions.getDynamicChildren(); @@ -532,9 +586,7 @@ public void BankerGuide() { Rs2Walker.walkTo(npc.getWorldLocation(), 3); Rs2Player.waitForWalking(); - if (npc.click("Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else if (Microbot.getVarbitPlayerValue(281) == 531) { var widget = Rs2Widget.findWidget("Account Management", true); Rs2Widget.clickWidget(widget); // switchToAccountManagementTab @@ -544,9 +596,7 @@ public void BankerGuide() { clickContinue(); return; } - if (npc.click("Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } } @@ -556,9 +606,7 @@ public void CombatGuide() { if (Microbot.getVarbitPlayerValue(281) <= 370) { Rs2Walker.walkTo(new WorldPoint(Rs2Random.between(3106, 3108), Rs2Random.between(9508, 9510), 0)); Rs2Player.waitForWalking(); - if (npc.click("Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else if (Microbot.getVarbitPlayerValue(281) <= 410) { if (isInDialogue()) { clickContinue(); @@ -590,9 +638,7 @@ public void CombatGuide() { } } - if (npc.click("Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else if (Microbot.getVarbitPlayerValue(281) == 500) { Rs2Walker.walkTo(new WorldPoint(3111, 9526, Rs2Player.getWorldLocation().getPlane())); Rs2Player.waitForWalking(); @@ -601,37 +647,38 @@ public void CombatGuide() { } else if (Microbot.getVarbitPlayerValue(281) == 480 || Microbot.getVarbitPlayerValue(281) == 490) { Actor rat = Rs2Player.getInteracting(); if (rat != null && rat.getName().equalsIgnoreCase("giant rat")) return; - Rs2Inventory.wield("Shortbow"); - Rs2Random.waitEx(600, 100); - Rs2Inventory.wield("Bronze arrow"); - Rs2Random.waitEx(600, 100); - if (Rs2Random.between(1, 5) == 2) { - Rs2Walker.walkTo(new WorldPoint(3110, 9523, 0), 4); + if (Rs2Inventory.hasItem("Shortbow")) { + Rs2Inventory.wield("Shortbow"); + Rs2Random.waitEx(600, 100); } - Rs2Player.waitForWalking(); - Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("Giant rat").interact("Attack")); - } else if (Microbot.getVarbitPlayerValue(281) == 470) { - Rs2Walker.walkTo(npc.getWorldLocation()); - Rs2Player.waitForWalking(); - if (npc.click("Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); + if (Rs2Inventory.hasItem("Bronze arrow")) { + Rs2Inventory.wield("Bronze arrow"); + Rs2Random.waitEx(600, 100); } + walkAndAttackRat(); + } else if (Microbot.getVarbitPlayerValue(281) == 470) { + if (npc == null) return; + walkAndTalk(npc); + } else if (Microbot.getVarbitPlayerValue(281) == 430) { + var widget = Rs2Widget.findWidget("Combat Options", true); + Rs2Widget.clickWidget(widget); + Rs2Random.waitEx(1200, 300); } else if (Microbot.getVarbitPlayerValue(281) >= 420) { + if (isInDialogue()) { + clickContinue(); + return; + } if (Microbot.getClient().getLocalPlayer().isInteracting() || Rs2Player.isAnimating()) return; if (Rs2Equipment.isWearing("Bronze sword")) { - var widget = Rs2Widget.findWidget("Combat Options", true); - Rs2Widget.clickWidget(widget); // switchToQuestTab - Rs2Random.waitEx(1200, 300); - WorldPoint worldPoint = new WorldPoint(3105, 9517, 0); - Rs2Walker.walkTo(worldPoint, 3); - Rs2Player.waitForWalking(); - Microbot.getClientThread().invoke(() -> Microbot.getRs2NpcCache().query().withName("Giant rat").interact("Attack")); - } else { + walkAndAttackRat(); + } else if (Rs2Inventory.hasItem("Bronze sword")) { Rs2Tab.switchTo(InterfaceTab.INVENTORY); Rs2Random.waitEx(600, 100); Rs2Inventory.wield("Bronze sword"); Rs2Random.waitEx(600, 100); Rs2Inventory.wield("Wooden shield"); + } else { + walkAndTalk(npc); } } } @@ -641,9 +688,7 @@ public void MiningGuide() { if (Microbot.getVarbitPlayerValue(281) == 260) { Rs2Walker.walkTo(new WorldPoint(Rs2Random.between(3082, 3085), Rs2Random.between(9502, 9505), 0)); - if (npc.click("Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else { if (Rs2Inventory.contains("Bronze dagger")) { Microbot.getRs2TileObjectCache().query().interact(ObjectID.GATE_9718, "Open"); @@ -659,9 +704,7 @@ public void MiningGuide() { return; } if (Rs2Inventory.contains("Bronze bar") && !Rs2Inventory.contains("Hammer")) { - if (npc.click("Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); return; } if (Rs2Inventory.contains("Bronze pickaxe") && (!Rs2Inventory.contains("Copper ore") || !Rs2Inventory.contains("Tin ore"))) { @@ -701,8 +744,7 @@ public void QuestGuide() { Microbot.getRs2TileObjectCache().query().interact(9716, "Open"); Rs2Random.waitEx(1200, 300); } else if (Microbot.getVarbitPlayerValue(281) == 220 || Microbot.getVarbitPlayerValue(281) == 240) { - npc.click("Talk-to"); - sleepUntil(Rs2Dialogue::isInDialogue); + walkAndTalk(npc); } else if (Microbot.getVarbitPlayerValue(281) == 230) { var widget = Rs2Widget.findWidget("Quest List", true); Rs2Widget.clickWidget(widget); // switchToQuestTab @@ -727,9 +769,7 @@ public void CookingGuide() { Microbot.getRs2TileObjectCache().query().interact(ObjectID.DOOR_9709, "Open"); sleepUntil(() -> Microbot.getVarbitPlayerValue(281) != 130); } else if (Microbot.getVarbitPlayerValue(281) == 140) { - if (npc.click("Talk-to")) { - sleepUntil(Rs2Dialogue::isInDialogue); - } + walkAndTalk(npc); } else if (Microbot.getVarbitPlayerValue(281) >= 150 && Microbot.getVarbitPlayerValue(281) < 200) { if (!Rs2Inventory.contains("Bread dough") && !Rs2Inventory.contains("Bread")) { Rs2Inventory.combine("Bucket of water", "Pot of flour"); @@ -763,14 +803,14 @@ public void CutTree() { public void fishShrimp() { Microbot.getRs2NpcCache().query().withId(NpcID.FISHING_SPOT_3317).interact("Net"); - sleepUntil(() -> Rs2Inventory.contains("Raw shrimps")); + sleepUntil(() -> Rs2Inventory.contains(false, "shrimps")); } private boolean widgetCast() { if (Rs2Player.isAnimating() || Rs2Player.getInteracting() != null) return true; - Widget windStrike = Rs2Widget.getWidget(218, 8); - if (windStrike == null) windStrike = Rs2Widget.findWidget("Wind Strike", null, true); + Widget windStrike = Rs2Widget.findWidget("Wind Strike", null, true); + if (windStrike == null) windStrike = Rs2Widget.getWidget(218, 11); if (windStrike == null) return false; boolean hidden; From 29eec039667c381ccba907c837a08b7f685ad307 Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Sun, 19 Apr 2026 23:42:16 -0400 Subject: [PATCH 55/95] fix(hunter): add black chinchompa shaking box ID to reset logic (#409) The AutoChinScript was missing ObjectID.SHAKING_BOX (721) in the handleIdleState() reset checks. This ID is used for black chinchompa caught boxes in the wilderness, causing the plugin to not reset successfully trapped boxes there. Co-authored-by: runsonmypc --- .../plugins/microbot/microhunter/scripts/AutoChinScript.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/net/runelite/client/plugins/microbot/microhunter/scripts/AutoChinScript.java b/src/main/java/net/runelite/client/plugins/microbot/microhunter/scripts/AutoChinScript.java index 7e2495a0b4..46b8df1b23 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/microhunter/scripts/AutoChinScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/microhunter/scripts/AutoChinScript.java @@ -116,6 +116,11 @@ private void handleIdleState() { currentState = State.CATCHING; return; } + // Black chinchompa shaking box + if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.SHAKING_BOX).within(4).interact("reset")) { + currentState = State.CATCHING; + return; + } if (Microbot.getRs2TileObjectCache().query().withId(ObjectID.BOX_TRAP_9385).within(4).interact("reset")) { currentState = State.CATCHING; From 4a9c2c9dbd1a4e2ef4655a04ef83b6ccef86544c Mon Sep 17 00:00:00 2001 From: JThomasDevs <95548936+JThomasDevs@users.noreply.github.com> Date: Sun, 19 Apr 2026 21:42:24 -0600 Subject: [PATCH 56/95] fix prayer bug and add trio mode to charge pillars (#408) --- .../HueycoatlPrayer/HueyPrayerConfig.java | 102 ++++++++++- .../HueycoatlPrayer/HueyPrayerPlugin.java | 159 +++++++++++++++++- 2 files changed, 251 insertions(+), 10 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerConfig.java b/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerConfig.java index ea38412d39..134231e23d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerConfig.java @@ -1,14 +1,31 @@ package net.runelite.client.plugins.microbot.HueycoatlPrayer; import net.runelite.client.config.*; +import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @ConfigGroup("hueyprayer") public interface HueyPrayerConfig extends Config { + @ConfigSection( + name = "General", + description = "Core Huey prayer behaviour", + position = 0 + ) + String generalSection = "general"; + + @ConfigSection( + name = "Trio mode", + description = "After a Huey projectile hits, set protection for pillar charging (fixed or autobalance)", + position = 1 + ) + String trioSection = "trio"; + @ConfigItem( keyName = "enabled", name = "Enable", - description = "Enable auto prayer" + description = "Enable auto prayer", + position = 0, + section = generalSection ) default boolean enabled() { @@ -18,7 +35,9 @@ default boolean enabled() @ConfigItem( keyName = "disableAfterImpact", name = "Disable after impact", - description = "When enabled, turns off protection prayer after the projectile hits (saves prayer). When disabled, prayers stay on until the next attack switches them." + description = "When enabled, turns off protection prayer after the projectile hits (saves prayer). When disabled, prayers stay on until the next attack switches them.", + position = 1, + section = generalSection ) default boolean disableAfterImpact() { @@ -28,10 +47,85 @@ default boolean disableAfterImpact() @ConfigItem( keyName = "debug", name = "Debug Projectiles", - description = "Print projectile IDs" + description = "Print projectile IDs", + position = 2, + section = generalSection ) default boolean debug() { return false; } -} \ No newline at end of file + + @ConfigItem( + keyName = "trioMode", + name = "Trio mode", + description = "While incoming Huey projectiles still use the correct protect vs type, after impact (requires Disable after impact) switches to your pillar role: Fixed or Autobalance protection from teammates' overheads.", + position = 0, + section = trioSection + ) + default boolean trioMode() + { + return false; + } + + @ConfigItem( + keyName = "trioRoleStyle", + name = "Role style", + description = "Fixed: always use the protection prayer below. Autobalance: among other players in radius, count melee / missiles / magic overheads — you pray the least-covered protection.", + position = 1, + section = trioSection + ) + default TrioRoleStyle trioRoleStyle() + { + return TrioRoleStyle.FIXED; + } + + @ConfigItem( + keyName = "trioFixedProtection", + name = "Fixed protection", + description = "Used when Role style is Fixed.", + position = 2, + section = trioSection + ) + default TrioFixedProtection trioFixedProtection() + { + return TrioFixedProtection.PROTECT_RANGE; + } + + @ConfigItem( + keyName = "trioRadius", + name = "Autobalance radius", + description = "Tiles from your tile to include other players when autobalancing (they must still be loaded).", + position = 3, + section = trioSection + ) + default int trioRadius() + { + return 15; + } + + enum TrioRoleStyle + { + FIXED, + AUTOBALANCE + } + + enum TrioFixedProtection + { + PROTECT_MELEE(Rs2PrayerEnum.PROTECT_MELEE), + PROTECT_RANGE(Rs2PrayerEnum.PROTECT_RANGE), + PROTECT_MAGIC(Rs2PrayerEnum.PROTECT_MAGIC); + + private final Rs2PrayerEnum prayer; + + TrioFixedProtection(Rs2PrayerEnum prayer) + { + this.prayer = prayer; + } + + public Rs2PrayerEnum getPrayer() + { + return prayer; + } + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerPlugin.java index 78a4ccfa89..b7ec7c9632 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerPlugin.java @@ -6,7 +6,11 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; +import net.runelite.api.HeadIcon; +import net.runelite.api.Player; import net.runelite.api.Projectile; +import net.runelite.api.WorldView; +import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.ProjectileMoved; @@ -35,7 +39,7 @@ ) public class HueyPrayerPlugin extends Plugin { - static final String VERSION = "1.0.4"; + static final String VERSION = "1.0.7"; @Inject private Client client; @@ -129,8 +133,7 @@ public void onProjectileMoved(ProjectileMoved event) incomingHueyProjectiles.remove(projectile); if (incomingHueyProjectiles.isEmpty()) { - Rs2Prayer.disableAllPrayers(); - currentPrayer = null; + onHueyProjectilePhaseEnded(); } } return; @@ -173,9 +176,16 @@ private void switchPrayer(Rs2PrayerEnum prayer) { int tick = client.getTickCount(); - // prevent spam + duplicate toggles - if (currentPrayer == prayer) return; - if (tick == lastSwitchTick) return; + if (Rs2Prayer.isPrayerActive(prayer)) + { + currentPrayer = prayer; + return; + } + + if (tick == lastSwitchTick) + { + return; + } Rs2Prayer.disableAllPrayers(); Rs2Prayer.toggle(prayer, true); @@ -183,4 +193,141 @@ private void switchPrayer(Rs2PrayerEnum prayer) currentPrayer = prayer; lastSwitchTick = tick; } + + /** + * Last Huey projectile toward us has landed — either clear prayers or switch to trio pillar protection. + */ + private void onHueyProjectilePhaseEnded() + { + if (config.trioMode()) + { + Rs2PrayerEnum pillar = resolveTrioProtectionPrayer(); + if (pillar != null) + { + switchPrayer(pillar); + } + return; + } + Rs2Prayer.disableAllPrayers(); + currentPrayer = null; + } + + /** + * Fixed or autobalance protection for pillar charging (after projectiles, not during). + */ + private Rs2PrayerEnum resolveTrioProtectionPrayer() + { + if (config.trioRoleStyle() == HueyPrayerConfig.TrioRoleStyle.FIXED) + { + return config.trioFixedProtection().getPrayer(); + } + return pickAutobalanceProtectionPrayer(); + } + + private Rs2PrayerEnum pickAutobalanceProtectionPrayer() + { + Player local = client.getLocalPlayer(); + if (local == null) + { + return null; + } + WorldPoint localPoint = local.getWorldLocation(); + WorldView worldView = client.getTopLevelWorldView(); + if (worldView == null) + { + return null; + } + + int melee = 0; + int ranged = 0; + int magic = 0; + int radius = config.trioRadius(); + if (radius < 1) + { + radius = 1; + } + + for (Player p : worldView.players()) + { + if (p == null) + { + continue; + } + if (p == local) + { + continue; + } + WorldPoint wp = p.getWorldLocation(); + if (wp == null) + { + continue; + } + if (wp.distanceTo(localPoint) > radius) + { + continue; + } + HeadIcon overhead = p.getOverheadIcon(); + if (overhead == HeadIcon.MELEE) + { + melee++; + } + else if (overhead == HeadIcon.RANGED) + { + ranged++; + } + else if (overhead == HeadIcon.MAGIC) + { + magic++; + } + } + + if (melee == 0) + { + if (ranged == 0) + { + if (magic == 0) + { + return protectionMatchingLocalOverhead(local); + } + } + } + + int minCount = melee; + if (ranged < minCount) + { + minCount = ranged; + } + if (magic < minCount) + { + minCount = magic; + } + + if (melee == minCount) + { + return Rs2PrayerEnum.PROTECT_MELEE; + } + if (ranged == minCount) + { + return Rs2PrayerEnum.PROTECT_RANGE; + } + return Rs2PrayerEnum.PROTECT_MAGIC; + } + + private static Rs2PrayerEnum protectionMatchingLocalOverhead(Player local) + { + HeadIcon overhead = local.getOverheadIcon(); + if (overhead == HeadIcon.MELEE) + { + return Rs2PrayerEnum.PROTECT_MELEE; + } + if (overhead == HeadIcon.RANGED) + { + return Rs2PrayerEnum.PROTECT_RANGE; + } + if (overhead == HeadIcon.MAGIC) + { + return Rs2PrayerEnum.PROTECT_MAGIC; + } + return Rs2PrayerEnum.PROTECT_MELEE; + } } \ No newline at end of file From 2067e1a28f4c0e59b00552c44ddcda7926e57a86 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 19 Apr 2026 20:42:40 -0700 Subject: [PATCH 57/95] feat(LeaguesToolkit): Toci Gem Store, Thieving, Easy Clues, Telegrab, Transmutation (#401) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(LeaguesToolkit): add Toci's Gem Store + Transmutation features Toci's Gem Store — three modes: - Buy & Bank: fast stockpile uncut gems via briefcase - Buy, Cut & Sell: buy, cut with chisel, sell cut back to Toci - Buy, Cut & Bank: buy, cut, bank via briefcase Walks to Toci automatically, mass-clicks buy/sell at 100-250ms, handles chisel + SPACE dialog for cutting, briefcase Last-destination for banking, Rs2Walker for return trips. Transmutation — casts Alchemic Divergence/Convergence on noted items: - Dropdown item selection for start/target (all categories) - Auto-detects category, walks chain tier by tier - Non-blocking tick-based monitoring with shop-aware timeout - Re-casts if auto-recast interrupted (45s normal, 3min if shop open) Categories: Ores, Fish, Gems, Runes, Logs, Bones, Hides, Ashes, Compost. Also updates info panel with full feature descriptions. Bumps version to 1.2.0. Work in progress. * feat(LeaguesToolkit): add Wealthy Citizen Thieving, Snape Grass Telegrab, Easy Clue Opener Wealthy Citizen Thieving: - Pickpockets Wealthy citizen (Larcenist relic, 100% success) - Opens coin pouches at configurable threshold (default 200, max 280) - Uses item ID 28822 for accurate stack count Snape Grass Telegrab: - Walks to spawn (1736, 3170, 0), casts Telekinetic Grab - Banks via briefcase Last-destination or walks to nearest bank - Requires 33 Magic, law runes, air runes/staff Easy Clue Opener: - Aldarin bank easy clue method for farming reward caskets - Opens Scroll box (easy), digs if clue ID 29853, drops non-dig clues - Caskets stack in inventory - Configurable dig delay and action delay Also updates info panel with all feature descriptions. --------- Co-authored-by: dev --- .../leaguestoolkit/EasyClueOpener.java | 83 +++++++ .../microbot/leaguestoolkit/GemCutter.java | 200 ++++++++++++--- .../leaguestoolkit/GemCutterMode.java | 19 ++ .../leaguestoolkit/GemCutterState.java | 4 +- .../leaguestoolkit/LeaguesToolkitConfig.java | 216 +++++++++++++++-- .../leaguestoolkit/LeaguesToolkitPlugin.java | 2 +- .../leaguestoolkit/LeaguesToolkitScript.java | 57 +++++ .../leaguestoolkit/SnapeGrassTelegrabber.java | 145 +++++++++++ .../leaguestoolkit/TransmuteCategory.java | 69 ++++++ .../leaguestoolkit/TransmuteDirection.java | 6 + .../leaguestoolkit/TransmuteItem.java | 114 +++++++++ .../microbot/leaguestoolkit/Transmuter.java | 229 ++++++++++++++++++ .../leaguestoolkit/WealthyCitizenThiever.java | 77 ++++++ 13 files changed, 1169 insertions(+), 52 deletions(-) create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/EasyClueOpener.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutterMode.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/SnapeGrassTelegrabber.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/TransmuteCategory.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/TransmuteDirection.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/TransmuteItem.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/Transmuter.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/WealthyCitizenThiever.java diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/EasyClueOpener.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/EasyClueOpener.java new file mode 100644 index 0000000000..3a181a4e70 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/EasyClueOpener.java @@ -0,0 +1,83 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +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 static net.runelite.client.plugins.microbot.util.Global.sleep; +import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; + +@Slf4j +public class EasyClueOpener { + + private static final int SCROLL_BOX_EASY = 24362; + private static final int CLUE_SCROLL_DIG = 29853; + private static final int SPADE = 952; + + @Getter + private String status = "Idle"; + + public void reset() { + status = "Idle"; + } + + public boolean tick(LeaguesToolkitConfig config) { + if (!Rs2Inventory.hasItem(SPADE)) { + status = "No spade in inventory"; + return false; + } + + if (!Rs2Inventory.hasItem(SCROLL_BOX_EASY) && !hasClueScroll()) { + status = "No scroll boxes or clues left"; + return false; + } + + int digMin = Math.min(config.clueDigDelayMin(), config.clueDigDelayMax()); + int digMax = Math.max(config.clueDigDelayMin(), config.clueDigDelayMax()); + int actionDelay = config.clueActionDelay(); + + if (hasClueScroll()) { + if (Rs2Inventory.hasItem(CLUE_SCROLL_DIG)) { + status = "Digging"; + Rs2Inventory.interact(SPADE, "Dig"); + sleep(Rs2Random.between(digMin, digMax)); + sleepUntil(() -> !Rs2Player.isAnimating(), 3000); + sleep(Rs2Random.between(actionDelay / 2, actionDelay)); + return true; + } else { + status = "Dropping non-dig clue"; + dropNonDigClue(); + sleep(Rs2Random.between(actionDelay / 2, actionDelay)); + return true; + } + } + + if (Rs2Inventory.hasItem(SCROLL_BOX_EASY)) { + status = "Opening scroll box"; + Rs2Inventory.interact(SCROLL_BOX_EASY, "Open"); + sleep(Rs2Random.between(actionDelay, actionDelay + 200)); + sleepUntil(this::hasClueScroll, 3000); + return true; + } + + status = "Waiting..."; + return true; + } + + private boolean hasClueScroll() { + return Rs2Inventory.hasItem("Clue scroll (easy)"); + } + + private void dropNonDigClue() { + Rs2Inventory.items(item -> + item.getName() != null + && item.getName().toLowerCase().contains("clue scroll") + && item.getId() != CLUE_SCROLL_DIG + ).findFirst().ifPresent(item -> { + log.info("[EasyClue] Dropping non-dig clue ID={}", item.getId()); + Rs2Inventory.interact(item, "Drop"); + }); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutter.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutter.java index 7e4aba37a7..19d0002d56 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutter.java +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutter.java @@ -5,6 +5,7 @@ import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; 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.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; @@ -13,6 +14,7 @@ import net.runelite.client.plugins.microbot.util.player.Rs2Player; 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; @@ -22,7 +24,6 @@ @Slf4j public class GemCutter { - // Toci's actual tile in Aldarin, Varlamore private static final WorldPoint TOCI_LOCATION = new WorldPoint(1428, 2975, 0); private static final String TOCI_NPC_NAME = "Toci"; private static final String CHISEL_NAME = "Chisel"; @@ -43,20 +44,13 @@ public boolean tick(LeaguesToolkitConfig config) { GemType gem = config.gemType(); if (gem == null) { status = "No gem selected"; + log.info("[GemCutter] tick: no gem selected"); return false; } - if (!gem.hasRequiredLevel()) { - status = "Crafting level too low for " + gem.getCutName() + " (need " + gem.getCraftingLevel() + ")"; - return false; - } + log.info("[GemCutter] tick: state={}, cutGems={}, mode={}", state, shouldCut(config), config.gemCutterMode()); - if (!Rs2Inventory.hasItem(CHISEL_NAME)) { - status = "No chisel in inventory"; - return false; - } - - // Need to bank for coins if we're idle (no gems in hand) and low on coins + // Need to bank for coins if idle with low funds if (state == GemCutterState.WALKING_TO_SHOP && Rs2Inventory.itemQuantity(COINS_ID) < config.gemCutterMinCoins() && !Rs2Inventory.hasItem(gem.getUncutName()) @@ -70,11 +64,15 @@ public boolean tick(LeaguesToolkitConfig config) { case WALKING_TO_SHOP: return handleWalkingToShop(); case BUYING: - return handleBuying(gem); + return handleBuying(gem, config); case CUTTING: - return handleCutting(gem); + return handleCutting(gem, config); case SELLING: return handleSelling(gem, config); + case BRIEFCASE_BANKING: + return handleBriefcaseBanking(gem, config); + case TELEPORTING_BACK: + return handleTeleportingBack(); } return true; } @@ -85,7 +83,14 @@ private boolean handleBanking(LeaguesToolkitConfig config) { return true; } - if (config.gemCutterUseBriefcase() && Rs2Inventory.hasItem(BRIEFCASE_NAME)) { + if (Rs2Equipment.isWearing(BRIEFCASE_NAME)) { + if (!Rs2Bank.isOpen()) { + status = "Using equipped briefcase to bank"; + Rs2Equipment.interact(BRIEFCASE_NAME, "Last-destination"); + sleepUntil(Rs2Bank::isOpen, 5000); + return true; + } + } else if (Rs2Inventory.hasItem(BRIEFCASE_NAME)) { if (!Rs2Bank.isOpen()) { status = "Using briefcase to bank"; Rs2Inventory.interact(BRIEFCASE_NAME, "Bank"); @@ -115,19 +120,66 @@ private boolean handleBanking(LeaguesToolkitConfig config) { } private boolean handleWalkingToShop() { - if (Rs2Npc.getNpc(TOCI_NPC_NAME) != null) { - status = "At Toci"; + log.info("[GemCutter] handleWalkingToShop entered"); + + if (Rs2Shop.isOpen()) { + log.info("[GemCutter] Shop already open — transitioning to BUYING"); + status = "Shop already open"; state = GemCutterState.BUYING; return true; } - status = "Walking to Toci"; - if (!Rs2Player.isMoving()) { - Rs2Walker.walkTo(TOCI_LOCATION, 6); + + WorldPoint playerPos = Rs2Player.getWorldLocation(); + if (playerPos == null) { + log.info("[GemCutter] Player position is null"); + status = "Waiting for player position..."; + return true; + } + + int distance = playerPos.distanceTo(TOCI_LOCATION); + log.info("[GemCutter] Player at {}, Toci at {}, distance={}", playerPos, TOCI_LOCATION, distance); + + // Far away — walk first, don't try to open shop + if (distance > 15) { + status = "Walking to Toci (" + distance + " tiles away)"; + log.info("[GemCutter] Walking to Toci..."); + Rs2Walker.walkTo(TOCI_LOCATION, 4); + sleep(3000, 5000); + return true; + } + + // Close enough — try to open shop + status = "Near Toci — opening shop"; + log.info("[GemCutter] Close enough, opening shop"); + boolean opened = Rs2Shop.openShop(TOCI_NPC_NAME); + log.info("[GemCutter] openShop returned {}", opened); + if (opened) { + sleepUntil(Rs2Shop::isOpen, 5000); + if (Rs2Shop.isOpen()) { + state = GemCutterState.BUYING; + return true; + } } return true; } - private boolean handleBuying(GemType gem) { + private boolean shouldCut(LeaguesToolkitConfig config) { + GemCutterMode mode = config.gemCutterMode(); + return mode == GemCutterMode.BUY_CUT_SELL || mode == GemCutterMode.BUY_CUT_BANK; + } + + private boolean shouldUseBriefcase(LeaguesToolkitConfig config) { + GemCutterMode mode = config.gemCutterMode(); + return mode == GemCutterMode.BUY_AND_BANK || mode == GemCutterMode.BUY_CUT_BANK; + } + + private GemCutterState nextStateAfterCutting(LeaguesToolkitConfig config) { + return shouldUseBriefcase(config) + ? GemCutterState.BRIEFCASE_BANKING + : GemCutterState.SELLING; + } + + private boolean handleBuying(GemType gem, LeaguesToolkitConfig config) { if (!Rs2Shop.isOpen()) { status = "Opening Toci's shop"; if (!Rs2Shop.openShop(TOCI_NPC_NAME)) { @@ -141,17 +193,17 @@ private boolean handleBuying(GemType gem) { int uncutCount = Rs2Inventory.count(gem.getUncutName()); if (Rs2Inventory.isFull()) { - status = "Inventory full — moving to cut"; + status = "Inventory full"; Rs2Shop.closeShop(); - state = GemCutterState.CUTTING; + state = shouldCut(config) ? GemCutterState.CUTTING : nextStateAfterCutting(config); return true; } if (!Rs2Shop.hasStock(gem.getUncutName())) { if (uncutCount > 0) { - status = "Shop out of stock — cutting what we have"; + status = "Shop out of stock"; Rs2Shop.closeShop(); - state = GemCutterState.CUTTING; + state = shouldCut(config) ? GemCutterState.CUTTING : nextStateAfterCutting(config); return true; } status = "Shop out of " + gem.getUncutName() + " — waiting"; @@ -159,8 +211,7 @@ private boolean handleBuying(GemType gem) { return true; } - // Mass-click buy at 100-250ms intervals. Stop when 2 consecutive clicks - // fail to add a ruby to inventory (inventory full OR shop out of stock). + // Mass-click buy at 100-250ms intervals status = "Rapid-buying " + gem.getUncutName(); int safetyMax = 32; int missedInRow = 0; @@ -179,24 +230,33 @@ private boolean handleBuying(GemType gem) { return true; } - private boolean handleCutting(GemType gem) { + private boolean handleCutting(GemType gem, LeaguesToolkitConfig config) { + // Skip cutting if disabled OR no chisel — go straight to sell/bank + if (!shouldCut(config) || !Rs2Inventory.hasItem(CHISEL_NAME)) { + log.info("[GemCutter] Skipping CUTTING (cutGems={}, hasChisel={}), going to next state", + shouldCut(config), Rs2Inventory.hasItem(CHISEL_NAME)); + state = nextStateAfterCutting(config); + return true; + } + if (!Rs2Inventory.hasItem(gem.getUncutName())) { - status = "All gems cut — moving to sell"; - state = GemCutterState.SELLING; + status = "All gems cut"; + if (shouldUseBriefcase(config)) { + state = GemCutterState.BRIEFCASE_BANKING; + } else { + state = GemCutterState.SELLING; + } return true; } - // Start the cut: chisel on uncut gem status = "Starting to cut " + gem.getCutName(); Rs2Inventory.use(CHISEL_NAME); sleep(300, 500); Rs2Inventory.use(gem.getUncutName()); - // Wait for "How many do you wish to make?" dialog, then press space for All sleep(600, 900); Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); - // Wait for cutting to finish (XP stops flowing OR all uncut gems gone) sleep(2000, 3000); status = "Cutting " + gem.getCutName() + "..."; sleepUntil(() -> !Microbot.isGainingExp || !Rs2Inventory.hasItem(gem.getUncutName()), 60000); @@ -204,15 +264,16 @@ private boolean handleCutting(GemType gem) { return true; } + // === SELL MODE === + private boolean handleSelling(GemType gem, LeaguesToolkitConfig config) { if (!Rs2Inventory.hasItem(gem.getCutName())) { status = "All cut gems sold — looping"; if (Rs2Inventory.itemQuantity(COINS_ID) < config.gemCutterMinCoins()) { - // Only close when we need to walk somewhere (banking) if (Rs2Shop.isOpen()) Rs2Shop.closeShop(); state = GemCutterState.BANKING; } else { - // Leave shop open — handleBuying will use the already-open shop next tick + // Keep shop open for next buy cycle state = GemCutterState.BUYING; } return true; @@ -228,8 +289,7 @@ private boolean handleSelling(GemType gem, LeaguesToolkitConfig config) { return true; } - // Mass-click sell at 100-250ms intervals — click the LAST slot containing - // a cut gem, repeat until inventory runs out. Two consecutive misses = stop. + // Mass-click sell from bottom of inventory int initialCount = Rs2Inventory.count(gem.getCutName()); status = "Rapid-selling " + gem.getCutName() + " x" + initialCount; @@ -259,4 +319,72 @@ private boolean handleSelling(GemType gem, LeaguesToolkitConfig config) { return true; } + + // === BRIEFCASE BANKING MODE === + + private boolean handleBriefcaseBanking(GemType gem, LeaguesToolkitConfig config) { + log.info("[GemCutter] handleBriefcaseBanking: bankOpen={}, wearing={}, hasInInv={}", + Rs2Bank.isOpen(), + Rs2Equipment.isWearing(BRIEFCASE_NAME), + Rs2Inventory.hasItem(BRIEFCASE_NAME)); + + // Step 1: Teleport to bank via briefcase, then open bank + if (!Rs2Bank.isOpen()) { + // First teleport to the bank + status = "Teleporting to bank via briefcase"; + if (Rs2Equipment.isWearing(BRIEFCASE_NAME)) { + log.info("[GemCutter] Clicking equipped briefcase 'Last-destination'"); + Rs2Equipment.interact(BRIEFCASE_NAME, "Last-destination"); + } else { + status = "No briefcase equipped — walking to bank"; + if (!Rs2Bank.walkToBankAndUseBank()) return true; + sleepUntil(Rs2Bank::isOpen, 10000); + return true; + } + + // Wait for teleport to finish + sleep(2000, 3000); + sleepUntil(() -> !Rs2Player.isAnimating() && !Rs2Player.isMoving(), 8000); + sleep(500, 1000); + + // Now open the bank normally + status = "Opening bank"; + log.info("[GemCutter] Teleported, now opening bank"); + Rs2Bank.openBank(); + sleepUntil(Rs2Bank::isOpen, 5000); + log.info("[GemCutter] Bank open: {}", Rs2Bank.isOpen()); + return true; + } + + // Step 2: Deposit gems (cut or uncut depending on config) + status = "Depositing gems"; + if (shouldCut(config) && Rs2Inventory.hasItem(gem.getCutName())) { + Rs2Bank.depositAll(gem.getCutName()); + sleep(300, 500); + } + if (!shouldCut(config) && Rs2Inventory.hasItem(gem.getUncutName())) { + Rs2Bank.depositAll(gem.getUncutName()); + sleep(300, 500); + } + + // Step 3: Withdraw coins if low + if (Rs2Inventory.itemQuantity(COINS_ID) < config.gemCutterMinCoins() && Rs2Bank.hasItem(COINS_ID)) { + Rs2Bank.withdrawAll(COINS_ID); + sleep(300, 500); + } + + Rs2Bank.closeBank(); + sleepUntil(() -> !Rs2Bank.isOpen(), 3000); + + state = GemCutterState.TELEPORTING_BACK; + return true; + } + + private boolean handleTeleportingBack() { + // Walk back to Toci using the web walker + status = "Walking back to Toci"; + log.info("[GemCutter] TELEPORTING_BACK → transitioning to WALKING_TO_SHOP"); + state = GemCutterState.WALKING_TO_SHOP; + return true; + } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutterMode.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutterMode.java new file mode 100644 index 0000000000..7334b38e7d --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutterMode.java @@ -0,0 +1,19 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum GemCutterMode { + BUY_AND_BANK("Buy & Bank (fast stockpile uncut gems via briefcase)"), + BUY_CUT_SELL("Buy, Cut & Sell (buy uncut, cut, sell cut back to Toci)"), + BUY_CUT_BANK("Buy, Cut & Bank (buy uncut, cut, bank via briefcase)"); + + private final String description; + + @Override + public String toString() { + return description; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutterState.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutterState.java index d08593c9f6..02c7aabaa1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutterState.java +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/GemCutterState.java @@ -5,5 +5,7 @@ public enum GemCutterState { WALKING_TO_SHOP, BUYING, CUTTING, - SELLING + SELLING, + BRIEFCASE_BANKING, + TELEPORTING_BACK } diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java index 68b324196d..58d8a4595d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java @@ -10,8 +10,29 @@ @ConfigGroup("LeaguesToolkit") @ConfigInformation("

    Leagues Toolkit

    " + "

    Version: " + LeaguesToolkitPlugin.version + "

    " + - "

    A grab-bag of Leagues-focused utilities. Start with Anti-AFK to keep long, " + - "auto-banking skilling sessions from getting logged out.

    ") + "

    Anti-AFK: Presses a random arrow key before the idle timer kicks in. " + + "Great for long AFK sessions with auto-bank relics (e.g. Endless Harvest).

    " + + "

    Toci's Gem Store: Walks to Toci in Aldarin, buys uncut gems, " + + "and either sells cut gems back or banks them via the Banker's Briefcase. Three modes:

    " + + "
      " + + "
    • Buy & Bank — fast stockpile: buy uncut gems, briefcase to bank, walk back, repeat.
    • " + + "
    • Buy, Cut & Sell — buy uncut, cut with chisel, sell cut gems back to Toci for profit.
    • " + + "
    • Buy, Cut & Bank — buy uncut, cut, bank via briefcase, walk back, repeat.
    • " + + "
    " + + "

    Wealthy Citizen Thieving: Pickpockets Wealthy citizens with auto coin pouch opening. " + + "Requires the Larcenist relic for 100% success rate (no stuns). " + + "Configure the pouch threshold (max 280 before they auto-destroy).

    " + + "

    Easy Clue Opener: Farms reward caskets using the Aldarin bank easy clue method. " + + "Opens Scroll box (easy) — if the clue is a dig type, digs with spade repeatedly until a casket " + + "or a different clue appears. Non-dig clues are dropped and the next scroll box opens. " + + "Caskets stack in your inventory. Configurable action speed. Requires a spade and scroll boxes.

    " + + "

    Snape Grass Telegrab: Walks to the snape grass spawn and casts Telekinetic Grab " + + "on repeat. Requires 33 Magic, law runes, and air runes (or air staff equipped). " + + "Stops when inventory is full.

    " + + "

    Transmutation: Casts Alchemic Divergence or Convergence on noted items " + + "to upgrade/downgrade through tiers (e.g. Iron ore all the way to Runite ore). " + + "Have the starting items noted in your inventory before enabling. " + + "Requires the Transmutation relic and the transmutation ledger.

    ") public interface LeaguesToolkitConfig extends Config { @ConfigSection( @@ -68,8 +89,8 @@ default int antiAfkBufferMax() { } @ConfigSection( - name = "Toci's Gem Cutter", - description = "Buys uncut gems from Toci in Aldarin, cuts them, sells them back", + name = "Toci's Gem Store", + description = "Automated gem buying, cutting, and selling/banking at Toci in Aldarin", position = 1, closedByDefault = true ) @@ -77,8 +98,8 @@ default int antiAfkBufferMax() { @ConfigItem( keyName = "enableGemCutter", - name = "Enable gem cutter", - description = "Walks to Toci, buys uncut gems, cuts them, sells cut gems back — repeats", + name = "Enable", + description = "Walks to Toci's Gem Store in Aldarin and runs the selected mode. Requires coins in inventory.", position = 0, section = gemCutterSection ) @@ -86,11 +107,24 @@ default boolean enableGemCutter() { return false; } + @ConfigItem( + keyName = "gemCutterMode", + name = "Mode", + description = "Buy & Bank: fast stockpile uncut gems (briefcase required). " + + "Buy, Cut & Sell: buy uncut, cut with chisel, sell cut back to Toci. " + + "Buy, Cut & Bank: buy uncut, cut, bank via briefcase.", + position = 1, + section = gemCutterSection + ) + default GemCutterMode gemCutterMode() { + return GemCutterMode.BUY_AND_BANK; + } + @ConfigItem( keyName = "gemType", name = "Gem", - description = "Which gem to cut (requires chisel + coins + crafting level)", - position = 1, + description = "Which gem to buy/cut. Cut modes require a chisel and the crafting level.", + position = 2, section = gemCutterSection ) default GemType gemType() { @@ -102,21 +136,175 @@ default GemType gemType() { keyName = "gemCutterMinCoins", name = "Min coins to keep", description = "When coins drop below this, withdraw more from the bank", - position = 2, + position = 3, section = gemCutterSection ) default int gemCutterMinCoins() { return 10_000; } + @ConfigSection( + name = "Wealthy Citizen Thieving", + description = "Pickpockets Wealthy citizens, opens coin pouches at a threshold", + position = 2, + closedByDefault = true + ) + String thievingSection = "thievingSection"; + + @ConfigItem( + keyName = "enableThieving", + name = "Enable", + description = "Pickpockets the nearest Wealthy citizen with 100% success (Larcenist relic required). " + + "Opens coin pouches at the configured threshold. Stand near a Wealthy citizen before enabling.", + position = 0, + section = thievingSection + ) + default boolean enableThieving() { + return false; + } + + @Range(min = 1, max = 280) + @ConfigItem( + keyName = "coinPouchThreshold", + name = "Open pouches at", + description = "Open coin pouches when this many have accumulated (max 280 before they auto-destroy)", + position = 1, + section = thievingSection + ) + default int coinPouchThreshold() { + return 200; + } + + @ConfigSection( + name = "Snape Grass Telegrab", + description = "Telegrab snape grass at a fixed location", + position = 2, + closedByDefault = true + ) + String snapeGrassSection = "snapeGrassSection"; + + @ConfigItem( + keyName = "enableSnapeGrass", + name = "Enable", + description = "Walks to snape grass spawn (1736, 3170), casts Telekinetic Grab, waits for respawn, repeats. " + + "Requires 33 Magic, law runes, and air runes or air staff equipped.", + position = 0, + section = snapeGrassSection + ) + default boolean enableSnapeGrass() { + return false; + } + + @ConfigSection( + name = "Easy Clue Opener", + description = "Opens scroll boxes, digs dig-clues, drops non-dig clues, opens reward caskets", + position = 4, + closedByDefault = true + ) + String easyClueSection = "easyClueSection"; + @ConfigItem( - keyName = "gemCutterUseBriefcase", - name = "Use Bank Heist briefcase", - description = "Use the banker's briefcase to bank (Leagues relic) instead of walking to a bank", + keyName = "enableEasyClue", + name = "Enable", + description = "Uses the Aldarin bank easy clue method to farm reward caskets. " + + "Opens Scroll box (easy), checks if the clue is a dig type (ID 29853) — " + + "if so, digs with spade repeatedly until you get a casket or a different clue. " + + "Non-dig clues are dropped and the next scroll box is opened. " + + "Caskets stack in your inventory. Requires a spade and scroll boxes.", + position = 0, + section = easyClueSection + ) + default boolean enableEasyClue() { + return false; + } + + @Range(min = 100, max = 3000) + @ConfigItem( + keyName = "clueDigDelayMin", + name = "Dig delay min (ms)", + description = "Minimum delay after digging before the next action", + position = 1, + section = easyClueSection + ) + default int clueDigDelayMin() { + return 400; + } + + @Range(min = 100, max = 3000) + @ConfigItem( + keyName = "clueDigDelayMax", + name = "Dig delay max (ms)", + description = "Maximum delay after digging before the next action", + position = 2, + section = easyClueSection + ) + default int clueDigDelayMax() { + return 700; + } + + @Range(min = 50, max = 2000) + @ConfigItem( + keyName = "clueActionDelay", + name = "Action delay (ms)", + description = "Delay between opening scroll boxes, dropping clues, etc.", position = 3, - section = gemCutterSection + section = easyClueSection + ) + default int clueActionDelay() { + return 300; + } + + @ConfigSection( + name = "Transmutation", + description = "Casts Alchemic Divergence/Convergence to upgrade or downgrade noted items through tiers", + position = 5, + closedByDefault = true + ) + String transmuteSection = "transmuteSection"; + + @ConfigItem( + keyName = "enableTransmute", + name = "Enable transmutation", + description = "Have the starting noted items in your inventory before enabling. " + + "The script casts the spell on each tier until it reaches the target. " + + "Requires the Transmutation relic and the transmutation ledger equipped or in inventory.", + position = 0, + section = transmuteSection ) - default boolean gemCutterUseBriefcase() { + default boolean enableTransmute() { return false; } + + @ConfigItem( + keyName = "transmuteStartItem", + name = "Starting item", + description = "The item you currently have noted in your inventory. Must be in the same category as the target.", + position = 1, + section = transmuteSection + ) + default TransmuteItem transmuteStartItem() { + return TransmuteItem.IRON_ORE; + } + + @ConfigItem( + keyName = "transmuteTargetItem", + name = "Target item", + description = "The final item you want. Must be in the same category as the starting item.", + position = 2, + section = transmuteSection + ) + default TransmuteItem transmuteTargetItem() { + return TransmuteItem.RUNITE_ORE; + } + + @ConfigItem( + keyName = "transmuteDirection", + name = "Direction", + description = "Upgrade (Alchemic Divergence / High Alch) or Downgrade (Alchemic Convergence / Low Alch)", + position = 4, + section = transmuteSection + ) + default TransmuteDirection transmuteDirection() { + return TransmuteDirection.UPGRADE; + } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java index e766bbf2fd..c1f55f5ea3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java @@ -20,7 +20,7 @@ ) @Slf4j public class LeaguesToolkitPlugin extends Plugin { - public static final String version = "1.1.0"; + public static final String version = "1.2.0"; @Inject private LeaguesToolkitConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java index 604a68cc3a..dad6391061 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java @@ -21,8 +21,20 @@ public class LeaguesToolkitScript extends Script { @Getter private final GemCutter gemCutter = new GemCutter(); + @Getter + private final Transmuter transmuter = new Transmuter(); + @Getter + private final WealthyCitizenThiever wealthyCitizenThiever = new WealthyCitizenThiever(); + @Getter + private final EasyClueOpener easyClueOpener = new EasyClueOpener(); + @Getter + private final SnapeGrassTelegrabber snapeGrassTelegrabber = new SnapeGrassTelegrabber(); private boolean gemCutterWasEnabled = false; + private boolean thievingWasEnabled = false; + private boolean easyClueWasEnabled = false; + private boolean snapeGrassWasEnabled = false; + private boolean transmuteWasEnabled = false; public boolean run(LeaguesToolkitConfig config) { mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { @@ -38,11 +50,55 @@ public boolean run(LeaguesToolkitConfig config) { if (!gemCutterWasEnabled) { gemCutter.reset(); gemCutterWasEnabled = true; + log.info("[LeaguesToolkit] Gem cutter enabled — state: {}", gemCutter.getState()); } gemCutter.tick(config); } else { gemCutterWasEnabled = false; } + + if (config.enableThieving()) { + if (!thievingWasEnabled) { + wealthyCitizenThiever.reset(); + thievingWasEnabled = true; + } + wealthyCitizenThiever.tick(config); + } else { + thievingWasEnabled = false; + } + + if (config.enableEasyClue()) { + if (!easyClueWasEnabled) { + easyClueOpener.reset(); + easyClueWasEnabled = true; + } + easyClueOpener.tick(config); + } else { + easyClueWasEnabled = false; + } + + if (config.enableSnapeGrass()) { + if (!snapeGrassWasEnabled) { + snapeGrassTelegrabber.reset(); + snapeGrassWasEnabled = true; + } + snapeGrassTelegrabber.tick(config); + } else { + snapeGrassWasEnabled = false; + } + + if (config.enableTransmute()) { + if (!transmuteWasEnabled) { + transmuter.reset(); + transmuteWasEnabled = true; + } + if (!transmuter.tick(config)) { + // Transmuter finished or errored — keep running plugin but stop transmuting + log.info("Transmuter stopped: {}", transmuter.getStatus()); + } + } else { + transmuteWasEnabled = false; + } } catch (Exception ex) { log.error("LeaguesToolkitScript loop error", ex); } @@ -74,5 +130,6 @@ private void runAntiAfk(LeaguesToolkitConfig config) { @Override public void shutdown() { super.shutdown(); + transmuter.reset(); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/SnapeGrassTelegrabber.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/SnapeGrassTelegrabber.java new file mode 100644 index 0000000000..a5f3e39041 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/SnapeGrassTelegrabber.java @@ -0,0 +1,145 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Skill; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.microbot.Microbot; +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.grounditem.Rs2GroundItem; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; +import net.runelite.client.plugins.skillcalculator.skills.MagicAction; + +import static net.runelite.client.plugins.microbot.util.Global.sleep; +import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; + +@Slf4j +public class SnapeGrassTelegrabber { + + private static final WorldPoint SNAPE_GRASS_LOCATION = new WorldPoint(1736, 3170, 0); + private static final int SNAPE_GRASS_ITEM_ID = 231; + private static final int REQUIRED_MAGIC_LEVEL = 33; + private static final int GRAB_RANGE = 15; + private static final String BRIEFCASE_NAME = "Banker's briefcase"; + private static final int SNAPE_GRASS_NOTED_ID = 232; + + private boolean banking = false; + + @Getter + private String status = "Idle"; + + public void reset() { + status = "Idle"; + banking = false; + } + + public boolean tick(LeaguesToolkitConfig config) { + // Handle banking if inventory was full + if (banking) { + return handleBanking(config); + } + // Check magic level + if (!Rs2Player.getSkillRequirement(Skill.MAGIC, REQUIRED_MAGIC_LEVEL)) { + status = "Need 33 Magic for Telekinetic Grab"; + return false; + } + + // Check for runes — need law runes + air runes (or air staff) + boolean hasLawRunes = Rs2Inventory.hasItem("Law rune"); + boolean hasAirSource = Rs2Inventory.hasItem("Air rune") + || Rs2Equipment.isWearing("Staff of air") + || Rs2Equipment.isWearing("Air battlestaff") + || Rs2Equipment.isWearing("Mystic air staff"); + + if (!hasLawRunes) { + status = "No law runes in inventory"; + return false; + } + if (!hasAirSource) { + status = "No air runes or air staff equipped"; + return false; + } + + // Check if inventory is full — bank and come back + if (Rs2Inventory.isFull()) { + banking = true; + return handleBanking(config); + } + + // Walk to location if not nearby + WorldPoint playerPos = Rs2Player.getWorldLocation(); + if (playerPos == null) return true; + + int distance = playerPos.distanceTo(SNAPE_GRASS_LOCATION); + if (distance > 20) { + status = "Walking to snape grass (" + distance + " tiles)"; + Rs2Walker.walkTo(SNAPE_GRASS_LOCATION, 4); + sleep(3000, 5000); + return true; + } + + // Wait if still moving/animating + if (Rs2Player.isMoving() || Rs2Player.isAnimating()) { + status = "Moving..."; + return true; + } + + // Check if snape grass is on the ground + if (!Rs2GroundItem.exists(SNAPE_GRASS_ITEM_ID, GRAB_RANGE)) { + status = "Waiting for snape grass to respawn..."; + return true; + } + + // Cast Telekinetic Grab + status = "Casting Telekinetic Grab on snape grass"; + if (!Rs2Magic.cast(MagicAction.TELEKINETIC_GRAB)) { + status = "Failed to cast — check spellbook"; + return true; + } + sleep(300, 500); + + // Click the ground item + Rs2GroundItem.interact(SNAPE_GRASS_ITEM_ID, "Cast", GRAB_RANGE); + sleep(600, 900); + + // Wait for the grab animation to finish + sleepUntil(() -> !Rs2Player.isAnimating(), 5000); + sleep(300, 600); + + status = "Grabbed snape grass"; + return true; + } + + private boolean handleBanking(LeaguesToolkitConfig config) { + if (!Rs2Bank.isOpen()) { + if (Rs2Equipment.isWearing(BRIEFCASE_NAME)) { + status = "Teleporting to bank via briefcase"; + Rs2Equipment.interact(BRIEFCASE_NAME, "Last-destination"); + sleep(2000, 3000); + sleepUntil(() -> !Rs2Player.isAnimating() && !Rs2Player.isMoving(), 8000); + sleep(500, 1000); + status = "Opening bank"; + Rs2Bank.openBank(); + sleepUntil(Rs2Bank::isOpen, 5000); + } else { + status = "Walking to nearest bank"; + if (!Rs2Bank.walkToBankAndUseBank()) return true; + } + return true; + } + + status = "Depositing snape grass"; + Rs2Bank.depositAll("Snape grass"); + sleep(300, 500); + Rs2Bank.closeBank(); + sleepUntil(() -> !Rs2Bank.isOpen(), 3000); + + banking = false; + status = "Walking back to snape grass"; + return true; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/TransmuteCategory.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/TransmuteCategory.java new file mode 100644 index 0000000000..2dd474bffa --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/TransmuteCategory.java @@ -0,0 +1,69 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +import java.util.Arrays; +import java.util.List; + +@Getter +@RequiredArgsConstructor +public enum TransmuteCategory { + ORES("Ores", Arrays.asList( + "Tin ore", "Copper ore", "Iron ore", "Coal", "Mithril ore", "Adamantite ore", "Runite ore" + )), + FISH("Fish", Arrays.asList( + "Raw shrimps", "Raw sardine", "Raw herring", "Raw mackerel", "Raw trout", + "Raw cod", "Raw pike", "Raw salmon", "Raw tuna", "Raw lobster", + "Raw bass", "Raw swordfish", "Raw karambwan", "Raw shark", "Raw anglerfish" + )), + GEMS("Gems", Arrays.asList( + "Uncut sapphire", "Uncut emerald", "Uncut ruby", "Uncut diamond", + "Uncut opal", "Uncut jade", "Uncut red topaz", "Uncut dragonstone" + )), + RUNES("Runes", Arrays.asList( + "Air rune", "Water rune", "Earth rune", "Fire rune", + "Chaos rune", "Nature rune", "Cosmic rune", "Law rune", + "Death rune", "Astral rune", "Blood rune", "Soul rune", "Wrath rune" + )), + ASHES("Ashes", Arrays.asList( + "Ashes", "Volcanic ash", "Fiendish ashes", "Vile ashes", + "Malicious ashes", "Abyssal ashes", "Infernal ashes" + )), + COMPOST("Compost", Arrays.asList( + "Compost", "Supercompost", "Ultracompost" + )), + LOGS("Logs", Arrays.asList( + "Logs", "Oak logs", "Willow logs", "Teak logs", "Maple logs", + "Mahogany logs", "Yew logs", "Magic logs", "Redwood logs" + )), + BONES("Bones", Arrays.asList( + "Bones", "Bat bones", "Big bones", "Wyrmling bones", "Baby dragon bones", + "Wyrm bones", "Dragon bones", "Drake bones", "Lava dragon bones", + "Hydra bones", "Dagannoth bones", "Superior dragon bones" + )), + HIDES("Hides", Arrays.asList( + "Cowhide", "Snakeskin", "Green dragonhide", "Blue dragonhide", + "Red dragonhide", "Black dragonhide" + )); + + private final String displayName; + private final List chain; + + public int indexOf(String itemName) { + for (int i = 0; i < chain.size(); i++) { + if (chain.get(i).equalsIgnoreCase(itemName)) return i; + } + return -1; + } + + public String getItem(int index) { + if (index < 0 || index >= chain.size()) return null; + return chain.get(index); + } + + @Override + public String toString() { + return displayName; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/TransmuteDirection.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/TransmuteDirection.java new file mode 100644 index 0000000000..46cdf9f42c --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/TransmuteDirection.java @@ -0,0 +1,6 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +public enum TransmuteDirection { + UPGRADE, + DOWNGRADE +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/TransmuteItem.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/TransmuteItem.java new file mode 100644 index 0000000000..47bfc43d40 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/TransmuteItem.java @@ -0,0 +1,114 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum TransmuteItem { + // Ores + TIN_ORE("Tin ore", TransmuteCategory.ORES), + COPPER_ORE("Copper ore", TransmuteCategory.ORES), + IRON_ORE("Iron ore", TransmuteCategory.ORES), + COAL("Coal", TransmuteCategory.ORES), + MITHRIL_ORE("Mithril ore", TransmuteCategory.ORES), + ADAMANTITE_ORE("Adamantite ore", TransmuteCategory.ORES), + RUNITE_ORE("Runite ore", TransmuteCategory.ORES), + + // Fish + RAW_SHRIMPS("Raw shrimps", TransmuteCategory.FISH), + RAW_SARDINE("Raw sardine", TransmuteCategory.FISH), + RAW_HERRING("Raw herring", TransmuteCategory.FISH), + RAW_MACKEREL("Raw mackerel", TransmuteCategory.FISH), + RAW_TROUT("Raw trout", TransmuteCategory.FISH), + RAW_COD("Raw cod", TransmuteCategory.FISH), + RAW_PIKE("Raw pike", TransmuteCategory.FISH), + RAW_SALMON("Raw salmon", TransmuteCategory.FISH), + RAW_TUNA("Raw tuna", TransmuteCategory.FISH), + RAW_LOBSTER("Raw lobster", TransmuteCategory.FISH), + RAW_BASS("Raw bass", TransmuteCategory.FISH), + RAW_SWORDFISH("Raw swordfish", TransmuteCategory.FISH), + RAW_KARAMBWAN("Raw karambwan", TransmuteCategory.FISH), + RAW_SHARK("Raw shark", TransmuteCategory.FISH), + RAW_ANGLERFISH("Raw anglerfish", TransmuteCategory.FISH), + + // Gems (transmute order: sapphire → emerald → ruby → diamond → opal → jade → red topaz → dragonstone) + UNCUT_SAPPHIRE("Uncut sapphire", TransmuteCategory.GEMS), + UNCUT_EMERALD("Uncut emerald", TransmuteCategory.GEMS), + UNCUT_RUBY("Uncut ruby", TransmuteCategory.GEMS), + UNCUT_DIAMOND("Uncut diamond", TransmuteCategory.GEMS), + UNCUT_OPAL("Uncut opal", TransmuteCategory.GEMS), + UNCUT_JADE("Uncut jade", TransmuteCategory.GEMS), + UNCUT_RED_TOPAZ("Uncut red topaz", TransmuteCategory.GEMS), + UNCUT_DRAGONSTONE("Uncut dragonstone", TransmuteCategory.GEMS), + + // Runes + AIR_RUNE("Air rune", TransmuteCategory.RUNES), + WATER_RUNE("Water rune", TransmuteCategory.RUNES), + EARTH_RUNE("Earth rune", TransmuteCategory.RUNES), + FIRE_RUNE("Fire rune", TransmuteCategory.RUNES), + CHAOS_RUNE("Chaos rune", TransmuteCategory.RUNES), + NATURE_RUNE("Nature rune", TransmuteCategory.RUNES), + COSMIC_RUNE("Cosmic rune", TransmuteCategory.RUNES), + LAW_RUNE("Law rune", TransmuteCategory.RUNES), + DEATH_RUNE("Death rune", TransmuteCategory.RUNES), + ASTRAL_RUNE("Astral rune", TransmuteCategory.RUNES), + BLOOD_RUNE("Blood rune", TransmuteCategory.RUNES), + SOUL_RUNE("Soul rune", TransmuteCategory.RUNES), + WRATH_RUNE("Wrath rune", TransmuteCategory.RUNES), + + // Logs + LOGS("Logs", TransmuteCategory.LOGS), + OAK_LOGS("Oak logs", TransmuteCategory.LOGS), + WILLOW_LOGS("Willow logs", TransmuteCategory.LOGS), + TEAK_LOGS("Teak logs", TransmuteCategory.LOGS), + MAPLE_LOGS("Maple logs", TransmuteCategory.LOGS), + MAHOGANY_LOGS("Mahogany logs", TransmuteCategory.LOGS), + YEW_LOGS("Yew logs", TransmuteCategory.LOGS), + MAGIC_LOGS("Magic logs", TransmuteCategory.LOGS), + REDWOOD_LOGS("Redwood logs", TransmuteCategory.LOGS), + + // Bones + BONES("Bones", TransmuteCategory.BONES), + BAT_BONES("Bat bones", TransmuteCategory.BONES), + BIG_BONES("Big bones", TransmuteCategory.BONES), + WYRMLING_BONES("Wyrmling bones", TransmuteCategory.BONES), + BABY_DRAGON_BONES("Baby dragon bones", TransmuteCategory.BONES), + WYRM_BONES("Wyrm bones", TransmuteCategory.BONES), + DRAGON_BONES("Dragon bones", TransmuteCategory.BONES), + DRAKE_BONES("Drake bones", TransmuteCategory.BONES), + LAVA_DRAGON_BONES("Lava dragon bones", TransmuteCategory.BONES), + HYDRA_BONES("Hydra bones", TransmuteCategory.BONES), + DAGANNOTH_BONES("Dagannoth bones", TransmuteCategory.BONES), + SUPERIOR_DRAGON_BONES("Superior dragon bones", TransmuteCategory.BONES), + + // Hides + COWHIDE("Cowhide", TransmuteCategory.HIDES), + SNAKESKIN("Snakeskin", TransmuteCategory.HIDES), + GREEN_DRAGONHIDE("Green dragonhide", TransmuteCategory.HIDES), + BLUE_DRAGONHIDE("Blue dragonhide", TransmuteCategory.HIDES), + RED_DRAGONHIDE("Red dragonhide", TransmuteCategory.HIDES), + BLACK_DRAGONHIDE("Black dragonhide", TransmuteCategory.HIDES), + + // Ashes + ASHES("Ashes", TransmuteCategory.ASHES), + VOLCANIC_ASH("Volcanic ash", TransmuteCategory.ASHES), + FIENDISH_ASHES("Fiendish ashes", TransmuteCategory.ASHES), + VILE_ASHES("Vile ashes", TransmuteCategory.ASHES), + MALICIOUS_ASHES("Malicious ashes", TransmuteCategory.ASHES), + ABYSSAL_ASHES("Abyssal ashes", TransmuteCategory.ASHES), + INFERNAL_ASHES("Infernal ashes", TransmuteCategory.ASHES), + + // Compost + COMPOST("Compost", TransmuteCategory.COMPOST), + SUPERCOMPOST("Supercompost", TransmuteCategory.COMPOST), + ULTRACOMPOST("Ultracompost", TransmuteCategory.COMPOST); + + private final String itemName; + private final TransmuteCategory category; + + @Override + public String toString() { + return itemName; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/Transmuter.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/Transmuter.java new file mode 100644 index 0000000000..bec017a625 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/Transmuter.java @@ -0,0 +1,229 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.plugins.microbot.Microbot; +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.shop.Rs2Shop; +import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; +import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; +import net.runelite.client.plugins.microbot.globval.enums.InterfaceTab; + +import java.util.List; + +import static net.runelite.client.plugins.microbot.util.Global.sleep; +import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; + +@Slf4j +public class Transmuter { + + @Getter + private String status = "Idle"; + @Getter + private boolean running = false; + + // Track state across ticks instead of blocking + private boolean casting = false; + private long lastCastTime = 0; + private int lastItemCount = 0; + private String currentItemName = null; + + // If item count hasn't changed for this long AND shop is closed, re-cast. + // Auto-recast processes 10 items per batch with gaps between, so be patient. + private static final long RECAST_TIMEOUT_MS = 45_000; + // When shop is open, auto-recast pauses entirely — use a much longer timeout + private static final long RECAST_TIMEOUT_SHOP_OPEN_MS = 180_000; + + public void reset() { + status = "Idle"; + running = false; + casting = false; + lastCastTime = 0; + lastItemCount = 0; + currentItemName = null; + } + + /** + * Run one tick of the transmute loop. + * Returns false if the feature should be disabled (done or error). + */ + public boolean tick(LeaguesToolkitConfig config) { + running = true; + TransmuteItem startEnum = config.transmuteStartItem(); + TransmuteItem targetEnum = config.transmuteTargetItem(); + TransmuteDirection direction = config.transmuteDirection(); + + if (startEnum == null || targetEnum == null) { + status = "Config incomplete"; + return false; + } + + if (startEnum.getCategory() != targetEnum.getCategory()) { + status = "Start and target must be in the same category (" + + startEnum.getCategory().getDisplayName() + " vs " + + targetEnum.getCategory().getDisplayName() + ")"; + return false; + } + + TransmuteCategory category = startEnum.getCategory(); + String startItem = startEnum.getItemName(); + String targetItem = targetEnum.getItemName(); + List chain = category.getChain(); + int startIdx = category.indexOf(startItem); + int targetIdx = category.indexOf(targetItem); + + if (startIdx == -1 || targetIdx == -1) { + status = "Item not found in " + category.getDisplayName() + " chain"; + return false; + } + + // Determine direction based on chain positions — the user picks upgrade/downgrade + // to select the spell, but we walk the chain in whichever direction goes from start to target + int step = (targetIdx > startIdx) ? 1 : -1; + int currentIdx = findCurrentTier(chain, startIdx, targetIdx, step); + + // Check if we're done: target exists AND no intermediate items remain + if (Rs2Inventory.hasItem(targetItem)) { + boolean intermediatesRemain = false; + for (int i = startIdx; i != targetIdx; i += step) { + if (i < 0 || i >= chain.size()) break; + if (Rs2Inventory.hasItem(chain.get(i))) { + intermediatesRemain = true; + break; + } + } + if (!intermediatesRemain) { + status = "Done — all items are now " + targetItem; + running = false; + casting = false; + return false; + } + // Target exists but intermediates remain — keep going + } + + if (currentIdx == -1) { + // Log what we searched for to debug + log.info("[Transmuter] No items found. Searched chain indices {} to {} (step {}):", startIdx, targetIdx, step); + for (int i = startIdx; i != targetIdx + step; i += step) { + if (i < 0 || i >= chain.size()) break; + String name = chain.get(i); + boolean has = Rs2Inventory.hasItem(name); + log.info("[Transmuter] {} = {}", name, has); + } + status = "No transmutable items found in inventory"; + running = false; + casting = false; + return false; + } + + String currentItem = chain.get(currentIdx); + String nextItem = chain.get(currentIdx + step); + + // Are we mid-cast and the item changed? Advance. + if (casting && currentItemName != null && !currentItemName.equals(currentItem)) { + log.info("Tier advanced: {} → {}", currentItemName, currentItem); + casting = false; + currentItemName = null; + } + + // Check if auto-recast is still running + if (casting) { + int currentCount = Rs2Inventory.count(currentItem); + + if (currentCount == 0) { + // All items transmuted — next tick will pick up new tier + casting = false; + currentItemName = null; + status = "Tier complete → " + nextItem; + return true; + } + + // Check if count is still decreasing (auto-recast active) + if (currentCount < lastItemCount) { + lastItemCount = currentCount; + lastCastTime = System.currentTimeMillis(); + status = "Auto-recasting " + currentItem + " → " + nextItem + " (" + currentCount + " left)"; + return true; + } + + // Count hasn't changed — check if we've timed out (recast interrupted) + // Shop windows pause auto-recast, so use a longer timeout when shop is open + long elapsed = System.currentTimeMillis() - lastCastTime; + long timeout = Rs2Shop.isOpen() ? RECAST_TIMEOUT_SHOP_OPEN_MS : RECAST_TIMEOUT_MS; + + if (elapsed < timeout) { + String reason = Rs2Shop.isOpen() ? " (shop open — recast paused)" : ""; + status = "Waiting for recast... " + currentItem + " (" + currentCount + " left)" + reason; + return true; + } + + // Timed out — recast got interrupted, try again + log.info("Auto-recast interrupted for {} (shop open: {}), re-casting", currentItem, Rs2Shop.isOpen()); + casting = false; + } + + // Cast the spell on the current tier item + // In Leagues, High Alch = "Alchemic Divergence", Low Alch = "Alchemic Convergence" + String spellName = direction == TransmuteDirection.UPGRADE + ? "Alchemic Divergence" + : "Alchemic Convergence"; + status = "Casting " + spellName + " on " + currentItem + " → " + nextItem; + + Rs2ItemModel item = Rs2Inventory.get(currentItem); + if (item == null) { + status = "Lost " + currentItem + " from inventory"; + return true; + } + + // Switch to magic tab and click the spell by name + Rs2Tab.switchToMagicTab(); + sleepUntil(() -> Microbot.getClientThread().runOnClientThreadOptional( + () -> Rs2Tab.getCurrentTab() == InterfaceTab.MAGIC).orElse(false)); + sleep(200, 400); + + if (!Rs2Widget.clickWidget(spellName)) { + status = "Could not find " + spellName + " spell — do you have the Transmutation relic?"; + return true; + } + + // Wait for inventory tab to appear, then click the item + sleepUntil(() -> Microbot.getClientThread().runOnClientThreadOptional( + () -> Rs2Tab.getCurrentTab() == InterfaceTab.INVENTORY).orElse(false)); + sleep(300, 500); + Rs2Inventory.interact(item, "Cast"); + sleep(600, 900); + + // Switch back to inventory tab so we can monitor item count changes + Rs2Tab.switchToInventoryTab(); + sleepUntil(() -> Microbot.getClientThread().runOnClientThreadOptional( + () -> Rs2Tab.getCurrentTab() == InterfaceTab.INVENTORY).orElse(false)); + + // Mark as casting — subsequent ticks will monitor progress + casting = true; + currentItemName = currentItem; + lastItemCount = Rs2Inventory.count(currentItem); + lastCastTime = System.currentTimeMillis(); + status = "Auto-recasting " + currentItem + " → " + nextItem + " (" + lastItemCount + " remaining)"; + + return true; + } + + /** + * Finds which tier item is currently in the inventory. + * Searches the entire chain (not just start→target) in case a previous + * transmutation went in an unexpected direction. + */ + private int findCurrentTier(List chain, int startIdx, int targetIdx, int step) { + // First: search from start toward target (expected path) + for (int i = startIdx; i != targetIdx + step; i += step) { + if (i < 0 || i >= chain.size()) break; + if (Rs2Inventory.hasItem(chain.get(i))) return i; + } + // Fallback: search the entire chain for any matching item + for (int i = 0; i < chain.size(); i++) { + if (Rs2Inventory.hasItem(chain.get(i))) return i; + } + return -1; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/WealthyCitizenThiever.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/WealthyCitizenThiever.java new file mode 100644 index 0000000000..c8b7f638ba --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/WealthyCitizenThiever.java @@ -0,0 +1,77 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; + +import static net.runelite.client.plugins.microbot.util.Global.sleep; +import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; + +@Slf4j +public class WealthyCitizenThiever { + + private static final String NPC_NAME = "Wealthy citizen"; + private static final String COIN_POUCH = "Coin pouch"; + private static final int COIN_POUCH_ID = 28822; + + @Getter + private String status = "Idle"; + + private boolean pickpocketing = false; + + public void reset() { + status = "Idle"; + pickpocketing = false; + } + + public boolean tick(LeaguesToolkitConfig config) { + // Check if inventory is full (excluding coin pouches which stack) + if (Rs2Inventory.isFull() && !Rs2Inventory.hasItem(COIN_POUCH)) { + status = "Inventory full"; + return false; + } + + // Open coin pouches if at threshold (use itemQuantity for stack count) + int pouchCount = Rs2Inventory.itemQuantity(COIN_POUCH_ID); + if (pouchCount >= config.coinPouchThreshold()) { + status = "Opening " + pouchCount + " coin pouches"; + pickpocketing = false; + Rs2Inventory.interact(COIN_POUCH, "Open-all"); + sleepUntil(() -> !Rs2Inventory.hasItem(COIN_POUCH), 3000); + sleep(200, 400); + return true; + } + + // If already pickpocketing (Larcenist auto-repickpockets), just monitor + if (pickpocketing && (Rs2Player.isAnimating() || Rs2Player.isInteracting())) { + status = "Pickpocketing... (" + pouchCount + "/" + config.coinPouchThreshold() + " pouches)"; + return true; + } + + // Not actively pickpocketing — click the NPC once to start + Rs2NpcModel npc = Microbot.getRs2NpcCache().query() + .where(n -> { + String name = Microbot.getClientThread() + .runOnClientThreadOptional(n::getName).orElse(null); + return NPC_NAME.equalsIgnoreCase(name); + }) + .nearest(); + + if (npc == null) { + status = "No Wealthy citizen found nearby"; + pickpocketing = false; + return true; + } + + status = "Starting pickpocket on Wealthy citizen"; + Rs2Npc.pickpocket(npc.getNpc()); + pickpocketing = true; + sleep(600, 900); + + return true; + } +} From 76be9bfa3bef599cb6a55ce3c3fbb8b29d08fd8f Mon Sep 17 00:00:00 2001 From: dginovker Date: Sun, 19 Apr 2026 22:34:24 -0700 Subject: [PATCH 58/95] feat(AIOAIO): add record & replay plugin Adds the AIO AIO plugin: record in-game menu actions and play them back on demand or on loop. Captures MenuOptionClicked events, persists recordings as JSON under ~/.runelite/microbot-recordings/, and re-resolves NPC/object/ground-item targets by id/name at replay time via the queryable cache so recordings survive target movement and respawns. Inventory item actions delegate to Rs2Inventory.interact() to avoid stale-slot misses. Panel UI supports renaming, deleting, reordering, duplicating, and editing per-step tick delays. Recordings sort by last-used timestamp. Author: Red Bracket --- .../actionreplay/ActionReplayConfig.java | 19 + .../actionreplay/ActionReplayOverlay.java | 74 ++ .../actionreplay/ActionReplayPanel.java | 648 ++++++++++++++++++ .../actionreplay/ActionReplayPlugin.java | 488 +++++++++++++ .../actionreplay/ActionReplayScript.java | 328 +++++++++ .../actionreplay/model/RecordedAction.java | 32 + .../actionreplay/model/Recording.java | 23 + .../actionreplay/model/TargetType.java | 71 ++ .../microbot/actionreplay/docs/README.md | 28 + 9 files changed, 1711 insertions(+) create mode 100644 src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayConfig.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayOverlay.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPanel.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPlugin.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayScript.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/RecordedAction.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/Recording.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/TargetType.java create mode 100644 src/main/resources/net/runelite/client/plugins/microbot/actionreplay/docs/README.md diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayConfig.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayConfig.java new file mode 100644 index 0000000000..48253edc8d --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayConfig.java @@ -0,0 +1,19 @@ +package net.runelite.client.plugins.microbot.actionreplay; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigInformation; + +@ConfigGroup(ActionReplayConfig.GROUP) +@ConfigInformation( + "Enable the plugin, then open the panel.
    " + + "Hit Start recording, do the actions in-game, then stop.
    " + + "Play back anytime from the panel.
    " + + "
    " + + "Developed by Red Bracket. Feel free to make improvements,
    " + + "just try to keep it simple :)" +) +public interface ActionReplayConfig extends Config +{ + String GROUP = "actionReplay"; +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayOverlay.java new file mode 100644 index 0000000000..cae7e3f8d7 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayOverlay.java @@ -0,0 +1,74 @@ +package net.runelite.client.plugins.microbot.actionreplay; + +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.overlay.Overlay; +import net.runelite.client.ui.overlay.OverlayLayer; +import net.runelite.client.ui.overlay.OverlayPosition; +import net.runelite.client.ui.overlay.OverlayPriority; + +import javax.inject.Inject; +import java.awt.BasicStroke; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; +import java.awt.RenderingHints; + +public class ActionReplayOverlay extends Overlay +{ + private static final int DOT_RADIUS = 6; + private static final Color RECORD_COLOR = new Color(220, 40, 40); + private static final Color PLAY_COLOR = new Color(40, 200, 80); + + private final ActionReplayPlugin plugin; + + @Inject + public ActionReplayOverlay(ActionReplayPlugin plugin) + { + this.plugin = plugin; + setPosition(OverlayPosition.TOP_RIGHT); + setLayer(OverlayLayer.ABOVE_WIDGETS); + setPriority(OverlayPriority.HIGH); + } + + @Override + public Dimension render(Graphics2D g) + { + boolean recording = plugin.isRecording(); + boolean playing = plugin.isPlaying(); + if (!recording && !playing) + { + return null; + } + + boolean blink = ((System.currentTimeMillis() / 500) % 2) == 0; + Color color = recording ? RECORD_COLOR : PLAY_COLOR; + String label = recording ? "AIO AIO REC" : "AIO AIO PLAY"; + + g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE); + g.setFont(FontManager.getRunescapeSmallFont()); + int labelWidth = g.getFontMetrics().stringWidth(label); + int textBaseline = g.getFontMetrics().getAscent(); + int height = Math.max(DOT_RADIUS * 2 + 2, textBaseline + 2); + int width = DOT_RADIUS * 2 + 6 + labelWidth; + + int dotY = (height - DOT_RADIUS * 2) / 2; + if (blink || playing) + { + g.setColor(color); + g.fillOval(0, dotY, DOT_RADIUS * 2, DOT_RADIUS * 2); + } + g.setColor(color.darker()); + g.setStroke(new BasicStroke(1f)); + g.drawOval(0, dotY, DOT_RADIUS * 2, DOT_RADIUS * 2); + + int textX = DOT_RADIUS * 2 + 6; + int textY = (height + textBaseline) / 2 - 1; + g.setColor(Color.BLACK); + g.drawString(label, textX + 1, textY + 1); + g.setColor(Color.WHITE); + g.drawString(label, textX, textY); + + return new Dimension(width, height); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPanel.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPanel.java new file mode 100644 index 0000000000..ffd6e36b79 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPanel.java @@ -0,0 +1,648 @@ +package net.runelite.client.plugins.microbot.actionreplay; + +import net.runelite.client.plugins.microbot.actionreplay.model.RecordedAction; +import net.runelite.client.plugins.microbot.actionreplay.model.Recording; +import net.runelite.client.ui.ColorScheme; +import net.runelite.client.ui.FontManager; +import net.runelite.client.ui.PluginPanel; + +import javax.inject.Inject; +import javax.swing.Box; +import javax.swing.BoxLayout; +import javax.swing.DefaultListModel; +import javax.swing.JButton; +import javax.swing.JComboBox; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JScrollPane; +import javax.swing.JSpinner; +import javax.swing.ScrollPaneConstants; +import javax.swing.SpinnerNumberModel; +import javax.swing.SwingUtilities; +import javax.swing.border.EmptyBorder; +import java.awt.BorderLayout; +import java.awt.Color; +import java.awt.Component; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.GridLayout; +import java.awt.Insets; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +public class ActionReplayPanel extends PluginPanel +{ + private static final Color MUTED = new Color(160, 160, 160); + private static final String CURRENT_LABEL = "(Current recording)"; + + private ActionReplayPlugin plugin; + + private final JLabel countLabel = new JLabel(" "); + private final JButton recordButton = new JButton("● Record script"); + private final JButton stopPlaybackButton = new JButton("■ Stop script"); + + private final JComboBox scriptSelector = new JComboBox<>(); + private final DefaultListModel actionsModel = new DefaultListModel<>(); + private final JList actionsList = new JList<>(actionsModel); + + private final JButton upButton = new JButton("↑"); + private final JButton downButton = new JButton("↓"); + private final JButton deleteStepButton = new JButton("✕"); + private final JButton editStepButton = new JButton("✎"); + private final JButton duplicateStepButton = new JButton("⧉"); + private final JButton playButton = new JButton("▶ Run script"); + private final JButton renameButton = new JButton("✎ Rename"); + private final JButton deleteScriptButton = new JButton("🗑 Delete"); + + private Recording viewedRecording; + private Recording lastLiveRecording; + private List savedRecordings = new ArrayList<>(); + private boolean suppressSelectorEvents = false; + + @Inject + public ActionReplayPanel() + { + super(false); + setBorder(new EmptyBorder(10, 10, 10, 10)); + setLayout(new BorderLayout(0, 10)); + setBackground(ColorScheme.DARK_GRAY_COLOR); + + add(buildHeader(), BorderLayout.NORTH); + add(buildCenter(), BorderLayout.CENTER); + } + + private JPanel buildHeader() + { + JPanel header = new JPanel(); + header.setLayout(new BoxLayout(header, BoxLayout.Y_AXIS)); + header.setBackground(ColorScheme.DARK_GRAY_COLOR); + + JLabel title = new JLabel("AIO AIO"); + title.setFont(FontManager.getRunescapeBoldFont()); + title.setForeground(Color.WHITE); + title.setAlignmentX(Component.LEFT_ALIGNMENT); + header.add(title); + header.add(Box.createVerticalStrut(6)); + + countLabel.setAlignmentX(Component.LEFT_ALIGNMENT); + header.add(countLabel); + + header.add(Box.createVerticalStrut(8)); + + recordButton.setAlignmentX(Component.LEFT_ALIGNMENT); + recordButton.setForeground(Color.WHITE); + recordButton.addActionListener(e -> onRecordClicked()); + + playButton.setAlignmentX(Component.LEFT_ALIGNMENT); + playButton.setForeground(Color.WHITE); + playButton.setToolTipText("Loop selected script until stopped"); + playButton.addActionListener(e -> onPlay(true)); + + stopPlaybackButton.setAlignmentX(Component.LEFT_ALIGNMENT); + stopPlaybackButton.setForeground(Color.WHITE); + stopPlaybackButton.addActionListener(e -> plugin.stopPlayback()); + + header.add(playButton); + header.add(Box.createVerticalStrut(4)); + header.add(stopPlaybackButton); + header.add(Box.createVerticalStrut(4)); + header.add(recordButton); + + return header; + } + + private JPanel buildCenter() + { + JPanel center = new JPanel(); + center.setLayout(new BoxLayout(center, BoxLayout.Y_AXIS)); + center.setBackground(ColorScheme.DARK_GRAY_COLOR); + + JLabel selectorLabel = new JLabel("Script:"); + selectorLabel.setForeground(MUTED); + selectorLabel.setFont(selectorLabel.getFont().deriveFont(11f)); + selectorLabel.setAlignmentX(Component.LEFT_ALIGNMENT); + center.add(selectorLabel); + center.add(Box.createVerticalStrut(2)); + + scriptSelector.setAlignmentX(Component.LEFT_ALIGNMENT); + scriptSelector.setMaximumSize(new Dimension(Integer.MAX_VALUE, 24)); + scriptSelector.addActionListener(e -> + { + if (!suppressSelectorEvents) + { + onSelectorChanged(); + } + }); + center.add(scriptSelector); + center.add(Box.createVerticalStrut(4)); + + actionsList.setBackground(ColorScheme.DARKER_GRAY_COLOR); + actionsList.setForeground(Color.WHITE); + actionsList.addMouseListener(new MouseAdapter() + { + @Override + public void mouseClicked(MouseEvent e) + { + if (e.getClickCount() == 2) + { + int idx = actionsList.locationToIndex(e.getPoint()); + if (idx >= 0) + { + actionsList.setSelectedIndex(idx); + onEditStep(); + } + } + } + }); + JScrollPane scroll = new JScrollPane(actionsList); + scroll.setPreferredSize(new Dimension(220, 200)); + scroll.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE)); + scroll.setAlignmentX(Component.LEFT_ALIGNMENT); + scroll.setBorder(null); + scroll.setHorizontalScrollBarPolicy(ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER); + center.add(scroll); + center.add(Box.createVerticalStrut(6)); + + JPanel editRow = new JPanel(new GridLayout(1, 5, 3, 0)); + editRow.setOpaque(false); + editRow.setAlignmentX(Component.LEFT_ALIGNMENT); + editRow.setMaximumSize(new Dimension(Integer.MAX_VALUE, 28)); + + initSmallButton(upButton, "Move step up"); + upButton.addActionListener(e -> onMoveUp()); + editRow.add(upButton); + + initSmallButton(downButton, "Move step down"); + downButton.addActionListener(e -> onMoveDown()); + editRow.add(downButton); + + initSmallButton(editStepButton, "Edit step (double-click works too)"); + editStepButton.addActionListener(e -> onEditStep()); + editRow.add(editStepButton); + + initSmallButton(duplicateStepButton, "Duplicate selected step"); + duplicateStepButton.addActionListener(e -> onDuplicateStep()); + editRow.add(duplicateStepButton); + + initSmallButton(deleteStepButton, "Delete selected step(s)"); + deleteStepButton.addActionListener(e -> onDeleteStep()); + editRow.add(deleteStepButton); + + center.add(editRow); + center.add(Box.createVerticalStrut(6)); + + JPanel scriptRow = new JPanel(new GridLayout(1, 2, 4, 4)); + scriptRow.setOpaque(false); + scriptRow.setAlignmentX(Component.LEFT_ALIGNMENT); + scriptRow.setMaximumSize(new Dimension(Integer.MAX_VALUE, 32)); + + renameButton.setToolTipText("Rename selected script"); + renameButton.addActionListener(e -> onRename()); + scriptRow.add(renameButton); + + deleteScriptButton.setToolTipText("Delete selected script"); + deleteScriptButton.addActionListener(e -> onDeleteScript()); + scriptRow.add(deleteScriptButton); + + center.add(scriptRow); + + return center; + } + + private void initSmallButton(JButton b, String tooltip) + { + b.setToolTipText(tooltip); + b.setMargin(new java.awt.Insets(2, 4, 2, 4)); + b.setFocusPainted(false); + } + + public void setPlugin(ActionReplayPlugin plugin) + { + this.plugin = plugin; + } + + public void refresh() + { + if (plugin == null) + { + return; + } + SwingUtilities.invokeLater(() -> + { + boolean rec = plugin.isRecording(); + boolean play = plugin.isPlaying(); + + if (rec) + { + recordButton.setText("■ Stop recording"); + countLabel.setText("Captured: " + plugin.getCurrentRecordingSize() + " actions"); + countLabel.setForeground(MUTED); + } + else + { + recordButton.setText("● Record script"); + countLabel.setText(" "); + } + recordButton.setEnabled(!play); + stopPlaybackButton.setEnabled(play); + + if (rec) + { + viewedRecording = null; + } + + refreshSelector(); + reloadActionList(); + updateButtonEnabled(rec, play); + + revalidate(); + repaint(); + }); + } + + private void updateButtonEnabled(boolean rec, boolean play) + { + Recording selected = getSelectedRecording(); + boolean hasActions = selected != null && selected.size() > 0; + boolean canEditSteps = !rec && !play && hasActions; + + upButton.setEnabled(canEditSteps); + downButton.setEnabled(canEditSteps); + deleteStepButton.setEnabled(canEditSteps); + editStepButton.setEnabled(canEditSteps); + duplicateStepButton.setEnabled(canEditSteps); + + playButton.setEnabled(!rec && !play && hasActions); + renameButton.setEnabled(!rec && !play && viewedRecording != null); + deleteScriptButton.setEnabled(!rec && !play && viewedRecording != null); + + scriptSelector.setEnabled(!rec); + } + + private void refreshSelector() + { + suppressSelectorEvents = true; + try + { + String prev = viewedRecording != null ? viewedRecording.getName() : CURRENT_LABEL; + scriptSelector.removeAllItems(); + scriptSelector.addItem(CURRENT_LABEL); + savedRecordings = plugin.listRecordings(); + for (Recording r : savedRecordings) + { + scriptSelector.addItem(r.getName()); + } + boolean matched = false; + for (int i = 0; i < scriptSelector.getItemCount(); i++) + { + if (prev.equals(scriptSelector.getItemAt(i))) + { + scriptSelector.setSelectedIndex(i); + matched = true; + break; + } + } + if (!matched) + { + scriptSelector.setSelectedIndex(0); + viewedRecording = null; + } + } + finally + { + suppressSelectorEvents = false; + } + } + + private void onSelectorChanged() + { + int idx = scriptSelector.getSelectedIndex(); + if (idx <= 0) + { + viewedRecording = null; + } + else + { + int savedIdx = idx - 1; + if (savedIdx >= 0 && savedIdx < savedRecordings.size()) + { + viewedRecording = savedRecordings.get(savedIdx); + } + } + reloadActionList(); + updateButtonEnabled(plugin.isRecording(), plugin.isPlaying()); + } + + private Recording getSelectedRecording() + { + if (viewedRecording != null) + { + return viewedRecording; + } + if (plugin.isRecording()) + { + return plugin.getCurrentRecording(); + } + return lastLiveRecording; + } + + private void reloadActionList() + { + int prevSelected = actionsList.getSelectedIndex(); + Recording r = getSelectedRecording(); + actionsModel.clear(); + if (r == null || r.getActions() == null || r.getActions().isEmpty()) + { + return; + } + for (int i = 0; i < r.getActions().size(); i++) + { + actionsModel.addElement(format(i, r.getActions().get(i))); + } + if (prevSelected >= 0 && prevSelected < actionsModel.size()) + { + actionsList.setSelectedIndex(prevSelected); + } + } + + public void onActionRecorded(RecordedAction action) + { + SwingUtilities.invokeLater(() -> + { + if (viewedRecording == null) + { + int idx = actionsModel.size(); + actionsModel.addElement(format(idx, action)); + actionsList.ensureIndexIsVisible(idx); + } + countLabel.setText("Captured: " + plugin.getCurrentRecordingSize() + " actions"); + countLabel.setForeground(MUTED); + }); + } + + private void onRecordClicked() + { + if (plugin.isRecording()) + { + Recording saved = plugin.stopRecording(true); + if (saved != null) + { + lastLiveRecording = saved; + } + viewedRecording = null; + refresh(); + } + else + { + actionsModel.clear(); + viewedRecording = null; + lastLiveRecording = null; + plugin.startRecording(); + refresh(); + } + } + + private void onMoveUp() + { + Recording r = getSelectedRecording(); + if (r == null) + { + return; + } + int idx = actionsList.getSelectedIndex(); + if (idx <= 0) + { + return; + } + List actions = r.getActions(); + RecordedAction moved = actions.remove(idx); + actions.add(idx - 1, moved); + reloadActionList(); + actionsList.setSelectedIndex(idx - 1); + persistIfSaved(r); + } + + private void onMoveDown() + { + Recording r = getSelectedRecording(); + if (r == null) + { + return; + } + int idx = actionsList.getSelectedIndex(); + if (idx < 0 || idx >= r.getActions().size() - 1) + { + return; + } + List actions = r.getActions(); + RecordedAction moved = actions.remove(idx); + actions.add(idx + 1, moved); + reloadActionList(); + actionsList.setSelectedIndex(idx + 1); + persistIfSaved(r); + } + + private void onDeleteStep() + { + Recording r = getSelectedRecording(); + if (r == null) + { + return; + } + int[] selected = actionsList.getSelectedIndices(); + if (selected.length == 0) + { + return; + } + for (int i = selected.length - 1; i >= 0; i--) + { + if (selected[i] >= 0 && selected[i] < r.getActions().size()) + { + r.getActions().remove(selected[i]); + } + } + reloadActionList(); + persistIfSaved(r); + } + + private void onEditStep() + { + Recording r = getSelectedRecording(); + if (r == null) + { + return; + } + int idx = actionsList.getSelectedIndex(); + if (idx < 0 || idx >= r.getActions().size()) + { + return; + } + RecordedAction a = r.getActions().get(idx); + + int currentTicks = a.getDelayTicksBefore() != null ? a.getDelayTicksBefore() : 0; + SpinnerNumberModel spinnerModel = new SpinnerNumberModel(currentTicks, 0, 1000, 1); + JSpinner ticksSpinner = new JSpinner(spinnerModel); + + JPanel form = new JPanel(new GridBagLayout()); + GridBagConstraints gc = new GridBagConstraints(); + gc.insets = new Insets(4, 4, 4, 4); + gc.anchor = GridBagConstraints.WEST; + + gc.gridx = 0; + gc.gridy = 0; + form.add(new JLabel("Action:"), gc); + gc.gridx = 1; + form.add(new JLabel(a.describe()), gc); + + gc.gridx = 0; + gc.gridy = 1; + form.add(new JLabel("Delay before (ticks):"), gc); + gc.gridx = 1; + form.add(ticksSpinner, gc); + + int choice = JOptionPane.showConfirmDialog(this, form, "Edit step #" + (idx + 1), + JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); + if (choice != JOptionPane.OK_OPTION) + { + return; + } + + int newTicks = (Integer) ticksSpinner.getValue(); + a.setDelayTicksBefore(newTicks); + a.setDelayMsBefore(newTicks * 600L); + reloadActionList(); + actionsList.setSelectedIndex(idx); + persistIfSaved(r); + } + + private void onDuplicateStep() + { + Recording r = getSelectedRecording(); + if (r == null) + { + return; + } + int idx = actionsList.getSelectedIndex(); + if (idx < 0 || idx >= r.getActions().size()) + { + return; + } + RecordedAction copy = cloneAction(r.getActions().get(idx)); + r.getActions().add(idx + 1, copy); + reloadActionList(); + actionsList.setSelectedIndex(idx + 1); + persistIfSaved(r); + } + + private static RecordedAction cloneAction(RecordedAction src) + { + RecordedAction dst = new RecordedAction(); + dst.setDelayMsBefore(src.getDelayMsBefore()); + dst.setDelayTicksBefore(src.getDelayTicksBefore()); + dst.setMenuOption(src.getMenuOption()); + dst.setMenuTarget(src.getMenuTarget()); + dst.setMenuAction(src.getMenuAction()); + dst.setTargetType(src.getTargetType()); + dst.setIdentifier(src.getIdentifier()); + dst.setParam0(src.getParam0()); + dst.setParam1(src.getParam1()); + dst.setItemId(src.getItemId()); + dst.setTargetName(src.getTargetName()); + dst.setTargetId(src.getTargetId()); + dst.setCanvasX(src.getCanvasX()); + dst.setCanvasY(src.getCanvasY()); + return dst; + } + + private void persistIfSaved(Recording r) + { + if (r == null || viewedRecording == null) + { + return; + } + try + { + plugin.save(r); + } + catch (IOException ex) + { + JOptionPane.showMessageDialog(this, + "Save failed: " + ex.getMessage(), + "AIO AIO", JOptionPane.ERROR_MESSAGE); + } + } + + private void onPlay(boolean loop) + { + Recording r = getSelectedRecording(); + if (r == null || r.size() == 0) + { + return; + } + plugin.play(r, loop); + } + + private void onRename() + { + if (viewedRecording == null) + { + return; + } + String name = JOptionPane.showInputDialog(this, "New name:", viewedRecording.getName()); + if (name == null || name.trim().isEmpty()) + { + return; + } + try + { + plugin.rename(viewedRecording, name.trim()); + } + catch (IOException | IllegalArgumentException ex) + { + JOptionPane.showMessageDialog(this, + "Rename failed: " + ex.getMessage(), + "AIO AIO", JOptionPane.ERROR_MESSAGE); + } + refresh(); + } + + private void onDeleteScript() + { + if (viewedRecording == null) + { + return; + } + int choice = JOptionPane.showConfirmDialog(this, + "Delete recording '" + viewedRecording.getName() + "'?", + "AIO AIO", JOptionPane.OK_CANCEL_OPTION); + if (choice != JOptionPane.OK_OPTION) + { + return; + } + try + { + plugin.delete(viewedRecording); + } + catch (IOException ex) + { + JOptionPane.showMessageDialog(this, + "Delete failed: " + ex.getMessage(), + "AIO AIO", JOptionPane.ERROR_MESSAGE); + } + viewedRecording = null; + refresh(); + } + + private static String format(int idx, RecordedAction a) + { + Integer ticks = a.getDelayTicksBefore(); + String delay = ticks == null ? "—" : ticks + "t"; + return String.format("%03d %s (%s)", idx + 1, a.describe(), delay); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPlugin.java new file mode 100644 index 0000000000..904826b176 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPlugin.java @@ -0,0 +1,488 @@ +package net.runelite.client.plugins.microbot.actionreplay; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.inject.Provides; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Point; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.MenuOptionClicked; +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.microbot.Microbot; +import net.runelite.client.plugins.microbot.PluginConstants; +import net.runelite.client.plugins.microbot.actionreplay.model.RecordedAction; +import net.runelite.client.plugins.microbot.actionreplay.model.Recording; +import net.runelite.client.plugins.microbot.actionreplay.model.TargetType; +import net.runelite.client.RuneLite; +import net.runelite.client.ui.ClientToolbar; +import net.runelite.client.ui.NavigationButton; +import net.runelite.client.ui.overlay.OverlayManager; +import net.runelite.client.util.ImageUtil; + +import javax.imageio.ImageIO; +import javax.inject.Inject; +import java.awt.image.BufferedImage; +import java.awt.image.RenderedImage; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.stream.Stream; + +@Slf4j +@PluginDescriptor( + name = PluginConstants.DEFAULT_PREFIX + "AIO AIO", + description = "Record and replay sequences of in-game actions. 80/20 automation for one-off tasks.", + tags = {"microbot", "aio", "record", "replay", "macro", "automation"}, + authors = { "Red Bracket" }, + version = ActionReplayPlugin.version, + minClientVersion = "2.1.32", + iconUrl = "https://chsami.github.io/Microbot-Hub/ActionReplayPlugin/assets/icon.png", + cardUrl = "https://chsami.github.io/Microbot-Hub/ActionReplayPlugin/assets/card.png", + enabledByDefault = PluginConstants.DEFAULT_ENABLED, + isExternal = PluginConstants.IS_EXTERNAL +) +public class ActionReplayPlugin extends Plugin +{ + public static final String version = "1.0.0"; + + static final Path RECORDINGS_DIR = RuneLite.RUNELITE_DIR.toPath().resolve("microbot-recordings"); + private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); + + @Inject + private ActionReplayConfig config; + + @Inject + private ClientToolbar clientToolbar; + + @Inject + private OverlayManager overlayManager; + + @Inject + private ActionReplayOverlay overlay; + + @Inject + private ActionReplayScript script; + + @Getter + private volatile boolean recording = false; + @Getter + private volatile boolean playing = false; + + @Getter + private Recording currentRecording; + private long lastActionMs; + private int gameTickCounter; + private int lastActionTickCounter; + + private ActionReplayPanel panel; + private NavigationButton navButton; + + @Provides + ActionReplayConfig provideConfig(ConfigManager cm) + { + return cm.getConfig(ActionReplayConfig.class); + } + + @Override + protected void startUp() throws Exception + { + try + { + Files.createDirectories(RECORDINGS_DIR); + } + catch (IOException e) + { + throw new IllegalStateException("ActionReplay: could not create recordings dir " + RECORDINGS_DIR, e); + } + + panel = injector.getInstance(ActionReplayPanel.class); + panel.setPlugin(this); + panel.refresh(); + + BufferedImage icon = loadIcon(); + navButton = NavigationButton.builder() + .tooltip("AIO AIO") + .icon(icon) + .priority(7) + .panel(panel) + .build(); + clientToolbar.addNavigation(navButton); + + overlayManager.add(overlay); + } + + @Override + protected void shutDown() throws Exception + { + stopRecording(false); + stopPlayback(); + overlayManager.remove(overlay); + if (navButton != null) + { + clientToolbar.removeNavigation(navButton); + } + } + + @Subscribe + public void onGameTick(GameTick event) + { + if (recording) + { + gameTickCounter++; + } + } + + @Subscribe + public void onMenuOptionClicked(MenuOptionClicked e) + { + Recording rec = currentRecording; + if (!recording || rec == null || e.isConsumed()) + { + return; + } + + long now = System.currentTimeMillis(); + long gap = lastActionMs == 0 ? 0 : now - lastActionMs; + lastActionMs = now; + + int tickDelta = rec.size() == 0 ? 0 : Math.max(0, gameTickCounter - lastActionTickCounter); + lastActionTickCounter = gameTickCounter; + + RecordedAction a = new RecordedAction(); + a.setDelayMsBefore(gap); + a.setDelayTicksBefore(tickDelta); + a.setMenuOption(stripColorTags(e.getMenuOption())); + a.setMenuTarget(stripColorTags(e.getMenuTarget())); + a.setMenuAction(e.getMenuAction() == null ? null : e.getMenuAction().name()); + a.setTargetType(TargetType.fromMenuAction(e.getMenuAction())); + a.setIdentifier(e.getId()); + a.setParam0(e.getParam0()); + a.setParam1(e.getParam1()); + a.setItemId(e.getItemId()); + + Point mouse = Microbot.getClient().getMouseCanvasPosition(); + if (mouse != null) + { + a.setCanvasX(mouse.getX()); + a.setCanvasY(mouse.getY()); + } + + switch (a.getTargetType()) + { + case NPC: + if (e.getMenuEntry() != null && e.getMenuEntry().getNpc() != null) + { + a.setTargetName(e.getMenuEntry().getNpc().getName()); + a.setTargetId(e.getMenuEntry().getNpc().getId()); + } + break; + case GAME_OBJECT: + a.setTargetId(e.getId()); + a.setTargetName(stripColorTags(e.getMenuTarget())); + break; + case GROUND_ITEM: + a.setTargetId(e.getId()); + a.setTargetName(stripColorTags(e.getMenuTarget())); + break; + default: + break; + } + + rec.getActions().add(a); + if (panel != null) + { + panel.onActionRecorded(a); + } + } + + public void startRecording() + { + if (recording) + { + return; + } + long now = System.currentTimeMillis(); + currentRecording = new Recording(); + currentRecording.setCreatedAtEpochMs(now); + currentRecording.setLastUsedAtEpochMs(now); + currentRecording.setName("Recording"); + lastActionMs = 0; + gameTickCounter = 0; + lastActionTickCounter = 0; + recording = true; + log.info("ActionReplay: recording started"); + } + + public Recording stopRecording(boolean save) + { + if (!recording) + { + return null; + } + recording = false; + Recording r = currentRecording; + currentRecording = null; + if (r == null || r.size() == 0) + { + log.info("ActionReplay: recording stopped (empty, not saved)"); + if (panel != null) + { + panel.refresh(); + } + return null; + } + if (save) + { + enrichName(r); + r.setName(uniqueName(r.getName())); + try + { + save(r); + } + catch (IOException e) + { + throw new IllegalStateException("ActionReplay: failed to save recording " + r.getName(), e); + } + } + log.info("ActionReplay: recording stopped ({} actions)", r.size()); + if (panel != null) + { + panel.refresh(); + } + return r; + } + + public int getCurrentRecordingSize() + { + return currentRecording == null ? 0 : currentRecording.size(); + } + + public void play(Recording r, boolean loop) + { + if (playing) + { + return; + } + if (r == null || r.size() == 0) + { + log.warn("ActionReplay: recording is empty, nothing to play"); + return; + } + r.setLastUsedAtEpochMs(System.currentTimeMillis()); + Path savedFile = RECORDINGS_DIR.resolve(r.getName() + ".json"); + if (Files.exists(savedFile)) + { + try + { + save(r); + } + catch (IOException e) + { + log.warn("ActionReplay: failed to persist lastUsedAt for {}: {}", r.getName(), e.getMessage()); + } + } + playing = true; + if (panel != null) + { + panel.refresh(); + } + boolean started = script.play(r, config, loop, () -> + { + playing = false; + if (panel != null) + { + panel.refresh(); + } + log.info("ActionReplay: playback finished"); + }); + if (!started) + { + playing = false; + if (panel != null) + { + panel.refresh(); + } + } + } + + public void stopPlayback() + { + if (!playing) + { + return; + } + script.shutdown(); + playing = false; + if (panel != null) + { + panel.refresh(); + } + } + + public List listRecordings() + { + List out = new ArrayList<>(); + if (!Files.isDirectory(RECORDINGS_DIR)) + { + return out; + } + try (Stream files = Files.list(RECORDINGS_DIR)) + { + files.filter(p -> p.getFileName().toString().endsWith(".json")) + .forEach(p -> + { + try + { + String json = new String(Files.readAllBytes(p)); + Recording r = GSON.fromJson(json, Recording.class); + if (r != null) + { + if (r.getName() == null) + { + String fn = p.getFileName().toString(); + r.setName(fn.substring(0, fn.length() - 5)); + } + if (r.getLastUsedAtEpochMs() == 0) + { + r.setLastUsedAtEpochMs(Files.getLastModifiedTime(p).toMillis()); + } + out.add(r); + } + } + catch (IOException ex) + { + log.warn("ActionReplay: failed to read {}: {}", p, ex.getMessage()); + } + }); + } + catch (IOException e) + { + throw new IllegalStateException("ActionReplay: failed to list recordings in " + RECORDINGS_DIR, e); + } + out.sort(Comparator.comparingLong(Recording::getLastUsedAtEpochMs).reversed()); + return out; + } + + public void save(Recording r) throws IOException + { + Files.createDirectories(RECORDINGS_DIR); + Path file = RECORDINGS_DIR.resolve(r.getName() + ".json"); + Files.write(file, GSON.toJson(r).getBytes()); + log.info("ActionReplay: saved {}", file); + } + + public void rename(Recording r, String newName) throws IOException + { + String clean = sanitize(newName); + if (clean.isEmpty()) + { + throw new IllegalArgumentException("ActionReplay: recording name cannot be empty"); + } + Path oldFile = RECORDINGS_DIR.resolve(r.getName() + ".json"); + Path newFile = RECORDINGS_DIR.resolve(clean + ".json"); + r.setName(clean); + Files.write(newFile, GSON.toJson(r).getBytes()); + if (!oldFile.equals(newFile) && Files.exists(oldFile)) + { + Files.delete(oldFile); + } + } + + public void delete(Recording r) throws IOException + { + Path file = RECORDINGS_DIR.resolve(r.getName() + ".json"); + Files.deleteIfExists(file); + } + + private static String sanitize(String s) + { + if (s == null) + { + return ""; + } + return s.replaceAll("[^a-zA-Z0-9_.-]", "_").trim(); + } + + private void enrichName(Recording r) + { + if (r == null || r.size() == 0) + { + return; + } + RecordedAction first = r.getActions().get(0); + String verb = sanitize(first.getMenuOption()); + String target = sanitize(first.getTargetName() != null ? first.getTargetName() : first.getMenuTarget()); + StringBuilder name = new StringBuilder(); + if (!verb.isEmpty()) + { + name.append(verb); + } + if (!target.isEmpty()) + { + if (name.length() > 0) + { + name.append("-"); + } + name.append(target); + } + if (name.length() > 0) + { + r.setName(name.toString()); + } + } + + private String uniqueName(String base) + { + if (base == null || base.isEmpty()) + { + base = "Recording"; + } + String candidate = base; + int n = 2; + while (Files.exists(RECORDINGS_DIR.resolve(candidate + ".json"))) + { + candidate = base + "-" + n; + n++; + } + return candidate; + } + + private static String stripColorTags(String s) + { + if (s == null) + { + return null; + } + return s.replaceAll("<[^>]+>", ""); + } + + private BufferedImage loadIcon() + { + try + { + return ImageUtil.loadImageResource(ActionReplayPlugin.class, "panel_icon.png"); + } + catch (Exception ex) + { + BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); + java.awt.Graphics2D g = img.createGraphics(); + try + { + g.setColor(new java.awt.Color(220, 40, 40)); + g.fillOval(2, 2, 12, 12); + } + finally + { + g.dispose(); + } + return img; + } + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayScript.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayScript.java new file mode 100644 index 0000000000..f493e6b14e --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayScript.java @@ -0,0 +1,328 @@ +package net.runelite.client.plugins.microbot.actionreplay; + +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.MenuAction; +import net.runelite.api.gameval.InterfaceID; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.actionreplay.model.RecordedAction; +import net.runelite.client.plugins.microbot.actionreplay.model.Recording; +import net.runelite.client.plugins.microbot.actionreplay.model.TargetType; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileitem.models.Rs2TileItemModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; +import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; +import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; +import net.runelite.client.plugins.microbot.util.menu.NewMenuEntry; +import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; + +import java.awt.Rectangle; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +@Slf4j +public class ActionReplayScript extends Script +{ + private static final int PLAYBACK_SPEED_PERCENT = 100; + private static final int MIN_STEP_DELAY_MS = 600; + private static final boolean SKIP_MISSING_TARGETS = true; + private static final int TARGET_LOOKUP_RADIUS = 20; + + private final AtomicBoolean abortFlag = new AtomicBoolean(false); + private Recording recording; + private boolean loop; + private Runnable onFinished; + private int currentIndex; + private boolean priorNaturalMouse; + + public boolean play(Recording recording, ActionReplayConfig config, boolean loop, Runnable onFinished) + { + if (recording == null || recording.getActions() == null || recording.getActions().isEmpty()) + { + log.warn("ActionReplay: recording is empty, nothing to play"); + return false; + } + this.recording = recording; + this.loop = loop; + this.onFinished = onFinished; + this.abortFlag.set(false); + + priorNaturalMouse = Rs2AntibanSettings.naturalMouse; + Rs2AntibanSettings.naturalMouse = true; + log.info("ActionReplay: enabling naturalMouse for playback (was {})", priorNaturalMouse); + + mainScheduledFuture = scheduledExecutorService.schedule(this::playbackLoop, 0, TimeUnit.MILLISECONDS); + return true; + } + + public int getCurrentIndex() + { + return currentIndex; + } + + @Override + public void shutdown() + { + abortFlag.set(true); + super.shutdown(); + } + + private void playbackLoop() + { + try + { + do + { + runOnce(); + } + while (loop && !abortFlag.get()); + } + catch (Exception e) + { + log.error("ActionReplay playback failed", e); + } + finally + { + Rs2AntibanSettings.naturalMouse = priorNaturalMouse; + Runnable cb = onFinished; + onFinished = null; + if (cb != null) + { + cb.run(); + } + } + } + + private void runOnce() + { + for (int i = 0; i < recording.getActions().size(); i++) + { + if (abortFlag.get()) + { + return; + } + currentIndex = i; + RecordedAction action = recording.getActions().get(i); + + Integer ticks = action.getDelayTicksBefore(); + long delayMs; + if (ticks != null) + { + delayMs = ticks * 600L; + } + else + { + delayMs = Math.max(MIN_STEP_DELAY_MS, action.getDelayMsBefore()); + } + long scaled = (delayMs * 100L) / PLAYBACK_SPEED_PERCENT; + if (scaled > 0) + { + sleep((int) scaled); + } + + if (!Microbot.isLoggedIn()) + { + log.warn("ActionReplay: not logged in, aborting playback"); + return; + } + + boolean ok = executeStep(action); + if (!ok) + { + if (SKIP_MISSING_TARGETS) + { + log.warn("ActionReplay: skipping step #{} ({})", i, action.describe()); + } + else + { + log.warn("ActionReplay: aborting playback at step #{} ({})", i, action.describe()); + return; + } + } + } + } + + private boolean executeStep(RecordedAction a) + { + TargetType type = a.getTargetType(); + if (type == null) + { + type = TargetType.UNKNOWN; + } + + String option = a.getMenuOption(); + log.debug("ActionReplay: step {} {} (id={}, type={})", option, a.getTargetName(), a.getTargetId(), type); + + switch (type) + { + case NPC: + return replayNpc(a); + case GAME_OBJECT: + return replayGameObject(a); + case GROUND_ITEM: + return replayGroundItem(a); + case WIDGET: + case WALK: + case PLAYER: + case UNKNOWN: + default: + return replayRaw(a); + } + } + + private boolean replayNpc(RecordedAction a) + { + Rs2NpcModel match = null; + if (a.getTargetId() != null) + { + match = Microbot.getRs2NpcCache().query() + .withId(a.getTargetId()) + .nearest(TARGET_LOOKUP_RADIUS); + } + if (match == null && a.getTargetName() != null) + { + match = Microbot.getRs2NpcCache().query() + .withName(a.getTargetName()) + .nearest(TARGET_LOOKUP_RADIUS); + } + if (match == null) + { + return false; + } + return Rs2Npc.interact(match.getId(), a.getMenuOption()); + } + + private boolean replayGameObject(RecordedAction a) + { + Rs2TileObjectModel match = null; + if (a.getTargetId() != null) + { + match = Microbot.getRs2TileObjectCache().query() + .withId(a.getTargetId()) + .nearest(TARGET_LOOKUP_RADIUS); + } + if (match == null && a.getTargetName() != null) + { + match = Microbot.getRs2TileObjectCache().query() + .withName(a.getTargetName()) + .nearest(TARGET_LOOKUP_RADIUS); + } + if (match == null) + { + return Rs2GameObject.interact(a.getIdentifier(), a.getMenuOption()); + } + return match.click(a.getMenuOption()); + } + + private boolean replayGroundItem(RecordedAction a) + { + if (a.getItemId() != 0) + { + Rs2TileItemModel match = Microbot.getRs2TileItemCache().query() + .withId(a.getItemId()) + .nearest(TARGET_LOOKUP_RADIUS); + if (match == null) + { + return false; + } + return Rs2GroundItem.loot(a.getItemId(), TARGET_LOOKUP_RADIUS); + } + if (a.getTargetName() != null) + { + return Rs2GroundItem.loot(a.getTargetName(), TARGET_LOOKUP_RADIUS); + } + return false; + } + + private boolean replayRaw(RecordedAction a) + { + MenuAction ma = parseMenuAction(a.getMenuAction()); + if (ma == null) + { + log.warn("ActionReplay: unknown MenuAction '{}', cannot replay raw step", a.getMenuAction()); + return false; + } + + int param1 = a.getParam1(); + + if (param1 > 0 && !Rs2Widget.isWidgetVisible(param1)) + { + log.warn("ActionReplay: widget {} not visible, skipping '{}'", param1, a.describe()); + return false; + } + + // Inventory item actions → delegate to Rs2Inventory.interact. It resolves the + // correct inventory widget (normal/bank/deposit/GE/shop), finds the item's + // current slot + bounds, and invokes with the correct params. Replaying a + // stored NewMenuEntry directly is fragile: stale canvas coords mean the + // physical click may land on an empty slot, at which point no MenuEntryAdded + // fires and MicrobotPlugin's targetMenu injection silently no-ops. + if (a.getItemId() > 0 && param1 > 0 && (param1 >>> 16) == InterfaceID.INVENTORY + && a.getMenuOption() != null && !a.getMenuOption().isEmpty()) + { + if (!Rs2Inventory.hasItem(a.getItemId())) + { + log.warn("ActionReplay: item {} not in inventory, skipping '{}'", a.getItemId(), a.describe()); + return false; + } + return Rs2Inventory.interact(a.getItemId(), a.getMenuOption()); + } + + String target = a.getMenuTarget() != null ? a.getMenuTarget() : ""; + NewMenuEntry entry = new NewMenuEntry( + a.getMenuOption(), + target, + a.getIdentifier(), + ma, + a.getParam0(), + param1, + false + ); + entry.setItemId(a.getItemId()); + try + { + Rectangle rect = buildClickRect(a); + Microbot.doInvoke(entry, rect); + return true; + } + catch (Exception e) + { + log.warn("ActionReplay: doInvoke failed for step {}: {}", a.describe(), e.getMessage()); + return false; + } + } + + private Rectangle buildClickRect(RecordedAction a) + { + if (a.getCanvasX() > 0 && a.getCanvasY() > 0) + { + return new Rectangle(a.getCanvasX() - 2, a.getCanvasY() - 2, 4, 4); + } + return new Rectangle(1, 1); + } + + private MenuAction parseMenuAction(String s) + { + if (s == null || s.isEmpty()) + { + return null; + } + try + { + return MenuAction.valueOf(s); + } + catch (IllegalArgumentException ex) + { + return null; + } + } + + public boolean isPlaying() + { + return mainScheduledFuture != null && !mainScheduledFuture.isDone() && !abortFlag.get(); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/RecordedAction.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/RecordedAction.java new file mode 100644 index 0000000000..a5ca697ab8 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/RecordedAction.java @@ -0,0 +1,32 @@ +package net.runelite.client.plugins.microbot.actionreplay.model; + +import lombok.Data; + +@Data +public class RecordedAction +{ + private long delayMsBefore; + private Integer delayTicksBefore; + private String menuOption; + private String menuTarget; + private String menuAction; + private TargetType targetType; + private int identifier; + private int param0; + private int param1; + private int itemId; + private String targetName; + private Integer targetId; + private int canvasX; + private int canvasY; + + public String describe() + { + String target = targetName != null ? targetName : menuTarget; + if (target == null || target.isEmpty()) + { + return menuOption; + } + return menuOption + " → " + target; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/Recording.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/Recording.java new file mode 100644 index 0000000000..17ad91d564 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/Recording.java @@ -0,0 +1,23 @@ +package net.runelite.client.plugins.microbot.actionreplay.model; + +import lombok.Data; + +import java.util.ArrayList; +import java.util.List; + +@Data +public class Recording +{ + public static final int CURRENT_VERSION = 1; + + private int version = CURRENT_VERSION; + private String name; + private long createdAtEpochMs; + private long lastUsedAtEpochMs; + private List actions = new ArrayList<>(); + + public int size() + { + return actions == null ? 0 : actions.size(); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/TargetType.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/TargetType.java new file mode 100644 index 0000000000..56b3d3a1cc --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/TargetType.java @@ -0,0 +1,71 @@ +package net.runelite.client.plugins.microbot.actionreplay.model; + +import net.runelite.api.MenuAction; + +public enum TargetType +{ + NPC, + GAME_OBJECT, + GROUND_ITEM, + PLAYER, + WIDGET, + WALK, + UNKNOWN; + + public static TargetType fromMenuAction(MenuAction action) + { + if (action == null) + { + return UNKNOWN; + } + switch (action) + { + case NPC_FIRST_OPTION: + case NPC_SECOND_OPTION: + case NPC_THIRD_OPTION: + case NPC_FOURTH_OPTION: + case NPC_FIFTH_OPTION: + case EXAMINE_NPC: + return NPC; + case GAME_OBJECT_FIRST_OPTION: + case GAME_OBJECT_SECOND_OPTION: + case GAME_OBJECT_THIRD_OPTION: + case GAME_OBJECT_FOURTH_OPTION: + case GAME_OBJECT_FIFTH_OPTION: + case EXAMINE_OBJECT: + return GAME_OBJECT; + case GROUND_ITEM_FIRST_OPTION: + case GROUND_ITEM_SECOND_OPTION: + case GROUND_ITEM_THIRD_OPTION: + case GROUND_ITEM_FOURTH_OPTION: + case GROUND_ITEM_FIFTH_OPTION: + case EXAMINE_ITEM_GROUND: + return GROUND_ITEM; + case PLAYER_FIRST_OPTION: + case PLAYER_SECOND_OPTION: + case PLAYER_THIRD_OPTION: + case PLAYER_FOURTH_OPTION: + case PLAYER_FIFTH_OPTION: + case PLAYER_SIXTH_OPTION: + case PLAYER_SEVENTH_OPTION: + case PLAYER_EIGHTH_OPTION: + return PLAYER; + case WALK: + return WALK; + case CC_OP: + case CC_OP_LOW_PRIORITY: + case WIDGET_TYPE_1: + case WIDGET_TYPE_4: + case WIDGET_TYPE_5: + case WIDGET_TARGET: + case WIDGET_TARGET_ON_GAME_OBJECT: + case WIDGET_TARGET_ON_GROUND_ITEM: + case WIDGET_TARGET_ON_NPC: + case WIDGET_TARGET_ON_PLAYER: + case WIDGET_TARGET_ON_WIDGET: + return WIDGET; + default: + return UNKNOWN; + } + } +} diff --git a/src/main/resources/net/runelite/client/plugins/microbot/actionreplay/docs/README.md b/src/main/resources/net/runelite/client/plugins/microbot/actionreplay/docs/README.md new file mode 100644 index 0000000000..01c3be6482 --- /dev/null +++ b/src/main/resources/net/runelite/client/plugins/microbot/actionreplay/docs/README.md @@ -0,0 +1,28 @@ +# AIO AIO — Record & Replay + +Record a sequence of in-game menu actions, then play it back on demand or on loop. 80/20 automation for one-off tasks. + +## What it does + +- Captures `MenuOptionClicked` events (NPC/object/ground-item interactions, inventory actions, widget clicks) +- Saves recordings as JSON under `~/.runelite/microbot-recordings/` +- Replays them with proper tick pacing; for inventory item actions it delegates to `Rs2Inventory.interact()` so stale slot positions don't cause misses +- For NPCs/objects/ground items it re-resolves the target by id/name each run (via the queryable cache) — your recording keeps working even if the target moves or respawns + +## Usage + +1. Enable the plugin, open the AIO AIO side panel +2. Click **Record script**, perform the actions in-game, click **Stop** when done +3. The recording shows in the **Script:** dropdown (sorted by last recorded/played time) +4. Select it, hit **Run script** — loops until you hit **Stop script** + +## Editing a recording + +- Double-click or ✎ to edit a step's pre-delay (in ticks) +- ↑ / ↓ to reorder, ⧉ to duplicate, ✕ to delete +- ✎ Rename / 🗑 Delete on the whole script + +## Limits + +- Actions whose target can't be found at replay time are skipped (see logs) +- The playback loop enables `naturalMouse` while running and restores it on exit From 036b959da914e5deaa582ca467fec4d0bc4363df Mon Sep 17 00:00:00 2001 From: dginovker Date: Mon, 20 Apr 2026 15:33:40 -0700 Subject: [PATCH 59/95] feat(AIOAIO): conditional steps, editable actions, append-record --- .../plugins/microbot/PluginConstants.java | 1 + .../actionreplay/ActionReplayPanel.java | 356 +++++++++++++----- .../actionreplay/ActionReplayPlugin.java | 80 ++-- .../actionreplay/ActionReplayScript.java | 121 ++---- .../actionreplay/model/Condition.java | 128 +++++++ .../model/ConditionComparator.java | 7 + .../actionreplay/model/ConditionType.java | 9 + .../actionreplay/model/RecordedAction.java | 3 +- .../actionreplay/model/Recording.java | 4 - .../microbot/actionreplay/model/StatKind.java | 7 + 10 files changed, 493 insertions(+), 223 deletions(-) create mode 100644 src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/Condition.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/ConditionComparator.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/ConditionType.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/StatKind.java diff --git a/src/main/java/net/runelite/client/plugins/microbot/PluginConstants.java b/src/main/java/net/runelite/client/plugins/microbot/PluginConstants.java index b7bd5eed0d..ed2554033d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/PluginConstants.java +++ b/src/main/java/net/runelite/client/plugins/microbot/PluginConstants.java @@ -36,6 +36,7 @@ private PluginConstants() public static final String BIGL = "[BL] "; public static final String PERT = "[P] "; public static final String DV = "[DV] "; + public static final String RED_BRACKET = "[RB] "; public static final boolean DEFAULT_ENABLED = false; public static final boolean IS_EXTERNAL = true; //test diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPanel.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPanel.java index ffd6e36b79..7620da1443 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPanel.java +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPanel.java @@ -1,7 +1,12 @@ package net.runelite.client.plugins.microbot.actionreplay; +import net.runelite.client.plugins.microbot.actionreplay.model.Condition; +import net.runelite.client.plugins.microbot.actionreplay.model.ConditionComparator; +import net.runelite.client.plugins.microbot.actionreplay.model.ConditionType; import net.runelite.client.plugins.microbot.actionreplay.model.RecordedAction; import net.runelite.client.plugins.microbot.actionreplay.model.Recording; +import net.runelite.client.plugins.microbot.actionreplay.model.StatKind; +import net.runelite.client.plugins.microbot.actionreplay.model.TargetType; import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.FontManager; import net.runelite.client.ui.PluginPanel; @@ -18,11 +23,13 @@ import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JSpinner; +import javax.swing.JTextField; import javax.swing.ScrollPaneConstants; import javax.swing.SpinnerNumberModel; import javax.swing.SwingUtilities; import javax.swing.border.EmptyBorder; import java.awt.BorderLayout; +import java.awt.CardLayout; import java.awt.Color; import java.awt.Component; import java.awt.Dimension; @@ -39,12 +46,13 @@ public class ActionReplayPanel extends PluginPanel { private static final Color MUTED = new Color(160, 160, 160); - private static final String CURRENT_LABEL = "(Current recording)"; + private static final String NO_SCRIPTS_PLACEHOLDER = "(no scripts)"; private ActionReplayPlugin plugin; private final JLabel countLabel = new JLabel(" "); private final JButton recordButton = new JButton("● Record script"); + private final JButton appendButton = new JButton("⊕"); private final JButton stopPlaybackButton = new JButton("■ Stop script"); private final JComboBox scriptSelector = new JComboBox<>(); @@ -55,13 +63,11 @@ public class ActionReplayPanel extends PluginPanel private final JButton downButton = new JButton("↓"); private final JButton deleteStepButton = new JButton("✕"); private final JButton editStepButton = new JButton("✎"); - private final JButton duplicateStepButton = new JButton("⧉"); private final JButton playButton = new JButton("▶ Run script"); private final JButton renameButton = new JButton("✎ Rename"); private final JButton deleteScriptButton = new JButton("🗑 Delete"); private Recording viewedRecording; - private Recording lastLiveRecording; private List savedRecordings = new ArrayList<>(); private boolean suppressSelectorEvents = false; @@ -102,7 +108,7 @@ private JPanel buildHeader() playButton.setAlignmentX(Component.LEFT_ALIGNMENT); playButton.setForeground(Color.WHITE); playButton.setToolTipText("Loop selected script until stopped"); - playButton.addActionListener(e -> onPlay(true)); + playButton.addActionListener(e -> onPlay()); stopPlaybackButton.setAlignmentX(Component.LEFT_ALIGNMENT); stopPlaybackButton.setForeground(Color.WHITE); @@ -186,9 +192,9 @@ public void mouseClicked(MouseEvent e) editStepButton.addActionListener(e -> onEditStep()); editRow.add(editStepButton); - initSmallButton(duplicateStepButton, "Duplicate selected step"); - duplicateStepButton.addActionListener(e -> onDuplicateStep()); - editRow.add(duplicateStepButton); + initSmallButton(appendButton, "Append more actions to the selected script"); + appendButton.addActionListener(e -> onAppendClicked()); + editRow.add(appendButton); initSmallButton(deleteStepButton, "Delete selected step(s)"); deleteStepButton.addActionListener(e -> onDeleteStep()); @@ -237,36 +243,30 @@ public void refresh() { boolean rec = plugin.isRecording(); boolean play = plugin.isPlaying(); + boolean appending = plugin.isAppendingToExisting(); if (rec) { - recordButton.setText("■ Stop recording"); countLabel.setText("Captured: " + plugin.getCurrentRecordingSize() + " actions"); countLabel.setForeground(MUTED); } else { - recordButton.setText("● Record script"); countLabel.setText(" "); } - recordButton.setEnabled(!play); - stopPlaybackButton.setEnabled(play); - if (rec) - { - viewedRecording = null; - } + stopPlaybackButton.setEnabled(play); refreshSelector(); reloadActionList(); - updateButtonEnabled(rec, play); + updateButtonEnabled(rec, play, appending); revalidate(); repaint(); }); } - private void updateButtonEnabled(boolean rec, boolean play) + private void updateButtonEnabled(boolean rec, boolean play, boolean appending) { Recording selected = getSelectedRecording(); boolean hasActions = selected != null && selected.size() > 0; @@ -276,13 +276,34 @@ private void updateButtonEnabled(boolean rec, boolean play) downButton.setEnabled(canEditSteps); deleteStepButton.setEnabled(canEditSteps); editStepButton.setEnabled(canEditSteps); - duplicateStepButton.setEnabled(canEditSteps); playButton.setEnabled(!rec && !play && hasActions); renameButton.setEnabled(!rec && !play && viewedRecording != null); deleteScriptButton.setEnabled(!rec && !play && viewedRecording != null); - scriptSelector.setEnabled(!rec); + scriptSelector.setEnabled(!rec && !savedRecordings.isEmpty()); + + if (rec && !appending) + { + recordButton.setText("■ Stop recording"); + recordButton.setEnabled(true); + appendButton.setText("⊕"); + appendButton.setEnabled(false); + } + else if (rec) + { + recordButton.setText("● Record script"); + recordButton.setEnabled(false); + appendButton.setText("■"); + appendButton.setEnabled(true); + } + else + { + recordButton.setText("● Record script"); + recordButton.setEnabled(!play); + appendButton.setText("⊕"); + appendButton.setEnabled(!play && viewedRecording != null); + } } private void refreshSelector() @@ -290,29 +311,37 @@ private void refreshSelector() suppressSelectorEvents = true; try { - String prev = viewedRecording != null ? viewedRecording.getName() : CURRENT_LABEL; - scriptSelector.removeAllItems(); - scriptSelector.addItem(CURRENT_LABEL); savedRecordings = plugin.listRecordings(); + scriptSelector.removeAllItems(); + + if (savedRecordings.isEmpty()) + { + scriptSelector.addItem(NO_SCRIPTS_PLACEHOLDER); + scriptSelector.setSelectedIndex(0); + viewedRecording = null; + return; + } + for (Recording r : savedRecordings) { scriptSelector.addItem(r.getName()); } - boolean matched = false; - for (int i = 0; i < scriptSelector.getItemCount(); i++) + + String targetName = viewedRecording != null ? viewedRecording.getName() : null; + int selectedIdx = 0; + if (targetName != null) { - if (prev.equals(scriptSelector.getItemAt(i))) + for (int i = 0; i < savedRecordings.size(); i++) { - scriptSelector.setSelectedIndex(i); - matched = true; - break; + if (targetName.equals(savedRecordings.get(i).getName())) + { + selectedIdx = i; + break; + } } } - if (!matched) - { - scriptSelector.setSelectedIndex(0); - viewedRecording = null; - } + scriptSelector.setSelectedIndex(selectedIdx); + viewedRecording = savedRecordings.get(selectedIdx); } finally { @@ -323,33 +352,25 @@ private void refreshSelector() private void onSelectorChanged() { int idx = scriptSelector.getSelectedIndex(); - if (idx <= 0) + if (idx >= 0 && idx < savedRecordings.size()) { - viewedRecording = null; + viewedRecording = savedRecordings.get(idx); } else { - int savedIdx = idx - 1; - if (savedIdx >= 0 && savedIdx < savedRecordings.size()) - { - viewedRecording = savedRecordings.get(savedIdx); - } + viewedRecording = null; } reloadActionList(); - updateButtonEnabled(plugin.isRecording(), plugin.isPlaying()); + updateButtonEnabled(plugin.isRecording(), plugin.isPlaying(), plugin.isAppendingToExisting()); } private Recording getSelectedRecording() { - if (viewedRecording != null) - { - return viewedRecording; - } if (plugin.isRecording()) { return plugin.getCurrentRecording(); } - return lastLiveRecording; + return viewedRecording; } private void reloadActionList() @@ -375,7 +396,7 @@ public void onActionRecorded(RecordedAction action) { SwingUtilities.invokeLater(() -> { - if (viewedRecording == null) + if (plugin.isRecording()) { int idx = actionsModel.size(); actionsModel.addElement(format(idx, action)); @@ -393,17 +414,33 @@ private void onRecordClicked() Recording saved = plugin.stopRecording(true); if (saved != null) { - lastLiveRecording = saved; + viewedRecording = saved; } - viewedRecording = null; refresh(); } else { actionsModel.clear(); viewedRecording = null; - lastLiveRecording = null; - plugin.startRecording(); + plugin.startRecording(null); + refresh(); + } + } + + private void onAppendClicked() + { + if (plugin.isRecording()) + { + plugin.stopRecording(true); + refresh(); + } + else + { + if (viewedRecording == null) + { + return; + } + plugin.startRecording(viewedRecording); refresh(); } } @@ -489,75 +526,205 @@ private void onEditStep() SpinnerNumberModel spinnerModel = new SpinnerNumberModel(currentTicks, 0, 1000, 1); JSpinner ticksSpinner = new JSpinner(spinnerModel); + String[] typeOptions = {"None", "HP", "Prayer", "NPC nearby", "Object nearby", "Inventory"}; + JComboBox typeCombo = new JComboBox<>(typeOptions); + JComboBox statCmpCombo = new JComboBox<>(new String[]{"below", "above"}); + JSpinner statSpinner = new JSpinner(new SpinnerNumberModel(20, 0, 9999, 1)); + JTextField npcNameField = new JTextField(15); + JComboBox npcPresentCombo = new JComboBox<>(new String[]{"present", "absent"}); + JTextField objNameField = new JTextField(15); + JComboBox objPresentCombo = new JComboBox<>(new String[]{"present", "absent"}); + JTextField invNameField = new JTextField(15); + JSpinner invCountSpinner = new JSpinner(new SpinnerNumberModel(1, 1, 9999, 1)); + JComboBox invPresentCombo = new JComboBox<>(new String[]{"present", "absent"}); + + Condition existing = a.getCondition(); + if (existing != null && existing.getType() != null) + { + switch (existing.getType()) + { + case STAT: + typeCombo.setSelectedItem(existing.getStat() == StatKind.PRAYER ? "Prayer" : "HP"); + statCmpCombo.setSelectedItem(existing.getComparator() == ConditionComparator.ABOVE ? "above" : "below"); + if (existing.getThreshold() != null) statSpinner.setValue(existing.getThreshold()); + break; + case NPC_NEARBY: + typeCombo.setSelectedItem("NPC nearby"); + if (existing.getName() != null) npcNameField.setText(existing.getName()); + npcPresentCombo.setSelectedItem(Boolean.FALSE.equals(existing.getPresent()) ? "absent" : "present"); + break; + case OBJECT_NEARBY: + typeCombo.setSelectedItem("Object nearby"); + if (existing.getName() != null) objNameField.setText(existing.getName()); + objPresentCombo.setSelectedItem(Boolean.FALSE.equals(existing.getPresent()) ? "absent" : "present"); + break; + case INVENTORY: + typeCombo.setSelectedItem("Inventory"); + if (existing.getName() != null) invNameField.setText(existing.getName()); + if (existing.getMinCount() != null && existing.getMinCount() > 0) invCountSpinner.setValue(existing.getMinCount()); + invPresentCombo.setSelectedItem(Boolean.FALSE.equals(existing.getPresent()) ? "absent" : "present"); + break; + } + } + + JPanel statPanel = hBox(statCmpCombo, statSpinner); + JPanel npcPanel = hBox(new JLabel("name:"), npcNameField, npcPresentCombo); + JPanel objPanel = hBox(new JLabel("name:"), objNameField, objPresentCombo); + JPanel invPanel = hBox(new JLabel("item:"), invNameField, new JLabel("min:"), invCountSpinner, invPresentCombo); + + CardLayout cardLayout = new CardLayout(); + JPanel cards = new JPanel(cardLayout); + cards.add(new JPanel(), "None"); + cards.add(statPanel, "Stat"); + cards.add(npcPanel, "NPC nearby"); + cards.add(objPanel, "Object nearby"); + cards.add(invPanel, "Inventory"); + + Runnable showCard = () -> + { + String sel = (String) typeCombo.getSelectedItem(); + String card; + if ("HP".equals(sel) || "Prayer".equals(sel)) card = "Stat"; + else if (sel == null || "None".equals(sel)) card = "None"; + else card = sel; + cardLayout.show(cards, card); + }; + typeCombo.addActionListener(e -> showCard.run()); + showCard.run(); + JPanel form = new JPanel(new GridBagLayout()); GridBagConstraints gc = new GridBagConstraints(); gc.insets = new Insets(4, 4, 4, 4); gc.anchor = GridBagConstraints.WEST; gc.gridx = 0; + JTextField verbField = new JTextField(a.getMenuOption() == null ? "" : a.getMenuOption(), 12); + JTextField targetField = new JTextField(a.getMenuTarget() == null ? "" : a.getMenuTarget(), 15); + gc.gridy = 0; form.add(new JLabel("Action:"), gc); gc.gridx = 1; - form.add(new JLabel(a.describe()), gc); + form.add(verbField, gc); gc.gridx = 0; gc.gridy = 1; + form.add(new JLabel("Target:"), gc); + gc.gridx = 1; + form.add(targetField, gc); + + gc.gridx = 0; + gc.gridy = 2; form.add(new JLabel("Delay before (ticks):"), gc); gc.gridx = 1; form.add(ticksSpinner, gc); - int choice = JOptionPane.showConfirmDialog(this, form, "Edit step #" + (idx + 1), - JOptionPane.OK_CANCEL_OPTION, JOptionPane.PLAIN_MESSAGE); - if (choice != JOptionPane.OK_OPTION) + gc.gridx = 0; + gc.gridy = 3; + form.add(new JLabel("Condition:"), gc); + gc.gridx = 1; + form.add(typeCombo, gc); + + gc.gridx = 0; + gc.gridy = 4; + gc.gridwidth = 2; + form.add(cards, gc); + + Object[] options = {"OK", "Delete", "Cancel"}; + int choice = JOptionPane.showOptionDialog(this, form, "Edit step #" + (idx + 1), + JOptionPane.DEFAULT_OPTION, JOptionPane.PLAIN_MESSAGE, null, options, options[0]); + if (choice == 1) + { + r.getActions().remove(idx); + reloadActionList(); + persistIfSaved(r); + return; + } + if (choice != 0) { return; } int newTicks = (Integer) ticksSpinner.getValue(); a.setDelayTicksBefore(newTicks); - a.setDelayMsBefore(newTicks * 600L); + String newVerb = verbField.getText().trim(); + a.setMenuOption(newVerb.isEmpty() ? null : newVerb); + String newTarget = targetField.getText().trim(); + a.setMenuTarget(newTarget.isEmpty() ? null : newTarget); + TargetType tt = a.getTargetType(); + if (tt == TargetType.NPC || tt == TargetType.GAME_OBJECT || tt == TargetType.GROUND_ITEM) + { + a.setTargetName(newTarget.isEmpty() ? null : newTarget); + } + a.setCondition(buildCondition(typeCombo, statCmpCombo, statSpinner, npcNameField, npcPresentCombo, + objNameField, objPresentCombo, invNameField, invCountSpinner, invPresentCombo)); reloadActionList(); actionsList.setSelectedIndex(idx); persistIfSaved(r); } - private void onDuplicateStep() + private static JPanel hBox(Component... components) { - Recording r = getSelectedRecording(); - if (r == null) + JPanel p = new JPanel(); + p.setLayout(new BoxLayout(p, BoxLayout.X_AXIS)); + for (int i = 0; i < components.length; i++) { - return; + if (i > 0) p.add(Box.createHorizontalStrut(4)); + p.add(components[i]); } - int idx = actionsList.getSelectedIndex(); - if (idx < 0 || idx >= r.getActions().size()) - { - return; - } - RecordedAction copy = cloneAction(r.getActions().get(idx)); - r.getActions().add(idx + 1, copy); - reloadActionList(); - actionsList.setSelectedIndex(idx + 1); - persistIfSaved(r); + return p; } - private static RecordedAction cloneAction(RecordedAction src) + private static Condition buildCondition(JComboBox typeCombo, JComboBox statCmpCombo, JSpinner statSpinner, + JTextField npcNameField, JComboBox npcPresentCombo, + JTextField objNameField, JComboBox objPresentCombo, + JTextField invNameField, JSpinner invCountSpinner, JComboBox invPresentCombo) { - RecordedAction dst = new RecordedAction(); - dst.setDelayMsBefore(src.getDelayMsBefore()); - dst.setDelayTicksBefore(src.getDelayTicksBefore()); - dst.setMenuOption(src.getMenuOption()); - dst.setMenuTarget(src.getMenuTarget()); - dst.setMenuAction(src.getMenuAction()); - dst.setTargetType(src.getTargetType()); - dst.setIdentifier(src.getIdentifier()); - dst.setParam0(src.getParam0()); - dst.setParam1(src.getParam1()); - dst.setItemId(src.getItemId()); - dst.setTargetName(src.getTargetName()); - dst.setTargetId(src.getTargetId()); - dst.setCanvasX(src.getCanvasX()); - dst.setCanvasY(src.getCanvasY()); - return dst; + String sel = (String) typeCombo.getSelectedItem(); + if (sel == null || "None".equals(sel)) + { + return null; + } + Condition c = new Condition(); + switch (sel) + { + case "HP": + case "Prayer": + c.setType(ConditionType.STAT); + c.setStat("Prayer".equals(sel) ? StatKind.PRAYER : StatKind.HEALTH); + c.setComparator("above".equals(statCmpCombo.getSelectedItem()) ? ConditionComparator.ABOVE : ConditionComparator.BELOW); + c.setThreshold((Integer) statSpinner.getValue()); + return c; + case "NPC nearby": + { + String nm = npcNameField.getText().trim(); + if (nm.isEmpty()) return null; + c.setType(ConditionType.NPC_NEARBY); + c.setName(nm); + c.setPresent(!"absent".equals(npcPresentCombo.getSelectedItem())); + return c; + } + case "Object nearby": + { + String nm = objNameField.getText().trim(); + if (nm.isEmpty()) return null; + c.setType(ConditionType.OBJECT_NEARBY); + c.setName(nm); + c.setPresent(!"absent".equals(objPresentCombo.getSelectedItem())); + return c; + } + case "Inventory": + { + String nm = invNameField.getText().trim(); + if (nm.isEmpty()) return null; + c.setType(ConditionType.INVENTORY); + c.setName(nm); + c.setMinCount((Integer) invCountSpinner.getValue()); + c.setPresent(!"absent".equals(invPresentCombo.getSelectedItem())); + return c; + } + default: + return null; + } } private void persistIfSaved(Recording r) @@ -578,14 +745,14 @@ private void persistIfSaved(Recording r) } } - private void onPlay(boolean loop) + private void onPlay() { Recording r = getSelectedRecording(); if (r == null || r.size() == 0) { return; } - plugin.play(r, loop); + plugin.play(r); } private void onRename() @@ -643,6 +810,15 @@ private static String format(int idx, RecordedAction a) { Integer ticks = a.getDelayTicksBefore(); String delay = ticks == null ? "—" : ticks + "t"; - return String.format("%03d %s (%s)", idx + 1, a.describe(), delay); + String prefix = ""; + if (a.getCondition() != null) + { + String desc = a.getCondition().describe(); + if (!desc.isEmpty()) + { + prefix = "[" + desc + "] "; + } + } + return prefix + a.describe() + " (" + delay + ")"; } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPlugin.java index 904826b176..b6193a5989 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPlugin.java @@ -21,12 +21,9 @@ import net.runelite.client.ui.ClientToolbar; import net.runelite.client.ui.NavigationButton; import net.runelite.client.ui.overlay.OverlayManager; -import net.runelite.client.util.ImageUtil; -import javax.imageio.ImageIO; import javax.inject.Inject; import java.awt.image.BufferedImage; -import java.awt.image.RenderedImage; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -37,7 +34,7 @@ @Slf4j @PluginDescriptor( - name = PluginConstants.DEFAULT_PREFIX + "AIO AIO", + name = PluginConstants.RED_BRACKET + "AIO AIO", description = "Record and replay sequences of in-game actions. 80/20 automation for one-off tasks.", tags = {"microbot", "aio", "record", "replay", "macro", "automation"}, authors = { "Red Bracket" }, @@ -50,7 +47,7 @@ ) public class ActionReplayPlugin extends Plugin { - public static final String version = "1.0.0"; + public static final String version = "1.1.0"; static final Path RECORDINGS_DIR = RuneLite.RUNELITE_DIR.toPath().resolve("microbot-recordings"); private static final Gson GSON = new GsonBuilder().setPrettyPrinting().create(); @@ -77,7 +74,8 @@ public class ActionReplayPlugin extends Plugin @Getter private Recording currentRecording; - private long lastActionMs; + @Getter + private boolean appendingToExisting; private int gameTickCounter; private int lastActionTickCounter; @@ -106,10 +104,9 @@ protected void startUp() throws Exception panel.setPlugin(this); panel.refresh(); - BufferedImage icon = loadIcon(); navButton = NavigationButton.builder() .tooltip("AIO AIO") - .icon(icon) + .icon(buildIcon()) .priority(7) .panel(panel) .build(); @@ -148,15 +145,10 @@ public void onMenuOptionClicked(MenuOptionClicked e) return; } - long now = System.currentTimeMillis(); - long gap = lastActionMs == 0 ? 0 : now - lastActionMs; - lastActionMs = now; - int tickDelta = rec.size() == 0 ? 0 : Math.max(0, gameTickCounter - lastActionTickCounter); lastActionTickCounter = gameTickCounter; RecordedAction a = new RecordedAction(); - a.setDelayMsBefore(gap); a.setDelayTicksBefore(tickDelta); a.setMenuOption(stripColorTags(e.getMenuOption())); a.setMenuTarget(stripColorTags(e.getMenuTarget())); @@ -180,15 +172,10 @@ public void onMenuOptionClicked(MenuOptionClicked e) if (e.getMenuEntry() != null && e.getMenuEntry().getNpc() != null) { a.setTargetName(e.getMenuEntry().getNpc().getName()); - a.setTargetId(e.getMenuEntry().getNpc().getId()); } break; case GAME_OBJECT: - a.setTargetId(e.getId()); - a.setTargetName(stripColorTags(e.getMenuTarget())); - break; case GROUND_ITEM: - a.setTargetId(e.getId()); a.setTargetName(stripColorTags(e.getMenuTarget())); break; default: @@ -202,22 +189,29 @@ public void onMenuOptionClicked(MenuOptionClicked e) } } - public void startRecording() + public void startRecording(Recording existing) { if (recording) { return; } - long now = System.currentTimeMillis(); - currentRecording = new Recording(); - currentRecording.setCreatedAtEpochMs(now); - currentRecording.setLastUsedAtEpochMs(now); - currentRecording.setName("Recording"); - lastActionMs = 0; + if (existing != null) + { + currentRecording = existing; + appendingToExisting = true; + log.info("ActionReplay: recording started (appending to '{}')", existing.getName()); + } + else + { + currentRecording = new Recording(); + currentRecording.setLastUsedAtEpochMs(System.currentTimeMillis()); + currentRecording.setName("Recording"); + appendingToExisting = false; + log.info("ActionReplay: recording started (new)"); + } gameTickCounter = 0; lastActionTickCounter = 0; recording = true; - log.info("ActionReplay: recording started"); } public Recording stopRecording(boolean save) @@ -240,8 +234,11 @@ public Recording stopRecording(boolean save) } if (save) { - enrichName(r); - r.setName(uniqueName(r.getName())); + if (!appendingToExisting) + { + enrichName(r); + r.setName(uniqueName(r.getName())); + } try { save(r); @@ -264,7 +261,7 @@ public int getCurrentRecordingSize() return currentRecording == null ? 0 : currentRecording.size(); } - public void play(Recording r, boolean loop) + public void play(Recording r) { if (playing) { @@ -293,7 +290,7 @@ public void play(Recording r, boolean loop) { panel.refresh(); } - boolean started = script.play(r, config, loop, () -> + boolean started = script.play(r, () -> { playing = false; if (panel != null) @@ -463,26 +460,19 @@ private static String stripColorTags(String s) return s.replaceAll("<[^>]+>", ""); } - private BufferedImage loadIcon() + private static BufferedImage buildIcon() { + BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); + java.awt.Graphics2D g = img.createGraphics(); try { - return ImageUtil.loadImageResource(ActionReplayPlugin.class, "panel_icon.png"); + g.setColor(new java.awt.Color(220, 40, 40)); + g.fillOval(2, 2, 12, 12); } - catch (Exception ex) + finally { - BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); - java.awt.Graphics2D g = img.createGraphics(); - try - { - g.setColor(new java.awt.Color(220, 40, 40)); - g.fillOval(2, 2, 12, 12); - } - finally - { - g.dispose(); - } - return img; + g.dispose(); } + return img; } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayScript.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayScript.java index f493e6b14e..6309aba8af 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayScript.java @@ -9,13 +9,10 @@ import net.runelite.client.plugins.microbot.actionreplay.model.Recording; import net.runelite.client.plugins.microbot.actionreplay.model.TargetType; import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; -import net.runelite.client.plugins.microbot.api.tileitem.models.Rs2TileItemModel; import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; -import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; -import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.menu.NewMenuEntry; import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -27,19 +24,14 @@ @Slf4j public class ActionReplayScript extends Script { - private static final int PLAYBACK_SPEED_PERCENT = 100; - private static final int MIN_STEP_DELAY_MS = 600; - private static final boolean SKIP_MISSING_TARGETS = true; private static final int TARGET_LOOKUP_RADIUS = 20; private final AtomicBoolean abortFlag = new AtomicBoolean(false); private Recording recording; - private boolean loop; private Runnable onFinished; - private int currentIndex; private boolean priorNaturalMouse; - public boolean play(Recording recording, ActionReplayConfig config, boolean loop, Runnable onFinished) + public boolean play(Recording recording, Runnable onFinished) { if (recording == null || recording.getActions() == null || recording.getActions().isEmpty()) { @@ -47,7 +39,6 @@ public boolean play(Recording recording, ActionReplayConfig config, boolean loop return false; } this.recording = recording; - this.loop = loop; this.onFinished = onFinished; this.abortFlag.set(false); @@ -59,11 +50,6 @@ public boolean play(Recording recording, ActionReplayConfig config, boolean loop return true; } - public int getCurrentIndex() - { - return currentIndex; - } - @Override public void shutdown() { @@ -75,11 +61,10 @@ private void playbackLoop() { try { - do + while (!abortFlag.get()) { runOnce(); } - while (loop && !abortFlag.get()); } catch (Exception e) { @@ -105,23 +90,12 @@ private void runOnce() { return; } - currentIndex = i; RecordedAction action = recording.getActions().get(i); Integer ticks = action.getDelayTicksBefore(); - long delayMs; - if (ticks != null) - { - delayMs = ticks * 600L; - } - else - { - delayMs = Math.max(MIN_STEP_DELAY_MS, action.getDelayMsBefore()); - } - long scaled = (delayMs * 100L) / PLAYBACK_SPEED_PERCENT; - if (scaled > 0) + if (ticks != null && ticks > 0) { - sleep((int) scaled); + sleep(ticks * 600); } if (!Microbot.isLoggedIn()) @@ -130,18 +104,16 @@ private void runOnce() return; } - boolean ok = executeStep(action); - if (!ok) + if (action.getCondition() != null && !action.getCondition().check()) { - if (SKIP_MISSING_TARGETS) - { - log.warn("ActionReplay: skipping step #{} ({})", i, action.describe()); - } - else - { - log.warn("ActionReplay: aborting playback at step #{} ({})", i, action.describe()); - return; - } + log.info("ActionReplay: skipping step #{} ({}) — condition '{}' false", + i, action.describe(), action.getCondition().describe()); + continue; + } + + if (!executeStep(action)) + { + log.warn("ActionReplay: skipping step #{} ({})", i, action.describe()); } } } @@ -155,7 +127,7 @@ private boolean executeStep(RecordedAction a) } String option = a.getMenuOption(); - log.debug("ActionReplay: step {} {} (id={}, type={})", option, a.getTargetName(), a.getTargetId(), type); + log.debug("ActionReplay: step {} {} (type={})", option, a.getTargetName(), type); switch (type) { @@ -176,19 +148,13 @@ private boolean executeStep(RecordedAction a) private boolean replayNpc(RecordedAction a) { - Rs2NpcModel match = null; - if (a.getTargetId() != null) + if (a.getTargetName() == null) { - match = Microbot.getRs2NpcCache().query() - .withId(a.getTargetId()) - .nearest(TARGET_LOOKUP_RADIUS); - } - if (match == null && a.getTargetName() != null) - { - match = Microbot.getRs2NpcCache().query() - .withName(a.getTargetName()) - .nearest(TARGET_LOOKUP_RADIUS); + return false; } + Rs2NpcModel match = Microbot.getRs2NpcCache().query() + .withName(a.getTargetName()) + .nearest(TARGET_LOOKUP_RADIUS); if (match == null) { return false; @@ -198,44 +164,27 @@ private boolean replayNpc(RecordedAction a) private boolean replayGameObject(RecordedAction a) { - Rs2TileObjectModel match = null; - if (a.getTargetId() != null) + if (a.getTargetName() == null) { - match = Microbot.getRs2TileObjectCache().query() - .withId(a.getTargetId()) - .nearest(TARGET_LOOKUP_RADIUS); - } - if (match == null && a.getTargetName() != null) - { - match = Microbot.getRs2TileObjectCache().query() - .withName(a.getTargetName()) - .nearest(TARGET_LOOKUP_RADIUS); + return false; } + Rs2TileObjectModel match = Microbot.getRs2TileObjectCache().query() + .withName(a.getTargetName()) + .nearest(TARGET_LOOKUP_RADIUS); if (match == null) { - return Rs2GameObject.interact(a.getIdentifier(), a.getMenuOption()); + return false; } return match.click(a.getMenuOption()); } private boolean replayGroundItem(RecordedAction a) { - if (a.getItemId() != 0) + if (a.getTargetName() == null) { - Rs2TileItemModel match = Microbot.getRs2TileItemCache().query() - .withId(a.getItemId()) - .nearest(TARGET_LOOKUP_RADIUS); - if (match == null) - { - return false; - } - return Rs2GroundItem.loot(a.getItemId(), TARGET_LOOKUP_RADIUS); - } - if (a.getTargetName() != null) - { - return Rs2GroundItem.loot(a.getTargetName(), TARGET_LOOKUP_RADIUS); + return false; } - return false; + return Rs2GroundItem.loot(a.getTargetName(), TARGET_LOOKUP_RADIUS); } private boolean replayRaw(RecordedAction a) @@ -261,15 +210,23 @@ private boolean replayRaw(RecordedAction a) // stored NewMenuEntry directly is fragile: stale canvas coords mean the // physical click may land on an empty slot, at which point no MenuEntryAdded // fires and MicrobotPlugin's targetMenu injection silently no-ops. + // Match by name (exact, case-insensitive) rather than id — stack-count + // variants (e.g. Coin pouch x1 vs x3) have different ids but the same name. if (a.getItemId() > 0 && param1 > 0 && (param1 >>> 16) == InterfaceID.INVENTORY && a.getMenuOption() != null && !a.getMenuOption().isEmpty()) { - if (!Rs2Inventory.hasItem(a.getItemId())) + String itemName = a.getMenuTarget(); + if (itemName == null || itemName.isEmpty()) + { + log.warn("ActionReplay: no item name for inventory step, skipping '{}'", a.describe()); + return false; + } + if (!Rs2Inventory.hasItem(itemName, true)) { - log.warn("ActionReplay: item {} not in inventory, skipping '{}'", a.getItemId(), a.describe()); + log.warn("ActionReplay: item '{}' not in inventory, skipping '{}'", itemName, a.describe()); return false; } - return Rs2Inventory.interact(a.getItemId(), a.getMenuOption()); + return Rs2Inventory.interact(itemName, a.getMenuOption(), true); } String target = a.getMenuTarget() != null ? a.getMenuTarget() : ""; diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/Condition.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/Condition.java new file mode 100644 index 0000000000..2befbdde85 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/Condition.java @@ -0,0 +1,128 @@ +package net.runelite.client.plugins.microbot.actionreplay.model; + +import lombok.Data; +import net.runelite.api.Skill; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; + +@Data +public class Condition +{ + public static final int NEARBY_RADIUS = 20; + + private ConditionType type; + private StatKind stat; + private ConditionComparator comparator; + private Integer threshold; + private String name; + private Boolean present; + private Integer minCount; + + public boolean check() + { + if (type == null) + { + return true; + } + switch (type) + { + case STAT: + return checkStat(); + case NPC_NEARBY: + return checkNpcNearby(); + case OBJECT_NEARBY: + return checkObjectNearby(); + case INVENTORY: + return checkInventory(); + default: + return true; + } + } + + private boolean checkStat() + { + if (stat == null || comparator == null || threshold == null) + { + return true; + } + Skill skill = stat == StatKind.HEALTH ? Skill.HITPOINTS : Skill.PRAYER; + int current = Rs2Player.getBoostedSkillLevel(skill); + return comparator == ConditionComparator.ABOVE ? current > threshold : current < threshold; + } + + private boolean checkNpcNearby() + { + if (name == null || name.isEmpty() || present == null) + { + return true; + } + boolean found = Microbot.getRs2NpcCache().query() + .withName(name) + .nearest(NEARBY_RADIUS) != null; + return found == present; + } + + private boolean checkObjectNearby() + { + if (name == null || name.isEmpty() || present == null) + { + return true; + } + boolean found = Microbot.getRs2TileObjectCache().query() + .withName(name) + .nearest(NEARBY_RADIUS) != null; + return found == present; + } + + private boolean checkInventory() + { + if (name == null || name.isEmpty() || present == null) + { + return true; + } + int need = minCount == null ? 1 : Math.max(1, minCount); + int have = Rs2Inventory.count(name, true); + boolean meets = have >= need; + return meets == present; + } + + public String describe() + { + if (type == null) + { + return ""; + } + switch (type) + { + case STAT: + if (stat == null || comparator == null || threshold == null) + { + return ""; + } + String sn = stat == StatKind.HEALTH ? "HP" : "Prayer"; + String op = comparator == ConditionComparator.ABOVE ? ">" : "<"; + return "if " + sn + op + threshold; + case NPC_NEARBY: + case OBJECT_NEARBY: + if (name == null || present == null) + { + return ""; + } + return "if " + (present ? "" : "no ") + name + " nearby"; + case INVENTORY: + if (name == null || present == null) + { + return ""; + } + int need = minCount == null ? 1 : Math.max(1, minCount); + if (present) + { + return "if inv has " + (need > 1 ? need + "x " : "") + name; + } + return "if inv lacks " + name; + default: + return ""; + } + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/ConditionComparator.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/ConditionComparator.java new file mode 100644 index 0000000000..8b6c7a14d8 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/ConditionComparator.java @@ -0,0 +1,7 @@ +package net.runelite.client.plugins.microbot.actionreplay.model; + +public enum ConditionComparator +{ + ABOVE, + BELOW +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/ConditionType.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/ConditionType.java new file mode 100644 index 0000000000..52380b9b75 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/ConditionType.java @@ -0,0 +1,9 @@ +package net.runelite.client.plugins.microbot.actionreplay.model; + +public enum ConditionType +{ + STAT, + NPC_NEARBY, + OBJECT_NEARBY, + INVENTORY +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/RecordedAction.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/RecordedAction.java index a5ca697ab8..20c1fa72c0 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/RecordedAction.java +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/RecordedAction.java @@ -5,7 +5,6 @@ @Data public class RecordedAction { - private long delayMsBefore; private Integer delayTicksBefore; private String menuOption; private String menuTarget; @@ -16,9 +15,9 @@ public class RecordedAction private int param1; private int itemId; private String targetName; - private Integer targetId; private int canvasX; private int canvasY; + private Condition condition; public String describe() { diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/Recording.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/Recording.java index 17ad91d564..aa3ad132e2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/Recording.java +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/Recording.java @@ -8,11 +8,7 @@ @Data public class Recording { - public static final int CURRENT_VERSION = 1; - - private int version = CURRENT_VERSION; private String name; - private long createdAtEpochMs; private long lastUsedAtEpochMs; private List actions = new ArrayList<>(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/StatKind.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/StatKind.java new file mode 100644 index 0000000000..ec025d8c78 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/StatKind.java @@ -0,0 +1,7 @@ +package net.runelite.client.plugins.microbot.actionreplay.model; + +public enum StatKind +{ + HEALTH, + PRAYER +} From b90c6d30fda572695fbde5fde2ff000dd6e0a9b5 Mon Sep 17 00:00:00 2001 From: dginovker Date: Tue, 21 Apr 2026 08:40:18 -0700 Subject: [PATCH 60/95] fix(AIOAIO): unstick replay, drop menuTarget duplication --- .../actionreplay/ActionReplayPanel.java | 10 +---- .../actionreplay/ActionReplayPlugin.java | 30 +++++-------- .../actionreplay/ActionReplayScript.java | 45 +++++++++---------- .../actionreplay/model/Condition.java | 4 +- .../actionreplay/model/RecordedAction.java | 6 +-- 5 files changed, 39 insertions(+), 56 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPanel.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPanel.java index 7620da1443..3e3e8193ef 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPanel.java +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPanel.java @@ -6,7 +6,6 @@ import net.runelite.client.plugins.microbot.actionreplay.model.RecordedAction; import net.runelite.client.plugins.microbot.actionreplay.model.Recording; import net.runelite.client.plugins.microbot.actionreplay.model.StatKind; -import net.runelite.client.plugins.microbot.actionreplay.model.TargetType; import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.FontManager; import net.runelite.client.ui.PluginPanel; @@ -599,7 +598,7 @@ private void onEditStep() gc.gridx = 0; JTextField verbField = new JTextField(a.getMenuOption() == null ? "" : a.getMenuOption(), 12); - JTextField targetField = new JTextField(a.getMenuTarget() == null ? "" : a.getMenuTarget(), 15); + JTextField targetField = new JTextField(a.getTargetName() == null ? "" : a.getTargetName(), 15); gc.gridy = 0; form.add(new JLabel("Action:"), gc); @@ -649,12 +648,7 @@ private void onEditStep() String newVerb = verbField.getText().trim(); a.setMenuOption(newVerb.isEmpty() ? null : newVerb); String newTarget = targetField.getText().trim(); - a.setMenuTarget(newTarget.isEmpty() ? null : newTarget); - TargetType tt = a.getTargetType(); - if (tt == TargetType.NPC || tt == TargetType.GAME_OBJECT || tt == TargetType.GROUND_ITEM) - { - a.setTargetName(newTarget.isEmpty() ? null : newTarget); - } + a.setTargetName(newTarget.isEmpty() ? null : newTarget); a.setCondition(buildCondition(typeCombo, statCmpCombo, statSpinner, npcNameField, npcPresentCombo, objNameField, objPresentCombo, invNameField, invCountSpinner, invPresentCombo)); reloadActionList(); diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPlugin.java index b6193a5989..7137d20fdc 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPlugin.java @@ -151,7 +151,7 @@ public void onMenuOptionClicked(MenuOptionClicked e) RecordedAction a = new RecordedAction(); a.setDelayTicksBefore(tickDelta); a.setMenuOption(stripColorTags(e.getMenuOption())); - a.setMenuTarget(stripColorTags(e.getMenuTarget())); + a.setTargetName(cleanTarget(e.getMenuTarget())); a.setMenuAction(e.getMenuAction() == null ? null : e.getMenuAction().name()); a.setTargetType(TargetType.fromMenuAction(e.getMenuAction())); a.setIdentifier(e.getId()); @@ -166,22 +166,6 @@ public void onMenuOptionClicked(MenuOptionClicked e) a.setCanvasY(mouse.getY()); } - switch (a.getTargetType()) - { - case NPC: - if (e.getMenuEntry() != null && e.getMenuEntry().getNpc() != null) - { - a.setTargetName(e.getMenuEntry().getNpc().getName()); - } - break; - case GAME_OBJECT: - case GROUND_ITEM: - a.setTargetName(stripColorTags(e.getMenuTarget())); - break; - default: - break; - } - rec.getActions().add(a); if (panel != null) { @@ -415,7 +399,7 @@ private void enrichName(Recording r) } RecordedAction first = r.getActions().get(0); String verb = sanitize(first.getMenuOption()); - String target = sanitize(first.getTargetName() != null ? first.getTargetName() : first.getMenuTarget()); + String target = sanitize(first.getTargetName()); StringBuilder name = new StringBuilder(); if (!verb.isEmpty()) { @@ -460,6 +444,16 @@ private static String stripColorTags(String s) return s.replaceAll("<[^>]+>", ""); } + private static String cleanTarget(String menuTarget) + { + String s = stripColorTags(menuTarget); + if (s == null) + { + return null; + } + return s.replaceAll("\\s*\\(level-\\d+\\)\\s*$", "").trim(); + } + private static BufferedImage buildIcon() { BufferedImage img = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB); diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayScript.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayScript.java index 6309aba8af..f42add63e3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayScript.java @@ -10,10 +10,10 @@ import net.runelite.client.plugins.microbot.actionreplay.model.TargetType; import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; -import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.menu.NewMenuEntry; +import net.runelite.client.plugins.microbot.util.misc.Rs2UiHelper; import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -29,7 +29,6 @@ public class ActionReplayScript extends Script private final AtomicBoolean abortFlag = new AtomicBoolean(false); private Recording recording; private Runnable onFinished; - private boolean priorNaturalMouse; public boolean play(Recording recording, Runnable onFinished) { @@ -42,9 +41,7 @@ public boolean play(Recording recording, Runnable onFinished) this.onFinished = onFinished; this.abortFlag.set(false); - priorNaturalMouse = Rs2AntibanSettings.naturalMouse; - Rs2AntibanSettings.naturalMouse = true; - log.info("ActionReplay: enabling naturalMouse for playback (was {})", priorNaturalMouse); + log.info("ActionReplay: starting playback of '{}' ({} actions)", recording.getName(), recording.size()); mainScheduledFuture = scheduledExecutorService.schedule(this::playbackLoop, 0, TimeUnit.MILLISECONDS); return true; @@ -66,13 +63,19 @@ private void playbackLoop() runOnce(); } } - catch (Exception e) + catch (RuntimeException e) { - log.error("ActionReplay playback failed", e); + if (e.getCause() instanceof InterruptedException || abortFlag.get()) + { + log.info("ActionReplay: playback stopped"); + } + else + { + log.error("ActionReplay playback failed", e); + } } finally { - Rs2AntibanSettings.naturalMouse = priorNaturalMouse; Runnable cb = onFinished; onFinished = null; if (cb != null) @@ -152,9 +155,9 @@ private boolean replayNpc(RecordedAction a) { return false; } - Rs2NpcModel match = Microbot.getRs2NpcCache().query() +Rs2NpcModel match = Microbot.getRs2NpcCache().query() .withName(a.getTargetName()) - .nearest(TARGET_LOOKUP_RADIUS); + .nearestOnClientThread(TARGET_LOOKUP_RADIUS); if (match == null) { return false; @@ -166,13 +169,16 @@ private boolean replayGameObject(RecordedAction a) { if (a.getTargetName() == null) { + log.warn("ActionReplay: no target name for game object step, skipping '{}'", a.describe()); return false; } - Rs2TileObjectModel match = Microbot.getRs2TileObjectCache().query() +Rs2TileObjectModel match = Microbot.getRs2TileObjectCache().query() .withName(a.getTargetName()) - .nearest(TARGET_LOOKUP_RADIUS); + .nearestOnClientThread(TARGET_LOOKUP_RADIUS); if (match == null) { + log.warn("ActionReplay: no '{}' within {} tiles, skipping '{}'", + a.getTargetName(), TARGET_LOOKUP_RADIUS, a.describe()); return false; } return match.click(a.getMenuOption()); @@ -198,24 +204,16 @@ private boolean replayRaw(RecordedAction a) int param1 = a.getParam1(); - if (param1 > 0 && !Rs2Widget.isWidgetVisible(param1)) + if ((param1 >>> 16) > 0 && !Rs2Widget.isWidgetVisible(param1)) { log.warn("ActionReplay: widget {} not visible, skipping '{}'", param1, a.describe()); return false; } - // Inventory item actions → delegate to Rs2Inventory.interact. It resolves the - // correct inventory widget (normal/bank/deposit/GE/shop), finds the item's - // current slot + bounds, and invokes with the correct params. Replaying a - // stored NewMenuEntry directly is fragile: stale canvas coords mean the - // physical click may land on an empty slot, at which point no MenuEntryAdded - // fires and MicrobotPlugin's targetMenu injection silently no-ops. - // Match by name (exact, case-insensitive) rather than id — stack-count - // variants (e.g. Coin pouch x1 vs x3) have different ids but the same name. if (a.getItemId() > 0 && param1 > 0 && (param1 >>> 16) == InterfaceID.INVENTORY && a.getMenuOption() != null && !a.getMenuOption().isEmpty()) { - String itemName = a.getMenuTarget(); + String itemName = a.getTargetName(); if (itemName == null || itemName.isEmpty()) { log.warn("ActionReplay: no item name for inventory step, skipping '{}'", a.describe()); @@ -229,10 +227,9 @@ private boolean replayRaw(RecordedAction a) return Rs2Inventory.interact(itemName, a.getMenuOption(), true); } - String target = a.getMenuTarget() != null ? a.getMenuTarget() : ""; NewMenuEntry entry = new NewMenuEntry( a.getMenuOption(), - target, + a.getTargetName() != null ? a.getTargetName() : "", a.getIdentifier(), ma, a.getParam0(), diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/Condition.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/Condition.java index 2befbdde85..dc6c826628 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/Condition.java +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/Condition.java @@ -59,7 +59,7 @@ private boolean checkNpcNearby() } boolean found = Microbot.getRs2NpcCache().query() .withName(name) - .nearest(NEARBY_RADIUS) != null; + .nearestOnClientThread(NEARBY_RADIUS) != null; return found == present; } @@ -71,7 +71,7 @@ private boolean checkObjectNearby() } boolean found = Microbot.getRs2TileObjectCache().query() .withName(name) - .nearest(NEARBY_RADIUS) != null; + .nearestOnClientThread(NEARBY_RADIUS) != null; return found == present; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/RecordedAction.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/RecordedAction.java index 20c1fa72c0..fce5cf58b0 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/RecordedAction.java +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/RecordedAction.java @@ -7,7 +7,6 @@ public class RecordedAction { private Integer delayTicksBefore; private String menuOption; - private String menuTarget; private String menuAction; private TargetType targetType; private int identifier; @@ -21,11 +20,10 @@ public class RecordedAction public String describe() { - String target = targetName != null ? targetName : menuTarget; - if (target == null || target.isEmpty()) + if (targetName == null || targetName.isEmpty()) { return menuOption; } - return menuOption + " → " + target; + return menuOption + " → " + targetName; } } From 3a3986607210c125db440ba49abd2ddc5b27b146 Mon Sep 17 00:00:00 2001 From: papakonnekt <42449974+papakonnekt@users.noreply.github.com> Date: Sun, 3 May 2026 15:41:41 -0400 Subject: [PATCH 61/95] Add Sisyphus: Infernal Pact plugin - Yama's Lair stepping-stone automation (#423) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix prayer bug and add trio mode to charge pillars (#408) * Add Sisyphus: Infernal Pact plugin — Yama stepping-stone automation Automates the Yama's Lair stepping-stone circuit for the Demonic Pacts League. Features: - Fixed waypoint route covering the full entry path and central circle - Fire NPC detection (ID 15608) with 2-tile exclusion radius via dual-source polling - Anti-backtracking via 3-stone history deque — always moves forward in the loop - Rapid 200-400ms scheduler for near-instant stone hops - Configurable stone count (default 666 for the Demonic Pacts task) - Respects Microbot break handler Author: Sisyphus (papakonnekt) --------- Co-authored-by: chsami Co-authored-by: JThomasDevs <95548936+JThomasDevs@users.noreply.github.com> --- .../HueycoatlPrayer/HueyPrayerConfig.java | 102 +++++- .../HueycoatlPrayer/HueyPrayerPlugin.java | 159 ++++++++- .../SisyphusInfernalPactConfig.java | 31 ++ .../SisyphusInfernalPactPlugin.java | 55 ++++ .../SisyphusInfernalPactScript.java | 64 ++++ .../engine/YamaEngine.java | 306 ++++++++++++++++++ .../sisyphusinfernalpact/docs/README.md | 35 ++ 7 files changed, 742 insertions(+), 10 deletions(-) create mode 100644 src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/SisyphusInfernalPactConfig.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/SisyphusInfernalPactPlugin.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/SisyphusInfernalPactScript.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/engine/YamaEngine.java create mode 100644 src/main/resources/net/runelite/client/plugins/microbot/sisyphusinfernalpact/docs/README.md diff --git a/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerConfig.java b/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerConfig.java index ea38412d39..134231e23d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerConfig.java @@ -1,14 +1,31 @@ package net.runelite.client.plugins.microbot.HueycoatlPrayer; import net.runelite.client.config.*; +import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; @ConfigGroup("hueyprayer") public interface HueyPrayerConfig extends Config { + @ConfigSection( + name = "General", + description = "Core Huey prayer behaviour", + position = 0 + ) + String generalSection = "general"; + + @ConfigSection( + name = "Trio mode", + description = "After a Huey projectile hits, set protection for pillar charging (fixed or autobalance)", + position = 1 + ) + String trioSection = "trio"; + @ConfigItem( keyName = "enabled", name = "Enable", - description = "Enable auto prayer" + description = "Enable auto prayer", + position = 0, + section = generalSection ) default boolean enabled() { @@ -18,7 +35,9 @@ default boolean enabled() @ConfigItem( keyName = "disableAfterImpact", name = "Disable after impact", - description = "When enabled, turns off protection prayer after the projectile hits (saves prayer). When disabled, prayers stay on until the next attack switches them." + description = "When enabled, turns off protection prayer after the projectile hits (saves prayer). When disabled, prayers stay on until the next attack switches them.", + position = 1, + section = generalSection ) default boolean disableAfterImpact() { @@ -28,10 +47,85 @@ default boolean disableAfterImpact() @ConfigItem( keyName = "debug", name = "Debug Projectiles", - description = "Print projectile IDs" + description = "Print projectile IDs", + position = 2, + section = generalSection ) default boolean debug() { return false; } -} \ No newline at end of file + + @ConfigItem( + keyName = "trioMode", + name = "Trio mode", + description = "While incoming Huey projectiles still use the correct protect vs type, after impact (requires Disable after impact) switches to your pillar role: Fixed or Autobalance protection from teammates' overheads.", + position = 0, + section = trioSection + ) + default boolean trioMode() + { + return false; + } + + @ConfigItem( + keyName = "trioRoleStyle", + name = "Role style", + description = "Fixed: always use the protection prayer below. Autobalance: among other players in radius, count melee / missiles / magic overheads — you pray the least-covered protection.", + position = 1, + section = trioSection + ) + default TrioRoleStyle trioRoleStyle() + { + return TrioRoleStyle.FIXED; + } + + @ConfigItem( + keyName = "trioFixedProtection", + name = "Fixed protection", + description = "Used when Role style is Fixed.", + position = 2, + section = trioSection + ) + default TrioFixedProtection trioFixedProtection() + { + return TrioFixedProtection.PROTECT_RANGE; + } + + @ConfigItem( + keyName = "trioRadius", + name = "Autobalance radius", + description = "Tiles from your tile to include other players when autobalancing (they must still be loaded).", + position = 3, + section = trioSection + ) + default int trioRadius() + { + return 15; + } + + enum TrioRoleStyle + { + FIXED, + AUTOBALANCE + } + + enum TrioFixedProtection + { + PROTECT_MELEE(Rs2PrayerEnum.PROTECT_MELEE), + PROTECT_RANGE(Rs2PrayerEnum.PROTECT_RANGE), + PROTECT_MAGIC(Rs2PrayerEnum.PROTECT_MAGIC); + + private final Rs2PrayerEnum prayer; + + TrioFixedProtection(Rs2PrayerEnum prayer) + { + this.prayer = prayer; + } + + public Rs2PrayerEnum getPrayer() + { + return prayer; + } + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerPlugin.java index 78a4ccfa89..b7ec7c9632 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/HueycoatlPrayer/HueyPrayerPlugin.java @@ -6,7 +6,11 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; +import net.runelite.api.HeadIcon; +import net.runelite.api.Player; import net.runelite.api.Projectile; +import net.runelite.api.WorldView; +import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.ProjectileMoved; @@ -35,7 +39,7 @@ ) public class HueyPrayerPlugin extends Plugin { - static final String VERSION = "1.0.4"; + static final String VERSION = "1.0.7"; @Inject private Client client; @@ -129,8 +133,7 @@ public void onProjectileMoved(ProjectileMoved event) incomingHueyProjectiles.remove(projectile); if (incomingHueyProjectiles.isEmpty()) { - Rs2Prayer.disableAllPrayers(); - currentPrayer = null; + onHueyProjectilePhaseEnded(); } } return; @@ -173,9 +176,16 @@ private void switchPrayer(Rs2PrayerEnum prayer) { int tick = client.getTickCount(); - // prevent spam + duplicate toggles - if (currentPrayer == prayer) return; - if (tick == lastSwitchTick) return; + if (Rs2Prayer.isPrayerActive(prayer)) + { + currentPrayer = prayer; + return; + } + + if (tick == lastSwitchTick) + { + return; + } Rs2Prayer.disableAllPrayers(); Rs2Prayer.toggle(prayer, true); @@ -183,4 +193,141 @@ private void switchPrayer(Rs2PrayerEnum prayer) currentPrayer = prayer; lastSwitchTick = tick; } + + /** + * Last Huey projectile toward us has landed — either clear prayers or switch to trio pillar protection. + */ + private void onHueyProjectilePhaseEnded() + { + if (config.trioMode()) + { + Rs2PrayerEnum pillar = resolveTrioProtectionPrayer(); + if (pillar != null) + { + switchPrayer(pillar); + } + return; + } + Rs2Prayer.disableAllPrayers(); + currentPrayer = null; + } + + /** + * Fixed or autobalance protection for pillar charging (after projectiles, not during). + */ + private Rs2PrayerEnum resolveTrioProtectionPrayer() + { + if (config.trioRoleStyle() == HueyPrayerConfig.TrioRoleStyle.FIXED) + { + return config.trioFixedProtection().getPrayer(); + } + return pickAutobalanceProtectionPrayer(); + } + + private Rs2PrayerEnum pickAutobalanceProtectionPrayer() + { + Player local = client.getLocalPlayer(); + if (local == null) + { + return null; + } + WorldPoint localPoint = local.getWorldLocation(); + WorldView worldView = client.getTopLevelWorldView(); + if (worldView == null) + { + return null; + } + + int melee = 0; + int ranged = 0; + int magic = 0; + int radius = config.trioRadius(); + if (radius < 1) + { + radius = 1; + } + + for (Player p : worldView.players()) + { + if (p == null) + { + continue; + } + if (p == local) + { + continue; + } + WorldPoint wp = p.getWorldLocation(); + if (wp == null) + { + continue; + } + if (wp.distanceTo(localPoint) > radius) + { + continue; + } + HeadIcon overhead = p.getOverheadIcon(); + if (overhead == HeadIcon.MELEE) + { + melee++; + } + else if (overhead == HeadIcon.RANGED) + { + ranged++; + } + else if (overhead == HeadIcon.MAGIC) + { + magic++; + } + } + + if (melee == 0) + { + if (ranged == 0) + { + if (magic == 0) + { + return protectionMatchingLocalOverhead(local); + } + } + } + + int minCount = melee; + if (ranged < minCount) + { + minCount = ranged; + } + if (magic < minCount) + { + minCount = magic; + } + + if (melee == minCount) + { + return Rs2PrayerEnum.PROTECT_MELEE; + } + if (ranged == minCount) + { + return Rs2PrayerEnum.PROTECT_RANGE; + } + return Rs2PrayerEnum.PROTECT_MAGIC; + } + + private static Rs2PrayerEnum protectionMatchingLocalOverhead(Player local) + { + HeadIcon overhead = local.getOverheadIcon(); + if (overhead == HeadIcon.MELEE) + { + return Rs2PrayerEnum.PROTECT_MELEE; + } + if (overhead == HeadIcon.RANGED) + { + return Rs2PrayerEnum.PROTECT_RANGE; + } + if (overhead == HeadIcon.MAGIC) + { + return Rs2PrayerEnum.PROTECT_MAGIC; + } + return Rs2PrayerEnum.PROTECT_MELEE; + } } \ No newline at end of file diff --git a/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/SisyphusInfernalPactConfig.java b/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/SisyphusInfernalPactConfig.java new file mode 100644 index 0000000000..b03c1c3b2b --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/SisyphusInfernalPactConfig.java @@ -0,0 +1,31 @@ +package net.runelite.client.plugins.microbot.sisyphusinfernalpact; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; +import net.runelite.client.config.ConfigSection; +import net.runelite.client.config.Range; + +/** + * Config for Sisyphus: Infernal Pact — Yama's Lair stepping-stone automation. + */ +@ConfigGroup("sisyphusinfernalpact") +public interface SisyphusInfernalPactConfig extends Config { + + @ConfigSection( + name = "Yama Stepping Stones", + description = "Configure the Infernal Pact stepping-stone runner", + position = 0 + ) + String yamaSection = "yamaSection"; + + @ConfigItem( + keyName = "yamaStonesToStep", + name = "Stones to Step", + description = "Total safe stones to hop before stopping. Default 666 for the Demonic Pacts task.", + position = 0, + section = yamaSection + ) + @Range(min = 1, max = 5000) + default int yamaStonesToStep() { return 666; } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/SisyphusInfernalPactPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/SisyphusInfernalPactPlugin.java new file mode 100644 index 0000000000..dbdc8d7082 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/SisyphusInfernalPactPlugin.java @@ -0,0 +1,55 @@ +package net.runelite.client.plugins.microbot.sisyphusinfernalpact; + +import com.google.inject.Provides; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.microbot.PluginConstants; + +import javax.inject.Inject; + +/** + * Sisyphus: Infernal Pact + * + * Standalone plugin that automates Yama's stepping-stone circuit. + * Enable near the first entry stone and it loops until the configured + * stone count is reached, never touching fire-laden tiles. + */ +@PluginDescriptor( + name = "[SIS] " + "Sisyphus: Infernal Pact", + description = "Automates Yama's stepping-stone circuit — avoids fire, loops the route, spam-clicks safe stones.", + tags = {"leagues", "sisyphus", "yama", "demonic", "pacts", "infernal", "stepping stones"}, + version = SisyphusInfernalPactPlugin.VERSION, + minClientVersion = "2.0.13", + isExternal = PluginConstants.IS_EXTERNAL, + enabledByDefault = PluginConstants.DEFAULT_ENABLED, + authors = {"Sisyphus"} +) +@Slf4j +public class SisyphusInfernalPactPlugin extends Plugin { + + public static final String VERSION = "1.0.0"; + + @Inject private SisyphusInfernalPactConfig config; + @Inject private SisyphusInfernalPactScript script; + + @Provides + SisyphusInfernalPactConfig provideConfig(ConfigManager configManager) { + return configManager.getConfig(SisyphusInfernalPactConfig.class); + } + + @Override + protected void startUp() throws Exception { + log.info("[Sisyphus: Infernal Pact] Starting v{}", VERSION); + if (!script.isRunning()) { + script.run(config); + } + } + + @Override + protected void shutDown() throws Exception { + script.shutdown(); + log.info("[Sisyphus: Infernal Pact] Stopped."); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/SisyphusInfernalPactScript.java b/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/SisyphusInfernalPactScript.java new file mode 100644 index 0000000000..267413496b --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/SisyphusInfernalPactScript.java @@ -0,0 +1,64 @@ +package net.runelite.client.plugins.microbot.sisyphusinfernalpact; + +import lombok.extern.slf4j.Slf4j; +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.breakhandler.BreakHandlerScript; +import net.runelite.client.plugins.microbot.sisyphusinfernalpact.engine.YamaEngine; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.ThreadLocalRandom; + +/** + * Execution loop for Sisyphus: Infernal Pact. + * Schedules the YamaEngine at 200-400 ms for near-instant stone hops. + */ +@Slf4j +public class SisyphusInfernalPactScript extends Script { + + private final YamaEngine yamaEngine = new YamaEngine(); + + public boolean run(SisyphusInfernalPactConfig config) { + Rs2Antiban.resetAntibanSettings(); + Rs2AntibanSettings.actionCooldownActive = false; + + mainScheduledFuture = scheduledExecutorService.schedule( + () -> scheduleNextTick(config), 0, TimeUnit.MILLISECONDS); + + return true; + } + + @Override + public void shutdown() { + super.shutdown(); + yamaEngine.reset(); + log.info("[Sisyphus: Infernal Pact] Script shutdown."); + } + + private void scheduleNextTick(SisyphusInfernalPactConfig config) { + int delayMs = ThreadLocalRandom.current().nextInt(200, 401); + + mainScheduledFuture = scheduledExecutorService.schedule(() -> { + try { + if (!super.run()) return; + if (!Microbot.isLoggedIn()) return; + if (BreakHandlerScript.isBreakActive()) return; + + if (!yamaEngine.tick(config)) { + log.info("[Sisyphus: Infernal Pact] Engine stopped — shutting down."); + shutdown(); + return; + } + + } catch (Exception e) { + log.error("[Sisyphus: Infernal Pact] Error in tick: {}", e.getMessage(), e); + } finally { + if (isRunning()) { + scheduleNextTick(config); + } + } + }, delayMs, TimeUnit.MILLISECONDS); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/engine/YamaEngine.java b/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/engine/YamaEngine.java new file mode 100644 index 0000000000..cfe03fb8aa --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/sisyphusinfernalpact/engine/YamaEngine.java @@ -0,0 +1,306 @@ +package net.runelite.client.plugins.microbot.sisyphusinfernalpact.engine; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.GroundObject; +import net.runelite.api.NPC; +import net.runelite.api.coords.WorldPoint; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.sisyphusinfernalpact.SisyphusInfernalPactConfig; +import net.runelite.client.plugins.microbot.util.gameobject.Rs2GameObject; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; + +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Deque; +import java.util.List; +import java.util.concurrent.ThreadLocalRandom; + +/** + * Yama's Lair stepping-stone engine — spam-click edition. + * + *
      + *
    • Only targets the 14 whitelisted stone waypoints.
    • + *
    • Skips any stone within {@value #FIRE_EXCLUSION_RADIUS} tiles of NPC + * ID {@value #FIRE_NPC_ID}.
    • + *
    • Never re-clicks the last {@value #RECENT_HISTORY} stones jumped to + * (prevents backtracking).
    • + *
    • No internal tick delay — fires as fast as the outer scheduler allows.
    • + *
    + */ +@Slf4j +public class YamaEngine { + + private static final int STONE_GROUND_ID = 13621; + private static final int FIRE_NPC_ID = 15608; + private static final int FIRE_EXCLUSION_RADIUS = 2; + private static final int STONE_SNAP_RADIUS = 2; + + /** How many recently-visited stones to remember (prevents backtracking). */ + private static final int RECENT_HISTORY = 3; + + private static final long JUMP_TIMEOUT_MS = 5_000; + + /** Fixed-coordinate loop — entry path followed by the circular loop. */ + private static final WorldPoint[] STONE_ROUTE = { + // ── Entry path ── + new WorldPoint(1486, 5598, 0), + new WorldPoint(1484, 5596, 0), + new WorldPoint(1481, 5597, 0), + new WorldPoint(1479, 5599, 0), + // ── Loop ── + new WorldPoint(1480, 5602, 0), + new WorldPoint(1478, 5602, 0), + new WorldPoint(1477, 5605, 0), + new WorldPoint(1479, 5605, 0), + new WorldPoint(1481, 5605, 0), + new WorldPoint(1481, 5608, 0), + new WorldPoint(1479, 5608, 0), + new WorldPoint(1477, 5608, 0), + new WorldPoint(1478, 5611, 0), + new WorldPoint(1480, 5611, 0), + }; + + public enum State { IDLE, SCANNING, JUMPING, COMPLETE, STUCK } + + @Getter private String status = "Idle"; + @Getter private boolean running = false; + @Getter private State state = State.IDLE; + + private int stonesJumped = 0; + private int targetStones = 666; + private long jumpStartTime = 0; + private WorldPoint lastPlayerLocation = null; + private int stuckCounter = 0; + + /** Rolling window of the last {@value #RECENT_HISTORY} stones jumped to. */ + private final Deque recentlyJumped = new ArrayDeque<>(); + + public void reset() { + status = "Idle"; + running = false; + state = State.IDLE; + stonesJumped = 0; + jumpStartTime = 0; + lastPlayerLocation = null; + stuckCounter = 0; + recentlyJumped.clear(); + } + + // ── Main tick ───────────────────────────────────────────────────────────── + + public boolean tick(SisyphusInfernalPactConfig config) { + running = true; + targetStones = config.yamaStonesToStep(); + + if (!Microbot.isLoggedIn()) { + status = "Waiting for login"; + return true; + } + + WorldPoint playerLocation = Rs2Player.getWorldLocation(); + if (playerLocation == null) { + status = "Locating player"; + return true; + } + + if (stonesJumped >= targetStones) { + state = State.COMPLETE; + status = "Complete — " + stonesJumped + "/" + targetStones + " stones"; + return true; + } + + // ── Detect landing from a jump ── + if (Rs2Player.isAnimating() || Rs2Player.isMoving()) { + if (state == State.JUMPING) { + if (lastPlayerLocation != null && !lastPlayerLocation.equals(playerLocation)) { + // We moved — record this tile in the history and increment counter + recordJump(playerLocation); + stonesJumped++; + status = "Jumped — " + stonesJumped + "/" + targetStones; + // No artificial delay — let the outer scheduler call us back + state = State.IDLE; + } else if (System.currentTimeMillis() - jumpStartTime > JUMP_TIMEOUT_MS) { + status = "Jump timed out — retrying"; + state = State.IDLE; + } else { + status = "Jumping... " + stonesJumped + "/" + targetStones; + } + } else { + status = "Moving — " + stonesJumped + "/" + targetStones; + } + lastPlayerLocation = playerLocation; + return true; + } + + // ── Player is still — find and click next safe stone immediately ── + state = State.SCANNING; + + List fireLocs = getAllFireLocations(); + Microbot.log("[Yama] fire=" + fireLocs.size() + + " recent=" + recentlyJumped.size() + + " jumped=" + stonesJumped + "/" + targetStones); + + GroundObject target = findBestSafeStone(playerLocation, fireLocs); + + if (target == null) { + stuckCounter++; + if (stuckCounter > 8) { + state = State.STUCK; + status = "Stuck — no safe route stone reachable"; + } else { + status = "Waiting for safe stone (fire=" + fireLocs.size() + ")"; + } + return true; + } + + stuckCounter = 0; + if (Rs2GameObject.interact(target)) { + state = State.JUMPING; + jumpStartTime = System.currentTimeMillis(); + lastPlayerLocation = playerLocation; + status = "→ " + target.getWorldLocation() + + " [" + stonesJumped + "/" + targetStones + "]"; + } else { + status = "Click failed — " + target.getWorldLocation(); + } + + return true; + } + + // ── Stone selection ─────────────────────────────────────────────────────── + + private GroundObject findBestSafeStone(WorldPoint playerLocation, List fireLocs) { + List allGround = getGroundObjects(); + + GroundObject best = null; + int bestDist = Integer.MAX_VALUE; + + for (WorldPoint waypoint : STONE_ROUTE) { + // Skip the tile the player is currently on + if (sameXY(waypoint, playerLocation)) continue; + + // Skip stones we jumped to recently (prevents backtracking) + if (isRecentlyJumped(waypoint)) continue; + + // Skip if within 2 tiles of any fire NPC + if (isNearFire(waypoint, fireLocs)) continue; + + // Require an actual GroundObject at this waypoint + GroundObject obj = findStoneNear(waypoint, allGround); + if (obj == null) continue; + + int dist = distance2D(waypoint, playerLocation); + if (dist < bestDist) { + bestDist = dist; + best = obj; + } + } + return best; + } + + private GroundObject findStoneNear(WorldPoint waypoint, List groundObjects) { + GroundObject closest = null; + int minDist = Integer.MAX_VALUE; + for (GroundObject obj : groundObjects) { + if (obj.getId() != STONE_GROUND_ID) continue; + int d = distance2D(obj.getWorldLocation(), waypoint); + if (d <= STONE_SNAP_RADIUS && d < minDist) { + minDist = d; + closest = obj; + } + } + return closest; + } + + private List getGroundObjects() { + try { + List list = Rs2GameObject.getGroundObjects(); + return list != null ? list : Collections.emptyList(); + } catch (Exception e) { + log.debug("[Yama] Error getting ground objects", e); + return Collections.emptyList(); + } + } + + // ── Recent-stone tracking ───────────────────────────────────────────────── + + private void recordJump(WorldPoint location) { + // Keep only the most recent RECENT_HISTORY positions + recentlyJumped.addFirst(location); + while (recentlyJumped.size() > RECENT_HISTORY) { + recentlyJumped.removeLast(); + } + } + + private boolean isRecentlyJumped(WorldPoint waypoint) { + for (WorldPoint p : recentlyJumped) { + if (sameXY(p, waypoint)) return true; + } + return false; + } + + // ── Fire NPC detection ──────────────────────────────────────────────────── + + /** + * Dual-source fire NPC location collection. + * Queries both the Microbot NPC cache (manual ID filter) and the raw + * {@code Client.getNpcs()} list so fast-spawning fire effects are never missed. + */ + private List getAllFireLocations() { + List locations = new ArrayList<>(); + + // Source 1: Microbot NPC cache + try { + List cached = Microbot.getRs2NpcCache().query().toList(); + if (cached != null) { + for (Rs2NpcModel npc : cached) { + if (npc != null && npc.getId() == FIRE_NPC_ID) { + WorldPoint loc = npc.getWorldLocation(); + if (loc != null) locations.add(loc); + } + } + } + } catch (Exception e) { + log.debug("[Yama] NPC cache error", e); + } + + // Source 2: Raw client NPC list + try { + List clientNpcs = Microbot.getClient().getNpcs(); + if (clientNpcs != null) { + for (NPC npc : clientNpcs) { + if (npc != null && npc.getId() == FIRE_NPC_ID) { + WorldPoint loc = npc.getWorldLocation(); + if (loc != null) locations.add(loc); + } + } + } + } catch (Exception e) { + log.debug("[Yama] Client NPC list error", e); + } + + return locations; + } + + private boolean isNearFire(WorldPoint target, List fireLocs) { + for (WorldPoint fire : fireLocs) { + if (distance2D(fire, target) <= FIRE_EXCLUSION_RADIUS) return true; + } + return false; + } + + // ── Helpers ─────────────────────────────────────────────────────────────── + + private static boolean sameXY(WorldPoint a, WorldPoint b) { + if (a == null || b == null) return false; + return a.getX() == b.getX() && a.getY() == b.getY(); + } + + private static int distance2D(WorldPoint a, WorldPoint b) { + if (a == null || b == null) return Integer.MAX_VALUE; + return Math.abs(a.getX() - b.getX()) + Math.abs(a.getY() - b.getY()); + } +} diff --git a/src/main/resources/net/runelite/client/plugins/microbot/sisyphusinfernalpact/docs/README.md b/src/main/resources/net/runelite/client/plugins/microbot/sisyphusinfernalpact/docs/README.md new file mode 100644 index 0000000000..9904ee9939 --- /dev/null +++ b/src/main/resources/net/runelite/client/plugins/microbot/sisyphusinfernalpact/docs/README.md @@ -0,0 +1,35 @@ +# Sisyphus: Infernal Pact + +Automates the **Yama's Lair stepping-stone circuit** in the Demonic Pacts League. + +## What it does + +- Navigates the full stepping-stone route through Yama's Lair +- **Never clicks a tile with fire on it** (checks NPC ID 15608 with a 2-tile exclusion radius) +- Loops the central stone circuit continuously until the configured stone count is reached +- Uses rapid-fire 200–400 ms click intervals for maximum efficiency +- Prevents backtracking by tracking the last 3 visited stones + +## Setup + +1. Log into your account and travel to Yama's Lair +2. Stand near the first entry stone at `(1486, 5598)` +3. Enable the plugin in the Microbot plugin list +4. Set **Stones to Step** to however many you need (default 666 for the Demonic Pacts task) +5. Click **Start** — the bot handles the rest + +## Settings + +| Setting | Default | Description | +|---|---|---| +| Stones to Step | 666 | Total safe stone hops before the plugin stops | + +## Notes + +- The plugin stops automatically once the configured stone count is reached +- Fire NPCs move — the bot re-checks every tick so it will never step on an active fire tile +- Works on a tight 200–400 ms scheduler (effectively 1-tick clicking) + +## Author + +Sisyphus From 5be535b4460b0688cafbdc918d73b3a1fe6e4f51 Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Sun, 3 May 2026 15:42:18 -0400 Subject: [PATCH 62/95] fix(MKE_Wintertodt): brazier behavior, snowfall-safe fletch, antiban hardening (#422) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(MKE_Wintertodt): brazier detection + non-blocking snowfall dodge The exact-equals worldLocation filter for brazier objects in analyzeGameState never matched live state (SE brazier at (1638,3997) vs constant (1639,3998)), so the brazier / brokenBrazier / burningBrazier slots were permanently null. Result: no light, no relight, no fix, and NPE on feed-click. Replace with a within-3 filter anchored on the player-stand tile (sides are 17 tiles apart so radius 3 still unambiguously selects the chosen side). dodgeSnowfallDamage's sleepUntilTrue (5s nominal) wedged the executor for ~140s in practice (confirmed via "avg loop time: 141100ms" in round logs), starving handleEating and nearly killing the player. Strip the wait — the next tick's priority blocks already handle a broken/unlit brazier. Make the whole dodge opt-in via new DodgeSnowfall config (default off) since most players just rely on food/potions. Also: drop the burningBrazier-not-null gate from FLETCH_LOGS fletch- start and knife-preselect (fletching needs knife + roots, not a lit brazier nearby), null-guard the BURN_LOGS feed click, only consume the round-start light priority flag when a brazier is *definitively* burning so cache lag at round start doesn't abandon it, and run handleEating before dodgeSnowfallDamage in maintenance. Bump 2.1.3 -> 2.1.7. * feat(MKE_Wintertodt): snowfall-safe fletch tile + maintenance-first priority Fletch on a tile one south of the brazier-stand tile (the same offset the dodge logic uses) so we stop tanking snowfall AoE for an entire root pile. Feeding/lighting/repair still walk back to the brazier. Promote brazier fix/relight to the top of performMaintenanceTasks, ahead of handleEating. When snowfall breaks the brazier, the repair window closes fast (other players race for it) and an unlit brazier means zero points until relit — both beat eating, which can wait a tick. The per-state priority blocks remain as a safety net. Bump version to 2.2.0 (new feature). * fix(MKE_Wintertodt): walk to fletch tile only on fresh post-chop entry Mid-fletch interruptions (brazier fix/light, eat, cold tick) don't change state, so resume fletching wherever the player happens to be standing instead of trekking back to the safe tile for a partial pile. A new one-shot `needFletchTileWalk` is armed by `changeState` only on entry into FLETCH_LOGS and cleared after the first walk. Drop "(priority over eating)" from the maintenance log label since the top-level handler also fires from CHOP_ROOTS / WAITING where there's no eat to defer. * fix(MKE_Wintertodt): antiban hardening + font-safe config icons Issues surfaced by a code review of the maintenance-priority changes: - handleBrazierMaintenance was firing a bare click() every 60ms while the repair animation ran (~30 clicks in 2s). Added a resetActions guard at the top + sleepGaussian(200, 150) before each click. - handleBurnLogsState fix-block had no pre-click delay, inconsistent with its own relight-block and every other priority block. Added sleepGaussian(200, 150). - Feed click was missing Rs2Antiban.actionCooldown(), so feeding bypassed the PlayStyle pause regime that chop/fletch already use. - Action Cooldown log line now prints the actual chance value. Config icons swapped to the Dingbats / Misc Symbols blocks, which render reliably in the JVM emoji font. The previous icons (kitchen knife / wrench, Pictographs block) silently dropped to tofu in the client panel. * fix(MKE_Wintertodt): drop pre-click delays in brazier emergency paths Removed five sleepGaussian(200, 150) calls (50-350ms) that fired before the fix/light click in handleBrazierMaintenance, handleFletchLogsState, and handleBurnLogsState. These pre-click waits cost the bot the repair race against other players when snowfall broke the brazier. deselectSelectedItem already has its own internal 80ms sleep and natural mouse adds organic humanization downstream, so the extra wait was pure dead time. Reaction now fires within ~1 tick (60ms) of detection plus mouse-travel time. * fix(MKE_Wintertodt): route nudge/hover/spam-click through natural mouse Five direct Microbot.getMouse().move(x, y) calls dispatched a teleport MOUSE_MOVED event with no path, visually obvious as a bot: - maybeNudgeMouse() fired after every chop/fletch/feed click - pre-round-start hover before the round timer hit zero - spam-click pre-hover during the wait-for-round phase Added moveCursorHumanized(int, int) and moveCursorHumanized(Rectangle) helpers that route through Microbot.naturalMouse.moveTo() when natural mouse is available, falling back to the direct move only when it isn't. The remaining no-arg Microbot.getMouse().click() in the spam path is fine — it reads the live canvas mouse position (where natural mouse just moved the cursor) and the click itself routes through the natural-mouse path via planMovementOrFallback. --------- Co-authored-by: runsonmypc --- .../mke_wintertodt/MKE_WintertodtConfig.java | 15 +- .../mke_wintertodt/MKE_WintertodtPlugin.java | 2 +- .../mke_wintertodt/MKE_WintertodtScript.java | 262 +++++++++++++----- .../mke_wintertodt/enums/Brazier.java | 8 +- 4 files changed, 216 insertions(+), 71 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtConfig.java b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtConfig.java index 81c9d41588..a6cb592fe5 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtConfig.java @@ -103,7 +103,7 @@ default boolean relightBrazier() { @ConfigItem( keyName = "FletchRoots", - name = "🔪 Fletch Roots to Kindling", + name = "➳ Fletch Roots to Kindling", description = "Convert bruma roots to kindling for fletching XP and more points", position = 2, section = generalSection @@ -114,7 +114,7 @@ default boolean fletchRoots() { @ConfigItem( keyName = "FixBrazier", - name = "🔧 Fix Broken Braziers", + name = "⚒️ Fix Broken Braziers", description = "Repair broken braziers with hammer", position = 3, section = generalSection @@ -123,6 +123,17 @@ default boolean fixBrazier() { return true; } + @ConfigItem( + keyName = "DodgeSnowfall", + name = "❄️ Dodge Snowfall", + description = "Step out of incoming snowfall AoE damage. Most players don't bother — eating food/potions is usually more efficient than interrupting your action to step.", + position = 4, + section = generalSection + ) + default boolean dodgeSnowfall() { + return false; + } + // ==================== HEALING METHOD ==================== @ConfigItem( keyName = "HealingMethod", diff --git a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtPlugin.java index 32a7ee709f..9ea9d770e2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtPlugin.java @@ -42,7 +42,7 @@ ) @Slf4j public class MKE_WintertodtPlugin extends Plugin { - static final String version = "2.1.3"; + static final String version = "2.2.0"; // Core plugin components @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java index cf36784a2c..4693ae819f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java @@ -151,6 +151,11 @@ public class MKE_WintertodtScript extends Script { // Flag to prioritize brazier lighting at round start private static boolean shouldPriorizeBrazierAtStart = false; + // One-shot: walk to the snowfall-safe fletch tile only on a fresh entry to + // FLETCH_LOGS (i.e. coming from chopping). Resumed fletches after a brazier + // fix/light or an eat tick don't count — we fletch in place. + private static boolean needFletchTileWalk = false; + // For overlay public static double historicalEstimateSecondsLeft = 0; @@ -906,6 +911,11 @@ private static void changeState(State newState, boolean lock) { if (state == State.FLETCH_LOGS) lastFletchingXpDropTime = 0; if (state == State.BURN_LOGS) lastFiremakingXpDropTime = 0; + // Fresh entry to FLETCH_LOGS → arm the one-shot walk to the safe tile. + if (newState == State.FLETCH_LOGS) { + needFletchTileWalk = true; + } + System.out.println(String.format("[%d] State transition: %s -> %s %s", System.currentTimeMillis(), state, newState, lock ? "(LOCKED)" : "")); @@ -1262,7 +1272,9 @@ private void configureAntibanSettings() { Microbot.log("Activity: " + Rs2Antiban.getActivity().getMethod()); Microbot.log("Play Style: " + Rs2Antiban.getPlayStyle().getName()); Microbot.log("Micro Breaks: " + (Rs2AntibanSettings.takeMicroBreaks ? "Enabled" : "Disabled")); - Microbot.log("Action Cooldown: " + (Rs2AntibanSettings.usePlayStyle ? "Enabled" : "Disabled")); + Microbot.log("Action Cooldown: " + (Rs2AntibanSettings.usePlayStyle + ? "Enabled (chance " + Rs2AntibanSettings.actionCooldownChance + ")" + : "Disabled")); Microbot.log("Mouse Randomization: " + (Rs2AntibanSettings.moveMouseRandomly ? "Enabled" : "Disabled")); Microbot.log("============================"); @@ -1459,13 +1471,22 @@ private GameState analyzeGameState() { gameState.playerWarmth = getWarmthLevel(); - // Object detection + // Object detection. The previous filter required the brazier object's + // worldLocation to *exactly equal* a hardcoded constant in the Brazier + // enum, but the actual SW-corner of the brazier object differs by a + // tile from that constant (verified live: SE brazier at (1638,3997) + // while OBJECT_BRAZIER_LOCATION is (1639,3998)). Result: every brazier + // slot was permanently null → no light, no relight, no fix, NPE on + // feed. Switch to a tolerant within-radius filter anchored on the + // player-stand tile (BRAZIER_LOCATION). The two sides are 17 tiles + // apart, so a radius of 3 unambiguously selects the chosen side. + WorldPoint brazierAnchor = config.brazierLocation().getBRAZIER_LOCATION(); gameState.brazier = Microbot.getRs2TileObjectCache().query().withId(BRAZIER_29312) - .where(o -> o.getWorldLocation().equals(config.brazierLocation().getOBJECT_BRAZIER_LOCATION())).nearest(); + .within(brazierAnchor, 3).nearest(); gameState.brokenBrazier = Microbot.getRs2TileObjectCache().query().withId(BRAZIER_29313) - .where(o -> o.getWorldLocation().equals(config.brazierLocation().getOBJECT_BRAZIER_LOCATION())).nearest(); + .within(brazierAnchor, 3).nearest(); gameState.burningBrazier = Microbot.getRs2TileObjectCache().query().withId(BURNING_BRAZIER_29314) - .where(o -> o.getWorldLocation().equals(config.brazierLocation().getOBJECT_BRAZIER_LOCATION())).nearest(); + .within(brazierAnchor, 3).nearest(); // Health and food management - determine healing strategy if (!autoAdjustedPotionUsage) { @@ -1815,15 +1836,25 @@ private void performMaintenanceTasks(GameState gameState) { return; } + // Brazier maintenance is THE priority. When snowfall breaks the + // brazier, the fix window closes fast (other players race to repair), + // and an unlit brazier means zero points until it's relit. Both of + // those beat eating — we'll eat next tick once the brazier is back. + if (handleBrazierMaintenance(gameState)) { + return; + } + // Drop unnecessary items dropUnnecessaryItems(); - // Dodge falling snow/damage - dodgeSnowfallDamage(gameState); - - // Handle eating + // Eat — survival. Runs only after brazier is fixed/lit (handled above). handleEating(gameState); + // Dodge falling snow/damage (opt-in; most players just rely on food/potions) + if (config.dodgeSnowfall()) { + dodgeSnowfallDamage(gameState); + } + // Periodic camera check (every 2 minutes during normal operation) if (System.currentTimeMillis() - lastCameraMovement > 120000) { fixCameraPitchIfNeeded(); @@ -1996,10 +2027,12 @@ private void handleMainGameLoop(GameState gameState) if (shouldLightBrazier(gameState)) { Microbot.log("Prioritizing brazier lighting at round start (state: " + state + ")"); return; // Light the brazier first, then resume normal flow next tick - } else { - // Brazier is already lit or doesn't need lighting, reset the flag + } else if (gameState.burningBrazier != null) { + // Only consume the priority when a brazier is *definitively* burning + // (someone else lit it). If both burning- and unlit- slots are null, + // the cache may just be lagging — keep the flag and retry next tick. shouldPriorizeBrazierAtStart = false; - Microbot.log("Brazier lighting priority completed or not needed (state: " + state + ")"); + Microbot.log("Brazier lighting priority completed - already burning (state: " + state + ")"); } } @@ -2321,17 +2354,14 @@ private void handleFletchLogsState(GameState gameState) { /* ---------- PRIORITY BLOCK 1: FIX BROKEN BRAZIER FIRST ----------- */ if (gameState.brokenBrazier != null && config.fixBrazier()) { - - sleepGaussian(200, 150); - // Stop fletching temporarily to fix brazier if (fletchingState.isActive()) { fletchingState.stopFletching(FletchingInterruptType.BRAZIER_BROKEN); } - + // Deselect any items before fixing deselectSelectedItem(); - + gameState.brokenBrazier.click("fix"); Microbot.log("Fixing broken brazier (priority during fletching)"); resetActions = true; @@ -2341,19 +2371,16 @@ private void handleFletchLogsState(GameState gameState) { /* ----------------------------------------------------------------- */ /* ---------- PRIORITY BLOCK 2: RELIGHT BRAZIER SECOND ------------ */ - if (gameState.burningBrazier == null && gameState.brazier != null && + if (gameState.burningBrazier == null && gameState.brazier != null && config.relightBrazier() && gameState.isWintertodtAlive) { - - sleepGaussian(200, 150); - // Stop fletching temporarily to relight brazier if (fletchingState.isActive()) { fletchingState.stopFletching(FletchingInterruptType.BRAZIER_WENT_OUT); } - + // Deselect any items before relighting deselectSelectedItem(); - + gameState.brazier.click("light"); Microbot.log("Relighting brazier (priority during fletching)"); resetActions = true; @@ -2398,12 +2425,23 @@ private void handleFletchLogsState(GameState gameState) { } /* ---------- start / continue fletching ------------------- */ - if (!isCurrentlyFletching() && gameState.burningBrazier != null) { + // Fletching only needs knife + roots; a burning brazier nearby isn't a + // precondition. Gating on burningBrazier caused silent idles when the + // brazier went out and the relight priority block didn't fire (e.g. the + // brazier object briefly fell out of the cache). + if (!isCurrentlyFletching()) { sleepGaussian(250, 150); if (random.nextInt(100) < 10) { sleepGaussian(400, 600); } - navigateToBrazier(); + // Walk to the snowfall-safe fletch tile only on the first + // fletch of this state entry (i.e. fresh from chopping). + // Resumed fletches after a brazier fix/light/eat happen in + // place — no point trekking back for a partial pile. + if (needFletchTileWalk) { + navigateToFletchSpot(); + needFletchTileWalk = false; + } // Keep knife in slot-27 optimisation Rs2ItemModel knife = Rs2Inventory.get(WintertodtInventoryManager.knifeToUse); @@ -2452,7 +2490,7 @@ private void handleFletchLogsState(GameState gameState) { } /* Pre-select knife and hover when we have many roots */ - if (rootCount > knifePreselectThreshold && !isKnifeSelected() && gameState.burningBrazier != null) { + if (rootCount > knifePreselectThreshold && !isKnifeSelected()) { Rs2Inventory.interact(WintertodtInventoryManager.knifeToUse, "Use"); sleepGaussian(120, 40); @@ -2492,7 +2530,7 @@ private void handleBurnLogsState(GameState gameState) { if (feedingState.isActive()) { feedingState.stopFeeding(FeedingInterruptType.BRAZIER_BROKEN); } - + gameState.brokenBrazier.click("fix"); Microbot.log("Fixing broken brazier"); resetActions = true; @@ -2503,16 +2541,13 @@ private void handleBurnLogsState(GameState gameState) { /* ---------- PRIORITY BLOCK 2: RELIGHT BRAZIER SECOND ------------ */ Rs2TileObjectModel burningBrazier = gameState.burningBrazier; // side-specific - if (burningBrazier == null && gameState.brazier != null && + if (burningBrazier == null && gameState.brazier != null && config.relightBrazier() && gameState.isWintertodtAlive) { - - sleepGaussian(200, 150); - // Stop feeding temporarily to relight brazier if (feedingState.isActive()) { feedingState.stopFeeding(FeedingInterruptType.BRAZIER_WENT_OUT); } - + gameState.brazier.click("light"); Microbot.log("Relighting brazier"); resetActions = true; @@ -2532,14 +2567,15 @@ private void handleBurnLogsState(GameState gameState) { } /* ---------- start / continue feeding ------------------- */ - if (!isCurrentlyFeeding() && - gameState.hasItemsToBurn) { + if (!isCurrentlyFeeding() && + gameState.hasItemsToBurn && + burningBrazier != null) { sleepGaussian(200, 150); if (random.nextInt(100) < 10) { sleepGaussian(400, 600); } - + if (burningBrazier.click("feed")) { feedingState.startFeeding(); // Initialize animation tracking for new feeding session @@ -2548,6 +2584,10 @@ private void handleBurnLogsState(GameState gameState) { actionsPerformed++; Microbot.log("Started feeding brazier"); maybeNudgeMouse(); + + if (Rs2AntibanSettings.usePlayStyle) { + Rs2Antiban.actionCooldown(); + } } } @@ -3092,6 +3132,53 @@ private boolean shouldBurnLogs(GameState gameState) { * @param gameState Current game state * @return true if food was consumed */ + /** + * Top-of-loop brazier priority handler: fix a broken brazier, then relight + * an unlit one. Runs before {@link #handleEating} so a snowfall-induced + * break doesn't lose us the repair window to another player. Returns true + * if a click was issued (caller should skip the rest of the tick). + */ + private boolean handleBrazierMaintenance(GameState gameState) { + // Without this guard the 60ms loop would re-issue the same click every + // tick while the repair/light animation runs (~30 clicks in 2s) — an + // obvious bot signature. resetActions is cleared once the next action + // begins, so a single repair click can trigger. + if (resetActions) return false; + + if (gameState.brokenBrazier != null && config.fixBrazier()) { + if (fletchingState.isActive()) { + fletchingState.stopFletching(FletchingInterruptType.BRAZIER_BROKEN); + } + if (feedingState.isActive()) { + feedingState.stopFeeding(FeedingInterruptType.BRAZIER_BROKEN); + } + deselectSelectedItem(); + gameState.brokenBrazier.click("fix"); + Microbot.log("Fixing broken brazier (priority)"); + resetActions = true; + actionsPerformed++; + return true; + } + + if (gameState.burningBrazier == null && gameState.brazier != null + && config.relightBrazier() && gameState.isWintertodtAlive) { + if (fletchingState.isActive()) { + fletchingState.stopFletching(FletchingInterruptType.BRAZIER_WENT_OUT); + } + if (feedingState.isActive()) { + feedingState.stopFeeding(FeedingInterruptType.BRAZIER_WENT_OUT); + } + deselectSelectedItem(); + gameState.brazier.click("light"); + Microbot.log("Relighting brazier (priority)"); + resetActions = true; + actionsPerformed++; + return true; + } + + return false; + } + private boolean handleEating(GameState gameState) { if (gameState.playerWarmth <= config.eatAtWarmthLevel()) { try { @@ -3260,10 +3347,14 @@ private boolean shouldLightBrazier(GameState gameState) { if (gameState.brazier == null || gameState.burningBrazier != null) { setLockState(State.LIGHT_BRAZIER, false); - // Reset priority flag if brazier is already lit or doesn't exist - if (shouldPriorizeBrazierAtStart) { + // Only consume the round-start priority when a brazier is *definitively* + // burning. If both are null, the cache hasn't seen the brazier yet + // (common right after the round-start tick) — keep the flag so we'll + // retry once detection catches up, instead of giving up and chopping + // for the rest of the round with an unlit brazier. + if (shouldPriorizeBrazierAtStart && gameState.burningBrazier != null) { shouldPriorizeBrazierAtStart = false; - Microbot.log("Brazier priority reset - brazier already lit or unavailable"); + Microbot.log("Brazier priority reset - brazier already lit"); } return false; } @@ -3328,6 +3419,34 @@ private void navigateToBrazier() { } } + /** + * Navigates to the snowfall-safe fletch tile (one tile south of the + * brazier-stand tile). Used only while fletching so we don't tank + * snowfall AoE for the entire root pile. + */ + private void navigateToFletchSpot() { + try { + WorldPoint fletchLocation = config.brazierLocation().getFLETCH_LOCATION(); + double distance = Rs2Player.getWorldLocation().distanceTo(fletchLocation); + + if (!BreakHandlerScript.isLockState()) { + BreakHandlerScript.setLockState(true); + Microbot.log("Locking break handler"); + } + + if (distance > 8) { + Rs2Walker.walkTo(fletchLocation, 1); + Rs2Player.waitForWalking(); + } else if (distance >= 1) { + Rs2Walker.walkFastCanvas(fletchLocation); + sleepGaussian(400, 100); + } + + } catch (Exception e) { + System.err.println("Error navigating to fletch spot: " + e.getMessage()); + } + } + /** * Dodges snowfall damage by tracking specific projectiles. */ @@ -3369,29 +3488,15 @@ private void dodgeSnowfallDamage(GameState gameState) { Rs2Player.waitForWalking(1500); resetActions = true; Microbot.log("Dodged snowfall damage (80% chance triggered)"); - Microbot.log("Waiting for burning brazier to go out after snowfall damage..."); - Rs2TileObjectModel hoverTarget = Microbot.getRs2TileObjectCache().query() - .where(o -> o.getName() != null && o.getName().toLowerCase().contains("brazier") && o.isReachable()) - .within(4).nearest(); - if (hoverTarget != null && hoverTarget.getClickbox() != null) { - Microbot.getMouse().move(hoverTarget.getClickbox().getBounds()); - } - - boolean brazierWentOut = sleepUntilTrue( - () -> { - // Wait until burning brazier is gone OR we're no longer in burn state - Rs2TileObjectModel burningBrazier = Microbot.getRs2TileObjectCache().query().withId(BURNING_BRAZIER_29314).within(5).nearest(); - return burningBrazier == null || (state != State.BURN_LOGS && state != State.FLETCH_LOGS); - }, - 100, // Check every 100ms - 5000 // Timeout after 5 seconds - ); - - if (brazierWentOut) { - Microbot.log("Brazier state changed, continuing..."); - } else { - Microbot.log("Timeout waiting for brazier change - continuing anyway"); - } + // Don't block here. The previous implementation sleepUntilTrue'd + // for the brazier to go out (timeout 5s, but in practice the + // executor wedged for ~140s — see "avg loop time: 141100ms" in + // the round logs), which starved handleEating and nearly killed + // the player. The dodge already moved us out of the AoE; let + // the next script tick re-analyze: the priority blocks in + // handleFletchLogsState / handleBurnLogsState will detect a + // broken brazier and click "fix" (auto-walks back), or relight + // an unlit brazier — exactly what we want here. } else { Microbot.log("Snowfall detected but purposely NOT dodging (20% chance - staying put for realism ;) )"); } @@ -4253,7 +4358,7 @@ private void handleRoundTimerMouseBehavior(GameState gameState) { if (!hoveredForNextRound && !spamClickingActive && timeUntilStart > 0 && timeUntilStart <= hoverBeforeStartTime) { Rs2TileObjectModel nextObject = getNextInteractiveObject(gameState); if (nextObject != null) { - if (nextObject.getClickbox() != null) Microbot.getMouse().move(nextObject.getClickbox().getBounds()); + if (nextObject.getClickbox() != null) moveCursorHumanized(nextObject.getClickbox().getBounds()); hoveredForNextRound = true; Microbot.log("Hovering over next interactive object: " + nextObject.getId() + " (" + (timeUntilStart / 1000.0) + "s before round start)"); @@ -4473,7 +4578,7 @@ private void maybeNudgeMouse() dx = Math.max(-80, Math.min(80, dx)); dy = Math.max(-80, Math.min(80, dy)); - Microbot.getMouse().move(start.x + dx, start.y + dy); + moveCursorHumanized(start.x + dx, start.y + dy); /* 3. ~50 % chance of a quick follow-up wobble */ if (random.nextBoolean()) @@ -4485,13 +4590,38 @@ private void maybeNudgeMouse() dx2 = Math.max(-30, Math.min(30, dx2)); dy2 = Math.max(-30, Math.min(30, dy2)); - Microbot.getMouse().move(start.x + dx + dx2, - start.y + dy + dy2); + moveCursorHumanized(start.x + dx + dx2, + start.y + dy + dy2); } } catch (Exception ignored) {} } + /** + * Routes cursor movement through NaturalMouse when available. Direct + * Microbot.getMouse().move(x, y) dispatches a teleport MOUSE_MOVED event + * with no path — visually obvious and trivially bot-detectable. Whenever + * we want to *move* the cursor (hover, nudge, pre-position before a spam + * click), use this so the move follows a smooth Bezier-style curve. + */ + private static void moveCursorHumanized(int x, int y) + { + if (x <= 1 || y <= 1) return; + if (Microbot.naturalMouse != null) { + Microbot.naturalMouse.moveTo(x, y); + } else { + Microbot.getMouse().move(x, y); + } + } + + private static void moveCursorHumanized(java.awt.Rectangle rect) + { + if (rect == null) return; + int cx = (int) (rect.getX() + rect.getWidth() / 2.0); + int cy = (int) (rect.getY() + rect.getHeight() / 2.0); + moveCursorHumanized(cx, cy); + } + /* ------------ knife selection helpers ------------------------------ */ private boolean isKnifeSelected() { @@ -4783,7 +4913,7 @@ private void performSpamClick() { } // Just hover and click without actually interacting - if (spamClickTarget.getClickbox() != null) Microbot.getMouse().move(spamClickTarget.getClickbox().getBounds()); + if (spamClickTarget.getClickbox() != null) moveCursorHumanized(spamClickTarget.getClickbox().getBounds()); // Small delay between hover and click for realism sleepGaussian(60, 40); diff --git a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/enums/Brazier.java b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/enums/Brazier.java index 8a1322847f..41a12c903e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/enums/Brazier.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/enums/Brazier.java @@ -6,11 +6,15 @@ @AllArgsConstructor public enum Brazier { - SOUTH_EAST(new WorldPoint(1638, 3996, 0), new WorldPoint(1639, 3998, 0)), - SOUTH_WEST(new WorldPoint(1621, 3996, 0), new WorldPoint(1621, 3998, 0)); + SOUTH_EAST(new WorldPoint(1638, 3996, 0), new WorldPoint(1639, 3998, 0), new WorldPoint(1638, 3995, 0)), + SOUTH_WEST(new WorldPoint(1621, 3996, 0), new WorldPoint(1621, 3998, 0), new WorldPoint(1621, 3995, 0)); @Getter public final WorldPoint BRAZIER_LOCATION; @Getter public final WorldPoint OBJECT_BRAZIER_LOCATION; + // One tile south of the brazier-stand tile — the same offset the dodge logic uses. + // Snowfall AoE around the brazier doesn't reach here, so it's safe to fletch on. + @Getter + public final WorldPoint FLETCH_LOCATION; } From 590e6fa6849ca1082d78bc2a89c89d8814b2daed Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Sun, 3 May 2026 15:42:32 -0400 Subject: [PATCH 63/95] fix(auto-looter): stop world-hopping while walking to/from bank (#421) DefaultScript ran two concurrent schedulers (200ms state machine, 1000ms walk handler). The BANKING->LOOTING transition fired the moment inventory emptied, before the walk back from the bank had finished. The next LOOTING tick scanned Rs2GroundItem from the bank tile, found nothing, and after 5 misses (~1s) called Microbot.hopToWorld. Same hazard during stray-walk-back. - Add isAwayFromBase() helper: true when initialPlayerLocation is unset, the player is moving, or distance from base exceeds distanceToStray. - LOOTING now short-circuits while away from base. No scan, no failedLootAttempts increment, no world hop. - BANKING stays put until inventory is empty AND the player is parked at the loot spot. failedLootAttempts resets on the transition so each return to the spot gets a fresh 5-tick window. Refs #1747 Co-authored-by: runsonmypc --- .../plugins/microbot/looter/AutoLooterPlugin.java | 2 +- .../microbot/looter/scripts/DefaultScript.java | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/looter/AutoLooterPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/looter/AutoLooterPlugin.java index 80f4f14c21..29dc4412da 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/looter/AutoLooterPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/looter/AutoLooterPlugin.java @@ -25,7 +25,7 @@ isExternal = PluginConstants.IS_EXTERNAL ) public class AutoLooterPlugin extends Plugin { - public static final String version = "1.1.2"; + public static final String version = "1.1.3"; @Inject DefaultScript defaultScript; @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/DefaultScript.java b/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/DefaultScript.java index 1678628a9d..79bd84eb48 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/DefaultScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/looter/scripts/DefaultScript.java @@ -50,6 +50,10 @@ public boolean run(AutoLooterConfig config) { switch (state) { case LOOTING: + // Walker may still be carrying us back (post-bank or post-stray). + // Scanning from the wrong tile would trip the world-hop counter. + if (isAwayFromBase(config)) return; + if (config.worldHop()) { if (config.looterStyle() == DefaultLooterStyle.ITEM_LIST) { lootExists = Arrays.stream(config.listOfItemsToLoot().trim().split(",")) @@ -129,7 +133,12 @@ public boolean run(AutoLooterConfig config) { break; case BANKING: + // Stay in BANKING until the bank trip is fully complete: items deposited + // AND we're back at the loot spot. Flipping to LOOTING mid-walk-back + // would let the loot scan run from the bank tile. if (Rs2Inventory.emptySlotCount() <= config.minFreeSlots()) return; + if (isAwayFromBase(config)) return; + failedLootAttempts = 0; state = LooterState.LOOTING; break; } @@ -184,6 +193,12 @@ public boolean handleWalk(AutoLooterConfig config) { return true; } + private boolean isAwayFromBase(AutoLooterConfig config) { + return initialPlayerLocation == null + || Rs2Player.isMoving() + || Rs2Player.getWorldLocation().distanceTo(initialPlayerLocation) > config.distanceToStray(); + } + private void applyAntiBanSettings() { Rs2AntibanSettings.antibanEnabled = true; Rs2AntibanSettings.usePlayStyle = true; From 680e8e8f6d1a7681f15d5b4070b017ca8ccc2e35 Mon Sep 17 00:00:00 2001 From: JThomasDevs <95548936+JThomasDevs@users.noreply.github.com> Date: Sun, 3 May 2026 13:42:43 -0600 Subject: [PATCH 64/95] Jad helper tuning (#420) * fix prayer bug and add trio mode to charge pillars * Jad prayer timing fixes --- .../plugins/microbot/jad/JadPlugin.java | 33 +++- .../plugins/microbot/jad/JadScript.java | 152 +++++++++++++++++- 2 files changed, 179 insertions(+), 6 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/jad/JadPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/jad/JadPlugin.java index 71e92cae92..dd047f6f49 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/jad/JadPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/jad/JadPlugin.java @@ -2,14 +2,21 @@ import com.google.inject.Provides; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Projectile; +import net.runelite.api.events.ProjectileMoved; 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.microbot.PluginConstants; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.ui.overlay.OverlayManager; import javax.inject.Inject; import java.awt.*; +import java.util.Collections; +import java.util.IdentityHashMap; +import java.util.Set; @PluginDescriptor( name = PluginConstants.MOCROSOFT + "Jad Helper", @@ -25,7 +32,7 @@ ) @Slf4j public class JadPlugin extends Plugin { - static final String version = "1.0.7"; + static final String version = "1.0.9"; @Inject private JadConfig config; @@ -41,6 +48,8 @@ JadConfig provideConfig(ConfigManager configManager) { @Inject JadScript jadScript; + private final Set trackedJadProjectiles = + Collections.newSetFromMap(new IdentityHashMap<>()); @Override @@ -54,5 +63,27 @@ protected void startUp() throws AWTException { protected void shutDown() { jadScript.shutdown(); overlayManager.remove(jadOverlay); + trackedJadProjectiles.clear(); + } + + @Subscribe + public void onProjectileMoved(ProjectileMoved event) { + Projectile projectile = event.getProjectile(); + if (projectile == null || !jadScript.isInJadFight()) { + return; + } + + if (projectile.getInteracting() != Microbot.getClient().getLocalPlayer()) { + return; + } + + if (projectile.getRemainingCycles() > 0) { + trackedJadProjectiles.add(projectile); + return; + } + + if (trackedJadProjectiles.remove(projectile)) { + jadScript.onTrackedProjectileImpact(); + } } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/jad/JadScript.java b/src/main/java/net/runelite/client/plugins/microbot/jad/JadScript.java index 2fcad1e3c0..8126cb8272 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/jad/JadScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/jad/JadScript.java @@ -7,29 +7,49 @@ import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; +import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.PriorityQueue; +import java.util.Set; import java.util.concurrent.TimeUnit; public class JadScript extends Script { + private static final int JAD_MAGE_ANIMATION_1 = 7592; + private static final int JAD_MAGE_ANIMATION_2 = 2656; + private static final int JAD_RANGE_ANIMATION_1 = 7593; + private static final int JAD_RANGE_ANIMATION_2 = 2652; + private static final int MAGE_IMPACT_DELAY_TICKS = 4; + private static final int RANGE_IMPACT_DELAY_TICKS = 3; + private static final int PRAYER_SWITCH_LEAD_TICKS = 1; + public static final Map npcAttackCooldowns = new HashMap<>(); + private final Map npcLastAttackAnimations = new HashMap<>(); + private final PriorityQueue attackQueue = + new PriorityQueue<>(Comparator.comparingInt((QueuedJadAttack a) -> a.impactTick).thenComparingLong(a -> a.sequence)); + private long attackSequence = 0; + private JadAttackStyle queuedPrayerStyle = null; public boolean run(JadConfig config) { Microbot.enableAutoRunOn = false; mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { try { if (!Microbot.isLoggedIn() || !super.run()) return; + int currentTick = Microbot.getClient().getTickCount(); List jadNpcs = Microbot.getRs2NpcCache().query() .where(n -> n.getName() != null && n.getName().toLowerCase().contains("jad")) .toList(); + Set activeJadIndexes = new HashSet<>(); for (Rs2NpcModel jadNpc : jadNpcs) { if (jadNpc == null) continue; long currentTimeMillis = System.currentTimeMillis(); int npcIndex = jadNpc.getIndex(); + activeJadIndexes.add(npcIndex); if (npcAttackCooldowns.containsKey(npcIndex)) { if (currentTimeMillis - npcAttackCooldowns.get(npcIndex) < 4600) { @@ -40,12 +60,17 @@ public boolean run(JadConfig config) { } int npcAnimation = jadNpc.getNpc().getAnimation(); - handleJadPrayer(npcAnimation); + if (shouldHandleAttackAnimation(npcIndex, npcAnimation)) { + queueJadAttack(npcIndex, npcAnimation, currentTick); + } if (config.shouldAttackHealers()) { handleHealerInteraction(); npcAttackCooldowns.put(npcIndex, currentTimeMillis); } } + + removeStaleNpcData(activeJadIndexes); + processAttackQueue(currentTick); } catch (Exception ex) { System.out.println(ex.getMessage()); } @@ -78,13 +103,130 @@ private void handleHealerInteraction() { public void shutdown() { super.shutdown(); npcAttackCooldowns.clear(); + npcLastAttackAnimations.clear(); + synchronized (this) { + attackQueue.clear(); + } + queuedPrayerStyle = null; + } + + boolean shouldHandleAttackAnimation(int npcIndex, int animationId) { + if (!isJadAttackAnimation(animationId)) { + npcLastAttackAnimations.remove(npcIndex); + return false; + } + + Integer previousAnimation = npcLastAttackAnimations.put(npcIndex, animationId); + return previousAnimation == null || previousAnimation != animationId; + } + + private boolean isJadAttackAnimation(int animationId) { + return animationId == JAD_MAGE_ANIMATION_1 + || animationId == JAD_MAGE_ANIMATION_2 + || animationId == JAD_RANGE_ANIMATION_1 + || animationId == JAD_RANGE_ANIMATION_2; + } + + private void queueJadAttack(int npcIndex, int animationId, int currentTick) { + JadAttackStyle style = getAttackStyle(animationId); + if (style == null) { + return; + } + + int impactDelayTicks = style == JadAttackStyle.MAGIC ? MAGE_IMPACT_DELAY_TICKS : RANGE_IMPACT_DELAY_TICKS; + int impactTick = currentTick + impactDelayTicks; + synchronized (this) { + attackQueue.offer(new QueuedJadAttack(npcIndex, style, impactTick, attackSequence++)); + } + } + + private JadAttackStyle getAttackStyle(int animationId) { + if (animationId == JAD_MAGE_ANIMATION_1 || animationId == JAD_MAGE_ANIMATION_2) { + return JadAttackStyle.MAGIC; + } + if (animationId == JAD_RANGE_ANIMATION_1 || animationId == JAD_RANGE_ANIMATION_2) { + return JadAttackStyle.RANGED; + } + return null; + } + + private void removeStaleNpcData(Set activeJadIndexes) { + npcLastAttackAnimations.keySet().removeIf(index -> !activeJadIndexes.contains(index)); + synchronized (this) { + attackQueue.removeIf(attack -> !activeJadIndexes.contains(attack.npcIndex)); + } } - private void handleJadPrayer(int animationId) { - if (animationId == 7592 || animationId == 2656) { + private void processAttackQueue(int currentTick) { + synchronized (this) { + QueuedJadAttack nextAttack = attackQueue.peek(); + if (nextAttack == null) { + queuedPrayerStyle = null; + return; + } + + int switchTick = Math.max(0, nextAttack.impactTick - PRAYER_SWITCH_LEAD_TICKS); + if (currentTick < switchTick) { + return; + } + + if (queuedPrayerStyle != nextAttack.style) { + switchPrayer(nextAttack.style); + queuedPrayerStyle = nextAttack.style; + } + } + } + + synchronized void onTrackedProjectileImpact() { + if (attackQueue.isEmpty()) { + queuedPrayerStyle = null; + return; + } + + attackQueue.poll(); + QueuedJadAttack nextAttack = attackQueue.peek(); + if (nextAttack == null) { + queuedPrayerStyle = null; + return; + } + + if (queuedPrayerStyle != nextAttack.style) { + switchPrayer(nextAttack.style); + queuedPrayerStyle = nextAttack.style; + } + } + + boolean isInJadFight() { + var jad = Microbot.getRs2NpcCache().query() + .where(n -> n.getName() != null && n.getName().toLowerCase().contains("jad")) + .nearest(); + return jad != null; + } + + private void switchPrayer(JadAttackStyle style) { + if (style == JadAttackStyle.MAGIC) { Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MAGIC, true); - } else if (animationId == 7593 || animationId == 2652) { + } else { Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_RANGE, true); } } -} \ No newline at end of file + + private static final class QueuedJadAttack { + private final int npcIndex; + private final JadAttackStyle style; + private final int impactTick; + private final long sequence; + + private QueuedJadAttack(int npcIndex, JadAttackStyle style, int impactTick, long sequence) { + this.npcIndex = npcIndex; + this.style = style; + this.impactTick = impactTick; + this.sequence = sequence; + } + } + + private enum JadAttackStyle { + MAGIC, + RANGED + } +} From cbf11a7e5a8b830d72690936d19539c0ddf43fe5 Mon Sep 17 00:00:00 2001 From: Dan G Date: Sun, 3 May 2026 12:43:24 -0700 Subject: [PATCH 65/95] feat(AIOAIO): record walks via destination polling (#419) * feat(AIOAIO): record walks via destination polling * fix(AIOAIO): suppress side-effect walks by target location, not timing * fix(AIOAIO): capture walks via destination poll on WALK/CANCEL --- .../actionreplay/ActionReplayPlugin.java | 62 +++++++++++++++++-- .../actionreplay/ActionReplayScript.java | 11 +++- .../actionreplay/model/RecordedAction.java | 3 + 3 files changed, 71 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPlugin.java index 7137d20fdc..b3c5d60188 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayPlugin.java @@ -5,7 +5,10 @@ import com.google.inject.Provides; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.MenuAction; import net.runelite.api.Point; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.GameTick; import net.runelite.api.events.MenuOptionClicked; import net.runelite.client.config.ConfigManager; @@ -78,6 +81,8 @@ public class ActionReplayPlugin extends Plugin private boolean appendingToExisting; private int gameTickCounter; private int lastActionTickCounter; + private MenuAction lastMenuAction; + private WorldPoint lastSeenDestination; private ActionReplayPanel panel; private NavigationButton navButton; @@ -130,9 +135,50 @@ protected void shutDown() throws Exception @Subscribe public void onGameTick(GameTick event) { - if (recording) + if (!recording) { - gameTickCounter++; + return; + } + gameTickCounter++; + WorldPoint dest = currentDestination(); + if ((lastMenuAction == MenuAction.WALK || lastMenuAction == MenuAction.CANCEL) + && dest != null + && !java.util.Objects.equals(dest, lastSeenDestination)) + { + lastMenuAction = null; + addWalkAction(dest); + } + lastSeenDestination = dest; + } + + private WorldPoint currentDestination() + { + LocalPoint local = Microbot.getClient().getLocalDestinationLocation(); + return local != null ? WorldPoint.fromLocalInstance(Microbot.getClient(), local) : null; + } + + private void addWalkAction(WorldPoint dest) + { + Recording rec = currentRecording; + if (rec == null) + { + return; + } + int tickDelta = rec.size() == 0 ? 0 : Math.max(0, gameTickCounter - lastActionTickCounter); + lastActionTickCounter = gameTickCounter; + RecordedAction a = new RecordedAction(); + a.setDelayTicksBefore(tickDelta); + a.setMenuOption("Walk to"); + a.setTargetName("(" + dest.getX() + ", " + dest.getY() + ", " + dest.getPlane() + ")"); + a.setMenuAction("WALK"); + a.setTargetType(TargetType.WALK); + a.setTargetX(dest.getX()); + a.setTargetY(dest.getY()); + a.setTargetPlane(dest.getPlane()); + rec.getActions().add(a); + if (panel != null) + { + panel.onActionRecorded(a); } } @@ -144,6 +190,12 @@ public void onMenuOptionClicked(MenuOptionClicked e) { return; } + MenuAction action = e.getMenuAction(); + lastMenuAction = action; + if (action == MenuAction.WALK || action == MenuAction.CANCEL) + { + return; + } int tickDelta = rec.size() == 0 ? 0 : Math.max(0, gameTickCounter - lastActionTickCounter); lastActionTickCounter = gameTickCounter; @@ -152,8 +204,8 @@ public void onMenuOptionClicked(MenuOptionClicked e) a.setDelayTicksBefore(tickDelta); a.setMenuOption(stripColorTags(e.getMenuOption())); a.setTargetName(cleanTarget(e.getMenuTarget())); - a.setMenuAction(e.getMenuAction() == null ? null : e.getMenuAction().name()); - a.setTargetType(TargetType.fromMenuAction(e.getMenuAction())); + a.setMenuAction(action == null ? null : action.name()); + a.setTargetType(TargetType.fromMenuAction(action)); a.setIdentifier(e.getId()); a.setParam0(e.getParam0()); a.setParam1(e.getParam1()); @@ -195,6 +247,8 @@ public void startRecording(Recording existing) } gameTickCounter = 0; lastActionTickCounter = 0; + lastMenuAction = null; + lastSeenDestination = currentDestination(); recording = true; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayScript.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayScript.java index f42add63e3..b216b60d7b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/ActionReplayScript.java @@ -2,6 +2,7 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.MenuAction; +import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.InterfaceID; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; @@ -15,6 +16,7 @@ import net.runelite.client.plugins.microbot.util.menu.NewMenuEntry; import net.runelite.client.plugins.microbot.util.misc.Rs2UiHelper; import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import java.awt.Rectangle; @@ -140,8 +142,9 @@ private boolean executeStep(RecordedAction a) return replayGameObject(a); case GROUND_ITEM: return replayGroundItem(a); - case WIDGET: case WALK: + return replayWalk(a); + case WIDGET: case PLAYER: case UNKNOWN: default: @@ -184,6 +187,12 @@ private boolean replayGameObject(RecordedAction a) return match.click(a.getMenuOption()); } + private boolean replayWalk(RecordedAction a) + { + WorldPoint dest = new WorldPoint(a.getTargetX(), a.getTargetY(), a.getTargetPlane()); + return Rs2Walker.walkTo(dest); + } + private boolean replayGroundItem(RecordedAction a) { if (a.getTargetName() == null) diff --git a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/RecordedAction.java b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/RecordedAction.java index fce5cf58b0..eb642aedd3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/RecordedAction.java +++ b/src/main/java/net/runelite/client/plugins/microbot/actionreplay/model/RecordedAction.java @@ -16,6 +16,9 @@ public class RecordedAction private String targetName; private int canvasX; private int canvasY; + private int targetX; + private int targetY; + private int targetPlane; private Condition condition; public String describe() From 07bfbc0cc8e73b0366d50f6b4d19dff3bb01a181 Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Sun, 3 May 2026 15:43:39 -0400 Subject: [PATCH 66/95] fix(qol): drop FieldUtil reflective writes that crash on client 2.5.4 (#418) Upstream commit 048a44d213 deleted util/antiban/FieldUtil to remove sun.misc.Unsafe usage, so QoLPlugin throws NoClassDefFoundError on every game state change against the official 2.5.4 jar, cascading into BlockingEventManager timeouts and freezing the canvas until shutdown. Removes the two FieldUtil.setFinalStatic calls that mutated ColorScheme.BRAND_ORANGE and MicrobotPluginToggleButton.ON_SWITCHER, plus the now-unused imports (FieldUtil, ColorScheme, java.lang.reflect.Field). There is no supported JDK 17+ replacement for writing static-final fields of another class (commons-lang3 FieldUtils.removeFinalModifier is deprecated; VarHandle requires non-final declaration). Per-instance label color and toggle icon recoloring (the parts that don't require static-final mutation) are preserved. Bumps version 1.8.12 -> 1.8.13. Co-authored-by: runsonmypc --- .../microbot/qualityoflife/QoLPlugin.java | 17 +---------------- 1 file changed, 1 insertion(+), 16 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java index d749d30ceb..e87a11146b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java @@ -32,7 +32,6 @@ import net.runelite.client.plugins.microbot.qualityoflife.scripts.wintertodt.WintertodtOverlay; import net.runelite.client.plugins.microbot.qualityoflife.scripts.wintertodt.WintertodtScript; import net.runelite.client.plugins.microbot.util.Global; -import net.runelite.client.plugins.microbot.util.antiban.FieldUtil; 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.inventory.Rs2Inventory; @@ -47,7 +46,6 @@ import net.runelite.client.plugins.microbot.util.tabs.Rs2Tab; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import net.runelite.client.plugins.skillcalculator.skills.MagicAction; -import net.runelite.client.ui.ColorScheme; import net.runelite.client.ui.SplashScreen; import net.runelite.client.ui.overlay.OverlayManager; import net.runelite.client.util.ImageUtil; @@ -59,7 +57,6 @@ import java.awt.datatransfer.DataFlavor; import java.awt.event.KeyEvent; import java.awt.image.BufferedImage; -import java.lang.reflect.Field; import java.util.Arrays; import java.util.LinkedList; import java.util.List; @@ -84,7 +81,7 @@ ) @Slf4j public class QoLPlugin extends Plugin implements KeyListener { - public static final String version = "1.8.12"; + public static final String version = "1.8.13"; public static final List bankMenuEntries = new LinkedList<>(); public static final List furnaceMenuEntries = new LinkedList<>(); public static final List anvilMenuEntries = new LinkedList<>(); @@ -748,18 +745,6 @@ public void updateWintertodtInterupted(boolean interupted) { */ private boolean updateUiElements() { try { - // Get the Field object for the accent color (BRAND_ORANGE) in the ColorScheme class - Field accentColorField = ColorScheme.class.getDeclaredField("BRAND_ORANGE"); - // Update the accent color with the value from the config - FieldUtil.setFinalStatic(accentColorField, config.accentColor()); - - // Get the PluginToggleButton class to access its ON_SWITCHER field - Class pluginButton = Class.forName("net.runelite.client.plugins.microbot.ui.MicrobotPluginToggleButton"); - Field onSwitcherPluginPanel = pluginButton.getDeclaredField("ON_SWITCHER"); - onSwitcherPluginPanel.setAccessible(true); - // Update the ON_SWITCHER field with a remapped image based on the config toggle button color - FieldUtil.setFinalStatic(onSwitcherPluginPanel, remapImage(SWITCHER_ON_IMG, config.toggleButtonColor())); - // Find the ConfigPlugin instance from the plugin manager MicrobotPlugin microbotPlugin = (MicrobotPlugin) Microbot.getPluginManager().getPlugins().stream() .filter(plugin -> plugin instanceof MicrobotPlugin) From 7173fb58f98af1f02d844ef6cf833fee7f33dc31 Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Sun, 3 May 2026 15:43:51 -0400 Subject: [PATCH 67/95] fix(aiofighter): make NPC attack work inside instances (#417) Inside an instance, npc.getWorldLocation() returns the instance-side (high-corner template region) coord, while config.centerLocation() and Rs2Player.getWorldLocation() are template/overworld coords. The radius check at AttackNpcScript filtered out every NPC because the two sides of distanceTo() were in different coordinate spaces, so the script silently did nothing in any instance. Convert the NPC location via WorldPoint.fromLocalInstance(client, npc.getLocalLocation()) when the scene is instanced, for both the radius/reachable filter and the path-distance sort. Bumps version to 2.1.7. Co-authored-by: runsonmypc --- .../microbot/aiofighter/AIOFighterPlugin.java | 2 +- .../aiofighter/combat/AttackNpcScript.java | 31 ++++++++++++++++--- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java index 81b1d0da69..7ba43ad252 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java @@ -61,7 +61,7 @@ ) @Slf4j public class AIOFighterPlugin extends Plugin { - public static final String version = "2.1.6"; + public static final String version = "2.1.7"; public static boolean needShopping = false; private static final String SET = "Set"; private static final String CENTER_TILE = ColorUtil.wrapWithColorTag("Center Tile", JagexColors.MENU_TARGET); diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java index e328439fc4..991fbf8336 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java @@ -4,6 +4,8 @@ import net.runelite.api.Actor; import net.runelite.api.NPC; import net.runelite.api.Player; +import net.runelite.api.WorldView; +import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; import net.runelite.client.plugins.microbot.Microbot; @@ -135,14 +137,18 @@ public void run(AIOFighterConfig config) { final int attackRadius = config.attackRadius(); final boolean requireReachable = config.attackReachableNpcs(); final Rs2WorldPoint rs2PlayerPoint = Rs2Player.getRs2WorldPoint(); + // Inside an instance, npc.getWorldLocation() returns the instance-side coord + // (high-corner template region), but centerLocation/centre-tile and + // Rs2Player.getWorldLocation() are template/overworld coords. Convert via the + // npc's scene-local point so both sides of the radius/path checks match. + final WorldView worldView = Microbot.getClient().getTopLevelWorldView(); + final boolean instanced = worldView != null && worldView.getScene().isInstance(); List attackableNpcs = Microbot.getRs2NpcCache().query() .where(npc -> npc.getCombatLevel() > 0 && !npc.isDead()) .where(npc -> !npc.isInteracting() || Objects.equals(npc.getInteracting(), localPlayer)) .where(npc -> { - // Single getWorldLocation() call combines the radius and reachable filters - // (each model access is a client-thread invoke; one fetch per NPC per tick). - WorldPoint loc = npc.getWorldLocation(); + WorldPoint loc = npcWorldLocation(npc, instanced); if (loc == null) return false; if (loc.distanceTo(centerLocation) > attackRadius) return false; return !requireReachable || rs2PlayerPoint.distanceToPath(loc) < Integer.MAX_VALUE; @@ -155,7 +161,7 @@ public void run(AIOFighterConfig config) { .stream() .sorted(Comparator .comparingInt((Rs2NpcModel npc) -> Objects.equals(npc.getInteracting(), localPlayer) ? 0 : 1) - .thenComparingInt(npc -> rs2PlayerPoint.distanceToPath(npc.getWorldLocation()))) + .thenComparingInt(npc -> rs2PlayerPoint.distanceToPath(npcWorldLocation(npc, instanced)))) .collect(Collectors.toList()); filteredAttackableNpcs.set(attackableNpcs); @@ -334,6 +340,23 @@ private Rs2NpcModel findReanimatedHeadOnPlayer() { .first(); } + /** + * Returns the npc's world location in the same coordinate space as + * {@code config.centerLocation()} / {@code Rs2Player.getWorldLocation()}. + * Inside an instance the raw {@code npc.getWorldLocation()} reports the + * instance-side coord (high-corner template region) while the centre tile + * is stored as a template/overworld coord, so distances would never match. + */ + private static WorldPoint npcWorldLocation(Rs2NpcModel npc, boolean instanced) { + if (instanced) { + LocalPoint lp = npc.getLocalLocation(); + if (lp != null) { + return WorldPoint.fromLocalInstance(Microbot.getClient(), lp); + } + } + return npc.getWorldLocation(); + } + @Override public void shutdown() { super.shutdown(); From e1cb59658c154392c38a398309366c1529e2c428 Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Sun, 3 May 2026 15:44:02 -0400 Subject: [PATCH 68/95] feat(Kraken): slayer-task gate, food check, spawn recovery, fix Leagues loot (#416) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(Kraken): add Kraken boss plugin AFK five-click loop: disturb 4 small whirlpools + main, let auto-retaliate do the damage, detect Kraken NPC despawn, loot, wait for respawn, repeat. - Kraken uniques (tentacle, trident of the seas, jar of dirt, pet) are always looted regardless of user settings. - Config kept minimal: extra-names list, optional GE-value loot toggle + min price. - Click delays drawn from a skewed Gaussian (1800-2400ms, mode 2000) for humanization; all other waits use Rs2Random ranges. * feat(Kraken): slayer-task gate, food check, spawn recovery, fix Leagues loot - Slayer task gate via Rs2Slayer varbit + chat-message backstop; stops the plugin once the assigned task no longer contains "kraken". - Stop-when-out-of-food toggle (default on), checked only at IDLE so a kill is never aborted mid-fight. - FIGHTING now waits up to 8s for the Kraken to actually spawn before counting a kill — recovers cleanly when a small whirlpool was missed. - Loot: drop unconditional addCoins/addUntradables. On Leagues, drops are account-bound and report as untradeable, which was sweeping up every noted stack (monkfish, battlestaves, soul runes). * fix(Kraken): tighten disturb click cadence to ~180ms A click alone is enough to register the disturb — no need to wait for an attack to land. Drops total disturb sequence from ~8s to ~720ms while keeping enough variance to avoid metronomic timing. --------- Co-authored-by: runsonmypc --- .../plugins/microbot/kraken/KrakenConfig.java | 49 ++++ .../microbot/kraken/KrakenOverlay.java | 51 ++++ .../plugins/microbot/kraken/KrakenPlugin.java | 76 +++++ .../plugins/microbot/kraken/KrakenScript.java | 262 ++++++++++++++++++ .../plugins/microbot/kraken/KrakenState.java | 9 + .../java/net/runelite/client/Microbot.java | 4 +- 6 files changed, 450 insertions(+), 1 deletion(-) create mode 100644 src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenConfig.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenOverlay.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenPlugin.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenScript.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenState.java diff --git a/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenConfig.java b/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenConfig.java new file mode 100644 index 0000000000..45113ab7af --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenConfig.java @@ -0,0 +1,49 @@ +package net.runelite.client.plugins.microbot.kraken; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup("kraken") +public interface KrakenConfig extends Config { + + @ConfigItem( + keyName = "listOfItemsToLoot", + name = "Extra loot names", + description = "Comma-separated item names to loot in addition to Kraken uniques (which are always looted).", + position = 0 + ) + default String listOfItemsToLoot() { + return "dragon trident"; + } + + @ConfigItem( + keyName = "toggleLootByValue", + name = "Also loot by GE value", + description = "If on, also loot any item above the min price below. If off, only uniques + extra names are looted.", + position = 1 + ) + default boolean toggleLootByValue() { + return true; + } + + @ConfigItem( + keyName = "minPriceOfItemsToLoot", + name = "Min GE value", + description = "Min GE value (price × qty) to loot. Only used when 'Also loot by GE value' is on.", + position = 2 + ) + default int minPriceOfItemsToLoot() { + return 5000; + } + + @ConfigItem( + keyName = "stopWhenOutOfFood", + name = "Stop when out of food", + description = "If on, stop the plugin when the inventory has no edible food. Only checked between kills (during IDLE) so a kill is never aborted mid-fight.", + position = 3 + ) + default boolean stopWhenOutOfFood() { + return true; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenOverlay.java new file mode 100644 index 0000000000..556560205b --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenOverlay.java @@ -0,0 +1,51 @@ +package net.runelite.client.plugins.microbot.kraken; + +import net.runelite.client.plugins.microbot.Microbot; +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.Color; +import java.awt.Dimension; +import java.awt.Graphics2D; + +public class KrakenOverlay extends OverlayPanel { + + private final KrakenScript script; + + @Inject + KrakenOverlay(KrakenPlugin plugin, KrakenScript script) { + super(plugin); + this.script = script; + setPosition(OverlayPosition.TOP_LEFT); + setNaughty(); + } + + @Override + public Dimension render(Graphics2D graphics) { + try { + panelComponent.setPreferredSize(new Dimension(210, 120)); + panelComponent.getChildren().add(TitleComponent.builder() + .text("Kraken " + KrakenPlugin.version) + .color(Color.CYAN) + .build()); + panelComponent.getChildren().add(LineComponent.builder().build()); + panelComponent.getChildren().add(LineComponent.builder() + .left("State:") + .right(script.getState().name()) + .build()); + panelComponent.getChildren().add(LineComponent.builder() + .left("Kills:") + .right(String.valueOf(script.getKillCount())) + .build()); + panelComponent.getChildren().add(LineComponent.builder() + .left(Microbot.status == null ? "" : Microbot.status) + .build()); + } catch (Exception ex) { + System.out.println(ex.getMessage()); + } + return super.render(graphics); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenPlugin.java new file mode 100644 index 0000000000..fc2c6b6b9a --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenPlugin.java @@ -0,0 +1,76 @@ +package net.runelite.client.plugins.microbot.kraken; + +import com.google.inject.Provides; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.ChatMessageType; +import net.runelite.api.events.ChatMessage; +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.microbot.PluginConstants; +import net.runelite.client.ui.overlay.OverlayManager; + +import javax.inject.Inject; + +@PluginDescriptor( + name = PluginConstants.PERT + "Kraken", + description = "AFK Kraken boss: disturbs whirlpools, auto-fights, loots.", + tags = {"kraken", "boss", "slayer", "afk"}, + authors = {"Pert"}, + version = KrakenPlugin.version, + minClientVersion = "2.0.13", + enabledByDefault = PluginConstants.DEFAULT_ENABLED, + isExternal = PluginConstants.IS_EXTERNAL +) +@Slf4j +public class KrakenPlugin extends Plugin { + public static final String version = "1.0.0"; + + @Inject + private KrakenConfig config; + @Inject + private OverlayManager overlayManager; + @Inject + private KrakenOverlay overlay; + @Inject + private KrakenScript script; + + @Provides + KrakenConfig provideConfig(ConfigManager configManager) { + return configManager.getConfig(KrakenConfig.class); + } + + @Override + protected void startUp() { + if (overlayManager != null) { + overlayManager.add(overlay); + } + script.run(config); + } + + @Override + protected void shutDown() { + script.shutdown(); + if (overlayManager != null) { + overlayManager.remove(overlay); + } + } + + @Subscribe + public void onChatMessage(ChatMessage event) { + if (event.getType() != ChatMessageType.GAMEMESSAGE) return; + String msg = event.getMessage().toLowerCase(); + if (msg.contains("stick to your slayer") + || msg.contains("not on a slayer task") + || msg.contains("not currently assigned")) { + script.requestStop("Off-task chat message: \"" + event.getMessage() + "\""); + return; + } + if (msg.contains("you've completed your task") + || msg.contains("you have completed your task") + || msg.contains("completed your slayer task")) { + script.requestStop("Slayer task completed."); + } + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenScript.java b/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenScript.java new file mode 100644 index 0000000000..52ffcff67c --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenScript.java @@ -0,0 +1,262 @@ +package net.runelite.client.plugins.microbot.kraken; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.NPCComposition; +import net.runelite.client.plugins.grounditems.GroundItem; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.util.grounditem.LootingParameters; +import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; +import net.runelite.client.plugins.microbot.util.grounditem.Rs2LootEngine; +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 net.runelite.client.plugins.microbot.util.skills.slayer.Rs2Slayer; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; + +@Slf4j +public class KrakenScript extends Script { + + private static final int SMALL_WHIRLPOOL_NPC_ID = 5534; + private static final int MAIN_WHIRLPOOL_NPC_ID = 496; + // Attackable Kraken form. Used only to detect death (despawn = kill signal) — we never click it. + private static final int KRAKEN_NPC_ID = 494; + + @Getter + private volatile KrakenState state = KrakenState.IDLE; + @Getter + private volatile int killCount = 0; + + // Tracks NPC scene indexes of small whirlpools we've already clicked this kill, + // so we can fire clicks in sequence without waiting for each to transform. + private final Set clickedSmallIndexes = new HashSet<>(); + + public boolean run(KrakenConfig config) { + state = KrakenState.IDLE; + killCount = 0; + clickedSmallIndexes.clear(); + + mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { + try { + if (!Microbot.isLoggedIn()) return; + if (!super.run()) return; + if (Microbot.pauseAllScripts.get()) return; + + switch (state) { + case IDLE: + handleIdle(config); + break; + case DISTURBING: + handleDisturb(config); + break; + case FIGHTING: + handleFighting(config); + break; + case LOOTING: + handleLooting(config); + break; + case STOPPED: + return; + } + } catch (Exception ex) { + log.error("[Kraken] loop error: {}", ex.getMessage(), ex); + } + }, 0, 600, TimeUnit.MILLISECONDS); + + return true; + } + + private void handleIdle(KrakenConfig config) { + Microbot.status = "Kraken: waiting for whirlpools"; + // Slayer-task gate: Kraken can only be killed while assigned. Re-checked every + // cycle so we exit the moment the task finishes. + if (!isOnKrakenTask()) { + requestStop("Not on a Kraken slayer task."); + return; + } + // Out-of-food gate. Only checked here (between kills) so we never abort mid-fight. + if (config.stopWhenOutOfFood() && Rs2Inventory.getInventoryFood().isEmpty()) { + requestStop("Out of food."); + return; + } + if (anyWhirlpoolSpawned()) { + // Small human reaction delay before the first click fires. + sleep((int) Rs2Random.normalRange(150L, 400L, 0.0)); + state = KrakenState.DISTURBING; + } + } + + public void requestStop(String reason) { + if (state == KrakenState.STOPPED) return; + Microbot.log("[Kraken] Stopping: " + reason); + state = KrakenState.STOPPED; + } + + private static boolean isOnKrakenTask() { + String task = Rs2Slayer.getSlayerTask(); + return task != null && task.toLowerCase().contains("kraken"); + } + + private void handleDisturb(KrakenConfig config) { + // Fire all 5 clicks in sequence: 4 small whirlpools + main. The click alone is + // enough — we don't need to wait for an attack to land. NPC index dedup + // guarantees no double-clicks on the smalls. + java.util.List smalls = Microbot.getRs2NpcCache().query() + .withId(SMALL_WHIRLPOOL_NPC_ID) + .where(npc -> hasAction(npc, "Disturb")) + .where(npc -> !clickedSmallIndexes.contains(npc.getIndex())) + .toList(); + + int smallsClicked = clickedSmallIndexes.size(); + for (Rs2NpcModel npc : smalls) { + if (smallsClicked >= 4) break; + Microbot.status = "Kraken: whirlpool " + (smallsClicked + 1) + "/5"; + if (npc.click("Disturb")) { + clickedSmallIndexes.add(npc.getIndex()); + smallsClicked++; + } + sleep(clickDelayMs()); + } + + Microbot.status = "Kraken: whirlpool 5/5"; + Rs2NpcModel main = Microbot.getRs2NpcCache().query() + .withId(MAIN_WHIRLPOOL_NPC_ID) + .where(npc -> hasAction(npc, "Disturb")) + .nearest(); + if (main != null) { + main.click("Disturb"); + sleep(clickDelayMs()); + } + + state = KrakenState.FIGHTING; + } + + // Smooth click cadence — mode ~180ms, bounded to [100, 350] with a long right tail. + // Just enough variance to avoid metronomic timing without slowing the sequence down. + private static int clickDelayMs() { + return (int) Rs2Random.skewedRand(180L, 100L, 350L, 0.0); + } + + private void handleFighting(KrakenConfig config) { + Microbot.status = "Kraken: fighting"; + // Confirm the 5-click sequence actually spawned the boss. If a small was + // missed, the main stays as a whirlpool and Kraken never emerges. + boolean spawned = sleepUntil(() -> Microbot.getRs2NpcCache().query() + .withId(KRAKEN_NPC_ID) + .first() != null, 8_000); + if (!spawned) { + Microbot.log("[Kraken] Boss didn't spawn — retrying disturb sequence."); + clickedSmallIndexes.clear(); + state = KrakenState.IDLE; + return; + } + // Death signal: the attackable Kraken NPC despawns the moment it dies. + // (isInCombat() lingers ~8s after the last hit — don't use that here.) + sleepUntil(() -> Microbot.getRs2NpcCache().query() + .withId(KRAKEN_NPC_ID) + .first() == null, 120_000); + // Short human-ish reaction delay before starting to loot. + sleep((int) Rs2Random.normalRange(250L, 600L, 0.0)); + killCount++; + state = KrakenState.LOOTING; + } + + private void handleLooting(KrakenConfig config) { + Microbot.status = "Kraken: looting"; + + // Instanced, so no range / ownership filters needed. No max price, no delay. + LootingParameters params = new LootingParameters( + config.minPriceOfItemsToLoot(), + Integer.MAX_VALUE, + Integer.MAX_VALUE, + /* minItems */ 1, + /* minInvSlots */ 0, + /* delayedLooting */ false, + /* antiLureProtection */ false + ); + + Rs2LootEngine.Builder builder = Rs2LootEngine.with(params) + .withLootAction(Rs2GroundItem::coreLoot); + + // Only: hardcoded uniques + user's name list. Value threshold is opt-in. + // No addCoins/addUntradables — on Leagues, drops are account-bound and report + // as untradeable, which would sweep up every noted stack (monkfish, staves, runes). + builder.addCustom("kraken-uniques", KrakenScript::isKrakenUnique, null); + addCustomNames(builder, config.listOfItemsToLoot()); + if (config.toggleLootByValue()) { + builder.addByValue(); + } + + builder.loot(); + sleep((int) Rs2Random.normalRange(150L, 400L, 0.0)); + + clickedSmallIndexes.clear(); + if (Rs2Inventory.isFull()) { + Microbot.log("[Kraken] Inventory full — stopping."); + state = KrakenState.STOPPED; + } else { + // Back to IDLE — anyWhirlpoolSpawned() polling picks up the respawn quickly. + state = KrakenState.IDLE; + } + } + + private static boolean hasAction(Rs2NpcModel npc, String action) { + NPCComposition comp = npc.getNpc().getTransformedComposition(); + if (comp == null) return false; + for (String a : comp.getActions()) { + if (action.equalsIgnoreCase(a)) return true; + } + return false; + } + + private static boolean anyWhirlpoolSpawned() { + return Microbot.getRs2NpcCache().query() + .withId(SMALL_WHIRLPOOL_NPC_ID) + .where(npc -> hasAction(npc, "Disturb")) + .first() != null + || Microbot.getRs2NpcCache().query() + .withId(MAIN_WHIRLPOOL_NPC_ID) + .where(npc -> hasAction(npc, "Disturb")) + .first() != null; + } + + private static boolean isKrakenUnique(GroundItem gi) { + String n = gi.getName() == null ? "" : gi.getName().trim().toLowerCase(); + return n.contains("kraken tentacle") + || n.contains("trident of the seas") + || n.contains("jar of dirt") + || n.contains("pet kraken"); + } + + private static void addCustomNames(Rs2LootEngine.Builder builder, String csvNames) { + if (csvNames == null) return; + Set needles = new HashSet<>(); + Arrays.stream(csvNames.split(",")) + .map(s -> s == null ? "" : s.trim().toLowerCase()) + .filter(s -> !s.isEmpty()) + .forEach(needles::add); + if (needles.isEmpty()) return; + + Predicate byNames = gi -> { + String n = gi.getName() == null ? "" : gi.getName().trim().toLowerCase(); + for (String needle : needles) { + if (n.contains(needle)) return true; + } + return false; + }; + builder.addCustom("names", byNames, null); + } + + @Override + public void shutdown() { + super.shutdown(); + state = KrakenState.STOPPED; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenState.java b/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenState.java new file mode 100644 index 0000000000..0060dc8fa8 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/kraken/KrakenState.java @@ -0,0 +1,9 @@ +package net.runelite.client.plugins.microbot.kraken; + +public enum KrakenState { + IDLE, + DISTURBING, + FIGHTING, + LOOTING, + STOPPED +} diff --git a/src/test/java/net/runelite/client/Microbot.java b/src/test/java/net/runelite/client/Microbot.java index 8933287b08..c57d3a1bf2 100644 --- a/src/test/java/net/runelite/client/Microbot.java +++ b/src/test/java/net/runelite/client/Microbot.java @@ -10,6 +10,7 @@ import net.runelite.client.plugins.microbot.astralrc.AstralRunesPlugin; import net.runelite.client.plugins.microbot.autofishing.AutoFishingPlugin; import net.runelite.client.plugins.microbot.example.ExamplePlugin; +import net.runelite.client.plugins.microbot.kraken.KrakenPlugin; import net.runelite.client.plugins.microbot.leftclickcast.LeftClickCastPlugin; import net.runelite.client.plugins.microbot.sailing.MSailingPlugin; import net.runelite.client.plugins.microbot.thieving.ThievingPlugin; @@ -22,7 +23,8 @@ public class Microbot private static final Class[] debugPlugins = { AIOFighterPlugin.class, AgentServerPlugin.class, - LeftClickCastPlugin.class + LeftClickCastPlugin.class, + KrakenPlugin.class }; public static void main(String[] args) throws Exception From e9ec3f97590e672588d336b6546de36f96b580ec Mon Sep 17 00:00:00 2001 From: Haliax <18099301+TheHaliax@users.noreply.github.com> Date: Sun, 3 May 2026 14:44:16 -0500 Subject: [PATCH 69/95] fix(QoL): drop FieldUtil static-final hacks (#415) * fix prayer bug and add trio mode to charge pillars (#408) * fix(QoL): drop FieldUtil static-final hacks FieldUtil no longer exists; stop mutating ColorScheme/ToggleButton statics. Patch Swing via UIManager defaults + per-component updates, queue UI refresh/restore safely, and keep toggles/labels consistent. --------- Co-authored-by: chsami Co-authored-by: JThomasDevs <95548936+JThomasDevs@users.noreply.github.com> --- .../microbot/qualityoflife/QoLPlugin.java | 627 +++++++++++++++++- 1 file changed, 600 insertions(+), 27 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java index e87a11146b..b5ece9ae0d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java @@ -53,15 +53,25 @@ import javax.inject.Inject; import javax.swing.*; +import javax.swing.plaf.ColorUIResource; import java.awt.*; import java.awt.datatransfer.DataFlavor; import java.awt.event.KeyEvent; import java.awt.image.BufferedImage; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Consumer; import static net.runelite.client.plugins.microbot.qualityoflife.scripts.wintertodt.WintertodtScript.isInWintertodtRegion; @@ -85,13 +95,45 @@ public class QoLPlugin extends Plugin implements KeyListener { public static final List bankMenuEntries = new LinkedList<>(); public static final List furnaceMenuEntries = new LinkedList<>(); public static final List anvilMenuEntries = new LinkedList<>(); - private static final AtomicReference> pluginList = new AtomicReference<>(); + private static final AtomicBoolean uiUpdateQueued = new AtomicBoolean(false); + private static final AtomicBoolean uiRestoreQueued = new AtomicBoolean(false); + private static final AtomicBoolean uiQueuedDuringSplash = new AtomicBoolean(false); + private static final AtomicBoolean uiUpdatePendingAfterRestore = new AtomicBoolean(false); + + private static final AtomicBoolean loggedMissingSwitcherOn = new AtomicBoolean(false); + private static final AtomicBoolean loggedMissingSwitcherOff = new AtomicBoolean(false); + private static final AtomicBoolean loggedThemeException = new AtomicBoolean(false); + private static final AtomicBoolean loggedRestoreException = new AtomicBoolean(false); + private static final AtomicBoolean loggedToggleReflectionException = new AtomicBoolean(false); + private static final AtomicBoolean loggedMissingUiManagerKey = new AtomicBoolean(false); + private static final AtomicBoolean loggedMicrobotPluginCacheFailure = new AtomicBoolean(false); + private static final AtomicBoolean loggedMicrobotPluginFallbackScan = new AtomicBoolean(false); + + private static final AtomicReference selfRef = new AtomicReference<>(); + private static final AtomicReference microbotPluginRef = new AtomicReference<>(); + + // Use explicit synchronization everywhere (avoid mixing synchronizedMap + synchronized blocks). + private static final Map originalToggleIcons = new WeakHashMap<>(); + private static final Map originalLabelColors = new WeakHashMap<>(); + private static final Set touchedLabels = Collections.newSetFromMap(new WeakHashMap<>()); + private static final Map originalUiManagerValues = new HashMap<>(); + private static final String[] UI_KEYS_TO_PATCH = new String[]{ + "Component.accentColor", + "ProgressBar.selectionForeground", + "ProgressBar.selectionBackground", + "Button.default.focusColor" + }; private static final int HALF_ROTATION = 1024; private static final int FULL_ROTATION = 2048; private static final int PITCH_INDEX = 0; private static final int YAW_INDEX = 1; private static final BufferedImage SWITCHER_ON_IMG = getImageFromConfigResource("switcher_on"); + private static final BufferedImage SWITCHER_OFF_IMG = getImageFromConfigResource("switcher_off"); private static final BufferedImage STAR_ON_IMG = getImageFromConfigResource("star_on"); + private static volatile Color lastAccentApplied = null; + private static volatile Color lastToggleColorApplied = null; + private static volatile ImageIcon lastToggleOnIcon = null; + private static volatile Window lastWindowPatched = null; public static InventorySetup loadoutToLoad = null; private static GameState lastGameState = GameState.UNKNOWN; private final int[] deltaCamera = new int[3]; @@ -163,28 +205,59 @@ private static BufferedImage getImageFromConfigResource(String imgName) { Class clazz = Class.forName("net.runelite.client.plugins.config.ConfigPanel"); return ImageUtil.loadImageResource(clazz, imgName.concat(".png")); } catch (Exception e) { - e.printStackTrace(); + log.debug("QoL: failed to load ConfigPanel image {}", imgName, e); return null; } } private static ImageIcon remapImage(BufferedImage image, Color color) { - if (color != null) { - BufferedImage img = new BufferedImage(image.getWidth(), image.getHeight(), 2); - Graphics2D graphics = img.createGraphics(); - graphics.drawImage(image, 0, 0, null); - graphics.setColor(color); - graphics.setComposite(AlphaComposite.getInstance(10, 1)); - graphics.fillRect(0, 0, image.getWidth(), image.getHeight()); - graphics.dispose(); - return new ImageIcon(img); - } else { + if (image == null || color == null) { return null; } + BufferedImage img = new BufferedImage(image.getWidth(), image.getHeight(), 2); + Graphics2D graphics = img.createGraphics(); + graphics.drawImage(image, 0, 0, null); + graphics.setColor(color); + graphics.setComposite(AlphaComposite.getInstance(10, 1)); + graphics.fillRect(0, 0, image.getWidth(), image.getHeight()); + graphics.dispose(); + return new ImageIcon(img); + } + + private static MicrobotPlugin findMicrobotPluginOnce() + { + MicrobotPlugin cached = microbotPluginRef.get(); + if (cached != null) + { + return cached; + } + + MicrobotPlugin found = (MicrobotPlugin) Microbot.getPluginManager().getPlugins().stream() + .filter(plugin -> plugin instanceof MicrobotPlugin) + .findAny().orElse(null); + if (found != null) + { + microbotPluginRef.compareAndSet(null, found); + } + return found; } @Override protected void startUp() throws AWTException { + selfRef.set(this); + // Cache MicrobotPlugin once (avoid scanning plugin list in steady-state UI updates). + try + { + findMicrobotPluginOnce(); + } + catch (Exception ex) + { + // Best-effort cache; updateUiElements() can still fall back if needed. + if (loggedMicrobotPluginCacheFailure.compareAndSet(false, true)) + { + log.debug("QoL: failed to cache MicrobotPlugin during startup", ex); + } + } if (overlayManager != null) { overlayManager.add(qoLOverlay); overlayManager.add(wintertodtOverlay); @@ -216,7 +289,10 @@ protected void startUp() throws AWTException { autoPrayer.run(config); keyManager.registerKeyListener(this); // pvpScript.run(config); - awaitExecutionUntil(() -> Microbot.getClientThread().invokeLater(this::updateUiElements), () -> !SplashScreen.isOpen(), 600); + uiQueuedDuringSplash.set(false); + awaitExecutionUntil(() -> queueUpdateUiElementsDuringSplash(), () -> !SplashScreen.isOpen(), 600); + // Splash closed; allow future splash-guarded queues. + uiQueuedDuringSplash.set(false); } @Override @@ -235,6 +311,56 @@ protected void shutDown() { potionManagerScript.shutdown(); autoPrayer.shutdown(); keyManager.unregisterKeyListener(this); + + // Best-effort restore of any UI theming we applied. + try + { + if (SwingUtilities.isEventDispatchThread()) + { + restoreUiElements(); + } + else + { + FutureTask restoreTask = new FutureTask<>(() -> + { + restoreUiElements(); + return null; + }); + SwingUtilities.invokeLater(restoreTask); + try + { + restoreTask.get(750, TimeUnit.MILLISECONDS); + } + catch (TimeoutException ignored) + { + log.warn("QoL: UI restore timed out during shutdown; continuing teardown."); + // Restore UIManager defaults immediately to reduce lingering accent. + if (SwingUtilities.isEventDispatchThread()) + { + restoreOriginalUiDefaultsOnly(); + } + else + { + SwingUtilities.invokeLater(QoLPlugin::restoreOriginalUiDefaultsOnly); + } + // Best-effort restore is already queued via `restoreTask`. + } + } + } + catch (Exception ex) + { + // shutdown path: avoid throwing; log once and continue teardown + if (loggedRestoreException.compareAndSet(false, true)) + { + log.warn("QoL: UI restore during shutdown failed", ex); + } + } + finally + { + // Always clear the ref so queued updates don't run post-shutdown. + selfRef.compareAndSet(this, null); + microbotPluginRef.set(null); + } } @Subscribe( @@ -244,7 +370,9 @@ public void onProfileChanged(ProfileChanged event) { log.info("Profile changed"); log.info("Updating UI elements"); // Wait for the splash screen to close before updating the UI elements - awaitExecutionUntil(() -> Microbot.getClientThread().invokeLater(this::updateUiElements), () -> !SplashScreen.isOpen(), 1000); + uiQueuedDuringSplash.set(false); + awaitExecutionUntil(() -> queueUpdateUiElementsDuringSplash(), () -> !SplashScreen.isOpen(), 1000); + uiQueuedDuringSplash.set(false); } @@ -280,7 +408,7 @@ public void onGameTick(GameTick event) { @Subscribe public void onGameStateChanged(GameStateChanged event) { if (event.getGameState() != GameState.UNKNOWN && lastGameState == GameState.UNKNOWN) { - updateUiElements(); + queueUpdateUiElements(); } if (event.getGameState() == GameState.LOGIN_SCREEN) { @@ -744,61 +872,506 @@ public void updateWintertodtInterupted(boolean interupted) { * @return true if the UI elements are successfully updated, false otherwise. */ private boolean updateUiElements() { + // Swing/FlatLaf layout is not thread-safe; enforce EDT execution. + if (!SwingUtilities.isEventDispatchThread()) { + QoLPlugin self = selfRef.get(); + if (self == null) + { + return false; + } + // If a run is already queued and we still have a live plugin, consider it pending. + if (uiUpdateQueued.get()) + { + return true; + } + return queueUpdateUiElements(); + } + try { // Find the ConfigPlugin instance from the plugin manager - MicrobotPlugin microbotPlugin = (MicrobotPlugin) Microbot.getPluginManager().getPlugins().stream() - .filter(plugin -> plugin instanceof MicrobotPlugin) - .findAny().orElse(null); + MicrobotPlugin microbotPlugin = findMicrobotPluginOnce(); + if (microbotPlugin == null && loggedMicrobotPluginFallbackScan.compareAndSet(false, true)) + { + log.debug("QoL: MicrobotPlugin ref was empty; fell back to plugin manager scan."); + } // If ConfigPlugin is not found, log an error and return false if (microbotPlugin == null) { Microbot.log("Config Plugin not found"); return false; } + microbotPluginRef.set(microbotPlugin); // Get the plugin list panel from the ConfigPlugin instance JPanel pluginListPanel = getPluginListPanel(microbotPlugin); + Window pluginWindow = SwingUtilities.getWindowAncestor(pluginListPanel); + if (pluginWindow != lastWindowPatched) + { + // If the config window closed while we had an accent applied, restore defaults now. + if (pluginWindow == null && lastAccentApplied != null) + { + restoreOriginalUiDefaultsOnly(); + lastAccentApplied = null; + } + lastWindowPatched = pluginWindow; + synchronized (originalToggleIcons) + { + originalToggleIcons.clear(); + } + synchronized (originalLabelColors) + { + originalLabelColors.clear(); + } + synchronized (touchedLabels) + { + touchedLabels.clear(); + } + lastAccentApplied = null; + lastToggleColorApplied = null; + lastToggleOnIcon = null; + } + + // Best-effort accent color behavior (no static-final mutation / no Unsafe). + try + { + Color accent = config.accentColor(); + if (accent != null) + { + ColorUIResource accentRes = new ColorUIResource(accent); + rememberOriginalUiDefaults(); + // Only apply+refresh when accent actually changed. + if (!accent.equals(lastAccentApplied)) + { + UIManager.put("Component.accentColor", accentRes); + UIManager.put("ProgressBar.selectionForeground", accentRes); + UIManager.put("ProgressBar.selectionBackground", accentRes); + UIManager.put("Button.default.focusColor", accentRes); + lastAccentApplied = accent; + + // Refresh UI tree to apply defaults. + try + { + if (pluginWindow != null) + { + SwingUtilities.updateComponentTreeUI(pluginWindow); + pluginWindow.invalidate(); + pluginWindow.validate(); + pluginWindow.repaint(); + } + } + catch (Exception ex) + { + if (loggedThemeException.compareAndSet(false, true)) + { + log.warn("QoL: UI refresh after accent update failed", ex); + } + } + } + } + else if (lastAccentApplied != null) + { + // Accent cleared; restore original defaults (if we captured them) and refresh UI. + restoreOriginalUiDefaultsOnly(); + lastAccentApplied = null; + + try + { + if (pluginWindow != null) + { + SwingUtilities.updateComponentTreeUI(pluginWindow); + pluginWindow.invalidate(); + pluginWindow.validate(); + pluginWindow.repaint(); + } + } + catch (Exception ex) + { + if (loggedThemeException.compareAndSet(false, true)) + { + log.warn("QoL: UI refresh after accent restore failed", ex); + } + } + } + } + catch (Exception ex) + { + if (loggedThemeException.compareAndSet(false, true)) + { + log.warn("QoL: UI theme update failed", ex); + } + } + // Set the plugin list using the retrieved plugin list panel - pluginList.set(getPluginList(pluginListPanel)); + List currentPluginList = getPluginList(pluginListPanel); // If the plugin list is still null, log an error and return false - if (pluginList.get() == null) { + if (currentPluginList == null) { Microbot.log("Plugin list is null, waiting for it to be initialized"); return false; } - // Iterate through each plugin in the plugin list - for (Object plugin : pluginList.get()) { + // Pass 1: capture originals (before applying any changes). + for (Object plugin : currentPluginList) + { + try + { + if (plugin instanceof JPanel) + { + for (Component component : ((JPanel) plugin).getComponents()) + { + if (component instanceof JLabel) + { + JLabel label = (JLabel) component; + synchronized (originalLabelColors) + { + originalLabelColors.computeIfAbsent(label, l -> l.getForeground()); + } + } + } + } + + JToggleButton onOffToggle = (JToggleButton) FieldUtils.readDeclaredField(plugin, "onOffToggle", true); + if (onOffToggle == null) + { + continue; + } + synchronized (originalToggleIcons) + { + originalToggleIcons.computeIfAbsent(onOffToggle, t -> new IconState(t.getIcon(), t.getSelectedIcon())); + } + } + catch (Exception ex) + { + // Best-effort: one broken row shouldn't abort the whole update. + if (loggedToggleReflectionException.compareAndSet(false, true)) + { + log.debug("QoL: reflection lookup for plugin toggle failed; UI theming may be partial.", ex); + } + } + } + + // Pass 2: apply theming changes. + final Color labelColor = config.pluginLabelColor(); + final ImageIcon onIcon = getCachedToggleOnIcon(config.toggleButtonColor()); + for (Object plugin : currentPluginList) { // If the plugin is a JPanel, update the color of any JLabel components within it if (plugin instanceof JPanel) { for (Component component : ((JPanel) plugin).getComponents()) { if (component instanceof JLabel) { + JLabel label = (JLabel) component; // Set the label color based on the config - component.setForeground(config.pluginLabelColor()); + if (labelColor != null) + { + if (!labelColor.equals(label.getForeground())) + { + synchronized (touchedLabels) + { + touchedLabels.add(label); + } + label.setForeground(labelColor); + } + } } } } // Get the on/off toggle button for the plugin and update its selected icon - JToggleButton onOffToggle = (JToggleButton) FieldUtils.readDeclaredField(plugin, "onOffToggle", true); - onOffToggle.setSelectedIcon(remapImage(SWITCHER_ON_IMG, config.toggleButtonColor())); + JToggleButton onOffToggle; + try + { + onOffToggle = (JToggleButton) FieldUtils.readDeclaredField(plugin, "onOffToggle", true); + if (onOffToggle == null) + { + continue; + } + } + catch (Exception ex) + { + continue; + } + // Only recolor the "ON" (selected) icon. Do not overwrite the "OFF" icon, + // otherwise disabled plugins will also appear enabled. + Icon offIcon = onOffToggle.getIcon(); + if (onIcon == null) + { + if (loggedMissingSwitcherOn.compareAndSet(false, true)) + { + log.warn("QoL: missing ConfigPanel switcher_on.png; leaving plugin toggle icons unchanged."); + } + continue; + } + onOffToggle.setSelectedIcon(onIcon); + if (offIcon != null) { + onOffToggle.setIcon(offIcon); + } else if (SWITCHER_OFF_IMG != null) { + // Fallback: ensure OFF icon is distinct if missing. + onOffToggle.setIcon(new ImageIcon(SWITCHER_OFF_IMG)); + } else { + if (loggedMissingSwitcherOff.compareAndSet(false, true)) + { + log.warn("QoL: missing ConfigPanel switcher_off.png; OFF icon fallback unavailable."); + } + } } return true; } catch (Exception e) { // Log any exceptions that occur during the UI update process String errorMessage = "QoL Error updating UI elements: " + e.getMessage(); - log.error(errorMessage); + log.error(errorMessage, e); Microbot.log(errorMessage); return false; } } + private static ImageIcon getCachedToggleOnIcon(Color toggleColor) + { + if (SWITCHER_ON_IMG == null) + { + return null; + } + + if (toggleColor == null) + { + lastToggleColorApplied = null; + lastToggleOnIcon = null; + return null; + } + + if (!toggleColor.equals(lastToggleColorApplied) || lastToggleOnIcon == null) + { + lastToggleOnIcon = remapImage(SWITCHER_ON_IMG, toggleColor); + lastToggleColorApplied = toggleColor; + } + + return lastToggleOnIcon; + } + + private static final class IconState + { + private final Icon icon; + private final Icon selectedIcon; + + private IconState(Icon icon, Icon selectedIcon) + { + this.icon = icon; + this.selectedIcon = selectedIcon; + } + } + + private static void rememberOriginalUiDefaults() + { + synchronized (originalUiManagerValues) + { + if (!originalUiManagerValues.isEmpty()) + { + return; + } + + for (String k : UI_KEYS_TO_PATCH) + { + Object v = UIManager.get(k); + originalUiManagerValues.put(k, v); + if (v == null && !UIManager.getDefaults().containsKey(k) && loggedMissingUiManagerKey.compareAndSet(false, true)) + { + log.debug("QoL: UIManager key '{}' not present in defaults; accent patch may be FlatLaf-version dependent.", k); + } + } + } + } + + private static void restoreOriginalUiDefaultsOnly() + { + synchronized (originalUiManagerValues) + { + if (originalUiManagerValues.isEmpty()) + { + return; + } + + for (String k : UI_KEYS_TO_PATCH) + { + UIManager.put(k, originalUiManagerValues.get(k)); + } + } + } + + private static void queueRestoreUiElements() + { + if (!uiRestoreQueued.compareAndSet(false, true)) + { + return; + } + + SwingUtilities.invokeLater(() -> + { + try + { + restoreUiElements(); + } + finally + { + uiRestoreQueued.set(false); + } + }); + } + + private static void restoreUiElements() + { + if (!SwingUtilities.isEventDispatchThread()) + { + log.warn("QoL: restoreUiElements called off-EDT; skipping."); + return; + } + + // Restore UIManager defaults. + synchronized (originalUiManagerValues) + { + if (!originalUiManagerValues.isEmpty()) + { + for (String k : UI_KEYS_TO_PATCH) + { + UIManager.put(k, originalUiManagerValues.get(k)); + } + } + } + + // Restore per-component UI state (best-effort; components may be gone/rebuilt). + synchronized (originalToggleIcons) + { + for (Map.Entry e : originalToggleIcons.entrySet()) + { + JToggleButton t = e.getKey(); + IconState s = e.getValue(); + if (t != null && s != null) + { + t.setIcon(s.icon); + t.setSelectedIcon(s.selectedIcon); + } + } + } + + synchronized (originalLabelColors) + { + for (Map.Entry e : originalLabelColors.entrySet()) + { + JLabel l = e.getKey(); + boolean touched; + synchronized (touchedLabels) + { + touched = touchedLabels.contains(l); + } + if (l != null && touched) + { + l.setForeground(e.getValue()); + } + } + } + + // Refresh UI tree to apply restored defaults. + try + { + QoLPlugin self = selfRef.get(); + MicrobotPlugin microbotPlugin = microbotPluginRef.get(); + if (self != null && microbotPlugin != null) + { + JPanel pluginListPanel = self.getPluginListPanel(microbotPlugin); + Window w = SwingUtilities.getWindowAncestor(pluginListPanel); + if (w != null) + { + SwingUtilities.updateComponentTreeUI(w); + w.invalidate(); + w.validate(); + w.repaint(); + } + } + } + catch (Exception ex) + { + if (loggedRestoreException.compareAndSet(false, true)) + { + log.warn("QoL: UI restore refresh failed", ex); + } + } + + // Clear caches so re-enable re-captures fresh state. + synchronized (originalToggleIcons) + { + originalToggleIcons.clear(); + } + synchronized (originalLabelColors) + { + originalLabelColors.clear(); + } + synchronized (touchedLabels) + { + touchedLabels.clear(); + } + // Intentionally keep captured defaults for plugin lifetime so we can restore later. + lastAccentApplied = null; + lastToggleColorApplied = null; + lastToggleOnIcon = null; + lastWindowPatched = null; + + // If an update was requested while restore was in-progress, run it now. + if (uiUpdatePendingAfterRestore.compareAndSet(true, false)) + { + queueUpdateUiElements(); + } + } + + private static boolean queueUpdateUiElements() + { + // Don't interleave apply with restore. + if (uiRestoreQueued.get()) + { + uiUpdatePendingAfterRestore.set(true); + return false; + } + + // Prevent re-entrant scheduling during layout/validate cascades. + if (!uiUpdateQueued.compareAndSet(false, true)) + { + return selfRef.get() != null; + } + + SwingUtilities.invokeLater(() -> + { + try + { + QoLPlugin self = selfRef.get(); + if (self != null) + { + self.updateUiElements(); + } + } + finally + { + uiUpdateQueued.set(false); + } + }); + + return true; + } + + private static boolean queueUpdateUiElementsDuringSplash() + { + // Prevent repeated enqueue spam while waiting for splash to close. + if (!uiQueuedDuringSplash.compareAndSet(false, true)) + { + return false; + } + return queueUpdateUiElements(); + } + private JPanel getPluginListPanel(MicrobotPlugin microbotPlugin) throws ClassNotFoundException { Class pluginListPanelClass = Class.forName("net.runelite.client.plugins.microbot.ui.MicrobotPluginListPanel"); - assert microbotPlugin != null; + if (microbotPlugin == null) + { + throw new IllegalStateException("MicrobotPlugin instance is null"); + } return (JPanel) microbotPlugin.getInjector().getProvider(pluginListPanelClass).get(); } From 12366df17dc2d5bbe3caf4d1a4aa2765987b7937 Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Sun, 3 May 2026 15:45:06 -0400 Subject: [PATCH 70/95] fix(moonsofperil): retry walker to reach boss lobby reliably (#413) walkToBoss called walkWithState(bossWorldPoint, 0) once (exact-tile match) then fell back to walkFastCanvas(bossWorldPoint). When the walker exited early on a single pass (cross-region transport, door handler interrupt) the canvas-click fallback tried to click a tile dozens of tiles off-screen, LocalPoint.fromWorld returned null, and the plugin looped forever without the player ever moving. Replace with a bounded retry loop: walkWithState(bp, 3) with 600ms spacing and a 90s overall budget, breaking on distance<=3. Null-guard Rs2Player.getWorldLocation() since it can transiently return null during region transitions (observed NPE at BossHandler.java:50). Callers (Blue/Eclipse/BloodMoonHandler, RewardHandler) only need scene proximity -- each follows with its own exact-tile interaction. Bump version 1.0.3 -> 1.0.4. Co-authored-by: runsonmypc --- .../moonsofperil/MoonsOfPerilPlugin.java | 2 +- .../moonsofperil/handlers/BossHandler.java | 29 ++++++++++++++----- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/MoonsOfPerilPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/MoonsOfPerilPlugin.java index 9b798212e0..7b791187b1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/MoonsOfPerilPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/MoonsOfPerilPlugin.java @@ -35,7 +35,7 @@ ) @Slf4j public class MoonsOfPerilPlugin extends Plugin { - static final String version = "1.0.3"; + static final String version = "1.0.4"; @Inject private MoonsOfPerilConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BossHandler.java b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BossHandler.java index 09cf57341c..90124359df 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BossHandler.java +++ b/src/main/java/net/runelite/client/plugins/microbot/moonsofperil/handlers/BossHandler.java @@ -37,19 +37,34 @@ public BossHandler(MoonsOfPerilConfig cfg) { this.debugLogging = cfg.debugLogging(); } - /** Walks to the chosen boss lobby. */ + /** Walks to the chosen boss lobby. + * + * Loops the walker with a reached-distance tolerance. The previous implementation called + * {@code walkWithState(bossWorldPoint, 0)} (exact-tile match) followed by a + * {@code walkFastCanvas(bossWorldPoint)} fallback; when the walker exited early on a single + * pass (e.g. cross-region transport, door handler interrupt) the canvas-click fallback tried + * to click a tile dozens of tiles off-screen, {@code LocalPoint.fromWorld} returned null, + * and the plugin looped forever without the player ever moving. + */ public void walkToBoss(Rs2InventorySetup inventorySetup, String bossName, WorldPoint bossWorldPoint) { if (inventorySetup != null) { equipInventorySetup(inventorySetup); } if (debugLogging) {Microbot.log("Walking to " + bossName + " lobby");} - Rs2Walker.walkWithState(bossWorldPoint, 0); - sleep(600); - if (!Rs2Player.getWorldLocation().equals(bossWorldPoint)) { - Rs2Walker.walkFastCanvas(bossWorldPoint); - sleepUntil(() -> Rs2Player.getWorldLocation().equals(bossWorldPoint)); + + final int reachedDistance = 3; + final long deadlineMs = System.currentTimeMillis() + 90_000L; + while (System.currentTimeMillis() < deadlineMs) { + WorldPoint loc = Rs2Player.getWorldLocation(); + if (loc != null && loc.distanceTo(bossWorldPoint) <= reachedDistance) { + break; + } + Rs2Walker.walkWithState(bossWorldPoint, reachedDistance); + sleep(600); } - if (Rs2Player.getWorldLocation().distanceTo(bossWorldPoint) <= 3) { + + WorldPoint finalLoc = Rs2Player.getWorldLocation(); + if (finalLoc != null && finalLoc.distanceTo(bossWorldPoint) <= reachedDistance) { if (debugLogging) {Microbot.log("Arrived at " + bossName + " lobby");} return; } From 087a3446fc4e192d9ff04c26e35c9ea3f22ce6da Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Sun, 3 May 2026 15:45:14 -0400 Subject: [PATCH 71/95] feat: add Shilo Village and Anywhere options to jewelry crafter (#414) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace brittle per-location furnace object IDs in CraftingLocation with a name-based lookup anchored at the configured WorldPoint (or the player when ANYWHERE), gated by a "Smelt" action check — matches AutoSmeltingScript's pattern. Adds ANYWHERE (use the nearest smelt-capable furnace wherever the player is) and SHILO_VILLAGE. Wires JewelryPlugin into debugPlugins for local debug runs. Co-authored-by: runsonmypc Co-authored-by: chsami --- .../crafting/jewelry/JewelryScript.java | 11 ++++++++-- .../jewelry/enums/CraftingLocation.java | 20 +++++++++---------- .../java/net/runelite/client/Microbot.java | 5 ++--- 3 files changed, 21 insertions(+), 15 deletions(-) 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 727801a313..338402bf14 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 @@ -3,6 +3,7 @@ import net.runelite.api.EquipmentInventorySlot; import net.runelite.api.ItemID; import net.runelite.api.Skill; +import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.crafting.jewelry.enums.*; @@ -308,11 +309,17 @@ public boolean run() { break; case CRAFTING: + WorldPoint furnaceLocation = plugin.getCraftingLocation().getFurnaceLocation(); + WorldPoint anchor = furnaceLocation != null ? furnaceLocation : Rs2Player.getWorldLocation(); Rs2TileObjectModel furnaceObject = Microbot.getRs2TileObjectCache().query() - .withId(plugin.getCraftingLocation().getFurnanceObjectID()).nearest(); + .withName("Furnace") + .where(o -> Rs2GameObject.hasAction(o, "Smelt")) + .nearest(anchor, 20); if (furnaceObject == null) { - Rs2Walker.walkTo(plugin.getCraftingLocation().getFurnaceLocation()); + if (furnaceLocation != null) { + Rs2Walker.walkTo(furnaceLocation); + } return; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/crafting/jewelry/enums/CraftingLocation.java b/src/main/java/net/runelite/client/plugins/microbot/crafting/jewelry/enums/CraftingLocation.java index e806d926f7..eb9cd14584 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/crafting/jewelry/enums/CraftingLocation.java +++ b/src/main/java/net/runelite/client/plugins/microbot/crafting/jewelry/enums/CraftingLocation.java @@ -2,7 +2,6 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -import net.runelite.api.ObjectID; import net.runelite.api.Quest; import net.runelite.api.QuestState; import net.runelite.api.coords.WorldPoint; @@ -12,17 +11,18 @@ @Getter @RequiredArgsConstructor public enum CraftingLocation { - - EDGEVILLE(new WorldPoint(3109, 3499, 0), ObjectID.FURNACE_16469, BankLocation.EDGEVILLE), - PORT_PHASMATYS(new WorldPoint(3687, 3479, 0), ObjectID.FURNACE_24009, BankLocation.PORT_PHASMATYS), - MOUNT_KARUULM(new WorldPoint(1324, 3808, 0), ObjectID.VOLCANIC_FURNACE, BankLocation.MOUNT_KARUULM), - ZANARIS(new WorldPoint(2401, 4473, 0), ObjectID.FURNACE_12100, BankLocation.ZANARIS), - FALADOR(new WorldPoint(2975, 3369, 0), ObjectID.FURNACE_24009, BankLocation.FALADOR_WEST); - + + ANYWHERE(null, null), + EDGEVILLE(new WorldPoint(3109, 3499, 0), BankLocation.EDGEVILLE), + PORT_PHASMATYS(new WorldPoint(3687, 3479, 0), BankLocation.PORT_PHASMATYS), + MOUNT_KARUULM(new WorldPoint(1324, 3808, 0), BankLocation.MOUNT_KARUULM), + ZANARIS(new WorldPoint(2401, 4473, 0), BankLocation.ZANARIS), + FALADOR(new WorldPoint(2975, 3369, 0), BankLocation.FALADOR_WEST), + SHILO_VILLAGE(new WorldPoint(2856, 2967, 0), BankLocation.SHILO_VILLAGE); + private final WorldPoint furnaceLocation; - private final int furnanceObjectID; private final BankLocation bankLocation; - + public boolean hasRequirements() { switch (this) { case PORT_PHASMATYS: diff --git a/src/test/java/net/runelite/client/Microbot.java b/src/test/java/net/runelite/client/Microbot.java index c57d3a1bf2..afcf25983c 100644 --- a/src/test/java/net/runelite/client/Microbot.java +++ b/src/test/java/net/runelite/client/Microbot.java @@ -9,6 +9,7 @@ import net.runelite.client.plugins.microbot.aiofighter.AIOFighterPlugin; import net.runelite.client.plugins.microbot.astralrc.AstralRunesPlugin; import net.runelite.client.plugins.microbot.autofishing.AutoFishingPlugin; +import net.runelite.client.plugins.microbot.crafting.jewelry.JewelryPlugin; import net.runelite.client.plugins.microbot.example.ExamplePlugin; import net.runelite.client.plugins.microbot.kraken.KrakenPlugin; import net.runelite.client.plugins.microbot.leftclickcast.LeftClickCastPlugin; @@ -22,9 +23,7 @@ public class Microbot private static final Class[] debugPlugins = { AIOFighterPlugin.class, - AgentServerPlugin.class, - LeftClickCastPlugin.class, - KrakenPlugin.class + AgentServerPlugin.class }; public static void main(String[] args) throws Exception From 2a6cd3c34649c037df55e12bd1d4ca875894c2e9 Mon Sep 17 00:00:00 2001 From: Jonathan Thomas <95548936+JThomasDevs@users.noreply.github.com> Date: Fri, 8 May 2026 08:13:15 -0600 Subject: [PATCH 72/95] first commit --- .../ColosseumPrayerArbiter.java | 47 ++ .../ColosseumPrayerConfig.java | 32 + .../ColosseumPrayerDemand.java | 24 + .../ColosseumPrayerPlugin.java | 47 ++ .../ColosseumPrayerScript.java | 734 ++++++++++++++++++ .../ColosseumPrayerThreat.java | 24 + .../ManticoreProjectilePrayers.java | 29 + 7 files changed, 937 insertions(+) create mode 100644 src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerArbiter.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerConfig.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerDemand.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerPlugin.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerScript.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerThreat.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ManticoreProjectilePrayers.java diff --git a/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerArbiter.java b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerArbiter.java new file mode 100644 index 0000000000..08739454c8 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerArbiter.java @@ -0,0 +1,47 @@ +package net.runelite.client.plugins.microbot.colosseumprayer; + +import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; + +import javax.annotation.Nullable; +import java.util.List; + +final class ColosseumPrayerArbiter { + + private ColosseumPrayerArbiter() { + } + + /** + * If multiple prayers are demanded the same tick, the threat with the lowest {@link ColosseumPrayerThreat#arbiterRank()} + * wins. Javelin + serpent shaman pairs are resolved in {@link net.runelite.client.plugins.microbot.colosseumprayer.ColosseumPrayerScript} + * by tick alternation, not here. If tied on rank, earliest entry in {@code demands} wins. + */ + @Nullable + static Rs2PrayerEnum resolve(List demands) { + if (demands.isEmpty()) { + return null; + } + ColosseumPrayerDemand best = null; + for (ColosseumPrayerDemand candidate : demands) { + if (best == null) { + best = candidate; + continue; + } + if (beats(candidate, best)) { + best = candidate; + } + } + if (best == null) { + return null; + } + return best.protection(); + } + + private static boolean beats(ColosseumPrayerDemand candidate, ColosseumPrayerDemand incumbent) { + int c = candidate.threat().arbiterRank(); + int i = incumbent.threat().arbiterRank(); + if (c < i) { + return true; + } + return false; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerConfig.java b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerConfig.java new file mode 100644 index 0000000000..6ef37c9f5a --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerConfig.java @@ -0,0 +1,32 @@ +package net.runelite.client.plugins.microbot.colosseumprayer; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigInformation; +import net.runelite.client.config.ConfigItem; + +@ConfigGroup(ColosseumPrayerConfig.configGroup) +@ConfigInformation("Prayer helper for Fortis Colosseum waves 1–11 (Manticore, Javelin Colossus, Serpent Shaman, Jaguar warrior, Shockwave Colossus). Wave 12 not included.") +public interface ColosseumPrayerConfig extends Config { + String configGroup = "micro-fortiscolosseum-prayer"; + + @ConfigItem( + keyName = "helperEnabled", + name = "Enable helper", + description = "Turn the scheduled prayer arbiter loop on.", + position = 0 + ) + default boolean helperEnabled() { + return true; + } + + @ConfigItem( + keyName = "debugLogSignals", + name = "Debug: log arbitration", + description = "Log winning prayer resolution each tick cycle (verbose).", + position = 1 + ) + default boolean debugLogSignals() { + return false; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerDemand.java b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerDemand.java new file mode 100644 index 0000000000..371f288c3e --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerDemand.java @@ -0,0 +1,24 @@ +package net.runelite.client.plugins.microbot.colosseumprayer; + +import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; + +/** + * Resolved demand for one protection overhead from a single threat class. + */ +final class ColosseumPrayerDemand { + private final ColosseumPrayerThreat threat; + private final Rs2PrayerEnum protection; + + ColosseumPrayerDemand(ColosseumPrayerThreat threat, Rs2PrayerEnum protection) { + this.threat = threat; + this.protection = protection; + } + + ColosseumPrayerThreat threat() { + return threat; + } + + Rs2PrayerEnum protection() { + return protection; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerPlugin.java new file mode 100644 index 0000000000..d9178ccb40 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerPlugin.java @@ -0,0 +1,47 @@ +package net.runelite.client.plugins.microbot.colosseumprayer; + +import com.google.inject.Provides; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.microbot.PluginConstants; + +import javax.inject.Inject; +import java.awt.*; + +@PluginDescriptor( + name = PluginConstants.DEFAULT_PREFIX + "Fortis Colosseum Prayer", + description = "Prayer helper for Fortis Colosseum waves 1–11: manticore, javelin colossus, serpent shaman, jaguar warrior, shockwave colossus.", + tags = {"colosseum", "fortis", "prayer", "microbot"}, + authors = {"Microbot Hub"}, + version = ColosseumPrayerPlugin.version, + minClientVersion = "1.9.8.8", + cardUrl = "https://chsami.github.io/Microbot-Hub/ColosseumPrayerPlugin/assets/card.png", + iconUrl = "https://chsami.github.io/Microbot-Hub/ColosseumPrayerPlugin/assets/icon.png", + enabledByDefault = PluginConstants.DEFAULT_ENABLED, + isExternal = PluginConstants.IS_EXTERNAL +) +@Slf4j +public class ColosseumPrayerPlugin extends Plugin { + + static final String version = "1.0.3"; + + @Provides + ColosseumPrayerConfig provideConfig(ConfigManager configManager) { + return configManager.getConfig(ColosseumPrayerConfig.class); + } + + @Inject + private ColosseumPrayerScript colosseumPrayerScript; + + @Override + protected void startUp() throws AWTException { + colosseumPrayerScript.register(); + } + + @Override + protected void shutDown() { + colosseumPrayerScript.unregister(); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerScript.java b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerScript.java new file mode 100644 index 0000000000..e40bb21e66 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerScript.java @@ -0,0 +1,734 @@ +package net.runelite.client.plugins.microbot.colosseumprayer; + +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Actor; +import net.runelite.api.ActorSpotAnim; +import net.runelite.api.Client; +import net.runelite.api.NPC; +import net.runelite.api.Player; +import net.runelite.api.Projectile; +import net.runelite.api.WorldView; +import net.runelite.api.coords.WorldArea; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.events.GameTick; +import net.runelite.api.events.ProjectileMoved; +import net.runelite.client.eventbus.EventBus; +import net.runelite.client.eventbus.Subscribe; +import net.runelite.client.plugins.microbot.Microbot; +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.prayer.Rs2Prayer; +import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; + +import javax.inject.Inject; +import javax.inject.Singleton; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * Arbiter-driven threats on {@link GameTick}; manticore triple uses {@link ProjectileMoved} signals. + */ +@Slf4j +@Singleton +public class ColosseumPrayerScript { + + private static final int TILE_RANGE_MELEE_THREAT = 3; + private static final int TILE_RANGE_RANGED_MAGING = 18; + + private final EventBus eventBus; + private final ColosseumPrayerConfig config; + private final Map manticoreStates = new HashMap<>(); + private final Map seenManticoreLaunches = new HashMap<>(); + private boolean registered; + /** Suppress duplicate toggles within the same game tick during manticore volleys (styles are one tick apart). */ + private int lastManticoreSwitchGameTick = -1; + private int lastManticoreDebugTick = -1; + private int lastAnyPrayerSwitchTick = -1; + private Rs2PrayerEnum lastAnyPrayerSwitchPrayer; + + @Inject + ColosseumPrayerScript(EventBus eventBus, ColosseumPrayerConfig config) { + this.eventBus = eventBus; + this.config = config; + } + + void register() { + if (registered) { + return; + } + eventBus.register(this); + registered = true; + } + + void unregister() { + if (!registered) { + return; + } + eventBus.unregister(this); + registered = false; + lastManticoreSwitchGameTick = -1; + lastManticoreDebugTick = -1; + lastAnyPrayerSwitchTick = -1; + lastAnyPrayerSwitchPrayer = null; + manticoreStates.clear(); + seenManticoreLaunches.clear(); + } + + @Subscribe + public void onProjectileMoved(ProjectileMoved event) { + if (!config.helperEnabled()) { + return; + } + if (!Microbot.isLoggedIn()) { + return; + } + Client client = Microbot.getClient(); + if (client == null) { + return; + } + Projectile projectile = event.getProjectile(); + if (projectile == null) { + return; + } + Rs2PrayerEnum incoming = ManticoreProjectilePrayers.prayerForProjectileId(projectile.getId()); + if (incoming == null) { + return; + } + if (manticoreStates.isEmpty()) { + return; + } + if (projectile.getRemainingCycles() <= 0) { + return; + } + int tickNow = client.getTickCount(); + WorldPoint targetPoint = projectile.getTargetPoint(); + long launchKey = buildManticoreLaunchKey(projectile.getId(), projectile.getStartCycle(), targetPoint); + Integer seenTick = seenManticoreLaunches.get(launchKey); + if (seenTick != null) { + return; + } + seenManticoreLaunches.put(launchKey, tickNow); + ManticoreCycleState state = selectLaunchState(tickNow); + if (state == null) { + return; + } + state.markSeen(tickNow); + state.onProjectileLaunch(incoming, tickNow); + Rs2PrayerEnum nextPrayer = state.currentDesiredPrayer(); + if (nextPrayer == null) { + nextPrayer = incoming; + } + switchManticoreProtection(client, nextPrayer); + if (config.debugLogSignals()) { + log.info("[ColosseumPrayer] Manticore idx={} projectile={} launched={} next={}", + state.npcIndex, projectile.getId(), incoming, nextPrayer); + } + } + + @Subscribe + public void onGameTick(GameTick tick) { + if (!Microbot.isLoggedIn()) { + return; + } + if (!config.helperEnabled()) { + return; + } + Client client = Microbot.getClient(); + if (client == null) { + return; + } + refreshManticoreStates(client); + Rs2PrayerEnum manticorePrayer = resolveManticorePrayer(client); + if (manticorePrayer != null) { + switchManticoreProtection(client, manticorePrayer); + if (config.debugLogSignals()) { + int tickNow = client.getTickCount(); + if (tickNow != lastManticoreDebugTick) { + log.info("[ColosseumPrayer] MantiTick tick={} choose={} states={}", + tickNow, manticorePrayer, formatManticoreStates()); + lastManticoreDebugTick = tickNow; + } + } + return; + } + if (!hasTrackedThreatNpcInScene()) { + return; + } + List demands = gatherDemands(); + Player localPlayer = client.getLocalPlayer(); + if (shouldFlickJavelinWithSerpentShaman(demands, localPlayer)) { + Client clientForFlick = Microbot.getClient(); + if (clientForFlick != null) { + applyJavelinSerpentShamanFlickPrayer(clientForFlick); + } + if (config.debugLogSignals()) { + log.info("[ColosseumPrayer] Javelin + Serpent Shaman flick tick={}", + Microbot.getClient() != null ? Microbot.getClient().getTickCount() : -1); + } + return; + } + if (shouldForceSerpentMagePrayer(demands, localPlayer)) { + trySwitchPrayer(client, Rs2PrayerEnum.PROTECT_MAGIC); + if (config.debugLogSignals()) { + log.info("[ColosseumPrayer] LOS override: shaman visible, javelin blocked -> PROTECT_MAGIC"); + } + return; + } + Rs2PrayerEnum resolved = ColosseumPrayerArbiter.resolve(demands); + if (resolved == null) { + if (config.debugLogSignals()) { + log.info("[ColosseumPrayer] Tick: tracked threats nearby but no active demand (interact/range)"); + } + return; + } + trySwitchPrayer(client, resolved); + if (config.debugLogSignals()) { + log.info("[ColosseumPrayer] demands={} resolved={}", demands.size(), resolved); + } + } + + private void refreshManticoreStates(Client client) { + WorldView worldView = client.getTopLevelWorldView(); + if (worldView == null) { + manticoreStates.clear(); + seenManticoreLaunches.clear(); + return; + } + int tickNow = client.getTickCount(); + seenManticoreLaunches.entrySet().removeIf(e -> tickNow - e.getValue() > 8); + Set liveIndexes = new HashSet<>(); + for (NPC npc : worldView.npcs()) { + if (npc == null) { + continue; + } + if (npc.isDead()) { + continue; + } + if (!isManticoreName(npc.getName())) { + continue; + } + int npcIndex = npc.getIndex(); + liveIndexes.add(npcIndex); + ManticoreCycleState state = manticoreStates.computeIfAbsent(npcIndex, ManticoreCycleState::new); + state.markSeen(tickNow); + List visualOrder = extractManticoreOrbOrder(npc); + if (visualOrder.size() == 3) { + state.updateVisualOrder(visualOrder, tickNow); + } + Player local = client.getLocalPlayer(); + if (local != null) { + WorldPoint npcWp = npc.getWorldLocation(); + WorldPoint playerWp = local.getWorldLocation(); + if (npcWp != null && playerWp != null) { + state.distanceToPlayer = playerWp.distanceTo(npcWp); + } + WorldArea npcArea = npc.getWorldArea(); + WorldArea playerArea = local.getWorldArea(); + if (npcArea != null && playerArea != null) { + state.hasLineOfSight = playerArea.hasLineOfSightTo(worldView, npcArea); + } else { + state.hasLineOfSight = false; + } + } else { + state.hasLineOfSight = false; + } + } + manticoreStates.entrySet().removeIf(e -> { + ManticoreCycleState state = e.getValue(); + if (!liveIndexes.contains(e.getKey())) { + return true; + } + return state.isStale(tickNow); + }); + } + + private Rs2PrayerEnum resolveManticorePrayer(Client client) { + if (manticoreStates.isEmpty()) { + return null; + } + int tickNow = client.getTickCount(); + ManticoreCycleState best = null; + for (ManticoreCycleState state : manticoreStates.values()) { + if (!state.hasLineOfSight) { + continue; + } + Rs2PrayerEnum desired = state.currentDesiredPrayer(); + if (desired == null) { + continue; + } + if (best == null) { + best = state; + continue; + } + if (state.beats(best, tickNow)) { + best = state; + } + } + if (best == null) { + return null; + } + return best.currentDesiredPrayer(); + } + + private static List extractManticoreOrbOrder(NPC npc) { + List visuals = new ArrayList<>(); + for (ActorSpotAnim spotAnim : npc.getSpotAnims()) { + if (spotAnim == null) { + continue; + } + Rs2PrayerEnum prayer = ManticoreProjectilePrayers.prayerForProjectileId(spotAnim.getId()); + if (prayer == null) { + continue; + } + visuals.add(new ManticoreOrbVisual(prayer, spotAnim.getHeight(), spotAnim.getStartCycle())); + } + visuals.sort(Comparator + .comparingInt((ManticoreOrbVisual v) -> v.height) + .thenComparingInt(v -> v.startCycle)); + List order = new ArrayList<>(); + for (ManticoreOrbVisual visual : visuals) { + order.add(visual.prayer); + } + return order; + } + + private static boolean isManticoreName(String name) { + if (name == null) { + return false; + } + return name.toLowerCase().contains("manticore"); + } + + private static long buildManticoreLaunchKey(int projectileId, int startCycle, WorldPoint targetPoint) { + long key = 17; + key = key * 31 + projectileId; + key = key * 31 + startCycle; + if (targetPoint != null) { + key = key * 31 + targetPoint.getX(); + key = key * 31 + targetPoint.getY(); + key = key * 31 + targetPoint.getPlane(); + } + return key; + } + + private ManticoreCycleState selectLaunchState(int tickNow) { + ManticoreCycleState best = null; + for (ManticoreCycleState candidate : manticoreStates.values()) { + if (!candidate.hasLineOfSight) { + continue; + } + if (candidate.currentDesiredPrayer() == null) { + continue; + } + if (best == null || candidate.beats(best, tickNow)) { + best = candidate; + } + } + return best; + } + + private String formatManticoreStates() { + if (manticoreStates.isEmpty()) { + return "none"; + } + List sorted = new ArrayList<>(manticoreStates.values()); + sorted.sort(Comparator.comparingInt(s -> s.npcIndex)); + List chunks = new ArrayList<>(); + for (ManticoreCycleState state : sorted) { + chunks.add(state.describe()); + } + return String.join(" | ", chunks); + } + + private void switchManticoreProtection(Client client, Rs2PrayerEnum prayer) { + int tickNow = client.getTickCount(); + if (Rs2Prayer.isPrayerActive(prayer)) { + return; + } + if (tickNow == lastManticoreSwitchGameTick) { + return; + } + if (!trySwitchPrayer(client, prayer)) { + return; + } + lastManticoreSwitchGameTick = tickNow; + } + + /** + * Alternate Protect Missiles and Protect Magic when both ranged javelin autos and serpent shamans threaten. + * Skipped while a jaguar warrior also demands melee (fallback to arbiter). + */ + private boolean shouldFlickJavelinWithSerpentShaman(List demands, Player player) { + if (!demandHasThreat(demands, ColosseumPrayerThreat.JAVELIN_COLOSSUS)) { + return false; + } + if (!demandHasThreat(demands, ColosseumPrayerThreat.SERPENT_SHAMAN)) { + return false; + } + if (demandHasThreat(demands, ColosseumPrayerThreat.JAGUAR_WARRIOR)) { + return false; + } + if (!hasLineOfSightThreat(ThreatKind.JAVELIN_COLOSSUS, player)) { + return false; + } + if (!hasLineOfSightThreat(ThreatKind.SERPENT_SHAMAN, player)) { + return false; + } + return true; + } + + private boolean shouldForceSerpentMagePrayer(List demands, Player player) { + if (!demandHasThreat(demands, ColosseumPrayerThreat.JAVELIN_COLOSSUS)) { + return false; + } + if (!demandHasThreat(demands, ColosseumPrayerThreat.SERPENT_SHAMAN)) { + return false; + } + boolean shamanLos = hasLineOfSightThreat(ThreatKind.SERPENT_SHAMAN, player); + boolean javelinLos = hasLineOfSightThreat(ThreatKind.JAVELIN_COLOSSUS, player); + if (shamanLos && !javelinLos) { + return true; + } + return false; + } + + private static boolean demandHasThreat(List demands, ColosseumPrayerThreat threat) { + for (ColosseumPrayerDemand d : demands) { + if (d.threat() == threat) { + return true; + } + } + return false; + } + + /** + * Even game ticks: Protect Missiles (javelin autos). Odd ticks: Protect Magic (serpent shaman). + */ + private void applyJavelinSerpentShamanFlickPrayer(Client client) { + int tick = client.getTickCount(); + Rs2PrayerEnum want; + if (tick % 2 == 0) { + want = Rs2PrayerEnum.PROTECT_RANGE; + } else { + want = Rs2PrayerEnum.PROTECT_MAGIC; + } + if (Rs2Prayer.isPrayerActive(want)) { + return; + } + trySwitchPrayer(client, want); + } + + private boolean trySwitchPrayer(Client client, Rs2PrayerEnum prayer) { + if (client == null || prayer == null) { + return false; + } + int tick = client.getTickCount(); + if (Rs2Prayer.isPrayerActive(prayer)) { + return false; + } + if (tick == lastAnyPrayerSwitchTick) { + if (prayer == lastAnyPrayerSwitchPrayer) { + return false; + } + return false; + } + boolean ok = Rs2Prayer.toggle(prayer, true, false); + if (ok) { + lastAnyPrayerSwitchTick = tick; + lastAnyPrayerSwitchPrayer = prayer; + } + return ok; + } + + private boolean hasLineOfSightThreat(ThreatKind kind, Player player) { + if (player == null) { + return false; + } + Client client = Microbot.getClient(); + if (client == null) { + return false; + } + WorldView worldView = client.getTopLevelWorldView(); + if (worldView == null) { + return false; + } + for (Rs2NpcModel npc : Microbot.getRs2NpcCache().query().toList()) { + if (npc == null || npc.isDead()) { + continue; + } + if (classifyThreat(npc) != kind) { + continue; + } + WorldArea npcArea = npc.getWorldArea(); + WorldArea playerArea = player.getWorldArea(); + if (npcArea == null || playerArea == null) { + continue; + } + if (playerArea.hasLineOfSightTo(worldView, npcArea)) { + return true; + } + } + return false; + } + + private boolean hasTrackedThreatNpcInScene() { + if (Microbot.getClient() == null) { + return false; + } + for (Rs2NpcModel npc : Microbot.getRs2NpcCache().query().toList()) { + if (npc == null) { + continue; + } + if (npc.isDead()) { + continue; + } + ThreatKind kind = classifyThreat(npc); + if (kind != null) { + return true; + } + } + return false; + } + + List gatherDemands() { + Player player = Microbot.getClient() != null ? Microbot.getClient().getLocalPlayer() : null; + List demands = new ArrayList<>(); + if (player == null) { + return demands; + } + WorldPoint playerWp = Rs2Player.getWorldLocation(); + boolean instanced = isInstanced(); + + for (Rs2NpcModel npc : Microbot.getRs2NpcCache().query().toList()) { + if (npc == null) { + continue; + } + if (npc.isDead()) { + continue; + } + ThreatKind kind = classifyThreat(npc); + if (kind == null) { + continue; + } + if (kind == ThreatKind.MANTICORE) { + continue; + } + WorldPoint npcWp = npcWorldLocation(npc, instanced); + if (npcWp == null) { + continue; + } + int dist = playerWp.distanceTo(npcWp); + + if (kind == ThreatKind.JAVELIN_COLOSSUS) { + if (threatEligible(player, npc, dist, TILE_RANGE_RANGED_MAGING)) { + demands.add(new ColosseumPrayerDemand(ColosseumPrayerThreat.JAVELIN_COLOSSUS, + Rs2PrayerEnum.PROTECT_RANGE)); + } + continue; + } + if (kind == ThreatKind.SERPENT_SHAMAN) { + if (threatEligible(player, npc, dist, TILE_RANGE_RANGED_MAGING)) { + demands.add(new ColosseumPrayerDemand(ColosseumPrayerThreat.SERPENT_SHAMAN, + Rs2PrayerEnum.PROTECT_MAGIC)); + } + continue; + } + if (kind == ThreatKind.SHOCKWAVE_COLOSSUS) { + if (threatEligible(player, npc, dist, TILE_RANGE_RANGED_MAGING)) { + demands.add(new ColosseumPrayerDemand(ColosseumPrayerThreat.SHOCKWAVE_COLOSSUS, + Rs2PrayerEnum.PROTECT_MAGIC)); + } + continue; + } + if (kind == ThreatKind.JAGUAR_WARRIOR) { + if (threatEligible(player, npc, dist, TILE_RANGE_MELEE_THREAT)) { + demands.add(new ColosseumPrayerDemand(ColosseumPrayerThreat.JAGUAR_WARRIOR, + Rs2PrayerEnum.PROTECT_MELEE)); + } + } + } + return demands; + } + + private enum ThreatKind { + MANTICORE, + JAVELIN_COLOSSUS, + SERPENT_SHAMAN, + SHOCKWAVE_COLOSSUS, + JAGUAR_WARRIOR + } + + private static ThreatKind classifyThreat(Rs2NpcModel npc) { + if (npc == null) { + return null; + } + String name = npc.getName(); + if (name == null) { + return null; + } + String normalized = name.toLowerCase(); + if (normalized.contains("manticore")) { + return ThreatKind.MANTICORE; + } + if (normalized.contains("serpent")) { + if (normalized.contains("shaman")) { + return ThreatKind.SERPENT_SHAMAN; + } + } + if (normalized.contains("shockwave")) { + return ThreatKind.SHOCKWAVE_COLOSSUS; + } + if (normalized.contains("colossus")) { + return ThreatKind.JAVELIN_COLOSSUS; + } + if (normalized.contains("javelin")) { + return ThreatKind.JAVELIN_COLOSSUS; + } + if (normalized.contains("jaguar")) { + return ThreatKind.JAGUAR_WARRIOR; + } + return null; + } + + private static boolean threatEligible(Player player, Rs2NpcModel npc, int chebyshevTiles, int maxRangeTiles) { + if (npc.isInteractingWithPlayer()) { + return true; + } + Actor interacting = npc.getInteracting(); + if (Objects.equals(interacting, player)) { + return true; + } + return chebyshevTiles <= maxRangeTiles; + } + + private static boolean isInstanced() { + Client c = Microbot.getClient(); + if (c == null) { + return false; + } + WorldView wv = c.getTopLevelWorldView(); + if (wv == null) { + return false; + } + return wv.getScene().isInstance(); + } + + private static WorldPoint npcWorldLocation(Rs2NpcModel npc, boolean instanced) { + if (npc == null) { + return null; + } + if (instanced) { + LocalPoint lp = npc.getLocalLocation(); + Client c = Microbot.getClient(); + if (lp == null) { + return null; + } + if (c == null) { + return null; + } + WorldPoint wp = WorldPoint.fromLocalInstance(c, lp); + return wp; + } + return npc.getWorldLocation(); + } + + private static final class ManticoreOrbVisual { + private final Rs2PrayerEnum prayer; + private final int height; + private final int startCycle; + + private ManticoreOrbVisual(Rs2PrayerEnum prayer, int height, int startCycle) { + this.prayer = prayer; + this.height = height; + this.startCycle = startCycle; + } + } + + private static final class ManticoreCycleState { + private final int npcIndex; + private final List orbOrder = new ArrayList<>(3); + private int nextPrayerIndex; + private int lastProjectileTick = -1000; + private int lastSeenTick = -1000; + private int distanceToPlayer = 99; + private boolean hasLineOfSight; + + private ManticoreCycleState(int npcIndex) { + this.npcIndex = npcIndex; + } + + private void updateVisualOrder(List order, int tickNow) { + if (!orbOrder.equals(order)) { + orbOrder.clear(); + orbOrder.addAll(order); + nextPrayerIndex = 0; + } + lastSeenTick = tickNow; + } + + private void onProjectileLaunch(Rs2PrayerEnum launchedPrayer, int tickNow) { + lastProjectileTick = tickNow; + lastSeenTick = tickNow; + if (orbOrder.isEmpty()) { + return; + } + int launchedIndex = orbOrder.indexOf(launchedPrayer); + if (launchedIndex >= 0) { + nextPrayerIndex = (launchedIndex + 1) % orbOrder.size(); + return; + } + nextPrayerIndex = (nextPrayerIndex + 1) % orbOrder.size(); + } + + private Rs2PrayerEnum currentDesiredPrayer() { + if (orbOrder.isEmpty()) { + return null; + } + if (nextPrayerIndex < 0 || nextPrayerIndex >= orbOrder.size()) { + nextPrayerIndex = 0; + } + return orbOrder.get(nextPrayerIndex); + } + + private void markSeen(int tickNow) { + lastSeenTick = tickNow; + } + + private boolean isStale(int tickNow) { + return tickNow - lastSeenTick > 10; + } + + private boolean beats(ManticoreCycleState other, int tickNow) { + boolean thisRecentlyLaunched = tickNow - this.lastProjectileTick <= 2; + boolean otherRecentlyLaunched = tickNow - other.lastProjectileTick <= 2; + if (thisRecentlyLaunched != otherRecentlyLaunched) { + return thisRecentlyLaunched; + } + if (this.lastProjectileTick != other.lastProjectileTick) { + return this.lastProjectileTick > other.lastProjectileTick; + } + if (this.distanceToPlayer != other.distanceToPlayer) { + return this.distanceToPlayer < other.distanceToPlayer; + } + return this.npcIndex < other.npcIndex; + } + + private String describe() { + return "idx=" + npcIndex + + ",next=" + currentDesiredPrayer() + + ",order=" + orbOrder + + ",dist=" + distanceToPlayer + + ",los=" + hasLineOfSight + + ",lastProjTick=" + lastProjectileTick; + } + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerThreat.java b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerThreat.java new file mode 100644 index 0000000000..82d4e01baa --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ColosseumPrayerThreat.java @@ -0,0 +1,24 @@ +package net.runelite.client.plugins.microbot.colosseumprayer; + +/** + * Threat sources for arbitration. Lower rank number wins same-tick conflicts. + * Baseline priority: manticore → javelin → serpent shaman → jaguar → shockwave. + * When javelin and serpent shaman both demand different prayers, the script alternates per tick instead of arbiter ranking. + */ +enum ColosseumPrayerThreat { + MANTICORE(1), + JAVELIN_COLOSSUS(2), + SERPENT_SHAMAN(3), + JAGUAR_WARRIOR(4), + SHOCKWAVE_COLOSSUS(5); + + private final int arbiterRank; + + ColosseumPrayerThreat(int arbiterRank) { + this.arbiterRank = arbiterRank; + } + + int arbiterRank() { + return arbiterRank; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ManticoreProjectilePrayers.java b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ManticoreProjectilePrayers.java new file mode 100644 index 0000000000..02b2425947 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/colosseumprayer/ManticoreProjectilePrayers.java @@ -0,0 +1,29 @@ +package net.runelite.client.plugins.microbot.colosseumprayer; + +import net.runelite.api.gameval.SpotanimID; +import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; + +import javax.annotation.Nullable; + +/** + * Maps Fortis Colosseum manticore orb projectiles ({@link SpotanimID}) to protection prayers. + */ +final class ManticoreProjectilePrayers { + + private ManticoreProjectilePrayers() { + } + + @Nullable + static Rs2PrayerEnum prayerForProjectileId(int projectileId) { + if (projectileId == SpotanimID.VFX_MANTICORE_01_PROJECTILE_MAGIC_01) { + return Rs2PrayerEnum.PROTECT_MAGIC; + } + if (projectileId == SpotanimID.VFX_MANTICORE_01_PROJECTILE_RANGED_01) { + return Rs2PrayerEnum.PROTECT_RANGE; + } + if (projectileId == SpotanimID.VFX_MANTICORE_01_PROJECTILE_MELEE_01) { + return Rs2PrayerEnum.PROTECT_MELEE; + } + return null; + } +} From 6b65bc651252cb716b7756f47dd7cbb39a8af091 Mon Sep 17 00:00:00 2001 From: Sami Date: Sun, 10 May 2026 07:40:23 +0200 Subject: [PATCH 73/95] fix: guard AIO Fighter slayer blacklist config --- .../microbot/aiofighter/AIOFighterConfig.java | 3 +-- .../microbot/aiofighter/AIOFighterPlugin.java | 24 ++++++++++++++++--- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterConfig.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterConfig.java index 5b11b15b63..ba84deb70d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterConfig.java @@ -915,7 +915,7 @@ default int slayerTaskWeaknessThreshold() { hidden = true ) default String blacklistedSlayerNpcs() { - return "null,"; + return ""; } @@ -964,4 +964,3 @@ default WorldPoint safeSpot() { } } - diff --git a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java index 7ba43ad252..c885c964d2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java @@ -38,6 +38,7 @@ import javax.inject.Inject; import java.awt.*; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.LinkedHashSet; @@ -263,13 +264,30 @@ public static InventorySetup getDefaultInventorySetup() { } public static void addBlacklistedSlayerNpcs(String npcName) { - String existing = getConfig("blacklistedSlayerNpcs", String.class); - setConfig("blacklistedSlayerNpcs", existing + npcName + ","); + if (npcName == null || npcName.trim().isEmpty()) { + return; + } + + LinkedHashSet blacklistedNpcs = getBlacklistedSlayerNpcSet(); + blacklistedNpcs.add(npcName.trim()); + setConfig("blacklistedSlayerNpcs", String.join(",", blacklistedNpcs) + ","); } public static List getBlacklistedSlayerNpcs() { + return new ArrayList<>(getBlacklistedSlayerNpcSet()); + } + + private static LinkedHashSet getBlacklistedSlayerNpcSet() { String stored = getConfig("blacklistedSlayerNpcs", String.class); - return Arrays.asList(stored.split(",")); + if (stored == null || stored.trim().isEmpty()) { + return new LinkedHashSet<>(); + } + + return Arrays.stream(stored.split(",")) + .map(String::trim) + .filter(entry -> !entry.isEmpty()) + .filter(entry -> !"null".equalsIgnoreCase(entry)) + .collect(Collectors.toCollection(LinkedHashSet::new)); } private static LinkedHashSet normalizeCsvEntries(String rawCsv) { From d07eb1d7e7565c6a4c4e226f2367da19e4b84131 Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Sun, 10 May 2026 02:53:21 -0400 Subject: [PATCH 74/95] fix(themess): rewrite script for batch correctness and burn recovery (#431) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(themess): rewrite script for batch-correctness, Make-All semantics, and burn recovery The Mess plugin was running 1-2 actions per dispatcher tick and re-issuing combine/cook calls between Make-X items, capping every batch at a few products and stalling repeatedly. This rewrite consolidates the per-dish flow around a single principle: each phase blocks until its consumed ingredient hits 0, so the dispatcher cannot pick a competing guard mid-chain. - cookOnOven and combineAll/combineDoughDialog now block on inventory delta (not animation, which combines don't have) until the consumed item is depleted, with stall guards. - Per-dish guards reordered so cooking-to-zero precedes any combine that uses its output (no more "started combining the moment 2 cooked meats appeared"). - Withdrawal targets size to predecessor counts so burn-induced leftover shells/water/etc. carry into the next batch and the chain tops up rather than over-fetching. - Cupboard withdraws use VarClientStr.INPUT_TEXT instead of typeString for the Enter Amount prompt, fixing digits leaking into game chat when the chatbox input hadn't taken focus. - Cupboard interactions use exact-tile lookup (findObjectByLocation) to avoid mis-targeting the food cupboard when querying near the utensil cupboard one tile away. - Burnt food dropped between phases (gated on !isAnimating and !isProductionWidgetOpen so the cook chain isn't interrupted). - Hard-fail invariant: if any chain item exceeds BATCH_SIZE the script shuts down loudly rather than over-fetching. - Bowl-return guard gated on count(PIE_SHELL) >= BATCH_SIZE so freshly withdrawn bowls aren't immediately returned during top-up. Version bump 1.0.3 → 1.1.0. * fix(themess): stew leftover recovery and combine-chain stall guard - Stew gates takeRawMeat on count(BOWL_WATER) >= BATCH_SIZE so leftover bowls from prior burns drive the top-up formula instead of looping a tiny 2-stew batch. - Reorder stew guards: fillBowls before BOWL_WATER consumers so a partial fill cannot under-fetch downstream raw meat. - Mirror Rs2GrandExchange.setQuantity timing (sleep 600 + 400 around setChatboxAmount) in withdrawFromCupboard and takeRawMeat. Without these, the chatbox silently drops small-qty values, hanging withdrawals. - fillBowls timeout 8s -> 15s; 14 bowls at game-tick speed is ~8.4s with no headroom. - waitForCombineChain stall guard from min(c1,c2) to c1+c2: knife persists at 2 during pizza pineapple-cutting, pinning min and tripping the stall guard after ~3 cuts. Sum decreases on every action. --------- Co-authored-by: runsonmypc --- .../plugins/microbot/mess/TheMessPlugin.java | 2 +- .../plugins/microbot/mess/TheMessScript.java | 1212 ++++++++--------- 2 files changed, 547 insertions(+), 667 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessPlugin.java index b7bdf07dab..74ad3af6fc 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessPlugin.java @@ -38,7 +38,7 @@ @Slf4j public class TheMessPlugin extends Plugin { - static final String version = "1.0.3"; + static final String version = "1.1.0"; @Inject private TheMessConfig config; diff --git a/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessScript.java b/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessScript.java index 58a8c0fa41..c374705532 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mess/TheMessScript.java @@ -1,10 +1,11 @@ package net.runelite.client.plugins.microbot.mess; import lombok.Getter; -import lombok.Setter; import lombok.extern.slf4j.Slf4j; import net.runelite.api.GameState; import net.runelite.api.Skill; +import net.runelite.api.TileObject; +import net.runelite.api.VarClientStr; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.InterfaceID; import net.runelite.api.gameval.ItemID; @@ -19,7 +20,6 @@ import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; import net.runelite.client.plugins.microbot.util.camera.Rs2Camera; import net.runelite.client.plugins.microbot.util.dialogues.Rs2Dialogue; -import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; 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; @@ -27,756 +27,640 @@ 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.settings.Rs2Settings; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; -import org.slf4j.event.Level; import java.awt.event.KeyEvent; import java.util.Set; import java.util.concurrent.TimeUnit; -import java.util.function.BooleanSupplier; import java.util.stream.Collectors; -import java.util.stream.IntStream; @Slf4j public class TheMessScript extends Script { private static final WorldPoint UTENSIL_CUPBOARD_LOC = new WorldPoint(1644, 3624, 0); - private static final WorldPoint FOOD_CUPBOARD_LOC = new WorldPoint(1645, 3623, 0); - private static final WorldPoint SINK_LOC = new WorldPoint(1644, 3628, 0); - private static final WorldPoint MEAT_TABLE_LOC = new WorldPoint(1645, 3630, 0); - private static final WorldPoint CLAY_OVEN_LOC = new WorldPoint(1648, 3627, 0); - private static final WorldPoint BUFFET_TABLE_LOC = new WorldPoint(1640, 3629, 0); - private static final int STEW_WIDGET_BAR_ID = 15400974; - private static final int PIE_WIDGET_BAR_ID = 15400966; - private static final int PIZZA_WIDGET_BAR_ID = 15400970; - private TheMessOverlay overlay; - private TheMessConfig config; + private static final WorldPoint FOOD_CUPBOARD_LOC = new WorldPoint(1645, 3623, 0); + private static final WorldPoint SINK_LOC = new WorldPoint(1644, 3628, 0); + private static final WorldPoint MEAT_TABLE_LOC = new WorldPoint(1645, 3630, 0); + private static final WorldPoint CLAY_OVEN_LOC = new WorldPoint(1648, 3627, 0); + private static final WorldPoint BUFFET_TABLE_LOC = new WorldPoint(1640, 3629, 0); + private static final WorldPoint MESS_HUB = new WorldPoint(1645, 3627, 0); + + private static final int SHOP_WIDGET_ID = 15859715; + private static final int SHOP_CLOSE_PARENT = 15859713; + + private static final int APPRECIATION_BAR_PIE = 15400966; + private static final int APPRECIATION_BAR_PIZZA = 15400970; + private static final int APPRECIATION_BAR_STEW = 15400974; - @Getter - @Setter - private State currentState = State.WAITING; + private static final int BATCH_SIZE = 14; + private static final int PIZZA_BATCH_SIZE = 13; // 2 knife slots reserved + + private TheMessConfig config; + private TheMessOverlay overlay; public boolean run(TheMessConfig config, TheMessOverlay overlay) { - debug("The Mess Script is starting up..."); - Microbot.enableAutoRunOn = false; - this.overlay = overlay; this.config = config; - - setCurrentState(State.WAITING); - setOrderOfStates(); + this.overlay = overlay; + Microbot.enableAutoRunOn = false; Rs2Antiban.setActivity(Activity.GENERAL_COOKING); Rs2AntibanSettings.naturalMouse = true; - Rs2AntibanSettings.actionCooldownActive = true; - Rs2Antiban.setTIMEOUT(Rs2Random.betweenInclusive(1, 4)); - /* - * Set camera settings for the script. - * To avoid clicking through UI elements like inventory and such. - */ Rs2Camera.setZoom(Rs2Random.randomGaussian(200, 20)); - Rs2Camera.setYaw((Rs2Random.dicePercentage(50)? Rs2Random.randomGaussian(750, 50) : Rs2Random.randomGaussian(1700, 50))); + Rs2Camera.setYaw(Rs2Random.dicePercentage(50) + ? Rs2Random.randomGaussian(750, 50) + : Rs2Random.randomGaussian(1700, 50)); Rs2Camera.setPitch(Rs2Random.betweenInclusive(418, 512)); - mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { - try { - if (!Microbot.isLoggedIn() || !super.run() || !isRunning()) return; - if (BreakHandlerScript.isBreakActive() && getCurrentState() == State.WAITING) return; + setStatus("Starting"); + mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(this::tick, + 0, 600, TimeUnit.MILLISECONDS); + return true; + } + + private long lastDecisionLogMs = 0; + private String lastDecision = ""; - handleState(); + private void tick() { + try { + if (!Microbot.isLoggedIn() || !super.run() || !isRunning()) return; + if (BreakHandlerScript.isBreakActive()) { setStatus("Break"); return; } - } catch (Exception ex) { - System.out.println(ex.getMessage()); + if (!inMess()) { logDecision("walkToMess"); walkToMess(); return; } + if (hasJunk()) { logDecision("cleanInventory"); runCleanInventory(); return; } + if (violatesBatchInvariant()) { setStatus("FAIL: batch invariant"); shutdown(); return; } + dropBurnt(); + + switch (config.dish()) { + case STEW: stewTick(); break; + case MEAT_PIE: meatPieTick(); break; + case PIZZA: pizzaTick(); break; } - }, 0, 600, TimeUnit.MILLISECONDS); - return true; + } catch (Exception ex) { + log.warn("tick failed: {}", ex.getMessage(), ex); + } } - private void handleState() { - switch (getCurrentState()) { - case WAITING: - if (!Microbot.isLoggedIn() || !super.run() || !isRunning()) return; - if (BreakHandlerScript.isBreakActive()) return; - break; - case GET_EMPTY_BOWLS: - case GET_KNIFE: - case GET_EMPTY_PIE_DISHES: - sleepUntil(getUtensils()); - break; - case RETURN_EMPTY_BOWLS: - sleepUntil(returnEmptyBowls()); - break; - case USE_SINK: - sleepUntil(fillBowl()); - break; - case GET_MEAT: - sleepUntil(getMeat()); - break; - case USE_CLAY_OVEN: - case FINISH_COOKING: - sleepUntil(cook()); - break; - case COMBINE_MEAT_PIE: - case COMBINE_MEAT_WATER: - case COMBINE_STEW: - case COMBINE_PASTRY_DOUGH: - case COMBINE_PIE_SHELL: - case COMBINE_PIZZA_BASE: - case COMBINE_PIZZA_TOMATO: - case COMBINE_PIZZA_CHEESE: - case COMBINE_PIZZA_PINEAPPLE: - case CUT_PINEAPPLE: - sleepUntil(combineItems(), 60000); - break; - case GET_CHEESE: - case GET_TOMATOES: - case GET_PINEAPPLES: - case GET_FLOUR: - case GET_POTATOES: - sleepUntil(getFood()); - break; - case USE_BUFFET_TABLE: - if (isUnderAppreciationThreshold()) { - setCurrentState(State.HOP_WORLD); - return; - } - if (Rs2GameObject.canReach(BUFFET_TABLE_LOC)) { - Microbot.getClientThread().invoke(() -> Microbot.getRs2TileObjectCache().query().withName("Buffet table").interact("Serve")); - sleepUntil(() -> Rs2Player.waitForXpDrop(Skill.COOKING), 10000); - } else { - debug("Cannot reach the buffet table, waiting..."); - } - break; - case GETTING_READY: - if (!inTheMess()) return; - sleepUntil(cleanInventory()); - break; - case HOP_WORLD: - if (!hopWorlds()) return; - break; - default: - break; - } - if (currentState.getNext() != null) { - setCurrentState(currentState.getNext()); - overlay.setStatus(currentState.getStatus()); + private void logDecision(String decision) { + long now = System.currentTimeMillis(); + // Only log when the decision changes, or every 10s on the same decision (so a stuck loop is visible). + if (!decision.equals(lastDecision) || now - lastDecisionLogMs > 10_000) { + log.info("[mess] step={} pos={} invCount={} dish={}", + decision, Rs2Player.getWorldLocation(), Rs2Inventory.count(), config.dish()); + lastDecision = decision; + lastDecisionLogMs = now; } } - private BooleanSupplier returnEmptyBowls() { - return () -> { - if (Rs2Inventory.hasItem(ItemID.BOWL_EMPTY)) { - Rs2TileObjectModel cupboard = Microbot.getRs2TileObjectCache().query().within(UTENSIL_CUPBOARD_LOC, 1).nearest(); - if (cupboard != null) Rs2Inventory.useItemOnObject(ItemID.BOWL_EMPTY, cupboard.getId()); - sleepUntil(() -> Rs2Inventory.count(ItemID.BOWL_EMPTY) == 0, 10000); - Rs2Antiban.actionCooldown(); - return !Rs2Inventory.hasItem(ItemID.BOWL_EMPTY); - } - return true; - }; + // ---------- per-dish flows ---------- + + private void stewTick() { + // Consume-to-zero phases (cooking) take priority over any combine that uses their output. + // Withdrawal targets size to predecessor counts so leftovers from burns carry into the next batch. + if (has(ItemID.HOSIDIUS_SERVERY_STEW)) { serveAndMaybeHop(APPRECIATION_BAR_STEW); return; } + if (has(ItemID.HOSIDIUS_SERVERY_UNCOOKED_STEW)) { cookOnOven(ItemID.HOSIDIUS_SERVERY_UNCOOKED_STEW); return; } + if (has(ItemID.HOSIDIUS_SERVERY_RAW_MEAT)) { cookOnOven(ItemID.HOSIDIUS_SERVERY_RAW_MEAT); return; } + if (has(ItemID.HOSIDIUS_SERVERY_MEATWATER) && has(ItemID.HOSIDIUS_SERVERY_POTATO)){ combineAll(ItemID.HOSIDIUS_SERVERY_MEATWATER, ItemID.HOSIDIUS_SERVERY_POTATO, "Combining stew"); return; } + if (has(ItemID.HOSIDIUS_SERVERY_MEATWATER)) { withdrawFromFood(ItemID.HOSIDIUS_SERVERY_POTATO, count(ItemID.HOSIDIUS_SERVERY_MEATWATER)); return; } + if (has(ItemID.HOSIDIUS_SERVERY_COOKED_MEAT) && has(ItemID.BOWL_WATER)) { combineAll(ItemID.BOWL_WATER, ItemID.HOSIDIUS_SERVERY_COOKED_MEAT, "Combining meat + water"); return; } + // Finish filling bowls before sizing the raw-meat withdrawal — otherwise a partial bowl_water + // count (e.g., fillBowls timed out at 12/14) would drive takeRawMeat to under-fetch. + if (has(ItemID.BOWL_EMPTY)) { fillBowls(); return; } + // Only advance to the raw-meat phase with a full batch of bowl_water. With fewer (e.g., burns + // left some unused last round), fall through to the bowl chain to top up to BATCH_SIZE rather + // than running a tiny 2-stew loop with all its withdraw/cook/combine round-trips. + if (count(ItemID.BOWL_WATER) >= BATCH_SIZE && !has(ItemID.HOSIDIUS_SERVERY_RAW_MEAT)) { takeRawMeat(BATCH_SIZE); return; } + + // Fresh batch — top up to BATCH_SIZE accounting for any leftover items in the chain. + int leftover = count(ItemID.BOWL_WATER) + count(ItemID.HOSIDIUS_SERVERY_MEATWATER) + count(ItemID.HOSIDIUS_SERVERY_UNCOOKED_STEW); + int needed = Math.max(1, BATCH_SIZE - leftover); + withdrawFromUtensil(ItemID.BOWL_EMPTY, needed); } - private boolean hopWorlds() { - info("Hopping worlds..."); - int currentWorld = Microbot.getClient().getWorld(); - Microbot.hopToWorld(Login.getRandomWorld(true)); - sleepUntil(() -> Microbot.getClient().getGameState() == GameState.HOPPING); - sleepUntil(() -> Microbot.getClient().getGameState() == GameState.LOGGED_IN); - if (Microbot.getClient().getWorld() != currentWorld) { - info("Successfully hopped to a new world."); - return true; - } else { - debug("Failed to hop worlds, retrying..."); - } - return false; + private void meatPieTick() { + // Consume-to-zero phases (cooking) take priority over any combine that uses their output. + // Withdrawal targets size to predecessor counts so burn-induced leftovers (extra shells) + // carry into the next loop and the chain tops up rather than over-fetching. + if (has(ItemID.HOSIDIUS_SERVERY_MEAT_PIE)) { serveAndMaybeHop(APPRECIATION_BAR_PIE); return; } + if (has(ItemID.HOSIDIUS_SERVERY_UNCOOKED_MEAT_PIE)) { cookOnOven(ItemID.HOSIDIUS_SERVERY_UNCOOKED_MEAT_PIE); return; } + if (has(ItemID.HOSIDIUS_SERVERY_RAW_MEAT)) { cookOnOven(ItemID.HOSIDIUS_SERVERY_RAW_MEAT); return; } + if (has(ItemID.HOSIDIUS_SERVERY_PIE_SHELL) && has(ItemID.HOSIDIUS_SERVERY_COOKED_MEAT)) { combineAll(ItemID.HOSIDIUS_SERVERY_PIE_SHELL, ItemID.HOSIDIUS_SERVERY_COOKED_MEAT, "Filling pies"); return; } + // Return bowls only when shells are at full batch (post-combine state of piedish+dough). + // Before that, any BOWL_EMPTY in inventory is feedstock for the dough chain — returning + // it would infinite-loop (withdraw → return → withdraw...). + if (count(ItemID.HOSIDIUS_SERVERY_PIE_SHELL) >= BATCH_SIZE && has(ItemID.BOWL_EMPTY)) { returnEmptyBowls(); return; } + // Only proceed to raw-meat phase when we have a full batch of shells. With fewer shells + // (e.g., burns left some unused), fall through to the dough chain to top up to BATCH_SIZE. + if (count(ItemID.HOSIDIUS_SERVERY_PIE_SHELL) >= BATCH_SIZE) { takeRawMeat(BATCH_SIZE); return; } + if (has(ItemID.HOSIDIUS_SERVERY_PIEDISH) && has(ItemID.HOSIDIUS_SERVERY_PASTRY_DOUGH)) { combineAll(ItemID.HOSIDIUS_SERVERY_PIEDISH, ItemID.HOSIDIUS_SERVERY_PASTRY_DOUGH, "Forming shells"); return; } + if (has(ItemID.HOSIDIUS_SERVERY_PASTRY_DOUGH) && has(ItemID.BOWL_EMPTY)) { returnEmptyBowls(); return; } + if (has(ItemID.HOSIDIUS_SERVERY_PASTRY_DOUGH) && !has(ItemID.HOSIDIUS_SERVERY_PIEDISH)) { withdrawFromUtensil(ItemID.HOSIDIUS_SERVERY_PIEDISH, count(ItemID.HOSIDIUS_SERVERY_PASTRY_DOUGH)); return; } + if (has(ItemID.BOWL_WATER) && has(ItemID.HOSIDIUS_SERVERY_POT_FLOUR)) { combineDoughDialog(ItemID.BOWL_WATER, ItemID.HOSIDIUS_SERVERY_POT_FLOUR, "Pastry dough"); return; } + if (has(ItemID.BOWL_EMPTY) && !has(ItemID.HOSIDIUS_SERVERY_POT_FLOUR)) { withdrawFromFood(ItemID.HOSIDIUS_SERVERY_POT_FLOUR, count(ItemID.BOWL_EMPTY)); return; } + if (has(ItemID.BOWL_EMPTY)) { fillBowls(); return; } + + // Fresh batch — top up to BATCH_SIZE accounting for any leftover items in the chain. + int leftover = count(ItemID.HOSIDIUS_SERVERY_PIE_SHELL) + + count(ItemID.HOSIDIUS_SERVERY_PASTRY_DOUGH) + + count(ItemID.BOWL_WATER); + int needed = Math.max(1, BATCH_SIZE - leftover); + withdrawFromUtensil(ItemID.BOWL_EMPTY, needed); } - private boolean isUnderAppreciationThreshold() { - Widget appreciationBarWidget; - switch (config.dish()) { - case MEAT_PIE: - appreciationBarWidget = Rs2Widget.getWidget(PIE_WIDGET_BAR_ID); - break; - case STEW: - appreciationBarWidget = Rs2Widget.getWidget(STEW_WIDGET_BAR_ID); - break; - case PIZZA: - appreciationBarWidget = Rs2Widget.getWidget(PIZZA_WIDGET_BAR_ID); - break; - default: - debug("Unknown dish selected, cannot check appreciation."); - return false; - } - - Widget filledBar = appreciationBarWidget.getChild(0); + private void pizzaTick() { + // Consume-to-zero phases (cooking) take priority over any combine that uses their output. + // Withdrawal targets size to predecessor counts so leftovers from burns carry into the next batch. + if (has(ItemID.HOSIDIUS_SERVERY_PINEAPPLE_PIZZA)) { serveAndMaybeHop(APPRECIATION_BAR_PIZZA); return; } + if (has(ItemID.HOSIDIUS_SERVERY_UNCOOKED_PIZZA)) { cookOnOven(ItemID.HOSIDIUS_SERVERY_UNCOOKED_PIZZA); return; } + if (has(ItemID.HOSIDIUS_SERVERY_PLAIN_PIZZA) && has(ItemID.HOSIDIUS_SERVERY_PINEAPPLE_CHUNKS)) { combineAll(ItemID.HOSIDIUS_SERVERY_PLAIN_PIZZA, ItemID.HOSIDIUS_SERVERY_PINEAPPLE_CHUNKS, "Adding pineapple"); return; } + if (has(ItemID.HOSIDIUS_SERVERY_PINEAPPLE) && has(ItemID.KNIFE)) { combineAll(ItemID.KNIFE, ItemID.HOSIDIUS_SERVERY_PINEAPPLE, "Cutting pineapple"); return; } + if (has(ItemID.HOSIDIUS_SERVERY_INCOMPLETE_PIZZA) && has(ItemID.HOSIDIUS_SERVERY_CHEESE)) { combineAll(ItemID.HOSIDIUS_SERVERY_INCOMPLETE_PIZZA, ItemID.HOSIDIUS_SERVERY_CHEESE, "Adding cheese"); return; } + if (has(ItemID.HOSIDIUS_SERVERY_INCOMPLETE_PIZZA) && !has(ItemID.HOSIDIUS_SERVERY_CHEESE)) { withdrawFromFood(ItemID.HOSIDIUS_SERVERY_CHEESE, count(ItemID.HOSIDIUS_SERVERY_INCOMPLETE_PIZZA)); return; } + if (has(ItemID.HOSIDIUS_SERVERY_PIZZA_BASE) && has(ItemID.HOSIDIUS_SERVERY_TOMATO)) { combineAll(ItemID.HOSIDIUS_SERVERY_PIZZA_BASE, ItemID.HOSIDIUS_SERVERY_TOMATO, "Adding tomato"); return; } + if (has(ItemID.HOSIDIUS_SERVERY_PIZZA_BASE) && !has(ItemID.HOSIDIUS_SERVERY_TOMATO)) { withdrawFromFood(ItemID.HOSIDIUS_SERVERY_TOMATO, count(ItemID.HOSIDIUS_SERVERY_PIZZA_BASE)); return; } + if (has(ItemID.HOSIDIUS_SERVERY_PLAIN_PIZZA) && !has(ItemID.HOSIDIUS_SERVERY_PINEAPPLE_CHUNKS) && !has(ItemID.HOSIDIUS_SERVERY_PINEAPPLE)) { withdrawFromFood(ItemID.HOSIDIUS_SERVERY_PINEAPPLE, count(ItemID.HOSIDIUS_SERVERY_PLAIN_PIZZA)); return; } + if (has(ItemID.HOSIDIUS_SERVERY_PIZZA_BASE) && has(ItemID.BOWL_EMPTY)) { returnEmptyBowls(); return; } + if (has(ItemID.BOWL_WATER) && has(ItemID.HOSIDIUS_SERVERY_POT_FLOUR)) { combineDoughDialog(ItemID.BOWL_WATER, ItemID.HOSIDIUS_SERVERY_POT_FLOUR, "Pizza base"); return; } + if (has(ItemID.BOWL_EMPTY) && !has(ItemID.HOSIDIUS_SERVERY_POT_FLOUR)) { withdrawFromFood(ItemID.HOSIDIUS_SERVERY_POT_FLOUR, count(ItemID.BOWL_EMPTY)); return; } + if (has(ItemID.BOWL_EMPTY)) { fillBowls(); return; } + if (count(ItemID.KNIFE) < 2) { withdrawFromUtensil(ItemID.KNIFE, 2); return; } + + // Fresh batch — top up to PIZZA_BATCH_SIZE accounting for any leftovers in the chain. + int leftover = count(ItemID.BOWL_WATER) + + count(ItemID.HOSIDIUS_SERVERY_PIZZA_BASE) + + count(ItemID.HOSIDIUS_SERVERY_INCOMPLETE_PIZZA) + + count(ItemID.HOSIDIUS_SERVERY_UNCOOKED_PIZZA) + + count(ItemID.HOSIDIUS_SERVERY_PLAIN_PIZZA); + int needed = Math.max(1, PIZZA_BATCH_SIZE - leftover); + withdrawFromUtensil(ItemID.BOWL_EMPTY, needed); + } - if (filledBar == null) { - debug("Appreciation bar widget not found, cannot check appreciation."); - return false; - } + // ---------- step handlers ---------- - int filledPercentage = filledBar.getWidth() * 100 / appreciationBarWidget.getWidth(); + private void walkToMess() { + setStatus("Walking to Mess"); + Rs2Walker.walkTo(MESS_HUB, 4); + } - if (filledPercentage < config.appreciation_threshold()) { - debug("Appreciation is below threshold."); - return true; - } else { - debug("Appreciation is above threshold."); - } - return false; + private boolean inMess() { + return Microbot.getClientThread().runOnClientThreadOptional( + () -> Rs2Widget.getWidget(InterfaceID.HosidiusServeryHud.CONTENT) != null + ).orElse(false); } - private boolean inTheMess() { - if (Rs2Widget.getWidget(InterfaceID.HosidiusServeryHud.CONTENT) != null) { - debug("Already in the area, no need to move location."); - return true; + private boolean withdrawFromUtensil(int itemId, int targetCount) { return withdrawFromCupboard(UTENSIL_CUPBOARD_LOC, itemId, targetCount); } + private boolean withdrawFromFood(int itemId, int targetCount) { return withdrawFromCupboard(FOOD_CUPBOARD_LOC, itemId, targetCount); } + + private boolean withdrawFromCupboard(WorldPoint loc, int itemId, int targetCount) { + int have = count(itemId); + if (have >= targetCount) return true; + + int free = 28 - Rs2Inventory.count(); + int need = Math.min(targetCount - have, free); + if (need <= 0) { + log.warn("[mess] withdraw blocked: itemId={} target={} have={} free=0", itemId, targetCount, have); + return false; } - info("Walking to the Hosidius Servery area..."); - return Rs2Walker.walkTo(new WorldPoint(1645, 3627, 0), 4); - } - private BooleanSupplier cleanInventory() { - return () -> { - if (!Rs2Inventory.isEmpty()) { - if (Rs2Inventory.count() == 2 && Rs2Inventory.count(ItemID.KNIFE) == 2 && config.dish() == Dish.PIZZA) { - debug("Only knife in inventory, skipping inventory cleanup."); - return true; - } - - Set itemsToDrop = Set.of( - ItemID.BOWL_EMPTY, - ItemID.BOWL_WATER, - ItemID.KNIFE, - ItemID.HOSIDIUS_SERVERY_PIEDISH, - ItemID.BURNT_PIZZA, - ItemID.BURNT_PIE, - ItemID.BURNT_STEW, - ItemID.BURNT_MEAT, - ItemID.HOSIDIUS_SERVERY_RAW_MEAT, - ItemID.HOSIDIUS_SERVERY_PINEAPPLE, - ItemID.HOSIDIUS_SERVERY_PINEAPPLE_CHUNKS, - ItemID.HOSIDIUS_SERVERY_TOMATO, - ItemID.HOSIDIUS_SERVERY_CHEESE, - ItemID.HOSIDIUS_SERVERY_POTATO, - ItemID.HOSIDIUS_SERVERY_POT_FLOUR, - ItemID.HOSIDIUS_SERVERY_PASTRY_DOUGH, - ItemID.HOSIDIUS_SERVERY_PIZZA_BASE, - ItemID.HOSIDIUS_SERVERY_INCOMPLETE_PIZZA, - ItemID.HOSIDIUS_SERVERY_PLAIN_PIZZA, - ItemID.HOSIDIUS_SERVERY_PIE_SHELL, - ItemID.HOSIDIUS_SERVERY_COOKED_MEAT, - ItemID.HOSIDIUS_SERVERY_UNCOOKED_MEAT_PIE, - ItemID.HOSIDIUS_SERVERY_UNCOOKED_STEW, - ItemID.HOSIDIUS_SERVERY_UNCOOKED_PIZZA, - ItemID.HOSIDIUS_SERVERY_MEATWATER - ); - - int droppedItemsCount = 0; - - info("Starting inventory cleanup. Total slots to check: 28"); - debug("Items to drop: " + itemsToDrop.toString()); - - // Iterate through inventory slots to drop items slot by slot - for (int slot = 0; slot < 28; slot++) { - if (!Rs2Inventory.isSlotEmpty(slot)) { - Rs2ItemModel item = Rs2Inventory.getItemInSlot(slot); - if (item != null) { - debug("Slot " + slot + ": Found item ID " + item.getId() + " (name: " + item.getName() + ")"); - if (itemsToDrop.contains(item.getId())) { - debug("Slot " + slot + ": Item ID " + item.getId() + " is in drop list, attempting to drop"); - Rs2Inventory.slotInteract(slot, "Drop"); - sleepGaussian(120, 40); - droppedItemsCount++; - debug("Slot " + slot + ": Drop action completed for item ID " + item.getId()); - } else { - debug("Slot " + slot + ": Item ID " + item.getId() + " NOT in drop list, keeping"); - } - } else { - debug("Slot " + slot + ": Item is null despite slot not being empty"); - } - } else { - debug("Slot " + slot + ": Empty slot, skipping"); - } - } - - info("Inventory cleanup completed. Total items dropped: " + droppedItemsCount); - - if (droppedItemsCount > 0) { - info("Dropped " + droppedItemsCount + " items from inventory."); - Rs2Antiban.actionCooldown(); - return false; - } - info("Walking to the bank to deposit items..."); - return Rs2Bank.bankItemsAndWalkBackToOriginalPosition( - Rs2Inventory.items().map(Rs2ItemModel::getName).collect(Collectors.toList()), - false, - BankLocation.HOSIDIUS_KITCHEN, - Rs2Player.getWorldLocation(), - 28, - 3 - ); + logDecision("withdraw[loc=" + loc + ",itemId=" + itemId + ",need=" + need + "]"); + setStatus("Getting supplies"); + + if (!Rs2GameObject.canReach(loc)) { log.info("[mess] walking to cupboard {}", loc); Rs2Walker.walkTo(loc, 4); return false; } + + if (!Rs2Widget.isWidgetVisible(SHOP_WIDGET_ID)) { + // Exact-tile interact: cupboards at (1644,3624) and (1645,3623) are 1 tile apart, so a within(loc,1) + // query catches both and may interact with the wrong one. findObjectByLocation matches the exact tile. + boolean interacted = Rs2GameObject.interact(loc, "Search"); + log.info("[mess] cupboard interact(Search) at {} -> {}", loc, interacted); + if (!interacted) return false; + if (!sleepUntil(() -> Rs2Widget.isWidgetVisible(SHOP_WIDGET_ID), 3000)) { + log.warn("[mess] shop widget never appeared after Search at {}", loc); + return false; } + } - debug("Inventory is clean, no action needed"); - return true; - }; - } + Widget shop = Rs2Widget.getWidget(SHOP_WIDGET_ID); + Widget[] children = shop != null ? shop.getDynamicChildren() : null; + if (children == null) { log.warn("[mess] shop widget has no children"); closeShop(); return false; } - private BooleanSupplier getUtensils() { - int itemId; - switch (getCurrentState()) { - case GET_EMPTY_BOWLS: - itemId = ItemID.BOWL_EMPTY; - break; - case GET_KNIFE: - if (Rs2Inventory.count(ItemID.KNIFE) >= 2) { - debug("Already have 2 knives, skipping getting knife."); - return () -> true; - } - itemId = ItemID.KNIFE; - break; - case GET_EMPTY_PIE_DISHES: - itemId = ItemID.HOSIDIUS_SERVERY_PIEDISH; - break; - default: - debug("Unknown state for getting utensils, cannot proceed."); - return () -> false; + int idx = -1; + for (int i = 0; i < children.length; i++) { + if (children[i].getItemId() == itemId) { idx = i; break; } } - return () -> { - if (Rs2GameObject.canReach(UTENSIL_CUPBOARD_LOC)) { - Rs2GameObject.interact(UTENSIL_CUPBOARD_LOC, "Search"); - sleepUntil(() -> Rs2Widget.isWidgetVisible(15859715)); - if (Rs2Widget.isWidgetVisible(15859715)) { - Widget utensilShop = Rs2Widget.getWidget(15859715); - if (utensilShop != null) { - Widget[] children = utensilShop.getDynamicChildren(); - int index = IntStream.range(0, children.length) - .filter(i -> children[i].getItemId() == itemId) - .findFirst() - .orElse(-1); - if (index != -1) { - Rs2Widget.clickWidgetFast(children[index], index, 5); - sleepUntil(() -> Rs2Widget.hasWidget("Enter amount")); - if (Rs2Widget.hasWidget("Enter amount")) { - String amount; - switch (getCurrentState()) { - case GET_KNIFE: - amount = "2"; - break; - case GET_EMPTY_BOWLS: - if (config.dish() == Dish.PIZZA) { - amount = "13"; - break; - } - default: - amount = "14"; - } - Rs2Keyboard.typeString(amount); - Rs2Keyboard.keyPress(KeyEvent.VK_ENTER); - Rs2Antiban.actionCooldown(); - sleepUntil(() -> closeMessShop()); - Rs2Inventory.waitForInventoryChanges(2000); - return Rs2Inventory.hasItem(itemId); - } - } - } - } + if (idx < 0) { + // Help diagnose mis-targeted cupboard / wrong item ID by logging what's actually on offer. + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < Math.min(children.length, 16); i++) { + if (children[i].getItemId() > 0) sb.append(children[i].getItemId()).append(','); } + log.warn("[mess] item {} not in cupboard at {}; items found: [{}]", itemId, loc, sb); + closeShop(); return false; - }; - } + } - private BooleanSupplier fillBowl() { - return () -> { - if (Rs2Inventory.hasItem(ItemID.BOWL_EMPTY)) { - Rs2TileObjectModel sink = Microbot.getRs2TileObjectCache().query().within(SINK_LOC, 1).nearest(); - if (sink != null) Rs2Inventory.useItemOnObject(ItemID.BOWL_EMPTY, sink.getId()); - sleepUntil(() -> !Rs2Inventory.hasItem(ItemID.BOWL_EMPTY), - 10000); - Rs2Antiban.actionCooldown(); - return true; - } - return false; - }; + log.info("[mess] clicking shop slot {} for itemId {} (qty {})", idx, itemId, need); + Rs2Widget.clickWidgetFast(children[idx], idx, 5); + if (!sleepUntil(() -> Rs2Widget.hasWidget("Enter amount"), 2000)) { log.warn("[mess] Enter amount widget never appeared"); closeShop(); return false; } + + // Mirror Rs2GrandExchange.setQuantity timing: the chatbox input doesn't reliably + // accept the value if you set it the same tick the widget appeared — small qty + // (e.g., 2) silently drops the value, large qty happens to race past the issue. + // typeString is unsafe here too: KEY_TYPED routes to canvas if the chatbox + // hasn't focused, leaking digits into game chat. Direct VarClientStr write + + // ~1s of sleep around it matches the pattern that works in GE. + sleep(600); + setChatboxAmount(need); + sleep(400); + Rs2Keyboard.keyPress(KeyEvent.VK_ENTER); + + boolean got = sleepUntil(() -> count(itemId) >= targetCount, 4000); + log.info("[mess] withdraw result for itemId {}: have={} target={} ok={}", itemId, count(itemId), targetCount, got); + closeShop(); + return got; } - private BooleanSupplier getMeat() { - return () -> { - if (Rs2GameObject.canReach(MEAT_TABLE_LOC)) { - Rs2GameObject.interact(MEAT_TABLE_LOC, "Take-X"); - sleepUntil(() -> Rs2Widget.hasWidget("Enter amount")); - if (Rs2Widget.hasWidget("Enter amount")) { - Rs2Keyboard.typeString("14"); - Rs2Keyboard.keyPress(KeyEvent.VK_ENTER); - Rs2Antiban.actionCooldown(); - } - Rs2Inventory.waitForInventoryChanges(2000); - Rs2Antiban.actionCooldown(); - return Rs2Inventory.hasItem(ItemID.HOSIDIUS_SERVERY_RAW_MEAT); - } - return false; - }; + private boolean takeRawMeat(int targetCount) { + int have = count(ItemID.HOSIDIUS_SERVERY_RAW_MEAT); + if (have >= targetCount) return true; + + int free = 28 - Rs2Inventory.count(); + int need = Math.min(targetCount - have, free); + if (need <= 0) return false; + + logDecision("takeRawMeat[need=" + need + "]"); + setStatus("Taking raw meat"); + if (!Rs2GameObject.canReach(MEAT_TABLE_LOC)) { Rs2Walker.walkTo(MEAT_TABLE_LOC, 4); return false; } + + boolean ok = Rs2GameObject.interact(MEAT_TABLE_LOC, "Take-X"); + log.info("[mess] meat-table interact(Take-X) -> {}", ok); + if (!ok) return false; + if (!sleepUntil(() -> Rs2Widget.hasWidget("Enter amount"), 2000)) { log.warn("[mess] Enter amount widget never appeared for raw meat"); return false; } + + // See withdrawFromCupboard for the timing rationale — small-qty values race past the input. + sleep(600); + setChatboxAmount(need); + sleep(400); + Rs2Keyboard.keyPress(KeyEvent.VK_ENTER); + return sleepUntil(() -> count(ItemID.HOSIDIUS_SERVERY_RAW_MEAT) >= targetCount, 4000); } - private BooleanSupplier cook() { - int itemId; - switch (getCurrentState()) { - case USE_CLAY_OVEN: - itemId = ItemID.HOSIDIUS_SERVERY_RAW_MEAT; - break; - case FINISH_COOKING: - if (config.dish() == Dish.MEAT_PIE) { - itemId = ItemID.HOSIDIUS_SERVERY_UNCOOKED_MEAT_PIE; - } else if (config.dish() == Dish.STEW) { - itemId = ItemID.HOSIDIUS_SERVERY_UNCOOKED_STEW; - } else if (config.dish() == Dish.PIZZA) { - itemId = ItemID.HOSIDIUS_SERVERY_UNCOOKED_PIZZA; - } else { - debug("Unknown dish selected, cannot finish cooking."); - return () -> false; - } - break; - default: - debug("Unknown state for cooking, cannot proceed."); - return () -> false; - } - return () -> { - if (Rs2GameObject.canReach(CLAY_OVEN_LOC)) { - Rs2TileObjectModel oven = Microbot.getRs2TileObjectCache().query().within(CLAY_OVEN_LOC, 1).nearest(); - if (oven != null) Rs2Inventory.useItemOnObject(itemId, oven.getId()); - sleepUntil(() -> Rs2Widget.hasWidget("How many would you like to cook?")); - if (Rs2Widget.hasWidget("How many would you like to cook?")) { - Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); - Rs2Antiban.actionCooldown(); - } - Rs2Inventory.waitForInventoryChanges(5000); - sleepUntil(() -> !Rs2Player.isAnimating(1000), 50000); - Rs2Antiban.actionCooldown(); - } - return false; - }; + private boolean fillBowls() { + if (!has(ItemID.BOWL_EMPTY)) return true; + logDecision("fillBowls"); + setStatus("Filling bowls"); + if (!Rs2GameObject.canReach(SINK_LOC)) { Rs2Walker.walkTo(SINK_LOC, 4); return false; } + + TileObject sink = Rs2GameObject.findObjectByLocation(SINK_LOC); + if (sink == null) { log.warn("[mess] sink not found at {}", SINK_LOC); return false; } + boolean used = Rs2Inventory.useItemOnObject(ItemID.BOWL_EMPTY, sink.getId()); + log.info("[mess] use bowl on sink id={} -> {}", sink.getId(), used); + return sleepUntil(() -> !has(ItemID.BOWL_EMPTY), 15000); + } + private boolean returnEmptyBowls() { + if (!has(ItemID.BOWL_EMPTY)) return true; + logDecision("returnEmptyBowls"); + setStatus("Returning empty bowls"); + if (!Rs2GameObject.canReach(UTENSIL_CUPBOARD_LOC)) { Rs2Walker.walkTo(UTENSIL_CUPBOARD_LOC, 4); return false; } + + TileObject cupboard = Rs2GameObject.findObjectByLocation(UTENSIL_CUPBOARD_LOC); + if (cupboard == null) { log.warn("[mess] utensil cupboard not found at {}", UTENSIL_CUPBOARD_LOC); return false; } + boolean used = Rs2Inventory.useItemOnObject(ItemID.BOWL_EMPTY, cupboard.getId()); + log.info("[mess] return bowl on cupboard id={} -> {}", cupboard.getId(), used); + return sleepUntil(() -> !has(ItemID.BOWL_EMPTY), 8000); } - private BooleanSupplier combineItems() { - int item1; - int item2; - switch (getCurrentState()) { - case COMBINE_PASTRY_DOUGH: - case COMBINE_PIZZA_BASE: - item1 = ItemID.BOWL_WATER; - item2 = ItemID.HOSIDIUS_SERVERY_POT_FLOUR; - break; - case COMBINE_PIE_SHELL: - item1 = ItemID.HOSIDIUS_SERVERY_PIEDISH; - item2 = ItemID.HOSIDIUS_SERVERY_PASTRY_DOUGH; - break; - case COMBINE_MEAT_PIE: - item1 = ItemID.HOSIDIUS_SERVERY_PIE_SHELL; - item2 = ItemID.HOSIDIUS_SERVERY_COOKED_MEAT; - break; - case COMBINE_MEAT_WATER: - item1 = ItemID.BOWL_WATER; - item2 = ItemID.HOSIDIUS_SERVERY_COOKED_MEAT; - break; - case COMBINE_STEW: - item1 = ItemID.HOSIDIUS_SERVERY_MEATWATER; - item2 = ItemID.HOSIDIUS_SERVERY_POTATO; - break; - case COMBINE_PIZZA_TOMATO: - item1 = ItemID.HOSIDIUS_SERVERY_PIZZA_BASE; - item2 = ItemID.HOSIDIUS_SERVERY_TOMATO; - break; - case COMBINE_PIZZA_CHEESE: - item1 = ItemID.HOSIDIUS_SERVERY_INCOMPLETE_PIZZA; - item2 = ItemID.HOSIDIUS_SERVERY_CHEESE; - break; - case COMBINE_PIZZA_PINEAPPLE: - item1 = ItemID.HOSIDIUS_SERVERY_PLAIN_PIZZA; - item2 = ItemID.HOSIDIUS_SERVERY_PINEAPPLE_CHUNKS; - break; - case CUT_PINEAPPLE: - item1 = ItemID.KNIFE; - item2 = ItemID.HOSIDIUS_SERVERY_PINEAPPLE; - break; - default: - debug("Unknown state for combining items, cannot proceed."); - return () -> false; + /** + * Cook all of {@code rawId} on the clay oven in one phase: issue the cook + * (Make-All via SPACE on the production widget), then BLOCK until the raw + * ingredient is fully consumed. Returning mid-chain lets the dispatcher + * pick a different guard (e.g., a combine that depends on the partial + * cooked-meat output), interrupting cooking — this is what the user + * explicitly forbids. + */ + private boolean cookOnOven(int rawId) { + if (!has(rawId)) return true; + logDecision("cookOnOven[rawId=" + rawId + "]"); + setStatus("Cooking"); + if (!Rs2GameObject.canReach(CLAY_OVEN_LOC)) { Rs2Walker.walkTo(CLAY_OVEN_LOC, 4); return false; } + + int before = count(rawId); + + if (Rs2Widget.isProductionWidgetOpen()) { + // Dialog already up from a previous tick — confirm Cook-All. + Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); + } else { + TileObject oven = Rs2GameObject.findObjectByLocation(CLAY_OVEN_LOC); + if (oven == null) { log.warn("[mess] clay oven not found at {}", CLAY_OVEN_LOC); return false; } + boolean used = Rs2Inventory.useItemOnObject(rawId, oven.getId()); + log.info("[mess] use {} on oven id={} -> {}", rawId, oven.getId(), used); + if (sleepUntil(Rs2Widget::isProductionWidgetOpen, 2500)) { + Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); + } } - return () -> { - if (Rs2Inventory.hasItem(item1) && Rs2Inventory.hasItem(item2)) { - boolean alreadyInRightSlots = (Rs2Inventory.slotContains(26, item1) && Rs2Inventory.slotContains(27, item2)) || - (Rs2Inventory.slotContains(26, item2) && Rs2Inventory.slotContains(27, item1)); - - if (!alreadyInRightSlots) { - Rs2ItemModel lastOfItem1 = Rs2Inventory.getLast(item1); - Rs2ItemModel lastOfItem2 = Rs2Inventory.getLast(item2); - - if (lastOfItem1 != null && lastOfItem2 != null) { - if (lastOfItem1.getSlot() != 26 && lastOfItem1.getSlot() != 27) { - if (lastOfItem2.getSlot() == 26) { - Rs2Inventory.moveItemToSlot(lastOfItem1, 27); - } else if (lastOfItem2.getSlot() == 27) { - Rs2Inventory.moveItemToSlot(lastOfItem1, 26); - } else { - Rs2Inventory.moveItemToSlot(lastOfItem1, 26); - sleepUntil(() -> Rs2Inventory.waitForInventoryChanges(2000)); - Rs2Inventory.moveItemToSlot(lastOfItem2, 27); - sleepUntil(() -> Rs2Inventory.waitForInventoryChanges(2000)); - } - } - } else { - debug("Failed to find items in inventory, cannot combine."); - return false; - } - } - - Widget item1Widget = Rs2Inventory.getInventoryWidget().getChild(26); - Widget item2Widget = Rs2Inventory.getInventoryWidget().getChild(27); - if (getCurrentState() == State.COMBINE_PASTRY_DOUGH || getCurrentState() == State.COMBINE_PIZZA_BASE) { - String option = (getCurrentState() == State.COMBINE_PASTRY_DOUGH) ? "Pastry dough" : "Pizza base"; - Rs2Widget.clickWidget(item1Widget); - Rs2Widget.clickWidget(item2Widget); - Rs2Dialogue.sleepUntilSelectAnOption(); - Rs2Dialogue.keyPressForDialogueOption(option); - sleepUntil(() -> !Rs2Inventory.hasItem(item1), 30000); - Rs2Antiban.actionCooldown(); - return true; - } else if (getCurrentState() == State.CUT_PINEAPPLE) { - while (Rs2Inventory.hasItem(ItemID.HOSIDIUS_SERVERY_PINEAPPLE) && canContinue()) { - Rs2Widget.clickWidget(item1Widget); - sleepGaussian(120, 40); - Rs2Widget.clickWidget(item2Widget); - sleepGaussian(120, 40); - } - - Rs2ItemModel knife = Rs2Inventory.getLast(ItemID.KNIFE); - if (knife != null) { - Rs2Inventory.moveItemToSlot(knife, (Rs2Inventory.slotContains(0, ItemID.KNIFE)) ? 1 : 0); - sleepUntil(() -> Rs2Inventory.waitForInventoryChanges(2000)); - return true; - } else { - debug("Failed to find knife in inventory, cannot move knife."); - return false; - } - } else { - while (Rs2Inventory.hasItem(item1) && canContinue()) { - Rs2Widget.clickWidget(item1Widget); - sleepGaussian(120, 40); - Rs2Widget.clickWidget(item2Widget); - sleepGaussian(120, 40); - } - return true; - } - } - return false; - }; + // Block until raw is fully consumed. Per-cook ~3 ticks (~1.8s), so an + // 8s stall guard is generous; hard cap 90s covers a 14-batch comfortably. + boolean done = waitForDepleted(rawId, before, 8000); + log.info("[mess] cookOnOven id={} done={} ({}->{})", rawId, done, before, count(rawId)); + return true; } - private BooleanSupplier getFood() { - int itemId; - switch (getCurrentState()) { - case GET_POTATOES: - itemId = ItemID.HOSIDIUS_SERVERY_POTATO; - break; - case GET_PINEAPPLES: - itemId = ItemID.HOSIDIUS_SERVERY_PINEAPPLE; - break; - case GET_TOMATOES: - itemId = ItemID.HOSIDIUS_SERVERY_TOMATO; - break; - case GET_CHEESE: - itemId = ItemID.HOSIDIUS_SERVERY_CHEESE; - break; - case GET_FLOUR: - itemId = ItemID.HOSIDIUS_SERVERY_POT_FLOUR; - break; - default: - debug("Unknown state for getting food, cannot proceed."); - return () -> false; - } - return () -> { - if (Rs2GameObject.canReach(FOOD_CUPBOARD_LOC)) { - Rs2GameObject.interact(FOOD_CUPBOARD_LOC, "Search"); - sleepUntil(() -> Rs2Widget.isWidgetVisible(15859715)); - if (Rs2Widget.isWidgetVisible(15859715)) { - Widget foodShop = Rs2Widget.getWidget(15859715); - if (foodShop != null) { - Widget[] children = foodShop.getDynamicChildren(); - int index = IntStream.range(0, children.length) - .filter(i -> children[i].getItemId() == itemId) - .findFirst() - .orElse(-1); - if (index != -1) { - Rs2Widget.clickWidgetFast(children[index], index, 4); - Rs2Inventory.waitForInventoryChanges(2000); - sleepUntil(() -> closeMessShop()); - Rs2Antiban.actionCooldown(); - return Rs2Inventory.hasItem(itemId); - } - } - } + /** Set the chatbox numeric input field directly. Avoids focus-routing race in typeString. */ + private static void setChatboxAmount(int amount) { + Microbot.getClientThread().runOnClientThreadOptional(() -> { + Widget input = Rs2Widget.getWidget(InterfaceID.Chatbox.MES_TEXT2); + if (input != null) { + input.setText(amount + "*"); } - return false; - }; + Microbot.getClient().setVarcStrValue(VarClientStr.INPUT_TEXT, String.valueOf(amount)); + return null; + }); } - private void setOrderOfStates() { - switch (config.dish()) { - case MEAT_PIE: - State.GETTING_READY.setNext(State.GET_EMPTY_BOWLS); - State.GET_EMPTY_BOWLS.setNext(State.GET_FLOUR); - State.GET_FLOUR.setNext(State.USE_SINK); - State.USE_SINK.setNext(State.COMBINE_PASTRY_DOUGH); - State.COMBINE_PASTRY_DOUGH.setNext(State.RETURN_EMPTY_BOWLS); - State.RETURN_EMPTY_BOWLS.setNext(State.GET_EMPTY_PIE_DISHES); - State.GET_EMPTY_PIE_DISHES.setNext(State.COMBINE_PIE_SHELL); - State.COMBINE_PIE_SHELL.setNext(State.GET_MEAT); - State.GET_MEAT.setNext(State.USE_CLAY_OVEN); - State.USE_CLAY_OVEN.setNext(State.COMBINE_MEAT_PIE); - State.COMBINE_MEAT_PIE.setNext(State.FINISH_COOKING); - State.FINISH_COOKING.setNext(State.USE_BUFFET_TABLE); - break; - case STEW: - State.GETTING_READY.setNext(State.GET_EMPTY_BOWLS); - State.GET_EMPTY_BOWLS.setNext(State.USE_SINK); - State.USE_SINK.setNext(State.GET_MEAT); - State.GET_MEAT.setNext(State.USE_CLAY_OVEN); - State.USE_CLAY_OVEN.setNext(State.COMBINE_MEAT_WATER); - State.COMBINE_MEAT_WATER.setNext(State.GET_POTATOES); - State.GET_POTATOES.setNext(State.COMBINE_STEW); - State.COMBINE_STEW.setNext(State.FINISH_COOKING); - State.FINISH_COOKING.setNext(State.USE_BUFFET_TABLE); - break; - case PIZZA: - State.GETTING_READY.setNext(State.GET_KNIFE); - State.GET_KNIFE.setNext(State.GET_EMPTY_BOWLS); - State.GET_EMPTY_BOWLS.setNext(State.GET_FLOUR); - State.GET_FLOUR.setNext(State.USE_SINK); - State.USE_SINK.setNext(State.COMBINE_PIZZA_BASE); - State.COMBINE_PIZZA_BASE.setNext(State.RETURN_EMPTY_BOWLS); - State.RETURN_EMPTY_BOWLS.setNext(State.GET_TOMATOES); - State.GET_TOMATOES.setNext(State.COMBINE_PIZZA_TOMATO); - State.COMBINE_PIZZA_TOMATO.setNext(State.GET_CHEESE); - State.GET_CHEESE.setNext(State.COMBINE_PIZZA_CHEESE); - State.COMBINE_PIZZA_CHEESE.setNext(State.GET_PINEAPPLES); - State.GET_PINEAPPLES.setNext(State.CUT_PINEAPPLE); - State.CUT_PINEAPPLE.setNext(State.FINISH_COOKING); - State.FINISH_COOKING.setNext(State.COMBINE_PIZZA_PINEAPPLE); - State.COMBINE_PIZZA_PINEAPPLE.setNext(State.USE_BUFFET_TABLE); - break; - default: - debug("Unknown dish selected, cannot set order of states."); + /** + * Combine two items with Make-All semantics. + *

    + * Issues one combine, presses SPACE on the Make-X production widget, then + * waits for one ingredient to deplete. Combine actions in this minigame + * have no sustained player animation (the bowl/pot/dish recipes are pure + * inventory transformations), so we cannot use {@code isAnimating} to + * detect the chain — we wait on {@link #count} change with a stall guard. + */ + private boolean combineAll(int item1, int item2, String statusText) { + if (!has(item1) || !has(item2)) return true; + logDecision("combineAll[" + item1 + "+" + item2 + "]"); + setStatus(statusText); + int before1 = count(item1); + int before2 = count(item2); + + Rs2Inventory.combine(item1, item2); + if (sleepUntil(Rs2Widget::isProductionWidgetOpen, 1500)) { + Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); } - State.USE_BUFFET_TABLE.setNext(State.WAITING); - State.WAITING.setNext(State.GETTING_READY); - State.HOP_WORLD.setNext(State.USE_BUFFET_TABLE); + boolean done = waitForCombineChain(item1, item2, before1, before2); + log.info("[mess] combineAll {} + {} -> done={} ({}->{} / {}->{})", + item1, item2, done, before1, count(item1), before2, count(item2)); + return true; } - private boolean closeMessShop() { - if (!Rs2Settings.isEscCloseInterfaceSettingEnabled()){ - closeWithESCKey(); - } else { - Widget w = Rs2Widget.getWidget(15859713); - if (w == null) { - debug("Closing button was not found, trying to close the shop using ESC key."); - closeWithESCKey(); - } else { - Widget[] children = w.getChildren(); - if (children != null && children.length > 0) { - Rs2Widget.clickWidget(children[children.length - 1]); - } else { - debug("No children found in the widget, trying to close the shop using ESC key."); - closeWithESCKey(); - } - } + /** + * Bowl-water + pot-flour combine: first pops a "Select an option" chat + * dialog (Pastry dough vs Pizza base), then a Make-X widget. Picks the + * option, presses SPACE for All, then blocks for full chain completion. + */ + private boolean combineDoughDialog(int item1, int item2, String dialogOption) { + if (!has(item1) || !has(item2)) return true; + logDecision("combineDough[" + dialogOption + "]"); + setStatus("Making " + dialogOption); + int before1 = count(item1); + int before2 = count(item2); + + Rs2Inventory.combine(item1, item2); + if (Rs2Dialogue.sleepUntilSelectAnOption()) { + Rs2Dialogue.keyPressForDialogueOption(dialogOption); + } + if (sleepUntil(Rs2Widget::isProductionWidgetOpen, 1500)) { + Rs2Keyboard.keyPress(KeyEvent.VK_SPACE); } + boolean done = waitForCombineChain(item1, item2, before1, before2); + log.info("[mess] combineDough {} + {} -> done={} (flour {}->{})", + item1, item2, done, before2, count(item2)); return true; } - private void closeWithESCKey() { - Rs2Keyboard.keyPress(KeyEvent.VK_ESCAPE); - if (!Rs2Inventory.isOpen()) { - Rs2Inventory.open(); + /** + * Wait for a combine chain to consume an ingredient. Returns when either + * {@code item1} or {@code item2} reaches 0. Stall guard: returns false if + * the combined count hasn't dropped for 5s. Hard cap 60s. + *

    + * Tracks the SUM of counts so any per-action depletion registers as + * progress. A min-based guard breaks for combines where one ingredient + * persists (e.g., knife+pineapple — knife stays at 2 while pineapple + * counts down, so min stays pinned at 2 and the stall trips early). + */ + private boolean waitForCombineChain(int item1, int item2, int before1, int before2) { + long start = System.currentTimeMillis(); + int lastSum = before1 + before2; + long lastChangeMs = start; + while (System.currentTimeMillis() - start < 60_000) { + int c1 = count(item1); + int c2 = count(item2); + if (c1 == 0 || c2 == 0) return true; + int currentSum = c1 + c2; + if (currentSum < lastSum) { + lastSum = currentSum; + lastChangeMs = System.currentTimeMillis(); + } else if (System.currentTimeMillis() - lastChangeMs > 5000) { + return false; + } + sleep(300); } + return false; } - private boolean canContinue() { - return isRunning() && super.isRunning() && Microbot.isLoggedIn() && !BreakHandlerScript.isBreakActive() && super.run(); - } - - private void info(String message) { - Microbot.log(Level.INFO, message); + /** + * Wait for a single inventory item to be fully consumed (count → 0). + * Stall guard {@code stallMs}: returns false if the count hasn't dropped + * for that long. Hard cap 90s. + */ + private boolean waitForDepleted(int itemId, int beforeCount, long stallMs) { + long start = System.currentTimeMillis(); + int lastCount = beforeCount; + long lastChangeMs = start; + while (System.currentTimeMillis() - start < 90_000) { + int now = count(itemId); + if (now == 0) return true; + if (now < lastCount) { + lastCount = now; + lastChangeMs = System.currentTimeMillis(); + } else if (System.currentTimeMillis() - lastChangeMs > stallMs) { + return false; + } + sleep(400); + } + return false; } - private void debug(String message) { - Microbot.log(Level.DEBUG, message); + /** + * Hard-fail invariant: no chain item should ever exceed {@link #BATCH_SIZE} + * (or {@link #PIZZA_BATCH_SIZE} for pizza). Exceeding it means our top-up + * math overshot or a leftover state went unhandled — we'd start over-fetching + * ingredients we'll never use, so stop loudly rather than waste resources. + */ + private boolean violatesBatchInvariant() { + int max = config.dish() == Dish.PIZZA ? PIZZA_BATCH_SIZE : BATCH_SIZE; + for (int id : chainItemsForDish()) { + int c = count(id); + if (c > max) { + log.error("[mess] BATCH INVARIANT VIOLATED: itemId={} count={} > max={} for dish={}", + id, c, max, config.dish()); + return true; + } + } + return false; } - @Override - public void shutdown() { - super.shutdown(); - setCurrentState(State.WAITING); - if (mainScheduledFuture != null && !mainScheduledFuture.isCancelled()) { - mainScheduledFuture.cancel(true); + private int[] chainItemsForDish() { + switch (config.dish()) { + case STEW: return new int[] { + ItemID.BOWL_EMPTY, ItemID.BOWL_WATER, + ItemID.HOSIDIUS_SERVERY_RAW_MEAT, ItemID.HOSIDIUS_SERVERY_COOKED_MEAT, + ItemID.HOSIDIUS_SERVERY_MEATWATER, ItemID.HOSIDIUS_SERVERY_POTATO, + ItemID.HOSIDIUS_SERVERY_UNCOOKED_STEW, ItemID.HOSIDIUS_SERVERY_STEW, + }; + case MEAT_PIE: return new int[] { + ItemID.BOWL_EMPTY, ItemID.BOWL_WATER, + ItemID.HOSIDIUS_SERVERY_POT_FLOUR, ItemID.HOSIDIUS_SERVERY_PASTRY_DOUGH, + ItemID.HOSIDIUS_SERVERY_PIEDISH, ItemID.HOSIDIUS_SERVERY_PIE_SHELL, + ItemID.HOSIDIUS_SERVERY_RAW_MEAT, ItemID.HOSIDIUS_SERVERY_COOKED_MEAT, + ItemID.HOSIDIUS_SERVERY_UNCOOKED_MEAT_PIE, ItemID.HOSIDIUS_SERVERY_MEAT_PIE, + }; + case PIZZA: return new int[] { + ItemID.BOWL_EMPTY, ItemID.BOWL_WATER, + ItemID.HOSIDIUS_SERVERY_POT_FLOUR, ItemID.HOSIDIUS_SERVERY_PASTRY_DOUGH, + ItemID.HOSIDIUS_SERVERY_PIZZA_BASE, ItemID.HOSIDIUS_SERVERY_TOMATO, + ItemID.HOSIDIUS_SERVERY_INCOMPLETE_PIZZA, ItemID.HOSIDIUS_SERVERY_CHEESE, + ItemID.HOSIDIUS_SERVERY_UNCOOKED_PIZZA, ItemID.HOSIDIUS_SERVERY_PLAIN_PIZZA, + ItemID.HOSIDIUS_SERVERY_PINEAPPLE, ItemID.HOSIDIUS_SERVERY_PINEAPPLE_CHUNKS, + ItemID.HOSIDIUS_SERVERY_PINEAPPLE_PIZZA, + }; } - debug("The Mess script has been shut down."); + return new int[0]; } - public enum State { - WAITING("Waiting", null), - GETTING_READY("Getting ready", null), - - GET_EMPTY_BOWLS("Getting empty bowls", null), - GET_EMPTY_PIE_DISHES("Getting empty pie dishes", null), - GET_KNIFE("Getting knife", null), - - RETURN_EMPTY_BOWLS("Returning empty bowls", null), - - GET_MEAT("Getting raw meat", null), + /** + * Drop any burnt food, but only between phases — never mid-cook. + * Dropping during a Cook-All chain interrupts the player's cooking animation + * and breaks the chain, so we gate on "not currently cooking." + */ + private void dropBurnt() { + if (!has(ItemID.BURNT_MEAT) && !has(ItemID.BURNT_PIE) && !has(ItemID.BURNT_STEW) && !has(ItemID.BURNT_PIZZA)) return; + if (Rs2Player.isAnimating(3500) || Rs2Widget.isProductionWidgetOpen()) return; + log.info("[mess] dropping burnt food"); + Rs2Inventory.dropAll(ItemID.BURNT_MEAT, ItemID.BURNT_PIE, ItemID.BURNT_STEW, ItemID.BURNT_PIZZA); + } - GET_POTATOES("Getting supplies", null), - GET_PINEAPPLES("Getting supplies", null), - GET_TOMATOES("Getting supplies", null), - GET_CHEESE("Getting supplies", null), - GET_FLOUR("Getting supplies", null), + private void serveAndMaybeHop(int appreciationWidgetId) { + if (isUnderAppreciationThreshold(appreciationWidgetId)) { + hopWorld(); + return; + } + if (!Rs2GameObject.canReach(BUFFET_TABLE_LOC)) { Rs2Walker.walkTo(BUFFET_TABLE_LOC, 4); return; } - USE_SINK("Using sink", null), + logDecision("serve"); + setStatus("Serving"); + boolean ok = Rs2GameObject.interact(BUFFET_TABLE_LOC, "Serve"); + log.info("[mess] buffet interact(Serve) -> {}", ok); + Rs2Player.waitForXpDrop(Skill.COOKING, 2500, false); + } - USE_CLAY_OVEN("Cooking", null), - FINISH_COOKING("Finishing cooking", null), + private boolean isUnderAppreciationThreshold(int widgetId) { + return Microbot.getClientThread().runOnClientThreadOptional(() -> { + Widget bar = Rs2Widget.getWidget(widgetId); + if (bar == null) return false; + Widget filled = bar.getChild(0); + if (filled == null || bar.getWidth() <= 0) return false; + int pct = filled.getWidth() * 100 / bar.getWidth(); + return pct < config.appreciation_threshold(); + }).orElse(false); + } - COMBINE_PASTRY_DOUGH("Combining ingredients", null), - COMBINE_PIE_SHELL("Combining ingredients", null), - COMBINE_MEAT_PIE("Combining ingredients", null), + private void hopWorld() { + setStatus("Hopping worlds"); + int currentWorld = Microbot.getClient().getWorld(); + Microbot.hopToWorld(Login.getRandomWorld(true)); + sleepUntil(() -> Microbot.getClient().getGameState() == GameState.HOPPING, 5000); + sleepUntil(() -> Microbot.getClient().getGameState() == GameState.LOGGED_IN, 15000); + if (Microbot.getClient().getWorld() == currentWorld) { + log.warn("World hop failed; will retry next tick."); + } + } - COMBINE_MEAT_WATER("Combining ingredients", null), - COMBINE_STEW("Combining ingredients", null), + // ---------- inventory cleanup ---------- + + private static final Set JUNK_ITEMS = Set.of( + ItemID.BOWL_EMPTY, ItemID.BOWL_WATER, ItemID.KNIFE, + ItemID.HOSIDIUS_SERVERY_PIEDISH, + ItemID.BURNT_PIZZA, ItemID.BURNT_PIE, ItemID.BURNT_STEW, ItemID.BURNT_MEAT, + ItemID.HOSIDIUS_SERVERY_RAW_MEAT, ItemID.HOSIDIUS_SERVERY_COOKED_MEAT, + ItemID.HOSIDIUS_SERVERY_PINEAPPLE, ItemID.HOSIDIUS_SERVERY_PINEAPPLE_CHUNKS, + ItemID.HOSIDIUS_SERVERY_TOMATO, ItemID.HOSIDIUS_SERVERY_CHEESE, ItemID.HOSIDIUS_SERVERY_POTATO, + ItemID.HOSIDIUS_SERVERY_POT_FLOUR, ItemID.HOSIDIUS_SERVERY_PASTRY_DOUGH, + ItemID.HOSIDIUS_SERVERY_PIZZA_BASE, ItemID.HOSIDIUS_SERVERY_INCOMPLETE_PIZZA, + ItemID.HOSIDIUS_SERVERY_PLAIN_PIZZA, ItemID.HOSIDIUS_SERVERY_PIE_SHELL, + ItemID.HOSIDIUS_SERVERY_UNCOOKED_MEAT_PIE, ItemID.HOSIDIUS_SERVERY_UNCOOKED_STEW, + ItemID.HOSIDIUS_SERVERY_UNCOOKED_PIZZA, ItemID.HOSIDIUS_SERVERY_MEATWATER, + // Cooked dishes — drop these too rather than walking to the bank with them + ItemID.HOSIDIUS_SERVERY_MEAT_PIE, ItemID.HOSIDIUS_SERVERY_STEW, ItemID.HOSIDIUS_SERVERY_PINEAPPLE_PIZZA + ); + + /** Inventory has any item that isn't (a) currently mid-batch (handled by tick predicates) or (b) reserved for the active dish. */ + private boolean hasJunk() { + if (Rs2Inventory.isEmpty()) return false; + // The per-dish flow handles every Hosidius/bowl/knife item. Junk = anything outside JUNK_ITEMS that we shouldn't process. + // Conversely, if everything in inventory IS in JUNK_ITEMS we don't need to clean — let the dish flow consume it. + // We only clean when there's a non-Mess item that the script can't progress with. + return Rs2Inventory.items().anyMatch(item -> !JUNK_ITEMS.contains(item.getId())); + } - COMBINE_PIZZA_BASE("Combining ingredients", null), - COMBINE_PIZZA_TOMATO("Combining ingredients", null), - COMBINE_PIZZA_CHEESE("Combining ingredients", null), - COMBINE_PIZZA_PINEAPPLE("Combining ingredients", null), + private void runCleanInventory() { + setStatus("Cleaning inventory"); + // Drop everything we recognize first (faster than banking). + int dropped = 0; + for (int slot = 0; slot < 28; slot++) { + if (Rs2Inventory.isSlotEmpty(slot)) continue; + Rs2ItemModel item = Rs2Inventory.getItemInSlot(slot); + if (item != null && JUNK_ITEMS.contains(item.getId())) { + Rs2Inventory.slotInteract(slot, "Drop"); + sleepGaussian(120, 40); + dropped++; + } + } + if (dropped > 0) return; + + // Anything left is unrecognized. Bank it. + if (Rs2Inventory.isEmpty()) return; + log.info("Banking {} unrecognized items at Hosidius Kitchen.", Rs2Inventory.count()); + Rs2Bank.bankItemsAndWalkBackToOriginalPosition( + Rs2Inventory.items().map(Rs2ItemModel::getName).collect(Collectors.toList()), + false, + BankLocation.HOSIDIUS_KITCHEN, + Rs2Player.getWorldLocation(), + 28, + 3 + ); + } - CUT_PINEAPPLE("Cutting the pineapples", null), + // ---------- shop close ---------- - USE_BUFFET_TABLE("Serving the food", null), - HOP_WORLD("Hopping worlds", null); + private void closeShop() { + if (!Rs2Widget.isWidgetVisible(SHOP_WIDGET_ID)) return; + Widget closeParent = Rs2Widget.getWidget(SHOP_CLOSE_PARENT); + Widget[] kids = closeParent != null ? closeParent.getChildren() : null; + if (kids != null && kids.length > 0) { + Rs2Widget.clickWidget(kids[kids.length - 1]); + } else { + Rs2Keyboard.keyPress(KeyEvent.VK_ESCAPE); + } + sleepUntil(() -> !Rs2Widget.isWidgetVisible(SHOP_WIDGET_ID), 1500); + } + // ---------- helpers ---------- - @Getter - private final String status; + private boolean has(int id) { return Rs2Inventory.hasItem(id); } + private int count(int id) { return Rs2Inventory.count(id); } - @Setter - @Getter - private State next; + private void setStatus(String s) { + if (overlay != null) overlay.setStatus(s); + } - State(String status, State next) { - this.status = status; - this.next = next; + @Override + public void shutdown() { + super.shutdown(); + if (mainScheduledFuture != null && !mainScheduledFuture.isCancelled()) { + mainScheduledFuture.cancel(true); } } @@ -785,11 +669,7 @@ public enum Dish { STEW("Servery Stew"), PIZZA("Servery Pineapple Pizza"); - @Getter - private final String name; - - Dish(String name) { - this.name = name; - } + @Getter private final String name; + Dish(String name) { this.name = name; } } } From 5df19c4f0655242ed91acfef900665933e27c077 Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Mon, 11 May 2026 15:18:19 -0400 Subject: [PATCH 75/95] fix(auto-smelting): rewrite bail-on-transient-state checks (#433) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The script popped "smithing level isn't high enough" / "Could not find item in bank" mid-run and shut down on transient state. Rewrites the condition checks so the loop self-heals instead of bailing on a single bad read. - Use getRealSkillLevel for the smithing gate (was getBoostedSkillLevel, which drains and reads 0 transiently); early-return on a 0 read. - Replace Microbot.showMessage + shutdown in the bank checks with Microbot.log + retry per PLUGIN_DEBUGGING_NOTES.md §6. - Wait for the deposit to settle before withdrawing so Rs2Bank's full-inventory guard doesn't silently skip the cycle-2 withdraw. - Fix duplicate isWearing(ICE_GLOVES) check that masked the regular-gloves branch. - Replace slow per-object ClientThread furnace lookup with withNameContains + nearestOnClientThread. - Catch printing ex.getMessage() -> Microbot.logStackTrace. - Scheduler tick 100ms -> 600ms (game-tick floor). Bumps version 1.0.3 -> 1.0.4. Co-authored-by: runsonmypc --- .../microbot/smelting/AutoSmeltingPlugin.java | 2 +- .../microbot/smelting/AutoSmeltingScript.java | 90 +++++++++---------- 2 files changed, 44 insertions(+), 48 deletions(-) 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 9599fc8865..195f2cf6c7 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 @@ -15,7 +15,6 @@ import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; -import java.text.MessageFormat; import java.util.Map; import java.util.concurrent.TimeUnit; @@ -35,8 +34,13 @@ public boolean run(AutoSmeltingConfig config) { try { if (!super.run()) return; if (!Microbot.isLoggedIn()) return; - if (config.SELECTED_BAR_TYPE().getRequiredSmithingLevel() > Rs2Player.getBoostedSkillLevel(Skill.SMITHING)) { - Microbot.showMessage("Your smithing level isn't high enough for " + config.SELECTED_BAR_TYPE().toString()); + int currentSmithing = Rs2Player.getRealSkillLevel(Skill.SMITHING); + // Skill reads return 0 transiently during login / region load. + if (currentSmithing <= 0) return; + int requiredSmithing = config.SELECTED_BAR_TYPE().getRequiredSmithingLevel(); + if (currentSmithing < requiredSmithing) { + Microbot.log("Smithing " + currentSmithing + " is below the " + requiredSmithing + + " required for " + config.SELECTED_BAR_TYPE() + ". Shutting down."); super.shutdown(); return; } @@ -61,6 +65,10 @@ public boolean run(AutoSmeltingConfig config) { if (!Rs2Player.isInMemberWorld()) { Rs2Bank.depositAll(); } else if (Rs2Player.isMember()) Rs2Bank.depositAllExcept(coalBag); + // depositAll fires a menu click and returns before the next game tick + // processes it. Wait for slots to free so the upcoming withdrawX isn't + // blocked by Rs2Bank's "inventory full of the wrong item" guard. + sleepUntil(() -> !Rs2Inventory.isFull(), 3000); if (config.SELECTED_BAR_TYPE().getId() == ItemID.IRON_BAR && Rs2Bank.hasItem(ItemID.RING_OF_FORGING) && !Rs2Equipment.isWearing(ItemID.RING_OF_FORGING)) { Rs2Bank.withdrawAndEquip(ItemID.RING_OF_FORGING); return; @@ -71,7 +79,7 @@ public boolean run(AutoSmeltingConfig config) { Rs2Bank.withdrawAndEquip(ItemID.GAUNTLETS_OF_GOLDSMITHING); return; } - if (selectedBar != i && (Rs2Bank.hasItem(ItemID.SMITHING_UNIFORM_GLOVES) || Rs2Bank.hasItem(ItemID.SMITHING_UNIFORM_GLOVES_ICE)) && (!Rs2Equipment.isWearing(ItemID.SMITHING_UNIFORM_GLOVES_ICE) ||!Rs2Equipment.isWearing(ItemID.SMITHING_UNIFORM_GLOVES_ICE))) { + if (selectedBar != i && (Rs2Bank.hasItem(ItemID.SMITHING_UNIFORM_GLOVES) || Rs2Bank.hasItem(ItemID.SMITHING_UNIFORM_GLOVES_ICE)) && !Rs2Equipment.isWearing(ItemID.SMITHING_UNIFORM_GLOVES_ICE) && !Rs2Equipment.isWearing(ItemID.SMITHING_UNIFORM_GLOVES)) { if (Rs2Bank.hasItem(ItemID.SMITHING_UNIFORM_GLOVES_ICE)) { Rs2Bank.withdrawAndEquip(ItemID.SMITHING_UNIFORM_GLOVES_ICE); return; @@ -107,52 +115,40 @@ public boolean run(AutoSmeltingConfig config) { withdrawRightAmountOfMaterials(config); return; } - Rs2TileObjectModel oneClickFurnace = Microbot.getRs2TileObjectCache().query() - .where(o -> o.getName() != null && o.getName().toLowerCase().contains("furnace")) - .within(initialPlayerLocation, 20) - .nearest(); - if (oneClickFurnace != null) { - if (Rs2Bank.isOpen()){ - Rs2Bank.closeBank(); - sleepUntil(() -> !Rs2Bank.isOpen(), 1000); - } - oneClickFurnace.click("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); - Rs2Antiban.actionCooldown(); - Rs2Antiban.takeMicroBreakByChance(); + if (Rs2Bank.isOpen()) { + Rs2Bank.closeBank(); + sleepUntil(() -> !Rs2Bank.isOpen(), 1500); return; } - // walk to the initial position (near furnace) - if (initialPlayerLocation.distanceTo(Rs2Player.getWorldLocation()) > 4) { - if (Rs2Bank.isOpen()) - Rs2Bank.closeBank(); - Rs2Walker.walkTo(initialPlayerLocation, 4); + // Run the lookup on the client thread so each getName() resolves in-place + // instead of round-tripping through ClientThread.invoke per scene object. + Rs2TileObjectModel furnace = Microbot.getRs2TileObjectCache().query() + .withNameContains("furnace") + .nearestOnClientThread(initialPlayerLocation, 20); + + if (furnace == null) { + if (initialPlayerLocation.distanceTo(Rs2Player.getWorldLocation()) > 4) { + Rs2Walker.walkTo(initialPlayerLocation, 4); + } else { + Microbot.status = "AutoSmelting: no furnace within 20 tiles of start — stand near a furnace and restart"; + } return; } - // interact with the furnace until the smelting dialogue opens in chat, click the selected bar icon - Rs2TileObjectModel furnace = Microbot.getRs2TileObjectCache().query() - .where(o -> o.getName() != null && o.getName().toLowerCase().contains("furnace")) - .within(initialPlayerLocation, 20) - .nearest(); - if (furnace != null) { - furnace.click("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); - Rs2Antiban.actionCooldown(); - Rs2Antiban.takeMicroBreakByChance(); - } + furnace.click("smelt"); + sleepUntil(Rs2Player::isMoving, 1000); + sleepUntil(() -> !Rs2Player.isMoving(), 6000); + 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); + Rs2Antiban.actionCooldown(); + Rs2Antiban.takeMicroBreakByChance(); } catch (Exception ex) { - System.out.println(ex.getMessage()); + Microbot.logStackTrace("AutoSmeltingScript", ex); } - }, 0, 100, TimeUnit.MILLISECONDS); + }, 0, 600, TimeUnit.MILLISECONDS); return true; } @@ -185,16 +181,16 @@ private void withdrawRightAmountOfMaterials(AutoSmeltingConfig config) { ? config.SELECTED_BAR_TYPE().getWithdrawalsWithCoalBag(Rs2Inventory.capacity()).get(requiredMaterials.getKey()) : config.SELECTED_BAR_TYPE().maxBarsForFullInventory() * amountForOne; if (!Rs2Bank.hasBankItem(name, totalAmount, true)) { - Microbot.showMessage(MessageFormat.format("Required Materials not in bank. You need {1} {0}.", name, totalAmount)); - super.shutdown(); + Microbot.log("Bank lacks " + totalAmount + " " + name + ". Shutting down."); + shutdown(); + return; } Rs2Bank.withdrawX(name, totalAmount, true); sleepUntil(() -> Rs2Inventory.hasItemAmount(name, totalAmount, false, true), 3500); - - // Exit if we did not end up finding it. + // Withdraw missed the verify window — let the next tick retry instead of bailing. if (!Rs2Inventory.hasItemAmount(name, totalAmount, false, true)) { - Microbot.showMessage("Could not find item in bank."); - shutdown(); + Microbot.log("Withdraw of " + totalAmount + " " + name + " didn't settle in 3.5s; retrying next tick."); + return; } } } From a5a0e85c5f161a61656b22b9e0aa0fbb0331ed8f Mon Sep 17 00:00:00 2001 From: Sami Date: Mon, 18 May 2026 20:08:51 +0200 Subject: [PATCH 76/95] fix: update version to 1.2.2 and add ITEM_NAME_SUFFIX_PATTERN regex --- .../microbot/blastoisefurnace/BlastoiseFurnacePlugin.java | 2 +- .../microbot/blastoisefurnace/BlastoiseFurnaceScript.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) 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 f73c4dc7e5..b433501c3a 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.1"; + final static String version = "1.2.2"; @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 a36472d803..7f5e2e6719 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 @@ -29,15 +29,16 @@ import java.util.*; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; +import java.util.regex.Pattern; import static net.runelite.api.gameval.ItemID.*; import static net.runelite.api.gameval.ObjectID.*; import static net.runelite.api.gameval.VarbitID.*; -import static net.runelite.client.plugins.microbot.util.misc.Rs2UiHelper.ITEM_NAME_SUFFIX_PATTERN; @Slf4j public class BlastoiseFurnaceScript extends Script { static final int coalBag = 12019; + private static final Pattern ITEM_NAME_SUFFIX_PATTERN = Pattern.compile("^(.*?)(?:\\s*\\((\\d+)\\))?$"); private static final int MAX_ORE_PER_INTERACTION = 27; private static final int MAX_ORE_PER_HYBRID_INTERACTION = 26; public static State state = State.BANKING; @@ -699,4 +700,3 @@ public boolean fullCoffer() { return coffer == 1; } } - From e7135ef63ffc3bc4721309524d04212ba1583c3d Mon Sep 17 00:00:00 2001 From: Max Date: Mon, 18 May 2026 11:10:54 -0700 Subject: [PATCH 77/95] feat(LeaguesToolkit): add Demonic Gorilla, Hespori, Kraken helpers and Ourania runner (#440) Adds four new Leagues utilities under the existing toolkit: - Demonic Gorilla prayer helper: event-driven prayer switching via onAnimationChanged, hit-tracking via onHitsplatApplied, and style-switch confirmation via the "Rhaaaaaaa!" overhead text. No polling. - Hespori (Echo) boss helper: prayer-only mode (fast loop) plus optional combat with flower-phase weapon switching and projectile-driven vine dodge. - Kraken boss helper: full fight automation (disturb tentacles, kill, disturb boss, kill, loot trident/tentacle/jar/pet) with Protect from Magic. - Ourania (ZMI) runner: bank -> walk -> craft loop using Banker's briefcase teleport for return trips. Each helper is gated by its own config toggle. Plugin version 1.2.0 -> 1.3.0. Co-authored-by: dev --- .../DemonicGorillaPrayerHelper.java | 178 +++++++ .../leaguestoolkit/HesporiBossHelper.java | 454 ++++++++++++++++++ .../leaguestoolkit/KrakenBossHelper.java | 188 ++++++++ .../leaguestoolkit/LeaguesToolkitConfig.java | 255 +++++++++- .../leaguestoolkit/LeaguesToolkitPlugin.java | 63 ++- .../leaguestoolkit/LeaguesToolkitScript.java | 64 +++ .../leaguestoolkit/OuraniaRunner.java | 253 ++++++++++ 7 files changed, 1428 insertions(+), 27 deletions(-) create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/DemonicGorillaPrayerHelper.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/HesporiBossHelper.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/KrakenBossHelper.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/OuraniaRunner.java diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/DemonicGorillaPrayerHelper.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/DemonicGorillaPrayerHelper.java new file mode 100644 index 0000000000..31929cfa50 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/DemonicGorillaPrayerHelper.java @@ -0,0 +1,178 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Actor; +import net.runelite.api.NPC; +import net.runelite.api.Player; +import net.runelite.api.events.AnimationChanged; +import net.runelite.api.events.HitsplatApplied; +import net.runelite.api.events.OverheadTextChanged; +import net.runelite.api.gameval.AnimationID; +import net.runelite.api.gameval.NpcID; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; +import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; + +import java.util.Set; + +/** + * Event-driven demonic gorilla prayer helper. + * Uses onAnimationChanged for instant prayer switching and + * onHitsplatApplied for hit counting to predict style switches. + */ +@Slf4j +public class DemonicGorillaPrayerHelper { + + private static final Set ALL_GORILLA_IDS = Set.of( + NpcID.MM2_DEMON_GORILLA_1_MELEE, NpcID.MM2_DEMON_GORILLA_1_RANGED, NpcID.MM2_DEMON_GORILLA_1_MAGIC, + NpcID.MM2_DEMON_GORILLA_2_MELEE, NpcID.MM2_DEMON_GORILLA_2_RANGED, NpcID.MM2_DEMON_GORILLA_2_MAGIC + ); + + private static final int ANIM_MAGIC = AnimationID.DEMONIC_GORILLA_MAGIC; // 7225 + private static final int ANIM_MELEE = AnimationID.DEMONIC_GORILLA_PUNCH; // 7226 + private static final int ANIM_RANGED = AnimationID.DEMONIC_GORILLA_RANGE; // 7227 + + @Getter + private String status = "Idle"; + @Getter + private String currentStyle = "Unknown"; + @Getter + private int blockedHitCount = 0; + + private int lastPrayedStyle = -1; + private boolean active = false; + + public void reset() { + status = "Idle"; + currentStyle = "Unknown"; + blockedHitCount = 0; + lastPrayedStyle = -1; + } + + public void setActive(boolean active) { + this.active = active; + if (!active && lastPrayedStyle != -1) { + deactivateCurrentPrayer(); + lastPrayedStyle = -1; + blockedHitCount = 0; + status = "Stopped"; + currentStyle = "None"; + } + } + + public boolean isActive() { + return active; + } + + /** + * Called from @Subscribe onAnimationChanged in the plugin. + * Fires INSTANTLY on the client thread when any actor's animation changes. + */ + public void onAnimationChanged(AnimationChanged event) { + if (!active) return; + + Actor actor = event.getActor(); + if (!(actor instanceof NPC)) return; + + NPC npc = (NPC) actor; + if (!ALL_GORILLA_IDS.contains(npc.getId())) return; + + // Check if this gorilla is targeting us + Player player = Microbot.getClient().getLocalPlayer(); + if (player == null || !player.equals(npc.getInteracting())) return; + + int anim = npc.getAnimation(); + int style = animToStyle(anim); + if (style == -1) return; // Not an attack animation + + if (style != lastPrayedStyle) { + switchPrayer(style); + lastPrayedStyle = style; + blockedHitCount = 0; // Reset count on style switch + } + } + + /** + * Called from @Subscribe onHitsplatApplied in the plugin. + * Tracks blocked hits (0 damage) to predict style switches. + */ + public void onHitsplatApplied(HitsplatApplied event) { + if (!active) return; + + // Only track hitsplats on the player + Actor actor = event.getActor(); + Player player = Microbot.getClient().getLocalPlayer(); + if (player == null || !actor.equals(player)) return; + + // 0 damage = blocked by prayer + if (event.getHitsplat().getAmount() == 0) { + blockedHitCount++; + status = "Praying " + currentStyle + " (" + blockedHitCount + "/3 blocked)"; + if (blockedHitCount >= 3) { + log.info("[DemonicGorilla] 3 blocked hits — expecting style switch!"); + status = "Switch incoming!"; + } + } else { + // Took damage — reset counter (prayer was wrong or boulder hit) + blockedHitCount = 0; + } + } + + /** + * Called from @Subscribe onOverheadTextChanged in the plugin. + * Detects the "Rhaaaaaaa!" scream that confirms a style switch. + */ + public void onOverheadTextChanged(OverheadTextChanged event) { + if (!active) return; + + Actor actor = event.getActor(); + if (!(actor instanceof NPC)) return; + + NPC npc = (NPC) actor; + if (!ALL_GORILLA_IDS.contains(npc.getId())) return; + + String text = event.getOverheadText(); + if (text != null && text.contains("Rhaaaaaaa")) { + log.info("[DemonicGorilla] Style switch confirmed via overhead scream!"); + blockedHitCount = 0; + status = "Style switched!"; + } + } + + private int animToStyle(int animation) { + if (animation == ANIM_MELEE) return 0; + if (animation == ANIM_RANGED) return 1; + if (animation == ANIM_MAGIC) return 2; + return -1; + } + + private void deactivateCurrentPrayer() { + switch (lastPrayedStyle) { + case 0: Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MELEE, false); break; + case 1: Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_RANGE, false); break; + case 2: Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MAGIC, false); break; + } + log.info("[DemonicGorilla] Deactivated prayer"); + } + + private void switchPrayer(int style) { + switch (style) { + case 0: + currentStyle = "Melee"; + log.info("[DemonicGorilla] → Protect from Melee (instant)"); + Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MELEE, true); + break; + case 1: + currentStyle = "Ranged"; + log.info("[DemonicGorilla] → Protect from Missiles (instant)"); + Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_RANGE, true); + break; + case 2: + currentStyle = "Magic"; + log.info("[DemonicGorilla] → Protect from Magic (instant)"); + Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MAGIC, true); + break; + } + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/HesporiBossHelper.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/HesporiBossHelper.java new file mode 100644 index 0000000000..5acc04ed78 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/HesporiBossHelper.java @@ -0,0 +1,454 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.HeadIcon; +import net.runelite.api.NPC; +import net.runelite.api.Perspective; +import net.runelite.api.Projectile; +import net.runelite.api.coords.LocalPoint; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.gameval.AnimationID; +import net.runelite.api.gameval.NpcID; +import net.runelite.api.gameval.SpotanimID; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.api.MenuAction; +import net.runelite.client.plugins.microbot.util.combat.Rs2Combat; +import net.runelite.client.plugins.microbot.util.menu.NewMenuEntry; +import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +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.prayer.Rs2Prayer; +import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; +import net.runelite.client.plugins.microbot.util.tile.Rs2Tile; +import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; + +import java.util.*; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import static net.runelite.client.plugins.microbot.util.Global.sleep; + +@Slf4j +public class HesporiBossHelper { + + // NPC IDs + private static final int HESPORI_NORMAL = NpcID.HESPORI; // 8583 + private static final int HESPORI_ECHO = NpcID.LEAGUE_6_HESPORI; // 15615 + private static final int HEALER_ACTIVE = NpcID.HESPORI_HEALER_ACTIVE; // 8584 + private static final int HEALER_INACTIVE = NpcID.HESPORI_HEALER_INACTIVE; // 8585 + + // Attack animations + private static final int ANIM_RANGED = AnimationID.HESPORI_ATTACK_RANGED; // 8224 + private static final int ANIM_SPECIAL = AnimationID.HESPORI_ATTACK_SPECIAL; // 8223 (magic) + + // Leagues 6 Hespori projectile IDs (discovered from in-game testing) + private static final int RANGE_PROJ = 3677; // Ranged attack — pray against, don't dodge + private static final int MAGIC_PROJ = 3678; // Magic attack — pray against, don't dodge + private static final int VINE_PROJ = 3680; // Vine/quadrant explosion — DODGE THIS + + @Getter + private String status = "Idle"; + @Getter + private String currentPrayer = "None"; + @Getter + private String phase = "Combat"; + + private int lastSeenAnimation = -1; + private int lastPrayedStyle = -1; // 0=missiles, 1=magic + @Getter @Setter + private volatile boolean vineDetected = false; + @Getter @Setter + private volatile int dodgeX = -1, dodgeY = -1; + private volatile long lastDodgeTime = 0; + private static final long DODGE_COOLDOWN_MS = 5000; // Don't dodge again for 5 seconds + + private ScheduledExecutorService fastExecutor; + private ScheduledFuture fastLoop; + private LeaguesToolkitConfig config; + + public void start(LeaguesToolkitConfig config) { + this.config = config; + reset(); + if (fastExecutor == null) { + fastExecutor = Executors.newSingleThreadScheduledExecutor(); + } + fastLoop = fastExecutor.scheduleWithFixedDelay(() -> { + try { + if (!Microbot.isLoggedIn()) return; + tick(); + } catch (Exception ex) { + log.error("[Hespori] Fast loop error", ex); + } + }, 0, 150, TimeUnit.MILLISECONDS); + log.info("[Hespori] Fast prayer loop started (150ms)"); + } + + public void stop() { + if (fastLoop != null) { + fastLoop.cancel(false); + fastLoop = null; + } + if (lastPrayedStyle != -1) { + Rs2Prayer.disableAllPrayers(); + lastPrayedStyle = -1; + } + status = "Stopped"; + currentPrayer = "None"; + } + + public void reset() { + status = "Idle"; + currentPrayer = "None"; + phase = "Combat"; + lastSeenAnimation = -1; + lastPrayedStyle = -1; + } + + public void tick() { + Rs2NpcModel hespori = findHespori(); + if (hespori == null) { + status = "No Hespori found"; + return; + } + + // Eat if HP low + if (Rs2Player.getHealthPercentage() <= 50) { + Rs2Player.eatAt(50); + } + + // Drink prayer if low + Rs2Player.drinkPrayerPotionAt(20); + + phase = "Combat"; + + // === FAST LOOP ONLY: prayer switching + eat/drink === + // ALL movement/clicking handled by tickCombat() on the main loop + + // Read boss animation for prayer switching + int anim = Microbot.getClientThread().runOnClientThreadOptional( + () -> hespori.getNpc().getAnimation() + ).orElse(-1); + + if (anim != -1 && anim != lastSeenAnimation) { + lastSeenAnimation = anim; + int style = animToStyle(anim); + if (style != -1 && style != lastPrayedStyle) { + switchPrayer(style); + lastPrayedStyle = style; + } + } + + // Default: keep Protect from Magic on if no recent attack detected + if (lastPrayedStyle == -1) { + Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MAGIC, true); + currentPrayer = "Magic (default)"; + } + + status = "Fighting Hespori (" + currentPrayer + ")"; + } + + /** + * Flower phase: boss is invulnerable, kill active flowers. + * Boss doesn't use magic during this phase — only ranged. + * Flowers show overhead prayers: + * - Protect from Melee (HeadIcon.MELEE) → use ranged/magic weapon + * - Protect from Ranged+Magic (HeadIcon.RANGE_MAGE) → use melee weapon + */ + private void handleFlowerPhase() { + // Keep Protect from Missiles on during flower phase (boss only uses ranged here) + if (!Rs2Prayer.isPrayerActive(Rs2PrayerEnum.PROTECT_RANGE)) { + Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_RANGE, true); + currentPrayer = "Missiles (flowers)"; + } + + // Kill ONE flower per tickCombat call — avoids blocking client thread with tight loop + Rs2NpcModel flower = findActiveFlower(); + if (flower == null) return; + + // Check overhead prayer for weapon switch + HeadIcon flowerPrayer = Microbot.getClientThread().runOnClientThreadOptional( + () -> flower.getHeadIcon() + ).orElse(null); + + if (flowerPrayer != null && config != null) { + boolean needsMelee = (flowerPrayer == HeadIcon.RANGED || flowerPrayer == HeadIcon.MAGIC + || flowerPrayer == HeadIcon.RANGE_MAGE); + if (needsMelee) { + String meleeWeapon = config.hesporiMeleeWeapon(); + if (!Rs2Equipment.isWearing(meleeWeapon) && Rs2Inventory.hasItem(meleeWeapon)) { + Rs2Inventory.wield(meleeWeapon); + sleep(150, 300); + } + } else { + String mageWeapon = config.hesporiMageWeapon(); + if (!Rs2Equipment.isWearing(mageWeapon) && Rs2Inventory.hasItem(mageWeapon)) { + Rs2Inventory.wield(mageWeapon); + sleep(150, 300); + } + } + } + + status = "Killing flower"; + Microbot.getRs2NpcCache().query() + .withId(flower.getId()) + .interact(flower.getId(), "Attack"); + } + + // Vine dodge is handled by handleVineDodge() called from tickCombat + + /** + * Called from tickCombat (main 1-second loop) — safe to click/walk here. + */ + public boolean handleVineDodge() { + if (!vineDetected) return false; + vineDetected = false; + dodgeX = -1; + dodgeY = -1; + + // Cooldown — don't dodge again within 5 seconds + if (System.currentTimeMillis() - lastDodgeTime < DODGE_COOLDOWN_MS) { + return false; + } + + WorldPoint myPos = Rs2Player.getWorldLocation(); + if (myPos == null) return false; + + // Determine which quadrant we're in relative to boss center + // Boss LocalPoint is always (7104, 7104) — convert to world offset direction + // Just move 5 tiles in the opposite direction from boss center + LocalPoint myLocal = Microbot.getClientThread().runOnClientThreadOptional(() -> { + var player = Microbot.getClient().getLocalPlayer(); + return player != null ? player.getLocalLocation() : null; + }).orElse(null); + + if (myLocal == null) return false; + + int bossLocalX = 7104, bossLocalY = 7104; + int dx = (myLocal.getX() > bossLocalX) ? -5 : 5; + int dy = (myLocal.getY() > bossLocalY) ? -5 : 5; + + WorldPoint dodgeTarget = new WorldPoint(myPos.getX() + dx, myPos.getY() + dy, myPos.getPlane()); + + status = "Dodging vine!"; + log.info("[Hespori] Vine dodge — Rs2Walker.walkFastCanvas to {}", dodgeTarget); + Rs2Walker.walkFastCanvas(dodgeTarget); + lastDodgeTime = System.currentTimeMillis(); + sleep(600, 900); + return true; + } + + private Rs2NpcModel findHespori() { + // Both normal and Echo Hespori use the same NPC ID (8583) + return findAliveNpc(HESPORI_NORMAL); + } + + /** + * Find an active flower by ID first, falling back to name-based search for Echo variants. + */ + private Rs2NpcModel findActiveFlower() { + // Try by known ID first + Rs2NpcModel byId = findAliveNpc(HEALER_ACTIVE); + if (byId != null) return byId; + + // Fallback: search by name "Flower" for Echo variants that might use different IDs + return Microbot.getRs2NpcCache().query() + .where(npc -> { + String name = npc.getName(); + return name != null && name.equalsIgnoreCase("Flower"); + }) + .where(npc -> !npc.isDead()) + .where(npc -> { + // Only active flowers (not inactive/cyan ones) + HeadIcon icon = Microbot.getClientThread().runOnClientThreadOptional( + () -> npc.getHeadIcon() + ).orElse(null); + return icon != null; // Active flowers have overhead prayers, inactive don't + }) + .nearest(); + } + + private Rs2NpcModel findAliveNpc(int id) { + return Microbot.getRs2NpcCache().query() + .withId(id) + .where(npc -> !npc.isDead()) + .nearest(); + } + + /** + * Called from the main 1-second toolkit loop to handle attacking. + * Separated from the fast prayer loop to avoid threading issues with doInvoke. + */ + public void tickCombat() { + log.info("[Hespori] tickCombat called"); + Rs2NpcModel hespori = findHespori(); + if (hespori == null) { + log.info("[Hespori] tickCombat: findHespori returned null — NPC not found or dead"); + return; + } + log.info("[Hespori] tickCombat: found NPC id={}, dead={}", hespori.getId(), hespori.isDead()); + + // Handle vine dodge FIRST — highest priority + if (handleVineDodge()) { + return; + } + + // Handle flower phase attacking + Rs2NpcModel activeFlower = findActiveFlower(); + if (activeFlower != null) { + handleFlowerPhase(); + return; + } + + // Ensure main weapon equipped + if (config != null) { + String mainWeapon = config.hesporiMainWeapon(); + if (!Rs2Equipment.isWearing(mainWeapon) && Rs2Inventory.hasItem(mainWeapon)) { + Rs2Inventory.wield(mainWeapon); + sleep(150, 300); + } + } + + // NOTE: No walk range check — player is already in the circular arena. + // Instance coordinate mismatch makes distance calculations unreliable. + + // Only click attack if not already interacting with Hespori + boolean alreadyFighting = Microbot.getClientThread().runOnClientThreadOptional(() -> { + var player = Microbot.getClient().getLocalPlayer(); + if (player == null) return false; + var target = player.getInteracting(); + if (target == null) return false; + return target.equals(hespori.getNpc()); + }).orElse(false); + + if (!alreadyFighting) { + status = "Attacking Hespori"; + // Dump full NPC state for debugging, then attack + Microbot.getClientThread().invoke(() -> { + try { + NPC rawNpc = hespori.getNpc(); + if (rawNpc == null) { + log.error("[Hespori] rawNpc is null"); + return; + } + + // Dump all info + log.info("[Hespori] === NPC DEBUG ==="); + log.info("[Hespori] NPC id={}, index={}, name={}", rawNpc.getId(), rawNpc.getIndex(), rawNpc.getName()); + log.info("[Hespori] worldLocation={}", rawNpc.getWorldLocation()); + log.info("[Hespori] localLocation={}", rawNpc.getLocalLocation()); + log.info("[Hespori] animation={}, isDead={}", rawNpc.getAnimation(), rawNpc.isDead()); + + var comp = Microbot.getClient().getNpcDefinition(rawNpc.getId()); + if (comp != null) { + log.info("[Hespori] Composition actions: {}", java.util.Arrays.toString(comp.getActions())); + } else { + log.error("[Hespori] NPCComposition is null for id={}", rawNpc.getId()); + } + + var canvasPoly = rawNpc.getCanvasTilePoly(); + log.info("[Hespori] canvasTilePoly={}", canvasPoly != null ? canvasPoly.getBounds() : "null"); + + // Verify the action exists and find its index + String[] actions = comp != null ? comp.getActions() : null; + if (actions == null) { + log.error("[Hespori] No actions on NPC"); + return; + } + + // Find attack action — don't hardcode, check what's available + String attackAction = null; + int attackIndex = -1; + for (int i = 0; i < actions.length; i++) { + if (actions[i] != null && actions[i].toLowerCase().contains("attack")) { + attackAction = actions[i]; + attackIndex = i; + break; + } + } + + if (attackAction == null) { + log.error("[Hespori] No attack-like action found in: {}", java.util.Arrays.toString(actions)); + return; + } + + log.info("[Hespori] Found action '{}' at index {}", attackAction, attackIndex); + + MenuAction menuAction = MenuAction.of(MenuAction.NPC_FIRST_OPTION.getId() + attackIndex); + log.info("[Hespori] MenuAction opcode: {} (id={})", menuAction, menuAction.getId()); + + Microbot.getClient().menuAction( + 0, 0, menuAction, rawNpc.getIndex(), -1, + attackAction, rawNpc.getName() != null ? rawNpc.getName() : "" + ); + log.info("[Hespori] menuAction dispatched successfully"); + } catch (Exception e) { + log.error("[Hespori] Attack error", e); + } + }); + sleep(1200, 1500); + } else { + status = "Fighting Hespori (" + currentPrayer + ")"; + } + } + + private boolean isMeleeWeaponEquipped(LeaguesToolkitConfig config) { + if (config == null) return false; + String mainWeapon = config.hesporiMainWeapon(); + String meleeWeapon = config.hesporiMeleeWeapon(); + // If the main weapon is the same as the melee weapon, we're in melee mode + return Rs2Equipment.isWearing(mainWeapon) && mainWeapon.equalsIgnoreCase(meleeWeapon); + } + + private int animToStyle(int animation) { + if (animation == ANIM_RANGED) return 0; // Ranged → Protect from Missiles + if (animation == ANIM_SPECIAL) return 1; // Magic → Protect from Magic + return -1; + } + + private void switchPrayer(int style) { + switch (style) { + case 0: + currentPrayer = "Missiles"; + log.info("[Hespori] → Protect from Missiles (ranged)"); + Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_RANGE, true); + break; + case 1: + currentPrayer = "Magic"; + log.info("[Hespori] → Protect from Magic"); + Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MAGIC, true); + break; + } + } + + private WorldPoint findSafeTile(WorldPoint center, int distance) { + Map dangerousTiles = Rs2Tile.getDangerousGraphicsObjectTiles(); + List candidates = new ArrayList<>(); + + for (int dx = -distance; dx <= distance; dx++) { + for (int dy = -distance; dy <= distance; dy++) { + if (dx == 0 && dy == 0) continue; + WorldPoint candidate = new WorldPoint( + center.getX() + dx, center.getY() + dy, center.getPlane()); + if (!dangerousTiles.containsKey(candidate) && Rs2Tile.isWalkable(candidate)) { + candidates.add(candidate); + } + } + } + + if (candidates.isEmpty()) return null; + + Rs2NpcModel boss = findHespori(); + if (boss != null) { + WorldPoint bossLoc = boss.getWorldLocation(); + candidates.sort(Comparator.comparingInt(c -> c.distanceTo(bossLoc))); + } + + return candidates.get(0); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/KrakenBossHelper.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/KrakenBossHelper.java new file mode 100644 index 0000000000..6dfd645e14 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/KrakenBossHelper.java @@ -0,0 +1,188 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.gameval.NpcID; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +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.combat.Rs2Combat; +import net.runelite.client.plugins.microbot.util.grounditem.Rs2GroundItem; +import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.prayer.Rs2Prayer; +import net.runelite.client.plugins.microbot.util.prayer.Rs2PrayerEnum; + +import static net.runelite.client.plugins.microbot.util.Global.sleep; + +@Slf4j +public class KrakenBossHelper { + + // NPC IDs + private static final int BOSS_WHIRLPOOL = NpcID.SLAYER_KRAKEN_BOSS_WHIRLPOOL; // 496 + private static final int BOSS_AWAKE = NpcID.SLAYER_KRAKEN_BOSS; // 494 + private static final int TENTACLE_WHIRLPOOL = NpcID.SLAYER_KRAKEN_BOSS_TENTACLE_WHIRLPOOL; // 5534 + private static final int TENTACLE_AWAKE = NpcID.SLAYER_KRAKEN_BOSS_TENTACLE; // 5535 + + @Getter + private String status = "Idle"; + private boolean antibanInitialized = false; + + public void reset() { + status = "Idle"; + antibanInitialized = false; + } + + public boolean tick(LeaguesToolkitConfig config) { + // Initialize antiban on first tick + if (!antibanInitialized) { + Rs2Antiban.resetAntibanSettings(); + Rs2Antiban.antibanSetupTemplates.applyCombatSetup(); + Rs2AntibanSettings.simulateMistakes = true; + Rs2AntibanSettings.takeMicroBreaks = true; + Rs2AntibanSettings.microBreakChance = 0.02; + Rs2AntibanSettings.actionCooldownChance = 0.15; + Rs2AntibanSettings.moveMouseOffScreen = true; + antibanInitialized = true; + log.info("[Kraken] Antiban initialized"); + } + + // Keep Protect from Magic on at all times + if (!Rs2Prayer.isPrayerActive(Rs2PrayerEnum.PROTECT_MAGIC)) { + Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MAGIC, true); + } + + // Eat food if HP low + if (Rs2Player.getHealthPercentage() <= config.krakenEatAt()) { + status = "Eating"; + Rs2Player.eatAt(config.krakenEatAt()); + sleep(300, 500); + } + + // Drink prayer pot if low + if (config.krakenDrinkPrayer()) { + Rs2Player.drinkPrayerPotionAt(config.krakenPrayerThreshold()); + } + + // Wait if already in combat + if (Rs2Player.isAnimating() || Rs2Player.isInteracting()) { + // Check what we're fighting + Rs2NpcModel awakeTentacle = findAliveNpc(TENTACLE_AWAKE); + Rs2NpcModel boss = findAliveNpc(BOSS_AWAKE); + if (awakeTentacle != null) { + status = "Killing tentacle"; + return true; + } + if (boss != null) { + status = "Fighting Kraken"; + return true; + } + } + + // Step 1: Kill any awake tentacles first + Rs2NpcModel awakeTentacle = findAliveNpc(TENTACLE_AWAKE); + if (awakeTentacle != null) { + if (!Rs2Combat.inCombat()) { + status = "Attacking tentacle"; + awakeTentacle.click("Attack"); + sleep(600, 900); + } else { + status = "Killing tentacle"; + } + return true; + } + + // Step 2: Disturb next tentacle whirlpool + Rs2NpcModel tentaclePool = findNpc(TENTACLE_WHIRLPOOL); + if (tentaclePool != null) { + status = "Disturbing tentacle"; + tentaclePool.click("Disturb"); + sleep(600, 900); + return true; + } + + // Step 3: All tentacles dead — disturb the boss whirlpool + Rs2NpcModel bossPool = findNpc(BOSS_WHIRLPOOL); + if (bossPool != null) { + status = "Disturbing Kraken boss"; + bossPool.click("Disturb"); + sleep(600, 900); + return true; + } + + // Step 4: Boss is awake — attack it + Rs2NpcModel boss = findAliveNpc(BOSS_AWAKE); + if (boss != null) { + if (!Rs2Combat.inCombat()) { + status = "Attacking Kraken"; + boss.click("Attack"); + sleep(600, 900); + } else { + status = "Fighting Kraken"; + } + return true; + } + + // Step 4: Boss is dead — loot valuable drops before respawn + if (lootValuableDrops()) { + status = "Looting"; + return true; + } + + // Between kills — safe to do antiban here while waiting + if (findNpc(BOSS_WHIRLPOOL) == null && findNpc(BOSS_AWAKE) == null) { + status = "Waiting for respawn..."; + Rs2Antiban.actionCooldown(); + Rs2Antiban.takeMicroBreakByChance(); + return true; + } + + // Also safe to antiban before starting a new kill cycle + if (findNpc(BOSS_WHIRLPOOL) != null && findNpc(TENTACLE_WHIRLPOOL) != null + && findAliveNpc(TENTACLE_AWAKE) == null && findAliveNpc(BOSS_AWAKE) == null) { + Rs2Antiban.actionCooldown(); + Rs2Antiban.takeMicroBreakByChance(); + } + + status = "Idle"; + return true; + } + + /** + * Loot high-value drops like Trident of the seas (full). + * Eats food to make space if inventory is full. + */ + private boolean lootValuableDrops() { + // Check for valuable drops nearby + String[] valuableItems = {"Trident of the seas (full)", "Kraken tentacle", "Jar of dirt", "Pet kraken"}; + for (String item : valuableItems) { + if (Rs2GroundItem.exists(item, 10)) { + if (Rs2Inventory.isFull()) { + // Eat food to make space + if (!Rs2Inventory.getInventoryFood().isEmpty()) { + Rs2Player.eatAt(100); + sleep(300, 500); + } + } + Rs2GroundItem.loot(item, 10); + sleep(600, 900); + return true; + } + } + return false; + } + + private Rs2NpcModel findNpc(int id) { + return Microbot.getRs2NpcCache().query() + .withId(id) + .nearest(); + } + + private Rs2NpcModel findAliveNpc(int id) { + return Microbot.getRs2NpcCache().query() + .withId(id) + .where(npc -> !npc.isDead()) + .nearest(); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java index 58d8a4595d..b234e61e7f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitConfig.java @@ -8,31 +8,45 @@ import net.runelite.client.config.Range; @ConfigGroup("LeaguesToolkit") -@ConfigInformation("

    Leagues Toolkit

    " + +@ConfigInformation("

    Leagues Toolkit (BETA)

    " + "

    Version: " + LeaguesToolkitPlugin.version + "

    " + - "

    Anti-AFK: Presses a random arrow key before the idle timer kicks in. " + + "

    This plugin is in BETA. Not all features are fully polished — some boss helpers " + + "may have edge cases in instanced areas. Use Prayer/eat only mode for boss helpers " + + "if the full automation isn't working reliably.

    " + + "
    " + + "

    Anti-AFK: Presses a random arrow key before the idle timer logs you out. " + "Great for long AFK sessions with auto-bank relics (e.g. Endless Harvest).

    " + - "

    Toci's Gem Store: Walks to Toci in Aldarin, buys uncut gems, " + - "and either sells cut gems back or banks them via the Banker's Briefcase. Three modes:

    " + + "

    Demonic Gorilla Prayer: Event-driven prayer switching — reacts instantly " + + "to gorilla attack animations via onAnimationChanged (no polling delay). " + + "Tracks blocked hits (0 damage) to predict style switches (3 blocked = switch incoming). " + + "Detects the 'Rhaaaaaaa!' overhead scream to confirm switches.

    " + + "

    Toci's Gem Store: Automated gem trading at Toci in Aldarin. Three modes:

    " + "
      " + - "
    • Buy & Bank — fast stockpile: buy uncut gems, briefcase to bank, walk back, repeat.
    • " + - "
    • Buy, Cut & Sell — buy uncut, cut with chisel, sell cut gems back to Toci for profit.
    • " + - "
    • Buy, Cut & Bank — buy uncut, cut, bank via briefcase, walk back, repeat.
    • " + + "
    • Buy & Bank — fast stockpile uncut gems via Banker's Briefcase Last-destination.
    • " + + "
    • Buy, Cut & Sell — buy uncut, cut with chisel, sell cut back to Toci.
    • " + + "
    • Buy, Cut & Bank — buy uncut, cut, bank via briefcase.
    • " + "
    " + - "

    Wealthy Citizen Thieving: Pickpockets Wealthy citizens with auto coin pouch opening. " + - "Requires the Larcenist relic for 100% success rate (no stuns). " + - "Configure the pouch threshold (max 280 before they auto-destroy).

    " + - "

    Easy Clue Opener: Farms reward caskets using the Aldarin bank easy clue method. " + - "Opens Scroll box (easy) — if the clue is a dig type, digs with spade repeatedly until a casket " + - "or a different clue appears. Non-dig clues are dropped and the next scroll box opens. " + - "Caskets stack in your inventory. Configurable action speed. Requires a spade and scroll boxes.

    " + - "

    Snape Grass Telegrab: Walks to the snape grass spawn and casts Telekinetic Grab " + - "on repeat. Requires 33 Magic, law runes, and air runes (or air staff equipped). " + - "Stops when inventory is full.

    " + - "

    Transmutation: Casts Alchemic Divergence or Convergence on noted items " + - "to upgrade/downgrade through tiers (e.g. Iron ore all the way to Runite ore). " + - "Have the starting items noted in your inventory before enabling. " + - "Requires the Transmutation relic and the transmutation ledger.

    ") + "

    Wealthy Citizen Thieving: Pickpockets Wealthy citizens (Larcenist relic required " + + "for 100% success). Opens coin pouches at configurable threshold (max 280).

    " + + "

    Easy Clue Opener: Aldarin bank easy clue farming. Opens scroll boxes, " + + "digs if clue ID is 29853, drops non-dig clues, stacks caskets. Configurable delays.

    " + + "

    Hespori (Echo): Prayer switches between Magic and Missiles based on attack animation. " + + "Kills flowers with correct combat style based on their overhead prayer (weapon switching). " + + "Vine dodge moves to opposite quadrant when projectile 3680 is detected. " + + "Prayer/eat only mode available for manual combat with automated prayer.

    " + + "

    Kraken Boss: Full automation — disturbs tentacles one by one (Disturb → Attack → kill), " + + "then wakes and kills the boss. Loots Trident of the seas, Kraken tentacle, Jar of dirt, Pet kraken. " + + "Keeps Protect from Magic on. Start inside the boss room with a slayer task.

    " + + "

    Snape Grass Telegrab: Walks to spawn, casts Telekinetic Grab on repeat. " + + "Banks via briefcase Last-destination or walks to nearest bank when full. " + + "Requires 33 Magic, law runes, air runes or air staff.

    " + + "

    Ourania Altar: Crafts runes at ZMI, banks via briefcase to Eniola. " + + "Deposits only crafted runes (keeps air runes + noted pure essence). " + + "Works with Transmutation running concurrently. Eniola requires auto-pay runes.

    " + + "

    Transmutation: Casts Alchemic Divergence/Convergence on noted items " + + "to upgrade/downgrade through tiers. Dropdown selection for start and target items. " + + "Shop-aware timeout (pauses detection when shop is open). " + + "Have starting items noted in inventory before enabling.

    ") public interface LeaguesToolkitConfig extends Config { @ConfigSection( @@ -88,10 +102,31 @@ default int antiAfkBufferMax() { return 1500; } + @ConfigSection( + name = "Demonic Gorilla Prayer", + description = "Auto-switches protection prayers based on the gorilla's current attack style", + position = 1, + closedByDefault = true + ) + String gorillaPrayerSection = "gorillaPrayerSection"; + + @ConfigItem( + keyName = "enableGorillaPrayer", + name = "Enable", + description = "Automatically switches between Protect from Melee, Missiles, and Magic " + + "based on which demonic gorilla variant is attacking you. " + + "Detects style changes by NPC ID transformation.", + position = 0, + section = gorillaPrayerSection + ) + default boolean enableGorillaPrayer() { + return false; + } + @ConfigSection( name = "Toci's Gem Store", description = "Automated gem buying, cutting, and selling/banking at Toci in Aldarin", - position = 1, + position = 2, closedByDefault = true ) String gemCutterSection = "gemCutterSection"; @@ -146,7 +181,7 @@ default int gemCutterMinCoins() { @ConfigSection( name = "Wealthy Citizen Thieving", description = "Pickpockets Wealthy citizens, opens coin pouches at a threshold", - position = 2, + position = 3, closedByDefault = true ) String thievingSection = "thievingSection"; @@ -178,7 +213,7 @@ default int coinPouchThreshold() { @ConfigSection( name = "Snape Grass Telegrab", description = "Telegrab snape grass at a fixed location", - position = 2, + position = 4, closedByDefault = true ) String snapeGrassSection = "snapeGrassSection"; @@ -198,7 +233,7 @@ default boolean enableSnapeGrass() { @ConfigSection( name = "Easy Clue Opener", description = "Opens scroll boxes, digs dig-clues, drops non-dig clues, opens reward caskets", - position = 4, + position = 5, closedByDefault = true ) String easyClueSection = "easyClueSection"; @@ -254,10 +289,178 @@ default int clueActionDelay() { return 300; } + @ConfigSection( + name = "Hespori (Echo)", + description = "Prayer switching and fight management for Hespori Echo boss", + position = 6, + closedByDefault = true + ) + String hesporiSection = "hesporiSection"; + + @ConfigItem( + keyName = "enableHespori", + name = "Enable", + description = "Manages the Hespori Echo fight. Auto-switches between Protect from Magic and Missiles " + + "based on attack animation, kills flowers with correct combat style based on their overhead prayer, " + + "dodges vine attacks. Uses a fast 150ms loop. " + + "Bring both a melee weapon and a ranged/magic weapon for flower phases.", + position = 0, + section = hesporiSection + ) + default boolean enableHespori() { + return false; + } + + @ConfigItem( + keyName = "hesporiPrayerOnly", + name = "Prayer/eat only", + description = "Only handle prayer switching, eating, and dodging. " + + "You control attacking and flower killing manually.", + position = 1, + section = hesporiSection + ) + default boolean hesporiPrayerOnly() { + return false; + } + + @ConfigItem( + keyName = "hesporiMainWeapon", + name = "Main weapon (boss)", + description = "Weapon to fight the boss with. Echo Hespori is weak to slash (e.g. Abyssal tentacle)", + position = 1, + section = hesporiSection + ) + default String hesporiMainWeapon() { + return "Abyssal tentacle"; + } + + @ConfigItem( + keyName = "hesporiMeleeWeapon", + name = "Melee weapon (flowers)", + description = "Weapon for flowers praying ranged+magic. Can be same as main weapon if main is melee.", + position = 2, + section = hesporiSection + ) + default String hesporiMeleeWeapon() { + return "Abyssal tentacle"; + } + + @ConfigItem( + keyName = "hesporiMageWeapon", + name = "Magic/Ranged weapon (flowers)", + description = "Weapon for flowers praying melee (e.g. Trident of the seas, Magic shortbow)", + position = 3, + section = hesporiSection + ) + default String hesporiMageWeapon() { + return "Trident of the seas"; + } + + @ConfigSection( + name = "Kraken Boss", + description = "Automates the Kraken boss fight — disturb tentacles, wake boss, attack, repeat", + position = 7, + closedByDefault = true + ) + String krakenSection = "krakenSection"; + + @ConfigItem( + keyName = "enableKraken", + name = "Enable", + description = "Automates the Kraken boss fight. Keeps Protect from Magic on, disturbs " + + "all 4 tentacle whirlpools, wakes the boss, attacks until dead, repeats. " + + "Requires a Kraken slayer task and magic combat. Start inside the boss room.", + position = 0, + section = krakenSection + ) + default boolean enableKraken() { + return false; + } + + @Range(min = 10, max = 90) + @ConfigItem( + keyName = "krakenEatAt", + name = "Eat at HP %", + description = "Eat food when HP drops below this percentage", + position = 1, + section = krakenSection + ) + default int krakenEatAt() { + return 50; + } + + @ConfigItem( + keyName = "krakenDrinkPrayer", + name = "Drink prayer pots", + description = "Automatically drink prayer potions", + position = 2, + section = krakenSection + ) + default boolean krakenDrinkPrayer() { + return true; + } + + @Range(min = 1, max = 99) + @ConfigItem( + keyName = "krakenPrayerThreshold", + name = "Drink prayer at", + description = "Drink prayer potion when points drop below this", + position = 3, + section = krakenSection + ) + default int krakenPrayerThreshold() { + return 20; + } + + @ConfigSection( + name = "Ourania Altar", + description = "Craft runes at the Ourania (ZMI) Altar with briefcase banking and transmutation", + position = 8, + closedByDefault = true + ) + String ouraniaSection = "ouraniaSection"; + + @ConfigItem( + keyName = "enableOurania", + name = "Enable", + description = "Crafts runes at Ourania Altar. Banks via briefcase Last-destination to Eniola. " + + "Transmutation runs concurrently to convert air runes to noted pure essence. " + + "Keep air runes, briefcase (equipped), and transmutation ledger in inventory. " + + "Eniola requires runes for banking (auto-pay).", + position = 0, + section = ouraniaSection + ) + default boolean enableOurania() { + return false; + } + + @Range(min = 1, max = 100) + @ConfigItem( + keyName = "ouraniaEatAtPercent", + name = "Eat at HP %", + description = "Eat food from bank when HP drops below this percentage", + position = 1, + section = ouraniaSection + ) + default int ouraniaEatAtPercent() { + return 50; + } + + @ConfigItem( + keyName = "ouraniaFoodName", + name = "Food name", + description = "Name of food to withdraw and eat at bank (e.g. Salmon, Lobster)", + position = 2, + section = ouraniaSection + ) + default String ouraniaFoodName() { + return "Salmon"; + } + @ConfigSection( name = "Transmutation", description = "Casts Alchemic Divergence/Convergence to upgrade or downgrade noted items through tiers", - position = 5, + position = 9, closedByDefault = true ) String transmuteSection = "transmuteSection"; diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java index c1f55f5ea3..6c907a5dd1 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitPlugin.java @@ -2,7 +2,12 @@ import com.google.inject.Provides; import lombok.extern.slf4j.Slf4j; +import net.runelite.api.events.AnimationChanged; +import net.runelite.api.events.HitsplatApplied; +import net.runelite.api.events.OverheadTextChanged; +import net.runelite.api.events.ProjectileMoved; 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.microbot.PluginConstants; @@ -20,7 +25,7 @@ ) @Slf4j public class LeaguesToolkitPlugin extends Plugin { - public static final String version = "1.2.0"; + public static final String version = "1.3.0"; @Inject private LeaguesToolkitConfig config; @@ -42,4 +47,60 @@ protected void startUp() { protected void shutDown() { leaguesToolkitScript.shutdown(); } + + @Subscribe + public void onAnimationChanged(AnimationChanged event) { + if (config.enableGorillaPrayer()) { + leaguesToolkitScript.getGorillaPrayerHelper().onAnimationChanged(event); + } + } + + @Subscribe + public void onHitsplatApplied(HitsplatApplied event) { + if (config.enableGorillaPrayer()) { + leaguesToolkitScript.getGorillaPrayerHelper().onHitsplatApplied(event); + } + } + + @Subscribe + public void onOverheadTextChanged(OverheadTextChanged event) { + if (config.enableGorillaPrayer()) { + leaguesToolkitScript.getGorillaPrayerHelper().onOverheadTextChanged(event); + } + } + + @Subscribe + public void onProjectileMoved(ProjectileMoved event) { + if (!config.enableHespori() || config.hesporiPrayerOnly()) return; + int id = event.getProjectile().getId(); + // Vine/quadrant explosion projectile — calculate dodge position on client thread + if (id == 3680) { + var helper = leaguesToolkitScript.getHesporiBossHelper(); + if (!helper.isVineDetected()) { + log.info("[LeaguesToolkit] Vine 3680 — calculating dodge position"); + // Calculate dodge canvas position (we're on client thread, safe to access) + var player = net.runelite.client.plugins.microbot.Microbot.getClient().getLocalPlayer(); + if (player != null) { + var myLocal = player.getLocalLocation(); + // Boss is always at center of arena: LocalPoint(7104, 7104) + // Determine which quadrant player is in relative to boss + // and move to the OPPOSITE quadrant + int bossX = 7104, bossY = 7104; + int dx = (myLocal.getX() > bossX) ? -768 : 768; // If east of boss → go west + int dy = (myLocal.getY() > bossY) ? -768 : 768; // If north of boss → go south + var dodgeLocal = new net.runelite.api.coords.LocalPoint( + myLocal.getX() + dx, myLocal.getY() + dy, myLocal.getWorldView()); + var poly = net.runelite.api.Perspective.getCanvasTilePoly( + net.runelite.client.plugins.microbot.Microbot.getClient(), dodgeLocal); + if (poly != null) { + var bounds = poly.getBounds(); + helper.setDodgeX((int) bounds.getCenterX()); + helper.setDodgeY((int) bounds.getCenterY()); + helper.setVineDetected(true); // Set AFTER coordinates are stored + log.info("[LeaguesToolkit] Dodge position: ({}, {})", bounds.getCenterX(), bounds.getCenterY()); + } + } + } + } + } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java index dad6391061..b20e44f9ca 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/LeaguesToolkitScript.java @@ -19,6 +19,8 @@ public class LeaguesToolkitScript extends Script { KeyEvent.VK_LEFT, KeyEvent.VK_RIGHT, KeyEvent.VK_UP, KeyEvent.VK_DOWN }; + @Getter + private final DemonicGorillaPrayerHelper gorillaPrayerHelper = new DemonicGorillaPrayerHelper(); @Getter private final GemCutter gemCutter = new GemCutter(); @Getter @@ -28,11 +30,21 @@ public class LeaguesToolkitScript extends Script { @Getter private final EasyClueOpener easyClueOpener = new EasyClueOpener(); @Getter + private final HesporiBossHelper hesporiBossHelper = new HesporiBossHelper(); + @Getter + private final KrakenBossHelper krakenBossHelper = new KrakenBossHelper(); + @Getter + private final OuraniaRunner ouraniaRunner = new OuraniaRunner(); + @Getter private final SnapeGrassTelegrabber snapeGrassTelegrabber = new SnapeGrassTelegrabber(); + private boolean gorillaPrayerWasEnabled = false; private boolean gemCutterWasEnabled = false; private boolean thievingWasEnabled = false; private boolean easyClueWasEnabled = false; + private boolean hesporiWasEnabled = false; + private boolean krakenWasEnabled = false; + private boolean ouraniaWasEnabled = false; private boolean snapeGrassWasEnabled = false; private boolean transmuteWasEnabled = false; @@ -46,6 +58,20 @@ public boolean run(LeaguesToolkitConfig config) { runAntiAfk(config); } + if (config.enableGorillaPrayer()) { + if (!gorillaPrayerWasEnabled) { + gorillaPrayerHelper.reset(); + gorillaPrayerHelper.setActive(true); + gorillaPrayerWasEnabled = true; + } + // Fully event-driven — no tick/poll needed + } else { + if (gorillaPrayerWasEnabled) { + gorillaPrayerHelper.setActive(false); + } + gorillaPrayerWasEnabled = false; + } + if (config.enableGemCutter()) { if (!gemCutterWasEnabled) { gemCutter.reset(); @@ -77,6 +103,42 @@ public boolean run(LeaguesToolkitConfig config) { easyClueWasEnabled = false; } + if (config.enableHespori()) { + if (!hesporiWasEnabled) { + hesporiBossHelper.start(config); + hesporiWasEnabled = true; + } + // Combat runs from the main loop (safe thread for doInvoke) + if (!config.hesporiPrayerOnly()) { + hesporiBossHelper.tickCombat(); + } + } else { + if (hesporiWasEnabled) { + hesporiBossHelper.stop(); + } + hesporiWasEnabled = false; + } + + if (config.enableKraken()) { + if (!krakenWasEnabled) { + krakenBossHelper.reset(); + krakenWasEnabled = true; + } + krakenBossHelper.tick(config); + } else { + krakenWasEnabled = false; + } + + if (config.enableOurania()) { + if (!ouraniaWasEnabled) { + ouraniaRunner.reset(); + ouraniaWasEnabled = true; + } + ouraniaRunner.tick(config); + } else { + ouraniaWasEnabled = false; + } + if (config.enableSnapeGrass()) { if (!snapeGrassWasEnabled) { snapeGrassTelegrabber.reset(); @@ -130,6 +192,8 @@ private void runAntiAfk(LeaguesToolkitConfig config) { @Override public void shutdown() { super.shutdown(); + gorillaPrayerHelper.setActive(false); + hesporiBossHelper.stop(); transmuter.reset(); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/OuraniaRunner.java b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/OuraniaRunner.java new file mode 100644 index 0000000000..71ed44ef16 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/leaguestoolkit/OuraniaRunner.java @@ -0,0 +1,253 @@ +package net.runelite.client.plugins.microbot.leaguestoolkit; + +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.coords.WorldArea; +import net.runelite.api.coords.WorldPoint; +import net.runelite.api.gameval.ItemID; +import net.runelite.api.gameval.NpcID; +import net.runelite.api.gameval.ObjectID; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +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.inventory.Rs2Inventory; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; +import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; + +import static net.runelite.client.plugins.microbot.util.Global.sleep; +import static net.runelite.client.plugins.microbot.util.Global.sleepUntil; + +@Slf4j +public class OuraniaRunner { + + private static final WorldArea ALTAR_AREA = new WorldArea(new WorldPoint(3054, 5574, 0), 12, 12); + private static final WorldPoint ENIOLA_AREA = new WorldPoint(3014, 5625, 0); + private static final String BRIEFCASE_NAME = "Banker's briefcase"; + private static final int PURE_ESSENCE_ID = 7936; // Pure essence (BLANKRUNE_HIGH in gameval) + private static final String PURE_ESSENCE_NAME = "Pure essence"; + + private enum State { + CRAFTING, + TELEPORTING_TO_BANK, + BANKING, + WALKING_TO_ALTAR + } + + @Getter + private String status = "Idle"; + private State state = State.CRAFTING; + private int bankFailCount = 0; + private static final int MAX_BANK_FAILS = 3; + + public void reset() { + status = "Idle"; + state = State.CRAFTING; + bankFailCount = 0; + } + + public boolean tick(LeaguesToolkitConfig config) { + switch (state) { + case CRAFTING: + return handleCrafting(); + case TELEPORTING_TO_BANK: + return handleTeleportToBank(); + case BANKING: + return handleBanking(config); + case WALKING_TO_ALTAR: + return handleWalkingToAltar(config); + } + return true; + } + + private boolean handleCrafting() { + // Check if we have pure essence to craft + if (!Rs2Inventory.hasItem(PURE_ESSENCE_ID)) { + if (isNearAltar()) { + status = "No essence — heading to bank"; + state = State.TELEPORTING_TO_BANK; + return true; + } else if (isNearEniola()) { + status = "At bank, no essence"; + state = State.BANKING; + return true; + } else { + status = "No essence — teleporting to bank"; + state = State.TELEPORTING_TO_BANK; + return true; + } + } + + if (!isNearAltar()) { + status = "Walking to altar"; + state = State.WALKING_TO_ALTAR; + return true; + } + + if (Rs2Player.isAnimating()) { + status = "Crafting..."; + return true; + } + + status = "Crafting runes at altar"; + Microbot.getRs2TileObjectCache().query() + .withId(ObjectID.RC_ZMI_DUNGEON_CRACKED_CENTER_ALTAR) + .interact("craft-rune"); + Rs2Inventory.waitForInventoryChanges(5000); + return true; + } + + private boolean handleTeleportToBank() { + if (Rs2Player.isAnimating() || Rs2Player.isMoving()) { + status = "In transit..."; + return true; + } + + if (isNearEniola()) { + status = "At Eniola"; + state = State.BANKING; + return true; + } + + // Use briefcase to teleport to bank + if (Rs2Equipment.isWearing(BRIEFCASE_NAME)) { + status = "Briefcase Last-destination to Eniola"; + Rs2Equipment.interact(BRIEFCASE_NAME, "Last-destination"); + sleep(2000, 3000); + sleepUntil(() -> !Rs2Player.isAnimating() && !Rs2Player.isMoving(), 10000); + sleep(500, 1000); + if (isNearEniola()) { + state = State.BANKING; + } + } else { + // No briefcase — walk to Eniola + status = "No briefcase — walking to Eniola"; + log.warn("[Ourania] Briefcase not equipped, falling back to walk"); + Rs2Walker.walkTo(ENIOLA_AREA, 4); + sleepUntil(() -> isNearEniola() || !Rs2Player.isMoving(), 30000); + if (isNearEniola()) { + state = State.BANKING; + } + } + return true; + } + + private boolean handleBanking(LeaguesToolkitConfig config) { + if (!Rs2Bank.isOpen()) { + Rs2NpcModel eniola = Microbot.getRs2NpcCache().query() + .withId(NpcID.RC_ZMI_BANKER).nearest(); + if (eniola == null) { + status = "Can't find Eniola — walking closer"; + Rs2Walker.walkTo(ENIOLA_AREA, 4); + sleepUntil(() -> !Rs2Player.isMoving(), 5000); + return true; + } + status = "Opening bank at Eniola"; + eniola.click("bank"); + sleepUntil(Rs2Bank::isOpen, 5000); + + // Auto-pay exhaustion detection — if bank didn't open after clicking, runes may be depleted + if (!Rs2Bank.isOpen()) { + bankFailCount++; + log.warn("[Ourania] Bank failed to open (attempt {}/{})", bankFailCount, MAX_BANK_FAILS); + if (bankFailCount >= MAX_BANK_FAILS) { + status = "Eniola refused banking — auto-pay runes depleted?"; + log.error("[Ourania] Stopping: Eniola refused banking {} times — check auto-pay runes", MAX_BANK_FAILS); + return false; + } + sleep(1000, 2000); + return true; + } + bankFailCount = 0; + return true; + } + + // Deposit crafted runes only — keep air runes, noted pure essence, ledger, etc. + status = "Depositing crafted runes"; + // Collect IDs to deposit first, then deposit (avoid modifying stream mid-iteration) + java.util.List toDeposit = Rs2Inventory.items() + .filter(item -> item.getName().toLowerCase().contains("rune") + && !item.getName().toLowerCase().contains("air rune") + && !item.getName().toLowerCase().contains("pure essence")) + .map(item -> item.getId()) + .distinct() + .collect(java.util.stream.Collectors.toList()); + + for (int id : toDeposit) { + Rs2Bank.depositAll(id); + Rs2Inventory.waitForInventoryChanges(1800); + } + + // Eat food if HP low + if (Rs2Player.getHealthPercentage() <= config.ouraniaEatAtPercent()) { + status = "Eating food at bank"; + int maxEats = 10; + while (--maxEats > 0 && Rs2Player.getHealthPercentage() < 100 && Rs2Bank.hasItem(config.ouraniaFoodName())) { + Rs2Bank.withdrawOne(config.ouraniaFoodName()); + Rs2Inventory.waitForInventoryChanges(1800); + Rs2Player.useFood(); + Rs2Inventory.waitForInventoryChanges(1800); + } + } + + // If no pure essence in bank, deposit our noted stack so it becomes available unnoted + if (!Rs2Bank.hasBankItem(PURE_ESSENCE_NAME)) { + if (Rs2Inventory.hasItem(PURE_ESSENCE_NAME)) { + status = "Depositing noted pure essence to unnote"; + Rs2Bank.depositAll(PURE_ESSENCE_ID); + Rs2Inventory.waitForInventoryChanges(1800); + } else { + status = "No pure essence anywhere — waiting for transmutation"; + Rs2Bank.closeBank(); + sleep(3000, 5000); + return true; + } + } + + status = "Withdrawing pure essence"; + Rs2Bank.withdrawAll(PURE_ESSENCE_ID); + Rs2Inventory.waitForInventoryChanges(1800); + Rs2Bank.closeBank(); + sleepUntil(() -> !Rs2Bank.isOpen(), 3000); + + state = State.WALKING_TO_ALTAR; + return true; + } + + private boolean handleWalkingToAltar(LeaguesToolkitConfig config) { + if (isNearAltar()) { + state = State.CRAFTING; + return true; + } + + if (Rs2Player.isMoving()) { + status = "Walking to altar..."; + // Eat while walking if HP low + if (Rs2Player.getHealthPercentage() <= config.ouraniaEatAtPercent()) { + if (Rs2Inventory.hasItem(config.ouraniaFoodName())) { + Rs2Player.useFood(); + } + } + return true; + } + + status = "Walking to altar"; + // Walk via the short path through the cave + Rs2Walker.walkTo(new WorldPoint(3060, 5580, 0), 6); + sleep(2000, 3000); + return true; + } + + private boolean isNearAltar() { + WorldPoint loc = Rs2Player.getWorldLocation(); + return loc != null && ALTAR_AREA.contains(loc); + } + + private boolean isNearEniola() { + Rs2NpcModel eniola = Microbot.getRs2NpcCache().query() + .withId(NpcID.RC_ZMI_BANKER).nearest(); + if (eniola == null) return false; + WorldPoint loc = Rs2Player.getWorldLocation(); + return loc != null && loc.distanceTo2D(eniola.getWorldLocation()) < 12; + } +} From 0bee498d1fdcfd3e8a247ae5ab5dae2879eec209 Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Mon, 18 May 2026 14:11:03 -0400 Subject: [PATCH 78/95] fix(birdhouse): rewrite seed matching, optimize state machine (#439) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(birdhouse): rewrite seed matching, fix plugin shutoff, optimize walking - Replace ID-based seed list with name-based BIRDHOUSE_SEED_NAMES (single source of truth) — fixes seeds-in-inventory not being recognized because Rs2Inventory.count() returns slot count, not stack quantity - Use Microbot.stopPlugin() instead of this.shutdown() so the plugin properly disables in the panel after a completed run - Use Rs2Walker.walkFastCanvas() for same-plane hops ≤15 tiles (H1→H2) instead of WebWalker (~3s vs ~12s) - Skip isMoving() gate in arrivedAndStill once within ARRIVAL_RADIUS so interactions fire immediately on arrival - Add comprehensive diagnostic logging throughout the state machine * perf(birdhouse): interact-first pattern, fall-through loop, 600ms tick - Try Rs2GameObject.interact before walking — if the birdhouse is loaded in the scene, the game auto-walks and interacts in one fluid motion - Fall back to Rs2Walker only when the object isn't in the scene, stopping at 25 tiles (SCENE_INTERACT_RANGE) instead of 4 so the interact can take over on the next tick - Wrap state machine in a while(advanced) loop so completed actions immediately fall through to the next state within the same tick (dismantle→build→seed in one tick instead of three) - Reduce scheduleWithFixedDelay from 1000ms to 600ms (one game tick) * fix(birdhouse): wait for actual seed quantity change before advancing sleepUntil used Rs2Inventory.count() which returns slot count (always 1 for stackable seeds), not stack quantity. Replaced with name-based findInventoryBirdhouseSeed().getQuantity() so the script waits for the real quantity drop (e.g. 40→30) before moving to the next state. * fix(birdhouse): gate varp reads on Fossil Island presence Varps read as 0 (stale/unloaded) when the player is off Fossil Island, causing the script to skip houses entirely. Added isOnFossilIsland() gate before reading varps in dismantle/build/seed — walks to the house first if not on the island. Also added region 14908 (mushtree hub) to FOSSIL_ISLAND_REGIONS which was incorrectly excluded. --------- Co-authored-by: runsonmypc --- .../FornBirdhouseRunsPlugin.java | 2 +- .../FornBirdhouseRunsScript.java | 621 +++++++++++++----- 2 files changed, 468 insertions(+), 155 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsPlugin.java index bebef016db..6906635900 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsPlugin.java @@ -26,7 +26,7 @@ ) @Slf4j public class FornBirdhouseRunsPlugin extends Plugin { - final static String version = "1.1.0"; + final static String version = "1.1.1"; @Provides FornBirdhouseRunsConfig provideConfig(ConfigManager configManager) { return configManager.getConfig(FornBirdhouseRunsConfig.class); diff --git a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java index 01748aafc8..d5c75785dc 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java @@ -2,10 +2,11 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.gameval.ItemID; +import net.runelite.api.gameval.VarPlayerID; import net.runelite.api.Quest; import net.runelite.api.QuestState; -import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; + import net.runelite.client.Notifier; import net.runelite.client.config.Notification; import net.runelite.client.plugins.microbot.Microbot; @@ -15,15 +16,20 @@ import net.runelite.client.plugins.microbot.util.Rs2InventorySetup; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.bank.enums.BankLocation; +import net.runelite.client.plugins.microbot.util.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.walker.Rs2Walker; -import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; + +import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import javax.inject.Inject; import java.util.Arrays; import java.util.List; +import java.util.Optional; +import java.util.Set; import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; import static net.runelite.client.plugins.microbot.birdhouseruns.FornBirdhouseRunsInfo.*; @@ -33,8 +39,47 @@ public class FornBirdhouseRunsScript extends Script { private static final WorldPoint birdhouseLocation2 = new WorldPoint(3768, 3761, 0); private static final WorldPoint birdhouseLocation3 = new WorldPoint(3677, 3882, 0); private static final WorldPoint birdhouseLocation4 = new WorldPoint(3679, 3815, 0); + // Each location maps to a BIRDHOUSE_TRANSMIT_* varp. See isEmpty/isBuilt/isSeeded + // below for the canonical state decoding (matches RuneLite's BirdHouseState). + private static final int VARP_HOUSE_1 = VarPlayerID.BIRDHOUSE_TRANSMIT_D; // Verdant SW + private static final int VARP_HOUSE_2 = VarPlayerID.BIRDHOUSE_TRANSMIT_C; // Verdant NE + private static final int VARP_HOUSE_3 = VarPlayerID.BIRDHOUSE_TRANSMIT_A; // Meadow N + private static final int VARP_HOUSE_4 = VarPlayerID.BIRDHOUSE_TRANSMIT_B; // Meadow S + private static final int ARRIVAL_RADIUS = 4; + private static final int SCENE_INTERACT_RANGE = 25; + // Canonical Fossil Island region IDs (matches RuneLite's BirdHouseTracker). + private static final java.util.Set FOSSIL_ISLAND_REGIONS = java.util.Set.of( + 14650, 14651, 14652, 14906, 14907, 14908, 15162, 15163); + // Single source of truth: a birdhouse-accepted seed is one whose item name + // (lowercased) is in this set. Bank lookup, inventory lookup, and seed pick + // all match the same way — no id-list drift, no placeholder/variant gotchas. + // Set lists every allotment/hop/flower seed birdhouses accept (Farming + // level ≤ 35, per OSRS Wiki). + private static final Set BIRDHOUSE_SEED_NAMES = Set.of( + "potato seed", + "onion seed", + "cabbage seed", + "tomato seed", + "sweetcorn seed", + "strawberry seed", + "barley seed", + "hammerstone seed", + "asgarnian seed", + "jute seed", + "yanillian seed", + "krandorian seed", + "wildblood seed", + "marigold seed", + "rosemary seed", + "nasturtium seed", + "woad seed", + "limpwurt seed" + ); + private static final long STATE_STALL_TIMEOUT_MS = 120_000L; private boolean initialized; private String setupErrorMessage = ""; + private states lastObservedStatus; + private long stateEnteredAtMs; @Inject private Notifier notifier; private final FornBirdhouseRunsPlugin plugin; @@ -79,94 +124,145 @@ public boolean run() { this.shutdown(); return; } - } else { - // Auto bank withdrawal + } else if (!hasRequiredInventory()) { + // Auto bank withdrawal — only if inventory isn't already prepared. if (!setupManualInventory()) { log.error("Birdhouse run failed: {}", setupErrorMessage); this.shutdown(); return; } + } else { + log.info("Inventory already prepared — skipping bank trip"); } botStatus = states.TELEPORTING; } if (!super.run()) return; - switch (botStatus) { - case TELEPORTING: - Rs2Walker.walkTo(new WorldPoint(3764, 3869, 1), 5); - botStatus = states.VERDANT_TELEPORT; - break; - case VERDANT_TELEPORT: - interactWithObject(30920); - sleepUntil(() -> Rs2Widget.findWidget("Mycelium Transportation System") != null); - Rs2Widget.clickWidget(39845895); - sleepUntil(() -> Rs2Player.distanceTo(birdhouseLocation1) < 20); - botStatus = states.DISMANTLE_HOUSE_1; - break; - case DISMANTLE_HOUSE_1: - dismantleBirdhouse(30568, states.BUILD_HOUSE_1); - break; - case BUILD_HOUSE_1: - buildBirdhouse(birdhouseLocation1, states.SEED_HOUSE_1); - break; - case SEED_HOUSE_1: - seedHouse(birdhouseLocation1, states.DISMANTLE_HOUSE_2); - case DISMANTLE_HOUSE_2: - dismantleBirdhouse(30567, states.BUILD_HOUSE_2); - break; - case BUILD_HOUSE_2: - buildBirdhouse(birdhouseLocation2, states.SEED_HOUSE_2); - break; - case SEED_HOUSE_2: - seedHouse(birdhouseLocation2, states.MUSHROOM_TELEPORT); - break; - case MUSHROOM_TELEPORT: - interactWithObject(30924); - sleepUntil(() -> Rs2Widget.findWidget("Mycelium Transportation System") != null); - Rs2Widget.clickWidget(39845903); - sleepUntil(() -> Rs2Player.distanceTo(birdhouseLocation3) < 20); - botStatus = states.DISMANTLE_HOUSE_3; - break; - case DISMANTLE_HOUSE_3: - dismantleBirdhouse(30565, states.BUILD_HOUSE_3); - break; - case BUILD_HOUSE_3: - buildBirdhouse(birdhouseLocation3, states.SEED_HOUSE_3); - break; - case SEED_HOUSE_3: - seedHouse(birdhouseLocation3, states.DISMANTLE_HOUSE_4); - break; - case DISMANTLE_HOUSE_4: - Rs2Walker.walkTo(new WorldPoint(3680, 3813, 0)); - dismantleBirdhouse(30566, states.BUILD_HOUSE_4); - break; - case BUILD_HOUSE_4: - buildBirdhouse(birdhouseLocation4, states.SEED_HOUSE_4); - break; - case SEED_HOUSE_4: - seedHouse(birdhouseLocation4, states.FINISHING); - break; - case FINISHING: - emptyNests(); - - if (config.goToBank()) { - Rs2Walker.walkTo(BankLocation.FOSSIL_ISLAND_WRECK.getWorldPoint()); - if (!Rs2Bank.isOpen()) Rs2Bank.openBank(); - Rs2Bank.depositAll(); - } + boolean advanced = true; + while (advanced) { + advanced = false; + if (botStatus != lastObservedStatus) { + log.info("State → {} (player at {}, region={}, onFossilIsland={}, inv=[{}])", + botStatus, + Rs2Player.getWorldLocation(), + Rs2Player.getWorldLocation() == null ? "null" : Rs2Player.getWorldLocation().getRegionID(), + isOnFossilIsland(), + dumpInventory()); + lastObservedStatus = botStatus; + stateEnteredAtMs = System.currentTimeMillis(); + } else if (botStatus != states.FINISHED + && System.currentTimeMillis() - stateEnteredAtMs > STATE_STALL_TIMEOUT_MS) { + log.error("Birdhouse run stalled in state {} for >{}ms — player at {}, inv=[{}] — aborting", + botStatus, STATE_STALL_TIMEOUT_MS, + Rs2Player.getWorldLocation(), dumpInventory()); + shutdown(); + return; + } + switch (botStatus) { + case TELEPORTING: + case VERDANT_TELEPORT: + botStatus = states.DISMANTLE_HOUSE_1; + advanced = true; + break; + case DISMANTLE_HOUSE_1: + if (dismantleBirdhouse(birdhouseLocation1, VARP_HOUSE_1)) { + botStatus = states.BUILD_HOUSE_1; + advanced = true; + } + break; + case BUILD_HOUSE_1: + if (buildBirdhouse(birdhouseLocation1, VARP_HOUSE_1)) { + botStatus = states.SEED_HOUSE_1; + advanced = true; + } + break; + case SEED_HOUSE_1: + if (seedHouse(birdhouseLocation1, VARP_HOUSE_1)) { + botStatus = states.DISMANTLE_HOUSE_2; + advanced = true; + } + break; + case DISMANTLE_HOUSE_2: + if (dismantleBirdhouse(birdhouseLocation2, VARP_HOUSE_2)) { + botStatus = states.BUILD_HOUSE_2; + advanced = true; + } + break; + case BUILD_HOUSE_2: + if (buildBirdhouse(birdhouseLocation2, VARP_HOUSE_2)) { + botStatus = states.SEED_HOUSE_2; + advanced = true; + } + break; + case SEED_HOUSE_2: + if (seedHouse(birdhouseLocation2, VARP_HOUSE_2)) { + botStatus = states.MUSHROOM_TELEPORT; + advanced = true; + } + break; + case MUSHROOM_TELEPORT: + botStatus = states.DISMANTLE_HOUSE_3; + advanced = true; + break; + case DISMANTLE_HOUSE_3: + if (dismantleBirdhouse(birdhouseLocation3, VARP_HOUSE_3)) { + botStatus = states.BUILD_HOUSE_3; + advanced = true; + } + break; + case BUILD_HOUSE_3: + if (buildBirdhouse(birdhouseLocation3, VARP_HOUSE_3)) { + botStatus = states.SEED_HOUSE_3; + advanced = true; + } + break; + case SEED_HOUSE_3: + if (seedHouse(birdhouseLocation3, VARP_HOUSE_3)) { + botStatus = states.DISMANTLE_HOUSE_4; + advanced = true; + } + break; + case DISMANTLE_HOUSE_4: + if (dismantleBirdhouse(birdhouseLocation4, VARP_HOUSE_4)) { + botStatus = states.BUILD_HOUSE_4; + advanced = true; + } + break; + case BUILD_HOUSE_4: + if (buildBirdhouse(birdhouseLocation4, VARP_HOUSE_4)) { + botStatus = states.SEED_HOUSE_4; + advanced = true; + } + break; + case SEED_HOUSE_4: + if (seedHouse(birdhouseLocation4, VARP_HOUSE_4)) { + botStatus = states.FINISHING; + advanced = true; + } + break; + case FINISHING: + emptyNests(); - botStatus = states.FINISHED; - notifier.notify(Notification.ON, "Birdhouse run is finished."); - this.shutdown(); - break; - case FINISHED: + if (config.goToBank()) { + Rs2Walker.walkTo(BankLocation.FOSSIL_ISLAND_WRECK.getWorldPoint()); + if (!Rs2Bank.isOpen()) Rs2Bank.openBank(); + Rs2Bank.depositAll(); + } + botStatus = states.FINISHED; + notifier.notify(Notification.ON, "Birdhouse run is finished."); + log.info("Birdhouse run finished — disabling plugin."); + Microbot.stopPlugin(plugin); + break; + case FINISHED: + break; + } } } catch (Exception ex) { log.error("Error in birdhouse run script", ex); } - }, 0, 1000, TimeUnit.MILLISECONDS); + }, 0, 600, TimeUnit.MILLISECONDS); return true; } @@ -193,72 +289,282 @@ public void shutdown() { super.shutdown(); initialized = false; botStatus = states.TELEPORTING; + lastObservedStatus = null; + stateEnteredAtMs = 0L; } - private boolean interactWithObject(int objectId) { - Microbot.getRs2TileObjectCache().query().withId(objectId).interact(); - sleepUntil(Rs2Player::isInteracting); - sleepUntil(() -> !Rs2Player.isInteracting()); + /** Throttle for arrivedAndStill log lines (one per second per target). */ + private long lastArrivedLogMs; + private WorldPoint lastArrivedLogTarget; + + private boolean arrivedAndStill(WorldPoint loc) { + WorldPoint pos = Rs2Player.getWorldLocation(); + int dist = Rs2Player.distanceTo(loc); + if (dist <= ARRIVAL_RADIUS) { + return true; + } + boolean moving = Rs2Player.isMoving(); + long now = System.currentTimeMillis(); + boolean logThisTick = !loc.equals(lastArrivedLogTarget) || now - lastArrivedLogMs >= 1000; + if (moving) { + if (logThisTick) { + log.info("arrivedAndStill[{}]: moving (at {}, dist={})", loc, pos, dist); + lastArrivedLogMs = now; + lastArrivedLogTarget = loc; + } + return false; + } + if (logThisTick) { + log.info("arrivedAndStill[{}]: not arrived (at {}, dist={}); walking via WebWalker (stop at {})", + loc, pos, dist, SCENE_INTERACT_RANGE); + lastArrivedLogMs = now; + lastArrivedLogTarget = loc; + } + Rs2Walker.walkTo(loc, SCENE_INTERACT_RANGE); + return false; + } + + // Canonical state predicates, matching BirdHouseState.fromVarpValue: + // varp == 0 → EMPTY space (need to Build) + // varp > 0, %3 != 0 → BUILT (covers "just built, no seeds" and "seeded, growing") + // varp > 0, %3 == 0 → SEEDED, ready (Empty action available) + private static boolean isEmpty(int varp) { return varp == 0; } + private static boolean isBuilt(int varp) { return varp > 0 && varp % 3 != 0; } + private static boolean isSeeded(int varp) { return varp > 0 && varp % 3 == 0; } + + /** Click Empty on the birdhouse at {@code loc}. Wait for varp to hit 0. */ + private boolean dismantleBirdhouse(WorldPoint loc, int varpId) { + if (!isOnFossilIsland()) { + if (!arrivedAndStill(loc)) return false; + } + int varp = Microbot.getVarbitPlayerValue(varpId); + if (!isSeeded(varp)) { + log.info("Dismantle[{}]: varp={} not seeded (empty={}, built={}) — skipping", + varpId, varp, isEmpty(varp), isBuilt(varp)); + return true; + } + log.info("Dismantle[{}]: varp={} → Empty at {}", varpId, varp, loc); + if (!Rs2GameObject.interact(loc, "Empty")) { + if (!arrivedAndStill(loc)) return false; + log.warn("Dismantle[{}]: object not found at {} after arriving", varpId, loc); + return false; + } + if (!sleepUntil(() -> isEmpty(Microbot.getVarbitPlayerValue(varpId)), 10000)) { + log.warn("Dismantle[{}]: timeout waiting for varp→0 after Empty click; varp={} (player at {})", + varpId, Microbot.getVarbitPlayerValue(varpId), Rs2Player.getWorldLocation()); + return false; + } + log.info("Dismantle[{}]: success (varp=0)", varpId); return true; } - private void seedHouse(WorldPoint worldPoint, states status) { - Rs2Inventory.use(" seed"); - sleepUntil(Rs2Inventory::isItemSelected); - Microbot.getRs2TileObjectCache().query().within(worldPoint, 0).interact(); - sleepUntil(() -> Rs2Widget.findWidget("full of seed") != null, 1000); - botStatus = status; + /** Click Build at {@code loc}. Game auto-combines hammer+log. Wait for varp != 0. */ + private boolean buildBirdhouse(WorldPoint loc, int varpId) { + if (!isOnFossilIsland()) { + if (!arrivedAndStill(loc)) return false; + } + int varp = Microbot.getVarbitPlayerValue(varpId); + if (!isEmpty(varp)) { + log.info("Build[{}]: varp={} not empty (built={}, seeded={}) — skipping", + varpId, varp, isBuilt(varp), isSeeded(varp)); + return true; + } + int logCount = Rs2Inventory.count(config.logType().getItemId()); + if (logCount == 0) { + log.error("Build[{}]: no {} (id={}) in inventory — aborting. Inventory: [{}]", + varpId, config.logType().getItemName(), config.logType().getItemId(), dumpInventory()); + shutdown(); + return false; + } + log.info("Build[{}]: varp=0 → Build at {} (logs in inv: {})", varpId, loc, logCount); + if (!Rs2GameObject.interact(loc, "Build")) { + if (!arrivedAndStill(loc)) return false; + log.warn("Build[{}]: object not found at {} after arriving", varpId, loc); + return false; + } + if (!sleepUntil(() -> !isEmpty(Microbot.getVarbitPlayerValue(varpId)), 15000)) { + log.warn("Build[{}]: timeout waiting for varp!=0 after Build click; varp={} (player at {}, logs={})", + varpId, Microbot.getVarbitPlayerValue(varpId), Rs2Player.getWorldLocation(), + Rs2Inventory.count(config.logType().getItemId())); + return false; + } + log.info("Build[{}]: success (varp={})", varpId, Microbot.getVarbitPlayerValue(varpId)); + return true; } - private void buildBirdhouse(WorldPoint worldPoint, states status) { - if (!Rs2Inventory.hasItem("bird house") && Rs2Inventory.hasItem(ItemID.POH_CLOCKWORK_MECHANISM)) { - Rs2Inventory.use(ItemID.HAMMER); - Rs2Inventory.use(" logs"); - Rs2Inventory.waitForInventoryChanges(5000); + /** Use a seed stack on the birdhouse at {@code loc}. Wait for seeds-down OR varp change. */ + private boolean seedHouse(WorldPoint loc, int varpId) { + if (!isOnFossilIsland()) { + if (!arrivedAndStill(loc)) return false; + } + int varp = Microbot.getVarbitPlayerValue(varpId); + if (isEmpty(varp)) { + log.error("Seed[{}]: varp=0, can't seed empty space — aborting. Inventory: [{}]", + varpId, dumpInventory()); + shutdown(); + return false; + } + if (isSeeded(varp)) { + log.info("Seed[{}]: varp={} already seeded — skipping", varpId, varp); + return true; + } + Rs2ItemModel seed = findInventoryBirdhouseSeed(10).orElse(null); + if (seed == null) { + log.error("Seed[{}]: no birdhouse-seed stack of ≥10 — aborting. Inventory: [{}]", + varpId, dumpInventory()); + shutdown(); + return false; + } + int seedId = seed.getId(); + int seedsBefore = seed.getQuantity(); + int varpBefore = varp; + log.info("Seed[{}]: use {} id={} (x{}) on {} (varp before={})", + varpId, seed.getName(), seedId, seedsBefore, loc, varpBefore); + if (!Rs2Inventory.use(seedId)) { + log.warn("Seed[{}]: Rs2Inventory.use({}) returned false. Inventory: [{}]", + varpId, seedId, dumpInventory()); + return false; + } + if (!sleepUntil(() -> Rs2Inventory.getSelectedItemId() == seedId, 2000)) { + log.warn("Seed[{}]: seed not selected within 2s. getSelectedItemId={}, looking for {}", + varpId, Rs2Inventory.getSelectedItemId(), seedId); + return false; + } + log.info("Seed[{}]: seed selected (id={}); clicking birdhouse at {}", varpId, seedId, loc); + if (!Rs2GameObject.interact(loc)) { + if (!arrivedAndStill(loc)) return false; + log.warn("Seed[{}]: object not found at {} after arriving", varpId, loc); + return false; } - Microbot.getRs2TileObjectCache().query().within(worldPoint, 0).interact("Build"); - sleepUntil(Rs2Player::isAnimating); - botStatus = status; + if (!sleepUntil(() -> + findInventoryBirdhouseSeed(1).map(Rs2ItemModel::getQuantity).orElse(0) < seedsBefore, + 10000)) { + int seedsNow = findInventoryBirdhouseSeed(1).map(Rs2ItemModel::getQuantity).orElse(0); + log.warn("Seed[{}]: no completion signal within 10s. seeds={} (before {}), varp={} (before {})", + varpId, seedsNow, seedsBefore, + Microbot.getVarbitPlayerValue(varpId), varpBefore); + return false; + } + int seedsAfter = findInventoryBirdhouseSeed(1).map(Rs2ItemModel::getQuantity).orElse(0); + log.info("Seed[{}]: success (varp={}, seeds left={})", + varpId, Microbot.getVarbitPlayerValue(varpId), seedsAfter); + return true; + } + + /** True if {@code item}'s lowercased name is in {@link #BIRDHOUSE_SEED_NAMES}. */ + private static boolean isBirdhouseSeed(Rs2ItemModel item) { + if (item == null) return false; + String name = item.getName(); + return name != null && BIRDHOUSE_SEED_NAMES.contains(name.toLowerCase()); + } + + /** First inventory stack of a birdhouse-accepted seed with quantity ≥ {@code minQty}. */ + private static Optional findInventoryBirdhouseSeed(int minQty) { + return Rs2Inventory.items() + .filter(FornBirdhouseRunsScript::isBirdhouseSeed) + .filter(item -> item.getQuantity() >= minQty) + .findFirst(); + } + + /** First bank stack of a birdhouse-accepted seed with quantity ≥ {@code minQty}. */ + private static Optional findBankBirdhouseSeed(int minQty) { + return Rs2Bank.bankItems().stream() + .filter(FornBirdhouseRunsScript::isBirdhouseSeed) + .filter(item -> item.getQuantity() >= minQty) + .findFirst(); + } + + /** Compact "name×qty(id=...)" listing of every inventory item, for diagnostics. */ + private static String dumpInventory() { + return Rs2Inventory.items() + .map(item -> item.getName() + "×" + item.getQuantity() + "(id=" + item.getId() + ")") + .collect(Collectors.joining(", ")); } - private void dismantleBirdhouse(int objectId, states status) { - Microbot.getRs2TileObjectCache().query().interact(objectId, "Empty"); - Rs2Player.waitForXpDrop(Skill.HUNTER); - botStatus = status; + private boolean isOnFossilIsland() { + WorldPoint loc = Rs2Player.getWorldLocation(); + return loc != null && FOSSIL_ISLAND_REGIONS.contains(loc.getRegionID()); + } + + /** True if the inventory already has everything a full run needs. The digsite + * pendant is only required when off Fossil Island (its sole purpose is the + * teleport onto the island); on-island, we can just walk. */ + private boolean hasRequiredInventory() { + if (Rs2Inventory.count(ItemID.CHISEL) < 1) { + log.info("hasRequiredInventory: no chisel"); + return false; + } + if (Rs2Inventory.count(ItemID.HAMMER) < 1) { + log.info("hasRequiredInventory: no hammer"); + return false; + } + if (!isOnFossilIsland()) { + boolean hasPendant = + Rs2Inventory.count(ItemID.NECKLACE_OF_DIGSITE_1) >= 1 + || Rs2Inventory.count(ItemID.NECKLACE_OF_DIGSITE_2) >= 1 + || Rs2Inventory.count(ItemID.NECKLACE_OF_DIGSITE_3) >= 1 + || Rs2Inventory.count(ItemID.NECKLACE_OF_DIGSITE_4) >= 1 + || Rs2Inventory.count(ItemID.NECKLACE_OF_DIGSITE_5) >= 1; + if (!hasPendant) { + log.info("hasRequiredInventory: off-island and no digsite pendant"); + return false; + } + } + int logCount = Rs2Inventory.count(config.logType().getItemId()); + if (logCount < 4) { + log.info("hasRequiredInventory: only {} {} (need 4)", logCount, config.logType().getItemName()); + return false; + } + Optional seed = findInventoryBirdhouseSeed(40); + if (seed.isEmpty()) { + log.info("hasRequiredInventory: no birdhouse-seed stack ≥ 40 in inventory. Inventory: [{}]", + dumpInventory()); + return false; + } + log.info("hasRequiredInventory: OK ({} x{}, {} logs)", + seed.get().getName(), seed.get().getQuantity(), logCount); + return true; } private boolean setupManualInventory() { - // Walk to nearest bank + log.info("setupManualInventory: start (player at {}, onFossilIsland={}, inv=[{}])", + Rs2Player.getWorldLocation(), isOnFossilIsland(), dumpInventory()); Rs2Walker.walkTo(Rs2Bank.getNearestBank().getWorldPoint(), 20); - - // Open bank + if (!Rs2Bank.openBank()) { setupErrorMessage = "Could not open bank"; log.error(setupErrorMessage); return false; } sleepUntil(Rs2Bank::isOpen); - - // Deposit all + log.info("setupManualInventory: bank open at {}", Rs2Player.getWorldLocation()); + Rs2Bank.depositAll(); Rs2Inventory.waitForInventoryChanges(5000); - - // Withdraw chisel + log.info("setupManualInventory: after depositAll, inv=[{}]", dumpInventory()); + if (!Rs2Bank.withdrawX(ItemID.CHISEL, 1)) { setupErrorMessage = "Missing chisel in bank"; log.error(setupErrorMessage); return false; } - - // Withdraw hammer + Rs2Inventory.waitForInventoryChanges(2000); + log.info("setupManualInventory: chisel withdrawn (inv count={})", Rs2Inventory.count(ItemID.CHISEL)); + if (!Rs2Bank.withdrawX(ItemID.HAMMER, 1)) { setupErrorMessage = "Missing hammer in bank"; log.error(setupErrorMessage); return false; } - - // Withdraw digsite pendant (prefer lower charges) - boolean pendantWithdrawn = false; + Rs2Inventory.waitForInventoryChanges(2000); + log.info("setupManualInventory: hammer withdrawn (inv count={})", Rs2Inventory.count(ItemID.HAMMER)); + + // Withdraw digsite pendant (prefer lower charges) — only when off Fossil Island. + // If we're already on the island, the pendant is dead weight; don't burn a charge. + boolean pendantWithdrawn = isOnFossilIsland(); + if (pendantWithdrawn) { + log.info("setupManualInventory: on Fossil Island, skipping pendant withdrawal"); + } List pendantIds = Arrays.asList( ItemID.NECKLACE_OF_DIGSITE_1, ItemID.NECKLACE_OF_DIGSITE_2, @@ -266,27 +572,29 @@ private boolean setupManualInventory() { ItemID.NECKLACE_OF_DIGSITE_4, ItemID.NECKLACE_OF_DIGSITE_5 ); - - for (int pendantId : pendantIds) { - if (!isRunning()) break; - if (Rs2Bank.withdrawX(pendantId, 1)) { - pendantWithdrawn = true; - break; + + if (!pendantWithdrawn) { + for (int pendantId : pendantIds) { + if (!isRunning()) break; + if (Rs2Bank.withdrawX(pendantId, 1)) { + Rs2Inventory.waitForInventoryChanges(2000); + pendantWithdrawn = true; + log.info("setupManualInventory: pendant withdrawn (id={})", pendantId); + break; + } } } - + if (!pendantWithdrawn) { setupErrorMessage = "Missing digsite pendant in bank"; log.error(setupErrorMessage); return false; } - - // Withdraw logs + Log selectedLogType = config.logType(); - // Check if bank has enough logs first - int logCount = Rs2Bank.count(selectedLogType.getItemId()); - if (logCount < 4) { - setupErrorMessage = "Need 4 " + selectedLogType.getItemName().toLowerCase() + " but only have " + logCount + " in bank"; + int bankLogCount = Rs2Bank.count(selectedLogType.getItemId()); + if (bankLogCount < 4) { + setupErrorMessage = "Need 4 " + selectedLogType.getItemName().toLowerCase() + " but only have " + bankLogCount + " in bank"; log.error(setupErrorMessage); return false; } @@ -295,49 +603,54 @@ private boolean setupManualInventory() { log.error(setupErrorMessage); return false; } - - // Withdraw seeds (smart selection) - boolean seedsWithdrawn = withdrawSeeds(); - if (!seedsWithdrawn) { + Rs2Inventory.waitForInventoryChanges(2000); + log.info("setupManualInventory: 4× {} withdrawn (inv count={})", + selectedLogType.getItemName(), Rs2Inventory.count(selectedLogType.getItemId())); + + if (!withdrawSeeds()) { // setupErrorMessage is set in withdrawSeeds return false; } - - // Close bank + Rs2Bank.closeBank(); sleepUntil(() -> !Rs2Bank.isOpen()); - - log.info("Inventory setup complete - starting birdhouse run"); + + log.info("setupManualInventory: complete. Final inv=[{}]", dumpInventory()); return true; } private boolean withdrawSeeds() { - // Priority list of seeds for birdhouses - List seedIds = Arrays.asList( - ItemID.POTATO_SEED, - ItemID.ONION_SEED, - ItemID.CABBAGE_SEED, - ItemID.TOMATO_SEED, - ItemID.BARLEY_SEED, - ItemID.HAMMERSTONE_HOP_SEED, - ItemID.YANILLIAN_HOP_SEED, - ItemID.KRANDORIAN_HOP_SEED - ); - - for (int seedId : seedIds) { - if (!isRunning()) break; - // Check if bank has enough BEFORE trying to withdraw - if (Rs2Bank.count(seedId) >= 40) { - if (Rs2Bank.withdrawX(seedId, 40)) { - log.info("Withdrew 40 of seed ID: {}", seedId); - return true; - } - } + // Log every birdhouse-eligible seed stack the bank has, so we can see + // both what was picked AND what the alternatives were. + String bankSeedSummary = Rs2Bank.bankItems().stream() + .filter(FornBirdhouseRunsScript::isBirdhouseSeed) + .map(item -> item.getName() + "×" + item.getQuantity() + "(id=" + item.getId() + ")") + .collect(Collectors.joining(", ")); + log.info("withdrawSeeds: bank birdhouse-seed stacks: [{}]", bankSeedSummary); + + Rs2ItemModel bankSeed = findBankBirdhouseSeed(40).orElse(null); + if (bankSeed == null) { + setupErrorMessage = "Need 40 seeds but no birdhouse seed type has 40+ in bank"; + log.error(setupErrorMessage); + return false; } - - // If we get here, no seed type had 40+ available - setupErrorMessage = "Need 40 seeds but no birdhouse seed type has 40+ in bank"; - log.error(setupErrorMessage); - return false; + int invBefore = Rs2Inventory.count(bankSeed.getId()); + log.info("withdrawSeeds: selected {} (id={}, bank qty={}); inv before withdraw: {} of id={}", + bankSeed.getName(), bankSeed.getId(), bankSeed.getQuantity(), invBefore, bankSeed.getId()); + if (!Rs2Bank.withdrawX(bankSeed.getId(), 40)) { + setupErrorMessage = "Failed to withdraw 40 " + bankSeed.getName(); + log.error(setupErrorMessage); + return false; + } + Rs2Inventory.waitForInventoryChanges(3000); + int invAfter = Rs2Inventory.count(bankSeed.getId()); + int invAfterByName = findInventoryBirdhouseSeed(1).map(Rs2ItemModel::getQuantity).orElse(0); + log.info("withdrawSeeds: withdrew 40 {} (id={}); inv after = {} of id={} (by-name lookup = {})", + bankSeed.getName(), bankSeed.getId(), invAfter, bankSeed.getId(), invAfterByName); + if (invAfter < 40) { + log.warn("withdrawSeeds: inventory count of id={} after withdraw is {} (<40). Full inv: [{}]", + bankSeed.getId(), invAfter, dumpInventory()); + } + return true; } } From af598216ae4400bb939f0fec728efc963ea8c12d Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Mon, 18 May 2026 14:11:13 -0400 Subject: [PATCH 79/95] fix(MKE_Wintertodt): skip brazier maintenance while chopping roots (#437) Roots are several tiles from the brazier, so abandoning a chop to walk over loses the repair/relight window to another player anyway. Bail out of handleBrazierMaintenance when state == CHOP_ROOTS so the chop cycle isn't interrupted for nothing. Co-authored-by: runsonmypc --- .../microbot/mke_wintertodt/MKE_WintertodtPlugin.java | 2 +- .../microbot/mke_wintertodt/MKE_WintertodtScript.java | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtPlugin.java index 9ea9d770e2..cbf922ff65 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtPlugin.java @@ -42,7 +42,7 @@ ) @Slf4j public class MKE_WintertodtPlugin extends Plugin { - static final String version = "2.2.0"; + static final String version = "2.2.1"; // Core plugin components @Inject diff --git a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java index 4693ae819f..3be48ad8de 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/mke_wintertodt/MKE_WintertodtScript.java @@ -3145,6 +3145,12 @@ private boolean handleBrazierMaintenance(GameState gameState) { // begins, so a single repair click can trigger. if (resetActions) return false; + // Skip while chopping — the roots are several tiles from the brazier, + // so by the time we abandon the chop and walk over, another player + // has already repaired/relit. The interruption costs us a chop cycle + // for nothing. + if (state == State.CHOP_ROOTS) return false; + if (gameState.brokenBrazier != null && config.fixBrazier()) { if (fletchingState.isActive()) { fletchingState.stopFletching(FletchingInterruptType.BRAZIER_BROKEN); From c26320a6800115a1779de10ada9d5e7569690c25 Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Mon, 18 May 2026 14:11:22 -0400 Subject: [PATCH 80/95] fix(leftclickcast): dropdown sync + preserve Attack on right-click (#436) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(leftclickcast): sync top spell dropdown with active slot The top "Spell" dropdown was dead legacy config — editing it did nothing, and slot hotkey presses didn't update it. Wire two-way sync: dropdown edits write into the active slot's spell config, and slot hotkeys / direct slot-spell edits write back into the top dropdown. Equality guards break the ConfigChanged echo loop; ExternalPluginsChanged is posted to rebuild the panel so the change is visible immediately. * fix(leftclickcast): keep original Attack entry; add Cast as new tail Previously the plugin mutated the game's Attack menu entry in place, changing its label and action to "Cast X". That replaced Attack entirely — right-clicking the NPC no longer offered a plain Attack option. Now we leave the Attack entry untouched and append a new RUNELITE-type "Cast X" entry at the tail via createMenuEntry(-1). The tail is the left-click slot in RuneLite's menu model, so Cast becomes left-click while Attack remains in the right-click menu. --------- Co-authored-by: runsonmypc --- .../leftclickcast/LeftClickCastConfig.java | 3 +- .../leftclickcast/LeftClickCastPlugin.java | 125 +++++++++++++++--- 2 files changed, 107 insertions(+), 21 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/LeftClickCastConfig.java b/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/LeftClickCastConfig.java index f387be7e69..b88d018a0f 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/LeftClickCastConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/LeftClickCastConfig.java @@ -20,11 +20,10 @@ default boolean enabled() return true; } - // Retained so existing stored config is not invalidated. Read once at startUp for migration into slot1Spell. @ConfigItem( keyName = "spell", name = "Spell", - description = "Legacy single-spell setting — migrated into Slot 1 on startup.", + description = "The currently active spell. Synced with the active slot — pressing a slot hotkey updates this, and editing this updates the active slot's spell.", position = 1 ) default PertTargetSpell spell() diff --git a/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/LeftClickCastPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/LeftClickCastPlugin.java index 0a0ed5f989..a5df550241 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/LeftClickCastPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/leftclickcast/LeftClickCastPlugin.java @@ -49,7 +49,7 @@ ) public class LeftClickCastPlugin extends Plugin { - static final String version = "1.3.0"; + static final String version = "1.3.2"; private static final int SLOT_COUNT = 5; @@ -114,6 +114,7 @@ public void hotkeyPressed() }; keyManager.registerKeyListener(enabledToggleListener); migrateLegacySpellKey(); + syncTopSpellToActiveSlot(); } @Override @@ -191,17 +192,20 @@ public void onPostMenuSort(PostMenuSort event) MenuEntry attack = entries[attackIdx]; final Actor dispatchTarget = targetActor; final PertTargetSpell dispatchSpell = spell; - attack.setOption("Cast " + dispatchSpell.getDisplayName()); - attack.setType(MenuAction.RUNELITE); - attack.onClick(e -> castOnTargetFast(dispatchSpell, dispatchTarget)); - // Move to the tail of the array — that slot is the left-click action in RuneLite's menu model. - if (attackIdx != entries.length - 1) - { - entries[attackIdx] = entries[entries.length - 1]; - entries[entries.length - 1] = attack; - menu.setMenuEntries(entries); - } + // Append a new RUNELITE-type "Cast X" entry at the tail. The tail is the left-click action in + // RuneLite's menu model, so Cast becomes left-click while the original Attack entry stays in the + // list — preserving right-click "Attack" access. Identifier/param0/param1/worldViewId are copied + // from the original so target highlighting behaves the same as a real Attack hover. + MenuEntry cast = menu.createMenuEntry(-1) + .setOption("Cast " + dispatchSpell.getDisplayName()) + .setTarget(attack.getTarget()) + .setType(MenuAction.RUNELITE) + .setIdentifier(attack.getIdentifier()) + .setParam0(attack.getParam0()) + .setParam1(attack.getParam1()) + .onClick(e -> castOnTargetFast(dispatchSpell, dispatchTarget)); + cast.setWorldViewId(attack.getWorldViewId()); } private Keybind slotHotkeyFor(int index) @@ -242,12 +246,57 @@ private PertTargetSpell slotSpellFor(int index) } } + private static String slotSpellKeyFor(int index) + { + switch (index) + { + case 0: + return "slot1Spell"; + case 1: + return "slot2Spell"; + case 2: + return "slot3Spell"; + case 3: + return "slot4Spell"; + case 4: + return "slot5Spell"; + default: + return "slot1Spell"; + } + } + + private static int slotIndexForKey(String key) + { + switch (key) + { + case "slot1Spell": + return 0; + case "slot2Spell": + return 1; + case "slot3Spell": + return 2; + case "slot4Spell": + return 3; + case "slot5Spell": + return 4; + default: + return -1; + } + } + private void onSlotHotkey(int index) { activeSlot = index; + PertTargetSpell spell = slotSpellFor(index); + if (spell != null && config.spell() != spell) + { + configManager.setConfiguration("leftclickcast", "spell", spell); + // MicrobotConfigPanel doesn't refresh individual widgets on ConfigChanged — force a rebuild + // so the top dropdown visibly matches the newly active slot. + eventBus.post(new ExternalPluginsChanged()); + } if (config.chatFeedback()) { - PertTargetSpell spell = slotSpellFor(index); String display = spell != null ? spell.getDisplayName() : "(no spell)"; chatMessageManager.queue(QueuedMessage.builder() .type(ChatMessageType.GAMEMESSAGE) @@ -270,19 +319,57 @@ private void onEnabledToggleHotkey() @Subscribe public void onConfigChanged(ConfigChanged event) { - if (!"leftclickcast".equals(event.getGroup()) || !"enabled".equals(event.getKey())) + if (!"leftclickcast".equals(event.getGroup())) { return; } - if (!config.chatFeedback()) + String key = event.getKey(); + if ("enabled".equals(key)) { + if (!config.chatFeedback()) + { + return; + } + boolean enabled = "true".equals(event.getNewValue()); + chatMessageManager.queue(QueuedMessage.builder() + .type(ChatMessageType.GAMEMESSAGE) + .value("Left-Click Cast: " + (enabled ? "enabled" : "disabled")) + .build()); return; } - boolean enabled = "true".equals(event.getNewValue()); - chatMessageManager.queue(QueuedMessage.builder() - .type(ChatMessageType.GAMEMESSAGE) - .value("Left-Click Cast: " + (enabled ? "enabled" : "disabled")) - .build()); + if ("spell".equals(key)) + { + // User edited the top dropdown — mirror the value into the currently active slot's config. + // The equality guard stops the ConfigChanged→write→ConfigChanged loop. + PertTargetSpell newSpell = config.spell(); + PertTargetSpell activeSpell = slotSpellFor(activeSlot); + if (newSpell != null && newSpell != activeSpell) + { + configManager.setConfiguration("leftclickcast", slotSpellKeyFor(activeSlot), newSpell); + eventBus.post(new ExternalPluginsChanged()); + } + return; + } + int slot = slotIndexForKey(key); + if (slot == activeSlot && slot >= 0) + { + // User edited the spell for the currently active slot — mirror into the top dropdown. + PertTargetSpell newSpell = slotSpellFor(slot); + if (newSpell != null && newSpell != config.spell()) + { + configManager.setConfiguration("leftclickcast", "spell", newSpell); + eventBus.post(new ExternalPluginsChanged()); + } + } + } + + private void syncTopSpellToActiveSlot() + { + PertTargetSpell active = slotSpellFor(activeSlot); + if (active != null && config.spell() != active) + { + configManager.setConfiguration("leftclickcast", "spell", active); + } } // Fast-path cast: fire two synchronous client.menuAction packets back-to-back so the server processes From e48d683a2c46d8ffd8a91004c5d618b8e34af297 Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Mon, 18 May 2026 14:11:34 -0400 Subject: [PATCH 81/95] fix(BlastoiseFurnace): inline ITEM_NAME_SUFFIX_PATTERN to unblock dev build (#438) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Client 2.6.0 dropped `Rs2UiHelper.ITEM_NAME_SUFFIX_PATTERN`. The static import failed early in `:compileJava`, which cascaded through Lombok's javac patch and silently disabled annotation processing for the rest of the source set — surfacing as 100 unrelated "cannot find symbol" errors across motherloadmine, jewelleryenchant, kittentracker, sisyphusinfernalpact, thievingstalls, driftnet, and herbiboar (missing `@Slf4j`-generated `log`, missing `@Getter` accessors, unrecognised `onConstructor_`/`onMethod_` Lombok meta-attributes). Inlining the regex locally (same pattern BankTabSorterScript already uses) removes the only failing import and restores the entire build. Bump 1.2.1 -> 1.2.2. Co-authored-by: runsonmypc --- .../microbot/blastoisefurnace/BlastoiseFurnaceScript.java | 1 + 1 file changed, 1 insertion(+) 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 7f5e2e6719..6a981630eb 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 @@ -37,6 +37,7 @@ @Slf4j public class BlastoiseFurnaceScript extends Script { + private static final Pattern ITEM_NAME_SUFFIX_PATTERN = Pattern.compile("^(.*?)(?:\\s*\\((\\d+)\\))?$"); static final int coalBag = 12019; private static final Pattern ITEM_NAME_SUFFIX_PATTERN = Pattern.compile("^(.*?)(?:\\s*\\((\\d+)\\))?$"); private static final int MAX_ORE_PER_INTERACTION = 27; From 666ae390af528e7c66ad58e974e6c10d2a64b7b7 Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Mon, 18 May 2026 14:11:46 -0400 Subject: [PATCH 82/95] feat(BanksShopper): direct-click bank/NPC, walker only when needed (#435) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When the bank object or shop NPC is loaded in the current scene, click it directly instead of invoking Rs2Walker — the game server handles pathing and opens the UI. The walker only fires on the return trip when the NPC isn't visible from the bank's deposit position. - tryDirectBankDeposit scans the scene for any Rs2BankID-matched object (booths, chests, deposit boxes) and clicks the closest one. Falls back to Rs2Bank.bankItemsAndWalkBackToOriginalPosition only when nothing matches. - ensureShopOpen sends exactly one Rs2Npc.interact(npc, "Trade") per scheduler tick instead of polling Rs2Shop.openShop, which re-clicked every iteration while the player was still walking to the NPC. - The async return-trip walker awaits its CompletableFuture after clearWalkingRoute, so the ShortestPath overlay clears and a late currentTarget write can't resurrect the route. - shopNpcLocation pins the walker target to the NPC's actual tile (captured on the first successful trade), not whatever tile the user toggled the plugin on. Co-authored-by: runsonmypc --- .../banksshopper/BanksShopperPlugin.java | 2 +- .../banksshopper/BanksShopperScript.java | 136 +++++++++++++++++- 2 files changed, 136 insertions(+), 2 deletions(-) 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..c4c679e13b 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; 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..2670596c54 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,15 +1,27 @@ package net.runelite.client.plugins.microbot.banksshopper; +import java.util.Collections; +import java.util.Comparator; +import java.util.concurrent.CompletableFuture; + +import net.runelite.api.TileObject; +import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.util.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.depositbox.Rs2DepositBox; +import net.runelite.client.plugins.microbot.util.gameobject.Rs2BankID; +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.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; 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 java.util.concurrent.TimeUnit; @@ -23,6 +35,7 @@ public class BanksShopperScript extends Script { private final BanksShopperPlugin plugin; private ShopperState state = ShopperState.SHOPPING; + private WorldPoint shopNpcLocation; public BanksShopperScript(final BanksShopperPlugin plugin) { this.plugin = plugin; @@ -32,6 +45,7 @@ public boolean run(BanksShopperConfig config) { Microbot.pauseAllScripts.compareAndSet(true, false); Microbot.enableAutoRunOn = false; initialPlayerLocation = null; + shopNpcLocation = null; Rs2Antiban.resetAntibanSettings(); Rs2AntibanSettings.naturalMouse = true; @@ -61,11 +75,17 @@ public boolean run(BanksShopperConfig config) { return; } - sleepUntil(() -> Rs2Shop.openShop(plugin.getNpcName(), plugin.isUseExactNaming()), 5000); + if (!Rs2Shop.isOpen() && !ensureShopOpen()) return; boolean successfullAction = false; boolean outOfStock = false; if (Rs2Shop.isOpen()) { + // First successful interaction pins the return-trip target to the + // NPC's actual tile, not whatever tile the user toggled the plugin on. + if (shopNpcLocation == null) { + Rs2NpcModel npc = Rs2Shop.getNearestShopNpc(plugin.getNpcName(), plugin.isUseExactNaming()); + if (npc != null) shopNpcLocation = npc.getWorldLocation(); + } for (String itemName : plugin.getItemNames()) { if (!isRunning() || Microbot.pauseAllScripts.get()) break; if (itemName.length() <= 1) continue; @@ -128,6 +148,11 @@ public boolean run(BanksShopperConfig config) { } break; case BANKING: + if (tryDirectBankDeposit()) { + state = ShopperState.SHOPPING; + return; + } + // No reachable bank in the loaded scene — fall back to the walker. if (!Rs2Bank.bankItemsAndWalkBackToOriginalPosition(plugin.getItemNames(), initialPlayerLocation)) return; state = ShopperState.SHOPPING; @@ -156,11 +181,120 @@ public void shutdown() { state = ShopperState.SHOPPING; // Reset state to SHOPPING for next run initialPlayerLocation = null; // Reset initial player location + shopNpcLocation = null; Rs2Antiban.resetAntibanSettings(); super.shutdown(); } + /** + * Fast-path bank deposit: if a bank booth or banker is loaded in the current scene + * and the pathfinder can reach its tile, click it directly so the game server handles + * pathing — no Rs2Walker invocation. After depositing, only invoke the walker for the + * return trip if the shop NPC isn't itself loaded + reachable from the new position. + * + * @return true when the bank was opened + deposited via direct interaction; false to + * signal the caller should fall back to the walker-based round trip. + */ + private boolean tryDirectBankDeposit() { + // Closest in-scene Rs2BankID-matched object — booths use "Bank", chests use "Use", + // deposit boxes use "Deposit". Same gate the agent server uses ("reachable" = same + // WorldView as player): if Rs2GameObject.getAll returns it, it's in scene. Clicking + // the default left-click action lets the server walk the player to an adjacent tile + // and open the appropriate UI (bank widget or deposit-box widget). + boolean alreadyOpen = Rs2Bank.isOpen() || Rs2DepositBox.isOpen(); + if (!alreadyOpen) { + TileObject bankObject = Rs2GameObject.getAll(o -> Rs2BankID.BANK_ID_SET.contains(o.getId())).stream() + .min(Comparator.comparingInt(o -> o.getWorldLocation().distanceTo(Rs2Player.getWorldLocation()))) + .orElse(null); + + if (bankObject != null) { + Microbot.log("[BanksShopper] direct-click bank id=" + bankObject.getId() + + " at " + bankObject.getWorldLocation() + + " from " + Rs2Player.getWorldLocation()); + if (!Rs2GameObject.interact(bankObject)) return false; + if (!sleepUntil(() -> Rs2Bank.isOpen() || Rs2DepositBox.isOpen(), 15_000)) { + Microbot.log("[BanksShopper] click sent but no bank/deposit UI opened"); + return false; + } + } else { + Rs2NpcModel banker = Rs2Npc.getBankerNPC(); + if (banker == null || !Rs2Bank.openBank(banker)) return false; + } + } + + if (Rs2Bank.isOpen()) { + for (String itemName : plugin.getItemNames()) { + if (itemName.matches("\\d+")) { + Rs2Bank.depositAll(Integer.parseInt(itemName)); + } else { + Rs2Bank.depositAll(itemName, false); + } + } + Rs2Bank.closeBank(); + } else if (Rs2DepositBox.isOpen()) { + for (String itemName : plugin.getItemNames()) { + if (itemName.matches("\\d+")) { + Rs2DepositBox.depositAll(Integer.parseInt(itemName)); + } else { + Rs2DepositBox.depositAll(Collections.singletonList(itemName)); + } + } + Rs2DepositBox.closeDepositBox(); + } else { + return false; + } + + // Return-trip walker is owned by SHOPPING (see ensureShopOpen): drive only when the + // NPC is missing from the loaded scene, then cancel + await so the visual route + // disappears immediately and the next SHOPPING tick can issue a clean Trade click. + return true; + } + + /** + * One-shot "open the shop" gate for the SHOPPING state. Handles the two failure modes + * separately so we never re-click while waiting: + *
      + *
    • NPC not loaded in the scene → start a background {@code Rs2Walker.walkTo} + * toward {@link #shopNpcLocation} (captured on the first trade), poll for the + * NPC to enter the scene, then {@code clearWalkingRoute} + await the async + * thread so the ShortestPath overlay clears and no late {@code currentTarget} + * write resurrects the route.
    • + *
    • NPC in scene but shop UI not yet open → send exactly one + * {@code Rs2Npc.interact(npc, "Trade")} and wait. The previous polling pattern + * ({@code sleepUntil(() -> Rs2Shop.openShop(...), 5000)}) re-invoked + * {@code Rs2Npc.interact} every scheduler tick, queuing redundant Trade clicks + * while the player was still walking to the NPC.
    • + *
    + * @return true when the shop UI is open and the caller can proceed with trades; + * false to defer to the next scheduler tick. + */ + private boolean ensureShopOpen() { + Rs2NpcModel npc = Rs2Shop.getNearestShopNpc(plugin.getNpcName(), plugin.isUseExactNaming()); + if (npc == null) { + WorldPoint walkTarget = shopNpcLocation != null ? shopNpcLocation : initialPlayerLocation; + if (walkTarget == null) return false; + + CompletableFuture walkFuture = CompletableFuture.runAsync( + () -> Rs2Walker.walkTo(walkTarget, 4)); + sleepUntil(() -> Rs2Shop.getNearestShopNpc(plugin.getNpcName(), plugin.isUseExactNaming()) != null + || Rs2Player.getWorldLocation().distanceTo(walkTarget) <= 4, + 30_000); + Rs2Walker.clearWalkingRoute("banksshopper:shop-npc-in-range"); + try { + walkFuture.get(2, TimeUnit.SECONDS); + } catch (Exception ignored) { + // walker thread already exited (most common) or interrupted — either way + // we've cleared the route; let the next tick retry. + } + return false; + } + + Rs2Npc.interact(npc, "Trade"); + sleepUntil(Rs2Shop::isOpen, 5000); + return Rs2Shop.isOpen(); + } + /** * Hops to a new world */ From 2ff8ad72b6548bd19f03efdd67b916e483650ba8 Mon Sep 17 00:00:00 2001 From: Sami Date: Mon, 18 May 2026 20:14:39 +0200 Subject: [PATCH 83/95] fix(BlastoiseFurnace): remove duplicate ITEM_NAME_SUFFIX_PATTERN declaration --- .../microbot/blastoisefurnace/BlastoiseFurnaceScript.java | 1 - 1 file changed, 1 deletion(-) 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 6a981630eb..e61fe7f9db 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 @@ -39,7 +39,6 @@ public class BlastoiseFurnaceScript extends Script { private static final Pattern ITEM_NAME_SUFFIX_PATTERN = Pattern.compile("^(.*?)(?:\\s*\\((\\d+)\\))?$"); static final int coalBag = 12019; - private static final Pattern ITEM_NAME_SUFFIX_PATTERN = Pattern.compile("^(.*?)(?:\\s*\\((\\d+)\\))?$"); private static final int MAX_ORE_PER_INTERACTION = 27; private static final int MAX_ORE_PER_HYBRID_INTERACTION = 26; public static State state = State.BANKING; From 936ac1a27ec3c18207199a34d3d9c009085be600 Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Tue, 19 May 2026 22:55:56 -0400 Subject: [PATCH 84/95] feat(FarmTreeRun): compost type selection, leprechaun support, and bug fixes (#442) * docs: add design spec for compost type selection in farm tree runner * docs: add implementation plan for compost type selection * feat(FarmTreeRun): add CompostType enum for compost selection * feat(FarmTreeRun): replace useCompost boolean with compostType dropdown * feat(FarmTreeRun): support all compost types with qty calc and bucket drop * fix(FarmTreeRun): fix protection dialog, skills necklace crash, and cleanup - Handle first-time protection dialog by clicking "don't ask again" - Make skills necklace optional (no shutdown if missing) - Consolidate drops into dropCrap() (pots, buckets, weeds) - Allow dropping while moving, only guard on isAnimating - Remove isInteracting checks throughout - Revert config group rename back to "example" * feat(FarmTreeRun): add tool leprechaun compost withdrawal and bank-on-finish - Non-reusable compost (regular/super/ultra) is now withdrawn from the Tool Leprechaun at each patch instead of from the bank, saving inventory slots. Bottomless bucket still comes from bank as before. - Plugin stops with an error if the leprechaun has no compost stored. - On run completion, plugin walks to the nearest bank, deposits all items, then shuts down cleanly. --------- Co-authored-by: runsonmypc --- .../2026-05-17-tree-runner-compost-types.md | 319 ++++++++++++++++++ ...-05-17-tree-runner-compost-types-design.md | 72 ++++ .../farmtreerun/FarmTreeRunConfig.java | 11 +- .../farmtreerun/FarmTreeRunScript.java | 155 ++++++--- .../farmtreerun/enums/CompostType.java | 24 ++ 5 files changed, 532 insertions(+), 49 deletions(-) create mode 100644 docs/superpowers/plans/2026-05-17-tree-runner-compost-types.md create mode 100644 docs/superpowers/specs/2026-05-17-tree-runner-compost-types-design.md create mode 100644 src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/CompostType.java diff --git a/docs/superpowers/plans/2026-05-17-tree-runner-compost-types.md b/docs/superpowers/plans/2026-05-17-tree-runner-compost-types.md new file mode 100644 index 0000000000..bbff2239cb --- /dev/null +++ b/docs/superpowers/plans/2026-05-17-tree-runner-compost-types.md @@ -0,0 +1,319 @@ +# Farm Tree Runner: Compost Type Selection — Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Replace the boolean compost toggle with a dropdown supporting Compost, Supercompost, Ultracompost, and Bottomless Compost Bucket, with automatic quantity calculation and empty-bucket cleanup. + +**Architecture:** New `CompostType` enum holds item IDs and a reusable flag. Config swaps the boolean for a dropdown. Script banking calculates withdrawal quantity based on unprotected patch count. `handlePlantingTree()` drops empty buckets after consumable compost use. + +**Tech Stack:** Java 11, RuneLite plugin API, Lombok + +--- + +### Task 1: Create `CompostType` enum + +**Files:** +- Create: `src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/CompostType.java` + +- [ ] **Step 1: Create the enum file** + +```java +package net.runelite.client.plugins.microbot.farmtreerun.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.runelite.api.gameval.ItemID; + +@Getter +@RequiredArgsConstructor +public enum CompostType { + NONE("None", -1, false), + COMPOST("Compost", ItemID.COMPOST, false), + SUPERCOMPOST("Supercompost", ItemID.SUPERCOMPOST, false), + ULTRACOMPOST("Ultracompost", ItemID.ULTRACOMPOST, false), + BOTTOMLESS_BUCKET("Bottomless bucket", ItemID.BOTTOMLESS_COMPOST_BUCKET_22997, true); + + private final String name; + private final int itemId; + private final boolean reusable; + + @Override + public String toString() { + return name; + } +} +``` + +- [ ] **Step 2: Build to verify compilation** + +Run: `cd /home/alex/Developer/MB/Microbot-Hub/.worktrees/tree-runner-compost && ./gradlew build -PpluginList=FarmTreeRunPlugin` +Expected: BUILD SUCCESSFUL + +- [ ] **Step 3: Commit** + +```bash +git add src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/CompostType.java +git commit -m "feat(FarmTreeRun): add CompostType enum for compost selection" +``` + +--- + +### Task 2: Update config to use `CompostType` dropdown + +**Files:** +- Modify: `src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunConfig.java` + +- [ ] **Step 1: Add import for `CompostType`** + +Add to the import block: + +```java +import net.runelite.client.plugins.microbot.farmtreerun.enums.CompostType; +``` + +- [ ] **Step 2: Replace the `useCompost()` boolean config with `compostType()` dropdown** + +Replace this block (lines 163-171): + +```java + @ConfigItem( + keyName = "useCompost", + name = "Use compost", + description = "Only bottomless compost bucket is supported", + position = 1, + section = gearSection + ) + default boolean useCompost() { return true; } +``` + +With: + +```java + @ConfigItem( + keyName = "compostType", + name = "Compost type", + description = "Select compost type. Only applied at patches without protection enabled.", + position = 1, + section = gearSection + ) + default CompostType compostType() { return CompostType.NONE; } +``` + +- [ ] **Step 3: Update `@ConfigInformation` HTML** + +In the `@ConfigInformation` annotation, replace: + +``` +
  1. Filled Bottomless compost bucket
  2. +``` + +With: + +``` +
  3. Compost / Supercompost / Ultracompost / Bottomless compost bucket
  4. +``` + +- [ ] **Step 4: Build to verify compilation** + +Run: `cd /home/alex/Developer/MB/Microbot-Hub/.worktrees/tree-runner-compost && ./gradlew build -PpluginList=FarmTreeRunPlugin` +Expected: BUILD FAILURE — `FarmTreeRunScript.java` still references `config.useCompost()`. This confirms the config change is wired in and the script needs updating next. + +- [ ] **Step 5: Commit** + +```bash +git add src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunConfig.java +git commit -m "feat(FarmTreeRun): replace useCompost boolean with compostType dropdown" +``` + +--- + +### Task 3: Update script to use `CompostType` + +**Files:** +- Modify: `src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java` + +- [ ] **Step 1: Add import for `CompostType`** + +Add to the import block: + +```java +import net.runelite.client.plugins.microbot.farmtreerun.enums.CompostType; +``` + +- [ ] **Step 2: Update `isCompostEnabled()` method (line ~998)** + +The method currently reads: + +```java +private boolean isCompostEnabled(FarmTreeRunConfig config) { + if (!config.useCompost()) + return false; + + if (!getSelectedTreePatches(config).isEmpty() && !config.protectTrees()) + return true; + + if (!getSelectedHardTreePatches(config).isEmpty() && !config.protectHardTrees()) + return true; + + return !getSelectedFruitTreePatches(config).isEmpty() && !config.protectFruitTrees(); +} +``` + +Replace `config.useCompost()` with `config.compostType() == CompostType.NONE`: + +```java +private boolean isCompostEnabled(FarmTreeRunConfig config) { + if (config.compostType() == CompostType.NONE) + return false; + + if (!getSelectedTreePatches(config).isEmpty() && !config.protectTrees()) + return true; + + if (!getSelectedHardTreePatches(config).isEmpty() && !config.protectHardTrees()) + return true; + + return !getSelectedFruitTreePatches(config).isEmpty() && !config.protectFruitTrees(); +} +``` + +- [ ] **Step 3: Update the non-banking path (line ~134-135)** + +Replace: + +```java +if (isCompostEnabled(config)) { + compostItemId = ItemID.BOTTOMLESS_COMPOST_BUCKET_22997; +} +``` + +With: + +```java +if (isCompostEnabled(config)) { + compostItemId = config.compostType().getItemId(); +} +``` + +- [ ] **Step 4: Update banking compost withdrawal block (lines ~460-467)** + +Replace: + +```java +if (isCompostEnabled(config)) { + if (Rs2Bank.hasItem(ItemID.BOTTOMLESS_COMPOST_BUCKET_22997)) { + compostItemId = ItemID.BOTTOMLESS_COMPOST_BUCKET_22997; + items.add(new FarmingItem(compostItemId, 1)); + } else { + Microbot.log("Only bottomless compost is supported. Skipping composting."); + } +} +``` + +With: + +```java +if (isCompostEnabled(config)) { + CompostType compostType = config.compostType(); + compostItemId = compostType.getItemId(); + if (compostType.isReusable()) { + if (Rs2Bank.hasItem(compostItemId)) { + items.add(new FarmingItem(compostItemId, 1)); + } else { + Microbot.log("Bottomless compost bucket not found in bank. Skipping composting."); + compostItemId = null; + } + } else { + int unprotectedCount = 0; + if (!config.protectTrees()) + unprotectedCount += getSelectedTreePatches(config).size(); + if (!config.protectFruitTrees()) + unprotectedCount += getSelectedFruitTreePatches(config).size(); + if (!config.protectHardTrees()) + unprotectedCount += getSelectedHardTreePatches(config).size(); + if (unprotectedCount > 0) { + if (Rs2Bank.hasItem(compostItemId)) { + items.add(new FarmingItem(compostItemId, unprotectedCount)); + } else { + Microbot.log("Selected compost not found in bank. Skipping composting."); + compostItemId = null; + } + } else { + compostItemId = null; + } + } +} +``` + +- [ ] **Step 5: Update `useCompostOnPatch()` method (lines ~927-938)** + +Replace: + +```java +private boolean useCompostOnPatch(FarmTreeRunConfig config, Patch patch) { + if (!config.useCompost() || compostItemId == null) + return false; + + if (!config.protectTrees() && patch.kind == TreeKind.TREE) + return true; + + if (!config.protectHardTrees() && patch.kind == TreeKind.HARD_TREE) + return true; + + return !config.protectFruitTrees() && patch.kind == TreeKind.FRUIT_TREE; +} +``` + +With: + +```java +private boolean useCompostOnPatch(FarmTreeRunConfig config, Patch patch) { + if (config.compostType() == CompostType.NONE || compostItemId == null) + return false; + + if (!config.protectTrees() && patch.kind == TreeKind.TREE) + return true; + + if (!config.protectHardTrees() && patch.kind == TreeKind.HARD_TREE) + return true; + + return !config.protectFruitTrees() && patch.kind == TreeKind.FRUIT_TREE; +} +``` + +- [ ] **Step 6: Add empty bucket drop in `handlePlantingTree()` (after line ~810)** + +After the compost application block, add a bucket drop for consumable compost. The current code: + +```java +if (useCompostOnPatch(config, patch)) { + Rs2Inventory.useItemOnObject(compostItemId, treePatch.getId()); + Rs2Player.waitForXpDrop(Skill.FARMING, 2000); + sleep(550, 2200); +} +``` + +Replace with: + +```java +if (useCompostOnPatch(config, patch)) { + Rs2Inventory.useItemOnObject(compostItemId, treePatch.getId()); + Rs2Player.waitForXpDrop(Skill.FARMING, 2000); + sleep(550, 2200); + if (!config.compostType().isReusable() && Rs2Inventory.hasItem(ItemID.BUCKET)) { + Rs2Inventory.drop(ItemID.BUCKET); + sleep(300, 600); + } +} +``` + +- [ ] **Step 7: Build to verify compilation** + +Run: `cd /home/alex/Developer/MB/Microbot-Hub/.worktrees/tree-runner-compost && ./gradlew build -PpluginList=FarmTreeRunPlugin` +Expected: BUILD SUCCESSFUL + +- [ ] **Step 8: Commit** + +```bash +git add src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java +git commit -m "feat(FarmTreeRun): support all compost types with qty calc and bucket drop" +``` diff --git a/docs/superpowers/specs/2026-05-17-tree-runner-compost-types-design.md b/docs/superpowers/specs/2026-05-17-tree-runner-compost-types-design.md new file mode 100644 index 0000000000..0828f5f9f2 --- /dev/null +++ b/docs/superpowers/specs/2026-05-17-tree-runner-compost-types-design.md @@ -0,0 +1,72 @@ +# Farm Tree Runner: Compost Type Selection + +## Summary + +Replace the boolean `useCompost` config (which only supported bottomless compost bucket) with a dropdown enum that supports all four compost types: Compost, Supercompost, Ultracompost, and Bottomless Compost Bucket. Compost is only applied at patches where protection is not enabled. + +## New Enum: `CompostType` + +File: `src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/CompostType.java` + +| Value | ItemID constant | ID | Reusable | Notes | +|-------|----------------|----|----------|-------| +| `NONE` | — | -1 | — | No compost | +| `COMPOST` | `COMPOST` | 6032 | No | Regular compost | +| `SUPERCOMPOST` | `SUPERCOMPOST` | 6034 | No | Supercompost | +| `ULTRACOMPOST` | `ULTRACOMPOST` | 21483 | No | Ultracompost | +| `BOTTOMLESS_BUCKET` | `BOTTOMLESS_COMPOST_BUCKET_22997` | 22997 | Yes | Reusable, withdraw 1 | + +Fields: `String name`, `int itemId`, `boolean reusable`. + +## Config Change + +File: `FarmTreeRunConfig.java` + +- Remove `useCompost()` boolean (keyName `"useCompost"`) +- Add `compostType()` returning `CompostType`, default `NONE` + - keyName: `"compostType"` (new key; old `useCompost` values ignored safely) + - Section: `gearSection`, position 1 + - Description: "Select compost type. Only applied at patches without protection enabled." + +## Script Changes + +File: `FarmTreeRunScript.java` + +### `isCompostEnabled(config)` + +Change from `config.useCompost()` to `config.compostType() != CompostType.NONE`. + +### `useCompostOnPatch(config, patch)` + +Change `config.useCompost()` check to `config.compostType() != CompostType.NONE`. Rest of the method (protection gating per tree kind) stays identical. + +### Banking: compost withdrawal block + +Replace the current bottomless-only block: + +1. If `compostType == NONE`: skip, `compostItemId = null`. +2. If `BOTTOMLESS_BUCKET`: check bank for item 22997, withdraw 1. Same as current. +3. If `COMPOST / SUPERCOMPOST / ULTRACOMPOST`: count unprotected patches across all three tree categories (regular trees if `!protectTrees()`, fruit trees if `!protectFruitTrees()`, hardwood if `!protectHardTrees()`). Withdraw that count of the selected compost item ID. + +Unprotected patch count reuses the existing `getSelectedTreePatches()`, `getSelectedFruitTreePatches()`, `getSelectedHardTreePatches()` methods — sum their sizes, filtered by protection config. + +### `handlePlantingTree()`: drop empty bucket after use + +After applying consumable compost (not bottomless) and waiting for the Farming XP drop, drop the resulting empty bucket (`ItemID.BUCKET`, 1925) to free the inventory slot before planting the sapling. + +### Non-banking path + +The `else` branch (when `config.banking()` is false) currently hardcodes `compostItemId = ItemID.BOTTOMLESS_COMPOST_BUCKET_22997`. Change to set `compostItemId = config.compostType().getItemId()` (or `null` if `NONE`). + +## Files Touched + +| File | Action | +|------|--------| +| `enums/CompostType.java` | New | +| `FarmTreeRunConfig.java` | Modify: replace boolean with enum dropdown | +| `FarmTreeRunScript.java` | Modify: banking qty logic, `isCompostEnabled`, `useCompostOnPatch`, `handlePlantingTree` bucket drop, non-banking path | + +## Config Information Update + +Update the `@ConfigInformation` HTML on the config interface: +- Change "Filled Bottomless compost bucket" in the Optional items list to "Compost / Supercompost / Ultracompost / Bottomless compost bucket" diff --git a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunConfig.java b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunConfig.java index 4c98ec618c..c4f6b73a3b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunConfig.java @@ -1,6 +1,7 @@ package net.runelite.client.plugins.microbot.farmtreerun; import net.runelite.client.config.*; +import net.runelite.client.plugins.microbot.farmtreerun.enums.CompostType; import net.runelite.client.plugins.microbot.farmtreerun.enums.FruitTreeEnum; import net.runelite.client.plugins.microbot.farmtreerun.enums.HardTreeEnums; import net.runelite.client.plugins.microbot.farmtreerun.enums.TreeEnums; @@ -32,7 +33,7 @@ "
    Optional:\n" + "
      \n" + "
    1. Items for protection payment
    2. \n" + - "
    3. Filled Bottomless compost bucket
    4. \n" + + "
    5. Compost / Supercompost / Ultracompost / Bottomless compost bucket
    6. \n" + "
    " + "
    Extra information:\n" + "
    If you want to stop the script during your farm run (maybe it gets stuck or whatever reason), make sure to disable 'Banking' and disable patches you previously ran." + @@ -161,13 +162,13 @@ public interface FarmTreeRunConfig extends Config { default boolean banking() { return true; } @ConfigItem( - keyName = "useCompost", - name = "Use compost", - description = "Only bottomless compost bucket is supported", + keyName = "compostType", + name = "Compost type", + description = "Select compost type. Only applied at patches without protection enabled.", position = 1, section = gearSection ) - default boolean useCompost() { return true; } + default CompostType compostType() { return CompostType.NONE; } @ConfigItem( keyName = "useGraceful", diff --git a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java index db370cec82..c0a406fc3c 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java @@ -4,8 +4,10 @@ import lombok.RequiredArgsConstructor; import net.runelite.api.*; import net.runelite.api.coords.WorldPoint; +import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.farmtreerun.enums.CompostType; import net.runelite.client.plugins.microbot.farmtreerun.enums.HardTreeEnums; import net.runelite.client.plugins.microbot.farmtreerun.enums.FruitTreeEnum; import net.runelite.client.plugins.microbot.farmtreerun.enums.TreeEnums; @@ -22,8 +24,12 @@ import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; 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.widget.Rs2Widget; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; +import java.awt.event.KeyEvent; + import javax.inject.Inject; import java.util.*; import java.util.concurrent.TimeUnit; @@ -122,7 +128,7 @@ public boolean run(FarmTreeRunConfig config) { checkSaplingLevelRequirement(config); if (!validateSpecialPatches(config)) return; - dropEmptyPlantPots(); + dropCrap(); Patch patch = null; boolean handledPatch = false; @@ -132,7 +138,7 @@ public boolean run(FarmTreeRunConfig config) { bank(config); } else { if (isCompostEnabled(config)) { - compostItemId = ItemID.BOTTOMLESS_COMPOST_BUCKET_22997; + compostItemId = config.compostType().getItemId(); } botStatus = net.runelite.client.plugins.microbot.farmtreerun.enums.FarmTreeRunState.HANDLE_GNOME_STRONGHOLD_FRUIT_PATCH; } @@ -349,6 +355,13 @@ public boolean run(FarmTreeRunConfig config) { case FINISHED: + if (!Rs2Bank.isOpen()) { + if (!Rs2Bank.walkToBank()) return; + if (!Rs2Bank.openBank()) return; + } + Rs2Bank.depositAll(); + sleepUntil(() -> Rs2Inventory.isEmpty(), 3000); + Rs2Bank.closeBank(); Microbot.getClientThread().runOnClientThreadOptional(() -> { Microbot.getClient().addChatMessage(ChatMessageType.ENGINE, "", "Tree run completed.", "Acun", false); Microbot.getClient().addChatMessage(ChatMessageType.ENGINE, "", "Made with love by Acun.", "Acun", false); @@ -403,12 +416,13 @@ private void checkSaplingLevelRequirement(FarmTreeRunConfig config) { config.selectedFruitTree().hasRequiredLevel(); } - private void dropEmptyPlantPots() { - int emptyPlantPot = ItemID.EMPTY_PLANT_POT; - if (!Rs2Player.isAnimating() && !Rs2Player.isMoving() && !Rs2Player.isInteracting()) { - if (Rs2Inventory.hasItem(emptyPlantPot)) { - Rs2Inventory.dropAll(emptyPlantPot); - sleepUntil(() -> !Rs2Inventory.hasItem(emptyPlantPot), 8000); + private void dropCrap() { + if (Rs2Player.isAnimating()) return; + int[] junk = {ItemID.EMPTY_PLANT_POT, ItemID.BUCKET, ItemID.WEEDS}; + for (int id : junk) { + if (Rs2Inventory.hasItem(id)) { + Rs2Inventory.dropAll(id); + sleepUntil(() -> !Rs2Inventory.hasItem(id), 8000); } } } @@ -458,11 +472,15 @@ private void bank(FarmTreeRunConfig config) { } if (isCompostEnabled(config)) { - if (Rs2Bank.hasItem(ItemID.BOTTOMLESS_COMPOST_BUCKET_22997)) { - compostItemId = ItemID.BOTTOMLESS_COMPOST_BUCKET_22997; - items.add(new FarmingItem(compostItemId, 1)); - } else { - Microbot.log("Only bottomless compost is supported. Skipping composting."); + CompostType compostType = config.compostType(); + compostItemId = compostType.getItemId(); + if (compostType.isReusable()) { + if (Rs2Bank.hasItem(compostItemId)) { + items.add(new FarmingItem(compostItemId, 1)); + } else { + Microbot.log("Bottomless compost bucket not found in bank. Skipping composting."); + compostItemId = null; + } } } @@ -490,18 +508,20 @@ private void bank(FarmTreeRunConfig config) { } if (config.useSkillsNecklace() && (config.farmingGuildTreePatch() || config.farmingGuildFruitTreePatch())) { - if (Rs2Bank.hasItem(ItemID.SKILLS_NECKLACE2)) { - items.add(new FarmingItem(ItemID.SKILLS_NECKLACE2, 1)); - } else if (Rs2Bank.hasItem(ItemID.SKILLS_NECKLACE3)) { - items.add(new FarmingItem(ItemID.SKILLS_NECKLACE3, 1)); - } else if (Rs2Bank.hasItem(ItemID.SKILLS_NECKLACE4)) { - items.add(new FarmingItem(ItemID.SKILLS_NECKLACE4, 1)); + if (Rs2Bank.hasItem(ItemID.SKILLS_NECKLACE6)) { + items.add(new FarmingItem(ItemID.SKILLS_NECKLACE6, 1, false, true)); } else if (Rs2Bank.hasItem(ItemID.SKILLS_NECKLACE5)) { - items.add(new FarmingItem(ItemID.SKILLS_NECKLACE5, 1)); - } else if (Rs2Bank.hasItem(ItemID.SKILLS_NECKLACE6)) { - items.add(new FarmingItem(ItemID.SKILLS_NECKLACE6, 1)); + items.add(new FarmingItem(ItemID.SKILLS_NECKLACE5, 1, false, true)); + } else if (Rs2Bank.hasItem(ItemID.SKILLS_NECKLACE4)) { + items.add(new FarmingItem(ItemID.SKILLS_NECKLACE4, 1, false, true)); + } else if (Rs2Bank.hasItem(ItemID.SKILLS_NECKLACE3)) { + items.add(new FarmingItem(ItemID.SKILLS_NECKLACE3, 1, false, true)); + } else if (Rs2Bank.hasItem(ItemID.SKILLS_NECKLACE2)) { + items.add(new FarmingItem(ItemID.SKILLS_NECKLACE2, 1, false, true)); + } else if (Rs2Bank.hasItem(ItemID.SKILLS_NECKLACE1)) { + items.add(new FarmingItem(ItemID.SKILLS_NECKLACE1, 1, false, true)); } else { - items.add(new FarmingItem(ItemID.SKILLS_NECKLACE1, 2)); + Microbot.log("No skills necklace found in bank. Skipping."); } } @@ -780,14 +800,21 @@ private boolean handlePayment(FarmTreeRunConfig config, Patch patch, PaymentKind sleep(500, 850); if (Rs2Dialogue.hasSelectAnOption()) { + if (action == PaymentKind.PROTECT) { + if (!Rs2Dialogue.clickOption("don't ask")) { + Rs2Dialogue.clickOption("Yes"); + } + sleep(500, 1500); + Rs2Dialogue.clickContinue(); + sleepUntil(() -> !Rs2Dialogue.isInDialogue(), 6000); + return true; + } Rs2Dialogue.clickOption("Yes"); sleepUntil(() -> isPatchEmpty(patch), 6000); if (isPatchEmpty(patch)) { return true; } - shutdown(); - - System.out.println("Failed gardener money payment."); + System.out.println("Failed gardener clear payment."); return false; } else { System.out.println("Failed gardener payment."); @@ -803,11 +830,21 @@ private boolean handlePlantingTree(GameObject treePatch, Patch patch, FarmTreeRu int saplingToUse = getSaplingToUse(patch, config); - Microbot.log("Reached here"); if (useCompostOnPatch(config, patch)) { - Rs2Inventory.useItemOnObject(compostItemId, treePatch.getId()); - Rs2Player.waitForXpDrop(Skill.FARMING, 2000); - sleep(550, 2200); + boolean hasCompost = Rs2Inventory.hasItem(compostItemId); + if (!hasCompost && !config.compostType().isReusable()) { + hasCompost = withdrawCompostFromLeprechaun(config.compostType()); + if (!hasCompost) { + Microbot.showMessage("Tool Leprechaun has no " + config.compostType() + ". Store compost with the leprechaun before starting."); + shutdown(); + return false; + } + } + if (hasCompost) { + Rs2Inventory.useItemOnObject(compostItemId, treePatch.getId()); + Rs2Player.waitForXpDrop(Skill.FARMING, 2000); + sleep(550, 2200); + } } sleep(250, 1000); @@ -846,17 +883,8 @@ private void handleRakeAction(GameObject treePatch) { Rs2GameObject.interact(treePatch, "rake"); Rs2Player.waitForAnimation(); - sleepUntil(() -> !Rs2Player.isAnimating() && !Rs2Player.isInteracting()); - - // Drop the weeds (assuming weeds are added to the inventory) - if (!Rs2Player.isMoving() && - !Rs2Player.isAnimating() && - !Rs2Player.isInteracting() && !Rs2Player.isMoving()) { - System.out.println("Dropping weeds..."); - Rs2Inventory.dropAll(ItemID.WEEDS); - Rs2Player.waitForAnimation(); - sleepUntil(() -> !Rs2Player.isAnimating() && !Rs2Player.isInteracting()); - } + sleepUntil(() -> !Rs2Player.isAnimating()); + } private void handleClearAction(GameObject treePatch) { @@ -874,7 +902,7 @@ private void handleClearAction(GameObject treePatch) { // Wait for the clearing animation to finish Rs2Player.waitForAnimation(); - sleepUntil(() -> !Rs2Player.isAnimating() && Rs2Player.isInteracting() && Rs2Player.isMoving()); + sleepUntil(() -> !Rs2Player.isAnimating() && Rs2Player.isMoving()); } private void equipGraceful() { @@ -924,8 +952,47 @@ private boolean isFruitTreePatch(Patch patch) { return patch.kind == TreeKind.FRUIT_TREE; } + private boolean withdrawCompostFromLeprechaun(CompostType compostType) { + Rs2NpcModel leprechaun = Rs2Npc.getNpc("Tool Leprechaun"); + if (leprechaun == null) { + Microbot.log("Tool Leprechaun not found nearby."); + return false; + } + + Rs2Npc.interact(leprechaun, "Exchange"); + sleepUntil(() -> Rs2Widget.isWidgetVisible(125, 0), 5000); + if (!Rs2Widget.isWidgetVisible(125, 0)) { + Microbot.log("Tool Leprechaun exchange interface did not open."); + return false; + } + sleep(300, 600); + + int childId; + switch (compostType) { + case COMPOST: childId = 17; break; + case SUPERCOMPOST: childId = 18; break; + case ULTRACOMPOST: childId = 19; break; + default: return false; + } + + Widget compostWidget = Rs2Widget.getWidget(125, childId); + if (compostWidget == null) { + Microbot.log("Compost widget not found in leprechaun interface."); + return false; + } + + Rs2Widget.clickWidget(compostWidget); + sleepUntil(() -> Rs2Inventory.hasItem(compostType.getItemId()), 3000); + sleep(300, 600); + + Rs2Keyboard.keyPress(KeyEvent.VK_ESCAPE); + sleepUntil(() -> !Rs2Widget.isWidgetVisible(125, 0), 2000); + + return Rs2Inventory.hasItem(compostType.getItemId()); + } + private boolean useCompostOnPatch(FarmTreeRunConfig config, Patch patch) { - if (!config.useCompost() || compostItemId == null) + if (config.compostType() == CompostType.NONE || compostItemId == null) return false; if (!config.protectTrees() && patch.kind == TreeKind.TREE) @@ -996,7 +1063,7 @@ private List getSelectedFruitTreePatches(FarmTreeRunConfig conf * @return true if configured by player, else false */ private boolean isCompostEnabled(FarmTreeRunConfig config) { - if (!config.useCompost()) + if (config.compostType() == CompostType.NONE) return false; if (!getSelectedTreePatches(config).isEmpty() && !config.protectTrees()) diff --git a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/CompostType.java b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/CompostType.java new file mode 100644 index 0000000000..21114bbe5f --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/CompostType.java @@ -0,0 +1,24 @@ +package net.runelite.client.plugins.microbot.farmtreerun.enums; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import net.runelite.api.gameval.ItemID; + +@Getter +@RequiredArgsConstructor +public enum CompostType { + NONE("None", -1, false), + COMPOST("Compost", ItemID.BUCKET_COMPOST, false), + SUPERCOMPOST("Supercompost", ItemID.BUCKET_SUPERCOMPOST, false), + ULTRACOMPOST("Ultracompost", ItemID.BUCKET_ULTRACOMPOST, false), + BOTTOMLESS_BUCKET("Bottomless bucket", ItemID.BOTTOMLESS_COMPOST_BUCKET_FILLED, true); + + private final String name; + private final int itemId; + private final boolean reusable; + + @Override + public String toString() { + return name; + } +} From edf3b47bfd46121c3adc959954d9a0a74e9b9b04 Mon Sep 17 00:00:00 2001 From: stonksCode <99895926+stonksCode@users.noreply.github.com> Date: Thu, 21 May 2026 22:23:58 -0400 Subject: [PATCH 85/95] Feat/butterfly catcher plugin (#448) * fix(combat-hotkeys): move prayer toggles off key event thread Prayer hotkeys were calling Rs2Prayer.toggle() directly on the key listener thread, causing focus loss and input lag on every press. Replaced runOnSeperateThread (silently drops calls when the shared ClientThread future is busy) with a plugin-owned ExecutorService. Also added a debug overlay panel toggled via config. Bump to v1.1.2. * feat(butterfly-catcher): add Butterfly Catcher plugin v1.0.0 Automates butterfly and moth catching for Hunter XP. Supports Ruby Harvest through Moonlight Moth (Varlamore). Barehanded and butterfly net modes with level/equipment checks on startup. --------- Co-authored-by: chsami --- .../plugins/microbot/PluginConstants.java | 1 + .../ButterflyCatcherConfig.java | 42 +++ .../ButterflyCatcherPlugin.java | 80 ++++++ .../ButterflyCatcherScript.java | 169 +++++++++++ .../butterflycatcher/ButterflyType.java | 139 +++++++++ .../microbot/butterflycatcher/CatchMode.java | 37 +++ .../combathotkeys/CombatHotkeysConfig.java | 164 ++++++----- .../combathotkeys/CombatHotkeysOverlay.java | 216 ++++++++++++-- .../combathotkeys/CombatHotkeysPlugin.java | 268 ++++++++++++++---- .../microbot/butterflycatcher/docs/README.md | 43 +++ 10 files changed, 1018 insertions(+), 141 deletions(-) create mode 100644 src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyCatcherConfig.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyCatcherPlugin.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyCatcherScript.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyType.java create mode 100644 src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/CatchMode.java create mode 100644 src/main/resources/net/runelite/client/plugins/microbot/butterflycatcher/docs/README.md diff --git a/src/main/java/net/runelite/client/plugins/microbot/PluginConstants.java b/src/main/java/net/runelite/client/plugins/microbot/PluginConstants.java index ed2554033d..0f078012af 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/PluginConstants.java +++ b/src/main/java/net/runelite/client/plugins/microbot/PluginConstants.java @@ -37,6 +37,7 @@ private PluginConstants() public static final String PERT = "[P] "; public static final String DV = "[DV] "; public static final String RED_BRACKET = "[RB] "; + public static final String STKS = "[STKS] "; public static final boolean DEFAULT_ENABLED = false; public static final boolean IS_EXTERNAL = true; //test diff --git a/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyCatcherConfig.java b/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyCatcherConfig.java new file mode 100644 index 0000000000..43fda2eae6 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyCatcherConfig.java @@ -0,0 +1,42 @@ +package net.runelite.client.plugins.microbot.butterflycatcher; + +import net.runelite.client.config.Config; +import net.runelite.client.config.ConfigGroup; +import net.runelite.client.config.ConfigInformation; +import net.runelite.client.config.ConfigItem; + +@ConfigInformation( + "Butterfly Catcher — by StonksCode

    " + + "Species (net / barehanded):
    " + + "Ruby Harvest 5/15  |  Sapphire Glacialis 25/35  |  Snowy Knight 35/45
    " + + "Black Warlock 45/55  |  Sunlight Moth 65/75  |  Moonlight Moth 75/85

    " + + "Barehanded — XP only, nothing in inventory
    " + + "Butterfly Net — equip net first, 10 levels lower requirement" +) +@ConfigGroup("ButterflyCatcher") +public interface ButterflyCatcherConfig extends Config { + + @ConfigItem( + keyName = "butterflyType", + name = "Butterfly / Moth", + description = "Which creature to hunt.
    " + + "Classic: Ruby Harvest, Sapphire Glacialis, Snowy Knight, Black Warlock.
    " + + "Varlamore: Sunlight Moth, Moonlight Moth.
    " + + "Net level shown in parentheses; barehanded = net + 10.", + position = 0 + ) + default ButterflyType butterflyType() { + return ButterflyType.BLACK_WARLOCK; + } + + @ConfigItem( + keyName = "catchMode", + name = "Catch Mode", + description = "BAREHANDED: catch and release for XP — nothing enters inventory.
    " + + "BUTTERFLY_NET: use an equipped butterfly net (lower level requirement).", + position = 1 + ) + default CatchMode catchMode() { + return CatchMode.BAREHANDED; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyCatcherPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyCatcherPlugin.java new file mode 100644 index 0000000000..029c0ad281 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyCatcherPlugin.java @@ -0,0 +1,80 @@ +package net.runelite.client.plugins.microbot.butterflycatcher; + +import com.google.inject.Provides; +import lombok.extern.slf4j.Slf4j; +import net.runelite.client.config.ConfigManager; +import net.runelite.client.plugins.Plugin; +import net.runelite.client.plugins.PluginDescriptor; +import net.runelite.client.plugins.microbot.PluginConstants; + +import javax.inject.Inject; +import java.awt.*; + +/** + * ===================================================================== + * Butterfly Catcher + * Author: StonksCode + * ===================================================================== + * + * Automates butterfly and moth catching for Hunter XP training. + * + * Supported species: + * - Ruby Harvest (net lvl 5 / bare lvl 15) + * - Sapphire Glacialis (net lvl 25 / bare lvl 35) + * - Snowy Knight (net lvl 35 / bare lvl 45) + * - Black Warlock (net lvl 45 / bare lvl 55) + * - Sunlight Moth (net lvl 65 / bare lvl 75) + * - Moonlight Moth (net lvl 75 / bare lvl 85) + * + * Catch modes: + * BAREHANDED — catch and release for XP; nothing enters inventory. + * BUTTERFLY_NET — equip a butterfly net or magic butterfly net before + * starting; allows catching at 10 levels lower than + * the barehanded requirement. + * + * Usage: + * 1. Stand near a spawn of your chosen species. + * 2. If using Butterfly Net mode, equip your net first. + * 3. Select your species and catch mode in the config panel. + * 4. Start the plugin — it will run indefinitely with no banking. + * ===================================================================== + */ +@PluginDescriptor( + name = PluginConstants.STKS + "Butterfly Catcher", + description = "Automates butterfly and moth catching for Hunter XP. Stand near a spawn, pick your species and mode, start the plugin.", + tags = {"hunter", "butterfly", "moth", "sunlight", "moonlight", "net", "microbot", "stonkscode"}, + authors = {"StonksCode"}, + version = ButterflyCatcherPlugin.version, + minClientVersion = "2.0.8", + enabledByDefault = PluginConstants.DEFAULT_ENABLED, + isExternal = PluginConstants.IS_EXTERNAL +) +@Slf4j +public class ButterflyCatcherPlugin extends Plugin { + + public static final String version = "1.0.0"; + + @Inject + private ButterflyCatcherConfig config; + + @Inject + private ButterflyCatcherScript script; + + @Provides + ButterflyCatcherConfig provideConfig(ConfigManager configManager) { + return configManager.getConfig(ButterflyCatcherConfig.class); + } + + @Override + protected void startUp() throws AWTException { + script.run(config); + log.info("[ButterflyCatcher] Started — target: {}, mode: {}", + config.butterflyType().getDisplayName(), config.catchMode()); + } + + @Override + protected void shutDown() { + script.shutdown(); + log.info("[ButterflyCatcher] Stopped."); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyCatcherScript.java b/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyCatcherScript.java new file mode 100644 index 0000000000..c0739d4bb9 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyCatcherScript.java @@ -0,0 +1,169 @@ +package net.runelite.client.plugins.microbot.butterflycatcher; + +import lombok.extern.slf4j.Slf4j; +import net.runelite.api.Skill; +import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; + +import java.util.Arrays; +import java.util.concurrent.TimeUnit; + +/** + * ButterflyCatcherScript + * + * Two modes — both run indefinitely with no banking: + * + * BAREHANDED: + * Click catch, wait for animation/movement to resolve, repeat. + * Requires Hunter level >= target.getBarehandedLevelRequired(). + * + * BUTTERFLY_NET: + * Same loop, but requires a butterfly net or magic butterfly net to be + * equipped. Verified once on startup. Requires Hunter level >= + * target.getNetLevelRequired() (10 levels lower than barehanded). + * + * Item IDs for nets: + * Butterfly net : 10010 + * Magic butterfly net : 11259 + */ +@Slf4j +public class ButterflyCatcherScript extends Script { + + private static final int NET_ITEM_ID = 10010; + private static final int MAGIC_NET_ITEM_ID = 11259; + private static final String TAG = "[ButterflyCatcher]"; + private static final int TICK = 600; + + private ButterflyType targetButterfly; + private CatchMode catchMode; + private boolean catchCommitted = false; + + // ------------------------------------------------------------------------- + // Entry point + // ------------------------------------------------------------------------- + + public boolean run(ButterflyCatcherConfig config) { + this.targetButterfly = config.butterflyType(); + this.catchMode = config.catchMode(); + this.catchCommitted = false; + + int requiredLevel = effectiveLevel(); + Microbot.log(TAG + " Starting — target: " + targetButterfly.getDisplayName() + + " | mode: " + catchMode + + " | Required Hunter level: " + requiredLevel); + + // Net equipment check (BUTTERFLY_NET mode only) + if (catchMode == CatchMode.BUTTERFLY_NET) { + if (!isNetEquipped()) { + Microbot.log(TAG + " No butterfly net equipped! " + + "Equip a Butterfly Net (id 10010) or Magic Butterfly Net (id 11259) " + + "before starting. Stopping."); + Microbot.showMessage("Butterfly Catcher: no butterfly net equipped. Equip one and restart."); + return false; + } + Microbot.log(TAG + " Butterfly net verified."); + } + + // Hunter level check + int hunterLevel = Microbot.getClient().getRealSkillLevel(Skill.HUNTER); + if (hunterLevel < requiredLevel) { + Microbot.log(TAG + " Hunter level too low (" + + hunterLevel + " / " + requiredLevel + + ") for " + targetButterfly.getDisplayName() + + " in " + catchMode + " mode. Stopping."); + Microbot.showMessage("Butterfly Catcher: Hunter level too low (" + + hunterLevel + " / " + requiredLevel + ")."); + return false; + } + + mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay( + this::tick, 0, TICK, TimeUnit.MILLISECONDS); + return true; + } + + @Override + public void shutdown() { + super.shutdown(); + catchCommitted = false; + } + + // ------------------------------------------------------------------------- + // Main tick + // ------------------------------------------------------------------------- + + private void tick() { + try { + if (!Microbot.isLoggedIn()) return; + if (!super.run()) return; + + // Stop if level drops below threshold (e.g. de-boost) + int hunterLevel = Microbot.getClient().getRealSkillLevel(Skill.HUNTER); + if (hunterLevel < effectiveLevel()) { + Microbot.log(TAG + " Hunter level too low — stopping."); + shutdown(); + return; + } + + tickCatching(); + + } catch (Exception e) { + log.error(TAG + " Unexpected error in tick: {}", e.getMessage(), e); + } + } + + // ------------------------------------------------------------------------- + // Catching logic + // ------------------------------------------------------------------------- + + private void tickCatching() { + // Don't spam-click while already committed to a catch attempt + if (catchCommitted) { + if (Rs2Player.isAnimating() || Rs2Player.isMoving()) { + return; + } + catchCommitted = false; + } + + if (Rs2Player.isAnimating()) return; + + // Find nearest target NPC using the singleton cache + var target = Microbot.getRs2NpcCache() + .query() + .withIds(targetButterfly.getNpcIds()) + .nearest(); + + if (target == null) { + Microbot.log(TAG + " No " + targetButterfly.getDisplayName() + + " found nearby (IDs: " + Arrays.toString(targetButterfly.getNpcIds()) + ") — waiting."); + return; + } + + boolean clicked = target.click("Catch"); + if (!clicked) { + log.warn(TAG + " click(Catch) failed on {} at {}", + targetButterfly.getDisplayName(), target.getWorldLocation()); + return; + } + + catchCommitted = true; + log.debug(TAG + " Clicked Catch on {} at {}", + targetButterfly.getDisplayName(), target.getWorldLocation()); + sleepUntil(() -> Rs2Player.isAnimating() || Rs2Player.isMoving(), 1500); + } + + // ------------------------------------------------------------------------- + // Helpers + // ------------------------------------------------------------------------- + + private int effectiveLevel() { + return catchMode == CatchMode.BUTTERFLY_NET + ? targetButterfly.getNetLevelRequired() + : targetButterfly.getBarehandedLevelRequired(); + } + + private boolean isNetEquipped() { + return Rs2Equipment.isWearing(NET_ITEM_ID) || Rs2Equipment.isWearing(MAGIC_NET_ITEM_ID); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyType.java b/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyType.java new file mode 100644 index 0000000000..dabff24f1f --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/ButterflyType.java @@ -0,0 +1,139 @@ +package net.runelite.client.plugins.microbot.butterflycatcher; + +import lombok.Getter; + +/** + * ButterflyType + * + * Defines all catchable butterflies and moths from the Hunter skill. + * Data sourced from: https://oldschool.runescape.wiki/w/Butterfly_(Hunter) + * + * Level requirements: + * The game requires a Hunter level 10 ABOVE the net level for barehanded catching. + * This enum stores the NET level as the baseline; the script derives the + * barehanded level by adding 10. + * + * Species Net Barehanded + * ─────────────────── ──── ────────── + * Ruby Harvest 5 15 + * Sapphire Glacialis 25 35 + * Snowy Knight 35 45 + * Black Warlock 45 55 + * Sunlight Moth 65 75 + * Moonlight Moth 75 85 + * + * Item IDs: + * Butterfly net : 10010 + * Magic butterfly net: 11259 + * Butterfly jar : 10012 (same for all species) + */ +@Getter +public enum ButterflyType { + + RUBY_HARVEST( + "Ruby Harvest", + new int[]{ 5525 }, + 5, + 10012, + 10009 + ), + SAPPHIRE_GLACIALIS( + "Sapphire Glacialis", + new int[]{ 5526 }, + 25, + 10012, + 10011 + ), + SNOWY_KNIGHT( + "Snowy Knight", + new int[]{ 5527 }, + 35, + 10012, + 10013 + ), + BLACK_WARLOCK( + "Black Warlock", + new int[]{ 5553 }, + 45, + 10012, + 10010 + ), + + /** + * Sunlight Moth — Avium Savannah south of the Hunter Guild. + * Net: 65 | Barehanded: 75 + */ + SUNLIGHT_MOTH( + "Sunlight Moth", + new int[]{ 12770 }, + 65, + 10012, + 28890 + ), + + /** + * Moonlight Moth — Neypotzli / Hunter Guild basement / Tonali Cavern. + * Net: 75 | Barehanded: 85 + * NPC IDs: 12771, 12772, 12773 (variants per location). + */ + MOONLIGHT_MOTH( + "Moonlight Moth", + new int[]{ 12771, 12772, 12773 }, + 75, + 10012, + 28893 + ); + + // ------------------------------------------------------------------------- + + /** Human-readable name shown in the config dropdown. */ + private final String displayName; + + /** + * All NPC IDs for this creature. + * Most species have exactly one. Moonlight Moth has three location variants. + */ + private final int[] npcIds; + + /** + * Hunter level required to catch with a butterfly net (or magic butterfly net). + */ + private final int netLevelRequired; + + /** Item ID of the empty butterfly jar (always 10012). */ + private final int jarItemId; + + /** Item ID placed in the inventory after a successful jar catch. */ + private final int caughtItemId; + + // ------------------------------------------------------------------------- + + ButterflyType(String displayName, int[] npcIds, int netLevelRequired, + int jarItemId, int caughtItemId) { + this.displayName = displayName; + this.npcIds = npcIds; + this.netLevelRequired = netLevelRequired; + this.jarItemId = jarItemId; + this.caughtItemId = caughtItemId; + } + + /** + * Hunter level required to catch bare-handed (always netLevelRequired + 10). + */ + public int getBarehandedLevelRequired() { + return netLevelRequired + 10; + } + + /** + * Returns the primary NPC ID (first in the array). Used for logging. + * The script always iterates all IDs in the array when searching. + */ + public int getNpcId() { + return npcIds[0]; + } + + @Override + public String toString() { + return displayName; + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/CatchMode.java b/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/CatchMode.java new file mode 100644 index 0000000000..f1aef6db59 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/butterflycatcher/CatchMode.java @@ -0,0 +1,37 @@ +package net.runelite.client.plugins.microbot.butterflycatcher; + +/** + * CatchMode + * + * Controls whether the plugin catches bare-handed (XP only) or uses a + * butterfly net / magic butterfly net. + * + * Banking has been intentionally removed — the script runs indefinitely + * without ever needing to visit a bank. + */ +public enum CatchMode { + + /** + * Catch the butterfly/moth bare-handed. + * The creature is instantly released on catch, so nothing enters the + * inventory. Requires Hunter level = netLevelRequired + 10. + */ + BAREHANDED, + + /** + * Catch using a butterfly net (or magic butterfly net). + * The net must be equipped before the script starts — the script will + * verify this on startup and stop with a message if not equipped. + * Requires Hunter level = netLevelRequired (the lower threshold). + */ + BUTTERFLY_NET; + + @Override + public String toString() { + switch (this) { + case BAREHANDED: return "Barehanded"; + case BUTTERFLY_NET: return "Butterfly Net"; + default: return name(); + } + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysConfig.java b/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysConfig.java index 2bb17e9285..d8a3e05141 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysConfig.java @@ -12,6 +12,35 @@ @ConfigGroup("combathotkeys") public interface CombatHotkeysConfig extends Config { + // ========================================================================= + // DEBUG SECTION — appears at the top (position 0) so it's always visible + // ========================================================================= + + @ConfigSection( + name = "Debug", + description = "Enable the on-screen debug panel and verbose logging to the RuneLite log", + position = 0, + closedByDefault = true + ) + String debugSection = "debugSection"; + + @ConfigItem( + keyName = "debugMode", + name = "Show debug panel", + description = "Renders a debug overlay showing the last key received, last action dispatched, " + + "submitted/succeeded/failed counters, and any error. Also enables TRACE-level logging " + + "from the plugin — check the RuneLite log (Help → Open logs folder) for full detail.", + position = 0, + section = debugSection + ) + default boolean debugMode() { + return false; + } + + // ========================================================================= + // OFFENSIVE SECTION + // ========================================================================= + @ConfigSection( name = "Offensive Hotkeys", description = "Offensive Prayer and attack hotkeys", @@ -86,6 +115,10 @@ public interface CombatHotkeysConfig extends Config { ) default Keybind specialAttackKey() { return Keybind.NOT_SET; } + // ========================================================================= + // DEFENSIVE PRAYERS SECTION + // ========================================================================= + @ConfigSection( name = "Defensive Prayers", description = "Defensive Prayer hotkeys", @@ -129,6 +162,10 @@ default Keybind protectFromMelee() return Keybind.NOT_SET; } + // ========================================================================= + // FOOD & POTIONS SECTION + // ========================================================================= + @ConfigSection( name = "Food & Potions", description = "Food & Potions", @@ -172,6 +209,10 @@ default Keybind drinkPrayerPotion() return Keybind.NOT_SET; } + // ========================================================================= + // GEAR SETUPS SECTION + // ========================================================================= + @ConfigSection( name = "Gear setups", description = "Gear setups", @@ -179,6 +220,17 @@ default Keybind drinkPrayerPotion() ) String gearSetup = "gearSetup"; + @ConfigItem( + keyName = "maxDelay", + name = "Max Equip Delay (ms)", + description = "Maximum random delay (in milliseconds) between equipping items", + position = 0, + section = gearSetup + ) + default int maxDelay() { + return 500; + } + @ConfigItem( keyName = "Hotkey for gear 1", name = "Hotkey for gear 1", @@ -286,13 +338,46 @@ default Keybind gear5() { keyName = "Gear IDs 5", name = "Gear IDs 5", description = "List of Gear IDs comma separated", - position = 2, + position = 10, section = gearSetup ) default String gearList5() { return ""; } + // ========================================================================= + // MULTISKILLING SECTION + // ========================================================================= + + @ConfigSection( + name = "Multiskilling", + description = "Multiskilling hotkeys", + position = 5 + ) + String multiskillingSection = "multiskillingSection"; + + @ConfigItem( + keyName = "Alchemy", + name = "Alchemy", + description = "Keybind to perform alchemy spell", + position = 0, + section = multiskillingSection + ) + default Keybind highAlchemyKey() { return Keybind.NOT_SET; } + + @ConfigItem( + keyName = "itemToAlch", + name = "Item to Alch", + description = "Enter exact item name to alch (e.g. 'Gold Bar')", + position = 1, + section = multiskillingSection + ) + default String itemToAlch() { return null; } + + // ========================================================================= + // DANCE SECTION + // ========================================================================= + @ConfigItem( keyName = "dance boolean", name = "dance", @@ -303,7 +388,6 @@ default boolean yesDance() { return false; } - // config item for a keybind to enable the dance feature @ConfigItem( keyName = "dance", name = "Dance", @@ -314,7 +398,6 @@ default Keybind dance() { return Keybind.NOT_SET; } - // hidden config for worldpoint called tile1 @ConfigItem( keyName = "tile1", name = "", @@ -324,6 +407,7 @@ default Keybind dance() { default WorldPoint tile1() { return null; } + @ConfigItem( keyName = "tile2", name = "", @@ -334,60 +418,22 @@ default WorldPoint tile2() { return null; } - @ConfigItem( - keyName = "maxDelay", - name = "Max Equip Delay (ms)", - description = "Maximum random delay (in milliseconds) between equipping items", - position = 0, - section = gearSetup - ) - default int maxDelay() { - return 500; // default max delay of 500ms - } - - @ConfigSection( - name = "Multiskilling", - description = "Multiskilling hotkeys", - position = 5 - ) - String multiskillingSection = "multiskillingSection"; + // ========================================================================= + // ENUMS + // ========================================================================= - @ConfigItem( - keyName = "Alchemy", - name = "Alchemy", - description = "Keybind to perform alchemy spell", - position = 0, - section = multiskillingSection - ) - default Keybind highAlchemyKey() { return Keybind.NOT_SET; } - - @ConfigItem( - keyName = "itemToAlch", - name = "Item to Alch", - description = "Enter exact item name to alch (e.g. 'Gold Bar')", - position = 1, - section = multiskillingSection - ) - default String itemToAlch() { return null; } - - public enum MeleePrayerOption { + enum MeleePrayerOption { SUPERHUMAN_STRENGTH(Rs2PrayerEnum.SUPERHUMAN_STRENGTH), ULTIMATE_STRENGTH(Rs2PrayerEnum.ULTIMATE_STRENGTH), CHIVALRY(Rs2PrayerEnum.CHIVALRY), PIETY(Rs2PrayerEnum.PIETY); private final Rs2PrayerEnum prayer; - - MeleePrayerOption(Rs2PrayerEnum prayer) { - this.prayer = prayer; - } - - public Rs2PrayerEnum getPrayer() { - return prayer; - } + MeleePrayerOption(Rs2PrayerEnum prayer) { this.prayer = prayer; } + public Rs2PrayerEnum getPrayer() { return prayer; } } - public enum RangedPrayerOption { + enum RangedPrayerOption { SHARP_EYE(Rs2PrayerEnum.SHARP_EYE), HAWK_EYE(Rs2PrayerEnum.HAWK_EYE), EAGLE_EYE(Rs2PrayerEnum.EAGLE_EYE), @@ -395,30 +441,18 @@ public enum RangedPrayerOption { RIGOUR(Rs2PrayerEnum.RIGOUR); private final Rs2PrayerEnum prayer; - - RangedPrayerOption(Rs2PrayerEnum prayer) { - this.prayer = prayer; - } - - public Rs2PrayerEnum getPrayer() { - return prayer; - } + RangedPrayerOption(Rs2PrayerEnum prayer) { this.prayer = prayer; } + public Rs2PrayerEnum getPrayer() { return prayer; } } - public enum MagicPrayerOption { + enum MagicPrayerOption { MYSTIC_WILL(Rs2PrayerEnum.MYSTIC_WILL), MYSTIC_LORE(Rs2PrayerEnum.MYSTIC_LORE), MYSTIC_MIGHT(Rs2PrayerEnum.MYSTIC_MIGHT), AUGURY(Rs2PrayerEnum.AUGURY); private final Rs2PrayerEnum prayer; - - MagicPrayerOption(Rs2PrayerEnum prayer) { - this.prayer = prayer; - } - - public Rs2PrayerEnum getPrayer() { - return prayer; - } + MagicPrayerOption(Rs2PrayerEnum prayer) { this.prayer = prayer; } + public Rs2PrayerEnum getPrayer() { return prayer; } } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysOverlay.java index b8b9fea28b..7e425e610d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysOverlay.java @@ -15,58 +15,234 @@ import javax.annotation.Nullable; import javax.inject.Inject; import java.awt.*; +import java.time.Instant; public class CombatHotkeysOverlay extends Overlay { - CombatHotkeysConfig config; + private static final int PANEL_X = 10; + private static final int PANEL_Y = 10; + private static final int PANEL_W = 310; + private static final int ROW_H = 16; + private static final int PAD = 6; + + private static final Color BG_COLOR = new Color(0, 0, 0, 180); + private static final Color BORDER_COLOR = new Color(255, 165, 0, 220); // orange + private static final Color LABEL_COLOR = new Color(180, 180, 180); + private static final Color VALUE_COLOR = Color.WHITE; + private static final Color OK_COLOR = new Color(80, 220, 80); + private static final Color ERR_COLOR = new Color(255, 80, 80); + private static final Color TITLE_COLOR = new Color(255, 165, 0); + + private final CombatHotkeysPlugin plugin; + private final CombatHotkeysConfig config; + @Inject CombatHotkeysOverlay(CombatHotkeysPlugin plugin, CombatHotkeysConfig config) { - super(plugin); + this.plugin = plugin; this.config = config; setPosition(OverlayPosition.DYNAMIC); setNaughty(); setLayer(OverlayLayer.ABOVE_SCENE); } + @Override public Dimension render(Graphics2D graphics) { try { - if(config.yesDance()) { + // ---------------------------------------------------------------- + // Dance tiles (original behaviour — always rendered when enabled) + // ---------------------------------------------------------------- + if (config.yesDance()) { drawTile(graphics, config.tile1(), Color.GREEN, "Tile 1", new BasicStroke(2)); drawTile(graphics, config.tile2(), Color.GREEN, "Tile 2", new BasicStroke(2)); } - } catch(Exception ex) { + + // ---------------------------------------------------------------- + // Debug panel — only rendered when debug mode is on + // ---------------------------------------------------------------- + if (config.debugMode()) { + renderDebugPanel(graphics); + } + + } catch (Exception ex) { Microbot.logStackTrace(this.getClass().getSimpleName(), ex); } return null; } - private void drawTile(Graphics2D graphics, WorldPoint point, Color color, @Nullable String label, Stroke borderStroke) + + // ------------------------------------------------------------------------- + // DEBUG PANEL + // ------------------------------------------------------------------------- + + /** + * Renders a compact diagnostic panel in the top-left corner of the game + * canvas. All data is read atomically from the plugin's public fields so + * this method never touches game state. + * + * Layout (each row is ROW_H px tall): + * ┌─ Combat Hotkeys DEBUG (v1.1.2) ──────────────────────────┐ + * │ Last key received : protectMelee │ + * │ Key age : 0.3 s ago │ + * │ Last action : toggle prayer PROTECT_MELEE │ + * │ Submitted : 4 │ + * │ Succeeded : 4 Failed: 0 │ + * │ Last error : - │ + * │ Logged in : true │ + * │ Thread : AWT-EventQueue-0 │ + * └──────────────────────────────────────────────────────────┘ + * + * Reading this panel tells you at a glance: + * - "Last key received" never updates → keyPressed is not firing; the + * keybind in config does not match what you're pressing, or the + * KeyManager is not registered. + * - "Last key received" updates but "Last action" doesn't → dispatch() + * was reached but the executor was null/shutdown (logged as an error). + * - Submitted increments but Succeeded doesn't → Rs2Prayer.toggle() + * threw; check "Last error" and the RuneLite log. + * - Everything looks fine but prayer doesn't toggle → Rs2Prayer itself + * has a bug (e.g. prayer tab not open, out of prayer points). + */ + private void renderDebugPanel(Graphics2D graphics) { + Font originalFont = graphics.getFont(); + Font monoFont = new Font(Font.MONOSPACED, Font.PLAIN, 11); + graphics.setFont(monoFont); + FontMetrics fm = graphics.getFontMetrics(monoFont); + + // Collect all rows to render first so we can size the background + String keyReceived = plugin.getLastKeyReceived().get(); + String lastAction = plugin.getLastActionDispatched().get(); + int submitted = plugin.getTotalActionsSubmitted().get(); + int succeeded = plugin.getTotalActionsSucceeded().get(); + int failed = plugin.getTotalActionsFailed().get(); + String lastError = plugin.getLastError().get(); + boolean loggedIn = Microbot.isLoggedIn(); + + // Age of last keypress + String keyAge; + long ts = plugin.getLastKeyTimestamp(); + if (ts == 0) { + keyAge = "never"; + } else { + long ageSec = (Instant.now().toEpochMilli() - ts); + keyAge = String.format("%.1f s ago", ageSec / 1000.0); + } + + String[] labels = { + "Last key :", + "Key age :", + "Last action :", + "Submitted :", + "Succeeded :", + "Failed :", + "Last error :", + "Logged in :", + }; + String[] values = { + keyReceived, + keyAge, + lastAction, + String.valueOf(submitted), + String.valueOf(succeeded), + String.valueOf(failed), + lastError, + String.valueOf(loggedIn), + }; + Color[] valueColors = { + VALUE_COLOR, + LABEL_COLOR, + VALUE_COLOR, + VALUE_COLOR, + succeeded > 0 ? OK_COLOR : LABEL_COLOR, + failed > 0 ? ERR_COLOR : LABEL_COLOR, + lastError.equals("-") ? LABEL_COLOR : ERR_COLOR, + loggedIn ? OK_COLOR : ERR_COLOR, + }; + + int rows = labels.length; + int titleH = ROW_H + 4; + int totalH = titleH + rows * ROW_H + PAD * 2; + int x = PANEL_X; + int y = PANEL_Y; + + // Background + graphics.setColor(BG_COLOR); + graphics.fillRoundRect(x, y, PANEL_W, totalH, 8, 8); + + // Border + graphics.setColor(BORDER_COLOR); + graphics.setStroke(new BasicStroke(1.5f)); + graphics.drawRoundRect(x, y, PANEL_W, totalH, 8, 8); + + // Title bar + graphics.setColor(TITLE_COLOR); + Font titleFont = monoFont.deriveFont(Font.BOLD, 11f); + graphics.setFont(titleFont); + String title = "Combat Hotkeys DEBUG v" + CombatHotkeysPlugin.version; + graphics.drawString(title, x + PAD, y + PAD + fm.getAscent()); + graphics.setFont(monoFont); + + // Divider under title + graphics.setColor(BORDER_COLOR); + graphics.setStroke(new BasicStroke(1f)); + graphics.drawLine(x + 1, y + titleH, x + PANEL_W - 1, y + titleH); + + // Data rows + int rowY = y + titleH + PAD; + for (int i = 0; i < rows; i++) { + int baseY = rowY + i * ROW_H + fm.getAscent(); + + // Label (dimmer) + graphics.setColor(LABEL_COLOR); + graphics.drawString(labels[i], x + PAD, baseY); + + // Value (coloured) + graphics.setColor(valueColors[i]); + int labelW = fm.stringWidth(labels[i]); + // Truncate long values so they don't overflow the panel + String val = truncate(values[i], fm, PANEL_W - labelW - PAD * 3); + graphics.drawString(val, x + PAD + labelW + 4, baseY); + } + + graphics.setFont(originalFont); + } + + /** Truncate a string to fit within maxWidth pixels, appending "…" if needed. */ + private String truncate(String s, FontMetrics fm, int maxWidth) { + if (s == null) return "null"; + if (fm.stringWidth(s) <= maxWidth) return s; + while (s.length() > 1 && fm.stringWidth(s + "…") > maxWidth) { + s = s.substring(0, s.length() - 1); + } + return s + "…"; + } + + // ------------------------------------------------------------------------- + // DANCE TILE DRAWING (unchanged from original) + // ------------------------------------------------------------------------- + + private void drawTile(Graphics2D graphics, WorldPoint point, Color color, + @Nullable String label, Stroke borderStroke) { + if (point == null) return; + WorldPoint playerLocation = Rs2Player.getWorldLocation(); + if (playerLocation == null) return; - if (point.distanceTo(playerLocation) >= 32) - { - return; - } + if (point.distanceTo(playerLocation) >= 32) return; LocalPoint lp = LocalPoint.fromWorld(Microbot.getClient(), point); - if (lp == null) - { - return; - } + if (lp == null) return; Polygon poly = Perspective.getCanvasTilePoly(Microbot.getClient(), lp); - if (poly != null) - { + if (poly != null) { OverlayUtil.renderPolygon(graphics, poly, color, new Color(0, 0, 0, 50), borderStroke); } - if (!Strings.isNullOrEmpty(label)) - { - Point canvasTextLocation = Perspective.getCanvasTextLocation(Microbot.getClient(), graphics, lp, label, 0); - if (canvasTextLocation != null) - { + if (!Strings.isNullOrEmpty(label)) { + Point canvasTextLocation = Perspective.getCanvasTextLocation( + Microbot.getClient(), graphics, lp, label, 0); + if (canvasTextLocation != null) { OverlayUtil.renderTextLocation(graphics, canvasTextLocation, label, color); } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysPlugin.java index 54974e744a..8ab43b4ba3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/combathotkeys/CombatHotkeysPlugin.java @@ -1,6 +1,7 @@ package net.runelite.client.plugins.microbot.combathotkeys; import com.google.inject.Provides; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import net.runelite.api.MenuAction; import net.runelite.api.events.MenuEntryAdded; @@ -24,6 +25,11 @@ import javax.inject.Inject; import java.awt.*; import java.awt.event.KeyEvent; +import java.time.Instant; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; import static net.runelite.client.plugins.microbot.util.Global.sleep; @@ -41,7 +47,57 @@ ) @Slf4j public class CombatHotkeysPlugin extends Plugin implements KeyListener { - public static final String version = "1.1.0"; + // v1.1.2 — fix: replaced runOnSeperateThread (silently drops calls when a prior + // task is still running on the shared ClientThread executor) with a + // dedicated single-thread ExecutorService owned by this plugin. + // Added debug logging + on-screen overlay panel to trace hotkey dispatch. + public static final String version = "1.1.2"; + + // ------------------------------------------------------------------------- + // DEBUG STATE — read by CombatHotkeysOverlay to render the debug panel + // ------------------------------------------------------------------------- + + /** True while debug mode is on (toggled via config). */ + @Getter + volatile boolean debugMode = false; + + /** Last hotkey name that reached keyPressed. */ + @Getter + final AtomicReference lastKeyReceived = new AtomicReference<>("-"); + + /** Timestamp of the last keyPressed hit (epoch ms). */ + @Getter + volatile long lastKeyTimestamp = 0; + + /** Last action that was dispatched to the executor. */ + @Getter + final AtomicReference lastActionDispatched = new AtomicReference<>("-"); + + /** How many hotkey actions have been submitted to the executor total. */ + @Getter + final AtomicInteger totalActionsSubmitted = new AtomicInteger(0); + + /** How many hotkey actions completed without throwing. */ + @Getter + final AtomicInteger totalActionsSucceeded = new AtomicInteger(0); + + /** How many hotkey actions threw an exception. */ + @Getter + final AtomicInteger totalActionsFailed = new AtomicInteger(0); + + /** Last error message from the executor, if any. */ + @Getter + final AtomicReference lastError = new AtomicReference<>("-"); + + // ------------------------------------------------------------------------- + // PRIVATE EXECUTOR + // runOnSeperateThread() uses a single scheduledFuture on the ClientThread + // singleton. If *any* other plugin or the script loop has submitted a task + // that hasn't finished yet the gate `if (!scheduledFuture.isDone()) return` + // silently drops our call. A plugin-owned executor has no such contention. + // ------------------------------------------------------------------------- + private ExecutorService hotkeyExecutor; + @Inject private CombatHotkeysConfig config; @@ -62,165 +118,265 @@ CombatHotkeysConfig provideConfig(ConfigManager configManager) { @Inject private CombatHotkeysScript script; + // ------------------------------------------------------------------------- + // LIFECYCLE + // ------------------------------------------------------------------------- @Override protected void startUp() throws AWTException { + hotkeyExecutor = Executors.newSingleThreadExecutor(r -> { + Thread t = new Thread(r, "CombatHotkeys-executor"); + t.setDaemon(true); + return t; + }); + log.info("[CombatHotkeys] Plugin starting — executor created"); + keyManager.registerKeyListener(this); if (overlayManager != null) { overlayManager.add(overlay); } script.run(config); + log.info("[CombatHotkeys] Plugin started successfully (v{})", version); } + @Override protected void shutDown() { script.shutdown(); keyManager.unregisterKeyListener(this); overlayManager.remove(overlay); + + if (hotkeyExecutor != null) { + hotkeyExecutor.shutdownNow(); + hotkeyExecutor = null; + } + log.info("[CombatHotkeys] Plugin shut down"); + } + + // ------------------------------------------------------------------------- + // HELPERS + // ------------------------------------------------------------------------- + + /** + * Submit an action to the plugin-owned executor. + * + * Every submission is logged so we can tell in the debug overlay (and in + * the RuneLite log) whether the keypress is reaching the dispatcher at all, + * whether the executor accepted it, and whether it threw. + */ + private void dispatch(String actionName, Runnable action) { + if (hotkeyExecutor == null || hotkeyExecutor.isShutdown()) { + log.warn("[CombatHotkeys] dispatch('{}') — executor is null/shutdown, ignoring", actionName); + lastError.set("executor null/shutdown for: " + actionName); + return; + } + + lastActionDispatched.set(actionName); + totalActionsSubmitted.incrementAndGet(); + log.debug("[CombatHotkeys] Submitting '{}' to executor", actionName); + + hotkeyExecutor.submit(() -> { + try { + log.debug("[CombatHotkeys] Executing '{}'", actionName); + action.run(); + totalActionsSucceeded.incrementAndGet(); + log.debug("[CombatHotkeys] '{}' completed OK", actionName); + } catch (Exception ex) { + totalActionsFailed.incrementAndGet(); + lastError.set(actionName + ": " + ex.getMessage()); + log.error("[CombatHotkeys] '{}' threw an exception: {}", actionName, ex.getMessage(), ex); + } + }); } + /** Record which key was just pressed and log it. */ + private void recordKeyHit(String keyName) { + lastKeyReceived.set(keyName); + lastKeyTimestamp = Instant.now().toEpochMilli(); + log.debug("[CombatHotkeys] keyPressed matched: '{}' | loggedIn={} | thread={}", + keyName, + Microbot.isLoggedIn(), + Thread.currentThread().getName()); + } + + // ------------------------------------------------------------------------- + // KEY LISTENER + // ------------------------------------------------------------------------- + @Override public void keyTyped(KeyEvent e) { } @Override public void keyPressed(KeyEvent e) { - if (!Microbot.isLoggedIn()){ + // Refresh debug flag from config on every keypress so toggling it in + // the config panel takes effect immediately without a restart. + debugMode = config.debugMode(); + + if (!Microbot.isLoggedIn()) { + if (debugMode) { + log.debug("[CombatHotkeys] keyPressed — not logged in, ignoring (keyCode={})", e.getKeyCode()); + } return; } - if(config.dance().matches(e)){ + if (config.dance().matches(e)) { + recordKeyHit("dance"); e.consume(); script.dance = !script.dance; + log.debug("[CombatHotkeys] dance toggled -> {}", script.dance); } + // ------------------------------------------------------------------ + // OFFENSIVE PRAYERS + // ------------------------------------------------------------------ if (config.offensiveMeleeKey().matches(e)) { + recordKeyHit("offensiveMelee"); e.consume(); - Rs2Prayer.toggle(config.offensiveMeleePrayer().getPrayer()); + final Rs2PrayerEnum prayer = config.offensiveMeleePrayer().getPrayer(); + dispatch("toggle prayer " + prayer.getName(), () -> Rs2Prayer.toggle(prayer)); } if (config.offensiveRangeKey().matches(e)) { + recordKeyHit("offensiveRange"); e.consume(); - Rs2Prayer.toggle(config.offensiveRangePrayer().getPrayer()); + final Rs2PrayerEnum prayer = config.offensiveRangePrayer().getPrayer(); + dispatch("toggle prayer " + prayer.getName(), () -> Rs2Prayer.toggle(prayer)); } if (config.offensiveMagicKey().matches(e)) { + recordKeyHit("offensiveMagic"); e.consume(); - Rs2Prayer.toggle(config.offensiveMagicPrayer().getPrayer()); + final Rs2PrayerEnum prayer = config.offensiveMagicPrayer().getPrayer(); + dispatch("toggle prayer " + prayer.getName(), () -> Rs2Prayer.toggle(prayer)); } if (config.specialAttackKey().matches(e)) { + recordKeyHit("specialAttack"); e.consume(); - Microbot.getClientThread().runOnSeperateThread(() -> { - Rs2Combat.setSpecState(!Rs2Combat.getSpecState()); - return null; - }); + dispatch("toggle spec", () -> Rs2Combat.setSpecState(!Rs2Combat.getSpecState())); } + // ------------------------------------------------------------------ + // DEFENSIVE / PROTECTION PRAYERS + // ------------------------------------------------------------------ if (config.protectFromMagic().matches(e)) { + recordKeyHit("protectMagic"); e.consume(); - Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MAGIC); + dispatch("toggle prayer PROTECT_MAGIC", () -> Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MAGIC)); } if (config.protectFromMissles().matches(e)) { + recordKeyHit("protectRange"); e.consume(); - Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_RANGE); + dispatch("toggle prayer PROTECT_RANGE", () -> Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_RANGE)); } if (config.protectFromMelee().matches(e)) { + recordKeyHit("protectMelee"); e.consume(); - Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MELEE); + dispatch("toggle prayer PROTECT_MELEE", () -> Rs2Prayer.toggle(Rs2PrayerEnum.PROTECT_MELEE)); } + // ------------------------------------------------------------------ + // FOOD & POTIONS + // ------------------------------------------------------------------ if (config.eatBestFood().matches(e)) { + recordKeyHit("eatBestFood"); e.consume(); - Microbot.getClientThread().runOnSeperateThread(() -> { - Rs2Player.useFood(); - return null; - }); + dispatch("eat best food", Rs2Player::useFood); } if (config.eatFastFood().matches(e)) { + recordKeyHit("eatFastFood"); e.consume(); - Microbot.getClientThread().runOnSeperateThread(() -> { - Rs2Player.useFastFood(); - return null; - }); + dispatch("eat fast food", Rs2Player::useFastFood); } if (config.drinkPrayerPotion().matches(e)) { + recordKeyHit("drinkPrayerPotion"); e.consume(); - Microbot.getClientThread().runOnSeperateThread(() -> { - Rs2Player.drinkPrayerPotion(); - return null; - }); + dispatch("drink prayer potion", Rs2Player::drinkPrayerPotion); } + // ------------------------------------------------------------------ + // GEAR SWAPS + // ------------------------------------------------------------------ if (config.gear1().matches(e)) { + recordKeyHit("gear1"); e.consume(); - Microbot.getClientThread().runOnSeperateThread(() -> { - equipGear(config.gearList1()); - return null; - }); + final String list = config.gearList1(); + dispatch("equip gear 1", () -> equipGear(list)); } if (config.gear2().matches(e)) { + recordKeyHit("gear2"); e.consume(); - Microbot.getClientThread().runOnSeperateThread(() -> { - equipGear(config.gearList2()); - return null; - }); + final String list = config.gearList2(); + dispatch("equip gear 2", () -> equipGear(list)); } if (config.gear3().matches(e)) { + recordKeyHit("gear3"); e.consume(); - Microbot.getClientThread().runOnSeperateThread(() -> { - equipGear(config.gearList3()); - return null; - }); + final String list = config.gearList3(); + dispatch("equip gear 3", () -> equipGear(list)); } if (config.gear4().matches(e)) { + recordKeyHit("gear4"); e.consume(); - Microbot.getClientThread().runOnSeperateThread(() -> { - equipGear(config.gearList4()); - return null; - }); + final String list = config.gearList4(); + dispatch("equip gear 4", () -> equipGear(list)); } if (config.gear5().matches(e)) { + recordKeyHit("gear5"); e.consume(); - Microbot.getClientThread().runOnSeperateThread(() -> { - equipGear(config.gearList5()); - return null; - }); + final String list = config.gearList5(); + dispatch("equip gear 5", () -> equipGear(list)); } + // ------------------------------------------------------------------ + // ALCHEMY + // ------------------------------------------------------------------ if (config.highAlchemyKey().matches(e)) { + recordKeyHit("highAlchemy"); e.consume(); - Microbot.getClientThread().runOnSeperateThread(() -> { - Rs2Magic.alch(config.itemToAlch(),50, 75); - return null; - }); + final String item = config.itemToAlch(); + dispatch("high alch " + item, () -> Rs2Magic.alch(item, 50, 75)); } } - private void equipGear(String gearListConfig) { + if (gearListConfig == null || gearListConfig.isBlank()) { + log.warn("[CombatHotkeys] equipGear called with empty/null gear list"); + return; + } String[] itemIDs = gearListConfig.split(","); - for (String value : itemIDs) { - int itemId = Integer.parseInt(value); - Rs2Inventory.equip(itemId); - - int delay = Rs2Random.between(0, config.maxDelay()); - sleep(delay); + value = value.trim(); + if (value.isEmpty()) continue; + try { + int itemId = Integer.parseInt(value); + log.debug("[CombatHotkeys] Equipping item id={}", itemId); + Rs2Inventory.equip(itemId); + int delay = Rs2Random.between(0, config.maxDelay()); + sleep(delay); + } catch (NumberFormatException ex) { + log.error("[CombatHotkeys] Invalid item ID in gear list: '{}'", value); + lastError.set("bad gear ID: " + value); + } } } @Override public void keyReleased(KeyEvent e) {} + // ------------------------------------------------------------------------- + // MENU ENTRY EVENTS (dance tile marking) + // ------------------------------------------------------------------------- + @Subscribe public void onMenuEntryAdded(MenuEntryAdded event) { diff --git a/src/main/resources/net/runelite/client/plugins/microbot/butterflycatcher/docs/README.md b/src/main/resources/net/runelite/client/plugins/microbot/butterflycatcher/docs/README.md new file mode 100644 index 0000000000..e213532d98 --- /dev/null +++ b/src/main/resources/net/runelite/client/plugins/microbot/butterflycatcher/docs/README.md @@ -0,0 +1,43 @@ +# Butterfly Catcher +**Author:** StonksCode | **Version:** 1.0.0 + +Automates butterfly and moth catching for Hunter XP. Runs indefinitely with no banking required. + +--- + +## Supported Species + +| Species | Net Level | Barehanded Level | Location | +|---|---|---|---| +| Ruby Harvest | 5 | 15 | Puro-Puro / various | +| Sapphire Glacialis | 25 | 35 | Asgarnian Ice Dungeon area | +| Snowy Knight | 35 | 45 | Asgarnian Ice Dungeon area | +| Black Warlock | 45 | 55 | Feldip Hunter area | +| Sunlight Moth | 65 | 75 | Avium Savannah (Varlamore) | +| Moonlight Moth | 75 | 85 | Hunter Guild / Tonali Cavern (Varlamore) | + +--- + +## Setup + +1. Travel to a spawn location for your chosen species. +2. If using **Butterfly Net** mode, equip your net before starting. +3. Open the plugin config, select your species and catch mode. +4. Enable the plugin — it will run until you stop it. + +--- + +## Catch Modes + +**Barehanded** — Catch and instantly release. Nothing enters your inventory. Requires Hunter level 10 higher than the net threshold. + +**Butterfly Net** — Requires a Butterfly Net (id 10010) or Magic Butterfly Net (id 11259) to be equipped before starting. Allows catching at 10 levels lower than barehanded. + +--- + +## Notes + +- The plugin checks your Hunter level on startup and stops with a message if you don't meet the requirement. +- In Butterfly Net mode, the plugin also verifies your net is equipped before starting. +- No banking — the script runs indefinitely at your chosen spawn. +- Moonlight Moth supports all three location NPC variants (Hunter Guild, Neypotzli, Tonali Cavern). From 4d9b375249ea7bf89a0c48193c94d7c26daec25d Mon Sep 17 00:00:00 2001 From: stonksCode <99895926+stonksCode@users.noreply.github.com> Date: Thu, 21 May 2026 22:24:09 -0400 Subject: [PATCH 86/95] fix(combat-hotkeys): move prayer toggles off key event thread (#447) Prayer hotkeys were calling Rs2Prayer.toggle() directly on the key listener thread, causing focus loss and input lag on every press. Replaced runOnSeperateThread (silently drops calls when the shared ClientThread future is busy) with a plugin-owned ExecutorService. Also added a debug overlay panel toggled via config. Bump to v1.1.2. Co-authored-by: chsami From a4fe1aa4357f6bc6cab04d1dee99384766269297 Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Thu, 21 May 2026 22:24:36 -0400 Subject: [PATCH 87/95] feat(birdhouse): disable teleports on Fossil Island, force southern rowboat, chain seaweed plugin (#446) - Set Rs2Walker.disableTeleports=true once on Fossil Island so the digsite pendant is only consumed once (for the initial teleport) - Walk to southern rowboat before banking to avoid the longer northern route - Wire up "Start Giant Seaweed after run" config to launch GiantSeaweedFarmerPlugin on birdhouse run completion - Fix withdrawSeeds false warning: use real stack quantity instead of Rs2Inventory.count() slot count, and fail fast if <40 seeds - Add debug section with override start state for testing - Bump version to 1.1.2 Co-authored-by: runsonmypc --- .../FornBirdhouseRunsConfig.java | 41 +++++++++++++++++++ .../FornBirdhouseRunsPlugin.java | 2 +- .../FornBirdhouseRunsScript.java | 40 ++++++++++++++---- .../java/net/runelite/client/Microbot.java | 6 ++- 4 files changed, 78 insertions(+), 11 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsConfig.java b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsConfig.java index 9666210874..48a0005ea6 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsConfig.java @@ -86,4 +86,45 @@ default boolean goToBank() { return false; } + @ConfigItem( + keyName = "startGiantSeaweedAfter", + name = "Start Giant Seaweed after run", + description = "When the birdhouse run finishes, start the TaF Giant Seaweed plugin (if installed)", + section = optionsSection, + position = 1 + ) + default boolean startGiantSeaweedAfter() { + return false; + } + + @ConfigSection( + name = "Debug", + description = "Debug and testing options", + position = 3, + closedByDefault = true + ) + String debugSection = "debug"; + + @ConfigItem( + keyName = "enableOverrideStartState", + name = "Enable Override Start State", + description = "When enabled, the plugin will skip to the selected state on startup", + section = debugSection, + position = 0 + ) + default boolean enableOverrideStartState() { + return false; + } + + @ConfigItem( + keyName = "overrideStartState", + name = "Override Start State", + description = "Skip to a specific state instead of starting from the beginning", + section = debugSection, + position = 1 + ) + default FornBirdhouseRunsInfo.states overrideStartState() { + return FornBirdhouseRunsInfo.states.GEARING; + } + } diff --git a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsPlugin.java index 6906635900..3606efdb99 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsPlugin.java @@ -26,7 +26,7 @@ ) @Slf4j public class FornBirdhouseRunsPlugin extends Plugin { - final static String version = "1.1.1"; + final static String version = "1.1.2"; @Provides FornBirdhouseRunsConfig provideConfig(ConfigManager configManager) { return configManager.getConfig(FornBirdhouseRunsConfig.class); diff --git a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java index d5c75785dc..874bceefab 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java @@ -39,6 +39,7 @@ public class FornBirdhouseRunsScript extends Script { private static final WorldPoint birdhouseLocation2 = new WorldPoint(3768, 3761, 0); private static final WorldPoint birdhouseLocation3 = new WorldPoint(3677, 3882, 0); private static final WorldPoint birdhouseLocation4 = new WorldPoint(3679, 3815, 0); + private static final WorldPoint SOUTH_ROWBOAT = new WorldPoint(3724, 3807, 0); // Each location maps to a BIRDHOUSE_TRANSMIT_* varp. See isEmpty/isBuilt/isSeeded // below for the canonical state decoding (matches RuneLite's BirdHouseState). private static final int VARP_HOUSE_1 = VarPlayerID.BIRDHOUSE_TRANSMIT_D; // Verdant SW @@ -105,8 +106,12 @@ public boolean run() { return; } initialized = true; - - if (config.useInventorySetup()) { + + states startOverride = config.enableOverrideStartState() ? config.overrideStartState() : states.GEARING; + if (startOverride != states.GEARING) { + log.info("Override start state → {}", startOverride); + botStatus = startOverride; + } else if (config.useInventorySetup()) { boolean hasInventorySetup = config.inventorySetup() != null && Rs2InventorySetup.isInventorySetup(config.inventorySetup().getName()); if (hasInventorySetup) { var inventorySetup = new Rs2InventorySetup(config.inventorySetup(), mainScheduledFuture); @@ -134,10 +139,17 @@ public boolean run() { } else { log.info("Inventory already prepared — skipping bank trip"); } - botStatus = states.TELEPORTING; + if (startOverride == states.GEARING) { + botStatus = states.TELEPORTING; + } } if (!super.run()) return; + if (!Rs2Walker.disableTeleports && isOnFossilIsland()) { + Rs2Walker.disableTeleports = true; + log.info("On Fossil Island — disabling teleports for remaining walks"); + } + boolean advanced = true; while (advanced) { advanced = false; @@ -244,6 +256,7 @@ public boolean run() { emptyNests(); if (config.goToBank()) { + Rs2Walker.walkTo(SOUTH_ROWBOAT, 3); Rs2Walker.walkTo(BankLocation.FOSSIL_ISLAND_WRECK.getWorldPoint()); if (!Rs2Bank.isOpen()) Rs2Bank.openBank(); Rs2Bank.depositAll(); @@ -252,6 +265,14 @@ public boolean run() { botStatus = states.FINISHED; notifier.notify(Notification.ON, "Birdhouse run is finished."); log.info("Birdhouse run finished — disabling plugin."); + + if (config.startGiantSeaweedAfter()) { + log.info("Starting Giant Seaweed Farmer plugin"); + if (!Microbot.startPlugin("net.runelite.client.plugins.microbot.GiantSeaweedFarmer.GiantSeaweedFarmerPlugin")) { + log.warn("Failed to start Giant Seaweed Farmer — is it installed?"); + } + } + Microbot.stopPlugin(plugin); break; case FINISHED: @@ -287,6 +308,7 @@ private void emptyNests() { @Override public void shutdown() { super.shutdown(); + Rs2Walker.disableTeleports = false; initialized = false; botStatus = states.TELEPORTING; lastObservedStatus = null; @@ -643,13 +665,13 @@ private boolean withdrawSeeds() { return false; } Rs2Inventory.waitForInventoryChanges(3000); - int invAfter = Rs2Inventory.count(bankSeed.getId()); - int invAfterByName = findInventoryBirdhouseSeed(1).map(Rs2ItemModel::getQuantity).orElse(0); - log.info("withdrawSeeds: withdrew 40 {} (id={}); inv after = {} of id={} (by-name lookup = {})", - bankSeed.getName(), bankSeed.getId(), invAfter, bankSeed.getId(), invAfterByName); + int invAfter = findInventoryBirdhouseSeed(1).map(Rs2ItemModel::getQuantity).orElse(0); + log.info("withdrawSeeds: withdrew 40 {} (id={}); inv seed qty = {}", + bankSeed.getName(), bankSeed.getId(), invAfter); if (invAfter < 40) { - log.warn("withdrawSeeds: inventory count of id={} after withdraw is {} (<40). Full inv: [{}]", - bankSeed.getId(), invAfter, dumpInventory()); + setupErrorMessage = "Withdrew seeds but only got " + invAfter + " (need 40)"; + log.error(setupErrorMessage); + return false; } return true; } diff --git a/src/test/java/net/runelite/client/Microbot.java b/src/test/java/net/runelite/client/Microbot.java index afcf25983c..debcae7bac 100644 --- a/src/test/java/net/runelite/client/Microbot.java +++ b/src/test/java/net/runelite/client/Microbot.java @@ -5,9 +5,11 @@ import java.util.stream.Collectors; import net.runelite.client.plugins.fishing.FishingPlugin; +import net.runelite.client.plugins.microbot.GiantSeaweedFarmer.GiantSeaweedFarmerPlugin; import net.runelite.client.plugins.microbot.agentserver.AgentServerPlugin; import net.runelite.client.plugins.microbot.aiofighter.AIOFighterPlugin; import net.runelite.client.plugins.microbot.astralrc.AstralRunesPlugin; +import net.runelite.client.plugins.microbot.birdhouseruns.FornBirdhouseRunsPlugin; import net.runelite.client.plugins.microbot.autofishing.AutoFishingPlugin; import net.runelite.client.plugins.microbot.crafting.jewelry.JewelryPlugin; import net.runelite.client.plugins.microbot.example.ExamplePlugin; @@ -23,7 +25,9 @@ public class Microbot private static final Class[] debugPlugins = { AIOFighterPlugin.class, - AgentServerPlugin.class + AgentServerPlugin.class, + FornBirdhouseRunsPlugin.class, + GiantSeaweedFarmerPlugin.class }; public static void main(String[] args) throws Exception From ecebab1272b94c6fd8c11bc9573108cbc62ab922 Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Thu, 21 May 2026 22:24:57 -0400 Subject: [PATCH 88/95] fix(GiantSeaweedFarmer): broken state detection, stale state, unclean shutdown (#445) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(GiantSeaweedFarmer): replace broken varbit state detection with action+name approach getSeaweedPatchState() read the varbit from the impostor composition instead of the base object, returning garbage values that always mapped to "Weeds" regardless of actual patch state. This caused infinite loops after harvesting. Replace with getPatchState() that checks the impostor's actions and name: Pick→Harvestable, Rake→Weeds, Clear→Dead, Cure→Diseased, "Seaweed patch" (no Rake)→Empty, "Seaweed"→Growing. Also fixes: Harvestable case sleepUntil waited for "Empty" (varbit 3) but post-harvest patches go to Weeds first, causing 20s timeouts every cycle. Now waits for not-Harvestable which resolves immediately. * fix(GiantSeaweedFarmer): reset all state on plugin restart BankSuccess, GSF_Running, and other fields were not reset in run(), so restarting the plugin reused stale state from the previous run (e.g. BankSuccess=true skipped banking entirely). * fix(GiantSeaweedFarmer): clean shutdown via Microbot.stopPlugin shutdownSequence() now calls Microbot.stopPlugin() which toggles the plugin off in the client UI, stops both the farmer and spore scripts, and removes the overlay — instead of just cancelling the script future while leaving everything else running. Also: reset BankSuccess in shutdown(), remove dead safetyCheck() method, remove redundant GSF_Running assignment in plugin startUp(). * fix(GiantSeaweedFarmer): fix typo and wrong description in override config --------- Co-authored-by: runsonmypc --- .../GiantSeaweedFarmerConfig.java | 4 +- .../GiantSeaweedFarmerPlugin.java | 1 - .../GiantSeaweedFarmerScript.java | 165 +++++++----------- 3 files changed, 62 insertions(+), 108 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerConfig.java b/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerConfig.java index e5f976c056..5d4bc2c5da 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerConfig.java @@ -24,8 +24,8 @@ enum modeType { @ConfigItem( keyName = "override", - name = "Overide Start State?", - description = "Should we use digsite pendant from inventory/bank?", + name = "Override Start State?", + description = "Skip to a specific state instead of starting from Banking", position = 0 ) default boolean override() { diff --git a/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerPlugin.java index 14a065b279..5232cfcdb3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerPlugin.java @@ -46,7 +46,6 @@ protected String getTimeRunning() { @Override protected void startUp() throws AWTException { scriptStartTime = Instant.now(); - giantSeaweedFarmerScript.GSF_Running = true; if (overlayManager != null) { overlayManager.add(giantSeaweedFarmerOverlay); } diff --git a/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerScript.java b/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerScript.java index a5ca3f85dd..7a34115fe2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/GiantSeaweedFarmer/GiantSeaweedFarmerScript.java @@ -1,20 +1,20 @@ package net.runelite.client.plugins.microbot.GiantSeaweedFarmer; +import net.runelite.api.ObjectComposition; import net.runelite.api.Skill; -import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.api.tileobject.models.Rs2TileObjectModel; 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.bank.enums.BankLocation; 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.util.keyboard.Rs2Keyboard; -import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -37,11 +37,8 @@ public class GiantSeaweedFarmerScript extends Script { public static GiantSeaweedFarmerStatus BOT_STATE = GiantSeaweedFarmerStatus.BANKING; public boolean GSF_Running = true; private boolean BankSuccess = false; - private TileObject currentPatch; private List handledPatches = new ArrayList<>(); private final List patches = List.of(30500, 30501); - private static int lastVarbitValue = -1; - private long lastApparatusCheck = System.currentTimeMillis(); private static final WorldPoint FossilIslandDiveChest = new WorldPoint(3766, 3899, 0); private static final WorldPoint FarmGuildSpiritTree = new WorldPoint(1250, 3749, 0); @@ -58,6 +55,13 @@ public GiantSeaweedFarmerScript(GiantSeaweedFarmerPlugin giantSeaweedPlugin) { public boolean run(GiantSeaweedFarmerConfig config) { Microbot.enableAutoRunOn = false; this.config = config; + + BOT_STATE = GiantSeaweedFarmerStatus.BANKING; + BankSuccess = false; + GSF_Running = true; + handledPatches.clear(); + inCriticalSection = false; + if (config.useAntiBan()){GSF_AntiBan_Setup();} if (config.override()) { @@ -160,48 +164,23 @@ private void getToFossilIsland() { } - // Track last varbit value to reduce log spam - - - // Using official RuneLite varbit ranges for seaweed patches - private static String getSeaweedPatchState(int patchId) { - var game_obj = Rs2GameObject.getObjectComposition(patchId); - if (game_obj == null) return "Empty"; - var varbitValue = Microbot.getVarbitValue(game_obj.getVarbitId()); - - // Only log when varbit value changes - if (varbitValue != lastVarbitValue) { - Microbot.log("Seaweed patch varbit value changed: " + lastVarbitValue + " -> " + varbitValue); - lastVarbitValue = varbitValue; - } - - // Official RuneLite varbit ranges for SEAWEED patches from PatchImplementation.java - // Note: varbit 3 means fully raked (0 rakes remaining) so it's ready for planting - if (varbitValue == 3) { - return "Empty"; // Fully raked, ready for planting - } - - if ((varbitValue >= 0 && varbitValue <= 2) || (varbitValue >= 17 && varbitValue <= 255)) { - return "Weeds"; // Needs raking (0=full weeds, 1=partial, 2=almost done) - } - - if (varbitValue >= 4 && varbitValue <= 7) { - return "Growing"; - } - - if (varbitValue >= 8 && varbitValue <= 10) { - return "Harvestable"; - } - - if (varbitValue >= 11 && varbitValue <= 13) { - return "Diseased"; - } - - if (varbitValue >= 14 && varbitValue <= 16) { - return "Dead"; // Needs clearing - Dead seaweed objects 30497,30498,30499 - } - - return "Empty"; + private static String getPatchState(Rs2TileObjectModel objModel) { + if (objModel == null) return "Unknown"; + ObjectComposition comp = objModel.getObjectComposition(); + if (comp == null) return "Unknown"; + String[] actions = comp.getActions(); + if (actions == null) return "Unknown"; + for (String action : actions) { + if (action == null) continue; + if (action.equalsIgnoreCase("Pick")) return "Harvestable"; + if (action.equalsIgnoreCase("Cure")) return "Diseased"; + if (action.equalsIgnoreCase("Clear")) return "Dead"; + if (action.equalsIgnoreCase("Rake")) return "Weeds"; + } + String name = comp.getName(); + if (name != null && name.equalsIgnoreCase("Seaweed patch")) return "Empty"; + if (name != null && name.equalsIgnoreCase("Seaweed")) return "Growing"; + return "Unknown"; } @@ -381,34 +360,10 @@ private boolean handlePatch(int patchId) { if (objModel == null) return false; final var patchObjModel = objModel; - var state = getSeaweedPatchState(patchId); + var state = getPatchState(patchObjModel); logDebug("Patch state detected as: " + state); switch (state) { - case "Empty": - inCriticalSection = true; - try { - boolean hasCompost = Rs2Inventory.contains("compost") || - Rs2Inventory.contains("Supercompost") || - Rs2Inventory.contains("Ultracompost") || - Rs2Inventory.contains("Bottomless compost bucket"); - - if (hasCompost) { - Rs2Inventory.use("compost"); - patchObjModel.click("Compost"); - Rs2Player.waitForXpDrop(Skill.FARMING); - } - - if (Rs2Inventory.contains("seaweed spore")) { - Rs2Inventory.use(" spore"); - patchObjModel.click("Plant"); - sleepUntil(() -> getSeaweedPatchState(patchId).equals("Growing"), 10000); - } - return true; - } finally { - inCriticalSection = false; - } case "Harvestable": - if (config.FarmingCape()) { if (Rs2Inventory.contains("Farming cape") && !Rs2Equipment.isWearing("Farming cape")) { Rs2Inventory.interact("Farming cape", "Wear"); @@ -421,10 +376,7 @@ private boolean handlePatch(int patchId) { } patchObjModel.click("Pick"); - sleepUntil(() -> { - String currentState = getSeaweedPatchState(patchId); - return currentState.equals("Empty") || Rs2Inventory.isFull(); - }, 20000); + sleepUntil(() -> !getPatchState(patchObjModel).equals("Harvestable") || Rs2Inventory.isFull(), 20000); if (!Rs2Equipment.isWearing("Diving apparatus") && Rs2Inventory.contains("Diving apparatus")) { Rs2Inventory.interact("Diving apparatus", "Wear"); @@ -433,24 +385,42 @@ private boolean handlePatch(int patchId) { return false; case "Weeds": patchObjModel.click("Rake"); - sleepUntil(() -> { - String currentState = getSeaweedPatchState(patchId); - return !currentState.equals("Weeds"); - }, 10000); + sleepUntil(() -> !getPatchState(patchObjModel).equals("Weeds"), 15000); return false; + case "Empty": + inCriticalSection = true; + try { + boolean hasCompost = Rs2Inventory.contains("compost") || + Rs2Inventory.contains("Supercompost") || + Rs2Inventory.contains("Ultracompost") || + Rs2Inventory.contains("Bottomless compost bucket"); + + if (hasCompost) { + Rs2Inventory.use("compost"); + patchObjModel.click("Compost"); + Rs2Player.waitForXpDrop(Skill.FARMING); + } + + if (Rs2Inventory.contains("seaweed spore")) { + Rs2Inventory.use(" spore"); + patchObjModel.click("Plant"); + sleepUntil(() -> getPatchState(patchObjModel).equals("Growing"), 10000); + } + return true; + } finally { + inCriticalSection = false; + } case "Dead": patchObjModel.click("Clear"); - sleepUntil(() -> { - String currentState = getSeaweedPatchState(patchId); - return !currentState.equals("Dead"); - }, 10000); + sleepUntil(() -> !getPatchState(patchObjModel).equals("Dead"), 10000); return false; case "Diseased": Microbot.showMessage("Diseased patch! Please turn off the script and then cure me manually as i cant do this automatically yet."); return false; - default: - currentPatch = null; + case "Growing": return true; + default: + return false; } } @@ -479,22 +449,6 @@ private void returnToBank() { } - private void safetyCheck() { - if (Rs2Player.getWorldLocation().getPlane() != 1) return; // only underwater - - if (!Rs2Equipment.isWearing("Diving apparatus")) { - // if > 2000 ms without apparatus underwater, force equip - if (System.currentTimeMillis() - lastApparatusCheck > 2000) { - if (Rs2Inventory.contains("Diving apparatus")) { - Rs2Inventory.interact("Diving apparatus", "Wear"); - Microbot.log("SAFETY: Diving apparatus re-equipped automatically!"); - } - } - } else { - lastApparatusCheck = System.currentTimeMillis(); - } - } - private void GSF_AntiBan_Setup(){ Microbot.enableAutoRunOn = false; Rs2Antiban.resetAntibanSettings(); @@ -520,15 +474,16 @@ private void logDebug(String msg) { } private void shutdownSequence(){ - this.shutdown(); + Microbot.stopPlugin(giantSeaweedPlugin); } @Override public void shutdown() { GSF_Running = false; BOT_STATE = GiantSeaweedFarmerStatus.IDLE; + BankSuccess = false; handledPatches = new ArrayList<>(); - inCriticalSection = false; // Ensure flag is cleared on shutdown + inCriticalSection = false; super.shutdown(); } } From d7e70e47cc4fc42654e236dfe6d5d15c3b5a81f4 Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Thu, 21 May 2026 22:25:20 -0400 Subject: [PATCH 89/95] fix(FarmTreeRun): remove isAnimating, shared leprechaun helper, withdrawal bugfix (#444) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(FarmTreeRun): remove isAnimating checks, delete planning docs, bump version - Remove all Rs2Player.isAnimating() checks from the script — they cause stalls and are unreliable for gating plugin logic. - Replace animation waits in handleRakeAction/handleClearAction with waitForXpDrop(Skill.FARMING) which is the actual completion signal. - Replace animation wait in bank() with sleepUntil(Rs2Bank::isOpen). - Delete docs/superpowers/ planning artifacts accidentally committed. - Bump plugin version to 1.2.0. * feat(FarmTreeRun): category toggles, proper shutdown, rake fix - Add "Run trees", "Run fruit trees", "Run hardwood trees" toggles in the sapling selection section to skip entire tree categories at once. - Plugin properly disables itself via Microbot.stopPlugin() on shutdown. - Increase rake XP drop wait to 10s and drop weeds immediately after raking instead of in the general drop loop. * feat(FarmTreeRun): shared Rs2Leprechaun helper, fix duplicate-item withdrawal bug Extract leprechaun compost withdrawal into shared Rs2Leprechaun utility (included in every plugin JAR alongside PluginConstants). Merge duplicate itemId entries before bank withdrawal so overlapping protection payments (e.g. Willow + Banana both needing baskets of apples) sum correctly. --------- Co-authored-by: runsonmypc --- .../2026-05-17-tree-runner-compost-types.md | 319 ------------------ ...-05-17-tree-runner-compost-types-design.md | 72 ---- gradle/plugin-utils.gradle | 1 + .../plugins/microbot/Rs2Leprechaun.java | 91 +++++ .../farmtreerun/FarmTreeRunConfig.java | 33 +- .../farmtreerun/FarmTreeRunPlugin.java | 2 +- .../farmtreerun/FarmTreeRunScript.java | 130 +++---- 7 files changed, 168 insertions(+), 480 deletions(-) delete mode 100644 docs/superpowers/plans/2026-05-17-tree-runner-compost-types.md delete mode 100644 docs/superpowers/specs/2026-05-17-tree-runner-compost-types-design.md create mode 100644 src/main/java/net/runelite/client/plugins/microbot/Rs2Leprechaun.java diff --git a/docs/superpowers/plans/2026-05-17-tree-runner-compost-types.md b/docs/superpowers/plans/2026-05-17-tree-runner-compost-types.md deleted file mode 100644 index bbff2239cb..0000000000 --- a/docs/superpowers/plans/2026-05-17-tree-runner-compost-types.md +++ /dev/null @@ -1,319 +0,0 @@ -# Farm Tree Runner: Compost Type Selection — Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Replace the boolean compost toggle with a dropdown supporting Compost, Supercompost, Ultracompost, and Bottomless Compost Bucket, with automatic quantity calculation and empty-bucket cleanup. - -**Architecture:** New `CompostType` enum holds item IDs and a reusable flag. Config swaps the boolean for a dropdown. Script banking calculates withdrawal quantity based on unprotected patch count. `handlePlantingTree()` drops empty buckets after consumable compost use. - -**Tech Stack:** Java 11, RuneLite plugin API, Lombok - ---- - -### Task 1: Create `CompostType` enum - -**Files:** -- Create: `src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/CompostType.java` - -- [ ] **Step 1: Create the enum file** - -```java -package net.runelite.client.plugins.microbot.farmtreerun.enums; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import net.runelite.api.gameval.ItemID; - -@Getter -@RequiredArgsConstructor -public enum CompostType { - NONE("None", -1, false), - COMPOST("Compost", ItemID.COMPOST, false), - SUPERCOMPOST("Supercompost", ItemID.SUPERCOMPOST, false), - ULTRACOMPOST("Ultracompost", ItemID.ULTRACOMPOST, false), - BOTTOMLESS_BUCKET("Bottomless bucket", ItemID.BOTTOMLESS_COMPOST_BUCKET_22997, true); - - private final String name; - private final int itemId; - private final boolean reusable; - - @Override - public String toString() { - return name; - } -} -``` - -- [ ] **Step 2: Build to verify compilation** - -Run: `cd /home/alex/Developer/MB/Microbot-Hub/.worktrees/tree-runner-compost && ./gradlew build -PpluginList=FarmTreeRunPlugin` -Expected: BUILD SUCCESSFUL - -- [ ] **Step 3: Commit** - -```bash -git add src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/CompostType.java -git commit -m "feat(FarmTreeRun): add CompostType enum for compost selection" -``` - ---- - -### Task 2: Update config to use `CompostType` dropdown - -**Files:** -- Modify: `src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunConfig.java` - -- [ ] **Step 1: Add import for `CompostType`** - -Add to the import block: - -```java -import net.runelite.client.plugins.microbot.farmtreerun.enums.CompostType; -``` - -- [ ] **Step 2: Replace the `useCompost()` boolean config with `compostType()` dropdown** - -Replace this block (lines 163-171): - -```java - @ConfigItem( - keyName = "useCompost", - name = "Use compost", - description = "Only bottomless compost bucket is supported", - position = 1, - section = gearSection - ) - default boolean useCompost() { return true; } -``` - -With: - -```java - @ConfigItem( - keyName = "compostType", - name = "Compost type", - description = "Select compost type. Only applied at patches without protection enabled.", - position = 1, - section = gearSection - ) - default CompostType compostType() { return CompostType.NONE; } -``` - -- [ ] **Step 3: Update `@ConfigInformation` HTML** - -In the `@ConfigInformation` annotation, replace: - -``` -
  5. Filled Bottomless compost bucket
  6. -``` - -With: - -``` -
  7. Compost / Supercompost / Ultracompost / Bottomless compost bucket
  8. -``` - -- [ ] **Step 4: Build to verify compilation** - -Run: `cd /home/alex/Developer/MB/Microbot-Hub/.worktrees/tree-runner-compost && ./gradlew build -PpluginList=FarmTreeRunPlugin` -Expected: BUILD FAILURE — `FarmTreeRunScript.java` still references `config.useCompost()`. This confirms the config change is wired in and the script needs updating next. - -- [ ] **Step 5: Commit** - -```bash -git add src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunConfig.java -git commit -m "feat(FarmTreeRun): replace useCompost boolean with compostType dropdown" -``` - ---- - -### Task 3: Update script to use `CompostType` - -**Files:** -- Modify: `src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java` - -- [ ] **Step 1: Add import for `CompostType`** - -Add to the import block: - -```java -import net.runelite.client.plugins.microbot.farmtreerun.enums.CompostType; -``` - -- [ ] **Step 2: Update `isCompostEnabled()` method (line ~998)** - -The method currently reads: - -```java -private boolean isCompostEnabled(FarmTreeRunConfig config) { - if (!config.useCompost()) - return false; - - if (!getSelectedTreePatches(config).isEmpty() && !config.protectTrees()) - return true; - - if (!getSelectedHardTreePatches(config).isEmpty() && !config.protectHardTrees()) - return true; - - return !getSelectedFruitTreePatches(config).isEmpty() && !config.protectFruitTrees(); -} -``` - -Replace `config.useCompost()` with `config.compostType() == CompostType.NONE`: - -```java -private boolean isCompostEnabled(FarmTreeRunConfig config) { - if (config.compostType() == CompostType.NONE) - return false; - - if (!getSelectedTreePatches(config).isEmpty() && !config.protectTrees()) - return true; - - if (!getSelectedHardTreePatches(config).isEmpty() && !config.protectHardTrees()) - return true; - - return !getSelectedFruitTreePatches(config).isEmpty() && !config.protectFruitTrees(); -} -``` - -- [ ] **Step 3: Update the non-banking path (line ~134-135)** - -Replace: - -```java -if (isCompostEnabled(config)) { - compostItemId = ItemID.BOTTOMLESS_COMPOST_BUCKET_22997; -} -``` - -With: - -```java -if (isCompostEnabled(config)) { - compostItemId = config.compostType().getItemId(); -} -``` - -- [ ] **Step 4: Update banking compost withdrawal block (lines ~460-467)** - -Replace: - -```java -if (isCompostEnabled(config)) { - if (Rs2Bank.hasItem(ItemID.BOTTOMLESS_COMPOST_BUCKET_22997)) { - compostItemId = ItemID.BOTTOMLESS_COMPOST_BUCKET_22997; - items.add(new FarmingItem(compostItemId, 1)); - } else { - Microbot.log("Only bottomless compost is supported. Skipping composting."); - } -} -``` - -With: - -```java -if (isCompostEnabled(config)) { - CompostType compostType = config.compostType(); - compostItemId = compostType.getItemId(); - if (compostType.isReusable()) { - if (Rs2Bank.hasItem(compostItemId)) { - items.add(new FarmingItem(compostItemId, 1)); - } else { - Microbot.log("Bottomless compost bucket not found in bank. Skipping composting."); - compostItemId = null; - } - } else { - int unprotectedCount = 0; - if (!config.protectTrees()) - unprotectedCount += getSelectedTreePatches(config).size(); - if (!config.protectFruitTrees()) - unprotectedCount += getSelectedFruitTreePatches(config).size(); - if (!config.protectHardTrees()) - unprotectedCount += getSelectedHardTreePatches(config).size(); - if (unprotectedCount > 0) { - if (Rs2Bank.hasItem(compostItemId)) { - items.add(new FarmingItem(compostItemId, unprotectedCount)); - } else { - Microbot.log("Selected compost not found in bank. Skipping composting."); - compostItemId = null; - } - } else { - compostItemId = null; - } - } -} -``` - -- [ ] **Step 5: Update `useCompostOnPatch()` method (lines ~927-938)** - -Replace: - -```java -private boolean useCompostOnPatch(FarmTreeRunConfig config, Patch patch) { - if (!config.useCompost() || compostItemId == null) - return false; - - if (!config.protectTrees() && patch.kind == TreeKind.TREE) - return true; - - if (!config.protectHardTrees() && patch.kind == TreeKind.HARD_TREE) - return true; - - return !config.protectFruitTrees() && patch.kind == TreeKind.FRUIT_TREE; -} -``` - -With: - -```java -private boolean useCompostOnPatch(FarmTreeRunConfig config, Patch patch) { - if (config.compostType() == CompostType.NONE || compostItemId == null) - return false; - - if (!config.protectTrees() && patch.kind == TreeKind.TREE) - return true; - - if (!config.protectHardTrees() && patch.kind == TreeKind.HARD_TREE) - return true; - - return !config.protectFruitTrees() && patch.kind == TreeKind.FRUIT_TREE; -} -``` - -- [ ] **Step 6: Add empty bucket drop in `handlePlantingTree()` (after line ~810)** - -After the compost application block, add a bucket drop for consumable compost. The current code: - -```java -if (useCompostOnPatch(config, patch)) { - Rs2Inventory.useItemOnObject(compostItemId, treePatch.getId()); - Rs2Player.waitForXpDrop(Skill.FARMING, 2000); - sleep(550, 2200); -} -``` - -Replace with: - -```java -if (useCompostOnPatch(config, patch)) { - Rs2Inventory.useItemOnObject(compostItemId, treePatch.getId()); - Rs2Player.waitForXpDrop(Skill.FARMING, 2000); - sleep(550, 2200); - if (!config.compostType().isReusable() && Rs2Inventory.hasItem(ItemID.BUCKET)) { - Rs2Inventory.drop(ItemID.BUCKET); - sleep(300, 600); - } -} -``` - -- [ ] **Step 7: Build to verify compilation** - -Run: `cd /home/alex/Developer/MB/Microbot-Hub/.worktrees/tree-runner-compost && ./gradlew build -PpluginList=FarmTreeRunPlugin` -Expected: BUILD SUCCESSFUL - -- [ ] **Step 8: Commit** - -```bash -git add src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java -git commit -m "feat(FarmTreeRun): support all compost types with qty calc and bucket drop" -``` diff --git a/docs/superpowers/specs/2026-05-17-tree-runner-compost-types-design.md b/docs/superpowers/specs/2026-05-17-tree-runner-compost-types-design.md deleted file mode 100644 index 0828f5f9f2..0000000000 --- a/docs/superpowers/specs/2026-05-17-tree-runner-compost-types-design.md +++ /dev/null @@ -1,72 +0,0 @@ -# Farm Tree Runner: Compost Type Selection - -## Summary - -Replace the boolean `useCompost` config (which only supported bottomless compost bucket) with a dropdown enum that supports all four compost types: Compost, Supercompost, Ultracompost, and Bottomless Compost Bucket. Compost is only applied at patches where protection is not enabled. - -## New Enum: `CompostType` - -File: `src/main/java/net/runelite/client/plugins/microbot/farmtreerun/enums/CompostType.java` - -| Value | ItemID constant | ID | Reusable | Notes | -|-------|----------------|----|----------|-------| -| `NONE` | — | -1 | — | No compost | -| `COMPOST` | `COMPOST` | 6032 | No | Regular compost | -| `SUPERCOMPOST` | `SUPERCOMPOST` | 6034 | No | Supercompost | -| `ULTRACOMPOST` | `ULTRACOMPOST` | 21483 | No | Ultracompost | -| `BOTTOMLESS_BUCKET` | `BOTTOMLESS_COMPOST_BUCKET_22997` | 22997 | Yes | Reusable, withdraw 1 | - -Fields: `String name`, `int itemId`, `boolean reusable`. - -## Config Change - -File: `FarmTreeRunConfig.java` - -- Remove `useCompost()` boolean (keyName `"useCompost"`) -- Add `compostType()` returning `CompostType`, default `NONE` - - keyName: `"compostType"` (new key; old `useCompost` values ignored safely) - - Section: `gearSection`, position 1 - - Description: "Select compost type. Only applied at patches without protection enabled." - -## Script Changes - -File: `FarmTreeRunScript.java` - -### `isCompostEnabled(config)` - -Change from `config.useCompost()` to `config.compostType() != CompostType.NONE`. - -### `useCompostOnPatch(config, patch)` - -Change `config.useCompost()` check to `config.compostType() != CompostType.NONE`. Rest of the method (protection gating per tree kind) stays identical. - -### Banking: compost withdrawal block - -Replace the current bottomless-only block: - -1. If `compostType == NONE`: skip, `compostItemId = null`. -2. If `BOTTOMLESS_BUCKET`: check bank for item 22997, withdraw 1. Same as current. -3. If `COMPOST / SUPERCOMPOST / ULTRACOMPOST`: count unprotected patches across all three tree categories (regular trees if `!protectTrees()`, fruit trees if `!protectFruitTrees()`, hardwood if `!protectHardTrees()`). Withdraw that count of the selected compost item ID. - -Unprotected patch count reuses the existing `getSelectedTreePatches()`, `getSelectedFruitTreePatches()`, `getSelectedHardTreePatches()` methods — sum their sizes, filtered by protection config. - -### `handlePlantingTree()`: drop empty bucket after use - -After applying consumable compost (not bottomless) and waiting for the Farming XP drop, drop the resulting empty bucket (`ItemID.BUCKET`, 1925) to free the inventory slot before planting the sapling. - -### Non-banking path - -The `else` branch (when `config.banking()` is false) currently hardcodes `compostItemId = ItemID.BOTTOMLESS_COMPOST_BUCKET_22997`. Change to set `compostItemId = config.compostType().getItemId()` (or `null` if `NONE`). - -## Files Touched - -| File | Action | -|------|--------| -| `enums/CompostType.java` | New | -| `FarmTreeRunConfig.java` | Modify: replace boolean with enum dropdown | -| `FarmTreeRunScript.java` | Modify: banking qty logic, `isCompostEnabled`, `useCompostOnPatch`, `handlePlantingTree` bucket drop, non-banking path | - -## Config Information Update - -Update the `@ConfigInformation` HTML on the config interface: -- Change "Filled Bottomless compost bucket" in the Optional items list to "Compost / Supercompost / Ultracompost / Bottomless compost bucket" diff --git a/gradle/plugin-utils.gradle b/gradle/plugin-utils.gradle index 7e17bcecb0..de272f7f2c 100644 --- a/gradle/plugin-utils.gradle +++ b/gradle/plugin-utils.gradle @@ -36,6 +36,7 @@ ext { java { srcDirs = ["src/main/java"] include "${project.ext.getPluginsIncludePath()}/PluginConstants.java" + include "${project.ext.getPluginsIncludePath()}/Rs2Leprechaun.java" include "${project.ext.getPluginsIncludePath()}/${plugin.dir.name}/**" } resources { diff --git a/src/main/java/net/runelite/client/plugins/microbot/Rs2Leprechaun.java b/src/main/java/net/runelite/client/plugins/microbot/Rs2Leprechaun.java new file mode 100644 index 0000000000..7ea9943381 --- /dev/null +++ b/src/main/java/net/runelite/client/plugins/microbot/Rs2Leprechaun.java @@ -0,0 +1,91 @@ +package net.runelite.client.plugins.microbot; + +import net.runelite.api.gameval.ItemID; +import net.runelite.api.widgets.Widget; +import net.runelite.client.plugins.microbot.util.Global; +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.npc.Rs2Npc; +import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; +import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; + +import java.awt.event.KeyEvent; +import java.util.HashMap; +import java.util.Map; + +/** + * Shared utility for interacting with the Tool Leprechaun's exchange interface. + * Included in every plugin JAR via the build system, alongside PluginConstants. + */ +public final class Rs2Leprechaun { + + private static final int EXCHANGE_GROUP = 125; + private static final int EXCHANGE_ROOT_CHILD = 0; + private static final Map COMPOST_WIDGET_CHILDREN = new HashMap<>(); + + static { + COMPOST_WIDGET_CHILDREN.put(ItemID.BUCKET_COMPOST, 17); + COMPOST_WIDGET_CHILDREN.put(ItemID.BUCKET_SUPERCOMPOST, 18); + COMPOST_WIDGET_CHILDREN.put(ItemID.BUCKET_ULTRACOMPOST, 19); + } + + private Rs2Leprechaun() { + throw new UnsupportedOperationException("Utility class"); + } + + public static boolean isExchangeOpen() { + return Rs2Widget.isWidgetVisible(EXCHANGE_GROUP, EXCHANGE_ROOT_CHILD); + } + + public static boolean openExchange() { + Rs2NpcModel leprechaun = Rs2Npc.getNpc("Tool Leprechaun"); + if (leprechaun == null) { + Microbot.log("Tool Leprechaun not found nearby."); + return false; + } + Rs2Npc.interact(leprechaun, "Exchange"); + Global.sleepUntil(Rs2Leprechaun::isExchangeOpen, 5000); + return isExchangeOpen(); + } + + public static void closeExchange() { + if (isExchangeOpen()) { + Rs2Keyboard.keyPress(KeyEvent.VK_ESCAPE); + Global.sleepUntil(() -> !isExchangeOpen(), 2000); + } + } + + /** + * Withdraws one compost of the given type from the nearest Tool Leprechaun. + * Opens the exchange interface, clicks the compost widget (Remove-1), then closes. + * + * @param compostItemId one of ItemID.BUCKET_COMPOST, BUCKET_SUPERCOMPOST, or BUCKET_ULTRACOMPOST + * @return true if the compost appeared in inventory + */ + public static boolean withdrawCompost(int compostItemId) { + Integer childId = COMPOST_WIDGET_CHILDREN.get(compostItemId); + if (childId == null) { + Microbot.log("Unsupported compost item ID for leprechaun withdrawal: " + compostItemId); + return false; + } + + if (!isExchangeOpen() && !openExchange()) { + return false; + } + Global.sleep(300, 600); + + Widget compostWidget = Rs2Widget.getWidget(EXCHANGE_GROUP, childId); + if (compostWidget == null) { + Microbot.log("Compost widget not found in leprechaun interface."); + closeExchange(); + return false; + } + + Rs2Widget.clickWidget(compostWidget); + Global.sleepUntil(() -> Rs2Inventory.hasItem(compostItemId), 3000); + Global.sleep(300, 600); + + closeExchange(); + return Rs2Inventory.hasItem(compostItemId); + } +} diff --git a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunConfig.java b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunConfig.java index c4f6b73a3b..a65ebed97b 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunConfig.java @@ -92,29 +92,56 @@ public interface FarmTreeRunConfig extends Config { /* ========================= * Sapling selection * ========================= */ + @ConfigItem( + keyName = "enableTrees", + name = "Run trees", + description = "Enable regular tree patches", + position = 0, + section = saplingSection + ) + default boolean enableTrees() { return true; } + @ConfigItem( keyName = "treeSapling", name = "Tree sapling", description = "Select tree sapling to use", - position = 0, + position = 1, section = saplingSection ) default TreeEnums selectedTree() { return TreeEnums.MAPLE; } + @ConfigItem( + keyName = "enableFruitTrees", + name = "Run fruit trees", + description = "Enable fruit tree patches", + position = 2, + section = saplingSection + ) + default boolean enableFruitTrees() { return true; } + @ConfigItem( keyName = "fruitTreeSapling", name = "Fruit tree sapling", description = "Select fruit tree sapling to use", - position = 1, + position = 3, section = saplingSection ) default FruitTreeEnum selectedFruitTree() { return FruitTreeEnum.PAPAYA; } + @ConfigItem( + keyName = "enableHardTrees", + name = "Run hardwood trees", + description = "Enable hardwood tree patches", + position = 4, + section = saplingSection + ) + default boolean enableHardTrees() { return true; } + @ConfigItem( keyName = "Fossil Island Tree", name = "Hard sapling", description = "Select Hard tree sapling to use", - position = 2, + position = 5, section = saplingSection ) default HardTreeEnums selectedHardTree() { return HardTreeEnums.MAHOGANY; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPlugin.java index 203b784346..7963f01659 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunPlugin.java @@ -30,7 +30,7 @@ ) @Slf4j public class FarmTreeRunPlugin extends Plugin { - public static final String version = "1.1.2"; + public static final String version = "1.2.0"; @Inject private FarmTreeRunConfig config; @Provides diff --git a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java index c0a406fc3c..c8e8925b9e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/farmtreerun/FarmTreeRunScript.java @@ -4,8 +4,8 @@ import lombok.RequiredArgsConstructor; import net.runelite.api.*; import net.runelite.api.coords.WorldPoint; -import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; +import net.runelite.client.plugins.microbot.Rs2Leprechaun; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.farmtreerun.enums.CompostType; import net.runelite.client.plugins.microbot.farmtreerun.enums.HardTreeEnums; @@ -24,12 +24,8 @@ import net.runelite.client.plugins.microbot.util.npc.Rs2Npc; import net.runelite.client.plugins.microbot.util.npc.Rs2NpcModel; 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.widget.Rs2Widget; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; -import java.awt.event.KeyEvent; - import javax.inject.Inject; import java.util.*; import java.util.concurrent.TimeUnit; @@ -145,7 +141,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_GNOME_STRONGHOLD_FRUIT_PATCH: patch = Patch.GNOME_STRONGHOLD_FRUIT_TREE_PATCH; - if (config.gnomeStrongholdFruitTreePatch()) { + if (config.enableFruitTrees() && config.gnomeStrongholdFruitTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -156,7 +152,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_GNOME_STRONGHOLD_TREE_PATCH: patch = Patch.GNOME_STRONGHOLD_TREE_PATCH; - if (config.gnomeStrongholdTreePatch()) { + if (config.enableTrees() && config.gnomeStrongholdTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -167,7 +163,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_FARMING_GUILD_TREE_PATCH: patch = Patch.FARMING_GUILD_TREE_PATCH; - if (config.farmingGuildTreePatch() && patch.hasRequiredLevel()) { + if (config.enableTrees() && config.farmingGuildTreePatch() && patch.hasRequiredLevel()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -178,7 +174,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_FARMING_GUILD_FRUIT_PATCH: patch = Patch.FARMING_GUILD_FRUIT_TREE_PATCH; - if (config.farmingGuildFruitTreePatch() && patch.hasRequiredLevel()) { + if (config.enableFruitTrees() && config.farmingGuildFruitTreePatch() && patch.hasRequiredLevel()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -189,7 +185,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_BRIMHAVEN_FRUIT_TREE_PATCH: patch = Patch.BRIMHAVEN_FRUIT_TREE_PATCH; - if (config.brimhavenFruitTreePatch()) { + if (config.enableFruitTrees() && config.brimhavenFruitTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -200,7 +196,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_TREE_GNOME_VILLAGE_FRUIT_TREE_PATCH: patch = Patch.TREE_GNOME_VILLAGE_FRUIT_TREE_PATCH; - if (config.treeGnomeVillageFruitTreePatch()) { + if (config.enableFruitTrees() && config.treeGnomeVillageFruitTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -211,7 +207,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_TAVERLEY_TREE_PATCH: patch = Patch.TAVERLEY_TREE_PATCH; - if (config.taverleyTreePatch()) { + if (config.enableTrees() && config.taverleyTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -221,7 +217,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_FALADOR_TREE_PATCH: patch = Patch.FALADOR_TREE_PATCH; - if (config.faladorTreePatch()) { + if (config.enableTrees() && config.faladorTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -231,7 +227,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_LUMBRIDGE_TREE_PATCH: patch = Patch.LUMBRIDGE_TREE_PATCH; - if (config.lumbridgeTreePatch()) { + if (config.enableTrees() && config.lumbridgeTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -242,7 +238,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_VARROCK_TREE_PATCH: patch = Patch.VARROCK_TREE_PATCH; - if (config.varrockTreePatch()) { + if (config.enableTrees() && config.varrockTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -253,7 +249,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_CATHERBY_FRUIT_TREE_PATCH: patch = Patch.CATHERBY_FRUIT_TREE_PATCH; - if (config.catherbyFruitTreePatch()) { + if (config.enableFruitTrees() && config.catherbyFruitTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -264,7 +260,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_FOSSIL_TREE_PATCH_A: patch = Patch.FOSSIL_TREE_PATCH_A; - if (config.fossilTreePatch()) { + if (config.enableHardTrees() && config.fossilTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -275,7 +271,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_FOSSIL_TREE_PATCH_B: patch = Patch.FOSSIL_TREE_PATCH_B; - if (config.fossilTreePatch()) { + if (config.enableHardTrees() && config.fossilTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -286,7 +282,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_FOSSIL_TREE_PATCH_C: patch = Patch.FOSSIL_TREE_PATCH_C; - if (config.fossilTreePatch()) { + if (config.enableHardTrees() && config.fossilTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -297,7 +293,7 @@ public boolean run(FarmTreeRunConfig config) { break; case HANDLE_LLETYA_FRUIT_TREE_PATCH: patch = Patch.LLETYA_FRUIT_TREE_PATCH; - if (config.lletyaFruitTreePatch()) { + if (config.enableFruitTrees() && config.lletyaFruitTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -309,7 +305,7 @@ public boolean run(FarmTreeRunConfig config) { case HANDLE_AUBURNVALE_TREE_PATCH: { patch = Patch.AUBURNVALE_TREE_PATCH; - if (config.auburnTreePatch()) { + if (config.enableTrees() && config.auburnTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -320,7 +316,7 @@ public boolean run(FarmTreeRunConfig config) { } case HANDLE_KASTORI_FRUIT_TREE_PATCH: { patch = Patch.KASTORI_FRUIT_TREE_PATCH; - if (config.kastoriFruitTreePatch()) { + if (config.enableFruitTrees() && config.kastoriFruitTreePatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -331,7 +327,7 @@ public boolean run(FarmTreeRunConfig config) { } case HANDLE_PRIFFDDINAS_CRYSTAL_TREE_PATCH: { patch = Patch.PRIFFDDINAS_CRYSTAL_TREE_PATCH; - if (config.priffddinasCrystalTreePatch() && patch.hasRequiredLevel()) { + if (config.enableTrees() && config.priffddinasCrystalTreePatch() && patch.hasRequiredLevel()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -343,7 +339,7 @@ public boolean run(FarmTreeRunConfig config) { case HANDLE_AVIUM_SAVANNAH_HARDWOOD_PATCH: { patch = Patch.AVIUM_SAVANNAH_HARDWOOD_PATCH; - if (config.aviumSavannahHardwoodPatch()) { + if (config.enableHardTrees() && config.aviumSavannahHardwoodPatch()) { if (walkToLocation(patch.getLocation())) { handledPatch = handlePatch(config, patch); } @@ -417,7 +413,6 @@ private void checkSaplingLevelRequirement(FarmTreeRunConfig config) { } private void dropCrap() { - if (Rs2Player.isAnimating()) return; int[] junk = {ItemID.EMPTY_PLANT_POT, ItemID.BUCKET, ItemID.WEEDS}; for (int id : junk) { if (Rs2Inventory.hasItem(id)) { @@ -428,18 +423,15 @@ private void dropCrap() { } private boolean walkToLocation(WorldPoint location) { - if (!Rs2Player.isAnimating()) { - Rs2Walker.walkTo(location); - sleepUntil(() -> Rs2Player.distanceTo(location) < 16); - return Rs2Player.distanceTo(location) < 16; - } - return false; + Rs2Walker.walkTo(location); + sleepUntil(() -> Rs2Player.distanceTo(location) < 16); + return Rs2Player.distanceTo(location) < 16; } private void bank(FarmTreeRunConfig config) { items.clear(); if (Rs2Bank.openBank() || Rs2Bank.walkToBank()) { - sleepUntil(() -> !Rs2Player.isAnimating()); + sleepUntil(Rs2Bank::isOpen, 5000); if (!Rs2Bank.isOpen()) return; sleep(600, 2200); @@ -590,6 +582,15 @@ private void bank(FarmTreeRunConfig config) { // TODO: Need to handle what happens if a required item does not exist + // Merge entries with the same itemId + noted flag so withdrawal doesn't under-count + Map merged = new LinkedHashMap<>(); + for (FarmingItem item : items) { + long key = ((long) item.getItemId() << 1) | (item.isNoted() ? 1 : 0); + merged.merge(key, item, (a, b) -> + new FarmingItem(a.getItemId(), a.getQuantity() + b.getQuantity(), a.isNoted(), a.isOptional())); + } + items = new ArrayList<>(merged.values()); + // Deposit only what we don't need: keep desired ids and their noted variants Set keepIds = new HashSet<>(); for (FarmingItem item : items) { @@ -755,7 +756,7 @@ private void handleNotingFruit(Patch patch) { ItemID.DRAGONFRUIT }; - if (!Rs2Inventory.hasItem(fruitIds) || Rs2Player.isAnimating()) return; + if (!Rs2Inventory.hasItem(fruitIds)) return; // Iterate through the fruit IDs for (int fruitId : fruitIds) { @@ -879,30 +880,23 @@ private void handleCheckHealth(GameObject treePatch) { private void handleRakeAction(GameObject treePatch) { System.out.println("Raking the patch..."); - // Rake the patch Rs2GameObject.interact(treePatch, "rake"); - - Rs2Player.waitForAnimation(); - sleepUntil(() -> !Rs2Player.isAnimating()); - + Rs2Player.waitForXpDrop(Skill.FARMING, 10000); + if (Rs2Inventory.hasItem(ItemID.WEEDS)) { + Rs2Inventory.dropAll(ItemID.WEEDS); + sleepUntil(() -> !Rs2Inventory.hasItem(ItemID.WEEDS), 5000); + } } private void handleClearAction(GameObject treePatch) { System.out.println("Clearing dead tree..."); - // Try to interact with the patch using the "clear" action boolean interactionSuccess = Rs2GameObject.interact(treePatch, "clear"); - Rs2Player.waitForAnimation(); - sleepUntil(() -> !Rs2Player.isAnimating()); - if (!interactionSuccess) { System.out.println("Failed to interact with the tree patch to clear it."); return; } - - // Wait for the clearing animation to finish - Rs2Player.waitForAnimation(); - sleepUntil(() -> !Rs2Player.isAnimating() && Rs2Player.isMoving()); + Rs2Player.waitForXpDrop(Skill.FARMING, 10000); } private void equipGraceful() { @@ -953,42 +947,7 @@ private boolean isFruitTreePatch(Patch patch) { } private boolean withdrawCompostFromLeprechaun(CompostType compostType) { - Rs2NpcModel leprechaun = Rs2Npc.getNpc("Tool Leprechaun"); - if (leprechaun == null) { - Microbot.log("Tool Leprechaun not found nearby."); - return false; - } - - Rs2Npc.interact(leprechaun, "Exchange"); - sleepUntil(() -> Rs2Widget.isWidgetVisible(125, 0), 5000); - if (!Rs2Widget.isWidgetVisible(125, 0)) { - Microbot.log("Tool Leprechaun exchange interface did not open."); - return false; - } - sleep(300, 600); - - int childId; - switch (compostType) { - case COMPOST: childId = 17; break; - case SUPERCOMPOST: childId = 18; break; - case ULTRACOMPOST: childId = 19; break; - default: return false; - } - - Widget compostWidget = Rs2Widget.getWidget(125, childId); - if (compostWidget == null) { - Microbot.log("Compost widget not found in leprechaun interface."); - return false; - } - - Rs2Widget.clickWidget(compostWidget); - sleepUntil(() -> Rs2Inventory.hasItem(compostType.getItemId()), 3000); - sleep(300, 600); - - Rs2Keyboard.keyPress(KeyEvent.VK_ESCAPE); - sleepUntil(() -> !Rs2Widget.isWidgetVisible(125, 0), 2000); - - return Rs2Inventory.hasItem(compostType.getItemId()); + return Rs2Leprechaun.withdrawCompost(compostType.getItemId()); } private boolean useCompostOnPatch(FarmTreeRunConfig config, Patch patch) { @@ -1005,7 +964,7 @@ private boolean useCompostOnPatch(FarmTreeRunConfig config, Patch patch) { } private List getSelectedTreePatches(FarmTreeRunConfig config) { - // Create a list of all possible tree patches + if (!config.enableTrees()) return Collections.emptyList(); List allTreePatches = List.of( config::faladorTreePatch, config::gnomeStrongholdTreePatch, @@ -1024,7 +983,7 @@ private List getSelectedTreePatches(FarmTreeRunConfig config) { } private List getSelectedHardTreePatches(FarmTreeRunConfig config) { - // Create a list of all possible tree patches + if (!config.enableHardTrees()) return Collections.emptyList(); List allHardTreePatches = List.of( config::fossilTreePatch, config::fossilTreePatch, @@ -1039,7 +998,7 @@ private List getSelectedHardTreePatches(FarmTreeRunConfig confi } private List getSelectedFruitTreePatches(FarmTreeRunConfig config) { - // Create a list of all possible fruit tree patches + if (!config.enableFruitTrees()) return Collections.emptyList(); List allFruitTreePatches = List.of( config::brimhavenFruitTreePatch, config::catherbyFruitTreePatch, @@ -1145,6 +1104,7 @@ public void shutdown() { if(isRunning()) { items.clear(); super.shutdown(); + Microbot.stopPlugin(plugin); } } } From 75ce2533ba7fa351b2999381a1603e641e893972 Mon Sep 17 00:00:00 2001 From: pistol pete <128332200+pjmarz@users.noreply.github.com> Date: Mon, 25 May 2026 03:49:02 -0400 Subject: [PATCH 90/95] fix(cluesolver): client-thread safety for clue task classes (#452) * fix(cluesolver): client-thread safety for clue task classes Multiple clue task classes (AnagramClueTask, CrypticClueTask, EmoteClueTask, RequirementHandlerTask, CoordinateClueTask, MusicClueTask) threw IllegalStateException 'must be called on client thread' when reading player world location from the script thread. Fix: added a getPlayerLocationSafe() helper in ClueTask base class that wraps the read with Microbot.getClientThread().runOnClientThreadOptional(). All callers updated to use the safe accessor. * chore(cluesolver): address Copilot review feedback - Remove dead `hasArrived(Player)` method from AnagramClueTask (never called; state machine uses isWithinRadius instead). - Remove unused Player/NPC imports across Anagram/Coordinate/Cryptic/Music tasks. - Reword `ClueTask#getPlayerLocationSafe` JavaDoc to drop hardcoded line numbers (drift hazard). Co-Authored-By: Claude Opus 4.7 (1M context) --------- Co-authored-by: Claude Opus 4.7 (1M context) --- .../microbot/cluesolver/ClueSolverPlugin.java | 2 +- .../cluesolver/cluetask/AnagramClueTask.java | 16 ++++++-------- .../cluesolver/cluetask/ClueTask.java | 17 +++++++++++++++ .../cluetask/CoordinateClueTask.java | 15 ++++++------- .../cluesolver/cluetask/CrypticClueTask.java | 12 +++++++---- .../cluesolver/cluetask/EmoteClueTask.java | 10 ++++++++- .../cluesolver/cluetask/MusicClueTask.java | 9 ++++---- .../cluetask/RequirementHandlerTask.java | 21 +++++++++++++++++-- 8 files changed, 72 insertions(+), 30 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/ClueSolverPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/ClueSolverPlugin.java index 365f8200ce..5ae92659d6 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/ClueSolverPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/ClueSolverPlugin.java @@ -29,7 +29,7 @@ @PluginDependency(ClueScrollPlugin.class) public class ClueSolverPlugin extends Plugin { - final static String version = "1.0.2"; + final static String version = "1.0.5"; @Inject private ClueSolverScript clueSolverScript; diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/AnagramClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/AnagramClueTask.java index dc2ae85082..6f97c3ccfb 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/AnagramClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/AnagramClueTask.java @@ -3,7 +3,6 @@ import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; -import net.runelite.api.Player; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.GameTick; import net.runelite.client.eventbus.EventBus; @@ -79,15 +78,16 @@ public void onGameTick(GameTick event) { } private void processGameTick(GameTick event) { - Player player = client.getLocalPlayer(); - if (player == null) - return; + // v1.0.5 fix: client-thread-safe location read. processGameTick runs in the + // background executor; direct client.getLocalPlayer() throws IllegalStateException. + net.runelite.api.coords.WorldPoint playerLocation = getPlayerLocationSafe(); + if (playerLocation == null) return; switch (state) { case WALKING_TO_LOCATION: - if (hasArrived(player)) { + if (playerLocation.equals(location)) { transitionToInteractionState(); - } else if (isWithinRadius(location, player.getWorldLocation(), 3)) { + } else if (isWithinRadius(location, playerLocation, 3)) { Rs2Walker.walkFastCanvas(location); } break; @@ -196,10 +196,6 @@ private boolean handleDialogue() { return false; } - private boolean hasArrived(Player player) { - return player.getWorldLocation().equals(location); - } - private boolean isWithinRadius(WorldPoint targetLocation, WorldPoint playerLocation, int radius) { int deltaX = Math.abs(targetLocation.getX() - playerLocation.getX()); int deltaY = Math.abs(targetLocation.getY() - playerLocation.getY()); diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/ClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/ClueTask.java index ba829ffdf2..88d46b2fa9 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/ClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/ClueTask.java @@ -3,7 +3,9 @@ import lombok.Setter; import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; +import net.runelite.api.coords.WorldPoint; import net.runelite.client.plugins.cluescrolls.ClueScrollPlugin; +import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.cluesolver.ClueSolverPlugin; import java.util.concurrent.CompletableFuture; @@ -65,4 +67,19 @@ protected void completeTask(boolean success) { protected boolean preTaskCheck() { return client != null && client.getLocalPlayer() != null; } + + /** + * v1.0.3 fix: thread-safe player location read. + * + *

    Subclasses submit {@code processGameTick} to a background executor, so direct + * {@code client.getLocalPlayer().getWorldLocation()} calls from those code paths throw + * {@code IllegalStateException: must be called on client thread}. Use this helper instead -- + * it hops to the client thread, reads the location, and returns null if the player isn't available. + */ + protected WorldPoint getPlayerLocationSafe() { + return Microbot.getClientThread().runOnClientThreadOptional(() -> { + if (client == null || client.getLocalPlayer() == null) return null; + return client.getLocalPlayer().getWorldLocation(); + }).orElse(null); + } } diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CoordinateClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CoordinateClueTask.java index 8cc082c4e5..604dafe5e6 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CoordinateClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CoordinateClueTask.java @@ -3,8 +3,6 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; import net.runelite.api.ItemID; -import net.runelite.api.NPC; -import net.runelite.api.Player; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.GameTick; import net.runelite.client.eventbus.EventBus; @@ -95,12 +93,13 @@ public void onGameTick(GameTick event) { } private void processGameTick(GameTick event) { - Player player = client.getLocalPlayer(); - if (player == null) return; + // v1.0.5 fix: client-thread-safe location read. + net.runelite.api.coords.WorldPoint playerLocation = getPlayerLocationSafe(); + if (playerLocation == null) return; switch (state) { case WALKING_TO_LOCATION: - if (isWithinRadius(location, player.getWorldLocation(), 5)) { + if (isWithinRadius(location, playerLocation, 5)) { log.info("Arrived at coordinate clue location."); state = (enemy != null) ? State.FIGHTING_ENEMY : State.DIGGING; } @@ -153,8 +152,10 @@ private boolean waitForEnemyDefeat(Rs2NpcModel targetNpc) { } private boolean prepareToDig() { - Player player = client.getLocalPlayer(); - if (!isWithinRadius(location, player.getWorldLocation(), 1)) { + // v1.0.5 fix: client-thread-safe location read. Called from processGameTick (background executor). + net.runelite.api.coords.WorldPoint playerLocation = getPlayerLocationSafe(); + if (playerLocation == null) return false; + if (!isWithinRadius(location, playerLocation, 1)) { log.info("Adjusting position to exact location."); Rs2Walker.walkFastCanvas(location); return false; diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CrypticClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CrypticClueTask.java index 9aeea69c35..8438c559a9 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CrypticClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/CrypticClueTask.java @@ -2,8 +2,6 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; -import net.runelite.api.NPC; -import net.runelite.api.Player; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.GameTick; import net.runelite.client.eventbus.EventBus; @@ -85,8 +83,14 @@ public void onGameTick(GameTick event) { } private void processGameTick(GameTick event) { - Player player = client.getLocalPlayer(); - WorldPoint playerLocation = player.getWorldLocation(); + // v1.0.3 fix: read player location via the client-thread-safe helper instead of + // calling client.getLocalPlayer().getWorldLocation() directly from the background + // executor (which throws IllegalStateException: must be called on client thread). + WorldPoint playerLocation = getPlayerLocationSafe(); + if (playerLocation == null) { + log.debug("Player location unavailable; will retry next tick"); + return; + } WorldPoint clueLocation = clue.getLocation(clueScrollPlugin); switch (state) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/EmoteClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/EmoteClueTask.java index e0c2d4ee33..b194307fc2 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/EmoteClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/EmoteClueTask.java @@ -131,7 +131,15 @@ private void processGameTick(GameTick event) { private void handleWalkingToLocation() { WorldPoint location = clue.getLocation(clueScrollPlugin); - if (client.getLocalPlayer().getWorldLocation().equals(location)) { + // v1.0.3 fix: read player location via the client-thread-safe helper instead of + // calling client.getLocalPlayer().getWorldLocation() directly from the background + // executor (which throws IllegalStateException: must be called on client thread). + WorldPoint playerLocation = getPlayerLocationSafe(); + if (playerLocation == null) { + log.debug("Player location unavailable; will retry next tick"); + return; + } + if (playerLocation.equals(location)) { log.info("Arrived at Emote Clue location."); state = State.PERFORMING_EMOTES; } else { diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MusicClueTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MusicClueTask.java index aaaa1fd3e7..a0882a1f78 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MusicClueTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/MusicClueTask.java @@ -2,8 +2,6 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.Client; -import net.runelite.api.NPC; -import net.runelite.api.Player; import net.runelite.api.coords.WorldPoint; import net.runelite.api.events.GameTick; import net.runelite.client.eventbus.EventBus; @@ -85,12 +83,13 @@ public void onGameTick(GameTick event) { } private void processGameTick(GameTick event) { - Player player = client.getLocalPlayer(); - if (player == null) return; + // v1.0.5 fix: client-thread-safe location read. + net.runelite.api.coords.WorldPoint playerLocation = getPlayerLocationSafe(); + if (playerLocation == null) return; switch (state) { case WALKING_TO_LOCATION: - if (isWithinRadius(location, player.getWorldLocation(), 5)) { + if (isWithinRadius(location, playerLocation, 5)) { log.info("Arrived at music clue location."); state = State.PLAYING_SONG; } diff --git a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/RequirementHandlerTask.java b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/RequirementHandlerTask.java index 4f5f4ce4b0..9231599ee4 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/RequirementHandlerTask.java +++ b/src/main/java/net/runelite/client/plugins/microbot/cluesolver/cluetask/RequirementHandlerTask.java @@ -90,7 +90,18 @@ private void fetchMissingItemsFromBank(List missingItems) { private void walkToBank() { if (Rs2Bank.isOpen()) { fetchNextItem(); - } else if (client.getLocalPlayer().getWorldLocation().distanceTo(Rs2Bank.getNearestBank().getWorldPoint()) > 10) { + return; + } + // v1.0.4 fix: read player location via the client-thread-safe helper. walkToBank() may + // be called from off-client-thread code paths (executeTask flow). Direct + // client.getLocalPlayer().getWorldLocation() throws IllegalStateException, which + // cascaded into "Failed to open the bank" + walker deadlock. + net.runelite.api.coords.WorldPoint playerLocation = getPlayerLocationSafe(); + if (playerLocation == null) { + log.debug("Player location unavailable in walkToBank; will retry next tick"); + return; + } + if (playerLocation.distanceTo(Rs2Bank.getNearestBank().getWorldPoint()) > 10) { Rs2Bank.walkToBankAndUseBank(); } else { openBank(); @@ -149,7 +160,13 @@ private void fetchNextItem() { public void onGameTick(GameTick event) { if (Rs2Bank.isOpen() && currentRequirement == null) { fetchNextItem(); - } else if (!Rs2Bank.isOpen() && client.getLocalPlayer().getWorldLocation().distanceTo(Rs2Bank.getNearestBank().getWorldPoint()) <= 5) { + return; + } + // v1.0.4 fix: defensive client-thread-safe read. onGameTick is @Subscribe-annotated so + // it normally runs on the client thread, but using the helper is harmless and consistent. + net.runelite.api.coords.WorldPoint playerLocation = getPlayerLocationSafe(); + if (playerLocation == null) return; + if (!Rs2Bank.isOpen() && playerLocation.distanceTo(Rs2Bank.getNearestBank().getWorldPoint()) <= 5) { openBank(); } } From a8b28705606908c8d6f53f7cb1d0753d2851d49c Mon Sep 17 00:00:00 2001 From: runsonmypc <45095641+runsonmypc@users.noreply.github.com> Date: Mon, 25 May 2026 03:49:19 -0400 Subject: [PATCH 91/95] fix(birdhouse): block rubber cap mushroom transports on Fossil Island (#451) Pathfinder routes through the rubber cap mushroom shortcuts which causes the walker to stall. Add tile restrictions on all 5 origin tiles when on Fossil Island, cleared on shutdown. Bump to 1.1.3. Co-authored-by: runsonmypc --- .../birdhouseruns/FornBirdhouseRunsPlugin.java | 2 +- .../birdhouseruns/FornBirdhouseRunsScript.java | 17 ++++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsPlugin.java index 3606efdb99..3be4a82047 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsPlugin.java @@ -26,7 +26,7 @@ ) @Slf4j public class FornBirdhouseRunsPlugin extends Plugin { - final static String version = "1.1.2"; + final static String version = "1.1.3"; @Provides FornBirdhouseRunsConfig provideConfig(ConfigManager configManager) { return configManager.getConfig(FornBirdhouseRunsConfig.class); diff --git a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java index 874bceefab..f4986560ca 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java @@ -20,6 +20,8 @@ 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.walker.Rs2Walker; +import net.runelite.client.plugins.microbot.shortestpath.Restriction; +import net.runelite.client.plugins.microbot.shortestpath.ShortestPathPlugin; import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; @@ -147,7 +149,8 @@ public boolean run() { if (!Rs2Walker.disableTeleports && isOnFossilIsland()) { Rs2Walker.disableTeleports = true; - log.info("On Fossil Island — disabling teleports for remaining walks"); + blockRubberCapMushrooms(); + log.info("On Fossil Island — disabling teleports and rubber cap mushrooms for remaining walks"); } boolean advanced = true; @@ -309,12 +312,24 @@ private void emptyNests() { public void shutdown() { super.shutdown(); Rs2Walker.disableTeleports = false; + ShortestPathPlugin.getPathfinderConfig().setRestrictedTiles(); initialized = false; botStatus = states.TELEPORTING; lastObservedStatus = null; stateEnteredAtMs = 0L; } + private static void blockRubberCapMushrooms() { + Restriction[] restrictions = new Restriction[] { + new Restriction(3663, 3808, 0), + new Restriction(3664, 3808, 0), + new Restriction(3665, 3808, 0), + new Restriction(3666, 3809, 0), + new Restriction(3666, 3810, 0) + }; + ShortestPathPlugin.getPathfinderConfig().setRestrictedTiles(restrictions); + } + /** Throttle for arrivedAndStill log lines (one per second per target). */ private long lastArrivedLogMs; private WorldPoint lastArrivedLogTarget; From feb7bb9433bdec8d52d1c1d5fe26ce63e8c65c34 Mon Sep 17 00:00:00 2001 From: Sami Date: Tue, 26 May 2026 09:12:51 +0200 Subject: [PATCH 92/95] fix(PestControlPlugin): update version to 2.3.4 and enhance special attack handling --- .../pestcontrol/PestControlPlugin.java | 2 +- .../pestcontrol/PestControlScript.java | 50 ++++++++++++++++++- 2 files changed, 49 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlPlugin.java index 0cc6102cbb..243dd15d59 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlPlugin.java @@ -34,7 +34,7 @@ @Slf4j public class PestControlPlugin extends Plugin { - static final String version = "2.3.3"; + static final String version = "2.3.4"; @Inject PestControlScript pestControlScript; diff --git a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java index 2fc7d51ac8..1c2cc51079 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/pestcontrol/PestControlScript.java @@ -1,9 +1,12 @@ package net.runelite.client.plugins.microbot.pestcontrol; import com.google.common.collect.ImmutableSet; +import net.runelite.api.Actor; +import net.runelite.api.EquipmentInventorySlot; import net.runelite.api.NPCComposition; import net.runelite.api.NpcID; import net.runelite.api.ObjectID; +import net.runelite.api.Player; import net.runelite.api.Skill; import net.runelite.api.coords.LocalPoint; import net.runelite.api.coords.WorldPoint; @@ -18,10 +21,13 @@ 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.combat.Rs2Combat; +import net.runelite.client.plugins.microbot.util.equipment.Rs2Equipment; import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; +import net.runelite.client.plugins.microbot.util.inventory.Rs2ItemModel; import net.runelite.client.plugins.microbot.util.magic.Rs2Magic; import net.runelite.client.plugins.microbot.util.math.Rs2Random; +import net.runelite.client.plugins.microbot.util.misc.SpecialAttackWeaponEnum; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; @@ -148,7 +154,7 @@ public boolean run(PestControlConfig config) { } } - Rs2Combat.setSpecState(true, config.specialAttackPercentage() * 10); + activateSpecialAttackIfReady(); Widget activity = Rs2Widget.getWidget(26738700); //145 = 100% if (activity != null && activity.getChild(0).getWidth() <= 20 && !Rs2Combat.inCombat()) { Rs2NpcModel attackableNpc = Microbot.getClientThread().invoke(() -> @@ -328,7 +334,7 @@ private boolean handleAttack(PestControlNpc npcType, int priority) { } } } else { - if (config.Priority2() == npcType) { + if (config.Priority3() == npcType) { if (npcType == PestControlNpc.BRAWLER) { return attackBrawler(); } else if (npcType == PestControlNpc.PORTAL) { @@ -427,6 +433,46 @@ private boolean attackBrawler() { return false; } + private void activateSpecialAttackIfReady() { + Optional specialAttackWeapon = getEquippedSpecialAttackWeapon(); + if (specialAttackWeapon.isEmpty() || !hasCombatTarget()) { + return; + } + + int configuredEnergyRequired = config.specialAttackPercentage() * 10; + if (configuredEnergyRequired <= 0) { + return; + } + + int energyRequired = Math.max(configuredEnergyRequired, specialAttackWeapon.get().getEnergyRequired()); + Rs2Combat.setSpecState(true, energyRequired); + } + + private Optional getEquippedSpecialAttackWeapon() { + Rs2ItemModel weapon = Rs2Equipment.get(EquipmentInventorySlot.WEAPON); + if (weapon == null || weapon.getName() == null) { + return Optional.empty(); + } + + String weaponName = weapon.getName().toLowerCase(Locale.ROOT); + return Arrays.stream(SpecialAttackWeaponEnum.values()) + .sorted(Comparator.comparingInt((SpecialAttackWeaponEnum specWeapon) -> specWeapon.getName().length()).reversed()) + .filter(specWeapon -> weaponName.contains(specWeapon.getName())) + .findFirst(); + } + + private boolean hasCombatTarget() { + return Microbot.getClientThread().runOnClientThreadOptional(() -> { + Player player = Microbot.getClient().getLocalPlayer(); + if (player == null) { + return false; + } + + Actor target = player.getInteracting(); + return target != null && !target.isDead(); + }).orElse(false); + } + @Override public void shutdown() { Microbot.log("Pest control about to shutdown"); From 2bec2c8cc0181219520fe02cd92035fe83ddd728 Mon Sep 17 00:00:00 2001 From: Alex <45095641+runsonmypc@users.noreply.github.com> Date: Sun, 24 May 2026 20:11:04 -0400 Subject: [PATCH 93/95] fix(birdhouse): invoke Rs2Walker in TELEPORTING state, hardcode mushtree transport MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TELEPORTING was a no-op that immediately advanced to DISMANTLE_HOUSE_1 without ever calling Rs2Walker, so the player never left the bank. Now calls Rs2Walker.walkTo(birdhouseLocation1) which routes via digsite pendant to Fossil Island. MUSHROOM_TELEPORT was also a no-op relying on Rs2Walker to route through the mushtree, but the walker kept detouring to the bank instead. Replaced with direct object interaction: Use mushtree → select Mushroom Meadow from the Mycelium Transportation System widget. --- .../microbot/birdhouseruns/FornBirdhouseRunsScript.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java index f4986560ca..b8d3eb5489 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java @@ -20,6 +20,7 @@ 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.walker.Rs2Walker; +import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import net.runelite.client.plugins.microbot.shortestpath.Restriction; import net.runelite.client.plugins.microbot.shortestpath.ShortestPathPlugin; @@ -42,6 +43,8 @@ public class FornBirdhouseRunsScript extends Script { private static final WorldPoint birdhouseLocation3 = new WorldPoint(3677, 3882, 0); private static final WorldPoint birdhouseLocation4 = new WorldPoint(3679, 3815, 0); private static final WorldPoint SOUTH_ROWBOAT = new WorldPoint(3724, 3807, 0); + private static final WorldPoint VERDANT_MUSHTREE = new WorldPoint(3757, 3757, 0); + private static final int MUSHTREE_OBJECT_ID = 30924; // Each location maps to a BIRDHOUSE_TRANSMIT_* varp. See isEmpty/isBuilt/isSeeded // below for the canonical state decoding (matches RuneLite's BirdHouseState). private static final int VARP_HOUSE_1 = VarPlayerID.BIRDHOUSE_TRANSMIT_D; // Verdant SW @@ -176,6 +179,7 @@ public boolean run() { switch (botStatus) { case TELEPORTING: case VERDANT_TELEPORT: + Rs2Walker.walkTo(birdhouseLocation1); botStatus = states.DISMANTLE_HOUSE_1; advanced = true; break; @@ -216,6 +220,10 @@ public boolean run() { } break; case MUSHROOM_TELEPORT: + Rs2GameObject.interact(MUSHTREE_OBJECT_ID, "Use"); + sleepUntil(() -> Rs2Widget.findWidget("Mycelium Transportation System") != null, 5000); + Rs2Widget.clickWidget("Mushroom Meadow"); + sleepUntil(() -> Rs2Player.distanceTo(birdhouseLocation3) < 20, 10000); botStatus = states.DISMANTLE_HOUSE_3; advanced = true; break; From 20eb85cafb4aa59b100739c4f32462f7cdbfb5ca Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Sun, 24 May 2026 23:18:52 -0400 Subject: [PATCH 94/95] fix(birdhouse): manually teleport via digsite pendant instead of relying on Rs2Walker Rs2Walker.walkTo() was blocking indefinitely trying to path from the mainland bank to Fossil Island. Now explicitly rubs the pendant, selects Fossil Island from the dialogue, and only uses the walker for local on-island navigation. --- .../FornBirdhouseRunsScript.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java index b8d3eb5489..50756b3ac6 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsScript.java @@ -19,6 +19,7 @@ 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.dialogues.Rs2Dialogue; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import net.runelite.client.plugins.microbot.util.widget.Rs2Widget; import net.runelite.client.plugins.microbot.shortestpath.Restriction; @@ -179,6 +180,10 @@ public boolean run() { switch (botStatus) { case TELEPORTING: case VERDANT_TELEPORT: + if (!isOnFossilIsland()) { + if (!teleportToFossilIsland()) break; + Rs2Walker.disableTeleports = true; + } Rs2Walker.walkTo(birdhouseLocation1); botStatus = states.DISMANTLE_HOUSE_1; advanced = true; @@ -531,6 +536,36 @@ private boolean isOnFossilIsland() { return loc != null && FOSSIL_ISLAND_REGIONS.contains(loc.getRegionID()); } + private boolean teleportToFossilIsland() { + List pendantIds = Arrays.asList( + ItemID.NECKLACE_OF_DIGSITE_1, + ItemID.NECKLACE_OF_DIGSITE_2, + ItemID.NECKLACE_OF_DIGSITE_3, + ItemID.NECKLACE_OF_DIGSITE_4, + ItemID.NECKLACE_OF_DIGSITE_5 + ); + for (int id : pendantIds) { + if (Rs2Inventory.contains(id)) { + log.info("Rubbing digsite pendant (id={}) to teleport to Fossil Island", id); + if (!Rs2Inventory.interact(id, "Rub")) return false; + if (!sleepUntil(Rs2Dialogue::hasSelectAnOption, 5000)) { + log.warn("Pendant rub did not open destination dialog"); + return false; + } + Rs2Dialogue.clickOption("Fossil Island"); + if (!sleepUntil(this::isOnFossilIsland, 15000)) { + log.warn("Did not arrive on Fossil Island after pendant teleport"); + return false; + } + log.info("Arrived on Fossil Island at {}", Rs2Player.getWorldLocation()); + return true; + } + } + log.error("No digsite pendant found in inventory"); + shutdown(); + return false; + } + /** True if the inventory already has everything a full run needs. The digsite * pendant is only required when off Fossil Island (its sole purpose is the * teleport onto the island); on-island, we can just walk. */ From d0c98f38fe90e9e73d480755af3b91b0c3919e9b Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Tue, 26 May 2026 17:42:31 -0400 Subject: [PATCH 95/95] chore(birdhouse): bump version to 1.1.4 --- .../plugins/microbot/birdhouseruns/FornBirdhouseRunsPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsPlugin.java index 3be4a82047..795fc3e042 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/birdhouseruns/FornBirdhouseRunsPlugin.java @@ -26,7 +26,7 @@ ) @Slf4j public class FornBirdhouseRunsPlugin extends Plugin { - final static String version = "1.1.3"; + final static String version = "1.1.4"; @Provides FornBirdhouseRunsConfig provideConfig(ConfigManager configManager) { return configManager.getConfig(FornBirdhouseRunsConfig.class);