diff --git a/CVTemplates.bat b/CVTemplates.bat index 9c47562..b43b526 100644 --- a/CVTemplates.bat +++ b/CVTemplates.bat @@ -48,25 +48,6 @@ curl -o "%MOUSE_DIR%\red_4.png" https://raw.githubusercontent.com/kelltom/OS-Bot echo Mouse clicks downloaded. -REM Download compass degree images from RuneDark repo -echo Cloning RuneDark repo to extract compass degrees... - -REM Create compass directory if it doesn't exist -if not exist "src\main\resources\images\ui\compass_degrees" ( - mkdir "src\main\resources\images\ui\compass_degrees" -) - -REM Clone the repo into a temp folder -git clone --depth 1 https://github.com/cemenenkoff/runedark-public.git temp_runedark - -REM Copy compass degree images -xcopy /E /Y "temp_runedark\src\img\bot\ui_templates\compass_degrees\*" "src\main\resources\images\ui\compass_degrees\" - -REM Clean up -rmdir /S /Q temp_runedark - -echo Compass degree images copied to src\main\resources\images\ui\compass_degrees\ - echo UI images downloaded to %UI_DIR% REM Now generate .index files for fonts diff --git a/README.md b/README.md index b977427..28676e2 100644 --- a/README.md +++ b/README.md @@ -36,21 +36,17 @@ Due to the separation of domain, core and actions utilities it provides a greate ## Mouse input Here's a very simple scripting example from the [DemoMiningScript](https://github.com/StaticSweep/ChromaScape/blob/main/src/main/java/com/chromascape/scripts/DemoMiningScript.java) that you'll see working below. ```java - private void clickOre() { - try { - BufferedImage gameView = controller().zones().getGameView(); - Point clickLoc = PointSelector.getRandomPointInColour(gameView, "Cyan", 15); - if (clickLoc == null) { - stop(); - return; - } - controller().mouse().moveTo(clickLoc, "medium"); - controller().mouse().leftClick(); - } catch (Exception e) { - logger.error(e); - logger.error(e.getStackTrace()); - } +private void clickOre() { + BufferedImage gameView = controller().zones().getGameView(); + Point clickLoc = PointSelector.getRandomPointInColour(gameView, "Cyan", 15); + if (clickLoc == null) { + logger.error("Click location is null"); + stop(); + return; } + controller().mouse().moveTo(clickLoc, "medium"); + controller().mouse().leftClick(); +} ``` @@ -99,4 +95,4 @@ ChromaScape includes functionality for identifying images or sprites by comparin ChromaScape utilises template matching for accurate and fast OCR. This solution - as opposed to machine learning - provides for ocr at runtime. This was inspired by SRL and OSBC. ### Note on dependencies: -This project downloads specific fonts and UI elements from the [OSBC](https://github.com/kelltom/OS-Bot-COLOR/tree/main), [RuneDark](https://github.com/cemenenkoff/runedark-public) and [SRL-dev](https://github.com/Villavu/SRL-Development/tree/master) projects to enable accurate template matching, OCR, and UI consistency. These resources are used solely for educational and research purposes and they are not packaged directly within this repository. +This project downloads specific fonts and UI elements from the [OSBC](https://github.com/kelltom/OS-Bot-COLOR/tree/main) and [SRL-dev](https://github.com/Villavu/SRL-Development/tree/master) projects to enable accurate template matching, OCR, and UI consistency. These resources are used solely for educational and research purposes and they are not packaged directly within this repository. diff --git a/src/main/java/com/chromascape/api/Dax.java b/src/main/java/com/chromascape/api/Dax.java index 3f71615..6357371 100644 --- a/src/main/java/com/chromascape/api/Dax.java +++ b/src/main/java/com/chromascape/api/Dax.java @@ -1,5 +1,8 @@ package com.chromascape.api; +import com.chromascape.utils.core.runtime.exception.DaxAuthException; +import com.chromascape.utils.core.runtime.exception.DaxException; +import com.chromascape.utils.core.runtime.exception.DaxRateLimitException; import java.awt.Point; import java.io.IOException; import java.net.URI; @@ -15,6 +18,9 @@ public class Dax { private static final String WALKER_ENDPOINT = "https://walker.dax.cloud/walker/generatePath"; + private final HttpClient client = + HttpClient.newBuilder().version(HttpClient.Version.HTTP_2).build(); + /** * Sends a pathfinding request to the DAX Walker API. * @@ -23,12 +29,13 @@ public class Dax { * @param members True if the player is a member; false otherwise. * @return Raw JSON string representing the generated path. * @throws IOException If an IO error occurs during the request. - * @throws InterruptedException If the thread is interrupted or the API returns - * RATE_LIMIT_EXCEEDED (HTTP 429) or INVALID_CREDENTIALS (HTTP 404). + * @throws InterruptedException If the thread is interrupted. + * @throws DaxRateLimitException If HTTP 429 is returned. + * @throws DaxAuthException If credentials or endpoint are invalid (400, 401, 404). */ public String generatePath(Point start, Point end, boolean members) throws IOException, InterruptedException { - HttpClient client = HttpClient.newHttpClient(); + String payload = String.format( """ @@ -39,6 +46,7 @@ public String generatePath(Point start, Point end, boolean members) } """, start.x, start.y, end.x, end.y, members); + HttpRequest request = HttpRequest.newBuilder() .uri(URI.create(WALKER_ENDPOINT)) @@ -48,11 +56,14 @@ public String generatePath(Point start, Point end, boolean members) .header("secret", "PUBLIC-KEY") .POST(HttpRequest.BodyPublishers.ofString(payload)) .build(); + HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); + return switch (response.statusCode()) { - case 429 -> "RATE_LIMIT_EXCEEDED"; - case 400, 401, 404 -> throw new InterruptedException("INVALID_CREDENTIALS"); - default -> response.body(); + case 200 -> response.body(); + case 429 -> throw new DaxRateLimitException(); + case 400, 401, 404 -> throw new DaxAuthException(); + default -> throw new DaxException("Unexpected API error: " + response.statusCode()); }; } } diff --git a/src/main/java/com/chromascape/base/BaseScript.java b/src/main/java/com/chromascape/base/BaseScript.java index 7341b11..a7c26b5 100644 --- a/src/main/java/com/chromascape/base/BaseScript.java +++ b/src/main/java/com/chromascape/base/BaseScript.java @@ -1,7 +1,7 @@ package com.chromascape.base; import com.chromascape.controller.Controller; -import com.chromascape.utils.core.runtime.ScriptStoppedException; +import com.chromascape.utils.core.runtime.exception.ScriptStoppedException; import com.chromascape.utils.core.state.BotState; import com.chromascape.utils.core.state.StateManager; import com.chromascape.utils.core.statistics.StatisticsManager; diff --git a/src/main/java/com/chromascape/scripts/DemoAgilityScript.java b/src/main/java/com/chromascape/scripts/DemoAgilityScript.java index 09172c8..a4b1621 100644 --- a/src/main/java/com/chromascape/scripts/DemoAgilityScript.java +++ b/src/main/java/com/chromascape/scripts/DemoAgilityScript.java @@ -93,20 +93,12 @@ public class DemoAgilityScript extends BaseScript { protected void cycle() { // Log the current XP before clicking obstacle for comparison later // The idea is to click the obstacle then wait for XP change then loop - int previousXp = -1; - try { - // Read XP - previousXp = Minimap.getXp(this); - // Make sure it's read properly - if (previousXp == -1) { - stop(); - DiscordNotification.send("Xp could not be read."); - } - } catch (IOException e) { - logger.error(e); + int previousXp = Minimap.getXp(this); + + // Make sure it's read properly + if (previousXp == -1) { stop(); - DiscordNotification.send( - "Bot couldn't read XP bar because of OCR font library load, stopping and logging :("); + DiscordNotification.send("Xp could not be read."); } // Check the state of the course @@ -121,14 +113,7 @@ protected void cycle() { // Interact with the detected obstacle // Clicking continuously until the Red X animation is detected - try { - MovingObject.clickMovingObjectByColourObjUntilRedClick(OBSTACLE_COLOUR, this); - } catch (Exception e) { - logger.error("Mouse movement interrupted while clicking moving object: {}", e.getMessage()); - stop(); - DiscordNotification.send( - "Mouse movement interrupted while clicking moving object: " + e.getMessage()); - } + MovingObject.clickMovingObjectByColourObjUntilRedClick(OBSTACLE_COLOUR, this); // Wait for the action to complete via XP update waitUntilXpChange(previousXp); @@ -145,22 +130,38 @@ protected void cycle() { } /** - * Manages the scenario when nothing is visible. - * Firstly, confirms that it's really lost, if so -> uses the walker to path back to the reset tile. - * Finally, waits for the player's animation to settle after reaching the true tile. + * Manages the scenario when nothing is visible. Firstly, confirms that it's really lost, if so -> + * uses the walker to path back to the reset tile. Finally, waits for the player's animation to + * settle after reaching the true tile. */ private void recoverToResetTile() { // Double check we are actually lost to protect against lag or rendering delays waitRandomMillis(600, 800); + if (!isObstacleVisible()) { - try { - logger.info("We are lost. Walking to reset tile."); - controller().walker().pathTo(RESET_TILE, true); - // wait for camera to stabilise and walking animation to finish at true tile. - waitRandomMillis(4000, 6000); - } catch (Exception e) { - logger.error("Walker error {}", e.getMessage()); - stop(); + + int attempts = 0; + int allowedAttempts = 5; + + while (attempts < allowedAttempts) { + try { + logger.info("We are lost. Walking to reset tile."); + controller().walker().pathTo(RESET_TILE, true); + // wait for camera to stabilise and walking animation to finish at true tile. + waitRandomMillis(4000, 6000); + break; + + } catch (IOException e) { + // This exception refers to Timeout or transport error + logger.error("Walker error {}", e.getMessage()); + attempts++; + + } catch (InterruptedException e) { + // This error means that the thread was interrupted while calling Dax + DiscordNotification.send("Walker thread interrupted, catastrophic failure."); + logger.error("Walker thread interrupted, catastrophic failure."); + stop(); + } } } } @@ -179,14 +180,9 @@ private boolean clickMarkOfGraceIfPresent() { Point clickLocation = PointSelector.getRandomPointByColourObj(gameView, MARK_COLOUR, 15, 15.0); if (clickLocation != null) { - try { - controller().mouse().moveTo(clickLocation, "medium"); - controller().mouse().leftClick(); - return true; - } catch (Exception e) { - logger.error("Mouse failed while moving to mark of grace {}", e.getMessage()); - stop(); - } + controller().mouse().moveTo(clickLocation, "medium"); + controller().mouse().leftClick(); + return true; } return false; } @@ -199,14 +195,8 @@ private boolean clickMarkOfGraceIfPresent() { private void waitUntilXpChange(int previousXp) { LocalDateTime endTime = LocalDateTime.now().plusSeconds(TIMEOUT_XP_CHANGE); // Ensure we do not hang if the initial OCR read failed and returned an empty string - try { - while (previousXp == Minimap.getXp(this) && LocalDateTime.now().isBefore(endTime)) { - waitMillis(300); - } - } catch (Exception e) { - logger.error(e); - stop(); - DiscordNotification.send("Bot couldn't read XP bar, stopping"); + while (previousXp == Minimap.getXp(this) && LocalDateTime.now().isBefore(endTime)) { + waitMillis(300); } } diff --git a/src/main/java/com/chromascape/scripts/DemoFishingScript.java b/src/main/java/com/chromascape/scripts/DemoFishingScript.java index dd93d6a..86d9094 100644 --- a/src/main/java/com/chromascape/scripts/DemoFishingScript.java +++ b/src/main/java/com/chromascape/scripts/DemoFishingScript.java @@ -1,19 +1,19 @@ package com.chromascape.scripts; import com.chromascape.base.BaseScript; +import com.chromascape.utils.actions.Idler; +import com.chromascape.utils.actions.ItemDropper; import com.chromascape.utils.actions.PointSelector; -import com.chromascape.utils.core.input.distribution.ClickDistribution; import com.chromascape.utils.core.screen.colour.ColourInstances; import com.chromascape.utils.core.screen.colour.ColourObj; +import com.chromascape.utils.core.screen.topology.MatchResult; import com.chromascape.utils.core.screen.topology.TemplateMatching; import com.chromascape.utils.core.screen.window.ScreenManager; import com.chromascape.utils.domain.ocr.Ocr; import java.awt.Point; import java.awt.Rectangle; import java.awt.image.BufferedImage; -import java.io.IOException; -import java.time.Duration; -import java.time.Instant; +import java.time.LocalDateTime; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -22,11 +22,11 @@ * * * *

