#include "doomtype.h"
#include "doomstat.h"
#include "d_main.h"
#include "d_player.h"
#include "g_game.h"
#include "d_net.h"
#include "p_local.h"
#include "s_sound.h"
#include "gi.h"
#include "i_net.h"
#include "i_system.h"
#include "c_dispatch.h"
#include "st_stuff.h"
#include "m_argv.h"
#include "cl_main.h"
#include "p_effect.h"
#include "c_console.h"
#include "s_sndseq.h"
#include "p_inter.h"

char guidstring[] = "GUID{A7EDB62B-4CDE-4c89-8383-B8B26E6B4F63}\n"; /// @todo add wad checksums for newer clients

buf_t     net_buffer;

bool      noservermsgs;
int       last_received;

netadr_t  serveraddr; // address of a server
netadr_t  lastconaddr;

int       packetseq[256];
byte      packetnum;

cactive_t clact;
int       connecttimeout = 0;

char dl_map[256];

// some doom functions (not csDoom)
void G_PlayerReborn (int player);
void CL_SpawnPlayer();
void P_KillMobj (AActor *source, AActor *target, AActor *inflictor);
void P_SetPsprite (player_t *player, int position, statenum_t stnum);
void P_ExplodeMissile (AActor* mo);
void G_SetDefaultTurbo (void);
void P_FireWeapon (player_t *player);
void P_CalcHeight (player_t *player);

char str[256];

void WriteDebug(char *Message)
{
    FILE *fn;

    fn = fopen("cs.dbg", "a+");
    fprintf(fn, "%s", Message);
    fclose(fn);
}


void Host_EndGame(char *msg)
{
    Printf(PRINT_HIGH, "%s", msg);
	connecttimeout = 0;

	for (int i=0; i<MAXPLAYERS; i++)
		playeringame[i] = 0;

	CL_QuitNetGame();
}


void CL_QuitNetGame(void)
{
	for (int i=0; i<MAXPLAYERS; i++)
		playeringame[i] = 0;

	if(net_buffer.data) // DL fix on app exit
	{
		MSG_WriteByte(&net_buffer, clc_disconnect);
		NET_SendPacket(net_buffer.cursize, net_buffer.data, serveraddr);
	    SZ_Clear(&net_buffer);
	}

    memset (&serveraddr, 0, sizeof(serveraddr));
    clact = ca_disconnected;
	gameaction = ga_fullconsole;
	
	CL_RestoreColor();
}

//
// CL_DisconnectClient
//
void CL_DisconnectClient(void)
{
	byte who = MSG_ReadByte();
	playeringame[who] = false;

    P_DisconnectEffect (players[who].mo);
}

/////// CONSOLE COMMANDS ///////

BEGIN_COMMAND (connect)
{
	if(clact != ca_disconnected && clact != ca_tryingtolaunch && clact != ca_tryingtoconnect && clact != ca_active)
		return;

	if (clact == ca_active)
		CL_QuitNetGame();

	clact = ca_tryingtolaunch;
	connecttimeout = 0;

	if (argc > 1)
	{
	   NET_StringToAdr (argv[1], &serveraddr);

       if (!serveraddr.port)
           I_SetPort(serveraddr, SERVERPORT);

	   lastconaddr = serveraddr;

	   for (int i=0; i<MAXPLAYERS; i++)
		   playeringame[i] = false;
	}
}
END_COMMAND (connect)


BEGIN_COMMAND (disconnect)
{
   CL_QuitNetGame();
}
END_COMMAND (disconnect)


BEGIN_COMMAND (reconnect)
{
	if (clact == ca_active)
	{
		MSG_WriteByte(&net_buffer, clc_disconnect);    
		NET_SendPacket(net_buffer.cursize, net_buffer.data, serveraddr);
		SZ_Clear(&net_buffer);
		gameaction = ga_fullconsole;
		CL_RestoreColor();
	}
	
	if (clact == ca_disconnected && lastconaddr.ip[0])
		serveraddr = lastconaddr;

	for (int i=0; i<MAXPLAYERS; i++)
		playeringame[i] = false;

	clact = ca_tryingtolaunch;
	connecttimeout = 0;
}
END_COMMAND (reconnect)


BEGIN_CUSTOM_CVAR (cl_predict_players, "1", CVAR_ARCHIVE)
{
	if (var.value > 1)
	{
		var.Set (1);
	}
}
END_CUSTOM_CVAR (cl_predict_players)


BEGIN_CUSTOM_CVAR (rate, "2500", CVAR_ARCHIVE)
{
	if (var.value < 100)
	{
		var.Set (100);
	}
	else if (var.value > 100000)
	{
		var.Set (100000);
	}
	else if (clact == ca_active)
	{
		MSG_WriteByte(&net_buffer, clc_rate);
		MSG_WriteLong(&net_buffer, (int)var.value);
	}
}
END_CUSTOM_CVAR (rate)


BEGIN_COMMAND (rcon)
{
	if (clact == ca_active && argc > 1)
	{
		char  command[256];

		if (argc == 2)
			sprintf(command, "%s", argv[1]);		
		if (argc == 3)
			sprintf(command, "%s %s", argv[1], argv[2]);
		if (argc == 4)
			sprintf(command, "%s %s %s", argv[1], argv[2], argv[3]);
		
		MSG_WriteByte(&net_buffer, clc_rcon);
		MSG_WriteString(&net_buffer, command);
	}
}
END_COMMAND (rcon)


