Welcome!

By registering with us, you'll be able to discuss, share and private message with other members of our community.

Sign up now!

Tutorial An Introduction to the State-based Script

Status
Not open for further replies.
Joined
Dec 10, 2014
Messages
3,255
An Introduction to the State-based Script!
By: SlashnHax
Contents:
  • Introduction
  • Overview
  • Pros and Cons
    • Pros
    • Cons
  • Creating the Script
    • The Skeleton
    • The Manifest
    • Defining the States and implementing getCurrentState()
    • Implementing the methods
    • Refining the methods
    • End product
  • Parting words
Introduction
G'day mates. Welcome to my tutorial: An Introduction to the State Script. Throughout this tutorial we will cover the basics of the State-based Script and create a functional Script (an iron powerminer) using this approach.
Overview
A basic tutorial covering the State Script. The Pros and Cons of using this approach are discussed, then we move into creating a functional example by working through the required processes.
Pros and Cons
Pros
  • All of your code is in the one place. This is perfect for simple, small scripts where there isn't much code.
  • State-based scripts are pretty straightforward, making them perfect for your first script.
Cons
  • You can't reuse code as easily as you can with a Task-based script.
  • Large projects become messy and hard to read.
Creating the Script
The Skeleton
latest
Code:
import com.runemate.game.api.script.framework.LoopingScript;

/**
* Skeleton for a State-based Script
* Created by SlashnHax
*/
public class StateSkeleton extends LoopingScript {

    private enum State{
    }

    @Override
    public void onStart(String... args){
    }

    @Override
    public void onLoop() {
    }

    @Override
    public void onStop(){
    }

    private State getCurrentState(){
        return null;
    }
}
Replace StateSkeleton with the name you're going to save the file as, without the .java extension. (e.g. PowerMiner)
The import statement at the top imports the LoopingScript class, which is the type of Script we will be using. The onLoop() statement is called over and over until the Script is stopped.
We will need to import more classes later on, but for now this is all we need.
The Manifest
Covered by Cloud in the official post. -link-
My manifest for this project (your main-class tag will be different):
Code:
<manifest>
    <main-class>com.slashnhax.tutorials.statescripttutorial.PowerMiner</main-class>
    <name>Tutorial PowerMiner</name>
    <description>Powermines iron.</description>
    <version>1.0</version>
    <compatibility>
        <game-type>RS3</game-type>
    </compatibility>
    <categories>
        <category>MINING</category>
    </categories>
    <!--Required to publish on the bot store-->
    <internal-id>TutorialPowerminer</internal-id>
    <!--The rest are optional-->
    <hidden>false</hidden>
    <open-source>true</open-source>
</manifest>
Defining the States and implementing getCurrentState() (*1)
We already have the State enum from the skeleton, but now we need to populate it with the States that our script is going to use.
If you don't understand enums or want to brush up your knowledge -here is the official java enum tutorial-
For now, our miner shall function as a powerminer, so we need to think of what is done while powermining: Mining, waiting and dropping. We add these to the State enum resulting in:
Code:
private enum State{
    MINE, WAIT, DROP;
}
Now we must implement getCurrentState(). The purpose of getCurrentState() is to determine which State we are currently in, allowing us to act accordingly in our onLoop().
We want to either drop when we have a full inventory, mine when we are idle or wait while we aren't idle. To translate this into code we must first work out what determines whether our Player is idle or not, which is when their animation is -1 (*2). Determining if we have a full inventory or not is pretty easy, as there is a method for it in the Inventory API.
Therefore our pseudocode for getCurrentState() is:
Code:
private State getCurrentState(){
    if(full inventory){
        return DROP;
    } else if (player is idle){
        return MINE;
    } else {
        return WAIT;
    }
}
Which makes our actual implementation:
Imports (these go up the top with the other import):
Code:
import com.runemate.game.api.hybrid.local.hud.interfaces.Inventory;
import com.runemate.game.api.hybrid.region.Players;
getCurrentState():
Code:
private State getCurrentState(){
    if(Inventory.isFull()){
        return State.DROP;
    } else if (Players.getLocal().getAnimationId() == -1){
        return State.MINE;
    } else {
        return State.WAIT;
    }
}
Your current code should resemble this:
Code:
import com.runemate.game.api.hybrid.local.hud.interfaces.Inventory;
import com.runemate.game.api.hybrid.region.Players;
import com.runemate.game.api.script.framework.LoopingScript;

