Jump to content

API, MethodProvider, and Bot - When to use Them


liverare

Recommended Posts

API (org.osbot.rs07.script.API)

When should you use this class? Well, when you're planning to build that stunning API. Think of it like this: you've got a bunch of thingy-bobs, such as bank, inventory, widgets, players, npcs, objects, etc etc, but there's always something missing! When you're working on something that has a bunch of moving parts that are all kind of related together, then it might be time to build yourself an API. For example, if I were to build a Duel Arena script, I would write an API to handle the bothersome bits of the staking and setup process, and even the duel itself. That way, I keep all my Duel Arena logicy-stuff out of my main script. Other benefits include:

  • SOLID!
  • Your API will have direct access to all those bank, inventory, widget, and other stuffs!
  • Reusable and maintainable. Your upkeep will be down to minimal changes here and there...unless the game goes through a drastic change again.

Also, this handy-dandy class has one thing I really like: an abstract method! When you use this class, you have to complete the initializeModule() method. So when might you use this? Well, if you're hooking up listeners (such as MessageListener) to the bot, or if you need to setup some CachedWidgets, and even initialise stuff.

"B-but why not initialise on the constructor?!" I hear you ask. Well, there's a chance you may wish to re-run that initialiseModule function twice in the same script. It'd be unusual, but it means you'd be resetting a bunch of stuff. I wrote a hunter tracking API and I frequently recalled that function, because the tracks change entirely and so the API needed 'cleaning'. If you do this, then be sure your second call clears lists and maps, and doesn't add duplicate listeners. Otherwise, things could go wrong.

Here's an example:

DuelArenaAPI

Spoiler

 


package com.duelarena.api;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import org.osbot.rs07.api.model.Item;
import org.osbot.rs07.api.model.Player;
import org.osbot.rs07.api.ui.Message;
import org.osbot.rs07.api.ui.Message.MessageType;
import org.osbot.rs07.api.ui.RS2Widget;
import org.osbot.rs07.listener.MessageListener;
import org.osbot.rs07.script.API;
import org.osbot.rs07.utility.ConditionalSleep;

public class DuelArenaAPI extends API implements MessageListener {

    static final int FIRST_SCREEN_WIDGET_ROOT = 482;
    
    static final int SECOND_SCREEN_WIDGET_ROOT = 481;
    static final int SECOND_SCREEN_YOUR_STAKE_AMOUNT = 17;
    static final int SECOND_SCREEN_YOUR_OPPONENT_INVENTORY = 31;
    
    static final int THIRD_SCREEN_WIDGET_ROOT = 476;
    
    static final int FIGHT_OVERVIEW_SCREEN_WIDGET_ROOT = 372;
    
    static final int RULES_CONFIG_ID = 286;
    
    Map<String, Long> requestsForAFight;
    
    RS2Widget firstScreen;
    RS2Widget secondScreen;
    RS2Widget thirdScreen;
    RS2Widget fightOverviewScreen;
    
    @Override
    public void initializeModule() {
        
        requestsForAFight = new WeakHashMap<>();
        
        bot.addMessageListener(this);
    }
    
    @Override
    public void onMessage(Message e) throws InterruptedException {
        
        String text = e.getMessage();
        
        if (e.getType() == MessageType.GAME) {
            
            if (text.endsWith(" wishes to duel with you.")) {
                
                requestsForAFight.put(e.getUsername(), e.getTime());
                
            } else if (text.equals("Your duel has been cancelled.")) {
                
                requestsForAFight.remove(e.getUsername());
            }
        }
    }
    
    // People stuff
    
    public boolean isBeingRequestedToFight(String username) {
        return requestsForAFight.containsKey(username);
    }
    
    public long getTimeOfWhenFightRequestWasMade(String username) {
        return requestsForAFight.getOrDefault(username, -1L);
    }

    public void clearFightRequestHistory() {
        requestsForAFight.clear();
    }
    
    public boolean challenge(String username) {
        Player p = players.closest(username);
        return p != null && p.interact("Challenge") && new SleepUntilDuelScreenOpens().sleep();
    }

    // First screen stuff

    public RS2Widget getFirstScreen() {
        
        if (firstScreen == null) {
            firstScreen = widgets.get(FIRST_SCREEN_WIDGET_ROOT, 1);
        }
        
        return firstScreen;
    }
    
    public boolean isFirstScreenOpen() {
        RS2Widget w = getFirstScreen();
        return w != null && w.isVisible();
    }
    
    public RS2Widget getAcceptButton() {
        return widgets.getWidgetContainingText(FIRST_SCREEN_WIDGET_ROOT, "Accept");
    }
    
    public RS2Widget getDeclineButton() {
        return widgets.getWidgetContainingText(FIRST_SCREEN_WIDGET_ROOT, "Decline");
    }
    
    public boolean isRuleEnabled(Rule rule) {
        return (configs.get(RULES_CONFIG_ID) & rule.mask) == rule.mask;
    }
    
    // Second screen stuff
    