BEGIN_COMMAND (rcon_password)
{
	if (clact == ca_active && argc > 1)
	{
		MSG_WriteByte(&net_buffer, clc_rcon_password);
		MSG_WriteString(&net_buffer, argv[1]);
	}
}
END_COMMAND (rcon_password)


/////////////////////////////////


void CL_MoveThing(AActor *mobj, fixed_t x, fixed_t y, fixed_t z)
{
   P_CheckPosition (mobj, x, y);
   mobj->UnlinkFromWorld ();

   mobj->x = x;
   mobj->y = y;
   mobj->z = z;
   mobj->floorz = tmfloorz;
   mobj->ceilingz = tmceilingz;

   mobj->LinkToWorld ();
}


void CL_SendUserInfo(void)
{
   player_t *p;

   p = &players[consoleplayer];

   MSG_WriteByte(&net_buffer, clc_userinfo);
   MSG_WriteString(&net_buffer, p->userinfo.netname);
   MSG_WriteString(&net_buffer, p->userinfo.team);
   MSG_WriteLong(&net_buffer, p->userinfo.gender);
   MSG_WriteLong(&net_buffer, p->userinfo.color);
   MSG_WriteLong(&net_buffer, p->userinfo.aimdist);
}


void CL_SetupUserInfo(void)
{
    player_t *p;
    byte      who;

    who = MSG_ReadByte();

	if(who < 0 || who > MAXPLAYERS)
		return;

    p = &players[who];

    strcpy(p->userinfo.netname, MSG_ReadString() );
    strcpy(p->userinfo.team, MSG_ReadString() );
	p->userinfo.gender = MSG_ReadLong();
    p->userinfo.color = MSG_ReadLong();
    R_BuildPlayerTranslation (who, p->userinfo.color);

	SB_state = -1;
}


//
// CL_UpdateFrags
//
void CL_UpdateFrags(void)
{
	int playernum = MSG_ReadByte();
	int f = MSG_ReadShort();

	if(playernum < 0 || playernum >= MAXPLAYERS)
		return;

	players[playernum].fragcount = f;
}


AActor* CL_FindThingById(int id)
{
    AActor *mo;
	
    TThinkerIterator<AActor> iterator;
    while ( (mo = iterator.Next() ) )
    {
		if (mo->netid == id)
    	    return mo;
    }

    return 0;
}

void CL_ClearID(int id)
{
    AActor *mo;
	
    TThinkerIterator<AActor> iterator;
    while ( (mo = iterator.Next() ) )
    {
		if (mo->netid == id)
    	    mo->netid = 0;
    }

}

//
// CL_MoveMobj
// 
void CL_MoveMobj(void)
{
	AActor  *mo;
	int      netid;
	fixed_t  x, y, z;

	netid = MSG_ReadShort();
	mo = CL_FindThingById (netid);

	x = MSG_ReadLong();
	y = MSG_ReadLong();
	z = MSG_ReadLong();
	
	if (!mo)
		return;

	CL_MoveThing (mo, x, y, z);
}

//
// CL_MoveMobj
// 
void CL_MoveMobjXY(void)
{
	AActor  *mo;
	int      netid;
	fixed_t  x, y;

	netid = MSG_ReadShort();
	mo = CL_FindThingById (netid);

	x = MSG_ReadLong();
	y = MSG_ReadLong();
	
	if (!mo)
		return;

	CL_MoveThing (mo, x, y, mo->z);
}

//
//  Connecting to a server...
//
bool CL_Connect(void)
{
	byte  msg;
	int   i;

	// Is this a launcher message to inform us about a WAD change?
	if(MSG_ReadFirstLong() == CHALLENGE && clact == ca_tryingtolaunch)
	{
		MSG_ReadLong();
		
		// Get server info
		char *cServerDescription = MSG_ReadString();
		Printf (PRINT_HIGH, "> Server: %s\n", cServerDescription);
		int iPlayers = MSG_ReadByte();
		int iMaxPlayers = MSG_ReadByte();
		char *cServerMap = MSG_ReadString();
		Printf (PRINT_HIGH, "> Map: %s\n", cServerMap);
		
		bool bWad = MSG_ReadByte() ? true : false;

		if(bWad)
		{
			char *cServerWad = MSG_ReadString();
			Printf (PRINT_HIGH, "> WAD: %s\n", cServerWad);

			if(!cServerWad)
			{
				clact = ca_disconnected;
				return true;
			}

			if(!strlen(cServerWad))
			{
				clact = ca_disconnected;
				return true;
			}

			if(!D_DoomWadReboot(cServerWad))
			{
				if(clact != ca_downloading)
					clact = ca_disconnected;
				return true;
			}
		}
		else
		{
			if(!D_DoomWadReboot(0))
			{
				if(clact != ca_downloading)
					clact = ca_disconnected;
				return true;
			}
		}

		// If we are ready with the right wads, ask server to start comms
		SZ_Clear(&net_message);
		SZ_Clear(&net_buffer);
		MSG_WriteLong(&net_buffer, CHALLENGE); // send challenge
		NET_SendPacket(net_buffer.cursize, net_buffer.data, serveraddr);
		SZ_Clear(&net_buffer);

		clact == ca_tryingtoconnect;

		return true;
	}

	msg = MSG_ReadByte(); /// @todo check where the message originated, make sure it is the server we want to connect to

	if (msg == S2C_CHALLENGE || msg == S2C_CONNECTION)
		Printf(PRINT_HIGH, "%s: ", NET_AdrToString(net_from));

	switch(msg)
	{
	case S2C_CHALLENGE:
		MSG_WriteShort(&net_buffer, VERSION); // send client version
		NET_SendPacket(net_buffer.cursize, net_buffer.data, serveraddr);
		SZ_Clear(&net_buffer);
		Printf (PRINT_HIGH, "challenge\n");
		break;

	case S2C_CONNECTION:
		Printf (PRINT_HIGH, "connection\n");
		if (clact >= ca_connected)
		{
			Printf (PRINT_HIGH, "Dup connect received.  Ignored.\n");
			break;
		}
		
		D_SetupUserInfo();
		CL_SendUserInfo(); // send userinfo
		MSG_WriteLong(&net_buffer, (int)rate.value);
		NET_SendPacket(net_buffer.cursize, net_buffer.data, serveraddr);
		SZ_Clear(&net_buffer);

		memset(packetseq, -1, sizeof(packetseq) );
		packetnum = 0;

		for (i=0; i<MAXPLAYERS; i++)
			playeringame[i] = false;
	
		noservermsgs = false;
		last_received = gametic;

		clact = ca_connected;
		Printf (PRINT_HIGH, "Connected.\n");		
		break;

	case S2C_FULL:
		Host_EndGame ("Server is full\n");
		return false;

	case svc_disconnect:
		Host_EndGame ("Server disconnected\nServer version may not be compatible\n");
		break;
		
	default:
		break;
	}	 

	return true;
}


