Stando Posted March 19, 2022 Share Posted March 19, 2022 2 hours ago, Gunman said: https://osbot.org/forum/topic/154399-script-development-setup/ Using IntelliJ actually helped me with my problem of getting a script to pop up in OSBot. It now appears after hitting refresh. Thanks. I am going to use the Tea Thiever script that Apaec used in his tutorial as a base, along with other scripts. Hopefully I can create a Woodcutting bot. 1 Quote Link to comment Share on other sites More sharing options...
Stando Posted March 19, 2022 Share Posted March 19, 2022 18 hours ago, Stando said: Using IntelliJ actually helped me with my problem of getting a script to pop up in OSBot. It now appears after hitting refresh. Thanks. I am going to use the Tea Thiever script that Apaec used in his tutorial as a base, along with other scripts. Hopefully I can create a Woodcutting bot. I have been somewhat successful in creating a Woodcutter. One issue I am having though is that once my bot gets 26 Oak logs in it's inventory, it should bank. Rather than banking, my bot will go all the way to the bank, pause, then forget about banking, just to go back to the trees with a full inventory, and loop this error again. /* WhizCutter Alpha Ver. 1.0 Created by Happy Chop trees in Lumbridge and banks them. Still waiting for first test. Bot got stuck after banking it's first load. If the bot is stuck in Lumby bank, it is probably searching for trees. Take back down to the trees behind the castle. Close the script, then restart it. The bot should now find trees. Possibly create one for different types of trees? Give ability to switch tree or location? Make sure to refresh bots(at times banking metod */ 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 = "WhizCutter", author = "Happy", info = "Cuts trees in Lumbridge", version = 1.0, logo = "") public final class WhizCutter extends Script { private final Area LumbyBank = new Area(3208, 3216, 3210, 3220); private final Area LumbyTrees = new Area(3197, 3208, 3187, 3235); private final Area VarrockWestBank = new Area(3180,3434,3185,3444); private final Area VarrockFrontOaks = new Area(3215,3359,3198,3373); @Override //Loops over and over until exit public final int onLoop() throws InterruptedException { Woodcut(); //May not need this line of code in the near future, but will need banking spots chopOakWood(); if (shouldIBankLogs() >= 26) { bank(); } return random(1000, 4000); } //Methods from here on alphabetized private void bank() throws InterruptedException { if (!Banks.VARROCK_WEST.contains(myPosition())) { getWalking().webWalk(Banks.VARROCK_WEST); } else if (!getBank().isOpen()) { getBank().open(); } else if (!getInventory().isEmptyExcept("Oak logs")) { getBank().depositAll("Oak logs"); } //stop(true); } private boolean chopOakWood() { return getObjects().closest("Oak").interact("Chop down"); } private boolean chopWood() { return getObjects().closest("Tree").interact("Chop down"); } private boolean inventoryIsFull() { return getInventory().isFull(); } private long shouldIBankLogs() { return getInventory().getAmount("Oak logs"); } private void Woodcut() { if (!VarrockFrontOaks.contains(myPosition())) { getWalking().webWalk(VarrockFrontOaks); } } } import org.osbot.rs07.utility.ConditionalSleep; import java.util.function.BooleanSupplier; 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(); } } Quote Link to comment Share on other sites More sharing options...
Stando Posted April 4, 2022 Share Posted April 4, 2022 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: Use the Entity hover debugger by hovering over a tile, this is enabled in the settings of the OSBot client. Using an OSBot script such as https://osbot.org/forum/topic/84109-explvs-location-assistant-free-paths-areas/ 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: Returning an integer value from the onLoop() method, this will result in the ScriptExecutor sleeping for the specified number of ms Calling the sleep(long ms) method in the MethodProvider class, note this method is static and so you can write: MethodProvider.sleep(long ms) 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: Get the closest Tree to the player 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) Perform the "Chop down" interaction on the tree Check that the interact() method returned true, if the return value is false, the interaction failed and we do not want to sleep Create a new ConditionalSleep with timeout set to 5000ms (5 seconds) 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) 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, Store, Trade.OurOffer, Trade.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 Hide 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 was following this tutorial, but the GUI portion automatically logs me out after hitting start. Could anyone explain the reason for this? Quote Link to comment Share on other sites More sharing options...
Spork Posted April 5, 2022 Share Posted April 5, 2022 16 hours ago, Stando said: I was following this tutorial, but the GUI portion automatically logs me out after hitting start. Could anyone explain the reason for this? Open log and see if it throws an error of some kind, post contents On 3/19/2022 at 5:39 PM, Stando said: I have been somewhat successful in creating a Woodcutter. One issue I am having though is that once my bot gets 26 Oak logs in it's inventory, it should bank. Rather than banking, my bot will go all the way to the bank, pause, then forget about banking, just to go back to the trees with a full inventory, and loop this error again. /* WhizCutter Alpha Ver. 1.0 Created by Happy Chop trees in Lumbridge and banks them. Still waiting for first test. Bot got stuck after banking it's first load. If the bot is stuck in Lumby bank, it is probably searching for trees. Take back down to the trees behind the castle. Close the script, then restart it. The bot should now find trees. Possibly create one for different types of trees? Give ability to switch tree or location? Make sure to refresh bots(at times banking metod */ 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 = "WhizCutter", author = "Happy", info = "Cuts trees in Lumbridge", version = 1.0, logo = "") public final class WhizCutter extends Script { private final Area LumbyBank = new Area(3208, 3216, 3210, 3220); private final Area LumbyTrees = new Area(3197, 3208, 3187, 3235); private final Area VarrockWestBank = new Area(3180,3434,3185,3444); private final Area VarrockFrontOaks = new Area(3215,3359,3198,3373); @Override //Loops over and over until exit public final int onLoop() throws InterruptedException { Woodcut(); //May not need this line of code in the near future, but will need banking spots chopOakWood(); if (shouldIBankLogs() >= 26) { bank(); } return random(1000, 4000); } //Methods from here on alphabetized private void bank() throws InterruptedException { if (!Banks.VARROCK_WEST.contains(myPosition())) { getWalking().webWalk(Banks.VARROCK_WEST); } else if (!getBank().isOpen()) { getBank().open(); } else if (!getInventory().isEmptyExcept("Oak logs")) { getBank().depositAll("Oak logs"); } //stop(true); } private boolean chopOakWood() { return getObjects().closest("Oak").interact("Chop down"); } private boolean chopWood() { return getObjects().closest("Tree").interact("Chop down"); } private boolean inventoryIsFull() { return getInventory().isFull(); } private long shouldIBankLogs() { return getInventory().getAmount("Oak logs"); } private void Woodcut() { if (!VarrockFrontOaks.contains(myPosition())) { getWalking().webWalk(VarrockFrontOaks); } } } import org.osbot.rs07.utility.ConditionalSleep; import java.util.function.BooleanSupplier; 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(); } } Whew lad, good first script but a couple things: You don't need to define the bank areas, and it seems you already are familiar with the Banks class, so yanno, get rid of that redundant stuff... Also what the heck is going on with private void Woodcut()? It looks like you went to make it a constructor but decided to make it a void that webwalks you lol... I'm not sure what your goal there was. Otherwise, keep it up! You'll get more comfortable with all of it soon Quote Link to comment Share on other sites More sharing options...
Stando Posted April 6, 2022 Share Posted April 6, 2022 On 4/5/2022 at 11:06 AM, Spork said: Open log and see if it throws an error of some kind, post contents Whew lad, good first script but a couple things: You don't need to define the bank areas, and it seems you already are familiar with the Banks class, so yanno, get rid of that redundant stuff... Also what the heck is going on with private void Woodcut()? It looks like you went to make it a constructor but decided to make it a void that webwalks you lol... I'm not sure what your goal there was. Otherwise, keep it up! You'll get more comfortable with all of it soon private void Woodcut() is void so it doesn't question for a return type when the method is actually activated within the loop. I left the extra areas in there on purpose since I got to level 15 and had the ability to chop Oaks. If it were a constructor, it would have the same name as the class of the script, which is WhizCutter. Quote Link to comment Share on other sites More sharing options...
osrstaurean Posted August 10, 2022 Share Posted August 10, 2022 You are definitely a legend, thank you for opening a new world for me! On 3/19/2022 at 5:39 PM, Stando said: I have been somewhat successful in creating a Woodcutter. One issue I am having though is that once my bot gets 26 Oak logs in it's inventory, it should bank. Rather than banking, my bot will go all the way to the bank, pause, then forget about banking, just to go back to the trees with a full inventory, and loop this error again. /* WhizCutter Alpha Ver. 1.0 Created by Happy Chop trees in Lumbridge and banks them. Still waiting for first test. Bot got stuck after banking it's first load. If the bot is stuck in Lumby bank, it is probably searching for trees. Take back down to the trees behind the castle. Close the script, then restart it. The bot should now find trees. Possibly create one for different types of trees? Give ability to switch tree or location? Make sure to refresh bots(at times banking metod */ 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 = "WhizCutter", author = "Happy", info = "Cuts trees in Lumbridge", version = 1.0, logo = "") public final class WhizCutter extends Script { private final Area LumbyBank = new Area(3208, 3216, 3210, 3220); private final Area LumbyTrees = new Area(3197, 3208, 3187, 3235); private final Area VarrockWestBank = new Area(3180,3434,3185,3444); private final Area VarrockFrontOaks = new Area(3215,3359,3198,3373); @Override //Loops over and over until exit public final int onLoop() throws InterruptedException { Woodcut(); //May not need this line of code in the near future, but will need banking spots chopOakWood(); if (shouldIBankLogs() >= 26) { bank(); } return random(1000, 4000); } //Methods from here on alphabetized private void bank() throws InterruptedException { if (!Banks.VARROCK_WEST.contains(myPosition())) { getWalking().webWalk(Banks.VARROCK_WEST); } else if (!getBank().isOpen()) { getBank().open(); } else if (!getInventory().isEmptyExcept("Oak logs")) { getBank().depositAll("Oak logs"); } //stop(true); } private boolean chopOakWood() { return getObjects().closest("Oak").interact("Chop down"); } private boolean chopWood() { return getObjects().closest("Tree").interact("Chop down"); } private boolean inventoryIsFull() { return getInventory().isFull(); } private long shouldIBankLogs() { return getInventory().getAmount("Oak logs"); } private void Woodcut() { if (!VarrockFrontOaks.contains(myPosition())) { getWalking().webWalk(VarrockFrontOaks); } } } import org.osbot.rs07.utility.ConditionalSleep; import java.util.function.BooleanSupplier; 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(); } } you should make use of getItem() to check if item is null and getInventory to see if inv is full Quote Link to comment Share on other sites More sharing options...
SlimGirl Posted February 25, 2023 Share Posted February 25, 2023 Thanks Explv. This has been a great help in the making of my scripts. Much appreciated! Quote Link to comment Share on other sites More sharing options...
lg100000 Posted September 5, 2023 Share Posted September 5, 2023 (edited) Nice!! Edited September 5, 2023 by lg100000 Quote Link to comment Share on other sites More sharing options...
Swisher Sweets Posted October 29, 2023 Share Posted October 29, 2023 Has helped me loads thanks so much! Quote Link to comment Share on other sites More sharing options...
sl0wkey Posted December 10, 2023 Share Posted December 10, 2023 Thanks a lot for this! Helped me so much! Quote Link to comment Share on other sites More sharing options...
Gangster Posted February 14 Share Posted February 14 Started writing some test scripts, when I compile them and they're outputted to OSBot/scripts, it isn't visible inside the client. Some people have mentioned that this could be due to requiring Java 1.8, some people have said other things, either way I can't see any one covering this topic for a long time and there's no recent information, and all the guides are multiple years old. Could someone give me a hand? Context : I've got all the most modern versions of Java/SDK/JRE - and my IntelliJ is setup correctly so that when I build, the JAR is outputting to the right folder, and yes I have a script manifest completed. I just can't see the script, I'm assuming it's a java version thing but I can't find any guidance anywhere. Quote Link to comment Share on other sites More sharing options...