/**
* Skeleton for a State-based Script
* Created by SlashnHax
*/
public class PowerMiner extends LoopingScript {

    private enum State{
        MINE, DROP, WAIT;
    }

    @Override
    public void onLoop() {

    }

    private State getCurrentState(){
        if(Inventory.isFull()){
            return State.DROP;
        } else if (Players.getLocal().getAnimationId() == -1){
            return State.MINE;
        } else {
            return State.WAIT;
        }
    }
}
Implementing the methods
Now we have our States and our getCurrentState() implementation, we can fill in our onLoop() method with the code needed to make the script work as we want it to. We manage this buy using a switch statement -here is the official java tutorial- and the State enum we've defined. In our switch statement we have our 3 cases, the States we defined earlier. Now we work out what we want to do in each of these cases.

case MINE:
Here we want to do two things: Find the nearest Iron ore rocks and Mine them. To do this we need to import the GameObject and GameObjects classes.
Code:
import com.runemate.game.api.hybrid.entities.GameObject;
import com.runemate.game.api.hybrid.region.GameObjects;
To get the nearest Iron ore rocks we will use a query to select the objects by name, then get the results and select the nearest one, we will then store it as a variable.
Code:
GameObject rocks = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
Now that we have our rocks we want to mine them. First we want to make sure they aren't null, then we want to mine them using .interact(String action) method:
Code:
if(rocks != null){
    rocks.interact("Mine");
}
case DROP:
Here we have a couple of choices: We can drop everything or only drop certain items. For now we will drop everything.
For our dropping, we will use a for-each loop to iterate through the items in the inventory and "Drop" each of them then wait for a small amount of time. To do this we need to import SpriteItem, which is what the items of the inventory are, and Execution, which handles the delay.
Code:
import com.runemate.game.api.hybrid.local.hud.interfaces.SpriteItem;
import com.runemate.game.api.script.Execution;
Then we will use a for-each loop to loop through the items, dropping them and waiting 500ms to 1000ms before moving onto the next one:
Code:
for(SpriteItem i:Inventory.getItems()){
    i.interact("Drop");
    Execution.delay(500, 1000);
}
case WAIT:
Here we do stuff we want to do while waiting, or nothing. For now we'll do nothing.
Our (barely) functional script so far:
Code:
package com.slashnhax.tutorials.statescripttutorial;



import com.runemate.game.api.hybrid.entities.GameObject;
import com.runemate.game.api.hybrid.local.hud.interfaces.Inventory;
import com.runemate.game.api.hybrid.local.hud.interfaces.SpriteItem;
import com.runemate.game.api.hybrid.region.GameObjects;
import com.runemate.game.api.hybrid.region.Players;
import com.runemate.game.api.script.Execution;
import com.runemate.game.api.script.framework.LoopingScript;

/**
* PowerMiner for RuneMate tutorial
* Created by SlashnHax
*/
public class PowerMiner extends LoopingScript {

    private enum State{
        MINE, DROP, WAIT;
    }

    @Override
    public void onStart(String... args){

    }

    @Override
    public void onLoop() {
        switch(getCurrentState()){
            case MINE:
                GameObject rock = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
                if(rock != null) {
                    rock.interact("Mine");
                }
                break;
            case DROP:
                for(SpriteItem i:Inventory.getItems()){
                    i.interact("Drop");
                    Execution.delay(500, 1000);
                }
                break;
            case WAIT:
                break;
        }
    }

    @Override
    public void onStop(){

    }

    private State getCurrentState(){
        if(Inventory.isFull()){
            return State.DROP;
        } else if (Players.getLocal().getAnimationId() == -1){
            return State.MINE;
        } else {
            return State.WAIT;
        }
    }
}
Refining the methods


If you were to run the above script, you would notice a few things:

  1. It only mines visible rocks and doesn't turn towards any it can't see.
  2. It spam clicks rocks.
  3. If rocks of a different type get in the way, it will mine whatever comes first in the menu.
  4. If someone mines the rocks, it will still wait until the animation stops.
  5. Sometimes it doesn't completely empty the inventory.
  6. It drops things we may want to keep, such as the uncut gems, strange rocks etc.
  7. Using the action bar would be better for dropping.
  8. We could be doing something more productive with in WAIT.
  9. We get a caution about using the default loop delay every time we run the script.