//
// CL_InitNetwork
//
void CL_InitNetwork (void)
{
    char      *v;
    char*     ipaddress;

    netgame = false;  // for old network code
    multiplayer = true;

    v = Args.CheckValue ("-port");
    if (v)
    {
       localport = atoi (v);
       Printf (PRINT_HIGH, "using alternate port %i\n", localport);
    }
    else
       localport = CLIENTPORT;

    // set up a socket and net_message buffer
    InitNetCommon();

    net_buffer.data = new byte[MAX_UDP_PACKET];
    net_buffer.maxsize = MAX_UDP_PACKET;
    SZ_Clear(&net_buffer);

	clact = ca_disconnected;

	ipaddress = Args.CheckValue ("-connect");
    if (ipaddress)
    {
		clact = ca_tryingtolaunch;

		NET_StringToAdr (ipaddress, &serveraddr);

		if (!serveraddr.port)
			I_SetPort(serveraddr, SERVERPORT);
	   
		lastconaddr = serveraddr;

		for (int i=0; i<MAXPLAYERS; i++)
			playeringame[i] = false;
    }

	G_SetDefaultTurbo ();

}


void CL_TryToConnect(void)
{
   if (!serveraddr.ip[0])
	   return;

   if (!connecttimeout)
   {
	  connecttimeout = 175; // 5 seconds

      Printf(PRINT_HIGH, "connecting to %s\n", NET_AdrToString(serveraddr));

	  SZ_Clear(&net_buffer);
	  MSG_WriteLong(&net_buffer, LAUNCHER_CHALLENGE); // send challenge
      NET_SendPacket(net_buffer.cursize, net_buffer.data, serveraddr);
	  SZ_Clear(&net_buffer);
   }

   connecttimeout--;
}

void CL_TryToDownloadWad(void)
{
	static bool fDownloading = 0;
	static int iLastProgress = 0;
	static int iTotal = 0;

	clact = ca_disconnected; /// todo remove
	Printf(PRINT_HIGH, "WAD downloading is disabled\n"); /// todo add a cvar that controls this
	return;

	if(!fDownloading)
	{
		iLastProgress = 0;
		iTotal = 0;

		if(!NET_InitDownload("voxelsoft.com", 80, "/csdoom/wad/", dl_map))
		{
			clact = ca_disconnected;
			Printf(PRINT_HIGH, "Failed to connect to wad file server\n");
		}
		else
		{
			fDownloading = true;
			Printf(PRINT_HIGH, "Download in progress...\n");
		}
	}

	int iProgress = NET_GetDownloadProgress();

	if(iProgress == -1) // Failed
	{
		fDownloading = false;
		clact = ca_disconnected;
		Printf(PRINT_HIGH, "\nDownload failed!\n", iProgress);
		return;
	}

	if(iProgress == -3) // 404
	{
		fDownloading = false;
		clact = ca_disconnected;
		Printf(PRINT_HIGH, "\nDownload failed: File not found on server (404)\n", iProgress);
		return;
	}

	if(iProgress == -2) // Completed
	{
		fDownloading = false;
		clact = ca_tryingtolaunch;
		connecttimeout = 0;
		Printf(PRINT_HIGH, "\nDownload complete, attempting to reconnect...\n", iProgress);
		return;
	}

	if(!iTotal && NET_GetDownloadTotal())
	{
		iTotal = NET_GetDownloadTotal();
		if(iTotal < 1024*1024)
			Printf(PRINT_HIGH, "File size: %dKb [. = 1%%]: ", iTotal/1024);
		else
			Printf(PRINT_HIGH, "File size: %d.%dMb [. = 1%%]: ", iTotal/(1024 * 1024), (iTotal/1024) - (iTotal/(1024 * 1024))*1024);
	}

	if(!iTotal)
		iProgress = 0;
	else
		iProgress = ((iProgress * 100) / iTotal);
	while(iProgress > iLastProgress)
	{
		// Echo to console string
		Printf(PRINT_HIGH, ".");
		iLastProgress += 1;
	}
}

