Jump to content
SkrrtNick

A Cooks Tutorial [SDK Scripting Tutorial]

Recommended Posts

Introduction

I felt that Tribot needed a tutorial on how to write a script using the new SDK to assist those who may be trying to learn.

I'm not claiming to be an authority on the best way to do things, I am just hoping to help you get on the right track :) 

I've tried to capture as much as I can in this thread, but even still I have most definitely missed things - if there's anything you'd like to see added or if you have any questions please drop me a message or comment on this thread.

Cheers

 

Pre-Requisites:

A basic knowledge of java 

Unlike other clients, the Tribot SDK makes use of Optionals so it’s important you have a basic understanding of how Optionals work and how they are different to nullable objects. I recommend checking out this link if you need to get your head around them https://www.baeldung.com/java-optional

I can recommend using the oracle java tutorials at https://docs.oracle.com/javase/tutorial/ or doing the mooc.fi java programming course https://java-programming.mooc.fi/ 

Either Tribot VIP standard or premium

Intellij IDEA

Frameworks:

There are a number of scripting frameworks that have been open sourced and are available in the tutorial section of the forums, the options I am aware of are:

Task by Encoded
Node by Worthy
Decision Trees by Nullable
Behaviour Trees (kotlin) by Nullable

The frameworks vary in complexity and some are easier to grasp than others, in the context of beginning scripting I think you should look into each one and decide what is the easiest for you to begin with.

As this is a beginners tutorial, for the sake of simplicity I will be using Encoded’s Task framework.

Important links:
The Docs:

The Gradle Template:

The Query System:

 

 

Setting up our environment:

Spoiler

1. Open IntelliJ IDEA 

image.png.3730608c979a0c38987580e202e979fc.png

2. Click File > New > Project from Version Control

image.png.4f4e30a011499553688b534f24bc5500.png

3. Paste the URL for the tribot script template in repository URL https://github.com/TribotRS/tribot-script-template

image.png.c9f447973755644966f5643d964da65b.png

4. Select the directory you want to have your environment setup in (it needs to be an empty directory)

image.png.4b4587b6e4aa08ff0d9c9a3efc3682c1.png

5. Click Clone

image.png.2eada85bf05a604dd6061919f156a512.png

6. Wait until the template finishes downloading and click Trust Project if prompted

image.png.802bfdc4dcdaea8ec77787bad29187dd.png

7. Once all background tasks finish running, click File > Project Structure 

image.png.8107b9a512b7cd2456bb76d109300bd7.png

8. On the Project tab, ensure a Java 11 JDK is selected or click the Project SDK dropdown* 

JQfDT3jRe5.gif.087a1ebe732722a7eb2caf49c618931c.gif

If you do not have a Java 11 JDK downloaded you can download it from this dropdown menu

image.png.16ac1852d2a8c91420f0f73f66c47bbc.png

image.png.3e26b53b5c91205f9c1856ae477719bc.png

9. Click Apply

image.png.8c7a8a642d9453f66c950469029dff75.png

10. Click OK to close the window

11. File > Settings

image.png.e10f1970a513627faece9dd1f1b9cf3d.png

12. Build, Execution,  Deployment

image.png.3b834a0f8b021bb7d591da604227f8bc.png

13. Gradle

image.png.9a8f8b143edcf81de86edfc02995bfd8.png

14. Ensure the Gradle JVM is set to a Java 11 JDK

image.png.8a7c6a902a0f3ceda0945d62e58e5bb4.png

15. Click OK to close the window

16. Build your project

image.png.70ccbfd0a1ff49f052879f37e0a94b60.png

Creating a library for our framework

Spoiler

1. Create a directory under libraries named however you like - I will be using "framework"

image.png.d9fe2e7b7ba14e4c42b52755b52f1539.png

2. Create a "src" directory under the newly created directory

image.png.ae94066991949f8a561cbba80e06e4fb.png

3. Create a "scripts" directory under the "src" directory

image.png.34c8cd554a8126cef68e03372dbae937.png

4. Open settings.gradle.kts

image.png.3bd710c00228cf156042f45c15e772bb.png

5. Add a line: "include("libraries:directoryName")" where directoryname is the name of the directory you created

image.png.48c13154d87565935b45af695f8db90a.png

6. Click the Load Gradle Changes icon

image.png.5aff00ba15d9f5615c16bcf0a7ebd8fb.png

7. Confirm it worked by looking at the directories you created

image.png.1eaa01e5d5a5869a9de2717353e214fa.png

8. Create the framework files under framework/src/scripts

image.png.e0146152957ffc617fba5e6a7f8cb9ad.png

Create a place for your new script

Spoiler

1. Copy the my-script file and paste it under scripts

image.png.660597dab5294b4a38e6537b389962fe.png

image.png.12184629b784b571cce773ac3aa42362.png

2. Create a name for your script, in this example I will use cooks-assistant

image.png.4516802617d6e74eb7460f4313d0cc44.png

3. Open settings.gradle.kts

image.png.b12598724dc110daa815766fc57073fe.png

4. Add the line include(scripts:scriptName) where scriptName is the name of your script

image.png.6330e0d3a09d44738d49401041a7e180.png

5. Click the Load Gradle Changes icon

image.png.5aff00ba15d9f5615c16bcf0a7ebd8fb.png

6. Confirm that it worked

image.png.8a80fef0f467e07dd0471c0f7f206721.png

Add the framework to your new script

Spoiler

1. Go into build.gradle.kts under your script's root directory

