import java.io.*; import java.util.*; import java.text.*; import com.swath.*; import com.swath.cmd.*; /** * A mo' betta Macro script for SWATH. Reads a list of predefined macros from * the file macros.txt in the SWATH directory. You may also save * game-specific predefined macros in macros-(gamename).txt. These * files must be readable by java.util.Properties.load(). An * example is included with this script. You can also enter macros on the fly * from the script parameter dialog. * *

All macros can contain command sequences enclosed by curly braces. * Commands are case-insensitive. The following commands are recognized: * *

*
{CR}
Send a carriage return
*
{D:#}
Delay for a number of seconds
*
{T:text}
Wait for text from the game
*
* *

Requires Java 1.4. * *

Changes:
* *

  • (20030324) Added game-specific macros.
  • *
  • (20030324) Fixed a few boneheaded things.
  • *
  • (20030226) Changed the class name back to "Macro" from "Macro2"
  • *
  • (20030226) Can now be called from another script like a regular SWATH * command, or instantiated and executed repeatedly.
  • * * *

    This version still contains some debugging code. When reporting bugs, * please include the macro string you were using and any errors printed to * the SWATH console window. * * @author Mongoose <kjkrum@yahoo.com> * @version 20030324 */ public class Macro extends UserDefinedScript { protected static final String title = "Macro"; /* script parameters */ protected Parameter selectionP; /* predefined macro selection */ protected Parameter macroP; /* custom macro string */ protected Parameter repeatP; /* number of times to send */ protected Parameter autoP; /* send automatically or wait for click */ /* predefined macros */ protected Properties macroDefs; /* parsed macro tokens */ Token[] tokens; /** * Returns the name of this script. */ public String getName() { return title; } /** * Constructor for instantiating this class in another script. */ public Macro(String macro) throws ParseException, IllegalArgumentException { parse(macro); } /** * Contructor for running as a standalone script. Do not use this * constructor to instantiate a Macro in another script, or you * will get a NullPointerException when you call * exec() on the resulting object. */ public Macro() { } /** * Gets user input for script parameters. */ public boolean initScript() throws Exception { macroP = new Parameter("Custom macro:"); macroP.setType(Parameter.STRING); repeatP = new Parameter("Repeat count (0 = unlim.)"); repeatP.setType(Parameter.INTEGER); autoP = new Parameter("Run continuously?"); autoP.setType(Parameter.BOOLEAN); /* load macro definitions */ macroDefs = new Properties(); try { /* global macros */ File file = new File(Swath.main.swathPath(), "macros.txt"); if(file.canRead()) { InputStream in = new FileInputStream(file); macroDefs.load(in); in.close(); }else { PrintTrace.exec("No predefined macros found"); } /* game-specific macros */ file = new File(Swath.main.swathPath(), "macros-" + Swath.main.gameName() + ".txt"); if(file.canRead()) { InputStream in = new FileInputStream(file); macroDefs.load(in); in.close(); } else { PrintTrace.exec("No custom macros found for " + Swath.main.gameName()); } } catch(IOException e) { PrintTrace.exec("Error loading predefined macros"); } /* set up macro choices */ selectionP = new Parameter("Select a macro:"); selectionP.setType(Parameter.CHOICE); selectionP.addChoice(0, " Custom (enter below)"); Enumeration e = macroDefs.propertyNames(); for(int i = 1; e.hasMoreElements(); ++i) { selectionP.addChoice(i, (String)e.nextElement()); } registerParam(selectionP); registerParam(macroP); registerParam(repeatP); registerParam(autoP); return true; } /* end initScript() */ /** * Executes the script. */ public boolean runScript() throws Exception { try { // DEBUG int repeat = repeatP.getInteger(); if(repeat < 0) repeat = 0; boolean auto = autoP.getBoolean(); String macro; int choice = selectionP.getCurrentChoice(); if(choice == 0) macro = macroP.getString(); else macro = macroDefs.getProperty(selectionP.getChoice(choice)); try { parse(macro); } catch(ParseException e) { PrintTrace.exec("Error parsing macro: " + e.getMessage()); return false; } /* execute */ for(int count = 0; repeat == 0 || count < repeat; ++count) { if(auto || MessageBox.exec("Run macro?\nRun count: " + count, title, MessageBox.ICON_INFORMATION, MessageBox.TYPE_OK_CANCEL) == MessageBox.RES_OK) exec(); else break; } } catch(Exception e) { // DEBUG PrintTrace.exec(e); } return true; } /** * Parses a macro string into executable tokens. */ private void parse(String macro) throws ParseException, IllegalArgumentException { macro = macro.replaceAll("\\{[Cc][Rr]\\}", "\r"); Tokenizer tokenizer = new Tokenizer(macro); LinkedList list = new LinkedList(); while(tokenizer.hasMoreTokens()) list.add(tokenizer.nextToken()); tokens = (Token[])list.toArray(new Token[0]); } /** * Executes parsed tokens. */ public void exec() throws Exception { for(int i = 0; i < tokens.length; ++i) tokens[i].exec(); } /** * Executes the specified macro string. If you need to execute the same macro * repeatedly, it is more efficient to create an instance of this class and * call its exec() method. */ public static void exec(String macro) throws Exception { Macro m = new Macro(macro); m.exec(); } ///////////////////////////////// // inner classes // ///////////////////////////////// private class Tokenizer { private String macro; private int len; private int cursor; public Tokenizer(String macro) throws ParseException { if(macro == null || macro.length() == 0) throw new ParseException("empty string!", 0); this.macro = macro; len = macro.length(); cursor = 0; } public boolean hasMoreTokens() { return cursor < len; } public Token nextToken() throws ParseException { int nextCommand = macro.indexOf("{", cursor); if(nextCommand == cursor) { /* return command */ int commandEnd = macro.indexOf("}", nextCommand); if(commandEnd == -1) throw new ParseException("unclosed command sequence", nextCommand); String token = macro.substring(nextCommand + 1, commandEnd); cursor = commandEnd + 1; return parseCommand(token); } if(nextCommand == -1) { /* no more commands, just text */ String token = macro.substring(cursor, len); cursor = len; return new Text(token); } else { /* return text up to next command */ String token = macro.substring(cursor, nextCommand); cursor = nextCommand; return new Text(token); } } private Token parseCommand(String text) throws ParseException { int split = text.indexOf(':'); if(split == -1 || split == 0 || split == text.length()) throw new ParseException(text + ": \"command:argument\" expected", 0); String cmd = text.substring(0, split); String arg = text.substring(split + 1); if(cmd.toUpperCase().equals("D")) { try { return new Delay(Integer.parseInt(arg)); } catch(NumberFormatException e) { throw new ParseException(arg + ": numeric argument expected", 0); } } else if(cmd.toUpperCase().equals("T")) { return new Trigger(arg); } else throw new ParseException(text + ": unrecognized command", 0); } } /* end inner class Tokenizer */ private interface Token { public void exec() throws Exception; } /* end inner class Token */ private class Text implements Token { private final String text; public Text(String text) { this.text = text; } public void exec() throws Exception { SendString.exec(text); } } /* end inner class Text */ private class Delay implements Token { private final int interval; public Delay(int interval) throws IllegalArgumentException { if(interval < 1) throw new IllegalArgumentException(); this.interval = interval; } public void exec() throws InterruptedException { Thread.sleep(interval * 1000); } } /* end inner class Delay */ private class Trigger implements Token { private final String trigger; public Trigger(String trigger) { this.trigger = trigger; } public void exec() throws Exception { WaitForText.exec(trigger); } } /* end inner class Trigger */ } /* end class Macro */