//
// CL_Print
//
void CL_Print (void)
{
	byte level = MSG_ReadByte();
	char *string_received  = MSG_ReadString();
	int iLen = strlen(string_received);

	if(strncmp(string_received, guidstring, strlen(guidstring)) == 0)
	{
		// DL - detected a new server version, the new server will support extended commands via chat
		// DL - respond so that server knows we are a new client, too
		MSG_WriteByte (&net_buffer, clc_say);
		MSG_WriteString (&net_buffer, (char *)guidstring);
		return;
	}

	Printf (level, "%s", string_received);
    
	if (level == PRINT_CHAT || level == PRINT_TEAMCHAT)
		S_Sound (CHAN_VOICE, gameinfo.chatSound, 1, ATTN_NONE);
}


void CL_UpdatePlayer()
{			
	int       n = MSG_ReadByte();
	int       x, y, z;
	byte      bits;
	player_t *p = &players[n];

	bits = MSG_ReadByte();

	if (!p->mo)
	{
#ifdef _DEBUG
		Printf(PRINT_HIGH, "Warning (CL_UpdatePlayer): !p->mo, n=%d\n", n);
#endif
		int count = 0;
		
		// skip the message
		if (bits & CM_X) count += 4;
		if (bits & CM_Y) count += 4;
		if (bits & CM_Z) count += 4;
		if (bits & CM_ANGLE) count += 4;
		if (bits & CM_FRAME) count ++;
		if (bits & CM_MOMX) count += 4;
		if (bits & CM_MOMY) count += 4;
		if (bits & CM_MOMZ) count += 4;

		while (count--)
			MSG_ReadByte();

		return;
	}

	x = p->mo->x;
	y = p->mo->y;
	z = p->mo->z;

	if (bits & CM_X)
		x = MSG_ReadLong();
	if (bits & CM_Y)
		y = MSG_ReadLong();
	if (bits & CM_Z)
		z = MSG_ReadLong();

	CL_MoveThing(p->mo, x, y, z);
	
	if (bits & CM_ANGLE)
		p->mo->angle = MSG_ReadLong();
	if (bits & CM_FRAME)
		p->mo->frame = MSG_ReadByte();
	if (bits & CM_MOMX)
		p->mo->momx = MSG_ReadLong();
	if (bits & CM_MOMY)
		p->mo->momy = MSG_ReadLong();
	if (bits & CM_MOMZ)
		p->mo->momz = MSG_ReadLong();

	// это такой очень яркий фрейм. Круто смотрится :)
	if (p->mo->frame == PLAYER_FULLBRIGHTFRAME)
		p->mo->frame = 32773;
   
	p->last_received = gametic;
}


ticcmd_t localcmds[MAXSAVETICS];

void CL_SaveCmd(void)
{
	ticcmd_t *cmd;
    cmd = &localcmds[gametic%MAXSAVETICS];	
	memcpy (cmd, &players[consoleplayer].cmd, sizeof(ticcmd_t));
}

//
// CL_UpdateLocalPlayer
//
void CL_UpdateLocalPlayer(void)
{
	player_t *p;

	p = &players[consoleplayer];

	p->tic = MSG_ReadLong();
	p->real_origin[0] = MSG_ReadLong();
	p->real_origin[1] = MSG_ReadLong();
	p->real_origin[2] = MSG_ReadLong();

	p->real_velocity[0] = MSG_ReadLong();
	p->real_velocity[1] = MSG_ReadLong();
	p->real_velocity[2] = MSG_ReadLong();
}


void CL_ResendSvGametic(void)
{
	int svgametic = MSG_ReadLong();

	MSG_WriteByte (&net_buffer, clc_svgametic);
	MSG_WriteLong (&net_buffer, svgametic);
}


//
// CL_UpdatePing
// Update ping value
//
void CL_UpdatePing(void)
{
	byte n = MSG_ReadByte();
	byte ping = MSG_ReadByte();

	if(n < 0 || n >= MAXPLAYERS)
		return;

	players[n].ping = ping;
}


//
// CL_SpawnMobj
//
void CL_SpawnMobj(void)
{
	fixed_t  x, y, z;
	short    type;
	short    netid;
	AActor  *mo;

	x = MSG_ReadLong();
	y = MSG_ReadLong();
	z = MSG_ReadLong();
	type = MSG_ReadShort();
	netid = MSG_ReadShort();

	CL_ClearID(netid);

	mo = new AActor (x, y, z, (mobjtype_t)type);
	mo->netid = netid;

	if (clact == ca_active && (mo->flags & MF_MISSILE ) && mo->info->seesound)
		S_Sound (mo, CHAN_VOICE, mo->info->seesound, 1, ATTN_NORM);

	if (mo->type == MT_IFOG)
		S_Sound (mo, CHAN_VOICE, "misc/spawn", 1, ATTN_IDLE);

	if (mo->type == MT_TFOG)
		S_Sound (mo, CHAN_VOICE, "misc/teleport", 1, ATTN_NORM);
}


