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: * *
Requires Java 1.4. * *
Changes:
*
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 */