/** * Warlock, the open-source cross-platform game client * * Copyright 2008, Warlock LLC, and individual contributors as indicated * by the @authors tag. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package cc.warlock.core.stormfront.script.wsl; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Stack; import java.util.concurrent.BlockingQueue; import java.util.concurrent.TimeUnit; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.antlr.runtime.CharStream; import org.antlr.runtime.CommonTokenStream; import org.antlr.runtime.RecognitionException; import cc.warlock.core.client.IStream; import cc.warlock.core.client.IWarlockClientViewer; import cc.warlock.core.script.AbstractScript; import cc.warlock.core.script.IMatch; import cc.warlock.core.script.IScriptCommands; import cc.warlock.core.script.IScriptEngine; import cc.warlock.core.script.IScriptInfo; import cc.warlock.core.script.configuration.ScriptConfiguration; import cc.warlock.core.script.internal.RegexMatch; import cc.warlock.core.stormfront.client.IStormFrontClient; import cc.warlock.core.stormfront.script.IStormFrontScriptCommands; import cc.warlock.core.stormfront.script.internal.StormFrontScriptCommands; public class WSLScript extends AbstractScript { private int debugLevel = 0; protected double delay = 0.0; private HashMap<String, Integer> labels = new HashMap<String, Integer>(); private int nextLine = 0; private WSLAbstractCommand curCommand; private String curLine; private HashMap<String, IWSLValue> specialVariables = new HashMap<String, IWSLValue>(); private HashMap<String, IWSLValue> localVariables = new HashMap<String, IWSLValue>(); private Stack<WSLFrame> callstack = new Stack<WSLFrame>(); private Thread scriptThread; private Pattern commandPattern = Pattern.compile("^([\\w]+)(\\s+(.*))?"); private boolean lastCondition = false; private ArrayList<WSLAbstractCommand> commands = new ArrayList<WSLAbstractCommand>(); private ArrayList<WSLMatch> matches = new ArrayList<WSLMatch>(); private BlockingQueue<String> matchQueue; protected WSLEngine engine; protected IStormFrontScriptCommands scriptCommands; public WSLScript (WSLEngine engine, IScriptInfo info, IWarlockClientViewer viewer) { super(info, viewer); this.engine = engine; if(!ScriptConfiguration.instance().getSupressExceptions().get()) debugLevel = 1; scriptCommands = new StormFrontScriptCommands(viewer, this); setSpecialVariable("rt", new WSLRoundTime()); setSpecialVariable("monstercount", new WSLMonsterCount()); setSpecialVariable("lhand", new WSLLeftHand()); setSpecialVariable("rhand", new WSLRightHand()); setSpecialVariable("spell", new WSLSpell()); setSpecialVariable("roomdesc", new WSLComponent(IStormFrontClient.COMPONENT_ROOM_DESCRIPTION)); setSpecialVariable("roomexits", new WSLComponent(IStormFrontClient.COMPONENT_ROOM_EXITS)); setSpecialVariable("roomplayers", new WSLComponent(IStormFrontClient.COMPONENT_ROOM_PLAYERS)); setSpecialVariable("roomobjects", new WSLComponent(IStormFrontClient.COMPONENT_ROOM_OBJECTS)); setSpecialVariable("roomtitle", new WSLRoomTitle()); setSpecialVariable("lastcommand", new WSLLastCommand()); } public IWSLValue getVariable(String name) { // these values are maintained by the script IWSLValue val = specialVariables.get(name); if (val != null) return val; // return value from settings. All user global variables are stored here String var = getSFClient().getVariable(name); if (var != null) return new WSLString(var); return null; } public boolean variableExists(String name) { return specialVariables.containsKey(name) || getSFClient().getVariable(name) != null; } public boolean localVariableExists(String name) { return localVariables.containsKey(name); } public IWSLValue getLocalVariable(String name) { return localVariables.get(name); } private class WSLFrame { private int line; private HashMap<String, IWSLValue> localVariables; public WSLFrame(int line, HashMap<String, IWSLValue> variables) { this.line = line; this.localVariables = variables; } public void restore() { WSLScript.this.localVariables = localVariables; curCommand = commands.get(line); nextLine = line; while(curCommand == null) { nextLine++; if(nextLine >= commands.size()) break; curCommand = commands.get(nextLine); } } } private class WSLRoundTime extends WSLAbstractNumber { public double toDouble() { return getSFClient().getRoundtime().get(); } } private class WSLMonsterCount extends WSLAbstractNumber { public double toDouble() { return getSFClient().getMonsterCount().get(); } } private class WSLLeftHand extends WSLAbstractString { public String toString() { return getSFClient().getLeftHand().get(); } } private class WSLRightHand extends WSLAbstractString { public String toString() { return getSFClient().getRightHand().get(); } } private class WSLSpell extends WSLAbstractString { public String toString() { return getSFClient().getCurrentSpell().get(); } } private class WSLRoomTitle extends WSLAbstractString { public String toString() { IStream roomStream = getSFClient().getStream("room"); if(roomStream == null) return ""; return roomStream.getFullTitle(); } } private class WSLComponent extends WSLAbstractString { protected String componentName; public WSLComponent(String componentName) { this.componentName = componentName; } public String toString () { return getSFClient().getComponent(componentName).get(); } } private class WSLLastCommand extends WSLAbstractString { public String toString() { return scriptCommands.getLastCommand(); } } private class ScriptRunner implements Runnable { public void run() { scriptCommands.addThread(Thread.currentThread()); try { Reader scriptReader = info.openReader(); CharStream input = new ANTLRNoCaseReaderStream(scriptReader); WSLLexer lex = new WSLLexer(input); CommonTokenStream tokens = new CommonTokenStream(lex); WSLParser parser = new WSLParser(tokens); parser.setScript(WSLScript.this); parser.script(); } catch(IOException e) { e.printStackTrace(); return; } catch (RecognitionException e) { e.printStackTrace(); return; } curCommand = commands.get(0); while(curCommand == null) { nextLine++; if(nextLine >= commands.size()) break; curCommand = commands.get(nextLine); } while(isRunning()) { if(curCommand == null) break; // find the next non-null command do { nextLine++; if(nextLine >= commands.size()) break; } while (commands.get(nextLine) == null); // crazy dance to make sure we're not suspended and not in a roundtime try { if(!curCommand.isInstant()) scriptCommands.waitForRoundtime(delay); while(scriptCommands.isSuspended()) { scriptCommands.waitForResume(); if(!curCommand.isInstant()) scriptCommands.waitForRoundtime(delay); } } catch(InterruptedException e) { } finally { if(!isRunning()) break; } try { curCommand.execute(); } catch(InterruptedException e) { if(!isRunning()) break; } if(nextLine >= commands.size()) break; curCommand = commands.get(nextLine); } if(isRunning()) stop(); } } public void start (Collection<String> arguments) { super.start(); StringBuffer totalArgs = new StringBuffer(); int i = 1; for (String argument : arguments) { setSpecialVariable(Integer.toString(i), argument); if (i > 1) totalArgs.append(" "); totalArgs.append(argument); i++; } // populate the rest of the argument variable for(; i <= 9; i++) { setSpecialVariable(Integer.toString(i), ""); } // set 0 to the entire list setSpecialVariable("0", totalArgs.toString()); scriptThread = new Thread(new ScriptRunner()); scriptThread.setName("Wizard Script: " + getName()); scriptThread.start(); } public void addLabel(String label, Integer line) { labels.put(label.toLowerCase(), line); } public int labelLineNumber(String label) { Integer line = labels.get(label.toLowerCase()); if(line != null) return line; else return -1; } protected void addCommand(WSLAbstractCommand command) { commands.add(command); } protected void execute(String line) throws InterruptedException { curLine = line; Matcher m = commandPattern.matcher(line.trim()); if (!m.find()) { return; } String commandName = m.group(1).toLowerCase(); String arguments = m.group(3); if(arguments == null) arguments = ""; IWSLCommandDefinition command = WSLScriptCommands.getCommand(commandName); if(command != null) { scriptDebug(2, "Debug: " + line); command.execute(this, arguments); } else { debug("Invalid command on line (" + curCommand.getLineNumber() + "): " + line); } } protected void scriptError(String message) { debug("Script error on line " + curCommand.getLineNumber() + " (" + curLine + "): " + message); stop(); } protected void scriptWarning(String message) { debug("Script warning on line " + curCommand.getLineNumber() + " (" + curLine + "): " + message); } protected void scriptDebug (int level, String message) { if (level <= debugLevel) { debug(message); } } protected void setGlobalVariable(String name, IWSLValue value) { setGlobalVariable(name, value.toString()); } protected void setGlobalVariable(String name, String value) { if(specialVariables.containsValue(name)) scriptError("Cannot overwrite special variable \"" + name + "\""); getSFClient().setVariable(name, value); } protected void setSpecialVariable(String name, String value) { setSpecialVariable(name, new WSLString(value)); } protected void setSpecialVariable(String name, IWSLValue value) { deleteVariable(name); specialVariables.put(name, value); } protected void deleteVariable(String name) { getSFClient().removeVariable(name); } protected void deleteLocalVariable(String name) { localVariables.remove(name); } public void setLocalVariable(String name, String value) { setLocalVariable(name, new WSLString(value)); } public void setLocalVariable(String name, IWSLValue value) { localVariables.put(name, value); } protected void setDebugLevel(int level) { this.debugLevel = level; } protected int getDebugLevel() { return this.debugLevel; } protected void setDelay(double delay) { this.delay = delay; } protected void matchWait(double timeout) throws InterruptedException { /* * Remove matchQueue before going into the wait rather than * after coming out so we don't end up trashing another's queue * as we come out. */ BlockingQueue<String> myQueue = matchQueue; matchQueue = null; try { boolean haveTimeout = timeout > 0.0; long timeoutEnd = 0L; if(haveTimeout) timeoutEnd = System.currentTimeMillis() + (long)(timeout * 1000.0); // run until we get a match or are told to stop while(true) { String text = null; // wait for some text if(haveTimeout) { long now = System.currentTimeMillis(); if(timeoutEnd >= now) text = myQueue.poll(timeoutEnd - now, TimeUnit.MILLISECONDS); if(text == null) return; } else { text = myQueue.take(); } // try all of our matches for(WSLMatch match : matches) { if(match.getMatch().matches(text)) { match.run(); return; } } } } finally { matches.clear(); scriptCommands.removeLineQueue(myQueue); } } protected void gotoCommand(int line) { curCommand = commands.get(line); nextLine = line; while(curCommand == null) { nextLine++; if(nextLine >= commands.size()) break; curCommand = commands.get(nextLine); } // if we're in an action, interrupt execution on the main thread if(Thread.currentThread() != scriptThread) { scriptCommands.interrupt(); } } protected void gotoLabel (String label) { // remove ":" from labels int pos = label.indexOf(':'); if(pos >= 0) label = label.substring(0, pos); Integer command = labels.get(label.toLowerCase()); if (command != null) { gotoCommand(command); } else { command = labels.get("labelerror"); if (command != null) { scriptDebug(1, "Label \"" + label + "\" does not exist, going to \"labelerror\""); gotoCommand(command); } else { scriptError("Label \"" + label + "\" does not exist"); } } } private static final Pattern gosubArgRegex = Pattern.compile("(?:(['\"])(.*?)(?<!\\\\)(?>\\\\\\\\)*\\1|([^\\s]+))"); protected void gosub (String label, String arguments) { WSLFrame frame = new WSLFrame(nextLine, localVariables); callstack.push(frame); // TODO perhaps abstract this localVariables = (HashMap<String, IWSLValue>)localVariables.clone(); setLocalVariable("0", arguments); // parse the args, splitting on " and ', and leaving in \-escaped quotes Matcher m = gosubArgRegex.matcher(arguments); ArrayList<String> matchList = new ArrayList<String>(); while (m.find()) { String phrase; if (m.group(2) != null) { // Add quoted string without the quotes phrase = m.group(2); } else { // Add unquoted word phrase = m.group(); } phrase = phrase.replaceAll("\\\\(['\"])", "\\1"); matchList.add(phrase); } int i = 1; for(String arg : matchList) { setLocalVariable(String.valueOf(i), arg); i++; } Integer command = labels.get(label.toLowerCase()); if (command != null) { gotoCommand(command); } else { scriptError("Invalid gosub statement, label \"" + label + "\" does not exist"); } } protected void gosubReturn () { if (callstack.empty()) { scriptError("Invalid use of return, not in a subroutine"); } else { WSLFrame frame = callstack.pop(); frame.restore(); } } protected void addMatch(String label, IMatch match) { if(matchQueue == null) matchQueue = scriptCommands.createLineQueue(); matches.add(new WSLTextMatch(label, match)); } protected void addMatchRe(String label, RegexMatch match) { if(matchQueue == null) matchQueue = scriptCommands.createLineQueue(); matches.add(new WSLRegexMatch(label, match)); } private abstract class WSLMatch { private IMatch match; public WSLMatch(IMatch match) { this.match = match; } public IMatch getMatch() { return match; } public abstract void run(); } private class WSLTextMatch extends WSLMatch { private String label; public WSLTextMatch(String label, IMatch match) { super(match); this.label = label; } public void run() { gotoLabel(label); } } private class WSLRegexMatch extends WSLMatch { private String label; private RegexMatch match; public WSLRegexMatch(String label, RegexMatch match) { super(match); this.label = label; this.match = match; } public void run() { setVariablesFromMatch(match); gotoLabel(label); } } protected void setVariablesFromMatch(RegexMatch match) { int i = 0; for(String var : match.groups()) { setLocalVariable(String.valueOf(i), var); i++; } } public void setLastCondition(boolean condition) { this.lastCondition = condition; } public boolean getLastCondition() { return lastCondition; } public IScriptEngine getScriptEngine() { return engine; } public IScriptCommands getCommands() { return scriptCommands; } protected IStormFrontClient getSFClient() { return (IStormFrontClient)getClient(); } }