<commit_before>/////////////////////////////////////////
//
//             OpenLieroX
//
// code under LGPL, based on JasonBs work,
// enhanced by <NAME> and <NAME>
//
//
/////////////////////////////////////////


// Client class - Parsing
// Created 1/7/02
// <NAME>



#include <cassert>
#include <time.h>

#include "LieroX.h"
#include "Cache.h"
#include "CClient.h"
#include "CServer.h"
#include "DeprecatedGUI/Menu.h"
#include "console.h"
#include "GfxPrimitives.h"
#include "FindFile.h"
#include "StringUtils.h"
#include "Protocol.h"
#include "CWorm.h"
#include "Error.h"
#include "Entity.h"
#include "MathLib.h"
#include "EndianSwap.h"
#include "Physics.h"
#include "AuxLib.h"
#include "NotifyUser.h"
#include "Timer.h"
#include "XMLutils.h"
#include "CClientNetEngine.h"
#include "CChannel.h"
#include "DeprecatedGUI/CBrowser.h"
#include "ProfileSystem.h"
#include "IRC.h"
#include "CWormHuman.h"
#include "CWormBot.h"
#include "Debug.h"




#ifdef _MSC_VER
#undef min
#undef max
#endif

// declare them only locally here as nobody really should use them explicitly
std::string Utf8String(const std::string &OldLxString);


///////////////////
// Parse a connectionless packet
void CClientNetEngine::ParseConnectionlessPacket(CBytestream *bs)
{
	std::string cmd = bs->readString(128);

	if(cmd == "lx::challenge")
		ParseChallenge(bs);

	else if(cmd == "lx::goodconnection")
		ParseConnected(bs);

	else if(cmd == "lx::pong")
		ParsePong();

	else if(cmd == "lx::timeis")
		ParseTimeIs(bs);

	// A Bad Connection
	else if(cmd == "lx::badconnect") {
		// If we are already connected, ignore this
		if (client->iNetStatus == NET_CONNECTED || client->iNetStatus == NET_PLAYING)  {
			printf("CClientNetEngine::ParseConnectionlessPacket: already connected, ignoring\n");
		} else {
			client->iNetStatus = NET_DISCONNECTED;
			client->bBadConnection = true;
			client->strBadConnectMsg = "Server message: " + Utf8String(bs->readString());
		}
	}

	// Host has OpenLX Beta 3+
	else if(cmd.find("lx::openbeta") == 0)  {
		if (cmd.size() > 12)  {
			int betaver = MAX(0, atoi(cmd.substr(12)));
			Version version = OLXBetaVersion(betaver);
			if(client->cServerVersion < version) {
				client->cServerVersion = version;
				notes << "host is at least using OLX Beta3" << endl;
			}
		}
	}

	// this is only important for Beta4 as it's the main method there to inform about the version
	// we send the version string now together with the challenge packet
	else if(cmd == "lx::version")  {
		std::string versionStr = bs->readString();
		Version version(versionStr);
		if(version > client->cServerVersion) { // only update if this version is really newer than what we know already
			client->cServerVersion = version;
			notes << "Host is using " << versionStr << endl;
		}
	}

	else if (cmd == "lx:mouseAllowed")
		client->bHostAllowsMouse = true;

	else if (cmd == "lx:strafingAllowed")
		client->bHostAllowsStrafing = true;

	else if(cmd == "lx::traverse")
		ParseTraverse(bs);

	else if(cmd == "lx::connect_here")
		ParseConnectHere(bs);

	// Unknown
	else  {
		warnings << "CClientNetEngine::ParseConnectionlessPacket: unknown command \"" << cmd << "\"" << endl;
		bs->SkipAll(); // Safety: ignore any data behind this unknown packet
	}
}


