Welcome!

By registering with us, you'll be able to discuss, share and private message with other members of our community.

Sign up now!

Tutorial How to write an Alchemy script

Author of MaxiBots
Joined
Dec 3, 2013
Messages
7,032
Hey guys, just posting the source for my old alcher here as it was originally open source and fully commented. I'm moving it to my new framework and didn't want that to be public too :)

This isn't quite a tutorial in the normal sense of the word but it's so heavily commented that it may as well be :p

I've only included the main script here as the other classes are really just helpers. Enjoy!


Code:
package com.runemate.maxiscripts.looping.alchemy;

//Imports are all the classes that we are going to use methods from
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;

import com.runemate.api.Calculations;
import com.runemate.api.actionbar;
import com.runemate.game.api.client.paint.PaintListener;
import com.runemate.game.api.hybrid.Environment;
import com.runemate.game.api.hybrid.RuneScape;
import com.runemate.game.api.hybrid.entities.definitions.ItemDefinition;
import com.runemate.game.api.hybrid.local.Skill;
import com.runemate.game.api.hybrid.local.hud.interfaces.InterfaceWindows;
import com.runemate.game.api.hybrid.local.hud.interfaces.Inventory;
import com.runemate.game.api.hybrid.local.hud.interfaces.SpriteItem;
import com.runemate.game.api.hybrid.util.StopWatch;
import com.runemate.game.api.osrs.local.hud.interfaces.Magic;
import com.runemate.game.api.rs3.local.CombatMode;
import com.runemate.game.api.rs3.local.hud.Powers;
import com.runemate.game.api.rs3.local.hud.interfaces.eoc.ActionBar;
import com.runemate.game.api.rs3.local.hud.interfaces.eoc.ActionWindow;
import com.runemate.game.api.rs3.local.hud.interfaces.eoc.SlotAction;
import com.runemate.game.api.script.Execution;
import com.runemate.game.api.script.framework.LoopingScript;

/* LoopingScript is the script framework we're going to use. Extending it forces us to have the onLoop method.
* It also gives us access to a heap of methods like onStart, getEventDispatcher and setLoopDelay to name a few.
* By extending it, our class is now of that type. For a full list of methods we inherit from LoopingScript look in
* the api or type 'this.' to list all methods we have direct access to.
* Implementing PaintListener gives us access to the onPaint method which is what draws things on the screen.
* For it to actually draw on the screen though you need to add this class to the event dispatcher as we do in onStart.
*/
public class MaxiAlcher extends LoopingScript implements PaintListener, MouseListener{

    private boolean cast = false; //This will store whether the alchemy spell is currently selected
    private String status = "Loading..."; //this will be used to store and print out the status
    private final StopWatch runtime = new StopWatch(); //This will be used to show how long the script has been running
    private int casts = 0; //stores how many times we've cast a spell and will be displayed to the user
    private int startXp = -1; //this will store how much magic experience the user had when the script started.
    private int startLvl = -1; //this will store what the users magic level was when the script started
    private int xpGained = 0; //this will store how much magic experience the user has gained while running the script
    private int lvlsGained = 0; //this will store how many magic levels the user has gained while running the script
    private GUI gui; //this is an object of the gui that pops up when the script starts. without it the gui wouldn't show up
    private Rectangle showGUI = new Rectangle(445, 300, 60, 25);
 
    /* This method gets called automatically when the script first starts.
    * args is a parameter parsed to the script
    * String... is called a varargs and basically just means it can either be a single String, multiple Strings separated by a comma or a String array(String[])
    * @Override means that you've inherited this method from one of the classes you've extended or implemented and that you want to call this version of the method
    * instead of the one in the class it's inherited from
    */
    @Override
    public void onStart(String... args) {
        getEventDispatcher().addListener(this); //This tells the client that this class has events that need to be listened for. In this case it's for the PaintListener to draw on the screen.
        setLoopDelay(100, 200); //Sets the length of time in milliseconds to wait before calling onLoop again
        System.out.println(getMetaData().getName() + " V" + getMetaData().getVersion() + " started!"); //This just prints to the console and consequentially the log file saying 'Thanks for using MaxiAlcher!'
        gui = new GUI(); //This is where we actually instantiate the gui object.
    }

