LOOHP-Limbo/src/com/loohp/limbo/Server/ClientConnection.java

413 lines
17 KiB
Java

package com.loohp.limbo.Server;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import com.loohp.limbo.DeclareCommands;
import com.loohp.limbo.Limbo;
import com.loohp.limbo.File.ServerProperties;
import com.loohp.limbo.Location.Location;
import com.loohp.limbo.Player.Player;
import com.loohp.limbo.Server.Packets.Packet;
import com.loohp.limbo.Server.Packets.PacketHandshakingIn;
import com.loohp.limbo.Server.Packets.PacketLoginInLoginStart;
import com.loohp.limbo.Server.Packets.PacketLoginOutDisconnect;
import com.loohp.limbo.Server.Packets.PacketLoginOutLoginSuccess;
import com.loohp.limbo.Server.Packets.PacketOut;
import com.loohp.limbo.Server.Packets.PacketPlayInChat;
import com.loohp.limbo.Server.Packets.PacketPlayInKeepAlive;
import com.loohp.limbo.Server.Packets.PacketPlayInPosition;
import com.loohp.limbo.Server.Packets.PacketPlayInPositionAndLook;
import com.loohp.limbo.Server.Packets.PacketPlayInRotation;
import com.loohp.limbo.Server.Packets.PacketPlayInTabComplete;
import com.loohp.limbo.Server.Packets.PacketPlayOutDeclareCommands;
import com.loohp.limbo.Server.Packets.PacketPlayOutDisconnect;
import com.loohp.limbo.Server.Packets.PacketPlayOutLogin;
import com.loohp.limbo.Server.Packets.PacketPlayOutMapChunk;
import com.loohp.limbo.Server.Packets.PacketPlayOutPlayerAbilities;
import com.loohp.limbo.Server.Packets.PacketPlayOutPlayerAbilities.PlayerAbilityFlags;
import com.loohp.limbo.Server.Packets.PacketPlayOutPlayerInfo;
import com.loohp.limbo.Server.Packets.PacketPlayOutPlayerInfo.PlayerInfoAction;
import com.loohp.limbo.Server.Packets.PacketPlayOutPlayerInfo.PlayerInfoData;
import com.loohp.limbo.Server.Packets.PacketPlayOutPlayerInfo.PlayerInfoData.PlayerInfoDataAddPlayer.PlayerSkinProperty;
import com.loohp.limbo.Server.Packets.PacketPlayOutPositionAndLook;
import com.loohp.limbo.Server.Packets.PacketPlayOutShowPlayerSkins;
import com.loohp.limbo.Server.Packets.PacketPlayOutSpawnPosition;
import com.loohp.limbo.Server.Packets.PacketPlayOutTabComplete;
import com.loohp.limbo.Server.Packets.PacketPlayOutTabComplete.TabCompleteMatches;
import com.loohp.limbo.Server.Packets.PacketPlayOutUpdateViewPosition;
import com.loohp.limbo.Server.Packets.PacketStatusInPing;
import com.loohp.limbo.Server.Packets.PacketStatusInRequest;
import com.loohp.limbo.Server.Packets.PacketStatusOutPong;
import com.loohp.limbo.Server.Packets.PacketStatusOutResponse;
import com.loohp.limbo.Utils.CustomStringUtils;
import com.loohp.limbo.Utils.DataTypeIO;
import com.loohp.limbo.Utils.MojangAPIUtils;
import com.loohp.limbo.Utils.MojangAPIUtils.SkinResponse;
import com.loohp.limbo.World.BlockPosition;
import com.loohp.limbo.World.DimensionRegistry;
import com.loohp.limbo.World.World;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.chat.ComponentSerializer;
import net.querz.mca.Chunk;
public class ClientConnection extends Thread {
public enum ClientState {
HANDSHAKE,
STATUS,
LOGIN,
PLAY,
DISCONNECTED;
}
private Socket client_socket;
private boolean running;
private ClientState state;
private Player player;
private long lastKeepAlivePayLoad;
protected DataOutputStream output;
protected DataInputStream input;
private InetAddress iNetAddress;
public ClientConnection(Socket client_socket) {
this.client_socket = client_socket;
this.iNetAddress = client_socket.getInetAddress();
}
public InetAddress getiNetAddress() {
return iNetAddress;
}
public long getLastKeepAlivePayLoad() {
return lastKeepAlivePayLoad;
}
public void setLastKeepAlivePayLoad(long payLoad) {
this.lastKeepAlivePayLoad = payLoad;
}
public Player getPlayer() {
return player;
}
public ClientState getClientState() {
return state;
}
public Socket getSocket() {
return client_socket;
}
public boolean isRunning() {
return running;
}
public void sendPacket(PacketOut packet) throws IOException {
byte[] packetByte = packet.serializePacket();
DataTypeIO.writeVarInt(output, packetByte.length);
output.write(packetByte);
}
public void disconnect(BaseComponent[] reason) {
try {
PacketPlayOutDisconnect packet = new PacketPlayOutDisconnect(ComponentSerializer.toString(reason));
byte[] packetByte = packet.serializePacket();
DataTypeIO.writeVarInt(output, packetByte.length);
output.write(packetByte);
output.flush();
} catch (IOException e) {
e.printStackTrace();
}
try {
client_socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
public void disconnectDuringLogin(BaseComponent[] reason) {
try {
PacketLoginOutDisconnect packet = new PacketLoginOutDisconnect(ComponentSerializer.toString(reason));
byte[] packetByte = packet.serializePacket();
DataTypeIO.writeVarInt(output, packetByte.length);
output.write(packetByte);
output.flush();
} catch (IOException e) {
e.printStackTrace();
}
try {
client_socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void run() {
running = true;
state = ClientState.HANDSHAKE;
try {
client_socket.setKeepAlive(true);
input = new DataInputStream(client_socket.getInputStream());
output = new DataOutputStream(client_socket.getOutputStream());
DataTypeIO.readVarInt(input);
//int handShakeId = DataTypeIO.readVarInt(input);
DataTypeIO.readVarInt(input);
PacketHandshakingIn handshake = new PacketHandshakingIn(input);
boolean isBungeecord = Limbo.getInstance().getServerProperties().isBungeecord();
String bungeeForwarding = handshake.getServerAddress();
UUID bungeeUUID = null;
SkinResponse bungeeSkin = null;
switch (handshake.getHandshakeType()) {
case STATUS:
state = ClientState.STATUS;
while (client_socket.isConnected()) {
DataTypeIO.readVarInt(input);
int packetId = DataTypeIO.readVarInt(input);
Class<? extends Packet> packetType = Packet.getStatusIn().get(packetId);
if (packetType == null) {
//do nothing
} else if (packetType.equals(PacketStatusInRequest.class)) {
String str = client_socket.getInetAddress().getHostName() + ":" + client_socket.getPort();
Limbo.getInstance().getConsole().sendMessage("[/" + str + "] <-> Handshake Status has pinged");
PacketStatusOutResponse packet = new PacketStatusOutResponse(Limbo.getInstance().getServerListResponseJson());
sendPacket(packet);
} else if (packetType.equals(PacketStatusInPing.class)) {
PacketStatusInPing ping = new PacketStatusInPing(input);
PacketStatusOutPong packet = new PacketStatusOutPong(ping.getPayload());
sendPacket(packet);
break;
}
}
break;
case LOGIN:
state = ClientState.LOGIN;
if (isBungeecord) {
try {
String[] data = bungeeForwarding.split("\\x00");
//String host = data[0];
String ip = data[1];
bungeeUUID = UUID.fromString(data[2].replaceFirst("([0-9a-fA-F]{8})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]{4})([0-9a-fA-F]+)", "$1-$2-$3-$4-$5"));
iNetAddress = InetAddress.getByName(ip);
String skinJson = data[3];
String skin = skinJson.split("\"value\":\"")[1].split("\"")[0];
String signature = skinJson.split("\"signature\":\"")[1].split("\"")[0];
bungeeSkin = new SkinResponse(skin, signature);
} catch (Exception e) {
Limbo.getInstance().getConsole().sendMessage("If you wish to use bungeecord's IP forwarding, please enable that in your bungeecord config.yml as well!");
disconnectDuringLogin(new BaseComponent[] {new TextComponent(ChatColor.RED + "Please connect from the proxy!")});
}
}
while (client_socket.isConnected()) {
int size = DataTypeIO.readVarInt(input);
int packetId = DataTypeIO.readVarInt(input);
Class<? extends Packet> packetType = Packet.getLoginIn().get(packetId);
if (packetType == null) {
input.skipBytes(size - DataTypeIO.getVarIntLength(packetId));
} else if (packetType.equals(PacketLoginInLoginStart.class)) {
PacketLoginInLoginStart start = new PacketLoginInLoginStart(input);
String username = start.getUsername();
UUID uuid = isBungeecord ? bungeeUUID : UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8));
PacketLoginOutLoginSuccess success = new PacketLoginOutLoginSuccess(uuid, username);
sendPacket(success);
state = ClientState.PLAY;
player = new Player(this, username, uuid, Limbo.getInstance().entityCount.getAndIncrement(), Limbo.getInstance().getServerProperties().getWorldSpawn());
Limbo.getInstance().addPlayer(player);
break;
} else {
input.skipBytes(size - DataTypeIO.getVarIntLength(packetId));
}
}
break;
}
if (state == ClientState.PLAY) {
TimeUnit.MILLISECONDS.sleep(500);
ServerProperties p = Limbo.getInstance().getServerProperties();
PacketPlayOutLogin join = new PacketPlayOutLogin(player.getEntityId(), false, p.getDefaultGamemode(), new String[] {p.getLevelName().toString()}, DimensionRegistry.getCodec(), p.getLevelDimension().toString(), p.getLevelName().toString(), 0, (byte) p.getMaxPlayers(), 8, p.isReducedDebugInfo(), true, false, false);
sendPacket(join);
Location s = p.getWorldSpawn();
//PacketPlayOutKeepAlive alive = new PacketPlayOutKeepAlive((long) (Math.random() * Long.MAX_VALUE));
World world = s.getWorld();
for (int x = 0; x < world.getChunks().length; x++) {
for (int z = 0; z < world.getChunks()[x].length; z++) {
Chunk chunk = world.getChunks()[x][z];
if (chunk != null) {
PacketPlayOutMapChunk chunkdata = new PacketPlayOutMapChunk(x, z, chunk);
sendPacket(chunkdata);
//System.out.println(x + ", " + z);
}
}
}
PacketPlayOutSpawnPosition spawnPos = new PacketPlayOutSpawnPosition(BlockPosition.from(s));
sendPacket(spawnPos);
PacketPlayOutPositionAndLook positionLook = new PacketPlayOutPositionAndLook(s.getX(), s.getY(), s.getZ(), s.getYaw(), s.getPitch(), 1);
player.setLocation(new Location(world, s.getX(), s.getY(), s.getZ(), s.getYaw(), s.getPitch()));
sendPacket(positionLook);
SkinResponse skinresponce = isBungeecord ? bungeeSkin : MojangAPIUtils.getSkinFromMojangServer(player.getName());
PlayerSkinProperty skin = skinresponce != null ? new PlayerSkinProperty(skinresponce.getSkin(), skinresponce.getSignature()) : null;
PacketPlayOutPlayerInfo info = new PacketPlayOutPlayerInfo(PlayerInfoAction.ADD_PLAYER, player.getUUID(), new PlayerInfoData.PlayerInfoDataAddPlayer(player.getName(), Optional.ofNullable(skin), p.getDefaultGamemode(), 0, false, Optional.empty()));
sendPacket(info);
/*
for (ClientConnection client : Limbo.getInstance().getServerConnection().getClients()) {
DataOutputStream other = new DataOutputStream(client.getSocket().getOutputStream());
DataTypeIO.writeVarInt(other, packetByte.length);
other.write(packetByte);
}
*/
PacketPlayOutShowPlayerSkins show = new PacketPlayOutShowPlayerSkins(player.getEntityId());
sendPacket(show);
PacketPlayOutPlayerAbilities abilities;
if (p.isAllowFlight()) {
abilities = new PacketPlayOutPlayerAbilities(0.05F, 0.1F, PlayerAbilityFlags.ALLOW_FLYING);
} else {
abilities = new PacketPlayOutPlayerAbilities(0.05F, 0.1F);
}
sendPacket(abilities);
String str = client_socket.getInetAddress().getHostName() + ":" + client_socket.getPort() + "|" + player.getName();
Limbo.getInstance().getConsole().sendMessage("[/" + str + "] <-> Player had connected to the Limbo server!");
PacketPlayOutDeclareCommands declare = DeclareCommands.getDeclareCommandPacket();
sendPacket(declare);
while (client_socket.isConnected()) {
try {
int size = DataTypeIO.readVarInt(input);
int packetId = DataTypeIO.readVarInt(input);
Class<? extends Packet> packetType = Packet.getPlayIn().get(packetId);
//System.out.println(packetType);
if (packetType == null) {
input.skipBytes(size - DataTypeIO.getVarIntLength(packetId));
} else if (packetType.equals(PacketPlayInPositionAndLook.class)) {
PacketPlayInPositionAndLook pos = new PacketPlayInPositionAndLook(input);
player.setLocation(new Location(player.getWorld(), pos.getX(), pos.getY(), pos.getZ(), pos.getYaw(), pos.getPitch()));
PacketPlayOutUpdateViewPosition response = new PacketPlayOutUpdateViewPosition((int) player.getLocation().getX() >> 4, (int) player.getLocation().getZ() >> 4);
sendPacket(response);
} else if (packetType.equals(PacketPlayInPosition.class)) {
PacketPlayInPosition pos = new PacketPlayInPosition(input);
player.setLocation(new Location(player.getWorld(), pos.getX(), pos.getY(), pos.getZ(), player.getLocation().getYaw(), player.getLocation().getPitch()));
PacketPlayOutUpdateViewPosition response = new PacketPlayOutUpdateViewPosition((int) player.getLocation().getX() >> 4, (int) player.getLocation().getZ() >> 4);
sendPacket(response);
} else if (packetType.equals(PacketPlayInRotation.class)) {
PacketPlayInRotation pos = new PacketPlayInRotation(input);
player.setLocation(new Location(player.getWorld(), player.getLocation().getX(), player.getLocation().getY(), player.getLocation().getZ(), pos.getYaw(), pos.getPitch()));
PacketPlayOutUpdateViewPosition response = new PacketPlayOutUpdateViewPosition((int) player.getLocation().getX() >> 4, (int) player.getLocation().getZ() >> 4);
sendPacket(response);
} else if (packetType.equals(PacketPlayInKeepAlive.class)) {
PacketPlayInKeepAlive alive = new PacketPlayInKeepAlive(input);
if (alive.getPayload() != lastKeepAlivePayLoad) {
Limbo.getInstance().getConsole().sendMessage("Incorrect Payload recieved in KeepAlive packet for player " + player.getName());
break;
}
} else if (packetType.equals(PacketPlayInTabComplete.class)) {
PacketPlayInTabComplete request = new PacketPlayInTabComplete(input);
String[] command = CustomStringUtils.splitStringToArgs(request.getText());
System.out.println(request.getText());
List<TabCompleteMatches> matches = new ArrayList<TabCompleteMatches>();
TabCompleteMatches spawn = new TabCompleteMatches("spawn", new TextComponent(ChatColor.GOLD + "Teleports you back to the Limbo spawn!"));
switch (command.length) {
case 0:
matches.add(spawn);
break;
case 1:
if ("spawn".startsWith(command[0])) {
matches.add(spawn);
}
break;
}
int start = CustomStringUtils.getIndexOfArg(request.getText(), command.length - 1);
int length = command[command.length - 1].length();
PacketPlayOutTabComplete response = new PacketPlayOutTabComplete(request.getId(), start, length, matches.toArray(new TabCompleteMatches[matches.size()]));
sendPacket(response);
} else if (packetType.equals(PacketPlayInChat.class)) {
PacketPlayInChat chat = new PacketPlayInChat(input);
if (chat.getMessage().startsWith("/")) {
//TO-DO COMMANDS
String[] command = CustomStringUtils.splitStringToArgs(chat.getMessage().substring(1));
if (command[0].equalsIgnoreCase("spawn")) {
player.teleport(p.getWorldSpawn());
Limbo.getInstance().getConsole().sendMessage(player.getName() + " executed server command: /spawn");
player.sendMessage(new TextComponent(ChatColor.GOLD + "Teleporting you to spawn!"));
}
} else {
String message = "<" + player.getName() + "> " + chat.getMessage();
Limbo.getInstance().getConsole().sendMessage(message);
for (Player each : Limbo.getInstance().getPlayers()) {
each.sendMessage(message);
}
}
} else {
input.skipBytes(size - DataTypeIO.getVarIntLength(packetId));
}
} catch (Exception e) {
break;
}
}
str = client_socket.getInetAddress().getHostName() + ":" + client_socket.getPort() + "|" + player.getName();
Limbo.getInstance().getConsole().sendMessage("[/" + str + "] <-> Player had disconnected!");
}
} catch (Exception e) {}
try {
client_socket.close();
} catch (IOException e) {}
state = ClientState.DISCONNECTED;
if (player != null) {
Limbo.getInstance().removePlayer(player);
}
Limbo.getInstance().getServerConnection().getClients().remove(this);
running = false;
}
}