image.png.907e460b8c5deda3246b32f172d9de68.png

2. Add the line: "implementation(project(":libraries:libraryName"))" where libraryName is the name of your library

image.png.eaa05c913b4b9f50def80c40eaaff555.png

3. Click the Load Gradle Changes icon

image.png.5aff00ba15d9f5615c16bcf0a7ebd8fb.png

Planning the script

Spoiler

In the interest of doing as much as we can with this exercise, we will be gathering the required items instead of relying on the Grand Exchange for getting them.

It's important to plan a script before you start coding, this helps in reducing the amount of time you spend refactoring and rewriting down the track. 

Lets look at getting an egg:

flowchart.thumb.png.d94ef3844f3ad5b48fc8bb1bae012bb9.png

This should make it fairly clear how we need to handle the task of getting an egg, before we've started actually writing any code

Collecting data

Spoiler

Most scripts are going to need a considerable amount of data gathered, this data is generally stored as a static constant. 

1. I have created a data package under my scripts folder

image.png.d77026053bd3f0871a6fee51725788ac.png

2. I have created a Constants class in the data package

image.png.c0a2bcffd759bf409a68f32c31ed5041.png

package scripts.data;

import org.tribot.script.sdk.types.Area;
import org.tribot.script.sdk.types.WorldTile;

public class Constants {
    public static final int EGG = 1944;
    public static final int BUCKET = 1925;
    public static final int BUCKET_OF_MILK = 1927;
    public static final int GRAIN = 1947;
    public static final int POT = 1931;
    public static final int POT_OF_FLOUR = 1933;

    public static final Area COW_PEN = Area.fromRadius(new WorldTile(3258, 3276, 0), 4);
    public static final Area CHICKEN_COOP = Area.fromRadius(new WorldTile(3230, 3298, 0), 2);
    public static final Area BUCKET_AREA = Area.fromRadius(new WorldTile(3228, 3291, 0), 2);
    public static final Area WHEAT_FARM = Area.fromRadius(new WorldTile(3158, 3299, 0), 4);
    public static final Area FLOUR_MILL_TOP = Area.fromRadius(new WorldTile(3166, 3307, 2), 2);
    public static final Area FLOUR_MILL_GROUND = Area.fromRadius(new WorldTile(3166, 3307, 0), 2);
    public static final WorldTile COOKS_KITCHEN = new WorldTile(3209, 3213, 0);

    public static final int FLOUR_BIN_SETTING = 695;

    public static final String[] COOK_DIALOGUE = {"What's wrong?", "Yes."};
}

Setting up an Enum to handle states

Spoiler

An enum should be used anytime we need to represent a fixed set of constants. An example in the context of this tutorial is an enum type that would have a field for an item id and a method to determine if the inventory contains the item id.

I have created an enum type called Ingredient in the data package of our script.

image.png.091b4b5557820bf4de730f80eb630b32.png

Using the Lombok annotation @AllArgsConstructor I don't need to worry about creating a constructor




package scripts.data;

import lombok.AllArgsConstructor;
import org.tribot.script.sdk.Inventory;

@AllArgsConstructor
public enum Ingredient {
    EGG(Constants.EGG),
    BUCKET_OF_MILK(Constants.BUCKET_OF_MILK),
    POT_OF_FLOUR(Constants.POT_OF_FLOUR),
    ;
    private int itemID;

    public boolean hasIngredient() {
        return Inventory.contains(this.itemID);
    }
}

Storing Variables

Spoiler

There are a few ways to go about storing variables that are used by your script.

One of the more common ones is to create a Vars object that is initialised at the start of the script and use that to get and set the values as your script runs.

Here I have created a Vars class under the data package.

image.png.3623628db2bfe029f94604d1b92f1a22.png

I've populated it with commonly used variables, as well as added a CurrentIngredient object which will be used to store the current ingredient being gathered.




package scripts.data;

import lombok.Getter;
import scripts.Ingredient;

@Getter
public class Vars {

    private boolean isRunning = true;

    private String status = null;
    
    private Ingredient currentIngredient = null;

    private static Vars instance = new Vars();

    public static Vars get() {
        return instance;
    }

}

Creating the Gathering Tasks

Spoiler

First I am going to create a package named tasks that sits under my scripts package:

image.png.1936896c358981b41e3543de4ea5a47d.png

Then I'll make a template task




package scripts.tasks;

import scripts.Priority;
import scripts.Task;

public class TemplateTask implements Task {
    @Override
    public Priority priority() {
        return Priority.MEDIUM;
    }

    @Override
    public boolean validate() {
        return false;
    }

    @Override
    public void execute() {

    }
}

Next I will copy and paste it 3 times (once for each item to gather):

image.png.dd04d3448121277aa75ec4b06d67c6fb.png

OK, let's start with the egg task




package scripts.tasks;

import org.tribot.script.sdk.Log;
import org.tribot.script.sdk.Waiting;
import org.tribot.script.sdk.query.Query;
import org.tribot.script.sdk.util.TribotRandom;
import org.tribot.script.sdk.walking.GlobalWalking;
import scripts.Priority;
import scripts.Task;
import scripts.data.Constants;
import scripts.data.Ingredient;
import scripts.data.Vars;

public class EggTask implements Task {
    @Override
    public Priority priority() {
        return Priority.MEDIUM;
    }

    @Override
    public boolean validate() {
        // We only want to execute this task when the current ingredient is equal to egg AND we do not have an egg in our inventory
        return Vars.get().getCurrentIngredient().equals(Ingredient.EGG) && !Vars.get().getCurrentIngredient().hasIngredient();
    }

