Jump to content

Explv's Scripting 101


Explv

Recommended Posts

9 hours ago, boogeymanOSRS said:

Hey, after trying to follow the tutorial and giving the script a silly name I get this when I try to load the scripts "Failed to load local script : Woodcutter.class". It is on the right directory so not sure what could be wrong.

Possible your project language level isn't set to 8

  • Like 1
Link to comment
Share on other sites

3 hours ago, jackknife32 said:

b6e1my5.png
g7vSf8n.pngHLfVevE.png

You're including the OSBot.jar in your script, instead of the compiled output.

In the bottom right of your screen shot, where it says "Available Elements", you should be including "FirstBot2 compile output" (it should show up on the left side under the "Woodcutting_Script.jar". The "OSBot 2.6.20" should be on the right hand side (excluded from the compiled script)

Link to comment
Share on other sites

 

12 minutes ago, Explv said:

You're including the OSBot.jar in your script, instead of the compiled output.

In the bottom right of your screen shot, where it says "Available Elements", you should be including "FirstBot2 compile output" (it should show up on the left side under the "Woodcutting_Script.jar". The "OSBot 2.6.20" should be on the right hand side (excluded from the compiled script)

Id kiss you on the forehead if I could.... 

It loads now!

Link to comment
Share on other sites

  • 5 weeks later...
  • 11 months later...
On 1/31/2017 at 7:08 PM, Explv said:

Explv's Scripting 101

Prerequisite: basic knowledge of Java

 

1. Setting up the Java Development Kit and an Integrated Development Environment (IDE)

  Reveal hidden contents

You can use any IDE that supports Java, but in this tutorial I will be showing you how to set up IntelliJ IDEA by JetBrains

1. Download and install the latest Java 8 JDK from the Oracle website http://www.oracle.com/technetwork/java/javase/downloads/jdk8-downloads-2133151.html

2. Download and install IntelliJ IDEA from the JetBrains website https://www.jetbrains.com/idea/

3. Open IntelliJ IDEA and select "Create New Project"

http://i.imgur.com/b8IBakI.png

4. Create a new Java project giving it an appropriate name

http://i.imgur.com/CkaNMOL.png

http://i.imgur.com/a6HpksO.png

5. Open the module settings by pressing F4, or right clicking the module and selecting "Open Module Settings"

6. Navigate to the "Libraries" section on the left side of the pane, add a new Java library, and in the file chooser select the OSBot .jar file

http://i.imgur.com/5UscxfQ.png

7. Navigate to the "Artifacts" section on the left side of the pane, and add a new empty JAR.

8. Give the JAR an appropriate name, such as the name of your script.

9. Set the output directory of the JAR to C:\Users\Username\OSBot\Scripts in Windows, or ~/OSBot/Scripts in Mac/Linux

10. Add the compiled output of your module to the JAR by double clicking it on the right hand side of the window

http://i.imgur.com/D00XpaN.png

11. Select apply and close the window.

 

2. The Script Skeleton

  Reveal hidden contents

1. Add a new class to your project (by right clicking on the src/ directory and select New -> Java Class)

2. Give the class an appropriate name, this class will be the entry point to your script, so a name such as Woodcutter for a woodcutting script would make sense.

3. Add the following code:


import org.osbot.rs07.script.Script;
import org.osbot.rs07.script.ScriptManifest;

@ScriptManifest(author = "Explv", name = "An Example Script", info = "Just an empty script :(", version = 0.1, logo = "")
public final class ExampleScript extends Script  {
    @Override
    public final int onLoop() throws InterruptedException {
        return 0;
    }
}

 

This is the ScriptManifest annotation:


@ScriptManifest(author = "Explv", name = "An Example Script", info = "Just an empty script :(", version = 0.1, logo = "")

It is required that your main class has this annotation, as it provides information used in the OSBot script selector. Set author to your OSBot username, name to the name of your script, info to a short description etc.

For the logo parameter, you should give a URL to a 180px x 180px image hosted on imgur

 

The main class of your script must extend the Script class:


public final class ExampleScript extends Script

The Script class defines several useful methods, they can all be found on the API documentation here http://osbot.org/api/org/osbot/rs07/script/Script.html

It is abstract, it cannot be instantiated directly, you must subclass it, and override any abstract methods, in this case you must override the onLoop method. 

 

The onLoop method is where all of the core functionality of your script will be called from:


@Override
public final int onLoop() throws InterruptedException {
    return 0;
}

The onLoop method is called repeatedly while your script is running, the delay between each time onLoop is called is defined by the integer value it returns. In the case of this example, the script executor would sleep for 0ms in between each onLoop call.

 

3. Building the script

  Reveal hidden contents

In Intellij IDEA, to build your script simply go to Build (In the top menu) -> Build Artifacts -> Build

Then in the OSBot client, refresh the script selector, and your script will be loaded / reloaded

 

4. The Script class continued

  Reveal hidden contents

You have already seen the onLoop method in the Script class, but there are also other methods that you can optionally override:

The onStart method will be called when the script starts:


@Override
public final void onStart() {
    log("This will be printed to the logger when the script starts");
}

 

The onExit method will be called when the script exits


@Override
public final void onExit() {
    log("This will be printed to the logger when the script exits");
}

 

The onMessage method is called when a message arrives in the RuneScape chatbox:


@Override
public final void onMessage(final Message message) {
    log("A message arrived in the chatbox: " + message.getMessage());
}

 

The onPaint method provides you with a Graphics2D instance to draw on the screen:


@Override
public void onPaint(final Graphics2D g) {
    g.drawString("Some text", 10, 10);
}

 

The full list of methods in the Script class can be found here: http://osbot.org/api/org/osbot/rs07/script/Script.html

 

5. The MethodProvider class, accessing the Inventory, Bank, Player, etc. instances

  Reveal hidden contents

The Script class as you have already seen, extends a class called MethodProvider.

The MethodProvider class contains many useful variables, and getter methods for those variables, this is how you access the core parts of the OSBot API.

Because the Script class extends MethodProvider, and your main class extends the Script class, you can also access these variables and getter methods from within your main script class:


import org.osbot.rs07.script.Script;
import org.osbot.rs07.script.ScriptManifest;

@ScriptManifest(author = "Explv", name = "An Example Script", info = "", version = 0.1, logo = "")
public final class ExampleScript extends Script  {
    
    @Override
    public final int onLoop() throws InterruptedException {
        getInventory(); // returns the Inventory instance
        myPlayer(); // returns the Player instance
        getWalking(); // returns the Walking instance
        return 0;
    }
}

 

This also means that if you want to access these methods from other classes, you must pass your instance of MethodProvider to instances of those other classes:

import org.osbot.rs07.script.MethodProvider;
import org.osbot.rs07.script.Script;
import org.osbot.rs07.script.ScriptManifest;

@ScriptManifest(author = "Explv", name = "An Example Script", info = "", version = 0.1, logo = "")
public final class ExampleScript extends Script  {
    @Override
    public final int onLoop() throws InterruptedException {
        SomeOtherClass someOtherClass = new SomeOtherClass(this);
        return 0;
    }
}

final class SomeOtherClass {
    private final MethodProvider methods;

    public SomeOtherClass(final MethodProvider methods) {
        this.methods = methods;
    }
  
    public final void someMethod() {
        methods.getInventory(); // I can now access the Inventory instance from this class 
    }
}

 

6. Positions, areas and moving the player

  Reveal hidden contents

The RuneScape world is split into tiles, each tile has an (x, y, z) coordinate associated with it:

    x coordinate increases as you move East across the world, and decreases as you move West.

    y coordinate increases as you move North, decreases as you move South

    z coordinate increases as you move "up" e.g. climbing up a staircase, and decreases as you go "down" e.g. climbing down a staircase.

 

To get the (x, y, z) coordinate of a tile, or create a path or area you have a few options:

  1. Use the Entity hover debugger by hovering over a tile, this is enabled in the settings of the OSBot client.
  2. Using an OSBot script such as https://osbot.org/forum/topic/84109-explvs-location-assistant-free-paths-areas/ 
  3. Or using a map tool that I created: https://explv.github.io/

 

In the OSBot API (x, y, z) coordinates are represented with the Position class.

You can create a new Position like so:

Position position = new Position(x, y, z);

You can retrieve the Position of the Player by calling the myPosition() method in the MethodProvider class:

Position position = myPosition();

All Entities such as NPCs, Players, RS2Objects, GroundItems have a method to get their position called getPosition():

Entity bones = getGroundItems().closest("Bones");
if (bones != null) {
    Position bonesPosition = bones.getPosition();
}

 

An Area is a polygon consisting of one or more Positions.

You can create an Area in a few different ways:

Area area = new Area(int[][] positions); // Constructs a polygonal area using each set in a 2d array as a pair of x and y position coordinates.
Area area = new Area(int x1, int y1, int x2, int y2); // Constructs a rectangular area using x and y coordinates from two separate positions.
Area area = new Area(Position[] positions); // Constructs a polygonal area using each position as a point in the polygon.
Area area = new Area(Position southWest, Position northEast); // Constructs a rectangular area using two positions 

Areas are typically used to determine if an entity is in a certain location in the RuneScape world or for walking to a certain location.

For example, to determine if your player is in the Varrock West bank you would write:

if (Banks.VARROCK_WEST.contains(myPosition())) { // VARROCK_WEST is a static Area variable found in the Banks class
    // The player is in the Varrock West bank
}

 

To move your player to an Area or Position, you can use the Walking class. To access the Walking instance you use the getWaking() method in the MethodProvider class.

There are three main methods of walking in the Walking class, either walking to a nearby position with the same z coordinate as the player's position using the walk method, note that the destination must be close to your Player otherwise it will not be able to find a path:

getWalking().walk(new Position(x, y, z)); // Walks to the Position with coordinates x, y, z

getWalking().walk(new Area(x1, y1, x2, y2)); // Walks to a Position in the Area with coordinates x1, y1, x2, y2

getWalking().walk(entity); // Walks to the entity's position.

 

Walking along a fixed path (all positions must have the same z coordinate as the player's current position):

List<Position> path = new ArrayList<>();
path.add(new Position(x, y, z));
path.add(new Position(x, y, z));
// etc.
  
getWalking().walkPath(path);

 

Or for long distances, where the destination tile is not visible to the player, and creating a fixed path would be inappropriate, you can use web walking:

getWalking().webWalk(Banks.VARROCK_WEST); // Will attempt to walk to the Varrock West bank from anywhere in the RuneScape world

When using web walking, common obstacles such as staircases and gates will be handled for you, which also means you can web walk to positions with different z coordinates to the player's current position.

Due to the nature of web walking, in some less commonly traveled locations in the RuneScape world, the web walker may not be able to find a path to your destination, when this occurs, you can use a different walking method to a location where you can web walk from, and also request that web walking links be added: https://osbot.org/forum/forum/335-web-walking-links/

 

7. Entities (Players, RS2Objects, NPCs and GroundItems)

  Reveal hidden contents

Players, RS2Objects, NPCs and GroundItems are the main entities that you will be using in your scripts. 

To access these, again, there are getter methods in the MethodProvider class:

getPlayers() // returns an instance of Players
getNpcs() // returns an instance of NPCS
getGroundItems() // returns an instance of GroundItems
getObjects() // returns an instance of Objects

The Players class is used for retrieving the Player instances near your character.

The NPCS class is used for retrieving NPCS, for example Cows in Lumbridge

The GroundItems class is used for retrieving GroundItems, for example bones on the ground

The Objects class is used for retrieving RS2Objects, for example Trees

Each of these classes extend the EntityAPI class. The EntityAPI provides methods such as closest() for getting the closest Entity to your player, get(x, y) to get the Entity at the specified x, y coordinates on the same plane as your player, and iterator() to return an Iterator of the collection of entities near your player.

An example of using one of the closest() methods is:

Entity cow = getNpcs().closest("Cow");

This will return the closest Cow to your player, or null if no Cow exists.

 

8. Interactions

  Reveal hidden contents

Now that you know how to retrieve the closest Entity to your player, you need to know how to interact with it.

Any class that implements the Interactable interface, will have an interact(String interaction) method.

The Entity class implements Interactable, therefore the Entity class has an interact(String interaction) method.

Here is a simple interaction example:

Entity tree = getObjects().closest("Tree");
if (tree != null) {
    tree.interact("Chop down");
}

In this example, we retrieve the closest Tree to the player, we check that the returned value is not null (because if no Tree exists, closest() will return null), and then perform the interaction "Chop down" on the tree.

This will result in the player clicking on the Tree, or right-clicking and selecting the "Chop down" option

 

9. Sleeping

  Reveal hidden contents

In the previous section I showed you how to interact with the closest Tree to the player. However, if you tried that you would notice that the bot spam clicks on the Tree. This is where sleeping comes in.

There are three main ways to sleep in a script:

  1. Returning an integer value from the onLoop() method, this will result in the ScriptExecutor sleeping for the specified number of ms
  2. Calling the sleep(long ms) method in the MethodProvider class, note this method is static and so you can write: MethodProvider.sleep(long ms)
  3. Using a ConditionalSleep (sleep until a condition is matched, or until a specified timeout is reached)

Wherever possible I would recommend that you use ConditionalSleeps. You can use a ConditionalSleep like so:

Entity tree = getObjects().closest("Tree");
if (tree != null && tree.interact("Chop down")) {
      new ConditionalSleep(5000) {
        @Override
        public boolean condition() {
            return myPlayer().isAnimating() || !tree.exists();
        }
    }.sleep();
}

What this snippet does is:

  1. Get the closest Tree to the player
  2. Check the tree value is not null (because if it is null, no tree exists and so we don't wan't to do anything)
  3. Perform the "Chop down" interaction on the tree
  4. Check that the interact() method returned true, if the return value is false, the interaction failed and we do not want to sleep
  5. Create a new ConditionalSleep with timeout set to 5000ms (5 seconds)
  6. Override the ConditionalSleep's condition so that we stop sleeping before the timeout if either the player is animating (chopping the tree), or the tree no longer exists (someone else has chopped it down)
  7. Call the ConditionalSleep's sleep() method to start sleeping.

 

As you will be using the CondtionalSleep class frequently, you may want to use an alternative that I wrote, just so that your script is less cluttered:

package util;

import org.osbot.rs07.utility.ConditionalSleep;

import java.util.function.BooleanSupplier;

public final class Sleep extends ConditionalSleep {

    private final BooleanSupplier condition;

    public Sleep(final BooleanSupplier condition, final int timeout) {
        super(timeout);
        this.condition = condition;
    }

    @Override
    public final boolean condition() throws InterruptedException {
        return condition.getAsBoolean();
    }

    public static boolean sleepUntil(final BooleanSupplier condition, final int timeout) {
        return new Sleep(condition, timeout).sleep();
    }
}

 

Using this class, you do not have to override the condition() method, instead you can pass a BooleanSupplier as the condition. Here is the same snippet as before, but using this Sleep class instead:

Entity tree = getObjects().closest("Tree");
if (tree != null && tree.interact("Chop down")) {
    Sleep.sleepUntil(() -> myPlayer().isAnimating() || !tree.exists(), 5000);
}

 

Or:

Entity tree = getObjects().closest("Tree");
if (tree != null && tree.interact("Chop down")) {
    new Sleep(() -> myPlayer().isAnimating() || !tree.exists(), 5000).sleep();
}

 

Some methods in the OSBot API will already have sleeps built into them, and so you do not need to add your own, for example the method getTabs().open(Tab.INVENTORY) will sleep until the inventory tab is open.

 

10. Items and ItemContainers (Inventory, Bank, Equipment, Store, ...)

  Reveal hidden contents

Items such as "Logs" and "Bones" are represented by the Item class

The Item class provides various different useful methods such as getName() to return the name of an item, getAmount() to return the amount of the item (e.g. the amount of the item in your inventory, or the amount of the item in the bank), isNote() and getActions() which returns a String[] of all the possible actions your player could perform on the item.

Like with the Entity class, the Item class implements the Interactable interface, which provides you with an interact(String interaction) method.

So in the same way as you would interact with a Tree Entity to chop it down, you can interact with an Item, for example to eat it.

 

Items are stored inside of ItemContainers, and so to retrieve an instance of Item you must retrieve it from the relevant ItemContainer.

The current available ItemContainers are: Bank, DepositBox, Equipment, Inventory, StoreTrade.OurOfferTrade.TheirOffer

You can retrieve the instances of these ItemContainers using the associated getter methods in the MethodProvider class:

getInventory(); // returns the instance of Inventory
getBank(); // returns the instance of Bank (or null if the player is not near a bank)
getEquipment(); // returns the instance of Equipment
// etc.

Note that, getBank() and getDepositBox() will both return null if your player is not close to a bank or deposit box, and so you must check that the returned instance is not null before calling any methods on it.

 

All of the ItemContainers share the same useful methods:

boolean contains(...) // Check if the ItemContainer contains any items with the specified names, ids, filters etc.
long getAmount(...) // Get the sum of the amount of items matching the specified name, ids, filters etc.
Item getItem(...) // Get the Item with matching name, id etc. or null if it does not exist

 

But each of the classes that extend ItemContainer will have some extra methods added to them. For example, the Bank class also defines the methods:

boolean isOpen(); // Check if the Bank is open
boolean open(); // Open the bank
boolean withdraw(...) // Withdraw an item from the bank

Both the Bank and DepositBox require you to open them before you can use any of the methods.

 

Here is an example of using an ItemContainer, getting a Lobster item from the Inventory and eating it:

if (getInventory().contains("Lobster")) {
    if (getInventory().getSelectedItemName() == null) {
        getInventory().getItem("Lobster").interact("Eat"); 
    } else {
        getInventory().deselectItem(); 
    }
}

 

11. Filtering

  Reveal hidden contents

In the previous sections I showed you how to retrieve the closest Entity to the player using it's name, and also how to retrieve an Item using its name from an ItemContainer.

There will be cases however where using just the name of the Identifiable is not enough, and you want to retrieve an Item or Entity based on more conditions, this is where filtering comes in.

As I mentioned previously, the Players, GroundItems, NPCS and Objects classes all extend the EntityAPI class which provides the closest() method and a few others.

One of the "closest" methods the EntityAPI provides is:

E closest(Filter<E>... filters)

What this method allows you to do is retrieve the closest Entity to the player, that matches the Filters that you provide.

The Filter<V> interface is very simple, it contains only a single method:

boolean match(V obj);

When you create a new Filter, because it is an interface, you must provide an implementation for this method. The implementation should return true if the object matches your criteria, and false if it does not.

For example, if we want to create a Filter<Entity> that only returns true if the Entity's name begins with "Cow", we would write:

Filter<Entity> cowFilter = new Filter<>() {
    @Override
    public boolean match(final Entity entity) {
        return entity.getName().startsWith("Cow");
    }
};

We can then use this Filter<Entity> as a parameter to the EntityAPI's closest(Filter<E> filter) method, to retrieve the closest Entity who's name begins with "Cow":

Entity cow = getNpcs().closest(cowFilter);

In the OSBot API, you will find several predefined Filters that you can make use of: ActionFilter, AreaFilter, ContainsModelFilter, ContainsNameFilter, IdFilter, ItemListFilter, ModelFilter, NameFilter, PositionFilter

The EntityAPI class, which Players, Objects etc. extend, extends the FilterAPI class

The FilterAPI class provides you with more filtering methods:

java.util.List<E> filter(java.util.Collection<E> entities, Filter<E>... filters)
java.util.List<E> filter(Filter<E>... filters)
E singleFilter(java.util.Collection<E> entities, Filter<E>... filters)

The filter() methods will apply your Filters to a collection of objects, returning a List of the ones that matched.

The singleFilter() method will apply your Filters to a collection of objects, and return a single object that matched (there may be more than one that matched, but the method will return just one).

So for example, instead of using closest(cowFilter) to return the closest Cow to the player, you could write:

Entity cow = getNpcs().singleFilter(getNpcs().getAll(), cowFilter);

Which would return a Cow near to your player (or null if no cow exists), but not necessarily the closest Cow.

 

You can also use Filters for some methods in the ItemContainer class, for example, to check if the Inventory contains a pickaxe you might write:

Filter<Item> pickaxeFilter = new Filter<>() {
    @Override
    public boolean match(final Item item) {
        return item.getName().endsWith("pickaxe"); 
    }
}

if (getInventory().contains(pickaxeFilter)) {
    // inventory contains a pickaxe 
}

 

Because the Filter interface is a FunctionalInterface we can use lambda expressions to declare the Filter, which looks cleaner and saves some typing. Using a lambda expression we can write the snippet above as:

if (getInventory().contains(item -> item.getName().endsWith("pickaxe")) {
    // inventory contains a pickaxe 
}

This simply means, if the inventory contains an item where the item's name ends with "pickaxe", return true, otherwise return false.

 

12. Widgets

  Reveal hidden contents

Widgets are individual elements of the RuneScape interface. They are represented in OSBot by the RS2Widget class. They are useful for when you need to interact with something on screen, but no API method exists allowing you to do that.

You can view the currently visible widgets at any time by enabling the Widgets setting in the Debug tab of the OSBot options:

http://i.imgur.com/pDTxBXx.png

When the widget setting is enabled, you can hover your mouse over different elements of the RuneScape GUI and see something like this:

http://i.imgur.com/cr01KWJ.png

Each different coloured box represents a different widget, and the corresponding widget ids can be seen in the same colour as the box on the left.

RS2Widgets are identified in a tree-like structure, an individual RS2Widget can have 1-3 ids.

Root id -> Second level id -> Third level id

In the example image above, the widget for the level tab icon has two ids: 548, 46.

548 is the root id, this means there is a parent widget with id 548, to which this widget belongs.

46 is the second level id, it identifies this widget in the children of parent widget 548.

Lets imagine the widget instead had the ids 548, 46, 3. That would mean that 548 is the root, 46 is the child of 548, and 3 is the child of 46 (this widget)

You can retrieve a widget in your script using the Widgets class, you can retrieve an instance of this class using the getWidgets() method in the MethodProvider class

For example, to retrieve the level tab icon widget:

RS2Widget levelTabIcon = getWidgets().get(548, 46);

Note that when retrieving a widget, if it does not exist on screen, the method will return null.

Some widgets may have text, for example, the "Join Chat" button in the clan chat tab:

http://i.imgur.com/pcHFOyn.png

Instead of using ids, we can retrieve this widget using it's text:

RS2Widget joinChatButton = getWidgets().getWidgetContainingText("Join Chat");

Like with the Entity class, the  RS2Widget class implements the Interactable interface, meaning the RS2Widget class has an interact(String interaction) method, allowing us to interact with the widget.

Using the example of the "Join Chat" button again:

RS2Widget joinChatButton = getWidgets().getWidgetContainingText("Join Chat");
if (joinChatButton != null) {
    joinChatButton.interact("Join Chat"); 
}

 

13. Configs

  Reveal hidden contents

Configs are persistent id-value pairs that are updated on the RuneScape servers when certain events occur in game.

For example, there is a config for your player's attack style, it has a fixed id, and it's value changes whenever you select a different attack style in game.

There is also a config for your sound settings, and configs for quests, where the value indicates your player's progress through that quest.

Like with widgets, there is an option in the OSBot debug settings to enable a config view:

http://i.imgur.com/9Whhgka.png

When you enable this you will see a list of id:value pairs appear:

http://i.imgur.com/iochpa7.png

If you then perform an action in game, for example changing attack style, you will see new id:value pairs appear at the top. If you have multiple id:value pairs with the same id, the highest one on the list displays the current value.

For example, the id of the config for attack style is 43.

When I start changing the attack style, I can see the config 43's value changing:

http://i.imgur.com/PgeyyXB.png

To retrieve a config's value, you can use the Configs class, to retrieve the instance of this class there is a getter method in the MethodProvider class

So to retrieve the current attack style I would write:

int attackStyle = getConfigs().get(43);

To check if the first attack style is selected I would write:

if (getConfigs().get(43) == 0) {
    // First attack style is selected
}

 

14. Adding a paint  

  Reveal hidden contents

You can add a paint to your script using one of two methods:

- By overriding the onPaint method in the Script class (this method is available because the Script class implements the Painter interface):

import org.osbot.rs07.script.Script;
import org.osbot.rs07.script.ScriptManifest;

import java.awt.*;

@ScriptManifest(name = "Paint example", author = "Explv", info = "", version = 0.1, logo = "")
public class PaintExample extends Script {

    @Override
    public int onLoop() throws InterruptedException {
        return 0;
    }
    
    @Override
    public void onPaint(final Graphics2D g) {
        // Add painting code here
    }
}

 

Or by creating a class that implements the Painter interface, and then adding a new instance of your painter when the script starts using the addPainter method in the Bot class:

import org.osbot.rs07.canvas.paint.Painter;
import org.osbot.rs07.script.Script;
import org.osbot.rs07.script.ScriptManifest;

import java.awt.*;

@ScriptManifest(name = "Paint example", author = "Explv", info = "", version = 0.1, logo = "")
public class PaintExample extends Script {

    @Override
    public void onStart() {
        getBot().addPainter(new Paint());
    }
    
    @Override
    public int onLoop() throws InterruptedException {
        return 0;
    }
}

class Paint implements Painter {
    
    @Override
    public void onPaint(Graphics2D g) {
        
    }
}

 

To paint text, shapes etc. simply call methods found in the Graphics2D class

For examples of things you can paint onScreen see my other tutorial

 

15. Putting it all together, an example script (Smelting iron bars in Al-kharid)

  Reveal hidden contents

This script will be a simple Al-kharid iron bar smelter.

First, I will write some pseudocode:

if the inventory contains iron ore:
    if the player is in the smelting room:
        if the what would you like to smelt widget is visible:
            if interact with the iron bar widget with the Smelt X interaction successful:
                sleep until the enter amount dialog is visible
        else if the enter amount dialog is visible:
            if enter the amount successful:
                sleep until the inventory does not contain iron ore, or until the player levels up
        else:
            interact with the furnace
else if the player is not in the bank:
    walk to the bank
else if the bank is not open:
    open the bank
else if the inventory contains an item that is not iron ore:
    deposit all
else if the bank contains iron ore:
    withdraw all iron ore
else:
    logout and stop the script

Now to convert this into Java:

import org.osbot.rs07.api.map.Area;
import org.osbot.rs07.api.map.constants.Banks;
import org.osbot.rs07.api.ui.RS2Widget;
import org.osbot.rs07.script.MethodProvider;
import org.osbot.rs07.script.Script;
import org.osbot.rs07.script.ScriptManifest;
import org.osbot.rs07.utility.ConditionalSleep;

import java.util.function.BooleanSupplier;

@ScriptManifest(name = "Iron smelter", author = "Explv", info = "Smelts iron bars in Al-kharid", version = 0.1, logo = "")
public final class IronSmelter extends Script {

    private final Area smeltingRoom = new Area(3279, 3184, 3272, 3188);

    @Override
    public final int onLoop() throws InterruptedException {
        if (canSmeltIronBars()) {
            smelt();
        } else {
            bank();
        }
        return random(150, 200);
    }

    private void smelt() {
        if (!smeltingRoom.contains(myPosition())) {
            getWalking().webWalk(smeltingRoom);
        } else if (isSmeltScreenVisible()) {
            if (smeltXIron()) {
                Sleep.sleepUntil(this::isEnterAmountPromptVisible, 3000);
            }
        } else if (isEnterAmountPromptVisible()) {
            if (enterRandomAmount()) {
                Sleep.sleepUntil(() -> !canSmeltIronBars() || getDialogues().isPendingContinuation(), 100_000);
            }
        } else if (useFurnace()) {
            Sleep.sleepUntil(this::isSmeltScreenVisible, 5000);
        }
    }

    private boolean canSmeltIronBars() {
        return getInventory().contains("Iron ore");
    }

    private boolean useFurnace() {
        return getObjects().closest("Furnace").interact("Smelt");
    }

    private boolean isSmeltScreenVisible() {
        return getWidgets().getWidgetContainingText("What would you like to smelt?") != null;
    }

    private boolean smeltXIron() {
        return getWidgets().getWidgetContainingText("Iron").interact("Smelt X Iron");
    }

    // Using a custom filter here instead of getWidgetContainingText() because it ignores widgets with root id 162
    private boolean isEnterAmountPromptVisible(){
        RS2Widget amountWidget = getWidgets().singleFilter(getWidgets().getAll(), widget -> widget.getMessage().contains("Enter amount"));
        return amountWidget != null && amountWidget.isVisible();
    }

    private boolean enterRandomAmount() {
        return getKeyboard().typeString("" + MethodProvider.random(28, 99));
    }

    private void bank() throws InterruptedException {
        if (!Banks.AL_KHARID.contains(myPosition())) {
            getWalking().webWalk(Banks.AL_KHARID);
        } else if (!getBank().isOpen()) {
            getBank().open();
        } else if (!getInventory().isEmptyExcept("Iron ore")) {
            getBank().depositAll();
        } else if (getBank().contains("Iron ore")) {
            getBank().withdrawAll("Iron ore");
        } else {
            stop(true);
        }
    }
}

class Sleep extends ConditionalSleep {

    private final BooleanSupplier condition;

    public Sleep(final BooleanSupplier condition, final int timeout) {
        super(timeout);
        this.condition = condition;
    }

    @Override
    public final boolean condition() throws InterruptedException {
        return condition.getAsBoolean();
    }

    public static boolean sleepUntil(final BooleanSupplier condition, final int timeout) {
        return new Sleep(condition, timeout).sleep();
    }
}

 

16. Adding a GUI

  Reveal hidden contents

GUIs (graphical user interfaces) for OSBot are written using the Java Swing library.

There are plenty of tutorials on how to use this library online, so I will only cover the basics here.

In this section I will show you how to create a simple woodcutting GUI, allowing the user to select a type of tree:

-- placeholder for image of end result --

The first step to creating a GUI, is to create the top-level component, examples of which are JFrame, JDialog and JApplet.

For our GUIs we will be using the JDialog component.

There are two key reasons why we will be using JDialog:

1. We can set the JDialog to be a modal. A modal is a child window which appears in front of it's parent (In this case the OSBot application), and disables the parent window until the user's interaction with the modal is complete. This is useful if you want to prevent the script from running until the user has finished setting up everything in the GUI.

2. Best practice dictates that an application should not have more than one JFrame. Because OSBot already uses a JFrame, we should use something else for our child GUI.

 

JDialogs have both a content pane and a menu bar.

The content pane is where you will add the main interface of your GUI (labels, input boxes, check boxes, buttons etc.)

The menu bar isn't commonly used for OSBot GUIs, but you can make use of it for adding things like save / load / help options.

Creating a JDialog is simple: 

import javax.swing.*;
import java.awt.*;

public class GUI {
    private final JDialog mainDialog;

    public GUI() {
        mainDialog = new JDialog();
        mainDialog.setTitle("Explv's Woodcutter");
        mainDialog.setModal(true);
        mainDialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
    }
}

In the above snippet I have created a class called GUI with one private member variable "mainDialog" which is of type JDialog. In the class' constructor I initialise the JDialog, set the title to "Explv's Woodcutter", and set the JDialog to be a modal. Because the JDialog is a modal, when it is open it will block input to all parent windows.

 

We can then add to the JDialog's content pane by calling:

mainDialog.getContentPane().add(...)

 

Or set the JDialog's menu bar:

mainDialog.setMenuBar(...);

 

Now we will define a couple of functions to open and close the GUI:

import javax.swing.*;
import java.awt.*;

public class GUI {
    private final JDialog mainDialog;

    public GUI() {
        mainDialog = new JDialog();
        mainDialog.setTitle("Explv's Woodcutter");
        mainDialog.setModal(true);
        mainDialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);
    }

    public void open() {
        mainDialog.setVisible(true);
    }

    public void close() {
        mainDialog.setVisible(false);
        mainDialog.dispose();
    }
}

The open() function just sets the JDialog to be visible. The close() function sets the JDialog to not be visible, and then also calls dispose().

Now that we have a JDialog, we need to start thinking about it's layout and contents. Conveniently the Swing library provides you with an assortment of layout managers that handles the positioning and sizing of components for you. An overview of the different layout managers can be found here: https://docs.oracle.com/javase/tutorial/uiswing/layout/visual.html

DO NOT use an "absolute" layout (no layout manager). You will see many amateurs that are too lazy to understand the different layout managers and just resort to providing explicit x and y coordinates for each of their components. This will result in a GUI that is very fragile, cannot be resized, and is generally difficult to maintain. there is a reason why the layout managers exist, use them.

We can achieve a GUI where different parts use different layouts by using containers. The container you will be using most frequently is the JPanel. A JPanel is just an empty container, something which can have it's own layout and borders, that you can then add other components to.

For this GUI we will create a JPanel (empty container) with a vertical BoxLayout (the components are stacked vertically) as our main container. We will also add a 20px border, so that our components don't go right to the edge of the GUI:

import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;

public class GUI {
    private final JDialog mainDialog;

    public GUI() {
        mainDialog = new JDialog();
        mainDialog.setTitle("Explv's Woodcutter");
        mainDialog.setModal(true);
        mainDialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);

        JPanel mainPanel = new JPanel();
        mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS));
        mainPanel.setBorder(new EmptyBorder(20, 20, 20, 20));

        mainDialog.getContentPane().add(mainPanel);
    }

    public void open() {
        mainDialog.setVisible(true);
    }

    public void close() {
        mainDialog.setVisible(false);
        mainDialog.dispose();
    }
}

