Delays

Write more reactive and performant bots.

Using delays in bots is crucial for several reasons:

  • Mimic human behaviour

  • Avoid detection

  • Increase reliability

  • Improving performance

This is especially important in a game like RuneScape that runs on a long (600ms) game tick, as bots can loop much quicker than this.

There are two kinds of delay: static and dynamic. Static delays block execution for a fixed amount of time, whereas conditional delays can be used to block execution until/while a certain condition is met. RuneMate's Execution API allows you to use both of these delay types, with some additional utility baked-in.

Static delays

Static delays are the most basic type of delay, and simply block execution for n milliseconds. For example, the following will delay for 600 milliseconds:

Execution.delay(600);

Random delays can used by providing a minimum and maximum timeout:

Execution.delay(600, 1200);

Dynamic delays

Dynamic delays are the most powerful type of delay. Let's use the example of chopping a tree:

public void onLoop() {
    GameObject tree = GameObjects.newQuery().names("Tree").results().nearest();
    if (tree != null) {
        tree.interact("Chop");
    }
}

In this example, the bot will attempt to chop the tree and then will immediately re-loop and likely try to chop it again, potentially multiple times before the game performs the next tick and player actually starts chopping. That's not particularly human-like!

Remember your null checks! RuneScape is a dynamic game and queries are not guaranteed to succeed, so failing to properly null check may result in crashes due to NullPointerExceptions.

Let's fix that by using the Execution.delayUntil() method, which will delay until a certain condition is met:

public void onLoop() {
    Player player = Players.getLocal();
    GameObject tree = GameObjects.newQuery().names("Tree").results().nearest();
    if (player != null && tree != null && tree.interact("Chop")) {
        Execution.delayUntil(() -> player.getAnimationId() != -1, 600);
    }
}

Now when our interaction with the tree succeeds, execution will be delayed until the player starts to animation, or until the timeout of 600 milliseconds (1 game tick) expires. This is an improvement, as the bot will not spam click the tree as much, but 600ms is still a rather short delay. What if we aren't standing next to the tree and movement is required to start chopping it? We could use a longer delay, but that is clunky, and if something interrupts our attempt to chop the tree, we'll be hanging around for an unnecessary amount of time.

Fortunately, the method also accepts a reset condition which can be used to make the delay more reactive:

public void onLoop() {
    Player player = Players.getLocal();
    GameObject tree = GameObjects.newQuery().names("Tree").results().nearest();
    if (player != null && tree != null && tree.interact("Chop")) {
        Execution.delayUntil(() -> player.getAnimationId() != -1, () -> player.isMoving(), 600);
    }
}

When this second condition is met it will reset the timeout, meaning that we can use a shorter primary delay.

Note that every delay method returns a boolean result, which is true if the condition is met before the timeout. This can be useful for more advanced control flow.

Last updated