    @Override
    public void execute() {
        if (!Constants.CHICKEN_COOP.containsMyPlayer()) {
            Log.info("We need to go to the chicken coop, to get an egg");
            if (GlobalWalking.walkTo(Constants.CHICKEN_COOP.getRandomTile()) && Waiting.waitUntil(Constants.CHICKEN_COOP::containsMyPlayer)) {
                //This code will execute if we have successfully clicked on a random tile in the chicken coop
                // and we have successfully waited until we are in the chicken coop
                Waiting.waitNormal(600, 90); // let's sleep before the next action
            } else {
                //this code will execute if we have either failed to walk to the random tile in the chicken coop,
                // or the waiting condition has timed out before we make it to the coop
                return; //lets return early as we don't want to execute anything underneath this
            }
        }
        if (takeEgg() && Waiting.waitUntil(TribotRandom.uniform(1800, 2400), () -> Vars.get().getCurrentIngredient().hasIngredient())) { 
            //this code will execute if the take egg method returns true AND waiting until the player has the curent ingredient succeeds
            //note that I've added a uniform timeout to the Waiting.waitUntil method here
            Waiting.waitNormal(600, 90);
        }
    }

    private boolean takeEgg() {
        return Query.groundItems() //An egg is a ground item, so let's query those
                .idEquals(Constants.EGG) //We only want ground items that have an id matching an eggs
                .findBestInteractable() // Let's make tribot decide which egg to get
                .map(egg -> egg.interact("Take")) //if there is an egg let's try to pick it up
                .orElse(false); // if there is not an egg, or taking it fails, let's return false
    }
}

Now let's move onto the milk task

package scripts.tasks;

import org.tribot.script.sdk.Inventory;
import org.tribot.script.sdk.Waiting;
import org.tribot.script.sdk.query.Query;
import org.tribot.script.sdk.util.TribotRandom;
import org.tribot.script.sdk.walking.GlobalWalking;
import scripts.Priority;
import scripts.Task;
import scripts.data.Constants;
import scripts.data.Ingredient;
import scripts.data.Vars;

public class MilkTask implements Task {
    @Override
    public Priority priority() {
        return Priority.MEDIUM;
    }

    @Override
    public boolean validate() {
        // We only want to execute this task when the current ingredient is equal to bucket of milk AND we do not have a bucket of milk in our inventory
        return Vars.get().getCurrentIngredient().equals(Ingredient.BUCKET_OF_MILK) && !Vars.get().getCurrentIngredient().hasIngredient();
    }

    @Override
    public void execute() {
        if (!Inventory.contains(Constants.BUCKET)) { //This code will only execute if we do not have a bucket - we'll need one before we can milk the cow!
            if (!Constants.BUCKET_AREA.containsMyPlayer()) {
                Vars.get().setStatus("Walking to bucket");
                if (GlobalWalking.walkTo(Constants.BUCKET_AREA.getRandomTile()) && Waiting.waitUntil(Constants.BUCKET_AREA::containsMyPlayer)) {
                    Waiting.waitNormal(600, 90);
                }
            }
            if (Constants.BUCKET_AREA.containsMyPlayer()) {
                Vars.get().setStatus("Taking Bucket");
                if (takeBucket() && Waiting.waitUntil(TribotRandom.uniform(1800, 2400), () -> Inventory.contains(Constants.BUCKET))) {
                    //this code will execute if the take bucket method returns true AND waiting until the inventory contains an item that matches the bucket item id
                    //note that I've added a uniform timeout to the Waiting.waitUntil method here
                    Waiting.waitNormal(600, 90);
                }
            }
            //lets return here, because assuming all the code has executed successfully the inventory will now contain a bucket and this code will not execute again
            return;
        }
        if (dairyCowDistance() > TribotRandom.normal(4, 6, 5, 1)) {
            // lets only trigger this code if a dairy cow is not nearby
            Vars.get().setStatus("Walking to cow pen");
            if (GlobalWalking.walkTo(Constants.COW_PEN.getRandomTile()) && Waiting.waitUntil(Constants.COW_PEN::containsMyPlayer)) {
                Waiting.waitNormal(600, 90);
            }
        }
        if (Constants.COW_PEN.containsMyPlayer()) {
            Vars.get().setStatus("Milking Cow");
            if (milkCow() && Waiting.waitUntil(TribotRandom.uniform(2400, 4800), () -> Vars.get().getCurrentIngredient().hasIngredient())) {
                //this code will execute if the milkCow method returns true AND waiting until the player has the curent ingredient succeeds
                //note that I've added a uniform timeout to the Waiting.waitUntil method here
                Waiting.waitNormal(600, 90);
            }
        }
    }

    private boolean takeBucket() {
        return Query.groundItems() //Like the egg a bucket is a ground item, so let's query those
                .idEquals(Constants.BUCKET) //We only want ground items that have an id matching an a bucket
                .findBestInteractable() // Let's make tribot decide which bucket to get
                .map(bucket -> bucket.interact("Take")) //if there is a bucket let's try to pick it up
                .orElse(false); // if there is not a bucket, or interacting with it fails, let's return false
    }

    private boolean milkCow() {
        return Query.gameObjects() //dairy cows are game objects so that's what we'll query
                .nameEquals("Dairy cow") //We only want game objects that have a name matching Dairy cow
                .findBestInteractable() // Let's make tribot decide which dairy cow to get to get
                .map(cow -> cow.interact("Milk")) //if there is a dairy cow let's try to milk it
                .orElse(false); // if there is not a cow, or interacting with it fails, let's return false
    }