///////////////////
// Parse a challenge packet
void CClientNetEngine::ParseChallenge(CBytestream *bs)
{
	// If we are already connected, ignore this
	if (client->iNetStatus == NET_CONNECTED || client->iNetStatus == NET_PLAYING)  {
		printf("CClientNetEngine::ParseChallenge: already connected, ignoring\n");
		return;
	}

#ifdef DEBUG
	if (client->bConnectingBehindNat)
		printf("Got a challenge from the server.\n");
#endif

	CBytestream bytestr;
	bytestr.Clear();
	client->iChallenge = bs->readInt(4);
	if( ! bs->isPosAtEnd() ) {
		client->setServerVersion( bs->readString(128) );
		printf("CClient: connected to %s server\n", client->getServerVersion().asString().c_str());
	} else
		printf("CClient: connected to old (<= OLX beta3) server\n");

	// TODO: move this out here
	// Tell the server we are connecting, and give the server our details
	bytestr.writeInt(-1,4);
	bytestr.writeString("lx::connect");
	bytestr.writeInt(PROTOCOL_VERSION,1);
	bytestr.writeInt(client->iChallenge,4);
	bytestr.writeInt(client->iNetSpeed,1);
	bytestr.writeInt(client->iNumWorms, 1);

	// Send my worms info
    //
    // __MUST__ match the layout in CWorm::writeInfo() !!!
    //

	for(uint i=0;i<client->iNumWorms;i++) {
		// TODO: move this out here
		bytestr.writeString(RemoveSpecialChars(client->tProfiles[i]->sName));
		bytestr.writeInt(client->tProfiles[i]->iType,1);
		bytestr.writeInt(client->tProfiles[i]->iTeam,1);
		bytestr.writeString(client->tProfiles[i]->cSkin.getFileName());
		bytestr.writeInt(client->tProfiles[i]->R,1);
		bytestr.writeInt(client->tProfiles[i]->G,1);
		bytestr.writeInt(client->tProfiles[i]->B,1);
	}

	NetworkAddr addr;
	GetRemoteNetAddr(client->tSocket, addr);
	SetRemoteNetAddr(client->tSocket, addr);
	bytestr.Send(client->tSocket);

	client->setNetEngineFromServerVersion(); // *this may be deleted here! so it's the last command
}


///////////////////
// Parse a connected packet
void CClientNetEngine::ParseConnected(CBytestream *bs)
{
	NetworkAddr addr;

	// If already connected, ignore this
	if (client->iNetStatus == NET_CONNECTED)  {
		printf("CClientNetEngine::ParseConnected: already connected but server received our connect-package twice and we could have other worm-ids\n");
	}
	else
	if(client->iNetStatus == NET_PLAYING) {
		printf("CClientNetEngine::ParseConnected: currently playing; it's too risky to proceed a reconnection, so we ignore this\n");
		return;
	}

	// Setup the client
	client->iNetStatus = NET_CONNECTED;

	// small cleanup (needed for example for reconnecting)
	for(int i = 0; i < MAX_WORMS; i++) {
		client->cRemoteWorms[i].setUsed(false);
		client->cRemoteWorms[i].setAlive(false);
		client->cRemoteWorms[i].setKills(0);
		client->cRemoteWorms[i].setLives(WRM_OUT);
		client->cRemoteWorms[i].setProfile(NULL);
		client->cRemoteWorms[i].setType(PRF_HUMAN);
		client->cRemoteWorms[i].setLocal(false);
		client->cRemoteWorms[i].setTagIT(false);
		client->cRemoteWorms[i].setTagTime(0);
		client->cRemoteWorms[i].setClientVersion(Version());
	}

	if( client->iNumWorms < 0 )
		// TODO: why? we should allow iNumWorms = 0 for sure!
		printf("Error %s:%i: client->iNumWorms = %i is less than zero \n", __FILE__, __LINE__, client->iNumWorms );
	
	// Get the id's
	int id=0;
	for(ushort i=0;i<client->iNumWorms;i++) {
		id = bs->readInt(1);
		if (id < 0 || id >= MAX_WORMS)
			continue;
		client->cLocalWorms[i] = &client->cRemoteWorms[id];
		client->cLocalWorms[i]->setUsed(true);
		client->cLocalWorms[i]->setClient(NULL); // Local worms won't get CServerConnection owner
		client->cLocalWorms[i]->setGameScript(client->cGameScript.get()); // TODO: why was this commented out?
		//client->cLocalWorms[i]->setLoadingTime(client->fLoadingTime);  // TODO: why is this commented out?
		client->cLocalWorms[i]->setProfile(client->tProfiles[i]);
		client->cLocalWorms[i]->setTeam(client->tProfiles[i]->iTeam);
		client->cLocalWorms[i]->setLocal(true);
        client->cLocalWorms[i]->setType(WormType::fromInt(client->tProfiles[i]->iType));
		client->cLocalWorms[i]->setClientVersion(client->getClientVersion());
	}

	// TODO: why do we setup the viewports only if we have at least one worm?
	if(!bDedicated && client->iNumWorms > 0) {
		// Setup the viewports
		client->SetupViewports();

		client->SetupGameInputs();
	}

	// Create my channel
	GetRemoteNetAddr(client->tSocket, addr);

	if( ! client->createChannel( std::min( client->getServerVersion(), GetGameVersion() ) ) )
	{
		client->bServerError = true;
		client->strServerErrorMsg = "Your client is incompatible to this server";
		return;
	}
	client->cNetChan->Create(&addr,client->tSocket);

	DeprecatedGUI::bJoin_Update = true;
	DeprecatedGUI::bHost_Update = true;

	client->bHostAllowsMouse = false;
	client->bHostAllowsStrafing = false;
	
	if( GetGlobalIRC() )
		GetGlobalIRC()->setAwayMessage("Server: " + client->getServerName());
}

