diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterConfig.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterConfig.java index 0d6a7adea61..40393b49b51 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterConfig.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterConfig.java @@ -398,6 +398,29 @@ default boolean toggleHighAlchProfitable() { ) default boolean eatFoodForSpace() { return false; } + @ConfigItem( + keyName = "waitForLoot", + name = "Wait for Loot", + description = "Wait for loot to appear before attacking next NPC", + position = 103, + section = lootSection + ) + default boolean toggleWaitForLoot() { + return false; + } + + @Range(min = 1, max = 10) + @ConfigItem( + keyName = "lootWaitTimeout", + name = "Loot Wait Timeout (s)", + description = "Seconds to wait for loot before resuming combat (1-10)", + position = 104, + section = lootSection + ) + default int lootWaitTimeout() { + return 6; + } + //set center tile manually @ConfigItem( keyName = "Center Tile", diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java index 333e7b008a5..e9cae7f32e0 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/AIOFighterPlugin.java @@ -68,6 +68,26 @@ public class AIOFighterPlugin extends Plugin { @Getter @Setter public static int cooldown = 0; + + @Getter @Setter + private static volatile long lastNpcKilledTime = 0; + + @Getter @Setter + private static volatile boolean waitingForLoot = false; + + /** + * Centralized method to clear wait-for-loot state + * @param reason Optional reason for clearing the state (for logging) + */ + public static void clearWaitForLoot(String reason) { + setWaitingForLoot(false); + setLastNpcKilledTime(0L); + AttackNpcScript.cachedTargetNpcIndex = -1; + if (reason != null) { + Microbot.log("Clearing wait-for-loot state: " + reason); + } + } + private final CannonScript cannonScript = new CannonScript(); private final AttackNpcScript attackNpc = new AttackNpcScript(); @@ -117,6 +137,9 @@ protected void startUp() throws AWTException { return; } setState(State.IDLE); + // Reset wait for loot state on startup + setWaitingForLoot(false); + setLastNpcKilledTime(0L); // Get the future from the reference and cancel it ScheduledFuture scheduledFuture = futureRef.get(); if (scheduledFuture != null) { @@ -167,6 +190,10 @@ protected void startUp() throws AWTException { } protected void shutDown() { + // Reset wait for loot state on shutdown + setWaitingForLoot(false); + setLastNpcKilledTime(0L); + highAlchScript.shutdown(); lootScript.shutdown(); cannonScript.shutdown(); diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java index 85e8f4e964a..5981e1da9cc 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/combat/AttackNpcScript.java @@ -45,6 +45,7 @@ public class AttackNpcScript extends Script { public static Actor currentNpc = null; public static AtomicReference> filteredAttackableNpcs = new AtomicReference<>(new ArrayList<>()); public static Rs2WorldArea attackableArea = null; + public static volatile int cachedTargetNpcIndex = -1; private boolean messageShown = false; private int noNpcCount = 0; @@ -103,6 +104,53 @@ public void run(AIOFighterConfig config) { if (config.state().equals(State.BANKING) || config.state().equals(State.WALKING)) return; + // Check if we should pause while looting is happening + if (Microbot.pauseAllScripts.get()) { + return; // Don't attack while looting + } + + // 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; + // Update our cached target to who we're fighting + if (npc.getHealthRatio() > 0 && !npc.isDead()) { + cachedTargetNpcIndex = npc.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); + + if (cachedNpcModel != null && (cachedNpcModel.isDead() || (cachedNpcModel.getHealthRatio() == 0 && cachedNpcModel.getHealthScale() > 0))) { + AIOFighterPlugin.setWaitingForLoot(true); + AIOFighterPlugin.setLastNpcKilledTime(System.currentTimeMillis()); + Microbot.status = "Waiting for loot..."; + Microbot.log("NPC died, waiting for loot..."); + cachedTargetNpcIndex = -1; + return; + } + } + + // Check if we're waiting for loot + if (config.toggleWaitForLoot() && AIOFighterPlugin.isWaitingForLoot()) { + long timeSinceKill = System.currentTimeMillis() - AIOFighterPlugin.getLastNpcKilledTime(); + int timeoutMs = config.lootWaitTimeout() * 1000; + if (timeSinceKill >= timeoutMs) { + // Timeout reached, resume combat + AIOFighterPlugin.clearWaitForLoot("Loot wait timeout reached, resuming combat"); + cachedTargetNpcIndex = -1; // Clear cached NPC on timeout + } else { + // Still waiting for loot, don't attack + int secondsLeft = (int) Math.max(1, TimeUnit.MILLISECONDS.toSeconds(timeoutMs - timeSinceKill)); + Microbot.status = "Waiting for loot... " + secondsLeft + "s"; + return; + } + } if (config.toggleCenterTile() && config.centerLocation().getX() == 0 && config.centerLocation().getY() == 0) { @@ -123,6 +171,7 @@ public void run(AIOFighterConfig config) { if (!attackableNpcs.isEmpty()) { noNpcCount = 0; + Rs2NpcModel npc = attackableNpcs.stream().findFirst().orElse(null); if (!Rs2Camera.isTileOnScreen(npc.getLocalLocation())) diff --git a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/loot/LootScript.java b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/loot/LootScript.java index f3e4a0ce511..5d5c00d8e09 100644 --- a/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/loot/LootScript.java +++ b/runelite-client/src/main/java/net/runelite/client/plugins/microbot/aiofighter/loot/LootScript.java @@ -38,7 +38,7 @@ public boolean run(AIOFighterConfig config) { if (AIOFighterPlugin.getState().equals(State.BANKING) || AIOFighterPlugin.getState().equals(State.WALKING)) { return; } - if (Rs2Player.isInCombat() && !config.toggleForceLoot()) { + if (Rs2Player.isInCombat() && !config.toggleForceLoot() && !AIOFighterPlugin.isWaitingForLoot()) { return; } @@ -58,26 +58,41 @@ public boolean run(AIOFighterConfig config) { if (config.toggleDelayedLooting()) { groundItems.sort(Comparator.comparingInt(Rs2GroundItem::calculateDespawnTime)); } - //Pause other scripts before looting - Microbot.pauseAllScripts.getAndSet(true); - for (GroundItem groundItem : groundItems) { - if (Rs2Inventory.emptySlotCount() <= minFreeSlots && !canStackItem(groundItem)) { - Microbot.log("Unable to pick loot: " + groundItem.getName() + " making space"); - if (!config.eatFoodForSpace()) { + // Defer clearing wait-for-loot until we successfully pick at least one item + //Pause other scripts before looting and always release + boolean previousPauseState = Microbot.pauseAllScripts.getAndSet(true); + try { + boolean clearedWait = false; + for (GroundItem groundItem : groundItems) { + if (Rs2Inventory.emptySlotCount() <= minFreeSlots && !canStackItem(groundItem)) { + Microbot.log("Unable to pick loot: " + groundItem.getName() + " making space"); + if (!config.eatFoodForSpace()) { + continue; + } + int emptySlots = Rs2Inventory.emptySlotCount(); + if (Rs2Player.eatAt(100, true)) { + sleepUntil(() -> emptySlots < Rs2Inventory.emptySlotCount(), 1200); + } + // If we still don't have space and can't stack this item, skip it + if (Rs2Inventory.emptySlotCount() <= minFreeSlots && !canStackItem(groundItem)) { + continue; + } + } + Microbot.log("Picking up loot: " + groundItem.getName()); + if (!waitForGroundItemDespawn(() -> interact(groundItem), groundItem)) { + // Skip this item and continue to the next rather than aborting the whole pass continue; } - int emptySlots = Rs2Inventory.emptySlotCount(); - if (Rs2Player.eatAt(100)) { - sleepUntil(() -> emptySlots < Rs2Inventory.emptySlotCount(), 1200); + // Clear wait state after first successful pickup + if (!clearedWait && AIOFighterPlugin.isWaitingForLoot()) { + AIOFighterPlugin.clearWaitForLoot("First loot item picked up"); + clearedWait = true; } } - Microbot.log("Picking up loot: " + groundItem.getName()); - if (!waitForGroundItemDespawn(() -> interact(groundItem), groundItem)) { - return; - } + Microbot.log("Looting complete"); + } finally { + Microbot.pauseAllScripts.set(previousPauseState); } - Microbot.log("Looting complete"); - Microbot.pauseAllScripts.compareAndSet(true, false); } catch (Exception ex) { Microbot.log("Looterscript: " + ex.getMessage()); }