    private int dairyCowDistance() {
        return Query.gameObjects() //dairy cows are game objects so that's what we'll query
                .nameEquals("Dairy cow") //We only want game objects that have a name matching Dairy cow
                .findClosest() // we only care about the closest in this case
                .map(cow -> cow.distance()) // let's grab the distance of the closest cow
                .orElse(100); // if there is no nearby cow, let's assume it is 100 tiles away
    }
}

Now let's move onto the flour task, usually it would be better to separate this code even further, with a PickWheatTask/MakeGrainTask... in order to ensure the code is concise and readable




package scripts.tasks;

import org.tribot.script.sdk.GameState;
import org.tribot.script.sdk.Inventory;
import org.tribot.script.sdk.Waiting;
import org.tribot.script.sdk.query.Query;
import org.tribot.script.sdk.util.TribotRandom;
import org.tribot.script.sdk.walking.GlobalWalking;
import scripts.Priority;
import scripts.Task;
import scripts.data.Constants;
import scripts.data.Ingredient;
import scripts.data.Vars;

public class FlourTask implements Task {
    @Override
    public Priority priority() {
        return Priority.MEDIUM;
    }

    @Override
    public boolean validate() {
        // We only want to execute this task when the current ingredient is equal to pot of flour AND we do not have an egg in our inventory
        return Vars.get().getCurrentIngredient().equals(Ingredient.POT_OF_FLOUR) && !Vars.get().getCurrentIngredient().hasIngredient();
    }

    @Override
    public void execute() {
        if (!Inventory.contains(Constants.POT)) { //This code will only execute if we do not have a bucket - we'll need one before we can milk the cow!
            int distance = TribotRandom.normal(3, 6, 4, 1);
            if (Constants.COOKS_KITCHEN.distance() > distance) {
                Vars.get().setStatus("Walking to Pot");
                if (GlobalWalking.walkTo(Constants.COOKS_KITCHEN) && Waiting.waitUntil(() -> Constants.COOKS_KITCHEN.distance() <= distance)) {
                    Waiting.waitNormal(600, 90);
                }
            }
            if (Constants.COOKS_KITCHEN.distance() <= distance) {
                Vars.get().setStatus("Taking Pot");
                if (takePot() && Waiting.waitUntil(TribotRandom.uniform(1800, 2400), () -> Inventory.contains(Constants.POT))) {
                    //this code will execute if the takePot method returns true AND waiting until the inventory contains an item that matches the pot item id
                    //note that I've added a uniform timeout to the Waiting.waitUntil method here
                    Waiting.waitNormal(600, 90);
                }
            }
            //lets return here, because assuming all the code has executed successfully the inventory will now contain a pot and this code will not execute again
            return;
        }
        if (isFlourBinEmpty()) {
            if (!Inventory.contains(Constants.GRAIN)) {
                if (!Constants.WHEAT_FARM.containsMyPlayer() && GlobalWalking.walkTo(Constants.WHEAT_FARM.getRandomTile()) && Waiting.waitUntil(Constants.WHEAT_FARM::containsMyPlayer)) {
                    //This code will execute if we are not initially in the wheat from
                    // then have successfully clicked on a random tile in the wheat farm
                    // and we have successfully waited until we are in the wheat farm
                    Waiting.waitNormal(600, 90); // let's sleep before the next action
                }
                if (Constants.WHEAT_FARM.containsMyPlayer()) {
                    Vars.get().setStatus("Picking Wheat");
                    if (pickWheat() && Waiting.waitUntil(TribotRandom.uniform(1800, 2400), () -> Inventory.contains(Constants.GRAIN))) {
                        //this code will execute if the pickWheat method returns true AND waiting until the inventory contains an item that matches the grain item id
                        //note that I've added a uniform timeout to the Waiting.waitUntil method here
                        Waiting.waitNormal(600, 90);
                    }
                }
                //lets return here, because assuming all the code has executed successfully the inventory will now contain grain and this code will not execute again
                return;
            }
            if (!Constants.FLOUR_MILL_TOP.containsMyPlayer()) {
                Vars.get().setStatus("Walking to the Top of the Flour Mill");
                if (GlobalWalking.walkTo(Constants.FLOUR_MILL_TOP.getRandomTile()) && Waiting.waitUntil(Constants.FLOUR_MILL_TOP::containsMyPlayer)) {
                    Waiting.waitNormal(600, 90); // let's sleep before the next action
                }
            }
            if (Constants.FLOUR_MILL_TOP.containsMyPlayer()) {
                if (fillHopper() && Waiting.waitUntil(() -> !Inventory.contains(Constants.GRAIN) && !MyPlayer.isAnimating())) {
                    Waiting.waitNormal(600, 90);// let's sleep before the next action
                }
                if (operateControls() && Waiting.waitUntil(() -> !isFlourBinEmpty())) {
                    // after operating with the hopper, the setting that stores the value for whether or not there is flour in the bin changes from 0 to 1
                    Waiting.waitNormal(600, 90);// let's sleep before the next action
                }
            }
        }
        if(!isFlourBinEmpty()){
            if (!Constants.FLOUR_MILL_GROUND.containsMyPlayer()) {
                Vars.get().setStatus("Walking to the Ground of the Flour Mill");
                if (GlobalWalking.walkTo(Constants.FLOUR_MILL_GROUND.getRandomTile()) && Waiting.waitUntil(Constants.FLOUR_MILL_GROUND::containsMyPlayer)) {
                    Waiting.waitNormal(600, 90); // let's sleep before the next action
                }
            }
            Vars.get().setStatus("Emptying Bin");
            if (emptyBin() && Waiting.waitUntil(TribotRandom.uniform(1800, 2400), () -> Vars.get().getCurrentIngredient().hasIngredient())) {
                //this code will execute if the emptyBin method returns true AND waiting until the player has the curent ingredient succeeds
                //note that I've added a uniform timeout to the Waiting.waitUntil method here
                Waiting.waitNormal(600, 90);
            }
        }
    }