We will now address these issues.
1. Only mining visible rocks:

To fix this we check if our rocks are visible, and if they aren't we turn the camera towards it. First we import Camera:
Code:
import com.runemate.game.api.hybrid.local.Camera;
Then we check if we can see the rocks and if we can't we turn the Camera towards them. We'll do this before we try to mine them. This makes our current MINE case:
Code:
case MINE:
    GameObject rocks = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
    if(rocks != null){
        if(!rocks.isVisible()){
            Camera.turnTo(rocks);
        }
        rocks.interact("Mine");
    }
    break;
2. Spam clicking:

To prevent spam clicking we will use Execution.delayUntil(Callable<Boolean> callable, int min, int max). This method delays the script for at least 'min' ms until either callable returns true or it has delayed for 'max' ms. To use this method without lambdas we would need to import Callable, but since we're going to use lambdas, we don't need to. We only want to delay if we are successful in interacting with the rock and luckily for us .interact(String action) is a boolean that returns true if the action was selected, so we can use it as the condition for an if statement with the delay as it's body. We will delay until our animation becomes something other than -1, or delayUntil times out (lets make it time out after 5 seconds). Our MINE case is now:
Code:
case MINE:
    GameObject rocks = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
    if(rocks != null){
        if(!rocks.isVisible()){
            Camera.turnTo(rocks);
        }
        if(rocks.interact("Mine")){
            Execution.delayUntil(()->Players.getLocal().getAnimationId() != -1, 500, 5000);
        }
    }
    break;
3. Not always mining the correct rock:

The easiest way to make sure we mine the correct rock is to get its name from its definition and add it to our interact method. To do this we need to add another null-check for rocks.getDefinition() alongside rocks != null, get the name using rocks.getDefinition().getName();, and then add the name as the second argument in the interact method. This will make your MINE case look like this:
Code:
case MINE:
    GameObject rocks = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
    if(rocks != null && rocks.getDefinition() != null){
        if(!rocks.isVisible()){
            Camera.turnTo(rocks);
        }
        if(rocks.interact("Mine", rocks.getDefinition().getName())){
            Execution.delayUntil(()->Players.getLocal().getAnimationId() != -1, 500, 5000);
        }
    }
    break;
4. The bot waiting if someone takes the rock:

The easiest way to manage this is to make 'rocks' a member variable instead of a local variable in onLoop(), and check to see if it's null or invalid in our getCurrentState(). If it is null or invalid, our getCurrentState() will return MINE, causing the script to move to the next suitable rock. This takes a few steps. First we make 'rocks' a member variable by declaring "GameObject rocks;" above our onStart(String... args) and removing the "GameObject" keyword from our first line in our MINE case. Then we make getCurrentState() return MINE if our player's animationId is -1, 'rocks' is null, or rocks isn't valid. Our code should now look like this:
Code:
package com.slashnhax.tutorials.statescripttutorial;

import com.runemate.game.api.hybrid.entities.GameObject;
import com.runemate.game.api.hybrid.local.Camera;
import com.runemate.game.api.hybrid.local.hud.interfaces.Inventory;
import com.runemate.game.api.hybrid.local.hud.interfaces.SpriteItem;
import com.runemate.game.api.hybrid.region.GameObjects;
import com.runemate.game.api.hybrid.region.Players;
import com.runemate.game.api.script.Execution;
import com.runemate.game.api.script.framework.LoopingScript;

/**
* PowerMiner for RuneMate tutorial
* Created by SlashnHax
*/
public class PowerMiner extends LoopingScript {

    private enum State{
        MINE, DROP, WAIT;
    }
    GameObject rocks;

    @Override
    public void onStart(String... args){

    }