There are many components available in the Swing library, a visual guide to them can be found here: http://web.mit.edu/6.005/www/sp14/psets/ps4/java-6-tutorial/components.html

For this simple GUI, we will only be using a JLabel (for labelling things), a JComboBox (for selecting from a list of options), and a JButton (a button that when pressed will start the script)

The first thing we will add is a label that says "Select tree:", and a select box that contains different types of tree the user can choose from.

Because we want these two things to be grouped together in the GUI, I will create a JPanel to put both of them in:

JPanel treeSelectionPanel = new JPanel();
treeSelectionPanel.setLayout(new FlowLayout(FlowLayout.LEFT));

This JPanel uses a left aligned FlowLayout (components are placed in a row, if the width of the row is too small, components will be placed on multiple rows).

Now I will add the tree label to this panel:

JPanel treeSelectionPanel = new JPanel();
treeSelectionPanel.setLayout(new FlowLayout(FlowLayout.LEFT));

JLabel treeSelectionLabel = new JLabel("Select tree:");
treeSelectionPanel.add(treeSelectionLabel);

And finally I need to add the select box with different types of tree.

For the types of tree, I will be using an Enum. "An enum type is a special data type that enables for a variable to be a set of predefined constants. The variable must be equal to one of the values that have been predefined for it. Common examples include compass directions (values of NORTH, SOUTH, EAST, and WEST) and the days of the week."