    private boolean emptyBin() {
        return Query.gameObjects() // The flour bin is a game object, so let's query those
                .nameEquals("Flour bin") // We only want game objects named flour bin
                .findBestInteractable() // Let's make tribot decide which flour bin to get
                .map(wheat -> wheat.interact("Empty")) //if there is a flour bin let's try to empty it
                .orElse(false); // if there is no flour bin, or interacting with it fails, let's return false
    }

    private boolean operateControls() {
        return Query.gameObjects() // The hopper controls are a game object, so let's query those
                .nameEquals("Hopper controls") // We only want game objects named hopper controls
                .findBestInteractable() // Let's make tribot decide which hopper control to get
                .map(wheat -> wheat.interact("Operate")) //if there is a hopper controls object let's try to operate it
                .orElse(false); // if there is no hopper controls, or interacting with it fails, let's return false
    }


    private boolean fillHopper() {
        return Query.gameObjects() // The hopper is a game object, so let's query those
                .nameEquals("Hopper") // We only want game objects named hopper
                .findBestInteractable() // Let's make tribot decide which hopper to get
                .map(wheat -> wheat.interact("Fill")) //if there is a hopper object let's try to fill it
                .orElse(false); // if there is no hopper, or interacting with it fails, let's return false
    }

    private boolean pickWheat() {
        return Query.gameObjects() // Wheat is a game object, so let's query those
                .nameEquals("Wheat") // We only want game objects named wheat
                .findBestInteractable() // Let's make tribot decide which Wheat to get
                .map(wheat -> wheat.interact("Pick")) //if there is a wheat object let's try to pick it
                .orElse(false); // if there is no wheat, or interacting with it fails, let's return false
    }

    private boolean takePot() {
        return Query.groundItems() //the pot is a ground item, so let's query those
                .idEquals(Constants.POT) //We only want ground items that have an id matching a pot
                .findBestInteractable() // Let's make tribot decide which bucket to get
                .map(bucket -> bucket.interact("Take")) //if there is a bucket let's try to pick it up
                .orElse(false); // if there is not a bucket, or interacting with it fails, let's return false
    }

    private boolean isFlourBinEmpty() {
        // here we check if the flour bin has flour in it by checking the setting that stores the value, this value is gathered via the settings explorer
        return GameState.getSetting(Constants.FLOUR_BIN_SETTING) == 0;
    }
}

Goodness that's a lot of repeated code! Let's make it a bit tidier by making one method to handle all of our game object interactions! 







private boolean interactObject(String name, String action) {
    return Query.gameObjects() 
            .nameEquals(name) 
            .findBestInteractable()
            .map(wheat -> wheat.interact(action)) 
            .orElse(false); 
}

Great so now we have the code to gather flour, eggs and milk!

Turning in the Quest

Spoiler

Let's copy the TemplateTask class and paste it in the tasks package. I'll be calling mine "TurnInQuestTask"

image.png.483a192ef81de74abacb7dff759b05f2.png




package scripts.tasks;

import org.tribot.script.sdk.ChatScreen;
import org.tribot.script.sdk.Log;
import org.tribot.script.sdk.Waiting;
import org.tribot.script.sdk.query.Query;
import org.tribot.script.sdk.util.TribotRandom;
import org.tribot.script.sdk.walking.GlobalWalking;
import scripts.Priority;
import scripts.Task;
import scripts.data.Constants;
import scripts.data.Ingredient;
import scripts.data.Vars;

import java.util.Arrays;

public class TurnInQuestTask implements Task {
    @Override
    public Priority priority() {
        return Priority.MEDIUM;
    }

    @Override
    public boolean validate() {
        //we only want to execute this code when we have all the ingredients
        return Arrays.stream(Ingredient.values()).allMatch(Ingredient::hasIngredient);
    }

    @Override
    public void execute() {
        int distance = TribotRandom.normal(4, 1);
        if (Constants.COOKS_KITCHEN.distance() > distance) {
            Vars.get().setStatus("Walking to Cook");
            if (GlobalWalking.walkTo(Constants.COOKS_KITCHEN) && Waiting.waitUntil(() -> Constants.COOKS_KITCHEN.distance() <= distance)) {
                Waiting.waitNormal(600, 90);
            }
        }
        if(Constants.COOKS_KITCHEN.distance() <= distance){
            Vars.get().setStatus("Talking to Cook");
            if(talkToCook() && Waiting.waitUntil(()-> ChatScreen.isOpen())){
                Waiting.waitNormal(600, 90);
                if(ChatScreen.handle(Constants.COOK_DIALOGUE) && Waiting.waitUntil(()->Arrays.stream(Ingredient.values()).noneMatch(Ingredient::hasIngredient))){
                    Log.info("Successfully handed in quest");
                    //YAY! This code will only execute if we successfully handle the Cook's chatscreen AND our ingredients are taken from our inventory
                }
            }
        }
    }

