private bool handlePacket(int packetID, List <byte> packetData)
        {
            if (login_phase)
            {
                switch (packetID) //Packet IDs are different while logging in
                {
                case 0x03:
                    if (protocolversion >= MC18Version)
                    {
                        compression_treshold = readNextVarInt(packetData);
                    }
                    break;

                default:
                    return(false);    //Ignored packet
                }
            }
            switch (getPacketIncomingType(packetID))
            {
            case PacketIncomingType.KeepAlive:
                SendPacket(PacketOutgoingType.KeepAlive, packetData);
                handler.OnKeepAlive();
                break;

            case PacketIncomingType.JoinGame:
                handler.OnGameJoin();
                readNextInt(packetData);
                readNextByte(packetData);
                if (protocolversion >= MC191Version)
                {
                    this.currentDimension = readNextInt(packetData);
                }
                else
                {
                    this.currentDimension = (sbyte)readNextByte(packetData);
                }
                readNextByte(packetData);
                readNextByte(packetData);
                readNextString(packetData);
                if (protocolversion >= MC18Version)
                {
                    readNextBool(packetData);      // Reduced debug info - 1.8 and above
                }
                break;

            case PacketIncomingType.ChatMessage:
                //string message = readNextString(packetData);
                break;

            case PacketIncomingType.Respawn:
                this.currentDimension = readNextInt(packetData);
                readNextByte(packetData);
                readNextByte(packetData);
                readNextString(packetData);
                break;

            case PacketIncomingType.PlayerPositionAndLook:
                if (protocolversion >= MC19Version)
                {
                    int teleportID = readNextVarInt(packetData);
                    // Teleport confirm packet
                    SendPacket(PacketOutgoingType.TeleportConfirm, getVarInt(teleportID));
                }
                break;

            case PacketIncomingType.TabCompleteResult:
                if (protocolversion >= MC113Version)
                {
                    autocomplete_transaction_id = readNextVarInt(packetData);
                    readNextVarInt(packetData);     // Start of text to replace
                    readNextVarInt(packetData);     // Length of text to replace
                }
                int autocomplete_count = readNextVarInt(packetData);
                break;

            case PacketIncomingType.PluginMessage:
                String channel = readNextString(packetData);
                if (protocolversion < MC18Version)
                {
                    if (forgeInfo == null)
                    {
                        // 1.7 and lower prefix plugin channel packets with the length.
                        // We can skip it, though.
                        readNextShort(packetData);
                    }
                    else
                    {
                        // Forge does something even weirder with the length.
                        readNextVarShort(packetData);
                    }
                }

                // The remaining data in the array is the entire payload of the packet.
                //handler.OnPluginChannelMessage(channel, packetData.ToArray());

                #region Forge Login
                if (forgeInfo != null && fmlHandshakeState != FMLHandshakeClientState.DONE)
                {
                    if (channel == "FML|HS")
                    {
                        FMLHandshakeDiscriminator discriminator = (FMLHandshakeDiscriminator)readNextByte(packetData);

                        if (discriminator == FMLHandshakeDiscriminator.HandshakeReset)
                        {
                            fmlHandshakeState = FMLHandshakeClientState.START;
                            return(true);
                        }

                        switch (fmlHandshakeState)
                        {
                        case FMLHandshakeClientState.START:
                            if (discriminator != FMLHandshakeDiscriminator.ServerHello)
                            {
                                return(false);
                            }

                            // Send the plugin channel registration.
                            // REGISTER is somewhat special in that it doesn't actually include length information,
                            // and is also \0-separated.
                            // Also, yes, "FML" is there twice.  Don't ask me why, but that's the way forge does it.
                            string[] channels = { "FML|HS", "FML", "FML|MP", "FML", "FORGE" };
                            SendPluginChannelPacket("REGISTER", Encoding.UTF8.GetBytes(string.Join("\0", channels)));

                            byte fmlProtocolVersion = readNextByte(packetData);

                            if (fmlProtocolVersion >= 1)
                            {
                                this.currentDimension = readNextInt(packetData);
                            }

                            // Tell the server we're running the same version.
                            SendForgeHandshakePacket(FMLHandshakeDiscriminator.ClientHello, new byte[] { fmlProtocolVersion });

                            // Then tell the server that we're running the same mods.
                            byte[][] mods = new byte[forgeInfo.Mods.Count][];
                            for (int i = 0; i < forgeInfo.Mods.Count; i++)
                            {
                                ForgeInfo.ForgeMod mod = forgeInfo.Mods[i];
                                mods[i] = concatBytes(getString(mod.ModID), getString(mod.Version));
                            }
                            SendForgeHandshakePacket(FMLHandshakeDiscriminator.ModList,
                                                     concatBytes(getVarInt(forgeInfo.Mods.Count), concatBytes(mods)));

                            fmlHandshakeState = FMLHandshakeClientState.WAITINGSERVERDATA;

                            return(true);

                        case FMLHandshakeClientState.WAITINGSERVERDATA:
                            if (discriminator != FMLHandshakeDiscriminator.ModList)
                            {
                                return(false);
                            }

                            Thread.Sleep(2000);

                            // Tell the server that yes, we are OK with the mods it has
                            // even though we don't actually care what mods it has.

                            SendForgeHandshakePacket(FMLHandshakeDiscriminator.HandshakeAck,
                                                     new byte[] { (byte)FMLHandshakeClientState.WAITINGSERVERDATA });

                            fmlHandshakeState = FMLHandshakeClientState.WAITINGSERVERCOMPLETE;
                            return(false);

                        case FMLHandshakeClientState.WAITINGSERVERCOMPLETE:
                            // The server now will tell us a bunch of registry information.
                            // We need to read it all, though, until it says that there is no more.
                            if (discriminator != FMLHandshakeDiscriminator.RegistryData)
                            {
                                return(false);
                            }

                            if (protocolversion < MC18Version)
                            {
                                // 1.7.10 and below have one registry
                                // with blocks and items.
                                int registrySize = readNextVarInt(packetData);

                                fmlHandshakeState = FMLHandshakeClientState.PENDINGCOMPLETE;
                            }
                            else
                            {
                                // 1.8+ has more than one registry.

                                bool   hasNextRegistry = readNextBool(packetData);
                                string registryName    = readNextString(packetData);
                                int    registrySize    = readNextVarInt(packetData);
                                if (!hasNextRegistry)
                                {
                                    fmlHandshakeState = FMLHandshakeClientState.PENDINGCOMPLETE;
                                }
                            }

                            return(false);

                        case FMLHandshakeClientState.PENDINGCOMPLETE:
                            // The server will ask us to accept the registries.
                            // Just say yes.
                            if (discriminator != FMLHandshakeDiscriminator.HandshakeAck)
                            {
                                return(false);
                            }
                            SendForgeHandshakePacket(FMLHandshakeDiscriminator.HandshakeAck,
                                                     new byte[] { (byte)FMLHandshakeClientState.PENDINGCOMPLETE });
                            fmlHandshakeState = FMLHandshakeClientState.COMPLETE;

                            return(true);

                        case FMLHandshakeClientState.COMPLETE:
                            // One final "OK".  On the actual forge source, a packet is sent from
                            // the client to the client saying that the connection was complete, but
                            // we don't need to do that.

                            SendForgeHandshakePacket(FMLHandshakeDiscriminator.HandshakeAck,
                                                     new byte[] { (byte)FMLHandshakeClientState.COMPLETE });
                            fmlHandshakeState = FMLHandshakeClientState.DONE;
                            return(true);
                        }
                    }
                }
                #endregion
                return(false);

            case PacketIncomingType.KickPacket:
                handler.OnConnectionLost(BotUtils.DisconnectReason.InGameKick, readNextString(packetData));
                return(false);

            case PacketIncomingType.NetworkCompressionTreshold:
                if (protocolversion >= MC18Version && protocolversion < MC19Version)
                {
                    compression_treshold = readNextVarInt(packetData);
                }
                break;

            case PacketIncomingType.ResourcePackSend:
                string url  = readNextString(packetData);
                string hash = readNextString(packetData);
                //Send back "accepted" and "successfully loaded" responses for plugins making use of resource pack mandatory
                byte[] responseHeader = new byte[0];
                if (protocolversion < MC110Version)     //MC 1.10 does not include resource pack hash in responses
                {
                    responseHeader = concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash));
                }
                SendPacket(PacketOutgoingType.ResourcePackStatus, concatBytes(responseHeader, getVarInt(3)));     //Accepted pack
                SendPacket(PacketOutgoingType.ResourcePackStatus, concatBytes(responseHeader, getVarInt(0)));     //Successfully loaded
                break;

            default:
                return(false); //Ignored packet
            }
            return(true);      //Packet processed
        }