//
// CL_MobjAngle
// Set mobj angle.
//
void CL_MobjAngle()
{
	int netid = MSG_ReadShort();
	int angle = MSG_ReadLong();

	AActor *mo = CL_FindThingById(netid);

	if (!mo) 
		return;

	mo->angle = angle;
}


//
// CL_Corpse
// Called after killed player is created.
//
void CL_Corpse(void)
{
	int     netid;
	int     frame;
	AActor* mo;

	netid = MSG_ReadShort();
	frame = MSG_ReadByte();

	mo = CL_FindThingById(netid);
	
	if (!mo)
		return;

	P_SetMobjState(mo, mo->info->deathstate);

	mo->frame = frame;

	mo->flags &= ~(MF_SHOOTABLE|MF_FLOAT|MF_SKULLFLY);
	mo->flags |= MF_CORPSE|MF_DROPOFF;
	mo->flags2 &= ~MF2_PASSMOBJ;
	mo->height >>= 2;
	mo->flags &= ~MF_SOLID;

	if (mo->player)
		mo->player->playerstate = PST_DEAD;
}

//
// CL_TouchSpecialThing
//
void CL_TouchSpecialThing (void)
{
	int     playernum;
	byte    sprite;

	playernum = MSG_ReadByte();
	sprite = MSG_ReadByte();
	
	if (!players[playernum].mo)
		return;

	P_TouchSpecialThing ((spritenum_t) sprite, players[playernum].mo);
}


extern fixed_t cl_viewheight[MAXSAVETICS];
extern fixed_t cl_deltaviewheight[MAXSAVETICS];

//
// CL_SpawnPlayer
//
void CL_SpawnPlayer()
{
	int		  playernum;
	player_t *p;
	AActor	 *mobj, *tfog;
	fixed_t   x, y, z;
	int       id;
	angle_t   angle;
	int       i;
	byte      playerstate;


	playernum = MSG_ReadByte();
	playerstate = MSG_ReadByte();
	id = MSG_ReadShort();
	angle = MSG_ReadLong();
	x = MSG_ReadLong();
	y = MSG_ReadLong();
	z = MSG_ReadLong();

	if (playernum >= MAXPLAYERS)
		return;

	CL_ClearID(id);

	// spawn a teleport fog
	if ( players[consoleplayer].camera 
		&& playernum != consoleplayer 
		&& clact == ca_active)
	{
		tfog = new AActor (x, y, z, MT_TFOG);
	    S_Sound (tfog, CHAN_VOICE, "misc/teleport", 1, ATTN_NORM);
	}

	playeringame[playernum] = true;
	p = &players[playernum];

	// first disassociate the corpse
	if (p->mo)
	{
		p->mo->player = NULL;
		p->mo->health = 0;
	}

	if (playerstate == PST_REBORN)
		G_PlayerReborn (playernum);

	mobj = new AActor (x, y, z, MT_PLAYER);

	// set color translations for player sprites
	mobj->translation = translationtables + 256*playernum;
	mobj->angle = angle;
	mobj->pitch = mobj->roll = 0;
	mobj->player = p;
	mobj->health = p->health;
	mobj->netid = id;

	// [RH] Set player sprite based on skin
	mobj->sprite = skins[p->userinfo.skin].sprite;	

	p->mo = p->camera = mobj;	
	p->fov = 90.0f;
	p->playerstate = PST_LIVE;
	p->refire = 0;
	p->damagecount = 0;
	p->bonuscount = 0;
	p->extralight = 0;
	p->fixedcolormap = 0;
	
	p->viewheight = VIEWHEIGHT;
	for (i=0; i<MAXSAVETICS; i++)
	{
		cl_viewheight[i] = VIEWHEIGHT;
		cl_deltaviewheight[i] = 0;
	}

	p->inconsistant = 0;
	p->attacker = NULL;
	p->viewz = z + VIEWHEIGHT;
	p->momx = p->momy = 0;	// killough 10/98: initialize bobbing to 0.

	players[consoleplayer].camera = players[displayplayer].mo;

	// setup gun psprite
	P_SetupPsprites (p);

	// give all cards in death match mode
	if (deathmatch.value)
		for (i = 0; i < NUMCARDS; i++)
			p->cards[i] = true;

	ST_Start ();
}

//
// CL_SetMobjSpeedAndAngle
//
void CL_SetMissileSpeedAndAngle(void)
{
	AActor *mo;
	int     netid;

	netid = MSG_ReadShort();
	mo = CL_FindThingById(netid);

	if (!mo) 
	{
		for (int i=0; i<4; i++)
			MSG_ReadLong();
		return;
	}
	
	mo->angle = MSG_ReadLong();
	mo->momx = MSG_ReadLong();
	mo->momy = MSG_ReadLong();
	mo->momz = MSG_ReadLong();
	
	// move the missile forward a bit
	mo->x += mo->momx>>1;
	mo->y += mo->momy>>1;
	mo->z += mo->momz>>1;
}

//
// CL_ExplodeMissile
//
void CL_ExplodeMissile(void)
{
    AActor *mo;
	int     netid;

	netid = MSG_ReadShort();
	mo = CL_FindThingById(netid);

	if (!mo)
		return;

	P_ExplodeMissile(mo);
}


