Jump to content

Writing a simple Prayer script


saturn

Recommended Posts

Hey everyone, in this tutorial I'll detail how to create a simple script that loots bones and buries them for free prayer exp. I noticed on the forums that there are a lot of people who are new to script writing and are interested in writing their own scripts. This tutorial will teach you some Java basics and walk you through writing a simple script. Hopefully you are able to learn something from this and can start to write your own scripts. There are plenty of open source scripts on the forum already so you can use those as a resource as well.

What will be covered in this tutorial?

  • LoopScript basics
  • Understanding and Passing the API Context
  • Simple Java functions
  • Using the GroundItems API
  • Using the Inventory API
  • Referring to the Javadoc and why it's important

The best way to learn from this tutorial is to read and try to understand what is happening, if you simply copy and paste code then you won't learn much. This tutorial assumes you have IntelliJ IDEA installed and setup for EpicBot. If not there is a tutorial you can follow here.

One thing to keep in mind: Coding and script writing is not for everyone. Some of you will really enjoy it, and some of you will hate it. It is what it is, if you don't like it then you're better off using free and premium scripts instead.

Let's get started:

LoopScript Basics:

A LoopScript is a type of script that can be run by EpicBot, there are other types of scripts like a TreeScript but that won't be covered in this tutorial. A LoopScript is exactly what it sounds like, it's a loop that continues indefinitely until you tell it to stop.

Here is a basic skeleton of a LoopScript, I will go over the important bits that are necessary for making our bone burying script, called Gravedigger:

@ScriptManifest(...): This is where the script selector gets it's information about the script, mainly the name of the script and the game type. Here we named the script Gravedigger, and the script is meant for OSRS

loop(): This is the meat of the script, everything that you want your script to do should go in this loop() method.

onStart(String... strings): This function runs once when the script first starts, it is useful for setting up certain settings for your script or anything up that is required for your script to run. This could be something like setting a variable with the start time of the script, which is useful for calculating the total runtime of a script (which can be displayed on screen using Paint). We won't concern ourselves with the (String... strings) inside the brackets of this function since we are just writing a simple script.

@ScriptManifest(name = "Gravedigger", gameType = GameType.OS)
public class Gravedigger extends LoopScript {

    @Override
    protected int loop() {
        return 600;
    }

    @Override
    public boolean onStart(String... strings) {
        return true;
    } 
}

What is that "return 600" in the loop() method?:

The return value of the loop tells EpicBot how long to sleep before running the loop again. The return value of 600 means that EpicBot will wait 600 milliseconds before starting the loop again. This is important because it keeps CPU usage at a reasonable level. If you set it to 0 your CPU usage will be much higher and you don't gain anything from this. Remember that a game tick in OSRS is 600 milliseconds, so nothing in game will have changed until the next game tick, so it doesn't make sense to have your loop running every 100ms  because it won't be doing anything. Of course it takes time to execute the loop and all the methods inside it, so if you have a fairly complex script then it makes sense to have a return value lower than 600. But since this is a very basic script, we will have it sleep 600ms between each loop.

TIP: You can choose a random number between a certain range to add a bit more variation to the script if you would like. Simply use this line instead:

return Random.nextInt(450, 600);

This will have the script sleep at a random interval between 450ms and 600ms.

What is that "return true" in the onStart() methods?:

When you return true in the onStart method, this simply tells the script to start. You might be wondering "Well of course I want my script to start, what is the point of this?". Consider this scenario: You are creating a fletching script, and your character doesn't have a knife in their inventory, if you don't want the script to be able to run without a knife in the inventory, then this is a very handy way of stopping the script before it even starts. How would you do this? Check if the user has a knife in their inventory, if they don't then return false in the onStart method and the script will stop before it even starts. I won't post a code snippet of how to do this, consider it an exercise after you complete the tutorial. If you follow along to the end of the tutorial then you should be able to figure out how to do this yourself.

Is there another way to stop the script?

