Pandemic 2675 Share Posted February 26, 2023 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: 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: 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 NurarihyonAlt, find me, Neffarion and 5 others 7 1 Link to comment Share on other sites More sharing options...
xyz111 68 Share Posted February 26, 2023 Thank you! Link to comment Share on other sites More sharing options...
HermesOSRS 10 Share Posted February 28, 2023 Thanks trying to learn the Dreambot Api so this definitely helps! Link to comment Share on other sites More sharing options...
0x1nsomnia 1 Share Posted January 14 (edited) 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 January 14 by 0x1nsomnia Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now