    private boolean talkToCook(){
        return Query.npcs()//The cook is an npc, so let's query those
                .nameEquals("Cook") //We only want npcs named cook
                .findBestInteractable() // Let's make tribot decide which egg to get
                .map(cook -> cook.interact("Talk-to")) //if there is a cook let's try to Talk-to him
                .orElse(false); // if there is not a cook, or Talking to him fails, let's return false
    }
}

 

 Putting it together

Spoiler

First let's create a taskset in Vars which contains each of the tasks we've made!




private TaskSet tasks = new TaskSet(new EggTask(), new FlourTask(), new MilkTask(), new TurnInQuestTask());

Let's make some changes to our MyScript Class - starting with a rename (Refactor > Rename)

image.png.3386915879b8ff45b63283530e786b00.png

Let's update the script manifest with some meaningful information




@TribotScriptManifest(name = "A Cooks Tutorial", author = "SkrrtNick", category = "Quest", description = "Completes Cooks Assistant")

Let's remove the code from the script template in the execute method




@Override
public void execute(final String args) {

}

Now let's add a while loop that iterates over each of the tasks 




     while (Vars.get().isRunning()) {

         for (Task task : Vars.get().getTasks()) {
             if (task.validate()) {
                 task.execute();
             }
         }

         Waiting.waitUniform(20,40); // we need this sleep here to prevent iterating too quickly
     }

Great! 

Now we need a way to reassign an ingredient if we have it (or if the value is null e.g. at the start of the script)




    while (Vars.get().isRunning()) {

        if (Vars.get().getCurrentIngredient() == null || Vars.get().getCurrentIngredient().hasIngredient()) {
            //we only want this code to execute if we do not have a currentIngredient set, or if we already ahve the currentIngredient
            Arrays.stream(Ingredient.values()) // this converts the array of Ingredients into a Stream so that we can operate on it
                    .filter(ingredient -> !ingredient.hasIngredient()) // lets only choose from ingredients that we don't have
                    .findAny() // we can choose any as the order in which we grab ingredients does not matter
                    .ifPresent(nextIngredient -> Vars.get().setCurrentIngredient(nextIngredient)); // lets assign the result, if any to currentIngredient
        }

        for (Task task : Vars.get().getTasks()) {
            if (task.validate()) {
                task.execute();
            }
        }

        Waiting.waitUniform(20, 40); // we need this sleep here to prevent iterating too quickly
    }
}

Let's make sure the login bot and break handler are enabled by overriding the configure method and setting their values as true.


@Override
public void configure(@NotNull ScriptConfig config) {
    config.setRandomsAndLoginHandlerEnabled(true);
    config.setBreakHandlerEnabled(true);
}

Adding a Paint

Spoiler

Making a pretty paint has never been easier.

In the execute method of our main class (but not in the while loop) we can add this code:




PaintTextRow runningPaintTemplate = 
        PaintTextRow.builder()
        .background(new Color(120, 123, 128, 180)) // this is the bg colour for our rows
        .build();
BasicPaintTemplate paint = BasicPaintTemplate.builder()
        .row(PaintRows.scriptName(runningPaintTemplate.toBuilder())) //this method will use the manifest to grab the script name
        .row(PaintRows.runtime(runningPaintTemplate.toBuilder())) //this method will automatically print the value of runtime
        .row(runningPaintTemplate.toBuilder().label("Status")
                .value(() -> Vars.get().getStatus()).build()) // we can also display our own values 
        .row(runningPaintTemplate.toBuilder().label("Current Ingredient")
                .condition(()->Vars.get().getCurrentIngredient() != null) // we can assign conditions which will dictate whether or not a value is displayed
                .value(() -> Vars.get().getCurrentIngredient()).build()) 
        .location(PaintLocation.BOTTOM_LEFT_VIEWPORT)
        .build();
Painting.addPaint(i -> paint.render(i)); // this is the most important part of painting, actually doing the render

 

Video of the final product

 

You can also get my source here: https://github.com/SkrrtNick/a-cooks-tutorial

Edited by SkrrtNick
  • Like 14
  • Thanks 1
Link to post
Share on other sites
1 hour ago, trentnz said:

I’m not sure if I’m the only one struggling slightly but then again I’m not sure many would even attempt with zero coding experience 😂 

 

A video would make this a 10/10

 

Currently experimenting with AI written code but I need to get to that stage first 🤣

I would definitely suggest avoiding AI code if you want to learn - I assume you mean Chat GPT?

I would also suggest that you spend a little time learning Java before you start scripting!