//
// CL_UpdateMobjFrame
//
void CL_UpdateMobjFrame(void)
{
	AActor *mo;
	int     netid;
	byte    frame;

	netid = MSG_ReadShort();
	frame = MSG_ReadByte();

	mo = CL_FindThingById(netid);

	if (!mo)
		return;

	mo->frame = frame;
}


//
// CL_RemoveMobj
//
void CL_RemoveMobj(void)
{
	AActor *mo;
	int     netid;

	netid = MSG_ReadShort();
	mo = CL_FindThingById(netid);

	if (!mo) 
		return;

	mo->Destroy();
}


//
// CL_DamagePlayer
//
void CL_DamagePlayer(void)
{
	player_t *p;	
	int       temp;
	int       health;
	int       damage;
	byte      playernum;

	playernum = MSG_ReadByte();

	p = &players[playernum];

	p->armorpoints = MSG_ReadByte();
	health         = MSG_ReadShort();

	damage = p->health - health;
	if(p->mo) // changed - added
		p->mo->health = p->health = health;

	if (p->health < 0)
		p->health = 0;

    if (damage < 0)  // can't be!
		return;

	p->damagecount += damage;

	if (p->damagecount > 100)
		p->damagecount = 100;

	if (playernum == consoleplayer)
	{
		temp = damage < 100 ? damage : 100;
        I_Tactile (40,10,40+temp*2);
	}
}

extern int MeansOfDeath;

//
// CL_KillMobj
//
void CL_KillMobj(void)
{
	AActor *source;
	AActor *target;
	AActor *inflictor;
	
	source = CL_FindThingById (MSG_ReadShort() );
	target = CL_FindThingById (MSG_ReadShort() );
	inflictor = CL_FindThingById (MSG_ReadShort() );
	
	MeansOfDeath = MSG_ReadLong();

	if (!target)
		return;
	
	P_KillMobj (source, target, inflictor);
}


///////////////////////////////////////////////////////////
///// CL_Fire* called when someone uses a weapon  /////////
///////////////////////////////////////////////////////////

//
// CL_FirePistol
//
void CL_FirePistol(void)
{
	int playernum = MSG_ReadByte();
	player_t *player = &players[playernum];
	
	if (playernum != consoleplayer)
		S_Sound (player->mo, CHAN_WEAPON, "weapons/pistol", 1, ATTN_NORM);		
}

//
// CL_FireShotgun
//
void CL_FireShotgun(void)
{
	int playernum = MSG_ReadByte();
	player_t *player = &players[playernum];
	
	// playernum != consoleplayer
	S_Sound (player->mo, CHAN_WEAPON,  "weapons/shotgf", 1, ATTN_NORM);
}

//
// CL_FireSSG
//
void CL_FireSSG(void)
{
	int playernum = MSG_ReadByte();
	player_t *player = &players[playernum];
	
	S_Sound (player->mo, CHAN_WEAPON, "weapons/sshotf", 1, ATTN_NORM);
}


//
// CL_FireChainGun
//
void CL_FireChainGun(void)
{
	int playernum;

	playernum = MSG_ReadByte();
	player_t *player = &players[playernum];

	S_Sound (player->mo, CHAN_WEAPON, "weapons/chngun", 1, ATTN_NORM);
}

/////////////////////////////////////////////////////////

void CL_ChangeWeapon (void)
{
	player_t* player = &players[consoleplayer];
	player->pendingweapon = (weapontype_t)MSG_ReadByte();
}



//
// CL_Sound
//
void CL_Sound(void)
{	
	AActor *mo;

	int netid = MSG_ReadShort();
	byte channel = MSG_ReadByte();
	byte sfx_id = MSG_ReadByte();
	byte attenuation = MSG_ReadByte();

	mo = CL_FindThingById (netid);
	if (!mo)
		return;

	S_SoundID (mo, channel, sfx_id, 1, attenuation);
}

void CL_SoundOrigin(void)
{
	int x = MSG_ReadLong();
	int y = MSG_ReadLong();
	byte channel = MSG_ReadByte();
	byte sfx_id = MSG_ReadByte();
	byte attenuation = MSG_ReadByte();
	
	S_SoundID (x, y, channel, sfx_id, 1, attenuation);
}

//
// CL_StartSectorSequence
//
void CL_StartSectorSequence(void)
{
	short     sec;
	sector_t *sector;
	int       sequence;
	seqtype_t type;

	sec = MSG_ReadShort();
	sequence = MSG_ReadShort();
	type = (seqtype_t)MSG_ReadByte();

	if(!sectors)
		return;

	if (sec >= numsectors)
		return;

	sector = &sectors[sec];

	SN_StartSequence (sector, sequence, type);
}

//
// CL_StopSectorSequence
//
void CL_StopSectorSequence(void)
{
	short     sec;
	sector_t *sector;

	sec = MSG_ReadShort();

	if(!sectors)
		return;

	if (sec >= numsectors)
		return;

	sector = &sectors[sec];

	SN_StopSequence (sector);
}

//
// CL_UpdateSector
// Updates floorheight and ceilingheight of a sector.
//
void CL_UpdateSector(void)
{
	unsigned short s = (unsigned short)MSG_ReadShort();

	int f = MSG_ReadShort();
	int c = MSG_ReadShort();

	// DL - bugfix
	if(!sectors)
		return;

	if(s >= numsectors)
		return;

	sector_t *sec = &sectors[s];

	sec->floorheight = f << FRACBITS;
	sec->ceilingheight = c << FRACBITS;	
	P_ChangeSector (sec, -1);
}