This is a DEMO script. It's not intended to be used, but rather as a reference. ChromaScape is @@ -34,15 +34,13 @@ */ public class DemoFishingScript extends BaseScript { - // Only one type of rod and one bait is necessary to be downloaded - private static final String fishingRod = "/images/user/Fishing_rod.png"; private static final String flyFishingRod = "/images/user/Fly_fishing_rod.png"; - private static final String bait = "/images/user/Fishing_bait.png"; private static final String feather = "/images/user/Feather.png"; private static final Logger logger = LogManager.getLogger(DemoFishingScript.class); - private static String lastMessage; + private static final int IDLE_TIMEOUT_SECONDS = 300; + private static final int WALK_TIMEOUT_SECONDS = 17; /** * Overridden cycle. Repeats all tasks within, until stop() is called from either the Web UI, or @@ -51,19 +49,17 @@ public class DemoFishingScript extends BaseScript { @Override protected void cycle() { if (!checkIfCorrectInventoryLayout()) { - logger.warn("Normal or fly-fishing rod must be in inventory slot 27"); - logger.warn("Bait/feathers must be in inventory slot 28"); + logger.warn("Fly-fishing rod must be in inventory slot 27 / idx 26"); + logger.warn("Feathers must be in inventory slot 28 / idx 27"); logger.info("The top of your bait or feather images should be cropped by 10 px"); stop(); } clickFishingSpot(); - // Waiting, if the player needs to walk - adjust as needed - waitRandomMillis(5000, 6000); + waitUntilStoppedMoving(); - // Check if stopped fishing - waitUntilStoppedFishing(300); + waitUntilStoppedFishing(); logger.info("Is idle"); // If ran out of bait, stop @@ -79,44 +75,64 @@ protected void cycle() { } } + /** + * Queries {@link DemoFishingScript#getCurrentWorldPos()} every tick until the player has stopped + * moving or the WALK_TIMEOUT_SECONDS is reached. Blocks execution of the script until either + * condition is met. + */ + private void waitUntilStoppedMoving() { + LocalDateTime end = LocalDateTime.now().plusSeconds(WALK_TIMEOUT_SECONDS); + String currentTile = getCurrentWorldPos(); + while (LocalDateTime.now().isBefore(end)) { + waitMillis(650); + if (currentTile.equals(getCurrentWorldPos())) { + return; + } + currentTile = getCurrentWorldPos(); + } + } + + /** + * Uses Ocr on the Grid Info box's Tile subzone. + * + * @return the tile position as a String. + */ + private String getCurrentWorldPos() { + Rectangle zone = controller().zones().getGridInfo().get("Tile"); + ColourObj colour = ColourInstances.getByName("White"); + return Ocr.extractText(zone, "Plain 12", colour, true); + } + /** * Checks if the inventory layout is as expected. The inventory layout needs to be in a specific - * format to ensure that the dropping of items looks human. + * format to ensure that the dropping of items looks human. Will check for a fly-fishing rod in + * index 26 and feathers in index 27. * * @return {@code boolean} true if correct, false if not. */ private boolean checkIfCorrectInventoryLayout() { logger.info("Checking if inventory layout is valid"); + Rectangle invSlot27 = controller().zones().getInventorySlots().get(26); Rectangle invSlot28 = controller().zones().getInventorySlots().get(27); + BufferedImage invSlot27Image = ScreenManager.captureZone(invSlot27); BufferedImage invSlot28Image = ScreenManager.captureZone(invSlot28); - Rectangle slot27Match; - Rectangle slot28Match; - - try { - slot27Match = TemplateMatching.match(fishingRod, invSlot27Image, 0.15, false); - slot28Match = TemplateMatching.match(bait, invSlot28Image, 0.15, false); - if (slot27Match != null && slot28Match != null) { - logger.info("Inventory layout matched"); - return true; - } + MatchResult slot27Match = TemplateMatching.match(flyFishingRod, invSlot27Image, 0.15); + MatchResult slot28Match = TemplateMatching.match(feather, invSlot28Image, 0.15); - slot27Match = TemplateMatching.match(flyFishingRod, invSlot27Image, 0.05, false); - slot28Match = TemplateMatching.match(feather, invSlot28Image, 0.05, false); + if (!slot27Match.success()) { + logger.error("Slot 27 / idx 26 does not contain a fly fishing rod."); + return false; + } - if (slot27Match != null && slot28Match != null) { - logger.info("Inventory layout matched"); - return true; - } else { - return false; - } - } catch (Exception e) { - logger.error( - "checkIfCorrectInventoryLayout() template matching failed: {}", String.valueOf(e)); + if (!slot28Match.success()) { + logger.error("Slot 28 / idx 27 does not contain feathers."); + return false; } - return false; + + return true; } /** @@ -125,47 +141,9 @@ private boolean checkIfCorrectInventoryLayout() { */ private void dropAllFish() { logger.info("Dropping all fish"); - int currentSlot = 0; - int alternateSlot = 4; - controller().keyboard().sendModifierKey(401, "shift"); - waitRandomMillis(100, 250); - for (int i = 0; i < 3; i++) { - for (int j = 0; j < 4; j++) { - clickPoint( - ClickDistribution.generateRandomPoint( - controller().zones().getInventorySlots().get(currentSlot))); - waitRandomMillis(20, 50); - clickPoint( - ClickDistribution.generateRandomPoint( - controller().zones().getInventorySlots().get(alternateSlot))); - waitRandomMillis(40, 80); - currentSlot++; - alternateSlot = currentSlot + 4; - } - waitRandomMillis(100, 200); - currentSlot = currentSlot + 4; - alternateSlot = currentSlot + 4; - } - clickPoint( - ClickDistribution.generateRandomPoint(controller().zones().getInventorySlots().get(24))); - clickPoint( - ClickDistribution.generateRandomPoint(controller().zones().getInventorySlots().get(25))); - waitRandomMillis(100, 250); - controller().keyboard().sendModifierKey(402, "shift"); - } - /** - * Helper method to left-click a {@link Point} location on-screen. - * - * @param clickPoint The point to click. - */ - private void clickPoint(Point clickPoint) { - try { - controller().mouse().moveTo(clickPoint, "medium"); - } catch (InterruptedException e) { - logger.error("Mouse interrupted: {}", String.valueOf(e)); - } - controller().mouse().leftClick(); + int[] excludeSlots = {26, 27}; + ItemDropper.dropAll(this, ItemDropper.DropPattern.ZIGZAG, excludeSlots); } /** @@ -178,13 +156,8 @@ private void clickPoint(Point clickPoint) { private boolean checkChatPopup(String phrase) { Rectangle chat = controller().zones().getChatTabs().get("Chat"); ColourObj black = ColourInstances.getByName("Black"); - String extraction = null; - try { - extraction = Ocr.extractText(chat, "Quill 8", black, true); - } catch (IOException e) { - logger.error("OCR failed while loading font: {}", String.valueOf(e)); - } - return extraction != null && extraction.contains(phrase); + String extraction = Ocr.extractText(chat, "Quill 8", black, true); + return extraction.contains(phrase); } /** @@ -194,52 +167,34 @@ private boolean checkChatPopup(String phrase) { */ private void clickFishingSpot() { logger.info("Clicking fishing spot"); - BufferedImage gameView = null; - try { - gameView = controller().zones().getGameView(); - } catch (Exception e) { - logger.error("gameView not found: {}", String.valueOf(e)); - } + BufferedImage gameView = controller().zones().getGameView(); Point clickLocation = PointSelector.getRandomPointInColour(gameView, "Cyan", 15); if (clickLocation == null) { logger.error("clickLocation is null!"); stop(); } - clickPoint(clickLocation); + controller().mouse().moveTo(clickLocation, "medium"); + controller().mouse().leftClick(); } /** - * A modified version of the {@link com.chromascape.utils.actions.Idler#waitUntilIdle(BaseScript, - * int) waitUntilIdle} method. Altered to also check if the inventory is full and or if the user - * has run out of bait/feathers. - * - * @param timeoutSeconds The maximum number of seconds to remain idle before continuing. + * Iterates over checking for idle, if the player can't carry any more fish, and if the player has + * run out of bait. Blocks the main thread until one of these events occurs or the TIMEOUT_SECONDS + * have elapsed. */ - private void waitUntilStoppedFishing(int timeoutSeconds) { - logger.info("Waiting until stopped fishing"); - checkInterrupted(); - try { - Instant start = Instant.now(); - Instant deadline = start.plus(Duration.ofSeconds(timeoutSeconds)); - while (Instant.now().isBefore(deadline)) { - Rectangle latestMessage = controller().zones().getChatTabs().get("Latest Message"); - ColourObj red = ColourInstances.getByName("ChatRed"); - ColourObj black = ColourInstances.getByName("Black"); - String idleText = Ocr.extractText(latestMessage, "Plain 12", red, true); - String timeStamp = Ocr.extractText(latestMessage, "Plain 12", black, true); - if ((idleText.contains("moving") || idleText.contains("idle")) - && !timeStamp.equals(lastMessage)) { - lastMessage = timeStamp; - return; - } else if (checkChatPopup("carry")) { - return; - } else if (checkChatPopup("have")) { - return; - } + private void waitUntilStoppedFishing() { + LocalDateTime end = LocalDateTime.now().plusSeconds(IDLE_TIMEOUT_SECONDS); + while (LocalDateTime.now().isBefore(end)) { + if (Idler.waitUntilIdle(this, 3)) { + return; + } + if (checkChatPopup("carry")) { + return; + } + if (checkChatPopup("have")) { + return; } - } catch (Exception e) { - logger.error("Error while waiting for idle", e); } } } diff --git a/src/main/java/com/chromascape/scripts/DemoMiningScript.java b/src/main/java/com/chromascape/scripts/DemoMiningScript.java index 69c3fa8..e6acdbb 100644 --- a/src/main/java/com/chromascape/scripts/DemoMiningScript.java +++ b/src/main/java/com/chromascape/scripts/DemoMiningScript.java @@ -40,7 +40,7 @@ public class DemoMiningScript extends BaseScript { @Override protected void cycle() { if (isInventoryFull()) { - ItemDropper.dropAll(controller()); + ItemDropper.dropAll(this); } clickOre(); waitRandomMillis(800, 1000); @@ -53,19 +53,15 @@ protected void cycle() { *

If no suitable rock is found, the script stops. */ private void clickOre() { - try { - BufferedImage gameView = controller().zones().getGameView(); - Point clickLoc = PointSelector.getRandomPointInColour(gameView, "Cyan", 15); - if (clickLoc == null) { - stop(); - return; - } - controller().mouse().moveTo(clickLoc, "medium"); - controller().mouse().leftClick(); - } catch (Exception e) { - logger.error(e); - logger.error(e.getStackTrace()); + BufferedImage gameView = controller().zones().getGameView(); + Point clickLoc = PointSelector.getRandomPointInColour(gameView, "Cyan", 15); + if (clickLoc == null) { + logger.error("Click location is null"); + stop(); + return; } + controller().mouse().moveTo(clickLoc, "medium"); + controller().mouse().leftClick(); } /** @@ -75,14 +71,9 @@ private void clickOre() { * @return {@code true} if the inventory is full, otherwise {@code false} */ private boolean isInventoryFull() { - try { - Rectangle invSlot = controller().zones().getInventorySlots().get(27); - BufferedImage invSlotImg = ScreenManager.captureZone(invSlot); - Rectangle match = TemplateMatching.match(ironOre, invSlotImg, 0.05, false); - return match != null; - } catch (Exception e) { - logger.error(e); - } - return false; + Rectangle invSlot = controller().zones().getInventorySlots().get(27); + BufferedImage invSlotImg = ScreenManager.captureZone(invSlot); + Rectangle match = TemplateMatching.match(ironOre, invSlotImg, 0.05).bounds(); + return match != null; } } diff --git a/src/main/java/com/chromascape/scripts/DemoWineScript.java b/src/main/java/com/chromascape/scripts/DemoWineScript.java index ede364b..71c3722 100644 --- a/src/main/java/com/chromascape/scripts/DemoWineScript.java +++ b/src/main/java/com/chromascape/scripts/DemoWineScript.java @@ -3,6 +3,7 @@ import com.chromascape.base.BaseScript; import com.chromascape.utils.actions.PointSelector; import com.chromascape.utils.core.input.distribution.ClickDistribution; +import com.chromascape.utils.core.screen.topology.MatchResult; import com.chromascape.utils.core.screen.topology.TemplateMatching; import com.chromascape.utils.core.screen.window.ScreenManager; import java.awt.Point; @@ -28,7 +29,7 @@ public class DemoWineScript extends BaseScript { private static final String grapes = "/images/user/Grapes.png"; private static final String jugs = "/images/user/Jug_of_water.png"; private static final String dumpBank = "/images/user/Dump_bank.png"; - private static final String unfermented = "/images/user/Unfermented.png"; + private static final String unfermented = "/images/user/Unfermented_wine.png"; private static final int MAX_ATTEMPTS = 15; private static final int INVENT_SLOT_GRAPES = 13; @@ -59,10 +60,10 @@ protected void cycle() { pressEscape(); // Exit bank UI waitRandomMillis(600, 800); - clickInventSlot(INVENT_SLOT_JUGS, "fast"); // Click the jugs of water in the inventory + clickInvSlot(INVENT_SLOT_JUGS, "fast"); // Click the jugs of water in the inventory waitRandomMillis(400, 500); - clickInventSlot(INVENT_SLOT_GRAPES, "slow"); // Use the jugs on the grapes to start making wine + clickInvSlot(INVENT_SLOT_GRAPES, "medium"); // Use the jugs on the grapes to start making wine waitRandomMillis(800, 900); pressSpace(); // Accept the start button @@ -74,7 +75,7 @@ protected void cycle() { clickImage(dumpBank, "medium", 0.055); // Put the fermenting wines in the bank to repeat waitRandomMillis(650, 750); - if (checkIfImageInv(unfermented, 0.055)) { // Repeating because bank is weird + if (checkIfImageInvSlot1(unfermented, 0.055)) { // Repeating because bank is weird controller().mouse().leftClick(); waitRandomMillis(600, 800); } @@ -107,29 +108,19 @@ private void pressSpace() { * click successfully. */ private void clickBank() { - Point clickLocation = new Point(); - try { - clickLocation = - PointSelector.getRandomPointInColour( - controller().zones().getGameView(), "Purple", MAX_ATTEMPTS); - } catch (Exception e) { - logger.error("Failed while generating bank click location: {}", String.valueOf(e)); - stop(); - } + Point clickLocation = + PointSelector.getRandomPointInColour( + controller().zones().getGameView(), "Cyan", MAX_ATTEMPTS); if (clickLocation == null) { logger.error("clickBank click location is null"); stop(); } - try { - controller().mouse().moveTo(clickLocation, "medium"); - controller().mouse().leftClick(); - logger.info("Clicked on purple bank object at {}", clickLocation); - } catch (Exception e) { - logger.error(e.getMessage()); - stop(); - } + controller().mouse().moveTo(clickLocation, "medium"); + logger.info("Clicked on purple bank object at {}", clickLocation); + + controller().mouse().leftClick(); } /** @@ -141,23 +132,18 @@ private void clickBank() { * @param threshold the openCV threshold to decide if a match exists */ private void clickImage(String imagePath, String speed, double threshold) { - try { - BufferedImage gameView = controller().zones().getGameView(); - Point clickLocation = PointSelector.getRandomPointInImage(imagePath, gameView, threshold); + BufferedImage gameView = controller().zones().getGameView(); + Point clickLocation = PointSelector.getRandomPointInImage(imagePath, gameView, threshold); - if (clickLocation == null) { - logger.error("clickImage click location is null"); - stop(); - } - - controller().mouse().moveTo(clickLocation, speed); - controller().mouse().leftClick(); - logger.info("Clicked on image at {}", clickLocation); - - } catch (Exception e) { - logger.error("clickImage failed: {}", e.getMessage()); + if (clickLocation == null) { + logger.error("clickImage click location is null"); stop(); } + + controller().mouse().moveTo(clickLocation, speed); + + controller().mouse().leftClick(); + logger.info("Clicked on image at {}", clickLocation); } /** @@ -166,56 +152,40 @@ private void clickImage(String imagePath, String speed, double threshold) { * @param slot the index of the inventory slot to click (0-27) * @param speed the speed that the mouse moves to click the image */ - private void clickInventSlot(int slot, String speed) { - try { - Rectangle boundingBox = controller().zones().getInventorySlots().get(slot); - if (boundingBox == null || boundingBox.isEmpty()) { - logger.info("Inventory slot {} not found.", slot); - stop(); - return; - } - - Point clickLocation = ClickDistribution.generateRandomPoint(boundingBox); - - if (clickLocation == null) { - logger.error("clickInventSlot click location is null"); - stop(); - } - - controller().mouse().moveTo(clickLocation, speed); - controller().mouse().leftClick(); - logger.info("Clicked inventory slot {} at {}", slot, clickLocation); + private void clickInvSlot(int slot, String speed) { + Rectangle boundingBox = controller().zones().getInventorySlots().get(slot); + if (boundingBox == null || boundingBox.isEmpty()) { + logger.info("Inventory slot {} not found.", slot); + stop(); + return; + } + + Point clickLocation = ClickDistribution.generateRandomPoint(boundingBox); - } catch (Exception e) { - logger.error("clickInventSlot failed: {}", e.getMessage()); + if (clickLocation == null) { + logger.error("clickInventSlot click location is null"); stop(); } + + controller().mouse().moveTo(clickLocation, speed); + + controller().mouse().leftClick(); + logger.info("Clicked inventory slot {} at {}", slot, clickLocation); } /** - * Checks if an image exists on the screen and returns a boolean referring to if it was detected. + * Checks if an image exists in the first inventory slot. * * @param imagePath the path to the image being searched * @param threshold the openCV threshold to decide if a match exists * @return true if the image exists in the inventory slot 1, else false */ - private boolean checkIfImageInv(String imagePath, double threshold) { - try { - BufferedImage inventorySlot1 = - ScreenManager.captureZone(controller().zones().getInventorySlots().get(0)); - Rectangle boundingBox = TemplateMatching.match(imagePath, inventorySlot1, threshold, false); - - if (boundingBox == null || boundingBox.isEmpty()) { - logger.error("Template match failed: No valid inventory bounding box."); - return false; - } + private boolean checkIfImageInvSlot1(String imagePath, double threshold) { + BufferedImage inventorySlot1 = + ScreenManager.captureZone(controller().zones().getInventorySlots().get(0)); - return true; + MatchResult result = TemplateMatching.match(imagePath, inventorySlot1, threshold); - } catch (Exception e) { - logger.error("checkIfImageInv failed: {}", e.getMessage()); - stop(); - } - return false; + return result.success(); } } diff --git a/src/main/java/com/chromascape/utils/actions/Idler.java b/src/main/java/com/chromascape/utils/actions/Idler.java index b00bcba..a6165cf 100644 --- a/src/main/java/com/chromascape/utils/actions/Idler.java +++ b/src/main/java/com/chromascape/utils/actions/Idler.java @@ -36,28 +36,26 @@ public class Idler { * * @param base the active {@link BaseScript} instance, usually passed as {@code this} * @param timeoutSeconds the maximum number of seconds to remain idle before continuing + * @return {@code true} if the idle message was found, {@code false} if the timeout was reached */ - public static void waitUntilIdle(BaseScript base, int timeoutSeconds) { + public static boolean waitUntilIdle(BaseScript base, int timeoutSeconds) { // Initial wait to prevent race condition to previous idle message. BaseScript.waitMillis(600); BaseScript.checkInterrupted(); - try { - Instant start = Instant.now(); - Instant deadline = start.plus(Duration.ofSeconds(timeoutSeconds)); - while (Instant.now().isBefore(deadline)) { - // Throttle wait to reduce lag, this is enough. - BaseScript.waitMillis(300); - Rectangle latestMessage = base.controller().zones().getChatTabs().get("Latest Message"); - String idleText = Ocr.extractText(latestMessage, "Plain 12", chatRed, true); - String timeStamp = Ocr.extractText(latestMessage, "Plain 12", black, true); - if ((idleText.contains("moving") || idleText.contains("idle")) - && !timeStamp.equals(lastMessage)) { - lastMessage = timeStamp; - return; - } + Instant start = Instant.now(); + Instant deadline = start.plus(Duration.ofSeconds(timeoutSeconds)); + while (Instant.now().isBefore(deadline)) { + // Throttle wait to reduce lag, this is enough. + BaseScript.waitMillis(300); + Rectangle latestMessage = base.controller().zones().getChatTabs().get("Latest Message"); + String idleText = Ocr.extractText(latestMessage, "Plain 12", chatRed, true); + String timeStamp = Ocr.extractText(latestMessage, "Plain 12", black, true); + if ((idleText.contains("moving") || idleText.contains("idle")) + && !timeStamp.equals(lastMessage)) { + lastMessage = timeStamp; + return true; } - } catch (Exception e) { - logger.error("Error while waiting for idle", e); } + return false; } } diff --git a/src/main/java/com/chromascape/utils/actions/ItemDropper.java b/src/main/java/com/chromascape/utils/actions/ItemDropper.java index 303aa69..0536ba3 100644 --- a/src/main/java/com/chromascape/utils/actions/ItemDropper.java +++ b/src/main/java/com/chromascape/utils/actions/ItemDropper.java @@ -1,11 +1,11 @@ package com.chromascape.utils.actions; import com.chromascape.base.BaseScript; -import com.chromascape.controller.Controller; import com.chromascape.utils.core.input.distribution.ClickDistribution; import java.awt.Point; import java.awt.Rectangle; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -44,20 +44,21 @@ public enum DropPattern { /** * Drops all items in the inventory using the default ZigZag (2-Row Strip) pattern. * - * @param controller The BaseScript's {@link Controller} object. + * @param baseScript The script that's running (Keyword: {@code this}). */ - public static void dropAll(Controller controller) { - dropAll(controller, DropPattern.ZIGZAG); + public static void dropAll(BaseScript baseScript) { + dropAll(baseScript, DropPattern.ZIGZAG, new int[0]); } /** * Drops all items in the inventory using a specified pattern. * - * @param controller The BaseScript's {@link Controller} object. + * @param baseScript The script that's running (Keyword: {@code this}). * @param pattern The {@link DropPattern} to use for index generation. + * @param exclude An int array with indexes NOT to be dropped. */ - public static void dropAll(Controller controller, DropPattern pattern) { - if (controller == null) { + public static void dropAll(BaseScript baseScript, DropPattern pattern, int[] exclude) { + if (baseScript.controller() == null) { logger.error("Controller is null, cannot drop items."); return; } @@ -67,24 +68,29 @@ public static void dropAll(Controller controller, DropPattern pattern) { List slotsToDrop = generateSlotIndices(pattern); // Start Shift-Drop - controller.keyboard().sendModifierKey(KEY_PRESS, "shift"); + baseScript.controller().keyboard().sendModifierKey(KEY_PRESS, "shift"); BaseScript.waitRandomMillis(100, 250); try { for (int slotIndex : slotsToDrop) { - if (slotIndex >= controller.zones().getInventorySlots().size()) { + if (slotIndex >= baseScript.controller().zones().getInventorySlots().size()) { continue; } - Rectangle slotZone = controller.zones().getInventorySlots().get(slotIndex); + if (Arrays.stream(exclude).anyMatch(x -> x == slotIndex)) { + continue; + } + + Rectangle slotZone = baseScript.controller().zones().getInventorySlots().get(slotIndex); Point clickPoint = ClickDistribution.generateRandomPoint(slotZone); - clickPoint(clickPoint, controller); + baseScript.controller().mouse().moveTo(clickPoint, "fast"); + baseScript.controller().mouse().leftClick(); BaseScript.waitRandomMillis(40, 90); } } finally { BaseScript.waitRandomMillis(100, 200); - controller.keyboard().sendModifierKey(KEY_RELEASE, "shift"); + baseScript.controller().keyboard().sendModifierKey(KEY_RELEASE, "shift"); } } @@ -121,20 +127,4 @@ private static List generateSlotIndices(DropPattern pattern) { } return indices; } - - /** - * Helper method to left-click a {@link Point} location on-screen. - * - * @param clickPoint The point to click. - * @param controller The controller instance. - */ - private static void clickPoint(Point clickPoint, Controller controller) { - try { - controller.mouse().moveTo(clickPoint, "fast"); - controller.mouse().leftClick(); - } catch (InterruptedException e) { - logger.error("Mouse interrupted during drop operation", e); - Thread.currentThread().interrupt(); - } - } } diff --git a/src/main/java/com/chromascape/utils/actions/Minimap.java b/src/main/java/com/chromascape/utils/actions/Minimap.java index 6dfab6b..b7558ca 100644 --- a/src/main/java/com/chromascape/utils/actions/Minimap.java +++ b/src/main/java/com/chromascape/utils/actions/Minimap.java @@ -4,7 +4,6 @@ import com.chromascape.utils.core.screen.colour.ColourObj; import com.chromascape.utils.domain.ocr.Ocr; import java.awt.Rectangle; -import java.io.IOException; import org.bytedeco.opencv.opencv_core.Scalar; /** @@ -23,9 +22,8 @@ public class Minimap { * Returns the character's current hitpoints, or -1 if not found. * * @param script The current running script (typically pass {@code this}) - * @throws IOException if the OCR failed to load the font */ - public static int getHp(BaseScript script) throws IOException { + public static int getHp(BaseScript script) { Rectangle textArea = script.controller().zones().getMinimap().get("hpText"); String hpText = Ocr.extractText(textArea, "Plain 11", textColour, true); if (hpText.isEmpty()) { @@ -38,9 +36,8 @@ public static int getHp(BaseScript script) throws IOException { * Returns the character's current prayer, or -1 if not found. * * @param script The current running script (typically pass {@code this}) - * @throws IOException if the OCR failed to load the font */ - public static int getPrayer(BaseScript script) throws IOException { + public static int getPrayer(BaseScript script) { Rectangle textArea = script.controller().zones().getMinimap().get("prayerText"); String prayerText = Ocr.extractText(textArea, "Plain 11", textColour, true); if (prayerText.isEmpty()) { @@ -53,9 +50,8 @@ public static int getPrayer(BaseScript script) throws IOException { * Returns the character's current run energy, or -1 if not found. * * @param script The current running script (typically pass {@code this}) - * @throws IOException if the OCR failed to load the font */ - public static int getRun(BaseScript script) throws IOException { + public static int getRun(BaseScript script) { Rectangle textArea = script.controller().zones().getMinimap().get("runText"); String runText = Ocr.extractText(textArea, "Plain 11", textColour, true); if (runText.isEmpty()) { @@ -68,9 +64,8 @@ public static int getRun(BaseScript script) throws IOException { * Returns the character's current special attack energy, or -1 if not found. * * @param script The current running script (typically pass {@code this}) - * @throws IOException if the OCR failed to load the font */ - public static int getSpec(BaseScript script) throws IOException { + public static int getSpec(BaseScript script) { Rectangle textArea = script.controller().zones().getMinimap().get("specText"); String specText = Ocr.extractText(textArea, "Plain 11", textColour, true); if (specText.isEmpty()) { @@ -82,14 +77,13 @@ public static int getSpec(BaseScript script) throws IOException { /** * Retrieves the current XP from beside the minimap UI element. * - *

It is highly recommended to set the XP bar to permanent, as seen here: - * https://github.com/StaticSweep/ChromaScape/wiki/Requirements + *

It is highly recommended to set the XP bar to permanent, as seen here: see Requirements * * @param script The current running script (typically pass {@code this}) * @return the XP integer, or empty if not found - * @throws IOException if the OCR failed to load the font */ - public static int getXp(BaseScript script) throws IOException { + public static int getXp(BaseScript script) { Rectangle xpZone = script.controller().zones().getMinimap().get("totalXP"); String xpText = Ocr.extractText(xpZone, "Plain 12", white, true); return Integer.parseInt(xpText.trim().replace(",", "")); diff --git a/src/main/java/com/chromascape/utils/actions/MouseOver.java b/src/main/java/com/chromascape/utils/actions/MouseOver.java index 08ced05..d6c121c 100644 --- a/src/main/java/com/chromascape/utils/actions/MouseOver.java +++ b/src/main/java/com/chromascape/utils/actions/MouseOver.java @@ -12,7 +12,6 @@ import com.chromascape.utils.domain.ocr.Ocr; import java.awt.Rectangle; import java.awt.image.BufferedImage; -import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -50,9 +49,8 @@ public class MouseOver { * * @param baseScript Your script instance, typically {@code this}. * @return The string found within the MouseOverText zone (No spaces). - * @throws IOException If font loading failed within OCR. */ - public static String getText(BaseScript baseScript) throws IOException { + public static String getText(BaseScript baseScript) { // Get image of MouseOverText Rectangle zone = baseScript.controller().zones().getMouseOver(); BufferedImage capture = ScreenManager.captureZone(zone); diff --git a/src/main/java/com/chromascape/utils/actions/MovingObject.java b/src/main/java/com/chromascape/utils/actions/MovingObject.java index 5f90cc0..6ba717c 100644 --- a/src/main/java/com/chromascape/utils/actions/MovingObject.java +++ b/src/main/java/com/chromascape/utils/actions/MovingObject.java @@ -9,7 +9,6 @@ import java.awt.Rectangle; import java.awt.image.BufferedImage; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -59,7 +58,7 @@ public static boolean clickMovingObjectInColourUntilRedClick(String colour, Base /** * Attempts to click a moving target defined by a ColourObj and verifies the action by looking for - * a rec click. + * a red click. * *