package org.jrenner.fps.net.client;
import com.badlogic.gdx.math.Vector3;
import com.badlogic.gdx.utils.Array;
import com.badlogic.gdx.utils.GdxRuntimeException;
import com.badlogic.gdx.utils.IntIntMap;
import com.badlogic.gdx.utils.TimeUtils;
import com.esotericsoftware.kryonet.Client;
import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.FrameworkMessage;
import com.esotericsoftware.kryonet.Listener;
import org.jrenner.fps.Log;
import org.jrenner.fps.Main;
import org.jrenner.fps.Player;
import org.jrenner.fps.Tools;
import org.jrenner.fps.entity.DynamicEntity;
import org.jrenner.fps.entity.Entity;
import org.jrenner.fps.event.ClientEvent;
import org.jrenner.fps.net.NetManager;
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.CommandPackage;
import org.jrenner.fps.net.packages.EntityInfoRequest;
import org.jrenner.fps.net.packages.EntityUpdate;
import org.jrenner.fps.net.packages.ServerMessage;
import org.jrenner.fps.net.packages.ServerUpdate;
import java.io.IOException;
public class NetClient extends AbstractClient {
private Client client;
public ClientUpdate clientUpdate = new ClientUpdate();
/** will be set to server's tickInterval after connecting */
public float tickInterval;
/** server tick number */
public int tickNum;
/** this client's input tick number, not related to server tick */
public int inputTick;
public int ping;
private int pingRefreshInterval = 1000; // ms
private long lastPingUpdateTime;
private int clientTimeout = 5000; // ms
public int playerId = -1;
public final Array<ServerUpdate> incomingUpdates = new Array<>();
public NetClient() {
client = new Client(NetManager.writeBufferSize, NetManager.objectBufferSize);
client.addListener(createListener());
NetManager.registerKryoClasses(client.getKryo());
client.start();
connect();
}
public void sendClientUpdateToServer(CommandPackage cmdPack) {
if (player != null) {
clientUpdate.reset();
clientUpdate.entityId = player.entity.id;
clientUpdate.cmdPack = cmdPack;
clientUpdate.inputTick = inputTick++;
client.sendUDP(clientUpdate);
}
// update ping
long now = TimeUtils.millis();
if (now - lastPingUpdateTime >= pingRefreshInterval) {
lastPingUpdateTime = now;
client.updateReturnTripTime();
}
}
public void connect() {
try {
client.connect(clientTimeout, NetManager.host, NetManager.tcpPort, NetManager.udpPort);
} catch (IOException e) {
throw new GdxRuntimeException(e);
}
}
public void handleConnect(Connection conn) {
connectToServer();
}
private void handleDisconnect(Connection conn) {
}
@Override
public void connectToServer() {
Log.debug("connected to server");
client.sendTCP(ClientRequest.CreateNewPlayerAssignID);
requestServerInfo();
requestLevelGeometry();
}
public void requestServerInfo() {
client.sendTCP(ClientRequest.RequestServerInfo);
}
public void requestLevelGeometry() {
client.sendTCP(ClientRequest.RequestLevelGeometry);
}
@Override
public void disconnectedFromServer() {
Log.debug("disconnected from server");
}
private long highestTickNumUpdateReceived;
private long lastServerUpdateTime;
private void handleReceived(Connection conn, Object obj) {
//Log.debug("received from server: " + obj);
if (obj == null) {
Log.error("received null object from kryonet");
}
if (obj instanceof ServerMessage.AssignPlayerEntityId) {
handleAssignPlayerEntityId((ServerMessage.AssignPlayerEntityId) obj);
}
else if (obj instanceof ServerMessage.DestroyEntity) {
ServerMessage.DestroyEntity destroy = (ServerMessage.DestroyEntity) obj;
Main.inst.clientEventManager.addEventToQueue(new ClientEvent.DestroyEntity(destroy.id));
}
else if (obj instanceof ServerMessage.ServerInfo) {
handleServerInfo((ServerMessage.ServerInfo) obj);
}
else if (obj instanceof ServerMessage.LevelGeometry) {
handleLevelGeometry((ServerMessage.LevelGeometry) obj);
}
else if (obj instanceof ChatMessage) {
ChatMessage chat = (ChatMessage) obj;
// creation time based on when client receives it, override the value
chat.createTime = TimeUtils.millis();
Main.inst.clientEventManager.addEventToQueue(new ClientEvent.ReceivedChatMessage(chat));
}
else if (obj instanceof ServerUpdate) {
ServerUpdate serverUpdate = (ServerUpdate) obj;
if (serverUpdate.tickNum <= highestTickNumUpdateReceived) {
Log.debug("discarding outdated serverupdate, ticknum: " + serverUpdate.tickNum + ", current tick: " + highestTickNumUpdateReceived);
return;
}
/*long now = TimeUtils.millis();
long elapsed = now - lastServerUpdateTime;
lastServerUpdateTime = now;
Log.debug("server update elapsed time (tick: " + serverUpdate.tickNum + "): " + elapsed);*/
handleUpdateFromServer(serverUpdate);
}
else if (obj instanceof EntityInfoRequest.Response) {
handleEntityInfoRequestResponse((EntityInfoRequest.Response) obj);
}
else if (obj instanceof FrameworkMessage.Ping) {
if (((FrameworkMessage.Ping) obj).isReply) {
ping = client.getReturnTripTime();
//System.out.println("new ping: " + ping);
}
}
else if (obj instanceof BulletPackage) {
handleBulletPackage((BulletPackage) obj);
} else {
//Log.debug("unhandled object from server: " + obj);
if (!obj.getClass().getName().contains("com.esotericsoftware.kryonet")) {
throw new GdxRuntimeException("unhandled object from server: " + obj);
}
}
}
private void handleIdle(Connection conn) {}
private Listener createListener() {
return 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);
}
};
}
// Process Incoming
private void handleUpdateFromServer(ServerUpdate upd) {
synchronized (incomingUpdates) {
incomingUpdates.add(upd);
}
}
@Override
public void update() {
processServerUpdateQueue();
}
private void processServerUpdateQueue() {
synchronized (incomingUpdates) {
for (ServerUpdate upd : incomingUpdates) {
upd.applyUpdates();
}
incomingUpdates.clear();
}
}
private void handleAssignPlayerEntityId(ServerMessage.AssignPlayerEntityId assign) {
Main.inst.clientEventManager.addEventToQueue(new ClientEvent.AssignPlayerId(assign.id));
}
private static final Vector3 tmp = new Vector3();
private void handleBulletPackage(BulletPackage bpack) {
//Log.debug("client creating bullets: " + bpack.locations.length);
for (Vector3 hitLoc : bpack.locations) {
//Log.debug("\tlocation: " + Tools.fmt(hitLoc));
Main.inst.clientEventManager.addEventToQueue(new ClientEvent.CreateBullet(hitLoc.x, hitLoc.y, hitLoc.z));
}
}
private void handleServerInfo(ServerMessage.ServerInfo info) {
this.tickNum = info.tickNum;
this.tickInterval = info.tickInterval;
Log.info("Server Name: " + info.serverName);
Log.info("Server Message: " + info.serverMsg);
Log.info("Set tick from server: " + info.tickNum);
}
private void handleEntityInfoRequestResponse(EntityInfoRequest.Response resp) {
Log.debug("EntityInfo response: " + resp.id);
Main.inst.clientEventManager.addEventToQueue(new ClientEvent.CreateEntity(resp.id, resp.isPlayer, resp.graphicsType));
}
private void handleLevelGeometry(ServerMessage.LevelGeometry geo) {
Log.debug("Server sent level static geometry information, size: " + geo.staticPieces.length);
Main.inst.clientEventManager.addEventToQueue(new ClientEvent.CreateLevelStatics(geo.staticPieces));
}
// Send Outgoing
private IntIntMap entityIdInfoRequestTicks = new IntIntMap();
private int requestInterval = 10; // server ticks
public void requestEntityInfo(int id) {
int lastRequestTime = entityIdInfoRequestTicks.get(id, -1);
if (lastRequestTime == -1 || (tickNum - lastRequestTime) > requestInterval) {
EntityInfoRequest req = new EntityInfoRequest();
req.id = id;
client.sendTCP(req);
entityIdInfoRequestTicks.put(id, tickNum);
Log.debug("sent entity info request for id: " + id);
} else {
Log.error("Already requested info for entity, id: " + id + ". Must wait a bit");
}
}
@Override
public void sendChatMessage(ChatMessage chat) {
client.sendTCP(chat);
}
@Override
public void requestResetPosition() {
client.sendTCP(ClientRequest.RequestResetPlayerPosition);
}
}