    //This is called when the script gets paused via the pause button on the client
    @Override
    public void onPause() {
        runtime.stop(); //Stops the runtime timer so that the runtime doesn't keep counting time while the script is paused
    }

    //This is called when the script is unpaused via the resume button on the client
    @Override
    public void onResume() {
        runtime.start(); //Resumes the runtime timer
    }

    //This is called just before the script stops
    @Override
    public void onStop() {
        //All of the println's below print the session stats to the console/log before the script stops
        System.out.println("---"+getMetaData().getName()+"---");
        System.out.println("Runtime: " + runtime.getRuntimeAsString());
        System.out.println("Last status: "+status);
        System.out.println("Level: " + Skill.MAGIC.getCurrentLevel() + "(+" + lvlsGained + ")");
        System.out.println("Xp gained: " + xpGained);
        System.out.println("Casts: " + casts);
        System.out.println("---Thanks for using " + getMetaData().getName() + "!---");
        if (gui != null) {
            gui.setVisible(false); //Makes sure the gui isn't visible
            gui.dispose(); //Tells the garbage collector that the gui's memory can be freed for use elsewhere
        }
    }

    //This gets called over and over and is the main body of your script.
    @Override
    public void onLoop() {
        if (RuneScape.isLoggedIn() && !gui.isVisible()) { //If we're logged in to the game and the gui isn't visible continue inside this set of {}
            /* If the startXp is still -1 (the initial value) and the players constitution experience is greater than 0 continue inside this set of {}
            * We check the constitution to make sure the user is fully logged in as to avoid false startXp values
            */
            if (startXp == -1 && Skill.CONSTITUTION.getExperience() > 0) {
                runtime.start(); //Start the runtime timer
                startXp = Skill.MAGIC.getExperience(); //Set the startXp to the players current magic experience
                startLvl = Skill.MAGIC.getBaseLevel(); //Set the startLvl to the players current magic level
            }

            if (Inventory.containsAllOf("Nature rune") && Inventory.containsAnyOf(gui.getItems())) { //If the Inventory contains at least one Nature rune and one of the item the user has said to alch continue inside this set of {}
                if (Environment.isOSRS() || CombatMode.LEGACY.isCurrent()) { //If we're playing osrs or the CombatMode is set to legacy continue inside this set of {}
                    if (InterfaceWindows.getMagic().isOpen()) { //If the magic interface is open continue inside this set of {}

                        /*
                        * This will set the status to 'Activating High Alchemy' if gui.highAlchemy() returns true
                        * If it returns false it will set the status to 'Activating Low Alchemy'.
                        * (gui.highAlchemy() ? "High Alchemy" : "Low Alchemy") is called a ternary statement
                        * and tells the script whether to say "High Alchemy" or "Low Alchemy"
                        * It's the same as writing the following:
                        * if (gui.highAlchemy() {
                        *     return "High Alchemy";
                        * }
                        * else {
                        *     return "Low Alchemy";
                        * }
                        */
                        status = "Activating " + (gui.highAlchemy() ? "High Alchemy" : "Low Alchemy");

                        /*
                        * This time we've got a ternary statement as the return type of the first ternary statement
                        * This single line is the same as writing the following 16 lines:
                        * if (Environment.isOSRS()) {
                        *     if (gui.highAlchemy()) {
                        *         Magic.HIGH_LEVEL_ALCHEMY.activate();
                        *     }
                        *     else {
                        *         Magic.LOW_LEVEL_ALCHEMY.activate();
                        *     }
                        * }
                        * else {
                        *       if (gui.highAlchemy()) {
                        *         Powers.Magic.HIGH_LEVEL_ALCHEMY.activate();
                        *     }
                        *     else {
                        *         Powers.Magic.HIGH_LEVEL_ALCHEMY.activate();
                        *     }
                        * }
                        */
                        if (Environment.isOSRS() ? (gui.highAlchemy() ? Magic.HIGH_LEVEL_ALCHEMY.activate() : Magic.LOW_LEVEL_ALCHEMY.activate()) : (gui.highAlchemy() ? Powers.Magic.HIGH_LEVEL_ALCHEMY.activate() : Powers.Magic.LOW_LEVEL_ALCHEMY.activate())) {
                            cast = true; //Set cast to true so the script knows the alchemy spell has been selected
                            Execution.delay(100, 200); //Wait a random amount of time between 100ms and 200ms before continuing
                        }
                    }//If the magic window wasn't open, check if the alchemy spell is selected. If not call openMagic()
                    else if (!cast) {
                        openMagic();
                    }
                    else {//If the magic window wasn't open and the alchemy spell is selected continue in here

                        if (InterfaceWindows.getInventory().isOpen()) {//if the inventory is open call castOn()
                            castOn();
                        }
                        else { //if the inventory isn't open, open it
                            if (InterfaceWindows.getInventory().open()) {
                                /*
                                * Here we wait for either the inventory to open or for 1 second to have passed.
                                * () -> is a new feature in java 8 and is called a lambda
                                * If i wasn't to use a lambda here this is what the code would have looked like:
                                * Execution.delayUntil(new Callable<Boolean>() {
                                *
                                *  @Override
                                *  public Boolean call() throws Exception {
                                *     return InterfaceWindows.getInventory().open();
                                *  }
                                * }, 1000);
                                *
                                * Lambdas strip away all that extra junk and leaves you with the functional line you actually wanted to write.
                                * () represents the call method and if it needed to take parameters we put the parameters like this (param1, param2, etc...) -> doSomething
                                */
                                Execution.delayUntil(() -> InterfaceWindows.getInventory().open(), 1000);
                            }
                        }
                    }
                }
                else if (cast){ //If we're in RS3 and the CombatMode isn't Legacy and we have selected the alchemy spell call castOn()
                    castOn();
                }
                else { //If we're in RS3 and the CombatMode isn't Legacy and we haven't selected the alchemy spell call alchEoC()
                    alchEoC();
                }
            }
        }
    }