    @Override
    public void onLoop() {
        switch(getCurrentState()){
            case MINE:
                rocks = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
                if(rocks != null && rocks.getDefinition() != null){
                    if(!rocks.isVisible()){
                        Camera.turnTo(rocks);
                    }
                    if(rocks.interact("Mine")){
                        Execution.delayUntil(()->Players.getLocal().getAnimationId() != -1, 500, 5000);
                    }
                }
                break;
            case DROP:
                for(SpriteItem i:Inventory.getItems()){
                    i.interact("Drop");
                    Execution.delay(500, 1000);
                }
                break;
            case WAIT:
                break;
        }
    }

    @Override
    public void onStop(){

    }

    private State getCurrentState(){
        if(Inventory.isFull()){
            return State.DROP;
        } else if (Players.getLocal().getAnimationId() == -1 || rocks == null || !rocks.isValid()){
            return State.MINE;
        } else {
            return State.WAIT;
        }
    }
}
 
Last edited:
Joined
Dec 10, 2014
Messages
3,255
Implementing the methods

Now we have our States and our getCurrentState() implementation, we can fill in our onLoop() method with the code needed to make the script work as we want it to. We manage this buy using a switch statement -here is the official java tutorial- and the State enum we've defined. In our switch statement we have our 3 cases, the States we defined earlier. Now we work out what we want to do in each of these cases.

case MINE:
Here we want to do two things: Find the nearest Iron ore rocks and Mine them. To do this we need to import the GameObject and GameObjects classes.
Code:
import com.runemate.game.api.hybrid.entities.GameObject;
import com.runemate.game.api.hybrid.region.GameObjects;
To get the nearest Iron ore rocks we will use a query to select the objects by name, then get the results and select the nearest one, we will then store it as a variable.
Code:
GameObject rocks = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
Now that we have our rocks we want to mine them. First we want to make sure they aren't null, then we want to mine them using .interact(String action) method:
Code:
if(rocks != null){
    rocks.interact("Mine");
}
case DROP:
Here we have a couple of choices: We can drop everything or only drop certain items. For now we will drop everything.
For our dropping, we will use a for-each loop to iterate through the items in the inventory and "Drop" each of them then wait for a small amount of time. To do this we need to import SpriteItem, which is what the items of the inventory are, and Execution, which handles the delay.
Code:
import com.runemate.game.api.hybrid.local.hud.interfaces.SpriteItem;
import com.runemate.game.api.script.Execution;
Then we will use a for-each loop to loop through the items, dropping them and waiting 500ms to 1000ms before moving onto the next one:
Code:
for(SpriteItem i:Inventory.getItems()){
    i.interact("Drop");
    Execution.delay(500, 1000);
}
case WAIT:
Here we do stuff we want to do while waiting, or nothing. For now we'll do nothing.
Our (barely) functional script so far:
Code:
package com.slashnhax.tutorials.statescripttutorial;


import com.runemate.game.api.hybrid.entities.GameObject;
import com.runemate.game.api.hybrid.local.hud.interfaces.Inventory;
import com.runemate.game.api.hybrid.local.hud.interfaces.SpriteItem;
import com.runemate.game.api.hybrid.region.GameObjects;
import com.runemate.game.api.hybrid.region.Players;
import com.runemate.game.api.script.Execution;
import com.runemate.game.api.script.framework.LoopingScript;

/**
* PowerMiner for RuneMate tutorial
* Created by SlashnHax
*/
public class PowerMiner extends LoopingScript {

    private enum State{
        MINE, DROP, WAIT;
    }

    @Override
    public void onStart(String... args){

    }

    @Override
    public void onLoop() {
        switch(getCurrentState()){
            case MINE:
                GameObject rock = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
                if(rock != null) {
                    rock.interact("Mine");
                }
                break;
            case DROP:
                for(SpriteItem i:Inventory.getItems()){
                    i.interact("Drop");
                    Execution.delay(500, 1000);
                }
                break;
            case WAIT:
                break;
        }
    }

    @Override
    public void onStop(){

    }

    private State getCurrentState(){
        if(Inventory.isFull()){
            return State.DROP;
        } else if (Players.getLocal().getAnimationId() == -1){
            return State.MINE;
        } else {
            return State.WAIT;
        }
    }
}
Refining the methods

