From fcc3ecc8dea32c930559d914092e24e45a2b9f80 Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Mon, 18 May 2026 16:26:59 -0400 Subject: [PATCH 01/17] fix(tempoross): remove broken isOnOurSide filter, fix client-thread regressions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The X-based center-line check (CENTER_X=3047) only works near the boats. South of the boats the fishing areas converge — east-side fish spots, spirit pool, and fires all have X < 3047, so the filter excluded them. - Remove isOnOurSide from fish spots (distanceTo rangePoint already scopes) - Remove isOnOurSide from spirit pool (use distanceTo spiritPoolPoint ≤ 15) - Remove isOnOurSide from fires, tighten fire range from 35 to 5 tiles - Remove broken inCloud filter from fish spots (was no-op before cloud fix) - Fix walkToSpiritPool spam: use distance ≤ 2 instead of exact equality - Fix client-thread NPEs (Rs2Player.getWorldLocation, fightFiresInPath) - Remove all isInteracting gates (unreliable), use target-identity checks - Remove blocking waits, add isMoving guards for non-blocking loop - Add cloud dodge via LocalPoint comparison (fix coordinate space mismatch) - Lock tether target on wave tick to prevent oscillation - Add runtime counter to overlay - Sort item fetching by proximity - Use walkFastCanvas/walkFastLocal instead of blocking Rs2Walker.walkTo --- .../microbot/tempoross/TemporossOverlay.java | 24 +- .../TemporossProgressionOverlay.java | 12 +- .../microbot/tempoross/TemporossScript.java | 448 +++++++++--------- .../microbot/tempoross/TemporossWorkArea.java | 54 ++- 4 files changed, 283 insertions(+), 255 deletions(-) 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 ee8e8d4454..32d81dd2de 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,6 +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.player.Rs2Player; import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.ui.overlay.Overlay; import net.runelite.client.ui.overlay.OverlayLayer; @@ -57,17 +58,24 @@ 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.getWorldLocation()); - Rs2WorldPoint playerLocation = new Rs2WorldPoint(Microbot.getClient().getLocalPlayer().getWorldLocation()); - renderNpcOverlay(graphics, npc, Color.RED, npcLocation.distanceToPath(playerLocation.getWorldPoint()) + " tiles"); + WorldPoint playerWp = Rs2Player.getWorldLocation(); + if (playerWp != null) { + Rs2WorldPoint playerLocation = new Rs2WorldPoint(playerWp); + for (Rs2NpcModel npc : npcList) { + Rs2WorldPoint npcLocation = new Rs2WorldPoint(npc.getWorldLocation()); + renderNpcOverlay(graphics, npc, Color.RED, npcLocation.distanceToPath(playerLocation.getWorldPoint()) + " tiles"); + } } } if (ammoList != null) { - for (Rs2NpcModel npc : ammoList) { - 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())); + WorldPoint playerWp = Rs2Player.getWorldLocation(); + if (playerWp != null) { + Rs2WorldPoint playerLocation = new Rs2WorldPoint(playerWp); + for (Rs2NpcModel npc : ammoList) { + Rs2WorldPoint npcLocation = new Rs2WorldPoint(npc.getWorldLocation()); + String name = npc.getName(); + renderNpcOverlay(graphics, npc, Color.RED, npcLocation.distanceToPath(playerLocation.getWorldPoint()) + " " + (name != null ? Text.removeTags(name) : "")); + } } } if (fishList != null) { diff --git a/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossProgressionOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossProgressionOverlay.java index 9e06f3a19e..337d1c8202 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossProgressionOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossProgressionOverlay.java @@ -42,6 +42,16 @@ public Dimension render(Graphics2D graphics) { .color(Color.CYAN) .build()); + // Runtime + long elapsed = System.currentTimeMillis() - TemporossScript.startTime; + long seconds = (elapsed / 1000) % 60; + long minutes = (elapsed / (1000 * 60)) % 60; + long hours = elapsed / (1000 * 60 * 60); + panelComponent.getChildren().add(LineComponent.builder() + .left("Runtime:") + .right(String.format("%02d:%02d:%02d", hours, minutes, seconds)) + .build()); + // Add current state as a line component panelComponent.getChildren().add(LineComponent.builder() .left("Current State:") @@ -86,7 +96,7 @@ public Dimension render(Graphics2D graphics) { // Get interacting panelComponent.getChildren().add(LineComponent.builder() .left("Interacting with:") - .right(Rs2Player.getInteracting() != null ? Text.removeTags(Rs2Player.getInteracting().getName()) : "None") + .right(Rs2Player.getInteracting() != null && Rs2Player.getInteracting().getName() != null ? Text.removeTags(Rs2Player.getInteracting().getName()) : "None") .build()); if(currentState.isComplete()){ 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 2bab58e767..905675d97a 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 @@ -26,6 +26,7 @@ import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; import java.util.*; +import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -55,10 +56,14 @@ public class TemporossScript extends Script { public static List sortedFires = new ArrayList<>(); public static List sortedClouds = new ArrayList<>(); public static List fishSpots = new ArrayList<>(); + private static NPC lastCatchSpotNpc = null; + private static boolean walkedToFishArea = false; public static List walkPath = new ArrayList<>(); + public static long startTime; public boolean run(TemporossConfig config) { temporossConfig = config; + startTime = System.currentTimeMillis(); ENERGY = 0; INTENSITY = 0; ESSENCE = 0; @@ -89,6 +94,8 @@ public boolean run(TemporossConfig config) { handleMinigame(); handleStateLoop(); + if (handleCloudDodge()) + return; if(areItemsMissing()) return; // In solo mode, continuously handle fires. @@ -149,13 +156,20 @@ private void determineWorkArea() { } private void finishGame() { - Rs2WorldPoint playerLocation = new Rs2WorldPoint(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation())); + WorldPoint playerLocation = Rs2Player.getWorldLocation(); + if (playerLocation == null || workArea == null) { + return; + } + // Only consider Leave NPCs on our side of the bay. workArea.exitNpc is + // the anchor for our boat/beach — the opposite-bay captain is well + // outside this radius (the bay is much wider than 20 tiles). 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")) + && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Leave") + && npc.getWorldLocation().distanceTo(workArea.exitNpc) <= 20) .toList().stream() - .min(Comparator.comparingInt(value -> playerLocation.distanceToPath(value.getWorldLocation()))) + .min(Comparator.comparingInt(value -> playerLocation.distanceTo(value.getWorldLocation()))) .orElse(null); if (exitNpc != null) { int emptyBucketCount = Rs2Inventory.count(ItemID.BUCKET); @@ -181,6 +195,7 @@ private void reset(){ workArea = null; isFilling = false; isFightingFire = false; + walkedToFishArea = false; walkPath = null; TemporossPlugin.incomingWave = false; TemporossPlugin.isTethered = false; @@ -268,109 +283,86 @@ private boolean areItemsMissing() private void fetchMissingItems() { - // 1) Harpoon - if (!hasHarpoon() && harpoonType != HarpoonType.BAREHAND) - { - harpoonType = HarpoonType.HARPOON; - log("Missing selected harpoon, setting to default harpoon"); - TemporossPlugin.setHarpoonType(harpoonType); + WorldPoint playerLoc = Rs2Player.getWorldLocation(); + if (playerLoc == null) return; - // Before interacting, clear fires along the path to the harpoon crate. - if (!fightFiresInPath(workArea.harpoonPoint)) - { - log("Could not douse fires in path to harpoon crate, forfeiting"); - forfeit(); - return; - } + // Build list of needed items with their target points + List needed = new ArrayList<>(); + // 0=harpoon, 1=buckets, 2=fill, 3=rope, 4=hammer - if (workArea.getHarpoonCrate() != null && workArea.getHarpoonCrate().click("Take")) - { - log("Taking harpoon"); - sleepUntil(this::hasHarpoon, 10000); - } - return; + if (!hasHarpoon() && harpoonType != HarpoonType.BAREHAND) { + needed.add(new int[]{0, playerLoc.distanceTo(workArea.harpoonPoint)}); } - // 2) Buckets int bucketCount = Rs2Inventory.count(item -> item.getId() == ItemID.BUCKET || item.getId() == ItemID.BUCKET_OF_WATER); - if ((bucketCount < temporossConfig.buckets() && state == State.INITIAL_CATCH) || bucketCount == 0) - { - log("Buckets: " + bucketCount); - - // Before interacting, clear fires along the path to the bucket crate. - if (!fightFiresInPath(workArea.bucketPoint)) - { - log("Could not douse fires in path to bucket crate, forfeiting"); - forfeit(); - return; - } - sleepUntil(() -> Rs2Inventory.count(item -> - item.getId() == ItemID.BUCKET || item.getId() == ItemID.BUCKET_OF_WATER) >= temporossConfig.buckets(),() -> { - if (workArea.getBucketCrate() != null && workArea.getBucketCrate().click("Take")) { - log("Taking buckets"); - Rs2Inventory.waitForInventoryChanges(3000); - }},10000,300); - - - return; + boolean needBuckets = (bucketCount < temporossConfig.buckets() && state == State.INITIAL_CATCH) || bucketCount == 0; + if (needBuckets) { + needed.add(new int[]{1, playerLoc.distanceTo(workArea.bucketPoint)}); } - // 3) Fill Buckets + // Fill only eligible if we have empty buckets but no full ones int fullBucketCount = Rs2Inventory.count(ItemID.BUCKET_OF_WATER); - if (fullBucketCount <= 0) - { - // Before interacting, clear fires along the path to the pump. - if (!fightFiresInPath(workArea.pumpPoint)) - { - log("Could not douse fires in path to pump, forfeiting"); - forfeit(); - return; - } - - if (workArea.getPump() != null && workArea.getPump().click("Use")) - { - log("Filling buckets"); - sleepUntil(() -> Rs2Inventory.count(ItemID.BUCKET) <= 0, 10000); - } - return; + if (!needBuckets && fullBucketCount <= 0) { + needed.add(new int[]{2, playerLoc.distanceTo(workArea.pumpPoint)}); } - // 4) Rope (if required) - if (temporossConfig.rope() && !temporossConfig.spiritAnglers() && !Rs2Inventory.contains(ItemID.ROPE)) - { - // Before interacting, clear fires along the path to the rope crate. - if (!fightFiresInPath(workArea.ropePoint)) - { - log("Could not douse fires in path to rope crate, forfeiting"); - forfeit(); - return; - } + if (temporossConfig.rope() && !temporossConfig.spiritAnglers() && !Rs2Inventory.contains(ItemID.ROPE)) { + needed.add(new int[]{3, playerLoc.distanceTo(workArea.ropePoint)}); + } - if (workArea.getRopeCrate() != null && workArea.getRopeCrate().click("Take")) - { - log("Taking rope"); - sleepUntil(() -> Rs2Inventory.waitForInventoryChanges(10000)); - } - return; + if (temporossConfig.hammer() && !Rs2Inventory.contains(ItemID.HAMMER)) { + needed.add(new int[]{4, playerLoc.distanceTo(workArea.hammerPoint)}); } - // 5) Hammer (if required) - if (temporossConfig.hammer() && !Rs2Inventory.contains(ItemID.HAMMER)) - { - // Before interacting, clear fires along the path to the hammer crate. - if (!fightFiresInPath(workArea.hammerPoint)) - { - log("Could not douse fires in path to hammer crate, forfeiting"); - forfeit(); - return; - } + if (needed.isEmpty()) return; - if (workArea.getHammerCrate() != null && workArea.getHammerCrate().click("Take")) - { - log("Taking hammer"); - sleepUntil(() -> Rs2Inventory.waitForInventoryChanges(10000)); - } + // Sort by distance, fetch closest + needed.sort(Comparator.comparingInt(a -> a[1])); + int closest = needed.get(0)[0]; + + switch (closest) { + case 0: // Harpoon + harpoonType = HarpoonType.HARPOON; + log("Missing selected harpoon, setting to default harpoon"); + TemporossPlugin.setHarpoonType(harpoonType); + if (!fightFiresInPath(workArea.harpoonPoint)) { forfeit(); return; } + if (workArea.getHarpoonCrate() != null && workArea.getHarpoonCrate().click("Take")) { + log("Taking harpoon"); + sleepUntil(this::hasHarpoon, 10000); + } + break; + case 1: // Buckets + if (!fightFiresInPath(workArea.bucketPoint)) { forfeit(); return; } + sleepUntil(() -> Rs2Inventory.count(item -> + item.getId() == ItemID.BUCKET || item.getId() == ItemID.BUCKET_OF_WATER) >= temporossConfig.buckets(), () -> { + if (workArea.getBucketCrate() != null && workArea.getBucketCrate().click("Take")) { + log("Taking buckets"); + Rs2Inventory.waitForInventoryChanges(3000); + } + }, 10000, 300); + break; + case 2: // Fill buckets + if (!fightFiresInPath(workArea.pumpPoint)) { forfeit(); return; } + if (workArea.getPump() != null && workArea.getPump().click("Use")) { + log("Filling buckets"); + sleepUntil(() -> Rs2Inventory.count(ItemID.BUCKET) <= 0, 10000); + } + break; + case 3: // Rope + if (!fightFiresInPath(workArea.ropePoint)) { forfeit(); return; } + if (workArea.getRopeCrate() != null && workArea.getRopeCrate().click("Take")) { + log("Taking rope"); + sleepUntil(() -> Rs2Inventory.waitForInventoryChanges(10000)); + } + break; + case 4: // Hammer + if (!fightFiresInPath(workArea.hammerPoint)) { forfeit(); return; } + if (workArea.getHammerCrate() != null && workArea.getHammerCrate().click("Take")) { + log("Taking hammer"); + sleepUntil(() -> Rs2Inventory.waitForInventoryChanges(10000)); + } + break; } } @@ -451,10 +443,10 @@ public static void updateFireData(){ .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())); + WorldPoint playerLocation = Rs2Player.getWorldLocation(); sortedFires = allFires.stream() - .filter(y -> playerLocation.distanceToPath(y.getWorldLocation()) < 35) - .sorted(Comparator.comparingInt(x -> playerLocation.distanceToPath(x.getWorldLocation()))) + .filter(y -> playerLocation.distanceTo(y.getWorldLocation()) <= 5) + .sorted(Comparator.comparingInt(x -> playerLocation.distanceTo(x.getWorldLocation()))) .collect(Collectors.toList()); TemporossOverlay.setNpcList(sortedFires); } @@ -463,10 +455,15 @@ public static void updateCloudData(){ List allClouds = Rs2GameObject.getGameObjects().stream() .filter(obj -> obj.getId() == NullObjectID.NULL_41006) .collect(Collectors.toList()); - Rs2WorldPoint playerLocation = new Rs2WorldPoint(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation())); + LocalPoint playerLocal = Microbot.getClient().getLocalPlayer() != null + ? Microbot.getClient().getLocalPlayer().getLocalLocation() : null; + if (playerLocal == null) { + sortedClouds = Collections.emptyList(); + return; + } sortedClouds = allClouds.stream() - .filter(y -> playerLocation.distanceToPath(y.getWorldLocation()) < 30) - .sorted(Comparator.comparingInt(x -> playerLocation.distanceToPath(x.getWorldLocation()))) + .filter(y -> y.getLocalLocation() != null && playerLocal.distanceTo(y.getLocalLocation()) < 30 * 128) + .sorted(Comparator.comparingInt(x -> playerLocal.distanceTo(x.getLocalLocation()))) .collect(Collectors.toList()); TemporossOverlay.setCloudList(sortedClouds); } @@ -476,6 +473,7 @@ public static void updateAmmoCrateData(){ List ammoCrates = Microbot.getRs2NpcCache().query() .where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Fill") + && workArea.isOnOurSide(npc.getWorldLocation()) && npc.getWorldLocation().distanceTo(workArea.mastPoint) <= 4 && !inCloud(npc.getWorldLocation(), 2)) .toList(); @@ -483,11 +481,9 @@ public static void updateAmmoCrateData(){ } public static void updateFishSpotData(){ - // if double fishing spot is present, prioritize it 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) + .where(npc -> npc.getWorldLocation().distanceTo(workArea.rangePoint) <= 20) .toList().stream() .sorted(Comparator .comparingInt(npc -> npc.getId() == NpcID.FISHING_SPOT_10569 ? 0 : 1)) @@ -519,10 +515,11 @@ private void handleFires() { Microbot.log("Filling, skipping fire"); return; } - if (Rs2Player.isInteracting()) { - if (Objects.equals(Rs2Player.getInteracting(), fire.getNpc())) { - return; - } + // Skip only if already dousing THIS specific fire. Target-identity + // check is reliable; the outer isInteracting() gate is not. + Actor current = Rs2Player.getInteracting(); + if (current != null && current == fire.getNpc()) { + return; } if (fire.click("Douse")) { log("Dousing fire"); @@ -533,14 +530,14 @@ private void handleFires() { } private void handleDamagedMast() { - if (Rs2Player.isMoving() || Rs2Player.isInteracting() || (temporossConfig.hammer() && !Rs2Inventory.contains("Hammer")) || !temporossConfig.hammer()) + if ((temporossConfig.hammer() && !Rs2Inventory.contains("Hammer")) || !temporossConfig.hammer()) return; Rs2TileObjectModel damagedMast = workArea.getBrokenMast(); if(damagedMast == null) return; - if (Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation().distanceTo(damagedMast.getWorldLocation())) <= 5) { - sleep(600); + WorldPoint playerLocMast = Rs2Player.getWorldLocation(); + if (playerLocMast != null && playerLocMast.distanceTo(damagedMast.getWorldLocation()) <= 5) { if (damagedMast.click("Repair")) { log("Repairing mast"); Rs2Player.waitForXpDrop(Skill.CONSTRUCTION, 2500); @@ -549,14 +546,14 @@ private void handleDamagedMast() { } private void handleDamagedTotem() { - if (Rs2Player.isMoving() || Rs2Player.isInteracting() || (temporossConfig.hammer() && !Rs2Inventory.contains("Hammer")) || !temporossConfig.hammer()) + if ((temporossConfig.hammer() && !Rs2Inventory.contains("Hammer")) || !temporossConfig.hammer()) return; Rs2TileObjectModel damagedTotem = workArea.getBrokenTotem(); if(damagedTotem == null) return; - if (Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation().distanceTo(damagedTotem.getWorldLocation())) <= 5) { - sleep(600); + WorldPoint playerLocTotem = Rs2Player.getWorldLocation(); + if (playerLocTotem != null && playerLocTotem.distanceTo(damagedTotem.getWorldLocation()) <= 5) { if (damagedTotem.click("Repair")) { log("Repairing totem"); Rs2Player.waitForXpDrop(Skill.CONSTRUCTION, 2500); @@ -564,32 +561,42 @@ private void handleDamagedTotem() { } } + private Rs2TileObjectModel lockedTether = null; + private void handleTether() { - Rs2TileObjectModel tether = workArea.getClosestTether(); - if (tether == null) { - return; - } if (TemporossPlugin.incomingWave != TemporossPlugin.isTethered) { - ShortestPathPlugin.exit(); - Rs2Walker.setTarget(null); - String action = TemporossPlugin.incomingWave ? "Tether" : "Untether"; - Rs2Camera.turnTo(tether.getLocalLocation()); - - if (action.equals("Tether")) { - if (tether.click(action)) { - log(action + "ing"); - sleepUntil(() -> TemporossPlugin.isTethered == TemporossPlugin.incomingWave, 3500); + if (TemporossPlugin.incomingWave) { + if (lockedTether == null) { + Rs2TileObjectModel mast = workArea.getMast(); + Rs2TileObjectModel totem = workArea.getTotem(); + lockedTether = workArea.getClosestTether(); + WorldPoint playerLoc = Rs2Player.getWorldLocation(); + log("Tether decision: mast=" + (mast != null ? mast.getWorldLocation() + " dist=" + (playerLoc != null ? playerLoc.distanceTo(mast.getWorldLocation()) : "?") : "NULL") + + " | totem=" + (totem != null ? totem.getWorldLocation() + " dist=" + (playerLoc != null ? playerLoc.distanceTo(totem.getWorldLocation()) : "?") : "NULL") + + " | picked=" + (lockedTether != null ? lockedTether.getWorldLocation() : "NULL")); } + if (lockedTether == null) { + return; + } + ShortestPathPlugin.exit(); + Rs2Walker.setTarget(null); + Rs2Camera.turnTo(lockedTether.getLocalLocation()); + log("Tethering"); + sleepUntil(() -> TemporossPlugin.isTethered, () -> lockedTether.click("Tether"), 8000, 600); + } else { + lockedTether = null; } - if (action.equals("Untether")) { - log(action + "ing"); - sleepUntil(() -> TemporossPlugin.isTethered == TemporossPlugin.incomingWave, 3500); - } + } else if (!TemporossPlugin.incomingWave) { + lockedTether = null; } } private void handleStateLoop() { - temporossPool = Microbot.getRs2NpcCache().query().withId(NpcID.SPIRIT_POOL).toList().stream().min(Comparator.comparingInt(x -> workArea.spiritPoolPoint.distanceTo(x.getWorldLocation()))).orElse(null); + temporossPool = Microbot.getRs2NpcCache().query().withId(NpcID.SPIRIT_POOL) + .where(npc -> npc.getWorldLocation().distanceTo(workArea.spiritPoolPoint) <= 15) + .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) { @@ -629,79 +636,39 @@ private void handleMainLoop() { case SECOND_CATCH: case THIRD_CATCH: isFilling = false; - if (inCloud(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()), 1)) { - GameObject cloud = sortedClouds.stream() - .findFirst() - .orElse(null); - if (cloud != null) { - Rs2Walker.walkNextToInstance(cloud); - Rs2Player.waitForWalking(); - if (inCloud(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()), 1)) { - Microbot.log("Current spot is clouded, looking for a better fishing spot..."); - - var playerLocation = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); - - var safeFishSpot = fishSpots.stream() - .filter(spot -> !inCloud(spot.getWorldLocation(), 1)) - .min(Comparator.comparingInt(spot -> spot.getWorldLocation().distanceTo(playerLocation))) - .orElse(null); - - if (safeFishSpot != null) { - Rs2Camera.turnTo(safeFishSpot.getNpc()); - safeFishSpot.click("Harpoon"); - Microbot.log("Moved to a " + - (safeFishSpot.getId() == NpcID.FISHING_SPOT_10569 ? "double" : "single") + - " fish spot."); - Rs2Player.waitForWalking(2000); - } else { - Microbot.log("No safe fishing spots found. Waiting..."); - } - return; - } - } - } var fishSpot = fishSpots.stream() .findFirst() .orElse(null); - if (fishSpot != null) { - // In mass world mode, clear fires along the path to the fish spot before interacting. + if (fishSpot != null && fishSpot.getNpc() != null) { + walkedToFishArea = false; if (!temporossConfig.solo()) { if(!fightFiresInPath(fishSpot.getWorldLocation())) return; } - if (Rs2Player.isInteracting()) { - Actor currentTarget = Rs2Player.getInteracting(); - if (currentTarget != null && currentTarget instanceof NPC) { - NPC targetNpc = (NPC) currentTarget; - if (targetNpc.getId() == fishSpot.getId()) { - return; - } - } + if (fishSpot.getNpc() == lastCatchSpotNpc && (Rs2Player.isAnimating() || Rs2Player.isMoving())) { + return; } Rs2Camera.turnTo(fishSpot.getNpc()); fishSpot.click("Harpoon"); + lastCatchSpotNpc = fishSpot.getNpc(); log("Interacting with " + (fishSpot.getId() == NpcID.FISHING_SPOT_10569 ? "double" : "single") + " fish spot"); - Rs2Player.waitForWalking(2000); } else { - // In mass world mode, clear fires along the path to the totem pole before moving. + if (Rs2Player.isMoving() || walkedToFishArea) { + return; + } if (!temporossConfig.solo()) { if(!fightFiresInPath(workArea.totemPoint)) return; } - LocalPoint localPoint = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(),workArea.totemPoint); + LocalPoint localPoint = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), workArea.totemPoint); + if (localPoint == null) return; Rs2Camera.turnTo(localPoint); - if (Rs2Camera.isTileOnScreen(localPoint) && Microbot.isPluginEnabled(GpuPlugin.class)) { - Rs2Walker.walkFastLocal(localPoint); - log("Can't find the fish spot, walking to the totem pole"); - Rs2Player.waitForWalking(2000); - return; - } + WorldPoint instancePoint = WorldPoint.fromLocalInstance(Microbot.getClient(), localPoint); + Rs2Walker.walkFastCanvas(instancePoint); + walkedToFishArea = true; log("Can't find the fish spot, walking to the totem pole"); - if (localPoint != null) { - Rs2Walker.walkTo(WorldPoint.fromLocalInstance(Microbot.getClient(),localPoint)); - } return; } break; @@ -713,21 +680,16 @@ private void handleMainLoop() { int rawFishCount = Rs2Inventory.count(ItemID.RAW_HARPOONFISH); Rs2TileObjectModel range = workArea != null ? workArea.getRange() : null; if (range != null && rawFishCount > 0) { - if(Rs2Player.isInteracting()) { - return; - } - if (Rs2Player.isMoving() || Rs2Player.getAnimation() == AnimationID.COOKING_RANGE) { + if (Rs2Player.getAnimation() == AnimationID.COOKING_RANGE || Rs2Player.isMoving()) { return; } range.click("Cook-at"); log("Interacting with range"); - sleepUntil(Rs2Player::isAnimating, 5000); } else if (range == null) { log("Can't find the range, walking to the range point"); LocalPoint localPoint = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(),workArea.rangePoint); Rs2Camera.turnTo(localPoint); Rs2Walker.walkFastLocal(localPoint); - Rs2Player.waitForWalking(3000); } break; @@ -738,23 +700,27 @@ private void handleMainLoop() { .where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null && npc.getNpc().getComposition().getActions() != null && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Fill") + && workArea.isOnOurSide(npc.getWorldLocation()) && npc.getWorldLocation().distanceTo(workArea.mastPoint) <= 4 && !inCloud(npc.getWorldLocation(), 1)) .toList(); - WorldPoint fillPlayerLoc = Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()); + WorldPoint fillPlayerLoc = Rs2Player.getWorldLocation(); if (inCloud(fillPlayerLoc,5) && !isFilling) { GameObject cloud = sortedClouds.stream() .findFirst() .orElse(null); - Rs2Walker.walkNextToInstance(cloud); - Rs2Player.waitForWalking(); + if (cloud != null) { + Rs2Walker.walkNextToInstance(cloud); + } return; } if (ammoCrates.isEmpty()) { - log("Can't find ammo crate, walking to the safe point"); - walkToSafePoint(); + if (!Rs2Player.isMoving()) { + log("Can't find ammo crate, walking to the safe point"); + walkToSafePoint(); + } return; } @@ -765,7 +731,6 @@ private void handleMainLoop() { Rs2Camera.turnTo(ammoCrate.getNpc()); ammoCrate.click("Fill"); log("Switching ammo crate"); - Rs2Player.waitForWalking(5000); isFilling = true; return; } @@ -780,24 +745,22 @@ private void handleMainLoop() { } - if (Rs2Player.isInteracting()) { - if (Objects.equals(Objects.requireNonNull(Rs2Player.getInteracting()).getName(), ammoCrate.getName())) { - if(Rs2AntibanSettings.devDebug) - log("Interacting with: " + ammoCrate.getName()); - return; - } + if (isFilling && (Rs2Player.isAnimating() || Rs2Player.isMoving())) { + break; + } + if (ammoCrate == null || ammoCrate.getNpc() == null) { + break; } Rs2Camera.turnTo(ammoCrate.getNpc()); ammoCrate.click("Fill"); log("Interacting with ammo crate"); - Rs2Inventory.waitForInventoryChanges(5000); isFilling = true; break; case ATTACK_TEMPOROSS: isFilling = false; - if (temporossPool != null) { - if (Rs2Player.isInteracting()) { + if (temporossPool != null && temporossPool.getNpc() != null) { + if (Rs2Player.isAnimating() || Rs2Player.isMoving()) { if (ENERGY >= 95) { log("Energy is full, stopping attack"); state = null; @@ -824,14 +787,15 @@ private void handleMainLoop() { } temporossPool.click("Harpoon"); log("Harpooning Tempoross"); - Rs2Player.waitForWalking(2000); } else { if (ENERGY > 5) { state = null; return; } - log("Can't find Tempoross, walking to the Tempoross pool"); - walkToSpiritPool(); + if (!Rs2Player.isMoving()) { + log("Can't find Tempoross, walking to the Tempoross pool"); + walkToSpiritPool(); + } } break; } @@ -852,14 +816,8 @@ private void walkToSafePoint() { return; } LocalPoint localPoint = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(),workArea.safePoint); - WorldPoint worldPoint = WorldPoint.fromLocalInstance(Microbot.getClient(),localPoint); Rs2Camera.turnTo(localPoint); - if (Rs2Camera.isTileOnScreen(localPoint) && Microbot.isPluginEnabled(GpuPlugin.class)) { - Rs2Walker.walkFastLocal(localPoint); - Rs2Player.waitForWalking(2000); - } else { - Rs2Walker.walkTo(worldPoint); - } + Rs2Walker.walkFastLocal(localPoint); } /** @@ -872,15 +830,42 @@ private void walkToSpiritPool() { } LocalPoint localPoint = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(),workArea.spiritPoolPoint); Rs2Camera.turnTo(localPoint); - assert localPoint != null; - if(Objects.equals(Microbot.getClient().getLocalDestinationLocation(), localPoint) || Objects.equals(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation()), workArea.spiritPoolPoint)) + if (localPoint == null) return; + WorldPoint playerLoc = Rs2Player.getWorldLocation(); + if (playerLoc != null && playerLoc.distanceTo(workArea.spiritPoolPoint) <= 2) return; - if(Rs2Camera.isTileOnScreen(localPoint) && Microbot.isPluginEnabled(GpuPlugin.class)) { - Rs2Walker.walkFastLocal(localPoint); - Rs2Player.waitForWalking(2000); + if(Objects.equals(Microbot.getClient().getLocalDestinationLocation(), localPoint)) + return; + Rs2Walker.walkFastLocal(localPoint); + } + + private boolean handleCloudDodge() { + WorldPoint playerLoc = Rs2Player.getWorldLocation(); + if (playerLoc == null) { + return false; + } + if (!inCloud(playerLoc, 0)) { + return false; + } + // Already dodging — wait for movement to clear the cloud + if (Rs2Player.isMoving()) { + return true; + } + // Find the cloud on/near the player using local coordinates + LocalPoint playerLocal = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), playerLoc); + if (playerLocal == null) { + return false; } - else - Rs2Walker.walkTo(getTrueWorldPoint(workArea.spiritPoolPoint)); + GameObject cloud = sortedClouds.stream() + .filter(c -> c.getLocalLocation() != null && playerLocal.distanceTo(c.getLocalLocation()) <= 128) + .findFirst() + .orElse(sortedClouds.stream().findFirst().orElse(null)); + if (cloud != null) { + log("Standing in fire cloud — dodging"); + Rs2Walker.walkNextToInstance(cloud); + return true; + } + return false; } private boolean inCloud(LocalPoint point) { @@ -891,17 +876,23 @@ private boolean inCloud(LocalPoint point) { } public static boolean inCloud(WorldPoint point, int radius) { - Rs2WorldArea area = new Rs2WorldArea(point.toWorldArea()); - area = area.offset(radius); - if(sortedClouds.isEmpty()) + if (sortedClouds.isEmpty()) { return false; - Rs2WorldArea finalArea = area; - return sortedClouds.stream().anyMatch(cloud -> finalArea.contains(cloud.getWorldLocation())); + } + LocalPoint playerLocal = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), point); + if (playerLocal == null) { + return false; + } + int threshold = (radius + 1) * 128; + return sortedClouds.stream().anyMatch(cloud -> { + LocalPoint cloudLocal = cloud.getLocalLocation(); + return cloudLocal != null && playerLocal.distanceTo(cloudLocal) <= threshold; + }); } // method to fight fires that is in a path to a location public boolean fightFiresInPath(WorldPoint location) { - Rs2WorldPoint playerLocation = new Rs2WorldPoint(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getWorldLocation())); + Rs2WorldPoint playerLocation = new Rs2WorldPoint(Rs2Player.getWorldLocation()); List walkerPath = playerLocation.pathTo(location,true); walkPath = walkerPath; if (sortedFires.isEmpty()) { @@ -911,10 +902,11 @@ public boolean fightFiresInPath(WorldPoint location) { int fullBucketCount = Rs2Inventory.count(ItemID.BUCKET_OF_WATER); - // Filter fires that are actually on the path. - List firesInPath = sortedFires.stream() + // Filter fires that are actually on the path. getWorldArea() now + // requires the client thread (upstream RuneLite change). + List firesInPath = Microbot.getClientThread().invoke(() -> sortedFires.stream() .filter(fire -> walkerPath.stream().anyMatch(pathPoint -> fire.getNpc().getWorldArea().contains(pathPoint))) - .collect(Collectors.toList()); + .collect(Collectors.toList())); if (firesInPath.isEmpty()) { return true; 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 23038a16f7..0e17e6fa3f 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,10 +2,12 @@ import net.runelite.api.NullObjectID; import net.runelite.api.ObjectID; +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.api.tileobject.models.Rs2TileObjectModel; import net.runelite.client.plugins.microbot.util.coords.Rs2WorldPoint; +import net.runelite.client.plugins.microbot.util.player.Rs2Player; public class TemporossWorkArea @@ -22,8 +24,11 @@ public class TemporossWorkArea public final WorldPoint rangePoint; public final WorldPoint spiritPoolPoint; + public final boolean isWest; + public TemporossWorkArea(WorldPoint exitNpc, boolean isWest) { + this.isWest = isWest; this.exitNpc = exitNpc; this.safePoint = exitNpc.dx(1).dy(1); @@ -79,20 +84,19 @@ public Rs2TileObjectModel getHarpoonCrate() } public Rs2TileObjectModel getMast() { - Rs2TileObjectModel mast = Microbot.getRs2TileObjectCache().query().withIds(NullObjectID.NULL_41352, NullObjectID.NULL_41353).within(mastPoint, 2).nearest(); - return mast; + return Microbot.getRs2TileObjectCache().query().withIds(NullObjectID.NULL_41352, NullObjectID.NULL_41353).within(mastPoint, 10).nearest(); } public Rs2TileObjectModel getBrokenMast() { - return Microbot.getRs2TileObjectCache().query().withIds(ObjectID.DAMAGED_MAST_40996, ObjectID.DAMAGED_MAST_40997).within(mastPoint, 2).nearest(); + return Microbot.getRs2TileObjectCache().query().withIds(ObjectID.DAMAGED_MAST_40996, ObjectID.DAMAGED_MAST_40997).within(mastPoint, 10).nearest(); } public Rs2TileObjectModel getTotem() { - return Microbot.getRs2TileObjectCache().query().withIds(NullObjectID.NULL_41355, NullObjectID.NULL_41354).within(totemPoint, 2).nearest(); + return Microbot.getRs2TileObjectCache().query().withIds(NullObjectID.NULL_41355, NullObjectID.NULL_41354).within(totemPoint, 10).nearest(); } public Rs2TileObjectModel getBrokenTotem() { - return Microbot.getRs2TileObjectCache().query().withIds(ObjectID.DAMAGED_TOTEM_POLE, ObjectID.DAMAGED_TOTEM_POLE_41011).within(totemPoint, 2).nearest(); + return Microbot.getRs2TileObjectCache().query().withIds(ObjectID.DAMAGED_TOTEM_POLE, ObjectID.DAMAGED_TOTEM_POLE_41011).within(totemPoint, 10).nearest(); } public Rs2TileObjectModel getRange() @@ -101,24 +105,38 @@ public Rs2TileObjectModel getRange() } public Rs2TileObjectModel getClosestTether() { - Rs2TileObjectModel mast = getMast(); - Rs2TileObjectModel totem = getTotem(); + Rs2TileObjectModel mast = getMast(); + Rs2TileObjectModel totem = getTotem(); - if (mast == null) { - return totem; - } + if (mast == null) { + return totem; + } + + if (totem == null) { + return mast; + } - if (totem == null) { - return mast; + LocalPoint playerLocal = Microbot.getClient().getLocalPlayer() != null + ? Microbot.getClient().getLocalPlayer().getLocalLocation() : null; + if (playerLocal == null) { + return mast; + } + + int mastDist = playerLocal.distanceTo(mast.getLocalLocation()); + int totemDist = playerLocal.distanceTo(totem.getLocalLocation()); + return mastDist <= totemDist ? mast : totem; } - Rs2WorldPoint mastLocation = new Rs2WorldPoint(mast.getWorldLocation()); - Rs2WorldPoint totemLocation = new Rs2WorldPoint(totem.getWorldLocation()); - Rs2WorldPoint playerLocation = new Rs2WorldPoint(Microbot.getClient().getLocalPlayer().getWorldLocation()); + private static final int CENTER_X = 3047; - return mastLocation.distanceToPath(playerLocation.getWorldPoint()) < - totemLocation.distanceToPath(playerLocation.getWorldPoint()) ? mast : totem; -} + public boolean isOnOurSide(WorldPoint point) { + if (point == null) return false; + if (isWest) { + return point.getX() <= CENTER_X; + } else { + return point.getX() >= CENTER_X; + } + } public String getAllPointsAsString() { String sb = "exitNpc=" + exitNpc + From 1c602ace5d69821561f2a62dcc7a03c331d271ea Mon Sep 17 00:00:00 2001 From: runsonmypc Date: Mon, 18 May 2026 16:31:19 -0400 Subject: [PATCH 02/17] fix(tempoross): skip fish spots adjacent to clouds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Filter out fishing spots where the player's fishing position (adjacent tile) would be under a fire cloud. Uses inCloud radius 1 which covers 2 tiles from the spot center — enough to detect clouds on any adjacent tile the player would stand on. --- .../client/plugins/microbot/tempoross/TemporossScript.java | 1 + 1 file changed, 1 insertion(+) 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 905675d97a..cae93eb6dc 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 @@ -638,6 +638,7 @@ private void handleMainLoop() { isFilling = false; var fishSpot = fishSpots.stream() + .filter(npc -> !inCloud(npc.getWorldLocation(), 1)) .findFirst() .orElse(null); From 08405480892aa128e23ddb1f7f76f9b262d6ee57 Mon Sep 17 00:00:00 2001 From: Alex <45095641+runsonmypc@users.noreply.github.com> Date: Mon, 18 May 2026 17:48:06 -0400 Subject: [PATCH 03/17] fix(tempoross): eliminate wave freeze by short-circuiting script loop during incoming waves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The client thread was being saturated with ~20+ invoke() round-trips per 300ms script iteration during waves — inventory queries from areItemsMissing(), live State.getAllFish()/getTotalAvailableFishSlots() calls in handleStateLoop(), and state.isComplete() in onGameTick all dispatched to the client thread while it was at peak load processing wave NPC spawns. On Mac this stalled rendering for seconds. During a wave the only valid action is tethering, so the script loop now short-circuits to handleTether() immediately. onGameTick also returns early during waves. Additional fixes: - Replace live State method calls in handleStateLoop with cached values - Replace live isInMinigame() in overlays with cachedInMinigame - Remove dead Rs2WorldPoint allocation per fish spot per frame - Remove pathfinding (distanceToPath) from overlay render loop - Move state transitions and inventory queries out of overlay render - Add fire-adjacent fish spot detection and douse-before-fish logic - Enable fire handling in both solo and mass modes - Fix fire search radius (35 for solo, 5 for mass) - Remove Rs2Camera.turnTo before tether, click tether immediately - Add JVM args and javaLauncher to runDebug task for Mac JDK 11 --- build.gradle | 5 ++ .../microbot/tempoross/TemporossOverlay.java | 28 ++----- .../microbot/tempoross/TemporossPlugin.java | 24 +++--- .../TemporossProgressionOverlay.java | 78 ++++++------------- .../microbot/tempoross/TemporossScript.java | 52 ++++++++++--- .../java/net/runelite/client/Microbot.java | 15 +--- 6 files changed, 92 insertions(+), 110 deletions(-) diff --git a/build.gradle b/build.gradle index df23ae3d9d..c4c50dd6e8 100644 --- a/build.gradle +++ b/build.gradle @@ -133,6 +133,11 @@ tasks.register('runDebug', JavaExec) { dependsOn 'testClasses' classpath = sourceSets.test.runtimeClasspath mainClass = 'net.runelite.client.Microbot' + jvmArgs '-Xmx2048M', '-Xss2m' + javaLauncher = javaToolchains.launcherFor { + languageVersion = JavaLanguageVersion.of(project.ext.TARGET_JDK_VERSION) + vendor = JvmVendorSpec."${project.ext.JDK_VENDOR}" + } } // Plugin Logic 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 32d81dd2de..810382e6d4 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 @@ -8,7 +8,6 @@ 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.coords.Rs2WorldPoint; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.api.npc.models.Rs2NpcModel; import net.runelite.client.ui.overlay.Overlay; @@ -53,35 +52,24 @@ public TemporossOverlay(TemporossPlugin plugin) { @Override public Dimension render(Graphics2D graphics) { - if (!TemporossScript.isInMinigame()){ + if (!TemporossScript.cachedInMinigame){ return null; } // Render NPC overlays if the list is not null if (npcList != null) { - WorldPoint playerWp = Rs2Player.getWorldLocation(); - if (playerWp != null) { - Rs2WorldPoint playerLocation = new Rs2WorldPoint(playerWp); - for (Rs2NpcModel npc : npcList) { - Rs2WorldPoint npcLocation = new Rs2WorldPoint(npc.getWorldLocation()); - renderNpcOverlay(graphics, npc, Color.RED, npcLocation.distanceToPath(playerLocation.getWorldPoint()) + " tiles"); - } + for (Rs2NpcModel npc : npcList) { + renderNpcOverlay(graphics, npc, Color.RED, "Fire"); } } if (ammoList != null) { - WorldPoint playerWp = Rs2Player.getWorldLocation(); - if (playerWp != null) { - Rs2WorldPoint playerLocation = new Rs2WorldPoint(playerWp); - for (Rs2NpcModel npc : ammoList) { - Rs2WorldPoint npcLocation = new Rs2WorldPoint(npc.getWorldLocation()); - String name = npc.getName(); - renderNpcOverlay(graphics, npc, Color.RED, npcLocation.distanceToPath(playerLocation.getWorldPoint()) + " " + (name != null ? Text.removeTags(name) : "")); - } + for (Rs2NpcModel npc : ammoList) { + String name = npc.getName(); + renderNpcOverlay(graphics, npc, Color.RED, name != null ? Text.removeTags(name) : "Ammo"); } } if (fishList != null) { for (Rs2NpcModel npc : fishList) { - Rs2WorldPoint npcLocation = new Rs2WorldPoint(npc.getWorldLocation()); - renderNpcOverlay(graphics, npc, Color.RED, "Duck was here"); + renderNpcOverlay(graphics, npc, Color.RED, "Fish spot"); } } if (cloudList != null) { @@ -90,7 +78,7 @@ public Dimension render(Graphics2D graphics) { } } - if (TemporossScript.isInMinigame() && workArea != null) { + if (TemporossScript.cachedInMinigame && workArea != null) { // draw each work area WorldPoint renderWorldPoint(graphics, workArea.exitNpc, Color.RED, "Exit NPC"); 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 9f882747ef..ac64476851 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 @@ -92,22 +92,19 @@ protected void shutDown() throws Exception { @Subscribe public void onNpcChanged(NpcChanged event) { - - if (!TemporossScript.isInMinigame()) - return; - if (TemporossScript.workArea == null) - return; - TemporossScript.handleWidgetInfo(); - TemporossScript.updateFireData(); - TemporossScript.updateFishSpotData(); - TemporossScript.updateCloudData(); - TemporossScript.updateAmmoCrateData(); } @Subscribe public void onGameTick(GameTick e) { - if (!TemporossScript.isInMinigame()) + TemporossScript.cachedInMinigame = TemporossScript.isInMinigame(); + if (!TemporossScript.cachedInMinigame) return; + if (incomingWave) + return; + TemporossScript.cachedRawFish = State.getRawFish(); + TemporossScript.cachedCookedFish = State.getCookedFish(); + TemporossScript.cachedAllFish = State.getAllFish(); + TemporossScript.cachedTotalSlots = State.getTotalAvailableFishSlots(); if (TemporossScript.workArea == null) return; TemporossScript.handleWidgetInfo(); @@ -130,6 +127,11 @@ public void onGameTick(GameTick e) { if (TemporossScript.state == null) { TemporossScript.state = State.THIRD_CATCH; } + + if (TemporossScript.state != null && TemporossScript.state.isComplete()) { + TemporossScript.isFilling = false; + TemporossScript.state = TemporossScript.state.next == null ? State.THIRD_CATCH : TemporossScript.state.next; + } } @Subscribe diff --git a/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossProgressionOverlay.java b/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossProgressionOverlay.java index 337d1c8202..bca12ed892 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossProgressionOverlay.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossProgressionOverlay.java @@ -12,9 +12,6 @@ import javax.inject.Inject; import java.awt.*; -import static net.runelite.client.plugins.microbot.tempoross.State.getAllFish; -import static net.runelite.client.plugins.microbot.tempoross.State.getTotalAvailableFishSlots; - public class TemporossProgressionOverlay extends OverlayPanel { private final TemporossPlugin plugin; @@ -29,7 +26,7 @@ public TemporossProgressionOverlay(TemporossPlugin plugin) { @Override public Dimension render(Graphics2D graphics) { - if (TemporossScript.isInMinigame()) { + if (TemporossScript.cachedInMinigame) { State currentState = TemporossScript.state; if (currentState != null) { // Set up the panel's visual properties @@ -60,28 +57,28 @@ public Dimension render(Graphics2D graphics) { panelComponent.getChildren().add(LineComponent.builder() .left("Is completed:") - .right(currentState.isComplete() ? "Yes" : "No") + .right(currentState.next != null && currentState == TemporossScript.state ? "No" : "Yes") .build()); // Add fish count panelComponent.getChildren().add(LineComponent.builder() .left("Fish count:") - .right(String.valueOf(getAllFish())) + .right(String.valueOf(TemporossScript.cachedAllFish)) .build()); // Add cooked fish count panelComponent.getChildren().add(LineComponent.builder() .left("Cooked fish count:") - .right(String.valueOf(State.getCookedFish())) + .right(String.valueOf(TemporossScript.cachedCookedFish)) .build()); // Add raw fish count panelComponent.getChildren().add(LineComponent.builder() .left("Raw fish count:") - .right(String.valueOf(State.getRawFish())) + .right(String.valueOf(TemporossScript.cachedRawFish)) .build()); // Add total available fish slots panelComponent.getChildren().add(LineComponent.builder() .left("Total available fish slots:") - .right(String.valueOf(getTotalAvailableFishSlots())) + .right(String.valueOf(TemporossScript.cachedTotalSlots)) .build()); // Is filling panelComponent.getChildren().add(LineComponent.builder() @@ -99,11 +96,6 @@ public Dimension render(Graphics2D graphics) { .right(Rs2Player.getInteracting() != null && Rs2Player.getInteracting().getName() != null ? Text.removeTags(Rs2Player.getInteracting().getName()) : "None") .build()); - if(currentState.isComplete()){ - TemporossScript.isFilling = false; - TemporossScript.state = currentState.next == null ? State.THIRD_CATCH : currentState.next; - } - // Add progression bar double progression = calculateProgression(currentState); final ProgressBarComponent progressBar = new ProgressBarComponent(); @@ -121,61 +113,35 @@ public Dimension render(Graphics2D graphics) { } private double calculateProgression(State state) { + int cooked = TemporossScript.cachedCookedFish; + int all = TemporossScript.cachedAllFish; + int raw = TemporossScript.cachedRawFish; + int slots = TemporossScript.cachedTotalSlots; + boolean solo = TemporossScript.temporossConfig != null && TemporossScript.temporossConfig.solo(); + switch (state) { case ATTACK_TEMPOROSS: - // Progression based on energy level, capped at 94 (target). return Math.min(TemporossScript.ENERGY / 94.0, 1.0); - case SECOND_FILL: - // Progression goes up as cooked fish decreases (0 fish = 100% progress). - int cookedFishSecondFill = State.getCookedFish(); - return 1.0 - Math.min((double) cookedFishSecondFill / (TemporossScript.temporossConfig.solo() ? 19 : getTotalAvailableFishSlots()), 1.0); - + return 1.0 - Math.min((double) cooked / (solo ? 19 : slots), 1.0); case INITIAL_FILL: - // Progression goes up as cooked fish decreases (0 fish = 100% progress). - int cookedFishInitialFill = State.getCookedFish(); - return 1.0 - Math.min((double) cookedFishInitialFill / (TemporossScript.temporossConfig.solo() ? 17 : getTotalAvailableFishSlots()), 1.0); - + return 1.0 - Math.min((double) cooked / (solo ? 17 : slots), 1.0); case THIRD_COOK: - // Progression based on cooked fish count or intensity threshold. - int cookedFishThirdCook = State.getCookedFish(); - return Math.min((double) cookedFishThirdCook / (TemporossScript.temporossConfig.solo() ? 19 : getAllFish()), 1.0); - + return Math.min((double) cooked / (solo ? 19 : Math.max(all, 1)), 1.0); case THIRD_CATCH: - // Progression based on total fish count, target is 19. - int allFishThirdCatch = getAllFish(); - return Math.min((double) allFishThirdCatch / (TemporossScript.temporossConfig.solo() ? 19 : getTotalAvailableFishSlots()), 1.0); - + return Math.min((double) all / (solo ? 19 : slots), 1.0); case EMERGENCY_FILL: - // Progression reaches 100% when all fish count is zero. - int allFishEmergencyFill = getAllFish(); - return allFishEmergencyFill == 0 ? 1.0 : 0.0; - + return all == 0 ? 1.0 : 0.0; case SECOND_COOK: - // Progression based on cooked fish count reaching 17. - int cookedFishSecondCook = State.getCookedFish(); - return Math.min((double) cookedFishSecondCook / (TemporossScript.temporossConfig.solo() ? 17 : getAllFish()), 1.0); - + return Math.min((double) cooked / (solo ? 17 : Math.max(all, 1)), 1.0); case SECOND_CATCH: - // Progression based on total fish count, target is 17. - int allFishSecondCatch = getAllFish(); - return Math.min((double) allFishSecondCatch / (TemporossScript.temporossConfig.solo() ? 17 : getTotalAvailableFishSlots()), 1.0); - + return Math.min((double) all / (solo ? 17 : slots), 1.0); case INITIAL_COOK: - // Progression reaches 100% when raw fish count is zero. - int cookedFishInitialCook = State.getCookedFish(); - int allFishInitialCook = getAllFish(); - return Math.min((double) cookedFishInitialCook / allFishInitialCook, 1.0); - + return Math.min((double) cooked / Math.max(all, 1), 1.0); case INITIAL_CATCH: - // Progression based on raw fish count or total fish count; targets are 7 or 10. - int rawFishInitialCatch = State.getRawFish(); - int allFishInitialCatch = getAllFish(); return Math.max( - Math.min((double) rawFishInitialCatch / 7.0, 1.0), - Math.min((double) allFishInitialCatch / 10.0, 1.0) - ); - + Math.min((double) raw / 7.0, 1.0), + Math.min((double) all / 10.0, 1.0)); default: return 0.0; } 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 cae93eb6dc..2d4deb57bc 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 @@ -60,6 +60,11 @@ public class TemporossScript extends Script { private static boolean walkedToFishArea = false; public static List walkPath = new ArrayList<>(); public static long startTime; + public static int cachedRawFish; + public static int cachedCookedFish; + public static int cachedAllFish; + public static int cachedTotalSlots; + public static boolean cachedInMinigame; public boolean run(TemporossConfig config) { temporossConfig = config; @@ -92,17 +97,19 @@ public boolean run(TemporossConfig config) { sleep(300, 600); } else { + if (TemporossPlugin.incomingWave) { + handleTether(); + return; + } handleMinigame(); handleStateLoop(); if (handleCloudDodge()) return; if(areItemsMissing()) return; - // In solo mode, continuously handle fires. - // In mass world mode, fire-fighting is now handled dynamically before objectives. handleFires(); handleTether(); - if(isFightingFire || TemporossPlugin.isTethered || TemporossPlugin.incomingWave) + if(isFightingFire || TemporossPlugin.isTethered) return; handleDamagedMast(); handleDamagedTotem(); @@ -444,8 +451,9 @@ public static void updateFireData(){ && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Douse")) .toList(); WorldPoint playerLocation = Rs2Player.getWorldLocation(); + int fireRadius = temporossConfig != null && temporossConfig.solo() ? 35 : 5; sortedFires = allFires.stream() - .filter(y -> playerLocation.distanceTo(y.getWorldLocation()) <= 5) + .filter(y -> playerLocation.distanceTo(y.getWorldLocation()) <= fireRadius) .sorted(Comparator.comparingInt(x -> playerLocation.distanceTo(x.getWorldLocation()))) .collect(Collectors.toList()); TemporossOverlay.setNpcList(sortedFires); @@ -501,8 +509,7 @@ public static void updateLastWalkPath() { * is only triggered dynamically when an objective is set. */ private void handleFires() { - if (!temporossConfig.solo()) { - // Mass world mode: skip continuous fire-fighting. + if (TemporossPlugin.incomingWave) { return; } if (sortedFires.isEmpty() || state == State.ATTACK_TEMPOROSS) { @@ -580,7 +587,7 @@ private void handleTether() { } ShortestPathPlugin.exit(); Rs2Walker.setTarget(null); - Rs2Camera.turnTo(lockedTether.getLocalLocation()); + lockedTether.click("Tether"); log("Tethering"); sleepUntil(() -> TemporossPlugin.isTethered, () -> lockedTether.click("Tether"), 8000, 600); } else { @@ -618,8 +625,8 @@ private void handleStateLoop() { return; } - if (((TemporossScript.ENERGY < 30 && State.getAllFish() > 6) - || (TemporossScript.ENERGY < 50 && State.getAllFish() >= State.getTotalAvailableFishSlots())) + if (((TemporossScript.ENERGY < 30 && cachedAllFish > 6) + || (TemporossScript.ENERGY < 50 && cachedAllFish >= cachedTotalSlots)) && !temporossConfig.solo() && TemporossScript.state != State.ATTACK_TEMPOROSS) { log("Low energy, going for emergency fill"); @@ -639,16 +646,29 @@ private void handleMainLoop() { var fishSpot = fishSpots.stream() .filter(npc -> !inCloud(npc.getWorldLocation(), 1)) + .filter(npc -> { + boolean fireAdjacent = hasAdjacentFire(npc.getWorldLocation()); + return !fireAdjacent || Rs2Inventory.contains(ItemID.BUCKET_OF_WATER); + }) .findFirst() .orElse(null); if (fishSpot != null && fishSpot.getNpc() != null) { + Rs2NpcModel adjacentFire = getAdjacentFire(fishSpot.getWorldLocation()); + if (adjacentFire != null && Rs2Inventory.contains(ItemID.BUCKET_OF_WATER)) { + if (adjacentFire.click("Douse")) { + log("Dousing fire adjacent to fish spot"); + sleepUntil(() -> !Rs2Player.isInteracting(), 5000); + } + return; + } + walkedToFishArea = false; if (!temporossConfig.solo()) { if(!fightFiresInPath(fishSpot.getWorldLocation())) return; } - if (fishSpot.getNpc() == lastCatchSpotNpc && (Rs2Player.isAnimating() || Rs2Player.isMoving())) { + if (lastCatchSpotNpc != null && (Rs2Player.isAnimating() || Rs2Player.isMoving())) { return; } Rs2Camera.turnTo(fishSpot.getNpc()); @@ -891,6 +911,18 @@ public static boolean inCloud(WorldPoint point, int radius) { }); } + private boolean hasAdjacentFire(WorldPoint point) { + return sortedFires.stream() + .anyMatch(fire -> fire.getWorldLocation().distanceTo(point) <= 1); + } + + private Rs2NpcModel getAdjacentFire(WorldPoint point) { + return sortedFires.stream() + .filter(fire -> fire.getWorldLocation().distanceTo(point) <= 1) + .findFirst() + .orElse(null); + } + // method to fight fires that is in a path to a location public boolean fightFiresInPath(WorldPoint location) { Rs2WorldPoint playerLocation = new Rs2WorldPoint(Rs2Player.getWorldLocation()); diff --git a/src/test/java/net/runelite/client/Microbot.java b/src/test/java/net/runelite/client/Microbot.java index afcf25983c..fc57856a5b 100644 --- a/src/test/java/net/runelite/client/Microbot.java +++ b/src/test/java/net/runelite/client/Microbot.java @@ -4,25 +4,14 @@ import java.util.List; import java.util.stream.Collectors; -import net.runelite.client.plugins.fishing.FishingPlugin; 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.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; -import net.runelite.client.plugins.microbot.sailing.MSailingPlugin; -import net.runelite.client.plugins.microbot.thieving.ThievingPlugin; -import net.runelite.client.plugins.microbot.woodcutting.AutoWoodcuttingPlugin; -import net.runelite.client.plugins.woodcutting.WoodcuttingPlugin; +import net.runelite.client.plugins.microbot.tempoross.TemporossPlugin; public class Microbot { private static final Class[] debugPlugins = { - AIOFighterPlugin.class, + TemporossPlugin.class, AgentServerPlugin.class }; From d9e8054940b23f36af40ccc4e0980e99e05cc59d Mon Sep 17 00:00:00 2001 From: Alex <45095641+runsonmypc@users.noreply.github.com> Date: Tue, 19 May 2026 12:42:25 -0400 Subject: [PATCH 04/17] fix(tempoross): LocalPoint-based coordinate handling for instanced regions All distance checks, fire detection, work area determination, and NPC lookups now use LocalPoint instead of WorldPoint to avoid template vs instanced coordinate mismatches. Fixes wrong-side boat pathing, broken fire detection/dousing, ammo crate not found, and spirit pool spam. --- .../microbot/tempoross/TemporossScript.java | 214 +++++++++++------- 1 file changed, 135 insertions(+), 79 deletions(-) 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 2d4deb57bc..e3c4f70931 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 @@ -57,7 +57,6 @@ public class TemporossScript extends Script { public static List sortedClouds = new ArrayList<>(); public static List fishSpots = new ArrayList<>(); private static NPC lastCatchSpotNpc = null; - private static boolean walkedToFishArea = false; public static List walkPath = new ArrayList<>(); public static long startTime; public static int cachedRawFish; @@ -109,7 +108,7 @@ public boolean run(TemporossConfig config) { return; handleFires(); handleTether(); - if(isFightingFire || TemporossPlugin.isTethered) + if(isFightingFire) return; handleDamagedMast(); handleDamagedTotem(); @@ -145,8 +144,27 @@ private boolean hasHarpoon() { private void determineWorkArea() { if (workArea == null) { - 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(); + LocalPoint playerLocal = Microbot.getClient().getLocalPlayer() != null + ? Microbot.getClient().getLocalPlayer().getLocalLocation() : null; + if (playerLocal == null) return; + + List forfeitNpcs = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null + && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Forfeit")) + .toList(); + Rs2NpcModel forfeitNpc = forfeitNpcs.stream() + .filter(npc -> npc.getNpc().getLocalLocation() != null) + .min(Comparator.comparingInt(npc -> playerLocal.distanceTo(npc.getNpc().getLocalLocation()))) + .orElse(null); + + List ammoCrates = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null + && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Fill")) + .toList(); + Rs2NpcModel ammoCrate = ammoCrates.stream() + .filter(npc -> npc.getNpc().getLocalLocation() != null) + .min(Comparator.comparingInt(npc -> playerLocal.distanceTo(npc.getNpc().getLocalLocation()))) + .orElse(null); if (forfeitNpc == null || ammoCrate == null) { log("Can't find forfeit NPC or ammo crate"); @@ -154,11 +172,9 @@ private void determineWorkArea() { } boolean isWest = forfeitNpc.getWorldLocation().getX() < ammoCrate.getWorldLocation().getX(); workArea = new TemporossWorkArea(forfeitNpc.getWorldLocation(), isWest); - // log tempoross work area if its west or east - if(Rs2AntibanSettings.devDebug) { - log("Tempoross work area: " + (isWest ? "west" : "east")); - log(workArea.getAllPointsAsString()); - } + log("Tempoross work area: " + (isWest ? "west" : "east")); + log("Forfeit NPC at " + forfeitNpc.getWorldLocation() + " | Ammo crate at " + ammoCrate.getWorldLocation()); + log(workArea.getAllPointsAsString()); } } @@ -202,7 +218,8 @@ private void reset(){ workArea = null; isFilling = false; isFightingFire = false; - walkedToFishArea = false; + + lastCatchSpotNpc = null; walkPath = null; TemporossPlugin.incomingWave = false; TemporossPlugin.isTethered = false; @@ -213,19 +230,21 @@ private void reset(){ public void handleForfeit() { if ((INTENSITY >= 94 && state == State.THIRD_COOK)) { - 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 (forfeitNpc.click("Forfeit")) { - sleepUntil(() -> !isInMinigame(), 15000); - reset(); - BreakHandlerScript.setLockState(false); - } - } + forfeit(); } } private void forfeit() { - var forfeitNpc = Microbot.getRs2NpcCache().query().where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Forfeit")).nearest(); + LocalPoint playerLocal = Microbot.getClient().getLocalPlayer() != null + ? Microbot.getClient().getLocalPlayer().getLocalLocation() : null; + if (playerLocal == null) return; + var forfeitNpc = Microbot.getRs2NpcCache().query() + .where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null + && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Forfeit")) + .toList().stream() + .filter(npc -> npc.getNpc().getLocalLocation() != null) + .min(Comparator.comparingInt(npc -> playerLocal.distanceTo(npc.getNpc().getLocalLocation()))) + .orElse(null); if (forfeitNpc != null) { if (forfeitNpc.click("Forfeit")) { sleepUntil(() -> !isInMinigame(), 15000); @@ -450,11 +469,26 @@ public static void updateFireData(){ .where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Douse")) .toList(); - WorldPoint playerLocation = Rs2Player.getWorldLocation(); - int fireRadius = temporossConfig != null && temporossConfig.solo() ? 35 : 5; + LocalPoint playerLocal = Microbot.getClient().getLocalPlayer() != null + ? Microbot.getClient().getLocalPlayer().getLocalLocation() : null; + LocalPoint exitLocal = workArea != null + ? LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), workArea.exitNpc) : null; + int fireRadius = temporossConfig != null && temporossConfig.solo() ? 35 : 20; + int fireRadiusLocal = fireRadius * Perspective.LOCAL_TILE_SIZE; + int workAreaRadius = 30 * Perspective.LOCAL_TILE_SIZE; sortedFires = allFires.stream() - .filter(y -> playerLocation.distanceTo(y.getWorldLocation()) <= fireRadius) - .sorted(Comparator.comparingInt(x -> playerLocation.distanceTo(x.getWorldLocation()))) + .filter(y -> { + if (playerLocal == null || y.getNpc() == null || y.getNpc().getLocalLocation() == null) + return false; + if (exitLocal != null && y.getNpc().getLocalLocation().distanceTo(exitLocal) > workAreaRadius) + return false; + return y.getNpc().getLocalLocation().distanceTo(playerLocal) <= fireRadiusLocal; + }) + .sorted(Comparator.comparingInt(x -> { + if (playerLocal == null || x.getNpc() == null || x.getNpc().getLocalLocation() == null) + return Integer.MAX_VALUE; + return x.getNpc().getLocalLocation().distanceTo(playerLocal); + })) .collect(Collectors.toList()); TemporossOverlay.setNpcList(sortedFires); } @@ -478,11 +512,12 @@ public static void updateCloudData(){ // update ammo crate data public static void updateAmmoCrateData(){ + LocalPoint mastLocal = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), workArea.mastPoint); List ammoCrates = Microbot.getRs2NpcCache().query() .where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Fill") - && workArea.isOnOurSide(npc.getWorldLocation()) - && npc.getWorldLocation().distanceTo(workArea.mastPoint) <= 4 + && mastLocal != null && npc.getNpc().getLocalLocation() != null + && npc.getNpc().getLocalLocation().distanceTo(mastLocal) <= 4 * 128 && !inCloud(npc.getWorldLocation(), 2)) .toList(); TemporossOverlay.setAmmoList(ammoCrates); @@ -516,6 +551,10 @@ private void handleFires() { isFightingFire = false; return; } + if (!temporossConfig.solo()) { + isFightingFire = false; + return; + } isFightingFire = true; for (Rs2NpcModel fire : sortedFires) { if(isFilling){ @@ -628,7 +667,8 @@ private void handleStateLoop() { if (((TemporossScript.ENERGY < 30 && cachedAllFish > 6) || (TemporossScript.ENERGY < 50 && cachedAllFish >= cachedTotalSlots)) && !temporossConfig.solo() - && TemporossScript.state != State.ATTACK_TEMPOROSS) { + && TemporossScript.state != State.ATTACK_TEMPOROSS + && TemporossScript.state != State.EMERGENCY_FILL) { log("Low energy, going for emergency fill"); TemporossScript.state = State.EMERGENCY_FILL; } @@ -644,15 +684,29 @@ private void handleMainLoop() { case THIRD_CATCH: isFilling = false; + if (lastCatchSpotNpc != null && fishSpots.stream().noneMatch(npc -> npc.getNpc() == lastCatchSpotNpc)) { + lastCatchSpotNpc = null; + } + + long inCloudCount = fishSpots.stream().filter(npc -> inCloud(npc.getWorldLocation(), 1)).count(); + long fireCount = fishSpots.stream().filter(npc -> hasAdjacentFire(npc.getWorldLocation())).count(); + boolean alreadyFishing = Rs2Player.isAnimating() || Rs2Player.isInteracting(); + int emptySlots = cachedTotalSlots - cachedAllFish; var fishSpot = fishSpots.stream() .filter(npc -> !inCloud(npc.getWorldLocation(), 1)) .filter(npc -> { + if (alreadyFishing && npc.getId() == NpcID.FISHING_SPOT_10569 && emptySlots <= 4) + return false; boolean fireAdjacent = hasAdjacentFire(npc.getWorldLocation()); return !fireAdjacent || Rs2Inventory.contains(ItemID.BUCKET_OF_WATER); }) .findFirst() .orElse(null); + if (fishSpot == null && !fishSpots.isEmpty()) { + log("CATCH: " + fishSpots.size() + " spots found but all filtered (inCloud=" + inCloudCount + " fire=" + fireCount + ")"); + } + if (fishSpot != null && fishSpot.getNpc() != null) { Rs2NpcModel adjacentFire = getAdjacentFire(fishSpot.getWorldLocation()); if (adjacentFire != null && Rs2Inventory.contains(ItemID.BUCKET_OF_WATER)) { @@ -663,32 +717,35 @@ private void handleMainLoop() { return; } - walkedToFishArea = false; + if (!temporossConfig.solo()) { if(!fightFiresInPath(fishSpot.getWorldLocation())) return; } - if (lastCatchSpotNpc != null && (Rs2Player.isAnimating() || Rs2Player.isMoving())) { - return; + if (fishSpot.getNpc() == lastCatchSpotNpc) { + if (Rs2Player.isMoving()) { + return; + } + if (Rs2Player.isAnimating()) { + LocalPoint spotLocal = fishSpot.getNpc().getLocalLocation(); + LocalPoint pLocal = Microbot.getClient().getLocalPlayer() != null + ? Microbot.getClient().getLocalPlayer().getLocalLocation() : null; + if (spotLocal != null && pLocal != null && pLocal.distanceTo(spotLocal) <= Perspective.LOCAL_TILE_SIZE) { + return; + } + } } Rs2Camera.turnTo(fishSpot.getNpc()); fishSpot.click("Harpoon"); lastCatchSpotNpc = fishSpot.getNpc(); log("Interacting with " + (fishSpot.getId() == NpcID.FISHING_SPOT_10569 ? "double" : "single") + " fish spot"); } else { - if (Rs2Player.isMoving() || walkedToFishArea) { + if (Rs2Player.isMoving()) { return; } - if (!temporossConfig.solo()) { - if(!fightFiresInPath(workArea.totemPoint)) - return; - } LocalPoint localPoint = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), workArea.totemPoint); if (localPoint == null) return; - Rs2Camera.turnTo(localPoint); - WorldPoint instancePoint = WorldPoint.fromLocalInstance(Microbot.getClient(), localPoint); - Rs2Walker.walkFastCanvas(instancePoint); - walkedToFishArea = true; + Rs2Walker.walkFastLocal(localPoint); log("Can't find the fish spot, walking to the totem pole"); return; } @@ -717,26 +774,17 @@ private void handleMainLoop() { case EMERGENCY_FILL: case SECOND_FILL: case INITIAL_FILL: + LocalPoint mastLocal = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), workArea.mastPoint); 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") - && workArea.isOnOurSide(npc.getWorldLocation()) - && npc.getWorldLocation().distanceTo(workArea.mastPoint) <= 4 + && mastLocal != null && npc.getNpc().getLocalLocation() != null + && npc.getNpc().getLocalLocation().distanceTo(mastLocal) <= 4 * 128 && !inCloud(npc.getWorldLocation(), 1)) .toList(); WorldPoint fillPlayerLoc = Rs2Player.getWorldLocation(); - if (inCloud(fillPlayerLoc,5) && !isFilling) { - GameObject cloud = sortedClouds.stream() - .findFirst() - .orElse(null); - if (cloud != null) { - Rs2Walker.walkNextToInstance(cloud); - } - return; - } - if (ammoCrates.isEmpty()) { if (!Rs2Player.isMoving()) { log("Can't find ammo crate, walking to the safe point"); @@ -832,12 +880,14 @@ private static WorldPoint getTrueWorldPoint(WorldPoint point) { * In mass world mode, before walking to the safe point, clear fires along the path. */ private void walkToSafePoint() { - if (!temporossConfig.solo()) { - if(!fightFiresInPath(workArea.safePoint)) - return; - } - LocalPoint localPoint = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(),workArea.safePoint); - Rs2Camera.turnTo(localPoint); + LocalPoint localPoint = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), workArea.safePoint); + if (localPoint == null) return; + if (Objects.equals(Microbot.getClient().getLocalDestinationLocation(), localPoint)) + return; + LocalPoint playerLocal = Microbot.getClient().getLocalPlayer() != null + ? Microbot.getClient().getLocalPlayer().getLocalLocation() : null; + if (playerLocal != null && playerLocal.distanceTo(localPoint) < 3 * 128) + return; Rs2Walker.walkFastLocal(localPoint); } @@ -845,17 +895,13 @@ private void walkToSafePoint() { * In mass world mode, before walking to the spirit pool, clear fires along the path. */ private void walkToSpiritPool() { - if (!temporossConfig.solo()) { - if(!fightFiresInPath(workArea.safePoint)) - return; - } - LocalPoint localPoint = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(),workArea.spiritPoolPoint); - Rs2Camera.turnTo(localPoint); + LocalPoint localPoint = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), workArea.spiritPoolPoint); if (localPoint == null) return; - WorldPoint playerLoc = Rs2Player.getWorldLocation(); - if (playerLoc != null && playerLoc.distanceTo(workArea.spiritPoolPoint) <= 2) + if (Objects.equals(Microbot.getClient().getLocalDestinationLocation(), localPoint)) return; - if(Objects.equals(Microbot.getClient().getLocalDestinationLocation(), localPoint)) + LocalPoint playerLocal = Microbot.getClient().getLocalPlayer() != null + ? Microbot.getClient().getLocalPlayer().getLocalLocation() : null; + if (playerLocal != null && playerLocal.distanceTo(localPoint) < 3 * 128) return; Rs2Walker.walkFastLocal(localPoint); } @@ -923,43 +969,53 @@ private Rs2NpcModel getAdjacentFire(WorldPoint point) { .orElse(null); } - // method to fight fires that is in a path to a location public boolean fightFiresInPath(WorldPoint location) { - Rs2WorldPoint playerLocation = new Rs2WorldPoint(Rs2Player.getWorldLocation()); - List walkerPath = playerLocation.pathTo(location,true); - walkPath = walkerPath; if (sortedFires.isEmpty()) { return true; } - int fullBucketCount = Rs2Inventory.count(ItemID.BUCKET_OF_WATER); + LocalPoint playerLocal = Microbot.getClient().getLocalPlayer() != null + ? Microbot.getClient().getLocalPlayer().getLocalLocation() : null; + LocalPoint destLocal = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), location); + if (playerLocal == null || destLocal == null) { + return true; + } + int distToDest = playerLocal.distanceTo(destLocal); + int fullBucketCount = Rs2Inventory.count(ItemID.BUCKET_OF_WATER); - // Filter fires that are actually on the path. getWorldArea() now - // requires the client thread (upstream RuneLite change). - List firesInPath = Microbot.getClientThread().invoke(() -> sortedFires.stream() - .filter(fire -> walkerPath.stream().anyMatch(pathPoint -> fire.getNpc().getWorldArea().contains(pathPoint))) - .collect(Collectors.toList())); + List firesInPath = sortedFires.stream() + .filter(fire -> { + if (fire.getNpc() == null || fire.getNpc().getLocalLocation() == null) return false; + LocalPoint fireLocal = fire.getNpc().getLocalLocation(); + int distToFire = playerLocal.distanceTo(fireLocal); + int fireToDestDist = fireLocal.distanceTo(destLocal); + return distToFire < distToDest && fireToDestDist < distToDest; + }) + .sorted(Comparator.comparingInt(fire -> + playerLocal.distanceTo(fire.getNpc().getLocalLocation()))) + .collect(Collectors.toList()); if (firesInPath.isEmpty()) { return true; } - // Limit the number of fires doused based on available full buckets. if (firesInPath.size() > fullBucketCount) { firesInPath = firesInPath.subList(0, fullBucketCount); } for (Rs2NpcModel fire : firesInPath) { if (fire.click("Douse")) { - log("Dousing fire in path (mass world mode)"); + log("Dousing fire in path"); sleepUntil(Rs2Player::isInteracting, 2000); sleepUntil(() -> !Rs2Player.isInteracting(), 10000); } } - // Return true if sortedFires does not contain any fires in the path. - return sortedFires.stream().noneMatch(fire -> walkerPath.stream().anyMatch(pathPoint -> fire.getNpc().getWorldArea().contains(pathPoint))); + return firesInPath.stream().allMatch(fire -> { + if (fire.getNpc() == null || fire.getNpc().getLocalLocation() == null) return true; + return !sortedFires.contains(fire); + }); } @Override From 058ceabe7bab5626c1d391d93f5802fde840f28b Mon Sep 17 00:00:00 2001 From: Alex <45095641+runsonmypc@users.noreply.github.com> Date: Tue, 19 May 2026 13:36:17 -0400 Subject: [PATCH 05/17] fix(tempoross): wave-priority tethering, cloud radius, item gathering resilience All sleepUntil calls in item gathering break immediately on incoming wave so the script can tether. Removed forfeit-on-fire from item gathering to prevent skipping items when fires are near crates. Reduced ammo crate cloud filter radius from 1-2 tiles to 0 (only skip if cloud is directly on crate). Stale fish spot reference cleared when spot despawns. --- .../microbot/tempoross/TemporossScript.java | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) 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 e3c4f70931..9d8da4b071 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 @@ -352,41 +352,42 @@ private void fetchMissingItems() harpoonType = HarpoonType.HARPOON; log("Missing selected harpoon, setting to default harpoon"); TemporossPlugin.setHarpoonType(harpoonType); - if (!fightFiresInPath(workArea.harpoonPoint)) { forfeit(); return; } + fightFiresInPath(workArea.harpoonPoint); if (workArea.getHarpoonCrate() != null && workArea.getHarpoonCrate().click("Take")) { log("Taking harpoon"); - sleepUntil(this::hasHarpoon, 10000); + sleepUntil(() -> hasHarpoon() || TemporossPlugin.incomingWave, 10000); } break; case 1: // Buckets - if (!fightFiresInPath(workArea.bucketPoint)) { forfeit(); return; } + fightFiresInPath(workArea.bucketPoint); sleepUntil(() -> Rs2Inventory.count(item -> - item.getId() == ItemID.BUCKET || item.getId() == ItemID.BUCKET_OF_WATER) >= temporossConfig.buckets(), () -> { - if (workArea.getBucketCrate() != null && workArea.getBucketCrate().click("Take")) { + item.getId() == ItemID.BUCKET || item.getId() == ItemID.BUCKET_OF_WATER) >= temporossConfig.buckets() + || TemporossPlugin.incomingWave, () -> { + if (!TemporossPlugin.incomingWave && workArea.getBucketCrate() != null && workArea.getBucketCrate().click("Take")) { log("Taking buckets"); Rs2Inventory.waitForInventoryChanges(3000); } }, 10000, 300); break; case 2: // Fill buckets - if (!fightFiresInPath(workArea.pumpPoint)) { forfeit(); return; } + fightFiresInPath(workArea.pumpPoint); if (workArea.getPump() != null && workArea.getPump().click("Use")) { log("Filling buckets"); - sleepUntil(() -> Rs2Inventory.count(ItemID.BUCKET) <= 0, 10000); + sleepUntil(() -> Rs2Inventory.count(ItemID.BUCKET) <= 0 || TemporossPlugin.incomingWave, 10000); } break; case 3: // Rope - if (!fightFiresInPath(workArea.ropePoint)) { forfeit(); return; } + fightFiresInPath(workArea.ropePoint); if (workArea.getRopeCrate() != null && workArea.getRopeCrate().click("Take")) { log("Taking rope"); - sleepUntil(() -> Rs2Inventory.waitForInventoryChanges(10000)); + sleepUntil(() -> Rs2Inventory.contains(ItemID.ROPE) || TemporossPlugin.incomingWave, 10000); } break; case 4: // Hammer - if (!fightFiresInPath(workArea.hammerPoint)) { forfeit(); return; } + fightFiresInPath(workArea.hammerPoint); if (workArea.getHammerCrate() != null && workArea.getHammerCrate().click("Take")) { log("Taking hammer"); - sleepUntil(() -> Rs2Inventory.waitForInventoryChanges(10000)); + sleepUntil(() -> Rs2Inventory.contains(ItemID.HAMMER) || TemporossPlugin.incomingWave, 10000); } break; } @@ -518,7 +519,7 @@ public static void updateAmmoCrateData(){ && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Fill") && mastLocal != null && npc.getNpc().getLocalLocation() != null && npc.getNpc().getLocalLocation().distanceTo(mastLocal) <= 4 * 128 - && !inCloud(npc.getWorldLocation(), 2)) + && !inCloud(npc.getWorldLocation(), 0)) .toList(); TemporossOverlay.setAmmoList(ammoCrates); } @@ -781,7 +782,7 @@ private void handleMainLoop() { && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Fill") && mastLocal != null && npc.getNpc().getLocalLocation() != null && npc.getNpc().getLocalLocation().distanceTo(mastLocal) <= 4 * 128 - && !inCloud(npc.getWorldLocation(), 1)) + && !inCloud(npc.getWorldLocation(), 0)) .toList(); WorldPoint fillPlayerLoc = Rs2Player.getWorldLocation(); From 000e4cd126c3be9e93cbd27621ebec8ef2c0505c Mon Sep 17 00:00:00 2001 From: Alex <45095641+runsonmypc@users.noreply.github.com> Date: Tue, 19 May 2026 14:57:28 -0400 Subject: [PATCH 06/17] fix(tempoross): eliminate all remaining WorldPoint coordinate mismatches Convert finishGame, fetchMissingItems, isOnStartingBoat, FILL ammo crate sorting, and handleDamagedMast/Totem to use LocalPoint. Slow tether retries with Rs2Random gaussian. No template-vs-instanced comparisons remain in functional code. --- .../microbot/tempoross/TemporossScript.java | 79 ++++++++++++------- 1 file changed, 49 insertions(+), 30 deletions(-) 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 9d8da4b071..50eedb5df3 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 @@ -14,6 +14,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.camera.Rs2Camera; +import net.runelite.client.plugins.microbot.util.math.Rs2Random; 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; @@ -179,20 +180,23 @@ private void determineWorkArea() { } private void finishGame() { - WorldPoint playerLocation = Rs2Player.getWorldLocation(); - if (playerLocation == null || workArea == null) { + if (workArea == null) { + return; + } + LocalPoint playerLocal = Microbot.getClient().getLocalPlayer() != null + ? Microbot.getClient().getLocalPlayer().getLocalLocation() : null; + LocalPoint exitLocal = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), workArea.exitNpc); + if (playerLocal == null || exitLocal == null) { return; } - // Only consider Leave NPCs on our side of the bay. workArea.exitNpc is - // the anchor for our boat/beach — the opposite-bay captain is well - // outside this radius (the bay is much wider than 20 tiles). 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") - && npc.getWorldLocation().distanceTo(workArea.exitNpc) <= 20) + && npc.getNpc().getLocalLocation() != null + && npc.getNpc().getLocalLocation().distanceTo(exitLocal) <= 20 * Perspective.LOCAL_TILE_SIZE) .toList().stream() - .min(Comparator.comparingInt(value -> playerLocation.distanceTo(value.getWorldLocation()))) + .min(Comparator.comparingInt(value -> playerLocal.distanceTo(value.getNpc().getLocalLocation()))) .orElse(null); if (exitNpc != null) { int emptyBucketCount = Rs2Inventory.count(ItemID.BUCKET); @@ -309,36 +313,39 @@ private boolean areItemsMissing() private void fetchMissingItems() { - WorldPoint playerLoc = Rs2Player.getWorldLocation(); - if (playerLoc == null) return; + LocalPoint playerLocal = Microbot.getClient().getLocalPlayer() != null + ? Microbot.getClient().getLocalPlayer().getLocalLocation() : null; + if (playerLocal == null) return; - // Build list of needed items with their target points List needed = new ArrayList<>(); - // 0=harpoon, 1=buckets, 2=fill, 3=rope, 4=hammer if (!hasHarpoon() && harpoonType != HarpoonType.BAREHAND) { - needed.add(new int[]{0, playerLoc.distanceTo(workArea.harpoonPoint)}); + LocalPoint lp = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), workArea.harpoonPoint); + needed.add(new int[]{0, lp != null ? playerLocal.distanceTo(lp) : Integer.MAX_VALUE}); } int bucketCount = Rs2Inventory.count(item -> item.getId() == ItemID.BUCKET || item.getId() == ItemID.BUCKET_OF_WATER); boolean needBuckets = (bucketCount < temporossConfig.buckets() && state == State.INITIAL_CATCH) || bucketCount == 0; if (needBuckets) { - needed.add(new int[]{1, playerLoc.distanceTo(workArea.bucketPoint)}); + LocalPoint lp = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), workArea.bucketPoint); + needed.add(new int[]{1, lp != null ? playerLocal.distanceTo(lp) : Integer.MAX_VALUE}); } - // Fill only eligible if we have empty buckets but no full ones int fullBucketCount = Rs2Inventory.count(ItemID.BUCKET_OF_WATER); if (!needBuckets && fullBucketCount <= 0) { - needed.add(new int[]{2, playerLoc.distanceTo(workArea.pumpPoint)}); + LocalPoint lp = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), workArea.pumpPoint); + needed.add(new int[]{2, lp != null ? playerLocal.distanceTo(lp) : Integer.MAX_VALUE}); } if (temporossConfig.rope() && !temporossConfig.spiritAnglers() && !Rs2Inventory.contains(ItemID.ROPE)) { - needed.add(new int[]{3, playerLoc.distanceTo(workArea.ropePoint)}); + LocalPoint lp = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), workArea.ropePoint); + needed.add(new int[]{3, lp != null ? playerLocal.distanceTo(lp) : Integer.MAX_VALUE}); } if (temporossConfig.hammer() && !Rs2Inventory.contains(ItemID.HAMMER)) { - needed.add(new int[]{4, playerLoc.distanceTo(workArea.hammerPoint)}); + LocalPoint lp = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), workArea.hammerPoint); + needed.add(new int[]{4, lp != null ? playerLocal.distanceTo(lp) : Integer.MAX_VALUE}); } if (needed.isEmpty()) return; @@ -399,7 +406,11 @@ private boolean isOnStartingBoat() { log("Failed to find starting ladder"); return false; } - return Rs2Player.getWorldLocation().getX() < startingLadder.getWorldLocation().getX(); + LocalPoint playerLocal = Microbot.getClient().getLocalPlayer() != null + ? Microbot.getClient().getLocalPlayer().getLocalLocation() : null; + LocalPoint ladderLocal = startingLadder.getLocalLocation(); + if (playerLocal == null || ladderLocal == null) return false; + return playerLocal.getSceneX() < ladderLocal.getSceneX(); } private void handleEnterMinigame() { @@ -583,8 +594,10 @@ private void handleDamagedMast() { Rs2TileObjectModel damagedMast = workArea.getBrokenMast(); if(damagedMast == null) return; - WorldPoint playerLocMast = Rs2Player.getWorldLocation(); - if (playerLocMast != null && playerLocMast.distanceTo(damagedMast.getWorldLocation()) <= 5) { + LocalPoint playerLocal = Microbot.getClient().getLocalPlayer() != null + ? Microbot.getClient().getLocalPlayer().getLocalLocation() : null; + LocalPoint mastLocal = damagedMast.getLocalLocation(); + if (playerLocal != null && mastLocal != null && playerLocal.distanceTo(mastLocal) <= 5 * Perspective.LOCAL_TILE_SIZE) { if (damagedMast.click("Repair")) { log("Repairing mast"); Rs2Player.waitForXpDrop(Skill.CONSTRUCTION, 2500); @@ -599,8 +612,10 @@ private void handleDamagedTotem() { Rs2TileObjectModel damagedTotem = workArea.getBrokenTotem(); if(damagedTotem == null) return; - WorldPoint playerLocTotem = Rs2Player.getWorldLocation(); - if (playerLocTotem != null && playerLocTotem.distanceTo(damagedTotem.getWorldLocation()) <= 5) { + LocalPoint playerLocal = Microbot.getClient().getLocalPlayer() != null + ? Microbot.getClient().getLocalPlayer().getLocalLocation() : null; + LocalPoint totemLocal = damagedTotem.getLocalLocation(); + if (playerLocal != null && totemLocal != null && playerLocal.distanceTo(totemLocal) <= 5 * Perspective.LOCAL_TILE_SIZE) { if (damagedTotem.click("Repair")) { log("Repairing totem"); Rs2Player.waitForXpDrop(Skill.CONSTRUCTION, 2500); @@ -629,7 +644,7 @@ private void handleTether() { Rs2Walker.setTarget(null); lockedTether.click("Tether"); log("Tethering"); - sleepUntil(() -> TemporossPlugin.isTethered, () -> lockedTether.click("Tether"), 8000, 600); + sleepUntil(() -> TemporossPlugin.isTethered, () -> lockedTether.click("Tether"), 8000, Rs2Random.randomGaussian(2000, 400)); } else { lockedTether = null; } @@ -785,7 +800,8 @@ private void handleMainLoop() { && !inCloud(npc.getWorldLocation(), 0)) .toList(); - WorldPoint fillPlayerLoc = Rs2Player.getWorldLocation(); + LocalPoint fillPlayerLocal = Microbot.getClient().getLocalPlayer() != null + ? Microbot.getClient().getLocalPlayer().getLocalLocation() : null; if (ammoCrates.isEmpty()) { if (!Rs2Player.isMoving()) { log("Can't find ammo crate, walking to the safe point"); @@ -795,18 +811,21 @@ private void handleMainLoop() { } if (inCloud(Microbot.getClientThread().invoke(() -> Microbot.getClient().getLocalPlayer().getLocalLocation()))) { - log("In cloud, walking to safe point"); + log("In cloud, switching ammo crate"); Rs2NpcModel ammoCrate = ammoCrates.stream() - .max(Comparator.comparingInt(value -> new Rs2WorldPoint(value.getWorldLocation()).distanceToPath(fillPlayerLoc))).orElse(null); - Rs2Camera.turnTo(ammoCrate.getNpc()); - ammoCrate.click("Fill"); - log("Switching ammo crate"); + .max(Comparator.comparingInt(value -> fillPlayerLocal != null && value.getNpc().getLocalLocation() != null + ? fillPlayerLocal.distanceTo(value.getNpc().getLocalLocation()) : 0)).orElse(null); + if (ammoCrate != null) { + Rs2Camera.turnTo(ammoCrate.getNpc()); + ammoCrate.click("Fill"); + } isFilling = true; return; } var ammoCrate = ammoCrates.stream() - .min(Comparator.comparingInt(value -> new Rs2WorldPoint(value.getWorldLocation()).distanceToPath(fillPlayerLoc))).orElse(null); + .min(Comparator.comparingInt(value -> fillPlayerLocal != null && value.getNpc().getLocalLocation() != null + ? fillPlayerLocal.distanceTo(value.getNpc().getLocalLocation()) : Integer.MAX_VALUE)).orElse(null); // In mass world mode, clear fires along the path to the ammo crate before interacting. if (!temporossConfig.solo() && ammoCrate != null) { From d84584ac93ea6e557dfa694954c9c23b51d3a101 Mon Sep 17 00:00:00 2001 From: Alex <45095641+runsonmypc@users.noreply.github.com> Date: Wed, 20 May 2026 15:51:13 -0400 Subject: [PATCH 07/17] fix(tempoross): prevent cooking interruption, Rs2Walker recovery, spec log spam MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Gate item fetching to CATCH states only — handleMinigame was interrupting cooking by walking to the water pump when buckets were empty - Fall back to Rs2Walker.walkTo when LocalPoint.fromWorld returns null (player too far for canvas click) in all walk methods - Move harpoon spec code entirely inside enableHarpoonSpec config check so no spec logs emit when the option is disabled - Prevent fish spot switching while moving to a valid target - Widen Leave NPC search radius to 40 tiles - Add wave-break to fire douse sleepUntil in fightFiresInPath - Reduce fire douse timeout from 10s to 5s - Use Rs2Random.randomGaussian for tether retry delay - Add spirit pool coordinate logging for random walk diagnosis --- .../microbot/tempoross/TemporossScript.java | 88 +++++++++++-------- 1 file changed, 50 insertions(+), 38 deletions(-) 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 50eedb5df3..499fda25ef 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 @@ -105,7 +105,7 @@ public boolean run(TemporossConfig config) { handleStateLoop(); if (handleCloudDodge()) return; - if(areItemsMissing()) + if(areItemsMissing() && (state == State.INITIAL_CATCH || state == State.SECOND_CATCH || state == State.THIRD_CATCH)) return; handleFires(); handleTether(); @@ -194,7 +194,7 @@ private void finishGame() { && npc.getNpc().getComposition().getActions() != null && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Leave") && npc.getNpc().getLocalLocation() != null - && npc.getNpc().getLocalLocation().distanceTo(exitLocal) <= 20 * Perspective.LOCAL_TILE_SIZE) + && npc.getNpc().getLocalLocation().distanceTo(exitLocal) <= 40 * Perspective.LOCAL_TILE_SIZE) .toList().stream() .min(Comparator.comparingInt(value -> playerLocal.distanceTo(value.getNpc().getLocalLocation()))) .orElse(null); @@ -260,23 +260,16 @@ private void forfeit() { private void handleMinigame() { - // Do not proceed if the minigame phase is too advanced if (getPhase() > 2) return; - // Update the current harpoon type from the configuration harpoonType = temporossConfig.harpoonType(); - // Check if any required item is missing. If so, fetch it and return. - if (areItemsMissing()) - { - // Before interacting with crates, clear fires along the path to the crate. - // In mass world mode, only fires blocking the path will be doused. - fetchMissingItems(); + if (state == State.INITIAL_CATCH || state == State.SECOND_CATCH || state == State.THIRD_CATCH) { + if (areItemsMissing()) { + fetchMissingItems(); + } } - - // Continue with further minigame logic if all items are available - // ... } private boolean areItemsMissing() @@ -704,6 +697,10 @@ private void handleMainLoop() { lastCatchSpotNpc = null; } + if (lastCatchSpotNpc != null && Rs2Player.isMoving()) { + return; + } + long inCloudCount = fishSpots.stream().filter(npc -> inCloud(npc.getWorldLocation(), 1)).count(); long fireCount = fishSpots.stream().filter(npc -> hasAdjacentFire(npc.getWorldLocation())).count(); boolean alreadyFishing = Rs2Player.isAnimating() || Rs2Player.isInteracting(); @@ -760,8 +757,11 @@ private void handleMainLoop() { return; } LocalPoint localPoint = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), workArea.totemPoint); - if (localPoint == null) return; - Rs2Walker.walkFastLocal(localPoint); + if (localPoint == null) { + Rs2Walker.walkTo(workArea.totemPoint); + } else { + Rs2Walker.walkFastLocal(localPoint); + } log("Can't find the fish spot, walking to the totem pole"); return; } @@ -781,9 +781,13 @@ private void handleMainLoop() { log("Interacting with range"); } else if (range == null) { log("Can't find the range, walking to the range point"); - LocalPoint localPoint = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(),workArea.rangePoint); - Rs2Camera.turnTo(localPoint); - Rs2Walker.walkFastLocal(localPoint); + LocalPoint localPoint = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), workArea.rangePoint); + if (localPoint == null) { + Rs2Walker.walkTo(workArea.rangePoint); + } else { + Rs2Camera.turnTo(localPoint); + Rs2Walker.walkFastLocal(localPoint); + } } break; @@ -856,23 +860,16 @@ private void handleMainLoop() { } return; } - // --- Check and trigger the special attack if conditions are met --- - int currentSpecEnergy = Rs2Combat.getSpecEnergy()/ 10; - log("Current Spec Energy: " + currentSpecEnergy); - // Check if special attack is enabled and the harpoon is the correct type - if (temporossConfig.enableHarpoonSpec() // Check if special attack is enabled in config + if (temporossConfig.enableHarpoonSpec() && (temporossConfig.harpoonType() == HarpoonType.DRAGON_HARPOON || temporossConfig.harpoonType() == HarpoonType.INFERNAL_HARPOON - || temporossConfig.harpoonType() == HarpoonType.CRYSTAL_HARPOON) - && currentSpecEnergy >= 100) { // Ensure spec energy is >= 100% - - // Trigger the special attack only if energy is 100% or more - Rs2Combat.setSpecState(true, 100); // Activate special attack at 100% energy - sleep(600); // Wait for the special animation to complete - log("Using harpoon special attack at 100% energy"); - } else { - // Log message when special energy is below 100% - log("Special energy is below 100%, not using harpoon special attack."); + || temporossConfig.harpoonType() == HarpoonType.CRYSTAL_HARPOON)) { + int currentSpecEnergy = Rs2Combat.getSpecEnergy() / 10; + if (currentSpecEnergy >= 100) { + Rs2Combat.setSpecState(true, 100); + sleep(600); + log("Using harpoon special attack"); + } } temporossPool.click("Harpoon"); log("Harpooning Tempoross"); @@ -882,7 +879,13 @@ private void handleMainLoop() { return; } if (!Rs2Player.isMoving()) { - log("Can't find Tempoross, walking to the Tempoross pool"); + LocalPoint poolLocal = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), workArea.spiritPoolPoint); + LocalPoint pLocal = Microbot.getClient().getLocalPlayer() != null + ? Microbot.getClient().getLocalPlayer().getLocalLocation() : null; + log("Walking to spirit pool: target=" + workArea.spiritPoolPoint + + " localTarget=" + poolLocal + + " playerLocal=" + pLocal + + " dist=" + (poolLocal != null && pLocal != null ? pLocal.distanceTo(poolLocal) : "?")); walkToSpiritPool(); } } @@ -901,7 +904,11 @@ private static WorldPoint getTrueWorldPoint(WorldPoint point) { */ private void walkToSafePoint() { LocalPoint localPoint = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), workArea.safePoint); - if (localPoint == null) return; + if (localPoint == null) { + log("Safe point off-screen, using Rs2Walker"); + Rs2Walker.walkTo(workArea.safePoint); + return; + } if (Objects.equals(Microbot.getClient().getLocalDestinationLocation(), localPoint)) return; LocalPoint playerLocal = Microbot.getClient().getLocalPlayer() != null @@ -916,7 +923,11 @@ private void walkToSafePoint() { */ private void walkToSpiritPool() { LocalPoint localPoint = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), workArea.spiritPoolPoint); - if (localPoint == null) return; + if (localPoint == null) { + log("Spirit pool off-screen, using Rs2Walker"); + Rs2Walker.walkTo(workArea.spiritPoolPoint); + return; + } if (Objects.equals(Microbot.getClient().getLocalDestinationLocation(), localPoint)) return; LocalPoint playerLocal = Microbot.getClient().getLocalPlayer() != null @@ -1025,10 +1036,11 @@ public boolean fightFiresInPath(WorldPoint location) { } for (Rs2NpcModel fire : firesInPath) { + if (TemporossPlugin.incomingWave) return false; if (fire.click("Douse")) { log("Dousing fire in path"); - sleepUntil(Rs2Player::isInteracting, 2000); - sleepUntil(() -> !Rs2Player.isInteracting(), 10000); + sleepUntil(() -> Rs2Player.isInteracting() || TemporossPlugin.incomingWave, 2000); + sleepUntil(() -> !Rs2Player.isInteracting() || TemporossPlugin.incomingWave, 5000); } } From 7a4cfbe0129a9ac62df8978616a9f2cff5125144 Mon Sep 17 00:00:00 2001 From: Alex <45095641+runsonmypc@users.noreply.github.com> Date: Thu, 21 May 2026 16:37:39 -0400 Subject: [PATCH 08/17] fix(tempoross): fish spot stability, humanized thresholds, repair blocking, fire responsiveness - Simplify CATCH guard: return if animating/moving, allow double spot switch - Per-game randomized thresholds via fancyNormalSample for all energy/intensity checks - Repair sleepUntil blocks until mast/totem is actually fixed - Remove doused fires from sortedFires immediately, validate NPC in adjacency checks - Drop boat-anchor filter in finishGame, pick nearest Leave NPC to player - Gate item fetching to CATCH states only (no cooking interruption) - Tether retry uses fancyNormalSample(1200, 2800) - Config tooltips explain gameplay effects - Clean up spirit pool spam logging, add NPC coordinate logging - Rs2Walker fallback for all walk methods when LocalPoint is null --- .../microbot/tempoross/TemporossConfig.java | 14 +-- .../microbot/tempoross/TemporossPlugin.java | 2 +- .../microbot/tempoross/TemporossScript.java | 107 +++++++++--------- 3 files changed, 63 insertions(+), 60 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossConfig.java b/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossConfig.java index f939dfa24e..06a2933ac3 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossConfig.java +++ b/src/main/java/net/runelite/client/plugins/microbot/tempoross/TemporossConfig.java @@ -44,7 +44,7 @@ public interface TemporossConfig extends Config { @ConfigItem( keyName = "buckets", name = "Buckets", - description = "Number of buckets to bring", + description = "Buckets of water to douse fires and cool cannons. More buckets = more fire coverage but fewer inventory slots for fish.", position = 1, section = generalSection ) @@ -57,7 +57,7 @@ default int buckets() { @ConfigItem( keyName = "hammer", name = "Hammer", - description = "Bring a hammer", + description = "Bring a hammer to repair the mast and totem pole when damaged by waves. Earns Construction XP and prevents storm intensity from rising.", position = 2, section = generalSection ) @@ -70,7 +70,7 @@ default boolean hammer() { @ConfigItem( keyName = "rope", name = "Rope", - description = "Bring a rope", + description = "Bring a rope to tether to the mast or totem pole before waves hit. Without a rope, waves knock you back and deal damage. Not needed with Spirit Angler's outfit.", position = 3, section = generalSection ) @@ -81,7 +81,7 @@ default boolean rope() { @ConfigItem( keyName = "solo", name = "Solo", - description = "Play solo", + description = "Solo mode starts a private instance. Requires Infernal Harpoon and at least 19 free inventory slots. Mass mode joins the public boat with other players.", position = 4, section = generalSection ) @@ -96,7 +96,7 @@ default boolean solo() { @ConfigItem( keyName = "spiritAnglers", name = "Spirit Angler's", - description = "Spirit Angler's outfit", + description = "Enable if wearing the Spirit Angler's outfit. Grants automatic tethering during waves, so no rope is needed.", position = 1, section = equipmentSection ) @@ -109,7 +109,7 @@ default boolean spiritAnglers() { @ConfigItem( keyName = "harpoonType", name = "Harpoon", - description = "Harpoon type to use", + description = "Which harpoon to use for fishing. Dragon/Infernal/Crystal have special attacks. Infernal auto-cooks some fish. Barehand requires Barbarian Fishing training.", position = 1, section = harpoonSection ) @@ -120,7 +120,7 @@ default HarpoonType harpoonType() { @ConfigItem( keyName = "enableHarpoonSpec", name = "Use Harpoon Special", - description = "Use the harpoon's special attack when attacking Tempoross.", + description = "Use the harpoon's special attack when harpooning the spirit pool. Boosts fishing speed temporarily. Only works with Dragon, Infernal, or Crystal harpoons.", position = 2, section = harpoonSection ) 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 ac64476851..7aff870123 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 @@ -120,7 +120,7 @@ public void onGameTick(GameTick e) { TemporossScript.state = TemporossScript.state.next; } - if (TemporossScript.INTENSITY >= 94 && TemporossScript.state == State.THIRD_COOK) { + if (TemporossScript.INTENSITY >= TemporossScript.thresholdForfeitIntensity && TemporossScript.state == State.THIRD_COOK) { return; } 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 499fda25ef..625324686b 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 @@ -66,6 +66,15 @@ public class TemporossScript extends Script { public static int cachedTotalSlots; public static boolean cachedInMinigame; + // Per-game randomized thresholds (regenerated each game for humanization) + public static int thresholdForfeitIntensity = 94; + private int thresholdLowEnergy = 5; + private int thresholdAttackEnergy = 94; + private int thresholdFullEnergy = 95; + private int thresholdEmergencyEnergyLow = 30; + private int thresholdEmergencyEnergyHigh = 50; + private int thresholdEmergencyFishMin = 6; + public boolean run(TemporossConfig config) { temporossConfig = config; startTime = System.currentTimeMillis(); @@ -185,16 +194,14 @@ private void finishGame() { } LocalPoint playerLocal = Microbot.getClient().getLocalPlayer() != null ? Microbot.getClient().getLocalPlayer().getLocalLocation() : null; - LocalPoint exitLocal = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), workArea.exitNpc); - if (playerLocal == null || exitLocal == null) { + if (playerLocal == null) { return; } 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") - && npc.getNpc().getLocalLocation() != null - && npc.getNpc().getLocalLocation().distanceTo(exitLocal) <= 40 * Perspective.LOCAL_TILE_SIZE) + && npc.getNpc().getLocalLocation() != null) .toList().stream() .min(Comparator.comparingInt(value -> playerLocal.distanceTo(value.getNpc().getLocalLocation()))) .orElse(null); @@ -230,10 +237,28 @@ private void reset(){ TemporossPlugin.fireClouds = 0; TemporossPlugin.waves = 0; state = State.INITIAL_CATCH; + randomizeThresholds(); + } + + private void randomizeThresholds() { + thresholdForfeitIntensity = Rs2Random.fancyNormalSample(91, 96); + thresholdLowEnergy = Rs2Random.fancyNormalSample(2, 8); + thresholdAttackEnergy = Rs2Random.fancyNormalSample(90, 97); + thresholdFullEnergy = Math.max(thresholdAttackEnergy + 1, Rs2Random.fancyNormalSample(92, 98)); + thresholdEmergencyEnergyLow = Rs2Random.fancyNormalSample(24, 36); + thresholdEmergencyEnergyHigh = Math.max(thresholdEmergencyEnergyLow + 10, Rs2Random.fancyNormalSample(44, 56)); + thresholdEmergencyFishMin = Rs2Random.fancyNormalSample(4, 8); + log("Game thresholds: forfeit=" + thresholdForfeitIntensity + + " lowE=" + thresholdLowEnergy + + " attackE=" + thresholdAttackEnergy + + " fullE=" + thresholdFullEnergy + + " emergLow=" + thresholdEmergencyEnergyLow + + " emergHigh=" + thresholdEmergencyEnergyHigh + + " emergFish=" + thresholdEmergencyFishMin); } public void handleForfeit() { - if ((INTENSITY >= 94 && state == State.THIRD_COOK)) { + if ((INTENSITY >= thresholdForfeitIntensity && state == State.THIRD_COOK)) { forfeit(); } } @@ -593,7 +618,7 @@ private void handleDamagedMast() { if (playerLocal != null && mastLocal != null && playerLocal.distanceTo(mastLocal) <= 5 * Perspective.LOCAL_TILE_SIZE) { if (damagedMast.click("Repair")) { log("Repairing mast"); - Rs2Player.waitForXpDrop(Skill.CONSTRUCTION, 2500); + sleepUntil(() -> workArea.getBrokenMast() == null || TemporossPlugin.incomingWave, 5000); } } } @@ -611,7 +636,7 @@ private void handleDamagedTotem() { if (playerLocal != null && totemLocal != null && playerLocal.distanceTo(totemLocal) <= 5 * Perspective.LOCAL_TILE_SIZE) { if (damagedTotem.click("Repair")) { log("Repairing totem"); - Rs2Player.waitForXpDrop(Skill.CONSTRUCTION, 2500); + sleepUntil(() -> workArea.getBrokenTotem() == null || TemporossPlugin.incomingWave, 5000); } } } @@ -637,7 +662,7 @@ private void handleTether() { Rs2Walker.setTarget(null); lockedTether.click("Tether"); log("Tethering"); - sleepUntil(() -> TemporossPlugin.isTethered, () -> lockedTether.click("Tether"), 8000, Rs2Random.randomGaussian(2000, 400)); + sleepUntil(() -> TemporossPlugin.isTethered, () -> lockedTether.click("Tether"), 8000, Rs2Random.fancyNormalSample(1200, 2800)); } else { lockedTether = null; } @@ -660,21 +685,21 @@ private void handleStateLoop() { } if ((TemporossScript.state == State.THIRD_CATCH || TemporossScript.state == State.EMERGENCY_FILL) - && TemporossScript.ENERGY <= ( isFilling ? 0 : 5) + && TemporossScript.ENERGY <= ( isFilling ? 0 : thresholdLowEnergy) && !temporossConfig.solo()) { log("Very low energy, better wait on Tempoross pool"); TemporossScript.state = State.ATTACK_TEMPOROSS; return; } - if (temporossPool != null && TemporossScript.state != State.SECOND_FILL && TemporossScript.state != State.ATTACK_TEMPOROSS && TemporossScript.ENERGY < 94) { + if (temporossPool != null && TemporossScript.state != State.SECOND_FILL && TemporossScript.state != State.ATTACK_TEMPOROSS && TemporossScript.ENERGY < thresholdAttackEnergy) { log("Tempoross pool detected, attacking Tempoross"); TemporossScript.state = State.ATTACK_TEMPOROSS; return; } - if (((TemporossScript.ENERGY < 30 && cachedAllFish > 6) - || (TemporossScript.ENERGY < 50 && cachedAllFish >= cachedTotalSlots)) + if (((TemporossScript.ENERGY < thresholdEmergencyEnergyLow && cachedAllFish > thresholdEmergencyFishMin) + || (TemporossScript.ENERGY < thresholdEmergencyEnergyHigh && cachedAllFish >= cachedTotalSlots)) && !temporossConfig.solo() && TemporossScript.state != State.ATTACK_TEMPOROSS && TemporossScript.state != State.EMERGENCY_FILL) { @@ -693,23 +718,21 @@ private void handleMainLoop() { case THIRD_CATCH: isFilling = false; - if (lastCatchSpotNpc != null && fishSpots.stream().noneMatch(npc -> npc.getNpc() == lastCatchSpotNpc)) { - lastCatchSpotNpc = null; - } - - if (lastCatchSpotNpc != null && Rs2Player.isMoving()) { - return; + if (Rs2Player.isAnimating() || Rs2Player.isMoving()) { + boolean atDouble = lastCatchSpotNpc != null && lastCatchSpotNpc.getId() == NpcID.FISHING_SPOT_10569; + boolean doubleAvailable = fishSpots.stream().anyMatch( + npc -> npc.getId() == NpcID.FISHING_SPOT_10569 && !inCloud(npc.getWorldLocation(), 1)); + if (atDouble || !doubleAvailable) { + return; + } } long inCloudCount = fishSpots.stream().filter(npc -> inCloud(npc.getWorldLocation(), 1)).count(); long fireCount = fishSpots.stream().filter(npc -> hasAdjacentFire(npc.getWorldLocation())).count(); - boolean alreadyFishing = Rs2Player.isAnimating() || Rs2Player.isInteracting(); int emptySlots = cachedTotalSlots - cachedAllFish; var fishSpot = fishSpots.stream() .filter(npc -> !inCloud(npc.getWorldLocation(), 1)) .filter(npc -> { - if (alreadyFishing && npc.getId() == NpcID.FISHING_SPOT_10569 && emptySlots <= 4) - return false; boolean fireAdjacent = hasAdjacentFire(npc.getWorldLocation()); return !fireAdjacent || Rs2Inventory.contains(ItemID.BUCKET_OF_WATER); }) @@ -730,24 +753,10 @@ private void handleMainLoop() { return; } - if (!temporossConfig.solo()) { if(!fightFiresInPath(fishSpot.getWorldLocation())) return; } - if (fishSpot.getNpc() == lastCatchSpotNpc) { - if (Rs2Player.isMoving()) { - return; - } - if (Rs2Player.isAnimating()) { - LocalPoint spotLocal = fishSpot.getNpc().getLocalLocation(); - LocalPoint pLocal = Microbot.getClient().getLocalPlayer() != null - ? Microbot.getClient().getLocalPlayer().getLocalLocation() : null; - if (spotLocal != null && pLocal != null && pLocal.distanceTo(spotLocal) <= Perspective.LOCAL_TILE_SIZE) { - return; - } - } - } Rs2Camera.turnTo(fishSpot.getNpc()); fishSpot.click("Harpoon"); lastCatchSpotNpc = fishSpot.getNpc(); @@ -854,7 +863,7 @@ private void handleMainLoop() { isFilling = false; if (temporossPool != null && temporossPool.getNpc() != null) { if (Rs2Player.isAnimating() || Rs2Player.isMoving()) { - if (ENERGY >= 95) { + if (ENERGY >= thresholdFullEnergy) { log("Energy is full, stopping attack"); state = null; } @@ -871,21 +880,15 @@ private void handleMainLoop() { log("Using harpoon special attack"); } } - temporossPool.click("Harpoon"); - log("Harpooning Tempoross"); + log("Harpooning Tempoross at " + temporossPool.getWorldLocation() + + " local=" + temporossPool.getNpc().getLocalLocation()); + temporossPool.click("Harpoon"); } else { - if (ENERGY > 5) { + if (ENERGY > thresholdLowEnergy) { state = null; return; } if (!Rs2Player.isMoving()) { - LocalPoint poolLocal = LocalPoint.fromWorld(Microbot.getClient().getTopLevelWorldView(), workArea.spiritPoolPoint); - LocalPoint pLocal = Microbot.getClient().getLocalPlayer() != null - ? Microbot.getClient().getLocalPlayer().getLocalLocation() : null; - log("Walking to spirit pool: target=" + workArea.spiritPoolPoint - + " localTarget=" + poolLocal - + " playerLocal=" + pLocal - + " dist=" + (poolLocal != null && pLocal != null ? pLocal.distanceTo(poolLocal) : "?")); walkToSpiritPool(); } } @@ -990,12 +993,14 @@ public static boolean inCloud(WorldPoint point, int radius) { private boolean hasAdjacentFire(WorldPoint point) { return sortedFires.stream() - .anyMatch(fire -> fire.getWorldLocation().distanceTo(point) <= 1); + .anyMatch(fire -> fire.getNpc() != null && fire.getNpc().getComposition() != null + && fire.getWorldLocation().distanceTo(point) <= 1); } private Rs2NpcModel getAdjacentFire(WorldPoint point) { return sortedFires.stream() - .filter(fire -> fire.getWorldLocation().distanceTo(point) <= 1) + .filter(fire -> fire.getNpc() != null && fire.getNpc().getComposition() != null + && fire.getWorldLocation().distanceTo(point) <= 1) .findFirst() .orElse(null); } @@ -1041,13 +1046,11 @@ public boolean fightFiresInPath(WorldPoint location) { log("Dousing fire in path"); sleepUntil(() -> Rs2Player.isInteracting() || TemporossPlugin.incomingWave, 2000); sleepUntil(() -> !Rs2Player.isInteracting() || TemporossPlugin.incomingWave, 5000); + sortedFires.remove(fire); } } - return firesInPath.stream().allMatch(fire -> { - if (fire.getNpc() == null || fire.getNpc().getLocalLocation() == null) return true; - return !sortedFires.contains(fire); - }); + return true; } @Override From 4eb83572b4b3044218a3496722a4ffc5c4e37db4 Mon Sep 17 00:00:00 2001 From: Alex <45095641+runsonmypc@users.noreply.github.com> Date: Fri, 22 May 2026 12:36:22 -0400 Subject: [PATCH 09/17] fix(tempoross): improved repair timing, spirit pool action filter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Two-phase repair sleep: wait for animation start (3s), then wait for completion (10s) — prevents premature timeout when walking to repair - Spirit pool query now requires "Harpoon" action, filtering out inactive pools that caused recurring error log spam --- .../plugins/microbot/tempoross/TemporossScript.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) 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 625324686b..ffa5405830 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 @@ -618,7 +618,8 @@ private void handleDamagedMast() { if (playerLocal != null && mastLocal != null && playerLocal.distanceTo(mastLocal) <= 5 * Perspective.LOCAL_TILE_SIZE) { if (damagedMast.click("Repair")) { log("Repairing mast"); - sleepUntil(() -> workArea.getBrokenMast() == null || TemporossPlugin.incomingWave, 5000); + sleepUntil(() -> Rs2Player.isAnimating() || Rs2Player.isMoving() || TemporossPlugin.incomingWave, 3000); + sleepUntil(() -> workArea.getBrokenMast() == null || TemporossPlugin.incomingWave, 10000); } } } @@ -636,7 +637,8 @@ private void handleDamagedTotem() { if (playerLocal != null && totemLocal != null && playerLocal.distanceTo(totemLocal) <= 5 * Perspective.LOCAL_TILE_SIZE) { if (damagedTotem.click("Repair")) { log("Repairing totem"); - sleepUntil(() -> workArea.getBrokenTotem() == null || TemporossPlugin.incomingWave, 5000); + sleepUntil(() -> Rs2Player.isAnimating() || Rs2Player.isMoving() || TemporossPlugin.incomingWave, 3000); + sleepUntil(() -> workArea.getBrokenTotem() == null || TemporossPlugin.incomingWave, 10000); } } } @@ -673,7 +675,9 @@ private void handleTether() { private void handleStateLoop() { temporossPool = Microbot.getRs2NpcCache().query().withId(NpcID.SPIRIT_POOL) - .where(npc -> npc.getWorldLocation().distanceTo(workArea.spiritPoolPoint) <= 15) + .where(npc -> npc.getNpc() != null && npc.getNpc().getComposition() != null + && Arrays.asList(npc.getNpc().getComposition().getActions()).contains("Harpoon") + && npc.getWorldLocation().distanceTo(workArea.spiritPoolPoint) <= 15) .toList().stream() .min(Comparator.comparingInt(x -> workArea.spiritPoolPoint.distanceTo(x.getWorldLocation()))) .orElse(null); From 936447feb92321f18074f2cb7bacae99ef4c128d Mon Sep 17 00:00:00 2001 From: Alex <45095641+runsonmypc@users.noreply.github.com> Date: Fri, 22 May 2026 12:44:37 -0400 Subject: [PATCH 10/17] chore(tempoross): bump version to 2.0.0 Major version bump reflecting the comprehensive rewrite of coordinate handling, state machine, fire detection, and humanization systems. --- .../client/plugins/microbot/tempoross/TemporossPlugin.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 7aff870123..aaae86789b 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 @@ -35,7 +35,7 @@ ) @Slf4j public class TemporossPlugin extends Plugin { - public static final String version = "1.4.3"; + public static final String version = "2.0.0"; @Inject private TemporossConfig config; From 3e3f051bd0ff0cfb5c1880b7176852a3f12618bb Mon Sep 17 00:00:00 2001 From: Alex <45095641+runsonmypc@users.noreply.github.com> Date: Thu, 11 Jun 2026 12:50:52 -0400 Subject: [PATCH 11/17] Fix Animated Armour food handling and armoury navigation - Raise eat threshold from ~30% to ~60% HP (Gaussian-randomized) so the bot eats sooner and survives longer between kills. - Never bank mid-kill: only restock food before animating the next suit, when fewer than two food remain. Prevents losing the suit to a bank trip in the middle of a fight. - Add an emergency mid-fight bank as a last resort only (food empty AND HP <= ~20%) to avoid dying outright. - Rework armoury navigation: walk with a distance tolerance instead of an exact tile (the slow, re-pathing invocation), with an explicit door-open fallback (IDs 24309/24306) if the walker stalls on the wrong side. - Guard armour-stand targeting on arrival distance and null-guard getWorldLocation() so we re-walk instead of failing the lookup. - Eat opportunistically during the animate delay, one tick after the animate click, when it won't overheal (missing HP >= the food's heal). - Handle InterruptedException in the loop: restore the flag and exit quietly instead of logging a misleading ERROR on stop/restart. - Adopt the queryable cache API (Rs2TileObjectModel.click()) for the armour stand; bump plugin version 1.0.1 -> 1.0.2. --- .../animatedarmour/AnimatedArmourPlugin.java | 2 +- .../animatedarmour/AnimatedArmourScript.java | 95 ++++++++++++++++--- 2 files changed, 82 insertions(+), 15 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/animatedarmour/AnimatedArmourPlugin.java b/src/main/java/net/runelite/client/plugins/microbot/animatedarmour/AnimatedArmourPlugin.java index 7d01a5c0a7..644aed7d3e 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/animatedarmour/AnimatedArmourPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/animatedarmour/AnimatedArmourPlugin.java @@ -26,7 +26,7 @@ @Slf4j public class AnimatedArmourPlugin extends Plugin { - static final String version = "1.0.1"; + static final String version = "1.0.2"; @Inject private AnimatedArmourConfig 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 a1944a0ccb..ae10637d16 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,11 +1,13 @@ package net.runelite.client.plugins.microbot.animatedarmour; +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.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; @@ -20,6 +22,16 @@ public class AnimatedArmourScript extends Script { public static boolean test = false; + // Armoury entrance double-door (plane 0). From the shortest-path transport data: + // 2855 3546 0 <-> 2855 3545 0 (Open;Door;24309) + // 2854 3546 0 <-> 2854 3545 0 (Open;Door;24306) + // These are already in the walker's transport graph, so pathfinding opens them + // automatically; the explicit interact below is only a fallback when the walker + // stalls on the wrong side of the door. + private static final int[] ARMOURY_DOOR_IDS = {24309, 24306}; + // Armour stand object tile (unchanged from the original plugin). + private static final WorldPoint ARMOUR_STAND_TILE = new WorldPoint(2851, 3536, 0); + public boolean run(AnimatedArmourConfig config) { Microbot.enableAutoRunOn = false; @@ -32,17 +44,25 @@ public boolean run(AnimatedArmourConfig config) { boolean hasArmorPieces = Rs2Inventory.contains(item -> item.getName().contains("platebody")) && Rs2Inventory.contains(item -> item.getName().contains("platelegs")) && Rs2Inventory.contains(item -> item.getName().contains("full helm")); - if (Rs2Inventory.getInventoryFood().isEmpty() && config.foodAmount() > 0) { - Rs2Bank.walkToBank(BankLocation.WARRIORS_GUILD); - Rs2Bank.openBank(); - Rs2Bank.withdrawX(config.food().getId(), config.foodAmount()); - Rs2Walker.walkTo(2855, 3540, 0); - } + if (hasArmorPieces) { - animateArmor(); + // Never bank mid-kill. Only restock food before animating the next + // suit, when fewer than two food remain. + if (Rs2Inventory.getInventoryFood().size() < 2 && config.foodAmount() > 0) { + bankForFood(config); + } else { + animateArmor(config); + } } else { - Rs2Player.eatAt(Rs2Random.randomGaussian(30, 3)); - loot(); + Rs2Player.eatAt(Rs2Random.randomGaussian(60, 5)); + // Emergency only: if we ran dry mid-fight and are about to die, + // abandon the kill and bank rather than dying. + if (Rs2Inventory.getInventoryFood().isEmpty() && config.foodAmount() > 0 + && Rs2Player.getHealthPercentage() <= Rs2Random.randomGaussian(20, 3)) { + bankForFood(config); + } else { + loot(); + } } long endTime = System.currentTimeMillis(); @@ -50,22 +70,69 @@ public boolean run(AnimatedArmourConfig config) { System.out.println("Total time for loop " + totalTime); } catch (Exception ex) { + // A stop/restart can interrupt a client-thread call mid-flight (e.g. the + // door-open fallback). Restore the flag and exit quietly instead of logging + // a misleading ERROR stack trace. + if (ex instanceof InterruptedException || ex.getCause() instanceof InterruptedException) { + Thread.currentThread().interrupt(); + return; + } Microbot.logStackTrace(this.getClass().getSimpleName(), ex); } }, 0, 1000, TimeUnit.MILLISECONDS); return true; } - public void animateArmor() { - WorldPoint armorStandLocation = new WorldPoint(2851, 3536, 0); - Rs2TileObjectModel armorStand = Microbot.getRs2TileObjectCache().query().within(armorStandLocation, 0).nearest(); + private void bankForFood(AnimatedArmourConfig config) { + Rs2Bank.walkToBank(BankLocation.WARRIORS_GUILD); + Rs2Bank.openBank(); + Rs2Bank.withdrawX(config.food().getId(), config.foodAmount()); + Rs2Bank.closeBank(); + walkToArmourRoom(); + } + + private void walkToArmourRoom() { + // Walk with a distance tolerance instead of demanding the exact tile -- the + // exact-tile invocation is what made the short hop slow and prone to re-pathing. + Rs2Walker.walkTo(ARMOUR_STAND_TILE, 4); + // Fallback: if we stalled on the far side of the door, click it directly. The + // door is located by id once it has loaded, so no approach tile is hardcoded. + WorldPoint loc = Rs2Player.getWorldLocation(); + if (loc != null && loc.distanceTo(ARMOUR_STAND_TILE) > 4) { + if (Rs2GameObject.interact(ARMOURY_DOOR_IDS, "Open")) { + sleepUntil(() -> !Rs2Player.isMoving(), 3000); + Rs2Walker.walkTo(ARMOUR_STAND_TILE, 4); + } + } + } + + public void animateArmor(AnimatedArmourConfig config) { + // Make sure we actually reached the stand before trying to target it. + WorldPoint loc = Rs2Player.getWorldLocation(); + if (loc == null || loc.distanceTo(ARMOUR_STAND_TILE) > 8) { + walkToArmourRoom(); + return; + } + Rs2TileObjectModel armorStand = Microbot.getRs2TileObjectCache().query().within(ARMOUR_STAND_TILE, 0).nearest(); if (armorStand != null) { armorStand.click(); + // Eat one tick later, never on the same tick as the animate click (which + // would cancel it). This uses the idle animate delay to top up HP, but + // only when it won't be wasted (missing HP >= the food's heal amount). + sleepUntilNextTick(); + eatDuringAnimateIfEfficient(config); Rs2Player.waitForAnimation(); } - } + private void eatDuringAnimateIfEfficient(AnimatedArmourConfig config) { + if (Rs2Inventory.getInventoryFood().isEmpty()) return; + int missing = Rs2Player.getRealSkillLevel(Skill.HITPOINTS) + - Rs2Player.getBoostedSkillLevel(Skill.HITPOINTS); + if (missing >= config.food().getHeal()) { + Rs2Player.useFood(); + } + } public void loot() { LootingParameters valueParams = new LootingParameters( @@ -86,4 +153,4 @@ public void loot() { public void shutdown() { super.shutdown(); } -} \ No newline at end of file +} From 371a025fa7489bcf016be8b2c8a06bf9c9b10bad Mon Sep 17 00:00:00 2001 From: Alex <45095641+runsonmypc@users.noreply.github.com> Date: Mon, 15 Jun 2026 18:11:43 -0400 Subject: [PATCH 12/17] feat(slayer): add finishing item support (ice cooler, bag of salt, rock hammer) The Slayer plugin lacked the ability to use finishing items on monsters that require them (desert lizards, rockslugs, gargoyles). This adds: - Item-on-NPC finishing during combat when monsters reach low HP - Automatic withdrawal of finishing items from bank (quantity = remaining kills) - Banking trigger when finishing items are depleted - Vanilla autokill varbit checks to skip when player has auto-finish enabled - Inventory-full guard with log warning --- .../plugins/microbot/slayer/SlayerPlugin.java | 2 +- .../plugins/microbot/slayer/SlayerScript.java | 93 ++++++++++++++++++- 2 files changed, 93 insertions(+), 2 deletions(-) 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 40d93f5093..cc1fe0928a 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.2"; + static final String version = "1.1.0"; @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 ce2bfe254f..51fb7654a6 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 @@ -3,8 +3,13 @@ import lombok.extern.slf4j.Slf4j; import net.runelite.api.Actor; import net.runelite.api.GameState; +import net.runelite.api.Player; import net.runelite.api.Skill; import net.runelite.api.coords.WorldPoint; +import net.runelite.api.gameval.ItemID; +import static net.runelite.api.gameval.VarbitID.SLAYER_AUTOKILL_DESERTLIZARDS; +import static net.runelite.api.gameval.VarbitID.SLAYER_AUTOKILL_GARGOYLES; +import static net.runelite.api.gameval.VarbitID.SLAYER_AUTOKILL_ROCKSLUGS; import net.runelite.api.widgets.Widget; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; @@ -1385,7 +1390,8 @@ private void handleBankingState() { // Check if already matching if (inventorySetup.doesEquipmentMatch() && inventorySetup.doesInventoryMatch()) { - log.info("Inventory setup matches, closing bank"); + log.info("Inventory setup matches"); + withdrawFinishingItems(); Rs2Bank.closeBank(); sleepUntil(() -> !Rs2Bank.isOpen(), 2000); initialSetupDone = true; @@ -1408,6 +1414,7 @@ private void handleBankingState() { if (equipmentLoaded && inventoryLoaded) { log.info("Inventory setup loaded successfully"); + withdrawFinishingItems(); Rs2Bank.closeBank(); sleepUntil(() -> !Rs2Bank.isOpen(), 2000); initialSetupDone = true; @@ -1426,6 +1433,29 @@ private void handleBankingState() { } } + private void withdrawFinishingItems() { + int finishingItemId = getRequiredFinishingItemId(); + if (finishingItemId == -1) return; + + int alreadyHave = Rs2Inventory.itemQuantity(finishingItemId); + int remaining = Rs2Slayer.getSlayerTaskSize(); + int needed = remaining - alreadyHave; + if (needed <= 0) return; + + if (alreadyHave == 0 && Rs2Inventory.isFull()) { + log.warn("Inventory is full, cannot withdraw finishing items — add a free slot to your inventory setup"); + return; + } + + if (Rs2Bank.hasBankItem(finishingItemId, 1)) { + log.info("Withdrawing {} finishing items (have {}, task remaining {})", needed, alreadyHave, remaining); + Rs2Bank.withdrawX(finishingItemId, needed); + sleep(300, 600); + } else { + log.warn("Finishing item (ID {}) not found in bank", finishingItemId); + } + } + // Spellbook integer values from InventorySetup // 0=Standard, 1=Ancient, 2=Lunar, 3=Arceuus, 4=None private static final int SPELLBOOK_STANDARD = 0; @@ -2018,6 +2048,9 @@ private void handleFightingState(boolean hasTask, int remaining) { // Handle fungicide spray refill (for zygomite tasks) handleFungicideRefill(); + // Handle slayer finishing items (ice cooler, bag of salt, rock hammer) + handleSlayerFinishingItems(); + // Handle eating food BEFORE POH check // This ensures we eat food first, only use POH if still low HP after eating boolean ateFood = handleEating(); @@ -2264,6 +2297,57 @@ private int parseFungicideCharges(String name) { } } + private static final List LIZARD_VARIANTS = Arrays.asList("Lizard", "Desert Lizard", "Small Lizard"); + + /** + * Returns the finishing item ID required for the current slayer task, or -1 if none needed. + * Checks the vanilla autokill varbit — if the player already has auto-finish enabled in-game + * for that monster, we don't need to carry the item. + */ + private int getRequiredFinishingItemId() { + String task = Rs2Slayer.getSlayerTask(); + if (task == null) return -1; + String taskLower = task.toLowerCase(); + if (taskLower.contains("lizard") && !taskLower.contains("lizardm") + && Microbot.getVarbitValue(SLAYER_AUTOKILL_DESERTLIZARDS) == 0) { + return ItemID.SLAYER_ICY_WATER; + } else if (taskLower.contains("rockslug") + && Microbot.getVarbitValue(SLAYER_AUTOKILL_ROCKSLUGS) == 0) { + return ItemID.SLAYER_BAG_OF_SALT; + } else if (taskLower.contains("gargoyle") + && Microbot.getVarbitValue(SLAYER_AUTOKILL_GARGOYLES) == 0) { + return ItemID.SLAYER_ROCK_HAMMER; + } + return -1; + } + + private void handleSlayerFinishingItems() { + final Player localPlayer = Microbot.getClient().getLocalPlayer(); + if (localPlayer == null) return; + + Rs2NpcModel npc = Microbot.getRs2NpcCache().query() + .where(n -> n.isDead() && java.util.Objects.equals(n.getInteracting(), localPlayer)) + .first(); + if (npc == null) return; + + 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 + && LIZARD_VARIANTS.contains(npc.getName()) && npc.getHealthRatio() < 5) { + Rs2Inventory.useItemOnNpc(ItemID.SLAYER_ICY_WATER, legacyNpc); + Rs2Player.waitForAnimation(); + } else if (Microbot.getVarbitValue(SLAYER_AUTOKILL_ROCKSLUGS) == 0 + && "Rockslug".equalsIgnoreCase(npc.getName()) && npc.getHealthRatio() < 5) { + Rs2Inventory.useItemOnNpc(ItemID.SLAYER_BAG_OF_SALT, legacyNpc); + Rs2Player.waitForAnimation(); + } else if (Microbot.getVarbitValue(SLAYER_AUTOKILL_GARGOYLES) == 0 + && "Gargoyle".equalsIgnoreCase(npc.getName()) && npc.getHealthRatio() < 3) { + Rs2Inventory.useItemOnNpc(ItemID.SLAYER_ROCK_HAMMER, legacyNpc); + Rs2Player.waitForAnimation(); + } + } + /** * Varbit for antifire protection. * 0 = no protection, >0 = protected (ticks remaining) @@ -3265,6 +3349,13 @@ private boolean needsBanking() { } } + // Check finishing items (ice cooler, bag of salt, rock hammer) + int finishingItemId = getRequiredFinishingItemId(); + if (finishingItemId != -1 && Rs2Inventory.itemQuantity(finishingItemId) == 0) { + log.info("Out of finishing items (item ID {}), need to bank", finishingItemId); + return true; + } + return false; } From 9e1d17e47886182f61be0d65c4c038bc259d1df0 Mon Sep 17 00:00:00 2001 From: chsami Date: Thu, 25 Jun 2026 17:32:56 +0200 Subject: [PATCH 13/17] chore: promote development to main Promote the current development branch, including the agility mark-of-grace looting fix and QoL camera yaw API compatibility fix. --- .../microbot/agility/AgilityScript.java | 43 ++++++++++--------- .../microbot/agility/MicroAgilityPlugin.java | 2 +- .../microbot/qualityoflife/QoLPlugin.java | 6 +-- 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/agility/AgilityScript.java b/src/main/java/net/runelite/client/plugins/microbot/agility/AgilityScript.java index ea77c3875a..845a6e9fec 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/agility/AgilityScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/agility/AgilityScript.java @@ -4,22 +4,20 @@ import net.runelite.api.TileObject; import net.runelite.api.coords.WorldPoint; import net.runelite.api.gameval.ItemID; -import net.runelite.client.plugins.agility.AgilityPlugin; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; import net.runelite.client.plugins.microbot.agility.courses.BrimhavenSpikeCourse; import net.runelite.client.plugins.microbot.agility.courses.GnomeStrongholdCourse; import net.runelite.client.plugins.microbot.agility.courses.PrifddinasCourse; import net.runelite.client.plugins.microbot.agility.courses.WerewolfCourse; +import net.runelite.client.plugins.microbot.api.tileitem.models.Rs2TileItemModel; 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.camera.Rs2Camera; 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.magic.Rs2Magic; -import net.runelite.client.plugins.microbot.util.models.RS2Item; import net.runelite.client.plugins.microbot.util.player.Rs2Player; import net.runelite.client.plugins.microbot.util.walker.Rs2Walker; @@ -283,26 +281,31 @@ private Optional getAlchItem() private boolean lootMarksOfGrace() { - final List marksOfGrace = AgilityPlugin.getMarksOfGrace(); final int lootDistance = plugin.getCourseHandler().getLootDistance(); - if (!marksOfGrace.isEmpty() && !Rs2Inventory.isFull()) + if (Rs2Inventory.isFull() && !Rs2Inventory.contains(ItemID.GRACE)) { - for (RS2Item markOfGraceTile : marksOfGrace) - { - if (Microbot.getClient().getTopLevelWorldView().getPlane() != markOfGraceTile.getTile().getPlane()) - { - continue; - } - if (!Rs2GameObject.canReach(markOfGraceTile.getTile().getWorldLocation(), lootDistance, lootDistance, lootDistance, lootDistance)) - { - continue; - } - Rs2GroundItem.loot(markOfGraceTile.getItem().getId()); - Rs2Player.waitForWalking(); - return true; - } + return false; } - return false; + + Rs2TileItemModel markOfGrace = Microbot.getRs2TileItemCache().query() + .fromWorldView() + .withId(ItemID.GRACE) + .where(Rs2TileItemModel::isLootAble) + .where(item -> Rs2GameObject.canReach(item.getWorldLocation(), lootDistance, lootDistance, lootDistance, lootDistance)) + .nearest(lootDistance); + + if (markOfGrace == null) + { + return false; + } + + if (!markOfGrace.pickup()) + { + return false; + } + + Rs2Player.waitForWalking(); + return true; } private boolean handleFood() 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 1aca10a22f..60ffdb09cb 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.6"; + public static final String version = "1.2.7"; @Inject private MicroAgilityConfig config; @Inject 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 b5ece9ae0d..f66c0f4e01 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/qualityoflife/QoLPlugin.java @@ -91,7 +91,7 @@ ) @Slf4j public class QoLPlugin extends Plugin implements KeyListener { - public static final String version = "1.8.13"; + public static final String version = "1.8.14"; public static final List bankMenuEntries = new LinkedList<>(); public static final List furnaceMenuEntries = new LinkedList<>(); public static final List anvilMenuEntries = new LinkedList<>(); @@ -671,7 +671,7 @@ private void addLoadoutMenuEntries(MenuEntryAdded event, String target) { @Subscribe public void onConfigChanged(ConfigChanged ev) { if ("smoothRotation".equals(ev.getKey()) && config.smoothCameraTracking() && Microbot.isLoggedIn()) { - previousCamera[YAW_INDEX] = Microbot.getClient().getMapAngle(); + previousCamera[YAW_INDEX] = Microbot.getClient().getCameraYawTarget(); } if (ev.getKey().equals("accentColor") || ev.getKey().equals("toggleButtonColor") || ev.getKey().equals("pluginLabelColor")) { updateUiElements(); @@ -807,7 +807,7 @@ private void quickTeleportToHouse(MenuEntry entry) { private void applySmoothingToAngle(int index) { - int currentAngle = index == YAW_INDEX ? Microbot.getClient().getMapAngle() : 0; + int currentAngle = index == YAW_INDEX ? Microbot.getClient().getCameraYawTarget() : 0; int newDeltaAngle = getSmallestAngle(previousCamera[index], currentAngle); deltaCamera[index] += newDeltaAngle; From bb2b37f7a82f2f90966525556defec9f9cdcf119 Mon Sep 17 00:00:00 2001 From: dev Date: Wed, 1 Jul 2026 13:54:54 -0700 Subject: [PATCH 14/17] feat(WineMaker): add antiban support and out-of-materials hardening - Wire in Rs2Antiban with the cooking setup template (play style, fatigue/attention simulation, natural mouse, action cooldowns) - Enable micro breaks and random mouse movement - Trigger action cooldown / micro break chance after each craft batch - Reset antiban settings on shutdown - Use up remaining partial batches instead of stopping at <14 materials - Confirm bank close and logout before stopping the plugin when out of grapes or jugs of water - Bump version to 1.2.0 --- .../microbot/natewinemaker/WinePlugin.java | 2 +- .../microbot/natewinemaker/WineScript.java | 26 ++++++++++++++++--- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/natewinemaker/WinePlugin.java b/src/main/java/net/runelite/client/plugins/microbot/natewinemaker/WinePlugin.java index 1d1408b20d..8903cc1c87 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/natewinemaker/WinePlugin.java +++ b/src/main/java/net/runelite/client/plugins/microbot/natewinemaker/WinePlugin.java @@ -30,7 +30,7 @@ ) @Slf4j public class WinePlugin extends Plugin { - public final static String version = "1.1.1"; + public final static String version = "1.2.0"; @Inject private Client client; diff --git a/src/main/java/net/runelite/client/plugins/microbot/natewinemaker/WineScript.java b/src/main/java/net/runelite/client/plugins/microbot/natewinemaker/WineScript.java index 95ce4c16ad..7223204e34 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/natewinemaker/WineScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/natewinemaker/WineScript.java @@ -3,6 +3,8 @@ import net.runelite.client.plugins.Plugin; import net.runelite.client.plugins.microbot.Microbot; import net.runelite.client.plugins.microbot.Script; +import net.runelite.client.plugins.microbot.util.antiban.Rs2Antiban; +import net.runelite.client.plugins.microbot.util.antiban.Rs2AntibanSettings; import net.runelite.client.plugins.microbot.util.bank.Rs2Bank; import net.runelite.client.plugins.microbot.util.inventory.Rs2Inventory; import net.runelite.client.plugins.microbot.util.keyboard.Rs2Keyboard; @@ -14,15 +16,23 @@ public class WineScript extends Script { public boolean run(WineConfig config) { + Rs2Antiban.resetAntibanSettings(); + Rs2Antiban.antibanSetupTemplates.applyCookingSetup(); + Rs2AntibanSettings.takeMicroBreaks = true; + Rs2AntibanSettings.moveMouseRandomly = true; mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { try { if (!super.run()) return; if (!Microbot.isLoggedIn()) return; + if (Rs2AntibanSettings.actionCooldownActive) return; + if (Rs2AntibanSettings.microBreakActive) return; if (Rs2Inventory.count("grapes") > 0 && (Rs2Inventory.count("jug of water") > 0)) { Rs2Inventory.combine("jug of water", "grapes"); sleepUntil(() -> Rs2Widget.getWidget(17694734) != null); Rs2Keyboard.keyPress('1'); sleepUntil(() -> !Rs2Inventory.hasItem("jug of water"),25000); + Rs2Antiban.actionCooldown(); + Rs2Antiban.takeMicroBreakByChance(); } else { bank(); } @@ -37,21 +47,30 @@ private void bank(){ Rs2Bank.openBank(); if(Rs2Bank.isOpen()){ Rs2Bank.depositAll(); - if(Rs2Bank.hasBankItem("jug of water",14) && Rs2Bank.hasBankItem("grapes",14)) { - Rs2Bank.withdrawDeficit("jug of water", 14); + int jugsInBank = Rs2Bank.count("jug of water"); + int grapesInBank = Rs2Bank.count("grapes"); + if(jugsInBank > 0 && grapesInBank > 0) { + // Withdraw up to 14 of each, or whatever is left for a final partial batch + int amount = Math.min(14, Math.min(jugsInBank, grapesInBank)); + Rs2Bank.withdrawDeficit("jug of water", amount); sleepUntil(() -> Rs2Inventory.hasItem("jug of water")); - Rs2Bank.withdrawDeficit("grapes", 14); + Rs2Bank.withdrawDeficit("grapes", amount); sleepUntil(() -> Rs2Inventory.hasItem("grapes")); } else { + // Out of grapes or jugs of water: log out and stop the plugin Microbot.getNotifier().notify("Run out of Materials"); + Microbot.status = "[Shutting down] - Reason: out of grapes or jugs of water."; Rs2Bank.closeBank(); + sleepUntil(() -> !Rs2Bank.isOpen()); Rs2Player.logout(); + sleepUntil(() -> !Microbot.isLoggedIn(), 10000); Plugin wineMakerPlugin = Microbot.getPluginManager().getPlugins().stream() .filter(x -> x.getClass().getName().equals(WinePlugin.class.getName())) .findFirst() .orElse(null); Microbot.stopPlugin(wineMakerPlugin); shutdown(); + return; } } Rs2Bank.closeBank(); @@ -60,6 +79,7 @@ private void bank(){ @Override public void shutdown() { + Rs2Antiban.resetAntibanSettings(); super.shutdown(); } } From 02a8e4a7a340f1588bcee84b85df9fe671add13c Mon Sep 17 00:00:00 2001 From: dev Date: Wed, 1 Jul 2026 14:12:11 -0700 Subject: [PATCH 15/17] fix(WineMaker): respect user's antiban micro break setting instead of forcing it on --- .../plugins/microbot/natewinemaker/WineScript.java | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/natewinemaker/WineScript.java b/src/main/java/net/runelite/client/plugins/microbot/natewinemaker/WineScript.java index 7223204e34..b768ed64dc 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/natewinemaker/WineScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/natewinemaker/WineScript.java @@ -16,9 +16,12 @@ public class WineScript extends Script { public boolean run(WineConfig config) { + // Respect the user's antiban micro break setting: the reset + cooking template + // below would otherwise wipe it, so capture it first and restore it after. + boolean microBreaksEnabled = Rs2AntibanSettings.takeMicroBreaks; Rs2Antiban.resetAntibanSettings(); Rs2Antiban.antibanSetupTemplates.applyCookingSetup(); - Rs2AntibanSettings.takeMicroBreaks = true; + Rs2AntibanSettings.takeMicroBreaks = microBreaksEnabled; Rs2AntibanSettings.moveMouseRandomly = true; mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { try { @@ -32,7 +35,9 @@ public boolean run(WineConfig config) { Rs2Keyboard.keyPress('1'); sleepUntil(() -> !Rs2Inventory.hasItem("jug of water"),25000); Rs2Antiban.actionCooldown(); - Rs2Antiban.takeMicroBreakByChance(); + if (Rs2AntibanSettings.takeMicroBreaks) { + Rs2Antiban.takeMicroBreakByChance(); + } } else { bank(); } From 7558cba4f90ca38d1d6cc02865fba38b19b4e63e Mon Sep 17 00:00:00 2001 From: dev Date: Wed, 1 Jul 2026 14:23:19 -0700 Subject: [PATCH 16/17] fix(WineMaker): overlay user's saved antiban panel settings over the cooking template --- .../client/plugins/microbot/natewinemaker/WineScript.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/natewinemaker/WineScript.java b/src/main/java/net/runelite/client/plugins/microbot/natewinemaker/WineScript.java index b768ed64dc..9d0d51855a 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/natewinemaker/WineScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/natewinemaker/WineScript.java @@ -16,13 +16,12 @@ public class WineScript extends Script { public boolean run(WineConfig config) { - // Respect the user's antiban micro break setting: the reset + cooking template - // below would otherwise wipe it, so capture it first and restore it after. - boolean microBreaksEnabled = Rs2AntibanSettings.takeMicroBreaks; + // Apply the cooking template as a baseline, then overlay the user's saved + // antiban panel settings so anything toggled there wins over the template. Rs2Antiban.resetAntibanSettings(); Rs2Antiban.antibanSetupTemplates.applyCookingSetup(); - Rs2AntibanSettings.takeMicroBreaks = microBreaksEnabled; Rs2AntibanSettings.moveMouseRandomly = true; + Rs2AntibanSettings.loadFromProfile(); mainScheduledFuture = scheduledExecutorService.scheduleWithFixedDelay(() -> { try { if (!super.run()) return; From 95361c3d006817b51dffa946ce29107890e4806a Mon Sep 17 00:00:00 2001 From: dev Date: Wed, 1 Jul 2026 14:51:02 -0700 Subject: [PATCH 17/17] fix(WineMaker): trigger action cooldown when batch starts crafting, not after it finishes --- .../client/plugins/microbot/natewinemaker/WineScript.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/net/runelite/client/plugins/microbot/natewinemaker/WineScript.java b/src/main/java/net/runelite/client/plugins/microbot/natewinemaker/WineScript.java index 9d0d51855a..833c94151d 100644 --- a/src/main/java/net/runelite/client/plugins/microbot/natewinemaker/WineScript.java +++ b/src/main/java/net/runelite/client/plugins/microbot/natewinemaker/WineScript.java @@ -32,11 +32,15 @@ public boolean run(WineConfig config) { Rs2Inventory.combine("jug of water", "grapes"); sleepUntil(() -> Rs2Widget.getWidget(17694734) != null); Rs2Keyboard.keyPress('1'); - sleepUntil(() -> !Rs2Inventory.hasItem("jug of water"),25000); + // Trigger the cooldown (incl. mouse off screen / random moves) as soon as + // the batch starts crafting, like a human looking away mid-batch, rather + // than after the last wine finishes. + Rs2Inventory.waitForInventoryChanges(3000); Rs2Antiban.actionCooldown(); if (Rs2AntibanSettings.takeMicroBreaks) { Rs2Antiban.takeMicroBreakByChance(); } + sleepUntil(() -> !Rs2Inventory.hasItem("jug of water"),25000); } else { bank(); }