Learn how to program
by playing video games.

Learn JavaScript by playing RuneScape

Perfecting our Woodcutting Bot

April 5, 2020
Part of: Learn JavaScript by playing RuneScape

Today we finish coding our woodcutting RuneScape bot! In part 4 we use our pixel color matching skills to confirm when logs appear in our inventory. This fixes a bug where we could drop something valuable instead, and it allows us to greatly speed up our bot. Finally, we use the color botting technique one more time to verify that every automated click performs the expected action.

Links
GitHub repo: https://github.com/learncodebygaming/woodcutter (view source code here)
RobotJS Documentation: http://robotjs.io/docs/syntax
Eloquent JavaScript: Free Online Edition / Paperback or Ebook Edition
W3Schools: https://www.w3schools.com/js/default.asp

So we've actually covered every concept you need to know to build an awesome bot in Runescape. In this video, we're going to put that knowledge into practice by fixing all the issues with our woodcutting bot. It's this last mile that really makes all the difference in the software you write. It's the difference between a robust, impressive application, or a frustrating, bug-riddled experience.

So the first thing I want to do with our bot is overhaul the dropLogs() function. I'm going to show you how we can use our new knowledge about pixel matching to detect when a log appears in our inventory. This will prevent a potential major bug with our code, where we might accidentally drop a valuable item instead of just a log. It will also allow us to greatly speed up our harvesting, because as soon as we see a log in our inventory we’ll know we can move on to the next tree.

Once we get that working, we'll again use pixel matching, this time to verify that a tree we are about to click on is actually a tree that we want to chop. This will prevent those situations where our screen click doesn't quite result in a tree being cut, either because we've clicked on the wrong type of tree, or we've spotted some other brown pixel in the game, like one from the piles of logs our bot is leaving everywhere.

With these improvements, our automation is going to be looking very good.

So starting in our dropLogs() function, let's check to see what color of pixel appears when there's a log in our inventory. We'll be checking the pixel position where we perform our initial right click.

function dropLogs() {
    var inventory_x = 1882;
    var inventory_y = 830;

	var pixel_color = robot.getPixelColor(inventory_x, inventory_y);
    console.log("Inventory log color is: " + pixel_color);

    ...
}

Running this, I find that the pixel color at that position should be "765b37" where there are logs in that inventory position. So now we can use that pixel color to confirm that there are actually logs there before we go ahead with the code to drop that item.

Previously when we did pixel matching to find a tree to click on, we first took a screenshot and then looked at the pixel colors in that image. That's still the best way to it if you are trying to look at multiple places on the screen at once. But if you just need to check one pixel value, you can do that more simply by using robot.getPixelColor(x, y).

function dropLogs() {
    var inventory_x = 1882;
    var inventory_y = 830;
    var inventory_log_color = "765b37";

    // check to confirm that logs are there
    var pixel_color = robot.getPixelColor(inventory_x, inventory_y);
    //console.log("Inventory log color is: " + pixel_color);

    // drop logs from the inventory if the color matches the expected log color
    if (pixel_color == inventory_log_color) {
        robot.moveMouse(inventory_x, inventory_y);
        robot.mouseClick('right');
        // adding a little delay here because sometimes the second click misses
        sleep(300);
        robot.moveMouse(inventory_x, inventory_y + 70);
        robot.mouseClick();
        sleep(1000);
    }
}

So now we're protected from dropping something out of our inventory that doesn't look like a log.

Now that we can detect when a log appears in our inventory, we can use that to our advantage to harvest faster. In the main function, right now we're always waiting 8 seconds between each click on a tree. That means our character is often wasting time just standing around after chopping down a tree. Or it might mean that we click away too soon before the chopping has actually completed. So let's begin by changing that base wait time to just 3 seconds.

Then inside dropLogs(), let's create a holding pattern that waits until we see logs appear in the inventory. We'll check this pixel color once every second, and we'll set some maximum so that we don't wait forever if one never appears. To do this, we'll use a while loop.

var wait_cycles = 0;
var max_wait_cycles = 9;
while (pixel_color != inventory_log_color && wait_cycles < max_wait_cycles) {
    // we don't have a log in our inventory yet at the expected position.
    // waiting a little bit longer to see if the chopping finishes
    sleep(1000);
    // sample the pixel color again after waiting
    pixel_color = robot.getPixelColor(inventory_x, inventory_y);
    // increment our counter
    wait_cycles++;
}