If you were to run the above script, you would notice a few things:
  1. It only mines visible rocks and doesn't turn towards any it can't see.
  2. It spam clicks rocks.
  3. If rocks of a different type get in the way, it will mine whatever comes first in the menu.
  4. If someone mines the rocks, it will still wait until the animation stops.
  5. Sometimes it doesn't completely empty the inventory.
  6. It drops things we may want to keep, such as the uncut gems, strange rocks etc.
  7. Using the action bar would be better for dropping.
  8. We could be doing something more productive with in WAIT.
  9. We get a caution about using the default loop delay every time we run the script.
We will now address these issues.
1. Only mining visible rocks:
To fix this we check if our rocks are visible, and if they aren't we turn the camera towards it. First we import Camera:
Code:
import com.runemate.game.api.hybrid.local.Camera;
Then we check if we can see the rocks and if we can't we turn the Camera towards them. We'll do this before we try to mine them. This makes our current MINE case:
Code:
case MINE:
    GameObject rocks = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
    if(rocks != null){
        if(!rocks.isVisible()){
            Camera.turnTo(rocks);
        }
        rocks.interact("Mine");
    }
    break;
2. Spam clicking:
To prevent spam clicking we will use Execution.delayUntil(Callable<Boolean> callable, int min, int max). This method delays the script for at least 'min' ms until either callable returns true or it has delayed for 'max' ms. To use this method without lambdas we would need to import Callable, but since we're going to use lambdas, we don't need to. We only want to delay if we are successful in interacting with the rock and luckily for us .interact(String action) is a boolean that returns true if the action was selected, so we can use it as the condition for an if statement with the delay as it's body. We will delay until our animation becomes something other than -1, or delayUntil times out (lets make it time out after 5 seconds). Our MINE case is now:
Code:
case MINE:
    GameObject rocks = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
    if(rocks != null){
        if(!rocks.isVisible()){
            Camera.turnTo(rocks);
        }
        if(rocks.interact("Mine")){
            Execution.delayUntil(()->Players.getLocal().getAnimationId() != -1, 500, 5000);
        }
    }
    break;
3. Not always mining the correct rock:
The easiest way to make sure we mine the correct rock is to get its name from its definition and add it to our interact method. To do this we need to add another null-check for rocks.getDefinition() alongside rocks != null, get the name using rocks.getDefinition().getName();, and then add the name as the second argument in the interact method. This will make your MINE case look like this:
Code:
case MINE:
    GameObject rocks = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
    if(rocks != null && rocks.getDefinition() != null){
        if(!rocks.isVisible()){
            Camera.turnTo(rocks);
        }
        if(rocks.interact("Mine", rocks.getDefinition().getName())){
            Execution.delayUntil(()->Players.getLocal().getAnimationId() != -1, 500, 5000);
        }
    }
    break;
4. The bot waiting if someone takes the rock:
The easiest way to manage this is to make 'rocks' a member variable instead of a local variable in onLoop(), and check to see if it's null or invalid in our getCurrentState(). If it is null or invalid, our getCurrentState() will return MINE, causing the script to move to the next suitable rock. This takes a few steps. First we make 'rocks' a member variable by declaring "GameObject rocks;" above our onStart(String... args) and removing the "GameObject" keyword from our first line in our MINE case. Then we make getCurrentState() return MINE if our player's animationId is -1, 'rocks' is null, or rocks isn't valid. Our code should now look like this:
Code:
package com.slashnhax.tutorials.statescripttutorial;

import com.runemate.game.api.hybrid.entities.GameObject;
import com.runemate.game.api.hybrid.local.Camera;
import com.runemate.game.api.hybrid.local.hud.interfaces.Inventory;
import com.runemate.game.api.hybrid.local.hud.interfaces.SpriteItem;
import com.runemate.game.api.hybrid.region.GameObjects;
import com.runemate.game.api.hybrid.region.Players;
import com.runemate.game.api.script.Execution;
import com.runemate.game.api.script.framework.LoopingScript;

/**
* PowerMiner for RuneMate tutorial
* Created by SlashnHax
*/
public class PowerMiner extends LoopingScript {

    private enum State{
        MINE, DROP, WAIT;
    }
    GameObject rocks;

    @Override
    public void onStart(String... args){

    }

