package net.tacospice.stevenet;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import net.tacospice.stevenet.NetworkMessages.ChatMessage;
import net.tacospice.stevenet.NetworkMessages.CreateNode;
import net.tacospice.stevenet.NetworkMessages.DestroyNode;
import net.tacospice.stevenet.NetworkMessages.Disconnect;
import net.tacospice.stevenet.NetworkMessages.Disconnect.Reason;
import net.tacospice.stevenet.NetworkMessages.Login;
import net.tacospice.stevenet.NetworkMessages.UserConnect;
import net.tacospice.stevenet.NetworkMessages.UserDisconnect;
import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.FrameworkMessage.KeepAlive;
import com.esotericsoftware.kryonet.Listener;
import com.esotericsoftware.kryonet.Server;
public class NetServer extends ClientServer
{
private final Server kryoServer;
private ServerEventHandler eventHandler = null;
private int nextNodeId = 0;
public NetServer()
{
super(true);
kryoServer = new com.esotericsoftware.kryonet.Server()
{
@Override
protected Connection newConnection()
{
return new NetConnection();
}
};
NetworkMessages.registerBuiltInMessages(kryoServer);
kryoServer.addListener(new Listener()
{
@Override
public void received(Connection c, Object object)
{
NetConnection connection = (NetConnection) c;
// Replicator update from client
if (object instanceof ReplicatorMessage)
{
int nodeId = ((ReplicatorMessage) object).nodeId;
int repId = ((ReplicatorMessage) object).replicatorId;
Node n = nodes.get(nodeId);
if (n != null && c.getID() == n.getOwner())
{
Replicator r = n.getReplicator(repId);
if (r != null && r.getOwnerToServer())
r.updateFromMessage((ReplicatorMessage) object);
}
return;
}
// Login request
if (object instanceof Login)
{
// Already registered?
if (connection.name != null)
{
logMessage("Discarded login from " + c.getID() + " (already logged in as " + connection.name + ")");
Disconnect dc = new Disconnect();
dc.reason = Reason.AlreadyLoggedIn;
sendToTCP(connection.getID(), dc);
connection.close();
return;
}
// Invalid name?
String name = ((Login) object).name;
if (name == null)
{
logMessage("Discarded login from " + c.getRemoteAddressTCP() + " (name missing)");
Disconnect dc = new Disconnect();
dc.reason = Reason.NoName;
sendToTCP(connection.getID(), dc);
connection.close();
return;
}
name = name.trim();
if (name.length() == 0)
{
logMessage("Discarded login from " + c.getRemoteAddressTCP() + " (name has zero length)");
Disconnect dc = new Disconnect();
dc.reason = Reason.ZeroNameLength;
sendToTCP(connection.getID(), dc);
connection.close();
return;
}
// Store the name on the connection
connection.name = name;
// TODO: verify connection with ServerEventHandler
clientConnected(connection);
// Replicate existing nodes
replicateAllNodes(connection.getID());
// Notify users
UserConnect uc = new UserConnect();
uc.name = connection.name;
sendToAllTCP(uc);
logMessage("Accepted login from " + c.getRemoteAddressTCP() + ": " + name);
return;
}
// Ignore any other messages by unregistered users
if (connection.name == null)
{
// TODO: disconnect user?
logMessage("Disconnected " + c.getRemoteAddressTCP() + " (not registered)");
Disconnect dc = new Disconnect();
dc.reason = Reason.DidNotRegister;
sendToTCP(connection.getID(), dc);
connection.close();
return;
}
if (object instanceof ChatMessage)
{
ChatMessage message = (ChatMessage) object;
// Invalid text?
String messageText = message.text;
if (messageText == null)
return;
messageText = messageText.trim();
if (messageText.length() == 0)
return;
// Prepend the connection's name and send to everyone.
message.text = connection.name + ": " + message.text;
sendToAllTCP(message);
return;
}
if (object instanceof KeepAlive)
return;
unknownMessage(object);
}
@Override
public void disconnected(Connection c)
{
NetConnection connection = (NetConnection) c;
if (connection.name != null)
{
UserDisconnect uc = new UserDisconnect();
uc.name = connection.name;
sendToAllExceptTCP(connection.getID(), uc);
clientDisconnected(connection);
Iterator<Map.Entry<Integer, Node>> it = nodes.entrySet().iterator();
while (it.hasNext())
{
Map.Entry<Integer, Node> entry = it.next();
if (entry.getValue().getOwner() == connection.getID())
{
announceNodeRemoval(entry.getValue().getId(), entry.getValue().getOwner());
it.remove();
}
}
}
}
});
}
public void listen(int tcpPort, int udpPort) throws IOException
{
kryoServer.bind(tcpPort, udpPort);
kryoServer.start();
}
public void close()
{
kryoServer.close();
kryoServer.stop();
clearNodes();
}
public Server getKryoServer()
{
return kryoServer;
}
public void setEventHandler(ServerEventHandler handler)
{
eventHandler = handler;
}
public Node createNode(int classId)
{
return new Node(nextNodeId++, classId, true);
}
private void replicateAllNodes(int connectionId)
{
for (Node n : nodes.values())
{
if (!n.wasBroadcast())
continue;
CreateNode cn = new CreateNode();
cn.id = n.getId();
cn.classId = n.getClassId();
cn.owner = n.getOwner() == connectionId;
sendToTCP(connectionId, cn);
}
}
public List<Node> getNodesByOwner(int ownerId)
{
List<Node> result = new ArrayList<Node>();
Iterator<Map.Entry<Integer, Node>> it = nodes.entrySet().iterator();
while (it.hasNext())
{
Map.Entry<Integer, Node> entry = it.next();
if (entry.getValue().getOwner() == ownerId)
result.add(entry.getValue());
}
return result;
}
public void announceNodeRemoval(int nodeId, int except)
{
DestroyNode dn = new DestroyNode();
dn.id = nodeId;
sendToAllExceptTCP(except, dn);
}
/*
* Event handler methods
*/
private void logMessage(String message)
{
if (eventHandler != null)
eventHandler.logMessage(message);
}
private void clientConnected(NetConnection connection)
{
if (eventHandler != null)
eventHandler.clientConnected(connection);
}
private void clientDisconnected(NetConnection connection)
{
if (eventHandler != null)
eventHandler.clientDisconnected(connection);
}
private void unknownMessage(Object message)
{
if (eventHandler != null)
eventHandler.unknownMessage(message);
}
/*
* NetEndpoint methods
*/
@Override
public void sendToAllTCP(Object message)
{
kryoServer.sendToAllTCP(message);
}
@Override
public void sendToAllExceptTCP(int id, Object message)
{
kryoServer.sendToAllExceptTCP(id, message);
}
@Override
public void sendToTCP(int id, Object message)
{
kryoServer.sendToTCP(id, message);
}
@Override
public void sendToAllUDP(Object message)
{
kryoServer.sendToAllUDP(message);
}
@Override
public void sendToAllExceptUDP(int id, Object message)
{
kryoServer.sendToAllExceptUDP(id, message);
}
@Override
public void sendToUDP(int id, Object message)
{
kryoServer.sendToUDP(id, message);
}
/*
* Unused methods
*/
@Override
public void sendTCP(Object message)
{
}
@Override
public void sendUDP(Object message)
{
}
}