    private void castOn() {
        final SpriteItem item = Inventory.getItems(gui.getItems()).first(); //Get the first item in the inventory with the name that the user specified in the gui
        if (item != null) { //If the item exists
            String name = "item";
            final ItemDefinition def = item.getDefinition();
            if (def != null) {
                final String temp = def.getName();
                if (temp != null && !temp.isEmpty()) {
                    name = temp;
                }
            }
            status = "Casting alchemy on " + name;
            if (item.interact("Cast")) { //Try to interact with the item and if successful
                cast = false; //Set cast to false as the spell is no longer selected
                casts++; //Increase our spell cast counter
                Execution.delayUntil(() -> InterfaceWindows.getMagic().isOpen(), 2000); //Wait until the magic window has opened again or until 2000 ms (2 seconds) has passed
            }
            else {
                cast = false;
            }
        }
    }
    private void alchEoC() {
        if (!ActionBar.isExpanded()) { //If the actionbar isn't expanded call expandActionbar()
            expandActionbar();
        }
        else { //If it is expanded
            final SlotAction action = actionbar.getSlot(gui.highAlchemy() ? "High Level Alch" : "Low Level Alch"); //Get the slot in the actionbar that contains the selected alchemy spell

            if (action != null) { //If there actually is a slot with the spell on it call activateAlchemy() and parse the actionbar slot as a parameter
                activateAlchemy(action);
            }
            else { //If there isn't a slot with the spell on it
                if (InterfaceWindows.getMagic().isOpen()) { //If the magic interface is open
                    final Powers.Magic spell = (gui.highAlchemy() ? Powers.Magic.HIGH_LEVEL_ALCHEMY : Powers.Magic.LOW_LEVEL_ALCHEMY); //Get the spell
                    status = "Adding "+(gui.highAlchemy() ? "High Level Alchemy" : "Low Level Alchemy")+" to the action bar";
                    if (spell != null) { //if the spell exists
                        ActionBar.Slot empty = actionbar.getEmptySlot(); //Get the first empty actionbar slot
                        if (empty != null) { //if there was an empty slot
                            if (actionbar.addToActionbar(spell.getComponent().getBounds(), empty)) { //Add the spell to the actionbar in the empty slot
                                if (ActionWindow.MAGIC_ABILITIES.close()) { //close the magic window as we no longer need it (the spell is now on the actionbar)
                                    Execution.delay(100, 200);
                                }
                            }
                        }
                    }
                }
                else { //If the magic interface isn't open call openMagic()
                    openMagic();
                }
            }
        }
    }