    @Override
    public void onLoop() {
        switch(getCurrentState()){
            case MINE:
                rocks = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
                if(rocks != null && rocks.getDefinition() != null){
                    if(!rocks.isVisible()){
                        Camera.turnTo(rocks);
                    }
                    if(rocks.interact("Mine")){
                        Execution.delayUntil(()->Players.getLocal().getAnimationId() != -1, 500, 5000);
                    }
                }
                break;
            case DROP:
                for(SpriteItem i:Inventory.getItems()){
                    i.interact("Drop");
                    Execution.delay(500, 1000);
                }
                break;
            case WAIT:
                break;
        }
    }

    @Override
    public void onStop(){

    }

    private State getCurrentState(){
        if(Inventory.isFull()){
            return State.DROP;
        } else if (Players.getLocal().getAnimationId() == -1 || rocks == null || !rocks.isValid()){
            return State.MINE;
        } else {
            return State.WAIT;
        }
    }
}
 
Last edited:
Joined
Dec 10, 2014
Messages
3,255
5. Inventory not being completely emptied:
This is caused by two things: either we're trying to drop too fast, or the client is making occasional miss-clicks. We can fix this by adding some sort of loop to our DROP case and making sure that we delay for an adequate amount of time. A while loop could work, but I'd prefer we use a for loop with a limited amount of attempts (a random amount between 2 and 5 will do), and after some testing it seems that our current delay is adequate for our dropping. Firstly, we'll get the random amount of max attempts and store it as an int, then we will surround our existing dropping code in a for loop which will loop until the amount of max attempts are reached or the inventory is empty. We need to import Random:
Code:
import com.runemate.game.api.hybrid.util.calculations.Random;
Our DROP case with the loop now looks like this:
Code:
case DROP:
    int maxAttempts = Random.nextInt(2, 5);
    for(int attempts = 0; attempts < maxAttempts && !Inventory.isEmpty(); attempts++) {
        for (SpriteItem i : Inventory.getItems()) {
            i.interact("Drop");
            Execution.delay(500, 1000);
        }
    }
    break;
6. The bot drops valuable items, not just the iron ore:
This is a simple fix. In our drop we change Inventory.getItems() to Inventory.getItems("Iron ore") and !Inventory.isEmpty() to Inventory.contains("Iron ore") Our DROP case now looks like this:
Code:
case DROP:
    int maxAttempts = Random.nextInt(2, 5);
    for(int attempts = 0; attempts < maxAttempts && Inventory.contains("Iron ore"); attempts++) {
        for (SpriteItem i : Inventory.getItems("Iron ore")) {
            i.interact("Drop");
            Execution.delay(500, 1000);
        }
    }
    break;
7. Using the ActionBar would be more efficient:
To implement this change we need to import both ActionBar and SlotAction:
Code:
import com.runemate.game.api.rs3.local.hud.interfaces.eoc.ActionBar;
import com.runemate.game.api.rs3.local.hud.interfaces.eoc.SlotAction;
.
Now we get the SlotAction of the Iron ore using ActionBar.getFirstAction("Iron ore"), and replace i.interact("Drop") with the code needed to activate the SlotAction. Our DROP case now looks like this:
Code:
case DROP:
    int maxAttempts = Random.nextInt(2, 5);
    for(int attempts = 0; attempts < maxAttempts && !Inventory.isEmpty(); attempts++) {
        SlotAction ironOre = ActionBar.getFirstAction("Iron ore");
        for (SpriteItem i : Inventory.getItems("Iron ore")) {
            ironOre.activate();
            Execution.delay(500, 1000);
        }
    }
    break;
8. We could be doing something more productive with WAIT:
We should probably do something while we wait to finish mining our current rocks, such as hover over the next closest rock in order to get the edge over people who aren't. We do this by making a new query, selecting the GameObjects with the name "Iron ore rocks" that aren't our current rocks, getting the results of that query and then getting the nearest one. To do this we need to import the Filter class
Code:
import com.runemate.game.api.hybrid.util.Filter;
. To achieve the desired result our WAIT case will look like this:
Code:
case WAIT:
    GameObject nextRock = GameObjects.newQuery().names("Iron ore rocks").filter(new Filter<GameObject>() {
        @Override
        public boolean accepts(GameObject gameObject) {
            return !gameObject.equals(rocks);
        }
    }).results().nearest();
    if (nextRock != null && nextRock.isVisible()) {
        nextRock.hover();
    }
    break;
