package client;
import common.Player;
import common.Constants;
import common.SpawnPoint;
import common.messages.Message;
import common.messages.MessageAnalyzer;
import common.messages.PlayerJoinMessage;
import common.messages.PlayerLeaveMessage;
import common.messages.PlayerMotionMessage;
import common.messages.ProjectileMessage;
//import common.messages.LoginMessage;
import Extasys.Network.UDP.Client.Connectors.UDPConnector;
import Extasys.Network.UDP.Client.ExtasysUDPClient;
import Extasys.Network.UDP.Client.IUDPClient;
//import Extasys.Network.UDP.Client.Connectors.UDPConnector;
import Extasys.Network.UDP.Client.Connectors.MulticastConnector;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.InetSocketAddress;
//import java.net.SocketException;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Client connection to the server and other clients.
*/
public class ClientExtaSysConnection extends ExtasysUDPClient implements IUDPClient, Constants, ClientConnection
{
public static Logger logger = Logger.getLogger(CLIENT_LOGGER_NAME);
public static final int GAME_CONNECTOR = 1;
public static final int SERVER_CONNECTOR = 0;
public static final byte ERROR_ID = -2;
private SendUpdateMessages fSendUpdateMessagesThread;
private GameEngine engine;
private boolean isConnected;
private boolean waitingForLeaveAck;
private long currentTime;
/**
* Creates a client connection
*/
public ClientExtaSysConnection(InetAddress remoteHostIP, int remoteHostPort, GameEngine engine) throws Exception {
super("SphereorityClient", "The client connection for sphereority", 8,64);
this.engine = engine;
isConnected = false;
// Add a UDP connector to this UDP client.
// You can add more than one connectors if you need to.
AddConnector("ServerConnector", 10240, 8000, remoteHostIP, remoteHostPort,true);
}
/**
* Starts the ClientConnection
*/
// @Override
public void start() throws Exception {
try {
if(!isConnected)
establishServerConnection();
else
logger.log(Level.INFO,"Connection Already Established");
logger.log(Level.INFO,"Starting ClientExtasysConnection");
currentTime = System.currentTimeMillis();
super.Start();
// Restart all the connectors
// Start sending the messages.
startSendingMessages();
} catch (Exception ex) {
stop();
throw ex;
}
}
/**
* Stops the ClientConnection
*/
// @Override
public void stop() {
try {
stopSendingMessages();
endServerConnection();
} catch (Exception ex) {
ex.printStackTrace();
}
super.Stop();
logger.log(Level.INFO,"Stopping ClientExtasysConnection");
long timeElapsed = System.currentTimeMillis() - currentTime;
if(timeElapsed != 0) {
logger.log(Level.INFO,"Total Bytes In: " + getBytesIn() +
" @ rate of " +
(getBytesIn() * 1000) / timeElapsed + " bytes/sec");
logger.log(Level.INFO,"Total Bytes Out: " + getBytesOut() +
" @ rate of " +
(getBytesOut() * 1000) / timeElapsed + " bytes/sec");
}
}
/**
* Attempts to establish a connection to the server.
*/
protected void establishServerConnection() throws Exception {
logger.log(Level.INFO,"Establishing Server Connection");
super.Start();
InetSocketAddress serverAddress
= new InetSocketAddress(SERVER_ADDRESS,SERVER_PORT);
SpawnPoint sp = new SpawnPoint(engine.localPlayer.getPosition());
// Request to the server that we log in.
// Note: Do not need to specify address here.
// Is here just to avoid a nullpointer when writing the message
sendMessage(
new PlayerJoinMessage((byte)-1,
engine.localPlayer.getPlayerName(),
serverAddress,
sp,
false),
SERVER_CONNECTOR);
// Make sure that we only wait at most 10 seconds
long waitTime = System.currentTimeMillis() + WAIT_TIME;
// Wait until we have logged in and prepared the game
while(!isConnected) {
Thread.yield();
// Stop attempting to connect if 10 seconds has past
if(System.currentTimeMillis() > waitTime) {
throw new Exception("Unable to connect to server!");
}
}
super.Stop();
logger.log(Level.INFO,"Server Connection Established!");
}
/**
* Ends the connection to the server.
*/
protected void endServerConnection() throws Exception {
logger.log(Level.INFO,"Ending Server Connection");
// Stop if we already disconnected from the server
if(!isConnected)
return;
waitingForLeaveAck = true;
sendMessage(new PlayerLeaveMessage(engine.localPlayer.getPlayerID()),
SERVER_CONNECTOR);
// Make sure that we only wait at most 5 seconds
long waitTime = System.currentTimeMillis() + WAIT_TIME;
// Wait until we have logged out (received an ACK that it is ok to
// to disconnect).
while(waitingForLeaveAck) {
Thread.yield();
// Stop attempting to connect if 10 seconds has past
if(System.currentTimeMillis() > waitTime) {
throw new Exception("Unable to contact to server!");
}
}
logger.log(Level.INFO,"Disconnected from Server");
}
/**
* How to handle received messages.
* @param connector The connector the message was received from.
* @param packet The packet the message was sent from.
*/
//@Override
public void OnDataReceive(UDPConnector connector, DatagramPacket packet) {
try {
// Retrieve the message
Message message = MessageAnalyzer.getMessage(packet.getData());
// Ignore messages that are sent to yourself
if( (message.getPlayerId() == engine.localPlayer.getPlayerID()
&& !message.isAck()) ||
message.getPlayerId() == -1)
return;
switch(message.getMessageType()) {
case PlayerMotion:
PlayerMotionMessage pm = (PlayerMotionMessage)message;
// New player was added?
engine.processPlayerMotion(pm);
logger.log(Level.FINE,"PlayerMotion: " + pm.getPlayerId()
+ " "
+ pm.getPosition()
+ " "
+ pm.getVelocity()
+ " "
+ pm.getTime());
break;
case PlayerJoin:
PlayerJoinMessage pj = (PlayerJoinMessage) message;
logger.log(Level.FINE,"PlayerJoin: " + pj.getPlayerId());
String myName = engine.localPlayer.getPlayerName();
// Waiting for a server message?
if(!isConnected) {
handleLogin(pj);
}
// Playing the game and message is not about me?
else if(pj.getPlayerId() != ERROR_ID && !pj.getName().equals(myName)) {
engine.processPlayerJoin(pj);
}
break;
case PlayerLeave:
logger.log(Level.FINE,"PlayerLeave: " + message.getPlayerId());
// Connected?
if(isConnected) {
// Handle a logout message if it came from the server.
handleLogout((PlayerLeaveMessage)message);
}
// Ignore otherwise
break;
case Projectile:
ProjectileMessage p = (ProjectileMessage) message;
logger.log(Level.FINE,"Projectile: " + p.getPlayerId()
+ " "
+ p.getStartPosition()
+ " "
+ p.getDirection());
engine.processProjectile(p);
break;
}
}
catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* Handles login given a PlayerJoin Message.
*/
protected void handleLogin(PlayerJoinMessage message) throws Exception {
String myName = engine.localPlayer.getPlayerName();
// Message is from the server?
if(!message.isAck())
return;
// Message is intended for me?
if(!message.getName().equals(myName))
return;
// Check for success or failure
if(message.getPlayerId() == ERROR_ID) {
throw new Exception("Unable to login!");
}
else {
engine.localPlayer.setPlayerID(message.getPlayerId());
AddConnector("GameConnector", 10240, 8000,
message.getAddress().getAddress(),
message.getAddress().getPort(),true);
isConnected = true;
}
}
/**
* Handles logout of players given a PlayerLeave Message.
*/
protected void handleLogout(PlayerLeaveMessage message) throws Exception {
// Message did not come from server?
if(!message.isAck())
return;
// Message is not intended for me remove that player from the game.
if(message.getPlayerId() != engine.localPlayer.getPlayerID()) {
engine.removeActor(engine.getPlayer(message.getPlayerId()));
logger.log(Level.INFO,"Removed a player");
}
// Message is for me so am I actually waiting to disconnect?
else if(waitingForLeaveAck) {
waitingForLeaveAck = false;
isConnected = false;
}
}
/**
* Add a new connector to this client that can be either unicast or multicast.
* @param name is the name of the connector.
* @param readBufferSize is maximum number of bytes the connector can read at a time.
* @param readTimeOut is the maximum time in milliseconds the connector can use to read incoming data.
* @param serverIP is the server's ip address the connector will use to send data.
* @param serverPort is the server's udp port.
* @return the connector.
*/
public UDPConnector AddConnector(String name, int readBufferSize, int readTimeOut, InetAddress serverIP, int serverPort, boolean isMulticast)
{
UDPConnector connector;
connector = isMulticast ? new MulticastConnector(this, name, readBufferSize, readTimeOut, serverIP, serverPort) :
new UDPConnector(this, name, readBufferSize, readTimeOut, serverIP, serverPort);
getConnectors().add(connector);
return connector;
}
/**
* Sends a Sphereority message via all the connectors.
*/
public void sendMessage(Message message) throws Exception {
byte[] msgToSend = message.getByteMessage();
for(UDPConnector connector : getConnectors()) {
connector.SendData(msgToSend, 0, msgToSend.length);
}
}
/**
* Sends a Sphereority message via all the connectors.
*/
public void sendMessage(Message message, int connector) throws Exception {
byte[] msgToSend = message.getByteMessage();
getConnectors().get(connector).SendData(msgToSend, 0, msgToSend.length);
}
/**
* Start sending the messages.
*/
public void startSendingMessages() {
stopSendingMessages();
fSendUpdateMessagesThread = new SendUpdateMessages(this,engine);
fSendUpdateMessagesThread.start();
}
/**
* Stop sending the messages.
*/
public void stopSendingMessages() {
if (fSendUpdateMessagesThread != null)
{
fSendUpdateMessagesThread.Dispose();
fSendUpdateMessagesThread.interrupt();
}
}
}
/**
* Thread used for sending messages
*/
class SendUpdateMessages extends Thread implements Constants
{
private ClientExtaSysConnection fMyClient;
private GameEngine engine;
private boolean fActive = true;
public SendUpdateMessages(ClientExtaSysConnection client, GameEngine engine)
{
fMyClient = client;
this.engine = engine;
}
//@Override
public void run()
{
// int messageCount = 0;
int checkNames = 0;
ClientExtaSysConnection.logger.log(Level.INFO,"Beginning to send game messages");
while(fActive)
{
try
{
if(checkNames == 5)
checkNames = 0;
sendGameMessages(checkNames++);
}
catch (Exception ex)
{
ex.printStackTrace();
Dispose();
fMyClient.stopSendingMessages();
}
}
}
/**
* Send messages about the local player to the game's multicast channel.
* @param checkNames Counter used for knowing when to resolve names.
*/
protected void sendGameMessages(int checkNames) throws Exception {
LocalPlayer localPlayer = engine.localPlayer;
byte playerId = localPlayer.getPlayerID();
float currentTime = (float)System.currentTimeMillis();
// Send where the player is now
fMyClient.sendMessage(localPlayer.getMotionPacket(currentTime),
ClientExtaSysConnection.GAME_CONNECTOR);
// Go through all the projectiles in the game
synchronized(engine.bulletList) {
for(common.Projectile p : engine.bulletList) {
// Check if this is our own projectile and whether
// we have sent information about it
if(!p.isDelivered() && !(p.getOwner() != playerId)) {
// Deliver the information about the projectile
fMyClient.sendMessage(new ProjectileMessage(playerId,
p.getStartPosition(),
p.getDirection()),
ClientExtaSysConnection.GAME_CONNECTOR);
p.delivered();
}
}
}
// Resolve names that have not been found every 4th time
if(checkNames == 4) {
synchronized(engine.playerList) {
for(Player player : engine.playerList) {
if (player.getPlayerName().equals(RESOLVING_NAME)) {
ClientExtaSysConnection.logger.log(Level.INFO,"WHOIS " + player.getPlayerID());
fMyClient.sendMessage(new PlayerJoinMessage(
player.getPlayerID(),
RESOLVING_NAME,
new InetSocketAddress(SERVER_ADDRESS,SERVER_PORT),
new SpawnPoint(player.getPosition())),
ClientExtaSysConnection.SERVER_CONNECTOR);
}
}
}
}
Thread.sleep(RESEND_DELAY);
}
public void Dispose()
{
fActive = false;
}
}