/** * 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.internal; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.concurrent.LinkedBlockingQueue; import cc.warlock.core.client.IStream; import cc.warlock.core.client.IWarlockClient; import cc.warlock.core.client.IWarlockClientViewer; import cc.warlock.core.script.IMatch; import cc.warlock.core.script.IScript; import cc.warlock.core.script.internal.ScriptCommands; import cc.warlock.core.stormfront.client.IStormFrontClient; import cc.warlock.core.stormfront.script.IStormFrontScriptCommands; public class StormFrontScriptCommands extends ScriptCommands implements IStormFrontScriptCommands { protected IScript script; private int typeAhead = 0; /** * Storage for active actions. Users must acquire the monitor first! */ private HashMap<IMatch, Runnable> actions = new HashMap<IMatch, Runnable>(); private Thread actionThread = null; public StormFrontScriptCommands(IWarlockClientViewer viewer, String name) { super(viewer, name); /* TODO which streams should we listen to here? client.getDeathsStream().addStreamListener(this); client.getFamiliarStream().addStreamListener(this); client.getAssessStream().addStreamListener(this); client.getExperienceStream().addStreamListener(this); */ } public StormFrontScriptCommands(IWarlockClientViewer viewer, IScript script) { this(viewer, script.getName()); this.script = script; } public IStormFrontClient getStormFrontClient() { IWarlockClient client = viewer.getWarlockClient(); // TODO handle this more appropriately. if(!(client instanceof IStormFrontClient)) return null; return (IStormFrontClient)client; } @Override public void put(String text) throws InterruptedException { if(typeAhead >= 2) this.waitForPrompt(); synchronized(this) { typeAhead++; } super.put(text); } @Override public void streamPrompted(IStream stream, String prompt) { synchronized(this) { if(typeAhead > 0) typeAhead--; } super.streamPrompted(stream, prompt); } public void waitForRoundtime(double delay) throws InterruptedException { getStormFrontClient().waitForRoundtime(delay); } private class ScriptActionThread extends Thread { public void run() { addThread(this); LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<String>(); textWaiters.add(queue); try { while(true) { assert !script.isRunning(); String text = null; try { text = queue.take(); } catch(InterruptedException e) { synchronized(actions) { /* We must clear actionThread in the same critical * section as the decision to terminate the thread */ if(actions.size() == 0) { actionThread = null; return; } continue; } } synchronized(actions) { for(Map.Entry<IMatch, Runnable> action : actions.entrySet()) { if(action.getKey().matches(text)) action.getValue().run(); } } } } finally { textWaiters.remove(queue); removeThread(this); synchronized(actions) { // FIXME What to do for a runtime exception? if (actionThread == this) actionThread = null; } } } } public void addAction(Runnable action, IMatch match) { synchronized(actions) { actions.put(match, action); if(actionThread == null) { actionThread = new ScriptActionThread(); actionThread.start(); } } } public void clearActions() { synchronized(actions) { actions.clear(); if(actionThread != null) actionThread.interrupt(); } } public void removeAction(IMatch action) { synchronized(actions) { actions.remove(action); if(actionThread != null) actionThread.interrupt(); } } public void removeAction(String text) { synchronized(actions) { boolean changed = false; for(Iterator<IMatch> iter = actions.keySet().iterator(); iter.hasNext(); ) { IMatch match = iter.next(); // remove the element with the same name as text if(match.getText().equals(text)) { iter.remove(); changed = true; } } if(changed && actionThread != null) actionThread.interrupt(); } } @Override public void stop() { super.stop(); clearActions(); /*sfClient.getDeathsStream().removeStreamListener(this); sfClient.getFamiliarStream().removeStreamListener(this); sfClient.getAssessStream().removeStreamListener(this); sfClient.getExperienceStream().removeStreamListener(this);*/ } }