Yes there is, say you have started the script already, therefore the onStart method has already ran once and won't run again since it only runs once when the script first starts. How do we stop the script then? In a LoopScript, it's very simple. Remember the return value in the loop() method? When given a positive number, it sleeps for that amount of time in milliseconds, try passing a negative number instead. What happens? The script stops. In a LoopScript, if you return any negative number in the loop() method, it will stop the script.

Let's go back to that fletching knife scenario from earlier. Say you want your script to check the bank for a knife first before stopping, in case the user forgot to withdraw it from their bank. You write a method to check the bank, and it looks like they don't have a knife in the bank either! Simply have the method return a negative number and pass that as the return value and it will stop the script. This may sound confusing at first but once you write your first script it will start to make sense.

Before we start writing code, we need to understand the API Context.

 

Understanding and Passing the API Context

The API Context is possibly the single most important thing you will use when writing scripts. It's how you get your script to do anything really. Need to get an item in your inventory, use the API Context. Need to figure out where your player is standing, use the API Context. How do I attack an NPC...? Use the API Context. See where I'm going with this?

There are two ways to get the API Context in your script. In the main script file, your LoopScript, there is a function called "getAPIContext()", you can call this function and store it's result in a APIContext variable. Like so:

APIContext ctx = getAPIContext();

This is my preferred method, it stores the API Context in a variable called "ctx" (shorthand for the word context). Afterwards you simply pass the API Context to any function that requires it. How do we do this?:

To do this you must understand how a Java function is structured:

private void openBank(APIContext ctx){
	ctx.bank().open();
}

Access Level Modifier: private

Return Type: void

Function Name: openBank

Function Arguments: APIContext ctx

Access Level Modifiers (Might seem complicated at first):

This restricts or provides access for different levels of your Java program. Here is a StackOverflow answer about Access Level modifiers. This might seem confusing at first, so I'll try to phrase it in a basic way. Think of private functions and variables as items you will only need to access in this specific file. Protected functions and variables are items you can access in a package (You can think of a package as a folder). Public functions and variables are items you can access throughout your program. Generally you want to provide a minimum level of access, meaning functions and variables should always be private unless you need to use them elsewhere. There are ways to provide access to private functions and variables for use elsewhere but I won't go into detail about this since this is already a lot of information to take in. Did you understand all that? If you're a newbie then probably not, but that's okay, as you begin to write scripts and get a better understanding of Java, these concepts will start to become clearer. The best way to learn coding is through practical application, so even if you don't understand this now, after writing your first script or two or five? you will start to connect the dots.

Return Type:

In the above example the return type is void. What does this mean? void means the function does not return any value. Remember in the loop() method of the LoopScript, you return an integer and it sleeps for that amount of time in milliseconds. Take a look at that loop() function again, do you see it's return type? It's "int", not "void" like this example, that means the loop() function returns an integer. There are other return types like boolean (true/false), double (numbers with a decimal like 3.14), etc. Google Intro to Java Functions if you want to learn more.

Function Name:

This should be obvious, it's the name of the function, this is what you type in the loop() function when you want to call this specific function in your script. You would call this function by using the following line "openBank(ctx)" Fairly straightforward.

Function Arguments:

