I've been interested in writing a bot for a while but doing 2000 laps of the Ape Atoll course is what finally broke me.
Here's the completed script I wrote for doing the course. It'll only work if you run it at the start or end tile of an obstacle. I tried to add code to get back on the course if the player went off, but I ran into a bunch of issues navigating to the start/end tiles and gave up. Please let me know about any Java best practices I missed and how I can improve/clean this up since it's my first script.
import com.epicbot.api.shared.APIContext;
import com.epicbot.api.shared.GameType;
import com.epicbot.api.shared.entity.ItemWidget;
import com.epicbot.api.shared.entity.SceneObject;
import com.epicbot.api.shared.methods.*;
import com.epicbot.api.shared.model.Tile;
import com.epicbot.api.shared.script.LoopScript;
import com.epicbot.api.shared.script.ScriptManifest;
import com.epicbot.api.shared.util.Random;
import com.epicbot.api.shared.util.paint.frame.PaintFrame;
import com.epicbot.api.shared.util.time.Time;
import java.awt.*;
@ScriptManifest(name = "ApeAtollAgility", gameType = GameType.OS)
public class ApeAtollAgility extends LoopScript {
private long startTime;
private int laps;
private int originalExperience;
private ICameraAPI camera() { return getAPIContext().camera(); }
private IInventoryAPI inventory() { return getAPIContext().inventory(); }
private ILocalPlayerAPI localPlayer() { return getAPIContext().localPlayer(); }
private IMouseAPI mouse() { return getAPIContext().mouse(); }
private IObjectsAPI objects() { return getAPIContext().objects(); }
private IWebWalkingAPI webWalking() { return getAPIContext().webWalking(); }
private IWalkingAPI walking() { return getAPIContext().walking(); }
private final String[] COURSE = { "Stepping stone", "Tropical tree", "Monkeybars", "Skull slope", "Rope", "Tropical tree" };
private final String[] ACTIONS = { "Jump-to", "Climb", "Swing Across", "Climb-up", "Swing", "Climb-down" };
private final Tile[] STARTS = { new Tile(2755, 2742, 0),
new Tile(2753, 2742, 0),
new Tile(2752, 2742, 2),
new Tile(2747, 2741, 0),
new Tile(2751, 2731, 0),
new Tile(2758, 2734, 0) };
private final Tile[] ENDS = { new Tile(2753, 2742, 0),
new Tile(2753, 2742, 2),
new Tile(2747, 2741, 0),
new Tile(2742, 2741, 0),
new Tile(2756, 2731, 0),
new Tile(2770, 2747, 0) };
private int missed = 0;
private boolean isIdle() {
return !localPlayer().isAnimating() && !localPlayer().isMoving();
}
private boolean isOnTile(Tile t) {
Tile currTile = localPlayer().getLocation();
return currTile.equals(t);
}
private boolean needStaminaDose() {
return walking().getRunEnergy() < Math.random() * 40 && !localPlayer().isStaminaActive();
}
private ItemWidget findStaminaDose() {
String fmt = "Stamina potion(%d)";
for (int i = 1; i <= 4; i++) {
ItemWidget stam = inventory().getItem(String.format(fmt, i));
if (stam != null) {
return stam;
}
}
return null;
}
private boolean shouldExit() {
if (!getAPIContext().client().isLoggedIn()) {
return false;
}
return findStaminaDose() == null && walking().getRunEnergy() < 20;
}
private void doHumanWait() {
double p = Math.random();
if (p < 0.01) {
int secs = Random.nextInt(90, 150);
getLogger().info(String.format("Long sleeping for %d seconds", secs));
mouse().moveOffScreen();
Time.sleep(secs * 1000);
} else if (p < 0.04) {
int secs = Random.nextInt(10, 20);
getLogger().info(String.format("Sleeping for %d seconds", secs));
mouse().moveOffScreen();
Time.sleep(secs * 1000);
}
}
private int findCurrentStep(Tile t) {
for (int i = 0; i < STARTS.length; i++) {
Tile s = STARTS[i];
if (t.equals(s)) {
return i;
}
}
for (int i = 0; i < ENDS.length; i++) {
Tile e = ENDS[i];
if (t.equals(e)) {
return (i + 1) % ENDS.length;
}
}
return -1;
}
private boolean doCourseStep() throws Exception {
Tile curr = localPlayer().getLocation();
int i = findCurrentStep(curr);
if (i == -1) {
if (missed >= 10) {
throw new Exception("Missed too many obstacles");
}
// usually only logs on the stepping stone since it "wakes" when on the stone
getLogger().info("Did not find current step");
missed++;
return false;
}
missed = 0;
String name = COURSE[i];
String action = ACTIONS[i];
Tile s = STARTS[i];
Tile e = ENDS[i];
getLogger().info(String.format("%s %s: %d", action, name, i));
SceneObject obstacle = objects().query().named(name).results().nearest();
if (obstacle == null) { // if we don't see it, move to the start
walking().walkOnMap(s);
Time.sleep(4000, () -> isOnTile(s) && isIdle());
} else { // if we see it, do the action on it
if (obstacle.interact(action)) {
doHumanWait();
Time.sleep(3000, () -> isOnTile(e) && isIdle());
}
}
if (i == COURSE.length - 1) {
laps++;
}
// Time.sleep(100);
return isOnTile(e);
}
private String convertToHHMMSS(long ms) {
int total = (int) (ms / 1000);
int h = total / 3600;
int m = (total % 3600) / 60;
int s = total % 60;
return String.format("%02d:%02d:%02d", h, m, s);
}
private int getExpGained() {
return getAPIContext().skills().agility().getExperience() - originalExperience;
}
@Override
protected int loop() {
if (shouldExit()) {
return -1;
}
if (!isIdle()) {
Time.sleep(600, () -> isIdle());
} else if (needStaminaDose()) {
ItemWidget stam = findStaminaDose();
if (stam != null) {
stam.interact("Drink");
}
} else {
try {
doCourseStep();
} catch (Exception e) {
getLogger().error(e.toString());
return -1;
}
}
return 690;
}
@Override
public boolean onStart(String... strings) {
if (!getAPIContext().client().isLoggedIn()) {
return false;
}
startTime = System.currentTimeMillis();
laps = 0;
originalExperience = getAPIContext().skills().agility().getExperience();
getLogger().info("Starting script");
ICameraAPI cam = camera();
cam.setPitch(98, false);
cam.setYawDeg(0, false);
return true;
}
@Override
protected void onPaint(Graphics2D g, APIContext ctx) {
if (!getAPIContext().client().isLoggedIn()) {
Time.sleep(15000, () -> getAPIContext().client().isLoggedIn());
}
long totalMillis = System.currentTimeMillis() - startTime;
PaintFrame frame = new PaintFrame();
frame.setTitle("Stats");
frame.addLine("Time elapsed: ", convertToHHMMSS(totalMillis));
frame.addLine("Total laps: ", laps);
frame.addLine("Exp gained: ", getExpGained());
frame.draw(g, 0, 90, ctx);
}
}