    private void expandActionbar() {
        status = "Expanding the action bar";
        if (ActionBar.toggleExpansion()) { //Attempt to toggle the actionbars expansion, if successful wait until it has expanded or until 1 second has passed
            Execution.delayUntil(() -> ActionBar.isExpanded(), 1000);
        }
    }

    private void activateAlchemy(SlotAction action) {
        final ActionBar.Slot slot = action.getSlot(); //Get the slot that contains the action
        if (slot != null) { //If the slot exists
            status = "Activating "+(gui.highAlchemy() ? "High Level Alchemy" : "Low Level Alchemy");
            if (slot.getAction().activate()) { //Activate the slot (cast the spell)
                cast = true;
                Execution.delay(100, 200);
            }
        }
    }

    private void openMagic() {
        status = "Opening spellbook";
        if (InterfaceWindows.getMagic().open()) { //Attempt to open the magic book and is successful
            Execution.delayUntil(() -> InterfaceWindows.getMagic().isOpen(), 1000);
        }
    }


    /*
    * This is where we put everything that we want to draw to the screen
    * Graphics2D is the class that contains all the paint methods
    */
    @Override
    public void onPaint(final Graphics2D g) {
        int x=50,y=50; //Set the initial drawing coordinates to 50 px to the right and down from the top left of the applet
        g.setColor(Color.black);
        g.fillRect(445, 300, 60, 25);
        g.setColor(Color.white);
        g.drawString("Settings", 450, 315);
        xpGained = startXp == -1 ? 0 : Skill.MAGIC.getExperience() - startXp; //Set the xpGained to 0 if we haven't set the startXp yet, otherwise set it to the currentXp - startXp
        final int lvl = Skill.MAGIC.getCurrentLevel(); //this will store the current magic level
        lvlsGained = (startLvl == -1 ? 0 : lvl - startLvl); //Same deal as with xpGained

        g.drawString("Version " + getMetaData().getVersion(), x, y+=15); //This will draw a string at the current x value and the current y value and then add 15 to the y value
        g.drawString("Runtime: " + runtime.getRuntimeAsString(), x, y+=15);
        g.drawString("Status: " + status, x, y+=15);
        if (gui != null) g.drawString("Type: " + (gui.highAlchemy() ? "High Alchemy" : "Low Alchemy"), x, y+=15); //If the gui exists draw the spell that the user selected
        g.drawString("Level: " + lvl + "(+" + lvlsGained + ")" , x, y+=15);
        g.drawString("Xp Gained(/hr): " + xpGained + "(/" + Calculations.getHourly(xpGained, runtime.getRuntime()) + ")", x, y+=15); //Draw the xpGained as well as the amount of Xp they're gaining every hour at the current rate
        g.drawString("Xp to next level: "+Skill.MAGIC.getExperienceToNextLevel(), x, y+=15);
        g.drawString("Casts(/hr): " + casts + "(/" + Calculations.getHourly(casts, runtime.getRuntime()) + ")", x, y+=15);
    }

    @Override
    public void mouseClicked(MouseEvent m) {
        if (showGUI.contains(m.getPoint())) {
            if (gui != null && !gui.isVisible())
                gui.setVisible(true);
        }
    }

    @Override
    public void mouseEntered(MouseEvent arg0) {}

    @Override
    public void mouseExited(MouseEvent arg0) {}

    @Override
    public void mousePressed(MouseEvent arg0) {}

    @Override
    public void mouseReleased(MouseEvent arg0) {}
}

And here's the manifest. If you don't write a manifest for your script it won't show in the script selector. Save it as a .xml file anywhere in your script folder
Code:
<manifest>
    <main-class>com.runemate.maxiscripts.looping.alchemy.MaxiAlcher</main-class>
    <name>MaxiAlcher</name>
    <description>Casts Low/High Alchemy</description>
    <version>1.0.5</version>
    <open-source>false</open-source>
    <hidden>false</hidden>
    <compatibility>
        <game-type>OSRS</game-type>
        <game-type>RS3</game-type>
    </compatibility>
    <categories>
        <category>MAGIC</category>
        <category>MONEYMAKING</category>
    </categories>
    <tags>
        <tag>alchemy</tag>
        <tag>money</tag>
    </tags>
    <tag>magic</tag><!--Required to publish on the bot store--><internal-id>alcher</internal-id>
</manifest>
 
Last edited:
Top