The Enum will look like this:

enum Tree {
    NORMAL,
    OAK,
    WILLOW;
    
    @Override
    public String toString() {
        return name().toLowerCase();
    }
}

Note that in this Enum I am overriding the toString() function. Normally toString() would return the name of the Enum value e.g. "NORMAL" or "OAK". However having the name capitalised like that in the GUI is ugly, so I am returning the name in lowercase form.

Now I will create a select box with the different tree options and add it to our "treeSelectionPanel":

JPanel treeSelectionPanel = new JPanel();
treeSelectionPanel.setLayout(new FlowLayout(FlowLayout.LEFT));

JLabel treeSelectionLabel = new JLabel("Select tree:");
treeSelectionPanel.add(treeSelectionLabel);

JComboBox<Tree> treeSelector = new JComboBox<>(Tree.values());
treeSelectionPanel.add(treeSelector);

The selection panel is now complete, so I will add the selection panel to our "mainPanel".

 

 

The last component we need is a start button. When the user clicks this button, the GUI will close and the script will start, using the options that the user specified in the GUI:

JButton startButton = new JButton("Start");
startButton.addActionListener(e -> {
    started = true;
    close();
});
mainPanel.add(startButton);

We create a JButton with the text "Start", and we add an action listener to it. When the start button is clicked, the ActionListener will be called.
The ActionListener just sets a global boolean variable called "started" to true, and then calls the close() function we defined, to close the GUI.

