/** * 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.script.internal; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.BlockingQueue; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import cc.warlock.core.client.ICommand; import cc.warlock.core.client.IRoomListener; import cc.warlock.core.client.IStream; import cc.warlock.core.client.IStreamListener; import cc.warlock.core.client.IWarlockClient; import cc.warlock.core.client.IWarlockClientViewer; import cc.warlock.core.client.WarlockString; import cc.warlock.core.client.internal.Command; import cc.warlock.core.script.IMatch; import cc.warlock.core.script.IScriptCommands; public class ScriptCommands implements IScriptCommands, IStreamListener, IRoomListener { protected IWarlockClientViewer viewer; protected Collection<LinkedBlockingQueue<String>> textWaiters = Collections.synchronizedCollection(new ArrayList<LinkedBlockingQueue<String>>()); private StringBuffer receiveBuffer = new StringBuffer(); private String scriptName; private boolean suspended = false; /** * Used to count room changes. In order to enable waitForRoom to reliably * detect when we've entered a new room, we need a persistent state change * of some sort. Unless you miss 4 billion rooms, this number will be * different. * * @see #waitNextRoom() */ private int room = 0; private int prompt = 0; private boolean atPrompt; private String lastCommand = null; private final Lock lock = new ReentrantLock(); private final Condition gotResume = lock.newCondition(); private final Condition nextRoom = lock.newCondition(); private final Condition atPromptCond = lock.newCondition(); private List<Thread> scriptThreads = Collections.synchronizedList(new ArrayList<Thread>()); public ScriptCommands(IWarlockClientViewer viewer, String scriptName) { this.viewer = viewer; this.scriptName = scriptName; atPrompt = getClient().getDefaultStream().isPrompting(); getClient().getDefaultStream().addStreamListener(this); getClient().addRoomListener(this); } public void echo(String text) { getClient().getDefaultStream().echo("[" + scriptName + "]: " + text + "\n"); } public BlockingQueue<String> createLineQueue() { LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<String>(); synchronized(textWaiters) { textWaiters.add(queue); } return queue; } public boolean removeLineQueue(BlockingQueue<String> queue) { return textWaiters.remove(queue); } public IMatch matchWait(Collection<IMatch> matches, BlockingQueue<String> matchQueue, double timeout) throws InterruptedException { 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 = matchQueue.poll(timeoutEnd - now, TimeUnit.MILLISECONDS); if(text == null) return null; } else { text = matchQueue.take(); } // try all of our matches for(IMatch match : matches) { if(match.matches(text)) { return match; } } } } finally { textWaiters.remove(matchQueue); } } public void move(String direction) throws InterruptedException { put(direction); waitNextRoom(); } public void waitNextRoom() throws InterruptedException { lock.lock(); try { int curRoom = room; while (room == curRoom) nextRoom.await(); while(!atPrompt) atPromptCond.await(); } finally { lock.unlock(); } } public void pause(double seconds) throws InterruptedException { long now = System.currentTimeMillis(); long pauseEnd = now + (long)(seconds * 1000.0); while(pauseEnd > now) { Thread.sleep(pauseEnd - now); now = System.currentTimeMillis(); } } public void put(String text) throws InterruptedException { lastCommand = text; Command command = new Command(text, true); command.setPrefix("[" + scriptName + "]: "); getClient().send(command); } public void waitFor(IMatch match) throws InterruptedException { LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<String>(); textWaiters.add(queue); try { while(true) { String text = queue.take(); if(match.matches(text)) { break; } } } finally { textWaiters.remove(queue); } } public void waitForPrompt() throws InterruptedException { lock.lock(); try { int oldPrompt = prompt; while(oldPrompt == prompt) atPromptCond.await(); } finally { lock.unlock(); } } public IWarlockClient getClient() { return viewer.getWarlockClient(); } public void streamCleared(IStream stream) {} public void streamFlush(IStream stream) {} public void streamPrompted(IStream stream, String prompt) { lock.lock(); try { atPrompt = true; this.prompt++; atPromptCond.signalAll(); } finally { lock.unlock(); } receiveLine(prompt); } public void streamReceivedCommand(IStream stream, ICommand command) { atPrompt = false; if(!command.fromScript()) receiveText(command.getCommand()); } public void streamReceivedText(IStream stream, WarlockString text) { if(text.hasStyleNamed("debug")) return; if(!text.hasStyleNamed("echo")) atPrompt = false; receiveText(text.toString()); } public void componentUpdated(IStream stream, String id, WarlockString text) { } protected void receiveText(String text) { int end; receiveBuffer.append(text); while ((end = receiveBuffer.indexOf("\n")) != -1) { receiveLine(receiveBuffer.substring(0, end + 1)); receiveBuffer.delete(0, end + 1); } } protected void receiveLine(String line) { synchronized(textWaiters) { for(LinkedBlockingQueue<String> queue : textWaiters) { try { queue.put(line); } catch(InterruptedException e) { e.printStackTrace(); } } } } public void nextRoom() { lock.lock(); try { atPrompt = false; room++; nextRoom.signalAll(); } finally { lock.unlock(); } } public void stop() { interrupt(); getClient().getDefaultStream().removeStreamListener(this); getClient().removeRoomListener(this); } public void interrupt() { lock.lock(); try { synchronized(scriptThreads) { for(Thread scriptThread : scriptThreads) scriptThread.interrupt(); } } finally { lock.unlock(); } } public void resume() { lock.lock(); try { suspended = false; gotResume.signalAll(); } finally { lock.unlock(); } } public void suspend() { this.suspended = true; } public boolean isSuspended() { return suspended; } public void waitForResume() throws InterruptedException { // Don't grab the lock if we don't need to if(!suspended) return; lock.lock(); try { while(suspended) gotResume.await(); } finally { lock.unlock(); } } public void addThread(Thread thread) { scriptThreads.add(thread); } public void removeThread(Thread thread) { scriptThreads.remove(thread); } public String getLastCommand() { return lastCommand; } public void playSound(InputStream stream) { getClient().playSound(stream); } public void streamTitleChanged(IStream stream, String title) { // TODO Auto-generated method stub } public void streamCreated(IStream stream) { // TODO Auto-generated method stub } }