Skip to main content

Writing a basic cooker

In our API, a Task represents a modular, self-contained unit of work or action within your script. It encapsulates a specific action or behavior that your script needs to perform, such as interacting with objects, moving the player, or managing resources like health or inventory.

Modeling a script

When it comes to building a script, the first thing you want to do is model it. What tasks will your script do? What features will it have? How will you break it down? How much will you break it down? Well, you're going to have to decide all that.

For my first script I think I'll start with something small, perhaps a cooker. Breaking it down, all we will really do for now is cook raw fish and bank them!

cooker-tree-1

Now let's start putting that model into code

package cooking;

import cooking.tasks.BankTask;
import cooking.tasks.CookTask;
import org.rspeer.commons.ArrayUtils;
import org.rspeer.commons.logging.Log;
import org.rspeer.game.script.Task;
import org.rspeer.game.script.TaskScript;
import org.rspeer.game.script.meta.ScriptMeta;

@ScriptMeta(
name = "Cooker",
developer = "Doga",
version = 0.1,
desc = "My first ever script. Totally!"
)
public class Cooker extends TaskScript {

@Override
public void initialize() {
Log.info("Hello!");
}

@Override
public void shutdown() {
Log.info("Goodbye!");
}

@Override
public Class<? extends Task>[] tasks() {
return ArrayUtils.getTypeSafeArray(
CookTask.class,
BankTask.class
);
}
}
package cooking.tasks;

import org.rspeer.game.script.Task;
import org.rspeer.game.script.TaskDescriptor;

@TaskDescriptor(name = "Cooking the raw fish")
public class CookTask extends Task {

@Override
public boolean execute() {
return false;
}
}
package cooking.tasks;

import org.rspeer.game.script.Task;
import org.rspeer.game.script.TaskDescriptor;

@TaskDescriptor(name = "Banking the cooked fish")
public class BankTask extends Task {

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

So there's our skeleton, we don't have to worry about designing the script - just filling in the functionality now, which in this case sounds simple enough.


Prerequisites and actions

When it comes to functionality, the 2 things you need to consider are:

  • What actions will this task perform
  • What prerequisite conditions need to be met before this task can run
TaskActionPrerequisite
CookTaskCook the fishHave raw fish in inventory
BankTaskOpen bank, deposit cooked, withdraw rawsHave no raw fish in inventory

Implementation

Now that we've broken down our functionality, implementing will be a bit easier. Without further ado, let's get started!

The first condition we need in order to cook is to check if we have raw fish in the inventory. If we don't, then we skip execution of that task.

Backpack inv = Inventories.backpack();
if (!inv.contains(iq -> iq.names("Raw shrimps").results())) {
return false;
}

For BankTask, it's the complete opposite since we want no raw fish in the inventory. Simply invert that condition and there's our banking condition done.

I will be cooking in Rogues' Den since the fire is permanent and close to a bank. In order to cook fish, we need to use it on the fire. So let's get an instance of said fire...

SceneObject fire = SceneObjects.query()
.ids(43475)
.results()
.nearest();

Notice how I opted to use an ID here? That's because we want this very specific fire. It has a different ID to player-lit fires.

Let's add some debugging in case a user doesn't realise it's a Rogues' den cooker and tries cooking elsewhere.

if (fire == null) {
Log.warn("Make sure you start the script in Rogues den!");
return false;
}

Moving on to the actual cooking part, that involves using the fish on the fire, and then selecting an option in the Make-X interface to begin cooking. Lastly, we don't want to repeat this if we're already cooking.

Let's break that down and see exactly what we need:

  • If we're already cooking, don't try to cook. We will use our players animation to determine that
  • If the Make-X interface is open, select the cook all option
  • If the Make-X interface is not open, open it by using a fish on the fire

Checking your animation and then returning early:

if (Players.self().isAnimating()) {
return false;
}

Handling an interface would typically involve using the interfaces and components API. Luckily for you, this particular interface is a commonly used one so we have a higher level API to interact with it called Production

Production production = Production.getActive();
if (production != null && production.isOpen()) {
production.initiate(0);
} else {
inv.use(iq -> iq.names("Raw shrimps").results().first(), fire);
}

return true;

Looks easy enough, doesn't it? Time for banking. First up, opening the bank if it's not open

if (!Bank.isOpen()) {
Bank.open();
return true;
}

Now, actually depositing the cooked food and withdrawing more raws

Bank bank = Inventories.bank();
bank.depositAllExcept(iq -> iq.names("Raw shrimps").results());
bank.withdrawAll(iq -> iq.names("Raw shrimps").results().first());
return true;

There we go, our cooker is ready to test :)


Issues

Upon running it, I've ran into my first ever bug! When cooking an inventory of fish, the animation isn't constant. Between each fish, it resets for a short period and that may cause the script to think it's not cooking.

Fear not, as the API offers a specific sleep method that works very well for this - conditional sleep with a reset condition

Player self = Players.self();
if (self.isAnimating()) {
sleepUntil(() -> !self.isAnimating(), () -> self.isAnimating(), 2);
return false;
}

How does this work?

The first parameter is the condition we want to wait for. Since we're animating (cooking), we want to wait until we're not cooking, so the condition is that we are no longer animating.

Let's move onto the final parameter first, as it's easier to explain the second if you know this one. The number represents a timeout in ticks as a failsafe. That means it'll sleep until it's no longer animating OR the timeout of 2 ticks is reached.

The second parameter is a "reset" condition. If this condition is valid, it resets the timeout counter. So if 1 tick has passed and we're animating, then it'll reset it back to 0.

The full source code to the script created until this part can be found here!


Exercise

Hopefully you've all learnt something from writing this basic cooking script. Over the next few tutorials we will be expanding on it, making it do everything you can imagine. That's right - features ranging from GUIs, quickstart, progressive mode, restocking, 2 ticking, and 1 ticking. You're going to learn how to do all of this.

But for now, let's start with a small exercise if you're up for it. Write a script that crafts leather into leather gloves. If you need any help, we're always available on discord for questions!

Interested in contributing to the docs? Please let me know your GitHub name using a ticket in our Discord