Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
58b2a84
feat(AIOFighter): Add Wait for Loot feature
Jul 23, 2025
fb6063d
Merge upstream/development branch changes
Aug 19, 2025
b2ab130
fix(AIOFighter): Fix wait-for-loot interrupting looting actions
Aug 19, 2025
7f1b5f9
fix(AIOFighter): Remove unnecessary formatting changes from LootScript
Aug 19, 2025
02141e3
Merge branch 'development' of https://github.com/chsami/Microbot into…
Aug 21, 2025
b2e5a93
fix(AIOFighter): Improve wait-for-loot reliability based on PR feedback
Aug 21, 2025
83f6db3
fix(AIOFighter): Allow looting during wait period even when attacked
Aug 21, 2025
cfd416d
Merge branch 'development' of https://github.com/chsami/Microbot into…
Aug 23, 2025
f202325
feat(aiofighter): add looting bag support and blighted food detection
Aug 23, 2025
e479d0e
Merge branch 'development' of https://github.com/chsami/Microbot into…
Aug 24, 2025
91ac511
fix(aiofighter): use NPC index comparison instead of object reference
Aug 24, 2025
8c75bd8
fix(aiofighter): add thread safety for wait-for-loot state variables
Aug 24, 2025
feaaade
fix(aiofighter): ensure pauseAllScripts is released in finally block
Aug 24, 2025
ff781f1
fix(aiofighter): correct instanceof check for Rs2NpcModel in target c…
Aug 24, 2025
e23cae4
fix(aiofighter): add volatile to cachedTargetNpcIndex and clamp timer…
Aug 24, 2025
ee5d188
chore(aiofighter): remove changes accidentally introduced from anothe…
Aug 24, 2025
9f1a7b2
Merge branch 'development' of https://github.com/chsami/Microbot into…
Aug 24, 2025
93ec43b
fix(aiofighter): improve UX with immediate status, cleanup imports, a…
Aug 24, 2025
e86c272
fix(aiofighter): improve inventory handling with fast food and re-check
Aug 25, 2025
285c006
fix(aiofighter): preserve concurrent pause state in LootScript
Aug 25, 2025
dd44c73
Merge branch 'development' of https://github.com/chsami/Microbot into…
Aug 25, 2025
750385a
fix(AIOfighter): fix line endings
Aug 26, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ public class AttackNpcScript extends Script {
public static Actor currentNpc = null;
public static AtomicReference<List<Rs2NpcModel>> filteredAttackableNpcs = new AtomicReference<>(new ArrayList<>());
public static Rs2WorldArea attackableArea = null;
public static volatile int cachedTargetNpcIndex = -1;
private boolean messageShown = false;
private int noNpcCount = 0;

Expand Down Expand Up @@ -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) {
Expand All @@ -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()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, if we dont have toggleForceLoot enabled & we dont have isWaitingForLoot, we just aren't looting at all?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's:

  • Skip looting if: in combat AND force loot is off AND NOT waiting for loot
  • This means: Allow looting when waiting for loot, even if in combat

return;
}

Expand All @@ -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());
}
Expand Down