This code will wait 1 second, then check the pixel color again, until the sampled color is the log color we are looking for. We also have a wait cycle counter that allows us to set a maximum number of times before we exit the loop no matter what. We can continue our loop while both these conditions are true by using the && (AND) operator in the while condition. By setting the base wait time to 3 seconds, and then going through the loop a maximum of 9 times, we will wait up to 12 seconds total before we move onto the next tree.

Running our script now, you'll see that it does move from tree to tree much faster, as long as we're successfully clicking on a tree. But whenever we have a mistake and don't click on a tree successfully, then we're waiting the max 12 seconds. So that means we'll be able to really speed up our bot if we can somehow confirm that what we're about to click on will actually result in chopping down a tree.

One way I noticed we could do that is: when you hover your mouse over a certain location, in the upper left hand corner it will show the action you're about to take if you were to click where your mouse location is. So my thinking is we can check a pixel inside this action text to help confirm the action we're about to take.

So first I grab the color of the action text I'm looking for ("00ffff") and a pixel position with this color that is unique to the word "Tree".

We'll begin this improvement in the findTree() function. Right before we return the coordinates of the tree pixel we found, let's first consult a new function we'll name confirmTree(). If this verifies that we did find a tree, by inspecting the action text, then we'll continue with the return of those coordinates. Otherwise we can continue the loop inside findTree() to get a different tree pixel.

// if we don't confirm that this coordinate is a tree, the loop will continue
if (confirmTree(screen_x, screen_y)) {
    console.log("Found a tree at: " + screen_x + ", " + screen_y + " color " + sample_color);
    return {x: screen_x, y: screen_y};
} else {
    // this just helps us debug the script
    console.log("Unconfirmed tree at: " + screen_x + ", " + screen_y + " color " + sample_color);
}

So now we just need to write the confirmTree() function to return true or false depending on if it finds the cyan colored "Tree" text in the upper left corner. To do this, we'll first move the mouse to the suggested tree location, then use robot.getPixelColor(x, y) again.

function confirmTree(screen_x, screen_y) {
    // first move the mouse to the given coordinates
    robot.moveMouse(screen_x, screen_y);
    // wait a moment for the help text to appear
    sleep(300);

    // now check the color of the action text
    var check_x = 103;
    var check_y = 63;
    var pixel_color = robot.getPixelColor(check_x, check_y);

    // returns true if the pixel color is cyan
    return pixel_color == "00ffff";
}

Our bot is finished! After these last few finishing touches, you should find that the bot now woodcuts rapidly and without error for long stretches of time.

To wrap up this tutorial, I want to leave you with some ideas for what your next steps could be. You've followed along with me up to this point, so now to really lock in your understanding it'd be a great idea to take some steps out on your own.

Here's five ideas for improving and expanding upon this bot, from easiest to hardest:

  1. Exit the script if you do 5 camera rotations in a row, because if that happens you've clearly left the forest.
  2. Auto logout from the game when your script ends. You can do this by automating a couple of mouse clicks.
  3. Adapt this script for mining instead of woodcutting. It should be very similar, but obviously the colors you're looking for will be different. I haven't tried this myself, so there could be some unforeseen complications with this one.
  4. Auto logout if you're attacked by monitoring your health.
  5. Instead of dropping logs one by one, try dumping 8-12 at once. You'll have to consider that you’ll be dealing with 8-12 inventory locations. If you continue to use the one log color, you'll also need to be very precise about what pixels you check. Using relative coordinates, that is how far offset one inventory item is from another, will make this task much easier. Our wait cycle code will also need to be updated to look at the next open location in your inventory as it waits for the chopping to finish.

If you are attempting any of the exercises and need a little help, feel free to reach out to me about that in the comments, and I'll try my best to help you. Good luck!


Ben Johnson My name is Ben and I help people learn how to code by gaming. I believe in the power of project-based learning to foster a deep understanding and joy in the craft of software development. On this site I share programming tutorials, coding-game reviews, and project ideas for you to explore.