9. The Loop Delay Caution:
This is a simple fix, we simply set our desired loop delay in our onStart using setLoopDelay(int min, int max). After we do this our onStart will look like:
Code:
@Override
public void onStart(String... args){
    setLoopDelay(150, 600);
}
End product
And here we have it, a functioning iron power-miner using the State approach!
Code:
package com.slashnhax.tutorials.statescripttutorial;

import com.runemate.game.api.hybrid.entities.GameObject;
import com.runemate.game.api.hybrid.local.Camera;
import com.runemate.game.api.hybrid.local.hud.interfaces.Inventory;
import com.runemate.game.api.hybrid.local.hud.interfaces.SpriteItem;
import com.runemate.game.api.hybrid.region.GameObjects;
import com.runemate.game.api.hybrid.region.Players;
import com.runemate.game.api.hybrid.util.Filter;
import com.runemate.game.api.hybrid.util.calculations.Random;
import com.runemate.game.api.rs3.local.hud.interfaces.eoc.ActionBar;
import com.runemate.game.api.rs3.local.hud.interfaces.eoc.SlotAction;
import com.runemate.game.api.script.Execution;
import com.runemate.game.api.script.framework.LoopingScript;

/**
* PowerMiner for RuneMate tutorial
* Created by SlashnHax
*/
public class PowerMiner extends LoopingScript {

    private enum State{
        MINE, DROP, WAIT;
    }
    GameObject rocks;

    @Override
    public void onStart(String... args){
        setLoopDelay(150, 600);
    }

    @Override
    public void onLoop() {
        switch(getCurrentState()){
            case MINE:
                rocks = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
                if(rocks != null && rocks.getDefinition() != null){
                    if(!rocks.isVisible()){
                        Camera.turnTo(rocks);
                    }
                    if(rocks.interact("Mine")){
                        Execution.delayUntil(()->Players.getLocal().getAnimationId() != -1, 500, 5000);
                    }
                }
                break;
            case DROP:
                int maxAttempts = Random.nextInt(2, 5);
                for(int attempts = 0; attempts < maxAttempts && !Inventory.isEmpty(); attempts++) {
                    SlotAction ironOre = ActionBar.getFirstAction("Iron ore");
                    for (SpriteItem i : Inventory.getItems("Iron ore")) {
                        ironOre.activate();
                        Execution.delay(500, 1000);
                    }
                }
                break;
            case WAIT:
                GameObject nextRock = GameObjects.newQuery().names("Iron ore rocks").filter(new Filter<GameObject>() {
                    @Override
                    public boolean accepts(GameObject gameObject) {
                        return !gameObject.equals(rocks);
                    }
                }).results().nearest();
                if (nextRock != null && nextRock.isVisible()) {
                    nextRock.hover();
                }
                break;
        }
    }

    @Override
    public void onStop(){

    }

    private State getCurrentState(){
        if(Inventory.isFull()){
            return State.DROP;
        } else if (Players.getLocal().getAnimationId() == -1 || rocks == null || !rocks.isValid()){
            return State.MINE;
        } else {
            return State.WAIT;
        }
    }
}
Parting words
Thanks for reading my first RuneMate tutorial, although I think I may have made it a bit verbose and will try to improve on that. Feedback, criticism and suggestions are welcome as I intend to make more tutorials in the future and I want to make sure they're done right. If you have any specific tutorial requests, feel free to let me know what they are and I'll try my best to fulfill them :)
Footnotes:
*1 : getState() is already taken by AbstractScript and refers to the Scripts running state, so we have to use another name.
*2 : Some places (Such as the Hefin agility course) have more than one 'idle animation'.
 
Last edited:
Joined
Jan 1, 2015
Messages
272
ohh this take back to my days when I first started out over at kbot, haven't we all moved on to task based scripting now? all tho you are correct its good practice for the starter and small based scripts.

goodjob well written 10/10 for effort
 
Joined
Nov 3, 2013
Messages
2,387
@Arbiter Raise the character limit here like you did in the Guides section, please :)

@Quantum I'm pretty sure you have the ability to sticky yourself? It's under "Thread tools"

@SlashnHax I'm not a scripter, so you could be telling me that blue is a number here, but this looks very well organized and detailed. Good job!
 