    public RS2Widget getSecondScreen() {

        if (secondScreen == null) {
            secondScreen = widgets.get(SECOND_SCREEN_WIDGET_ROOT, 1);
        }
        
        return secondScreen;
    }

    public boolean isSecondScreenOpen() {
        RS2Widget w = getSecondScreen();
        return w != null && w.isVisible();
    }
    
    public long getYourStakedAmount() throws NumberFormatException {
        long amount = 0;
        RS2Widget w = widgets.get(SECOND_SCREEN_WIDGET_ROOT, SECOND_SCREEN_YOUR_STAKE_AMOUNT);
        String s;
        if (w != null) {
            s = w.getMessage();
            s = s.replaceAll(",|gp", "").trim();
            amount = Long.parseLong(s);
        }
        return amount;
    }
    
    public List<Item> getOpponentsInventoryItems() {
        List<Item> items = null;
        RS2Widget w = widgets.get(SECOND_SCREEN_WIDGET_ROOT, SECOND_SCREEN_YOUR_OPPONENT_INVENTORY);
        if (w != null) {
            items = Arrays.asList(w.getItems());
        }
        return items;
    }
    
    // Third screen stuff

    public RS2Widget getThirdScreen() {

        if (thirdScreen == null) {
            thirdScreen = widgets.get(THIRD_SCREEN_WIDGET_ROOT, 1);
        }
        
        return thirdScreen;
    }
    
    public boolean isThirdScreenOpen() {
        RS2Widget w = getThirdScreen();
        return w != null && w.isVisible();
    }

    // Fight overview screen stuff
    
    public RS2Widget getFightOverviewScreen() {

        if (fightOverviewScreen == null) {
            fightOverviewScreen = widgets.get(FIGHT_OVERVIEW_SCREEN_WIDGET_ROOT, 1);
        }
        
        return fightOverviewScreen;
    }
    
    public boolean isFightOverviewScreenOpen() {
        RS2Widget w = getFightOverviewScreen();
        return w != null && w.isVisible();
    }

    // Extra stuff
    
    private class SleepUntilDuelScreenOpens extends ConditionalSleep {

        public SleepUntilDuelScreenOpens() {
            super(2500);
        }

        @Override
        public boolean condition() throws InterruptedException {

            return isFirstScreenOpen();
        }

    }
    
    /**
     * First page - configurable rules
     */
    public static enum Rule {

        NO_RANGED(0x10, true),
        NO_MELEE(0x20, false),
        NO_MAGIC(0x40, true),
        NO_SPECIAL_ATTACK(0x2000, true),
        YES_FUN_WEAPONS(0x1000, false),
        NO_FOREFIT(0x1, true),
        NO_PRAYER(0x200, true),
        NO_DRINKS(0x80, true),
        NO_FOOD(0x100, true),
        NO_MOVEMENT(0x2, true),
        YES_OBSTACLES(0x400, false),
        NO_WEAPON_SWITCH(0x4, false),
        YES_SHOW_INVENTORY(0x8, true),

        NO_EQUIP_HEAD(0x4000, true),
        NO_EQUIP_CAPE(0x8000, true),
        NO_EQUIP_NECK(0x10000, true),
        NO_EQUIP_AMMO(0x8000000, true),
        NO_EQUIP_WEAPON(0x20000, false),
        NO_EQUIP_BODY(0x40000, true),
        NO_EQUIP_SHIELD(0x80000, true),
        NO_EQUIP_LEGS(0x200000, true),
        NO_EQUIP_HANDS(0x800000, true),
        NO_EQUIP_FEET(0x1000000, true),
        NO_EQUIP_RING(0x4000000, true);

        private final int mask;
        private final boolean required;

        private Rule(int mask, boolean required) {
            this.mask = mask;
            this.required = required;
        }

        public int getMask() {
            return mask;
        }

        public boolean isRequired() {
            return required;
        }

    }
}

 

 

 

Admittedly, not everything in this API snippet is best practice. That's because my original Duel Arena API is based on the old interfaces and setup. This was an last-minute-adhoc-scrape-together. But it works!

However, one thing you should not do is to extend an existing API, like bank. Why? Because OSBot already has an implementation of it. If you're expanding upon the bank API, then make it a separate API and name it accordingly (such as "ExtraBankAPI" or something). This also makes sure you're keeping inheriting to a minimum, so your objects are fine-gained. This is good, because inheriting an inherited inherit can get inheritingly messy!

Example Script

Spoiler

 


@ScriptManifest(author = "", info = "", logo = "", name = "Duel Arena", version = 0)
public class DuelArena extends Script {

    DuelArenaAPI duelArena;

    @Override
    public void onStart() throws InterruptedException {
        
        duelArena = new DuelArenaAPI();
        duelArena.exchangeContext(bot);
        duelArena.initializeModule();
        
    }

    @Override
    public int onLoop() throws InterruptedException {

        letsD_D_Duel();

        return 250;
    }
    
