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
  • DreamBot 3 Miner Tutorial, Part 2


    Pandemic

    Recommended Posts

    Hello everyone, it has been a couple years since I released the first part of this so I thought it was about time to write the next part.

    Anywho, this will directly build off of what we had in Part 1, which you can find here:

    Prerequisites

    You should have already have a project with everything we added in Part 1.

    Project Layout

    This will be the final layout of the project once we're finished:

    layout.png

    The Script

    Just as in Part 1, let's define what our goals are for the end of this part:

    • We want to show a GUI to let the user choose if they want to power mine or bank at the nearest bank
      • Since all great scripts support QuickStart, we also want to support QuickStart parameters to avoid needing manual input from the GUI
    • We need to add a new task to handling banking, and extend our mining task to let it walk back once it's done banking

    src/gui/GUI.java, the script's GUI

    Here's the simple GUI that we'll be using:

    Spoiler
    package src.gui;
    
    import net.miginfocom.swing.MigLayout;
    import src.Miner;
    
    import javax.swing.*;
    
    public class GUI extends JFrame {
        public GUI(Miner script) {
            super();
    
            setTitle("Miner");
            setLayout(new MigLayout("fill, gap 5, insets 10"));
            setResizable(false);
    
            JLabel modeLabel = new JLabel("Mode:");
            JComboBox<Miner.Mode> modeComboBox = new JComboBox<>(Miner.Mode.values());
            JButton startScriptButton = new JButton("Start Script");
    
            // When the start script button is pressed, we let the script know which mode to run in and remove the GUI
            startScriptButton.addActionListener((_event) -> {
                Miner.Mode selectedMiningMode = (Miner.Mode) modeComboBox.getSelectedItem();
    
                script.setMode(selectedMiningMode);
    
                dispose();
            });
    
            // GUI layout:
            //
            // |-Label-|-ComboBox-|
            // |-----Button-------|
    
            add(modeLabel, "split"); // split will split the current row
            add(modeComboBox, "grow, wrap"); // grow will let it get as large as it can, wrap will end this row
            add(startScriptButton, "grow"); // on the second row now, let the button fill the entire row
    
            pack(); // Fit all of our components to the frame
            setLocationRelativeTo(null); // Center the frame on the screen
            setVisible(true); // Show the GUI
        }
    }

    The final GUI once we add the modes will look like this, just as we designed it above:

    GUI.png

    Points of Interest:

    • JFrame: Our GUI class extends JFrame, which is provided by Java's Swing
    • Constructor: Our constructor takes one parameter, our main script instance, so that we can let it know what mode to start the script in
    • MigLayout: You can see we set our frame's layout to use MigLayout, this is mostly just preference however I would highly recommend using it as it's pretty easy to learn and extremely powerful
    • JLabel/JComboBox/JButton: These are all Swing components that make up our GUI, see the link above for more examples of these and how to use them
    • JComboBox<Miner.Mode> modeComboBox: This is the dropdown that will let the users select which mode they want to use. It's provided all of Miner.Mode's possible values, which we'll add below. I prefer using enums instead of String's or anything else here simply because it's much harder to mess up and it allows us to add more (or change) modes in the future without ever worrying about the GUI class.

    src/Miner.java, the script's main class

    Show the GUI

    We have a few changes we have to make to our main class to show the new GUI and use the mode it sends over, so let's remove this line from our onStart:

    addNodes(new MiningTask(), new DropTask());

    and add this line in its place:

    SwingUtilities.invokeLater(() -> new GUI(this));

    This will create and pass in the script instance so the GUI can let the know script once the users chooses a mode. You should always perform any Swing component creation or modification on the AWT thread, which you can do just by wrapping it in that invokeLater.

    Add the Mode Enum

    Now we'll need to provide the script modes we want the GUI to show, so we add this at the bottom of our script class (but still before the final closing curly brace):

    public enum Mode {
        POWER_MINE, BANK;
    
        @Override
        public String toString() {
            // We override the toString so the GUI can show friendlier options than POWER_MINE to the user
    
            switch (this) {
                case POWER_MINE:
                    return "Power mine";
                case BANK:
                    return "Bank at the nearest bank";
                default:
                    return "Blame Yeeter";
            }
        }
    }

    Add the setMode Method

    Now we have the mode enum which the GUI will use to show available options, but we still need to create the setMode method we used in the GUI class, which you can add after your onPaint:

    /**
     * This adds the tasks necessary to run the script in the provided {@link Mode}
     * @param mode the script mode to use
     */
    public void setMode(Mode mode) {
        addNodes(new MiningTask()); // We always want to mine
    
        switch (mode) {
            case POWER_MINE:
                addNodes(new DropTask()); // Add the drop task if the user selected power mining
                break;
            case BANK:
                addNodes(new BankTask()); // Add the bank task if they chose banking
                break;
        }
    }

    QuickStart Support

    So now the GUI can set the script's mode, but what if you want to support QuickStart so you don't have to use a GUI every time?

    Supporting QuickStart script parameters is pretty easy, as the client will call onStart(String...) instead of the normal onStart we have now, so we can support it by adding in this new onStart after our current one:

    /**
     * This onStart is called only if the user starts the script using QuickStart with parameters.
     *
     * We check the parameters to see how to set the script up without needing to show a GUI that would require user
     * input.
     *
     * @param params the QuickStart parameters passed in from the command line
     */
    @Override
    public void onStart(String... params) {
        // Start DreamBot's skill tracker for the mining skill, so we can later see how much experience we've gained
        SkillTracker.start(Skill.MINING);
    
        if (params != null && params.length > 0) {
            String modeParameter = params[0]; // Grab the first parameter passed via QuickStart
    
            if ("powermine".equalsIgnoreCase(modeParameter)) {
                setMode(Mode.POWER_MINE);
            } else if ("bank".equalsIgnoreCase(modeParameter)) {
                setMode(Mode.BANK);
            } else {
                Logger.error("Unknown script mode: '" + modeParameter + "', try 'bank' or 'powermine' instead.");
                stop();
            }
        }
    }

    That's it!

    If you followed along above, our Miner.java class should look like this now:

    Spoiler
    package src;
    
    import org.dreambot.api.methods.skills.Skill;
    import org.dreambot.api.methods.skills.SkillTracker;
    import org.dreambot.api.script.Category;
    import org.dreambot.api.script.ScriptManifest;
    import org.dreambot.api.script.impl.TaskScript;
    import org.dreambot.api.utilities.Logger;
    import src.gui.GUI;
    import src.tasks.BankTask;
    import src.tasks.DropTask;
    import src.tasks.MiningTask;
    
    import javax.swing.*;
    import java.awt.*;
    
    // Every script needs a ScriptManifest so it can be seen in the script manager
    @ScriptManifest(category = Category.MINING, name = "Miner", description = "Mines anything, and drops inventory when full.", author = "Pandemic", version = 1.0)
    public class Miner extends TaskScript {
        @Override
        public void onStart() {
            // Start DreamBot's skill tracker for the mining skill, so we can later see how much experience we've gained
            SkillTracker.start(Skill.MINING);
    
            // Show our fancy new GUI to let the user set the script up
            SwingUtilities.invokeLater(() -> new GUI(this));
    
            // Since we no longer add nodes/tasks in our onStart, the script itself
            // won't do anything until the user handles the GUI
        }
    
        /**
         * This onStart is called only if the user starts the script using QuickStart with parameters.
         * <p>
         * We check the parameters to see how to set the script up without needing to show a GUI that would require user
         * input.
         *
         * @param params the QuickStart parameters passed in from the command line
         */
        @Override
        public void onStart(String... params) {
            // Start DreamBot's skill tracker for the mining skill, so we can later see how much experience we've gained
            SkillTracker.start(Skill.MINING);
    
            if (params != null && params.length > 0) {
                String modeParameter = params[0]; // Grab the first parameter passed via QuickStart
    
                if ("powermine".equalsIgnoreCase(modeParameter)) {
                    setMode(Mode.POWER_MINE);
                } else if ("bank".equalsIgnoreCase(modeParameter)) {
                    setMode(Mode.BANK);
                } else {
                    Logger.error("Unknown script mode: '" + modeParameter + "', try 'bank' or 'powermine' instead.");
                    stop();
                }
            }
        }
    
        @Override
        public void onPaint(Graphics2D g) {
            String experienceGainedText = String.format(
                    "Mining Experience: %d (%d per hour)", // The paint's text format. '%d' will be replaced with the next two arguments.
                    SkillTracker.getGainedExperience(Skill.MINING),
                    SkillTracker.getGainedExperiencePerHour(Skill.MINING)
            );
    
            // Now we'll draw the text on the canvas at (5, 35). (0, 0) is the top left of the canvas.
            g.drawString(experienceGainedText, 5, 35);
        }
    
        /**
         * This adds the tasks necessary to run the script in the provided {@link Mode}
         *
         * @param mode the script mode to use
         */
        public void setMode(Mode mode) {
            addNodes(new MiningTask()); // We always want to mine
    
            switch (mode) {
                case POWER_MINE:
                    addNodes(new DropTask()); // Add the drop task if the user selected power mining
                    break;
                case BANK:
                    addNodes(new BankTask()); // Add the bank task if they chose banking
                    break;
            }
        }
    
        public enum Mode {
            POWER_MINE, BANK;
    
            @Override
            public String toString() {
                // We override the toString so the GUI can show friendlier options than POWER_MINE to the user
    
                switch (this) {
                    case POWER_MINE:
                        return "Power mine";
                    case BANK:
                        return "Bank at the nearest bank";
                    default:
                        return "Blame Yeeter";
                }
            }
        }
    }

    src/tasks/BankTask.java, the banking task

    Now that the GUI (and QuickStart) can set the script mode, we need to add the final missing piece which is banking support:

    Spoiler
    package src.tasks;
    
    import org.dreambot.api.methods.Calculations;
    import org.dreambot.api.methods.container.impl.Inventory;
    import org.dreambot.api.methods.container.impl.bank.Bank;
    import org.dreambot.api.methods.interactive.Players;
    import org.dreambot.api.methods.walking.impl.Walking;
    import org.dreambot.api.script.TaskNode;
    import org.dreambot.api.utilities.Sleep;
    
    public class BankTask extends TaskNode {
        @Override
        public boolean accept() {
            // If our inventory is full, we should execute this task
            return Inventory.isFull();
        }
    
        @Override
        public int execute() {
            if (Bank.open()) { // Bank#open will return true if the bank is opened, otherwise it will walk towards the closest bank
                Bank.depositAllExcept(item -> item.getName().contains("pickaxe"));
            } else {
                // If Bank#open ended up walking, we should sleep awhile to avoid spam walking
                Sleep.sleepUntil(Walking::shouldWalk, () -> Players.getLocal().isMoving(), 1000, 100);
                return 100;
            }
    
            return Calculations.random(300, 600);
        }
    }

    So just like the DropTask class, the BankTask class will execute if the inventory is full. It's a fairly simple class that just walks towards the nearest bank and deposits everything (except your pickaxe). DreamBot knows about most of the banks in the game, and our web walker is capable of walking to a vast majority of the map without needing more information from the scripter. This is why you're able to just call Bank#open (or Walking#walk) from anywhere and it knows where to go, regardless of where you start this script.

    src/tasks/MiningTask.java, the mining task

    Back to our MiningTask class, we now need to add a couple things since now after banking we'll be lost and have no clue how to get back to the rocks we were mining earlier.

    First we'll add a new class member which is a Tile where we'll store the last mined rock location, just add this right after the first opening curly brace:

    public class MiningTask extends TaskNode {
        private Tile lastMinedRockTile = null;
      
        // the rest of the script

    Now we need to set this tile whenever we interact with a rock, so we can add that right after interacting with a rock:

    if (rock.interact("Mine")) { // If we successfully click on the rock
        lastMinedRockTile = rock.getTile(); // Set the last rock tile in case we need to walk back at some point
        Sleep.sleepUntil(this::isMining, 2500); // Wait until we're mining, with a max wait time of 2,500ms (2.5 seconds)
    }

    So now that tile is set after we interact with any rocks, so let's let the script walk us back here if we're too far away (most likely from banking) which we can do inside of our null check of the rock:

    if (rock == null) { // If there aren't any available rocks near us
        if (lastMinedRockTile != null && lastMinedRockTile.distance() > 8) { // and we're far from the last mined rock
            Walking.walk(lastMinedRockTile); // we should walk towards that rock
            Sleep.sleepUntil(Walking::shouldWalk, () -> Players.getLocal().isMoving(), 1000, 100);
            return 100;
        }
    
        // We should just wait until one's available
        return Calculations.random(500, 1000);
    }

    Finally, let's add one more check to our getClosestRock method to make sure we're close enough to it:

    /**
     * Finds the closest acceptable rock that we can mine
     *
     * @return The closest GameObject that has the name 'Rocks', with the action 'Mine', and non-null model colors
     * within 10 tiles
     */
    private GameObject getClosestRock() {
        return GameObjects.closest(object -> object.getName().equalsIgnoreCase("Rocks") &&
                object.hasAction("Mine") &&
                object.distance() < 10 && // new distance check here
                object.getModelColors() != null);
    }

    That's it!

    If you followed along above, our MiningTask.java class should look like this now:

    Spoiler
    package src.tasks;
    
    import org.dreambot.api.methods.Calculations;
    import org.dreambot.api.methods.container.impl.Inventory;
    import org.dreambot.api.methods.interactive.GameObjects;
    import org.dreambot.api.methods.interactive.Players;
    import org.dreambot.api.methods.map.Tile;
    import org.dreambot.api.methods.walking.impl.Walking;
    import org.dreambot.api.script.TaskNode;
    import org.dreambot.api.utilities.Sleep;
    import org.dreambot.api.wrappers.interactive.GameObject;
    
    public class MiningTask extends TaskNode {
        private Tile lastMinedRockTile = null;
    
        @Override
        public boolean accept() {
            // If our inventory isn't full and we're not mining, we should start
            return !Inventory.isFull() && !isMining();
        }
    
        @Override
        public int execute() {
            GameObject rock = getClosestRock();
    
            if (rock == null) { // If there aren't any available rocks near us
                if (lastMinedRockTile != null && lastMinedRockTile.distance() > 8) { // and we're far from the last mined rock
                    Walking.walk(lastMinedRockTile); // we should walk towards that rock
                    Sleep.sleepUntil(Walking::shouldWalk, () -> Players.getLocal().isMoving(), 1000, 100);
                    return 100;
                }
    
                // We should just wait until one's available
                return Calculations.random(500, 1000);
            }
    
            if (rock.interact("Mine")) { // If we successfully click on the rock
                lastMinedRockTile = rock.getTile(); // Set the last rock tile in case we need to walk back at some point
                Sleep.sleepUntil(this::isMining, 2500); // Wait until we're mining, with a max wait time of 2,500ms (2.5 seconds)
            }
    
            return Calculations.random(500, 1000);
        }
    
        /**
         * Finds the closest acceptable rock that we can mine
         *
         * @return The closest GameObject that has the name 'Rocks', with the action 'Mine', and non-null model colors
         * within 10 tiles
         */
        private GameObject getClosestRock() {
            return GameObjects.closest(object -> object.getName().equalsIgnoreCase("Rocks") &&
                    object.hasAction("Mine") &&
                    object.distance() < 10 &&
                    object.getModelColors() != null);
        }
    
        /**
         * For part 2, we'll consider our player doing any animation as mining
         * This will be improved/changed in a future part
         *
         * @return true if the player is mining, otherwise false
         */
        private boolean isMining() {
            return Players.getLocal().isAnimating();
        }
    }

     

    We're Done!

    Now just as before, just compile and build the artifact and try out the script.

    Attached below is the entire project source that you can use in case you got lost anywhere.

    If you have any questions or have anything you'd like to see included for the end result, please post it below.

    Thanks for reading!

    Miner-Part-2.zip

    Link to comment
    Share on other sites

    • 10 months later...

    Hi Pandemic, just want to thank you for sharing this. I'm not the best programmer and didn't really know much Java to get started with, so this really gave me a big boost.

    You mentioned it is possible to prioritize tasks -- could you share a resource or some info on that if and when you have time? I have a buying tasks for my script and the accept() method gets called, returns true, but instead of calling execute() of buy tasks, something happens and instead an entirely different accept() method of a different tasks is called so things break. Is this something to do with async stuff? What is a good way to prevent this from happening? Maybe it sounds like I am trying to do synchronous stuff and don't know how to properly handle async?

    Really appreciate any pointers or nudges in the right direction! Even any recommended resources on continuation of this TaskScript style is appreciated; I am either looking in the wrong spots or are just dumb :)

    EDIT: I still don't quite know exactly what I'm doing, but I got things working by instantiating my Buy and Sell task class
    inside of another task class being used, then calling Buy.execute() and Sell.execute() directly which feels very wrong and dirty (especially considering that completely skips .accept()), but it works for now 🤷‍♂️

    Edited by 0x1nsomnia
    Link to comment
    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
    ×
    ×
    • 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.