package org.jrenner.fps.net;
import com.badlogic.gdx.math.MathUtils;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.ObjectMap;
import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.Listener;
import com.esotericsoftware.kryonet.Server;
import org.jrenner.fps.LevelBuilder;
import org.jrenner.fps.LevelStatic;
import org.jrenner.fps.Log;
import java.io.IOException;
import org.jrenner.fps.entity.DynamicEntity;
import org.jrenner.fps.entity.Entity;
import org.jrenner.fps.net.client.ClientUpdate;
import org.jrenner.fps.net.packages.BulletPackage;
import org.jrenner.fps.net.packages.ChatMessage;
import org.jrenner.fps.net.packages.ClientRequest;
import org.jrenner.fps.net.packages.EntityInfoRequest;
import org.jrenner.fps.net.packages.ServerMessage;
import org.jrenner.fps.net.packages.ServerUpdate;
import org.jrenner.fps.utils.Pooler;
public class NetServer extends AbstractServer {
private Server server;
public Array<Connection> connectedClients = new Array<>();
private ObjectMap<Connection, ClientInfo> clientMap;
public Array<Integer> playerEntityIds = new Array<>();
public static int lagMin;
public static int lagMax;
public static boolean simulateLag = false;
public NetServer() {
clientMap = new ObjectMap<>();
server = new Server(NetManager.writeBufferSize, NetManager.objectBufferSize);
NetManager.registerKryoClasses(server.getKryo());
try {
server.bind(NetManager.tcpPort, NetManager.udpPort);
server.start();
server.addListener(createListener(simulateLag));
Log.debug("server is listening at: " + NetManager.host + ":" + NetManager.tcpPort);
} catch (IOException e) {
Log.error(e.toString());
throw new GdxRuntimeException(e);
}
}
private void handleConnect(Connection conn) {
Log.debug("connected to server: " + conn);
ClientInfo clientInfo = new ClientInfo();
clientMap.put(conn, clientInfo);
connectedClients.add(conn);
}
private void handleDisconnect(Connection conn) {
Log.debug("disconnected from server: " + conn);
ClientInfo clientInfo = clientMap.get(conn);
Entity.destroy(clientInfo.playerEntityId);
connectedClients.removeValue(conn, true);
}
private void handleReceived(Connection conn, Object obj) {
//AsLog.debug("received from client (" + conn + "): " + obj);
if (obj instanceof ClientRequest) {
handleClientRequest(conn, (ClientRequest) obj);
} else if (obj instanceof ClientUpdate) {
ClientUpdate clientUpdate = (ClientUpdate) obj;
ClientInfo clientInfo = clientMap.get(conn);
clientInfo.lastInputTick = clientUpdate.inputTick;
handleClientUpdate(clientUpdate);
} else if (obj instanceof EntityInfoRequest) {
handleEntityInfoRequest(conn, (EntityInfoRequest) obj);
} else if (obj instanceof ChatMessage) {
ChatMessage chat = (ChatMessage) obj;
chat.playerId = clientMap.get(conn).playerEntityId;
queueChatMessage((ChatMessage) obj);
} else {
if (!obj.getClass().getName().contains("com.esotericsoftware.kryonet")) {
String err = "unhandled object from client to server: " + obj;
Log.debug(err);
queueChatMessage(new ChatMessage(err));
}
}
}
private void handleIdle(Connection conn) {}
@Override
public void update() {
super.update();
}
private Listener createListener(boolean lagListener) {
Listener listener = new Listener() {
@Override
public void connected(Connection connection) {
handleConnect(connection);
}
@Override
public void disconnected(Connection connection) {
handleDisconnect(connection);
}
@Override
public void received(Connection connection, Object object) {
handleReceived(connection, object);
}
@Override
public void idle(Connection connection) {
handleIdle(connection);
}
};
if (lagListener) {
return new Listener.LagListener(lagMin, lagMax, listener);
} else {
return listener;
}
}
@Override
public void sendUpdateToClients() {
ServerUpdate serverUpdate = ServerUpdate.createServerUpdate();
for (Connection client : connectedClients) {
ClientInfo clientInfo = clientMap.get(client);
serverUpdate.playerInputTick = clientInfo.lastInputTick;
client.sendUDP(serverUpdate);
}
serverUpdate.free();
}
public void handleClientRequest(Connection conn, ClientRequest req) {
ClientInfo info = clientMap.get(conn);
switch (req) {
case CreateNewPlayerAssignID:
DynamicEntity player = createPlayer();
ServerMessage.AssignPlayerEntityId playerAssignment = new ServerMessage.AssignPlayerEntityId();
playerAssignment.id = player.id;
info.playerEntityId = player.id;
Log.debug("server creating new player, assigning id: " + player.id);
sendPlayerConnectedChatMessage(player.id);
playerEntityIds.add(player.id);
conn.sendTCP(playerAssignment);
break;
case RequestServerInfo:
ServerMessage.ServerInfo serverInfo = new ServerMessage.ServerInfo();
serverInfo.tickNum = tickNum;
serverInfo.tickInterval = tickInterval;
serverInfo.serverName = serverName;
serverInfo.serverMsg = serverMsg;
conn.sendTCP(serverInfo);
break;
case RequestLevelGeometry:
ServerMessage.LevelGeometry levelGeometry = new ServerMessage.LevelGeometry();
levelGeometry.staticPieces = LevelBuilder.staticPieces.toArray(LevelStatic.class);
conn.sendTCP(levelGeometry);
break;
case RequestResetPlayerPosition:
ClientInfo clientInfo = clientMap.get(conn);
Log.debug("resetting player position due to client request, id: " + clientInfo.playerEntityId);
resetPlayerPosition(clientInfo.playerEntityId);
break;
default:
throw new GdxRuntimeException("unhandled");
}
}
public void handleEntityInfoRequest(Connection conn, EntityInfoRequest req) {
Entity ent = Entity.getEntityById(req.id);
// TODO in the future put more fields into the response
EntityInfoRequest.Response resp = new EntityInfoRequest.Response();
resp.isPlayer = isPlayerEntity(ent.id);
resp.id = ent.id;
if (isPlayerEntity(ent.id)) {
resp.graphicsType = Entity.EntityGraphicsType.Model;
} else {
resp.graphicsType = Entity.EntityGraphicsType.Decal;
}
conn.sendTCP(resp);
}
public boolean isPlayerEntity(int id) {
return playerEntityIds.contains(id, false);
}
@Override
public void processBulletHits() {
synchronized (bulletHitQueue) {
BulletPackage bpack = new BulletPackage();
bpack.locations = new Vector3[bulletHitQueue.size];
for (int i = 0; i < bulletHitQueue.size; i++) {
bpack.locations[i] = bulletHitQueue.get(i);
}
server.sendToAllUDP(bpack);
for (Vector3 v3 : bulletHitQueue) {
Pooler.free(v3);
}
}
bulletHitQueue.clear();
}
@Override
public void processChatMessages() {
synchronized (chatQueue) {
for (ChatMessage msg : chatQueue) {
server.sendToAllTCP(msg);
}
chatQueue.clear();
}
}
@Override
protected void sendDestroyedEntityMessage(int id) {
ServerMessage.DestroyEntity destroyMsg = new ServerMessage.DestroyEntity();
destroyMsg.id = id;
server.sendToAllTCP(destroyMsg);
String text = "Entity " + id + " has been destroyed";
ChatMessage chatMsg = new ChatMessage(text);
queueChatMessage(chatMsg);
}
}