/**
* 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.client.internal;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import cc.warlock.core.client.ICharacterStatus;
import cc.warlock.core.client.ICommand;
import cc.warlock.core.client.IProperty;
import cc.warlock.core.client.IRoomListener;
import cc.warlock.core.client.IStream;
import cc.warlock.core.client.IStreamListener;
import cc.warlock.core.client.IWarlockSkin;
import cc.warlock.core.client.IWarlockStyle;
import cc.warlock.core.client.WarlockClientRegistry;
import cc.warlock.core.client.WarlockString;
import cc.warlock.core.client.internal.CharacterStatus;
import cc.warlock.core.client.internal.Property;
import cc.warlock.core.client.internal.Stream;
import cc.warlock.core.client.internal.WarlockClient;
import cc.warlock.core.client.internal.WarlockStyle;
import cc.warlock.core.client.settings.IClientSettings;
import cc.warlock.core.client.settings.IVariable;
import cc.warlock.core.client.settings.internal.VariableConfigurationProvider;
import cc.warlock.core.configuration.ConfigurationUtil;
import cc.warlock.core.script.IScript;
import cc.warlock.core.script.IScriptListener;
import cc.warlock.core.script.ScriptEngineRegistry;
import cc.warlock.core.script.configuration.ScriptConfiguration;
import cc.warlock.core.stormfront.client.IStormFrontClient;
import cc.warlock.core.stormfront.client.IStormFrontClientViewer;
import cc.warlock.core.stormfront.client.IStormFrontDialogMessage;
import cc.warlock.core.stormfront.network.StormFrontConnection;
import cc.warlock.core.stormfront.settings.IStormFrontClientSettings;
import cc.warlock.core.stormfront.settings.StormFrontServerSettings;
import cc.warlock.core.stormfront.settings.internal.StormFrontClientSettings;
import cc.warlock.core.stormfront.settings.skin.DefaultSkin;
import cc.warlock.core.stormfront.settings.skin.IStormFrontSkin;
import cc.warlock.core.stormfront.xml.StormFrontDocument;
import cc.warlock.core.stormfront.xml.StormFrontElement;
import cc.warlock.core.util.Pair;
import com.martiansoftware.jsap.CommandLineTokenizer;
/**
* @author Marshall
*
* TODO To change the template for this generated type comment go to
* Window - Preferences - Java - Code Style - Code Templates
*/
public class StormFrontClient extends WarlockClient implements IStormFrontClient, IScriptListener, IRoomListener {
protected ICharacterStatus status;
protected Property<Integer> roundtime = new Property<Integer>();
protected Property<Integer> casttime = new Property<Integer>();
protected Property<Integer> monsterCount = new Property<Integer>();
protected Property<String> leftHand = new Property<String>();
protected Property<String> rightHand = new Property<String>();
protected Property<String> currentSpell = new Property<String>();
protected StringBuffer buffer = new StringBuffer();
protected Property<String> characterName = new Property<String>();
protected Property<String> roomDescription = new Property<String>();
protected String gameCode, playerId;
protected StormFrontClientSettings clientSettings;
protected StormFrontServerSettings serverSettings;
protected long timeDelta;
protected Long roundtimeEnd, casttimeEnd;
protected int roundtimeLength, casttimeLength;
protected Thread roundtimeThread = null;
protected Thread casttimeThread = null;
protected ArrayList<IScript> runningScripts;
protected ArrayList<IScriptListener> scriptListeners;
protected DefaultSkin skin;
protected HashMap<String, Property<String>> components =
new HashMap<String, Property<String>>();
protected HashMap<String, IStream> componentStreams = new HashMap<String, IStream>();
protected HashMap<String, String> commands;
protected HashMap<String, Property<IStormFrontDialogMessage>> dialogs =
new HashMap<String, Property<IStormFrontDialogMessage>>();
protected HashMap<String, String> vitals = new HashMap<String, String>();
public StormFrontClient(String gameCode) {
super();
this.gameCode = gameCode;
status = new CharacterStatus(this);
roundtimeEnd = null;
casttimeEnd = null;
roundtimeLength = 0;
casttimeLength = 0;
runningScripts = new ArrayList<IScript>();
scriptListeners = new ArrayList<IScriptListener>();
WarlockClientRegistry.activateClient(this);
}
@Override
public void dispose() {
if (clientSettings != null) {
// Shut down settings.
clientSettings.dispose();
}
super.dispose();
}
@Override
public void send(ICommand command) {
String scriptPrefix = ScriptConfiguration.instance().getScriptPrefix();
if (command.getCommand().startsWith(scriptPrefix)){
runScript(command.getCommand().substring(scriptPrefix.length()));
} else {
super.send(command);
}
}
public void runScript(String command) {
command = command.replaceAll("[\\r\\n]", "");
int firstSpace = command.indexOf(" ");
String scriptName = command.substring(0, (firstSpace < 0 ? command.length() : firstSpace));
String[] arguments = new String[0];
if (firstSpace > 0)
{
String args = command.substring(firstSpace+1);
arguments = CommandLineTokenizer.tokenize(args);
}
IScript script = ScriptEngineRegistry.startScript(scriptName, viewer, arguments);
if (script != null)
{
script.addScriptListener(this);
for (IScriptListener listener : scriptListeners) listener.scriptStarted(script);
runningScripts.add(script);
}
}
public void scriptPaused(IScript script) {
for (IScriptListener listener : scriptListeners) listener.scriptPaused(script);
}
public void scriptResumed(IScript script) {
for (IScriptListener listener : scriptListeners) listener.scriptResumed(script);
}
public void scriptStarted(IScript script) {
for (IScriptListener listener : scriptListeners) listener.scriptStarted(script);
}
public void scriptStopped(IScript script, boolean userStopped) {
runningScripts.remove(script);
for (IScriptListener listener : scriptListeners) listener.scriptStopped(script, userStopped);
}
public IProperty<Integer> getRoundtime() {
return roundtime;
}
public IProperty<Integer> getCasttime() {
return casttime;
}
public IProperty<Integer> getMonsterCount() {
return monsterCount;
}
public Property<IStormFrontDialogMessage> getDialog(String id) {
Property<IStormFrontDialogMessage> dialog = dialogs.get(id);
if(dialog == null) {
dialog = new Property<IStormFrontDialogMessage>();
dialogs.put(id, dialog);
}
return dialog;
}
private class RoundtimeThread extends Thread
{
public void run()
{
for (;;) {
long now = System.currentTimeMillis();
long rt = 0;
// Synchronize with external roundtime updates
synchronized(StormFrontClient.this) {
if (roundtimeEnd != null)
rt = roundtimeEnd * 1000L + timeDelta - now;
if (rt <= 0) {
roundtimeThread = null;
roundtimeEnd = null;
roundtimeLength = 0;
roundtime.set(0);
StormFrontClient.this.notifyAll();
return;
}
}
// Update the world with the new roundtime
// Avoid flicker caused by redundant updates
int rtSeconds = (int)((rt + 999) / 1000);
if (roundtime.get() != rtSeconds)
roundtime.set(rtSeconds);
// Compute how long until next roundtime update
long waitTime = rt % 1000;
if (waitTime == 0) waitTime = 1000;
try {
Thread.sleep(waitTime);
} catch (InterruptedException e) {
// This is not supposed to happen.
e.printStackTrace();
}
}
}
}
private class CasttimeThread extends Thread
{
public void run()
{
for (;;) {
long now = System.currentTimeMillis();
long ct = 0;
// Synchronize with external casttime updates
synchronized(StormFrontClient.this) {
if (casttimeEnd != null)
ct = casttimeEnd * 1000L + timeDelta - now;
if (ct <= 0) {
casttimeThread = null;
casttimeEnd = null;
casttimeLength = 0;
casttime.set(0);
StormFrontClient.this.notifyAll();
return;
}
}
// Update the world with the new casttime
// Avoid flicker caused by redundant updates
int ctSeconds = (int)((ct + 999) / 1000);
if (casttime.get() != ctSeconds)
casttime.set(ctSeconds);
// Compute how long until next casttime update
long waitTime = ct % 1000;
if (waitTime == 0) waitTime = 1000;
try {
Thread.sleep(waitTime);
} catch (InterruptedException e) {
// This is not supposed to happen.
e.printStackTrace();
}
}
}
}
public synchronized void setupRoundtime(Long end)
{
roundtimeEnd = end;
}
public synchronized void setupCasttime(Long end)
{
casttimeEnd = end;
}
public synchronized void syncTime(Long now)
{
if (roundtimeEnd == null && casttimeEnd == null) return;
long newTimeDelta = System.currentTimeMillis() - now * 1000L;
if (roundtimeThread != null || casttimeThread != null) {
// Don't decrease timeDelta while roundtimes are active.
if (newTimeDelta > timeDelta) timeDelta = newTimeDelta;
return;
}
timeDelta = newTimeDelta;
/* let's us know if we need to notify clients */
boolean notify = false;
if (roundtimeEnd != null) {
if (roundtimeEnd > now) {
// We need to do this now due to scheduling delays in the thread
roundtimeLength = (int)(roundtimeEnd - now);
roundtime.set(roundtimeLength);
roundtimeThread = new RoundtimeThread();
roundtimeThread.start();
} else {
roundtime.set(0);
roundtimeEnd = null;
roundtimeLength = 0;
notify = true;
}
}
if (casttimeEnd != null) {
if (casttimeEnd > now) {
// We need to do this now due to scheduling delays in the thread
casttimeLength = (int)(casttimeEnd - now);
casttime.set(casttimeLength);
casttimeThread = new CasttimeThread();
casttimeThread.start();
} else {
casttime.set(0);
casttimeEnd = null;
casttimeLength = 0;
notify = true;
}
}
if (notify) {
StormFrontClient.this.notifyAll();
}
}
public int getRoundtimeLength() {
return roundtimeLength;
}
public int getCasttimeLength() {
return casttimeLength;
}
public synchronized void waitForRoundtime(double delay) throws InterruptedException {
while (roundtimeEnd != null) {
wait();
Thread.sleep((long)(delay * 1000));
}
}
public synchronized void waitForCasttime(double delay) throws InterruptedException {
while (casttimeEnd != null) {
wait();
Thread.sleep((long)(delay * 1000));
}
}
public String getVital(String id) {
return vitals.get(id);
}
public String setVital(String id, String value) {
return vitals.put(id, value);
}
public void connect(String server, int port, String key) throws IOException {
connection = new StormFrontConnection(this, key);
connection.connect(server, port);
WarlockClientRegistry.clientConnected(this);
}
public void streamCleared() {
// TODO Auto-generated method stub
}
public String getPlayerId() {
return playerId;
}
public void setPlayerId(String playerId) {
this.playerId = playerId;
clientSettings = new StormFrontClientSettings(this);
skin = new DefaultSkin(clientSettings);
skin.loadDefaultStyles(clientSettings.getHighlightConfigurationProvider());
serverSettings = new StormFrontServerSettings();
clientSettings.addChildProvider(serverSettings);
WarlockClientRegistry.clientSettingsLoaded(this);
}
public IClientSettings getClientSettings() {
return clientSettings;
}
public IStormFrontClientSettings getStormFrontClientSettings() {
return clientSettings;
}
public IProperty<String> getCharacterName() {
return characterName;
}
public String getGameCode() {
return gameCode;
}
public String getClientId() {
//return gameCode + "_" + playerId;
return playerId;
}
public IProperty<String> getLeftHand() {
return leftHand;
}
public IProperty<String> getRightHand() {
return rightHand;
}
public IProperty<String> getCurrentSpell() {
return currentSpell;
}
public ICharacterStatus getCharacterStatus() {
return status;
}
public Collection<IScript> getRunningScripts() {
return runningScripts;
}
public void addScriptListener(IScriptListener listener)
{
scriptListeners.add(listener);
}
public void removeScriptListener (IScriptListener listener)
{
scriptListeners.remove(listener);
}
public IWarlockSkin getSkin() {
return skin;
}
public IStormFrontSkin getStormFrontSkin() {
return skin;
}
@Override
public IStream getDefaultStream() {
IStream stream = this.getStream(DEFAULT_STREAM_NAME);
if(stream != null)
return stream;
stream = createStream(DEFAULT_STREAM_NAME);
stream.setLogging(true);
return stream;
}
public void setComponent (String componentName, String value, IStream stream)
{
String name = componentName.toLowerCase();
Property<String> component = components.get(name);
if(component == null)
components.put(name, new Property<String>(value));
else
component.set(value);
componentStreams.put(name, stream);
//stream.addComponent(name);
}
public void updateComponent(String componentName, WarlockString value) {
String name = componentName.toLowerCase();
Property<String> component = components.get(name);
if(component != null) {
component.set(value.toString());
} else {
component = new Property<String>(value.toString());
components.put(name, component);
}
IStream stream = componentStreams.get(name);
// FIXME: The streams store them in a case-senstive fashion.
if(stream != null)
stream.updateComponent(componentName, value);
}
public IProperty<String> getComponent(String componentName) {
return components.get(componentName.toLowerCase());
}
@Override
protected void finalize() throws Throwable {
WarlockClientRegistry.removeClient(this);
super.finalize();
}
public IWarlockStyle getCommandStyle() {
IWarlockStyle style = clientSettings.getNamedStyle(StormFrontClientSettings.PRESET_COMMAND);
if (style == null) {
return new WarlockStyle();
}
return style;
}
public void loadCmdlist()
{
try {
commands = new HashMap<String, String>();
FileInputStream stream = new FileInputStream(ConfigurationUtil.getConfigurationFile("cmdlist1.xml"));
StormFrontDocument document = new StormFrontDocument(stream);
stream.close();
StormFrontElement cmdlist = document.getRootElement();
for (StormFrontElement cliElement : cmdlist.elements())
{
if(cliElement.getName().equals("cli")) {
String coord = cliElement.attributeValue("coord");
String command = cliElement.attributeValue("command");
if(coord != null && command != null)
commands.put(coord, command);
}
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public String getCommand(String coord) {
if(commands == null) return null;
return commands.get(coord);
}
/* Internal only.. meant for importing/exporting stormfront's savings */
public StormFrontServerSettings getServerSettings() {
return serverSettings;
}
public void launchURL(String url) {
if (viewer instanceof IStormFrontClientViewer)
{
try {
((IStormFrontClientViewer)viewer).launchURL(new URL(url));
} catch (MalformedURLException e) {
e.printStackTrace();
}
}
}
public void appendImage(String pictureId) {
try {
URL url = new URL("http://www.play.net/bfe/DR-art/" + pictureId + "_t.jpg");
if (viewer instanceof IStormFrontClientViewer)
((IStormFrontClientViewer) viewer).appendImage(url);
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public void startedDownloadingServerSettings() {
if (viewer instanceof IStormFrontClientViewer)
((IStormFrontClientViewer)viewer).startedDownloadingServerSettings();
}
public void finishedDownloadingServerSettings(String str) {
File settingsFile = ConfigurationUtil.getConfigurationFile("serverSettings_" + getClientId() + ".xml");
InputStream inStream = new ByteArrayInputStream(str.getBytes());
try {
FileWriter writer = new FileWriter(settingsFile);
StormFrontDocument document = new StormFrontDocument(inStream);
document.saveTo(writer, true);
writer.close();
inStream.close();
buffer.setLength(0);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
InputStream stream = new FileInputStream(settingsFile);
serverSettings.importServerSettings(stream, clientSettings);
stream.close();
} catch (Exception e) {
e.printStackTrace();
}
if (viewer instanceof IStormFrontClientViewer)
((IStormFrontClientViewer)viewer).finishedDownloadingServerSettings();
}
public void receivedServerSetting(String setting) {
if (viewer instanceof IStormFrontClientViewer)
((IStormFrontClientViewer)viewer).receivedServerSetting(setting);
}
@Override
public IStream createStream(String streamName) {
synchronized(streams) {
IStream stream = getStream(streamName);
if(stream == null) {
stream = new Stream(this, streamName);
streams.put(streamName, stream);
for(Iterator<Pair<String, IStreamListener>> iter =
potentialListeners.iterator(); iter.hasNext(); ) {
Pair<String, IStreamListener> pair = iter.next();
if(pair.first().equals(streamName)) {
stream.addStreamListener(pair.second());
iter.remove();
}
}
// TODO: or should this always be called?
stream.create();
}
return stream;
}
}
public String getVariable(String id) {
if(clientSettings == null)
return null;
IVariable var = clientSettings.getVariable(id);
if(var == null)
return null;
return var.getValue();
}
public void setVariable(String id, String value) {
if(clientSettings == null)
return;
VariableConfigurationProvider varProvider = clientSettings.getVariableConfigurationProvider();
if(varProvider == null)
return;
varProvider.addVariable(id, value);
}
public void removeVariable(String id) {
if(clientSettings == null)
return;
VariableConfigurationProvider varProvider = clientSettings.getVariableConfigurationProvider();
if(varProvider == null)
return;
varProvider.removeVariable(id);
}
}