Tutorial An Introduction to the State-based Script

Discussion in 'Tutorials & Resources' started by SlashnHax, Jan 9, 2015.

  1. An Introduction to the State-based script bot!
    By: SlashnHax
    Contents:
    • Introduction
    • Overview
    • Pros and Cons
      • Pros
      • Cons
    • Creating the script bot
      • 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 bot. Throughout this tutorial we will cover the basics of the State-based script bot and create a functional script bot (an iron powerminer) using this approach.
    Overview
    A basic tutorial covering the State script bot. 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 bots where there isn't much code.
    • State-based scripts bots are pretty straightforward, making them perfect for your first script bot.
    Cons
    • You can't reuse code as easily as you can with a Task-based script bot.
    • Large projects become messy and hard to read.
    Creating the script bot
    The Skeleton
    [​IMG]
    Code (Text):
    1. import com.runemate.game.api.script.framework.LoopingScript;
    2.  
    3. /**
    4. * Skeleton for a State-based Script
    5. * Created by SlashnHax
    6. */
    7. public class StateSkeleton extends LoopingScript {
    8.  
    9.     private enum State{
    10.     }
    11.  
    12.     @Override
    13.     public void onStart(String... args){
    14.     }
    15.  
    16.     @Override
    17.     public void onLoop() {
    18.     }
    19.  
    20.     @Override
    21.     public void onStop(){
    22.     }
    23.  
    24.     private State getCurrentState(){
    25.         return null;
    26.     }
    27. }
    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 bot we will be using. The onLoop() statement is called over and over until the script bot 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 (Text):
    1. <manifest>
    2.     <main-class>com.slashnhax.tutorials.statescripttutorial.PowerMiner</main-class>
    3.     <name>Tutorial PowerMiner</name>
    4.     <description>Powermines iron.</description>
    5.     <version>1.0</version>
    6.     <compatibility>
    7.         <game-type>RS3</game-type>
    8.     </compatibility>
    9.     <categories>
    10.         <category>MINING</category>
    11.     </categories>
    12.     <!--Required to publish on the bot store-->
    13.     <internal-id>TutorialPowerminer</internal-id>
    14.     <!--The rest are optional-->
    15.     <hidden>false</hidden>
    16.     <open-source>true</open-source>
    17. </manifest>
    18.  
    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 bot 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 (Text):
    1. private enum State{
    2.     MINE, WAIT, DROP;
    3. }
    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 (Text):
    1. private State getCurrentState(){
    2.     if(full inventory){
    3.         return DROP;
    4.     } else if (player is idle){
    5.         return MINE;
    6.     } else {
    7.         return WAIT;
    8.     }
    9. }
    Which makes our actual implementation:
    Imports (these go up the top with the other import):
    Code (Text):
    1. import com.runemate.game.api.hybrid.local.hud.interfaces.Inventory;
    2. import com.runemate.game.api.hybrid.region.Players;
    getCurrentState():
    Code (Text):
    1. private State getCurrentState(){
    2.     if(Inventory.isFull()){
    3.         return State.DROP;
    4.     } else if (Players.getLocal().getAnimationId() == -1){
    5.         return State.MINE;
    6.     } else {
    7.         return State.WAIT;
    8.     }
    9. }
    Your current code should resemble this:
    Code (Text):
    1. import com.runemate.game.api.hybrid.local.hud.interfaces.Inventory;
    2. import com.runemate.game.api.hybrid.region.Players;
    3. import com.runemate.game.api.script.framework.LoopingScript;
    4.  
    5. /**
    6. * Skeleton for a State-based Script
    7. * Created by SlashnHax
    8. */
    9. public class PowerMiner extends LoopingScript {
    10.  
    11.     private enum State{
    12.         MINE, DROP, WAIT;
    13.     }
    14.  
    15.     @Override
    16.     public void onLoop() {
    17.  
    18.     }
    19.  
    20.     private State getCurrentState(){
    21.         if(Inventory.isFull()){
    22.             return State.DROP;
    23.         } else if (Players.getLocal().getAnimationId() == -1){
    24.             return State.MINE;
    25.         } else {
    26.             return State.WAIT;
    27.         }
    28.     }
    29. }
    30.  
    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 bot 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 (Text):
    1. import com.runemate.game.api.hybrid.entities.GameObject;
    2. 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 (Text):
    1. 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 (Text):
    1.  
    2. if(rocks != null){
    3.     rocks.interact("Mine");
    4. }
    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 (Text):
    1. import com.runemate.game.api.hybrid.local.hud.interfaces.SpriteItem;
    2. 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 (Text):
    1. for(SpriteItem i:Inventory.getItems()){
    2.     i.interact("Drop");
    3.     Execution.delay(500, 1000);
    4. }
    5.  
    case WAIT:
    Here we do stuff we want to do while waiting, or nothing. For now we'll do nothing.
    Our (barely) functional script bot so far:
    Code (Text):
    1. package com.slashnhax.tutorials.statescripttutorial;
    2.  
    3.  
    4.  
    5. import com.runemate.game.api.hybrid.entities.GameObject;
    6. import com.runemate.game.api.hybrid.local.hud.interfaces.Inventory;
    7. import com.runemate.game.api.hybrid.local.hud.interfaces.SpriteItem;
    8. import com.runemate.game.api.hybrid.region.GameObjects;
    9. import com.runemate.game.api.hybrid.region.Players;
    10. import com.runemate.game.api.script.Execution;
    11. import com.runemate.game.api.script.framework.LoopingScript;
    12.  
    13. /**
    14. * PowerMiner for RuneMate tutorial
    15. * Created by SlashnHax
    16. */
    17. public class PowerMiner extends LoopingScript {
    18.  
    19.     private enum State{
    20.         MINE, DROP, WAIT;
    21.     }
    22.  
    23.     @Override
    24.     public void onStart(String... args){
    25.  
    26.     }
    27.  
    28.     @Override
    29.     public void onLoop() {
    30.         switch(getCurrentState()){
    31.             case MINE:
    32.                 GameObject rock = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
    33.                 if(rock != null) {
    34.                     rock.interact("Mine");
    35.                 }
    36.                 break;
    37.             case DROP:
    38.                 for(SpriteItem i:Inventory.getItems()){
    39.                     i.interact("Drop");
    40.                     Execution.delay(500, 1000);
    41.                 }
    42.                 break;
    43.             case WAIT:
    44.                 break;
    45.         }
    46.     }
    47.  
    48.     @Override
    49.     public void onStop(){
    50.  
    51.     }
    52.  
    53.     private State getCurrentState(){
    54.         if(Inventory.isFull()){
    55.             return State.DROP;
    56.         } else if (Players.getLocal().getAnimationId() == -1){
    57.             return State.MINE;
    58.         } else {
    59.             return State.WAIT;
    60.         }
    61.     }
    62. }
    63.  
    64.  
    Refining the methods


    If you were to run the above script bot, 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 bot.
    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 (Text):
    1. 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 (Text):
    1. case MINE:
    2.     GameObject rocks = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
    3.     if(rocks != null){
    4.         if(!rocks.isVisible()){
    5.             Camera.turnTo(rocks);
    6.         }
    7.         rocks.interact("Mine");
    8.     }
    9.     break;
    10.  
    2. Spam clicking:

    To prevent spam clicking we will use Execution.delayUntil(Callable<Boolean> callable, int min, int max). This method delays the script bot 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 (Text):
    1. case MINE:
    2.     GameObject rocks = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
    3.     if(rocks != null){
    4.         if(!rocks.isVisible()){
    5.             Camera.turnTo(rocks);
    6.         }
    7.         if(rocks.interact("Mine")){
    8.             Execution.delayUntil(()->Players.getLocal().getAnimationId() != -1, 500, 5000);
    9.         }
    10.     }
    11.     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 (Text):
    1. case MINE:
    2.     GameObject rocks = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
    3.     if(rocks != null && rocks.getDefinition() != null){
    4.         if(!rocks.isVisible()){
    5.             Camera.turnTo(rocks);
    6.         }
    7.         if(rocks.interact("Mine", rocks.getDefinition().getName())){
    8.             Execution.delayUntil(()->Players.getLocal().getAnimationId() != -1, 500, 5000);
    9.         }
    10.     }
    11.     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 bot 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 (Text):
    1.  
    2. package com.slashnhax.tutorials.statescripttutorial;
    3.  
    4. import com.runemate.game.api.hybrid.entities.GameObject;
    5. import com.runemate.game.api.hybrid.local.Camera;
    6. import com.runemate.game.api.hybrid.local.hud.interfaces.Inventory;
    7. import com.runemate.game.api.hybrid.local.hud.interfaces.SpriteItem;
    8. import com.runemate.game.api.hybrid.region.GameObjects;
    9. import com.runemate.game.api.hybrid.region.Players;
    10. import com.runemate.game.api.script.Execution;
    11. import com.runemate.game.api.script.framework.LoopingScript;
    12.  
    13. /**
    14. * PowerMiner for RuneMate tutorial
    15. * Created by SlashnHax
    16. */
    17. public class PowerMiner extends LoopingScript {
    18.  
    19.     private enum State{
    20.         MINE, DROP, WAIT;
    21.     }
    22.     GameObject rocks;
    23.  
    24.     @Override
    25.     public void onStart(String... args){
    26.  
    27.     }
    28.  
    29.     @Override
    30.     public void onLoop() {
    31.         switch(getCurrentState()){
    32.             case MINE:
    33.                 rocks = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
    34.                 if(rocks != null && rocks.getDefinition() != null){
    35.                     if(!rocks.isVisible()){
    36.                         Camera.turnTo(rocks);
    37.                     }
    38.                     if(rocks.interact("Mine")){
    39.                         Execution.delayUntil(()->Players.getLocal().getAnimationId() != -1, 500, 5000);
    40.                     }
    41.                 }
    42.                 break;
    43.             case DROP:
    44.                 for(SpriteItem i:Inventory.getItems()){
    45.                     i.interact("Drop");
    46.                     Execution.delay(500, 1000);
    47.                 }
    48.                 break;
    49.             case WAIT:
    50.                 break;
    51.         }
    52.     }
    53.  
    54.     @Override
    55.     public void onStop(){
    56.  
    57.     }
    58.  
    59.     private State getCurrentState(){
    60.         if(Inventory.isFull()){
    61.             return State.DROP;
    62.         } else if (Players.getLocal().getAnimationId() == -1 || rocks == null || !rocks.isValid()){
    63.             return State.MINE;
    64.         } else {
    65.             return State.WAIT;
    66.         }
    67.     }
    68. }
    69.  
     
    #1 SlashnHax, Jan 9, 2015
    Last edited: Jan 11, 2015
    creativeself, Furor and skrall like this.
  2. 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 bot 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 (Text):
    1. import com.runemate.game.api.hybrid.entities.GameObject;
    2. 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 (Text):
    1. 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 (Text):
    1.  
    2. if(rocks != null){
    3.     rocks.interact("Mine");
    4. }
    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 (Text):
    1. import com.runemate.game.api.hybrid.local.hud.interfaces.SpriteItem;
    2. 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 (Text):
    1. for(SpriteItem i:Inventory.getItems()){
    2.     i.interact("Drop");
    3.     Execution.delay(500, 1000);
    4. }
    5.  
    case WAIT:
    Here we do stuff we want to do while waiting, or nothing. For now we'll do nothing.
    Our (barely) functional script bot so far:
    Code (Text):
    1. package com.slashnhax.tutorials.statescripttutorial;
    2.  
    3.  
    4. import com.runemate.game.api.hybrid.entities.GameObject;
    5. import com.runemate.game.api.hybrid.local.hud.interfaces.Inventory;
    6. import com.runemate.game.api.hybrid.local.hud.interfaces.SpriteItem;
    7. import com.runemate.game.api.hybrid.region.GameObjects;
    8. import com.runemate.game.api.hybrid.region.Players;
    9. import com.runemate.game.api.script.Execution;
    10. import com.runemate.game.api.script.framework.LoopingScript;
    11.  
    12. /**
    13. * PowerMiner for RuneMate tutorial
    14. * Created by SlashnHax
    15. */
    16. public class PowerMiner extends LoopingScript {
    17.  
    18.     private enum State{
    19.         MINE, DROP, WAIT;
    20.     }
    21.  
    22.     @Override
    23.     public void onStart(String... args){
    24.  
    25.     }
    26.  
    27.     @Override
    28.     public void onLoop() {
    29.         switch(getCurrentState()){
    30.             case MINE:
    31.                 GameObject rock = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
    32.                 if(rock != null) {
    33.                     rock.interact("Mine");
    34.                 }
    35.                 break;
    36.             case DROP:
    37.                 for(SpriteItem i:Inventory.getItems()){
    38.                     i.interact("Drop");
    39.                     Execution.delay(500, 1000);
    40.                 }
    41.                 break;
    42.             case WAIT:
    43.                 break;
    44.         }
    45.     }
    46.  
    47.     @Override
    48.     public void onStop(){
    49.  
    50.     }
    51.  
    52.     private State getCurrentState(){
    53.         if(Inventory.isFull()){
    54.             return State.DROP;
    55.         } else if (Players.getLocal().getAnimationId() == -1){
    56.             return State.MINE;
    57.         } else {
    58.             return State.WAIT;
    59.         }
    60.     }
    61. }
    62.  
    63.  
    Refining the methods

    If you were to run the above script bot, 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 bot.
    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 (Text):
    1. 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 (Text):
    1. case MINE:
    2.     GameObject rocks = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
    3.     if(rocks != null){
    4.         if(!rocks.isVisible()){
    5.             Camera.turnTo(rocks);
    6.         }
    7.         rocks.interact("Mine");
    8.     }
    9.     break;
    10.  
    2. Spam clicking:
    To prevent spam clicking we will use Execution.delayUntil(Callable<Boolean> callable, int min, int max). This method delays the script bot 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 (Text):
    1. case MINE:
    2.     GameObject rocks = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
    3.     if(rocks != null){
    4.         if(!rocks.isVisible()){
    5.             Camera.turnTo(rocks);
    6.         }
    7.         if(rocks.interact("Mine")){
    8.             Execution.delayUntil(()->Players.getLocal().getAnimationId() != -1, 500, 5000);
    9.         }
    10.     }
    11.     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 (Text):
    1. case MINE:
    2.     GameObject rocks = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
    3.     if(rocks != null && rocks.getDefinition() != null){
    4.         if(!rocks.isVisible()){
    5.             Camera.turnTo(rocks);
    6.         }
    7.         if(rocks.interact("Mine", rocks.getDefinition().getName())){
    8.             Execution.delayUntil(()->Players.getLocal().getAnimationId() != -1, 500, 5000);
    9.         }
    10.     }
    11.     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 bot 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 (Text):
    1.  
    2. package com.slashnhax.tutorials.statescripttutorial;
    3.  
    4. import com.runemate.game.api.hybrid.entities.GameObject;
    5. import com.runemate.game.api.hybrid.local.Camera;
    6. import com.runemate.game.api.hybrid.local.hud.interfaces.Inventory;
    7. import com.runemate.game.api.hybrid.local.hud.interfaces.SpriteItem;
    8. import com.runemate.game.api.hybrid.region.GameObjects;
    9. import com.runemate.game.api.hybrid.region.Players;
    10. import com.runemate.game.api.script.Execution;
    11. import com.runemate.game.api.script.framework.LoopingScript;
    12.  
    13. /**
    14. * PowerMiner for RuneMate tutorial
    15. * Created by SlashnHax
    16. */
    17. public class PowerMiner extends LoopingScript {
    18.  
    19.     private enum State{
    20.         MINE, DROP, WAIT;
    21.     }
    22.     GameObject rocks;
    23.  
    24.     @Override
    25.     public void onStart(String... args){
    26.  
    27.     }
    28.  
    29.     @Override
    30.     public void onLoop() {
    31.         switch(getCurrentState()){
    32.             case MINE:
    33.                 rocks = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
    34.                 if(rocks != null && rocks.getDefinition() != null){
    35.                     if(!rocks.isVisible()){
    36.                         Camera.turnTo(rocks);
    37.                     }
    38.                     if(rocks.interact("Mine")){
    39.                         Execution.delayUntil(()->Players.getLocal().getAnimationId() != -1, 500, 5000);
    40.                     }
    41.                 }
    42.                 break;
    43.             case DROP:
    44.                 for(SpriteItem i:Inventory.getItems()){
    45.                     i.interact("Drop");
    46.                     Execution.delay(500, 1000);
    47.                 }
    48.                 break;
    49.             case WAIT:
    50.                 break;
    51.         }
    52.     }
    53.  
    54.     @Override
    55.     public void onStop(){
    56.  
    57.     }
    58.  
    59.     private State getCurrentState(){
    60.         if(Inventory.isFull()){
    61.             return State.DROP;
    62.         } else if (Players.getLocal().getAnimationId() == -1 || rocks == null || !rocks.isValid()){
    63.             return State.MINE;
    64.         } else {
    65.             return State.WAIT;
    66.         }
    67.     }
    68. }
    69.  
     
    #2 SlashnHax, Jan 10, 2015
    Last edited: Jan 11, 2015
    creativeself, Furor and Alexxzander like this.
  3. 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 (Text):
    1. import com.runemate.game.api.hybrid.util.calculations.Random;
    2.  
    Our DROP case with the loop now looks like this:
    Code (Text):
    1. case DROP:
    2.     int maxAttempts = Random.nextInt(2, 5);
    3.     for(int attempts = 0; attempts < maxAttempts && !Inventory.isEmpty(); attempts++) {
    4.         for (SpriteItem i : Inventory.getItems()) {
    5.             i.interact("Drop");
    6.             Execution.delay(500, 1000);
    7.         }
    8.     }
    9.     break;
    10.  
    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 (Text):
    1. case DROP:
    2.     int maxAttempts = Random.nextInt(2, 5);
    3.     for(int attempts = 0; attempts < maxAttempts && Inventory.contains("Iron ore"); attempts++) {
    4.         for (SpriteItem i : Inventory.getItems("Iron ore")) {
    5.             i.interact("Drop");
    6.             Execution.delay(500, 1000);
    7.         }
    8.     }
    9.     break;
    10.  
    7. Using the ActionBar would be more efficient:
    To implement this change we need to import both ActionBar and SlotAction:
    Code (Text):
    1. import com.runemate.game.api.rs3.local.hud.interfaces.eoc.ActionBar;
    2. 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 (Text):
    1. case DROP:
    2.     int maxAttempts = Random.nextInt(2, 5);
    3.     for(int attempts = 0; attempts < maxAttempts && !Inventory.isEmpty(); attempts++) {
    4.         SlotAction ironOre = ActionBar.getFirstAction("Iron ore");
    5.         for (SpriteItem i : Inventory.getItems("Iron ore")) {
    6.             ironOre.activate();
    7.             Execution.delay(500, 1000);
    8.         }
    9.     }
    10.     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 (Text):
    1. import com.runemate.game.api.hybrid.util.Filter;
    . To achieve the desired result our WAIT case will look like this:
    Code (Text):
    1. case WAIT:
    2.     GameObject nextRock = GameObjects.newQuery().names("Iron ore rocks").filter(new Filter<GameObject>() {
    3.         @Override
    4.         public boolean accepts(GameObject gameObject) {
    5.             return !gameObject.equals(rocks);
    6.         }
    7.     }).results().nearest();
    8.     if (nextRock != null && nextRock.isVisible()) {
    9.         nextRock.hover();
    10.     }
    11.     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 (Text):
    1. @Override
    2. public void onStart(String... args){
    3.     setLoopDelay(150, 600);
    4. }
    End product
    And here we have it, a functioning iron power-miner using the State approach!
    Code (Text):
    1. package com.slashnhax.tutorials.statescripttutorial;
    2.  
    3. import com.runemate.game.api.hybrid.entities.GameObject;
    4. import com.runemate.game.api.hybrid.local.Camera;
    5. import com.runemate.game.api.hybrid.local.hud.interfaces.Inventory;
    6. import com.runemate.game.api.hybrid.local.hud.interfaces.SpriteItem;
    7. import com.runemate.game.api.hybrid.region.GameObjects;
    8. import com.runemate.game.api.hybrid.region.Players;
    9. import com.runemate.game.api.hybrid.util.Filter;
    10. import com.runemate.game.api.hybrid.util.calculations.Random;
    11. import com.runemate.game.api.rs3.local.hud.interfaces.eoc.ActionBar;
    12. import com.runemate.game.api.rs3.local.hud.interfaces.eoc.SlotAction;
    13. import com.runemate.game.api.script.Execution;
    14. import com.runemate.game.api.script.framework.LoopingScript;
    15.  
    16. /**
    17. * PowerMiner for RuneMate tutorial
    18. * Created by SlashnHax
    19. */
    20. public class PowerMiner extends LoopingScript {
    21.  
    22.     private enum State{
    23.         MINE, DROP, WAIT;
    24.     }
    25.     GameObject rocks;
    26.  
    27.     @Override
    28.     public void onStart(String... args){
    29.         setLoopDelay(150, 600);
    30.     }
    31.  
    32.     @Override
    33.     public void onLoop() {
    34.         switch(getCurrentState()){
    35.             case MINE:
    36.                 rocks = GameObjects.newQuery().names("Iron ore rocks").results().nearest();
    37.                 if(rocks != null && rocks.getDefinition() != null){
    38.                     if(!rocks.isVisible()){
    39.                         Camera.turnTo(rocks);
    40.                     }
    41.                     if(rocks.interact("Mine")){
    42.                         Execution.delayUntil(()->Players.getLocal().getAnimationId() != -1, 500, 5000);
    43.                     }
    44.                 }
    45.                 break;
    46.             case DROP:
    47.                 int maxAttempts = Random.nextInt(2, 5);
    48.                 for(int attempts = 0; attempts < maxAttempts && !Inventory.isEmpty(); attempts++) {
    49.                     SlotAction ironOre = ActionBar.getFirstAction("Iron ore");
    50.                     for (SpriteItem i : Inventory.getItems("Iron ore")) {
    51.                         ironOre.activate();
    52.                         Execution.delay(500, 1000);
    53.                     }
    54.                 }
    55.                 break;
    56.             case WAIT:
    57.                 GameObject nextRock = GameObjects.newQuery().names("Iron ore rocks").filter(new Filter<GameObject>() {
    58.                     @Override
    59.                     public boolean accepts(GameObject gameObject) {
    60.                         return !gameObject.equals(rocks);
    61.                     }
    62.                 }).results().nearest();
    63.                 if (nextRock != null && nextRock.isVisible()) {
    64.                     nextRock.hover();
    65.                 }
    66.                 break;
    67.         }
    68.     }
    69.  
    70.     @Override
    71.     public void onStop(){
    72.  
    73.     }
    74.  
    75.     private State getCurrentState(){
    76.         if(Inventory.isFull()){
    77.             return State.DROP;
    78.         } else if (Players.getLocal().getAnimationId() == -1 || rocks == null || !rocks.isValid()){
    79.             return State.MINE;
    80.         } else {
    81.             return State.WAIT;
    82.         }
    83.     }
    84. }
    85.  
    86.  
    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 bots running state, so we have to use another name.
    *2 : Some places (Such as the Hefin agility course) have more than one 'idle animation'.
     
    #3 SlashnHax, Jan 11, 2015
    Last edited: Jan 11, 2015
    Alexxzander likes this.
  4. 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 bots.

    goodjob well written 10/10 for effort
     
    SlashnHax likes this.
  5. Sick tutorial, should be stickied @Arbiter
     
    SlashnHax likes this.
  6. @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!
     
    SlashnHax likes this.
  7. Hey could you delete my post and Slash's post because it screwed up the format of the guide.
     
    SlashnHax likes this.
  8. 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 bots for sure.
     
    SlashnHax likes this.
  9. @EvilCabbage Yes I know how the site works. However, I am in no authority to sticky other people's thread.
     

  10. im sure a tutorial like this wont be frowned on if you sticky it
     
  11. When you set up TFA you'll have it. :p
     
  12. Quadrupled the character limit globally.
     
    SlashnHax likes this.
  13. Done.
     
    SlashnHax likes this.
  14. Yeah, you're right, the majority of scripts bots are Task based now, although I feel that State-based scripts bots are more simple and easy to follow, making them good to begin with. I do plan on making a task-based script bot tutorial at some time though.

    Thanks :)

    Thanks, and blue is a number! #0000ff :p

    Thanks :)
     
  15. This is a fantastic tutorial!
     
  16. 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!?
     
  17. 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 bot. I have no idea what that is, so if you could make a similar tutorial for a task based script bot, 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
     
  18. A tutorial for a task based script bot 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 :/
     
  19. Going to be having a go at making a shilo village script bot soon, so this helps a bunch!
    --- Double Post Merged, Feb 25, 2015, Original Post Date: Feb 22, 2015 ---
    How does the <main-class> tag and package work? How do I properly set it so runemate recognizes the script bot? Sorry new java programmer here..
     
  20. 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.
     

Share This Page

Loading...