package net.sf.colossus.client;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ConnectException;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.sf.colossus.client.Client.ConnectionInitException;
import net.sf.colossus.common.Constants;
import net.sf.colossus.common.Options;
import net.sf.colossus.game.EntrySide;
import net.sf.colossus.game.Legion;
import net.sf.colossus.game.PlayerColor;
import net.sf.colossus.game.actions.Recruitment;
import net.sf.colossus.game.actions.Summoning;
import net.sf.colossus.server.IServer;
import net.sf.colossus.util.BuildInfo;
import net.sf.colossus.util.ErrorUtils;
import net.sf.colossus.util.Glob;
import net.sf.colossus.util.InstanceTracker;
import net.sf.colossus.util.Split;
import net.sf.colossus.util.SystemInfo;
import net.sf.colossus.variant.BattleHex;
import net.sf.colossus.variant.CreatureType;
import net.sf.colossus.variant.MasterHex;
/**
* Thread to handle server connection on client side.
*
* @author David Ripton
*/
final class SocketClientThread extends Thread implements IServer,
IServerConnection
{
private static final Logger LOGGER = Logger
.getLogger(SocketClientThread.class.getName());
private ClientThread clientThread;
// only needed for reconnect, to check whether there's something still
// already received but not processed (= still in queue)
private ClientThread disposedClientThread = null;
private Socket socket;
private BufferedReader in;
private PrintWriter out;
private boolean goingDown = false;
private boolean selfInterrupted = false;
private boolean serverReceiveTimedout = false;
/**
* Those are stored at the moment only to be able to reconnect
*/
private final String host;
private final int port;
private String playerName;
private final boolean remote;
private final boolean spectator;
private final boolean internalSpectator;
// The two below are only needed for debugging:
// these here are related to whether replay/redo msgs were *received*;
// those in ClientThread relate to "messages handled" state.
private boolean replayReceived = false;
private boolean redoReceived = false;
private final boolean _MSG_TRACKING = false;
private final static String sep = Constants.protocolTermSeparator;
private String reasonFail = null;
private String initialLine = null;
private String variantNameForInit;
private Collection<String> preliminaryPlayerNames;
private final Object isWaitingLock = new Object();
private boolean isWaiting = false;
private int ownMessageCounter = -1;
private int connectionId = -1;
public static SocketClientThread createConnection(String host, int port,
String initialName, boolean remote, boolean spectator)
throws ConnectionInitException
{
LOGGER.info("SCT: trying recreateConnection to host " + host
+ " at port " + port + " for playerName " + initialName);
SocketClientThread conn = new SocketClientThread(host, port,
initialName, remote, spectator, -1);
String reasonFail = conn.getReasonFail();
if (reasonFail != null)
{
// If this failed here, it is usually a "could not connect"-problem
// (wrong host or port or server not yet up).
// In this case we just do cleanup and end.
LOGGER.warning("Client startup failed: " + reasonFail);
if (!Options.isStresstest())
{
String title = "Socket initialization failed!";
ErrorUtils.showErrorDialog(null, title, reasonFail);
}
throw new ConnectionInitException(reasonFail);
}
return conn;
}
protected static SocketClientThread recreateConnection(
IServerConnection prevConnection) throws ConnectionInitException
{
SocketClientThread previousConnection = (SocketClientThread)prevConnection;
int prevConnId = previousConnection.getConnectionId();
String host = previousConnection.host;
int port = previousConnection.port;
// Must already be the real name, not a "<bySomething>" any more
// - server won't recognize it.
String playerName = previousConnection.playerName;
boolean remote = previousConnection.remote;
boolean spectator = previousConnection.spectator;
LOGGER.info("SCT: trying recreateConnection to host " + host
+ " at port " + port + " for playerName " + playerName
+ " witgh conectionId " + prevConnId);
SocketClientThread newConn = new SocketClientThread(host, port,
playerName, remote, spectator, prevConnId);
String reasonFail = newConn.getReasonFail();
if (reasonFail != null)
{
LOGGER.warning("Reconnecting to server failed: " + reasonFail);
/*
if (!Options.isStresstest())
{
String title = "Socket initialialization failed!";
ErrorUtils.showErrorDialog(null, title, reasonFail);
}
*/
throw new ConnectionInitException(reasonFail);
}
return newConn;
}
/**
*
* @param host
* @param port
* @param initialName
* @param isRemote
* @param spectator
* @param prevId Id of connection to replace, or -1 if initial
*/
SocketClientThread(String host, int port, String initialName,
boolean isRemote, boolean spectator, int prevId)
{
super("SCT-" + initialName);
this.host = host;
this.port = port;
// Note: for a reconnect case we are given already the "real" name,
// in first connect it will be replace as soon as server sends
// us the setName().
this.playerName = initialName;
this.remote = isRemote;
this.spectator = spectator;
this.internalSpectator = (spectator && playerName
.equals(Constants.INTERNAL_DUMMY_CLIENT_NAME));
this.connectionId = prevId;
InstanceTracker.register(this, "SCT " + initialName);
String task = "";
try
{
task = "Creating Socket to connect to " + host + ":" + port;
LOGGER.log(Level.FINEST, "Next: " + task);
socket = new Socket(host, port);
int receiveBufferSize = socket.getReceiveBufferSize();
LOGGER.info("Client socket receive buffer size for Client "
+ initialName + " is " + receiveBufferSize);
task = "Preparing BufferedReader";
LOGGER.log(Level.FINEST, "Next: " + task);
in = new BufferedReader(new InputStreamReader(
socket.getInputStream()));
task = "Waiting for prompt";
LOGGER.log(Level.FINEST, "Next: " + task);
waitForPrompt();
task = "Preparing PrintWriter";
LOGGER.log(Level.FINEST, "Next: " + task);
out = new PrintWriter(socket.getOutputStream(), true);
task = "Sending signOn message";
LOGGER.log(Level.FINEST, "Next: " + task);
signOn(initialName, isRemote, IServer.CLIENT_VERSION,
BuildInfo.getFullBuildInfoString(), spectator, connectionId);
task = "Waiting for signOn acknowledge";
LOGGER.log(Level.FINEST, "Next: " + task);
reasonFail = waitForSignonOk();
if (reasonFail == null)
{
task = "Sending System Info";
LOGGER.log(Level.FINEST, "Next: " + task);
sendSystemInfo();
task = "Requesting GameInfo";
LOGGER.log(Level.FINEST, "Next: " + task);
requestGameInfo();
task = "Waiting for GameInfo";
LOGGER.log(Level.FINEST, "Next: " + task);
reasonFail = waitForGameInfo();
}
}
catch (UnknownHostException e)
{
LOGGER.log(Level.INFO, "UnknownHostException ('" + e.getMessage()
+ "') " + "in SCT during " + task);
reasonFail = "UnknownHostException ('" + e.getMessage() + "') "
+ "during " + task + ".\n(This probably means:\n"
+ "You have given a server as name istead of IP address and "
+ "the name cannot be resolved to an address (typo?).";
return;
}
// Could not connect - probably Firewall/NAT, or wrong IP or port
catch (ConnectException e)
{
String msg = e.getMessage();
String possReason = "";
if (msg.startsWith("Connection timed out"))
{
possReason = ".\n(This probably means: "
+ "Either you have given wrong Server name or "
+ "address, or a network issue (firewall, proxy, NAT) is "
+ "preventing the connection)";
}
else if (msg.startsWith("Connection refused"))
{
possReason = ".\n(This probably means: "
+ "Either you have given wrong Server and/or port, "
+ "or tried it too early and server side wasn't up yet)";
}
else
{
possReason = ".\n(No typical case is known causing this "
+ "situation; check the exception details for any "
+ "information what might be wrong)";
}
LOGGER.log(Level.INFO, "ConnectException ('" + msg + "') "
+ "in SCT during " + task);
reasonFail = "ConnectException ('" + e.getMessage() + "') "
+ "during " + task + possReason;
return;
}
// e.g. readLine in initialRead()
catch (SocketTimeoutException ste)
{
String msg = ste.getMessage();
LOGGER.log(Level.INFO, "SocketTimeoutException ('" + msg + "') "
+ "in SCT during " + task);
reasonFail = "Server not responding (could connect, "
+ "but didn't got any initial data within 5 seconds. "
+ "Probably the game has already as many clients as "
+ "it expects).";
return;
}
// e.g. setSoTimeout calls in tryInitialRead
catch (SocketException se)
{
String msg = se.getMessage();
LOGGER.log(Level.SEVERE, "SocketException ('" + msg + "') "
+ "in SCT during " + task + ": ", se);
reasonFail = "Exception during " + task + ": " + se.toString()
+ "\n(No typical case is known causing this situation; "
+ "check the exception details for any information what "
+ "might be wrong)";
return;
}
// e.g. readLine() in tryInitialRead
catch (IOException e)
{
LOGGER.log(Level.SEVERE, "IOException in SCT during " + task
+ ": ", e);
reasonFail = "Exception during " + task + ": " + e.toString()
+ "\n(No typical case is known causing this situation; "
+ "check the exception details for any information what "
+ "might be wrong)";
return;
}
catch (Exception e)
{
LOGGER.log(Level.SEVERE, "Unusual Exception in SCT during " + task
+ ": ", e);
reasonFail = "Exception during " + task + ": " + e.toString()
+ "\n(No typical case is known causing this situation; "
+ "check the exception details for any information what "
+ "might be wrong)";
return;
}
}
private String readOneLine() throws IOException
{
String line = in.readLine();
showDebugOutput(line);
return line;
}
private boolean msg_tracking()
{
return _MSG_TRACKING;
}
private void showDebugOutput(String line)
{
if (line == null || !msg_tracking())
{
return;
}
if (line.startsWith(Constants.replayOngoing + " ~ false"))
{
replayReceived = false;
}
if (line.startsWith(Constants.redoOngoing + " ~ false"))
{
redoReceived = false;
}
boolean show = false;
if (playerName.equals("remote") || playerName.equals("spectator"))
{
show = true;
}
if (internalSpectator)
{
show = false;
}
if (line.startsWith("setOption ~ ViewMode"))
{
// show this as the only one
}
else if (line.startsWith(Constants.updateCreatureCount)
// || line.startsWith(Constants.setLegionStatus)
// || line.startsWith(Constants.tellLegionLocation)
|| line.startsWith(Constants.serverConnectionOK)
|| line.startsWith(Constants.relayBackProcessedMsg)
|| line.startsWith(Constants.relayBackReceivedMsg)
// || line.startsWith(Constants.)
|| line.startsWith(Constants.pingRequest)
|| line.startsWith(Constants.syncOption))
{
show = false;
}
// Logging/tracking of received messages for development purposes
if (show)
{
if (line.startsWith(Constants.setupTurnState))
{
System.out.println("");
}
String indent = (redoReceived ? " " : "")
+ (replayReceived ? " " : "");
String printLine = line;
int _MAXLEN = 120;
int len = line.length();
if (len > _MAXLEN)
{
printLine = line.substring(0, _MAXLEN) + "...";
}
System.out.println(indent + "<<<" + printLine);
}
if (line.startsWith(Constants.replayOngoing + " ~ true"))
{
replayReceived = true;
}
if (line.startsWith(Constants.redoOngoing + " ~ true"))
{
redoReceived = true;
}
}
public void waitForPrompt() throws SocketTimeoutException,
SocketException, IOException
{
// Directly after connect we should get some first message
// rather quickly... if not, probably Server has already enough
// clients and we would hang in the queue...
socket.setSoTimeout(5000);
initialLine = readOneLine();
if (initialLine.startsWith("SignOn:"))
{
LOGGER.fine("Got prompt: '" + initialLine + "' - ok!");
initialLine = null;
}
// ... but after we got first data, during game it might take
// unpredictable time before next thing comes, so reset it to 0
// ( = wait forever).
socket.setSoTimeout(0);
return;
}
private String waitForSignonOk() throws IOException
{
String line;
boolean signonOk = false;
while (!signonOk)
{
line = readOneLine();
if (line.startsWith("Ack: signOn"))
{
LOGGER.fine("Got SignOn ACK: '" + line + "' - ok!");
signonOk = true;
}
else if (line.startsWith(Constants.setConnectionId))
{
// sets the connection id we get assigned from server.
parseLine(line);
}
else if (line.startsWith(Constants.nak))
{
return "SignOn rejected with NAK: " + line;
}
else if (line.startsWith(Constants.log))
{
// XXX TODO Handle better
// Earlier/Normally this would be forwarded to Client to
// give it to the logger / to LogWindow, but Client is not
// up / available yet.
LOGGER.info("ServerLog: " + line);
}
else if (line.startsWith(Constants.pingRequest))
{
// silently ignore
}
else
{
LOGGER.warning("Ignoring unexpected line from server: '"
+ line + "'");
}
}
// Everything is ok:
return null;
}
private String waitForGameInfo() throws IOException
{
String line;
boolean gotInfo = false;
while (!gotInfo)
{
line = readOneLine();
if (line.startsWith(Constants.gameInitInfo))
{
LOGGER.fine("Got initGameInfo: '" + line + "' - ok!");
parseLine(line);
gotInfo = true;
}
else if (line.startsWith(Constants.nak))
{
return "GameInfo request got NAK: " + line;
}
else if (line.startsWith(Constants.log))
{
// XXX TODO Handle better
LOGGER.info("ServerLog: " + line);
}
else if (line.startsWith(Constants.pingRequest))
{
// silently ignore
}
else
{
LOGGER.warning(getPrintName() + ": got '" + line
+ "' but no use for it ...");
}
}
// Everything is ok:
return null;
}
public String getReasonFail()
{
return reasonFail;
}
public void appendToConnectionLog(String s)
{
if (clientThread != null)
{
clientThread.appendToConnectionLog(s);
}
}
public String getVariantNameForInit()
{
return variantNameForInit;
}
public Collection<String> getPreliminaryPlayerNames()
{
return Collections.unmodifiableCollection(this.preliminaryPlayerNames);
}
public IServer getIServer()
{
return this;
}
public void setClient(Client client)
{
this.clientThread = new ClientThread(client);
clientThread.start();
}
public int getDisposedQueueLen()
{
if (disposedClientThread != null)
{
return disposedClientThread.getQueueLen();
}
else
{
LOGGER.warning(getPrintName()
+ ": can't ask null disposedClientThread for it's queueLen!");
return 0;
}
}
// Implements the method of the "generic" IServerConnection
public void startThread()
{
this.start();
}
@Override
public void run()
{
if (reasonFail != null)
{
// If SCT setup (constructor or tryInitRead() failed,
// they set the reasonFail. SCT.start() is then only called
// so that thread "was run" - otherwise GC would not collect it.
// Then we end up here, do some cleanup, and that's it...
cleanupSocket();
clientThread = null;
goingDown = true;
return;
}
if (reasonFail != null)
{
goingDown = true;
String message = "Server not responding (could connect, "
+ "but didn't got any initial data within 5 seconds).";
String title = "Joining game failed!";
ErrorUtils.showErrorDialog(null, title, message);
}
// ---------------------------------------------------------------
// This is the heart of the whole SocketClientThread.run():
readAndParseUntilDone();
// ---------------------------------------------------------------
// After here: Cleaning up...
if (serverReceiveTimedout)
{
// Right now this should never happen, but since we have
// the catch and set the flag, let's do something with it:)
String title = "No messages from server!";
String message = "No messages from server for very long time. "
+ "Right now this should never happen because in normal game "
+ "situation we work with infinite timeout... ??";
ErrorUtils.showErrorDialog(null, title, message);
}
cleanupSocket();
if (clientThread != null)
{
disposedClientThread = clientThread;
clientThread.disposeQueue();
if (!abandoned)
{
clientThread.disposeClient();
}
clientThread = null;
}
else
{
LOGGER.log(Level.WARNING, "SCT run() " + getName()
+ ": after loop, client already null??");
}
LOGGER.log(Level.FINEST, "SCT run() ending " + getName());
}
private void readAndParseUntilDone()
{
// -----------------------------------------
// Now the "read and parse until done" loop:
String fromServer = null;
try
{
// first !goingDown: server did send dispose, parseLine did set
// goingDown true; when body of loop completes it ends the loop.
// Or client side did set it to true while SCT was in parseLine.
// second !goingDown: Client side did set goingDown to true, while
// SCT was waiting for line from socket, and interrupted it.
// So SCT returns from waitForLine and shall exit the loop.
while (!goingDown && (fromServer = waitForLine()) != null
&& !goingDown)
{
if (fromServer.length() > 0)
{
try
{
LOGGER.finest("SCT of client '" + getName()
+ "' got message from server: " + fromServer);
parseLine(fromServer);
}
catch (Exception ex)
{
LOGGER.log(
Level.WARNING,
"\n++++++\nSCT SocketClientThread " + getName()
+ ", parseLine(): got Exception "
+ ex.toString() + "\n" + ex.getMessage()
+ "\nline=" + fromServer, ex);
}
// increment it now, so that during parsing of next it has right value.
// It's not safe to do it inside parseLine after processing, because it
// would be omitted if an exception occurs.
if (ownMessageCounter != -1)
{
ownMessageCounter++;
}
}
else
{
LOGGER.warning(getPrintName()
+ ": got empty message from server?");
}
}
if (fromServer == null)
{
LOGGER.info("** SCT after loop, got null line from Server!");
}
LOGGER.log(Level.FINE,
"Clean end of SocketClientThread while loop");
}
// just in case...
catch (Exception e)
{
LOGGER.log(Level.WARNING,
"\n^^^^^^^^^^\nSCT.run() major try/catch???\n", e);
setWaiting(false);
}
// catch this (e.g. out of memory error), so that the user know that
// something is seriously wrong, ...
catch (VirtualMachineError vme)
{
String message = "Woooah! A Fatal JVM error was caught while "
+ "processing in " + getPrintName()
+ " the input line:\n === " + fromServer + " ===\n"
+ "\nStack trace:\n" + ErrorUtils.makeStackTraceString(vme)
+ "\n\nGame might be unstable or hang from now on...";
LOGGER.severe(message);
ErrorUtils.showExceptionDialog(null, message, "Fatal JVM Error!",
true);
setWaiting(false);
// ... but throw again because all hope is lost anyway, and e.g.
// assertions and so on (stresstest) should proceed to outside
// as before.
throw (vme);
}
}
private void setWaiting(boolean val)
{
synchronized (isWaitingLock)
{
isWaiting = val;
}
}
private String waitForLine()
{
String line = null;
setWaiting(true);
// First round, the unhandled line from tryInitialRead:
if (initialLine != null)
{
line = initialLine;
initialLine = null;
}
// if client did set it while we were doing parseLine or
// waited in line above we can skip the next read.
else if (!goingDown)
{
try
{
line = readOneLine();
}
catch (SocketTimeoutException ex)
{
serverReceiveTimedout = true;
goingDown = true;
}
catch (SocketException ex)
{
if (selfInterrupted)
{
// ok, interrupted to go down.
}
else
{
// clientThread.setClosedByServer();
LOGGER
.log(Level.WARNING,
"SCT SocketClientThread " + getName()
+ ": got SocketException " + ex.toString());
}
goingDown = true;
}
catch (IOException ex)
{
LOGGER.log(Level.SEVERE, "SCT SocketClientThread " + getName()
+ ", got an IOException ", ex);
goingDown = true;
}
catch (Exception any)
{
LOGGER.log(Level.SEVERE, "SCT SocketClientThread " + getName()
+ ", got Any Exception ", any);
}
}
setWaiting(false);
return line;
}
public boolean isAlreadyDown()
{
return (clientThread == null);
}
private void cleanupSocket()
{
try
{
if (socket != null && !socket.isClosed())
{
socket.close();
}
else
{
LOGGER.log(Level.FINEST, "SCT Closing socket not needed in "
+ getName());
}
}
catch (IOException e)
{
LOGGER.log(Level.WARNING, "SocketClientThread " + getName()
+ ", during socket.close(), got IOException ", e);
}
catch (Exception e)
{
LOGGER.log(Level.WARNING, "SocketClientThread " + getName()
+ ", during socket.close(), got Whatever Exception ", e);
}
finally
{
socket = null;
}
}
@Override
public void interrupt()
{
super.interrupt();
try
{
if (socket != null)
{
socket.close();
}
}
catch (IOException e)
{
// quietly close
}
catch (Exception e)
{
LOGGER.log(Level.SEVERE, "SCT.interrupt() in " + this.getName()
+ ": unexpected Exception.", e);
}
}
/**
* Client originates the dispose:
* If done because all is over, player chose close etc, send also a
* disconnect so that server knows client is "gone". If done because
* of actually or suspected "connection dead/problems", just shut down
* the SCT peacefully, do not inform server, client might want to
* reconnect later with a new SCT / ClientThread pair.
* @param sendConnect If true, sends a disconnect message to server
*/
public void stopSocketClientThread(boolean sendConnect)
{
if (goingDown)
{
return;
}
goingDown = true;
if (sendConnect)
{
sendDisconnect();
}
synchronized (isWaitingLock)
{
// If socketReader is currently waiting on the socket,
// we need to interrupt it.
if (isWaiting)
{
selfInterrupted = true;
this.interrupt();
}
// Otherwise, it will return to back of loop
// anyway once it has done the parseLine execution.
else
{
// no need to interrupt - nothing to do.
}
}
// Now cleanup things go same way as if server would have send dispose.
}
// Client told us we are not relevant any more; must not confirm any
// commit point to server any more, and when SCT thread ends, do not
// call the "dispose whole client" functionality.
private boolean abandoned = false;
public int abandonAndGetMessageCounter()
{
if (abandoned)
{
return -2;
}
abandoned = true;
stopSocketClientThread(false);
return ownMessageCounter;
}
private synchronized void parseLine(String s)
{
if (!goingDown)
{
List<String> li = Split.split(sep, s);
String method = li.remove(0);
callMethod(method, li);
}
}
private void callMethod(String method, List<String> args)
{
if (method.equals(Constants.pingRequest))
{
long requestReceived = new Date().getTime();
int requestNr = -1;
long requestSent = -1L;
if (args.size() >= 2)
{
requestNr = Integer.parseInt(args.remove(0));
requestSent = Long.parseLong(args.remove(0));
}
LOGGER.fine("SCT " + getName() + " received ping request #"
+ requestNr + " from server");
replyToPing(requestNr, requestSent, requestReceived);
/*
if (getName().equals("SCT-clemens"))
{
if (requestNr >= 3 && requestNr < 6)
{
System.out.println("Purposefully not sending ping reply for request #"
+ requestNr);
}
else
{
replyToPing(requestNr, requestSent, requestReceived);
}
}
else
{
replyToPing(requestNr, requestSent, requestReceived);
}
*/
if (clientThread != null
&& clientThread.isEngagementStartupOngoing())
{
String itemsText = "";
int len = clientThread.getQueueLen();
if (len > 0)
{
itemsText = "; items: "
+ clientThread.getQueueContentSummary();
}
logMsgToServer("I", "PingRequest #" + requestNr
+ ": ClientThread (" + playerName + ") queue length is "
+ len + itemsText);
}
}
else if (method.equals(Constants.commitPoint))
{
int commitPointNr = Integer.parseInt(args.remove(0));
int messageNr = Integer.parseInt(args.remove(0));
if (ownMessageCounter == -1)
{
LOGGER.fine("SCT " + getName()
+ ": initializing own counter in commit point #"
+ commitPointNr);
ownMessageCounter = messageNr;
}
if (messageNr == ownMessageCounter)
{
LOGGER.finest(getPrintName() + ": received commit point "
+ commitPointNr + " msg Nr " + messageNr + " own counter "
+ ownMessageCounter);
}
else
{
LOGGER.warning(getPrintName() + ": received commit point "
+ commitPointNr + " msg Nr " + messageNr
+ ", but own counter is " + ownMessageCounter
+ " -adjusting.");
ownMessageCounter = messageNr;
}
if (abandoned)
{
LOGGER.warning(getPrintName() + " already "
+ "abandoned; suppressing confirmCommitPoint for CP# "
+ commitPointNr);
}
else
{
sendToServer(Constants.confirmCommitPoint + sep
+ commitPointNr);
}
}
else if (method.equals(Constants.gameInitInfo))
{
this.variantNameForInit = args.remove(0);
String nameList = args.remove(0);
this.preliminaryPlayerNames = Split.split(Glob.sep, nameList);
}
else if (method.equals(Constants.dispose))
{
clientThread.setClosedByServer();
goingDown = true;
}
else if (method.equals(Constants.relayedPeerRequest))
{
String requestingClientName = args.get(0);
int queueLen = clientThread.getQueueLen();
peerRequestReceived(requestingClientName, queueLen);
// in this one, both the socket reading thread and the actual
// client are supposed to respond.
clientThread.enqueue(method, args);
}
else if (method.equals(Constants.setConnectionId))
{
this.connectionId = Integer.parseInt(args.remove(0));
LOGGER.finer("Server told me my connection id " + connectionId);
}
else if (method.equals(Constants.nak) && args.size() > 0
&& args.get(0) != null && args.get(0).equals("SignOn"))
{
// All other nak's are handled by clientThread/client!
String reason = args.remove(0);
String message = args.remove(0);
goingDown = true;
String title = "Joining game (" + reason + ") failed!";
ErrorUtils.showErrorDialog(null, title, message);
}
else
{
clientThread.enqueue(method, args);
}
}
private String getPrintName()
{
// at the moment, initially "SCT-<initialName>", e.g. SCT-<byName>,
// but as soon as server "fixed" our name it is SCT-<realPlayerName>
// e.g. SCT-katzer .
return getName();
}
private int getConnectionId()
{
return this.connectionId;
}
private void sendToServer(String message)
{
if (socket != null)
{
LOGGER.finer("Client '" + getPrintName() + "' sends to server: "
+ message);
out.println(message);
clientThread.notifyUserIfGameIsPaused(message);
}
else if (message.startsWith(Constants.replyToPing))
{
// silently ignore;
// replyToPing method(s) write directly to socket.
}
else
{
if (clientThread != null)
{
clientThread.notifyThatNotConnected();
}
else if (disposedClientThread != null)
{
disposedClientThread.notifyThatNotConnected();
}
else
{
LOGGER.log(Level.WARNING, getPrintName()
+ ": Attempt to send message '" + message
+ "' but the socket is closed and/or client already null"
+ " and cant't inform any clientThread?");
}
}
}
// Setup method
private void signOn(String loginName, boolean isRemote, int version,
String buildInfo, boolean spectator, int prevConnId)
{
out.println(Constants.signOn + sep + loginName + sep + isRemote + sep
+ version + sep + buildInfo + sep + spectator + sep + prevConnId);
}
private void sendSystemInfo()
{
out.println(Constants.systemInfo + sep + SystemInfo.getOsInfo() + sep
+ SystemInfo.getFullJavaInfo());
}
// Setup method
private void requestGameInfo()
{
out.println(Constants.requestGameInfo);
}
/* Server tells client changed name, Client calls us to keep in sync */
public void updatePlayerName(String playerName)
{
// was initialized to initialName, which might have been "<bySomething>"
this.playerName = playerName;
// Set the thread name
setName("SCT-" + playerName);
}
// IServer methods, called from client and sent over the
// socket to the server.
public void leaveCarryMode()
{
sendToServer(Constants.leaveCarryMode);
}
public void doneWithBattleMoves()
{
sendToServer(Constants.doneWithBattleMoves);
}
public void doneWithStrikes()
{
sendToServer(Constants.doneWithStrikes);
}
public void acquireAngel(Legion legion, CreatureType angelType)
{
sendToServer(Constants.acquireAngel + sep + legion.getMarkerId() + sep
+ angelType);
}
public void doSummon(Summoning event)
{
if (event == null)
{
sendToServer(Constants.doSummon + sep + "null" + sep + "null"
+ sep + "null");
}
else
{
sendToServer(Constants.doSummon + sep + event.getLegion() + sep
+ event.getDonor() + sep + event.getAddedCreatureType());
}
}
public void doRecruit(Recruitment event)
{
CreatureType recruiter = event.getRecruiter();
CreatureType recruited = event.getRecruited();
sendToServer(Constants.doRecruit + sep
+ event.getLegion().getMarkerId() + sep
+ ((recruited == null) ? null : recruited.getName()) + sep
+ ((recruiter == null) ? null : recruiter.getName()));
}
public void engage(MasterHex hex)
{
sendToServer(Constants.engage + sep + hex.getLabel());
}
public void concede(Legion legion)
{
sendToServer(Constants.concede + sep + legion);
}
public void doNotConcede(Legion legion)
{
sendToServer(Constants.doNotConcede + sep + legion.getMarkerId());
}
public void flee(Legion legion)
{
sendToServer(Constants.flee + sep + legion);
}
public void doNotFlee(Legion legion)
{
sendToServer(Constants.doNotFlee + sep + legion);
}
public void makeProposal(String proposalString)
{
sendToServer(Constants.makeProposal + sep + proposalString);
}
public void fight(MasterHex hex)
{
sendToServer(Constants.fight + sep + hex.getLabel());
}
public void doBattleMove(int tag, BattleHex hex)
{
sendToServer(Constants.doBattleMove + sep + tag + sep + hex.getLabel());
}
public synchronized void strike(int tag, BattleHex hex)
{
sendToServer(Constants.strike + sep + tag + sep + hex.getLabel());
}
public synchronized void applyCarries(BattleHex hex)
{
sendToServer(Constants.applyCarries + sep + hex.getLabel());
}
public void undoBattleMove(BattleHex hex)
{
sendToServer(Constants.undoBattleMove + sep + hex.getLabel());
}
public void assignStrikePenalty(String prompt)
{
sendToServer(Constants.assignStrikePenalty + sep + prompt);
}
public void mulligan()
{
sendToServer(Constants.mulligan);
}
public void requestExtraRoll()
{
sendToServer(Constants.requestExtraRoll);
}
public void extraRollResponse(boolean approved, int requestId)
{
sendToServer(Constants.extraRollResponse + sep + approved + sep + requestId);
}
public void undoSplit(Legion splitoff)
{
sendToServer(Constants.undoSplit + sep + splitoff.getMarkerId());
}
public void undoMove(Legion legion)
{
sendToServer(Constants.undoMove + sep + legion.getMarkerId());
}
public void undoRecruit(Legion legion)
{
sendToServer(Constants.undoRecruit + sep + legion.getMarkerId());
}
public void doneWithSplits()
{
sendToServer(Constants.doneWithSplits);
}
public void doneWithMoves()
{
sendToServer(Constants.doneWithMoves);
}
public void doneWithEngagements()
{
sendToServer(Constants.doneWithEngagements);
}
public void doneWithRecruits()
{
sendToServer(Constants.doneWithRecruits);
}
public void withdrawFromGame()
{
LOGGER.log(Level.FINEST, "SCT " + getName() + " sending withDraw");
sendToServer(Constants.withdrawFromGame);
}
public void sendDisconnect()
{
LOGGER.log(Level.FINEST, "SCT " + getName() + " sending disconnect");
sendToServer(Constants.disconnect);
}
public void stopGame()
{
LOGGER.log(Level.FINEST, "SCT " + getName() + " sending stopGame");
sendToServer(Constants.stopGame);
}
public void doSplit(Legion parent, String childMarker,
List<CreatureType> creaturesToSplit)
{
sendToServer(Constants.doSplit + sep + parent.getMarkerId() + sep
+ childMarker + sep + Glob.glob(",", creaturesToSplit));
}
public void doMove(Legion legion, MasterHex hex, EntrySide entrySide,
boolean teleport, CreatureType teleportingLord)
{
sendToServer(Constants.doMove + sep + legion.getMarkerId() + sep
+ hex.getLabel() + sep + entrySide.getLabel() + sep + teleport
+ sep + teleportingLord);
}
public void assignColor(PlayerColor color)
{
sendToServer(Constants.assignColor + sep + color.getName());
}
public void assignFirstMarker(String markerId)
{
sendToServer(Constants.assignFirstMarker + sep + markerId);
}
public void newGame()
{
sendToServer(Constants.newGame);
}
public void loadGame(String filename)
{
sendToServer(Constants.loadGame + sep + filename);
}
// TODO Can this be removed, because save game is done directly
// instead of via socket message? Or do we keep it, for games
// that are run on the "Web Server" ?
public void saveGame(String filename)
{
sendToServer(Constants.saveGame + sep + filename);
}
public void requestToSuspendGame(boolean save)
{
sendToServer(Constants.suspendGame);
}
public void suspendResponse(boolean approved)
{
sendToServer(Constants.suspendResponse + sep + approved);
}
public void checkServerConnection()
{
sendToServer(Constants.checkConnection);
}
public void checkAllConnections(String requestingClientName)
{
sendToServer(Constants.checkAllConnections + sep
+ requestingClientName);
}
public void clientConfirmedCatchup()
{
sendToServer(Constants.catchupConfirmation);
}
public void logMsgToServer(String severity, String message)
{
sendToServer(Constants.logMsgToServer + sep + severity + sep + message);
}
public void cheatModeDestroyLegion(Legion legion)
{
sendToServer(Constants.cheatModeDestroyLegion + sep
+ legion.getMarkerId());
}
public void joinGame(String playerName)
{
sendToServer(Constants.joinGame + sep + playerName);
}
public void watchGame()
{
sendToServer(Constants.watchGame);
}
public void requestSyncDelta(int msgNr, int syncCounter)
{
sendToServer(Constants.requestSyncDelta + sep + msgNr + sep
+ syncCounter);
}
public void peerRequestReceived(String requestingClientName, int queueLen)
{
sendToServer(Constants.peerRequestReceived + sep
+ requestingClientName + sep + queueLen);
}
public void peerRequestProcessed(String requestingClientName)
{
sendToServer(Constants.peerRequestProcessed + sep
+ requestingClientName);
}
public void replyToPing(int requestNr, long requestSent,
long requestReceived)
{
out.println(Constants.replyToPing + sep + requestNr + sep
+ requestSent + sep + requestReceived);
// sendToServer(Constants.replyToPing);
}
public void enforcedConnectionException()
{
if (socket == null)
{
LOGGER.info(getPrintName()
+ ": socket already null, can't fake disconnect...");
return;
}
LOGGER.fine(getPrintName() + ": doing enforced disconnect!");
try
{
appendToConnectionLog("Disconnecting (closing socket)...");
LOGGER.fine("Disconnecting (closing socket)...");
socket.close();
// TODO we should set it here also to null,
// but needs some more testing does it break anything
// socket = null;
// Other ways than close()...
// socket.shutdownOutput();
// socket.shutdownInput();
}
catch (IOException e)
{
LOGGER.warning(getPrintName()
+ ": hm, did fake disconnect and this time got IOException?");
}
}
}