//////////////////
// Parse the server's ping reply
void CClientNetEngine::ParsePong()
{
	if (client->fMyPingSent > 0)  {
		int png = (int) ((tLX->fCurTime-client->fMyPingSent)*1000);

		// Make the ping slighter
		if (png - client->iMyPing > 5 && client->iMyPing && png)
			png = (png + client->iMyPing + client->iMyPing)/3;
		if (client->iMyPing - png > 5 && client->iMyPing && png)
			png = (png + png + client->iMyPing)/3;

		client->iMyPing = png;
	}
}

//////////////////
// Parse the server's servertime request reply
void CClientNetEngine::ParseTimeIs(CBytestream* bs)
{
	float time = bs->readFloat();
	if (time > client->fServertime)
		client->fServertime = time;

	// This is the response of the lx::time packet, which is sent instead of the lx::ping.
	// Therefore we should also handle this as a normal response to a ping.
	ParsePong();
}


//////////////////
// Parse a NAT traverse packet
void CClientNetEngine::ParseTraverse(CBytestream *bs)
{
	client->iNatTraverseState = NAT_SEND_CHALLENGE;
	client->iNatTryPort = 0;
	std::string addr = bs->readString();
	if( addr.find(":") == std::string::npos )
		return;
	StringToNetAddr(addr, client->cServerAddr); // HINT: this changes the address so the lx::challenge in CClientNetEngine::ConnectingBehindNat is sent to the real server
	int port = atoi( addr.substr( addr.find(":") + 1 ) );
	SetNetAddrPort(client->cServerAddr, port);
	NetAddrToString( client->cServerAddr, addr );

	// HINT: the connecting process now continues by sending a challenge in CClientNetEngine::ConnectingBehindNAT()

	printf("CClientNetEngine::ParseTraverse() %s port %i\n", addr.c_str(), port);
};

/////////////////////
// Parse a connect-here packet (when a public-ip client connects to a symmetric-nat server)
void CClientNetEngine::ParseConnectHere(CBytestream *bs)
{
	client->iNatTraverseState = NAT_SEND_CHALLENGE;
	client->iNatTryPort = 0;

	NetworkAddr addr;
	GetRemoteNetAddr(client->tSocket, addr);
	std::string a1, a2;
	NetAddrToString( client->cServerAddr, a1 );
	NetAddrToString( addr, a2 );
	printf("CClientNetEngine::ParseConnectHere(): addr %s to %s %s\n", a1.c_str(), a2.c_str(), a1 != a2 ? "- server behind symmetric NAT" : "" );

	GetRemoteNetAddr(client->tSocket, client->cServerAddr);
	CBytestream bs1;
	bs1.writeInt(-1,4);
	bs1.writeString("lx::ping");	// So NAT/firewall will understand we really want to connect there
	bs1.Send(client->tSocket);
	bs1.Send(client->tSocket);
	bs1.Send(client->tSocket);
};


/*
=======================================
		Connected Packets
=======================================
*/