Finally, now that all of the components have been added to the JDialog, we need to "pack" it (resize all the components to their preferred sizes):

mainDialog.pack();

Now the GUI is complete. Note that in the following snippet I have added a boolean function isStarted() to return the value of the started boolean, and I have added a getSelectedTree() function to return the value of our tree selector. These functions exist so that they can be used from our script class:

 

import javax.swing.*;
import javax.swing.border.EmptyBorder;
import java.awt.*;

public class GUI {
    private final JDialog mainDialog;
    private final JComboBox<Tree> treeSelector;

    private boolean started;

    public GUI() {
        mainDialog = new JDialog();
        mainDialog.setTitle("Explv's Woodcutter");
        mainDialog.setModal(true);
        mainDialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL);

        JPanel mainPanel = new JPanel();
        mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS));
        mainPanel.setBorder(new EmptyBorder(20, 20, 20, 20));
        mainDialog.getContentPane().add(mainPanel);

        JPanel treeSelectionPanel = new JPanel();
        treeSelectionPanel.setLayout(new FlowLayout(FlowLayout.LEFT));

        JLabel treeSelectionLabel = new JLabel("Select tree:");
        treeSelectionPanel.add(treeSelectionLabel);

        treeSelector = new JComboBox<>(Tree.values());
        treeSelectionPanel.add(treeSelector);

        mainPanel.add(treeSelectionPanel);

        JButton startButton = new JButton("Start");
        startButton.addActionListener(e -> {
            started = true;
            close();
        });
        mainPanel.add(startButton);

        mainDialog.pack();
    }

    public boolean isStarted() {
        return started;
    }

    public Tree getSelectedTree() {
        return (Tree) treeSelector.getSelectedItem();
    }

    public void open() {
        mainDialog.setVisible(true);
    }

    public void close() {
        mainDialog.setVisible(false);
        mainDialog.dispose();
    }
}