void CL_UpdateFloorHeight(void)
{
	unsigned short s = (unsigned short)MSG_ReadShort();
	sector_t *sec = &sectors[s]; // dl - crash sectors==0

	sec->floorheight = MSG_ReadShort() << FRACBITS;
	P_ChangeSector (sec, -1);
}

void CL_UpdateCeilingHeight(void)
{
	unsigned short s = (unsigned short)MSG_ReadShort();

	if(!sectors)
		return;

	if (s >= numsectors)
		return;

	sector_t *sec = &sectors[s];

	sec->ceilingheight = MSG_ReadShort() << FRACBITS;
	P_ChangeSector (sec, -1);
}


// 
// CL_CheckMissedPacket
//
void CL_CheckMissedPacket(void)
{
	int    sequence;
	short  size;

	sequence = MSG_ReadLong();
	size = MSG_ReadShort();

	for (int n=0; n<256; n++)
	{
		// skip a duplicated packet
		if (packetseq[n] == sequence)
		{
			for (int i=0; i<size; i++)
				MSG_ReadByte();
//			Printf (PRINT_HIGH, "duplicated packet\n");
			return;
		}
	}
}


// 
// CL_ReadPacketHeader
//

void CL_ReadPacketHeader(void)
{
	int sequence = MSG_ReadLong();

	MSG_WriteByte(&net_buffer, clc_ack);
	MSG_WriteLong(&net_buffer, sequence);

	packetseq[packetnum] = sequence;
	packetnum++;
}


// [NightFang] - Simple function to detect teamplay and if teamplay
// is set.  Change the user's color.
EXTERN_CVAR (teamplay)
EXTERN_CVAR (color)

char backupcolor[10];

void CL_SpecialSettings(void)
{
	int gametype = MSG_ReadByte();
	int team = MSG_ReadByte();
	int dm_value = MSG_ReadByte();

	teamplay.Set(gametype);
	deathmatch.Set(dm_value);

	if (teamplay.value)
	{
		strcpy (backupcolor, color.string);

		if (team == 0)
			AddCommandString ("set color \"ff 00 00\"");
		else
			AddCommandString ("set color \"00 00 ff\"");
	}
}


//
// CL_RestoreColor
// Restore player's color if it was changed for teamplay
//
void CL_RestoreColor(void)
{
	char str[64];

	if (teamplay.value)
	{
		sprintf(str, "set color \"%s\"", backupcolor);
		AddCommandString (str);
	}
}

//
// CL_SetMobjState
//
void CL_SetMobjState()
{
	AActor *mo = CL_FindThingById (MSG_ReadShort() );
	
	if (!mo)
	{
		MSG_ReadShort();
		return;
	}
	
	P_SetMobjState (mo, (statenum_t)MSG_ReadShort() );
}

//
// CL_Actor_Movedir
//
void CL_Actor_Movedir()
{
	AActor *actor = CL_FindThingById (MSG_ReadShort() );
	byte movedir = MSG_ReadByte();
	if (actor)
		actor->movedir = movedir;
}

//
// CL_Actor_Target
//
void CL_Actor_Target()
{
	AActor *actor = CL_FindThingById (MSG_ReadShort() );
	short targetid = MSG_ReadShort();

	if (!actor)
		return;

	if (!targetid)
	{
		actor->target = NULL;
		return;
	}

	AActor *target = CL_FindThingById (targetid);
	actor->target = target;
}

//
// CL_ChangeSwitchTexture
//
void CL_ChangeSwitchTexture()
{
	int l = MSG_ReadLong();
	byte useAgain = MSG_ReadByte();

	if (l >= numlines)
	{
		Printf(PRINT_HIGH, "Error (CL_ChangeSwitchTexture): l >= numlines\n");
		return;
	}

	P_ChangeSwitchTexture(&lines[l], useAgain*512);
}


