-
-
Notifications
You must be signed in to change notification settings - Fork 8
Making your first script
For this I'll use the example demo script DemoWineScript.
Every script in ChromaScape extends BaseScript and implements a looping method called cycle(). The web UI automatically detects and lists any classes you create in the scripts package.
Feel free to look at the complete DemoWineScript in the scripts folder if you get stuck.
Pro tip: If you hover over a function/class in intellij, it shows you the javaDocs which will show you the ins and outs of it all. Take advantage of this if you feel stuck.
Navigate to:
src/main/java/com/chromascape/scripts
This is where all of your scripts should live. The web UI scans this directory to populate the script selection side bar.
Create a public class and have it extend the BaseScript. This script should be named the activity you want to bot.
public class DemoWineScript extends BaseScript {
//Everything goes inside this from now on
}Create a matching Constructor and create the logger, it should look something like this:
private final Logger logger = LogManager.getLogger(DemoWineScript.class);
public DemoWineScript() {
super();
}Override the cycle() routine.
@Override
protected void cycle() {
logger.addLog("Hello World!");
waitRandomMillis(80, 100);
}All bot logic should go inside cycle(). This method runs repeatedly until stop() is called.
Now that you have a script file ready, we should learn how to access it and run it, it'll also help you understand the constructor parameters.
Run:
src/main/java/com/chromascape/web/ChromaScapeApplication.java
Open a browser and go to:
http://localhost:8080/
Once the application loads (may take a minute), you should be greeted with the web UI. This is where you'll click a script on the left hand side then click start.
If you've restarted the program, you may also need to refresh the page for it to work.
Let's walk though the idea of clicking an image somewhere on screen. We can also make it modular as this is something often repeated.
You should only ever need to store the path of a saved image, not load the file itself.
To store the image path as a class variable use the following structure:
private static final String imageName = "/images/user/your_image.png";Your image should be stored in:
src/main/resources/images/user
Because it's loaded as a resource.
You can download sprite/item images from the official OSRS wiki: https://oldschool.runescape.wiki/
For stacked/banked items (that have numbers over it) you need to crop out the top 10 pixels.
Let's create a function that combines a few utilities to click at a random point within an image.
/**
* Searches for the provided image template within the current game view, then clicks a random
* point within the detected bounding box if the match exceeds the defined threshold.
*
* @param imagePath the BufferedImage template to locate and click within the game view
* @param speed the speed that the mouse moves to click the image
* @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);
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());
stop();
}
}- This example shows you how to get zones from the ZoneManager.
- Specifically the gameView is stored as a buffered image because of its complex shape. Other zones are automatically rectangles.
- If a zone is a
Rectangleyou will need to callBufferedImage img = ScreenManager.captureZone(Rectangle zone);to save it as a buffered image. - The Threshold is used when the program looks for an image on the screen, a lower threshold means that the match needs to be more accurate. A threshold of
0.05is often preferred, with the maximum being0.15 - You can see how the stateful utility
mousemust be accessed through the controllercontroller().mouse().leftClick();
Exceptions aren't added to the BaseScript because they're caught locally, when there is an exception you must catch it to avoid changing the BaseScript.
In this example we will assume there is a purple object on screen that we must click.
/**
* Attempts to locate and click the purple bank object within the game view. It searches for
* purple contours, then clicks a randomly distributed point inside the contour,
* retrying up to a maximum number of attempts. Logs failures and stops the script if unable to
* 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();
}
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();
}
}More information on specific event ID's can be found in the VirtualKeyboardUtils class.
/**
* Simulates pressing the Escape key by sending the key press and release events to the client
* keyboard controller.
*/
private void pressEscape() {
controller().keyboard().sendModifierKey(401, "esc");
waitRandomMillis(80, 100);
controller().keyboard().sendModifierKey(402, "esc");
}- Most zones are saved as rectangles.
- Rectangle zones are generated dynamically created by the project. e.g. inventory slots, chatbox, control panel, minimap orbs.
- You can access the zones/rectangles through the controller as shown below.
- Learn more about accessing zones in the ZoneManager & SubZoneMapper wiki page.
Let's use inventory slots as an example.
/**
* Clicks a random point within the bounding box of a given inventory slot.
*
* @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.addLog("Inventory slot " + slot + " not found.");
stop();
return;
}
Point clickLocation = ClickDistribution.generateRandomPoint(boundingBox);
controller().mouse().moveTo(clickLocation, speed);
controller().mouse().leftClick();
logger.addLog("Clicked inventory slot " + slot + " at " + clickLocation);
} catch (Exception e) {
logger.addLog("clickInventSlot failed: " + e.getMessage());
stop();
}
}This function clicks any inventory slot you want, given the number of the slot and the speed of the mouse. Creating small and specific bits of code like this will allow you to modularise your bot and increase code reuse.