enum Tree {
    NORMAL,
    OAK,
    WILLOW;

    @Override
    public String toString() {
        return name().toLowerCase();
    }
}




Now we need to integrate the GUI into our Script:
 

import org.osbot.rs07.script.Script;
import org.osbot.rs07.script.ScriptManifest;

import javax.swing.*;
import java.lang.reflect.InvocationTargetException;

@ScriptManifest(author = "Explv", name = "Explv's Woodcutter", info = "", version = 0.1, logo = "")
public class ExampleScript extends Script {

    private GUI gui = new GUI();
    private Tree tree;

    @Override
    public void onStart() {
        try {
            SwingUtilities.invokeAndWait(() -> {
                gui = new GUI();
                gui.open();
            });
        } catch (InterruptedException | InvocationTargetException e) {
            e.printStackTrace();
            stop();
            return;
        }
        
        // If the user closed the dialog and didn't click the Start button
        if (!gui.isStarted()) {
            stop();
            return;
        }
        
        tree = gui.getSelectedTree();
    }

    @Override
    public int onLoop() throws InterruptedException {
        // go and chop down tree   
        return 600;
    }
    
    @Override
    public void onExit() {
        if (gui != null) {
            gui.close();
        }
    }
}


In the onStart() function we create an instance of our GUI, and we open it.
Note that any Swing operation should be performed on the EDT (Event dispatching thread). To do this, we call SwingUtilities.invokeAndWait(Runnable runnable):

try {
    SwingUtilities.invokeAndWait(() -> {
        gui = new GUI();
        gui.open();
    });
} catch (InterruptedException | InvocationTargetException e) {
    e.printStackTrace();
    stop();
    return;
}

Once the GUI is closed we check that the user actually clicked the start button (isStarted() would return true). If they didn't click the start button, and just closed the window, we don't want to start the script because the user didn't set it up correctly. So we just call stop().

if (!gui.isStarted()) {
    stop();
    return;
}

Otherwise if the user did start the script using the start button, we can retrieve the selected Tree from the GUI and then begin our woodcutting script:

tree = gui.getSelectedTree();

 

 

I tried to run the Java code for the GUI Woodcutter, but I got so many errors to show up just by having it sit in my editor. I wanted to try this script out to see how it looked, and then possibly create something more original. 

ExampleScriptWoodcutErrors.png

Link to comment
Share on other sites

57 minutes ago, Stando said:

I tried to run the Java code for the GUI Woodcutter, but I got so many errors to show up just by having it sit in my editor. I wanted to try this script out to see how it looked, and then possibly create something more original. 

ExampleScriptWoodcutErrors.png

Looks like OSBot isn't added as a dependency 

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...