//
// CL_ParseCommands
//
void CL_ParseCommands(void)
{
	int    cmd;
	char  *mapname;

	while(1)	 
	{  
		if (msg_badread)
		{
			Host_EndGame("CL_ParseCommands: Bad server message\n");
			break;
		}

		cmd = MSG_ReadByte();

		if (cmd == -1)  // EOM
			break;
	    
		switch(cmd)
		{
	    case svc_loadmap:
			mapname = MSG_ReadString();
            G_InitNew (mapname);
			clact = ca_active;
			break;
			
		case svc_gameskill:
			gameskill.Set((float)MSG_ReadByte());
			break;

		case svc_specialsettings:
			CL_SpecialSettings();
			break;

		case svc_dmflags:
			dmflags = MSG_ReadLong();
			break;
			  
		case svc_consoleplayer:
		    displayplayer = consoleplayer = MSG_ReadByte();
			// when the client knows consoleplayer number, it can
			// setup userinfo
			D_SetupUserInfo();  
		    break;

		case svc_updatefrags:
			CL_UpdateFrags();
			break;

		case svc_moveplayer:			
			CL_UpdatePlayer();
			break;

		case svc_updatelocalplayer:
			CL_UpdateLocalPlayer();
			break;

		case svc_userinfo:  // name, color, team
			CL_SetupUserInfo();
			break;

		case svc_svgametic:
			CL_ResendSvGametic();
			break;

		case svc_updateping:
			CL_UpdatePing();
			break;

		case svc_spawnmobj:
			CL_SpawnMobj();
			break;

		case svc_mobjangle:
			CL_MobjAngle();
			break;

		case svc_missilespeedangle:
			CL_SetMissileSpeedAndAngle();
			break;

		case svc_mobjframe:
			CL_UpdateMobjFrame();
			break;

		case svc_explodemissile:
			CL_ExplodeMissile();
			break;

		case svc_removemobj:
			CL_RemoveMobj();
			break;
		
		case svc_killmobj:
			CL_KillMobj();
			break;

		case svc_movemobj:
			CL_MoveMobj();
			break;
		
		case svc_movemobj_xy:
			CL_MoveMobjXY();
			break;
		
		case svc_corpse:
			CL_Corpse();
			break;

		case svc_spawnplayer:
			CL_SpawnPlayer();
			break;
			
		case svc_damageplayer:
			CL_DamagePlayer();
			break;

		case svc_firepistol:
			CL_FirePistol();
			break;

		case svc_fireshotgun:
			CL_FireShotgun();
			break;

		case svc_firessg:
			CL_FireSSG();
			break;

		case svc_firechaingun:
			CL_FireChainGun();
			break;

		case svc_fireweapon:
			P_FireWeapon(&players[consoleplayer]);
			break;

		case svc_changeweapon:
			CL_ChangeWeapon();
			break;

		case svc_disconnectclient:
			CL_DisconnectClient();
			break;

		case svc_sector:
			CL_UpdateSector();
			break;
		
		case svc_floorheight:
			CL_UpdateFloorHeight();
			break;
		
		case svc_ceilingheight:
			CL_UpdateCeilingHeight();
			break;
			
		case svc_print:
			CL_Print();
			break;

		case svc_startsound:
			CL_Sound();
			break;

		case svc_startsectorsequence:
			CL_StartSectorSequence();
			break;

		case svc_stopsectorsequence:
			CL_StopSectorSequence();
			break;

		case svc_soundorigin:
			CL_SoundOrigin();
			break;

		case svc_touchspecial:
			CL_TouchSpecialThing ();
			break;

		case svc_mobjstate:
			CL_SetMobjState();
			break;

		case svc_actor_movedir:
			CL_Actor_Movedir();
			break;
		
		case svc_actor_target:
			CL_Actor_Target();
			break;

		case svc_changeswitchtexture:
			CL_ChangeSwitchTexture();
			break;

		case svc_giveammo:
			{
				ammotype_t ammo = (ammotype_t)MSG_ReadByte();
				int num = MSG_ReadShort();
				P_GiveAmmo(&players[consoleplayer], ammo, num);
			}
			break;

		case svc_missedpacket:
			CL_CheckMissedPacket();
			break;

		case svc_exitlevel:
			gamestate = GS_INTERMISSION;
			clact = ca_onserver;
			break;

		case svc_disconnect:
			Host_EndGame ("Server disconnected\n");
			return;	
		
		case svc_reconnect:
            clact = ca_tryingtolaunch;
	        gameaction = ga_fullconsole;
			return;
		
		default:
			Host_EndGame ("CL_ParseCommands: Illegible server message\n");
			return;
		}	 
	}
}

extern int outrate;

//
// CL_SendCmd
//
void CL_SendCmd(void)
{		
	ticcmd_t *cmd;
	player_t *p;

	if (gametic < 1 )
		return;

	p = &players[consoleplayer];

	if(!p->mo)
		return;

	MSG_WriteByte(&net_buffer, clc_move);

    MSG_WriteLong(&net_buffer, gametic); // current tic

    // send the previous cmds in the message, so if the last packet 
    // was dropped, it can be recovered
    cmd = &localcmds[(gametic-1) % MAXSAVETICS];
			
	MSG_WriteByte(&net_buffer, cmd->ucmd.buttons);
	MSG_WriteShort(&net_buffer, p->mo->angle >> 16);
	MSG_WriteShort(&net_buffer, p->mo->pitch >> 16);
	MSG_WriteShort(&net_buffer, cmd->ucmd.forwardmove);
	MSG_WriteShort(&net_buffer, cmd->ucmd.sidemove);
	MSG_WriteByte(&net_buffer, cmd->ucmd.impulse);

    // send the current cmds in the message
    cmd = &players[consoleplayer].cmd;
	
	// Look up/down stuff
	P_PlayerLookUpDown(p);

	MSG_WriteByte(&net_buffer, cmd->ucmd.buttons);
	MSG_WriteShort(&net_buffer, (p->mo->angle + (cmd->ucmd.yaw << 16)) >> 16);
	MSG_WriteShort(&net_buffer, p->mo->pitch >> 16);
	MSG_WriteShort(&net_buffer, cmd->ucmd.forwardmove);
	MSG_WriteShort(&net_buffer, cmd->ucmd.sidemove);
	MSG_WriteByte(&net_buffer, cmd->ucmd.impulse);

    NET_SendPacket(net_buffer.cursize, net_buffer.data, serveraddr);
	outrate += net_buffer.cursize;
    SZ_Clear(&net_buffer);
}