Remember earlier when I talked about passing the API Context to functions, that is exactly what is happening here. When you call this function, the Java compiler expects you to pass in a variable of the type APIContext. If you don't then you will get an error and your script won't compile. You can have multiple arguments in a function like so (in this case, the multiply function's arguments are "int a" and "int b"):

public int multiply(int a, int b){
	return a*b;
}

Call the multiply function like this:
multiply(2,4);

Store the result returned by the function in a variable for later use:
int product = multiply(2,4);

The value stored in the variable "product" is 8.

Hopefully this example helps you understand function arguments.

That was a lot to take in. One last thing about the API Context:

Another method for getting the API Context is by calling APIContext.get(). You can do this anywhere in your script, although in the Epicbot Javadocs it say's not to rely on this method so that is why I prefer to use the "getAPIContext()" function like I mentioned earlier.

 

Simple Java Functions - Using the GroundItems API - Using the Inventory API

First let's map out what we want our script to do. We want it to loot bones on the ground and bury them. So what exactly does our script need to do?

  • Find bones on the ground
  • Pick up the bones from the ground
  • Keep looting bones until your inventory is full
  • Start burying bones when you have a full inventory
  • Repeat.

Sounds easy enough, but as you write the script you will notice that doing these tasks is not a single line of code but moreso a multistep process.

Lets start with finding bones on the ground. It is best to split up the script logic into smaller functions, each function should have only one specific purpose. This is called the Single Responsibility principle, by the name you should understand what this means. Essentially, it's consider bad practice to have functions do more than one specific thing, for instance: If we wrote a function that finds bones on the ground and then picks up the bones and then buries the bones, this violates the Single Responsibility principle. Following this principle will make your life easier in the long run, especially when its time to test and debug your script.

Find Bones on Ground:

How do we find items on the ground? we use the Ground Items API which is provided by the API Context. How did I know to use the GroundItems API? Simple. Check the Javadocs: https://epicbot.com/javadocs/ The Javadocs will be the best resource to read when you want to explore the API and see how to use specific functions. The EpicBot Javadocs are well documented, and when you see a function and you're not entirely sure how to use it, check the Javadocs.

Let's write a function that returns the nearest GroundItem when the function is given a specific item name.

private GroundItem getNearestItem(APIContext ctx, String itemName){
        return ctx.groundItems().query()
                .named(itemName)
                .results()
                .nearest();
}

How to use the function to find Bones:
GroundItem bones = getNearestItem(ctx, "Bones");

How does this function work? We use the "query()" method, try to use the query method when possible because it is easy to understand and efficient in terms of CPU usage. This function returns the nearest ground item with the name that was passed to the function. This function can be reused for any item, not just bones, since we can pass in any item name into the function.

What happens if there are no bones found in game?

This is very important to note, if there are no bones found in the game when this function is called, the return value will be "null". Null is essentially Java's way of saying whatever you are looking for, doesn't exist. If you try to operate on a null value, your script will throw a NullPointerException (NPE) and it will break the script. You want to avoid NPEs at all costs, and to do so it's very simple:

GroundItem bones = getNearestItem(ctx, "Bones");

if(bones != null){
	//do something
}

We use an if statement to check if bones does not equal (!=) null. This way if there are no bones available in game and the function returns a null value, the if statement protects us from operating on a null value, because the if statement only does something if the value inside the brackets is true, meaning the variable bones is not null. Whenever there is an ! exclamation mark in front of an equals or a boolean variable, that means "not". != means "does not equal"

 

Pick up bones from the ground:

Let's write a function to pick up bones from the ground, but wait, what if our inventory is full? Should we still try to pick up bones? Obviously not. So we will need to write a function that checks if our inventory is full. How do we check if our inventory is full? Use the API Context!

private boolean isInventoryFull(APIContext ctx){
	return ctx.inventory().isFull();
}

We can reuse this function any time we need to check if our inventory is full. So how do we check if our inventory is not full? Remember to use the ! exclamation mark to denote "not". So we would call the function like this:

if(!isInventoryFull(ctx)){
	//do something
}

Now to write the actual pick up logic:

private void pickUpBonesFromGround(GroundItem bones){
	if(bones != null){
		bones.interact("Take");
	}
}

Try using this function in your script and running it. (Make sure you do this on an account you don't care about)

What happens? Well it tries to pick up bones, but then immediately tries to pick up the bones again, it keeps spam clicking! What do I do?

We have to tell the script to sleep, we can tell the script to sleep until an event occurs. We will use a lambda function for shorthand, you can read up on Lambda functions here. Let's rewrite the function to include a sleep call.

private boolean pickUpBonesFromGround(APIContext ctx, GroundItem bones){
	final String BONES = "Bones";
	int count = ctx.inventory().getCount(BONES);
	
	if(bones != null && bones.interact("Take")){
		return Time.sleep(3600, () -> ctx.inventory().getCount(BONES) != count);
	}
	
	return false;
}

This function has changed considerably. Did you notice the return type is now a boolean and not void? What is the point of the BONES variable? And what are we doing with the count variable?

What is happening???

We changed the return type of this function to a boolean, why? This is so we know if we successfully picked up the bones or not. To understand how this works, we have to look at the Time.sleep() function. If you look in the Javadocs, or IntelliJ's Code Completion, we see that Time.sleep() returns a boolean value. This specific Time.sleep() function takes two values (There are multiple Time.sleep() functions, look up Java Overloading), the first is an integer, this represents the time to wait until we stop sleeping. So we're waiting a maximum of 3.6 seconds (arbitrary number, you can wait for as long as you like) to see if the script is able to pick up bones in that time. The sleep function knows to stop because of the second argument, the lambda function:  () -> ctx.inventory().getCount(BONES) != count). This is what the sleep function is waiting for, it checks to see in the number of bones in your inventory does not equal the number stored in the variable count. The variable count is the number of bones that we're in our inventory before we tried to pick up the bones. If that condition becomes true within 3.6 seconds, Time.sleep() will stop sleeping and send a return value of True, meaning we successfully picked up the bones. If not, then it returns false, meaning we didn't pick up the bones within 3.6 seconds. Starting to make sense? We also have the function return false if the condition in the if statement returns false which means the bones variable is null or the bones.interact() function failed for whatever reason.

Notice in the if statement the &&. This reads as "if bones does not equal null AND bones.interact() is successful", if we want to use an OR instead of an AND we use double bars: ||. We can have multiple conditions in a single if statement.

 

Keep looting bones until Inventory is full:

Now that we have functions to check if our inventory is full, one to get nearby bones, and one to pick up the bones, let's write a function that is able to put all of these actions together and record the number of bones we picked up.

private int bonesLooted = 0;

private void lootBones(APIContext ctx){
	GroundItem bones = getNearestItem(ctx, "Bones");
		
	if(pickUpBonesFromGround(ctx, bones)){
		bonesLooted++;
	}
}

We created a variable outside of the function called lootBones, when we do this, we can still access the variable in the lootBones function itself since it is a private variable and the function is inside the same file (Remember Access Level Modifiers?). But since this bonesLooted variable is outside of the lootBones function, other functions have access to this variable too (as long as it's in the same file, since its a private variable). This will be useful for our Paint. Notice we use the code:

bonesLooted++;

when we use ++, that basically means add 1 to the current variable. It is a shorthand for the following code:

bonesLooted = bonesLooted + 1;

 

Start burying bones when we have a full inventory:

This is the last part of our script logic, burying the bones, we do this when our inventory is full of bones. There are multiple ways to go about burying all 28 bones, we will use something in Java called a for loop, which is a loop we can define to run a certain number of times:

    private void buryBones(APIContext ctx){
        final String BONES = "Bones";

        for(int i = 0; i < 28; i++){
            int count = ctx.inventory().getCount(BONES);
            ItemWidget bones = ctx.inventory().getItem(BONES);

            if(bones != null && bones.interact("Bury")){
                Time.sleep(1200, () -> ctx.inventory().getCount(BONES) != count);
            }
        }
    }

One thing I forgot to mention earlier, if you find yourself using the same string over and over again, it's best to store it in a variable and use that variable instead, like the BONES variable.

Let's talk about this function, it loops 28 times (Number of inventory slots in your backpack) and buries a set of bones each loop. After it buries the bones, the script will wait for a maximum of 1.2 seconds (1200ms) until the the number of bones in your inventory is not equal to the number of bones that were in your inventory before trying to bury the bones.

There are a few different kinds of for loops, in this one we use an integer value as a counter. In this case the variable is i. Which is set to 0, the next bit: i < 28; this is the stopping condition of the for loop, as long as i is less than 28, the loop continues, each time the loop completes, i is incremented by 1, remember the ++ operator? So essentially what this loop is doing is starting from the very first inventory slot, it gets the first Bones item in the inventory, and then buries the bones. Remember that we are only getting the Bones item, since we call ctx.inventory().getItem(BONES); It will either return the first bones item in the inventory, or if there are no bones left, it returns a null value. We are safe from a NullPointerException here because we check if the variable bones is null before trying to operate on it. And that is basically it for the script logic. Well done for making it this far!

 

Referring to the Javadocs and why its important:

When scripting, the Javadocs are one of your best resources, it documents everything about EpicBot's API and explains the more complex function in an easy to understand manner. If you are unsure of how to use a function, or if you're wondering if a function to do your specific task already exists within the API, check the Javadocs:  https://epicbot.com/javadocs/

Seriously, I can't stress this enough. This is how I learned the basics of the EpicBot API and created my own scripts. Refer to the Javadocs whenever you are lost, and always check the Javadocs first before you ask for help, chances are it already has what you're looking for.

If you want to search for API Context functions, it may not be obvious where to look. When first starting to write scripts, you will most likely need to look at the IInventoryAPI or whatever API. IBankAPI, IGroundItemsAPI, ICombatAPI. That has all the API Context functions you will use and most likely need to look up. Once you start to create more complex scripts, you will find yourself looking through basically the entire Javadocs. Again, it's very well documented, and the more complex functions have helpful descriptions as well.

 

Putting it all together:

Now that you have learned how to write a basic script, and I have provided the overall functionality for you in the above functions. I'll leave it up to you as an exercise to call the functions in the loop() method of the LoopScript. If you're confused make sure to read up on the LoopScript basics section again, you should be able to complete this by yourself now that you have a better understanding of how things work. Hopefully. I hope you weren't expecting me to leave a completed script for you to copy and paste into IntelliJ at the end of this tutorial 😂 Instead I'll give you the structure of the script, its very simple.

In the main file:

  • Create an APIContext variable outside of the main loop
  • In the onStart method, intialize the APIContext variable by storing the result of the getAPIContext() method in the variable
  • Create a bonesLooted variable outside of the main loop like the APIContext variable, and make it equal to 0

In the main Loop method:

  • Check if the inventory is full
  • If it is full then bury bones
  • Otherwise (else) loot the bones
  • Done. A good place to run this script is the in the Lumbridge cow pens on a free world.

If you need help feel free to leave a comment, but this should be more than enough to help you create a simple bone burying script. I hope you learned something from this tutorial and if it helps you create your own script, then that's great! Good luck and have fun!

Edited by saturn
slight change to functions
  • Like 4
  • Thanks 1
Link to comment
Share on other sites

  • 4 weeks later...

Thank you for this guide it was really helpful.

The only feedback I have is for the sentence "Let's talk about this function, it checks if your inventory is full, and then loops 28 times and buries a set of bones each loop." from the code the function is not checking if the inventory is full first. This may confuse those that are not as familiar with programming.

Again thank you for putting in the time to make this.

Link to comment
Share on other sites

Thanks! Glad you found the tutorial useful.

1 hour ago, Chingling said:

The only feedback I have is for the sentence "Let's talk about this function, it checks if your inventory is full, and then loops 28 times and buries a set of bones each loop." from the code the function is not checking if the inventory is full first. This may confuse those that are not as familiar with programming.

Good catch, I changed that section a little bit to make it more clear.

  • Like 1
Link to comment
Share on other sites

  • 8 months later...

Is it possible to get a full script? so i can analyze it cuz im not getting it to work by my self

im not finding many small scripts that are easy to analyze

 

 

Edited by BGBM
Link to comment
Share on other sites

  • Global Moderators
On 4/8/2022 at 9:16 PM, BGBM said:

Is it possible to get a full script? so i can analyze it cuz im not getting it to work by my self

im not finding many small scripts that are easy to analyze

 

 

Dont understand what you mean by a "full script". There are plenty of open sourced scripts on here where you could look at the code and see what's implemented. I would suggest starting there.

Link to comment
Share on other sites

  • 3 weeks later...

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

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.

×
×
  • Create New...