    private void letsD_D_Duel() {
        
        List<Item> opponentInventory;
        
        if (duelArena.isFirstScreenOpen()) {

            if (duelArena.isRuleEnabled(DuelArenaAPI.Rule.NO_MELEE)) {
                
                // I think we might be getting scammed...
            }
            
        } else if (duelArena.isSecondScreenOpen()) {
            
            logger.debug(duelArena.getYourStakedAmount());
            
            opponentInventory = duelArena.getOpponentsInventoryItems();
            
            if (opponentInventory != null) {
                
                opponentInventory.forEach(logger::debug);
            }
            
            // Do more stuff
            
        } else if (duelArena.isThirdScreenOpen()) {
            
            // Do even more stuff
            
        } else if (duelArena.isFightOverviewScreenOpen()) {
            
            duelArena.clearFightRequestHistory();
            
            // Don't stop now - get crazy good
            
        } else if (duelArena.isBeingRequestedToFight("Zezima")) {
            
            logger.debug("We got challenged by that dude! Quickly now!");
            
            if (duelArena.challenge("Zezima")) {
                
            }
        }
    }

}

 

 

 

MethodProvider (org.osbot.rs07.script.MethodProvider)

MethodProvider is very similar to API. In fact, API inherits it! So, why not just API? Because MethodProvider is a method provider. If you're not looking to build a custom API but you still need all those juicy methods, then use MethodProvider. For example, if your script design is based on a task/node system, then those tasks and nodes could extend MethodProvider. Your task/node are likely going to have a generic structure whereby no new methods are exposed to the public scope, and all instances of tasks and nodes are treated the same.

DuelArenaTask

Spoiler

 


public abstract class DuelArenaTask extends MethodProvider implements Comparable<DuelArenaTask> {

    private final int priorityValue;

    public DuelArenaTask(int priorityValue) {
        this.priorityValue = priorityValue;
    }

    @Override
    public int compareTo(DuelArenaTask o) {
        return Integer.compare(priorityValue, o.priorityValue);
    }

    public abstract boolean validate();

    public abstract void execute();
    
    public int getPriorityValue() {
        return priorityValue;
    }
}

 

 

 


ChallengeZezimaTask

Spoiler

 


public class ChallengeZezimaTask extends DuelArenaTask {

    Player zezima;

    public ChallengeZezimaTask() {
        super(500);
    }

    @Override
    public boolean validate() {

        if (zezima == null || !zezima.exists()) {

            zezima = players.closest("Zezima");
        }

        return zezima != null;
    }

    @Override
    public void execute() {

        if (zezima.interact("Challenge")) {

            // sleep until first screen opens
        }
    }

}

 

 

 

Example Script

Spoiler

 


@ScriptManifest(author = "", info = "", logo = "", name = "Duel Arena", version = 0)
public class DuelArena extends Script {

    List<DuelArenaTask> tasks;

    @Override
    public void onStart() throws InterruptedException {
        
        tasks = new ArrayList<>();
        tasks.add(new ChallengeZezimaTask());
        
        tasks.forEach(t -> t.exchangeContext(bot));
    }

    @Override
    public int onLoop() throws InterruptedException {

        tasks.stream()
            .filter(DuelArenaTask::validate)
            .sorted()
            .forEach(DuelArenaTask::execute);

        return 250;
    }

}

 

 

 

Bot (org.osbot.rs07.Bot)

Bot is basically the robot playing the game for you. That "context" can be used in instances where neither an API nor MethodProvider are required. For example, you may want to save a bunch of game data to a file. In this case, we can use a static method, pass the bot through as a parameter, and then extract the values from the bot, and store them.

Example Script

Spoiler

 


@ScriptManifest(author = "", info = "", logo = "", name = "Duel Arena", version = 0)
public class DuelArena extends Script {
    
    @Override
    public int onLoop() throws InterruptedException {

        stop(false);

        return 250;
    }

    @Override
    public void onExit() throws InterruptedException {
        
        try {
            saveStuffToAFile(bot, new File(getDirectoryData(), "\\stuff.txt"));
        } catch (IOException e) {
            logger.error(e);
        }
    }

    public static void saveStuffToAFile(Bot bot, File file) throws IOException {

        MethodProvider mp = bot.getMethods();

        try (FileWriter writer = new FileWriter(file, true)) { // "true" = append to existing file
            
            writer.write(String.format("User: %s", mp.myPlayer().getName()));
            writer.write(String.format("Time: %s", System.currentTimeMillis()));
            writer.write(String.format("Mehh: ", mp.skills.getExperience(Skill.ATTACK)));
            // etc.
        }
    }
    
}

 

 

 

This isn't the best example and I can't think of a more relevant one, but it's an example nonetheless.

If you recall, the exchangeContext for API and MethodProvider requires Bot to be passed in as a parameter so that these objects have access to the current 'robot' playing the game on your behalf. You could do something similar to that and perhaps have a class that doesn't expose any public methods you did not write yourself.

 

I really hope this guide has helped you. Thanks for reading!

 

Note: I may edit this multiple times, especially if the format f**ks up.

Edited by liverare
Damn you, [spoilers]!
  • Like 6
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

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.

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...