cheers!

Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

  • Similar Content

    • By ELON
      Argument Support(save file name) Grand Exchange 25+ Quest Supported Mid Quest Support Safe spot Support Custom Equipment



      Death Support Shuffle Your Task List Advanced Custom Anti-Ban Prayer Support Buy Custom Equipment If Missing Walking Breaks ABC 2 Custom Equipment For Each Quest Automatic Attack Style Support For Each Equipment File Buy All Items Required Task And Much More  
    • By Final~Calibur
      Attention: The script has been retired. I'd like to thank everyone who supported FC scripts over the past couple of years! (See the last post for more details)
       
      FC Questing
       
      FC Questing is here for all to enjoy! This script is built on a framework which allows for easy chaining of quests, as well as new quests to be added with minimal effort. I will consistently add new quests to the script.
      Suicide Farm Edition:
      In addition to the full FC Questing script, I'm also offering a cheaper edition specifically for suicide farmers. The suicide farm edition will include the following:
      Tutorial Island Cook's Assistant Romeo & Juliet Sheep Shearer Pricing options are below.

      Features:
      Start anywhere Grand exchange support for quests that should utilize it Quest chaining Clean and detailed paint 15 quests supported and counting (GE indicates grand exchange support, GA indicates manual gathering support, SR indicates that the script will achieve skill requirements on it's own, QR indicates that the script will achieve quest point requirements on it's own)  Tutorial Island Doric's Quest GE GA Sheep Shearer GA Cook's Assistant GA Romeo & Juliet GA Rune Mysteries GA Imp Catcher GE Goblin Diplomacy GE Druidic Ritual GE Witch's Potion GE Prince Ali Rescue GE The Knight's Sword GE SR Ernest the Chicken GA The Restless Ghost GA Black Knight's Fortress GE QR Script Arguments:
      profile_name Simply type in the name of a previously saved profile, and it will be used! 7qp Romeo & Juliet Sheep Shearer Cook's Assistant all All supported quests, random order tut-ge Tutorial island Walks to GE after tutorial island tut Tutorial island Account Queue:
      FC Questing supports an account queue, meaning you can run many accounts through the script with ease. Simply create a file in the directory which is opened by the "load accts" button, and format it like so:
      username:password
      Each account must be on it's own line. The accounts will all be ran with the same settings that you have selected. If a banned / locked / invalid account is provided, it will skip over it.
      GE Support / Gathering:
      The script currently features Grand Exchange support for quests that should utilize it. Also, the script supports gathering items manually as well. Check the quest list above to see what each quest supports. Here are some things you should know:
      If supported, the script will attempt to purchase items from the GE first before gathering manually If the player doesn't have enough gold for all of the items, it will then resort to manually gathering the rest of the materials (if gathering is supported for the quest) The script will attempt to purchase items < 2,000 GP for double their market price, to try and avoid waiting. >= 2000 GP will be bought at 1.3 times their market price (this can be changed if you have suggestions) If the quest does not support manual gathering, and the GE process fails, the script will end The script will abort & resubmit any offers that don't sell instantly, for 15% higher (if the character has enough gp to do so). This helps avoid waiting around for items to sell. General Notes:
      As this is a quest script, there are a lot of moving parts. This almost ensures that bugs will pop up now and then. Please report them when they arise, provide a detailed description, and I will fix it as fast as possible.
      The script generally should be able to start in the middle of a quest, however please be aware that this may result in questionable script behavior and / or the quest not completing properly.
      Bug Reports / Suggestions / Questions:
      Please see the thread here on how to report an issue or suggestion. Direct posts on the thread or private messages will be redirected to here.
      Please join my discord channel here for other miscellaneous inquiries. This is the fastest way to contact me.
      Pricing for FC Questing (FULL):
      1 hour free trial (resets on the first of every month) $3.99 per month, 10 instances (great for the casual user) $9.99 per month, 30 instances (great for the large bot farmer) $14.99 per 6 months ($2.50 per month), 10 instances (great for the long term user) $39.99 per 6 months ($6.67 per month), 30 instances (great for the long term large bot farmer) Pricing for FC Questing Lite (TUTORIAL + 7QP):
      1 hour free trial (resets on the first of every month) $0.99 per month, 10 instances $2.49 per 3 months ($.83 cents per month), 10 instances $3.99 per 6 months ($.67 per month), 10 instances $9.99 per 6 months ($1.67 per month), 30 instances  
      Images:
       


      Time Lapse:
       
       
       
       
    • By Worthy
      |w| Quests
      By Worthy
      ·
      Features
      Gathers all auxiliary items for you Quick and easy to use GUI setup Sexy mouse paint Multi-threaded system for food support and combat defence Screenshots completed quests!
      All completed quests are put in your .tribot folder /Worthy Quests/Screenshots
      Flawless deathwalking ABCL AntiBan Intelligent World Hopping resource finder Chain Questing - see documentation on bottom of page ·
      Steps
      Enable Lite Mode. File > Settings Click the View stats and items button and make sure you have all the items specified in your inventory unless further specified. Turn off auto-retaliate. Input your accout details into the TRiBot Bot Manager Select a quest and, unless restarting it from a bug, leave the checkpoint on the default setting. Block user input when done. [NOTE] Detailed information on what the bot is doing can be found under the bot debug window: ·
       
      Easy 7 Minute Account Trade Setup
       

      Supported Quests
      Waterfall Quest Rune Mysteries Doric's Quest Monk's Friend Cook's Assistant Prince Ali Rescue Death Plateau Tree Gnome Village Dwarf Cannon The green quests are the most bug-free and tested. They work well almost every single time and can be relied upon without baby-sitting. Bugs in them are fixed with priority.
      The yellow quests should be baby-sat and may be buggy if using LG, or in general. Bugs here are fixed eventually. Do not buy the script if you plan on using the yellow quests only.
      The reason being, I plan on rewriting this questing script, from the ground up. This is one of the first scripts I wrote for TRiBot and I have not gotten around to rewriting yet with my learned experience and knowledge over the years. Supporting old code is time consuming and I would like to spend those efforts writing new code that is more reliable in the future  
       
      ·
      How to submit and bug report
      BUG REPORT 1) Name of quest: 2) Picture of screen (rs window (show inventory): I recommend gyazo/shareX 3) Detailed description of bug: 4) Bot Debug: Right click the tab and press copy to clipboard. I recommend to upload to www.pastebin.com 5) Client Debug: Example:
       

      ·
      In the Repository!
      --> Two Authorizations [monthly] (Player edition)
      --> Unlimited [lifetime] (Farming edition)

      ·
      Video Proggies
      ·
       
      ·
      Completed Quest Proggies:
      More proggies: http://imgur.com/a/wIHOS
      · ·
       
      Script Documentation and How-To:


      Enjoy!

    • By rewie
      - Completes Witch's potion quest
      - No GUI so can be used with script queue/client starter
      - Start at any point in the quest
      - Uses WebWalker so you can start from anywhere it supports
      - Let me know if there is any bugs/improvements
      - Gathers all the required items (see more in the spoiler)
      Change log:
      TO DO:
       
       
      Get it here
       
       
    • By killerjanne
      Hello!

      I'm releasing my Witch's House quest script. It's not fully tested yet but works great for me as it is now.
      Please make sure to babysit while running.
      And please write here if you get any errors 

      It safespot after two first monsters and all my testing has been at level 3 accounts.
      Req: none;
      Items needed:
      3 Cheese
      1 Leather gloves
      1 Staff of air
      500 Mind runes
      18 Trout
      You can start the quest everywhere Dax walker function.
      I recommend starting at Falador either with the items in bank or all of them in your inventory.

      Click here to try it now! :)
      Thanks to:
      Dax for his Webwalker and all the people helping me at tribots discord.
      EasyAsPie for helping me with packaging errors
  • Our picks

    • We've heard your complaints - the TRiBot API could be much easier to use. We've been dedicating our time to improving the scripter experience here and spent the past year working on a new and improved API - the TRiBot Script SDK.

       

      The TRiBot Script SDK is an easy-to-use library for building TRiBot scripts. It is the recommended approach to building scripts moving forward over the old TRiBot API. It contains all the core things you need to build a script, and a ton of additional helpful stuff to get you using your scripts quicker. See the documentation section for everything offered, and check out the brief overview link too.

       

      The SDK was announced in preview here:

       

      It is now officially released. The official release guarantees we will support backwards compatibility for some period of time. See the 'backwards compatibility' section below for more info.

       

      How to use:

      There is multiple options, listed in the order they are recommended.

      1) Use the gradle template mentioned below

      2) Obtain through gradle

      Add this dependency: api("org.tribot:tribot-script-sdk:+")

      Add this repository: maven("https://gitlab.com/api/v4/projects/20741387/packages/maven")

      3) Take from your local filesystem in your .tribot/install folder. For example, on windows, you'd find it at "C:\Users\[user]\AppData\Roaming\.tribot\install\tribot-client\lib\tribot-script-sdk-[version].jar"

       

      Documentation:

      Java docs: https://runeautomation.com/docs/sdk/javadocs/index.html?overview-summary.html

      Kotlin docs: https://runeautomation.com/docs/sdk/kdocs/index.html

       

      Backwards compatibility:

      We will be following a deprecation schedule whenever we perform a possible breaking change. We will deprecate the respective methods or classes and announce it in a topic. It will remain deprecated for some period of time (weeks or months), and then removed. This will give you time to fix anything, if we need to make a breaking change.

       

      Gradle template:

      Easily build scripts with a new pre-configured scripting gradle template

       

      Users of the current API:

      There is no plans to remove TRiBot API. It will still be available. However, non-critical bugs probably won't be fixed. The SDK does depend on some of the API so fixing some things in the SDK will indirectly fix the API. However, bugs that have existed in the API for awhile will likely not be fixed. It's recommended to use the SDK moving forward. Let us know if there's something the SDK is missing.

       

      Brief overview of the changes:

       

      Bug reports:

      Post bug reports in the bug reports section of the forums

       

       

      Let us know what your thoughts are! If you have questions, feel free to ask below or in discord.

       
        • Like
      • 0 replies
    • Support for the gradle launcher is being dropped. Read more about the new launcher here.
        • Like
      • 8 replies
    • What to expect from TRiBot moving forward.
        • Thanks
        • Like
      • 11 replies
    • TRiBot 12 Release Candidate

      The TRiBot team has been hard at work creating the last major version of TRiBot before the TRiBot X release. We've noticed many problems with TRiBot 11 with a lot of users preferring TRiBot 10 over 11. We've heard you, so we took TRiBot 10, added the new features introduced with 11, introduced some other new things, and created TRiBot 12. So without further adieu, here's TRiBot 12.
        • Sad
        • Like
      • 40 replies
    • Gradle is a build tool used to accelerate developer productivity.

      We recently setup a Maven repository (TRiBot Central) to make it easier for scripters to create scripts. Check it out here: https://gitlab.com/trilez-software/tribot/tribot-central/-/packages

      Furthermore, we've released a simple Gradle project to make it easy to run TRiBot and develop scripts for it. Check it out here: https://gitlab.com/trilez-software/tribot/tribot-gradle-launcher

      The goals of TRiBot Central are to:

      Deliver updates to TRiBot faster


      Better organize TRiBot's dependencies (AKA dependancies)


      Make it easier to develop scripts for TRiBot


      Make it easier to use and run TRiBot


      Note: TRiBot won't be able to run scripts from within this project until TRiBot's next release.
        • Like
      • 15 replies
  • Recently Browsing   0 members

    No registered users viewing this page.

×
×
  • Create New...