Jump to content

Explv's Scripting 101


Explv

Recommended Posts

  • 4 weeks later...
  • 2 weeks later...
  • 2 weeks later...
  • 3 months later...

Sorry if this is a dumb question, but in the conditional sleep examples you list, in the override you have it return if the player is animating, why is that? I understand that if the tree doesn't exist obviously you'd need to move to another, but if you're performing the chopping animation wouldn't that immediately end the sleep and cause it to attempt to finding and interacting with the closest tree again?

@Override
        public boolean condition() {
            return myPlayer().isAnimating() || !tree.exists();
        }

 

Thank you so much for this, it's been super helpful, almost got my first script working :> 

Link to comment
Share on other sites

  • 2 months later...

Just tried to write a simple chopper&banker in Draynor, the script is only chopping one tree and then won't do anything else. Could you help?

import org.osbot.rs07.api.map.Area;
import org.osbot.rs07.api.map.constants.Banks;
import org.osbot.rs07.api.model.Entity;
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 = "Draynor Cutter&Banker", author = "Pryzm", info = "Chops and Banks in Draynor village", version = 1, logo = "")
public final class DraynorCutter extends Script {

    private final Area eastTrees = new Area(3098, 3247, 3102, 3239);

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

    

	private void chop() {
        if (!eastTrees.contains(myPosition())) {
            getWalking().webWalk(eastTrees);{
            	Entity tree = getObjects().closest("Tree");
                if (tree != null && tree.interact("Chop down")) {
                    new Sleep(() -> myPlayer().isAnimating() || !tree.exists(), 5000).sleep();
                }	
            }
            
        }
    }
	
private boolean canChopTreeDown() {
	 if (!eastTrees.contains(myPosition()));

		return false;
		
}

    private void bank() throws InterruptedException {
        if (!Banks.DRAYNOR.contains(myPosition())) {
            getWalking().webWalk(Banks.DRAYNOR);
        } else if (!getBank().isOpen()) {
            getBank().open();
        } else if (!getInventory().isEmptyExcept("Logs")) {
            getBank().depositAll("Logs");
        } 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();
    }
}

 

Link to comment
Share on other sites

  • 1 month 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)

  Hide 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"

b8IBakI.png

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

CkaNMOL.png

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

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

D00XpaN.png

11. Select apply and close the window.

 

2. The Script Skeleton

  Hide 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

  Hide 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

  Hide 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

  Hide 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

  Hide 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:

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:

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:

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:

9Whhgka.png

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

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:

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 --

 

To create a Swing GUI for OSBot we will follow this hierarchical pattern:

 

3jframe.gif

 

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

For OSBot GUIs you only really need the JFrame which is just an empty window, with a title, minimize button, maximize button etc.

JFrame's 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 JFrame is simple: 



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

public class GUI {

    private final JFrame jFrame;
    
    public GUI() {
        jFrame = new JFrame("Explv's Woodcutter");
    }
}

In the above snippet I have created a class called GUI with one private member variable "jFrame" which is of type JFrame. In the class' constructor I initialise the JFrame, and provide it with a title "Explv's Woodcutter".

 

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



jFrame.getContentPane().add(...)

 

Or set the JFrame's menu bar:



jFrame.setMenuBar(...);

 

Now that we have a JFrame, we need to start thinking about it's layout. 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 JFrame jFrame;

    public GUI() {
        jFrame = new JFrame("Explv's Woodcutter");

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

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

 

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

 

To be continued...

 

 

 

so using this i can make an easy osbot script? just to test out say like a woodcutter script. if i don't have any prior knowledge of java. where can i get this basic knowledge of java for osbot scripts? 

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...