Joined
Nov 26, 2014
Messages
616
@Arbiter Raise the character limit here like you did in the Guides section, please :)

@Quantum I'm pretty sure you have the ability to sticky yourself? It's under "Thread tools"

@SlashnHax I'm not a scripter, so you could be telling me that blue is a number here, but this looks very well organized and detailed. Good job!
Hey could you delete my post and Slash's post because it screwed up the format of the guide.
 
Joined
Jun 21, 2014
Messages
350
This is a really cool guide, I think that first working on something more simple like this and then moving on to more complex systems is definitely the way to go. This will help me create some more scripts for sure.
 
Joined
Jul 26, 2013
Messages
154
@Arbiter Raise the character limit here like you did in the Guides section, please :)

@Quantum I'm pretty sure you have the ability to sticky yourself? It's under "Thread tools"

@SlashnHax I'm not a scripter, so you could be telling me that blue is a number here, but this looks very well organized and detailed. Good job!

@EvilCabbage Yes I know how the site works. However, I am in no authority to sticky other people's thread.
 
Mod Automation
Joined
Jul 26, 2013
Messages
3,046
@Arbiter Raise the character limit here like you did in the Guides section, please :)

@Quantum I'm pretty sure you have the ability to sticky yourself? It's under "Thread tools"

@SlashnHax I'm not a scripter, so you could be telling me that blue is a number here, but this looks very well organized and detailed. Good job!
Quadrupled the character limit globally.
 
Joined
Dec 10, 2014
Messages
3,255
ohh this take back to my days when I first started out over at kbot, haven't we all moved on to task based scripting now? all tho you are correct its good practice for the starter and small based scripts.

goodjob well written 10/10 for effort
Yeah, you're right, the majority of scripts are Task based now, although I feel that State-based scripts are more simple and easy to follow, making them good to begin with. I do plan on making a task-based script tutorial at some time though.

Sick tutorial, should be stickied @Arbiter
Thanks :)

@Arbiter Raise the character limit here like you did in the Guides section, please :)

@Quantum I'm pretty sure you have the ability to sticky yourself? It's under "Thread tools"

@SlashnHax I'm not a scripter, so you could be telling me that blue is a number here, but this looks very well organized and detailed. Good job!
Thanks, and blue is a number! #0000ff :p

This is a really cool guide, I think that first working on something more simple like this and then moving on to more complex systems is definitely the way to go. This will help me create some more scripts for sure.
Thanks :)
 
Joined
Jul 24, 2014
Messages
633
Tyvm for this excellent tutorial, I'm trying to transform this stuff into a Seren Stone miner and a Harp player ^^

Edit: why is this thread not pinned yet!?
 
Joined
Dec 27, 2014
Messages
287
By far one of the most helpful tutorials to help people with very little java experience such as me. Someone mentioned a task based script. I have no idea what that is, so if you could make a similar tutorial for a task based script, that would be amazing. It all probably take a bit of time, but it would help new scripters. I appreciate the time and effort you put in to this guide. :) @SlashnHax
 
Joined
Dec 10, 2014
Messages
3,255
By far one of the most helpful tutorials to help people with very little java experience such as me. Someone mentioned a task based script. I have no idea what that is, so if you could make a similar tutorial for a task based script, that would be amazing. It all probably take a bit of time, but it would help new scripters. I appreciate the time and effort you put in to this guide. :) @SlashnHax

A tutorial for a task based script would probably be shorter than this one, as the idea and implementation are pretty straight forward :D
I've been meaning to make one for a long time, but I haven't been able to find the time lately :/
 
Joined
Feb 17, 2015
Messages
112
Going to be having a go at making a shilo village script soon, so this helps a bunch!
 
How does the <main-class> tag and package work? How do I properly set it so runemate recognizes the script? Sorry new java programmer here..
 
Engineer
Joined
Jul 28, 2013
Messages
2,776
Going to be having a go at making a shilo village script soon, so this helps a bunch!
 
How does the <main-class> tag and package work? How do I properly set it so runemate recognizes the script? Sorry new java programmer here..
That's not something that has to do with java, it's part of RuneMate. The main class tag is the package location of the "initialization" file, so essentially the main file which contains your onLoop method.
 
Status
Not open for further replies.
Top