Jump to content
Frequently Asked Questions
  • Are you not able to open the client? Try following our getting started guide
  • Still not working? Try downloading and running JarFix
  • Help! My bot doesn't do anything! Enable fresh start in client settings and restart the client
  • How to purchase with PayPal/OSRS/Crypto gold? You can purchase vouchers from other users
  • How to create custom listeners


    Neffarion

    Recommended Posts

    In this tutorial I'll be showing you how to create a custom Listener plus a simple snippet for a GrandExchangeListener which you can learn and use/modify if you want.


    DreamBot already provides a bunch of listeners with the default API (InventoryListener, MessageListener for example) which everyone can use and cover a lot of use cases. However, there are specific situations where you might want a custom-made listener to help your script work better and more efficiently.

    What do they do?
    On a basic level, they provide you a way to control the flow of your script. They "listen" for changes on a specific piece of data and lets you act immediately on it. For example, the MessageListener which comes with the DreamBot API lets you act as soon as you get someone saying something in game chat. This listener is basically checking for new messages and lets you know when there's one.


    If you want to learn more about Event Listeners afterwards, check the following wiki page about the Observer design pattern.

     

     


    BASIC FRAMEWORK

     


     

    First we are going to need to create a basic framework to write a listener
     

    EventInterface.java

    Spoiler
    
    
    package dreambot.tutorial.listeners.base;
    
    public interface EventInterface {
    
        void start();
    
        void run();
    
        void stop();
    
        void fire(Object... params);
    
    }

     

     

    AbstractEvent.java

    Spoiler
    
    
    package dreambot.tutorial.listeners.base;
    
    import org.dreambot.api.script.AbstractScript;
    
    import java.util.EventListener;
    
    public abstract class AbstractEvent implements EventInterface {
    
        protected AbstractScript script;
        protected EventListener parentEvent;
        private Thread thread;
        private volatile boolean run = true;
    
        public AbstractEvent(AbstractScript script) {
            this.script = script;
            this.parentEvent = script;
        }
    
        public boolean canRun() {
            return run;
        }
    
        public void setRun(boolean run) {
            this.run = run;
        }
    
        public Thread getThread() {
            return thread;
        }
    
        protected void setThread(Thread thread) {
            this.thread = thread;
        }
      
        @Override
        public void start() {
            setThread(new Thread(this::run));
            getThread().start();
        }
    
        @Override
        public void stop() {
            this.setRun(false);
            setThread(null);
        }
    
    }

     

     

    We are going to need this class for creating listeners. The interface enforces those specific methods to exist, and the class will house its own Thread which the listener will run on and some other things like a boolean "run" which can be used to stop the listener if needed

    Now we are going to make a singleton class to handle any listeners we create
     

    ListenerManager.java

    Spoiler
    
    
    package dreambot.tutorial.listeners;
    
    import dreambot.tutorial.listeners.base.AbstractEvent;
    import dreambot.tutorial.listeners.base.EventInterface;
    
    import java.util.HashSet;
    import java.util.Set;
    
    public final class ListenerManager {
    
        private static ListenerManager instance = null;
    
        private final Set<AbstractEvent> listeners = new HashSet<>();
    
        private ListenerManager() {
        }
    
        public static ListenerManager getInstance() {
            if (instance == null) {
                instance = new ListenerManager();
            }
    
            return instance;
        }
    
        public Set<AbstractEvent> getListeners() {
            return listeners;
        }
    
        public void addListener(AbstractEvent listener) {
            listener.start();
            this.listeners.add(listener);
        }
    
        public void removeListeners() {
            this.listeners.forEach(EventInterface::stop);
            this.listeners.clear();
        }
    
    }

     

     

     


    CREATING THE LISTENER

     


    Like I said before, I will be showing an example with the Grand Exchange. The goal is to be able to get a listener that will call back when an item is fully bought or sold

    We are going to need an Item "Wrapper" in this case since we need to save the state of the item on the Grand Exchange (you might not need it, depends on your listener and what you want to do)
    It's nothing complex. All we want is to enter a GrandExchangeItem in its constructor and save its info in a separate object (The wrapper in this case). Also need to implement the equals() function because we are going to need it. Since the class is simple, if you use IntelliJ, you can get the function generated for you.

     

    GrandExchangeItemWrapper.java

    Spoiler
    
    package dreambot.tutorial.listeners.wrappers;
    
    import org.dreambot.api.methods.grandexchange.GrandExchangeItem;
    import org.dreambot.api.methods.grandexchange.Status;
    
    public final class GrandExchangeItemWrapper {
    
        private final int id;
        private final int slot;
        private final int amount;
        private final int price;
        private final Status status;
        private final String name;
    
        public GrandExchangeItemWrapper(GrandExchangeItem item) {
            this.id = item.getID();
            this.slot = item.getSlot();
            this.amount = item.getAmount();
            this.price = item.getPrice();
            this.name = item.getName();
            this.status = item.getStatus() == null ? Status.EMPTY : item.getStatus();
        }
    
        public int getId() {
            return id;
        }
    
        public int getSlot() {
            return slot;
        }
    
        public int getAmount() {
            return amount;
        }
    
        public int getPrice() {
            return price;
        }
    
        public Status getStatus() {
            return status;
        }
    
        public String getName() {
            return name;
        }
    
        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
    
            GrandExchangeItemWrapper that = (GrandExchangeItemWrapper) o;
    
            if (that.status == null || this.status == null) return false;
            if (!name.equals(that.name)) return false;
            if (id != that.id) return false;
            if (slot != that.slot) return false;
            if (amount != that.amount) return false;
            if (price != that.price) return false;
    
            return status.equals(that.status);
        }
    
    }

     

     

    Now we make the listener interface for the Grand Exchange. This will have the methods which you will have to implement in your AbstractScript. We will want to have the item on the parameters so that we can determine which item got sold and bought, etc...

    GrandExchangeListener.java

    Spoiler
    
    
    package dreambot.tutorial.listeners.impl;
    
    import dreambot.tutorial.listeners.wrappers.GrandExchangeItemWrapper;
    
    import java.util.EventListener;
    
    public interface GrandExchangeListener extends EventListener {
    
        void onItemBought(GrandExchangeItemWrapper item);
    
        void onItemSold(GrandExchangeItemWrapper item);
    
    }

     

     

    This class is the brain of the listener. Here we will add our logic to check for changes and act accordingly.
    Since the "run()" method executes on a different thread, we have to add a "while" loop to keep checking for changes every X milliseconds. Since the Grand Exchange isn't that intensive in timing and actions, I just went with 1000ms sleep. But you might want to lower it if you need it to be updated more often.

    The way this particular event is working is by creating a "snapshot" of every slot in Grand Exchange, then waiting 1 second and doing it again. Then it checks the differences and anything that isn't the same gets thrown into the "fire(Object params...)" method.

    Then inside the "fire(Object params...)" we can deal with the items a bit more and determine which are buying orders and sell orders. Which we afterwards just need to call the methods on our GrandExchangeListener "event" interface which was coupled with our AbstractScript in the class Constructor.

    You should be careful about the "canVerify()" and "shouldStop()" function. You only want to do any difference checking if you can actually get any items from the game, or to put it in other words, your character is online. Also, the listener should only be working as long as the script is running. Otherwise, you are going to have a thread running all the time in the background, even if you stop the script.

     

    GrandExchangeEvent.java

    Spoiler
    
    package dreambot.tutorial.listeners.events;
    
    import dreambot.tutorial.listeners.base.AbstractEvent;
    import dreambot.tutorial.listeners.base.EventInterface;
    import dreambot.tutorial.listeners.impl.GrandExchangeListener;
    import dreambot.tutorial.listeners.wrappers.GrandExchangeItemWrapper;
    import org.dreambot.api.Client;
    import org.dreambot.api.methods.grandexchange.GrandExchange;
    import org.dreambot.api.methods.grandexchange.GrandExchangeItem;
    import org.dreambot.api.methods.grandexchange.Status;
    import org.dreambot.api.methods.interactive.Players;
    import org.dreambot.api.script.AbstractScript;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    public final class GrandExchangeEvent extends AbstractEvent implements EventInterface {
    
        private final GrandExchangeListener event;
    
        public GrandExchangeEvent(AbstractScript script) {
            super(script);
            this.event = (GrandExchangeListener) parentEvent;
        }
    
    
        @Override
        public final void run() {
            Map<Integer, GrandExchangeItemWrapper> current, next = null;
            List<GrandExchangeItemWrapper> difference;
    
            while (!shouldStop() && canRun()) {
                if (canVerify()) {
                    current = fetchItems();
                    if (current != null) {
                        difference = getDifference(next, current);
                        if (difference != null && !difference.isEmpty()) {
                            for (GrandExchangeItemWrapper i : difference) {
                                fire(i);
                            }
                        }
    
                        next = new HashMap<>(current);
                    }
                }
    
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException ignored) {
                }
            }
        }
    
    
        @Override
        public void fire(Object... params) {
            if (params != null && params.length > 0 && params[0] != null) {
                GrandExchangeItemWrapper item = (GrandExchangeItemWrapper) params[0];
                if (item.getStatus() != null && item.getName() != null && item.getId() != 0 && item.getPrice() != 0) {
                    if (item.getStatus() == Status.BUY_COLLECT) {
                        this.event.onItemBought(item);
                    } else if (item.getStatus() == Status.SELL_COLLECT) {
                        this.event.onItemSold(item);
                    }
                }
            }
        }
    
        private List<GrandExchangeItemWrapper> getDifference(Map<Integer, GrandExchangeItemWrapper> before,
                                                             Map<Integer, GrandExchangeItemWrapper> after) {
            if (before == null || after == null) {
                return null;
            }
    
            List<GrandExchangeItemWrapper> list = new ArrayList<>();
            for (Map.Entry<Integer, GrandExchangeItemWrapper> entry : before.entrySet()) {
                int slot = entry.getKey();
                GrandExchangeItemWrapper item1 = entry.getValue();
                GrandExchangeItemWrapper item2 = after.get(slot);
    
                if ((item1.getStatus() == Status.SELL_COLLECT || item1.getStatus() == Status.BUY_COLLECT) && item2.getStatus() == Status.EMPTY) {
                    continue;
                }
    
                if (!item1.equals(item2)) {
                    if (item1.getStatus() == Status.EMPTY) {
                        list.add(item2);
                    } else {
                        list.add(item1);
                    }
                }
            }
    
            return list;
        }
    
        private Map<Integer, GrandExchangeItemWrapper> fetchItems() {
            GrandExchangeItem[] items = GrandExchange.getItems();
            if (items != null && items.length > 0) {
                Map<Integer, GrandExchangeItemWrapper> map = new HashMap<>();
                for (int i = 0; i < items.length; i++) {
                    map.put(i, new GrandExchangeItemWrapper(items[i]));
                }
    
                return map;
            }
    
            return null;
        }
    
        private boolean canVerify() {
            return Client.isLoggedIn() && !Client.getInstance().getRandomManager().isSolving()
                    && Players.localPlayer() != null && Players.localPlayer().exists();
        }
    
        private boolean shouldStop() {
            return !Client.getInstance().getScriptManager().isRunning();
        }
    }

     

     

    Now to add all this in our AbstractScript!
    You need to implement the GrandExchangeListener which we created before and have the ListenerManager add the GrandExchangeEvent.

    Script.java

    Spoiler
    
    
    public class Script extends AbstractScript implements GrandExchangeListener {
    
        @Override
        public void onStart() {
            ListenerManager.getInstance().addListener(new GrandExchangeEvent(this));
        }
    
        @Override
        public int onLoop() {
            return 2000;
        }
    
        @Override
        public void onItemBought(GrandExchangeItemWrapper item) {
            MethodProvider.log("Item bought: " + item.getName());
        }
    
        @Override
        public void onItemSold(GrandExchangeItemWrapper item) {
            MethodProvider.log("Item sold: " + item.getName());
        }
    
    }

     

    If you compile the JAR, run this and look at the console while buying/selling things off the GE, you can notice it will catch all your offers and tell you the items name. Though you obviously might want to use it for more than this.

     

    Before ending this tutorial I need to mention one thing you should avoid doing.

    Never try to control your Character through the listener methods.
    I know this example doesn't make much sense. However, if you do this your script will most likely have issues with the mouse and glitch.

    Spoiler
    
        @Override
        public void onItemSold(GrandExchangeItemWrapper item) {
            Bank.open();
        }

     

    Only control your character in your onLoop().

    What you could do if you want to act on your character with the help of your listener is to have some kind of global boolean variable changed on the listener method and then check for that variable on the onLoop()



    And that's all, I hope you learned something new!

    All the files are located on this GitHub Repository
     

     

    Link to comment
    Share on other sites

    Thanks so much for this. It's exactly the kind of thing I need! There are certainly a few listeners I have in mind I'd like to implement.  I'm going to give this a whirl tomorrow and report back with my progress (and probably questions!). Tutorial is also well formatted and easy to follow.

    Link to comment
    Share on other sites

    I had a go at making my own listener and managed to create an animation listener! Animations that aren't to do with movement or idling will trigger the event. For now it only gives you the animationID from localPlayer but I could probably extend it to listen for all animations on the client and pass in the source (player/npc/object?) of the animation too.

    Spoiler

     

    Thanks for the help. I do have an issue though. The original listener I wanted was a menu interaction listener. These custom listeners rely on things that are already in the API (in your case the GE methods and in this case animations). However, menu interactions are not available on the api. So I was wondering if you had any tips for figuring out how I'd listen for those too, using this tutorial as a framework. By menu interactions I'm referring to the ones I mentioned in my previous topic:

    Thanks again, very useful stuff.

    Link to comment
    Share on other sites

    13 minutes ago, Succulent said:

    I had a go at making my own listener and managed to create an animation listener! Animations that aren't to do with movement or idling will trigger the event. For now it only gives you the animationID from localPlayer but I could probably extend it to listen for all animations on the client and pass in the source (player/npc/object?) of the animation too.

      Hide contents

     

    Thanks for the help. I do have an issue though. The original listener I wanted was a menu interaction listener. These custom listeners rely on things that are already in the API (in your case the GE methods and in this case animations). However, menu interactions are not available on the api. So I was wondering if you had any tips for figuring out how I'd listen for those too, using this tutorial as a framework. By menu interactions I'm referring to the ones I mentioned in my previous topic:

    Thanks again, very useful stuff.

    Nice job on that one!
    As for the menu listener, you need to check the API.
    You will need to keep track of the mouse and the menu actions your mouse is hovering. For instance 

    getMenu().getDefaultAction()

    Might help you find what action there is when your mouse moves over an item

    Link to comment
    Share on other sites

    4 minutes ago, Neffarion said:

    Nice job on that one!
    As for the menu listener, you need to check the API.
    You will need to keep track of the mouse and the menu actions your mouse is hovering. For instance 

    getMenu().getDefaultAction()

    Might help you find what action there is when your mouse moves over an item

    Yeah the menu api is good - but it wouldn't actually be accurate that those menu options were clicked. It would confirm that we were hovering over an option and we clicked the mouse button, but other APIs will have listeners for menu interactions that the actual osrs client receives/emits. I think to get this fully accurate I'd have to do much more complex stuff with injection or something. Perhaps in the future we'll get it added to the API! Thanks anyway :)

    Link to comment
    Share on other sites

    1 hour ago, Succulent said:

    Yeah the menu api is good - but it wouldn't actually be accurate that those menu options were clicked. It would confirm that we were hovering over an option and we clicked the mouse button, but other APIs will have listeners for menu interactions that the actual osrs client receives/emits. I think to get this fully accurate I'd have to do much more complex stuff with injection or something. Perhaps in the future we'll get it added to the API! Thanks anyway :)

    That is true, it might get messy implementing such thing. Maybe @Nuclear Nezz or @Pandemic could help you with that further if you ask them

    Link to comment
    Share on other sites

    Archived

    This topic is now archived and is closed to further replies.

    ×
    ×
    • Create New...

    Important Information

    We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.