///////////////////
// Parse a packet
void CClientNetEngine::ParsePacket(CBytestream *bs)
{
	uchar cmd;

	while(!bs->isPosAtEnd()) {
		cmd = bs->readInt(1);

		switch(cmd) {


			// Prepare the game
			case S2C_PREPAREGAME:
				if(!ParsePrepareGame(bs))					
                    return;
				break;

			// Start the game
			case S2C_STARTGAME:
				ParseStartGame(bs);
				break;

			// Spawn a worm
			case S2C_SPAWNWORM:
				ParseSpawnWorm(bs);
				break;

			// Worm info
			case S2C_WORMINFO:
				ParseWormInfo(bs);
				break;

			// Worm weapon info
			case S2C_WORMWEAPONINFO:
				ParseWormWeaponInfo(bs);
				break;

			// Text
			case S2C_TEXT:
				ParseText(bs);
				break;

			// Chat command completion solution
			case S2C_CHATCMDCOMPLSOL:
				ParseChatCommandCompletionSolution(bs);
				break;

			// chat command completion list
			case S2C_CHATCMDCOMPLLST:
				ParseChatCommandCompletionList(bs);
				break;


			// AFK message
			case S2C_AFK:
				ParseAFK(bs);
				break;

			// Worm score
			case S2C_SCOREUPDATE:
				ParseScoreUpdate(bs);
				break;

			// Game over
			case S2C_GAMEOVER:
				ParseGameOver(bs);
				break;

			// Spawn bonus
			case S2C_SPAWNBONUS:
				ParseSpawnBonus(bs);
				break;

			// Tag update
			case S2C_TAGUPDATE:
				ParseTagUpdate(bs);
				break;

			// Some worms are ready to play (not lobby)
			case S2C_CLREADY:
				ParseCLReady(bs);
				break;

			// Update the lobby state of some worms
			case S2C_UPDATELOBBY:
				ParseUpdateLobby(bs);
				break;

			// Client has left
			case S2C_WORMSOUT:
				ParseWormsOut(bs);
				break;

			// Worm state update
			case S2C_UPDATEWORMS:
				ParseUpdateWorms(bs);
				break;

			// Game lobby update
			case S2C_UPDATELOBBYGAME:
				ParseUpdateLobbyGame(bs);
				break;

			// Worm down packet
			case S2C_WORMDOWN:
				ParseWormDown(bs);
				break;

			// Server has left
			case S2C_LEAVING:
				ParseServerLeaving(bs);
				break;

			// Single shot
			case S2C_SINGLESHOOT:
				ParseSingleShot(bs);
				break;

			// Multiple shots
			case S2C_MULTISHOOT:
				ParseMultiShot(bs);
				break;

			// Stats
			case S2C_UPDATESTATS:
				ParseUpdateStats(bs);
				break;

			// Destroy bonus
			case S2C_DESTROYBONUS:
				ParseDestroyBonus(bs);
				break;

			// Goto lobby
			case S2C_GOTOLOBBY:
				ParseGotoLobby(bs);
				break;

            // I have been dropped
            case S2C_DROPPED:
                ParseDropped(bs);
                break;

            case S2C_SENDFILE:
                ParseSendFile(bs);
                break;

            case S2C_REPORTDAMAGE:
                ParseReportDamage(bs);
                break;

			case S2C_HIDEWORM:
				ParseHideWorm(bs);
				break;

			default:
#if !defined(FUZZY_ERROR_TESTING_S2C)
				printf("cl: Unknown packet\n");
#ifdef DEBUG
				printf("Bytestream dump:\n\n");
				bs->Dump();
				printf("\nDone dumping bytestream\n");
#endif //DEBUG

#endif //FUZZY_ERROR_TESTING
				return;

		}
	}
}


///////////////////
// Parse a prepare game packet
bool CClientNetEngine::ParsePrepareGame(CBytestream *bs)
{
	printf("Got ParsePrepareGame\n");

	if(Warning_QuitEngineFlagSet("CClientNetEngine::ParsePrepareGame: ")) {
		printf("HINT: some previous action tried to quit the GameLoop; we are ignoring this now\n");
		ResetQuitEngineFlag();
	}

	// We've already got this packet
	if (client->bGameReady)  {
		printf("CClientNetEngine::ParsePrepareGame: we already got this\n");
		return false;
	}

	// If we're playing, the game has to be ready
	if (client->iNetStatus == NET_PLAYING)  {
		printf("CClientNetEngine::ParsePrepareGame: playing, had to get this\n");
		client->bGameReady = true;
		return false;
	}

	NotifyUserOnEvent();


	// remove from notifier; we don't want events anymore, we have a fixed FPS rate ingame
	RemoveSocketFromNotifierGroup( client->tSocket );

	client->bGameReady = true;

	int random = bs->readInt(1);
	std::string sMapName;
	if(!random)
		sMapName = bs->readString();

	// Other game details
	client->iGameType = bs->readInt(1);
	client->iLives = bs->readInt16();
	client->iMaxKills = bs->readInt16();
	client->tGameInfo.fTimeLimit = (float)bs->readInt16();
	int l = bs->readInt16();
	client->fLoadingTime = (float)l/100.0f;
	client->bBonusesOn = bs->readBool();
	client->bShowBonusName = bs->readBool();

	if(client->iGameType == GMT_TIME)
		client->iTagLimit = bs->readInt16();

	// Load the gamescript
	client->sModName = bs->readString();

	// Bad packet
	if (client->sModName == "")  {
		printf("CClientNetEngine::ParsePrepareGame: invalid mod name (none)\n");
		client->bGameReady = false;
		return false;
	}

	// Clear any previous instances of the map
	if(tLX->iGameType == GME_JOIN) {
		if(client->cMap) {
			client->cMap->Shutdown();
			delete client->cMap;
			client->cMap = NULL;
		}
	}

	// HINT: gamescript is shut down by the cache

    //bs->Dump();<commit_msg>Reset server time when getting ParsePrepareGame to make the countdown timer correct even when selecting weapons<commit_after>/////////////////////////////////////////
//
//             OpenLieroX
//
// code under LGPL, based on JasonBs work,
// enhanced by <NAME> and <NAME>
//
//
/////////////////////////////////////////


// Client class - Parsing
// Created 1/7/02
// <NAME>



#include <cassert>
#include <time.h>

#include "LieroX.h"
#include "Cache.h"
#include "CClient.h"
#include "CServer.h"
#include "DeprecatedGUI/Menu.h"
#include "console.h"
#include "GfxPrimitives.h"
#include "FindFile.h"
#include "StringUtils.h"
#include "Protocol.h"
#include "CWorm.h"
#include "Error.h"
#include "Entity.h"
#include "MathLib.h"
#include "EndianSwap.h"
#include "Physics.h"
#include "AuxLib.h"
#include "NotifyUser.h"
#include "Timer.h"
#include "XMLutils.h"
#include "CClientNetEngine.h"
#include "CChannel.h"
#include "DeprecatedGUI/CBrowser.h"
#include "ProfileSystem.h"
#include "IRC.h"
#include "CWormHuman.h"
#include "CWormBot.h"
#include "Debug.h"




#ifdef _MSC_VER
#undef min
#undef max
#endif

// declare them only locally here as nobody really should use them explicitly
std::string Utf8String(const std::string &OldLxString);


///////////////////
// Parse a connectionless packet
void CClientNetEngine::ParseConnectionlessPacket(CBytestream *bs)
{
	std::string cmd = bs->readString(128);

	if(cmd == "lx::challenge")
		ParseChallenge(bs);

	else if(cmd == "lx::goodconnection")
		ParseConnected(bs);

	else if(cmd == "lx::pong")
		ParsePong();

	else if(cmd == "lx::timeis")
		ParseTimeIs(bs);

	// A Bad Connection
	else if(cmd == "lx::badconnect") {
		// If we are already connected, ignore this
		if (client->iNetStatus == NET_CONNECTED || client->iNetStatus == NET_PLAYING)  {
			printf("CClientNetEngine::ParseConnectionlessPacket: already connected, ignoring\n");
		} else {
			client->iNetStatus = NET_DISCONNECTED;
			client->bBadConnection = true;
			client->strBadConnectMsg = "Server message: " + Utf8String(bs->readString());
		}
	}

	// Host has OpenLX Beta 3+
	else if(cmd.find("lx::openbeta") == 0)  {
		if (cmd.size() > 12)  {
			int betaver = MAX(0, atoi(cmd.substr(12)));
			Version version = OLXBetaVersion(betaver);
			if(client->cServerVersion < version) {
				client->cServerVersion = version;
				notes << "host is at least using OLX Beta3" << endl;
			}
		}
	}

	// this is only important for Beta4 as it's the main method there to inform about the version
	// we send the version string now together with the challenge packet
	else if(cmd == "lx::version")  {
		std::string versionStr = bs->readString();
		Version version(versionStr);
		if(version > client->cServerVersion) { // only update if this version is really newer than what we know already
			client->cServerVersion = version;
			notes << "Host is using " << versionStr << endl;
		}
	}

	else if (cmd == "lx:mouseAllowed")
		client->bHostAllowsMouse = true;

	else if (cmd == "lx:strafingAllowed")
		client->bHostAllowsStrafing = true;

	else if(cmd == "lx::traverse")
		ParseTraverse(bs);

	else if(cmd == "lx::connect_here")
		ParseConnectHere(bs);

	// Unknown
	else  {
		warnings << "CClientNetEngine::ParseConnectionlessPacket: unknown command \"" << cmd << "\"" << endl;
		bs->SkipAll(); // Safety: ignore any data behind this unknown packet
	}
}


///////////////////
// Parse a challenge packet
void CClientNetEngine::ParseChallenge(CBytestream *bs)
{
	// If we are already connected, ignore this
	if (client->iNetStatus == NET_CONNECTED || client->iNetStatus == NET_PLAYING)  {
		printf("CClientNetEngine::ParseChallenge: already connected, ignoring\n");
		return;
	}

#ifdef DEBUG
	if (client->bConnectingBehindNat)
		printf("Got a challenge from the server.\n");
#endif

	CBytestream bytestr;
	bytestr.Clear();
	client->iChallenge = bs->readInt(4);
	if( ! bs->isPosAtEnd() ) {
		client->setServerVersion( bs->readString(128) );
		printf("CClient: connected to %s server\n", client->getServerVersion().asString().c_str());
	} else
		printf("CClient: connected to old (<= OLX beta3) server\n");

	// TODO: move this out here
	// Tell the server we are connecting, and give the server our details
	bytestr.writeInt(-1,4);
	bytestr.writeString("lx::connect");
	bytestr.writeInt(PROTOCOL_VERSION,1);
	bytestr.writeInt(client->iChallenge,4);
	bytestr.writeInt(client->iNetSpeed,1);
	bytestr.writeInt(client->iNumWorms, 1);

	// Send my worms info
    //
    // __MUST__ match the layout in CWorm::writeInfo() !!!
    //

	for(uint i=0;i<client->iNumWorms;i++) {
		// TODO: move this out here
		bytestr.writeString(RemoveSpecialChars(client->tProfiles[i]->sName));
		bytestr.writeInt(client->tProfiles[i]->iType,1);
		bytestr.writeInt(client->tProfiles[i]->iTeam,1);
		bytestr.writeString(client->tProfiles[i]->cSkin.getFileName());
		bytestr.writeInt(client->tProfiles[i]->R,1);
		bytestr.writeInt(client->tProfiles[i]->G,1);
		bytestr.writeInt(client->tProfiles[i]->B,1);
	}

	NetworkAddr addr;
	GetRemoteNetAddr(client->tSocket, addr);
	SetRemoteNetAddr(client->tSocket, addr);
	bytestr.Send(client->tSocket);

	client->setNetEngineFromServerVersion(); // *this may be deleted here! so it's the last command
}


///////////////////
// Parse a connected packet
void CClientNetEngine::ParseConnected(CBytestream *bs)
{
	NetworkAddr addr;

	// If already connected, ignore this
	if (client->iNetStatus == NET_CONNECTED)  {
		printf("CClientNetEngine::ParseConnected: already connected but server received our connect-package twice and we could have other worm-ids\n");
	}
	else
	if(client->iNetStatus == NET_PLAYING) {
		printf("CClientNetEngine::ParseConnected: currently playing; it's too risky to proceed a reconnection, so we ignore this\n");
		return;
	}

	// Setup the client
	client->iNetStatus = NET_CONNECTED;

	// small cleanup (needed for example for reconnecting)
	for(int i = 0; i < MAX_WORMS; i++) {
		client->cRemoteWorms[i].setUsed(false);
		client->cRemoteWorms[i].setAlive(false);
		client->cRemoteWorms[i].setKills(0);
		client->cRemoteWorms[i].setLives(WRM_OUT);
		client->cRemoteWorms[i].setProfile(NULL);
		client->cRemoteWorms[i].setType(PRF_HUMAN);
		client->cRemoteWorms[i].setLocal(false);
		client->cRemoteWorms[i].setTagIT(false);
		client->cRemoteWorms[i].setTagTime(0);
		client->cRemoteWorms[i].setClientVersion(Version());
	}

	if( client->iNumWorms < 0 )
		// TODO: why? we should allow iNumWorms = 0 for sure!
		printf("Error %s:%i: client->iNumWorms = %i is less than zero \n", __FILE__, __LINE__, client->iNumWorms );
	
	// Get the id's
	int id=0;
	for(ushort i=0;i<client->iNumWorms;i++) {
		id = bs->readInt(1);
		if (id < 0 || id >= MAX_WORMS)
			continue;
		client->cLocalWorms[i] = &client->cRemoteWorms[id];
		client->cLocalWorms[i]->setUsed(true);
		client->cLocalWorms[i]->setClient(NULL); // Local worms won't get CServerConnection owner
		client->cLocalWorms[i]->setGameScript(client->cGameScript.get()); // TODO: why was this commented out?
		//client->cLocalWorms[i]->setLoadingTime(client->fLoadingTime);  // TODO: why is this commented out?
		client->cLocalWorms[i]->setProfile(client->tProfiles[i]);
		client->cLocalWorms[i]->setTeam(client->tProfiles[i]->iTeam);
		client->cLocalWorms[i]->setLocal(true);
        client->cLocalWorms[i]->setType(WormType::fromInt(client->tProfiles[i]->iType));
		client->cLocalWorms[i]->setClientVersion(client->getClientVersion());
	}

	// TODO: why do we setup the viewports only if we have at least one worm?
	if(!bDedicated && client->iNumWorms > 0) {
		// Setup the viewports
		client->SetupViewports();

		client->SetupGameInputs();
	}

	// Create my channel
	GetRemoteNetAddr(client->tSocket, addr);

	if( ! client->createChannel( std::min( client->getServerVersion(), GetGameVersion() ) ) )
	{
		client->bServerError = true;
		client->strServerErrorMsg = "Your client is incompatible to this server";
		return;
	}
	client->cNetChan->Create(&addr,client->tSocket);

	DeprecatedGUI::bJoin_Update = true;
	DeprecatedGUI::bHost_Update = true;

	client->bHostAllowsMouse = false;
	client->bHostAllowsStrafing = false;
	
	if( GetGlobalIRC() )
		GetGlobalIRC()->setAwayMessage("Server: " + client->getServerName());
}

//////////////////
// Parse the server's ping reply
void CClientNetEngine::ParsePong()
{
	if (client->fMyPingSent > 0)  {
		int png = (int) ((tLX->fCurTime-client->fMyPingSent)*1000);

		// Make the ping slighter
		if (png - client->iMyPing > 5 && client->iMyPing && png)
			png = (png + client->iMyPing + client->iMyPing)/3;
		if (client->iMyPing - png > 5 && client->iMyPing && png)
			png = (png + png + client->iMyPing)/3;

		client->iMyPing = png;
	}
}

//////////////////
// Parse the server's servertime request reply
void CClientNetEngine::ParseTimeIs(CBytestream* bs)
{
	float time = bs->readFloat();
	if (time > client->fServertime)
		client->fServertime = time;

	// This is the response of the lx::time packet, which is sent instead of the lx::ping.
	// Therefore we should also handle this as a normal response to a ping.
	ParsePong();
}


//////////////////
// Parse a NAT traverse packet
void CClientNetEngine::ParseTraverse(CBytestream *bs)
{
	client->iNatTraverseState = NAT_SEND_CHALLENGE;
	client->iNatTryPort = 0;
	std::string addr = bs->readString();
	if( addr.find(":") == std::string::npos )
		return;
	StringToNetAddr(addr, client->cServerAddr); // HINT: this changes the address so the lx::challenge in CClientNetEngine::ConnectingBehindNat is sent to the real server
	int port = atoi( addr.substr( addr.find(":") + 1 ) );
	SetNetAddrPort(client->cServerAddr, port);
	NetAddrToString( client->cServerAddr, addr );

	// HINT: the connecting process now continues by sending a challenge in CClientNetEngine::ConnectingBehindNAT()

	printf("CClientNetEngine::ParseTraverse() %s port %i\n", addr.c_str(), port);
};

/////////////////////
// Parse a connect-here packet (when a public-ip client connects to a symmetric-nat server)
void CClientNetEngine::ParseConnectHere(CBytestream *bs)
{
	client->iNatTraverseState = NAT_SEND_CHALLENGE;
	client->iNatTryPort = 0;

	NetworkAddr addr;
	GetRemoteNetAddr(client->tSocket, addr);
	std::string a1, a2;
	NetAddrToString( client->cServerAddr, a1 );
	NetAddrToString( addr, a2 );
	printf("CClientNetEngine::ParseConnectHere(): addr %s to %s %s\n", a1.c_str(), a2.c_str(), a1 != a2 ? "- server behind symmetric NAT" : "" );

	GetRemoteNetAddr(client->tSocket, client->cServerAddr);
	CBytestream bs1;
	bs1.writeInt(-1,4);
	bs1.writeString("lx::ping");	// So NAT/firewall will understand we really want to connect there
	bs1.Send(client->tSocket);
	bs1.Send(client->tSocket);
	bs1.Send(client->tSocket);
};


/*
=======================================
		Connected Packets
=======================================
*/


///////////////////
// Parse a packet
void CClientNetEngine::ParsePacket(CBytestream *bs)
{
	uchar cmd;

	while(!bs->isPosAtEnd()) {
		cmd = bs->readInt(1);

		switch(cmd) {


			// Prepare the game
			case S2C_PREPAREGAME:
				if(!ParsePrepareGame(bs))					
                    return;
				break;

			// Start the game
			case S2C_STARTGAME:
				ParseStartGame(bs);
				break;

			// Spawn a worm
			case S2C_SPAWNWORM:
				ParseSpawnWorm(bs);
				break;

			// Worm info
			case S2C_WORMINFO:
				ParseWormInfo(bs);
				break;

			// Worm weapon info
			case S2C_WORMWEAPONINFO:
				ParseWormWeaponInfo(bs);
				break;

			// Text
			case S2C_TEXT:
				ParseText(bs);
				break;

			// Chat command completion solution
			case S2C_CHATCMDCOMPLSOL:
				ParseChatCommandCompletionSolution(bs);
				break;

			// chat command completion list
			case S2C_CHATCMDCOMPLLST:
				ParseChatCommandCompletionList(bs);
				break;


			// AFK message
			case S2C_AFK:
				ParseAFK(bs);
				break;

			// Worm score
			case S2C_SCOREUPDATE:
				ParseScoreUpdate(bs);
				break;

			// Game over
			case S2C_GAMEOVER:
				ParseGameOver(bs);
				break;

			// Spawn bonus
			case S2C_SPAWNBONUS:
				ParseSpawnBonus(bs);
				break;

			// Tag update
			case S2C_TAGUPDATE:
				ParseTagUpdate(bs);
				break;

			// Some worms are ready to play (not lobby)
			case S2C_CLREADY:
				ParseCLReady(bs);
				break;

			// Update the lobby state of some worms
			case S2C_UPDATELOBBY:
				ParseUpdateLobby(bs);
				break;

			// Client has left
			case S2C_WORMSOUT:
				ParseWormsOut(bs);
				break;

			// Worm state update
			case S2C_UPDATEWORMS:
				ParseUpdateWorms(bs);
				break;

			// Game lobby update
			case S2C_UPDATELOBBYGAME:
				ParseUpdateLobbyGame(bs);
				break;

			// Worm down packet
			case S2C_WORMDOWN:
				ParseWormDown(bs);
				break;

			// Server has left
			case S2C_LEAVING:
				ParseServerLeaving(bs);
				break;

			// Single shot
			case S2C_SINGLESHOOT:
				ParseSingleShot(bs);
				break;

			// Multiple shots
			case S2C_MULTISHOOT:
				ParseMultiShot(bs);
				break;

			// Stats
			case S2C_UPDATESTATS:
				ParseUpdateStats(bs);
				break;

			// Destroy bonus
			case S2C_DESTROYBONUS:
				ParseDestroyBonus(bs);
				break;

			// Goto lobby
			case S2C_GOTOLOBBY:
				ParseGotoLobby(bs);
				break;

            // I have been dropped
            case S2C_DROPPED:
                ParseDropped(bs);
                break;

            case S2C_SENDFILE:
                ParseSendFile(bs);
                break;

            case S2C_REPORTDAMAGE:
                ParseReportDamage(bs);
                break;

			case S2C_HIDEWORM:
				ParseHideWorm(bs);
				break;

			default:
#if !defined(FUZZY_ERROR_TESTING_S2C)
				printf("cl: Unknown packet\n");
#ifdef DEBUG
				printf("Bytestream dump:\n\n");
				bs->Dump();
				printf("\nDone dumping bytestream\n");
#endif //DEBUG

#endif //FUZZY_ERROR_TESTING
				return;

		}
	}
}


///////////////////
// Parse a prepare game packet
bool CClientNetEngine::ParsePrepareGame(CBytestream *bs)
{
	printf("Got ParsePrepareGame\n");

	if(Warning_QuitEngineFlagSet("CClientNetEngine::ParsePrepareGame: ")) {
		printf("HINT: some previous action tried to quit the GameLoop; we are ignoring this now\n");
		ResetQuitEngineFlag();
	}

	// We've already got this packet
	if (client->bGameReady)  {
		printf("CClientNetEngine::ParsePrepareGame: we already got this\n");
		return false;
	}

	// If we're playing, the game has to be ready
	if (client->iNetStatus == NET_PLAYING)  {
		printf("CClientNetEngine::ParsePrepareGame: playing, had to get this\n");
		client->bGameReady = true;
		return false;
	}

	NotifyUserOnEvent();


	// remove from notifier; we don't want events anymore, we have a fixed FPS rate ingame
	RemoveSocketFromNotifierGroup( client->tSocket );

	client->bGameReady = true;

	int random = bs->readInt(1);
	std::string sMapName;
	if(!random)
		sMapName = bs->readString();

	// Other game details
	client->iGameType = bs->readInt(1);
	client->iLives = bs->readInt16();
	client->iMaxKills = bs->readInt16();
	client->tGameInfo.fTimeLimit = (float)bs->readInt16();
	int l = bs->readInt16();
	client->fLoadingTime = (float)l/100.0f;
	client->bBonusesOn = bs->readBool();
	client->bShowBonusName = bs->readBool();
	client->fServertime = 0;

	if(client->iGameType == GMT_TIME)
		client->iTagLimit = bs->readInt16();

	// Load the gamescript
	client->sModName = bs->readString();

	// Bad packet
	if (client->sModName == "")  {
		printf("CClientNetEngine::ParsePrepareGame: invalid mod name (none)\n");
		client->bGameReady = false;
		return false;
	}

	// Clear any previous instances of the map
	if(tLX->iGameType == GME_JOIN) {
		if(client->cMap) {
			client->cMap->Shutdown();
			delete client->cMap;
			client->cMap = NULL;
		}
	}

	// HINT: gamescript is shut down by the cache

    //bs->Dump();
{'fraction_non_alphanumeric': 0.1575237118687516, 'fraction_numerical': 0.007722378877210972, 'mean_word_length': 3.6160331311936105, 'pattern_counts': {'":': 4, '<': 63, '<?xml version=': 0, '>': 378, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3075146', 'n_tokens_mistral': 12073, 'n_tokens_neox': 10696, 'n_words': 2442}